diff --git a/.clang-tidy b/.clang-tidy index 436dcf244..ef5166da4 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,5 +1,23 @@ Checks: - modernize-use-using - readability-avoid-const-params-in-decls + - misc-unused-parameters, + - readability-identifier-naming -SystemHeaders: false +# ^ Without unused-parameters the readability-identifier-naming check doesn't cause any warnings. + +CheckOptions: + - { key: readability-identifier-naming.ClassCase, value: PascalCase } + - { key: readability-identifier-naming.EnumCase, value: PascalCase } + - { key: readability-identifier-naming.FunctionCase, value: camelCase } + - { key: readability-identifier-naming.GlobalVariableCase, value: camelCase } + - { key: readability-identifier-naming.GlobalFunctionCase, value: camelCase } + - { key: readability-identifier-naming.GlobalConstantCase, value: SCREAMING_SNAKE_CASE } + - { key: readability-identifier-naming.MacroDefinitionCase, value: SCREAMING_SNAKE_CASE } + - { key: readability-identifier-naming.ClassMemberCase, value: camelCase } + - { key: readability-identifier-naming.PrivateMemberPrefix, value: m_ } + - { key: readability-identifier-naming.ProtectedMemberPrefix, value: m_ } + - { key: readability-identifier-naming.PrivateStaticMemberPrefix, value: s_ } + - { key: readability-identifier-naming.ProtectedStaticMemberPrefix, value: s_ } + - { key: readability-identifier-naming.PublicStaticConstantCase, value: SCREAMING_SNAKE_CASE } + - { key: readability-identifier-naming.EnumConstantCase, value: SCREAMING_SNAKE_CASE } \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dc45252fa..ae4deb7a5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,9 +39,6 @@ on: APPLE_NOTARIZE_PASSWORD: description: Password used for notarizing macOS builds required: false - CACHIX_AUTH_TOKEN: - description: Private token for authenticating against Cachix cache - required: false GPG_PRIVATE_KEY: description: Private key for AppImage signing required: false @@ -59,14 +56,14 @@ jobs: qt_ver: 5 qt_host: linux qt_arch: "" - qt_version: "5.12.8" + qt_version: "5.15.2" qt_modules: "qtnetworkauth" - - os: ubuntu-20.04 + - os: ubuntu-22.04 qt_ver: 6 qt_host: linux qt_arch: "" - qt_version: "6.2.4" + qt_version: "6.5.3" qt_modules: "qt5compat qtimageformats qtnetworkauth" - os: windows-2022 @@ -80,9 +77,9 @@ jobs: architecture: "x64" vcvars_arch: "amd64" qt_ver: 6 - qt_host: windows - qt_arch: "" - qt_version: "6.7.3" + qt_host: "windows" + qt_arch: "win64_msvc2022_64" + qt_version: "6.8.1" qt_modules: "qt5compat qtimageformats qtnetworkauth" nscurl_tag: "v24.9.26.122" nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0" @@ -93,9 +90,9 @@ jobs: architecture: "arm64" vcvars_arch: "amd64_arm64" qt_ver: 6 - qt_host: windows - qt_arch: "win64_msvc2019_arm64" - qt_version: "6.7.3" + qt_host: "windows" + qt_arch: "win64_msvc2022_arm64_cross_compiled" + qt_version: "6.8.1" qt_modules: "qt5compat qtimageformats qtnetworkauth" nscurl_tag: "v24.9.26.122" nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0" @@ -106,7 +103,7 @@ jobs: qt_ver: 6 qt_host: mac qt_arch: "" - qt_version: "6.7.3" + qt_version: "6.8.1" qt_modules: "qt5compat qtimageformats qtnetworkauth" - os: macos-14 @@ -167,13 +164,13 @@ jobs: - name: Setup ccache if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug' - uses: hendrikmuhs/ccache-action@v1.2.14 + uses: hendrikmuhs/ccache-action@v1.2.17 with: key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }} - name: Retrieve ccache cache (Windows MinGW-w64) if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug' - uses: actions/cache@v4.1.1 + uses: actions/cache@v4.2.1 with: path: '${{ github.workspace }}\.ccache' key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }} @@ -206,7 +203,7 @@ jobs: if: runner.os == 'Linux' run: | sudo apt-get -y update - sudo apt-get -y install ninja-build extra-cmake-modules scdoc appstream + sudo apt-get -y install ninja-build extra-cmake-modules scdoc appstream libxcb-cursor-dev - name: Install Dependencies (macOS) if: runner.os == 'macOS' @@ -216,14 +213,14 @@ jobs: - name: Install host Qt (Windows MSVC arm64) if: runner.os == 'Windows' && matrix.architecture == 'arm64' - uses: jurplel/install-qt-action@v3 + uses: jurplel/install-qt-action@v4 with: aqtversion: "==3.1.*" py7zrversion: ">=0.20.2" version: ${{ matrix.qt_version }} host: "windows" target: "desktop" - arch: "" + arch: ${{ matrix.qt_arch }} modules: ${{ matrix.qt_modules }} cache: ${{ inputs.is_qt_cached }} cache-key-prefix: host-qt-arm64-windows @@ -232,7 +229,7 @@ jobs: - name: Install Qt (macOS, Linux & Windows MSVC) if: matrix.msystem == '' - uses: jurplel/install-qt-action@v3 + uses: jurplel/install-qt-action@v4 with: aqtversion: "==3.1.*" py7zrversion: ">=0.20.2" @@ -259,12 +256,12 @@ jobs: wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/continuous/AppImageUpdate-x86_64.AppImage" - sudo apt install libopengl0 + sudo apt install libopengl0 libfuse2 - name: Add QT_HOST_PATH var (Windows MSVC arm64) if: runner.os == 'Windows' && matrix.architecture == 'arm64' run: | - echo "QT_HOST_PATH=${{ github.workspace }}\HostQt\Qt\${{ matrix.qt_version }}\msvc2019_64" >> $env:GITHUB_ENV + echo "QT_HOST_PATH=${{ github.workspace }}\HostQt\Qt\${{ matrix.qt_version }}\msvc2022_64" >> $env:GITHUB_ENV - name: Setup java (macOS) if: runner.os == 'macOS' @@ -380,11 +377,13 @@ jobs: if [ -n '${{ secrets.APPLE_CODESIGN_ID }}' ]; then APPLE_CODESIGN_ID='${{ secrets.APPLE_CODESIGN_ID }}' + ENTITLEMENTS_FILE='../program_info/App.entitlements' else APPLE_CODESIGN_ID='-' + ENTITLEMENTS_FILE='../program_info/AdhocSignedApp.entitlements' fi - sudo codesign --sign "$APPLE_CODESIGN_ID" --deep --force --entitlements "../program_info/App.entitlements" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher" + sudo codesign --sign "$APPLE_CODESIGN_ID" --deep --force --entitlements "$ENTITLEMENTS_FILE" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher" mv "PrismLauncher.app" "Prism Launcher.app" - name: Notarize (macOS) @@ -410,9 +409,8 @@ jobs: if: matrix.name == 'macOS' run: | 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.zip -inkey ed25519-priv.pem | openssl base64 | tr -d \\n) + signature=$(/opt/homebrew/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: @@ -520,8 +518,8 @@ jobs: cp -r ${{ runner.workspace }}/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines - cp /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ - cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ + cp /usr/lib/x86_64-linux-gnu/libcrypto.so.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ + cp /usr/lib/x86_64-linux-gnu/libssl.so.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ cp /usr/lib/x86_64-linux-gnu/libOpenGL.so.0* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib" @@ -556,9 +554,9 @@ jobs: mkdir ${{ env.INSTALL_PORTABLE_DIR }}/lib cp /lib/x86_64-linux-gnu/libbz2.so.1.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib cp /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib - cp /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 ${{ env.INSTALL_PORTABLE_DIR }}/lib - cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 ${{ env.INSTALL_PORTABLE_DIR }}/lib - cp /usr/lib/x86_64-linux-gnu/libffi.so.7 ${{ env.INSTALL_PORTABLE_DIR }}/lib + cp /usr/lib/x86_64-linux-gnu/libcrypto.so.* ${{ env.INSTALL_PORTABLE_DIR }}/lib + cp /usr/lib/x86_64-linux-gnu/libssl.so.* ${{ env.INSTALL_PORTABLE_DIR }}/lib + cp /usr/lib/x86_64-linux-gnu/libffi.so.*.* ${{ env.INSTALL_PORTABLE_DIR }}/lib mv ${{ env.INSTALL_PORTABLE_DIR }}/bin/*.so* ${{ 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 @@ -630,71 +628,3 @@ jobs: shell: msys2 {0} run: | ccache -s - - flatpak: - runs-on: ubuntu-latest - container: - image: bilelmoussaoui/flatpak-github-actions:kde-6.7 - options: --privileged - steps: - - name: Checkout - uses: actions/checkout@v4 - if: inputs.build_type == 'Debug' - with: - submodules: "true" - - name: Build Flatpak (Linux) - if: inputs.build_type == 'Debug' - uses: flatpak/flatpak-github-actions/flatpak-builder@v6 - with: - bundle: "Prism Launcher.flatpak" - manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml - - nix: - name: Nix (${{ matrix.system }}) - - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-22.04 - system: x86_64-linux - - - os: macos-13 - system: x86_64-darwin - - - os: macos-14 - system: aarch64-darwin - - runs-on: ${{ matrix.os }} - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Install Nix - uses: cachix/install-nix-action@v30 - - # For PRs - - name: Setup Nix Magic Cache - uses: DeterminateSystems/magic-nix-cache-action@v8 - - # For in-tree builds - - name: Setup Cachix - uses: cachix/cachix-action@v15 - with: - name: prismlauncher - authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} - - - name: Run flake checks - run: | - nix flake check --print-build-logs --show-trace - - - name: Build debug package - if: ${{ inputs.build_type == 'Debug' }} - run: | - nix build --print-build-logs .#prismlauncher-debug - - - name: Build release package - if: ${{ inputs.build_type != 'Debug' }} - run: | - nix build --print-build-logs .#prismlauncher diff --git a/.github/workflows/flatpak.yml b/.github/workflows/flatpak.yml new file mode 100644 index 000000000..41cc2a51d --- /dev/null +++ b/.github/workflows/flatpak.yml @@ -0,0 +1,62 @@ +name: Flatpak + +on: + push: + paths-ignore: + - "**.md" + - "**/LICENSE" + - ".github/ISSUE_TEMPLATE/**" + - ".markdownlint**" + - "nix/**" + # We don't do anything with these artifacts on releases. They go to Flathub + tags-ignore: + - "*" + pull_request: + paths-ignore: + - "**.md" + - "**/LICENSE" + - ".github/ISSUE_TEMPLATE/**" + - ".markdownlint**" + - "nix/**" + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + name: Build (${{ matrix.arch }}) + + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-22.04 + arch: x86_64 + + - os: ubuntu-22.04-arm + arch: aarch64 + + runs-on: ${{ matrix.os }} + + container: + image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.8 + options: --privileged + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: true + + - name: Set short version + shell: bash + run: | + echo "VERSION=${GITHUB_SHA::7}" >> "$GITHUB_ENV" + + - name: Build Flatpak + uses: flatpak/flatpak-github-actions/flatpak-builder@v6 + with: + bundle: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-Flatpak.flatpak + manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml + arch: ${{ matrix.arch }} diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml new file mode 100644 index 000000000..b44011711 --- /dev/null +++ b/.github/workflows/nix.yml @@ -0,0 +1,90 @@ +name: Nix + +on: + push: + paths-ignore: + - "**.md" + - "**/LICENSE" + - ".github/ISSUE_TEMPLATE/**" + - ".markdownlint**" + - "flatpak/**" + pull_request_target: + paths-ignore: + - "**.md" + - "**/LICENSE" + - ".github/ISSUE_TEMPLATE/**" + - ".markdownlint**" + - "flatpak/**" + workflow_dispatch: + +permissions: + contents: read + +env: + DEBUG: ${{ github.ref_type != 'tag' }} + USE_DETERMINATE: ${{ github.event_name == 'pull_request' }} + +jobs: + build: + name: Build (${{ matrix.system }}) + + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-22.04 + system: x86_64-linux + + - os: ubuntu-22.04-arm + system: aarch64-linux + + - os: macos-13 + system: x86_64-darwin + + - os: macos-14 + system: aarch64-darwin + + runs-on: ${{ matrix.os }} + + permissions: + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@v16 + with: + determinate: ${{ env.USE_DETERMINATE }} + + # For PRs + - name: Setup Nix Magic Cache + if: ${{ env.USE_DETERMINATE }} + uses: DeterminateSystems/flakehub-cache-action@v1 + + # For in-tree builds + - name: Setup Cachix + if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }} + uses: cachix/cachix-action@v15 + with: + name: prismlauncher + authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} + + - name: Run Flake checks + run: | + nix flake check --print-build-logs --show-trace + + - name: Build debug package + if: ${{ env.DEBUG }} + run: | + nix build \ + --no-link --print-build-logs --print-out-paths \ + .#prismlauncher-debug >> "$GITHUB_STEP_SUMMARY" + + - name: Build release package + if: ${{ !env.DEBUG }} + run: | + nix build \ + --no-link --print-build-logs --print-out-paths \ + .#prismlauncher >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000..d49eb4b8d --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,45 @@ +name: Publish + +on: + release: + types: [ released ] + +permissions: + contents: read + +jobs: + flakehub: + name: FlakeHub + + runs-on: ubuntu-latest + + permissions: + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + + - name: Install Nix + uses: cachix/install-nix-action@v30 + + - name: Publish on FlakeHub + uses: determinatesystems/flakehub-push@v5 + with: + visibility: "public" + + winget: + name: Winget + + runs-on: windows-latest + + steps: + - name: Publish on Winget + uses: vedantmgoyal2009/winget-releaser@v2 + with: + identifier: PrismLauncher.PrismLauncher + version: ${{ github.event.release.tag_name }} + installers-regex: 'PrismLauncher-Windows-MSVC(:?-arm64|-Legacy)?-Setup-.+\.exe$' + token: ${{ secrets.WINGET_TOKEN }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..106a7844f --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,29 @@ +name: Stale + +on: + schedule: + # run weekly on sunday + - cron: "0 0 * * 0" + workflow_dispatch: + +jobs: + label: + name: Label issues and PRs + + runs-on: ubuntu-latest + + permissions: + issues: write + pull-requests: write + + steps: + - uses: actions/stale@v9 + with: + days-before-stale: 60 + days-before-close: -1 # Don't close anything + exempt-issue-labels: rfc,nostale,help wanted + exempt-all-milestones: true + exempt-all-assignees: true + operations-per-run: 1000 + stale-issue-label: inactive + stale-pr-label: inactive diff --git a/.github/workflows/trigger_builds.yml b/.github/workflows/trigger_builds.yml index 0b8386d69..9efafc8cc 100644 --- a/.github/workflows/trigger_builds.yml +++ b/.github/workflows/trigger_builds.yml @@ -38,6 +38,5 @@ jobs: 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 }} - CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} 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 e800653e3..134281b2c 100644 --- a/.github/workflows/trigger_release.yml +++ b/.github/workflows/trigger_release.yml @@ -22,7 +22,6 @@ jobs: 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 }} - CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }} diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml deleted file mode 100644 index eacf23099..000000000 --- a/.github/workflows/winget.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Publish to WinGet -on: - release: - types: [released] - -jobs: - publish: - runs-on: windows-latest - steps: - - uses: vedantmgoyal2009/winget-releaser@v2 - with: - identifier: PrismLauncher.PrismLauncher - version: ${{ github.event.release.tag_name }} - installers-regex: 'PrismLauncher-Windows-MSVC(:?-arm64|-Legacy)?-Setup-.+\.exe$' - token: ${{ secrets.WINGET_TOKEN }} diff --git a/CMakeLists.txt b/CMakeLists.txt index b677b0b7c..a9d543e83 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,6 +78,13 @@ else() # ATL's pack list needs more than the default 1 Mib stack on windows if(WIN32) set(CMAKE_EXE_LINKER_FLAGS "-Wl,--stack,8388608 ${CMAKE_EXE_LINKER_FLAGS}") + + # -ffunction-sections and -fdata-sections help reduce binary size + # -mguard=cf enables Control Flow Guard + # TODO: Look into -gc-sections to further reduce binary size + foreach(lang C CXX) + set("CMAKE_${lang}_FLAGS_RELEASE" "-ffunction-sections -fdata-sections -mguard=cf") + endforeach() endif() endif() @@ -106,14 +113,14 @@ if ((CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebI else() # AppleClang and Clang message(STATUS "Address Sanitizer available on Clang") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=null") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=null") endif() elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") # GCC message(STATUS "Address Sanitizer available on GCC") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover") link_libraries("asan") elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") message(STATUS "Address Sanitizer available on MSVC") @@ -180,7 +187,7 @@ set(Launcher_LOGIN_CALLBACK_URL "https://prismlauncher.org/successful-login" CAC set(Launcher_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" CACHE STRING "URL for FML Libraries.") ######## Set version numbers ######## -set(Launcher_VERSION_MAJOR 9) +set(Launcher_VERSION_MAJOR 10) set(Launcher_VERSION_MINOR 0) set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}") @@ -299,6 +306,8 @@ include(QtVersionlessBackport) if(Launcher_QT_VERSION_MAJOR EQUAL 5) set(QT_VERSION_MAJOR 5) find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml NetworkAuth) + find_package(Qt5 COMPONENTS DBus) + list(APPEND Launcher_QT_DBUS Qt5::DBus) if(NOT Launcher_FORCE_BUNDLED_LIBS) find_package(QuaZip-Qt5 1.3 QUIET) @@ -313,6 +322,8 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 5) elseif(Launcher_QT_VERSION_MAJOR EQUAL 6) set(QT_VERSION_MAJOR 6) find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat NetworkAuth) + find_package(Qt6 COMPONENTS DBus) + list(APPEND Launcher_QT_DBUS Qt6::DBus) list(APPEND Launcher_QT_LIBS Qt6::Core5Compat) if(NOT Launcher_FORCE_BUNDLED_LIBS) @@ -397,8 +408,8 @@ if(UNIX AND APPLE) set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=" CACHE STRING "Public key for Sparkle update feed") set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml" CACHE STRING "URL for Sparkle update feed") - set(MACOSX_SPARKLE_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.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_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.6.4/Sparkle-2.6.4.tar.xz" CACHE STRING "URL to Sparkle release archive") + set(MACOSX_SPARKLE_SHA256 "50612a06038abc931f16011d7903b8326a362c1074dabccb718404ce8e585f0b" CACHE STRING "SHA256 checksum for Sparkle release archive") set(MACOSX_SPARKLE_DIR "${CMAKE_BINARY_DIR}/frameworks/Sparkle") # directories to look for dependencies diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 072916772..5965f4d8e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,16 +2,59 @@ ## Code formatting -Try to follow the existing formatting. -If there is no existing formatting, you may use `clang-format` with our included `.clang-format` configuration. +All files are formatted with `clang-format` using the configuration in `.clang-format`. Ensure it is run on changed files before committing! -In general, in order of importance: +Please also follow the project's conventions for C++: -- Make sure your IDE is not messing up line endings or whitespace and avoid using linters. -- Prefer readability over dogma. -- Keep to the existing formatting. -- Indent with 4 space unless it's in a submodule. -- Keep lists (of arguments, parameters, initializers...) as lists, not paragraphs. It should either read from top to bottom, or left to right. Not both. +- Class and type names should be formatted as `PascalCase`: `MyClass`. +- Private or protected class data members should be formatted as `camelCase` prefixed with `m_`: `m_myCounter`. +- Private or protected `static` class data members should be formatted as `camelCase` prefixed with `s_`: `s_instance`. +- Public class data members should be formatted as `camelCase` without the prefix: `dateOfBirth`. +- Public, private or protected `static const` class data members should be formatted as `SCREAMING_SNAKE_CASE`: `MAX_VALUE`. +- Class function members should be formatted as `camelCase` without a prefix: `incrementCounter`. +- Global functions and non-`const` global variables should be formatted as `camelCase` without a prefix: `globalData`. +- `const` global variables, macros, and enum constants should be formatted as `SCREAMING_SNAKE_CASE`: `LIGHT_GRAY`. +- Avoid inventing acronyms or abbreviations especially for a name of multiple words - like `tp` for `texturePack`. + +Most of these rules are included in the `.clang-tidy` file, so you can run `clang-tidy` to check for any violations. + +Here is what these conventions with the formatting configuration look like: + +```c++ +#define AWESOMENESS 10 + +constexpr double PI = 3.14159; + +enum class PizzaToppings { HAM_AND_PINEAPPLE, OREO_AND_KETCHUP }; + +struct Person { + QString name; + QDateTime dateOfBirth; + + long daysOld() const { return dateOfBirth.daysTo(QDateTime::currentDateTime()); } +}; + +class ImportantClass { + public: + void incrementCounter() + { + if (m_counter + 1 > MAX_COUNTER_VALUE) + throw std::runtime_error("Counter has reached limit!"); + + ++m_counter; + } + + int counter() const { return m_counter; } + + private: + static constexpr int MAX_COUNTER_VALUE = 100; + int m_counter; +}; + +ImportantClass importantClassInstance; +``` + +If you see any names which do not follow these conventions, it is preferred that you leave them be - renames increase the number of changes therefore make reviewing harder and make your PR more prone to conflicts. However, if you're refactoring a whole class anyway, it's fine. ## Signing your work diff --git a/README.md b/README.md index 9c4909509..7b2878c2f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +

This is a fork of PrismLauncher, unlocked to be able to use offline accounts without owning the game.

+

diff --git a/cmake/MacOSXBundleInfo.plist.in b/cmake/MacOSXBundleInfo.plist.in index 6d3845dfc..3a8c8fbfe 100644 --- a/cmake/MacOSXBundleInfo.plist.in +++ b/cmake/MacOSXBundleInfo.plist.in @@ -8,6 +8,8 @@ A Minecraft mod wants to access your microphone. NSDownloadsFolderUsageDescription Prism uses access to your Downloads folder to help you more quickly add mods that can't be automatically downloaded to your instance. You can change where Prism scans for downloaded mods in Settings or the prompt that appears. + NSLocalNetworkUsageDescription + Minecraft uses the local network to find and connect to LAN servers. NSPrincipalClass NSApplication NSHighResolutionCapable diff --git a/flake.lock b/flake.lock index a82e6f65f..dd50b5395 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "lastModified": 1733328505, + "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=", "owner": "edolstra", "repo": "flake-compat", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", "type": "github" }, "original": { @@ -34,11 +34,11 @@ }, "nix-filter": { "locked": { - "lastModified": 1710156097, - "narHash": "sha256-1Wvk8UP7PXdf8bCCaEoMnOT1qe5/Duqgj+rL8sRQsSM=", + "lastModified": 1731533336, + "narHash": "sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms=", "owner": "numtide", "repo": "nix-filter", - "rev": "3342559a24e85fc164b295c3444e8a139924675b", + "rev": "f7653272fd234696ae94229839a99b73c9ab7de0", "type": "github" }, "original": { @@ -49,11 +49,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1729256560, - "narHash": "sha256-/uilDXvCIEs3C9l73JTACm4quuHUsIHcns1c+cHUJwA=", + "lastModified": 1739446958, + "narHash": "sha256-+/bYK3DbPxMIvSL4zArkMX0LQvS7rzBKXnDXLfKyRVc=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "4c2fcb090b1f3e5b47eaa7bd33913b574a11e0a0", + "rev": "2ff53fe64443980e139eaa286017f53f88336dd0", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index f4ca782ec..54add656d 100644 --- a/flake.nix +++ b/flake.nix @@ -85,24 +85,18 @@ formatter = forAllSystems (system: nixpkgsFor.${system}.nixfmt-rfc-style); - overlays.default = - final: prev: - let - version = builtins.substring 0 8 self.lastModifiedDate or "dirty"; - in - { - prismlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix { - inherit - libnbtplusplus - nix-filter - self - version - ; - }; - - prismlauncher = final.callPackage ./nix/wrapper.nix { }; + overlays.default = final: prev: { + prismlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix { + inherit + libnbtplusplus + nix-filter + self + ; }; + prismlauncher = final.callPackage ./nix/wrapper.nix { }; + }; + packages = forAllSystems ( system: let diff --git a/flatpak/flite.json b/flatpak/flite.json new file mode 100644 index 000000000..1bf280af1 --- /dev/null +++ b/flatpak/flite.json @@ -0,0 +1,20 @@ +{ + "name": "flite", + "config-opts": [ + "--enable-shared", + "--with-audio=pulseaudio" + ], + "no-parallel-make": true, + "sources": [ + { + "type": "git", + "url": "https://github.com/festvox/flite.git", + "tag": "v2.2", + "commit": "e9e2e37c329dbe98bfeb27a1828ef9a71fa84f88", + "x-checker-data": { + "type": "git", + "tag-pattern": "^v([\\d.]+)$" + } + } + ] +} diff --git a/flatpak/libdecor.json b/flatpak/libdecor.json index 589310a35..1652a2f04 100644 --- a/flatpak/libdecor.json +++ b/flatpak/libdecor.json @@ -1,22 +1,18 @@ { - "name": "libdecor", - "buildsystem": "meson", - "config-opts": [ - "-Ddemo=false" - ], - "sources": [ - { - "type": "git", - "url": "https://gitlab.freedesktop.org/libdecor/libdecor.git", - "commit": "73260393a97291c887e1074ab7f318e031be0ac6" - }, - { - "type": "patch", - "path": "patches/weird_libdecor.patch" - } - ], - "cleanup": [ - "/include", - "/lib/pkgconfig" - ] + "name": "libdecor", + "buildsystem": "meson", + "config-opts": [ + "-Ddemo=false" + ], + "sources": [ + { + "type": "git", + "url": "https://gitlab.freedesktop.org/libdecor/libdecor.git", + "commit": "c2bd8ad6fa42c0cb17553ce77ad8a87d1f543b1f" + } + ], + "cleanup": [ + "/include", + "/lib/pkgconfig" + ] } diff --git a/flatpak/org.prismlauncher.PrismLauncher.yml b/flatpak/org.prismlauncher.PrismLauncher.yml index 09dd8d73b..136aef91a 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: 6.7 +runtime-version: '6.8' sdk: org.kde.Sdk sdk-extensions: - org.freedesktop.Sdk.Extension.openjdk17 @@ -19,6 +19,12 @@ finish-args: - --filesystem=xdg-download:ro # FTBApp import - --filesystem=~/.ftba:ro + # Userspace visibility for manual hugepages configuration + # Required for -XX:+UseLargePages + - --filesystem=/sys/kernel/mm/hugepages:ro + # Userspace visibility for transparent hugepages configuration + # Required for -XX:+UseTransparentHugePages + - --filesystem=/sys/kernel/mm/transparent_hugepage:ro modules: # Might be needed by some Controller mods (see https://github.com/isXander/Controlify/issues/31) @@ -27,11 +33,16 @@ modules: # Needed for proper Wayland support - libdecor.json + # Text to Speech in the game + - flite.json + - name: prismlauncher buildsystem: cmake-ninja builddir: true config-opts: - -DLauncher_BUILD_PLATFORM=flatpak + # This allows us to manage and update Java independently of this Flatpak + - -DLauncher_ENABLE_JAVA_DOWNLOADER=ON - -DCMAKE_BUILD_TYPE=RelWithDebInfo build-options: env: @@ -47,18 +58,14 @@ modules: config-opts: - -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DBUILD_SHARED_LIBS:BOOL=ON - - -DGLFW_USE_WAYLAND:BOOL=ON + - -DGLFW_BUILD_WAYLAND:BOOL=ON - -DGLFW_BUILD_DOCS:BOOL=OFF sources: - type: git url: https://github.com/glfw/glfw.git - commit: 3fa2360720eeba1964df3c0ecf4b5df8648a8e52 + commit: 7b6aead9fb88b3623e3b3725ebb42670cbe4c579 # 3.4 - type: patch - path: patches/0003-Don-t-crash-on-calls-to-focus-or-icon.patch - - type: patch - path: patches/0005-Add-warning-about-being-an-unofficial-patch.patch - - type: patch - path: patches/0007-Platform-Prefer-Wayland-over-X11.patch + path: patches/0009-Defer-setting-cursor-position-until-the-cursor-is-lo.patch cleanup: - /include - /lib/cmake @@ -68,8 +75,8 @@ modules: buildsystem: autotools sources: - type: archive - url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.2.tar.xz - sha256: c8bee4790d9058bacc4b6246456c58021db58a87ddda1a9d0139bf5f18f1f240 + url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.3.tar.xz + sha256: f8dd7566adb74147fab9964680b6bbadee87cf406a7fcff51718a5e6949b841c x-checker-data: type: anitya project-id: 14957 @@ -91,8 +98,8 @@ modules: sources: - type: archive dest-filename: gamemode.tar.gz - url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.8.1 - sha256: 969cf85b5ca3944f3e315cd73a0ee9bea4f9c968cd7d485e9f4745bc1e679c4e + url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.8.2 + sha256: 2886d4ce543c78bd2a364316d5e7fd59ef06b71de63f896b37c6d3dc97658f60 x-checker-data: type: json url: https://api.github.com/repos/FeralInteractive/gamemode/releases/latest diff --git a/flatpak/patches/0003-Don-t-crash-on-calls-to-focus-or-icon.patch b/flatpak/patches/0003-Don-t-crash-on-calls-to-focus-or-icon.patch deleted file mode 100644 index 9130e856c..000000000 --- a/flatpak/patches/0003-Don-t-crash-on-calls-to-focus-or-icon.patch +++ /dev/null @@ -1,24 +0,0 @@ -diff --git a/src/wl_window.c b/src/wl_window.c -index 52d3b9eb..4ac4eb5d 100644 ---- a/src/wl_window.c -+++ b/src/wl_window.c -@@ -2117,8 +2117,7 @@ void _glfwSetWindowTitleWayland(_GLFWwindow* window, const char* title) - void _glfwSetWindowIconWayland(_GLFWwindow* window, - int count, const GLFWimage* images) - { -- _glfwInputError(GLFW_FEATURE_UNAVAILABLE, -- "Wayland: The platform does not support setting the window icon"); -+ fprintf(stderr, "!!! Ignoring Error: Wayland: The platform does not support setting the window icon\n"); - } - - void _glfwGetWindowPosWayland(_GLFWwindow* window, int* xpos, int* ypos) -@@ -2361,8 +2360,7 @@ void _glfwRequestWindowAttentionWayland(_GLFWwindow* window) - - void _glfwFocusWindowWayland(_GLFWwindow* window) - { -- _glfwInputError(GLFW_FEATURE_UNAVAILABLE, -- "Wayland: The platform does not support setting the input focus"); -+ fprintf(stderr, "!!! Ignoring Error: Wayland: The platform does not support setting the input focus\n"); - } - - void _glfwSetWindowMonitorWayland(_GLFWwindow* window, diff --git a/flatpak/patches/0005-Add-warning-about-being-an-unofficial-patch.patch b/flatpak/patches/0005-Add-warning-about-being-an-unofficial-patch.patch deleted file mode 100644 index b031d739f..000000000 --- a/flatpak/patches/0005-Add-warning-about-being-an-unofficial-patch.patch +++ /dev/null @@ -1,17 +0,0 @@ -diff --git a/src/init.c b/src/init.c -index 06dbb3f2..a7c6da86 100644 ---- a/src/init.c -+++ b/src/init.c -@@ -449,6 +449,12 @@ GLFWAPI int glfwInit(void) - _glfw.initialized = GLFW_TRUE; - - glfwDefaultWindowHints(); -+ -+ fprintf(stderr, "!!! Patched GLFW from https://github.com/Admicos/minecraft-wayland\n" -+ "!!! If any issues with the window, or some issues with rendering, occur, " -+ "first try with the built-in GLFW, and if that solves the issue, report there first.\n" -+ "!!! Use outside Minecraft is untested, and things might break.\n"); -+ - return GLFW_TRUE; - } - diff --git a/flatpak/patches/0007-Platform-Prefer-Wayland-over-X11.patch b/flatpak/patches/0007-Platform-Prefer-Wayland-over-X11.patch deleted file mode 100644 index 4eeb81309..000000000 --- a/flatpak/patches/0007-Platform-Prefer-Wayland-over-X11.patch +++ /dev/null @@ -1,20 +0,0 @@ -diff --git a/src/platform.c b/src/platform.c -index c5966ae7..3e7442f9 100644 ---- a/src/platform.c -+++ b/src/platform.c -@@ -49,12 +49,12 @@ static const struct - #if defined(_GLFW_COCOA) - { GLFW_PLATFORM_COCOA, _glfwConnectCocoa }, - #endif --#if defined(_GLFW_X11) -- { GLFW_PLATFORM_X11, _glfwConnectX11 }, --#endif - #if defined(_GLFW_WAYLAND) - { GLFW_PLATFORM_WAYLAND, _glfwConnectWayland }, - #endif -+#if defined(_GLFW_X11) -+ { GLFW_PLATFORM_X11, _glfwConnectX11 }, -+#endif - }; - - GLFWbool _glfwSelectPlatform(int desiredID, _GLFWplatform* platform) diff --git a/flatpak/patches/0009-Defer-setting-cursor-position-until-the-cursor-is-lo.patch b/flatpak/patches/0009-Defer-setting-cursor-position-until-the-cursor-is-lo.patch new file mode 100644 index 000000000..70cec9981 --- /dev/null +++ b/flatpak/patches/0009-Defer-setting-cursor-position-until-the-cursor-is-lo.patch @@ -0,0 +1,59 @@ +From 9997ae55a47de469ea26f8437c30b51483abda5f Mon Sep 17 00:00:00 2001 +From: Dan Klishch +Date: Sat, 30 Sep 2023 23:38:05 -0400 +Subject: Defer setting cursor position until the cursor is locked + +--- + src/wl_platform.h | 3 +++ + src/wl_window.c | 14 ++++++++++++-- + 2 files changed, 15 insertions(+), 2 deletions(-) + +diff --git a/src/wl_platform.h b/src/wl_platform.h +index ca34f66e..cd1f227f 100644 +--- a/src/wl_platform.h ++++ b/src/wl_platform.h +@@ -403,6 +403,9 @@ typedef struct _GLFWwindowWayland + int scaleSize; + int compositorPreferredScale; + ++ double askedCursorPosX, askedCursorPosY; ++ GLFWbool didAskForSetCursorPos; ++ + struct zwp_relative_pointer_v1* relativePointer; + struct zwp_locked_pointer_v1* lockedPointer; + struct zwp_confined_pointer_v1* confinedPointer; +diff --git a/src/wl_window.c b/src/wl_window.c +index 1de26558..0df16747 100644 +--- a/src/wl_window.c ++++ b/src/wl_window.c +@@ -2586,8 +2586,9 @@ void _glfwGetCursorPosWayland(_GLFWwindow* window, double* xpos, double* ypos) + + void _glfwSetCursorPosWayland(_GLFWwindow* window, double x, double y) + { +- _glfwInputError(GLFW_FEATURE_UNAVAILABLE, +- "Wayland: The platform does not support setting the cursor position"); ++ window->wl.didAskForSetCursorPos = true; ++ window->wl.askedCursorPosX = x; ++ window->wl.askedCursorPosY = y; + } + + void _glfwSetCursorModeWayland(_GLFWwindow* window, int mode) +@@ -2819,6 +2820,15 @@ static const struct zwp_relative_pointer_v1_listener relativePointerListener = + static void lockedPointerHandleLocked(void* userData, + struct zwp_locked_pointer_v1* lockedPointer) + { ++ _GLFWwindow* window = userData; ++ ++ if (window->wl.didAskForSetCursorPos) ++ { ++ window->wl.didAskForSetCursorPos = false; ++ zwp_locked_pointer_v1_set_cursor_position_hint(window->wl.lockedPointer, ++ wl_fixed_from_double(window->wl.askedCursorPosX), ++ wl_fixed_from_double(window->wl.askedCursorPosY)); ++ } + } + + static void lockedPointerHandleUnlocked(void* userData, +-- +2.42.0 + diff --git a/flatpak/patches/weird_libdecor.patch b/flatpak/patches/weird_libdecor.patch deleted file mode 100644 index 3a400b820..000000000 --- a/flatpak/patches/weird_libdecor.patch +++ /dev/null @@ -1,40 +0,0 @@ -diff --git a/src/libdecor.c b/src/libdecor.c -index a9c1106..1aa38b3 100644 ---- a/src/libdecor.c -+++ b/src/libdecor.c -@@ -1391,22 +1391,32 @@ calculate_priority(const struct libdecor_plugin_description *plugin_description) - static bool - check_symbol_conflicts(const struct libdecor_plugin_description *plugin_description) - { -+ bool ret = true; - char * const *symbol; -+ void* main_prog = dlopen(NULL, RTLD_LAZY); -+ if (!main_prog) { -+ fprintf(stderr, "Plugin \"%s\" couldn't check conflicting symbols: \"%s\".\n", -+ plugin_description->description, dlerror()); -+ return false; -+ } -+ - - symbol = plugin_description->conflicting_symbols; - while (*symbol) { - dlerror(); -- dlsym (RTLD_DEFAULT, *symbol); -+ dlsym (main_prog, *symbol); - if (!dlerror()) { - fprintf(stderr, "Plugin \"%s\" uses conflicting symbol \"%s\".\n", - plugin_description->description, *symbol); -- return false; -+ ret = false; -+ break; - } - - symbol++; - } - -- return true; -+ dlclose(main_prog); -+ return ret; - } - - static struct plugin_loader * diff --git a/flatpak/shared-modules b/flatpak/shared-modules index f2b0c16a2..f5d368a31 160000 --- a/flatpak/shared-modules +++ b/flatpak/shared-modules @@ -1 +1 @@ -Subproject commit f2b0c16a2a217a1822ce5a6538ba8f755ed1dd32 +Subproject commit f5d368a31d6ef046eb2955c74ec6f54f32ed5c4e diff --git a/launcher/Application.cpp b/launcher/Application.cpp index b8dcc1099..6f675dcee 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -48,6 +48,7 @@ #include "net/PasteUpload.h" #include "pathmatcher/MultiMatcher.h" #include "pathmatcher/SimplePrefixMatcher.h" +#include "tasks/Task.h" #include "tools/GenericProfiler.h" #include "ui/InstanceWindow.h" #include "ui/MainWindow.h" @@ -58,8 +59,6 @@ #include "ui/pages/BasePageProvider.h" #include "ui/pages/global/APIPage.h" #include "ui/pages/global/AccountListPage.h" -#include "ui/pages/global/CustomCommandsPage.h" -#include "ui/pages/global/EnvironmentVariablesPage.h" #include "ui/pages/global/ExternalToolsPage.h" #include "ui/pages/global/JavaPage.h" #include "ui/pages/global/LanguagePage.h" @@ -242,6 +241,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) { { "s", "server" }, "Join the specified server on launch (only valid in combination with --launch)", "address" }, { { "w", "world" }, "Join the specified world on launch (only valid in combination with --launch)", "world" }, { { "a", "profile" }, "Use the account specified by its profile name (only valid in combination with --launch)", "profile" }, + { { "o", "offline" }, "Launch offline, with given player name (only valid in combination with --launch)", "offline" }, { "alive", "Write a small '" + liveCheckFile + "' file after the launcher starts" }, { { "I", "import" }, "Import instance or resource from specified local path or URL", "url" }, { "show", "Opens the window for the specified instance (by instance ID)", "show" } }); @@ -257,6 +257,10 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_serverToJoin = parser.value("server"); m_worldToJoin = parser.value("world"); m_profileToUse = parser.value("profile"); + if (parser.isSet("offline")) { + m_offline = true; + m_offlineName = parser.value("offline"); + } m_liveCheck = parser.isSet("alive"); m_instanceIdToShowWindowOf = parser.value("show"); @@ -271,8 +275,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) } // error if --launch is missing with --server or --profile - if (((!m_serverToJoin.isEmpty() || !m_worldToJoin.isEmpty()) || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty()) { - std::cerr << "--server and --profile can only be used in combination with --launch!" << std::endl; + if ((!m_serverToJoin.isEmpty() || !m_worldToJoin.isEmpty() || !m_profileToUse.isEmpty() || m_offline) && + m_instanceIdToLaunch.isEmpty()) { + std::cerr << "--server, --profile and --offline can only be used in combination with --launch!" << std::endl; m_status = Application::Failed; return; } @@ -397,6 +402,10 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) if (!m_profileToUse.isEmpty()) { launch.args["profile"] = m_profileToUse; } + if (m_offline) { + launch.args["offline_enabled"] = "true"; + launch.args["offline_name"] = m_offlineName; + } m_peerInstance->sendMessage(launch.serialize(), timeout); } m_status = Application::Succeeded; @@ -605,6 +614,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_settings->registerSetting("IconsDir", "icons"); m_settings->registerSetting("DownloadsDir", QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); m_settings->registerSetting("DownloadsDirWatchRecursive", false); + m_settings->registerSetting("MoveModsFromDownloadsDir", false); m_settings->registerSetting("SkinsDir", "skins"); m_settings->registerSetting("JavaDir", "java"); @@ -790,8 +800,6 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_globalSettingsProvider->addPage(); m_globalSettingsProvider->addPage(); m_globalSettingsProvider->addPage(); - m_globalSettingsProvider->addPage(); - m_globalSettingsProvider->addPage(); m_globalSettingsProvider->addPage(); m_globalSettingsProvider->addPage(); m_globalSettingsProvider->addPage(); @@ -835,7 +843,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) ":/icons/multimc/128x128/instances/", ":/icons/multimc/scalable/instances/" }; m_icons.reset(new IconList(instFolders, setting->get().toString())); connect(setting.get(), &Setting::SettingChanged, - [&](const Setting&, QVariant value) { m_icons->directoryChanged(value.toString()); }); + [this](const Setting&, QVariant value) { m_icons->directoryChanged(value.toString()); }); qDebug() << "<> Instance icons initialized."; } @@ -1070,8 +1078,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) bool Application::createSetupWizard() { - bool javaRequired = [&]() { - bool ignoreJavaWizard = m_settings->get("IgnoreJavaWizard").toBool(); + bool javaRequired = [this]() { + if (BuildConfig.JAVA_DOWNLOADER_ENABLED && settings()->get("AutomaticJavaDownload").toBool()) { + return false; + } + bool ignoreJavaWizard = settings()->get("IgnoreJavaWizard").toBool(); if (ignoreJavaWizard) { return false; } @@ -1083,13 +1094,10 @@ bool Application::createSetupWizard() } QString currentJavaPath = settings()->get("JavaPath").toString(); QString actualPath = FS::ResolveExecutable(currentJavaPath); - if (actualPath.isNull()) { - return true; - } - return false; + return actualPath.isNull(); }(); - bool askjava = BuildConfig.JAVA_DOWNLOADER_ENABLED && !javaRequired && !m_settings->get("AutomaticJavaDownload").toBool() && - !m_settings->get("AutomaticJavaSwitch").toBool() && !m_settings->get("UserAskedAboutAutomaticJavaDownload").toBool(); + bool askjava = BuildConfig.JAVA_DOWNLOADER_ENABLED && !javaRequired && !settings()->get("AutomaticJavaDownload").toBool() && + !settings()->get("AutomaticJavaSwitch").toBool() && !settings()->get("UserAskedAboutAutomaticJavaDownload").toBool(); bool languageRequired = settings()->get("Language").toString().isEmpty(); bool pasteInterventionRequired = settings()->get("PastebinURL") != ""; bool validWidgets = m_themeManager->isValidApplicationTheme(settings()->get("ApplicationTheme").toString()); @@ -1209,7 +1217,7 @@ void Application::performMainStartupAction() qDebug() << " Launching with account" << m_profileToUse; } - launch(inst, true, false, targetToJoin, accountToUse); + launch(inst, !m_offline, false, targetToJoin, accountToUse, m_offlineName); return; } } @@ -1308,6 +1316,8 @@ void Application::messageReceived(const QByteArray& message) QString server = received.args["server"]; QString world = received.args["world"]; QString profile = received.args["profile"]; + bool offline = received.args["offline_enabled"] == "true"; + QString offlineName = received.args["offline_name"]; InstancePtr instance; if (!id.isEmpty()) { @@ -1337,7 +1347,7 @@ void Application::messageReceived(const QByteArray& message) } } - launch(instance, true, false, serverObject, accountObject); + launch(instance, !offline, false, serverObject, accountObject, offlineName); } else { qWarning() << "Received invalid message" << message; } @@ -1375,11 +1385,17 @@ bool Application::openJsonEditor(const QString& filename) } } -bool Application::launch(InstancePtr instance, bool online, bool demo, MinecraftTarget::Ptr targetToJoin, MinecraftAccountPtr accountToUse) +bool Application::launch(InstancePtr instance, + bool online, + bool demo, + MinecraftTarget::Ptr targetToJoin, + MinecraftAccountPtr accountToUse, + const QString& offlineName) { if (m_updateRunning) { qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed."; } else if (instance->canLaunch()) { + QMutexLocker locker(&m_instanceExtrasMutex); auto& extras = m_instanceExtras[instance->id()]; auto window = extras.window; if (window) { @@ -1395,6 +1411,7 @@ bool Application::launch(InstancePtr instance, bool online, bool demo, Minecraft controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get()); controller->setTargetToJoin(targetToJoin); controller->setAccountToUse(accountToUse); + controller->setOfflineName(offlineName); if (window) { controller->setParentWidget(window); } else if (m_mainWindow) { @@ -1404,7 +1421,7 @@ bool Application::launch(InstancePtr instance, bool online, bool demo, Minecraft connect(controller.get(), &LaunchController::failed, this, &Application::controllerFailed); connect(controller.get(), &LaunchController::aborted, this, [this] { controllerFailed(tr("Aborted")); }); addRunningInstance(); - controller->start(); + QMetaObject::invokeMethod(controller.get(), &Task::start, Qt::QueuedConnection); return true; } else if (instance->isRunning()) { showInstanceWindow(instance, "console"); @@ -1422,9 +1439,11 @@ bool Application::kill(InstancePtr instance) qWarning() << "Attempted to kill instance" << instance->id() << ", which isn't running."; return false; } + QMutexLocker locker(&m_instanceExtrasMutex); auto& extras = m_instanceExtras[instance->id()]; // NOTE: copy of the shared pointer keeps it alive auto controller = extras.controller; + locker.unlock(); if (controller) { return controller->abort(); } @@ -1478,12 +1497,14 @@ void Application::controllerSucceeded() if (!controller) return; auto id = controller->id(); + + QMutexLocker locker(&m_instanceExtrasMutex); auto& extras = m_instanceExtras[id]; // on success, do... if (controller->instance()->settings()->get("AutoCloseConsole").toBool()) { if (extras.window) { - extras.window->close(); + QMetaObject::invokeMethod(extras.window, &QWidget::close, Qt::QueuedConnection); } } extras.controller.reset(); @@ -1503,6 +1524,7 @@ void Application::controllerFailed(const QString& error) if (!controller) return; auto id = controller->id(); + QMutexLocker locker(&m_instanceExtrasMutex); auto& extras = m_instanceExtras[id]; // on failure, do... nothing @@ -1560,6 +1582,7 @@ InstanceWindow* Application::showInstanceWindow(InstancePtr instance, QString pa if (!instance) return nullptr; auto id = instance->id(); + QMutexLocker locker(&m_instanceExtrasMutex); auto& extras = m_instanceExtras[id]; auto& window = extras.window; @@ -1597,6 +1620,7 @@ void Application::on_windowClose() m_openWindows--; auto instWindow = qobject_cast(QObject::sender()); if (instWindow) { + QMutexLocker locker(&m_instanceExtrasMutex); auto& extras = m_instanceExtras[instWindow->instanceId()]; extras.window = nullptr; if (extras.controller) { @@ -1844,7 +1868,7 @@ bool Application::handleDataMigration(const QString& currentData, matcher->add(std::make_shared("themes/")); ProgressDialog diag; - DataMigrationTask task(nullptr, oldData, currentData, matcher); + DataMigrationTask task(oldData, currentData, matcher); if (diag.execWithTask(&task)) { qDebug() << "<> Migration succeeded"; setDoNotMigrate(); @@ -1883,3 +1907,31 @@ const QString Application::javaPath() { return m_settings->get("JavaDir").toString(); } + +void Application::addQSavePath(QString path) +{ + QMutexLocker locker(&m_qsaveResourcesMutex); + m_qsaveResources[path] = m_qsaveResources.value(path, 0) + 1; +} + +void Application::removeQSavePath(QString path) +{ + QMutexLocker locker(&m_qsaveResourcesMutex); + auto count = m_qsaveResources.value(path, 0) - 1; + if (count <= 0) { + m_qsaveResources.remove(path); + } else { + m_qsaveResources[path] = count; + } +} + +bool Application::checkQSavePath(QString path) +{ + QMutexLocker locker(&m_qsaveResourcesMutex); + for (auto partialPath : m_qsaveResources.keys()) { + if (path.startsWith(partialPath) && m_qsaveResources.value(partialPath, 0) > 0) { + return true; + } + } + return false; +} \ No newline at end of file diff --git a/launcher/Application.h b/launcher/Application.h index bd1cb2dea..dad76d702 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -42,6 +42,7 @@ #include #include #include +#include #include #include @@ -210,7 +211,8 @@ class Application : public QApplication { bool online = true, bool demo = false, MinecraftTarget::Ptr targetToJoin = nullptr, - MinecraftAccountPtr accountToUse = nullptr); + MinecraftAccountPtr accountToUse = nullptr, + const QString& offlineName = QString()); bool kill(InstancePtr instance); void closeCurrentWindow(); @@ -278,6 +280,7 @@ class Application : public QApplication { shared_qobject_ptr controller; }; std::map m_instanceExtras; + mutable QMutex m_instanceExtrasMutex; // main state variables size_t m_openWindows = 0; @@ -299,8 +302,19 @@ class Application : public QApplication { QString m_serverToJoin; QString m_worldToJoin; QString m_profileToUse; + bool m_offline = false; + QString m_offlineName; bool m_liveCheck = false; QList m_urlsToImport; QString m_instanceIdToShowWindowOf; std::unique_ptr logFile; + + public: + void addQSavePath(QString); + void removeQSavePath(QString); + bool checkQSavePath(QString); + + private: + QHash m_qsaveResources; + mutable QMutex m_qsaveResourcesMutex; }; diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 69cf95e3c..ccfd0b847 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -411,3 +411,8 @@ void BaseInstance::updateRuntimeContext() { // NOOP } + +bool BaseInstance::isLegacy() +{ + return traits().contains("legacyLaunch") || traits().contains("alphaLaunch"); +} diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 2be28d1ec..9827a08b4 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -269,6 +269,8 @@ class BaseInstance : public QObject, public std::enable_shared_from_this -DataMigrationTask::DataMigrationTask(QObject* parent, - const QString& sourcePath, - const QString& targetPath, - const IPathMatcher::Ptr pathMatcher) - : Task(parent), m_sourcePath(sourcePath), m_targetPath(targetPath), m_pathMatcher(pathMatcher), m_copy(sourcePath, targetPath) +DataMigrationTask::DataMigrationTask(const QString& sourcePath, const QString& targetPath, const IPathMatcher::Ptr pathMatcher) + : Task(), m_sourcePath(sourcePath), m_targetPath(targetPath), m_pathMatcher(pathMatcher), m_copy(sourcePath, targetPath) { m_copy.matcher(m_pathMatcher.get()).whitelist(true); } @@ -27,7 +24,7 @@ void DataMigrationTask::executeTask() // 1. Scan // Check how many files we gotta copy - m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&] { + m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] { return m_copy(true); // dry run to collect amount of files }); connect(&m_copyFutureWatcher, &QFutureWatcher::finished, this, &DataMigrationTask::dryRunFinished); @@ -60,7 +57,7 @@ void DataMigrationTask::dryRunFinished() setProgress(m_copy.totalCopied(), m_toCopy); setStatus(tr("Copying %1…").arg(shortenedName)); }); - m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&] { + m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] { return m_copy(false); // actually copy now }); connect(&m_copyFutureWatcher, &QFutureWatcher::finished, this, &DataMigrationTask::copyFinished); diff --git a/launcher/DataMigrationTask.h b/launcher/DataMigrationTask.h index aba9f2399..fc613cd5e 100644 --- a/launcher/DataMigrationTask.h +++ b/launcher/DataMigrationTask.h @@ -18,7 +18,7 @@ class DataMigrationTask : public Task { Q_OBJECT public: - explicit DataMigrationTask(QObject* parent, const QString& sourcePath, const QString& targetPath, IPathMatcher::Ptr pathmatcher); + explicit DataMigrationTask(const QString& sourcePath, const QString& targetPath, IPathMatcher::Ptr pathmatcher); ~DataMigrationTask() override = default; protected: diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 7f38cff04..954e7936e 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -45,7 +45,6 @@ #include #include #include -#include #include #include #include @@ -54,6 +53,7 @@ #include #include "DesktopServices.h" +#include "PSaveFile.h" #include "StringUtils.h" #if defined Q_OS_WIN32 @@ -191,8 +191,8 @@ void ensureExists(const QDir& dir) void write(const QString& filename, const QByteArray& data) { ensureExists(QFileInfo(filename).dir()); - QSaveFile file(filename); - if (!file.open(QSaveFile::WriteOnly)) { + PSaveFile file(filename); + if (!file.open(PSaveFile::WriteOnly)) { throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString()); } if (data.size() != file.write(data)) { @@ -213,8 +213,8 @@ void appendSafe(const QString& filename, const QByteArray& data) buffer = QByteArray(); } buffer.append(data); - QSaveFile file(filename); - if (!file.open(QSaveFile::WriteOnly)) { + PSaveFile file(filename); + if (!file.open(PSaveFile::WriteOnly)) { throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString()); } if (buffer.size() != file.write(buffer)) { @@ -341,7 +341,7 @@ bool copy::operator()(const QString& offset, bool dryRun) opt |= copy_opts::overwrite_existing; // Function that'll do the actual copying - auto copy_file = [&](QString src_path, QString relative_dst_path) { + auto copy_file = [this, dryRun, src, dst, opt, &err](QString src_path, QString relative_dst_path) { if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist)) return; @@ -428,7 +428,7 @@ void create_link::make_link_list(const QString& offset) m_recursive = true; // Function that'll do the actual linking - auto link_file = [&](QString src_path, QString relative_dst_path) { + auto link_file = [this, dst](QString src_path, QString relative_dst_path) { if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist)) { qDebug() << "path" << relative_dst_path << "in black list or not in whitelist"; return; @@ -523,7 +523,7 @@ void create_link::runPrivileged(const QString& offset) QString serverName = BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink_server" + StringUtils::getRandomAlphaNumeric(); - connect(&m_linkServer, &QLocalServer::newConnection, this, [&]() { + connect(&m_linkServer, &QLocalServer::newConnection, this, [this, &gotResults]() { qDebug() << "Client connected, sending out pairs"; // construct block of data to send QByteArray block; @@ -605,7 +605,7 @@ void create_link::runPrivileged(const QString& offset) } ExternalLinkFileProcess* linkFileProcess = new ExternalLinkFileProcess(serverName, m_useHardLinks, this); - connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [&]() { emit finishedPrivileged(gotResults); }); + connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [this, gotResults]() { emit finishedPrivileged(gotResults); }); connect(linkFileProcess, &ExternalLinkFileProcess::finished, linkFileProcess, &QObject::deleteLater); linkFileProcess->start(); @@ -971,8 +971,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri if (!args.empty()) argstring = " \"" + args.join("\" \"") + "\""; - stream << "#!/bin/bash" - << "\n"; + stream << "#!/bin/bash" << "\n"; stream << "\"" << target << "\" " << argstring << "\n"; stream.flush(); @@ -1016,12 +1015,9 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri if (!args.empty()) argstring = " '" + args.join("' '") + "'"; - stream << "[Desktop Entry]" - << "\n"; - stream << "Type=Application" - << "\n"; - stream << "Categories=Game;ActionGame;AdventureGame;Simulation" - << "\n"; + stream << "[Desktop Entry]" << "\n"; + stream << "Type=Application" << "\n"; + stream << "Categories=Game;ActionGame;AdventureGame;Simulation" << "\n"; stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n"; stream << "Name=" << name.toLocal8Bit() << "\n"; if (!icon.isEmpty()) { @@ -1299,7 +1295,7 @@ bool clone::operator()(const QString& offset, bool dryRun) std::error_code err; // Function that'll do the actual cloneing - auto cloneFile = [&](QString src_path, QString relative_dst_path) { + auto cloneFile = [this, dryRun, dst, &err](QString src_path, QString relative_dst_path) { if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist)) return; diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index 0220a4144..d335b11c4 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -91,7 +91,7 @@ void InstanceCopyTask::executeTask() QEventLoop loop; bool got_priv_results = false; - connect(&folderLink, &FS::create_link::finishedPrivileged, this, [&](bool gotResults) { + connect(&folderLink, &FS::create_link::finishedPrivileged, this, [&got_priv_results, &loop](bool gotResults) { if (!gotResults) { qDebug() << "Privileged run exited without results!"; } diff --git a/launcher/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp index 3e7b3142f..bd3514798 100644 --- a/launcher/InstanceCreationTask.cpp +++ b/launcher/InstanceCreationTask.cpp @@ -61,6 +61,6 @@ void InstanceCreationTask::executeTask() return; } } - - emitSucceeded(); + if (!m_abort) + emitSucceeded(); } diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 57cc77527..71630656d 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -69,9 +69,11 @@ bool InstanceImportTask::abort() if (!canAbort()) return false; - if (task) - task->abort(); - return Task::abort(); + bool wasAborted = false; + if (m_task) + wasAborted = m_task->abort(); + Task::abort(); + return wasAborted; } void InstanceImportTask::executeTask() @@ -104,7 +106,7 @@ void InstanceImportTask::downloadFromUrl() connect(filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propagateStepProgress); connect(filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::emitFailed); connect(filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::emitAborted); - task.reset(filesNetJob); + m_task.reset(filesNetJob); filesNetJob->start(); } @@ -193,7 +195,7 @@ void InstanceImportTask::processZipPack() stepProgress(*progressStep); }); - connect(zipTask.get(), &Task::succeeded, this, &InstanceImportTask::extractFinished); + connect(zipTask.get(), &Task::succeeded, this, &InstanceImportTask::extractFinished, Qt::QueuedConnection); connect(zipTask.get(), &Task::aborted, this, &InstanceImportTask::emitAborted); connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) { progressStep->state = TaskStepState::Failed; @@ -210,12 +212,13 @@ void InstanceImportTask::processZipPack() progressStep->status = status; stepProgress(*progressStep); }); - task.reset(zipTask); + m_task.reset(zipTask); zipTask->start(); } void InstanceImportTask::extractFinished() { + setAbortable(false); QDir extractDir(m_stagingPath); qDebug() << "Fixing permissions for extracted pack files..."; @@ -289,8 +292,11 @@ void InstanceImportTask::processFlame() inst_creation_task->setGroup(m_instGroup); inst_creation_task->setConfirmUpdate(shouldConfirmUpdate()); - connect(inst_creation_task.get(), &Task::succeeded, this, [this, inst_creation_task] { - setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID()); + auto weak = inst_creation_task.toWeakRef(); + connect(inst_creation_task.get(), &Task::succeeded, this, [this, weak] { + if (auto sp = weak.lock()) { + setOverride(sp->shouldOverride(), sp->originalInstanceID()); + } emitSucceeded(); }); connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed); @@ -299,11 +305,12 @@ void InstanceImportTask::processFlame() connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus); connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails); - connect(this, &Task::aborted, inst_creation_task.get(), &InstanceCreationTask::abort); connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort); connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable); - inst_creation_task->start(); + m_task.reset(inst_creation_task); + setAbortable(true); + m_task->start(); } void InstanceImportTask::processTechnic() @@ -350,7 +357,7 @@ void InstanceImportTask::processMultiMC() void InstanceImportTask::processModrinth() { - ModrinthCreationTask* inst_creation_task = nullptr; + shared_qobject_ptr inst_creation_task = nullptr; if (!m_extra_info.isEmpty()) { auto pack_id_it = m_extra_info.constFind("pack_id"); Q_ASSERT(pack_id_it != m_extra_info.constEnd()); @@ -367,7 +374,7 @@ void InstanceImportTask::processModrinth() original_instance_id = original_instance_id_it.value(); inst_creation_task = - new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id); + makeShared(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id); } else { QString pack_id; if (!m_sourceUrl.isEmpty()) { @@ -376,7 +383,7 @@ void InstanceImportTask::processModrinth() } // FIXME: Find a way to get the ID in directly imported ZIPs - inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id); + inst_creation_task = makeShared(m_stagingPath, m_globalSettings, m_parent, pack_id); } inst_creation_task->setName(*this); @@ -384,20 +391,23 @@ void InstanceImportTask::processModrinth() inst_creation_task->setGroup(m_instGroup); inst_creation_task->setConfirmUpdate(shouldConfirmUpdate()); - connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] { - setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID()); + auto weak = inst_creation_task.toWeakRef(); + connect(inst_creation_task.get(), &Task::succeeded, this, [this, weak] { + if (auto sp = weak.lock()) { + setOverride(sp->shouldOverride(), sp->originalInstanceID()); + } emitSucceeded(); }); - connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed); - connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress); - connect(inst_creation_task, &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress); - connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus); - connect(inst_creation_task, &Task::details, this, &InstanceImportTask::setDetails); - connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater); + connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed); + connect(inst_creation_task.get(), &Task::progress, this, &InstanceImportTask::setProgress); + connect(inst_creation_task.get(), &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress); + connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus); + connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails); - connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort); - connect(inst_creation_task, &Task::aborted, this, &Task::abort); - connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortable); + connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort); + connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable); - inst_creation_task->start(); + m_task.reset(inst_creation_task); + setAbortable(true); + m_task->start(); } diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h index cf86af4ea..8884e0801 100644 --- a/launcher/InstanceImportTask.h +++ b/launcher/InstanceImportTask.h @@ -40,16 +40,13 @@ #include #include "InstanceTask.h" -#include -#include - class QuaZip; class InstanceImportTask : public InstanceTask { Q_OBJECT public: explicit InstanceImportTask(const QUrl& sourceUrl, QWidget* parent = nullptr, QMap&& extra_info = {}); - + virtual ~InstanceImportTask() = default; bool abort() override; protected: @@ -70,7 +67,7 @@ class InstanceImportTask : public InstanceTask { private: /* data */ QUrl m_sourceUrl; QString m_archivePath; - Task::Ptr task; + Task::Ptr m_task; enum class ModpackType { Unknown, MultiMC, diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index e1fa755dd..918fa1073 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -487,7 +487,7 @@ InstanceList::InstListError InstanceList::loadList() int front_bookmark = -1; int back_bookmark = -1; int currentItem = -1; - auto removeNow = [&]() { + auto removeNow = [this, &front_bookmark, &back_bookmark, ¤tItem]() { beginRemoveRows(QModelIndex(), front_bookmark, back_bookmark); m_instances.erase(m_instances.begin() + front_bookmark, m_instances.begin() + back_bookmark + 1); endRemoveRows(); diff --git a/launcher/InstancePageProvider.h b/launcher/InstancePageProvider.h index 174041f89..1d7c193f8 100644 --- a/launcher/InstancePageProvider.h +++ b/launcher/InstancePageProvider.h @@ -43,7 +43,7 @@ class InstancePageProvider : protected QObject, public BasePageProvider { values.append(new ServersPage(onesix)); // values.append(new GameOptionsPage(onesix.get())); values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots"))); - values.append(new InstanceSettingsPage(onesix.get())); + values.append(new InstanceSettingsPage(onesix)); auto logMatcher = inst->getLogFileMatcher(); if (logMatcher) { values.append(new OtherLogsPage(inst->getLogFileRoot(), logMatcher)); diff --git a/launcher/JavaCommon.cpp b/launcher/JavaCommon.cpp index 3cbf9f9d5..188edb943 100644 --- a/launcher/JavaCommon.cpp +++ b/launcher/JavaCommon.cpp @@ -116,7 +116,7 @@ void JavaCommon::TestCheck::run() emit finished(); return; } - checker.reset(new JavaChecker(m_path, "", 0, 0, 0, 0, this)); + checker.reset(new JavaChecker(m_path, "", 0, 0, 0, 0)); connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinished); checker->start(); } @@ -128,7 +128,7 @@ void JavaCommon::TestCheck::checkFinished(const JavaChecker::Result& result) emit finished(); return; } - checker.reset(new JavaChecker(m_path, m_args, m_maxMem, m_maxMem, result.javaVersion.requiresPermGen() ? m_permGen : 0, 0, this)); + checker.reset(new JavaChecker(m_path, m_args, m_maxMem, m_maxMem, result.javaVersion.requiresPermGen() ? m_permGen : 0, 0)); connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinishedWithArgs); checker->start(); } diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 687da1322..0aded4a95 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -43,6 +43,7 @@ #include "ui/InstanceWindow.h" #include "ui/MainWindow.h" #include "ui/dialogs/CustomMessageBox.h" +#include "ui/dialogs/MSALoginDialog.h" #include "ui/dialogs/ProfileSelectDialog.h" #include "ui/dialogs/ProfileSetupDialog.h" #include "ui/dialogs/ProgressDialog.h" @@ -61,7 +62,7 @@ #include "launch/steps/TextPrint.h" #include "tasks/Task.h" -LaunchController::LaunchController(QObject* parent) : Task(parent) {} +LaunchController::LaunchController() : Task() {} void LaunchController::executeTask() { @@ -234,10 +235,15 @@ void LaunchController::login() if (!m_session->wants_online) { // we ask the user for a player name bool ok = false; - auto name = askOfflineName(m_session->player_name, m_session->demo, ok); - if (!ok) { - tryagain = false; - break; + QString name; + if (m_offlineName.isEmpty()) { + name = askOfflineName(m_session->player_name, m_session->demo, ok); + if (!ok) { + tryagain = false; + break; + } + } else { + name = m_offlineName; } m_session->MakeOffline(name); // offline flavored game from here :3 @@ -287,10 +293,8 @@ void LaunchController::login() continue; } 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, - QMessageBox::StandardButton::Ok); - emitFailed(errorString); + if (reauthenticateCurrentAccount()) + continue; return; } case AccountState::Disabled: { @@ -314,6 +318,33 @@ void LaunchController::login() emitFailed(tr("Failed to launch.")); } +bool LaunchController::reauthenticateCurrentAccount() +{ + auto button = + QMessageBox::warning(m_parentWidget, tr("Account refresh failed"), + tr("The account has expired and needs to be reauthenticated. Do you want to reauthenticate this account?"), + QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, QMessageBox::StandardButton::Yes); + if (button == QMessageBox::StandardButton::Yes) { + auto accounts = APPLICATION->accounts(); + bool isDefault = accounts->defaultAccount() == m_accountToUse; + accounts->removeAccount(accounts->index(accounts->findAccountByProfileId(m_accountToUse->profileId()))); + if (m_accountToUse->accountType() == AccountType::MSA) { + auto newAccount = MSALoginDialog::newAccount(m_parentWidget); + accounts->addAccount(newAccount); + if (isDefault) { + accounts->setDefaultAccount(newAccount); + } + m_accountToUse = nullptr; + decideAccount(); + return true; + } + emitFailed(tr("Account expired and re-login attempt failed")); + } else { + emitFailed(tr("The account has expired and needs to be reauthenticated")); + } + return false; +} + void LaunchController::launchInstance() { Q_ASSERT_X(m_instance != NULL, "launchInstance", "instance is NULL"); diff --git a/launcher/LaunchController.h b/launcher/LaunchController.h index 6e2a94258..7e6a27d91 100644 --- a/launcher/LaunchController.h +++ b/launcher/LaunchController.h @@ -47,7 +47,7 @@ class LaunchController : public Task { public: void executeTask() override; - LaunchController(QObject* parent = nullptr); + LaunchController(); virtual ~LaunchController() = default; void setInstance(InstancePtr instance) { m_instance = instance; } @@ -56,6 +56,8 @@ class LaunchController : public Task { void setOnline(bool online) { m_online = online; } + void setOfflineName(const QString& offlineName) { m_offlineName = offlineName; } + void setDemo(bool demo) { m_demo = demo; } void setProfiler(BaseProfilerFactory* profiler) { m_profiler = profiler; } @@ -76,6 +78,7 @@ class LaunchController : public Task { void decideAccount(); bool askPlayDemo(); QString askOfflineName(QString playerName, bool demo, bool& ok); + bool reauthenticateCurrentAccount(); private slots: void readyForLaunch(); @@ -87,6 +90,7 @@ class LaunchController : public Task { private: BaseProfilerFactory* m_profiler = nullptr; bool m_online = true; + QString m_offlineName; bool m_demo = false; InstancePtr m_instance; QWidget* m_parentWidget = nullptr; diff --git a/launcher/Launcher.in b/launcher/Launcher.in index 1a23f2555..706d7022b 100755 --- a/launcher/Launcher.in +++ b/launcher/Launcher.in @@ -39,8 +39,16 @@ if [ "x$DEPS_LIST" = "x" ]; then # Just to be sure... chmod +x "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" + ARGS=("${LAUNCHER_DIR}/${LAUNCHER_NAME}" "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}") + + if [ -f portable.txt ]; then + ARGS+=("-d" "${LAUNCHER_DIR}") + fi + + ARGS+=("$@") + # Run the launcher - exec -a "${LAUNCHER_DIR}/${LAUNCHER_NAME}" "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" -d "${LAUNCHER_DIR}" "$@" + exec -a "${ARGS[@]}" # Run the launcher in valgrind # valgrind --log-file="valgrind.log" --leak-check=full --track-origins=yes "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" -d "${LAUNCHER_DIR}" "$@" diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index dcf3d566f..b38aca17a 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -378,7 +378,7 @@ std::optional extractDir(QString fileCompressed, QString dir) if (fileInfo.size() == 22) { return QStringList(); } - qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError(); + qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError(); ; return std::nullopt; } @@ -395,7 +395,7 @@ std::optional extractDir(QString fileCompressed, QString subdir, QS if (fileInfo.size() == 22) { return QStringList(); } - qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError(); + qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError(); ; return std::nullopt; } @@ -412,7 +412,7 @@ bool extractFile(QString fileCompressed, QString file, QString target) if (fileInfo.size() == 22) { return true; } - qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError(); + qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError(); return false; } return extractRelFile(&zip, file, target); @@ -577,7 +577,7 @@ auto ExtractZipTask::extractZip() -> ZipResult auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(m_subdirectory.size())); auto original_name = relative_file_name; - setStatus("Unziping: " + relative_file_name); + setStatus("Unpacking: " + relative_file_name); // Fix subdirs/files ending with a / getting transformed into absolute paths if (relative_file_name.startsWith('/')) diff --git a/launcher/PSaveFile.h b/launcher/PSaveFile.h new file mode 100644 index 000000000..ba6154ad8 --- /dev/null +++ b/launcher/PSaveFile.h @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023-2024 Trial97 + * + * 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 . + */ +#pragma once + +#include +#include +#include "Application.h" + +#if defined(LAUNCHER_APPLICATION) + +/* PSaveFile + * A class that mimics QSaveFile for Windows. + * + * When reading resources, we need to avoid accessing temporary files + * generated by QSaveFile. If we start reading such a file, we may + * inadvertently keep it open while QSaveFile is trying to remove it, + * or we might detect the file just before it is removed, leading to + * race conditions and errors. + * + * Unfortunately, QSaveFile doesn't provide a way to retrieve the + * temporary file name or to set a specific template for the temporary + * file name it uses. By default, QSaveFile appends a `.XXXXXX` suffix + * to the original file name, where the `XXXXXX` part is dynamically + * generated to ensure uniqueness. + * + * This class acts like a lock by adding and removing the target file + * name into/from a global string set, helping to manage access to + * files during critical operations. + * + * Note: Please do not use the `setFileName` function directly, as it + * is not virtual and cannot be overridden. + */ +class PSaveFile : public QSaveFile { + public: + PSaveFile(const QString& name) : QSaveFile(name) { addPath(name); } + PSaveFile(const QString& name, QObject* parent) : QSaveFile(name, parent) { addPath(name); } + virtual ~PSaveFile() + { + if (auto app = APPLICATION_DYN) { + app->removeQSavePath(m_absoluteFilePath); + } + } + + private: + void addPath(const QString& path) + { + m_absoluteFilePath = QFileInfo(path).absoluteFilePath() + "."; // add dot for tmp files only + if (auto app = APPLICATION_DYN) { + app->addQSavePath(m_absoluteFilePath); + } + } + QString m_absoluteFilePath; +}; +#else +#define PSaveFile QSaveFile +#endif \ No newline at end of file diff --git a/launcher/QObjectPtr.h b/launcher/QObjectPtr.h index a1c64b433..88c17c0b2 100644 --- a/launcher/QObjectPtr.h +++ b/launcher/QObjectPtr.h @@ -33,7 +33,7 @@ class shared_qobject_ptr : public QSharedPointer { {} void reset() { QSharedPointer::reset(); } - void reset(T*&& other) + void reset(T* other) { shared_qobject_ptr t(other); this->swap(t); diff --git a/launcher/ResourceDownloadTask.cpp b/launcher/ResourceDownloadTask.cpp index 6f5b9a189..0fe082ac4 100644 --- a/launcher/ResourceDownloadTask.cpp +++ b/launcher/ResourceDownloadTask.cpp @@ -35,9 +35,9 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack, QString custom_target_folder) : m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs), m_custom_target_folder(custom_target_folder) { - if (auto model = dynamic_cast(m_pack_model.get()); model && is_indexed) { - m_update_task.reset(new LocalModUpdateTask(model->indexDir(), *m_pack, m_pack_version)); - connect(m_update_task.get(), &LocalModUpdateTask::hasOldMod, this, &ResourceDownloadTask::hasOldResource); + if (is_indexed) { + m_update_task.reset(new LocalResourceUpdateTask(m_pack_model->indexDir(), *m_pack, m_pack_version)); + connect(m_update_task.get(), &LocalResourceUpdateTask::hasOldResource, this, &ResourceDownloadTask::hasOldResource); addTask(m_update_task); } @@ -91,12 +91,8 @@ void ResourceDownloadTask::downloadSucceeded() m_filesNetJob.reset(); auto name = std::get<0>(to_delete); auto filename = std::get<1>(to_delete); - if (!name.isEmpty() && filename != m_pack_version.fileName) { - if (auto model = dynamic_cast(m_pack_model.get()); model) - model->uninstallMod(filename, true); - else - m_pack_model->uninstallResource(filename); - } + if (!name.isEmpty() && filename != m_pack_version.fileName) + m_pack_model->uninstallResource(filename, true); } void ResourceDownloadTask::downloadFailed(QString reason) diff --git a/launcher/ResourceDownloadTask.h b/launcher/ResourceDownloadTask.h index f686e819a..a10e0ac2c 100644 --- a/launcher/ResourceDownloadTask.h +++ b/launcher/ResourceDownloadTask.h @@ -22,7 +22,7 @@ #include "net/NetJob.h" #include "tasks/SequentialTask.h" -#include "minecraft/mod/tasks/LocalModUpdateTask.h" +#include "minecraft/mod/tasks/LocalResourceUpdateTask.h" #include "modplatform/ModIndex.h" class ResourceFolderModel; @@ -50,7 +50,7 @@ class ResourceDownloadTask : public SequentialTask { QString m_custom_target_folder; NetJob::Ptr m_filesNetJob; - LocalModUpdateTask::Ptr m_update_task; + LocalResourceUpdateTask::Ptr m_update_task; void downloadProgressChanged(qint64 current, qint64 total); void downloadFailed(QString reason); diff --git a/launcher/SysInfo.cpp b/launcher/SysInfo.cpp index 0dfa74de7..cfcf63805 100644 --- a/launcher/SysInfo.cpp +++ b/launcher/SysInfo.cpp @@ -81,9 +81,9 @@ QString getSupportedJavaArchitecture() if (arch == "arm64") return "mac-os-arm64"; if (arch.contains("64")) - return "mac-os-64"; + return "mac-os-x64"; if (arch.contains("86")) - return "mac-os-86"; + return "mac-os-x86"; // Unknown, maybe something new, appending arch return "mac-os-" + arch; } else if (sys == "linux") { diff --git a/launcher/Version.cpp b/launcher/Version.cpp index 2edb17e72..03a16e8a0 100644 --- a/launcher/Version.cpp +++ b/launcher/Version.cpp @@ -79,7 +79,7 @@ void Version::parse() if (m_string.isEmpty()) return; - auto classChange = [&](QChar lastChar, QChar currentChar) { + auto classChange = [¤tSection](QChar lastChar, QChar currentChar) { if (lastChar.isNull()) return false; if (lastChar.isDigit() != currentChar.isDigit()) diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp index bdf173ebc..b641b41d5 100644 --- a/launcher/filelink/FileLink.cpp +++ b/launcher/filelink/FileLink.cpp @@ -104,11 +104,11 @@ void FileLinkApp::joinServer(QString server) in.setDevice(&socket); - connect(&socket, &QLocalSocket::connected, this, [&]() { qDebug() << "connected to server"; }); + connect(&socket, &QLocalSocket::connected, this, []() { qDebug() << "connected to server"; }); connect(&socket, &QLocalSocket::readyRead, this, &FileLinkApp::readPathPairs); - connect(&socket, &QLocalSocket::errorOccurred, this, [&](QLocalSocket::LocalSocketError socketError) { + connect(&socket, &QLocalSocket::errorOccurred, this, [this](QLocalSocket::LocalSocketError socketError) { m_status = Failed; switch (socketError) { case QLocalSocket::ServerNotFoundError: @@ -132,7 +132,7 @@ void FileLinkApp::joinServer(QString server) } }); - connect(&socket, &QLocalSocket::disconnected, this, [&]() { + connect(&socket, &QLocalSocket::disconnected, this, [this]() { qDebug() << "disconnected from server, should exit"; m_status = Succeeded; exit(); diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp index e4157ea2d..f4022e0fb 100644 --- a/launcher/icons/IconList.cpp +++ b/launcher/icons/IconList.cpp @@ -47,24 +47,24 @@ #define MAX_SIZE 1024 -IconList::IconList(const QStringList& builtinPaths, QString path, QObject* parent) : QAbstractListModel(parent) +IconList::IconList(const QStringList& builtinPaths, const QString& path, QObject* parent) : QAbstractListModel(parent) { QSet builtinNames; // add builtin icons - for (auto& builtinPath : builtinPaths) { - QDir instance_icons(builtinPath); - auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name); - for (auto file_info : file_info_list) { - builtinNames.insert(file_info.completeBaseName()); + for (const auto& builtinPath : builtinPaths) { + QDir instanceIcons(builtinPath); + auto fileInfoList = instanceIcons.entryInfoList(QDir::Files, QDir::Name); + for (const auto& fileInfo : fileInfoList) { + builtinNames.insert(fileInfo.baseName()); } } - for (auto& builtinName : builtinNames) { + for (const auto& builtinName : builtinNames) { addThemeIcon(builtinName); } m_watcher.reset(new QFileSystemWatcher()); - is_watching = false; + m_isWatching = false; connect(m_watcher.get(), &QFileSystemWatcher::directoryChanged, this, &IconList::directoryChanged); connect(m_watcher.get(), &QFileSystemWatcher::fileChanged, this, &IconList::fileChanged); @@ -77,91 +77,131 @@ IconList::IconList(const QStringList& builtinPaths, QString path, QObject* paren void IconList::sortIconList() { qDebug() << "Sorting icon list..."; - std::sort(icons.begin(), icons.end(), [](const MMCIcon& a, const MMCIcon& b) { return a.m_key.localeAwareCompare(b.m_key) < 0; }); + std::sort(m_icons.begin(), m_icons.end(), [](const MMCIcon& a, const MMCIcon& b) { + bool aIsSubdir = a.m_key.contains(QDir::separator()); + bool bIsSubdir = b.m_key.contains(QDir::separator()); + if (aIsSubdir != bIsSubdir) { + return !aIsSubdir; // root-level icons come first + } + return a.m_key.localeAwareCompare(b.m_key) < 0; + }); reindex(); } +// Helper function to add directories recursively +bool IconList::addPathRecursively(const QString& path) +{ + QDir dir(path); + if (!dir.exists()) + return false; + + // Add the directory itself + bool watching = m_watcher->addPath(path); + + // Add all subdirectories + QFileInfoList entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + for (const QFileInfo& entry : entries) { + if (addPathRecursively(entry.absoluteFilePath())) { + watching = true; + } + } + return watching; +} + +QStringList IconList::getIconFilePaths() const +{ + QStringList iconFiles{}; + QStringList directories{ m_dir.absolutePath() }; + while (!directories.isEmpty()) { + QString first = directories.takeFirst(); + QDir dir(first); + for (QFileInfo& fileInfo : dir.entryInfoList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot, QDir::Name)) { + if (fileInfo.isDir()) + directories.push_back(fileInfo.absoluteFilePath()); + else + iconFiles.push_back(fileInfo.absoluteFilePath()); + } + } + return iconFiles; +} + +QString formatName(const QDir& iconsDir, const QFileInfo& iconFile) +{ + if (iconFile.dir() == iconsDir) + return iconFile.baseName(); + + constexpr auto delimiter = " » "; + QString relativePathWithoutExtension = iconsDir.relativeFilePath(iconFile.dir().path()) + QDir::separator() + iconFile.baseName(); + return relativePathWithoutExtension.replace(QDir::separator(), delimiter); +} + +/// Split into a separate function because the preprocessing impedes readability +QSet toStringSet(const QList& list) +{ +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + QSet set(list.begin(), list.end()); +#else + QSet set = list.toSet(); +#endif + return set; +} + void IconList::directoryChanged(const QString& path) { - QDir new_dir(path); - if (m_dir.absolutePath() != new_dir.absolutePath()) { - m_dir.setPath(path); + QDir newDir(path); + if (m_dir.absolutePath() != newDir.absolutePath()) { + if (!path.startsWith(m_dir.absolutePath())) + m_dir.setPath(path); m_dir.refresh(); - if (is_watching) + if (m_isWatching) stopWatching(); startWatching(); } - if (!m_dir.exists()) - if (!FS::ensureFolderPathExists(m_dir.absolutePath())) - return; + if (!m_dir.exists() && !FS::ensureFolderPathExists(m_dir.absolutePath())) + return; m_dir.refresh(); - auto new_list = m_dir.entryList(QDir::Files, QDir::Name); - for (auto it = new_list.begin(); it != new_list.end(); it++) { - QString& foo = (*it); - foo = m_dir.filePath(foo); - } -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) - QSet new_set(new_list.begin(), new_list.end()); -#else - auto new_set = new_list.toSet(); -#endif - QList current_list; - for (auto& it : icons) { + const QStringList newFileNamesList = getIconFilePaths(); + const QSet newSet = toStringSet(newFileNamesList); + QSet currentSet; + for (const MMCIcon& it : m_icons) { if (!it.has(IconType::FileBased)) continue; - current_list.push_back(it.m_images[IconType::FileBased].filename); + currentSet.insert(it.m_images[IconType::FileBased].filename); } -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) - QSet current_set(current_list.begin(), current_list.end()); -#else - QSet current_set = current_list.toSet(); -#endif + QSet toRemove = currentSet - newSet; + QSet toAdd = newSet - currentSet; - QSet to_remove = current_set; - to_remove -= new_set; - - QSet to_add = new_set; - to_add -= current_set; - - for (auto remove : to_remove) { - qDebug() << "Removing " << remove; - QFileInfo rmfile(remove); - QString key = rmfile.completeBaseName(); - - QString suffix = rmfile.suffix(); - // The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well - if (!IconUtils::isIconSuffix(suffix)) - key = rmfile.fileName(); + for (const QString& removedPath : toRemove) { + qDebug() << "Removing icon " << removedPath; + QFileInfo removedFile(removedPath); + QString key = m_dir.relativeFilePath(removedFile.absoluteFilePath()); int idx = getIconIndex(key); if (idx == -1) continue; - icons[idx].remove(IconType::FileBased); - if (icons[idx].type() == IconType::ToBeDeleted) { + m_icons[idx].remove(FileBased); + if (m_icons[idx].type() == ToBeDeleted) { beginRemoveRows(QModelIndex(), idx, idx); - icons.remove(idx); + m_icons.remove(idx); reindex(); endRemoveRows(); } else { dataChanged(index(idx), index(idx)); } - m_watcher->removePath(remove); + m_watcher->removePath(removedPath); emit iconUpdated(key); } - for (auto add : to_add) { - qDebug() << "Adding " << add; + for (const QString& addedPath : toAdd) { + qDebug() << "Adding icon " << addedPath; - QFileInfo addfile(add); - QString key = addfile.completeBaseName(); + QFileInfo addfile(addedPath); + QString relativePath = m_dir.relativeFilePath(addfile.absoluteFilePath()); + QString key = QFileInfo(relativePath).completeBaseName(); + QString name = formatName(m_dir, addfile); - QString suffix = addfile.suffix(); - // The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well - if (!IconUtils::isIconSuffix(suffix)) - key = addfile.fileName(); - - if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased)) { - m_watcher->addPath(add); + if (addIcon(key, name, addfile.filePath(), IconType::FileBased)) { + m_watcher->addPath(addedPath); emit iconUpdated(key); } } @@ -171,24 +211,24 @@ void IconList::directoryChanged(const QString& path) void IconList::fileChanged(const QString& path) { - qDebug() << "Checking " << path; + qDebug() << "Checking icon " << path; QFileInfo checkfile(path); if (!checkfile.exists()) return; - QString key = checkfile.completeBaseName(); + QString key = m_dir.relativeFilePath(checkfile.absoluteFilePath()); int idx = getIconIndex(key); if (idx == -1) return; QIcon icon(path); - if (!icon.availableSizes().size()) + if (icon.availableSizes().empty()) return; - icons[idx].m_images[IconType::FileBased].icon = icon; + m_icons[idx].m_images[IconType::FileBased].icon = icon; dataChanged(index(idx), index(idx)); emit iconUpdated(key); } -void IconList::SettingChanged(const Setting& setting, QVariant value) +void IconList::SettingChanged(const Setting& setting, const QVariant& value) { if (setting.id() != "IconsDir") return; @@ -200,8 +240,8 @@ void IconList::startWatching() { auto abs_path = m_dir.absolutePath(); FS::ensureFolderPathExists(abs_path); - is_watching = m_watcher->addPath(abs_path); - if (is_watching) { + m_isWatching = addPathRecursively(abs_path); + if (m_isWatching) { qDebug() << "Started watching " << abs_path; } else { qDebug() << "Failed to start watching " << abs_path; @@ -212,7 +252,7 @@ void IconList::stopWatching() { m_watcher->removePaths(m_watcher->files()); m_watcher->removePaths(m_watcher->directories()); - is_watching = false; + m_isWatching = false; } QStringList IconList::mimeTypes() const @@ -242,7 +282,7 @@ bool IconList::dropMimeData(const QMimeData* data, if (data->hasUrls()) { auto urls = data->urls(); QStringList iconFiles; - for (auto url : urls) { + for (const auto& url : urls) { // only local files may be dropped... if (!url.isLocalFile()) continue; @@ -263,33 +303,33 @@ Qt::ItemFlags IconList::flags(const QModelIndex& index) const QVariant IconList::data(const QModelIndex& index, int role) const { if (!index.isValid()) - return QVariant(); + return {}; int row = index.row(); - if (row < 0 || row >= icons.size()) - return QVariant(); + if (row < 0 || row >= m_icons.size()) + return {}; switch (role) { case Qt::DecorationRole: - return icons[row].icon(); + return m_icons[row].icon(); case Qt::DisplayRole: - return icons[row].name(); + return m_icons[row].name(); case Qt::UserRole: - return icons[row].m_key; + return m_icons[row].m_key; default: - return QVariant(); + return {}; } } int IconList::rowCount(const QModelIndex& parent) const { - return parent.isValid() ? 0 : icons.size(); + return parent.isValid() ? 0 : m_icons.size(); } void IconList::installIcons(const QStringList& iconFiles) { - for (QString file : iconFiles) + for (const QString& file : iconFiles) installIcon(file, {}); } @@ -312,12 +352,13 @@ bool IconList::iconFileExists(const QString& key) const return iconEntry && iconEntry->has(IconType::FileBased); } +/// Returns the icon with the given key or nullptr if it doesn't exist. const MMCIcon* IconList::icon(const QString& key) const { int iconIdx = getIconIndex(key); if (iconIdx == -1) return nullptr; - return &icons[iconIdx]; + return &m_icons[iconIdx]; } bool IconList::deleteIcon(const QString& key) @@ -332,22 +373,22 @@ bool IconList::trashIcon(const QString& key) bool IconList::addThemeIcon(const QString& key) { - auto iter = name_index.find(key); - if (iter != name_index.end()) { - auto& oldOne = icons[*iter]; + auto iter = m_nameIndex.find(key); + if (iter != m_nameIndex.end()) { + auto& oldOne = m_icons[*iter]; oldOne.replace(Builtin, key); dataChanged(index(*iter), index(*iter)); return true; } // add a new icon - beginInsertRows(QModelIndex(), icons.size(), icons.size()); + beginInsertRows(QModelIndex(), m_icons.size(), m_icons.size()); { MMCIcon mmc_icon; mmc_icon.m_name = key; mmc_icon.m_key = key; mmc_icon.replace(Builtin, key); - icons.push_back(mmc_icon); - name_index[key] = icons.size() - 1; + m_icons.push_back(mmc_icon); + m_nameIndex[key] = m_icons.size() - 1; } endInsertRows(); return true; @@ -359,22 +400,22 @@ bool IconList::addIcon(const QString& key, const QString& name, const QString& p QIcon icon(path); if (icon.isNull()) return false; - auto iter = name_index.find(key); - if (iter != name_index.end()) { - auto& oldOne = icons[*iter]; + auto iter = m_nameIndex.find(key); + if (iter != m_nameIndex.end()) { + auto& oldOne = m_icons[*iter]; oldOne.replace(type, icon, path); dataChanged(index(*iter), index(*iter)); return true; } // add a new icon - beginInsertRows(QModelIndex(), icons.size(), icons.size()); + beginInsertRows(QModelIndex(), m_icons.size(), m_icons.size()); { MMCIcon mmc_icon; mmc_icon.m_name = name; mmc_icon.m_key = key; mmc_icon.replace(type, icon, path); - icons.push_back(mmc_icon); - name_index[key] = icons.size() - 1; + m_icons.push_back(mmc_icon); + m_nameIndex[key] = m_icons.size() - 1; } endInsertRows(); return true; @@ -389,33 +430,32 @@ void IconList::saveIcon(const QString& key, const QString& path, const char* for void IconList::reindex() { - name_index.clear(); - int i = 0; - for (auto& iter : icons) { - name_index[iter.m_key] = i; - i++; + m_nameIndex.clear(); + for (int i = 0; i < m_icons.size(); i++) { + m_nameIndex[m_icons[i].m_key] = i; + emit iconUpdated(m_icons[i].m_key); // prevents incorrect indices with proxy model } } QIcon IconList::getIcon(const QString& key) const { - int icon_index = getIconIndex(key); + int iconIndex = getIconIndex(key); - if (icon_index != -1) - return icons[icon_index].icon(); + if (iconIndex != -1) + return m_icons[iconIndex].icon(); - // Fallback for icons that don't exist. - icon_index = getIconIndex("grass"); + // Fallback for icons that don't exist.b + iconIndex = getIconIndex("grass"); - if (icon_index != -1) - return icons[icon_index].icon(); - return QIcon(); + if (iconIndex != -1) + return m_icons[iconIndex].icon(); + return {}; } int IconList::getIconIndex(const QString& key) const { - auto iter = name_index.find(key == "default" ? "grass" : key); - if (iter != name_index.end()) + auto iter = m_nameIndex.find(key == "default" ? "grass" : key); + if (iter != m_nameIndex.end()) return *iter; return -1; @@ -425,3 +465,15 @@ QString IconList::getDirectory() const { return m_dir.absolutePath(); } + +/// Returns the directory of the icon with the given key or the default directory if it's a builtin icon. +QString IconList::iconDirectory(const QString& key) const +{ + for (const auto& mmcIcon : m_icons) { + if (mmcIcon.m_key == key && mmcIcon.has(IconType::FileBased)) { + QFileInfo iconFile(mmcIcon.getFilePath()); + return iconFile.dir().path(); + } + } + return getDirectory(); +} diff --git a/launcher/icons/IconList.h b/launcher/icons/IconList.h index 553946c42..8936195c3 100644 --- a/launcher/icons/IconList.h +++ b/launcher/icons/IconList.h @@ -51,7 +51,7 @@ class QFileSystemWatcher; class IconList : public QAbstractListModel { Q_OBJECT public: - explicit IconList(const QStringList& builtinPaths, QString path, QObject* parent = 0); + explicit IconList(const QStringList& builtinPaths, const QString& path, QObject* parent = 0); virtual ~IconList() {}; QIcon getIcon(const QString& key) const; @@ -72,6 +72,7 @@ class IconList : public QAbstractListModel { bool deleteIcon(const QString& key); bool trashIcon(const QString& key); bool iconFileExists(const QString& key) const; + QString iconDirectory(const QString& key) const; void installIcons(const QStringList& iconFiles); void installIcon(const QString& file, const QString& name); @@ -91,18 +92,20 @@ class IconList : public QAbstractListModel { IconList& operator=(const IconList&) = delete; void reindex(); void sortIconList(); + bool addPathRecursively(const QString& path); + QStringList getIconFilePaths() const; public slots: void directoryChanged(const QString& path); protected slots: void fileChanged(const QString& path); - void SettingChanged(const Setting& setting, QVariant value); + void SettingChanged(const Setting& setting, const QVariant& value); private: shared_qobject_ptr m_watcher; - bool is_watching; - QMap name_index; - QVector icons; + bool m_isWatching; + QMap m_nameIndex; + QVector m_icons; QDir m_dir; }; diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp index c54a5b04b..07b5d7b40 100644 --- a/launcher/java/JavaChecker.cpp +++ b/launcher/java/JavaChecker.cpp @@ -44,8 +44,8 @@ #include "FileSystem.h" #include "java/JavaUtils.h" -JavaChecker::JavaChecker(QString path, QString args, int minMem, int maxMem, int permGen, int id, QObject* parent) - : Task(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen), m_id(id) +JavaChecker::JavaChecker(QString path, QString args, int minMem, int maxMem, int permGen, int id) + : Task(), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen), m_id(id) {} void JavaChecker::executeTask() @@ -171,7 +171,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) auto os_arch = results["os.arch"]; auto java_version = results["java.version"]; auto java_vendor = results["java.vendor"]; - bool is_64 = os_arch == "x86_64" || os_arch == "amd64" || os_arch == "aarch64" || os_arch == "arm64"; + bool is_64 = os_arch == "x86_64" || os_arch == "amd64" || os_arch == "aarch64" || os_arch == "arm64" || os_arch == "riscv64"; result.validity = Result::Validity::Valid; result.is_64bit = is_64; diff --git a/launcher/java/JavaChecker.h b/launcher/java/JavaChecker.h index 171a18b76..a04b68170 100644 --- a/launcher/java/JavaChecker.h +++ b/launcher/java/JavaChecker.h @@ -1,7 +1,6 @@ #pragma once #include #include -#include #include "JavaVersion.h" #include "QObjectPtr.h" @@ -26,7 +25,7 @@ class JavaChecker : public Task { enum class Validity { Errored, ReturnedInvalidData, Valid } validity = Validity::Errored; }; - explicit JavaChecker(QString path, QString args, int minMem = 0, int maxMem = 0, int permGen = 0, int id = 0, QObject* parent = 0); + explicit JavaChecker(QString path, QString args, int minMem = 0, int maxMem = 0, int permGen = 0, int id = 0); signals: void checkFinished(const Result& result); diff --git a/launcher/java/JavaInstallList.cpp b/launcher/java/JavaInstallList.cpp index 569fda306..aa7fab8a0 100644 --- a/launcher/java/JavaInstallList.cpp +++ b/launcher/java/JavaInstallList.cpp @@ -163,7 +163,7 @@ void JavaListLoadTask::executeTask() JavaUtils ju; QList candidate_paths = m_only_managed_versions ? getPrismJavaBundle() : ju.FindJavaPaths(); - ConcurrentTask::Ptr job(new ConcurrentTask(this, "Java detection", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())); + ConcurrentTask::Ptr job(new ConcurrentTask("Java detection", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())); m_job.reset(job); connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished); connect(m_job.get(), &Task::progress, this, &Task::setProgress); @@ -171,7 +171,7 @@ void JavaListLoadTask::executeTask() qDebug() << "Probing the following Java paths: "; int id = 0; for (QString candidate : candidate_paths) { - auto checker = new JavaChecker(candidate, "", 0, 0, 0, id, this); + auto checker = new JavaChecker(candidate, "", 0, 0, 0, id); connect(checker, &JavaChecker::checkFinished, [this](const JavaChecker::Result& result) { m_results << result; }); job->addTask(Task::Ptr(checker)); id++; diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index bc8026348..072cb1d16 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -102,6 +102,8 @@ QProcessEnvironment CleanEnviroment() QString newValue = stripVariableEntries(key, value, rawenv.value("LAUNCHER_" + key)); qDebug() << "Env: stripped" << key << value << "to" << newValue; + + value = newValue; } #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) // Strip IBus @@ -403,7 +405,7 @@ QList JavaUtils::FindJavaPaths() { QList javas; javas.append(this->GetDefaultJava()->path); - auto scanJavaDir = [&]( + auto scanJavaDir = [&javas]( const QString& dirPath, const std::function& filter = [](const QFileInfo&) { return true; }) { QDir dir(dirPath); @@ -422,7 +424,7 @@ QList JavaUtils::FindJavaPaths() }; // java installed in a snap is installed in the standard directory, but underneath $SNAP auto snap = qEnvironmentVariable("SNAP"); - auto scanJavaDirs = [&](const QString& dirPath) { + auto scanJavaDirs = [scanJavaDir, snap](const QString& dirPath) { scanJavaDir(dirPath); if (!snap.isNull()) { scanJavaDir(snap + dirPath); @@ -440,9 +442,15 @@ QList JavaUtils::FindJavaPaths() QString fileName = info.fileName(); return fileName.startsWith("openjdk-") || fileName.startsWith("openj9-"); }; + // AOSC OS's locations for openjdk + auto aoscFilter = [](const QFileInfo& info) { + QString fileName = info.fileName(); + return fileName == "java" || fileName.startsWith("java-"); + }; scanJavaDir("/usr/lib64", gentooFilter); scanJavaDir("/usr/lib", gentooFilter); scanJavaDir("/opt", gentooFilter); + scanJavaDir("/usr/lib", aoscFilter); // javas stored in Prism Launcher's folder scanJavaDirs("java"); // manually installed JDKs in /opt @@ -544,12 +552,12 @@ QStringList getPrismJavaBundle() { QList javas; - auto scanDir = [&](QString prefix) { + auto scanDir = [&javas](QString prefix) { javas.append(FS::PathCombine(prefix, "jre", "bin", JavaUtils::javaExecutable)); javas.append(FS::PathCombine(prefix, "bin", JavaUtils::javaExecutable)); javas.append(FS::PathCombine(prefix, JavaUtils::javaExecutable)); }; - auto scanJavaDir = [&](const QString& dirPath) { + auto scanJavaDir = [scanDir](const QString& dirPath) { QDir dir(dirPath); if (!dir.exists()) return; diff --git a/launcher/java/download/ManifestDownloadTask.cpp b/launcher/java/download/ManifestDownloadTask.cpp index 836afeaac..20b39e751 100644 --- a/launcher/java/download/ManifestDownloadTask.cpp +++ b/launcher/java/download/ManifestDownloadTask.cpp @@ -86,11 +86,10 @@ void ManifestDownloadTask::downloadJava(const QJsonDocument& doc) if (type == "directory") { FS::ensureFolderPathExists(file); } else if (type == "link") { - // this is linux only ! + // this is *nix only ! auto path = Json::ensureString(meta, "target"); if (!path.isEmpty()) { - auto target = FS::PathCombine(file, "../" + path); - QFile(target).link(file); + QFile::link(path, file); } } else if (type == "file") { // TODO download compressed version if it exists ? diff --git a/launcher/launch/LaunchStep.cpp b/launcher/launch/LaunchStep.cpp index f3e9dfce0..0b352ea9f 100644 --- a/launcher/launch/LaunchStep.cpp +++ b/launcher/launch/LaunchStep.cpp @@ -16,7 +16,7 @@ #include "LaunchStep.h" #include "LaunchTask.h" -LaunchStep::LaunchStep(LaunchTask* parent) : Task(parent), m_parent(parent) +LaunchStep::LaunchStep(LaunchTask* parent) : Task(), m_parent(parent) { connect(this, &LaunchStep::readyForLaunch, parent, &LaunchTask::onReadyForLaunch); connect(this, &LaunchStep::logLine, parent, &LaunchTask::onLogLine); diff --git a/launcher/launch/LaunchTask.cpp b/launcher/launch/LaunchTask.cpp index 0251b302d..4b93d2077 100644 --- a/launcher/launch/LaunchTask.cpp +++ b/launcher/launch/LaunchTask.cpp @@ -254,20 +254,60 @@ void LaunchTask::emitFailed(QString reason) Task::emitFailed(reason); } -void LaunchTask::substituteVariables(QStringList& args) const +QString expandVariables(const QString& input, QProcessEnvironment dict) { - auto env = m_instance->createEnvironment(); + QString result = input; - for (auto key : env.keys()) { - args.replaceInStrings("$" + key, env.value(key)); + enum { base, maybeBrace, variable, brace } state = base; + int startIdx = -1; + for (int i = 0; i < result.length();) { + QChar c = result.at(i++); + switch (state) { + case base: + if (c == '$') + state = maybeBrace; + break; + case maybeBrace: + if (c == '{') { + state = brace; + startIdx = i; + } else if (c.isLetterOrNumber() || c == '_') { + state = variable; + startIdx = i - 1; + } else { + state = base; + } + break; + case brace: + if (c == '}') { + const auto res = dict.value(result.mid(startIdx, i - 1 - startIdx), ""); + if (!res.isEmpty()) { + result.replace(startIdx - 2, i - startIdx + 2, res); + i = startIdx - 2 + res.length(); + } + state = base; + } + break; + case variable: + if (!c.isLetterOrNumber() && c != '_') { + const auto res = dict.value(result.mid(startIdx, i - startIdx - 1), ""); + if (!res.isEmpty()) { + result.replace(startIdx - 1, i - startIdx, res); + i = startIdx - 1 + res.length(); + } + state = base; + } + break; + } } + if (state == variable) { + if (const auto res = dict.value(result.mid(startIdx), ""); !res.isEmpty()) + result.replace(startIdx - 1, result.length() - startIdx + 1, res); + } + return result; } -void LaunchTask::substituteVariables(QString& cmd) const +QString LaunchTask::substituteVariables(QString& cmd, bool isLaunch) const { - auto env = m_instance->createEnvironment(); - - for (auto key : env.keys()) { - cmd.replace("$" + key, env.value(key)); - } + return expandVariables(cmd, isLaunch ? m_instance->createLaunchEnvironment() : m_instance->createEnvironment()); } diff --git a/launcher/launch/LaunchTask.h b/launcher/launch/LaunchTask.h index 56065af5b..2e87ece95 100644 --- a/launcher/launch/LaunchTask.h +++ b/launcher/launch/LaunchTask.h @@ -87,8 +87,7 @@ class LaunchTask : public Task { shared_qobject_ptr getLogModel(); public: - void substituteVariables(QStringList& args) const; - void substituteVariables(QString& cmd) const; + QString substituteVariables(QString& cmd, bool isLaunch = false) const; QString censorPrivateInfo(QString in); protected: /* methods */ diff --git a/launcher/launch/LogModel.h b/launcher/launch/LogModel.h index 18e51d7e3..167f74190 100644 --- a/launcher/launch/LogModel.h +++ b/launcher/launch/LogModel.h @@ -32,7 +32,7 @@ class LogModel : public QAbstractListModel { private /* types */: struct entry { - MessageLevel::Enum level; + MessageLevel::Enum level = MessageLevel::Enum::Unknown; QString line; }; diff --git a/launcher/launch/steps/CheckJava.cpp b/launcher/launch/steps/CheckJava.cpp index 55d13b58c..0f8d27e94 100644 --- a/launcher/launch/steps/CheckJava.cpp +++ b/launcher/launch/steps/CheckJava.cpp @@ -94,7 +94,7 @@ void CheckJava::executeTask() // if timestamps are not the same, or something is missing, check! if (m_javaSignature != storedSignature || storedVersion.size() == 0 || storedArchitecture.size() == 0 || storedRealArchitecture.size() == 0 || storedVendor.size() == 0) { - m_JavaChecker.reset(new JavaChecker(realJavaPath, "", 0, 0, 0, 0, this)); + m_JavaChecker.reset(new JavaChecker(realJavaPath, "", 0, 0, 0, 0)); emit logLine(QString("Checking Java version..."), MessageLevel::Launcher); connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished); m_JavaChecker->start(); diff --git a/launcher/launch/steps/PostLaunchCommand.cpp b/launcher/launch/steps/PostLaunchCommand.cpp index 725101224..5d893c71f 100644 --- a/launcher/launch/steps/PostLaunchCommand.cpp +++ b/launcher/launch/steps/PostLaunchCommand.cpp @@ -47,25 +47,21 @@ PostLaunchCommand::PostLaunchCommand(LaunchTask* parent) : LaunchStep(parent) void PostLaunchCommand::executeTask() { - // FIXME: where to put this? + auto cmd = m_parent->substituteVariables(m_command); + emit logLine(tr("Running Post-Launch command: %1").arg(cmd), MessageLevel::Launcher); #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) - auto args = QProcess::splitCommand(m_command); - m_parent->substituteVariables(args); + auto args = QProcess::splitCommand(cmd); - emit logLine(tr("Running Post-Launch command: %1").arg(args.join(' ')), MessageLevel::Launcher); const QString program = args.takeFirst(); m_process.start(program, args); #else - m_parent->substituteVariables(m_command); - - emit logLine(tr("Running Post-Launch command: %1").arg(m_command), MessageLevel::Launcher); - m_process.start(m_command); + m_process.start(cmd); #endif } void PostLaunchCommand::on_state(LoggedProcess::State state) { - auto getError = [&]() { return tr("Post-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); }; + auto getError = [this]() { return tr("Post-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); }; switch (state) { case LoggedProcess::Aborted: case LoggedProcess::Crashed: diff --git a/launcher/launch/steps/PreLaunchCommand.cpp b/launcher/launch/steps/PreLaunchCommand.cpp index 6d071a66e..318237e99 100644 --- a/launcher/launch/steps/PreLaunchCommand.cpp +++ b/launcher/launch/steps/PreLaunchCommand.cpp @@ -47,25 +47,20 @@ PreLaunchCommand::PreLaunchCommand(LaunchTask* parent) : LaunchStep(parent) void PreLaunchCommand::executeTask() { - // FIXME: where to put this? + auto cmd = m_parent->substituteVariables(m_command); + emit logLine(tr("Running Pre-Launch command: %1").arg(cmd), MessageLevel::Launcher); #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) - auto args = QProcess::splitCommand(m_command); - m_parent->substituteVariables(args); - - emit logLine(tr("Running Pre-Launch command: %1").arg(args.join(' ')), MessageLevel::Launcher); + auto args = QProcess::splitCommand(cmd); const QString program = args.takeFirst(); m_process.start(program, args); #else - m_parent->substituteVariables(m_command); - - emit logLine(tr("Running Pre-Launch command: %1").arg(m_command), MessageLevel::Launcher); - m_process.start(m_command); + m_process.start(cmd); #endif } void PreLaunchCommand::on_state(LoggedProcess::State state) { - auto getError = [&]() { return tr("Pre-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); }; + auto getError = [this]() { return tr("Pre-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); }; switch (state) { case LoggedProcess::Aborted: case LoggedProcess::Crashed: diff --git a/launcher/meta/Index.cpp b/launcher/meta/Index.cpp index bd0745b6b..1707854be 100644 --- a/launcher/meta/Index.cpp +++ b/launcher/meta/Index.cpp @@ -140,8 +140,8 @@ Task::Ptr Index::loadVersion(const QString& uid, const QString& version, Net::Mo } auto versionList = get(uid); - auto loadTask = makeShared( - this, tr("Load meta for %1:%2", "This is for the task name that loads the meta index.").arg(uid, version)); + auto loadTask = + makeShared(tr("Load meta for %1:%2", "This is for the task name that loads the meta index.").arg(uid, version)); if (status() != BaseEntity::LoadStatus::Remote || force) { loadTask->addTask(this->loadTask(mode)); } diff --git a/launcher/meta/VersionList.cpp b/launcher/meta/VersionList.cpp index 6856b5f8d..1de4e7f36 100644 --- a/launcher/meta/VersionList.cpp +++ b/launcher/meta/VersionList.cpp @@ -34,8 +34,7 @@ VersionList::VersionList(const QString& uid, QObject* parent) : BaseVersionList( Task::Ptr VersionList::getLoadTask() { - auto loadTask = - makeShared(this, tr("Load meta for %1", "This is for the task name that loads the meta index.").arg(m_uid)); + auto loadTask = makeShared(tr("Load meta for %1", "This is for the task name that loads the meta index.").arg(m_uid)); loadTask->addTask(APPLICATION->metadataIndex()->loadTask(Net::Mode::Online)); loadTask->addTask(this->loadTask(Net::Mode::Online)); return loadTask; @@ -159,8 +158,8 @@ Version::Ptr VersionList::getVersion(const QString& version) bool VersionList::hasVersion(QString version) const { - auto ver = - std::find_if(m_versions.constBegin(), m_versions.constEnd(), [&](Meta::Version::Ptr const& a) { return a->version() == version; }); + auto ver = std::find_if(m_versions.constBegin(), m_versions.constEnd(), + [version](Meta::Version::Ptr const& a) { return a->version() == version; }); return (ver != m_versions.constEnd()); } diff --git a/launcher/minecraft/Component.cpp b/launcher/minecraft/Component.cpp index 1073ef324..ad7ef545c 100644 --- a/launcher/minecraft/Component.cpp +++ b/launcher/minecraft/Component.cpp @@ -222,11 +222,12 @@ bool Component::isMoveable() return true; } -bool Component::isVersionChangeable() +bool Component::isVersionChangeable(bool wait) { auto list = getVersionList(); if (list) { - list->waitToLoad(); + if (wait) + list->waitToLoad(); return list->count() != 0; } return false; diff --git a/launcher/minecraft/Component.h b/launcher/minecraft/Component.h index 7ff30889f..203cc2241 100644 --- a/launcher/minecraft/Component.h +++ b/launcher/minecraft/Component.h @@ -72,7 +72,7 @@ class Component : public QObject, public ProblemProvider { bool isRevertible(); bool isRemovable(); bool isCustom(); - bool isVersionChangeable(); + bool isVersionChangeable(bool wait = true); bool isKnownModloader(); QStringList knownConflictingComponents(); diff --git a/launcher/minecraft/ComponentUpdateTask.cpp b/launcher/minecraft/ComponentUpdateTask.cpp index 6656a84f8..36a07ee72 100644 --- a/launcher/minecraft/ComponentUpdateTask.cpp +++ b/launcher/minecraft/ComponentUpdateTask.cpp @@ -38,7 +38,7 @@ * If the component list changes, start over. */ -ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list, QObject* parent) : Task(parent) +ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list) : Task() { d.reset(new ComponentUpdateTaskData); d->m_profile = list; diff --git a/launcher/minecraft/ComponentUpdateTask.h b/launcher/minecraft/ComponentUpdateTask.h index 484c0bedd..64c55877b 100644 --- a/launcher/minecraft/ComponentUpdateTask.h +++ b/launcher/minecraft/ComponentUpdateTask.h @@ -14,7 +14,7 @@ class ComponentUpdateTask : public Task { enum class Mode { Launch, Resolution }; public: - explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list, QObject* parent = 0); + explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list); virtual ~ComponentUpdateTask(); protected: diff --git a/launcher/minecraft/Library.cpp b/launcher/minecraft/Library.cpp index 4f04f0eb9..0bc462474 100644 --- a/launcher/minecraft/Library.cpp +++ b/launcher/minecraft/Library.cpp @@ -65,7 +65,7 @@ void Library::getApplicableFiles(const RuntimeContext& runtimeContext, { bool local = isLocal(); // Lambda function to get the absolute file path - auto actualPath = [&](QString relPath) { + auto actualPath = [this, local, overridePath](QString relPath) { relPath = FS::RemoveInvalidPathChars(relPath); QFileInfo out(FS::PathCombine(storagePrefix(), relPath)); if (local && !overridePath.isEmpty()) { @@ -115,7 +115,7 @@ QList Library::getDownloads(const RuntimeContext& runtimeC bool local = isLocal(); // Lambda function to check if a local file exists - auto check_local_file = [&](QString storage) { + auto check_local_file = [overridePath, &failedLocalFiles](QString storage) { QFileInfo fileinfo(storage); QString fileName = fileinfo.fileName(); auto fullPath = FS::PathCombine(overridePath, fileName); @@ -128,7 +128,7 @@ QList Library::getDownloads(const RuntimeContext& runtimeC }; // Lambda function to add a download request - auto add_download = [&](QString storage, QString url, QString sha1) { + auto add_download = [this, local, check_local_file, cache, stale, &out](QString storage, QString url, QString sha1) { if (local) { return check_local_file(storage); } @@ -198,7 +198,7 @@ QList Library::getDownloads(const RuntimeContext& runtimeC } } } else { - auto raw_dl = [&]() { + auto raw_dl = [this, raw_storage]() { if (!m_absoluteURL.isEmpty()) { return m_absoluteURL; } diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 98ac7647f..d1780d497 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -91,13 +91,56 @@ #include "tools/BaseProfiler.h" #include +#include +#include +#include #ifdef Q_OS_LINUX #include "MangoHud.h" #endif +#ifdef WITH_QTDBUS +#include +#endif + #define IBUS "@im=ibus" +static bool switcherooSetupGPU(QProcessEnvironment& env) +{ +#ifdef WITH_QTDBUS + if (!QDBusConnection::systemBus().isConnected()) + return false; + + QDBusInterface switcheroo("net.hadess.SwitcherooControl", "/net/hadess/SwitcherooControl", "org.freedesktop.DBus.Properties", + QDBusConnection::systemBus()); + + if (!switcheroo.isValid()) + return false; + + QDBusReply reply = + switcheroo.call(QStringLiteral("Get"), QStringLiteral("net.hadess.SwitcherooControl"), QStringLiteral("GPUs")); + if (!reply.isValid()) + return false; + + QDBusArgument arg = qvariant_cast(reply.value().variant()); + QList gpus; + arg >> gpus; + + for (const auto& gpu : gpus) { + QString name = qvariant_cast(gpu[QStringLiteral("Name")]); + bool defaultGpu = qvariant_cast(gpu[QStringLiteral("Default")]); + if (!defaultGpu) { + QStringList envList = qvariant_cast(gpu[QStringLiteral("Environment")]); + for (int i = 0; i + 1 < envList.size(); i += 2) { + env.insert(envList[i], envList[i + 1]); + } + return true; + } + } +#endif + return false; +} + // all of this because keeping things compatible with deprecated old settings // if either of the settings {a, b} is true, this also resolves to true class OrSetting : public Setting { @@ -530,7 +573,7 @@ QStringList MinecraftInstance::javaArguments() QString MinecraftInstance::getLauncher() { // use legacy launcher if the traits are set - if (traits().contains("legacyLaunch") || traits().contains("alphaLaunch")) + if (isLegacy()) return "legacy"; return "standard"; @@ -551,6 +594,13 @@ QMap MinecraftInstance::getVariables() out.insert("INST_JAVA", settings()->get("JavaPath").toString()); out.insert("INST_JAVA_ARGS", javaArguments().join(' ')); out.insert("NO_COLOR", "1"); +#ifdef Q_OS_MACOS + // get library for Steam overlay support + QString steamDyldInsertLibraries = qEnvironmentVariable("STEAM_DYLD_INSERT_LIBRARIES"); + if (!steamDyldInsertLibraries.isEmpty()) { + out.insert("DYLD_INSERT_LIBRARIES", steamDyldInsertLibraries); + } +#endif return out; } @@ -606,6 +656,7 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment() // dlsym variant is only needed for OpenGL and not included in the vulkan layer appendLib("libMangoHud_dlsym.so"); appendLib("libMangoHud_opengl.so"); + appendLib("libMangoHud_shim.so"); preloadList << mangoHudLibString; } @@ -614,12 +665,14 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment() } if (settings()->get("UseDiscreteGpu").toBool()) { - // Open Source Drivers - env.insert("DRI_PRIME", "1"); - // Proprietary Nvidia Drivers - env.insert("__NV_PRIME_RENDER_OFFLOAD", "1"); - env.insert("__VK_LAYER_NV_optimus", "NVIDIA_only"); - env.insert("__GLX_VENDOR_LIBRARY_NAME", "nvidia"); + if (!switcherooSetupGPU(env)) { + // Open Source Drivers + env.insert("DRI_PRIME", "1"); + // Proprietary Nvidia Drivers + env.insert("__NV_PRIME_RENDER_OFFLOAD", "1"); + env.insert("__VK_LAYER_NV_optimus", "NVIDIA_only"); + env.insert("__GLX_VENDOR_LIBRARY_NAME", "nvidia"); + } } if (settings()->get("UseZink").toBool()) { @@ -752,11 +805,34 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftT // window size, title and state, legacy { QString windowParams; - if (settings()->get("LaunchMaximized").toBool()) - windowParams = "maximized"; - else + if (settings()->get("LaunchMaximized").toBool()) { + // FIXME doesn't support maximisation + if (!isLegacy()) { + auto screen = QGuiApplication::primaryScreen(); + auto screenGeometry = screen->availableSize(); + + // small hack to get the widow decorations + for (auto w : QApplication::topLevelWidgets()) { + auto mainWindow = qobject_cast(w); + if (mainWindow) { + auto m = mainWindow->windowHandle()->frameMargins(); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) + screenGeometry = screenGeometry.shrunkBy(m); +#else + screenGeometry = { screenGeometry.width() - m.left() - m.right(), screenGeometry.height() - m.top() - m.bottom() }; +#endif + break; + } + } + + windowParams = QString("%1x%2").arg(screenGeometry.width()).arg(screenGeometry.height()); + } else { + windowParams = "maximized"; + } + } else { windowParams = QString("%1x%2").arg(settings()->get("MinecraftWinWidth").toInt()).arg(settings()->get("MinecraftWinHeight").toInt()); + } launchScript += "windowTitle " + windowTitle() + "\n"; launchScript += "windowParams " + windowParams + "\n"; } @@ -828,7 +904,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr out << "Libraries:"; QStringList jars, nativeJars; profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot()); - auto printLibFile = [&](const QString& path) { + auto printLibFile = [&out](const QString& path) { QFileInfo info(path); if (info.exists()) { out << " " + path; @@ -848,7 +924,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr } // mods and core mods - auto printModList = [&](const QString& label, ModFolderModel& model) { + auto printModList = [&out](const QString& label, ModFolderModel& model) { if (model.size()) { out << QString("%1:").arg(label); auto modList = model.allMods(); @@ -1083,17 +1159,10 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt process->appendStep(step); } - // run pre-launch command if that's needed - if (getPreLaunchCommand().size()) { - auto step = makeShared(pptr); - step->setWorkingDirectory(gameRoot()); - process->appendStep(step); - } - // load meta { auto mode = session->status != AuthSession::PlayableOffline ? Net::Mode::Online : Net::Mode::Offline; - process->appendStep(makeShared(pptr, makeShared(this, mode, pptr))); + process->appendStep(makeShared(pptr, makeShared(this, mode))); } // check java @@ -1102,6 +1171,13 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt process->appendStep(makeShared(pptr)); } + // run pre-launch command if that's needed + if (getPreLaunchCommand().size()) { + auto step = makeShared(pptr); + step->setWorkingDirectory(gameRoot()); + process->appendStep(step); + } + // if we aren't in offline mode,. if (session->status != AuthSession::PlayableOffline) { if (!session->demo) { @@ -1177,7 +1253,7 @@ std::shared_ptr MinecraftInstance::loaderModList() { if (!m_loader_mod_list) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_loader_mod_list.reset(new ModFolderModel(modsRoot(), this, is_indexed)); + m_loader_mod_list.reset(new ModFolderModel(modsRoot(), this, is_indexed, true)); } return m_loader_mod_list; } @@ -1186,7 +1262,7 @@ std::shared_ptr MinecraftInstance::coreModList() { if (!m_core_mod_list) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_core_mod_list.reset(new ModFolderModel(coreModsDir(), this, is_indexed)); + m_core_mod_list.reset(new ModFolderModel(coreModsDir(), this, is_indexed, true)); } return m_core_mod_list; } @@ -1203,7 +1279,8 @@ std::shared_ptr MinecraftInstance::nilModList() std::shared_ptr MinecraftInstance::resourcePackList() { if (!m_resource_pack_list) { - m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), this)); + bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), this, is_indexed, true)); } return m_resource_pack_list; } @@ -1211,7 +1288,8 @@ std::shared_ptr MinecraftInstance::resourcePackList() std::shared_ptr MinecraftInstance::texturePackList() { if (!m_texture_pack_list) { - m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), this)); + bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), this, is_indexed, true)); } return m_texture_pack_list; } @@ -1219,11 +1297,17 @@ std::shared_ptr MinecraftInstance::texturePackList() std::shared_ptr MinecraftInstance::shaderPackList() { if (!m_shader_pack_list) { - m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), this)); + bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), this, is_indexed, true)); } return m_shader_pack_list; } +QList> MinecraftInstance::resourceLists() +{ + return { loaderModList(), coreModList(), nilModList(), resourcePackList(), texturePackList(), shaderPackList() }; +} + std::shared_ptr MinecraftInstance::worldList() { if (!m_world_list) { diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index 75e97ae45..5d9bb45ef 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -116,6 +116,7 @@ class MinecraftInstance : public BaseInstance { std::shared_ptr resourcePackList(); std::shared_ptr texturePackList(); std::shared_ptr shaderPackList(); + QList> resourceLists(); std::shared_ptr worldList(); std::shared_ptr gameOptionsModel(); diff --git a/launcher/minecraft/MinecraftLoadAndCheck.cpp b/launcher/minecraft/MinecraftLoadAndCheck.cpp index a9dcdf067..c0a82e61e 100644 --- a/launcher/minecraft/MinecraftLoadAndCheck.cpp +++ b/launcher/minecraft/MinecraftLoadAndCheck.cpp @@ -2,15 +2,16 @@ #include "MinecraftInstance.h" #include "PackProfile.h" -MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance* inst, Net::Mode netmode, QObject* parent) - : Task(parent), m_inst(inst), m_netmode(netmode) -{} +MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance* inst, Net::Mode netmode) : m_inst(inst), m_netmode(netmode) {} void MinecraftLoadAndCheck::executeTask() { // add offline metadata load task auto components = m_inst->getPackProfile(); - components->reload(m_netmode); + if (auto result = components->reload(m_netmode); !result) { + emitFailed(result.error); + return; + } m_task = components->getCurrentTask(); if (!m_task) { diff --git a/launcher/minecraft/MinecraftLoadAndCheck.h b/launcher/minecraft/MinecraftLoadAndCheck.h index 72e9e0caa..c05698bca 100644 --- a/launcher/minecraft/MinecraftLoadAndCheck.h +++ b/launcher/minecraft/MinecraftLoadAndCheck.h @@ -23,7 +23,7 @@ class MinecraftInstance; class MinecraftLoadAndCheck : public Task { Q_OBJECT public: - explicit MinecraftLoadAndCheck(MinecraftInstance* inst, Net::Mode netmode, QObject* parent = nullptr); + explicit MinecraftLoadAndCheck(MinecraftInstance* inst, Net::Mode netmode); virtual ~MinecraftLoadAndCheck() = default; void executeTask() override; diff --git a/launcher/minecraft/OneSixVersionFormat.cpp b/launcher/minecraft/OneSixVersionFormat.cpp index bd587beb2..684869c8d 100644 --- a/launcher/minecraft/OneSixVersionFormat.cpp +++ b/launcher/minecraft/OneSixVersionFormat.cpp @@ -176,7 +176,7 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument& doc } } - auto readLibs = [&](const char* which, QList& outList) { + auto readLibs = [&root, &out, &filename](const char* which, QList& outList) { for (auto libVal : requireArray(root.value(which))) { QJsonObject libObj = requireObject(libVal); // parse the library diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index f1d2473c2..d6534b910 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -173,29 +173,32 @@ static bool savePackProfile(const QString& filename, const ComponentContainer& c } // Read the given file into component containers -static bool loadPackProfile(PackProfile* parent, - const QString& filename, - const QString& componentJsonPattern, - ComponentContainer& container) +static PackProfile::Result loadPackProfile(PackProfile* parent, + const QString& filename, + const QString& componentJsonPattern, + ComponentContainer& container) { QFile componentsFile(filename); if (!componentsFile.exists()) { - qCWarning(instanceProfileC) << "Components file" << filename << "doesn't exist. This should never happen."; - return false; + auto message = QObject::tr("Components file %1 doesn't exist. This should never happen.").arg(filename); + qCWarning(instanceProfileC) << message; + return PackProfile::Result::Error(message); } if (!componentsFile.open(QFile::ReadOnly)) { - qCCritical(instanceProfileC) << "Couldn't open" << componentsFile.fileName() << " for reading:" << componentsFile.errorString(); + auto message = QObject::tr("Couldn't open %1 for reading: %2").arg(componentsFile.fileName(), componentsFile.errorString()); + qCCritical(instanceProfileC) << message; qCWarning(instanceProfileC) << "Ignoring overridden order"; - return false; + return PackProfile::Result::Error(message); } // and it's valid JSON QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(componentsFile.readAll(), &error); if (error.error != QJsonParseError::NoError) { - qCCritical(instanceProfileC) << "Couldn't parse" << componentsFile.fileName() << ":" << error.errorString(); + auto message = QObject::tr("Couldn't parse %1 as json: %2").arg(componentsFile.fileName(), error.errorString()); + qCCritical(instanceProfileC) << message; qCWarning(instanceProfileC) << "Ignoring overridden order"; - return false; + return PackProfile::Result::Error(message); } // and then read it and process it if all above is true. @@ -212,11 +215,13 @@ static bool loadPackProfile(PackProfile* parent, container.append(componentFromJsonV1(parent, componentJsonPattern, comp_obj)); } } catch ([[maybe_unused]] const JSONValidationError& err) { - qCCritical(instanceProfileC) << "Couldn't parse" << componentsFile.fileName() << ": bad file format"; + auto message = QObject::tr("Couldn't parse %1 : bad file format").arg(componentsFile.fileName()); + qCCritical(instanceProfileC) << message; + qCWarning(instanceProfileC) << "error:" << err.what(); container.clear(); - return false; + return PackProfile::Result::Error(message); } - return true; + return PackProfile::Result::Success(); } // END: component file format @@ -283,44 +288,43 @@ void PackProfile::save_internal() d->dirty = false; } -bool PackProfile::load() +PackProfile::Result PackProfile::load() { auto filename = componentsFilePath(); // load the new component list and swap it with the current one... ComponentContainer newComponents; - if (!loadPackProfile(this, filename, patchesPattern(), newComponents)) { + if (auto result = loadPackProfile(this, filename, patchesPattern(), newComponents); !result) { qCritical() << d->m_instance->name() << "|" << "Failed to load the component config"; - return false; - } else { - // FIXME: actually use fine-grained updates, not this... - beginResetModel(); - // disconnect all the old components - for (auto component : d->components) { - disconnect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged); - } - d->components.clear(); - d->componentIndex.clear(); - for (auto component : newComponents) { - if (d->componentIndex.contains(component->m_uid)) { - qWarning() << d->m_instance->name() << "|" << "Ignoring duplicate component entry" << component->m_uid; - continue; - } - connect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged); - d->components.append(component); - d->componentIndex[component->m_uid] = component; - } - endResetModel(); - d->loaded = true; - return true; + return result; } + // FIXME: actually use fine-grained updates, not this... + beginResetModel(); + // disconnect all the old components + for (auto component : d->components) { + disconnect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged); + } + d->components.clear(); + d->componentIndex.clear(); + for (auto component : newComponents) { + if (d->componentIndex.contains(component->m_uid)) { + qWarning() << d->m_instance->name() << "|" << "Ignoring duplicate component entry" << component->m_uid; + continue; + } + connect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged); + d->components.append(component); + d->componentIndex[component->m_uid] = component; + } + endResetModel(); + d->loaded = true; + return Result::Success(); } -void PackProfile::reload(Net::Mode netmode) +PackProfile::Result PackProfile::reload(Net::Mode netmode) { // Do not reload when the update/resolve task is running. It is in control. if (d->m_updateTask) { - return; + return Result::Success(); } // flush any scheduled saves to not lose state @@ -329,9 +333,11 @@ void PackProfile::reload(Net::Mode netmode) // FIXME: differentiate when a reapply is required by propagating state from components invalidateLaunchProfile(); - if (load()) { - resolve(netmode); + if (auto result = load(); !result) { + return result; } + resolve(netmode); + return Result::Success(); } Task::Ptr PackProfile::getCurrentTask() @@ -746,7 +752,7 @@ bool PackProfile::removeComponent_internal(ComponentPtr patch) } // FIXME: we need a generic way of removing local resources, not just jar mods... - auto preRemoveJarMod = [&](LibraryPtr jarMod) -> bool { + auto preRemoveJarMod = [this](LibraryPtr jarMod) -> bool { if (!jarMod->isLocal()) { return true; } diff --git a/launcher/minecraft/PackProfile.h b/launcher/minecraft/PackProfile.h index b2de26ea0..d812dfa48 100644 --- a/launcher/minecraft/PackProfile.h +++ b/launcher/minecraft/PackProfile.h @@ -62,6 +62,19 @@ class PackProfile : public QAbstractListModel { public: enum Columns { NameColumn = 0, VersionColumn, NUM_COLUMNS }; + struct Result { + bool success; + QString error; + + // Implicit conversion to bool + operator bool() const { return success; } + + // Factory methods for convenience + static Result Success() { return { true, "" }; } + + static Result Error(const QString& errorMessage) { return { false, errorMessage }; } + }; + explicit PackProfile(MinecraftInstance* instance); virtual ~PackProfile(); @@ -102,7 +115,7 @@ class PackProfile : public QAbstractListModel { bool revertToBase(int index); /// reload the list, reload all components, resolve dependencies - void reload(Net::Mode netmode); + Result reload(Net::Mode netmode); // reload all components, resolve dependencies void resolve(Net::Mode netmode); @@ -169,7 +182,7 @@ class PackProfile : public QAbstractListModel { void disableInteraction(bool disable); private: - bool load(); + Result load(); bool installJarMods_internal(QStringList filepaths); bool installCustomJar_internal(QString filepath); bool installAgents_internal(QStringList filepaths); diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp index 1eba148a5..bd28f9e9a 100644 --- a/launcher/minecraft/World.cpp +++ b/launcher/minecraft/World.cpp @@ -38,7 +38,6 @@ #include #include #include -#include #include #include @@ -57,6 +56,7 @@ #include #include "FileSystem.h" +#include "PSaveFile.h" using std::nullopt; using std::optional; @@ -183,7 +183,7 @@ bool putLevelDatDataToFS(const QFileInfo& file, QByteArray& data) if (fullFilePath.isNull()) { return false; } - QSaveFile f(fullFilePath); + PSaveFile f(fullFilePath); if (!f.open(QIODevice::WriteOnly)) { return false; } diff --git a/launcher/minecraft/auth/AuthFlow.cpp b/launcher/minecraft/auth/AuthFlow.cpp index 45926206c..19fbe15dd 100644 --- a/launcher/minecraft/auth/AuthFlow.cpp +++ b/launcher/minecraft/auth/AuthFlow.cpp @@ -19,7 +19,7 @@ #include -AuthFlow::AuthFlow(AccountData* data, Action action, QObject* parent) : Task(parent), m_data(data) +AuthFlow::AuthFlow(AccountData* data, Action action) : Task(), m_data(data) { if (data->type == AccountType::MSA) { if (action == Action::DeviceCode) { diff --git a/launcher/minecraft/auth/AuthFlow.h b/launcher/minecraft/auth/AuthFlow.h index 4d18ac845..bff4c04e4 100644 --- a/launcher/minecraft/auth/AuthFlow.h +++ b/launcher/minecraft/auth/AuthFlow.h @@ -17,7 +17,7 @@ class AuthFlow : public Task { public: enum class Action { Refresh, Login, DeviceCode }; - explicit AuthFlow(AccountData* data, Action action = Action::Refresh, QObject* parent = 0); + explicit AuthFlow(AccountData* data, Action action = Action::Refresh); virtual ~AuthFlow() = default; void executeTask() override; diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index 5b063604c..1ed39b5ca 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -121,7 +121,7 @@ shared_qobject_ptr MinecraftAccount::login(bool useDeviceCode) { Q_ASSERT(m_currentTask.get() == nullptr); - m_currentTask.reset(new AuthFlow(&data, useDeviceCode ? AuthFlow::Action::DeviceCode : AuthFlow::Action::Login, this)); + m_currentTask.reset(new AuthFlow(&data, useDeviceCode ? AuthFlow::Action::DeviceCode : AuthFlow::Action::Login)); connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded); connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed); connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); }); @@ -135,7 +135,7 @@ shared_qobject_ptr MinecraftAccount::refresh() return m_currentTask; } - m_currentTask.reset(new AuthFlow(&data, AuthFlow::Action::Refresh, this)); + m_currentTask.reset(new AuthFlow(&data, AuthFlow::Action::Refresh)); connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded); connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed); diff --git a/launcher/minecraft/auth/MinecraftAccount.h b/launcher/minecraft/auth/MinecraftAccount.h index f6fcfada2..3f39e6e38 100644 --- a/launcher/minecraft/auth/MinecraftAccount.h +++ b/launcher/minecraft/auth/MinecraftAccount.h @@ -116,7 +116,7 @@ class MinecraftAccount : public QObject, public Usable { [[nodiscard]] AccountType accountType() const noexcept { return data.type; } - bool ownsMinecraft() const { return data.type != AccountType::Offline && data.minecraftEntitlement.ownsMinecraft; } + bool ownsMinecraft() const { return true; } bool hasProfile() const { return data.profileId().size() != 0; } diff --git a/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp b/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp index c283b153e..38ff90a47 100644 --- a/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp +++ b/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp @@ -74,12 +74,12 @@ void MSADeviceCodeStep::perform() m_task->setAskRetry(false); m_task->addNetAction(m_request); - connect(m_task.get(), &Task::finished, this, &MSADeviceCodeStep::deviceAutorizationFinished); + connect(m_task.get(), &Task::finished, this, &MSADeviceCodeStep::deviceAuthorizationFinished); m_task->start(); } -struct DeviceAutorizationResponse { +struct DeviceAuthorizationResponse { QString device_code; QString user_code; QString verification_uri; @@ -90,17 +90,17 @@ struct DeviceAutorizationResponse { QString error_description; }; -DeviceAutorizationResponse parseDeviceAutorizationResponse(const QByteArray& data) +DeviceAuthorizationResponse parseDeviceAuthorizationResponse(const QByteArray& data) { QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); if (err.error != QJsonParseError::NoError) { - qWarning() << "Failed to parse device autorization response due to err:" << err.errorString(); + qWarning() << "Failed to parse device authorization response due to err:" << err.errorString(); return {}; } if (!doc.isObject()) { - qWarning() << "Device autorization response is not an object"; + qWarning() << "Device authorization response is not an object"; return {}; } auto obj = doc.object(); @@ -111,9 +111,9 @@ DeviceAutorizationResponse parseDeviceAutorizationResponse(const QByteArray& dat }; } -void MSADeviceCodeStep::deviceAutorizationFinished() +void MSADeviceCodeStep::deviceAuthorizationFinished() { - auto rsp = parseDeviceAutorizationResponse(*m_response); + auto rsp = parseDeviceAuthorizationResponse(*m_response); if (!rsp.error.isEmpty() || !rsp.error_description.isEmpty()) { qWarning() << "Device authorization failed:" << rsp.error; emit finished(AccountTaskState::STATE_FAILED_HARD, @@ -208,12 +208,12 @@ AuthenticationResponse parseAuthenticationResponse(const QByteArray& data) QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); if (err.error != QJsonParseError::NoError) { - qWarning() << "Failed to parse device autorization response due to err:" << err.errorString(); + qWarning() << "Failed to parse device authorization response due to err:" << err.errorString(); return {}; } if (!doc.isObject()) { - qWarning() << "Device autorization response is not an object"; + qWarning() << "Device authorization response is not an object"; return {}; } auto obj = doc.object(); @@ -274,4 +274,4 @@ void MSADeviceCodeStep::authenticationFinished() m_data->msaToken.refresh_token = rsp.refresh_token; m_data->msaToken.token = rsp.access_token; emit finished(AccountTaskState::STATE_WORKING, tr("Got")); -} \ No newline at end of file +} diff --git a/launcher/minecraft/auth/steps/MSADeviceCodeStep.h b/launcher/minecraft/auth/steps/MSADeviceCodeStep.h index 616008def..7f755563f 100644 --- a/launcher/minecraft/auth/steps/MSADeviceCodeStep.h +++ b/launcher/minecraft/auth/steps/MSADeviceCodeStep.h @@ -58,7 +58,7 @@ class MSADeviceCodeStep : public AuthStep { void authorizeWithBrowser(QString url, QString code, int expiresIn); private slots: - void deviceAutorizationFinished(); + void deviceAuthorizationFinished(); void startPoolTimer(); void authenticateUser(); void authenticationFinished(); diff --git a/launcher/minecraft/auth/steps/MSAStep.cpp b/launcher/minecraft/auth/steps/MSAStep.cpp index 74999414c..151ee4e68 100644 --- a/launcher/minecraft/auth/steps/MSAStep.cpp +++ b/launcher/minecraft/auth/steps/MSAStep.cpp @@ -85,8 +85,7 @@ class CustomOAuthOobReplyHandler : public QOAuthOobReplyHandler { MSAStep::MSAStep(AccountData* data, bool silent) : AuthStep(data), m_silent(silent) { m_clientId = APPLICATION->getMSAClientID(); - if (QCoreApplication::applicationFilePath().startsWith("/tmp/.mount_") || - QFile::exists(FS::PathCombine(APPLICATION->root(), "portable.txt")) || !isSchemeHandlerRegistered()) + if (QCoreApplication::applicationFilePath().startsWith("/tmp/.mount_") || APPLICATION->isPortable() || !isSchemeHandlerRegistered()) { auto replyHandler = new QOAuthHttpServerReplyHandler(this); diff --git a/launcher/minecraft/launch/AutoInstallJava.cpp b/launcher/minecraft/launch/AutoInstallJava.cpp index b23b23ed2..854590dd2 100644 --- a/launcher/minecraft/launch/AutoInstallJava.cpp +++ b/launcher/minecraft/launch/AutoInstallJava.cpp @@ -57,9 +57,7 @@ #include "tasks/SequentialTask.h" AutoInstallJava::AutoInstallJava(LaunchTask* parent) - : LaunchStep(parent) - , m_instance(m_parent->instance()) - , m_supported_arch(SysInfo::getSupportedJavaArchitecture()) {}; + : LaunchStep(parent), m_instance(m_parent->instance()), m_supported_arch(SysInfo::getSupportedJavaArchitecture()) {}; void AutoInstallJava::executeTask() { @@ -78,7 +76,7 @@ void AutoInstallJava::executeTask() auto java = std::dynamic_pointer_cast(javas->at(i)); if (java && packProfile->getProfile()->getCompatibleJavaMajors().contains(java->id.major())) { if (!java->is_64bit) { - emit logLine(tr("The automatic Java mechanism detected a 32-bit installation of Java."), MessageLevel::Info); + emit logLine(tr("The automatic Java mechanism detected a 32-bit installation of Java."), MessageLevel::Launcher); } setJavaPath(java->path); return; @@ -136,7 +134,7 @@ void AutoInstallJava::setJavaPath(QString path) settings->set("OverrideJavaLocation", true); settings->set("JavaPath", path); settings->set("AutomaticJava", true); - emit logLine(tr("Compatible Java found at: %1.").arg(path), MessageLevel::Info); + emit logLine(tr("Compatible Java found at: %1.").arg(path), MessageLevel::Launcher); emitSucceeded(); } @@ -179,7 +177,7 @@ void AutoInstallJava::downloadJava(Meta::Version::Ptr version, QString javaName) return; } #if defined(Q_OS_MACOS) - auto seq = makeShared(this, tr("Install Java")); + auto seq = makeShared(tr("Install Java")); seq->addTask(m_current_task); seq->addTask(makeShared(final_path)); m_current_task = seq; diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp index d9a2b9b6b..49d91e433 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.cpp +++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp @@ -54,8 +54,8 @@ LauncherPartLaunch::LauncherPartLaunch(LaunchTask* parent) { if (parent->instance()->settings()->get("CloseAfterLaunch").toBool()) { std::shared_ptr connection{ new QMetaObject::Connection }; - *connection = - connect(&m_process, &LoggedProcess::log, this, [=](const QStringList& lines, [[maybe_unused]] MessageLevel::Enum level) { + *connection = connect( + &m_process, &LoggedProcess::log, this, [connection](const QStringList& lines, [[maybe_unused]] MessageLevel::Enum level) { qDebug() << lines; if (lines.filter(QRegularExpression(".*Setting user.+", QRegularExpression::CaseInsensitiveOption)).length() != 0) { APPLICATION->closeAllWindows(); @@ -131,6 +131,7 @@ void LauncherPartLaunch::executeTask() QString wrapperCommandStr = instance->getWrapperCommand().trimmed(); if (!wrapperCommandStr.isEmpty()) { + wrapperCommandStr = m_parent->substituteVariables(wrapperCommandStr); auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr); auto wrapperCommand = wrapperArgs.takeFirst(); auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand); @@ -170,6 +171,7 @@ void LauncherPartLaunch::on_state(LoggedProcess::State state) case LoggedProcess::Aborted: case LoggedProcess::Crashed: { m_parent->setPid(-1); + m_parent->instance()->setMinecraftRunning(false); emitFailed(tr("Game crashed.")); return; } diff --git a/launcher/minecraft/mod/MetadataHandler.h b/launcher/minecraft/mod/MetadataHandler.h index fb3a10133..0b8cb124d 100644 --- a/launcher/minecraft/mod/MetadataHandler.h +++ b/launcher/minecraft/mod/MetadataHandler.h @@ -26,33 +26,48 @@ // launcher/minecraft/mod/Mod.h class Mod; -/* Abstraction file for easily changing the way metadata is stored / handled - * Needs to be a class because of -Wunused-function and no C++17 [[maybe_unused]] - * */ -class Metadata { - public: - using ModStruct = Packwiz::V1::Mod; - using ModSide = Packwiz::V1::Side; +namespace Metadata { +using ModStruct = Packwiz::V1::Mod; +using ModSide = Packwiz::V1::Side; - static auto create(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> ModStruct - { - return Packwiz::V1::createModFormat(index_dir, mod_pack, mod_version); - } +inline auto create(const QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> ModStruct +{ + return Packwiz::V1::createModFormat(index_dir, mod_pack, mod_version); +} - static auto create(QDir& index_dir, Mod& internal_mod, QString mod_slug) -> ModStruct - { - return Packwiz::V1::createModFormat(index_dir, internal_mod, mod_slug); - } +inline auto create(const QDir& index_dir, Mod& internal_mod, QString mod_slug) -> ModStruct +{ + return Packwiz::V1::createModFormat(index_dir, internal_mod, std::move(mod_slug)); +} - static void update(QDir& index_dir, ModStruct& mod) { Packwiz::V1::updateModIndex(index_dir, mod); } +inline void update(const QDir& index_dir, ModStruct& mod) +{ + Packwiz::V1::updateModIndex(index_dir, mod); +} - static void remove(QDir& index_dir, QString mod_slug) { Packwiz::V1::deleteModIndex(index_dir, mod_slug); } +inline void remove(const QDir& index_dir, QString mod_slug) +{ + Packwiz::V1::deleteModIndex(index_dir, mod_slug); +} - static void remove(QDir& index_dir, QVariant& mod_id) { Packwiz::V1::deleteModIndex(index_dir, mod_id); } +inline void remove(const QDir& index_dir, QVariant& mod_id) +{ + Packwiz::V1::deleteModIndex(index_dir, mod_id); +} - static auto get(QDir& index_dir, QString mod_slug) -> ModStruct { return Packwiz::V1::getIndexForMod(index_dir, mod_slug); } +inline auto get(const QDir& index_dir, QString mod_slug) -> ModStruct +{ + return Packwiz::V1::getIndexForMod(index_dir, std::move(mod_slug)); +} - static auto get(QDir& index_dir, QVariant& mod_id) -> ModStruct { return Packwiz::V1::getIndexForMod(index_dir, mod_id); } +inline auto get(const QDir& index_dir, QVariant& mod_id) -> ModStruct +{ + return Packwiz::V1::getIndexForMod(index_dir, mod_id); +} - static auto modSideToString(ModSide side) -> QString { return Packwiz::V1::sideToString(side); } -}; +inline auto modSideToString(ModSide side) -> QString +{ + return Packwiz::V1::sideToString(side); +} + +}; // namespace Metadata diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 9663026cd..50fb45d77 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -38,16 +38,15 @@ #include "Mod.h" #include -#include #include #include #include #include "MTPixmapCache.h" #include "MetadataHandler.h" +#include "Resource.h" #include "Version.h" #include "minecraft/mod/ModDetails.h" -#include "minecraft/mod/Resource.h" #include "minecraft/mod/tasks/LocalModParseTask.h" #include "modplatform/ModIndex.h" @@ -56,24 +55,6 @@ Mod::Mod(const QFileInfo& file) : Resource(file), m_local_details() m_enabled = (file.suffix() != "disabled"); } -Mod::Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata) : Mod(mods_dir.absoluteFilePath(metadata.filename)) -{ - m_name = metadata.name; - m_local_details.metadata = std::make_shared(std::move(metadata)); -} - -void Mod::setStatus(ModStatus status) -{ - m_local_details.status = status; -} -void Mod::setMetadata(std::shared_ptr&& metadata) -{ - if (status() == ModStatus::NoMetadata) - setStatus(ModStatus::Installed); - - m_local_details.metadata = metadata; -} - void Mod::setDetails(const ModDetails& details) { m_local_details = details; @@ -101,33 +82,28 @@ int Mod::compare(const Resource& other, SortType type) const return -1; break; } - case SortType::PROVIDER: { - return QString::compare(provider().value_or("Unknown"), cast_other->provider().value_or("Unknown"), Qt::CaseInsensitive); - } case SortType::SIDE: { - if (side() > cast_other->side()) - return 1; - else if (side() < cast_other->side()) - return -1; - break; - } - case SortType::LOADERS: { - if (loaders() > cast_other->loaders()) - return 1; - else if (loaders() < cast_other->loaders()) - return -1; + auto compare_result = QString::compare(side(), cast_other->side(), Qt::CaseInsensitive); + if (compare_result != 0) + return compare_result; break; } case SortType::MC_VERSIONS: { - auto thisVersion = mcVersions().join(","); - auto otherVersion = cast_other->mcVersions().join(","); - return QString::compare(thisVersion, otherVersion, Qt::CaseInsensitive); + auto compare_result = QString::compare(mcVersions(), cast_other->mcVersions(), Qt::CaseInsensitive); + if (compare_result != 0) + return compare_result; + break; + } + case SortType::LOADERS: { + auto compare_result = QString::compare(loaders(), cast_other->loaders(), Qt::CaseInsensitive); + if (compare_result != 0) + return compare_result; + break; } case SortType::RELEASE_TYPE: { - if (releaseType() > cast_other->releaseType()) - return 1; - else if (releaseType() < cast_other->releaseType()) - return -1; + auto compare_result = QString::compare(releaseType(), cast_other->releaseType(), Qt::CaseInsensitive); + if (compare_result != 0) + return compare_result; break; } } @@ -148,28 +124,6 @@ bool Mod::applyFilter(QRegularExpression filter) const return Resource::applyFilter(filter); } -auto Mod::destroy(QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool -{ - if (!preserve_metadata) { - qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name()); - - destroyMetadata(index_dir); - } - - return Resource::destroy(attempt_trash); -} - -void Mod::destroyMetadata(QDir& index_dir) -{ - if (metadata()) { - Metadata::remove(index_dir, metadata()->slug); - } else { - auto n = name(); - Metadata::remove(index_dir, n); - } - m_local_details.metadata = nullptr; -} - auto Mod::details() const -> const ModDetails& { return m_local_details; @@ -181,10 +135,7 @@ auto Mod::name() const -> QString if (!d_name.isEmpty()) return d_name; - if (metadata()) - return metadata()->name; - - return m_name; + return Resource::name(); } auto Mod::version() const -> QString @@ -192,16 +143,52 @@ auto Mod::version() const -> QString return details().version; } -auto Mod::homeurl() const -> QString +auto Mod::homepage() const -> QString { - return details().homeurl; + QString metaUrl = Resource::homepage(); + + if (metaUrl.isEmpty()) + return details().homeurl; + else + return metaUrl; } -auto Mod::metaurl() const -> QString +auto Mod::loaders() const -> QString { - if (metadata() == nullptr) - return homeurl(); - return ModPlatform::getMetaURL(metadata()->provider, metadata()->project_id); + if (metadata()) { + QStringList loaders; + auto modLoaders = metadata()->loaders; + for (auto loader : ModPlatform::modLoaderTypesToList(modLoaders)) { + loaders << getModLoaderAsString(loader); + } + return loaders.join(", "); + } + + return {}; +} + +auto Mod::side() const -> QString +{ + if (metadata()) + return Metadata::modSideToString(metadata()->side); + + return Metadata::modSideToString(Metadata::ModSide::UniversalSide); +} + +auto Mod::mcVersions() const -> QString +{ + if (metadata()) + return metadata()->mcVersions.join(", "); + + return {}; +} + +auto Mod::releaseType() const -> QString +{ + if (metadata()) + return metadata()->releaseType.toString(); + + return ModPlatform::IndexedVersionType().toString(); } auto Mod::description() const -> QString @@ -214,73 +201,17 @@ auto Mod::authors() const -> QStringList return details().authors; } -auto Mod::status() const -> ModStatus -{ - return details().status; -} - -auto Mod::metadata() -> std::shared_ptr -{ - return m_local_details.metadata; -} - -auto Mod::metadata() const -> const std::shared_ptr -{ - return m_local_details.metadata; -} - void Mod::finishResolvingWithDetails(ModDetails&& details) { m_is_resolving = false; m_is_resolved = true; - std::shared_ptr metadata = details.metadata; - if (details.status == ModStatus::Unknown) - details.status = m_local_details.status; - m_local_details = std::move(details); - if (metadata) - setMetadata(std::move(metadata)); if (!iconPath().isEmpty()) { m_packImageCacheKey.wasReadAttempt = false; } } -auto Mod::provider() const -> std::optional -{ - if (metadata()) - return ModPlatform::ProviderCapabilities::readableName(metadata()->provider); - return {}; -} - -auto Mod::side() const -> Metadata::ModSide -{ - if (metadata()) - return metadata()->side; - return Metadata::ModSide::UniversalSide; -} - -auto Mod::releaseType() const -> ModPlatform::IndexedVersionType -{ - if (metadata()) - return metadata()->releaseType; - return ModPlatform::IndexedVersionType::VersionType::Unknown; -} - -auto Mod::loaders() const -> ModPlatform::ModLoaderTypes -{ - if (metadata()) - return metadata()->loaders; - return {}; -} - -auto Mod::mcVersions() const -> QStringList -{ - if (metadata()) - return metadata()->mcVersions; - return {}; -} - auto Mod::licenses() const -> const QList& { return details().licenses; diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index a0d9797ed..8a352c66c 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -48,7 +48,6 @@ #include "ModDetails.h" #include "Resource.h" -#include "modplatform/ModIndex.h" class Mod : public Resource { Q_OBJECT @@ -58,24 +57,20 @@ class Mod : public Resource { Mod() = default; Mod(const QFileInfo& file); - Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata); Mod(QString file_path) : Mod(QFileInfo(file_path)) {} auto details() const -> const ModDetails&; auto name() const -> QString override; auto version() const -> QString; - auto homeurl() const -> QString; + auto homepage() const -> QString override; auto description() const -> QString; auto authors() const -> QStringList; - auto status() const -> ModStatus; - auto provider() const -> std::optional; auto licenses() const -> const QList&; auto issueTracker() const -> QString; - auto metaurl() const -> QString; - auto side() const -> Metadata::ModSide; - auto loaders() const -> ModPlatform::ModLoaderTypes; - auto mcVersions() const -> QStringList; - auto releaseType() const -> ModPlatform::IndexedVersionType; + auto side() const -> QString; + auto loaders() const -> QString; + auto mcVersions() const -> QString; + auto releaseType() const -> QString; /** Get the intneral path to the mod's icon file*/ QString iconPath() const { return m_local_details.icon_file; } @@ -84,17 +79,11 @@ class Mod : public Resource { /** Thread-safe. */ QPixmap setIcon(QImage new_image) const; - auto metadata() -> std::shared_ptr; - auto metadata() const -> const std::shared_ptr; - - void setStatus(ModStatus status); - void setMetadata(std::shared_ptr&& metadata); - void setMetadata(const Metadata::ModStruct& metadata) { setMetadata(std::make_shared(metadata)); } void setDetails(const ModDetails& details); bool valid() const override; - [[nodiscard]] int compare(Resource const& other, SortType type) const override; + [[nodiscard]] int compare(const Resource & other, SortType type) const override; [[nodiscard]] bool applyFilter(QRegularExpression filter) const override; // Delete all the files of this mod diff --git a/launcher/minecraft/mod/ModDetails.h b/launcher/minecraft/mod/ModDetails.h index a00d5a24b..9195c0368 100644 --- a/launcher/minecraft/mod/ModDetails.h +++ b/launcher/minecraft/mod/ModDetails.h @@ -43,13 +43,6 @@ #include "minecraft/mod/MetadataHandler.h" -enum class ModStatus { - Installed, // Both JAR and Metadata are present - NotInstalled, // Only the Metadata is present - NoMetadata, // Only the JAR is present - Unknown, // Default status -}; - struct ModLicense { QString name = {}; QString id = {}; @@ -149,12 +142,6 @@ struct ModDetails { /* Path of mod logo */ QString icon_file = {}; - /* Installation status of the mod */ - ModStatus status = ModStatus::Unknown; - - /* Metadata information, if any */ - std::shared_ptr metadata = nullptr; - ModDetails() = default; /** Metadata should be handled manually to properly set the mod status. */ @@ -169,40 +156,9 @@ struct ModDetails { , issue_tracker(other.issue_tracker) , licenses(other.licenses) , icon_file(other.icon_file) - , status(other.status) {} - ModDetails& operator=(const ModDetails& other) - { - this->mod_id = other.mod_id; - this->name = other.name; - this->version = other.version; - this->mcversion = other.mcversion; - this->homeurl = other.homeurl; - this->description = other.description; - this->authors = other.authors; - this->issue_tracker = other.issue_tracker; - this->licenses = other.licenses; - this->icon_file = other.icon_file; - this->status = other.status; + ModDetails& operator=(const ModDetails& other) = default; - return *this; - } - - ModDetails& operator=(const ModDetails&& other) - { - this->mod_id = other.mod_id; - this->name = other.name; - this->version = other.version; - this->mcversion = other.mcversion; - this->homeurl = other.homeurl; - this->description = other.description; - this->authors = other.authors; - this->issue_tracker = other.issue_tracker; - this->licenses = other.licenses; - this->icon_file = other.icon_file; - this->status = other.status; - - return *this; - } + ModDetails& operator=(ModDetails&& other) = default; }; diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 5e4fe7f11..027f3d4ca 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -51,18 +51,10 @@ #include "Application.h" -#include "Json.h" -#include "minecraft/mod/MetadataHandler.h" -#include "minecraft/mod/Resource.h" #include "minecraft/mod/tasks/LocalModParseTask.h" -#include "minecraft/mod/tasks/LocalModUpdateTask.h" -#include "minecraft/mod/tasks/ModFolderLoadTask.h" -#include "modplatform/ModIndex.h" -#include "modplatform/flame/FlameAPI.h" -#include "modplatform/flame/FlameModIndex.h" -ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir) - : ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed) +ModFolderModel::ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) + : ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent) { m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Size", "Side", "Loaders", "Minecraft Versions", "Release Type" }); @@ -92,7 +84,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const case NameColumn: return m_resources[row]->name(); case VersionColumn: { - switch (m_resources[row]->type()) { + switch (at(row).type()) { case ResourceType::FOLDER: return tr("Folder"); case ResourceType::SINGLEFILE: @@ -100,64 +92,50 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const default: break; } - return at(row)->version(); + return at(row).version(); } case DateColumn: - return m_resources[row]->dateTimeChanged(); + return at(row).dateTimeChanged(); case ProviderColumn: { - auto provider = at(row)->provider(); - if (!provider.has_value()) { - //: Unknown mod provider (i.e. not Modrinth, CurseForge, etc...) - return tr("Unknown"); - } - - return provider.value(); + return at(row).provider(); } case SideColumn: { - return Metadata::modSideToString(at(row)->side()); + return at(row).side(); } case LoadersColumn: { - QStringList loaders; - auto modLoaders = at(row)->loaders(); - for (auto loader : { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Cauldron, ModPlatform::LiteLoader, - ModPlatform::Fabric, ModPlatform::Quilt }) { - if (modLoaders & loader) { - loaders << getModLoaderAsString(loader); - } - } - return loaders.join(", "); + return at(row).loaders(); } case McVersionsColumn: { - return at(row)->mcVersions().join(", "); + return at(row).mcVersions(); } case ReleaseTypeColumn: { - return at(row)->releaseType().toString(); + return at(row).releaseType(); } case SizeColumn: - return m_resources[row]->sizeStr(); + return at(row).sizeStr(); default: return QVariant(); } case Qt::ToolTipRole: if (column == NameColumn) { - if (at(row)->isSymLinkUnder(instDirPath())) { + if (at(row).isSymLinkUnder(instDirPath())) { return m_resources[row]->internal_id() + tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." "\nCanonical Path: %1") - .arg(at(row)->fileinfo().canonicalFilePath()); + .arg(at(row).fileinfo().canonicalFilePath()); } - if (at(row)->isMoreThanOneHardLink()) { + if (at(row).isMoreThanOneHardLink()) { return m_resources[row]->internal_id() + tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original."); } } return m_resources[row]->internal_id(); case Qt::DecorationRole: { - if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink())) + if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink())) return APPLICATION->getThemedIcon("status-yellow"); if (column == ImageColumn) { - return at(row)->icon({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding); + return at(row).icon({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding); } return {}; } @@ -169,7 +147,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const case Qt::CheckStateRole: switch (column) { case ActiveColumn: - return at(row)->enabled() ? Qt::Checked : Qt::Unchecked; + return at(row).enabled() ? Qt::Checked : Qt::Unchecked; default: return QVariant(); } @@ -210,7 +188,7 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio case DateColumn: return tr("The date and time this mod was last changed (or added)."); case ProviderColumn: - return tr("Where the mod was downloaded from."); + return tr("The source provider of the mod."); case SideColumn: return tr("On what environment the mod is running."); case LoadersColumn: @@ -235,133 +213,16 @@ int ModFolderModel::columnCount(const QModelIndex& parent) const return parent.isValid() ? 0 : NUM_COLUMNS; } -Task* ModFolderModel::createUpdateTask() -{ - auto index_dir = indexDir(); - auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load); - m_first_folder_load = false; - return task; -} - Task* ModFolderModel::createParseTask(Resource& resource) { return new LocalModParseTask(m_next_resolution_ticket, resource.type(), resource.fileinfo()); } -bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadata) -{ - for (auto mod : allMods()) { - if (mod->getOriginalFileName() == filename) { - auto index_dir = indexDir(); - mod->destroy(index_dir, preserve_metadata, false); - - update(); - - return true; - } - } - - return false; -} - -bool ModFolderModel::deleteMods(const QModelIndexList& indexes) -{ - if (indexes.isEmpty()) - return true; - - for (auto i : indexes) { - if (i.column() != 0) { - continue; - } - auto m = at(i.row()); - auto index_dir = indexDir(); - m->destroy(index_dir); - } - - update(); - - return true; -} - -bool ModFolderModel::deleteModsMetadata(const QModelIndexList& indexes) -{ - if (indexes.isEmpty()) - return true; - - for (auto i : indexes) { - if (i.column() != 0) { - continue; - } - auto m = at(i.row()); - auto index_dir = indexDir(); - m->destroyMetadata(index_dir); - } - - update(); - - return true; -} - bool ModFolderModel::isValid() { return m_dir.exists() && m_dir.isReadable(); } -bool ModFolderModel::startWatching() -{ - // Remove orphaned metadata next time - m_first_folder_load = true; - return ResourceFolderModel::startWatching({ m_dir.absolutePath(), indexDir().absolutePath() }); -} - -bool ModFolderModel::stopWatching() -{ - return ResourceFolderModel::stopWatching({ m_dir.absolutePath(), indexDir().absolutePath() }); -} - -auto ModFolderModel::selectedMods(QModelIndexList& indexes) -> QList -{ - QList selected_resources; - for (auto i : indexes) { - if (i.column() != 0) - continue; - - selected_resources.push_back(at(i.row())); - } - return selected_resources; -} - -auto ModFolderModel::allMods() -> QList -{ - QList mods; - - for (auto& res : qAsConst(m_resources)) { - mods.append(static_cast(res.get())); - } - - return mods; -} - -void ModFolderModel::onUpdateSucceeded() -{ - auto update_results = static_cast(m_current_update_task.get())->result(); - - auto& new_mods = update_results->mods; - -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) - auto current_list = m_resources_index.keys(); - QSet current_set(current_list.begin(), current_list.end()); - - auto new_list = new_mods.keys(); - QSet new_set(new_list.begin(), new_list.end()); -#else - QSet current_set(m_resources_index.keys().toSet()); - QSet new_set(new_mods.keys().toSet()); -#endif - - applyUpdates(current_set, new_set, new_mods); -} - void ModFolderModel::onParseSucceeded(int ticket, QString mod_id) { auto iter = m_active_parse_tasks.constFind(ticket); @@ -379,51 +240,7 @@ void ModFolderModel::onParseSucceeded(int ticket, QString mod_id) auto result = cast_task->result(); if (result && resource) - resource->finishResolvingWithDetails(std::move(result->details)); + static_cast(resource.get())->finishResolvingWithDetails(std::move(result->details)); emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1)); } - -static const FlameAPI flameAPI; -bool ModFolderModel::installMod(QString file_path, ModPlatform::IndexedVersion& vers) -{ - if (vers.addonId.isValid()) { - ModPlatform::IndexedPack pack{ - vers.addonId, - ModPlatform::ResourceProvider::FLAME, - }; - - QEventLoop loop; - - auto response = std::make_shared(); - auto job = flameAPI.getProject(vers.addonId.toString(), response); - - QObject::connect(job.get(), &Task::failed, [&loop] { loop.quit(); }); - QObject::connect(job.get(), &Task::aborted, &loop, &QEventLoop::quit); - QObject::connect(job.get(), &Task::succeeded, [response, this, &vers, &loop, &pack] { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qDebug() << *response; - return; - } - try { - auto obj = Json::requireObject(Json::requireObject(doc), "data"); - FlameMod::loadIndexedPack(pack, obj); - } catch (const JSONValidationError& e) { - qDebug() << doc; - qWarning() << "Error while reading mod info: " << e.cause(); - } - LocalModUpdateTask update_metadata(indexDir(), pack, vers); - QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit); - update_metadata.start(); - }); - - job->start(); - - loop.exec(); - } - return ResourceFolderModel::installResource(file_path); -} diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index db5edf66e..42868dc91 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -47,11 +47,6 @@ #include "Mod.h" #include "ResourceFolderModel.h" -#include "minecraft/mod/tasks/LocalModParseTask.h" -#include "minecraft/mod/tasks/ModFolderLoadTask.h" -#include "modplatform/ModIndex.h" - -class LegacyInstance; class BaseInstance; class QFileSystemWatcher; @@ -76,8 +71,7 @@ class ModFolderModel : public ResourceFolderModel { ReleaseTypeColumn, NUM_COLUMNS }; - enum ModStatusAction { Disable, Enable, Toggle }; - ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed = false, bool create_dir = true); + ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); virtual QString id() const override { return "mods"; } @@ -86,34 +80,13 @@ class ModFolderModel : public ResourceFolderModel { QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; int columnCount(const QModelIndex& parent) const override; - [[nodiscard]] Task* createUpdateTask() override; + [[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new Mod(file); } [[nodiscard]] Task* createParseTask(Resource&) override; - bool installMod(QString file_path) { return ResourceFolderModel::installResource(file_path); } - bool installMod(QString file_path, ModPlatform::IndexedVersion& vers); - bool uninstallMod(const QString& filename, bool preserve_metadata = false); - - /// Deletes all the selected mods - bool deleteMods(const QModelIndexList& indexes); - bool deleteModsMetadata(const QModelIndexList& indexes); - bool isValid(); - bool startWatching() override; - bool stopWatching() override; - - QDir indexDir() { return { QString("%1/.index").arg(dir().absolutePath()) }; } - - auto selectedMods(QModelIndexList& indexes) -> QList; - auto allMods() -> QList; - RESOURCE_HELPERS(Mod) private slots: - void onUpdateSucceeded() override; void onParseSucceeded(int ticket, QString resource_id) override; - - protected: - bool m_is_indexed; - bool m_first_folder_load = true; }; diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index c52d76f1f..d1a7b8f9c 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -21,7 +21,7 @@ void Resource::setFile(QFileInfo file_info) parseFile(); } -std::tuple calculateFileSize(const QFileInfo& file) +static std::tuple calculateFileSize(const QFileInfo& file) { if (file.isDir()) { auto dir = QDir(file.absoluteFilePath()); @@ -72,6 +72,14 @@ void Resource::parseFile() m_changed_date_time = m_file_info.lastModified(); } +auto Resource::name() const -> QString +{ + if (metadata()) + return metadata()->name; + + return m_name; +} + static void removeThePrefix(QString& string) { QRegularExpression regex(QStringLiteral("^(?:the|teh) +"), QRegularExpression::CaseInsensitiveOption); @@ -79,6 +87,30 @@ static void removeThePrefix(QString& string) string = string.trimmed(); } +auto Resource::provider() const -> QString +{ + if (metadata()) + return ModPlatform::ProviderCapabilities::readableName(metadata()->provider); + + return tr("Unknown"); +} + +auto Resource::homepage() const -> QString +{ + if (metadata()) + return ModPlatform::getMetaURL(metadata()->provider, metadata()->project_id); + + return {}; +} + +void Resource::setMetadata(std::shared_ptr&& metadata) +{ + if (status() == ResourceStatus::NO_METADATA) + setStatus(ResourceStatus::INSTALLED); + + m_metadata = metadata; +} + int Resource::compare(const Resource& other, SortType type) const { switch (type) { @@ -93,6 +125,7 @@ int Resource::compare(const Resource& other, SortType type) const QString this_name{ name() }; QString other_name{ other.name() }; + // TODO do we need this? it could result in 0 being returned removeThePrefix(this_name); removeThePrefix(other_name); @@ -118,6 +151,12 @@ int Resource::compare(const Resource& other, SortType type) const return -1; break; } + case SortType::PROVIDER: { + auto compare_result = QString::compare(provider(), other.provider(), Qt::CaseInsensitive); + if (compare_result != 0) + return compare_result; + break; + } } return 0; @@ -174,10 +213,27 @@ bool Resource::enable(EnableAction action) return true; } -bool Resource::destroy(bool attemptTrash) +auto Resource::destroy(const QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool { m_type = ResourceType::UNKNOWN; - return (attemptTrash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath()); + + if (!preserve_metadata) { + qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name()); + destroyMetadata(index_dir); + } + + return (attempt_trash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath()); +} + +auto Resource::destroyMetadata(const QDir& index_dir) -> void +{ + if (metadata()) { + Metadata::remove(index_dir, metadata()->slug); + } else { + auto n = name(); + Metadata::remove(index_dir, n); + } + m_metadata = nullptr; } bool Resource::isSymLinkUnder(const QString& instPath) const diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index c99dab8db..42463fe8f 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -40,6 +40,7 @@ #include #include +#include "MetadataHandler.h" #include "QObjectPtr.h" enum class ResourceType { @@ -50,7 +51,14 @@ enum class ResourceType { LITEMOD, //!< The resource is a litemod }; -enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER, SIZE, SIDE, LOADERS, MC_VERSIONS, RELEASE_TYPE }; +enum class ResourceStatus { + INSTALLED, // Both JAR and Metadata are present + NOT_INSTALLED, // Only the Metadata is present + NO_METADATA, // Only the JAR is present + UNKNOWN, // Default status +}; + +enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER, SIZE, SIDE, MC_VERSIONS, LOADERS, RELEASE_TYPE }; enum class EnableAction { ENABLE, DISABLE, TOGGLE }; @@ -84,9 +92,19 @@ class Resource : public QObject { [[nodiscard]] QString sizeStr() const { return m_size_str; } [[nodiscard]] qint64 sizeInfo() const { return m_size_info; } - [[nodiscard]] virtual auto name() const -> QString { return m_name; } + [[nodiscard]] virtual auto name() const -> QString; [[nodiscard]] virtual bool valid() const { return m_type != ResourceType::UNKNOWN; } + [[nodiscard]] auto status() const -> ResourceStatus { return m_status; }; + [[nodiscard]] auto metadata() -> std::shared_ptr { return m_metadata; } + [[nodiscard]] auto metadata() const -> std::shared_ptr { return m_metadata; } + [[nodiscard]] auto provider() const -> QString; + [[nodiscard]] virtual auto homepage() const -> QString; + + void setStatus(ResourceStatus status) { m_status = status; } + void setMetadata(std::shared_ptr&& metadata); + void setMetadata(const Metadata::ModStruct& metadata) { setMetadata(std::make_shared(metadata)); } + /** Compares two Resources, for sorting purposes, considering a ascending order, returning: * > 0: 'this' comes after 'other' * = 0: 'this' is equal to 'other' @@ -117,7 +135,9 @@ class Resource : public QObject { } // Delete all files of this resource. - bool destroy(bool attemptTrash = true); + auto destroy(const QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool; + // Delete the metadata only. + auto destroyMetadata(const QDir& index_dir) -> void; [[nodiscard]] auto isSymLink() const -> bool { return m_file_info.isSymLink(); } @@ -146,6 +166,11 @@ class Resource : public QObject { /* The type of file we're dealing with. */ ResourceType m_type = ResourceType::UNKNOWN; + /* Installation status of the resource. */ + ResourceStatus m_status = ResourceStatus::UNKNOWN; + + std::shared_ptr m_metadata = nullptr; + /* Whether the resource is enabled (e.g. shows up in the game) or not. */ bool m_enabled = true; diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index ce3e16bce..adeb2e422 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -11,20 +11,23 @@ #include #include #include +#include #include "Application.h" #include "FileSystem.h" -#include "QVariantUtils.h" -#include "StringUtils.h" -#include "minecraft/mod/tasks/BasicFolderLoadTask.h" +#include "minecraft/mod/tasks/ResourceFolderLoadTask.h" +#include "Json.h" +#include "minecraft/mod/tasks/LocalResourceUpdateTask.h" +#include "modplatform/flame/FlameAPI.h" +#include "modplatform/flame/FlameModIndex.h" #include "settings/Setting.h" #include "tasks/Task.h" #include "ui/dialogs/CustomMessageBox.h" -ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObject* parent, bool create_dir) - : QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this) +ResourceFolderModel::ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) + : QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this), m_is_indexed(is_indexed) { if (create_dir) { FS::ensureFolderPathExists(m_dir.absolutePath()); @@ -48,6 +51,9 @@ ResourceFolderModel::~ResourceFolderModel() bool ResourceFolderModel::startWatching(const QStringList& paths) { + // Remove orphaned metadata next time + m_first_folder_load = true; + if (m_is_watching) return false; @@ -158,11 +164,55 @@ bool ResourceFolderModel::installResource(QString original_path) return false; } -bool ResourceFolderModel::uninstallResource(QString file_name) +bool ResourceFolderModel::installResourceWithFlameMetadata(QString path, ModPlatform::IndexedVersion& vers) +{ + if (vers.addonId.isValid()) { + ModPlatform::IndexedPack pack{ + vers.addonId, + ModPlatform::ResourceProvider::FLAME, + }; + + QEventLoop loop; + + auto response = std::make_shared(); + auto job = FlameAPI().getProject(vers.addonId.toString(), response); + + QObject::connect(job.get(), &Task::failed, [&loop] { loop.quit(); }); + QObject::connect(job.get(), &Task::aborted, &loop, &QEventLoop::quit); + QObject::connect(job.get(), &Task::succeeded, [response, this, &vers, &loop, &pack] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qDebug() << *response; + return; + } + try { + auto obj = Json::requireObject(Json::requireObject(doc), "data"); + FlameMod::loadIndexedPack(pack, obj); + } catch (const JSONValidationError& e) { + qDebug() << doc; + qWarning() << "Error while reading mod info: " << e.cause(); + } + LocalResourceUpdateTask update_metadata(indexDir(), pack, vers); + QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit); + update_metadata.start(); + }); + + job->start(); + + loop.exec(); + } + + return installResource(std::move(path)); +} + +bool ResourceFolderModel::uninstallResource(QString file_name, bool preserve_metadata) { for (auto& resource : m_resources) { if (resource->fileinfo().fileName() == file_name) { - auto res = resource->destroy(false); + auto res = resource->destroy(indexDir(), preserve_metadata, false); update(); @@ -178,13 +228,11 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes) return true; for (auto i : indexes) { - if (i.column() != 0) { + if (i.column() != 0) continue; - } auto& resource = m_resources.at(i.row()); - - resource->destroy(); + resource->destroy(indexDir()); } update(); @@ -192,6 +240,22 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes) return true; } +void ResourceFolderModel::deleteMetadata(const QModelIndexList& indexes) +{ + if (indexes.isEmpty()) + return; + + for (auto i : indexes) { + if (i.column() != 0) + continue; + + auto& resource = m_resources.at(i.row()); + resource->destroyMetadata(indexDir()); + } + + update(); +} + bool ResourceFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAction action) { if (indexes.isEmpty()) @@ -245,7 +309,7 @@ bool ResourceFolderModel::update() connect(m_current_update_task.get(), &Task::failed, this, &ResourceFolderModel::onUpdateFailed, Qt::ConnectionType::QueuedConnection); connect( m_current_update_task.get(), &Task::finished, this, - [=] { + [this] { m_current_update_task.reset(); if (m_scheduled_update) { m_scheduled_update = false; @@ -261,7 +325,7 @@ bool ResourceFolderModel::update() return true; } -void ResourceFolderModel::resolveResource(Resource* res) +void ResourceFolderModel::resolveResource(Resource::Ptr res) { if (!res->shouldResolve()) { return; @@ -277,11 +341,14 @@ void ResourceFolderModel::resolveResource(Resource* res) m_active_parse_tasks.insert(ticket, task); connect( - task.get(), &Task::succeeded, this, [=] { onParseSucceeded(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection); - connect(task.get(), &Task::failed, this, [=] { onParseFailed(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection); + task.get(), &Task::succeeded, this, [this, ticket, res] { onParseSucceeded(ticket, res->internal_id()); }, + Qt::ConnectionType::QueuedConnection); + connect( + task.get(), &Task::failed, this, [this, ticket, res] { onParseFailed(ticket, res->internal_id()); }, + Qt::ConnectionType::QueuedConnection); connect( task.get(), &Task::finished, this, - [=] { + [this, ticket] { m_active_parse_tasks.remove(ticket); emit parseFinished(); }, @@ -296,7 +363,7 @@ void ResourceFolderModel::resolveResource(Resource* res) void ResourceFolderModel::onUpdateSucceeded() { - auto update_results = static_cast(m_current_update_task.get())->result(); + auto update_results = static_cast(m_current_update_task.get())->result(); auto& new_resources = update_results->resources; @@ -317,7 +384,7 @@ void ResourceFolderModel::onUpdateSucceeded() void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id) { auto iter = m_active_parse_tasks.constFind(ticket); - if (iter == m_active_parse_tasks.constEnd()) + if (iter == m_active_parse_tasks.constEnd() || !m_resources_index.contains(resource_id)) return; int row = m_resources_index[resource_id]; @@ -326,7 +393,11 @@ void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id) Task* ResourceFolderModel::createUpdateTask() { - return new BasicFolderLoadTask(m_dir); + auto index_dir = indexDir(); + auto task = new ResourceFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load, + [this](const QFileInfo& file) { return createResource(file); }); + m_first_folder_load = false; + return task; } bool ResourceFolderModel::hasPendingParseTasks() const @@ -416,6 +487,8 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const return m_resources[row]->name(); case DateColumn: return m_resources[row]->dateTimeChanged(); + case ProviderColumn: + return m_resources[row]->provider(); case SizeColumn: return m_resources[row]->sizeStr(); default: @@ -487,22 +560,23 @@ QVariant ResourceFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien case ActiveColumn: case NameColumn: case DateColumn: + case ProviderColumn: case SizeColumn: return columnNames().at(section); default: return {}; } case Qt::ToolTipRole: { + //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. switch (section) { case ActiveColumn: - //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. return tr("Is the resource enabled?"); case NameColumn: - //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. return tr("The name of the resource."); case DateColumn: - //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. return tr("The date and time this resource was last changed (or added)."); + case ProviderColumn: + return tr("The source provider of the resource."); case SizeColumn: return tr("The size of the resource."); default: @@ -629,7 +703,7 @@ QString ResourceFolderModel::instDirPath() const void ResourceFolderModel::onParseFailed(int ticket, QString resource_id) { auto iter = m_active_parse_tasks.constFind(ticket); - if (iter == m_active_parse_tasks.constEnd()) + if (iter == m_active_parse_tasks.constEnd() || !m_resources_index.contains(resource_id)) return; auto removed_index = m_resources_index[resource_id]; @@ -648,3 +722,126 @@ void ResourceFolderModel::onParseFailed(int ticket, QString resource_id) } endRemoveRows(); } + +void ResourceFolderModel::applyUpdates(QSet& current_set, QSet& new_set, QMap& new_resources) +{ + // see if the kept resources changed in some way + { + QSet kept_set = current_set; + kept_set.intersect(new_set); + + for (auto const& kept : kept_set) { + auto row_it = m_resources_index.constFind(kept); + Q_ASSERT(row_it != m_resources_index.constEnd()); + auto row = row_it.value(); + + auto& new_resource = new_resources[kept]; + auto const& current_resource = m_resources.at(row); + + if (new_resource->dateTimeChanged() == current_resource->dateTimeChanged()) { + // no significant change, ignore... + continue; + } + + // If the resource is resolving, but something about it changed, we don't want to + // continue the resolving. + if (current_resource->isResolving()) { + auto ticket = current_resource->resolutionTicket(); + if (m_active_parse_tasks.contains(ticket)) { + auto task = (*m_active_parse_tasks.find(ticket)).get(); + task->abort(); + } + } + + m_resources[row].reset(new_resource); + resolveResource(m_resources.at(row)); + emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); + } + } + + // remove resources no longer present + { + QSet removed_set = current_set; + removed_set.subtract(new_set); + + QList removed_rows; + for (auto& removed : removed_set) + removed_rows.append(m_resources_index[removed]); + + std::sort(removed_rows.begin(), removed_rows.end(), std::greater()); + + for (auto& removed_index : removed_rows) { + auto removed_it = m_resources.begin() + removed_index; + + Q_ASSERT(removed_it != m_resources.end()); + + if ((*removed_it)->isResolving()) { + auto ticket = (*removed_it)->resolutionTicket(); + if (m_active_parse_tasks.contains(ticket)) { + auto task = (*m_active_parse_tasks.find(ticket)).get(); + task->abort(); + } + } + + beginRemoveRows(QModelIndex(), removed_index, removed_index); + m_resources.erase(removed_it); + endRemoveRows(); + } + } + + // add new resources to the end + { + QSet added_set = new_set; + added_set.subtract(current_set); + + // When you have a Qt build with assertions turned on, proceeding here will abort the application + if (added_set.size() > 0) { + beginInsertRows(QModelIndex(), static_cast(m_resources.size()), + static_cast(m_resources.size() + added_set.size() - 1)); + + for (auto& added : added_set) { + auto res = new_resources[added]; + m_resources.append(res); + resolveResource(m_resources.last()); + } + + endInsertRows(); + } + } + + // update index + { + m_resources_index.clear(); + int idx = 0; + for (auto const& mod : qAsConst(m_resources)) { + m_resources_index[mod->internal_id()] = idx; + idx++; + } + } +} +Resource::Ptr ResourceFolderModel::find(QString id) +{ + auto iter = + std::find_if(m_resources.constBegin(), m_resources.constEnd(), [&](Resource::Ptr const& r) { return r->internal_id() == id; }); + if (iter == m_resources.constEnd()) + return nullptr; + return *iter; +} +QList ResourceFolderModel::allResources() +{ + QList result; + result.reserve(m_resources.size()); + for (const Resource ::Ptr& resource : m_resources) + result.append((resource.get())); + return result; +} +QList ResourceFolderModel::selectedResources(const QModelIndexList& indexes) +{ + QList result; + for (const QModelIndex& index : indexes) { + if (index.column() != 0) + continue; + result.append(&at(index.row())); + } + return result; +} diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index ca919d3e3..ee26a74bc 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -19,6 +19,38 @@ class QSortFilterProxyModel; +/* A macro to define useful functions to handle Resource* -> T* more easily on derived classes */ +#define RESOURCE_HELPERS(T) \ + [[nodiscard]] T& at(int index) \ + { \ + return *static_cast(m_resources[index].get()); \ + } \ + [[nodiscard]] const T& at(int index) const \ + { \ + return *static_cast(m_resources.at(index).get()); \ + } \ + QList selected##T##s(const QModelIndexList& indexes) \ + { \ + QList result; \ + for (const QModelIndex& index : indexes) { \ + if (index.column() != 0) \ + continue; \ + \ + result.append(&at(index.row())); \ + } \ + return result; \ + } \ + QList all##T##s() \ + { \ + QList result; \ + result.reserve(m_resources.size()); \ + \ + for (const Resource::Ptr& resource : m_resources) \ + result.append(static_cast(resource.get())); \ + \ + return result; \ + } + /** A basic model for external resources. * * This model manages a list of resources. As such, external users of such resources do not own them, @@ -29,7 +61,7 @@ class QSortFilterProxyModel; class ResourceFolderModel : public QAbstractListModel { Q_OBJECT public: - ResourceFolderModel(QDir, BaseInstance* instance, QObject* parent = nullptr, bool create_dir = true); + ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); ~ResourceFolderModel() override; virtual QString id() const { return "resource"; } @@ -49,8 +81,10 @@ class ResourceFolderModel : public QAbstractListModel { bool stopWatching(const QStringList& paths); /* Helper methods for subclasses, using a predetermined list of paths. */ - virtual bool startWatching() { return startWatching({ m_dir.absolutePath() }); } - virtual bool stopWatching() { return stopWatching({ m_dir.absolutePath() }); } + virtual bool startWatching() { return startWatching({ indexDir().absolutePath(), m_dir.absolutePath() }); } + virtual bool stopWatching() { return stopWatching({ indexDir().absolutePath(), m_dir.absolutePath() }); } + + QDir indexDir() { return { QString("%1/.index").arg(dir().absolutePath()) }; } /** Given a path in the system, install that resource, moving it to its place in the * instance file hierarchy. @@ -59,12 +93,15 @@ class ResourceFolderModel : public QAbstractListModel { */ virtual bool installResource(QString path); + virtual bool installResourceWithFlameMetadata(QString path, ModPlatform::IndexedVersion& vers); + /** Uninstall (i.e. remove all data about it) a resource, given its file name. * * Returns whether the removal was successful. */ - virtual bool uninstallResource(QString file_name); + virtual bool uninstallResource(QString file_name, bool preserve_metadata = false); virtual bool deleteResources(const QModelIndexList&); + virtual void deleteMetadata(const QModelIndexList&); /** Applies the given 'action' to the resources in 'indexes'. * @@ -76,13 +113,17 @@ class ResourceFolderModel : public QAbstractListModel { virtual bool update(); /** Creates a new parse task, if needed, for 'res' and start it.*/ - virtual void resolveResource(Resource* res); + virtual void resolveResource(Resource::Ptr res); [[nodiscard]] qsizetype size() const { return m_resources.size(); } [[nodiscard]] bool empty() const { return size() == 0; } - [[nodiscard]] Resource& at(int index) { return *m_resources.at(index); } - [[nodiscard]] Resource const& at(int index) const { return *m_resources.at(index); } - [[nodiscard]] QList const& all() const { return m_resources; } + + [[nodiscard]] Resource& at(int index) { return *m_resources[index].get(); } + [[nodiscard]] const Resource& at(int index) const { return *m_resources.at(index).get(); } + QList selectedResources(const QModelIndexList& indexes); + QList allResources(); + + [[nodiscard]] Resource::Ptr find(QString id); [[nodiscard]] QDir const& dir() const { return m_dir; } @@ -96,7 +137,8 @@ class ResourceFolderModel : public QAbstractListModel { /* Qt behavior */ /* Basic columns */ - enum Columns { ActiveColumn = 0, NameColumn, DateColumn, SizeColumn, NUM_COLUMNS }; + enum Columns { ActiveColumn = 0, NameColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS }; + QStringList columnNames(bool translated = true) const { return translated ? m_column_names_translated : m_column_names; } [[nodiscard]] int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast(size()); } @@ -153,7 +195,9 @@ class ResourceFolderModel : public QAbstractListModel { * This Task is normally executed when opening a page, so it shouldn't contain much heavy work. * If such work is needed, try using it in the Task create by createParseTask() instead! */ - [[nodiscard]] virtual Task* createUpdateTask(); + [[nodiscard]] Task* createUpdateTask(); + + [[nodiscard]] virtual Resource* createResource(const QFileInfo& info) { return new Resource(info); } /** This creates a new parse task to be executed by onUpdateSucceeded(). * @@ -167,10 +211,8 @@ class ResourceFolderModel : public QAbstractListModel { * It uses set operations to find differences between the current state and the updated state, * to act only on those disparities. * - * The implementation is at the end of this header. */ - template - void applyUpdates(QSet& current_set, QSet& new_set, QMap& new_resources); + void applyUpdates(QSet& current_set, QSet& new_set, QMap& new_resources); protected slots: void directoryChanged(QString); @@ -195,19 +237,22 @@ class ResourceFolderModel : public QAbstractListModel { protected: // Represents the relationship between a column's index (represented by the list index), and it's sorting key. // As such, the order in with they appear is very important! - QList m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE, SortType::SIZE }; - QStringList m_column_names = { "Enable", "Name", "Last Modified", "Size" }; - QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("Size") }; + QList m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE, SortType::PROVIDER, SortType::SIZE }; + QStringList m_column_names = { "Enable", "Name", "Last Modified", "Provider", "Size" }; + QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size") }; QList m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, - QHeaderView::Interactive }; - QList m_columnsHideable = { false, false, true, true }; - QList m_columnsHiddenByDefault = { false, false, false, false }; + QHeaderView::Interactive, QHeaderView::Interactive }; + QList m_columnsHideable = { false, false, true, true, true }; + QList m_columnsHiddenByDefault = { false, false, false, false, true }; QDir m_dir; BaseInstance* m_instance; QFileSystemWatcher m_watcher; bool m_is_watching = false; + bool m_is_indexed; + bool m_first_folder_load = true; + Task::Ptr m_current_update_task = nullptr; bool m_scheduled_update = false; @@ -220,133 +265,3 @@ class ResourceFolderModel : public QAbstractListModel { QMap m_active_parse_tasks; std::atomic m_next_resolution_ticket = 0; }; - -/* A macro to define useful functions to handle Resource* -> T* more easily on derived classes */ -#define RESOURCE_HELPERS(T) \ - [[nodiscard]] T* operator[](int index) \ - { \ - return static_cast(m_resources[index].get()); \ - } \ - [[nodiscard]] T* at(int index) \ - { \ - return static_cast(m_resources[index].get()); \ - } \ - [[nodiscard]] const T* at(int index) const \ - { \ - return static_cast(m_resources.at(index).get()); \ - } \ - [[nodiscard]] T* first() \ - { \ - return static_cast(m_resources.first().get()); \ - } \ - [[nodiscard]] T* last() \ - { \ - return static_cast(m_resources.last().get()); \ - } \ - [[nodiscard]] T* find(QString id) \ - { \ - auto iter = std::find_if(m_resources.constBegin(), m_resources.constEnd(), \ - [&](Resource::Ptr const& r) { return r->internal_id() == id; }); \ - if (iter == m_resources.constEnd()) \ - return nullptr; \ - return static_cast((*iter).get()); \ - } - -/* Template definition to avoid some code duplication */ -template -void ResourceFolderModel::applyUpdates(QSet& current_set, QSet& new_set, QMap& new_resources) -{ - // see if the kept resources changed in some way - { - QSet kept_set = current_set; - kept_set.intersect(new_set); - - for (auto const& kept : kept_set) { - auto row_it = m_resources_index.constFind(kept); - Q_ASSERT(row_it != m_resources_index.constEnd()); - auto row = row_it.value(); - - auto& new_resource = new_resources[kept]; - auto const& current_resource = m_resources.at(row); - - if (new_resource->dateTimeChanged() == current_resource->dateTimeChanged()) { - // no significant change, ignore... - continue; - } - - // If the resource is resolving, but something about it changed, we don't want to - // continue the resolving. - if (current_resource->isResolving()) { - auto ticket = current_resource->resolutionTicket(); - if (m_active_parse_tasks.contains(ticket)) { - auto task = (*m_active_parse_tasks.find(ticket)).get(); - task->abort(); - } - } - - m_resources[row].reset(new_resource); - resolveResource(m_resources.at(row).get()); - emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); - } - } - - // remove resources no longer present - { - QSet removed_set = current_set; - removed_set.subtract(new_set); - - QList removed_rows; - for (auto& removed : removed_set) - removed_rows.append(m_resources_index[removed]); - - std::sort(removed_rows.begin(), removed_rows.end(), std::greater()); - - for (auto& removed_index : removed_rows) { - auto removed_it = m_resources.begin() + removed_index; - - Q_ASSERT(removed_it != m_resources.end()); - - if ((*removed_it)->isResolving()) { - auto ticket = (*removed_it)->resolutionTicket(); - if (m_active_parse_tasks.contains(ticket)) { - auto task = (*m_active_parse_tasks.find(ticket)).get(); - task->abort(); - } - } - - beginRemoveRows(QModelIndex(), removed_index, removed_index); - m_resources.erase(removed_it); - endRemoveRows(); - } - } - - // add new resources to the end - { - QSet added_set = new_set; - added_set.subtract(current_set); - - // When you have a Qt build with assertions turned on, proceeding here will abort the application - if (added_set.size() > 0) { - beginInsertRows(QModelIndex(), static_cast(m_resources.size()), - static_cast(m_resources.size() + added_set.size() - 1)); - - for (auto& added : added_set) { - auto res = new_resources[added]; - m_resources.append(res); - resolveResource(m_resources.last().get()); - } - - endInsertRows(); - } - } - - // update index - { - m_resources_index.clear(); - int idx = 0; - for (auto const& mod : qAsConst(m_resources)) { - m_resources_index[mod->internal_id()] = idx; - idx++; - } - } -} diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index 1aa3a2e8c..e106a2be9 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -44,19 +44,19 @@ #include "Application.h" #include "Version.h" -#include "minecraft/mod/Resource.h" -#include "minecraft/mod/tasks/BasicFolderLoadTask.h" #include "minecraft/mod/tasks/LocalResourcePackParseTask.h" -ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) +ResourcePackFolderModel::ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) + : ResourceFolderModel(dir, instance, is_indexed, create_dir, parent) { - m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Size" }); - m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Size") }); - m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE, SortType::SIZE }; - m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, + m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Provider", "Size" }); + m_column_names_translated = + QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Provider"), tr("Size") }); + m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, + SortType::DATE, SortType::PROVIDER, SortType::SIZE }; + m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; - m_columnsHideable = { false, true, false, true, true, true }; - m_columnsHiddenByDefault = { false, false, false, false, false, false }; + m_columnsHideable = { false, true, false, true, true, true, true }; } QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const @@ -73,12 +73,12 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const case NameColumn: return m_resources[row]->name(); case PackFormatColumn: { - auto resource = at(row); - auto pack_format = resource->packFormat(); + auto& resource = at(row); + auto pack_format = resource.packFormat(); if (pack_format == 0) return tr("Unrecognized"); - auto version_bounds = resource->compatibleVersions(); + auto version_bounds = resource.compatibleVersions(); if (version_bounds.first.toString().isEmpty()) return QString::number(pack_format); @@ -87,17 +87,18 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const } case DateColumn: return m_resources[row]->dateTimeChanged(); + case ProviderColumn: + return m_resources[row]->provider(); case SizeColumn: return m_resources[row]->sizeStr(); - default: return {}; } case Qt::DecorationRole: { - if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink())) + if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink())) return APPLICATION->getThemedIcon("status-yellow"); if (column == ImageColumn) { - return at(row)->image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding); + return at(row).image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding); } return {}; } @@ -107,14 +108,14 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const return tr("The resource pack format ID, as well as the Minecraft versions it was designed for."); } if (column == NameColumn) { - if (at(row)->isSymLinkUnder(instDirPath())) { + if (at(row).isSymLinkUnder(instDirPath())) { return m_resources[row]->internal_id() + tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." "\nCanonical Path: %1") - .arg(at(row)->fileinfo().canonicalFilePath()); + .arg(at(row).fileinfo().canonicalFilePath()); ; } - if (at(row)->isMoreThanOneHardLink()) { + if (at(row).isMoreThanOneHardLink()) { return m_resources[row]->internal_id() + tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original."); } @@ -129,7 +130,7 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const case Qt::CheckStateRole: switch (column) { case ActiveColumn: - return at(row)->enabled() ? Qt::Checked : Qt::Unchecked; + return at(row).enabled() ? Qt::Checked : Qt::Unchecked; default: return {}; } @@ -148,6 +149,7 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O case PackFormatColumn: case DateColumn: case ImageColumn: + case ProviderColumn: case SizeColumn: return columnNames().at(section); default: @@ -157,7 +159,7 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O case Qt::ToolTipRole: switch (section) { case ActiveColumn: - return tr("Is the resource pack enabled? (Only valid for ZIPs)"); + return tr("Is the resource pack enabled?"); case NameColumn: return tr("The name of the resource pack."); case PackFormatColumn: @@ -165,6 +167,8 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O return tr("The resource pack format ID, as well as the Minecraft versions it was designed for."); case DateColumn: return tr("The date and time this resource pack was last changed (or added)."); + case ProviderColumn: + return tr("The source provider of the resource pack."); case SizeColumn: return tr("The size of the resource pack."); default: @@ -185,11 +189,6 @@ int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const return parent.isValid() ? 0 : NUM_COLUMNS; } -Task* ResourcePackFolderModel::createUpdateTask() -{ - return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared(entry); }); -} - Task* ResourcePackFolderModel::createParseTask(Resource& resource) { return new LocalResourcePackParseTask(m_next_resolution_ticket, static_cast(resource)); diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.h b/launcher/minecraft/mod/ResourcePackFolderModel.h index 755b9c4c6..9dbf41b85 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.h +++ b/launcher/minecraft/mod/ResourcePackFolderModel.h @@ -7,18 +7,18 @@ class ResourcePackFolderModel : public ResourceFolderModel { Q_OBJECT public: - enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, SizeColumn, NUM_COLUMNS }; + enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS }; - explicit ResourcePackFolderModel(const QString& dir, BaseInstance* instance); + explicit ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); - virtual QString id() const override { return "resourcepacks"; } + QString id() const override { return "resourcepacks"; } [[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; [[nodiscard]] int columnCount(const QModelIndex& parent) const override; - [[nodiscard]] Task* createUpdateTask() override; + [[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new ResourcePack(file); } [[nodiscard]] Task* createParseTask(Resource&) override; RESOURCE_HELPERS(ResourcePack) diff --git a/launcher/minecraft/mod/ShaderPackFolderModel.h b/launcher/minecraft/mod/ShaderPackFolderModel.h index 186d02139..cd01f6226 100644 --- a/launcher/minecraft/mod/ShaderPackFolderModel.h +++ b/launcher/minecraft/mod/ShaderPackFolderModel.h @@ -2,24 +2,24 @@ #include "ResourceFolderModel.h" #include "minecraft/mod/ShaderPack.h" -#include "minecraft/mod/tasks/BasicFolderLoadTask.h" #include "minecraft/mod/tasks/LocalShaderPackParseTask.h" class ShaderPackFolderModel : public ResourceFolderModel { Q_OBJECT public: - explicit ShaderPackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) {} + explicit ShaderPackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr) + : ResourceFolderModel(dir, instance, is_indexed, create_dir, parent) + {} virtual QString id() const override { return "shaderpacks"; } - [[nodiscard]] Task* createUpdateTask() override - { - return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared(entry); }); - } + [[nodiscard]] Resource* createResource(const QFileInfo& info) override { return new ShaderPack(info); } [[nodiscard]] Task* createParseTask(Resource& resource) override { return new LocalShaderPackParseTask(m_next_resolution_ticket, static_cast(resource)); } + + RESOURCE_HELPERS(ShaderPack); }; diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index 48e940e9b..073ea7ca7 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -39,22 +39,18 @@ #include "TexturePackFolderModel.h" -#include "minecraft/mod/tasks/BasicFolderLoadTask.h" #include "minecraft/mod/tasks/LocalTexturePackParseTask.h" +#include "minecraft/mod/tasks/ResourceFolderLoadTask.h" -TexturePackFolderModel::TexturePackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) +TexturePackFolderModel::TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) + : ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent) { - m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified", "Size" }); - m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified"), tr("Size") }); - m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE, SortType::SIZE }; - m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, - QHeaderView::Interactive }; - m_columnsHideable = { false, true, false, true, true }; -} - -Task* TexturePackFolderModel::createUpdateTask() -{ - return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared(entry); }); + m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified", "Provider", "Size" }); + m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size") }); + m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE, SortType::PROVIDER, SortType::SIZE }; + m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; + m_columnsHideable = { false, true, false, true, true, true }; + m_columnsHiddenByDefault = { false, false, false, false, false, true }; } Task* TexturePackFolderModel::createParseTask(Resource& resource) @@ -77,6 +73,8 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const return m_resources[row]->name(); case DateColumn: return m_resources[row]->dateTimeChanged(); + case ProviderColumn: + return m_resources[row]->provider(); case SizeColumn: return m_resources[row]->sizeStr(); default: @@ -84,14 +82,14 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const } case Qt::ToolTipRole: if (column == NameColumn) { - if (at(row)->isSymLinkUnder(instDirPath())) { + if (at(row).isSymLinkUnder(instDirPath())) { return m_resources[row]->internal_id() + tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." "\nCanonical Path: %1") - .arg(at(row)->fileinfo().canonicalFilePath()); + .arg(at(row).fileinfo().canonicalFilePath()); ; } - if (at(row)->isMoreThanOneHardLink()) { + if (at(row).isMoreThanOneHardLink()) { return m_resources[row]->internal_id() + tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original."); } @@ -99,10 +97,10 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const return m_resources[row]->internal_id(); case Qt::DecorationRole: { - if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink())) + if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink())) return APPLICATION->getThemedIcon("status-yellow"); if (column == ImageColumn) { - return at(row)->image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding); + return at(row).image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding); } return {}; } @@ -130,6 +128,7 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or case NameColumn: case DateColumn: case ImageColumn: + case ProviderColumn: case SizeColumn: return columnNames().at(section); default: @@ -138,14 +137,13 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or case Qt::ToolTipRole: { switch (section) { case ActiveColumn: - //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. return tr("Is the texture pack enabled?"); case NameColumn: - //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. return tr("The name of the texture pack."); case DateColumn: - //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. return tr("The date and time this texture pack was last changed (or added)."); + case ProviderColumn: + return tr("The source provider of the texture pack."); case SizeColumn: return tr("The size of the texture pack."); default: diff --git a/launcher/minecraft/mod/TexturePackFolderModel.h b/launcher/minecraft/mod/TexturePackFolderModel.h index de90f879f..7a9264e8f 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.h +++ b/launcher/minecraft/mod/TexturePackFolderModel.h @@ -44,9 +44,9 @@ class TexturePackFolderModel : public ResourceFolderModel { Q_OBJECT public: - enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, DateColumn, SizeColumn, NUM_COLUMNS }; + enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS }; - explicit TexturePackFolderModel(const QString& dir, std::shared_ptr instance); + explicit TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); virtual QString id() const override { return "texturepacks"; } @@ -55,8 +55,7 @@ class TexturePackFolderModel : public ResourceFolderModel { [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; [[nodiscard]] int columnCount(const QModelIndex& parent) const override; - explicit TexturePackFolderModel(const QString& dir, BaseInstance* instance); - [[nodiscard]] Task* createUpdateTask() override; + [[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new TexturePack(file); } [[nodiscard]] Task* createParseTask(Resource&) override; RESOURCE_HELPERS(TexturePack) diff --git a/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h deleted file mode 100644 index 2bce2c137..000000000 --- a/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h +++ /dev/null @@ -1,81 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include - -#include "FileSystem.h" -#include "minecraft/mod/Resource.h" - -#include "tasks/Task.h" - -/** Very simple task that just loads a folder's contents directly. - */ -class BasicFolderLoadTask : public Task { - Q_OBJECT - public: - struct Result { - QMap resources; - }; - using ResultPtr = std::shared_ptr; - - [[nodiscard]] ResultPtr result() const { return m_result; } - - public: - BasicFolderLoadTask(QDir dir) : Task(nullptr, false), m_dir(dir), m_result(new Result), m_thread_to_spawn_into(thread()) - { - m_create_func = [](QFileInfo const& entry) -> Resource::Ptr { return makeShared(entry); }; - } - BasicFolderLoadTask(QDir dir, std::function create_function) - : Task(nullptr, false) - , m_dir(dir) - , m_result(new Result) - , m_create_func(std::move(create_function)) - , m_thread_to_spawn_into(thread()) - {} - - [[nodiscard]] bool canAbort() const override { return true; } - bool abort() override - { - m_aborted.store(true); - return true; - } - - void executeTask() override - { - if (thread() != m_thread_to_spawn_into) - connect(this, &Task::finished, this->thread(), &QThread::quit); - - m_dir.refresh(); - for (auto entry : m_dir.entryInfoList()) { - auto filePath = entry.absoluteFilePath(); - auto newFilePath = FS::getUniqueResourceName(filePath); - if (newFilePath != filePath) { - FS::move(filePath, newFilePath); - entry = QFileInfo(newFilePath); - } - auto resource = m_create_func(entry); - resource->moveToThread(m_thread_to_spawn_into); - m_result->resources.insert(resource->internal_id(), resource); - } - - if (m_aborted) - emit finished(); - else - emitSucceeded(); - } - - private: - QDir m_dir; - ResultPtr m_result; - - std::atomic m_aborted = false; - - std::function m_create_func; - - /** This is the thread in which we should put new mod objects */ - QThread* m_thread_to_spawn_into; -}; diff --git a/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp index b9288d2b3..b63d36361 100644 --- a/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp +++ b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp @@ -52,11 +52,10 @@ static bool checkDependencies(std::shared_ptrversion.loaders || sel->version.loaders & loaders); } -GetModDependenciesTask::GetModDependenciesTask(QObject* parent, - BaseInstance* instance, +GetModDependenciesTask::GetModDependenciesTask(BaseInstance* instance, ModFolderModel* folder, QList> selected) - : SequentialTask(parent, tr("Get dependencies")) + : SequentialTask(tr("Get dependencies")) , m_selected(selected) , m_flame_provider{ ModPlatform::ResourceProvider::FLAME, std::make_shared(*instance), std::make_shared() } @@ -185,7 +184,7 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen auto provider = providerName == m_flame_provider.name ? m_flame_provider : m_modrinth_provider; auto tasks = makeShared( - this, QString("DependencyInfo: %1").arg(dep.addonId.toString().isEmpty() ? dep.version : dep.addonId.toString())); + QString("DependencyInfo: %1").arg(dep.addonId.toString().isEmpty() ? dep.version : dep.addonId.toString())); if (!dep.addonId.toString().isEmpty()) { tasks->addTask(getProjectInfoTask(pDep)); diff --git a/launcher/minecraft/mod/tasks/GetModDependenciesTask.h b/launcher/minecraft/mod/tasks/GetModDependenciesTask.h index 7202b01e0..29c77f9fe 100644 --- a/launcher/minecraft/mod/tasks/GetModDependenciesTask.h +++ b/launcher/minecraft/mod/tasks/GetModDependenciesTask.h @@ -61,10 +61,7 @@ class GetModDependenciesTask : public SequentialTask { std::shared_ptr api; }; - explicit GetModDependenciesTask(QObject* parent, - BaseInstance* instance, - ModFolderModel* folder, - QList> selected); + explicit GetModDependenciesTask(BaseInstance* instance, ModFolderModel* folder, QList> selected); auto getDependecies() const -> QList> { return m_pack_dependencies; } QHash getExtraInfo(); diff --git a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp index 82f6b9df9..19b15709d 100644 --- a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp @@ -157,7 +157,7 @@ bool validate(QFileInfo file) } // namespace DataPackUtils -LocalDataPackParseTask::LocalDataPackParseTask(int token, DataPack& dp) : Task(nullptr, false), m_token(token), m_data_pack(dp) {} +LocalDataPackParseTask::LocalDataPackParseTask(int token, DataPack& dp) : Task(false), m_token(token), m_data_pack(dp) {} bool LocalDataPackParseTask::abort() { diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index d456211f8..7d101668e 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include "FileSystem.h" @@ -15,6 +16,8 @@ #include "minecraft/mod/ModDetails.h" #include "settings/INIFile.h" +static QRegularExpression newlineRegex("\r\n|\n|\r"); + namespace ModUtils { // NEW format @@ -24,7 +27,7 @@ namespace ModUtils { // https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc ModDetails ReadMCModInfo(QByteArray contents) { - auto getInfoFromArray = [&](QJsonArray arr) -> ModDetails { + auto getInfoFromArray = [](QJsonArray arr) -> ModDetails { if (!arr.at(0).isObject()) { return {}; } @@ -487,11 +490,11 @@ bool processZIP(Mod& mod, [[maybe_unused]] ProcessingLevel level) } // quick and dirty line-by-line parser - auto manifestLines = file.readAll().split('\n'); + auto manifestLines = QString(file.readAll()).split(newlineRegex); QString manifestVersion = ""; for (auto& line : manifestLines) { - if (QString(line).startsWith("Implementation-Version: ")) { - manifestVersion = QString(line).remove("Implementation-Version: "); + if (line.startsWith("Implementation-Version: ", Qt::CaseInsensitive)) { + manifestVersion = line.remove("Implementation-Version: ", Qt::CaseInsensitive); break; } } @@ -730,7 +733,7 @@ bool loadIconFile(const Mod& mod, QPixmap* pixmap) } // namespace ModUtils LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile) - : Task(nullptr, false), m_token(token), m_type(type), m_modFile(modFile), m_result(new Result()) + : Task(false), m_token(token), m_type(type), m_modFile(modFile), m_result(new Result()) {} bool LocalModParseTask::abort() diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.h b/launcher/minecraft/mod/tasks/LocalModParseTask.h index 91ee6f253..7ce5a84d2 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.h @@ -47,11 +47,6 @@ class LocalModParseTask : public Task { [[nodiscard]] int token() const { return m_token; } - private: - void processAsZip(); - void processAsFolder(); - void processAsLitemod(); - private: int m_token; ResourceType m_type; diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index 27fbf3c6d..0b80db82d 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -358,9 +358,7 @@ bool validate(QFileInfo file) } // namespace ResourcePackUtils -LocalResourcePackParseTask::LocalResourcePackParseTask(int token, ResourcePack& rp) - : Task(nullptr, false), m_token(token), m_resource_pack(rp) -{} +LocalResourcePackParseTask::LocalResourcePackParseTask(int token, ResourcePack& rp) : Task(false), m_token(token), m_resource_pack(rp) {} bool LocalResourcePackParseTask::abort() { diff --git a/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp b/launcher/minecraft/mod/tasks/LocalResourceUpdateTask.cpp similarity index 63% rename from launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp rename to launcher/minecraft/mod/tasks/LocalResourceUpdateTask.cpp index 4352fad91..c8fe1050a 100644 --- a/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourceUpdateTask.cpp @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -#include "LocalModUpdateTask.h" +#include "LocalResourceUpdateTask.h" #include "FileSystem.h" #include "minecraft/mod/MetadataHandler.h" @@ -26,12 +26,12 @@ #include #endif -LocalModUpdateTask::LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version) - : m_index_dir(index_dir), m_mod(mod), m_mod_version(mod_version) +LocalResourceUpdateTask::LocalResourceUpdateTask(QDir index_dir, ModPlatform::IndexedPack& project, ModPlatform::IndexedVersion& version) + : m_index_dir(index_dir), m_project(project), m_version(version) { // Ensure a '.index' folder exists in the mods folder, and create it if it does not if (!FS::ensureFolderPathExists(index_dir.path())) { - emitFailed(QString("Unable to create index for mod %1!").arg(m_mod.name)); + emitFailed(QString("Unable to create index directory at %1!").arg(index_dir.absolutePath())); } #ifdef Q_OS_WIN32 @@ -39,28 +39,28 @@ LocalModUpdateTask::LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack& #endif } -void LocalModUpdateTask::executeTask() +void LocalResourceUpdateTask::executeTask() { - setStatus(tr("Updating index for mod:\n%1").arg(m_mod.name)); + setStatus(tr("Updating index for resource:\n%1").arg(m_project.name)); - auto old_metadata = Metadata::get(m_index_dir, m_mod.addonId); + auto old_metadata = Metadata::get(m_index_dir, m_project.addonId); if (old_metadata.isValid()) { - emit hasOldMod(old_metadata.name, old_metadata.filename); - if (m_mod.slug.isEmpty()) - m_mod.slug = old_metadata.slug; + emit hasOldResource(old_metadata.name, old_metadata.filename); + if (m_project.slug.isEmpty()) + m_project.slug = old_metadata.slug; } - auto pw_mod = Metadata::create(m_index_dir, m_mod, m_mod_version); + auto pw_mod = Metadata::create(m_index_dir, m_project, m_version); if (pw_mod.isValid()) { Metadata::update(m_index_dir, pw_mod); emitSucceeded(); } else { - qCritical() << "Tried to update an invalid mod!"; + qCritical() << "Tried to update an invalid resource!"; emitFailed(tr("Invalid metadata")); } } -auto LocalModUpdateTask::abort() -> bool +auto LocalResourceUpdateTask::abort() -> bool { emitAborted(); return true; diff --git a/launcher/minecraft/mod/tasks/LocalModUpdateTask.h b/launcher/minecraft/mod/tasks/LocalResourceUpdateTask.h similarity index 74% rename from launcher/minecraft/mod/tasks/LocalModUpdateTask.h rename to launcher/minecraft/mod/tasks/LocalResourceUpdateTask.h index 5447083ba..f8869258e 100644 --- a/launcher/minecraft/mod/tasks/LocalModUpdateTask.h +++ b/launcher/minecraft/mod/tasks/LocalResourceUpdateTask.h @@ -23,12 +23,12 @@ #include "modplatform/ModIndex.h" #include "tasks/Task.h" -class LocalModUpdateTask : public Task { +class LocalResourceUpdateTask : public Task { Q_OBJECT public: - using Ptr = shared_qobject_ptr; + using Ptr = shared_qobject_ptr; - explicit LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version); + explicit LocalResourceUpdateTask(QDir index_dir, ModPlatform::IndexedPack& project, ModPlatform::IndexedVersion& version); auto canAbort() const -> bool override { return true; } auto abort() -> bool override; @@ -38,10 +38,10 @@ class LocalModUpdateTask : public Task { void executeTask() override; signals: - void hasOldMod(QString name, QString filename); + void hasOldResource(QString name, QString filename); private: QDir m_index_dir; - ModPlatform::IndexedPack m_mod; - ModPlatform::IndexedVersion m_mod_version; + ModPlatform::IndexedPack m_project; + ModPlatform::IndexedVersion m_version; }; diff --git a/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.cpp index 4deebcd1d..a6ecc5353 100644 --- a/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.cpp @@ -93,7 +93,7 @@ bool validate(QFileInfo file) } // namespace ShaderPackUtils -LocalShaderPackParseTask::LocalShaderPackParseTask(int token, ShaderPack& sp) : Task(nullptr, false), m_token(token), m_shader_pack(sp) {} +LocalShaderPackParseTask::LocalShaderPackParseTask(int token, ShaderPack& sp) : Task(false), m_token(token), m_shader_pack(sp) {} bool LocalShaderPackParseTask::abort() { diff --git a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp index 00cc2def2..18020808a 100644 --- a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp @@ -230,8 +230,7 @@ bool validate(QFileInfo file) } // namespace TexturePackUtils -LocalTexturePackParseTask::LocalTexturePackParseTask(int token, TexturePack& rp) : Task(nullptr, false), m_token(token), m_texture_pack(rp) -{} +LocalTexturePackParseTask::LocalTexturePackParseTask(int token, TexturePack& rp) : Task(false), m_token(token), m_texture_pack(rp) {} bool LocalTexturePackParseTask::abort() { diff --git a/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp b/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp index 9d564ddb3..74d8d8d60 100644 --- a/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp @@ -170,7 +170,7 @@ bool validate(QFileInfo file) } // namespace WorldSaveUtils -LocalWorldSaveParseTask::LocalWorldSaveParseTask(int token, WorldSave& save) : Task(nullptr, false), m_token(token), m_save(save) {} +LocalWorldSaveParseTask::LocalWorldSaveParseTask(int token, WorldSave& save) : Task(false), m_token(token), m_save(save) {} bool LocalWorldSaveParseTask::abort() { diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.cpp similarity index 55% rename from launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp rename to launcher/minecraft/mod/tasks/ResourceFolderLoadTask.cpp index 501d5be13..98dab9abb 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.cpp @@ -34,24 +34,30 @@ * limitations under the License. */ -#include "ModFolderLoadTask.h" +#include "ResourceFolderLoadTask.h" +#include "Application.h" #include "FileSystem.h" #include "minecraft/mod/MetadataHandler.h" #include -ModFolderLoadTask::ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan) - : Task(nullptr, false) - , m_mods_dir(mods_dir) +ResourceFolderLoadTask::ResourceFolderLoadTask(const QDir& resource_dir, + const QDir& index_dir, + bool is_indexed, + bool clean_orphan, + std::function create_function) + : Task(false) + , m_resource_dir(resource_dir) , m_index_dir(index_dir) , m_is_indexed(is_indexed) , m_clean_orphan(clean_orphan) + , m_create_func(create_function) , m_result(new Result()) , m_thread_to_spawn_into(thread()) {} -void ModFolderLoadTask::executeTask() +void ResourceFolderLoadTask::executeTask() { if (thread() != m_thread_to_spawn_into) connect(this, &Task::finished, this->thread(), &QThread::quit); @@ -62,40 +68,44 @@ void ModFolderLoadTask::executeTask() } // Read JAR files that don't have metadata - m_mods_dir.refresh(); - for (auto entry : m_mods_dir.entryInfoList()) { + m_resource_dir.refresh(); + for (auto entry : m_resource_dir.entryInfoList()) { auto filePath = entry.absoluteFilePath(); + if (auto app = APPLICATION_DYN; app && app->checkQSavePath(filePath)) { + continue; + } auto newFilePath = FS::getUniqueResourceName(filePath); if (newFilePath != filePath) { FS::move(filePath, newFilePath); entry = QFileInfo(newFilePath); } - Mod* mod(new Mod(entry)); - if (mod->enabled()) { - if (m_result->mods.contains(mod->internal_id())) { - m_result->mods[mod->internal_id()]->setStatus(ModStatus::Installed); + Resource* resource = m_create_func(entry); + + if (resource->enabled()) { + if (m_result->resources.contains(resource->internal_id())) { + m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::INSTALLED); // Delete the object we just created, since a valid one is already in the mods list. - delete mod; + delete resource; } else { - m_result->mods[mod->internal_id()].reset(std::move(mod)); - m_result->mods[mod->internal_id()]->setStatus(ModStatus::NoMetadata); + m_result->resources[resource->internal_id()].reset(resource); + m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::NO_METADATA); } } else { - QString chopped_id = mod->internal_id().chopped(9); - if (m_result->mods.contains(chopped_id)) { - m_result->mods[mod->internal_id()].reset(std::move(mod)); + QString chopped_id = resource->internal_id().chopped(9); + if (m_result->resources.contains(chopped_id)) { + m_result->resources[resource->internal_id()].reset(resource); - auto metadata = m_result->mods[chopped_id]->metadata(); + auto metadata = m_result->resources[chopped_id]->metadata(); if (metadata) { - mod->setMetadata(*metadata); + resource->setMetadata(*metadata); - m_result->mods[mod->internal_id()]->setStatus(ModStatus::Installed); - m_result->mods.remove(chopped_id); + m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::INSTALLED); + m_result->resources.remove(chopped_id); } } else { - m_result->mods[mod->internal_id()].reset(std::move(mod)); - m_result->mods[mod->internal_id()]->setStatus(ModStatus::NoMetadata); + m_result->resources[resource->internal_id()].reset(resource); + m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::NO_METADATA); } } } @@ -103,17 +113,17 @@ void ModFolderLoadTask::executeTask() // Remove orphan metadata to prevent issues // See https://github.com/PolyMC/PolyMC/issues/996 if (m_clean_orphan) { - QMutableMapIterator iter(m_result->mods); + QMutableMapIterator iter(m_result->resources); while (iter.hasNext()) { - auto mod = iter.next().value(); - if (mod->status() == ModStatus::NotInstalled) { - mod->destroy(m_index_dir, false, false); + auto resource = iter.next().value(); + if (resource->status() == ResourceStatus::NOT_INSTALLED) { + resource->destroy(m_index_dir, false, false); iter.remove(); } } } - for (auto mod : m_result->mods) + for (auto mod : m_result->resources) mod->moveToThread(m_thread_to_spawn_into); if (m_aborted) @@ -122,18 +132,18 @@ void ModFolderLoadTask::executeTask() emitSucceeded(); } -void ModFolderLoadTask::getFromMetadata() +void ResourceFolderLoadTask::getFromMetadata() { m_index_dir.refresh(); for (auto entry : m_index_dir.entryList(QDir::Files)) { auto metadata = Metadata::get(m_index_dir, entry); - if (!metadata.isValid()) { + if (!metadata.isValid()) continue; - } - auto* mod = new Mod(m_mods_dir, metadata); - mod->setStatus(ModStatus::NotInstalled); - m_result->mods[mod->internal_id()].reset(std::move(mod)); + auto* resource = m_create_func(QFileInfo(m_resource_dir.filePath(metadata.filename))); + resource->setMetadata(metadata); + resource->setStatus(ResourceStatus::NOT_INSTALLED); + m_result->resources[resource->internal_id()].reset(resource); } } diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h b/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.h similarity index 83% rename from launcher/minecraft/mod/tasks/ModFolderLoadTask.h rename to launcher/minecraft/mod/tasks/ResourceFolderLoadTask.h index 4200ef6d9..9950345ef 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.h @@ -44,17 +44,21 @@ #include "minecraft/mod/Mod.h" #include "tasks/Task.h" -class ModFolderLoadTask : public Task { +class ResourceFolderLoadTask : public Task { Q_OBJECT public: struct Result { - QMap mods; + QMap resources; }; using ResultPtr = std::shared_ptr; ResultPtr result() const { return m_result; } public: - ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan = false); + ResourceFolderLoadTask(const QDir& resource_dir, + const QDir& index_dir, + bool is_indexed, + bool clean_orphan, + std::function create_function); [[nodiscard]] bool canAbort() const override { return true; } bool abort() override @@ -69,9 +73,10 @@ class ModFolderLoadTask : public Task { void getFromMetadata(); private: - QDir m_mods_dir, m_index_dir; + QDir m_resource_dir, m_index_dir; bool m_is_indexed; bool m_clean_orphan; + std::function m_create_func; ResultPtr m_result; std::atomic m_aborted = false; diff --git a/launcher/minecraft/update/LibrariesTask.cpp b/launcher/minecraft/update/LibrariesTask.cpp index 1581b32ee..e64691d51 100644 --- a/launcher/minecraft/update/LibrariesTask.cpp +++ b/launcher/minecraft/update/LibrariesTask.cpp @@ -25,7 +25,7 @@ void LibrariesTask::executeTask() auto metacache = APPLICATION->metacache(); - auto processArtifactPool = [&](const QList& pool, QStringList& errors, const QString& localPath) { + auto processArtifactPool = [this, inst, metacache](const QList& pool, QStringList& errors, const QString& localPath) { for (auto lib : pool) { if (!lib) { emitFailed(tr("Null jar is specified in the metadata, aborting.")); diff --git a/launcher/modplatform/CheckUpdateTask.h b/launcher/modplatform/CheckUpdateTask.h index 24b82c28e..1ee820a63 100644 --- a/launcher/modplatform/CheckUpdateTask.h +++ b/launcher/modplatform/CheckUpdateTask.h @@ -3,6 +3,7 @@ #include "minecraft/mod/Mod.h" #include "minecraft/mod/tasks/GetModDependenciesTask.h" #include "modplatform/ModIndex.h" +#include "modplatform/ResourceAPI.h" #include "tasks/Task.h" class ResourceDownloadTask; @@ -12,13 +13,18 @@ class CheckUpdateTask : public Task { Q_OBJECT public: - CheckUpdateTask(QList& mods, + CheckUpdateTask(QList& resources, std::list& mcVersions, QList loadersList, - std::shared_ptr mods_folder) - : Task(nullptr), m_mods(mods), m_game_versions(mcVersions), m_loaders_list(loadersList), m_mods_folder(mods_folder) {}; + std::shared_ptr resourceModel) + : Task() + , m_resources(resources) + , m_game_versions(mcVersions) + , m_loaders_list(std::move(loadersList)) + , m_resource_model(std::move(resourceModel)) + {} - struct UpdatableMod { + struct Update { QString name; QString old_hash; QString old_version; @@ -30,28 +36,28 @@ class CheckUpdateTask : public Task { bool enabled = true; public: - UpdatableMod(QString name, - QString old_h, - QString old_v, - QString new_v, - std::optional new_v_type, - QString changelog, - ModPlatform::ResourceProvider p, - shared_qobject_ptr t, - bool enabled = true) - : name(name) - , old_hash(old_h) - , old_version(old_v) - , new_version(new_v) - , new_version_type(new_v_type) - , changelog(changelog) + Update(QString name, + QString old_h, + QString old_v, + QString new_v, + std::optional new_v_type, + QString changelog, + ModPlatform::ResourceProvider p, + shared_qobject_ptr t, + bool enabled = true) + : name(std::move(name)) + , old_hash(std::move(old_h)) + , old_version(std::move(old_v)) + , new_version(std::move(new_v)) + , new_version_type(std::move(new_v_type)) + , changelog(std::move(changelog)) , provider(p) - , download(t) + , download(std::move(t)) , enabled(enabled) {} }; - auto getUpdatable() -> std::vector&& { return std::move(m_updatable); } + auto getUpdates() -> std::vector&& { return std::move(m_updates); } auto getDependencies() -> QList>&& { return std::move(m_deps); } public slots: @@ -61,14 +67,14 @@ class CheckUpdateTask : public Task { void executeTask() override = 0; signals: - void checkFailed(Mod* failed, QString reason, QUrl recover_url = {}); + void checkFailed(Resource* failed, QString reason, QUrl recover_url = {}); protected: - QList& m_mods; + QList& m_resources; std::list& m_game_versions; QList m_loaders_list; - std::shared_ptr m_mods_folder; + std::shared_ptr m_resource_model; - std::vector m_updatable; + std::vector m_updates; QList> m_deps; }; diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp index f6f49f38d..8e910e984 100644 --- a/launcher/modplatform/EnsureMetadataTask.cpp +++ b/launcher/modplatform/EnsureMetadataTask.cpp @@ -7,7 +7,7 @@ #include "Json.h" #include "minecraft/mod/Mod.h" -#include "minecraft/mod/tasks/LocalModUpdateTask.h" +#include "minecraft/mod/tasks/LocalResourceUpdateTask.h" #include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/FlameModIndex.h" @@ -18,55 +18,57 @@ static ModrinthAPI modrinth_api; static FlameAPI flame_api; -EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::ResourceProvider prov) - : Task(nullptr), m_index_dir(dir), m_provider(prov), m_hashing_task(nullptr), m_current_task(nullptr) +EnsureMetadataTask::EnsureMetadataTask(Resource* resource, QDir dir, ModPlatform::ResourceProvider prov) + : Task(), m_index_dir(dir), m_provider(prov), m_hashingTask(nullptr), m_current_task(nullptr) { - auto hash_task = createNewHash(mod); - if (!hash_task) + auto hashTask = createNewHash(resource); + if (!hashTask) return; - connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) { m_mods.insert(hash, mod); }); - connect(hash_task.get(), &Task::failed, [this, mod] { emitFail(mod, "", RemoveFromList::No); }); - hash_task->start(); + connect(hashTask.get(), &Hashing::Hasher::resultsReady, [this, resource](QString hash) { m_resources.insert(hash, resource); }); + connect(hashTask.get(), &Task::failed, [this, resource] { emitFail(resource, "", RemoveFromList::No); }); + m_hashingTask = hashTask; } -EnsureMetadataTask::EnsureMetadataTask(QList& mods, QDir dir, ModPlatform::ResourceProvider prov) - : Task(nullptr), m_index_dir(dir), m_provider(prov), m_current_task(nullptr) +EnsureMetadataTask::EnsureMetadataTask(QList& resources, QDir dir, ModPlatform::ResourceProvider prov) + : Task(), m_index_dir(dir), m_provider(prov), m_current_task(nullptr) { - m_hashing_task.reset(new ConcurrentTask(this, "MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())); - for (auto* mod : mods) { - auto hash_task = createNewHash(mod); + auto hashTask = makeShared("MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); + m_hashingTask = hashTask; + for (auto* resource : resources) { + auto hash_task = createNewHash(resource); if (!hash_task) continue; - connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) { m_mods.insert(hash, mod); }); - connect(hash_task.get(), &Task::failed, [this, mod] { emitFail(mod, "", RemoveFromList::No); }); - m_hashing_task->addTask(hash_task); + connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, resource](QString hash) { m_resources.insert(hash, resource); }); + connect(hash_task.get(), &Task::failed, [this, resource] { emitFail(resource, "", RemoveFromList::No); }); + hashTask->addTask(hash_task); } } -EnsureMetadataTask::EnsureMetadataTask(QHash& mods, QDir dir, ModPlatform::ResourceProvider prov) - : Task(nullptr), m_mods(mods), m_index_dir(dir), m_provider(prov), m_current_task(nullptr) + +EnsureMetadataTask::EnsureMetadataTask(QHash& resources, QDir dir, ModPlatform::ResourceProvider prov) + : Task(), m_resources(resources), m_index_dir(dir), m_provider(prov), m_current_task(nullptr) {} -Hashing::Hasher::Ptr EnsureMetadataTask::createNewHash(Mod* mod) +Hashing::Hasher::Ptr EnsureMetadataTask::createNewHash(Resource* resource) { - if (!mod || !mod->valid() || mod->type() == ResourceType::FOLDER) + if (!resource || !resource->valid() || resource->type() == ResourceType::FOLDER) return nullptr; - return Hashing::createHasher(mod->fileinfo().absoluteFilePath(), m_provider); + return Hashing::createHasher(resource->fileinfo().absoluteFilePath(), m_provider); } -QString EnsureMetadataTask::getExistingHash(Mod* mod) +QString EnsureMetadataTask::getExistingHash(Resource* resource) { // Check for already computed hashes // (linear on the number of mods vs. linear on the size of the mod's JAR) - auto it = m_mods.keyValueBegin(); - while (it != m_mods.keyValueEnd()) { - if ((*it).second == mod) + auto it = m_resources.keyValueBegin(); + while (it != m_resources.keyValueEnd()) { + if ((*it).second == resource) break; it++; } // We already have the hash computed - if (it != m_mods.keyValueEnd()) { + if (it != m_resources.keyValueEnd()) { return (*it).first; } @@ -86,25 +88,25 @@ bool EnsureMetadataTask::abort() void EnsureMetadataTask::executeTask() { - setStatus(tr("Checking if mods have metadata...")); + setStatus(tr("Checking if resources have metadata...")); - for (auto* mod : m_mods) { - if (!mod->valid()) { - qDebug() << "Mod" << mod->name() << "is invalid!"; - emitFail(mod); + for (auto* resource : m_resources) { + if (!resource->valid()) { + qDebug() << "Resource" << resource->name() << "is invalid!"; + emitFail(resource); continue; } // They already have the right metadata :o - if (mod->status() != ModStatus::NoMetadata && mod->metadata() && mod->metadata()->provider == m_provider) { - qDebug() << "Mod" << mod->name() << "already has metadata!"; - emitReady(mod); + if (resource->status() != ResourceStatus::NO_METADATA && resource->metadata() && resource->metadata()->provider == m_provider) { + qDebug() << "Resource" << resource->name() << "already has metadata!"; + emitReady(resource); continue; } // Folders don't have metadata - if (mod->type() == ResourceType::FOLDER) { - emitReady(mod); + if (resource->type() == ResourceType::FOLDER) { + emitReady(resource); } } @@ -120,9 +122,9 @@ void EnsureMetadataTask::executeTask() } auto invalidade_leftover = [this] { - for (auto mod = m_mods.constBegin(); mod != m_mods.constEnd(); mod++) - emitFail(mod.value(), mod.key(), RemoveFromList::No); - m_mods.clear(); + for (auto resource = m_resources.constBegin(); resource != m_resources.constEnd(); resource++) + emitFail(resource.value(), resource.key(), RemoveFromList::No); + m_resources.clear(); emitSucceeded(); }; @@ -144,7 +146,7 @@ void EnsureMetadataTask::executeTask() return; } - connect(project_task.get(), &Task::finished, this, [=] { + connect(project_task.get(), &Task::finished, this, [this, invalidade_leftover, project_task] { invalidade_leftover(); project_task->deleteLater(); if (m_current_task) @@ -156,59 +158,59 @@ void EnsureMetadataTask::executeTask() project_task->start(); }); - connect(version_task.get(), &Task::finished, [=] { + connect(version_task.get(), &Task::finished, [this, version_task] { version_task->deleteLater(); if (m_current_task) m_current_task.reset(); }); - if (m_mods.size() > 1) + if (m_resources.size() > 1) setStatus(tr("Requesting metadata information from %1...").arg(ModPlatform::ProviderCapabilities::readableName(m_provider))); - else if (!m_mods.empty()) + else if (!m_resources.empty()) setStatus(tr("Requesting metadata information from %1 for '%2'...") - .arg(ModPlatform::ProviderCapabilities::readableName(m_provider), m_mods.begin().value()->name())); + .arg(ModPlatform::ProviderCapabilities::readableName(m_provider), m_resources.begin().value()->name())); m_current_task = version_task; version_task->start(); } -void EnsureMetadataTask::emitReady(Mod* m, QString key, RemoveFromList remove) +void EnsureMetadataTask::emitReady(Resource* resource, QString key, RemoveFromList remove) { - if (!m) { - qCritical() << "Tried to mark a null mod as ready."; + if (!resource) { + qCritical() << "Tried to mark a null resource as ready."; if (!key.isEmpty()) - m_mods.remove(key); + m_resources.remove(key); return; } - qDebug() << QString("Generated metadata for %1").arg(m->name()); - emit metadataReady(m); + qDebug() << QString("Generated metadata for %1").arg(resource->name()); + emit metadataReady(resource); if (remove == RemoveFromList::Yes) { if (key.isEmpty()) - key = getExistingHash(m); - m_mods.remove(key); + key = getExistingHash(resource); + m_resources.remove(key); } } -void EnsureMetadataTask::emitFail(Mod* m, QString key, RemoveFromList remove) +void EnsureMetadataTask::emitFail(Resource* resource, QString key, RemoveFromList remove) { - if (!m) { - qCritical() << "Tried to mark a null mod as failed."; + if (!resource) { + qCritical() << "Tried to mark a null resource as failed."; if (!key.isEmpty()) - m_mods.remove(key); + m_resources.remove(key); return; } - qDebug() << QString("Failed to generate metadata for %1").arg(m->name()); - emit metadataFailed(m); + qDebug() << QString("Failed to generate metadata for %1").arg(resource->name()); + emit metadataFailed(resource); if (remove == RemoveFromList::Yes) { if (key.isEmpty()) - key = getExistingHash(m); - m_mods.remove(key); + key = getExistingHash(resource); + m_resources.remove(key); } } @@ -219,7 +221,7 @@ Task::Ptr EnsureMetadataTask::modrinthVersionsTask() auto hash_type = ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first(); auto response = std::make_shared(); - auto ver_task = modrinth_api.currentVersions(m_mods.keys(), hash_type, response); + auto ver_task = modrinth_api.currentVersions(m_resources.keys(), hash_type, response); // Prevents unfortunate timings when aborting the task if (!ver_task) @@ -239,20 +241,20 @@ Task::Ptr EnsureMetadataTask::modrinthVersionsTask() try { auto entries = Json::requireObject(doc); - for (auto& hash : m_mods.keys()) { - auto mod = m_mods.find(hash).value(); + for (auto& hash : m_resources.keys()) { + auto resource = m_resources.find(hash).value(); try { auto entry = Json::requireObject(entries, hash); - setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(mod->name())); - qDebug() << "Getting version for" << mod->name() << "from Modrinth"; + setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(resource->name())); + qDebug() << "Getting version for" << resource->name() << "from Modrinth"; m_temp_versions.insert(hash, Modrinth::loadIndexedPackVersion(entry)); } catch (Json::JsonException& e) { qDebug() << e.cause(); qDebug() << entries; - emitFail(mod); + emitFail(resource); } } } catch (Json::JsonException& e) { @@ -324,23 +326,23 @@ Task::Ptr EnsureMetadataTask::modrinthProjectsTask() auto hash = addonIds.find(pack.addonId.toString()).value(); - auto mod_iter = m_mods.find(hash); - if (mod_iter == m_mods.end()) { + auto resource_iter = m_resources.find(hash); + if (resource_iter == m_resources.end()) { qWarning() << "Invalid project id from the API response."; continue; } - auto* mod = mod_iter.value(); + auto* resource = resource_iter.value(); try { - setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(mod->name())); + setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(resource->name())); - modrinthCallback(pack, m_temp_versions.find(hash).value(), mod); + modrinthCallback(pack, m_temp_versions.find(hash).value(), resource); } catch (Json::JsonException& e) { qDebug() << e.cause(); qDebug() << entries; - emitFail(mod); + emitFail(resource); } } }); @@ -354,7 +356,7 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask() auto response = std::make_shared(); QList fingerprints; - for (auto& murmur : m_mods.keys()) { + for (auto& murmur : m_resources.keys()) { fingerprints.push_back(murmur.toUInt()); } @@ -394,13 +396,13 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask() } auto fingerprint = QString::number(Json::ensureVariant(file_obj, "fileFingerprint").toUInt()); - auto mod = m_mods.find(fingerprint); - if (mod == m_mods.end()) { + auto resource = m_resources.find(fingerprint); + if (resource == m_resources.end()) { qWarning() << "Invalid fingerprint from the API response."; continue; } - setStatus(tr("Parsing API response from CurseForge for '%1'...").arg((*mod)->name())); + setStatus(tr("Parsing API response from CurseForge for '%1'...").arg((*resource)->name())); m_temp_versions.insert(fingerprint, FlameMod::loadIndexedPackVersion(file_obj)); } @@ -417,7 +419,7 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask() Task::Ptr EnsureMetadataTask::flameProjectsTask() { QHash addonIds; - for (auto const& hash : m_mods.keys()) { + for (auto const& hash : m_resources.keys()) { if (m_temp_versions.contains(hash)) { auto data = m_temp_versions.find(hash).value(); @@ -464,20 +466,20 @@ Task::Ptr EnsureMetadataTask::flameProjectsTask() auto id = QString::number(Json::requireInteger(entry_obj, "id")); auto hash = addonIds.find(id).value(); - auto mod = m_mods.find(hash).value(); + auto resource = m_resources.find(hash).value(); try { - setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(mod->name())); + setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(resource->name())); ModPlatform::IndexedPack pack; FlameMod::loadIndexedPack(pack, entry_obj); - flameCallback(pack, m_temp_versions.find(hash).value(), mod); + flameCallback(pack, m_temp_versions.find(hash).value(), resource); } catch (Json::JsonException& e) { qDebug() << e.cause(); qDebug() << entries; - emitFail(mod); + emitFail(resource); } } } catch (Json::JsonException& e) { @@ -489,17 +491,17 @@ Task::Ptr EnsureMetadataTask::flameProjectsTask() return proj_task; } -void EnsureMetadataTask::modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod* mod) +void EnsureMetadataTask::modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Resource* resource) { // Prevent file name mismatch - ver.fileName = mod->fileinfo().fileName(); + ver.fileName = resource->fileinfo().fileName(); if (ver.fileName.endsWith(".disabled")) ver.fileName.chop(9); QDir tmp_index_dir(m_index_dir); { - LocalModUpdateTask update_metadata(m_index_dir, pack, ver); + LocalResourceUpdateTask update_metadata(m_index_dir, pack, ver); QEventLoop loop; QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit); @@ -513,27 +515,27 @@ void EnsureMetadataTask::modrinthCallback(ModPlatform::IndexedPack& pack, ModPla auto metadata = Metadata::get(tmp_index_dir, pack.slug); if (!metadata.isValid()) { qCritical() << "Failed to generate metadata at last step!"; - emitFail(mod); + emitFail(resource); return; } - mod->setMetadata(metadata); + resource->setMetadata(metadata); - emitReady(mod); + emitReady(resource); } -void EnsureMetadataTask::flameCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod* mod) +void EnsureMetadataTask::flameCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Resource* resource) { try { // Prevent file name mismatch - ver.fileName = mod->fileinfo().fileName(); + ver.fileName = resource->fileinfo().fileName(); if (ver.fileName.endsWith(".disabled")) ver.fileName.chop(9); QDir tmp_index_dir(m_index_dir); { - LocalModUpdateTask update_metadata(m_index_dir, pack, ver); + LocalResourceUpdateTask update_metadata(m_index_dir, pack, ver); QEventLoop loop; QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit); @@ -547,16 +549,16 @@ void EnsureMetadataTask::flameCallback(ModPlatform::IndexedPack& pack, ModPlatfo auto metadata = Metadata::get(tmp_index_dir, pack.slug); if (!metadata.isValid()) { qCritical() << "Failed to generate metadata at last step!"; - emitFail(mod); + emitFail(resource); return; } - mod->setMetadata(metadata); + resource->setMetadata(metadata); - emitReady(mod); + emitReady(resource); } catch (Json::JsonException& e) { qDebug() << e.cause(); - emitFail(mod); + emitFail(resource); } } diff --git a/launcher/modplatform/EnsureMetadataTask.h b/launcher/modplatform/EnsureMetadataTask.h index 631b32ae7..4e5787841 100644 --- a/launcher/modplatform/EnsureMetadataTask.h +++ b/launcher/modplatform/EnsureMetadataTask.h @@ -1,26 +1,27 @@ #pragma once #include "ModIndex.h" +#include "net/NetJob.h" #include "modplatform/helpers/HashUtils.h" +#include "minecraft/mod/Resource.h" #include "tasks/ConcurrentTask.h" -#include - class Mod; +class QDir; class EnsureMetadataTask : public Task { Q_OBJECT public: - EnsureMetadataTask(Mod*, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH); - EnsureMetadataTask(QList&, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH); - EnsureMetadataTask(QHash&, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH); + EnsureMetadataTask(Resource*, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH); + EnsureMetadataTask(QList&, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH); + EnsureMetadataTask(QHash&, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH); ~EnsureMetadataTask() = default; - Task::Ptr getHashingTask() { return m_hashing_task; } + Task::Ptr getHashingTask() { return m_hashingTask; } public slots: bool abort() override; @@ -37,27 +38,27 @@ class EnsureMetadataTask : public Task { // Helpers enum class RemoveFromList { Yes, No }; - void emitReady(Mod*, QString key = {}, RemoveFromList = RemoveFromList::Yes); - void emitFail(Mod*, QString key = {}, RemoveFromList = RemoveFromList::Yes); + void emitReady(Resource*, QString key = {}, RemoveFromList = RemoveFromList::Yes); + void emitFail(Resource*, QString key = {}, RemoveFromList = RemoveFromList::Yes); // Hashes and stuff - auto createNewHash(Mod*) -> Hashing::Hasher::Ptr; - auto getExistingHash(Mod*) -> QString; + auto createNewHash(Resource*) -> Hashing::Hasher::Ptr; + auto getExistingHash(Resource*) -> QString; private slots: - void modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod*); - void flameCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod*); + void modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Resource*); + void flameCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Resource*); signals: - void metadataReady(Mod*); - void metadataFailed(Mod*); + void metadataReady(Resource*); + void metadataFailed(Resource*); private: - QHash m_mods; + QHash m_resources; QDir m_index_dir; ModPlatform::ResourceProvider m_provider; QHash m_temp_versions; - ConcurrentTask::Ptr m_hashing_task; + Task::Ptr m_hashingTask; Task::Ptr m_current_task; }; diff --git a/launcher/modplatform/ModIndex.cpp b/launcher/modplatform/ModIndex.cpp index 8c85ae122..c3ecccf8e 100644 --- a/launcher/modplatform/ModIndex.cpp +++ b/launcher/modplatform/ModIndex.cpp @@ -31,6 +31,19 @@ static const QMap s_indexed_version_ty { "alpha", IndexedVersionType::VersionType::Alpha } }; +static const QList loaderList = { NeoForge, Forge, Cauldron, LiteLoader, Quilt, Fabric }; + +QList modLoaderTypesToList(ModLoaderTypes flags) +{ + QList flagList; + for (auto flag : loaderList) { + if (flags.testFlag(flag)) { + flagList.append(flag); + } + } + return flagList; +} + IndexedVersionType::IndexedVersionType(const QString& type) : IndexedVersionType(enumFromString(type)) {} IndexedVersionType::IndexedVersionType(const IndexedVersionType::VersionType& type) diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index d5ee12473..8fae1bf6c 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -32,6 +32,7 @@ namespace ModPlatform { enum ModLoaderType { NeoForge = 1 << 0, Forge = 1 << 1, Cauldron = 1 << 2, LiteLoader = 1 << 3, Fabric = 1 << 4, Quilt = 1 << 5 }; Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType) +QList modLoaderTypesToList(ModLoaderTypes flags); enum class ResourceProvider { MODRINTH, FLAME }; diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h index b7364d9ab..62a1ff199 100644 --- a/launcher/modplatform/ResourceAPI.h +++ b/launcher/modplatform/ResourceAPI.h @@ -73,9 +73,10 @@ class ResourceAPI { std::optional search; std::optional sorting; std::optional loaders; - std::optional > versions; + std::optional> versions; std::optional side; std::optional categoryIds; + bool openSource; }; struct SearchCallbacks { std::function on_succeed; @@ -86,7 +87,7 @@ class ResourceAPI { struct VersionSearchArgs { ModPlatform::IndexedPack pack; - std::optional > mcVersions; + std::optional> mcVersions; std::optional loaders; VersionSearchArgs(VersionSearchArgs const&) = default; @@ -168,11 +169,23 @@ class ResourceAPI { protected: [[nodiscard]] inline QString debugName() const { return "External resource API"; } - [[nodiscard]] inline auto getGameVersionsString(std::list mcVersions) const -> QString + [[nodiscard]] inline QString mapMCVersionToModrinth(Version v) const + { + static const QString preString = " Pre-Release "; + auto verStr = v.toString(); + + if (verStr.contains(preString)) { + verStr.replace(preString, "-pre"); + } + verStr.replace(" ", "-"); + return verStr; + } + + [[nodiscard]] inline QString getGameVersionsString(std::list mcVersions) const { QString s; for (auto& ver : mcVersions) { - s += QString("\"%1\",").arg(ver.toString()); + s += QString("\"%1\",").arg(mapMCVersionToModrinth(ver)); } s.remove(s.length() - 1, 1); // remove last comma return s; diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index abe7d0177..a0898edbd 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -641,22 +641,22 @@ void PackInstallTask::installConfigs() jobPtr->addNetAction(dl); archivePath = entry->getFullPath(); - connect(jobPtr.get(), &NetJob::succeeded, this, [&]() { + connect(jobPtr.get(), &NetJob::succeeded, this, [this]() { abortable = false; jobPtr.reset(); extractConfigs(); }); - connect(jobPtr.get(), &NetJob::failed, [&](QString reason) { + connect(jobPtr.get(), &NetJob::failed, [this](QString reason) { abortable = false; jobPtr.reset(); emitFailed(reason); }); - connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total) { + connect(jobPtr.get(), &NetJob::progress, [this](qint64 current, qint64 total) { abortable = true; setProgress(current, total); }); connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propagateStepProgress); - connect(jobPtr.get(), &NetJob::aborted, [&] { + connect(jobPtr.get(), &NetJob::aborted, [this] { abortable = false; jobPtr.reset(); emitAborted(); @@ -685,8 +685,8 @@ void PackInstallTask::extractConfigs() m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/minecraft"); #endif - connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, [&]() { downloadMods(); }); - connect(&m_extractFutureWatcher, &QFutureWatcher::canceled, this, [&]() { emitAborted(); }); + connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, [this]() { downloadMods(); }); + connect(&m_extractFutureWatcher, &QFutureWatcher::canceled, this, [this]() { emitAborted(); }); m_extractFutureWatcher.setFuture(m_extractFuture); } diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp index 4c2f3d69e..7ff38d57e 100644 --- a/launcher/modplatform/flame/FileResolvingTask.cpp +++ b/launcher/modplatform/flame/FileResolvingTask.cpp @@ -87,6 +87,30 @@ void Flame::FileResolvingTask::executeTask() m_task->start(); } +PackedResourceType getResourceType(int classId) +{ + switch (classId) { + case 17: // Worlds + return PackedResourceType::WorldSave; + case 6: // Mods + return PackedResourceType::Mod; + case 12: // Resource Packs + // return PackedResourceType::ResourcePack; // not really a resourcepack + /* fallthrough */ + case 4546: // Customization + // return PackedResourceType::ShaderPack; // not really a shaderPack + /* fallthrough */ + case 4471: // Modpacks + /* fallthrough */ + case 5: // Bukkit Plugins + /* fallthrough */ + case 4559: // Addons + /* fallthrough */ + default: + return PackedResourceType::UNKNOWN; + } +} + void Flame::FileResolvingTask::netJobFinished() { setProgress(1, 3); @@ -144,7 +168,7 @@ void Flame::FileResolvingTask::netJobFinished() << " reason: " << parse_error.errorString(); qWarning() << *m_result; - failed(parse_error.errorString()); + getFlameProjects(); return; } @@ -232,6 +256,10 @@ void Flame::FileResolvingTask::getFlameProjects() setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(file->version.fileName)); FlameMod::loadIndexedPack(file->pack, entry_obj); + file->resourceType = getResourceType(Json::requireInteger(entry_obj, "classId", "modClassId")); + if (file->resourceType == PackedResourceType::WorldSave) { + file->targetFolder = "saves"; + } } } catch (Json::JsonException& e) { qDebug() << e.cause(); diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index ddd48c2b1..699eb792a 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -270,21 +270,44 @@ std::optional FlameAPI::getLatestVersion(QList instanceLoaders, ModPlatform::ModLoaderTypes modLoaders) { - // edge case: mod has installed for forge but the instance is fabric => fabric version will be prioritizated on update - auto bestVersion = [&versions](ModPlatform::ModLoaderTypes loader) { - std::optional ver; - for (auto file_tmp : versions) { - if (file_tmp.loaders & loader && (!ver.has_value() || file_tmp.date > ver->date)) { - ver = file_tmp; + static const auto noLoader = ModPlatform::ModLoaderType(0); + QHash bestMatch; + auto checkVersion = [&bestMatch](const ModPlatform::IndexedVersion& version, const ModPlatform::ModLoaderType& loader) { + if (bestMatch.contains(loader)) { + auto best = bestMatch.value(loader); + if (version.date > best.date) { + bestMatch[loader] = version; + } + } else { + bestMatch[loader] = version; + } + }; + for (auto file_tmp : versions) { + auto loaders = ModPlatform::modLoaderTypesToList(file_tmp.loaders); + if (loaders.isEmpty()) { + checkVersion(file_tmp, noLoader); + } else { + for (auto loader : loaders) { + checkVersion(file_tmp, loader); } } - return ver; - }; - for (auto l : instanceLoaders) { - auto ver = bestVersion(l); - if (ver.has_value()) { - return ver; + } + // edge case: mod has installed for forge but the instance is fabric => fabric version will be prioritizated on update + auto currentLoaders = instanceLoaders + ModPlatform::modLoaderTypesToList(modLoaders); + currentLoaders.append(noLoader); // add a fallback in case the versions do not define a loader + + for (auto loader : currentLoaders) { + if (bestMatch.contains(loader)) { + auto bestForLoader = bestMatch.value(loader); + // awkward case where the mod has only two loaders and one of them is not specified + if (loader != noLoader && bestMatch.contains(noLoader) && bestMatch.size() == 2) { + auto bestForNoLoader = bestMatch.value(noLoader); + if (bestForNoLoader.date > bestForLoader.date) { + return bestForNoLoader; + } + } + return bestForLoader; } } - return bestVersion(modLoaders); + return {}; } diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp index 370f37c5f..2b469276d 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.cpp +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -121,59 +121,65 @@ ModPlatform::IndexedVersion FlameCheckUpdate::getFileInfo(int addonId, int fileI * */ void FlameCheckUpdate::executeTask() { - setStatus(tr("Preparing mods for CurseForge...")); + setStatus(tr("Preparing resources for CurseForge...")); int i = 0; - for (auto* mod : m_mods) { - setStatus(tr("Getting API response from CurseForge for '%1'...").arg(mod->name())); - setProgress(i++, m_mods.size()); + for (auto* resource : m_resources) { + setStatus(tr("Getting API response from CurseForge for '%1'...").arg(resource->name())); + setProgress(i++, m_resources.size()); - auto latest_vers = api.getLatestVersions({ { mod->metadata()->project_id.toString() }, m_game_versions }); + auto latest_vers = api.getLatestVersions({ { resource->metadata()->project_id.toString() }, m_game_versions }); // Check if we were aborted while getting the latest version if (m_was_aborted) { aborted(); return; } - auto latest_ver = api.getLatestVersion(latest_vers, m_loaders_list, mod->loaders()); + auto latest_ver = api.getLatestVersion(latest_vers, m_loaders_list, resource->metadata()->loaders); - setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(mod->name())); + setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(resource->name())); if (!latest_ver.has_value() || !latest_ver->addonId.isValid()) { - emit checkFailed(mod, tr("No valid version found for this mod. It's probably unavailable for the current game " - "version / mod loader.")); + QString reason; + if (dynamic_cast(resource) != nullptr) + reason = + tr("No valid version found for this resource. It's probably unavailable for the current game " + "version / mod loader."); + else + reason = tr("No valid version found for this resource. It's probably unavailable for the current game version."); + + emit checkFailed(resource, reason); continue; } - if (latest_ver->downloadUrl.isEmpty() && latest_ver->fileId != mod->metadata()->file_id) { + if (latest_ver->downloadUrl.isEmpty() && latest_ver->fileId != resource->metadata()->file_id) { auto pack = getProjectInfo(latest_ver.value()); auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, latest_ver->fileId.toString()); - emit checkFailed(mod, tr("Mod has a new update available, but is not downloadable using CurseForge."), recover_url); + emit checkFailed(resource, tr("Resource has a new update available, but is not downloadable using CurseForge."), recover_url); continue; } // Fake pack with the necessary info to pass to the download task :) auto pack = std::make_shared(); - pack->name = mod->name(); - pack->slug = mod->metadata()->slug; - pack->addonId = mod->metadata()->project_id; - pack->websiteUrl = mod->homeurl(); - for (auto& author : mod->authors()) - pack->authors.append({ author }); - pack->description = mod->description(); + pack->name = resource->name(); + pack->slug = resource->metadata()->slug; + pack->addonId = resource->metadata()->project_id; pack->provider = ModPlatform::ResourceProvider::FLAME; - if (!latest_ver->hash.isEmpty() && (mod->metadata()->hash != latest_ver->hash || mod->status() == ModStatus::NotInstalled)) { - auto old_version = mod->version(); - if (old_version.isEmpty() && mod->status() != ModStatus::NotInstalled) { - auto current_ver = getFileInfo(latest_ver->addonId.toInt(), mod->metadata()->file_id.toInt()); - old_version = current_ver.version; + if (!latest_ver->hash.isEmpty() && + (resource->metadata()->hash != latest_ver->hash || resource->status() == ResourceStatus::NOT_INSTALLED)) { + auto old_version = resource->metadata()->version_number; + if (old_version.isEmpty()) { + if (resource->status() == ResourceStatus::NOT_INSTALLED) + old_version = tr("Not installed"); + else + old_version = tr("Unknown"); } - auto download_task = makeShared(pack, latest_ver.value(), m_mods_folder); - m_updatable.emplace_back(pack->name, mod->metadata()->hash, old_version, latest_ver->version, latest_ver->version_type, - api.getModFileChangelog(latest_ver->addonId.toInt(), latest_ver->fileId.toInt()), - ModPlatform::ResourceProvider::FLAME, download_task, mod->enabled()); + auto download_task = makeShared(pack, latest_ver.value(), m_resource_model); + m_updates.emplace_back(pack->name, resource->metadata()->hash, old_version, latest_ver->version, latest_ver->version_type, + api.getModFileChangelog(latest_ver->addonId.toInt(), latest_ver->fileId.toInt()), + ModPlatform::ResourceProvider::FLAME, download_task, resource->enabled()); } m_deps.append(std::make_shared(pack, latest_ver.value())); } diff --git a/launcher/modplatform/flame/FlameCheckUpdate.h b/launcher/modplatform/flame/FlameCheckUpdate.h index e30ae35b9..6543a0e04 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.h +++ b/launcher/modplatform/flame/FlameCheckUpdate.h @@ -7,11 +7,11 @@ class FlameCheckUpdate : public CheckUpdateTask { Q_OBJECT public: - FlameCheckUpdate(QList& mods, + FlameCheckUpdate(QList& resources, std::list& mcVersions, QList loadersList, - std::shared_ptr mods_folder) - : CheckUpdateTask(mods, mcVersions, loadersList, mods_folder) + std::shared_ptr resourceModel) + : CheckUpdateTask(resources, mcVersions, std::move(loadersList), std::move(resourceModel)) {} public slots: diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index b8c40ee42..6073e90a2 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -36,7 +36,7 @@ #include "FlameInstanceCreationTask.h" #include "QObjectPtr.h" -#include "minecraft/mod/tasks/LocalModUpdateTask.h" +#include "minecraft/mod/tasks/LocalResourceUpdateTask.h" #include "modplatform/flame/FileResolvingTask.h" #include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/FlameModIndex.h" @@ -75,12 +75,12 @@ bool FlameCreationTask::abort() return false; m_abort = true; - if (m_process_update_file_info_job) - m_process_update_file_info_job->abort(); - if (m_files_job) - m_files_job->abort(); - if (m_mod_id_resolver) - m_mod_id_resolver->abort(); + if (m_processUpdateFileInfoJob) + m_processUpdateFileInfoJob->abort(); + if (m_filesJob) + m_filesJob->abort(); + if (m_modIdResolver) + m_modIdResolver->abort(); return Task::abort(); } @@ -232,12 +232,12 @@ bool FlameCreationTask::updateInstance() 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; + m_processUpdateFileInfoJob = job; job->start(); loop.exec(); - m_process_update_file_info_job = nullptr; + m_processUpdateFileInfoJob = nullptr; } else { // We don't have an old index file, so we may duplicate stuff! auto dialog = CustomMessageBox::selectable(m_parent, tr("No index file."), @@ -430,25 +430,26 @@ 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); + if (!m_managedId.isEmpty()) + instance.setManagedPack("flame", m_managedId, m_pack.name, m_managedVersionId, m_pack.version); else instance.setManagedPack("flame", "", name(), "", ""); instance.setName(name()); - m_mod_id_resolver.reset(new Flame::FileResolvingTask(APPLICATION->network(), m_pack)); - connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::succeeded, this, [this, &loop] { idResolverSucceeded(loop); }); - connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) { - m_mod_id_resolver.reset(); + m_modIdResolver.reset(new Flame::FileResolvingTask(APPLICATION->network(), m_pack)); + connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, this, [this, &loop] { idResolverSucceeded(loop); }); + connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [this, &loop](QString reason) { + m_modIdResolver.reset(); setError(tr("Unable to resolve mod IDs:\n") + reason); loop.quit(); }); - connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::progress, this, &FlameCreationTask::setProgress); - connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::status, this, &FlameCreationTask::setStatus); - connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::stepProgress, this, &FlameCreationTask::propagateStepProgress); - connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::details, this, &FlameCreationTask::setDetails); - m_mod_id_resolver->start(); + connect(m_modIdResolver.get(), &Flame::FileResolvingTask::aborted, &loop, &QEventLoop::quit); + connect(m_modIdResolver.get(), &Flame::FileResolvingTask::progress, this, &FlameCreationTask::setProgress); + connect(m_modIdResolver.get(), &Flame::FileResolvingTask::status, this, &FlameCreationTask::setStatus); + connect(m_modIdResolver.get(), &Flame::FileResolvingTask::stepProgress, this, &FlameCreationTask::propagateStepProgress); + connect(m_modIdResolver.get(), &Flame::FileResolvingTask::details, this, &FlameCreationTask::setDetails); + m_modIdResolver->start(); loop.exec(); @@ -467,14 +468,14 @@ bool FlameCreationTask::createInstance() void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) { - auto results = m_mod_id_resolver->getResults(); + auto results = m_modIdResolver->getResults(); // first check for blocked mods QList blocked_mods; auto anyBlocked = false; for (const auto& result : results.files.values()) { - if (result.version.fileName.endsWith(".zip")) { - m_ZIP_resources.append(std::make_pair(result.version.fileName, result.targetFolder)); + if (result.resourceType != PackedResourceType::Mod) { + m_otherResources.append(std::make_pair(result.version.fileName, result.targetFolder)); } if (result.version.downloadUrl.isEmpty()) { @@ -506,7 +507,7 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) copyBlockedMods(blocked_mods); setupDownloadJob(loop); } else { - m_mod_id_resolver.reset(); + m_modIdResolver.reset(); setError("Canceled"); loop.quit(); } @@ -517,8 +518,8 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) void FlameCreationTask::setupDownloadJob(QEventLoop& loop) { - m_files_job.reset(new NetJob(tr("Mod Download Flame"), APPLICATION->network())); - auto results = m_mod_id_resolver->getResults().files; + m_filesJob.reset(new NetJob(tr("Mod Download Flame"), APPLICATION->network())); + auto results = m_modIdResolver->getResults().files; QStringList optionalFiles; for (auto& result : results) { @@ -553,26 +554,26 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop) if (!result.version.downloadUrl.isEmpty()) { qDebug() << "Will download" << result.version.downloadUrl << "to" << path; auto dl = Net::ApiDownload::makeFile(result.version.downloadUrl, path); - m_files_job->addNetAction(dl); + m_filesJob->addNetAction(dl); } } - connect(m_files_job.get(), &NetJob::finished, this, [this, &loop]() { - m_files_job.reset(); - validateZIPResources(loop); + connect(m_filesJob.get(), &NetJob::finished, this, [this, &loop]() { + m_filesJob.reset(); + validateOtherResources(loop); }); - connect(m_files_job.get(), &NetJob::failed, [&](QString reason) { - m_files_job.reset(); + connect(m_filesJob.get(), &NetJob::failed, [this](QString reason) { + m_filesJob.reset(); setError(reason); }); - connect(m_files_job.get(), &NetJob::progress, this, [this](qint64 current, qint64 total) { + connect(m_filesJob.get(), &NetJob::progress, this, [this](qint64 current, qint64 total) { setDetails(tr("%1 out of %2 complete").arg(current).arg(total)); setProgress(current, total); }); - connect(m_files_job.get(), &NetJob::stepProgress, this, &FlameCreationTask::propagateStepProgress); + connect(m_filesJob.get(), &NetJob::stepProgress, this, &FlameCreationTask::propagateStepProgress); setStatus(tr("Downloading mods...")); - m_files_job->start(); + m_filesJob->start(); } /// @brief copy the matched blocked mods to the instance staging area @@ -596,8 +597,14 @@ void FlameCreationTask::copyBlockedMods(QList const& blocked_mods) qDebug() << "Will try to copy" << mod.localPath << "to" << destPath; - if (!FS::copy(mod.localPath, destPath)()) { - qDebug() << "Copy of" << mod.localPath << "to" << destPath << "Failed"; + if (mod.move) { + if (!FS::move(mod.localPath, destPath)) { + qDebug() << "Move of" << mod.localPath << "to" << destPath << "Failed"; + } + } else { + if (!FS::copy(mod.localPath, destPath)()) { + qDebug() << "Copy of" << mod.localPath << "to" << destPath << "Failed"; + } } i++; @@ -607,11 +614,11 @@ void FlameCreationTask::copyBlockedMods(QList const& blocked_mods) setAbortable(true); } -void FlameCreationTask::validateZIPResources(QEventLoop& loop) +void FlameCreationTask::validateOtherResources(QEventLoop& loop) { - qDebug() << "Validating whether resources stored as .zip are in the right place"; + qDebug() << "Validating whether other resources are in the right place"; QStringList zipMods; - for (auto [fileName, targetFolder] : m_ZIP_resources) { + for (auto [fileName, targetFolder] : m_otherResources) { qDebug() << "Checking" << fileName << "..."; auto localPath = FS::PathCombine(m_stagingPath, "minecraft", targetFolder, fileName); @@ -671,21 +678,23 @@ void FlameCreationTask::validateZIPResources(QEventLoop& loop) installWorld(worldPath); break; case PackedResourceType::UNKNOWN: + /* fallthrough */ default: qDebug() << "Can't Identify" << fileName << "at" << localPath << ", leaving it where it is."; break; } } - auto task = makeShared(this, "CreateModMetadata", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); - auto results = m_mod_id_resolver->getResults().files; + // TODO make this work with other sorts of resource + auto task = makeShared("CreateModMetadata", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); + auto results = m_modIdResolver->getResults().files; auto folder = FS::PathCombine(m_stagingPath, "minecraft", "mods", ".index"); for (auto file : results) { if (file.targetFolder != "mods" || (file.version.fileName.endsWith(".zip") && !zipMods.contains(file.version.fileName))) { continue; } - task->addTask(makeShared(folder, file.pack, file.version)); + task->addTask(makeShared(folder, file.pack, file.version)); } connect(task.get(), &Task::finished, &loop, &QEventLoop::quit); - m_process_update_file_info_job = task; + m_processUpdateFileInfoJob = task; task->start(); } diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.h b/launcher/modplatform/flame/FlameInstanceCreationTask.h index 28ab176c2..3e586a416 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.h +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.h @@ -57,7 +57,7 @@ class FlameCreationTask final : public InstanceCreationTask { QString id, QString version_id, QString original_instance_id = {}) - : InstanceCreationTask(), m_parent(parent), m_managed_id(std::move(id)), m_managed_version_id(std::move(version_id)) + : InstanceCreationTask(), m_parent(parent), m_managedId(std::move(id)), m_managedVersionId(std::move(version_id)) { setStagingPath(staging_path); setParentSettings(global_settings); @@ -74,22 +74,22 @@ class FlameCreationTask final : public InstanceCreationTask { void idResolverSucceeded(QEventLoop&); void setupDownloadJob(QEventLoop&); void copyBlockedMods(QList const& blocked_mods); - void validateZIPResources(QEventLoop& loop); + void validateOtherResources(QEventLoop& loop); QString getVersionForLoader(QString uid, QString loaderType, QString version, QString mcVersion); private: QWidget* m_parent = nullptr; - shared_qobject_ptr m_mod_id_resolver; + shared_qobject_ptr m_modIdResolver; Flame::Manifest m_pack; // Handle to allow aborting - Task::Ptr m_process_update_file_info_job = nullptr; - NetJob::Ptr m_files_job = nullptr; + Task::Ptr m_processUpdateFileInfoJob = nullptr; + NetJob::Ptr m_filesJob = nullptr; - QString m_managed_id, m_managed_version_id; + QString m_managedId, m_managedVersionId; - QList> m_ZIP_resources; + QList> m_otherResources; std::optional m_instance; }; diff --git a/launcher/modplatform/flame/FlamePackExportTask.cpp b/launcher/modplatform/flame/FlamePackExportTask.cpp index d661f1f05..3405b702f 100644 --- a/launcher/modplatform/flame/FlamePackExportTask.cpp +++ b/launcher/modplatform/flame/FlamePackExportTask.cpp @@ -103,8 +103,7 @@ void FlamePackExportTask::collectHashes() setStatus(tr("Finding file hashes...")); setProgress(1, 5); auto allMods = mcInstance->loaderModList()->allMods(); - ConcurrentTask::Ptr hashingTask( - new ConcurrentTask(this, "MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())); + ConcurrentTask::Ptr hashingTask(new ConcurrentTask("MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())); task.reset(hashingTask); for (const QFileInfo& file : files) { const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath()); diff --git a/launcher/modplatform/flame/PackManifest.h b/launcher/modplatform/flame/PackManifest.h index 49a0b2d68..7af3b9d6b 100644 --- a/launcher/modplatform/flame/PackManifest.h +++ b/launcher/modplatform/flame/PackManifest.h @@ -40,6 +40,7 @@ #include #include #include +#include "minecraft/mod/tasks/LocalResourceParse.h" #include "modplatform/ModIndex.h" namespace Flame { @@ -54,6 +55,7 @@ struct File { // our QString targetFolder = QStringLiteral("mods"); + PackedResourceType resourceType; }; struct Modloader { diff --git a/launcher/modplatform/helpers/ExportToModList.cpp b/launcher/modplatform/helpers/ExportToModList.cpp index aea16ab50..bddc7e320 100644 --- a/launcher/modplatform/helpers/ExportToModList.cpp +++ b/launcher/modplatform/helpers/ExportToModList.cpp @@ -28,7 +28,7 @@ QString toHTML(QList mods, OptionalData extraData) auto meta = mod->metadata(); auto modName = mod->name().toHtmlEscaped(); if (extraData & Url) { - auto url = mod->metaurl().toHtmlEscaped(); + auto url = mod->homepage().toHtmlEscaped(); if (!url.isEmpty()) modName = QString("%2").arg(url, modName); } @@ -65,7 +65,7 @@ QString toMarkdown(QList mods, OptionalData extraData) auto meta = mod->metadata(); auto modName = toMarkdownEscaped(mod->name()); if (extraData & Url) { - auto url = mod->metaurl(); + auto url = mod->homepage(); if (!url.isEmpty()) modName = QString("[%1](%2)").arg(modName, url); } @@ -95,7 +95,7 @@ QString toPlainTXT(QList mods, OptionalData extraData) auto line = modName; if (extraData & Url) { - auto url = mod->metaurl(); + auto url = mod->homepage(); if (!url.isEmpty()) line += QString(" (%1)").arg(url); } @@ -124,7 +124,7 @@ QString toJSON(QList mods, OptionalData extraData) QJsonObject line; line["name"] = modName; if (extraData & Url) { - auto url = mod->metaurl(); + auto url = mod->homepage(); if (!url.isEmpty()) line["url"] = url; } @@ -156,7 +156,7 @@ QString toCSV(QList mods, OptionalData extraData) data << modName; if (extraData & Url) - data << mod->metaurl(); + data << mod->homepage(); if (extraData & Version) { auto ver = mod->version(); if (ver.isEmpty() && meta != nullptr) @@ -203,7 +203,7 @@ QString exportToModList(QList mods, QString lineTemplate) for (auto mod : mods) { auto meta = mod->metadata(); auto modName = mod->name(); - auto url = mod->metaurl(); + auto url = mod->homepage(); auto ver = mod->version(); if (ver.isEmpty() && meta != nullptr) ver = meta->version().toString(); diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.cpp b/launcher/modplatform/helpers/NetworkResourceAPI.cpp index 974e732a7..d0e1bb912 100644 --- a/launcher/modplatform/helpers/NetworkResourceAPI.cpp +++ b/launcher/modplatform/helpers/NetworkResourceAPI.cpp @@ -43,11 +43,16 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks& callbacks.on_succeed(doc); }); - QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) { + // Capture a weak_ptr instead of a shared_ptr to avoid circular dependency issues. + // This prevents the lambda from extending the lifetime of the shared resource, + // as it only temporarily locks the resource when needed. + auto weak = netJob.toWeakRef(); + QObject::connect(netJob.get(), &NetJob::failed, [weak, callbacks](const QString& reason) { int network_error_code = -1; - if (auto* failed_action = netJob->getFailedActions().at(0); failed_action) - network_error_code = failed_action->replyStatusCode(); - + if (auto netJob = weak.lock()) { + if (auto* failed_action = netJob->getFailedActions().at(0); failed_action) + network_error_code = failed_action->replyStatusCode(); + } callbacks.on_fail(reason, network_error_code); }); QObject::connect(netJob.get(), &NetJob::aborted, [callbacks] { callbacks.on_abort(); }); @@ -102,11 +107,17 @@ 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) - network_error_code = failed_action->replyStatusCode(); + // Capture a weak_ptr instead of a shared_ptr to avoid circular dependency issues. + // This prevents the lambda from extending the lifetime of the shared resource, + // as it only temporarily locks the resource when needed. + auto weak = netJob.toWeakRef(); + QObject::connect(netJob.get(), &NetJob::failed, [weak, callbacks](const QString& reason) { + int network_error_code = -1; + if (auto netJob = weak.lock()) { + if (auto* failed_action = netJob->getFailedActions().at(0); failed_action) + network_error_code = failed_action->replyStatusCode(); + } callbacks.on_fail(reason, network_error_code); }); @@ -141,7 +152,7 @@ Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args, netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response)); - QObject::connect(netJob.get(), &NetJob::succeeded, [=] { + QObject::connect(netJob.get(), &NetJob::succeeded, [response, callbacks, args] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { @@ -153,11 +164,17 @@ 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) - network_error_code = failed_action->replyStatusCode(); + // Capture a weak_ptr instead of a shared_ptr to avoid circular dependency issues. + // This prevents the lambda from extending the lifetime of the shared resource, + // as it only temporarily locks the resource when needed. + auto weak = netJob.toWeakRef(); + QObject::connect(netJob.get(), &NetJob::failed, [weak, callbacks](const QString& reason) { + int network_error_code = -1; + if (auto netJob = weak.lock()) { + if (auto* failed_action = netJob->getFailedActions().at(0); failed_action) + network_error_code = failed_action->replyStatusCode(); + } callbacks.on_fail(reason, network_error_code); }); return netJob; diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp index 7157f7f2d..d6252663f 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp @@ -138,7 +138,7 @@ void PackInstallTask::install() if (unzipMcDir.exists()) { // ok, found minecraft dir, move contents to instance dir if (!FS::move(m_stagingPath + "/unzip/minecraft", m_stagingPath + "/minecraft")) { - emitFailed(tr("Failed to move unzipped Minecraft!")); + emitFailed(tr("Failed to move unpacked Minecraft!")); return; } } diff --git a/launcher/modplatform/modrinth/ModrinthAPI.cpp b/launcher/modplatform/modrinth/ModrinthAPI.cpp index 323711e02..bdef1a0e5 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.cpp +++ b/launcher/modplatform/modrinth/ModrinthAPI.cpp @@ -34,7 +34,7 @@ Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_f auto body_raw = body.toJson(); netJob->addNetAction(Net::ApiUpload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files"), response, body_raw)); - + netJob->setAskRetry(false); return netJob; } @@ -54,7 +54,7 @@ Task::Ptr ModrinthAPI::latestVersion(QString hash, if (mcVersions.has_value()) { QStringList game_versions; for (auto& ver : mcVersions.value()) { - game_versions.append(ver.toString()); + game_versions.append(mapMCVersionToModrinth(ver)); } Json::writeStringList(body_obj, "game_versions", game_versions); } @@ -87,7 +87,7 @@ Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes, if (mcVersions.has_value()) { QStringList game_versions; for (auto& ver : mcVersions.value()) { - game_versions.append(ver.toString()); + game_versions.append(mapMCVersionToModrinth(ver)); } Json::writeStringList(body_obj, "game_versions", game_versions); } diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index 070f59dad..a0e7efb08 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -71,16 +71,33 @@ class ModrinthAPI : public NetworkResourceAPI { static auto getSideFilters(QString side) -> const QString { - if (side.isEmpty() || side == "both") { + if (side.isEmpty()) { return {}; } + if (side == "both") + return QString("\"client_side:required\"],[\"server_side:required\""); if (side == "client") - return QString("\"client_side:required\",\"client_side:optional\""); + return QString("\"client_side:required\",\"client_side:optional\"],[\"server_side:optional\",\"server_side:unsupported\""); if (side == "server") - return QString("\"server_side:required\",\"server_side:optional\""); + return QString("\"server_side:required\",\"server_side:optional\"],[\"client_side:optional\",\"client_side:unsupported\""); return {}; } + [[nodiscard]] static inline QString mapMCVersionFromModrinth(QString v) + { + static const QString preString = " Pre-Release "; + bool pre = false; + if (v.contains("-pre")) { + pre = true; + v.replace("-pre", preString); + } + v.replace("-", " "); + if (pre) { + v.replace(" Pre Release ", preString); + } + return v; + } + private: [[nodiscard]] static QString resourceTypeParameter(ModPlatform::ResourceType type) { @@ -116,6 +133,8 @@ class ModrinthAPI : public NetworkResourceAPI { } if (args.categoryIds.has_value() && !args.categoryIds->empty()) facets_list.append(QString("[%1]").arg(getCategoriesFilters(args.categoryIds.value()))); + if (args.openSource) + facets_list.append("[\"open_source:true\"]"); facets_list.append(QString("[\"project_type:%1\"]").arg(resourceTypeParameter(args.type))); @@ -170,7 +189,7 @@ class ModrinthAPI : public NetworkResourceAPI { { QString s; for (auto& ver : mcVersions) { - s += QString("\"versions:%1\",").arg(ver.toString()); + s += QString("\"versions:%1\",").arg(mapMCVersionToModrinth(ver)); } s.remove(s.length() - 1, 1); // remove last comma return s.isEmpty() ? QString() : s; @@ -187,7 +206,7 @@ class ModrinthAPI : public NetworkResourceAPI { : QString("%1/project/%2/version?game_versions=[\"%3\"]&loaders=[\"%4\"]") .arg(BuildConfig.MODRINTH_PROD_URL) .arg(args.dependency.addonId.toString()) - .arg(args.mcVersion.toString()) + .arg(mapMCVersionToModrinth(args.mcVersion)) .arg(getModLoaderStrings(args.loader).join("\",\"")); }; }; diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp index 70bf138a8..aa371f280 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp @@ -8,20 +8,13 @@ #include "QObjectPtr.h" #include "ResourceDownloadTask.h" +#include "modplatform/ModIndex.h" #include "modplatform/helpers/HashUtils.h" #include "tasks/ConcurrentTask.h" static ModrinthAPI api; -ModrinthCheckUpdate::ModrinthCheckUpdate(QList& mods, - std::list& mcVersions, - QList loadersList, - std::shared_ptr mods_folder) - : CheckUpdateTask(mods, mcVersions, loadersList, mods_folder) - , m_hash_type(ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first()) -{} - bool ModrinthCheckUpdate::abort() { if (m_job) @@ -36,24 +29,24 @@ bool ModrinthCheckUpdate::abort() * */ void ModrinthCheckUpdate::executeTask() { - setStatus(tr("Preparing mods for Modrinth...")); - setProgress(0, 9); + setStatus(tr("Preparing resources for Modrinth...")); + setProgress(0, (m_loaders_list.isEmpty() ? 1 : m_loaders_list.length()) * 2 + 1); auto hashing_task = - makeShared(this, "MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); - for (auto* mod : m_mods) { - auto hash = mod->metadata()->hash; + makeShared("MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); + for (auto* resource : m_resources) { + auto hash = resource->metadata()->hash; // Sadly the API can only handle one hash type per call, se we // need to generate a new hash if the current one is innadequate // (though it will rarely happen, if at all) - if (mod->metadata()->hash_format != m_hash_type) { - auto hash_task = Hashing::createHasher(mod->fileinfo().absoluteFilePath(), ModPlatform::ResourceProvider::MODRINTH); - connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) { m_mappings.insert(hash, mod); }); + if (resource->metadata()->hash_format != m_hash_type) { + auto hash_task = Hashing::createHasher(resource->fileinfo().absoluteFilePath(), ModPlatform::ResourceProvider::MODRINTH); + connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, resource](QString hash) { m_mappings.insert(hash, resource); }); connect(hash_task.get(), &Task::failed, [this] { failed("Failed to generate hash"); }); hashing_task->addTask(hash_task); } else { - m_mappings.insert(hash, mod); + m_mappings.insert(hash, resource); } } @@ -62,10 +55,28 @@ void ModrinthCheckUpdate::executeTask() hashing_task->start(); } -void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr response, - ModPlatform::ModLoaderTypes loader, - bool forceModLoaderCheck) +void ModrinthCheckUpdate::getUpdateModsForLoader(std::optional loader) { + setStatus(tr("Waiting for the API response from Modrinth...")); + setProgress(m_progress + 1, m_progressTotal); + + auto response = std::make_shared(); + QStringList hashes = m_mappings.keys(); + auto job = api.latestVersions(hashes, m_hash_type, m_game_versions, loader, response); + + connect(job.get(), &Task::succeeded, this, [this, response, loader] { checkVersionsResponse(response, loader); }); + + connect(job.get(), &Task::failed, this, &ModrinthCheckUpdate::checkNextLoader); + + m_job = job; + job->start(); +} + +void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr response, std::optional loader) +{ + setStatus(tr("Parsing the API response from Modrinth...")); + setProgress(m_progress + 1, m_progressTotal); + QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { @@ -77,31 +88,28 @@ void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr resp return; } - setStatus(tr("Parsing the API response from Modrinth...")); - setProgress(m_next_loader_idx * 2, 9); - try { - for (auto hash : m_mappings.keys()) { - if (forceModLoaderCheck && !(m_mappings[hash]->loaders() & loader)) { - continue; - } + auto iter = m_mappings.begin(); + + while (iter != m_mappings.end()) { + const QString hash = iter.key(); + Resource* resource = iter.value(); + auto project_obj = doc[hash].toObject(); // If the returned project is empty, but we have Modrinth metadata, // it means this specific version is not available if (project_obj.isEmpty()) { qDebug() << "Mod " << m_mappings.find(hash).value()->name() << " got an empty response." << "Hash: " << hash; - + ++iter; continue; } // Sometimes a version may have multiple files, one with "forge" and one with "fabric", // so we may want to filter it QString loader_filter; - static auto flags = { ModPlatform::ModLoaderType::NeoForge, ModPlatform::ModLoaderType::Forge, - ModPlatform::ModLoaderType::Quilt, ModPlatform::ModLoaderType::Fabric }; - for (auto flag : flags) { - if (loader.testFlag(flag)) { + if (loader.has_value()) { + for (auto flag : ModPlatform::modLoaderTypesToList(*loader)) { loader_filter = ModPlatform::getModLoaderAsString(flag); break; } @@ -116,106 +124,72 @@ void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr resp auto project_ver = Modrinth::loadIndexedPackVersion(project_obj, m_hash_type, loader_filter); if (project_ver.downloadUrl.isEmpty()) { qCritical() << "Modrinth mod without download url!" << project_ver.fileName; - + ++iter; continue; } - auto mod_iter = m_mappings.find(hash); - if (mod_iter == m_mappings.end()) { - qCritical() << "Failed to remap mod from Modrinth!"; - continue; - } - auto mod = *mod_iter; - m_mappings.remove(hash); - - auto key = project_ver.hash; - // Fake pack with the necessary info to pass to the download task :) auto pack = std::make_shared(); - pack->name = mod->name(); - pack->slug = mod->metadata()->slug; - pack->addonId = mod->metadata()->project_id; - pack->websiteUrl = mod->homeurl(); - for (auto& author : mod->authors()) - pack->authors.append({ author }); - pack->description = mod->description(); + pack->name = resource->name(); + pack->slug = resource->metadata()->slug; + pack->addonId = resource->metadata()->project_id; pack->provider = ModPlatform::ResourceProvider::MODRINTH; - if ((key != hash && project_ver.is_preferred) || (mod->status() == ModStatus::NotInstalled)) { - if (mod->version() == project_ver.version_number) - continue; + if ((project_ver.hash != hash && project_ver.is_preferred) || (resource->status() == ResourceStatus::NOT_INSTALLED)) { + auto download_task = makeShared(pack, project_ver, m_resource_model); - auto download_task = makeShared(pack, project_ver, m_mods_folder); + QString old_version = resource->metadata()->version_number; + if (old_version.isEmpty()) { + if (resource->status() == ResourceStatus::NOT_INSTALLED) + old_version = tr("Not installed"); + else + old_version = tr("Unknown"); + } - m_updatable.emplace_back(pack->name, hash, mod->version(), project_ver.version_number, project_ver.version_type, - project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task, mod->enabled()); + m_updates.emplace_back(pack->name, hash, old_version, project_ver.version_number, project_ver.version_type, + project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task, resource->enabled()); } m_deps.append(std::make_shared(pack, project_ver)); + + iter = m_mappings.erase(iter); } } catch (Json::JsonException& e) { - emitFailed(e.cause() + " : " + e.what()); + emitFailed(e.cause() + ": " + e.what()); return; } checkNextLoader(); } -void ModrinthCheckUpdate::getUpdateModsForLoader(ModPlatform::ModLoaderTypes loader, bool forceModLoaderCheck) -{ - auto response = std::make_shared(); - QStringList hashes; - if (forceModLoaderCheck) { - for (auto hash : m_mappings.keys()) { - if (m_mappings[hash]->loaders() & loader) { - hashes.append(hash); - } - } - } else { - hashes = m_mappings.keys(); - } - auto job = api.latestVersions(hashes, m_hash_type, m_game_versions, loader, response); - - connect(job.get(), &Task::succeeded, this, - [this, response, loader, forceModLoaderCheck] { checkVersionsResponse(response, loader, forceModLoaderCheck); }); - - connect(job.get(), &Task::failed, this, &ModrinthCheckUpdate::checkNextLoader); - - setStatus(tr("Waiting for the API response from Modrinth...")); - setProgress(m_next_loader_idx * 2 - 1, 9); - - m_job = job; - job->start(); -} - void ModrinthCheckUpdate::checkNextLoader() { if (m_mappings.isEmpty()) { emitSucceeded(); return; } - if (m_next_loader_idx < m_loaders_list.size()) { - getUpdateModsForLoader(m_loaders_list.at(m_next_loader_idx)); - m_next_loader_idx++; + + if (m_loaders_list.isEmpty() && m_loader_idx == 0) { + getUpdateModsForLoader({}); + m_loader_idx++; return; } - static auto flags = { ModPlatform::ModLoaderType::NeoForge, ModPlatform::ModLoaderType::Forge, ModPlatform::ModLoaderType::Quilt, - ModPlatform::ModLoaderType::Fabric }; - for (auto flag : flags) { - if (!m_loaders_list.contains(flag)) { - m_loaders_list.append(flag); - m_next_loader_idx++; - setProgress(m_next_loader_idx * 2 - 1, 9); - for (auto m : m_mappings) { - if (m->loaders() & flag) { - getUpdateModsForLoader(flag, true); - return; - } - } - setProgress(m_next_loader_idx * 2, 9); - } + + if (m_loader_idx < m_loaders_list.size()) { + getUpdateModsForLoader(m_loaders_list.at(m_loader_idx)); + m_loader_idx++; + return; } - for (auto m : m_mappings) { - emit checkFailed(m, - tr("No valid version found for this mod. It's probably unavailable for the current game version / mod loader.")); + + for (auto resource : m_mappings) { + QString reason; + + if (dynamic_cast(resource) != nullptr) + reason = + tr("No valid version found for this resource. It's probably unavailable for the current game " + "version / mod loader."); + else + reason = tr("No valid version found for this resource. It's probably unavailable for the current game version."); + + emit checkFailed(resource, reason); } + emitSucceeded(); - return; } diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.h b/launcher/modplatform/modrinth/ModrinthCheckUpdate.h index dab4bda2f..204b24784 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.h +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.h @@ -6,23 +6,26 @@ class ModrinthCheckUpdate : public CheckUpdateTask { Q_OBJECT public: - ModrinthCheckUpdate(QList& mods, + ModrinthCheckUpdate(QList& resources, std::list& mcVersions, QList loadersList, - std::shared_ptr mods_folder); + std::shared_ptr resourceModel) + : CheckUpdateTask(resources, mcVersions, std::move(loadersList), std::move(resourceModel)) + , m_hash_type(ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first()) + {} public slots: bool abort() override; protected slots: void executeTask() override; - void getUpdateModsForLoader(ModPlatform::ModLoaderTypes loader, bool forceModLoaderCheck = false); - void checkVersionsResponse(std::shared_ptr response, ModPlatform::ModLoaderTypes loader, bool forceModLoaderCheck = false); + void getUpdateModsForLoader(std::optional loader); + void checkVersionsResponse(std::shared_ptr response, std::optional loader); void checkNextLoader(); private: Task::Ptr m_job = nullptr; - QHash m_mappings; + QHash m_mappings; QString m_hash_type; - int m_next_loader_idx = 0; + int m_loader_idx = 0; }; diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 994dd774f..2cc8767a4 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -243,7 +243,8 @@ bool ModrinthCreationTask::createInstance() auto root_modpack_path = FS::PathCombine(m_stagingPath, m_root_path); auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path); - QHash mods; + // TODO make this work with other sorts of resource + QHash resources; for (auto file : m_files) { auto fileName = file.path; fileName = FS::RemoveInvalidPathChars(fileName); @@ -259,14 +260,16 @@ bool ModrinthCreationTask::createInstance() ModDetails d; d.mod_id = file_path; mod->setDetails(d); - mods[file.hash.toHex()] = mod; + resources[file.hash.toHex()] = mod; + } + if (file.downloads.empty()) { + setError(tr("The file '%1' is missing a download link. This is invalid in the pack format.").arg(fileName)); + return false; } - qDebug() << "Will try to download" << file.downloads.front() << "to" << file_path; auto dl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path); dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); downloadMods->addNetAction(dl); - if (!file.downloads.empty()) { // FIXME: This really needs to be put into a ConcurrentTask of // MultipleOptionsTask's , once those exist :) @@ -283,13 +286,13 @@ bool ModrinthCreationTask::createInstance() bool ended_well = false; - connect(downloadMods.get(), &NetJob::succeeded, this, [&]() { ended_well = true; }); - connect(downloadMods.get(), &NetJob::failed, [&](const QString& reason) { + connect(downloadMods.get(), &NetJob::succeeded, this, [&ended_well]() { ended_well = true; }); + connect(downloadMods.get(), &NetJob::failed, [this, &ended_well](const QString& reason) { ended_well = false; setError(reason); }); connect(downloadMods.get(), &NetJob::finished, &loop, &QEventLoop::quit); - connect(downloadMods.get(), &NetJob::progress, [&](qint64 current, qint64 total) { + connect(downloadMods.get(), &NetJob::progress, [this](qint64 current, qint64 total) { setDetails(tr("%1 out of %2 complete").arg(current).arg(total)); setProgress(current, total); }); @@ -302,18 +305,18 @@ bool ModrinthCreationTask::createInstance() loop.exec(); if (!ended_well) { - for (auto m : mods) { - delete m; + for (auto resource : resources) { + delete resource; } return ended_well; } QEventLoop ensureMetaLoop; QDir folder = FS::PathCombine(instance.modsRoot(), ".index"); - auto ensureMetadataTask = makeShared(mods, folder, ModPlatform::ResourceProvider::MODRINTH); - connect(ensureMetadataTask.get(), &Task::succeeded, this, [&]() { ended_well = true; }); + auto ensureMetadataTask = makeShared(resources, folder, ModPlatform::ResourceProvider::MODRINTH); + connect(ensureMetadataTask.get(), &Task::succeeded, this, [&ended_well]() { ended_well = true; }); connect(ensureMetadataTask.get(), &Task::finished, &ensureMetaLoop, &QEventLoop::quit); - connect(ensureMetadataTask.get(), &Task::progress, [&](qint64 current, qint64 total) { + connect(ensureMetadataTask.get(), &Task::progress, [this](qint64 current, qint64 total) { setDetails(tr("%1 out of %2 complete").arg(current).arg(total)); setProgress(current, total); }); @@ -323,10 +326,10 @@ bool ModrinthCreationTask::createInstance() m_task = ensureMetadataTask; ensureMetaLoop.exec(); - for (auto m : mods) { - delete m; + for (auto resource : resources) { + delete resource; } - mods.clear(); + resources.clear(); // Update information of the already installed instance, if any. if (m_instance && ended_well) { diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index b7c2757e5..d103170af 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -123,7 +123,7 @@ void ModrinthPackExportTask::collectHashes() modIter != allMods.end()) { const Mod* mod = *modIter; if (mod->metadata() != nullptr) { - QUrl& url = mod->metadata()->url; + const QUrl& url = mod->metadata()->url; // ensure the url is permitted on modrinth.com if (!url.isEmpty() && BuildConfig.MODRINTH_MRPACK_HOSTS.contains(url.host())) { qDebug() << "Resolving" << relative << "from index"; diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index 48b27a597..72550937c 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -131,9 +131,7 @@ void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArra pack.versionsLoaded = true; } -auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, - QString preferred_hash_type, - QString preferred_file_name) -> ModPlatform::IndexedVersion +ModPlatform::IndexedVersion Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_type, QString preferred_file_name) { ModPlatform::IndexedVersion file; @@ -145,7 +143,7 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, return {}; } for (auto mcVer : versionArray) { - file.mcVersion.append(mcVer.toString()); + file.mcVersion.append(ModrinthAPI::mapMCVersionFromModrinth(mcVer.toString())); } auto loaders = Json::requireArray(obj, "loaders"); for (auto loader : loaders) { @@ -247,9 +245,9 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, return {}; } -auto Modrinth::loadDependencyVersions([[maybe_unused]] const ModPlatform::Dependency& m, - QJsonArray& arr, - const BaseInstance* inst) -> ModPlatform::IndexedVersion +ModPlatform::IndexedVersion Modrinth::loadDependencyVersions([[maybe_unused]] const ModPlatform::Dependency& m, + QJsonArray& arr, + const BaseInstance* inst) { auto profile = (dynamic_cast(inst))->getPackProfile(); QString mcVersion = profile->getComponentVersion("net.minecraft"); diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp index c52a1743b..89ef6e4c4 100644 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp @@ -40,9 +40,6 @@ #include "modplatform/modrinth/ModrinthAPI.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" - #include static ModrinthAPI api; @@ -134,6 +131,7 @@ auto loadIndexedVersion(QJsonObject& obj) -> ModpackVersion auto gameVersions = Json::ensureArray(obj, "game_versions"); if (!gameVersions.isEmpty()) { file.gameVersion = Json::ensureString(gameVersions[0]); + file.gameVersion = ModrinthAPI::mapMCVersionFromModrinth(file.gameVersion); } auto loaders = Json::requireArray(obj, "loaders"); for (auto loader : loaders) { diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index 325b0a6e4..a3bb74399 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -35,7 +35,7 @@ namespace Packwiz { -auto getRealIndexName(QDir& index_dir, QString normalized_fname, bool should_find_match) -> QString +auto getRealIndexName(const QDir& index_dir, QString normalized_fname, bool should_find_match) -> QString { QFile index_file(index_dir.absoluteFilePath(normalized_fname)); @@ -72,7 +72,7 @@ auto stringEntry(toml::table table, QString entry_name) -> QString { auto node = table[StringUtils::toStdString(entry_name)]; if (!node) { - qCritical() << "Failed to read str property '" + entry_name + "' in mod metadata."; + qWarning() << "Failed to read str property '" + entry_name + "' in mod metadata."; return {}; } @@ -83,14 +83,14 @@ auto intEntry(toml::table table, QString entry_name) -> int { auto node = table[StringUtils::toStdString(entry_name)]; if (!node) { - qCritical() << "Failed to read int property '" + entry_name + "' in mod metadata."; + qWarning() << "Failed to read int property '" + entry_name + "' in mod metadata."; return {}; } return node.value_or(0); } -auto V1::createModFormat([[maybe_unused]] QDir& index_dir, +auto V1::createModFormat([[maybe_unused]] const QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod { @@ -119,10 +119,14 @@ auto V1::createModFormat([[maybe_unused]] QDir& index_dir, mod.mcVersions.sort(); mod.releaseType = mod_version.version_type; + mod.version_number = mod_version.version_number; + if (mod.version_number.isNull()) // on CurseForge, there is only a version name - not a version number + mod.version_number = mod_version.version; + return mod; } -auto V1::createModFormat(QDir& index_dir, [[maybe_unused]] ::Mod& internal_mod, QString slug) -> Mod +auto V1::createModFormat(const QDir& index_dir, [[maybe_unused]] ::Mod& internal_mod, QString slug) -> Mod { // Try getting metadata if it exists Mod mod{ getIndexForMod(index_dir, slug) }; @@ -134,7 +138,7 @@ auto V1::createModFormat(QDir& index_dir, [[maybe_unused]] ::Mod& internal_mod, return {}; } -void V1::updateModIndex(QDir& index_dir, Mod& mod) +void V1::updateModIndex(const QDir& index_dir, Mod& mod) { if (!mod.isValid()) { qCritical() << QString("Tried to update metadata of an invalid mod!"); @@ -186,11 +190,8 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod) } toml::array loaders; - for (auto loader : { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Cauldron, ModPlatform::LiteLoader, ModPlatform::Fabric, - ModPlatform::Quilt }) { - if (mod.loaders & loader) { - loaders.push_back(getModLoaderAsString(loader).toStdString()); - } + for (auto loader : ModPlatform::modLoaderTypesToList(mod.loaders)) { + loaders.push_back(getModLoaderAsString(loader).toStdString()); } toml::array mcVersions; for (auto version : mod.mcVersions) { @@ -211,6 +212,7 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod) { "x-prismlauncher-loaders", loaders }, { "x-prismlauncher-mc-versions", mcVersions }, { "x-prismlauncher-release-type", mod.releaseType.toString().toStdString() }, + { "x-prismlauncher-version-number", mod.version_number.toStdString() }, { "download", toml::table{ { "mode", mod.mode.toStdString() }, @@ -228,7 +230,7 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod) index_file.close(); } -void V1::deleteModIndex(QDir& index_dir, QString& mod_slug) +void V1::deleteModIndex(const QDir& index_dir, QString& mod_slug) { auto normalized_fname = indexFileName(mod_slug); auto real_fname = getRealIndexName(index_dir, normalized_fname); @@ -247,7 +249,7 @@ void V1::deleteModIndex(QDir& index_dir, QString& mod_slug) } } -void V1::deleteModIndex(QDir& index_dir, QVariant& mod_id) +void V1::deleteModIndex(const QDir& index_dir, QVariant& mod_id) { for (auto& file_name : index_dir.entryList(QDir::Filter::Files)) { auto mod = getIndexForMod(index_dir, file_name); @@ -259,7 +261,7 @@ void V1::deleteModIndex(QDir& index_dir, QVariant& mod_id) } } -auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod +auto V1::getIndexForMod(const QDir& index_dir, QString slug) -> Mod { Mod mod; @@ -295,7 +297,7 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod mod.name = stringEntry(table, "name"); mod.filename = stringEntry(table, "filename"); mod.side = stringToSide(stringEntry(table, "side")); - mod.releaseType = ModPlatform::IndexedVersionType(stringEntry(table, "x-prismlauncher-release-type")); + mod.releaseType = ModPlatform::IndexedVersionType(table["x-prismlauncher-release-type"].value_or("")); if (auto loaders = table["x-prismlauncher-loaders"]; loaders && loaders.is_array()) { for (auto&& loader : *loaders.as_array()) { if (loader.is_string()) { @@ -315,6 +317,7 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod mod.mcVersions.sort(); } } + mod.version_number = table["x-prismlauncher-version-number"].value_or(""); { // [download] info auto download_table = table["download"].as_table(); @@ -356,7 +359,7 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod return mod; } -auto V1::getIndexForMod(QDir& index_dir, QVariant& mod_id) -> Mod +auto V1::getIndexForMod(const QDir& index_dir, QVariant& mod_id) -> Mod { for (auto& file_name : index_dir.entryList(QDir::Filter::Files)) { auto mod = getIndexForMod(index_dir, file_name); diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h index 95362bbfe..44896e74c 100644 --- a/launcher/modplatform/packwiz/Packwiz.h +++ b/launcher/modplatform/packwiz/Packwiz.h @@ -32,11 +32,13 @@ class Mod; namespace Packwiz { -auto getRealIndexName(QDir& index_dir, QString normalized_index_name, bool should_match = false) -> QString; +auto getRealIndexName(const QDir& index_dir, QString normalized_index_name, bool should_match = false) -> QString; class V1 { public: enum class Side { ClientSide = 1 << 0, ServerSide = 1 << 1, UniversalSide = ClientSide | ServerSide }; + + // can also represent other resources beside loader mods - but this is what packwiz calls it struct Mod { QString slug{}; QString name{}; @@ -56,6 +58,7 @@ class V1 { ModPlatform::ResourceProvider provider{}; QVariant file_id{}; QVariant project_id{}; + QString version_number{}; public: // This is a totally heuristic, but should work for now. @@ -70,33 +73,33 @@ class V1 { /* Generates the object representing the information in a mod.pw.toml file via * its common representation in the launcher, when downloading mods. * */ - static auto createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod; + static auto createModFormat(const QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod; /* Generates the object representing the information in a mod.pw.toml file via * its common representation in the launcher, plus a necessary slug. * */ - static auto createModFormat(QDir& index_dir, ::Mod& internal_mod, QString slug) -> Mod; + static auto createModFormat(const QDir& index_dir, ::Mod& internal_mod, QString slug) -> Mod; /* Updates the mod index for the provided mod. * This creates a new index if one does not exist already * TODO: Ask the user if they want to override, and delete the old mod's files, or keep the old one. * */ - static void updateModIndex(QDir& index_dir, Mod& mod); + static void updateModIndex(const QDir& index_dir, Mod& mod); /* Deletes the metadata for the mod with the given slug. If the metadata doesn't exist, it does nothing. */ - static void deleteModIndex(QDir& index_dir, QString& mod_slug); + static void deleteModIndex(const QDir& index_dir, QString& mod_slug); /* Deletes the metadata for the mod with the given id. If the metadata doesn't exist, it does nothing. */ - static void deleteModIndex(QDir& index_dir, QVariant& mod_id); + static void deleteModIndex(const QDir& index_dir, QVariant& mod_id); /* Gets the metadata for a mod with a particular file name. * If the mod doesn't have a metadata, it simply returns an empty Mod object. * */ - static auto getIndexForMod(QDir& index_dir, QString slug) -> Mod; + static auto getIndexForMod(const QDir& index_dir, QString slug) -> Mod; /* Gets the metadata for a mod with a particular id. * If the mod doesn't have a metadata, it simply returns an empty Mod object. * */ - static auto getIndexForMod(QDir& index_dir, QVariant& mod_id) -> Mod; + static auto getIndexForMod(const QDir& index_dir, QVariant& mod_id) -> Mod; static auto sideToString(Side side) -> QString; static auto stringToSide(QString side) -> Side; diff --git a/launcher/net/FileSink.cpp b/launcher/net/FileSink.cpp index 1ecb21fdf..95c1a8f44 100644 --- a/launcher/net/FileSink.cpp +++ b/launcher/net/FileSink.cpp @@ -55,7 +55,7 @@ Task::State FileSink::init(QNetworkRequest& request) } wroteAnyData = false; - m_output_file.reset(new QSaveFile(m_filename)); + m_output_file.reset(new PSaveFile(m_filename)); if (!m_output_file->open(QIODevice::WriteOnly)) { qCCritical(taskNetLogC) << "Could not open " + m_filename + " for writing"; return Task::State::Failed; diff --git a/launcher/net/FileSink.h b/launcher/net/FileSink.h index 816254ff9..272f8ddc3 100644 --- a/launcher/net/FileSink.h +++ b/launcher/net/FileSink.h @@ -35,8 +35,7 @@ #pragma once -#include - +#include "PSaveFile.h" #include "Sink.h" namespace Net { @@ -60,6 +59,6 @@ class FileSink : public Sink { protected: QString m_filename; bool wroteAnyData = false; - std::unique_ptr m_output_file; + std::unique_ptr m_output_file; }; } // namespace Net diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index 3cd3958f7..335e360b2 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -45,7 +45,7 @@ #endif NetJob::NetJob(QString job_name, shared_qobject_ptr network, int max_concurrent) - : ConcurrentTask(nullptr, job_name), m_network(network) + : ConcurrentTask(job_name), m_network(network) { #if defined(LAUNCHER_APPLICATION) if (APPLICATION_DYN && max_concurrent < 0) diff --git a/launcher/resources/assets/underconstruction.png b/launcher/resources/assets/underconstruction.png index 6ae06476e..5f2fdf9e4 100644 Binary files a/launcher/resources/assets/underconstruction.png and b/launcher/resources/assets/underconstruction.png differ diff --git a/launcher/resources/backgrounds/kitteh-bday.png b/launcher/resources/backgrounds/kitteh-bday.png index 09a365669..f4a7bbc1f 100644 Binary files a/launcher/resources/backgrounds/kitteh-bday.png and b/launcher/resources/backgrounds/kitteh-bday.png differ diff --git a/launcher/resources/backgrounds/kitteh-spooky.png b/launcher/resources/backgrounds/kitteh-spooky.png index deb0bebbe..bb3765f92 100644 Binary files a/launcher/resources/backgrounds/kitteh-spooky.png and b/launcher/resources/backgrounds/kitteh-spooky.png differ diff --git a/launcher/resources/backgrounds/kitteh-xmas.png b/launcher/resources/backgrounds/kitteh-xmas.png index 8bdb1d5c8..1e92e9081 100644 Binary files a/launcher/resources/backgrounds/kitteh-xmas.png and b/launcher/resources/backgrounds/kitteh-xmas.png differ diff --git a/launcher/resources/backgrounds/kitteh.png b/launcher/resources/backgrounds/kitteh.png index e9de7f27c..fa3d52548 100644 Binary files a/launcher/resources/backgrounds/kitteh.png and b/launcher/resources/backgrounds/kitteh.png differ diff --git a/launcher/resources/backgrounds/rory-bday.png b/launcher/resources/backgrounds/rory-bday.png index 66b880948..8c796927c 100644 Binary files a/launcher/resources/backgrounds/rory-bday.png and b/launcher/resources/backgrounds/rory-bday.png differ diff --git a/launcher/resources/backgrounds/rory-flat-bday.png b/launcher/resources/backgrounds/rory-flat-bday.png index 8a6e366db..94c4509a4 100644 Binary files a/launcher/resources/backgrounds/rory-flat-bday.png and b/launcher/resources/backgrounds/rory-flat-bday.png differ diff --git a/launcher/resources/backgrounds/rory-flat-spooky.png b/launcher/resources/backgrounds/rory-flat-spooky.png index 6360c612f..4a0046c2b 100644 Binary files a/launcher/resources/backgrounds/rory-flat-spooky.png and b/launcher/resources/backgrounds/rory-flat-spooky.png differ diff --git a/launcher/resources/backgrounds/rory-flat-xmas.png b/launcher/resources/backgrounds/rory-flat-xmas.png index 96c3ae381..e6278ed5c 100644 Binary files a/launcher/resources/backgrounds/rory-flat-xmas.png and b/launcher/resources/backgrounds/rory-flat-xmas.png differ diff --git a/launcher/resources/backgrounds/rory-flat.png b/launcher/resources/backgrounds/rory-flat.png index ccec0662b..22fe61887 100644 Binary files a/launcher/resources/backgrounds/rory-flat.png and b/launcher/resources/backgrounds/rory-flat.png differ diff --git a/launcher/resources/backgrounds/rory-spooky.png b/launcher/resources/backgrounds/rory-spooky.png index a727619b4..1aa928671 100644 Binary files a/launcher/resources/backgrounds/rory-spooky.png and b/launcher/resources/backgrounds/rory-spooky.png differ diff --git a/launcher/resources/backgrounds/rory-xmas.png b/launcher/resources/backgrounds/rory-xmas.png index 107feb780..f33e92666 100644 Binary files a/launcher/resources/backgrounds/rory-xmas.png and b/launcher/resources/backgrounds/rory-xmas.png differ diff --git a/launcher/resources/backgrounds/rory.png b/launcher/resources/backgrounds/rory.png index 577f4dce9..5570499c2 100644 Binary files a/launcher/resources/backgrounds/rory.png and b/launcher/resources/backgrounds/rory.png differ diff --git a/launcher/resources/backgrounds/teawie-bday.png b/launcher/resources/backgrounds/teawie-bday.png index f4ecf247c..b4621f9b5 100644 Binary files a/launcher/resources/backgrounds/teawie-bday.png and b/launcher/resources/backgrounds/teawie-bday.png differ diff --git a/launcher/resources/backgrounds/teawie-spooky.png b/launcher/resources/backgrounds/teawie-spooky.png index cefc6c855..194d8ab7c 100644 Binary files a/launcher/resources/backgrounds/teawie-spooky.png and b/launcher/resources/backgrounds/teawie-spooky.png differ diff --git a/launcher/resources/backgrounds/teawie-xmas.png b/launcher/resources/backgrounds/teawie-xmas.png index 55fb7cfc6..54a09ae51 100644 Binary files a/launcher/resources/backgrounds/teawie-xmas.png and b/launcher/resources/backgrounds/teawie-xmas.png differ diff --git a/launcher/resources/backgrounds/teawie.png b/launcher/resources/backgrounds/teawie.png index dc32c51f9..99b60ad1e 100644 Binary files a/launcher/resources/backgrounds/teawie.png and b/launcher/resources/backgrounds/teawie.png differ diff --git a/launcher/resources/multimc/128x128/instances/chicken_legacy.png b/launcher/resources/multimc/128x128/instances/chicken_legacy.png index 71f6dedc5..b4945d75a 100644 Binary files a/launcher/resources/multimc/128x128/instances/chicken_legacy.png and b/launcher/resources/multimc/128x128/instances/chicken_legacy.png differ diff --git a/launcher/resources/multimc/128x128/instances/creeper_legacy.png b/launcher/resources/multimc/128x128/instances/creeper_legacy.png index 41b7d07db..92d923132 100644 Binary files a/launcher/resources/multimc/128x128/instances/creeper_legacy.png and b/launcher/resources/multimc/128x128/instances/creeper_legacy.png differ diff --git a/launcher/resources/multimc/128x128/instances/enderpearl_legacy.png b/launcher/resources/multimc/128x128/instances/enderpearl_legacy.png index 0a5bf91a4..fd910da47 100644 Binary files a/launcher/resources/multimc/128x128/instances/enderpearl_legacy.png and b/launcher/resources/multimc/128x128/instances/enderpearl_legacy.png differ diff --git a/launcher/resources/multimc/128x128/instances/flame_legacy.png b/launcher/resources/multimc/128x128/instances/flame_legacy.png index 6482975c4..3dd8500c6 100644 Binary files a/launcher/resources/multimc/128x128/instances/flame_legacy.png and b/launcher/resources/multimc/128x128/instances/flame_legacy.png differ diff --git a/launcher/resources/multimc/128x128/instances/forge.png b/launcher/resources/multimc/128x128/instances/forge.png index d8ff79a53..10c5f8d6b 100644 Binary files a/launcher/resources/multimc/128x128/instances/forge.png and b/launcher/resources/multimc/128x128/instances/forge.png differ diff --git a/launcher/resources/multimc/128x128/instances/ftb_glow.png b/launcher/resources/multimc/128x128/instances/ftb_glow.png index 86632b21d..a8bfbbb96 100644 Binary files a/launcher/resources/multimc/128x128/instances/ftb_glow.png and b/launcher/resources/multimc/128x128/instances/ftb_glow.png differ diff --git a/launcher/resources/multimc/128x128/instances/ftb_logo_legacy.png b/launcher/resources/multimc/128x128/instances/ftb_logo_legacy.png index e725b7fe4..01aa4d517 100644 Binary files a/launcher/resources/multimc/128x128/instances/ftb_logo_legacy.png and b/launcher/resources/multimc/128x128/instances/ftb_logo_legacy.png differ diff --git a/launcher/resources/multimc/128x128/instances/gear_legacy.png b/launcher/resources/multimc/128x128/instances/gear_legacy.png index 75c68a66f..bb46fe026 100644 Binary files a/launcher/resources/multimc/128x128/instances/gear_legacy.png and b/launcher/resources/multimc/128x128/instances/gear_legacy.png differ diff --git a/launcher/resources/multimc/128x128/instances/herobrine_legacy.png b/launcher/resources/multimc/128x128/instances/herobrine_legacy.png index 13f1494c4..d25d1b1b1 100644 Binary files a/launcher/resources/multimc/128x128/instances/herobrine_legacy.png and b/launcher/resources/multimc/128x128/instances/herobrine_legacy.png differ diff --git a/launcher/resources/multimc/128x128/instances/infinity_legacy.png b/launcher/resources/multimc/128x128/instances/infinity_legacy.png index 63e06e5b3..322ab4361 100644 Binary files a/launcher/resources/multimc/128x128/instances/infinity_legacy.png and b/launcher/resources/multimc/128x128/instances/infinity_legacy.png differ diff --git a/launcher/resources/multimc/128x128/instances/liteloader.png b/launcher/resources/multimc/128x128/instances/liteloader.png index 646217de0..acd977d7e 100644 Binary files a/launcher/resources/multimc/128x128/instances/liteloader.png and b/launcher/resources/multimc/128x128/instances/liteloader.png differ diff --git a/launcher/resources/multimc/128x128/instances/magitech_legacy.png b/launcher/resources/multimc/128x128/instances/magitech_legacy.png index 0f81a1997..c83d0c948 100644 Binary files a/launcher/resources/multimc/128x128/instances/magitech_legacy.png and b/launcher/resources/multimc/128x128/instances/magitech_legacy.png differ diff --git a/launcher/resources/multimc/128x128/instances/meat_legacy.png b/launcher/resources/multimc/128x128/instances/meat_legacy.png index fefc9bf11..14a50bec0 100644 Binary files a/launcher/resources/multimc/128x128/instances/meat_legacy.png and b/launcher/resources/multimc/128x128/instances/meat_legacy.png differ diff --git a/launcher/resources/multimc/128x128/instances/netherstar_legacy.png b/launcher/resources/multimc/128x128/instances/netherstar_legacy.png index 132085f02..86cc87b4a 100644 Binary files a/launcher/resources/multimc/128x128/instances/netherstar_legacy.png and b/launcher/resources/multimc/128x128/instances/netherstar_legacy.png differ diff --git a/launcher/resources/multimc/128x128/instances/skeleton_legacy.png b/launcher/resources/multimc/128x128/instances/skeleton_legacy.png index 55fcf5a99..416ca66e0 100644 Binary files a/launcher/resources/multimc/128x128/instances/skeleton_legacy.png and b/launcher/resources/multimc/128x128/instances/skeleton_legacy.png differ diff --git a/launcher/resources/multimc/128x128/instances/squarecreeper_legacy.png b/launcher/resources/multimc/128x128/instances/squarecreeper_legacy.png index c82d8406d..b7e2bdc13 100644 Binary files a/launcher/resources/multimc/128x128/instances/squarecreeper_legacy.png and b/launcher/resources/multimc/128x128/instances/squarecreeper_legacy.png differ diff --git a/launcher/resources/multimc/128x128/instances/steve_legacy.png b/launcher/resources/multimc/128x128/instances/steve_legacy.png index a07cbd2f9..afe8aaf46 100644 Binary files a/launcher/resources/multimc/128x128/instances/steve_legacy.png and b/launcher/resources/multimc/128x128/instances/steve_legacy.png differ diff --git a/launcher/resources/multimc/128x128/shaderpacks.png b/launcher/resources/multimc/128x128/shaderpacks.png index 1de0e9169..d2f1c0328 100644 Binary files a/launcher/resources/multimc/128x128/shaderpacks.png and b/launcher/resources/multimc/128x128/shaderpacks.png differ diff --git a/launcher/resources/multimc/128x128/unknown_server.png b/launcher/resources/multimc/128x128/unknown_server.png index ec98382d4..b9761e08f 100644 Binary files a/launcher/resources/multimc/128x128/unknown_server.png and b/launcher/resources/multimc/128x128/unknown_server.png differ diff --git a/launcher/resources/multimc/16x16/about.png b/launcher/resources/multimc/16x16/about.png index a6a986e19..ed7e56dd6 100644 Binary files a/launcher/resources/multimc/16x16/about.png and b/launcher/resources/multimc/16x16/about.png differ diff --git a/launcher/resources/multimc/16x16/bug.png b/launcher/resources/multimc/16x16/bug.png index 0c5b78b40..57e7d8203 100644 Binary files a/launcher/resources/multimc/16x16/bug.png and b/launcher/resources/multimc/16x16/bug.png differ diff --git a/launcher/resources/multimc/16x16/cat.png b/launcher/resources/multimc/16x16/cat.png index e6e31b44b..73d5fa856 100644 Binary files a/launcher/resources/multimc/16x16/cat.png and b/launcher/resources/multimc/16x16/cat.png differ diff --git a/launcher/resources/multimc/16x16/centralmods.png b/launcher/resources/multimc/16x16/centralmods.png index c1b91c763..0a573fb4e 100644 Binary files a/launcher/resources/multimc/16x16/centralmods.png and b/launcher/resources/multimc/16x16/centralmods.png differ diff --git a/launcher/resources/multimc/16x16/checkupdate.png b/launcher/resources/multimc/16x16/checkupdate.png index f37420588..9d08c56f0 100644 Binary files a/launcher/resources/multimc/16x16/checkupdate.png and b/launcher/resources/multimc/16x16/checkupdate.png differ diff --git a/launcher/resources/multimc/16x16/copy.png b/launcher/resources/multimc/16x16/copy.png index ccaed9e11..24251adcf 100644 Binary files a/launcher/resources/multimc/16x16/copy.png and b/launcher/resources/multimc/16x16/copy.png differ diff --git a/launcher/resources/multimc/16x16/coremods.png b/launcher/resources/multimc/16x16/coremods.png index af0f11667..3d3932dbe 100644 Binary files a/launcher/resources/multimc/16x16/coremods.png and b/launcher/resources/multimc/16x16/coremods.png differ diff --git a/launcher/resources/multimc/16x16/help.png b/launcher/resources/multimc/16x16/help.png index e6edf6ba2..3dee5a3f9 100644 Binary files a/launcher/resources/multimc/16x16/help.png and b/launcher/resources/multimc/16x16/help.png differ diff --git a/launcher/resources/multimc/16x16/instance-settings.png b/launcher/resources/multimc/16x16/instance-settings.png index b916cd245..6c9073b96 100644 Binary files a/launcher/resources/multimc/16x16/instance-settings.png and b/launcher/resources/multimc/16x16/instance-settings.png differ diff --git a/launcher/resources/multimc/16x16/jarmods.png b/launcher/resources/multimc/16x16/jarmods.png index 1a97c9c00..cdcbe788b 100644 Binary files a/launcher/resources/multimc/16x16/jarmods.png and b/launcher/resources/multimc/16x16/jarmods.png differ diff --git a/launcher/resources/multimc/16x16/loadermods.png b/launcher/resources/multimc/16x16/loadermods.png index b5ab3fced..ad0e6237d 100644 Binary files a/launcher/resources/multimc/16x16/loadermods.png and b/launcher/resources/multimc/16x16/loadermods.png differ diff --git a/launcher/resources/multimc/16x16/log.png b/launcher/resources/multimc/16x16/log.png index efa2a0b57..74324047e 100644 Binary files a/launcher/resources/multimc/16x16/log.png and b/launcher/resources/multimc/16x16/log.png differ diff --git a/launcher/resources/multimc/16x16/minecraft.png b/launcher/resources/multimc/16x16/minecraft.png index e9f2f2a5f..3de54f74a 100644 Binary files a/launcher/resources/multimc/16x16/minecraft.png and b/launcher/resources/multimc/16x16/minecraft.png differ diff --git a/launcher/resources/multimc/16x16/new.png b/launcher/resources/multimc/16x16/new.png index 2e56f5893..dfde06f61 100644 Binary files a/launcher/resources/multimc/16x16/new.png and b/launcher/resources/multimc/16x16/new.png differ diff --git a/launcher/resources/multimc/16x16/news.png b/launcher/resources/multimc/16x16/news.png index 872e85dbc..04e016da7 100644 Binary files a/launcher/resources/multimc/16x16/news.png and b/launcher/resources/multimc/16x16/news.png differ diff --git a/launcher/resources/multimc/16x16/noaccount.png b/launcher/resources/multimc/16x16/noaccount.png index b49bcf36a..544d68207 100644 Binary files a/launcher/resources/multimc/16x16/noaccount.png and b/launcher/resources/multimc/16x16/noaccount.png differ diff --git a/launcher/resources/multimc/16x16/patreon.png b/launcher/resources/multimc/16x16/patreon.png index 9150c478f..0c306e7cc 100644 Binary files a/launcher/resources/multimc/16x16/patreon.png and b/launcher/resources/multimc/16x16/patreon.png differ diff --git a/launcher/resources/multimc/16x16/refresh.png b/launcher/resources/multimc/16x16/refresh.png index 86b6f82c1..2e81c9246 100644 Binary files a/launcher/resources/multimc/16x16/refresh.png and b/launcher/resources/multimc/16x16/refresh.png differ diff --git a/launcher/resources/multimc/16x16/resourcepacks.png b/launcher/resources/multimc/16x16/resourcepacks.png index d862f5ca6..ac4c5dc43 100644 Binary files a/launcher/resources/multimc/16x16/resourcepacks.png and b/launcher/resources/multimc/16x16/resourcepacks.png differ diff --git a/launcher/resources/multimc/16x16/screenshots.png b/launcher/resources/multimc/16x16/screenshots.png index 460000d4b..f0e5e439e 100644 Binary files a/launcher/resources/multimc/16x16/screenshots.png and b/launcher/resources/multimc/16x16/screenshots.png differ diff --git a/launcher/resources/multimc/16x16/settings.png b/launcher/resources/multimc/16x16/settings.png index b916cd245..6c9073b96 100644 Binary files a/launcher/resources/multimc/16x16/settings.png and b/launcher/resources/multimc/16x16/settings.png differ diff --git a/launcher/resources/multimc/16x16/star.png b/launcher/resources/multimc/16x16/star.png index 4963e6ec9..20278be0c 100644 Binary files a/launcher/resources/multimc/16x16/star.png and b/launcher/resources/multimc/16x16/star.png differ diff --git a/launcher/resources/multimc/16x16/status-bad.png b/launcher/resources/multimc/16x16/status-bad.png index 5b3f20518..c71142b8a 100644 Binary files a/launcher/resources/multimc/16x16/status-bad.png and b/launcher/resources/multimc/16x16/status-bad.png differ diff --git a/launcher/resources/multimc/16x16/status-good.png b/launcher/resources/multimc/16x16/status-good.png index 5cbdee815..456a67c5b 100644 Binary files a/launcher/resources/multimc/16x16/status-good.png and b/launcher/resources/multimc/16x16/status-good.png differ diff --git a/launcher/resources/multimc/16x16/status-running.png b/launcher/resources/multimc/16x16/status-running.png index a4c42e392..7b7bfec91 100644 Binary files a/launcher/resources/multimc/16x16/status-running.png and b/launcher/resources/multimc/16x16/status-running.png differ diff --git a/launcher/resources/multimc/16x16/status-yellow.png b/launcher/resources/multimc/16x16/status-yellow.png index b25375d18..f652ddae2 100644 Binary files a/launcher/resources/multimc/16x16/status-yellow.png and b/launcher/resources/multimc/16x16/status-yellow.png differ diff --git a/launcher/resources/multimc/16x16/viewfolder.png b/launcher/resources/multimc/16x16/viewfolder.png index 98b8a9448..f5f401427 100644 Binary files a/launcher/resources/multimc/16x16/viewfolder.png and b/launcher/resources/multimc/16x16/viewfolder.png differ diff --git a/launcher/resources/multimc/16x16/worlds.png b/launcher/resources/multimc/16x16/worlds.png index 1a38f38e7..ed4249ec3 100644 Binary files a/launcher/resources/multimc/16x16/worlds.png and b/launcher/resources/multimc/16x16/worlds.png differ diff --git a/launcher/resources/multimc/22x22/about.png b/launcher/resources/multimc/22x22/about.png index 57775e25a..fbf18726f 100644 Binary files a/launcher/resources/multimc/22x22/about.png and b/launcher/resources/multimc/22x22/about.png differ diff --git a/launcher/resources/multimc/22x22/bug.png b/launcher/resources/multimc/22x22/bug.png index 90481bba6..8aeb25d66 100644 Binary files a/launcher/resources/multimc/22x22/bug.png and b/launcher/resources/multimc/22x22/bug.png differ diff --git a/launcher/resources/multimc/22x22/cat.png b/launcher/resources/multimc/22x22/cat.png index 3ea7ba69e..a5795b9b8 100644 Binary files a/launcher/resources/multimc/22x22/cat.png and b/launcher/resources/multimc/22x22/cat.png differ diff --git a/launcher/resources/multimc/22x22/centralmods.png b/launcher/resources/multimc/22x22/centralmods.png index a10f9a2b9..a54fdb0b0 100644 Binary files a/launcher/resources/multimc/22x22/centralmods.png and b/launcher/resources/multimc/22x22/centralmods.png differ diff --git a/launcher/resources/multimc/22x22/checkupdate.png b/launcher/resources/multimc/22x22/checkupdate.png index badb200cf..a44d47fe0 100644 Binary files a/launcher/resources/multimc/22x22/checkupdate.png and b/launcher/resources/multimc/22x22/checkupdate.png differ diff --git a/launcher/resources/multimc/22x22/copy.png b/launcher/resources/multimc/22x22/copy.png index ea236a241..5cdc69dbe 100644 Binary files a/launcher/resources/multimc/22x22/copy.png and b/launcher/resources/multimc/22x22/copy.png differ diff --git a/launcher/resources/multimc/22x22/help.png b/launcher/resources/multimc/22x22/help.png index da79b3e3f..db49f9e31 100644 Binary files a/launcher/resources/multimc/22x22/help.png and b/launcher/resources/multimc/22x22/help.png differ diff --git a/launcher/resources/multimc/22x22/instance-settings.png b/launcher/resources/multimc/22x22/instance-settings.png index daf56aad3..8eb9ee49d 100644 Binary files a/launcher/resources/multimc/22x22/instance-settings.png and b/launcher/resources/multimc/22x22/instance-settings.png differ diff --git a/launcher/resources/multimc/22x22/new.png b/launcher/resources/multimc/22x22/new.png index c707fbbfb..41edf3ef0 100644 Binary files a/launcher/resources/multimc/22x22/new.png and b/launcher/resources/multimc/22x22/new.png differ diff --git a/launcher/resources/multimc/22x22/news.png b/launcher/resources/multimc/22x22/news.png index 1953bf7b2..46eaab869 100644 Binary files a/launcher/resources/multimc/22x22/news.png and b/launcher/resources/multimc/22x22/news.png differ diff --git a/launcher/resources/multimc/22x22/patreon.png b/launcher/resources/multimc/22x22/patreon.png index f2c2076c0..8da8780ec 100644 Binary files a/launcher/resources/multimc/22x22/patreon.png and b/launcher/resources/multimc/22x22/patreon.png differ diff --git a/launcher/resources/multimc/22x22/refresh.png b/launcher/resources/multimc/22x22/refresh.png index 45b5535ce..f517f7ace 100644 Binary files a/launcher/resources/multimc/22x22/refresh.png and b/launcher/resources/multimc/22x22/refresh.png differ diff --git a/launcher/resources/multimc/22x22/screenshots.png b/launcher/resources/multimc/22x22/screenshots.png index 6fb42bbdf..780eb4351 100644 Binary files a/launcher/resources/multimc/22x22/screenshots.png and b/launcher/resources/multimc/22x22/screenshots.png differ diff --git a/launcher/resources/multimc/22x22/settings.png b/launcher/resources/multimc/22x22/settings.png index daf56aad3..8eb9ee49d 100644 Binary files a/launcher/resources/multimc/22x22/settings.png and b/launcher/resources/multimc/22x22/settings.png differ diff --git a/launcher/resources/multimc/22x22/status-bad.png b/launcher/resources/multimc/22x22/status-bad.png index 2707539e1..9d001ccd2 100644 Binary files a/launcher/resources/multimc/22x22/status-bad.png and b/launcher/resources/multimc/22x22/status-bad.png differ diff --git a/launcher/resources/multimc/22x22/status-good.png b/launcher/resources/multimc/22x22/status-good.png index f55debc37..9ac765abe 100644 Binary files a/launcher/resources/multimc/22x22/status-good.png and b/launcher/resources/multimc/22x22/status-good.png differ diff --git a/launcher/resources/multimc/22x22/status-running.png b/launcher/resources/multimc/22x22/status-running.png index 0dffba18f..21caa06b8 100644 Binary files a/launcher/resources/multimc/22x22/status-running.png and b/launcher/resources/multimc/22x22/status-running.png differ diff --git a/launcher/resources/multimc/22x22/status-yellow.png b/launcher/resources/multimc/22x22/status-yellow.png index 481eb7f3f..e125cf098 100644 Binary files a/launcher/resources/multimc/22x22/status-yellow.png and b/launcher/resources/multimc/22x22/status-yellow.png differ diff --git a/launcher/resources/multimc/22x22/viewfolder.png b/launcher/resources/multimc/22x22/viewfolder.png index b645167fc..7065e9ac9 100644 Binary files a/launcher/resources/multimc/22x22/viewfolder.png and b/launcher/resources/multimc/22x22/viewfolder.png differ diff --git a/launcher/resources/multimc/22x22/worlds.png b/launcher/resources/multimc/22x22/worlds.png index e8825bab4..ebb32f10c 100644 Binary files a/launcher/resources/multimc/22x22/worlds.png and b/launcher/resources/multimc/22x22/worlds.png differ diff --git a/launcher/resources/multimc/24x24/cat.png b/launcher/resources/multimc/24x24/cat.png index c93245f65..08b0ab1b0 100644 Binary files a/launcher/resources/multimc/24x24/cat.png and b/launcher/resources/multimc/24x24/cat.png differ diff --git a/launcher/resources/multimc/24x24/coremods.png b/launcher/resources/multimc/24x24/coremods.png index 90603d248..0cbd3f173 100644 Binary files a/launcher/resources/multimc/24x24/coremods.png and b/launcher/resources/multimc/24x24/coremods.png differ diff --git a/launcher/resources/multimc/24x24/jarmods.png b/launcher/resources/multimc/24x24/jarmods.png index 68cb8e9db..a4824c276 100644 Binary files a/launcher/resources/multimc/24x24/jarmods.png and b/launcher/resources/multimc/24x24/jarmods.png differ diff --git a/launcher/resources/multimc/24x24/loadermods.png b/launcher/resources/multimc/24x24/loadermods.png index 250a62609..cd4954d5a 100644 Binary files a/launcher/resources/multimc/24x24/loadermods.png and b/launcher/resources/multimc/24x24/loadermods.png differ diff --git a/launcher/resources/multimc/24x24/log.png b/launcher/resources/multimc/24x24/log.png index fe3020534..7978968c1 100644 Binary files a/launcher/resources/multimc/24x24/log.png and b/launcher/resources/multimc/24x24/log.png differ diff --git a/launcher/resources/multimc/24x24/minecraft.png b/launcher/resources/multimc/24x24/minecraft.png index b31177c9c..8869844cb 100644 Binary files a/launcher/resources/multimc/24x24/minecraft.png and b/launcher/resources/multimc/24x24/minecraft.png differ diff --git a/launcher/resources/multimc/24x24/noaccount.png b/launcher/resources/multimc/24x24/noaccount.png index ac12437cf..05d5cc584 100644 Binary files a/launcher/resources/multimc/24x24/noaccount.png and b/launcher/resources/multimc/24x24/noaccount.png differ diff --git a/launcher/resources/multimc/24x24/patreon.png b/launcher/resources/multimc/24x24/patreon.png index add806686..2e1cc0548 100644 Binary files a/launcher/resources/multimc/24x24/patreon.png and b/launcher/resources/multimc/24x24/patreon.png differ diff --git a/launcher/resources/multimc/24x24/resourcepacks.png b/launcher/resources/multimc/24x24/resourcepacks.png index 68359d395..b434fb124 100644 Binary files a/launcher/resources/multimc/24x24/resourcepacks.png and b/launcher/resources/multimc/24x24/resourcepacks.png differ diff --git a/launcher/resources/multimc/24x24/star.png b/launcher/resources/multimc/24x24/star.png index 7f16618a6..8527f5092 100644 Binary files a/launcher/resources/multimc/24x24/star.png and b/launcher/resources/multimc/24x24/star.png differ diff --git a/launcher/resources/multimc/24x24/status-bad.png b/launcher/resources/multimc/24x24/status-bad.png index d1547a474..eae695286 100644 Binary files a/launcher/resources/multimc/24x24/status-bad.png and b/launcher/resources/multimc/24x24/status-bad.png differ diff --git a/launcher/resources/multimc/24x24/status-good.png b/launcher/resources/multimc/24x24/status-good.png index 3545bc4c5..e315beaf3 100644 Binary files a/launcher/resources/multimc/24x24/status-good.png and b/launcher/resources/multimc/24x24/status-good.png differ diff --git a/launcher/resources/multimc/24x24/status-running.png b/launcher/resources/multimc/24x24/status-running.png index ecd64451f..9c6059462 100644 Binary files a/launcher/resources/multimc/24x24/status-running.png and b/launcher/resources/multimc/24x24/status-running.png differ diff --git a/launcher/resources/multimc/24x24/status-yellow.png b/launcher/resources/multimc/24x24/status-yellow.png index dd5fde67b..118efd890 100644 Binary files a/launcher/resources/multimc/24x24/status-yellow.png and b/launcher/resources/multimc/24x24/status-yellow.png differ diff --git a/launcher/resources/multimc/256x256/minecraft.png b/launcher/resources/multimc/256x256/minecraft.png index 77e3f03e2..0b24f5501 100644 Binary files a/launcher/resources/multimc/256x256/minecraft.png and b/launcher/resources/multimc/256x256/minecraft.png differ diff --git a/launcher/resources/multimc/32x32/about.png b/launcher/resources/multimc/32x32/about.png index 5174c4f19..261d2a44c 100644 Binary files a/launcher/resources/multimc/32x32/about.png and b/launcher/resources/multimc/32x32/about.png differ diff --git a/launcher/resources/multimc/32x32/bug.png b/launcher/resources/multimc/32x32/bug.png index ada466530..3d97be84a 100644 Binary files a/launcher/resources/multimc/32x32/bug.png and b/launcher/resources/multimc/32x32/bug.png differ diff --git a/launcher/resources/multimc/32x32/cat.png b/launcher/resources/multimc/32x32/cat.png index 78ff98e9e..b9b21e663 100644 Binary files a/launcher/resources/multimc/32x32/cat.png and b/launcher/resources/multimc/32x32/cat.png differ diff --git a/launcher/resources/multimc/32x32/centralmods.png b/launcher/resources/multimc/32x32/centralmods.png index cd2b8208e..7225ba08c 100644 Binary files a/launcher/resources/multimc/32x32/centralmods.png and b/launcher/resources/multimc/32x32/centralmods.png differ diff --git a/launcher/resources/multimc/32x32/checkupdate.png b/launcher/resources/multimc/32x32/checkupdate.png index 754005f97..c60f965b2 100644 Binary files a/launcher/resources/multimc/32x32/checkupdate.png and b/launcher/resources/multimc/32x32/checkupdate.png differ diff --git a/launcher/resources/multimc/32x32/copy.png b/launcher/resources/multimc/32x32/copy.png index c137b0f11..ce662604e 100644 Binary files a/launcher/resources/multimc/32x32/copy.png and b/launcher/resources/multimc/32x32/copy.png differ diff --git a/launcher/resources/multimc/32x32/coremods.png b/launcher/resources/multimc/32x32/coremods.png index 770d695eb..9718ec671 100644 Binary files a/launcher/resources/multimc/32x32/coremods.png and b/launcher/resources/multimc/32x32/coremods.png differ diff --git a/launcher/resources/multimc/32x32/help.png b/launcher/resources/multimc/32x32/help.png index b38542784..6e4cdbff6 100644 Binary files a/launcher/resources/multimc/32x32/help.png and b/launcher/resources/multimc/32x32/help.png differ diff --git a/launcher/resources/multimc/32x32/instance-settings.png b/launcher/resources/multimc/32x32/instance-settings.png index a9c0817c9..4be48c1d5 100644 Binary files a/launcher/resources/multimc/32x32/instance-settings.png and b/launcher/resources/multimc/32x32/instance-settings.png differ diff --git a/launcher/resources/multimc/32x32/instances/brick_legacy.png b/launcher/resources/multimc/32x32/instances/brick_legacy.png index c324fda06..7d35f4da5 100644 Binary files a/launcher/resources/multimc/32x32/instances/brick_legacy.png and b/launcher/resources/multimc/32x32/instances/brick_legacy.png differ diff --git a/launcher/resources/multimc/32x32/instances/chicken_legacy.png b/launcher/resources/multimc/32x32/instances/chicken_legacy.png index f870467a6..7991410e1 100644 Binary files a/launcher/resources/multimc/32x32/instances/chicken_legacy.png and b/launcher/resources/multimc/32x32/instances/chicken_legacy.png differ diff --git a/launcher/resources/multimc/32x32/instances/creeper_legacy.png b/launcher/resources/multimc/32x32/instances/creeper_legacy.png index a67ecfc35..571d2de19 100644 Binary files a/launcher/resources/multimc/32x32/instances/creeper_legacy.png and b/launcher/resources/multimc/32x32/instances/creeper_legacy.png differ diff --git a/launcher/resources/multimc/32x32/instances/diamond_legacy.png b/launcher/resources/multimc/32x32/instances/diamond_legacy.png index 1eb264697..3ad9c002f 100644 Binary files a/launcher/resources/multimc/32x32/instances/diamond_legacy.png and b/launcher/resources/multimc/32x32/instances/diamond_legacy.png differ diff --git a/launcher/resources/multimc/32x32/instances/dirt_legacy.png b/launcher/resources/multimc/32x32/instances/dirt_legacy.png index 9e19eb8fa..719a45ed5 100644 Binary files a/launcher/resources/multimc/32x32/instances/dirt_legacy.png and b/launcher/resources/multimc/32x32/instances/dirt_legacy.png differ diff --git a/launcher/resources/multimc/32x32/instances/enderpearl_legacy.png b/launcher/resources/multimc/32x32/instances/enderpearl_legacy.png index a818eb8e6..e0262f659 100644 Binary files a/launcher/resources/multimc/32x32/instances/enderpearl_legacy.png and b/launcher/resources/multimc/32x32/instances/enderpearl_legacy.png differ diff --git a/launcher/resources/multimc/32x32/instances/ftb_glow.png b/launcher/resources/multimc/32x32/instances/ftb_glow.png index c4e6fd5d3..7437b27cc 100644 Binary files a/launcher/resources/multimc/32x32/instances/ftb_glow.png and b/launcher/resources/multimc/32x32/instances/ftb_glow.png differ diff --git a/launcher/resources/multimc/32x32/instances/ftb_logo_legacy.png b/launcher/resources/multimc/32x32/instances/ftb_logo_legacy.png index 20df71710..a70109bbb 100644 Binary files a/launcher/resources/multimc/32x32/instances/ftb_logo_legacy.png and b/launcher/resources/multimc/32x32/instances/ftb_logo_legacy.png differ diff --git a/launcher/resources/multimc/32x32/instances/gear_legacy.png b/launcher/resources/multimc/32x32/instances/gear_legacy.png index da9ba2f9d..61dc9f500 100644 Binary files a/launcher/resources/multimc/32x32/instances/gear_legacy.png and b/launcher/resources/multimc/32x32/instances/gear_legacy.png differ diff --git a/launcher/resources/multimc/32x32/instances/gold_legacy.png b/launcher/resources/multimc/32x32/instances/gold_legacy.png index 593410fac..99d91795c 100644 Binary files a/launcher/resources/multimc/32x32/instances/gold_legacy.png and b/launcher/resources/multimc/32x32/instances/gold_legacy.png differ diff --git a/launcher/resources/multimc/32x32/instances/grass_legacy.png b/launcher/resources/multimc/32x32/instances/grass_legacy.png index f1694547a..400f21067 100644 Binary files a/launcher/resources/multimc/32x32/instances/grass_legacy.png and b/launcher/resources/multimc/32x32/instances/grass_legacy.png differ diff --git a/launcher/resources/multimc/32x32/instances/herobrine_legacy.png b/launcher/resources/multimc/32x32/instances/herobrine_legacy.png index e5460da31..8ed872a6f 100644 Binary files a/launcher/resources/multimc/32x32/instances/herobrine_legacy.png and b/launcher/resources/multimc/32x32/instances/herobrine_legacy.png differ diff --git a/launcher/resources/multimc/32x32/instances/infinity_legacy.png b/launcher/resources/multimc/32x32/instances/infinity_legacy.png index bd94a3dc2..62291c782 100644 Binary files a/launcher/resources/multimc/32x32/instances/infinity_legacy.png and b/launcher/resources/multimc/32x32/instances/infinity_legacy.png differ diff --git a/launcher/resources/multimc/32x32/instances/iron_legacy.png b/launcher/resources/multimc/32x32/instances/iron_legacy.png index 3e811bd63..d05d7c01e 100644 Binary files a/launcher/resources/multimc/32x32/instances/iron_legacy.png and b/launcher/resources/multimc/32x32/instances/iron_legacy.png differ diff --git a/launcher/resources/multimc/32x32/instances/magitech_legacy.png b/launcher/resources/multimc/32x32/instances/magitech_legacy.png index 6fd8ff604..bd630da8f 100644 Binary files a/launcher/resources/multimc/32x32/instances/magitech_legacy.png and b/launcher/resources/multimc/32x32/instances/magitech_legacy.png differ diff --git a/launcher/resources/multimc/32x32/instances/meat_legacy.png b/launcher/resources/multimc/32x32/instances/meat_legacy.png index 6694859db..422c88eeb 100644 Binary files a/launcher/resources/multimc/32x32/instances/meat_legacy.png and b/launcher/resources/multimc/32x32/instances/meat_legacy.png differ diff --git a/launcher/resources/multimc/32x32/instances/netherstar_legacy.png b/launcher/resources/multimc/32x32/instances/netherstar_legacy.png index 43cb51139..6f5c6f22b 100644 Binary files a/launcher/resources/multimc/32x32/instances/netherstar_legacy.png and b/launcher/resources/multimc/32x32/instances/netherstar_legacy.png differ diff --git a/launcher/resources/multimc/32x32/instances/planks_legacy.png b/launcher/resources/multimc/32x32/instances/planks_legacy.png index a94b75029..0ff6d19b0 100644 Binary files a/launcher/resources/multimc/32x32/instances/planks_legacy.png and b/launcher/resources/multimc/32x32/instances/planks_legacy.png differ diff --git a/launcher/resources/multimc/32x32/instances/skeleton_legacy.png b/launcher/resources/multimc/32x32/instances/skeleton_legacy.png index 0c8d3505a..2327a036a 100644 Binary files a/launcher/resources/multimc/32x32/instances/skeleton_legacy.png and b/launcher/resources/multimc/32x32/instances/skeleton_legacy.png differ diff --git a/launcher/resources/multimc/32x32/instances/squarecreeper_legacy.png b/launcher/resources/multimc/32x32/instances/squarecreeper_legacy.png index b78c4ae09..258c9b34d 100644 Binary files a/launcher/resources/multimc/32x32/instances/squarecreeper_legacy.png and b/launcher/resources/multimc/32x32/instances/squarecreeper_legacy.png differ diff --git a/launcher/resources/multimc/32x32/instances/steve_legacy.png b/launcher/resources/multimc/32x32/instances/steve_legacy.png index 07c6acdee..3467335f0 100644 Binary files a/launcher/resources/multimc/32x32/instances/steve_legacy.png and b/launcher/resources/multimc/32x32/instances/steve_legacy.png differ diff --git a/launcher/resources/multimc/32x32/instances/stone_legacy.png b/launcher/resources/multimc/32x32/instances/stone_legacy.png index 1b6ef7a43..7a4d88cf0 100644 Binary files a/launcher/resources/multimc/32x32/instances/stone_legacy.png and b/launcher/resources/multimc/32x32/instances/stone_legacy.png differ diff --git a/launcher/resources/multimc/32x32/instances/tnt_legacy.png b/launcher/resources/multimc/32x32/instances/tnt_legacy.png index e40d404d8..7ab83644f 100644 Binary files a/launcher/resources/multimc/32x32/instances/tnt_legacy.png and b/launcher/resources/multimc/32x32/instances/tnt_legacy.png differ diff --git a/launcher/resources/multimc/32x32/jarmods.png b/launcher/resources/multimc/32x32/jarmods.png index 5cda173a9..848be629f 100644 Binary files a/launcher/resources/multimc/32x32/jarmods.png and b/launcher/resources/multimc/32x32/jarmods.png differ diff --git a/launcher/resources/multimc/32x32/loadermods.png b/launcher/resources/multimc/32x32/loadermods.png index c4ca12e26..73d70a30a 100644 Binary files a/launcher/resources/multimc/32x32/loadermods.png and b/launcher/resources/multimc/32x32/loadermods.png differ diff --git a/launcher/resources/multimc/32x32/log.png b/launcher/resources/multimc/32x32/log.png index d620da122..6c2290f77 100644 Binary files a/launcher/resources/multimc/32x32/log.png and b/launcher/resources/multimc/32x32/log.png differ diff --git a/launcher/resources/multimc/32x32/minecraft.png b/launcher/resources/multimc/32x32/minecraft.png index 816bec98f..6b3642692 100644 Binary files a/launcher/resources/multimc/32x32/minecraft.png and b/launcher/resources/multimc/32x32/minecraft.png differ diff --git a/launcher/resources/multimc/32x32/new.png b/launcher/resources/multimc/32x32/new.png index a3555ba49..8fc4be64a 100644 Binary files a/launcher/resources/multimc/32x32/new.png and b/launcher/resources/multimc/32x32/new.png differ diff --git a/launcher/resources/multimc/32x32/news.png b/launcher/resources/multimc/32x32/news.png index c579fd44d..240803124 100644 Binary files a/launcher/resources/multimc/32x32/news.png and b/launcher/resources/multimc/32x32/news.png differ diff --git a/launcher/resources/multimc/32x32/noaccount.png b/launcher/resources/multimc/32x32/noaccount.png index a73afc946..98ca7130e 100644 Binary files a/launcher/resources/multimc/32x32/noaccount.png and b/launcher/resources/multimc/32x32/noaccount.png differ diff --git a/launcher/resources/multimc/32x32/patreon.png b/launcher/resources/multimc/32x32/patreon.png index 70085aa1c..440195d2e 100644 Binary files a/launcher/resources/multimc/32x32/patreon.png and b/launcher/resources/multimc/32x32/patreon.png differ diff --git a/launcher/resources/multimc/32x32/refresh.png b/launcher/resources/multimc/32x32/refresh.png index afa2a9d77..e67c5fe51 100644 Binary files a/launcher/resources/multimc/32x32/refresh.png and b/launcher/resources/multimc/32x32/refresh.png differ diff --git a/launcher/resources/multimc/32x32/resourcepacks.png b/launcher/resources/multimc/32x32/resourcepacks.png index c14759ef7..8af7fe31f 100644 Binary files a/launcher/resources/multimc/32x32/resourcepacks.png and b/launcher/resources/multimc/32x32/resourcepacks.png differ diff --git a/launcher/resources/multimc/32x32/screenshots.png b/launcher/resources/multimc/32x32/screenshots.png index 4fcd62246..95c8c7e93 100644 Binary files a/launcher/resources/multimc/32x32/screenshots.png and b/launcher/resources/multimc/32x32/screenshots.png differ diff --git a/launcher/resources/multimc/32x32/settings.png b/launcher/resources/multimc/32x32/settings.png index a9c0817c9..4be48c1d5 100644 Binary files a/launcher/resources/multimc/32x32/settings.png and b/launcher/resources/multimc/32x32/settings.png differ diff --git a/launcher/resources/multimc/32x32/star.png b/launcher/resources/multimc/32x32/star.png index b271f0d1b..c797ab34c 100644 Binary files a/launcher/resources/multimc/32x32/star.png and b/launcher/resources/multimc/32x32/star.png differ diff --git a/launcher/resources/multimc/32x32/status-bad.png b/launcher/resources/multimc/32x32/status-bad.png index 8c2c9d4f7..77ac8fe01 100644 Binary files a/launcher/resources/multimc/32x32/status-bad.png and b/launcher/resources/multimc/32x32/status-bad.png differ diff --git a/launcher/resources/multimc/32x32/status-good.png b/launcher/resources/multimc/32x32/status-good.png index 1a805e68b..b8f7095ad 100644 Binary files a/launcher/resources/multimc/32x32/status-good.png and b/launcher/resources/multimc/32x32/status-good.png differ diff --git a/launcher/resources/multimc/32x32/status-running.png b/launcher/resources/multimc/32x32/status-running.png index f561f01ad..8ff17a046 100644 Binary files a/launcher/resources/multimc/32x32/status-running.png and b/launcher/resources/multimc/32x32/status-running.png differ diff --git a/launcher/resources/multimc/32x32/status-yellow.png b/launcher/resources/multimc/32x32/status-yellow.png index 42c685520..36270afb9 100644 Binary files a/launcher/resources/multimc/32x32/status-yellow.png and b/launcher/resources/multimc/32x32/status-yellow.png differ diff --git a/launcher/resources/multimc/32x32/viewfolder.png b/launcher/resources/multimc/32x32/viewfolder.png index 74ab8fa63..32d7b4bae 100644 Binary files a/launcher/resources/multimc/32x32/viewfolder.png and b/launcher/resources/multimc/32x32/viewfolder.png differ diff --git a/launcher/resources/multimc/32x32/worlds.png b/launcher/resources/multimc/32x32/worlds.png index c986596c9..dce4d96b5 100644 Binary files a/launcher/resources/multimc/32x32/worlds.png and b/launcher/resources/multimc/32x32/worlds.png differ diff --git a/launcher/resources/multimc/48x48/about.png b/launcher/resources/multimc/48x48/about.png index b4ac71b8e..f6d4d11cb 100644 Binary files a/launcher/resources/multimc/48x48/about.png and b/launcher/resources/multimc/48x48/about.png differ diff --git a/launcher/resources/multimc/48x48/bug.png b/launcher/resources/multimc/48x48/bug.png index 298f9397c..8de0b0755 100644 Binary files a/launcher/resources/multimc/48x48/bug.png and b/launcher/resources/multimc/48x48/bug.png differ diff --git a/launcher/resources/multimc/48x48/cat.png b/launcher/resources/multimc/48x48/cat.png index 25912a3c0..f84221d7a 100644 Binary files a/launcher/resources/multimc/48x48/cat.png and b/launcher/resources/multimc/48x48/cat.png differ diff --git a/launcher/resources/multimc/48x48/centralmods.png b/launcher/resources/multimc/48x48/centralmods.png index d927e39b4..2425a7c74 100644 Binary files a/launcher/resources/multimc/48x48/centralmods.png and b/launcher/resources/multimc/48x48/centralmods.png differ diff --git a/launcher/resources/multimc/48x48/checkupdate.png b/launcher/resources/multimc/48x48/checkupdate.png index 2e2c7d6be..b181736a8 100644 Binary files a/launcher/resources/multimc/48x48/checkupdate.png and b/launcher/resources/multimc/48x48/checkupdate.png differ diff --git a/launcher/resources/multimc/48x48/copy.png b/launcher/resources/multimc/48x48/copy.png index ea40e34b7..4dc04b080 100644 Binary files a/launcher/resources/multimc/48x48/copy.png and b/launcher/resources/multimc/48x48/copy.png differ diff --git a/launcher/resources/multimc/48x48/help.png b/launcher/resources/multimc/48x48/help.png index 82d828fab..f57c6c896 100644 Binary files a/launcher/resources/multimc/48x48/help.png and b/launcher/resources/multimc/48x48/help.png differ diff --git a/launcher/resources/multimc/48x48/instance-settings.png b/launcher/resources/multimc/48x48/instance-settings.png index 6674eb236..ec298cd62 100644 Binary files a/launcher/resources/multimc/48x48/instance-settings.png and b/launcher/resources/multimc/48x48/instance-settings.png differ diff --git a/launcher/resources/multimc/48x48/log.png b/launcher/resources/multimc/48x48/log.png index 45f60e6b4..dc3eb4e27 100644 Binary files a/launcher/resources/multimc/48x48/log.png and b/launcher/resources/multimc/48x48/log.png differ diff --git a/launcher/resources/multimc/48x48/minecraft.png b/launcher/resources/multimc/48x48/minecraft.png index 38fc9f6cc..4fe522ffb 100644 Binary files a/launcher/resources/multimc/48x48/minecraft.png and b/launcher/resources/multimc/48x48/minecraft.png differ diff --git a/launcher/resources/multimc/48x48/new.png b/launcher/resources/multimc/48x48/new.png index a81753b31..a81ccf1b2 100644 Binary files a/launcher/resources/multimc/48x48/new.png and b/launcher/resources/multimc/48x48/new.png differ diff --git a/launcher/resources/multimc/48x48/news.png b/launcher/resources/multimc/48x48/news.png index 0f82d8577..d2f5d178a 100644 Binary files a/launcher/resources/multimc/48x48/news.png and b/launcher/resources/multimc/48x48/news.png differ diff --git a/launcher/resources/multimc/48x48/noaccount.png b/launcher/resources/multimc/48x48/noaccount.png index 4703796c7..c13e4d6d5 100644 Binary files a/launcher/resources/multimc/48x48/noaccount.png and b/launcher/resources/multimc/48x48/noaccount.png differ diff --git a/launcher/resources/multimc/48x48/patreon.png b/launcher/resources/multimc/48x48/patreon.png index 7aec4d7d3..7e8f25367 100644 Binary files a/launcher/resources/multimc/48x48/patreon.png and b/launcher/resources/multimc/48x48/patreon.png differ diff --git a/launcher/resources/multimc/48x48/refresh.png b/launcher/resources/multimc/48x48/refresh.png index 0b08b2388..87e113583 100644 Binary files a/launcher/resources/multimc/48x48/refresh.png and b/launcher/resources/multimc/48x48/refresh.png differ diff --git a/launcher/resources/multimc/48x48/screenshots.png b/launcher/resources/multimc/48x48/screenshots.png index 03c0059fa..694b96cd9 100644 Binary files a/launcher/resources/multimc/48x48/screenshots.png and b/launcher/resources/multimc/48x48/screenshots.png differ diff --git a/launcher/resources/multimc/48x48/settings.png b/launcher/resources/multimc/48x48/settings.png index 6674eb236..ec298cd62 100644 Binary files a/launcher/resources/multimc/48x48/settings.png and b/launcher/resources/multimc/48x48/settings.png differ diff --git a/launcher/resources/multimc/48x48/star.png b/launcher/resources/multimc/48x48/star.png index d9468e7e3..c5253c334 100644 Binary files a/launcher/resources/multimc/48x48/star.png and b/launcher/resources/multimc/48x48/star.png differ diff --git a/launcher/resources/multimc/48x48/status-bad.png b/launcher/resources/multimc/48x48/status-bad.png index 41c9cf227..083506d28 100644 Binary files a/launcher/resources/multimc/48x48/status-bad.png and b/launcher/resources/multimc/48x48/status-bad.png differ diff --git a/launcher/resources/multimc/48x48/status-good.png b/launcher/resources/multimc/48x48/status-good.png index df7cb59b6..0c3377ad7 100644 Binary files a/launcher/resources/multimc/48x48/status-good.png and b/launcher/resources/multimc/48x48/status-good.png differ diff --git a/launcher/resources/multimc/48x48/status-running.png b/launcher/resources/multimc/48x48/status-running.png index b8c0bf7cd..94598c927 100644 Binary files a/launcher/resources/multimc/48x48/status-running.png and b/launcher/resources/multimc/48x48/status-running.png differ diff --git a/launcher/resources/multimc/48x48/status-yellow.png b/launcher/resources/multimc/48x48/status-yellow.png index 4f3b11d69..bb76fcd69 100644 Binary files a/launcher/resources/multimc/48x48/status-yellow.png and b/launcher/resources/multimc/48x48/status-yellow.png differ diff --git a/launcher/resources/multimc/48x48/viewfolder.png b/launcher/resources/multimc/48x48/viewfolder.png index 0492a736c..2245ba30a 100644 Binary files a/launcher/resources/multimc/48x48/viewfolder.png and b/launcher/resources/multimc/48x48/viewfolder.png differ diff --git a/launcher/resources/multimc/48x48/worlds.png b/launcher/resources/multimc/48x48/worlds.png index 4fc337511..eb44150a3 100644 Binary files a/launcher/resources/multimc/48x48/worlds.png and b/launcher/resources/multimc/48x48/worlds.png differ diff --git a/launcher/resources/multimc/50x50/instances/enderman_legacy.png b/launcher/resources/multimc/50x50/instances/enderman_legacy.png index 9f3a72b3a..36c791eb0 100644 Binary files a/launcher/resources/multimc/50x50/instances/enderman_legacy.png and b/launcher/resources/multimc/50x50/instances/enderman_legacy.png differ diff --git a/launcher/resources/multimc/64x64/about.png b/launcher/resources/multimc/64x64/about.png index b83e92690..b9be9abef 100644 Binary files a/launcher/resources/multimc/64x64/about.png and b/launcher/resources/multimc/64x64/about.png differ diff --git a/launcher/resources/multimc/64x64/bug.png b/launcher/resources/multimc/64x64/bug.png index 156b03159..6c9ac6af2 100644 Binary files a/launcher/resources/multimc/64x64/bug.png and b/launcher/resources/multimc/64x64/bug.png differ diff --git a/launcher/resources/multimc/64x64/cat.png b/launcher/resources/multimc/64x64/cat.png index 2cc21f808..65681e6b8 100644 Binary files a/launcher/resources/multimc/64x64/cat.png and b/launcher/resources/multimc/64x64/cat.png differ diff --git a/launcher/resources/multimc/64x64/centralmods.png b/launcher/resources/multimc/64x64/centralmods.png index 8831f437c..d30735601 100644 Binary files a/launcher/resources/multimc/64x64/centralmods.png and b/launcher/resources/multimc/64x64/centralmods.png differ diff --git a/launcher/resources/multimc/64x64/checkupdate.png b/launcher/resources/multimc/64x64/checkupdate.png index dd1e29ac6..a4002a61e 100644 Binary files a/launcher/resources/multimc/64x64/checkupdate.png and b/launcher/resources/multimc/64x64/checkupdate.png differ diff --git a/launcher/resources/multimc/64x64/copy.png b/launcher/resources/multimc/64x64/copy.png index d12cf9c8a..69fa1c3fb 100644 Binary files a/launcher/resources/multimc/64x64/copy.png and b/launcher/resources/multimc/64x64/copy.png differ diff --git a/launcher/resources/multimc/64x64/coremods.png b/launcher/resources/multimc/64x64/coremods.png index 668be3341..b1b1f8237 100644 Binary files a/launcher/resources/multimc/64x64/coremods.png and b/launcher/resources/multimc/64x64/coremods.png differ diff --git a/launcher/resources/multimc/64x64/help.png b/launcher/resources/multimc/64x64/help.png index 0f3948c2c..e419f8600 100644 Binary files a/launcher/resources/multimc/64x64/help.png and b/launcher/resources/multimc/64x64/help.png differ diff --git a/launcher/resources/multimc/64x64/instance-settings.png b/launcher/resources/multimc/64x64/instance-settings.png index e3ff58faf..9df7fe9bc 100644 Binary files a/launcher/resources/multimc/64x64/instance-settings.png and b/launcher/resources/multimc/64x64/instance-settings.png differ diff --git a/launcher/resources/multimc/64x64/jarmods.png b/launcher/resources/multimc/64x64/jarmods.png index 55d1a42a0..5abd5ecc5 100644 Binary files a/launcher/resources/multimc/64x64/jarmods.png and b/launcher/resources/multimc/64x64/jarmods.png differ diff --git a/launcher/resources/multimc/64x64/loadermods.png b/launcher/resources/multimc/64x64/loadermods.png index 24618fd0b..485aa843a 100644 Binary files a/launcher/resources/multimc/64x64/loadermods.png and b/launcher/resources/multimc/64x64/loadermods.png differ diff --git a/launcher/resources/multimc/64x64/log.png b/launcher/resources/multimc/64x64/log.png index 0f531cdfc..decee34bd 100644 Binary files a/launcher/resources/multimc/64x64/log.png and b/launcher/resources/multimc/64x64/log.png differ diff --git a/launcher/resources/multimc/64x64/new.png b/launcher/resources/multimc/64x64/new.png index c3c6796c4..289a6ad0b 100644 Binary files a/launcher/resources/multimc/64x64/new.png and b/launcher/resources/multimc/64x64/new.png differ diff --git a/launcher/resources/multimc/64x64/news.png b/launcher/resources/multimc/64x64/news.png index e306eed37..a1c28fdd6 100644 Binary files a/launcher/resources/multimc/64x64/news.png and b/launcher/resources/multimc/64x64/news.png differ diff --git a/launcher/resources/multimc/64x64/patreon.png b/launcher/resources/multimc/64x64/patreon.png index ef5d690eb..5c2d88814 100644 Binary files a/launcher/resources/multimc/64x64/patreon.png and b/launcher/resources/multimc/64x64/patreon.png differ diff --git a/launcher/resources/multimc/64x64/refresh.png b/launcher/resources/multimc/64x64/refresh.png index 8373d8198..737bd0581 100644 Binary files a/launcher/resources/multimc/64x64/refresh.png and b/launcher/resources/multimc/64x64/refresh.png differ diff --git a/launcher/resources/multimc/64x64/resourcepacks.png b/launcher/resources/multimc/64x64/resourcepacks.png index fb874e7d3..703fde6b5 100644 Binary files a/launcher/resources/multimc/64x64/resourcepacks.png and b/launcher/resources/multimc/64x64/resourcepacks.png differ diff --git a/launcher/resources/multimc/64x64/screenshots.png b/launcher/resources/multimc/64x64/screenshots.png index af18e39ca..a57bf2772 100644 Binary files a/launcher/resources/multimc/64x64/screenshots.png and b/launcher/resources/multimc/64x64/screenshots.png differ diff --git a/launcher/resources/multimc/64x64/settings.png b/launcher/resources/multimc/64x64/settings.png index e3ff58faf..9df7fe9bc 100644 Binary files a/launcher/resources/multimc/64x64/settings.png and b/launcher/resources/multimc/64x64/settings.png differ diff --git a/launcher/resources/multimc/64x64/star.png b/launcher/resources/multimc/64x64/star.png index 4ed5d978f..24e9d75c7 100644 Binary files a/launcher/resources/multimc/64x64/star.png and b/launcher/resources/multimc/64x64/star.png differ diff --git a/launcher/resources/multimc/64x64/status-bad.png b/launcher/resources/multimc/64x64/status-bad.png index 64060ba09..669d3159d 100644 Binary files a/launcher/resources/multimc/64x64/status-bad.png and b/launcher/resources/multimc/64x64/status-bad.png differ diff --git a/launcher/resources/multimc/64x64/status-good.png b/launcher/resources/multimc/64x64/status-good.png index e862ddcdf..4d256cc04 100644 Binary files a/launcher/resources/multimc/64x64/status-good.png and b/launcher/resources/multimc/64x64/status-good.png differ diff --git a/launcher/resources/multimc/64x64/status-running.png b/launcher/resources/multimc/64x64/status-running.png index 38afda0f9..64d6d0a8d 100644 Binary files a/launcher/resources/multimc/64x64/status-running.png and b/launcher/resources/multimc/64x64/status-running.png differ diff --git a/launcher/resources/multimc/64x64/status-yellow.png b/launcher/resources/multimc/64x64/status-yellow.png index 3d54d320c..98013151b 100644 Binary files a/launcher/resources/multimc/64x64/status-yellow.png and b/launcher/resources/multimc/64x64/status-yellow.png differ diff --git a/launcher/resources/multimc/64x64/viewfolder.png b/launcher/resources/multimc/64x64/viewfolder.png index 7d531f9cc..d16cacc4d 100644 Binary files a/launcher/resources/multimc/64x64/viewfolder.png and b/launcher/resources/multimc/64x64/viewfolder.png differ diff --git a/launcher/resources/multimc/64x64/worlds.png b/launcher/resources/multimc/64x64/worlds.png index 1d40f1df7..25aa1d685 100644 Binary files a/launcher/resources/multimc/64x64/worlds.png and b/launcher/resources/multimc/64x64/worlds.png differ diff --git a/launcher/resources/multimc/8x8/noaccount.png b/launcher/resources/multimc/8x8/noaccount.png index 466e4c076..645ea1bed 100644 Binary files a/launcher/resources/multimc/8x8/noaccount.png and b/launcher/resources/multimc/8x8/noaccount.png differ diff --git a/launcher/resources/multimc/index.theme b/launcher/resources/multimc/index.theme index 4da8072d9..497106d6f 100644 --- a/launcher/resources/multimc/index.theme +++ b/launcher/resources/multimc/index.theme @@ -1,7 +1,6 @@ [Icon Theme] Name=Legacy Comment=Default Icons -Inherits=default Directories=8x8,16x16,22x22,24x24,32x32,32x32/instances,48x48,50x50/instances,64x64,128x128/instances,256x256,scalable,scalable/instances [8x8] diff --git a/launcher/resources/multimc/scalable/atlauncher-placeholder.png b/launcher/resources/multimc/scalable/atlauncher-placeholder.png index f4314c434..8b6dedad5 100644 Binary files a/launcher/resources/multimc/scalable/atlauncher-placeholder.png and b/launcher/resources/multimc/scalable/atlauncher-placeholder.png differ diff --git a/launcher/resources/sources/burfcat_hat.png b/launcher/resources/sources/burfcat_hat.png index a378c1fbb..6abf17820 100644 Binary files a/launcher/resources/sources/burfcat_hat.png and b/launcher/resources/sources/burfcat_hat.png differ diff --git a/launcher/settings/INIFile.cpp b/launcher/settings/INIFile.cpp index e97741f20..2c7620e65 100644 --- a/launcher/settings/INIFile.cpp +++ b/launcher/settings/INIFile.cpp @@ -39,7 +39,6 @@ #include #include -#include #include #include #include diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index f2ee40c31..ad2a14c42 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -38,7 +38,7 @@ #include #include "tasks/Task.h" -ConcurrentTask::ConcurrentTask(QObject* parent, QString task_name, int max_concurrent) : Task(parent), m_total_max_size(max_concurrent) +ConcurrentTask::ConcurrentTask(QString task_name, int max_concurrent) : Task(), m_total_max_size(max_concurrent) { setObjectName(task_name); } diff --git a/launcher/tasks/ConcurrentTask.h b/launcher/tasks/ConcurrentTask.h index 0d6709940..d988623b9 100644 --- a/launcher/tasks/ConcurrentTask.h +++ b/launcher/tasks/ConcurrentTask.h @@ -48,7 +48,7 @@ class ConcurrentTask : public Task { public: using Ptr = shared_qobject_ptr; - explicit ConcurrentTask(QObject* parent = nullptr, QString task_name = "", int max_concurrent = 6); + explicit ConcurrentTask(QString task_name = "", int max_concurrent = 6); ~ConcurrentTask() override; // safe to call before starting the task diff --git a/launcher/tasks/MultipleOptionsTask.cpp b/launcher/tasks/MultipleOptionsTask.cpp index 5afe03964..ba0c23542 100644 --- a/launcher/tasks/MultipleOptionsTask.cpp +++ b/launcher/tasks/MultipleOptionsTask.cpp @@ -36,7 +36,7 @@ #include -MultipleOptionsTask::MultipleOptionsTask(QObject* parent, const QString& task_name) : ConcurrentTask(parent, task_name, 1) {} +MultipleOptionsTask::MultipleOptionsTask(const QString& task_name) : ConcurrentTask(task_name, 1) {} void MultipleOptionsTask::executeNextSubTask() { diff --git a/launcher/tasks/MultipleOptionsTask.h b/launcher/tasks/MultipleOptionsTask.h index 9a88a9999..7a19ed6ad 100644 --- a/launcher/tasks/MultipleOptionsTask.h +++ b/launcher/tasks/MultipleOptionsTask.h @@ -42,7 +42,7 @@ class MultipleOptionsTask : public ConcurrentTask { Q_OBJECT public: - explicit MultipleOptionsTask(QObject* parent = nullptr, const QString& task_name = ""); + explicit MultipleOptionsTask(const QString& task_name = ""); ~MultipleOptionsTask() override = default; private slots: diff --git a/launcher/tasks/SequentialTask.cpp b/launcher/tasks/SequentialTask.cpp index 509d91cf7..2e48414f2 100644 --- a/launcher/tasks/SequentialTask.cpp +++ b/launcher/tasks/SequentialTask.cpp @@ -38,7 +38,7 @@ #include #include "tasks/ConcurrentTask.h" -SequentialTask::SequentialTask(QObject* parent, QString task_name) : ConcurrentTask(parent, task_name, 1) {} +SequentialTask::SequentialTask(QString task_name) : ConcurrentTask(task_name, 1) {} void SequentialTask::subTaskFailed(Task::Ptr task, const QString& msg) { diff --git a/launcher/tasks/SequentialTask.h b/launcher/tasks/SequentialTask.h index a7c101ab4..77cd4387f 100644 --- a/launcher/tasks/SequentialTask.h +++ b/launcher/tasks/SequentialTask.h @@ -47,7 +47,7 @@ class SequentialTask : public ConcurrentTask { Q_OBJECT public: - explicit SequentialTask(QObject* parent = nullptr, QString task_name = ""); + explicit SequentialTask(QString task_name = ""); ~SequentialTask() override = default; protected slots: diff --git a/launcher/tasks/Task.cpp b/launcher/tasks/Task.cpp index b17096ca7..1871c5df8 100644 --- a/launcher/tasks/Task.cpp +++ b/launcher/tasks/Task.cpp @@ -40,7 +40,7 @@ Q_LOGGING_CATEGORY(taskLogC, "launcher.task") -Task::Task(QObject* parent, bool show_debug) : QObject(parent), m_show_debug(show_debug) +Task::Task(bool show_debug) : m_show_debug(show_debug) { m_uid = QUuid::createUuid(); setAutoDelete(false); diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index 883408c97..e712700a1 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -87,7 +87,7 @@ class Task : public QObject, public QRunnable { enum class State { Inactive, Running, Succeeded, Failed, AbortedByUser }; public: - explicit Task(QObject* parent = 0, bool show_debug_log = true); + explicit Task(bool show_debug_log = true); virtual ~Task() = default; bool isRunning() const; diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index d03469b78..429ead47d 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -419,7 +419,7 @@ int TranslationsModel::columnCount([[maybe_unused]] const QModelIndex& parent) c QVector::Iterator TranslationsModel::findLanguage(const QString& key) { - return std::find_if(d->m_languages.begin(), d->m_languages.end(), [&](Language& lang) { return lang.key == key; }); + return std::find_if(d->m_languages.begin(), d->m_languages.end(), [key](Language& lang) { return lang.key == key; }); } std::optional TranslationsModel::findLanguageAsOptional(const QString& key) diff --git a/launcher/ui/GuiUtil.cpp b/launcher/ui/GuiUtil.cpp index 584a34710..d53ade86d 100644 --- a/launcher/ui/GuiUtil.cpp +++ b/launcher/ui/GuiUtil.cpp @@ -51,11 +51,35 @@ #include #include "Application.h" +constexpr int MaxMclogsLines = 25000; +constexpr int InitialMclogsLines = 10000; +constexpr int FinalMclogsLines = 14900; + +QString truncateLogForMclogs(const QString& logContent) +{ + QStringList lines = logContent.split("\n"); + if (lines.size() > MaxMclogsLines) { + QString truncatedLog = lines.mid(0, InitialMclogsLines).join("\n"); + truncatedLog += + "\n\n\n\n\n\n\n\n\n\n" + "------------------------------------------------------------\n" + "----------------------- Log truncated ----------------------\n" + "------------------------------------------------------------\n" + "----- Middle portion omitted to fit mclo.gs size limits ----\n" + "------------------------------------------------------------\n" + "\n\n\n\n\n\n\n\n\n\n"; + truncatedLog += lines.mid(lines.size() - FinalMclogsLines - 1).join("\n"); + return truncatedLog; + } + return logContent; +} + std::optional GuiUtil::uploadPaste(const QString& name, const QString& text, QWidget* parentWidget) { ProgressDialog dialog(parentWidget); auto pasteTypeSetting = static_cast(APPLICATION->settings()->get("PastebinType").toInt()); auto pasteCustomAPIBaseSetting = APPLICATION->settings()->get("PastebinCustomAPIBase").toString(); + bool shouldTruncate = false; { QUrl baseUrl; @@ -75,10 +99,36 @@ std::optional GuiUtil::uploadPaste(const QString& name, const QString& if (response != QMessageBox::Yes) return {}; + + if (baseUrl.toString() == "https://api.mclo.gs" && text.count("\n") > MaxMclogsLines) { + auto truncateResponse = CustomMessageBox::selectable( + parentWidget, QObject::tr("Confirm Truncation"), + QObject::tr("The log has %1 lines, exceeding mclo.gs' limit of %2.\n" + "The launcher can keep the first %3 and last %4 lines, trimming the middle.\n\n" + "If you choose 'No', mclo.gs will only keep the first %2 lines, cutting off " + "potentially useful info like crashes at the end.\n\n" + "Proceed with truncation?") + .arg(text.count("\n")) + .arg(MaxMclogsLines) + .arg(InitialMclogsLines) + .arg(FinalMclogsLines), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::No) + ->exec(); + + if (truncateResponse == QMessageBox::Cancel) { + return {}; + } + shouldTruncate = truncateResponse == QMessageBox::Yes; + } } } - std::unique_ptr paste(new PasteUpload(parentWidget, text, pasteCustomAPIBaseSetting, pasteTypeSetting)); + QString textToUpload = text; + if (shouldTruncate) { + textToUpload = truncateLogForMclogs(text); + } + + std::unique_ptr paste(new PasteUpload(parentWidget, textToUpload, pasteCustomAPIBaseSetting, pasteTypeSetting)); dialog.execWithTask(paste.get()); if (!paste->wasSuccessful()) { @@ -112,7 +162,7 @@ static QStringList BrowseForFileInternal(QString context, QFileDialog w(parentWidget, caption); QSet locations; - auto f = [&](QStandardPaths::StandardLocation l) { + auto f = [&locations](QStandardPaths::StandardLocation l) { QString location = QStandardPaths::writableLocation(l); QFileInfo finfo(location); if (!finfo.exists()) { diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 09c47b609..ee12eaa2d 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -154,7 +154,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi // Qt doesn't like vertical moving toolbars, so we have to force them... // See https://github.com/PolyMC/PolyMC/issues/493 connect(ui->instanceToolBar, &QToolBar::orientationChanged, - [=](Qt::Orientation) { ui->instanceToolBar->setOrientation(Qt::Vertical); }); + [this](Qt::Orientation) { ui->instanceToolBar->setOrientation(Qt::Vertical); }); // if you try to add a widget to a toolbar in a .ui file // qt designer will delete it when you save the file >:( @@ -1027,6 +1027,14 @@ void MainWindow::processURLs(QList urls) continue; } + if (APPLICATION->instances()->count() <= 0) { + CustomMessageBox::selectable(this, tr("No instance!"), + tr("No instance available to add the resource to.\nPlease create a new instance before " + "attempting to install this resource again."), + QMessageBox::Critical) + ->show(); + continue; + } ImportResourceDialog dlg(localFileName, type, this); if (dlg.exec() != QDialog::Accepted) @@ -1048,7 +1056,7 @@ void MainWindow::processURLs(QList urls) qWarning() << "Importing of Data Packs not supported at this time. Ignoring" << localFileName; break; case PackedResourceType::Mod: - minecraftInst->loaderModList()->installMod(localFileName, version); + minecraftInst->loaderModList()->installResourceWithFlameMetadata(localFileName, version); break; case PackedResourceType::ShaderPack: minecraftInst->shaderPackList()->installResource(localFileName); diff --git a/launcher/ui/dialogs/AboutDialog.cpp b/launcher/ui/dialogs/AboutDialog.cpp index b652ba991..a8d60aef1 100644 --- a/launcher/ui/dialogs/AboutDialog.cpp +++ b/launcher/ui/dialogs/AboutDialog.cpp @@ -77,7 +77,7 @@ QString getCreditsHtml() stream << QString("

d-513 %1

\n").arg(getGitHub("d-513")); stream << QString("

txtsd %1

\n").arg(getWebsite("https://ihavea.quest")); stream << QString("

timoreo %1

\n").arg(getGitHub("timoreo22")); - stream << QString("

Ezekiel Smith (ZekeSmith) %1

\n").arg(getGitHub("ZekeSmith")); + stream << QString("

ZekeZ %1

\n").arg(getGitHub("ZekeZDev")); stream << QString("

cozyGalvinism %1

\n").arg(getGitHub("cozyGalvinism")); stream << QString("

DioEgizio %1

\n").arg(getGitHub("DioEgizio")); stream << QString("

flowln %1

\n").arg(getGitHub("flowln")); @@ -101,7 +101,7 @@ QString getCreditsHtml() stream << "

" << QObject::tr("With thanks to", "About Credits") << "

\n"; stream << QString("

Boba %1

\n").arg(getWebsite("https://bobaonline.neocities.org/")); - stream << QString("

Davi Rafael %1

\n").arg(getWebsite("https://auti.one/")); + stream << QString("

AutiOne %1

\n").arg(getWebsite("https://auti.one/")); stream << QString("

Fulmine %1

\n").arg(getWebsite("https://fulmine.xyz/")); stream << QString("

ely %1

\n").arg(getGitHub("elyrodso")); stream << QString("

gon sawa %1

\n").arg(getGitHub("gonsawa")); diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp index 5c93053d1..0095f7af9 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.cpp +++ b/launcher/ui/dialogs/BlockedModsDialog.cpp @@ -46,11 +46,13 @@ BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, cons : QDialog(parent), ui(new Ui::BlockedModsDialog), m_mods(mods), m_hash_type(hash_type) { m_hashing_task = shared_qobject_ptr( - new ConcurrentTask(this, "MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())); + new ConcurrentTask("MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())); connect(m_hashing_task.get(), &Task::finished, this, &BlockedModsDialog::hashTaskFinished); ui->setupUi(this); + ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); + ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK")); m_openMissingButton = ui->buttonBox->addButton(tr("Open Missing"), QDialogButtonBox::ActionRole); connect(m_openMissingButton, &QPushButton::clicked, this, [this]() { openAll(true); }); @@ -286,6 +288,8 @@ void BlockedModsDialog::checkMatchHash(QString hash, QString path) qDebug() << "[Blocked Mods Dialog] Checking for match on hash: " << hash << "| From path:" << path; + auto downloadDir = QFileInfo(APPLICATION->settings()->get("DownloadsDir").toString()).absoluteFilePath(); + auto moveFiles = APPLICATION->settings()->get("MoveModsFromDownloadsDir").toBool(); for (auto& mod : m_mods) { if (mod.matched) { continue; @@ -293,6 +297,9 @@ void BlockedModsDialog::checkMatchHash(QString hash, QString path) if (mod.hash.compare(hash, Qt::CaseInsensitive) == 0) { mod.matched = true; mod.localPath = path; + if (moveFiles) { + mod.move = QFileInfo(path).absoluteFilePath().startsWith(downloadDir); + } match = true; qDebug() << "[Blocked Mods Dialog] Hash match found:" << mod.name << hash << "| From path:" << path; @@ -344,6 +351,8 @@ bool BlockedModsDialog::checkValidPath(QString path) return fsName.compare(metaName) == 0; }; + auto downloadDir = QFileInfo(APPLICATION->settings()->get("DownloadsDir").toString()).absoluteFilePath(); + auto moveFiles = APPLICATION->settings()->get("MoveModsFromDownloadsDir").toBool(); for (auto& mod : m_mods) { if (compare(filename, mod.name)) { // if the mod is not yet matched and doesn't have a hash then @@ -351,6 +360,9 @@ bool BlockedModsDialog::checkValidPath(QString path) if (!mod.matched && mod.hash.isEmpty()) { mod.matched = true; mod.localPath = path; + if (moveFiles) { + mod.move = QFileInfo(path).absoluteFilePath().startsWith(downloadDir); + } return false; } qDebug() << "[Blocked Mods Dialog] Name match found:" << mod.name << "| From path:" << path; diff --git a/launcher/ui/dialogs/BlockedModsDialog.h b/launcher/ui/dialogs/BlockedModsDialog.h index 09722bce9..b2d2c0374 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.h +++ b/launcher/ui/dialogs/BlockedModsDialog.h @@ -42,6 +42,7 @@ struct BlockedMod { bool matched; QString localPath; QString targetFolder; + bool move = false; }; QT_BEGIN_NAMESPACE diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index 770741a61..e5c2c301b 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -109,6 +109,9 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget* parent) auto HelpButton = ui->buttonBox->button(QDialogButtonBox::Help); connect(HelpButton, &QPushButton::clicked, this, &CopyInstanceDialog::help); + HelpButton->setText(tr("Help")); + ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); + ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK")); } CopyInstanceDialog::~CopyInstanceDialog() diff --git a/launcher/ui/dialogs/EditAccountDialog.cpp b/launcher/ui/dialogs/EditAccountDialog.cpp deleted file mode 100644 index 58036fd82..000000000 --- a/launcher/ui/dialogs/EditAccountDialog.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* 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. - */ - -#include "EditAccountDialog.h" -#include -#include -#include "ui_EditAccountDialog.h" - -EditAccountDialog::EditAccountDialog(const QString& text, QWidget* parent, int flags) : QDialog(parent), ui(new Ui::EditAccountDialog) -{ - ui->setupUi(this); - - ui->label->setText(text); - ui->label->setVisible(!text.isEmpty()); - - ui->userTextBox->setEnabled(flags & UsernameField); - ui->passTextBox->setEnabled(flags & PasswordField); -} - -EditAccountDialog::~EditAccountDialog() -{ - delete ui; -} - -void EditAccountDialog::on_label_linkActivated(const QString& link) -{ - DesktopServices::openUrl(QUrl(link)); -} - -void EditAccountDialog::setUsername(const QString& user) const -{ - ui->userTextBox->setText(user); -} - -QString EditAccountDialog::username() const -{ - return ui->userTextBox->text(); -} - -void EditAccountDialog::setPassword(const QString& pass) const -{ - ui->passTextBox->setText(pass); -} - -QString EditAccountDialog::password() const -{ - return ui->passTextBox->text(); -} diff --git a/launcher/ui/dialogs/EditAccountDialog.h b/launcher/ui/dialogs/EditAccountDialog.h deleted file mode 100644 index 7a9ccba79..000000000 --- a/launcher/ui/dialogs/EditAccountDialog.h +++ /dev/null @@ -1,52 +0,0 @@ -/* 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 - -#include - -namespace Ui { -class EditAccountDialog; -} - -class EditAccountDialog : public QDialog { - Q_OBJECT - - public: - explicit EditAccountDialog(const QString& text = "", QWidget* parent = 0, int flags = UsernameField | PasswordField); - ~EditAccountDialog(); - - void setUsername(const QString& user) const; - void setPassword(const QString& pass) const; - - QString username() const; - QString password() const; - - enum Flags { - NoFlags = 0, - - //! Specifies that the dialog should have a username field. - UsernameField, - - //! Specifies that the dialog should have a password field. - PasswordField, - }; - - private slots: - void on_label_linkActivated(const QString& link); - - private: - Ui::EditAccountDialog* ui; -}; diff --git a/launcher/ui/dialogs/EditAccountDialog.ui b/launcher/ui/dialogs/EditAccountDialog.ui deleted file mode 100644 index e87509bcb..000000000 --- a/launcher/ui/dialogs/EditAccountDialog.ui +++ /dev/null @@ -1,94 +0,0 @@ - - - EditAccountDialog - - - - 0 - 0 - 400 - 148 - - - - Login - - - - - - Message label placeholder. - - - Qt::RichText - - - Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Email - - - - - - - QLineEdit::Password - - - Password - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - buttonBox - accepted() - EditAccountDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - EditAccountDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp index 9f2b3ac42..d25cd32b6 100644 --- a/launcher/ui/dialogs/ExportInstanceDialog.cpp +++ b/launcher/ui/dialogs/ExportInstanceDialog.cpp @@ -51,6 +51,7 @@ #include #include #include +#include #include #include #include @@ -85,6 +86,9 @@ ExportInstanceDialog::ExportInstanceDialog(InstancePtr instance, QWidget* parent auto headerView = ui->treeView->header(); headerView->setSectionResizeMode(QHeaderView::ResizeToContents); headerView->setSectionResizeMode(0, QHeaderView::Stretch); + + ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); + ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK")); } ExportInstanceDialog::~ExportInstanceDialog() diff --git a/launcher/ui/dialogs/ExportPackDialog.cpp b/launcher/ui/dialogs/ExportPackDialog.cpp index 0278c6cb0..879d19a53 100644 --- a/launcher/ui/dialogs/ExportPackDialog.cpp +++ b/launcher/ui/dialogs/ExportPackDialog.cpp @@ -88,9 +88,9 @@ ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPla MinecraftInstance* mcInstance = dynamic_cast(instance.get()); if (mcInstance) { - const QDir index = mcInstance->loaderModList()->indexDir(); - if (index.exists()) - proxy->ignoreFilesWithPath().insert(root.relativeFilePath(index.absolutePath())); + for (auto& resourceModel : mcInstance->resourceLists()) + if (resourceModel->indexDir().exists()) + proxy->ignoreFilesWithPath().insert(root.relativeFilePath(resourceModel->indexDir().absolutePath())); } ui->files->setModel(proxy); @@ -103,6 +103,9 @@ ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPla QHeaderView* headerView = ui->files->header(); headerView->setSectionResizeMode(QHeaderView::ResizeToContents); headerView->setSectionResizeMode(0, QHeaderView::Stretch); + + ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); + ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK")); } ExportPackDialog::~ExportPackDialog() diff --git a/launcher/ui/dialogs/ExportToModListDialog.cpp b/launcher/ui/dialogs/ExportToModListDialog.cpp index 908743ab0..c2ba68f7a 100644 --- a/launcher/ui/dialogs/ExportToModListDialog.cpp +++ b/launcher/ui/dialogs/ExportToModListDialog.cpp @@ -64,6 +64,9 @@ ExportToModListDialog::ExportToModListDialog(QString name, QList mods, QWi this->ui->finalText->selectAll(); this->ui->finalText->copy(); }); + + ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); + ui->buttonBox->button(QDialogButtonBox::Save)->setText(tr("Save")); triggerImp(); } diff --git a/launcher/ui/dialogs/IconPickerDialog.cpp b/launcher/ui/dialogs/IconPickerDialog.cpp index a196fd587..b6e928a3d 100644 --- a/launcher/ui/dialogs/IconPickerDialog.cpp +++ b/launcher/ui/dialogs/IconPickerDialog.cpp @@ -15,7 +15,9 @@ #include #include +#include #include +#include #include "Application.h" @@ -33,6 +35,15 @@ IconPickerDialog::IconPickerDialog(QWidget* parent) : QDialog(parent), ui(new Ui ui->setupUi(this); setWindowModality(Qt::WindowModal); + searchBar = new QLineEdit(this); + searchBar->setPlaceholderText(tr("Search...")); + ui->verticalLayout->insertWidget(0, searchBar); + + proxyModel = new QSortFilterProxyModel(this); + proxyModel->setSourceModel(APPLICATION->icons().get()); + proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + ui->iconView->setModel(proxyModel); + auto contentsWidget = ui->iconView; contentsWidget->setViewMode(QListView::IconMode); contentsWidget->setFlow(QListView::LeftToRight); @@ -57,12 +68,15 @@ IconPickerDialog::IconPickerDialog(QWidget* parent) : QDialog(parent), ui(new Ui contentsWidget->installEventFilter(this); - contentsWidget->setModel(APPLICATION->icons().get()); + contentsWidget->setModel(proxyModel); // NOTE: ResetRole forces the button to be on the left, while the OK/Cancel ones are on the right. We win. auto buttonAdd = ui->buttonBox->addButton(tr("Add Icon"), QDialogButtonBox::ResetRole); buttonRemove = ui->buttonBox->addButton(tr("Remove Icon"), QDialogButtonBox::ResetRole); + ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); + ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK")); + connect(buttonAdd, SIGNAL(clicked(bool)), SLOT(addNewIcon())); connect(buttonRemove, SIGNAL(clicked(bool)), SLOT(removeSelectedIcon())); @@ -73,6 +87,9 @@ IconPickerDialog::IconPickerDialog(QWidget* parent) : QDialog(parent), ui(new Ui auto buttonFolder = ui->buttonBox->addButton(tr("Open Folder"), QDialogButtonBox::ResetRole); connect(buttonFolder, &QPushButton::clicked, this, &IconPickerDialog::openFolder); + connect(searchBar, &QLineEdit::textChanged, this, &IconPickerDialog::filterIcons); + // Prevent incorrect indices from e.g. filesystem changes + connect(APPLICATION->icons().get(), &IconList::iconUpdated, this, [this]() { proxyModel->invalidate(); }); } bool IconPickerDialog::eventFilter(QObject* obj, QEvent* evt) @@ -159,5 +176,10 @@ IconPickerDialog::~IconPickerDialog() void IconPickerDialog::openFolder() { - DesktopServices::openPath(APPLICATION->icons()->getDirectory(), true); + DesktopServices::openPath(APPLICATION->icons()->iconDirectory(selectedIconKey), true); } + +void IconPickerDialog::filterIcons(const QString& query) +{ + proxyModel->setFilterFixedString(query); +} \ No newline at end of file diff --git a/launcher/ui/dialogs/IconPickerDialog.h b/launcher/ui/dialogs/IconPickerDialog.h index 37e53dcce..db1315338 100644 --- a/launcher/ui/dialogs/IconPickerDialog.h +++ b/launcher/ui/dialogs/IconPickerDialog.h @@ -16,6 +16,8 @@ #pragma once #include #include +#include +#include namespace Ui { class IconPickerDialog; @@ -36,6 +38,8 @@ class IconPickerDialog : public QDialog { private: Ui::IconPickerDialog* ui; QPushButton* buttonRemove; + QLineEdit* searchBar; + QSortFilterProxyModel* proxyModel; private slots: void selectionChanged(QItemSelection, QItemSelection); @@ -44,4 +48,5 @@ class IconPickerDialog : public QDialog { void addNewIcon(); void removeSelectedIcon(); void openFolder(); + void filterIcons(const QString& text); }; diff --git a/launcher/ui/dialogs/ImportResourceDialog.cpp b/launcher/ui/dialogs/ImportResourceDialog.cpp index 84b692730..e3a1e9a6c 100644 --- a/launcher/ui/dialogs/ImportResourceDialog.cpp +++ b/launcher/ui/dialogs/ImportResourceDialog.cpp @@ -45,6 +45,9 @@ ImportResourceDialog::ImportResourceDialog(QString file_path, PackedResourceType ui->label->setText( tr("Choose the instance you would like to import this %1 to.").arg(ResourceUtils::getPackedTypeName(m_resource_type))); ui->label_file_path->setText(tr("File: %1").arg(m_file_path)); + + ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); + ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK")); } void ImportResourceDialog::activated(QModelIndex index) diff --git a/launcher/ui/dialogs/InstallLoaderDialog.cpp b/launcher/ui/dialogs/InstallLoaderDialog.cpp index 8e5463b31..7082125f2 100644 --- a/launcher/ui/dialogs/InstallLoaderDialog.cpp +++ b/launcher/ui/dialogs/InstallLoaderDialog.cpp @@ -104,6 +104,8 @@ InstallLoaderDialog::InstallLoaderDialog(std::shared_ptr profile, c buttons->setOrientation(Qt::Horizontal); buttons->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); + buttons->button(QDialogButtonBox::Ok)->setText(tr("Ok")); + buttons->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); buttonLayout->addWidget(buttons); diff --git a/launcher/ui/dialogs/MSALoginDialog.cpp b/launcher/ui/dialogs/MSALoginDialog.cpp index 4e38b4187..40d1eff1e 100644 --- a/launcher/ui/dialogs/MSALoginDialog.cpp +++ b/launcher/ui/dialogs/MSALoginDialog.cpp @@ -68,6 +68,8 @@ MSALoginDialog::MSALoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::MS } } }); + + ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); } int MSALoginDialog::exec() @@ -83,7 +85,7 @@ int MSALoginDialog::exec() connect(m_authflow_task.get(), &AuthFlow::authorizeWithBrowserWithExtra, this, &MSALoginDialog::authorizeWithBrowserWithExtra); connect(ui->buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, m_authflow_task.get(), &Task::abort); - m_devicecode_task.reset(new AuthFlow(m_account->accountData(), AuthFlow::Action::DeviceCode, this)); + m_devicecode_task.reset(new AuthFlow(m_account->accountData(), AuthFlow::Action::DeviceCode)); connect(m_devicecode_task.get(), &Task::failed, this, &MSALoginDialog::onTaskFailed); connect(m_devicecode_task.get(), &Task::succeeded, this, &QDialog::accept); connect(m_devicecode_task.get(), &Task::aborted, this, &MSALoginDialog::reject); diff --git a/launcher/ui/dialogs/NewComponentDialog.cpp b/launcher/ui/dialogs/NewComponentDialog.cpp index b47b85ff1..b5f8ff889 100644 --- a/launcher/ui/dialogs/NewComponentDialog.cpp +++ b/launcher/ui/dialogs/NewComponentDialog.cpp @@ -68,6 +68,9 @@ NewComponentDialog::NewComponentDialog(const QString& initialName, const QString ui->nameTextBox->setFocus(); + ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); + ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK")); + originalPlaceholderText = ui->uidTextBox->placeholderText(); updateDialogState(); } diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp index f16c50860..d9ea0aafb 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.cpp +++ b/launcher/ui/dialogs/NewInstanceDialog.cpp @@ -109,16 +109,19 @@ NewInstanceDialog::NewInstanceDialog(const QString& initialGroup, auto OkButton = m_buttons->button(QDialogButtonBox::Ok); OkButton->setDefault(true); OkButton->setAutoDefault(true); + OkButton->setText(tr("OK")); connect(OkButton, &QPushButton::clicked, this, &NewInstanceDialog::accept); auto CancelButton = m_buttons->button(QDialogButtonBox::Cancel); CancelButton->setDefault(false); CancelButton->setAutoDefault(false); + CancelButton->setText(tr("Cancel")); connect(CancelButton, &QPushButton::clicked, this, &NewInstanceDialog::reject); auto HelpButton = m_buttons->button(QDialogButtonBox::Help); HelpButton->setDefault(false); HelpButton->setAutoDefault(false); + HelpButton->setText(tr("Help")); connect(HelpButton, &QPushButton::clicked, m_container, &PageContainer::help); if (!url.isEmpty()) { diff --git a/launcher/ui/dialogs/OfflineLoginDialog.cpp b/launcher/ui/dialogs/OfflineLoginDialog.cpp index b9d1c2915..d8fbc04fd 100644 --- a/launcher/ui/dialogs/OfflineLoginDialog.cpp +++ b/launcher/ui/dialogs/OfflineLoginDialog.cpp @@ -9,6 +9,9 @@ OfflineLoginDialog::OfflineLoginDialog(QWidget* parent) : QDialog(parent), ui(ne ui->progressBar->setVisible(false); ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); + ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK")); + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); } diff --git a/launcher/ui/dialogs/ProfileSelectDialog.cpp b/launcher/ui/dialogs/ProfileSelectDialog.cpp index fe03e1b6b..95bdf99a9 100644 --- a/launcher/ui/dialogs/ProfileSelectDialog.cpp +++ b/launcher/ui/dialogs/ProfileSelectDialog.cpp @@ -18,6 +18,7 @@ #include #include +#include #include "Application.h" @@ -70,6 +71,9 @@ ProfileSelectDialog::ProfileSelectDialog(const QString& message, int flags, QWid ui->listView->setCurrentIndex(ui->listView->model()->index(0, 0)); connect(ui->listView, SIGNAL(doubleClicked(QModelIndex)), SLOT(on_buttonBox_accepted())); + + ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); + ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK")); } ProfileSelectDialog::~ProfileSelectDialog() diff --git a/launcher/ui/dialogs/ProfileSetupDialog.cpp b/launcher/ui/dialogs/ProfileSetupDialog.cpp index 385094e23..a6512784f 100644 --- a/launcher/ui/dialogs/ProfileSetupDialog.cpp +++ b/launcher/ui/dialogs/ProfileSetupDialog.cpp @@ -70,6 +70,9 @@ ProfileSetupDialog::ProfileSetupDialog(MinecraftAccountPtr accountToSetup, QWidg connect(&checkStartTimer, &QTimer::timeout, this, &ProfileSetupDialog::startCheck); setNameStatus(NameStatus::NotSet, QString()); + + ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); + ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK")); } ProfileSetupDialog::~ProfileSetupDialog() diff --git a/launcher/ui/dialogs/ProfileSetupDialog.ui b/launcher/ui/dialogs/ProfileSetupDialog.ui index 9dbabb4b3..947110da7 100644 --- a/launcher/ui/dialogs/ProfileSetupDialog.ui +++ b/launcher/ui/dialogs/ProfileSetupDialog.ui @@ -30,6 +30,9 @@ Choose your name carefully: true + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + nameEdit diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 02a8454b4..b2c07d490 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -148,10 +148,14 @@ void ResourceDownloadDialog::confirm() QStringList depNames; if (auto task = getModDependenciesTask(); task) { connect(task.get(), &Task::failed, this, - [&](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); + [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); - connect(task.get(), &Task::succeeded, this, [&]() { - QStringList warnings = task->warnings(); + auto weak = task.toWeakRef(); + connect(task.get(), &Task::succeeded, this, [this, weak]() { + QStringList warnings; + if (auto task = weak.lock()) { + warnings = task->warnings(); + } if (warnings.count()) { CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->exec(); } @@ -298,7 +302,7 @@ GetModDependenciesTask::Ptr ModDownloadDialog::getModDependenciesTask() selectedVers.append(std::make_shared(selected->getPack(), selected->getVersion())); } - return makeShared(this, m_instance, model, selectedVers); + return makeShared(m_instance, model, selectedVers); } } return nullptr; @@ -377,7 +381,7 @@ QList ShaderPackDownloadDialog::getPages() return pages; } -void ModDownloadDialog::setModMetadata(std::shared_ptr meta) +void ResourceDownloadDialog::setResourceMetadata(const std::shared_ptr& meta) { switch (meta->provider) { case ModPlatform::ResourceProvider::MODRINTH: diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h index 7a0d6e895..0c3e314bc 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.h +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -69,6 +69,8 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { const QList getTasks(); [[nodiscard]] const std::shared_ptr getBaseModel() const { return m_base_model; } + void setResourceMetadata(const std::shared_ptr& meta); + public slots: void accept() override; void reject() override; @@ -107,8 +109,6 @@ class ModDownloadDialog final : public ResourceDownloadDialog { QList getPages() override; GetModDependenciesTask::Ptr getModDependenciesTask() override; - void setModMetadata(std::shared_ptr); - private: BaseInstance* m_instance; }; diff --git a/launcher/ui/dialogs/ModUpdateDialog.cpp b/launcher/ui/dialogs/ResourceUpdateDialog.cpp similarity index 67% rename from launcher/ui/dialogs/ModUpdateDialog.cpp rename to launcher/ui/dialogs/ResourceUpdateDialog.cpp index f906cfcea..7e29e1192 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.cpp +++ b/launcher/ui/dialogs/ResourceUpdateDialog.cpp @@ -1,4 +1,4 @@ -#include "ModUpdateDialog.h" +#include "ResourceUpdateDialog.h" #include "Application.h" #include "ChooseProviderDialog.h" #include "CustomMessageBox.h" @@ -8,6 +8,7 @@ #include "minecraft/mod/tasks/GetModDependenciesTask.h" #include "modplatform/ModIndex.h" #include "modplatform/flame/FlameAPI.h" +#include "tasks/SequentialTask.h" #include "ui_ReviewMessageBox.h" #include "Markdown.h" @@ -36,27 +37,28 @@ static QList mcLoadersList(BaseInstance* inst) return static_cast(inst)->getPackProfile()->getModLoadersList(); } -ModUpdateDialog::ModUpdateDialog(QWidget* parent, - BaseInstance* instance, - const std::shared_ptr mods, - QList& search_for, - bool includeDeps) - : ReviewMessageBox(parent, tr("Confirm mods to update"), "") +ResourceUpdateDialog::ResourceUpdateDialog(QWidget* parent, + BaseInstance* instance, + const std::shared_ptr resource_model, + QList& search_for, + bool include_deps, + bool filter_loaders) + : ReviewMessageBox(parent, tr("Confirm resources to update"), "") , m_parent(parent) - , m_mod_model(mods) + , m_resource_model(resource_model) , m_candidates(search_for) - , m_second_try_metadata( - new ConcurrentTask(nullptr, "Second Metadata Search", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())) + , m_second_try_metadata(new ConcurrentTask("Second Metadata Search", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())) , m_instance(instance) - , m_include_deps(includeDeps) + , m_include_deps(include_deps) + , m_filter_loaders(filter_loaders) { ReviewMessageBox::setGeometry(0, 0, 800, 600); - ui->explainLabel->setText(tr("You're about to update the following mods:")); - ui->onlyCheckedLabel->setText(tr("Only mods with a check will be updated!")); + ui->explainLabel->setText(tr("You're about to update the following resources:")); + ui->onlyCheckedLabel->setText(tr("Only resources with a check will be updated!")); } -void ModUpdateDialog::checkCandidates() +void ResourceUpdateDialog::checkCandidates() { // Ensure mods have valid metadata auto went_well = ensureMetadata(); @@ -75,8 +77,8 @@ void ModUpdateDialog::checkCandidates() } ScrollMessageBox message_dialog(m_parent, tr("Metadata generation failed"), - tr("Could not generate metadata for the following mods:
" - "Do you wish to proceed without those mods?"), + tr("Could not generate metadata for the following resources:
" + "Do you wish to proceed without those resources?"), text); message_dialog.setModal(true); if (message_dialog.exec() == QDialog::Rejected) { @@ -87,28 +89,32 @@ void ModUpdateDialog::checkCandidates() } auto versions = mcVersions(m_instance); - auto loadersList = mcLoadersList(m_instance); + auto loadersList = m_filter_loaders ? mcLoadersList(m_instance) : QList(); - SequentialTask check_task(m_parent, tr("Checking for updates")); + SequentialTask check_task(tr("Checking for updates")); if (!m_modrinth_to_update.empty()) { - m_modrinth_check_task.reset(new ModrinthCheckUpdate(m_modrinth_to_update, versions, loadersList, m_mod_model)); + m_modrinth_check_task.reset(new ModrinthCheckUpdate(m_modrinth_to_update, versions, loadersList, m_resource_model)); connect(m_modrinth_check_task.get(), &CheckUpdateTask::checkFailed, this, - [this](Mod* mod, QString reason, QUrl recover_url) { m_failed_check_update.append({ mod, reason, recover_url }); }); + [this](Resource* resource, QString reason, QUrl recover_url) { + m_failed_check_update.append({ resource, reason, recover_url }); + }); check_task.addTask(m_modrinth_check_task); } if (!m_flame_to_update.empty()) { - m_flame_check_task.reset(new FlameCheckUpdate(m_flame_to_update, versions, loadersList, m_mod_model)); + m_flame_check_task.reset(new FlameCheckUpdate(m_flame_to_update, versions, loadersList, m_resource_model)); connect(m_flame_check_task.get(), &CheckUpdateTask::checkFailed, this, - [this](Mod* mod, QString reason, QUrl recover_url) { m_failed_check_update.append({ mod, reason, recover_url }); }); + [this](Resource* resource, QString reason, QUrl recover_url) { + m_failed_check_update.append({ resource, reason, recover_url }); + }); check_task.addTask(m_flame_check_task); } connect(&check_task, &Task::failed, this, - [&](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); + [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); - connect(&check_task, &Task::succeeded, this, [&]() { + connect(&check_task, &Task::succeeded, this, [this, &check_task]() { QStringList warnings = check_task.warnings(); if (warnings.count()) { CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->exec(); @@ -132,11 +138,11 @@ void ModUpdateDialog::checkCandidates() // Add found updates for Modrinth if (m_modrinth_check_task) { - auto modrinth_updates = m_modrinth_check_task->getUpdatable(); + auto modrinth_updates = m_modrinth_check_task->getUpdates(); for (auto& updatable : modrinth_updates) { qDebug() << QString("Mod %1 has an update available!").arg(updatable.name); - appendMod(updatable); + appendResource(updatable); m_tasks.insert(updatable.name, updatable.download); } selectedVers.append(m_modrinth_check_task->getDependencies()); @@ -144,11 +150,11 @@ void ModUpdateDialog::checkCandidates() // Add found updated for Flame if (m_flame_check_task) { - auto flame_updates = m_flame_check_task->getUpdatable(); + auto flame_updates = m_flame_check_task->getUpdates(); for (auto& updatable : flame_updates) { qDebug() << QString("Mod %1 has an update available!").arg(updatable.name); - appendMod(updatable); + appendResource(updatable); m_tasks.insert(updatable.name, updatable.download); } selectedVers.append(m_flame_check_task->getDependencies()); @@ -175,8 +181,8 @@ void ModUpdateDialog::checkCandidates() } ScrollMessageBox message_dialog(m_parent, tr("Failed to check for updates"), - tr("Could not check or get the following mods for updates:
" - "Do you wish to proceed without those mods?"), + tr("Could not check or get the following resources for updates:
" + "Do you wish to proceed without those resources?"), text); message_dialog.setModal(true); if (message_dialog.exec() == QDialog::Rejected) { @@ -187,55 +193,58 @@ void ModUpdateDialog::checkCandidates() } if (m_include_deps && !APPLICATION->settings()->get("ModDependenciesDisabled").toBool()) { // dependencies - auto depTask = makeShared(this, m_instance, m_mod_model.get(), selectedVers); + auto* mod_model = dynamic_cast(m_resource_model.get()); - connect(depTask.get(), &Task::failed, this, - [&](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); + if (mod_model != nullptr) { + auto depTask = makeShared(m_instance, mod_model, selectedVers); - connect(depTask.get(), &Task::succeeded, this, [&]() { - QStringList warnings = depTask->warnings(); - if (warnings.count()) { - CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->exec(); + connect(depTask.get(), &Task::failed, this, [this](const QString& reason) { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); + }); + auto weak = depTask.toWeakRef(); + connect(depTask.get(), &Task::succeeded, this, [this, weak]() { + QStringList warnings; + if (auto depTask = weak.lock()) { + warnings = depTask->warnings(); + } + if (warnings.count()) { + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->exec(); + } + }); + + ProgressDialog progress_dialog_deps(m_parent); + progress_dialog_deps.setSkipButton(true, tr("Abort")); + progress_dialog_deps.setWindowTitle(tr("Checking for dependencies...")); + auto dret = progress_dialog_deps.execWithTask(depTask.get()); + + // If the dialog was skipped / some download error happened + if (dret == QDialog::DialogCode::Rejected) { + m_aborted = true; + QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); + return; } - }); + static FlameAPI api; - ProgressDialog progress_dialog_deps(m_parent); - progress_dialog_deps.setSkipButton(true, tr("Abort")); - progress_dialog_deps.setWindowTitle(tr("Checking for dependencies...")); - auto dret = progress_dialog_deps.execWithTask(depTask.get()); + auto dependencyExtraInfo = depTask->getExtraInfo(); - // If the dialog was skipped / some download error happened - if (dret == QDialog::DialogCode::Rejected) { - m_aborted = true; - QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); - return; - } - static FlameAPI api; + for (const auto& dep : depTask->getDependecies()) { + auto changelog = dep->version.changelog; + if (dep->pack->provider == ModPlatform::ResourceProvider::FLAME) + changelog = api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt()); + auto download_task = makeShared(dep->pack, dep->version, m_resource_model); + auto extraInfo = dependencyExtraInfo.value(dep->version.addonId.toString()); + CheckUpdateTask::Update updatable = { + dep->pack->name, dep->version.hash, tr("Not installed"), dep->version.version, dep->version.version_type, + changelog, dep->pack->provider, download_task, !extraInfo.maybe_installed + }; - auto dependencyExtraInfo = depTask->getExtraInfo(); - - for (auto dep : depTask->getDependecies()) { - auto changelog = dep->version.changelog; - if (dep->pack->provider == ModPlatform::ResourceProvider::FLAME) - changelog = api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt()); - auto download_task = makeShared(dep->pack, dep->version, m_mod_model); - auto extraInfo = dependencyExtraInfo.value(dep->version.addonId.toString()); - CheckUpdateTask::UpdatableMod updatable = { dep->pack->name, - dep->version.hash, - "", - dep->version.version, - dep->version.version_type, - changelog, - dep->pack->provider, - download_task, - !extraInfo.maybe_installed }; - - appendMod(updatable, extraInfo.required_by); - m_tasks.insert(updatable.name, updatable.download); + appendResource(updatable, extraInfo.required_by); + m_tasks.insert(updatable.name, updatable.download); + } } } - // If there's no mod to be updated + // If there's no resource to be updated if (ui->modTreeWidget->topLevelItemCount() == 0) { m_no_updates = true; } else { @@ -257,35 +266,35 @@ void ModUpdateDialog::checkCandidates() } // Part 1: Ensure we have a valid metadata -auto ModUpdateDialog::ensureMetadata() -> bool +auto ResourceUpdateDialog::ensureMetadata() -> bool { auto index_dir = indexDir(); - SequentialTask seq(m_parent, tr("Looking for metadata")); + SequentialTask seq(tr("Looking for metadata")); // A better use of data structures here could remove the need for this QHash QHash should_try_others; - QList modrinth_tmp; - QList flame_tmp; + QList modrinth_tmp; + QList flame_tmp; bool confirm_rest = false; bool try_others_rest = false; bool skip_rest = false; ModPlatform::ResourceProvider provider_rest = ModPlatform::ResourceProvider::MODRINTH; - auto addToTmp = [&](Mod* m, ModPlatform::ResourceProvider p) { + auto addToTmp = [&modrinth_tmp, &flame_tmp](Resource* resource, ModPlatform::ResourceProvider p) { switch (p) { case ModPlatform::ResourceProvider::MODRINTH: - modrinth_tmp.push_back(m); + modrinth_tmp.push_back(resource); break; case ModPlatform::ResourceProvider::FLAME: - flame_tmp.push_back(m); + flame_tmp.push_back(resource); break; } }; for (auto candidate : m_candidates) { - if (candidate->status() != ModStatus::NoMetadata) { + if (candidate->status() != ResourceStatus::NO_METADATA) { onMetadataEnsured(candidate); continue; } @@ -304,7 +313,7 @@ auto ModUpdateDialog::ensureMetadata() -> bool } ChooseProviderDialog chooser(this); - chooser.setDescription(tr("The mod '%1' does not have a metadata yet. We need to generate it in order to track relevant " + chooser.setDescription(tr("The resource '%1' does not have a metadata yet. We need to generate it in order to track relevant " "information on how to update this mod. " "To do this, please select a mod provider which we can use to check for updates for this mod.") .arg(candidate->name())); @@ -328,8 +337,8 @@ auto ModUpdateDialog::ensureMetadata() -> bool if (!modrinth_tmp.empty()) { auto modrinth_task = makeShared(modrinth_tmp, index_dir, ModPlatform::ResourceProvider::MODRINTH); - connect(modrinth_task.get(), &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); }); - connect(modrinth_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) { + connect(modrinth_task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); + connect(modrinth_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Resource* candidate) { onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::MODRINTH); }); connect(modrinth_task.get(), &EnsureMetadataTask::failed, @@ -343,8 +352,8 @@ auto ModUpdateDialog::ensureMetadata() -> bool if (!flame_tmp.empty()) { auto flame_task = makeShared(flame_tmp, index_dir, ModPlatform::ResourceProvider::FLAME); - connect(flame_task.get(), &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); }); - connect(flame_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) { + connect(flame_task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); + connect(flame_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Resource* candidate) { onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::FLAME); }); connect(flame_task.get(), &EnsureMetadataTask::failed, @@ -366,18 +375,18 @@ auto ModUpdateDialog::ensureMetadata() -> bool return (ret_metadata != QDialog::DialogCode::Rejected); } -void ModUpdateDialog::onMetadataEnsured(Mod* mod) +void ResourceUpdateDialog::onMetadataEnsured(Resource* resource) { // When the mod is a folder, for instance - if (!mod->metadata()) + if (!resource->metadata()) return; - switch (mod->metadata()->provider) { + switch (resource->metadata()->provider) { case ModPlatform::ResourceProvider::MODRINTH: - m_modrinth_to_update.push_back(mod); + m_modrinth_to_update.push_back(resource); break; case ModPlatform::ResourceProvider::FLAME: - m_flame_to_update.push_back(mod); + m_flame_to_update.push_back(resource); break; } } @@ -394,31 +403,37 @@ ModPlatform::ResourceProvider next(ModPlatform::ResourceProvider p) return ModPlatform::ResourceProvider::FLAME; } -void ModUpdateDialog::onMetadataFailed(Mod* mod, bool try_others, ModPlatform::ResourceProvider first_choice) +void ResourceUpdateDialog::onMetadataFailed(Resource* resource, bool try_others, ModPlatform::ResourceProvider first_choice) { if (try_others) { auto index_dir = indexDir(); - 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); }); + auto task = makeShared(resource, index_dir, next(first_choice)); + connect(task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); + connect(task.get(), &EnsureMetadataTask::metadataFailed, [this](Resource* 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); + [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); + if (task->getHashingTask()) { + auto seq = makeShared(); + seq->addTask(task->getHashingTask()); + seq->addTask(task); + m_second_try_metadata->addTask(seq); + } else { + m_second_try_metadata->addTask(task); + } } else { QString reason{ tr("Couldn't find a valid version on the selected mod provider(s)") }; - m_failed_metadata.append({ mod, reason }); + m_failed_metadata.append({ resource, reason }); } } -void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStringList requiredBy) +void ResourceUpdateDialog::appendResource(CheckUpdateTask::Update const& info, QStringList requiredBy) { auto item_top = new QTreeWidgetItem(ui->modTreeWidget); item_top->setCheckState(0, info.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); if (!info.enabled) { - item_top->setToolTip(0, tr("Mod was disabled as it may be already instaled.")); + item_top->setToolTip(0, tr("Mod was disabled as it may be already installed.")); } item_top->setText(0, info.name); item_top->setExpanded(true); @@ -427,7 +442,7 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStri provider_item->setText(0, tr("Provider: %1").arg(ModPlatform::ProviderCapabilities::readableName(info.provider))); auto old_version_item = new QTreeWidgetItem(item_top); - old_version_item->setText(0, tr("Old version: %1").arg(info.old_version.isEmpty() ? tr("Not installed") : info.old_version)); + old_version_item->setText(0, tr("Old version: %1").arg(info.old_version)); auto new_version_item = new QTreeWidgetItem(item_top); new_version_item->setText(0, tr("New version: %1").arg(info.new_version)); @@ -481,7 +496,7 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStri ui->modTreeWidget->addTopLevelItem(item_top); } -auto ModUpdateDialog::getTasks() -> const QList +auto ResourceUpdateDialog::getTasks() -> const QList { QList list; diff --git a/launcher/ui/dialogs/ModUpdateDialog.h b/launcher/ui/dialogs/ResourceUpdateDialog.h similarity index 52% rename from launcher/ui/dialogs/ModUpdateDialog.h rename to launcher/ui/dialogs/ResourceUpdateDialog.h index de5ab46a5..de1d845d2 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.h +++ b/launcher/ui/dialogs/ResourceUpdateDialog.h @@ -13,22 +13,22 @@ class ModrinthCheckUpdate; class FlameCheckUpdate; class ConcurrentTask; -class ModUpdateDialog final : public ReviewMessageBox { +class ResourceUpdateDialog final : public ReviewMessageBox { Q_OBJECT public: - explicit ModUpdateDialog(QWidget* parent, BaseInstance* instance, std::shared_ptr mod_model, QList& search_for); - explicit ModUpdateDialog(QWidget* parent, - BaseInstance* instance, - std::shared_ptr mod_model, - QList& search_for, - bool includeDeps); + explicit ResourceUpdateDialog(QWidget* parent, + BaseInstance* instance, + std::shared_ptr resource_model, + QList& search_for, + bool include_deps, + bool filter_loaders); void checkCandidates(); - void appendMod(const CheckUpdateTask::UpdatableMod& info, QStringList requiredBy = {}); + void appendResource(const CheckUpdateTask::Update& info, QStringList requiredBy = {}); const QList getTasks(); - auto indexDir() const -> QDir { return m_mod_model->indexDir(); } + auto indexDir() const -> QDir { return m_resource_model->indexDir(); } auto noUpdates() const -> bool { return m_no_updates; }; auto aborted() const -> bool { return m_aborted; }; @@ -37,8 +37,8 @@ class ModUpdateDialog final : public ReviewMessageBox { auto ensureMetadata() -> bool; private slots: - void onMetadataEnsured(Mod*); - void onMetadataFailed(Mod*, + void onMetadataEnsured(Resource* resource); + void onMetadataFailed(Resource* resource, bool try_others = false, ModPlatform::ResourceProvider first_choice = ModPlatform::ResourceProvider::MODRINTH); @@ -48,15 +48,15 @@ class ModUpdateDialog final : public ReviewMessageBox { shared_qobject_ptr m_modrinth_check_task; shared_qobject_ptr m_flame_check_task; - const std::shared_ptr m_mod_model; + const std::shared_ptr m_resource_model; - QList& m_candidates; - QList m_modrinth_to_update; - QList m_flame_to_update; + QList& m_candidates; + QList m_modrinth_to_update; + QList m_flame_to_update; ConcurrentTask::Ptr m_second_try_metadata; - QList> m_failed_metadata; - QList> m_failed_check_update; + QList> m_failed_metadata; + QList> m_failed_check_update; QHash m_tasks; BaseInstance* m_instance; @@ -64,4 +64,5 @@ class ModUpdateDialog final : public ReviewMessageBox { bool m_no_updates = false; bool m_aborted = false; bool m_include_deps = false; + bool m_filter_loaders = false; }; diff --git a/launcher/ui/dialogs/ReviewMessageBox.cpp b/launcher/ui/dialogs/ReviewMessageBox.cpp index 66c36d400..96cc8149f 100644 --- a/launcher/ui/dialogs/ReviewMessageBox.cpp +++ b/launcher/ui/dialogs/ReviewMessageBox.cpp @@ -20,6 +20,9 @@ ReviewMessageBox::ReviewMessageBox(QWidget* parent, [[maybe_unused]] QString con connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &ReviewMessageBox::accept); connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &ReviewMessageBox::reject); + + ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); + ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK")); } ReviewMessageBox::~ReviewMessageBox() @@ -38,7 +41,7 @@ void ReviewMessageBox::appendResource(ResourceInformation&& info) itemTop->setCheckState(0, info.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); itemTop->setText(0, info.name); if (!info.enabled) { - itemTop->setToolTip(0, tr("Mod was disabled as it may be already instaled.")); + itemTop->setToolTip(0, tr("Mod was disabled as it may be already installed.")); } auto filenameItem = new QTreeWidgetItem(itemTop); diff --git a/launcher/ui/dialogs/ScrollMessageBox.cpp b/launcher/ui/dialogs/ScrollMessageBox.cpp index c04d87842..1cfb848f4 100644 --- a/launcher/ui/dialogs/ScrollMessageBox.cpp +++ b/launcher/ui/dialogs/ScrollMessageBox.cpp @@ -1,4 +1,5 @@ #include "ScrollMessageBox.h" +#include #include "ui_ScrollMessageBox.h" ScrollMessageBox::ScrollMessageBox(QWidget* parent, const QString& title, const QString& text, const QString& body) @@ -8,6 +9,9 @@ ScrollMessageBox::ScrollMessageBox(QWidget* parent, const QString& title, const this->setWindowTitle(title); ui->label->setText(text); ui->textBrowser->setText(body); + + ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); + ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK")); } ScrollMessageBox::~ScrollMessageBox() diff --git a/launcher/ui/dialogs/VersionSelectDialog.cpp b/launcher/ui/dialogs/VersionSelectDialog.cpp index 876d7470e..30377288b 100644 --- a/launcher/ui/dialogs/VersionSelectDialog.cpp +++ b/launcher/ui/dialogs/VersionSelectDialog.cpp @@ -68,6 +68,9 @@ VersionSelectDialog::VersionSelectDialog(BaseVersionList* vlist, QString title, m_buttonBox->setObjectName(QStringLiteral("buttonBox")); m_buttonBox->setOrientation(Qt::Horizontal); m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); + + m_buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Ok")); + m_buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); m_horizontalLayout->addWidget(m_buttonBox); m_verticalLayout->addLayout(m_horizontalLayout); diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index 6c85ffa96..f112e1acf 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -93,6 +93,9 @@ SkinManageDialog::SkinManageDialog(QWidget* parent, MinecraftAccountPtr acct) setupCapes(); ui->listView->setCurrentIndex(m_list.index(m_list.getSelectedAccountSkin())); + + ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); + ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK")); } SkinManageDialog::~SkinManageDialog() @@ -139,6 +142,9 @@ void SkinManageDialog::on_fileBtn_clicked() { auto filter = QMimeDatabase().mimeTypeForName("image/png").filterString(); QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), filter); + if (raw_path.isNull()) { + return; + } auto message = m_list.installSkin(raw_path, {}); if (!message.isEmpty()) { CustomMessageBox::selectable(this, tr("Selected file is not a valid skin"), message, QMessageBox::Critical)->show(); diff --git a/launcher/ui/java/InstallJavaDialog.cpp b/launcher/ui/java/InstallJavaDialog.cpp index 0ece3220b..5f69b9d46 100644 --- a/launcher/ui/java/InstallJavaDialog.cpp +++ b/launcher/ui/java/InstallJavaDialog.cpp @@ -132,9 +132,9 @@ class InstallJavaPage : public QWidget, public BasePage { m_recommended_majors = majors; recommendedFilterChanged(); } - void setRecomend(bool recomend) + void setRecommend(bool recommend) { - m_recommend = recomend; + m_recommend = recommend; recommendedFilterChanged(); } void recommendedFilterChanged() @@ -202,7 +202,7 @@ InstallDialog::InstallDialog(const QString& uid, BaseInstance* instance, QWidget recommendedCheckBox->setCheckState(Qt::CheckState::Checked); connect(recommendedCheckBox, &QCheckBox::stateChanged, this, [this](int state) { for (BasePage* page : container->getPages()) { - pageCast(page)->setRecomend(state == Qt::Checked); + pageCast(page)->setRecommend(state == Qt::Checked); } }); @@ -212,6 +212,7 @@ InstallDialog::InstallDialog(const QString& uid, BaseInstance* instance, QWidget buttons->setOrientation(Qt::Horizontal); buttons->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); buttons->button(QDialogButtonBox::Ok)->setText(tr("Download")); + buttons->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); buttonLayout->addWidget(buttons); @@ -260,7 +261,7 @@ InstallDialog::InstallDialog(const QString& uid, BaseInstance* instance, QWidget container->selectPage(page->id()); auto cast = pageCast(page); - cast->setRecomend(true); + cast->setRecommend(true); connect(cast, &InstallJavaPage::selectionChanged, this, [this, cast] { validate(cast); }); if (!recommendedJavas.isEmpty()) { cast->setRecommendedMajors(recommendedJavas); @@ -316,7 +317,7 @@ void InstallDialog::done(int result) deletePath(); } #if defined(Q_OS_MACOS) - auto seq = makeShared(this, tr("Install Java")); + auto seq = makeShared(tr("Install Java")); seq->addTask(task); seq->addTask(makeShared(final_path)); task = seq; @@ -343,4 +344,4 @@ void InstallDialog::done(int result) } // namespace Java -#include "InstallJavaDialog.moc" \ No newline at end of file +#include "InstallJavaDialog.moc" diff --git a/launcher/ui/pagedialog/PageDialog.cpp b/launcher/ui/pagedialog/PageDialog.cpp index 6514217cd..d211cb4d3 100644 --- a/launcher/ui/pagedialog/PageDialog.cpp +++ b/launcher/ui/pagedialog/PageDialog.cpp @@ -39,6 +39,8 @@ PageDialog::PageDialog(BasePageProvider* pageProvider, QString defaultId, QWidge QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Close); buttons->button(QDialogButtonBox::Close)->setDefault(true); + buttons->button(QDialogButtonBox::Close)->setText(tr("Close")); + buttons->button(QDialogButtonBox::Help)->setText(tr("Help")); buttons->setContentsMargins(6, 0, 6, 0); m_container->addButtons(buttons); diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui index 9c713aa79..05c256bb2 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -207,7 +207,7 @@ - <html><head/><body><p>Note: you only need to set this to access private data. Read the <a href="https://docs.modrinth.com/#section/Authentication">documentation</a> for more information.</p></body></html> + <html><head/><body><p>Note: you only need to set this to access private data. Read the <a href="https://docs.modrinth.com/api/#authentication">documentation</a> for more information.</p></body></html> true diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp index 041b8faff..90eb6becf 100644 --- a/launcher/ui/pages/global/AccountListPage.cpp +++ b/launcher/ui/pages/global/AccountListPage.cpp @@ -141,14 +141,6 @@ void AccountListPage::on_actionAddMicrosoft_triggered() void AccountListPage::on_actionAddOffline_triggered() { - if (!m_accounts->anyAccountIsValid()) { - QMessageBox::warning(this, tr("Error"), - tr("You must add a Microsoft account that owns Minecraft before you can add an offline account." - "

" - "If you have lost your account you can contact Microsoft for support.")); - return; - } - MinecraftAccountPtr account = OfflineLoginDialog::newAccount(this, tr("Please enter your desired username to add your offline account.")); diff --git a/launcher/ui/pages/global/CustomCommandsPage.cpp b/launcher/ui/pages/global/CustomCommandsPage.cpp deleted file mode 100644 index cc8518c2f..000000000 --- a/launcher/ui/pages/global/CustomCommandsPage.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (c) 2022 Jamie Mansfield - * Copyright (C) 2022 Sefa Eyeoglu - * - * 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. - */ - -#include "CustomCommandsPage.h" -#include -#include -#include - -CustomCommandsPage::CustomCommandsPage(QWidget* parent) : QWidget(parent) -{ - auto verticalLayout = new QVBoxLayout(this); - verticalLayout->setObjectName(QStringLiteral("verticalLayout")); - verticalLayout->setContentsMargins(0, 0, 0, 0); - - auto tabWidget = new QTabWidget(this); - tabWidget->setObjectName(QStringLiteral("tabWidget")); - commands = new CustomCommands(this); - commands->setContentsMargins(6, 6, 6, 6); - tabWidget->addTab(commands, "Foo"); - tabWidget->tabBar()->hide(); - verticalLayout->addWidget(tabWidget); - loadSettings(); -} - -CustomCommandsPage::~CustomCommandsPage() {} - -bool CustomCommandsPage::apply() -{ - applySettings(); - return true; -} - -void CustomCommandsPage::applySettings() -{ - auto s = APPLICATION->settings(); - s->set("PreLaunchCommand", commands->prelaunchCommand()); - s->set("WrapperCommand", commands->wrapperCommand()); - s->set("PostExitCommand", commands->postexitCommand()); -} - -void CustomCommandsPage::loadSettings() -{ - auto s = APPLICATION->settings(); - commands->initialize(false, true, s->get("PreLaunchCommand").toString(), s->get("WrapperCommand").toString(), - s->get("PostExitCommand").toString()); -} - -void CustomCommandsPage::retranslate() -{ - commands->retranslate(); -} diff --git a/launcher/ui/pages/global/EnvironmentVariablesPage.cpp b/launcher/ui/pages/global/EnvironmentVariablesPage.cpp deleted file mode 100644 index 2d44ed624..000000000 --- a/launcher/ui/pages/global/EnvironmentVariablesPage.cpp +++ /dev/null @@ -1,71 +0,0 @@ -// 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. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include -#include - -#include "EnvironmentVariablesPage.h" - -EnvironmentVariablesPage::EnvironmentVariablesPage(QWidget* parent) : QWidget(parent) -{ - auto verticalLayout = new QVBoxLayout(this); - verticalLayout->setObjectName(QStringLiteral("verticalLayout")); - verticalLayout->setContentsMargins(0, 0, 0, 0); - - auto tabWidget = new QTabWidget(this); - tabWidget->setObjectName(QStringLiteral("tabWidget")); - variables = new EnvironmentVariables(this); - variables->setContentsMargins(6, 6, 6, 6); - tabWidget->addTab(variables, "Foo"); - tabWidget->tabBar()->hide(); - verticalLayout->addWidget(tabWidget); - - variables->initialize(false, false, APPLICATION->settings()->get("Env").toMap()); -} - -QString EnvironmentVariablesPage::displayName() const -{ - return tr("Environment Variables"); -} - -QIcon EnvironmentVariablesPage::icon() const -{ - return APPLICATION->getThemedIcon("environment-variables"); -} - -QString EnvironmentVariablesPage::id() const -{ - return "environment-variables"; -} - -QString EnvironmentVariablesPage::helpPage() const -{ - return "Environment-variables"; -} - -bool EnvironmentVariablesPage::apply() -{ - APPLICATION->settings()->set("Env", variables->value()); - return true; -} - -void EnvironmentVariablesPage::retranslate() -{ - variables->retranslate(); -} diff --git a/launcher/ui/pages/global/EnvironmentVariablesPage.h b/launcher/ui/pages/global/EnvironmentVariablesPage.h deleted file mode 100644 index 6e80775ec..000000000 --- a/launcher/ui/pages/global/EnvironmentVariablesPage.h +++ /dev/null @@ -1,42 +0,0 @@ -// 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. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once - -#include - -#include "ui/pages/BasePage.h" -#include "ui/widgets/EnvironmentVariables.h" - -class EnvironmentVariablesPage : public QWidget, public BasePage { - Q_OBJECT - - public: - explicit EnvironmentVariablesPage(QWidget* parent = nullptr); - - QString displayName() const override; - QIcon icon() const override; - QString id() const override; - QString helpPage() const override; - - bool apply() override; - void retranslate() override; - - private: - EnvironmentVariables* variables; -}; diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index 0ae296815..b99d0c63e 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -62,25 +62,15 @@ JavaPage::JavaPage(QWidget* parent) : QWidget(parent), ui(new Ui::JavaPage) { ui->setupUi(this); - + if (BuildConfig.JAVA_DOWNLOADER_ENABLED) { ui->managedJavaList->initialize(new JavaInstallList(this, true)); ui->managedJavaList->setResizeOn(2); ui->managedJavaList->selectCurrent(); ui->managedJavaList->setEmptyString(tr("No managed Java versions are installed")); ui->managedJavaList->setEmptyErrorString(tr("Couldn't load the managed Java list!")); - connect(ui->autodetectJavaCheckBox, &QCheckBox::stateChanged, this, [this] { - ui->autodownloadCheckBox->setEnabled(ui->autodetectJavaCheckBox->isChecked()); - if (!ui->autodetectJavaCheckBox->isChecked()) - ui->autodownloadCheckBox->setChecked(false); - }); - } else { - ui->autodownloadCheckBox->setHidden(true); + } else ui->tabWidget->tabBar()->hide(); - } - - loadSettings(); - updateThresholds(); } JavaPage::~JavaPage() @@ -88,117 +78,18 @@ JavaPage::~JavaPage() delete ui; } +void JavaPage::retranslate() +{ + ui->retranslateUi(this); +} + bool JavaPage::apply() { - applySettings(); + ui->javaSettings->saveSettings(); + JavaCommon::checkJVMArgs(APPLICATION->settings()->get("JvmArgs").toString(), this); return true; } -void JavaPage::applySettings() -{ - auto s = APPLICATION->settings(); - - // Memory - int min = ui->minMemSpinBox->value(); - int max = ui->maxMemSpinBox->value(); - if (min < max) { - s->set("MinMemAlloc", min); - s->set("MaxMemAlloc", max); - } else { - s->set("MinMemAlloc", max); - s->set("MaxMemAlloc", min); - } - s->set("PermGen", ui->permGenSpinBox->value()); - - // Java Settings - s->set("JavaPath", ui->javaPathTextBox->text()); - s->set("JvmArgs", ui->jvmArgsTextBox->toPlainText().replace("\n", " ")); - s->set("IgnoreJavaCompatibility", ui->skipCompatibilityCheckbox->isChecked()); - s->set("IgnoreJavaWizard", ui->skipJavaWizardCheckbox->isChecked()); - s->set("AutomaticJavaSwitch", ui->autodetectJavaCheckBox->isChecked()); - s->set("AutomaticJavaDownload", ui->autodownloadCheckBox->isChecked()); - JavaCommon::checkJVMArgs(s->get("JvmArgs").toString(), this->parentWidget()); -} -void JavaPage::loadSettings() -{ - auto s = APPLICATION->settings(); - // Memory - int min = s->get("MinMemAlloc").toInt(); - int max = s->get("MaxMemAlloc").toInt(); - if (min < max) { - ui->minMemSpinBox->setValue(min); - ui->maxMemSpinBox->setValue(max); - } else { - ui->minMemSpinBox->setValue(max); - ui->maxMemSpinBox->setValue(min); - } - ui->permGenSpinBox->setValue(s->get("PermGen").toInt()); - - // Java Settings - ui->javaPathTextBox->setText(s->get("JavaPath").toString()); - ui->jvmArgsTextBox->setPlainText(s->get("JvmArgs").toString()); - ui->skipCompatibilityCheckbox->setChecked(s->get("IgnoreJavaCompatibility").toBool()); - ui->skipJavaWizardCheckbox->setChecked(s->get("IgnoreJavaWizard").toBool()); - ui->autodetectJavaCheckBox->setChecked(s->get("AutomaticJavaSwitch").toBool()); - ui->autodownloadCheckBox->setChecked(s->get("AutomaticJavaSwitch").toBool() && s->get("AutomaticJavaDownload").toBool()); -} - -void JavaPage::on_javaDetectBtn_clicked() -{ - if (JavaUtils::getJavaCheckPath().isEmpty()) { - JavaCommon::javaCheckNotFound(this); - return; - } - - JavaInstallPtr java; - - VersionSelectDialog vselect(APPLICATION->javalist().get(), tr("Select a Java version"), this, true); - vselect.setResizeOn(2); - vselect.exec(); - - if (vselect.result() == QDialog::Accepted && vselect.selectedVersion()) { - java = std::dynamic_pointer_cast(vselect.selectedVersion()); - ui->javaPathTextBox->setText(java->path); - if (!java->is_64bit && APPLICATION->settings()->get("MaxMemAlloc").toInt() > 2048) { - CustomMessageBox::selectable(this, tr("Confirm Selection"), - tr("You selected a 32-bit version of Java.\n" - "This installation does not support more than 2048MiB of RAM.\n" - "Please make sure that the maximum memory value is lower."), - QMessageBox::Warning, QMessageBox::Ok, QMessageBox::Ok) - ->exec(); - } - } -} - -void JavaPage::on_javaBrowseBtn_clicked() -{ - QString raw_path = QFileDialog::getOpenFileName(this, tr("Find Java executable")); - - // do not allow current dir - it's dirty. Do not allow dirs that don't exist - if (raw_path.isEmpty()) { - return; - } - - QString cooked_path = FS::NormalizePath(raw_path); - QFileInfo javaInfo(cooked_path); - ; - if (!javaInfo.exists() || !javaInfo.isExecutable()) { - return; - } - ui->javaPathTextBox->setText(cooked_path); -} - -void JavaPage::on_javaTestBtn_clicked() -{ - if (checker) { - return; - } - checker.reset(new JavaCommon::TestCheck(this, ui->javaPathTextBox->text(), ui->jvmArgsTextBox->toPlainText().replace("\n", " "), - ui->minMemSpinBox->value(), ui->maxMemSpinBox->value(), ui->permGenSpinBox->value())); - connect(checker.get(), SIGNAL(finished()), SLOT(checkerFinished())); - checker->run(); -} - void JavaPage::on_downloadJavaButton_clicked() { auto jdialog = new Java::InstallDialog({}, nullptr, this); @@ -206,51 +97,6 @@ void JavaPage::on_downloadJavaButton_clicked() ui->managedJavaList->loadList(); } -void JavaPage::on_maxMemSpinBox_valueChanged([[maybe_unused]] int i) -{ - updateThresholds(); -} - -void JavaPage::checkerFinished() -{ - checker.reset(); -} - -void JavaPage::retranslate() -{ - ui->retranslateUi(this); -} - -void JavaPage::updateThresholds() -{ - auto sysMiB = Sys::getSystemRam() / Sys::mebibyte; - unsigned int maxMem = ui->maxMemSpinBox->value(); - unsigned int minMem = ui->minMemSpinBox->value(); - - QString iconName; - - if (maxMem >= sysMiB) { - iconName = "status-bad"; - ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation exceeds your system memory capacity.")); - } else if (maxMem > (sysMiB * 0.9)) { - iconName = "status-yellow"; - ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity.")); - } else if (maxMem < minMem) { - iconName = "status-yellow"; - ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value")); - } else { - iconName = "status-good"; - ui->labelMaxMemIcon->setToolTip(""); - } - - { - auto height = ui->labelMaxMemIcon->fontInfo().pixelSize(); - QIcon icon = APPLICATION->getThemedIcon(iconName); - QPixmap pix = icon.pixmap(height, height); - ui->labelMaxMemIcon->setPixmap(pix); - } -} - void JavaPage::on_removeJavaButton_clicked() { auto version = ui->managedJavaList->selectedVersion(); diff --git a/launcher/ui/pages/global/JavaPage.h b/launcher/ui/pages/global/JavaPage.h index 0a1c4a6be..ea7724c1d 100644 --- a/launcher/ui/pages/global/JavaPage.h +++ b/launcher/ui/pages/global/JavaPage.h @@ -37,6 +37,7 @@ #include #include +#include "ui/widgets/JavaSettingsWidget.h" #include #include #include "JavaCommon.h" @@ -59,26 +60,15 @@ class JavaPage : public QWidget, public BasePage { QIcon icon() const override { return APPLICATION->getThemedIcon("java"); } QString id() const override { return "java-settings"; } QString helpPage() const override { return "Java-settings"; } - bool apply() override; void retranslate() override; - void updateThresholds(); - - private: - void applySettings(); - void loadSettings(); + bool apply() override; private slots: - void on_javaDetectBtn_clicked(); - void on_javaTestBtn_clicked(); - void on_javaBrowseBtn_clicked(); void on_downloadJavaButton_clicked(); void on_removeJavaButton_clicked(); void on_refreshJavaButton_clicked(); - void on_maxMemSpinBox_valueChanged(int i); - void checkerFinished(); private: Ui::JavaPage* ui; - unique_qobject_ptr checker; }; diff --git a/launcher/ui/pages/global/JavaPage.ui b/launcher/ui/pages/global/JavaPage.ui index e6bbeb15a..a4b2ac203 100644 --- a/launcher/ui/pages/global/JavaPage.ui +++ b/launcher/ui/pages/global/JavaPage.ui @@ -40,292 +40,27 @@ - - - Memory + + + true - - - - - Ma&ximum memory allocation: - - - maxMemSpinBox - - - - - - - &PermGen: - - - permGenSpinBox - - - - - - - &Minimum memory allocation: - - - minMemSpinBox - - - - - - - The amount of memory Minecraft is started with. - - - MiB - - - 8 - - - 1048576 - - - 128 - - - 256 - - - - - - - The maximum amount of memory Minecraft is allowed to use. - - - MiB - - - 8 - - - 1048576 - - - 128 - - - 1024 - - - - - - - The amount of memory available to store loaded Java classes. - - - MiB - - - 4 - - - 999999999 - - - 8 - - - 64 - - - - - - - - - - maxMemSpinBox - - - - + + + + 0 + 0 + 535 + 610 + + + + + + + + - - - - Java Runtime - - - - - - - 0 - 0 - - - - If enabled, the launcher will not check if an instance is compatible with the selected Java version. - - - &Skip Java compatibility checks - - - - - - - - - - 0 - 0 - - - - &Auto-detect... - - - - - - - - 0 - 0 - - - - &Test - - - - - - - - - - 0 - 0 - - - - JVM arguments: - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - If enabled, the launcher will not prompt you to choose a Java version if one isn't found. - - - Skip Java &Wizard - - - - - - - true - - - - 0 - 0 - - - - - 16777215 - 100 - - - - - - - - Automatically selects the Java version that is compatible with the current Minecraft instance, based on the major version required. - - - Autodetect Java version - - - - - - - - - - 0 - 0 - - - - &Java path: - - - javaPathTextBox - - - - - - - - - - - 0 - 0 - - - - Browse - - - - - - - - - false - - - Automatically downloads and selects the Java version recommended by Mojang. - - - Auto-download Mojang Java - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - -
@@ -416,20 +151,13 @@
ui/widgets/VersionSelectWidget.h
1 + + JavaSettingsWidget + QWidget +
ui/widgets/JavaSettingsWidget.h
+ 1 +
- - minMemSpinBox - maxMemSpinBox - permGenSpinBox - javaPathTextBox - javaBrowseBtn - javaDetectBtn - javaTestBtn - skipCompatibilityCheckbox - skipJavaWizardCheckbox - jvmArgsTextBox - tabWidget - diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 8bbed9643..04ee01b00 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -217,9 +217,6 @@ void LauncherPage::applySettings() s->set("RequestTimeout", ui->timeoutSecondsSpinBox->value()); // Console settings - s->set("ShowConsole", ui->showConsoleCheck->isChecked()); - s->set("AutoCloseConsole", ui->autoCloseConsoleCheck->isChecked()); - s->set("ShowConsoleOnError", ui->showConsoleErrorCheck->isChecked()); QString consoleFontFamily = ui->consoleFont->currentFont().family(); s->set("ConsoleFont", consoleFontFamily); s->set("ConsoleFontSize", ui->fontSizeBox->value()); @@ -235,6 +232,7 @@ void LauncherPage::applySettings() s->set("SkinsDir", ui->skinsDirTextBox->text()); s->set("JavaDir", ui->javaDirTextBox->text()); s->set("DownloadsDirWatchRecursive", ui->downloadsDirWatchRecursiveCheckBox->isChecked()); + s->set("MoveModsFromDownloadsDir", ui->downloadsDirMoveCheckBox->isChecked()); auto sortMode = (InstSortMode)ui->sortingModeGroup->checkedId(); switch (sortMode) { @@ -277,9 +275,6 @@ void LauncherPage::loadSettings() ui->timeoutSecondsSpinBox->setValue(s->get("RequestTimeout").toInt()); // Console settings - ui->showConsoleCheck->setChecked(s->get("ShowConsole").toBool()); - ui->autoCloseConsoleCheck->setChecked(s->get("AutoCloseConsole").toBool()); - ui->showConsoleErrorCheck->setChecked(s->get("ShowConsoleOnError").toBool()); QString fontFamily = APPLICATION->settings()->get("ConsoleFont").toString(); QFont consoleFont(fontFamily); ui->consoleFont->setCurrentFont(consoleFont); @@ -302,6 +297,7 @@ void LauncherPage::loadSettings() ui->skinsDirTextBox->setText(s->get("SkinsDir").toString()); ui->javaDirTextBox->setText(s->get("JavaDir").toString()); ui->downloadsDirWatchRecursiveCheckBox->setChecked(s->get("DownloadsDirWatchRecursive").toBool()); + ui->downloadsDirMoveCheckBox->setChecked(s->get("MoveModsFromDownloadsDir").toBool()); QString sortMode = s->get("InstSortMode").toString(); diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 3cba468ff..31c878f3e 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -46,317 +46,353 @@ - - - Update Settings + + + Qt::ScrollBarAsNeeded - - - - - Check for updates automatically - - - - - - - - - Update interval - - - - - - - Set it to 0 to only check on launch - - - h - - - 0 - - - 99999999 - - - - - - + + true + + + + + 0 + 0 + 473 + 770 + + + + + + + Update Settings + + + + + + Check for updates automatically + + + + + + + + + Update interval + + + + + + + Set it to 0 to only check on launch + + + h + + + 0 + + + 99999999 + + + + + + + + + + + + Folders + + + + + + &Downloads: + + + downloadsDirTextBox + + + + + + + Browse + + + + + + + + + + + + + &Skins: + + + skinsDirTextBox + + + + + + + &Icons: + + + iconsDirTextBox + + + + + + + + + When enabled, in addition to the downloads folder, its sub folders will also be searched when looking for resources (e.g. when looking for blocked mods on CurseForge). + + + Check downloads folder recursively + + + + + + + When enabled, it will move blocked resources instead of copying them. + + + Move blocked resources + + + + + + + + + + + + &Java: + + + javaDirTextBox + + + + + + + &Mods: + + + modsDirTextBox + + + + + + + + + + + + + + + + Browse + + + + + + + Browse + + + + + + + Browse + + + + + + + I&nstances: + + + instDirTextBox + + + + + + + Browse + + + + + + + Browse + + + + + + + + + + Mods + + + + + + Disable using metadata provided by mod providers (like Modrinth or CurseForge) for mods. + + + Disable using metadata for mods + + + + + + + <html><head/><body><p><span style=" font-weight:600; color:#f5c211;">Warning</span><span style=" color:#f5c211;">: Disabling mod metadata may also disable some QoL features, such as mod updating!</span></p></body></html> + + + true + + + + + + + Disable the automatic detection, installation, and updating of mod dependencies. + + + Disable automatic mod dependency management + + + + + + + When creating a new modpack instance, do not suggest updating existing instances instead. + + + Skip modpack update prompt + + + + + + + + + + Miscellaneous + + + + + + 1 + + + + + + + Number of concurrent tasks + + + + + + + 1 + + + + + + + Number of concurrent downloads + + + + + + + Number of manual retries + + + + + + + 0 + + + + + + + Seconds to wait until the requests are terminated + + + Timeout for HTTP requests + + + + + + + s + + + + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + - - - - Folders - - - - - - &Downloads: - - - downloadsDirTextBox - - - - - - - Browse - - - - - - - - - - - - - &Skins: - - - skinsDirTextBox - - - - - - - &Icons: - - - iconsDirTextBox - - - - - - - When enabled, in addition to the downloads folder, its sub folders will also be searched when looking for resources (e.g. when looking for blocked mods on CurseForge). - - - Check downloads folder recursively - - - - - - - - - - &Java: - - - javaDirTextBox - - - - - - - &Mods: - - - modsDirTextBox - - - - - - - - - - - - - - - - Browse - - - - - - - Browse - - - - - - - Browse - - - - - - - I&nstances: - - - instDirTextBox - - - - - - - Browse - - - - - - - Browse - - - - - - - - - - Mods - - - - - - Disable using metadata provided by mod providers (like Modrinth or CurseForge) for mods. - - - Disable using metadata for mods - - - - - - - <html><head/><body><p><span style=" font-weight:600; color:#f5c211;">Warning</span><span style=" color:#f5c211;">: Disabling mod metadata may also disable some QoL features, such as mod updating!</span></p></body></html> - - - true - - - - - - - Disable the automatic detection, installation, and updating of mod dependencies. - - - Disable automatic mod dependency management - - - - - - - When creating a new modpack instance, do not suggest updating existing instances instead. - - - Skip modpack update prompt - - - - - - - - - - Miscellaneous - - - - - - 1 - - - - - - - Number of concurrent tasks - - - - - - - 1 - - - - - - - Number of concurrent downloads - - - - - - - Number of manual retries - - - - - - - 0 - - - - - - - Seconds to wait until the requests are terminated - - - Timeout for HTTP requests - - - - - - - s - - - - - - - - - - Qt::Vertical - - - - 0 - 0 - - - -
@@ -501,36 +537,6 @@ Console - - - - Console Settings - - - - - - Show console while the game is &running - - - - - - - &Automatically close console when the game quits - - - - - - - Show console when the game &crashes - - - - - - @@ -593,7 +599,7 @@ - Qt::ScrollBarAlwaysOff + Qt::ScrollBarAsNeeded false @@ -645,18 +651,33 @@ tabWidget + scrollArea autoUpdateCheckBox + updateIntervalSpinBox instDirTextBox instDirBrowseBtn modsDirTextBox modsDirBrowseBtn iconsDirTextBox iconsDirBrowseBtn + javaDirTextBox + javaDirBrowseBtn + skinsDirTextBox + skinsDirBrowseBtn + downloadsDirTextBox + downloadsDirBrowseBtn + downloadsDirWatchRecursiveCheckBox + metadataDisableBtn + dependenciesDisableBtn + skipModpackUpdatePromptBtn + numberOfConcurrentTasksSpinBox + numberOfConcurrentDownloadsSpinBox + numberOfManualRetriesSpinBox + timeoutSecondsSpinBox sortLastLaunchedBtn sortByNameBtn - showConsoleCheck - autoCloseConsoleCheck - showConsoleErrorCheck + catOpacitySpinBox + preferMenuBarCheckBox lineLimitSpinBox checkStopLogging consoleFont @@ -668,4 +689,4 @@ - + \ No newline at end of file diff --git a/launcher/ui/pages/global/MinecraftPage.cpp b/launcher/ui/pages/global/MinecraftPage.cpp deleted file mode 100644 index 3431dcb9c..000000000 --- a/launcher/ui/pages/global/MinecraftPage.cpp +++ /dev/null @@ -1,185 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (c) 2022 Jamie Mansfield - * - * 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. - */ - -#include "MinecraftPage.h" -#include "BuildConfig.h" -#include "ui_MinecraftPage.h" - -#include -#include -#include - -#include "Application.h" -#include "settings/SettingsObject.h" - -#ifdef Q_OS_LINUX -#include "MangoHud.h" -#endif - -MinecraftPage::MinecraftPage(QWidget* parent) : QWidget(parent), ui(new Ui::MinecraftPage) -{ - ui->setupUi(this); - connect(ui->useNativeGLFWCheck, &QAbstractButton::toggled, this, &MinecraftPage::onUseNativeGLFWChanged); - connect(ui->useNativeOpenALCheck, &QAbstractButton::toggled, this, &MinecraftPage::onUseNativeOpenALChanged); - loadSettings(); - updateCheckboxStuff(); -} - -MinecraftPage::~MinecraftPage() -{ - delete ui; -} - -bool MinecraftPage::apply() -{ - applySettings(); - return true; -} - -void MinecraftPage::updateCheckboxStuff() -{ - ui->windowWidthSpinBox->setEnabled(!ui->maximizedCheckBox->isChecked()); - ui->windowHeightSpinBox->setEnabled(!ui->maximizedCheckBox->isChecked()); -} - -void MinecraftPage::on_maximizedCheckBox_clicked(bool checked) -{ - Q_UNUSED(checked); - updateCheckboxStuff(); -} - -void MinecraftPage::onUseNativeGLFWChanged(bool checked) -{ - ui->lineEditGLFWPath->setEnabled(checked); -} - -void MinecraftPage::onUseNativeOpenALChanged(bool checked) -{ - ui->lineEditOpenALPath->setEnabled(checked); -} - -void MinecraftPage::applySettings() -{ - auto s = APPLICATION->settings(); - - // Window Size - s->set("LaunchMaximized", ui->maximizedCheckBox->isChecked()); - s->set("MinecraftWinWidth", ui->windowWidthSpinBox->value()); - s->set("MinecraftWinHeight", ui->windowHeightSpinBox->value()); - - // Native library workarounds - s->set("UseNativeGLFW", ui->useNativeGLFWCheck->isChecked()); - s->set("CustomGLFWPath", ui->lineEditGLFWPath->text()); - s->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked()); - s->set("CustomOpenALPath", ui->lineEditOpenALPath->text()); - - // Peformance related options - 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()); - s->set("ShowGlobalGameTime", ui->showGlobalGameTime->isChecked()); - s->set("RecordGameTime", ui->recordGameTime->isChecked()); - s->set("ShowGameTimeWithoutDays", ui->showGameTimeWithoutDays->isChecked()); - - // Miscellaneous - s->set("CloseAfterLaunch", ui->closeAfterLaunchCheck->isChecked()); - s->set("QuitAfterGameStop", ui->quitAfterGameStopCheck->isChecked()); - - // Legacy settings - s->set("OnlineFixes", ui->onlineFixes->isChecked()); -} - -void MinecraftPage::loadSettings() -{ - auto s = APPLICATION->settings(); - - // Window Size - ui->maximizedCheckBox->setChecked(s->get("LaunchMaximized").toBool()); - ui->windowWidthSpinBox->setValue(s->get("MinecraftWinWidth").toInt()); - ui->windowHeightSpinBox->setValue(s->get("MinecraftWinHeight").toInt()); - - ui->useNativeGLFWCheck->setChecked(s->get("UseNativeGLFW").toBool()); - ui->lineEditGLFWPath->setText(s->get("CustomGLFWPath").toString()); - ui->lineEditGLFWPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.GLFW_LIBRARY_NAME)); -#ifdef Q_OS_LINUX - if (!APPLICATION->m_detectedGLFWPath.isEmpty()) - ui->lineEditGLFWPath->setPlaceholderText(tr("Auto detected path: %1").arg(APPLICATION->m_detectedGLFWPath)); -#endif - ui->useNativeOpenALCheck->setChecked(s->get("UseNativeOpenAL").toBool()); - ui->lineEditOpenALPath->setText(s->get("CustomOpenALPath").toString()); - ui->lineEditOpenALPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.OPENAL_LIBRARY_NAME)); -#ifdef Q_OS_LINUX - if (!APPLICATION->m_detectedOpenALPath.isEmpty()) - ui->lineEditOpenALPath->setPlaceholderText(tr("Auto detected path: %1").arg(APPLICATION->m_detectedOpenALPath)); -#endif - - 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); -#endif - - if (!(APPLICATION->capabilities() & Application::SupportsGameMode)) { - ui->enableFeralGamemodeCheck->setDisabled(true); - ui->enableFeralGamemodeCheck->setToolTip(tr("Feral Interactive's GameMode could not be found on your system.")); - } - - if (!(APPLICATION->capabilities() & Application::SupportsMangoHud)) { - ui->enableMangoHud->setDisabled(true); - ui->enableMangoHud->setToolTip(tr("MangoHud could not be found on your system.")); - } - - ui->showGameTime->setChecked(s->get("ShowGameTime").toBool()); - ui->showGlobalGameTime->setChecked(s->get("ShowGlobalGameTime").toBool()); - ui->recordGameTime->setChecked(s->get("RecordGameTime").toBool()); - ui->showGameTimeWithoutDays->setChecked(s->get("ShowGameTimeWithoutDays").toBool()); - - ui->closeAfterLaunchCheck->setChecked(s->get("CloseAfterLaunch").toBool()); - ui->quitAfterGameStopCheck->setChecked(s->get("QuitAfterGameStop").toBool()); - - ui->onlineFixes->setChecked(s->get("OnlineFixes").toBool()); -} - -void MinecraftPage::retranslate() -{ - ui->retranslateUi(this); -} diff --git a/launcher/ui/pages/global/MinecraftPage.h b/launcher/ui/pages/global/MinecraftPage.h index 5facfbb3f..b21862536 100644 --- a/launcher/ui/pages/global/MinecraftPage.h +++ b/launcher/ui/pages/global/MinecraftPage.h @@ -38,41 +38,27 @@ #include #include -#include +#include "Application.h" #include "java/JavaChecker.h" #include "ui/pages/BasePage.h" +#include "ui/widgets/MinecraftSettingsWidget.h" class SettingsObject; -namespace Ui { -class MinecraftPage; -} - -class MinecraftPage : public QWidget, public BasePage { +class MinecraftPage : public MinecraftSettingsWidget, public BasePage { Q_OBJECT public: - explicit MinecraftPage(QWidget* parent = 0); - ~MinecraftPage(); + explicit MinecraftPage(QWidget* parent = nullptr) : MinecraftSettingsWidget(nullptr, parent) {} + ~MinecraftPage() override {} QString displayName() const override { return tr("Minecraft"); } QIcon icon() const override { return APPLICATION->getThemedIcon("minecraft"); } QString id() const override { return "minecraft-settings"; } QString helpPage() const override { return "Minecraft-settings"; } - bool apply() override; - void retranslate() override; - - private: - void updateCheckboxStuff(); - void applySettings(); - void loadSettings(); - - private slots: - void on_maximizedCheckBox_clicked(bool checked); - - void onUseNativeGLFWChanged(bool checked); - void onUseNativeOpenALChanged(bool checked); - - private: - Ui::MinecraftPage* ui; + bool apply() override + { + saveSettings(); + return true; + } }; diff --git a/launcher/ui/pages/global/MinecraftPage.ui b/launcher/ui/pages/global/MinecraftPage.ui deleted file mode 100644 index 7d2741250..000000000 --- a/launcher/ui/pages/global/MinecraftPage.ui +++ /dev/null @@ -1,351 +0,0 @@ - - - MinecraftPage - - - - 0 - 0 - 936 - 541 - - - - - 0 - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QTabWidget::Rounded - - - 0 - - - - General - - - - - - Window Size - - - - - - Start Minecraft &maximized - - - - - - - - - Window &height: - - - windowHeightSpinBox - - - - - - - Window &width: - - - windowWidthSpinBox - - - - - - - 1 - - - 65536 - - - 1 - - - 854 - - - - - - - 1 - - - 65536 - - - 480 - - - - - - - - - - - - Game time - - - - - - Show time spent &playing instances - - - - - - - Show time spent playing across &all instances - - - - - - - &Record time spent playing instances - - - - - - - Show time spent playing in hours - - - - - - - - - - Miscellaneous - - - - - - <html><head/><body><p>The launcher will automatically reopen when the game crashes or exits.</p></body></html> - - - &Close the launcher after game window opens - - - - - - - <html><head/><body><p>The launcher will automatically quit after the game exits or crashes.</p></body></html> - - - &Quit the launcher after game window closes - - - - - - - - - - Qt::Vertical - - - - 0 - 0 - - - - - - - - - Tweaks - - - - - - Legacy settings - - - - - - <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) - - - - - - - - - - Native library workarounds - - - - - - Use system installation of &GLFW - - - - - - - &GLFW library path - - - lineEditGLFWPath - - - - - - - Use system installation of &OpenAL - - - - - - - &OpenAL library path - - - lineEditOpenALPath - - - - - - - false - - - - - - - false - - - - - - - - - - Performance - - - - - - <html><head/><body><p>Enable Feral Interactive's GameMode, to potentially improve gaming performance.</p></body></html> - - - Enable Feral GameMode - - - - - - - <html><head/><body><p>Enable MangoHud's advanced performance overlay.</p></body></html> - - - Enable MangoHud - - - - - - - <html><head/><body><p>Use the discrete GPU instead of the primary GPU.</p></body></html> - - - Use discrete GPU - - - - - - - <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 - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - maximizedCheckBox - windowWidthSpinBox - windowHeightSpinBox - - - - diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp index 8f8dab46d..50217f982 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp +++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp @@ -74,6 +74,7 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared connect(ui->actionRemoveItem, &QAction::triggered, this, &ExternalResourcesPage::removeItem); connect(ui->actionEnableItem, &QAction::triggered, this, &ExternalResourcesPage::enableItem); connect(ui->actionDisableItem, &QAction::triggered, this, &ExternalResourcesPage::disableItem); + connect(ui->actionViewHomepage, &QAction::triggered, this, &ExternalResourcesPage::viewHomepage); connect(ui->actionViewConfigs, &QAction::triggered, this, &ExternalResourcesPage::viewConfigs); connect(ui->actionViewFolder, &QAction::triggered, this, &ExternalResourcesPage::viewFolder); @@ -81,16 +82,28 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared connect(ui->treeView, &ModListView::activated, this, &ExternalResourcesPage::itemActivated); auto selection_model = ui->treeView->selectionModel(); - connect(selection_model, &QItemSelectionModel::currentChanged, this, &ExternalResourcesPage::current); + + connect(selection_model, &QItemSelectionModel::currentChanged, this, [this](const QModelIndex& current, const QModelIndex& previous) { + if (!current.isValid()) { + ui->frame->clear(); + return; + } + + updateFrame(current, previous); + }); + auto updateExtra = [this]() { if (updateExtraInfo) updateExtraInfo(id(), extraHeaderInfoString()); }; + connect(selection_model, &QItemSelectionModel::selectionChanged, this, updateExtra); connect(model.get(), &ResourceFolderModel::updateFinished, this, updateExtra); connect(model.get(), &ResourceFolderModel::parseFinished, this, updateExtra); - connect(ui->filterEdit, &QLineEdit::textChanged, this, &ExternalResourcesPage::filterTextChanged); + connect(selection_model, &QItemSelectionModel::selectionChanged, this, [this] { updateActions(); }); + connect(m_model.get(), &ResourceFolderModel::rowsInserted, this, [this] { updateActions(); }); + connect(m_model.get(), &ResourceFolderModel::rowsRemoved, this, [this] { updateActions(); }); auto viewHeader = ui->treeView->header(); viewHeader->setContextMenuPolicy(Qt::CustomContextMenu); @@ -99,6 +112,7 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared m_model->loadColumns(ui->treeView); connect(ui->treeView->header(), &QHeaderView::sectionResized, this, [this] { m_model->saveColumns(ui->treeView); }); + connect(ui->filterEdit, &QLineEdit::textChanged, this, &ExternalResourcesPage::filterTextChanged); } ExternalResourcesPage::~ExternalResourcesPage() @@ -289,6 +303,16 @@ void ExternalResourcesPage::disableItem() m_model->setResourceEnabled(selection.indexes(), EnableAction::DISABLE); } +void ExternalResourcesPage::viewHomepage() +{ + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); + for (auto resource : m_model->selectedResources(selection)) { + auto url = resource->homepage(); + if (!url.isEmpty()) + DesktopServices::openUrl(url); + } +} + void ExternalResourcesPage::viewConfigs() { DesktopServices::openPath(m_instance->instanceConfigFolder(), true); @@ -299,23 +323,32 @@ void ExternalResourcesPage::viewFolder() DesktopServices::openPath(m_model->dir().absolutePath(), true); } -bool ExternalResourcesPage::current(const QModelIndex& current, const QModelIndex& previous) +void ExternalResourcesPage::updateActions() { - if (!current.isValid()) { - ui->frame->clear(); - return false; - } + const bool hasSelection = ui->treeView->selectionModel()->hasSelection(); + const QModelIndexList selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); + const QList selectedResources = m_model->selectedResources(selection); - return onSelectionChanged(current, previous); + ui->actionUpdateItem->setEnabled(!m_model->empty()); + ui->actionResetItemMetadata->setEnabled(hasSelection); + + ui->actionChangeVersion->setEnabled(selectedResources.size() == 1 && selectedResources[0]->metadata() != nullptr); + + ui->actionRemoveItem->setEnabled(hasSelection); + ui->actionEnableItem->setEnabled(hasSelection); + ui->actionDisableItem->setEnabled(hasSelection); + + ui->actionViewHomepage->setEnabled(hasSelection && std::any_of(selectedResources.begin(), selectedResources.end(), + [](Resource* resource) { return !resource->homepage().isEmpty(); })); + ui->actionExportMetadata->setEnabled(!m_model->empty()); } -bool ExternalResourcesPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous) +void ExternalResourcesPage::updateFrame(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous) { auto sourceCurrent = m_filterModel->mapToSource(current); int row = sourceCurrent.row(); Resource const& resource = m_model->at(row); ui->frame->updateWithResource(resource); - return true; } QString ExternalResourcesPage::extraHeaderInfoString() diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.h b/launcher/ui/pages/instance/ExternalResourcesPage.h index d29be0fc3..00bb5d17d 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.h +++ b/launcher/ui/pages/instance/ExternalResourcesPage.h @@ -42,9 +42,8 @@ class ExternalResourcesPage : public QMainWindow, public BasePage { QMenu* createPopupMenu() override; public slots: - bool current(const QModelIndex& current, const QModelIndex& previous); - - virtual bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous); + virtual void updateActions(); + virtual void updateFrame(const QModelIndex& current, const QModelIndex& previous); protected slots: void itemActivated(const QModelIndex& index); @@ -57,6 +56,8 @@ class ExternalResourcesPage : public QMainWindow, public BasePage { virtual void enableItem(); virtual void disableItem(); + virtual void viewHomepage(); + virtual void viewFolder(); virtual void viewConfigs(); diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.ui b/launcher/ui/pages/instance/ExternalResourcesPage.ui index 9d6f61db0..5df8aafa2 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.ui +++ b/launcher/ui/pages/instance/ExternalResourcesPage.ui @@ -74,7 +74,7 @@ Actions - Qt::ToolButtonTextOnly + Qt::ToolButtonIconOnly true @@ -90,39 +90,50 @@ + + - &Add + &Add File - Add + Add a locally downloaded file. + + false + &Remove - Remove selected item + Remove all selected items. + + false + &Enable - Enable selected item + Enable all selected items. + + false + &Disable - Disable selected item + Disable all selected items. @@ -137,6 +148,9 @@ View &Folder + + Open the folder in the system file manager. + @@ -146,40 +160,70 @@ &Download - Download a new resource - - - - - false - - - Visit mod's page - - - Go to mods home page + Download resources from online mod platforms. - true + false Check for &Updates - Try to check or update all selected resources (all resources if none are selected) + Try to check or update all selected resources (all resources if none are selected). + + + + + Reset Update Metadata + + + QAction::NoRole + + + + + Verify Dependencies + + + QAction::NoRole - true + false - Export modlist + Export List - Export mod's metadata to text + Export resource's metadata to text. + + + + + false + + + Change Version + + + Change a resource's version. + + + QAction::NoRole + + + + + false + + + View Homepage + + + View the homepages of all selected items. diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp deleted file mode 100644 index cf8d86cd4..000000000 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ /dev/null @@ -1,610 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (c) 2022 Jamie Mansfield - * Copyright (C) 2022 Sefa Eyeoglu - * Copyright (C) 2022 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. - */ - -#include "InstanceSettingsPage.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/WorldList.h" -#include "settings/Setting.h" -#include "ui/dialogs/CustomMessageBox.h" -#include "ui/java/InstallJavaDialog.h" -#include "ui_InstanceSettingsPage.h" - -#include -#include -#include - -#include - -#include "ui/dialogs/VersionSelectDialog.h" -#include "ui/widgets/CustomCommands.h" - -#include "Application.h" -#include "BuildConfig.h" -#include "JavaCommon.h" -#include "minecraft/auth/AccountList.h" - -#include "FileSystem.h" -#include "java/JavaInstallList.h" -#include "java/JavaUtils.h" - -InstanceSettingsPage::InstanceSettingsPage(BaseInstance* inst, QWidget* parent) - : QWidget(parent), ui(new Ui::InstanceSettingsPage), m_instance(inst) -{ - m_settings = inst->settings(); - ui->setupUi(this); - - ui->javaDownloadBtn->setHidden(!BuildConfig.JAVA_DOWNLOADER_ENABLED); - - connect(ui->openGlobalJavaSettingsButton, &QCommandLinkButton::clicked, this, &InstanceSettingsPage::globalSettingsButtonClicked); - connect(APPLICATION, &Application::globalSettingsAboutToOpen, this, &InstanceSettingsPage::applySettings); - connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceSettingsPage::loadSettings); - connect(ui->instanceAccountSelector, QOverload::of(&QComboBox::currentIndexChanged), this, - &InstanceSettingsPage::changeInstanceAccount); - - connect(ui->useNativeGLFWCheck, &QAbstractButton::toggled, this, &InstanceSettingsPage::onUseNativeGLFWChanged); - connect(ui->useNativeOpenALCheck, &QAbstractButton::toggled, this, &InstanceSettingsPage::onUseNativeOpenALChanged); - - auto mInst = dynamic_cast(inst); - m_world_quickplay_supported = mInst && mInst->traits().contains("feature:is_quick_play_singleplayer"); - if (m_world_quickplay_supported) { - auto worlds = mInst->worldList(); - worlds->update(); - for (const auto& world : worlds->allWorlds()) { - ui->worldsCb->addItem(world.folderName()); - } - } else { - ui->worldsCb->hide(); - ui->worldJoinButton->hide(); - ui->serverJoinAddressButton->setChecked(true); - ui->serverJoinAddress->setEnabled(true); - ui->serverJoinAddressButton->setStyleSheet("QRadioButton::indicator { width: 0px; height: 0px; }"); - } - - loadSettings(); - - updateThresholds(); -} - -InstanceSettingsPage::~InstanceSettingsPage() -{ - delete ui; -} - -void InstanceSettingsPage::globalSettingsButtonClicked(bool) -{ - switch (ui->settingsTabs->currentIndex()) { - case 0: - APPLICATION->ShowGlobalSettings(this, "java-settings"); - return; - case 2: - APPLICATION->ShowGlobalSettings(this, "custom-commands"); - return; - case 3: - APPLICATION->ShowGlobalSettings(this, "environment-variables"); - return; - default: - APPLICATION->ShowGlobalSettings(this, "minecraft-settings"); - return; - } -} - -bool InstanceSettingsPage::apply() -{ - applySettings(); - return true; -} - -void InstanceSettingsPage::applySettings() -{ - SettingsObject::Lock lock(m_settings); - - // Miscellaneous - bool miscellaneous = ui->miscellaneousSettingsBox->isChecked(); - m_settings->set("OverrideMiscellaneous", miscellaneous); - if (miscellaneous) { - m_settings->set("CloseAfterLaunch", ui->closeAfterLaunchCheck->isChecked()); - m_settings->set("QuitAfterGameStop", ui->quitAfterGameStopCheck->isChecked()); - } else { - m_settings->reset("CloseAfterLaunch"); - m_settings->reset("QuitAfterGameStop"); - } - - // Console - bool console = ui->consoleSettingsBox->isChecked(); - m_settings->set("OverrideConsole", console); - if (console) { - m_settings->set("ShowConsole", ui->showConsoleCheck->isChecked()); - m_settings->set("AutoCloseConsole", ui->autoCloseConsoleCheck->isChecked()); - m_settings->set("ShowConsoleOnError", ui->showConsoleErrorCheck->isChecked()); - } else { - m_settings->reset("ShowConsole"); - m_settings->reset("AutoCloseConsole"); - m_settings->reset("ShowConsoleOnError"); - } - - // Window Size - bool window = ui->windowSizeGroupBox->isChecked(); - m_settings->set("OverrideWindow", window); - if (window) { - m_settings->set("LaunchMaximized", ui->maximizedCheckBox->isChecked()); - m_settings->set("MinecraftWinWidth", ui->windowWidthSpinBox->value()); - m_settings->set("MinecraftWinHeight", ui->windowHeightSpinBox->value()); - } else { - m_settings->reset("LaunchMaximized"); - m_settings->reset("MinecraftWinWidth"); - m_settings->reset("MinecraftWinHeight"); - } - - // Memory - bool memory = ui->memoryGroupBox->isChecked(); - m_settings->set("OverrideMemory", memory); - if (memory) { - int min = ui->minMemSpinBox->value(); - int max = ui->maxMemSpinBox->value(); - if (min < max) { - m_settings->set("MinMemAlloc", min); - m_settings->set("MaxMemAlloc", max); - } else { - m_settings->set("MinMemAlloc", max); - m_settings->set("MaxMemAlloc", min); - } - m_settings->set("PermGen", ui->permGenSpinBox->value()); - } else { - m_settings->reset("MinMemAlloc"); - m_settings->reset("MaxMemAlloc"); - m_settings->reset("PermGen"); - } - - // Java Install Settings - bool javaInstall = ui->javaSettingsGroupBox->isChecked(); - m_settings->set("OverrideJavaLocation", javaInstall); - if (javaInstall) { - m_settings->set("JavaPath", ui->javaPathTextBox->text()); - m_settings->set("IgnoreJavaCompatibility", ui->skipCompatibilityCheckbox->isChecked()); - } else { - m_settings->reset("JavaPath"); - m_settings->reset("IgnoreJavaCompatibility"); - } - - // Java arguments - bool javaArgs = ui->javaArgumentsGroupBox->isChecked(); - m_settings->set("OverrideJavaArgs", javaArgs); - if (javaArgs) { - m_settings->set("JvmArgs", ui->jvmArgsTextBox->toPlainText().replace("\n", " ")); - } else { - m_settings->reset("JvmArgs"); - } - - // Custom Commands - bool custcmd = ui->customCommands->checked(); - m_settings->set("OverrideCommands", custcmd); - if (custcmd) { - m_settings->set("PreLaunchCommand", ui->customCommands->prelaunchCommand()); - m_settings->set("WrapperCommand", ui->customCommands->wrapperCommand()); - m_settings->set("PostExitCommand", ui->customCommands->postexitCommand()); - } else { - m_settings->reset("PreLaunchCommand"); - m_settings->reset("WrapperCommand"); - m_settings->reset("PostExitCommand"); - } - - // Environment Variables - auto env = ui->environmentVariables->override(); - m_settings->set("OverrideEnv", env); - if (env) - m_settings->set("Env", ui->environmentVariables->value()); - else - m_settings->reset("Env"); - - // Workarounds - bool workarounds = ui->nativeWorkaroundsGroupBox->isChecked(); - m_settings->set("OverrideNativeWorkarounds", workarounds); - if (workarounds) { - m_settings->set("UseNativeGLFW", ui->useNativeGLFWCheck->isChecked()); - m_settings->set("CustomGLFWPath", ui->lineEditGLFWPath->text()); - m_settings->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked()); - m_settings->set("CustomOpenALPath", ui->lineEditOpenALPath->text()); - } else { - m_settings->reset("UseNativeGLFW"); - m_settings->reset("CustomGLFWPath"); - m_settings->reset("UseNativeOpenAL"); - m_settings->reset("CustomOpenALPath"); - } - - // Performance - bool performance = ui->perfomanceGroupBox->isChecked(); - m_settings->set("OverridePerformance", performance); - if (performance) { - 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 - bool gameTime = ui->gameTimeGroupBox->isChecked(); - m_settings->set("OverrideGameTime", gameTime); - if (gameTime) { - m_settings->set("ShowGameTime", ui->showGameTime->isChecked()); - m_settings->set("RecordGameTime", ui->recordGameTime->isChecked()); - } else { - m_settings->reset("ShowGameTime"); - m_settings->reset("RecordGameTime"); - } - - // Join server on launch - bool joinServerOnLaunch = ui->serverJoinGroupBox->isChecked(); - m_settings->set("JoinServerOnLaunch", joinServerOnLaunch); - if (joinServerOnLaunch) { - if (ui->serverJoinAddressButton->isChecked() || !m_world_quickplay_supported) { - m_settings->set("JoinServerOnLaunchAddress", ui->serverJoinAddress->text()); - m_settings->reset("JoinWorldOnLaunch"); - } else { - m_settings->set("JoinWorldOnLaunch", ui->worldsCb->currentText()); - m_settings->reset("JoinServerOnLaunchAddress"); - } - } else { - m_settings->reset("JoinServerOnLaunchAddress"); - m_settings->reset("JoinWorldOnLaunch"); - } - - // Use an account for this instance - bool useAccountForInstance = ui->instanceAccountGroupBox->isChecked(); - m_settings->set("UseAccountForInstance", useAccountForInstance); - if (!useAccountForInstance) { - m_settings->reset("InstanceAccountId"); - } - - bool overrideLegacySettings = ui->legacySettingsGroupBox->isChecked(); - m_settings->set("OverrideLegacySettings", overrideLegacySettings); - if (overrideLegacySettings) { - m_settings->set("OnlineFixes", ui->onlineFixes->isChecked()); - } else { - m_settings->reset("OnlineFixes"); - } - - // FIXME: This should probably be called by a signal instead - m_instance->updateRuntimeContext(); -} - -void InstanceSettingsPage::loadSettings() -{ - // Miscellaneous - ui->miscellaneousSettingsBox->setChecked(m_settings->get("OverrideMiscellaneous").toBool()); - ui->closeAfterLaunchCheck->setChecked(m_settings->get("CloseAfterLaunch").toBool()); - ui->quitAfterGameStopCheck->setChecked(m_settings->get("QuitAfterGameStop").toBool()); - - // Console - ui->consoleSettingsBox->setChecked(m_settings->get("OverrideConsole").toBool()); - ui->showConsoleCheck->setChecked(m_settings->get("ShowConsole").toBool()); - ui->autoCloseConsoleCheck->setChecked(m_settings->get("AutoCloseConsole").toBool()); - ui->showConsoleErrorCheck->setChecked(m_settings->get("ShowConsoleOnError").toBool()); - - // Window Size - ui->windowSizeGroupBox->setChecked(m_settings->get("OverrideWindow").toBool()); - ui->maximizedCheckBox->setChecked(m_settings->get("LaunchMaximized").toBool()); - ui->windowWidthSpinBox->setValue(m_settings->get("MinecraftWinWidth").toInt()); - ui->windowHeightSpinBox->setValue(m_settings->get("MinecraftWinHeight").toInt()); - - // Memory - ui->memoryGroupBox->setChecked(m_settings->get("OverrideMemory").toBool()); - int min = m_settings->get("MinMemAlloc").toInt(); - int max = m_settings->get("MaxMemAlloc").toInt(); - if (min < max) { - ui->minMemSpinBox->setValue(min); - ui->maxMemSpinBox->setValue(max); - } else { - ui->minMemSpinBox->setValue(max); - ui->maxMemSpinBox->setValue(min); - } - ui->permGenSpinBox->setValue(m_settings->get("PermGen").toInt()); - bool permGenVisible = m_settings->get("PermGenVisible").toBool(); - ui->permGenSpinBox->setVisible(permGenVisible); - ui->labelPermGen->setVisible(permGenVisible); - ui->labelPermgenNote->setVisible(permGenVisible); - - // Java Settings - bool overrideLocation = m_settings->get("OverrideJavaLocation").toBool(); - bool overrideArgs = m_settings->get("OverrideJavaArgs").toBool(); - - connect(m_settings->getSetting("OverrideJavaLocation").get(), &Setting::SettingChanged, ui->javaSettingsGroupBox, - [this] { ui->javaSettingsGroupBox->setChecked(m_settings->get("OverrideJavaLocation").toBool()); }); - ui->javaSettingsGroupBox->setChecked(overrideLocation); - ui->javaPathTextBox->setText(m_settings->get("JavaPath").toString()); - connect(m_settings->getSetting("JavaPath").get(), &Setting::SettingChanged, ui->javaSettingsGroupBox, - [this] { ui->javaPathTextBox->setText(m_settings->get("JavaPath").toString()); }); - - ui->skipCompatibilityCheckbox->setChecked(m_settings->get("IgnoreJavaCompatibility").toBool()); - - ui->javaArgumentsGroupBox->setChecked(overrideArgs); - ui->jvmArgsTextBox->setPlainText(m_settings->get("JvmArgs").toString()); - - // Custom commands - ui->customCommands->initialize(true, m_settings->get("OverrideCommands").toBool(), m_settings->get("PreLaunchCommand").toString(), - m_settings->get("WrapperCommand").toString(), m_settings->get("PostExitCommand").toString()); - - // Environment variables - ui->environmentVariables->initialize(true, m_settings->get("OverrideEnv").toBool(), m_settings->get("Env").toMap()); - - // Workarounds - ui->nativeWorkaroundsGroupBox->setChecked(m_settings->get("OverrideNativeWorkarounds").toBool()); - ui->useNativeGLFWCheck->setChecked(m_settings->get("UseNativeGLFW").toBool()); - ui->lineEditGLFWPath->setText(m_settings->get("CustomGLFWPath").toString()); -#ifdef Q_OS_LINUX - ui->lineEditGLFWPath->setPlaceholderText(APPLICATION->m_detectedGLFWPath); -#else - ui->lineEditGLFWPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.GLFW_LIBRARY_NAME)); -#endif - ui->useNativeOpenALCheck->setChecked(m_settings->get("UseNativeOpenAL").toBool()); - ui->lineEditOpenALPath->setText(m_settings->get("CustomOpenALPath").toString()); -#ifdef Q_OS_LINUX - ui->lineEditOpenALPath->setPlaceholderText(APPLICATION->m_detectedOpenALPath); -#else - ui->lineEditOpenALPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.OPENAL_LIBRARY_NAME)); -#endif - - // Performance - ui->perfomanceGroupBox->setChecked(m_settings->get("OverridePerformance").toBool()); - 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); -#endif - - if (!(APPLICATION->capabilities() & Application::SupportsGameMode)) { - ui->enableFeralGamemodeCheck->setDisabled(true); - ui->enableFeralGamemodeCheck->setToolTip(tr("Feral Interactive's GameMode could not be found on your system.")); - } - - if (!(APPLICATION->capabilities() & Application::SupportsMangoHud)) { - ui->enableMangoHud->setDisabled(true); - ui->enableMangoHud->setToolTip(tr("MangoHud could not be found on your system.")); - } - - // Miscellanous - ui->gameTimeGroupBox->setChecked(m_settings->get("OverrideGameTime").toBool()); - ui->showGameTime->setChecked(m_settings->get("ShowGameTime").toBool()); - ui->recordGameTime->setChecked(m_settings->get("RecordGameTime").toBool()); - - ui->serverJoinGroupBox->setChecked(m_settings->get("JoinServerOnLaunch").toBool()); - - if (auto server = m_settings->get("JoinServerOnLaunchAddress").toString(); !server.isEmpty()) { - ui->serverJoinAddress->setText(server); - ui->serverJoinAddressButton->setChecked(true); - ui->worldJoinButton->setChecked(false); - ui->serverJoinAddress->setEnabled(true); - ui->worldsCb->setEnabled(false); - } else if (auto world = m_settings->get("JoinWorldOnLaunch").toString(); !world.isEmpty() && m_world_quickplay_supported) { - ui->worldsCb->setCurrentText(world); - ui->serverJoinAddressButton->setChecked(false); - ui->worldJoinButton->setChecked(true); - ui->serverJoinAddress->setEnabled(false); - ui->worldsCb->setEnabled(true); - } else { - ui->serverJoinAddressButton->setChecked(true); - ui->worldJoinButton->setChecked(false); - ui->serverJoinAddress->setEnabled(true); - ui->worldsCb->setEnabled(false); - } - - ui->instanceAccountGroupBox->setChecked(m_settings->get("UseAccountForInstance").toBool()); - updateAccountsMenu(); - - ui->legacySettingsGroupBox->setChecked(m_settings->get("OverrideLegacySettings").toBool()); - ui->onlineFixes->setChecked(m_settings->get("OnlineFixes").toBool()); -} - -void InstanceSettingsPage::on_javaDownloadBtn_clicked() -{ - auto jdialog = new Java::InstallDialog({}, m_instance, this); - jdialog->exec(); -} - -void InstanceSettingsPage::on_javaDetectBtn_clicked() -{ - if (JavaUtils::getJavaCheckPath().isEmpty()) { - JavaCommon::javaCheckNotFound(this); - return; - } - - JavaInstallPtr java; - - VersionSelectDialog vselect(APPLICATION->javalist().get(), tr("Select a Java version"), this, true); - vselect.setResizeOn(2); - vselect.exec(); - - if (vselect.result() == QDialog::Accepted && vselect.selectedVersion()) { - java = std::dynamic_pointer_cast(vselect.selectedVersion()); - ui->javaPathTextBox->setText(java->path); - bool visible = java->id.requiresPermGen() && m_settings->get("OverrideMemory").toBool(); - ui->permGenSpinBox->setVisible(visible); - ui->labelPermGen->setVisible(visible); - ui->labelPermgenNote->setVisible(visible); - m_settings->set("PermGenVisible", visible); - - if (!java->is_64bit && m_settings->get("MaxMemAlloc").toInt() > 2048) { - CustomMessageBox::selectable(this, tr("Confirm Selection"), - tr("You selected a 32-bit version of Java.\n" - "This installation does not support more than 2048MiB of RAM.\n" - "Please make sure that the maximum memory value is lower."), - QMessageBox::Warning, QMessageBox::Ok, QMessageBox::Ok) - ->exec(); - } - } -} - -void InstanceSettingsPage::on_javaBrowseBtn_clicked() -{ - QString raw_path = QFileDialog::getOpenFileName(this, tr("Find Java executable")); - - // do not allow current dir - it's dirty. Do not allow dirs that don't exist - if (raw_path.isEmpty()) { - return; - } - QString cooked_path = FS::NormalizePath(raw_path); - - QFileInfo javaInfo(cooked_path); - if (!javaInfo.exists() || !javaInfo.isExecutable()) { - return; - } - ui->javaPathTextBox->setText(cooked_path); - - // custom Java could be anything... enable perm gen option - ui->permGenSpinBox->setVisible(true); - ui->labelPermGen->setVisible(true); - ui->labelPermgenNote->setVisible(true); - m_settings->set("PermGenVisible", true); -} - -void InstanceSettingsPage::on_javaTestBtn_clicked() -{ - if (checker) { - return; - } - checker.reset(new JavaCommon::TestCheck(this, ui->javaPathTextBox->text(), ui->jvmArgsTextBox->toPlainText().replace("\n", " "), - ui->minMemSpinBox->value(), ui->maxMemSpinBox->value(), ui->permGenSpinBox->value())); - connect(checker.get(), SIGNAL(finished()), SLOT(checkerFinished())); - checker->run(); -} - -void InstanceSettingsPage::onUseNativeGLFWChanged(bool checked) -{ - ui->lineEditGLFWPath->setEnabled(checked); -} - -void InstanceSettingsPage::onUseNativeOpenALChanged(bool checked) -{ - ui->lineEditOpenALPath->setEnabled(checked); -} - -void InstanceSettingsPage::updateAccountsMenu() -{ - ui->instanceAccountSelector->clear(); - auto accounts = APPLICATION->accounts(); - int accountIndex = accounts->findAccountByProfileId(m_settings->get("InstanceAccountId").toString()); - - for (int i = 0; i < accounts->count(); i++) { - MinecraftAccountPtr account = accounts->at(i); - ui->instanceAccountSelector->addItem(getFaceForAccount(account), account->profileName(), i); - if (i == accountIndex) - ui->instanceAccountSelector->setCurrentIndex(i); - } -} - -QIcon InstanceSettingsPage::getFaceForAccount(MinecraftAccountPtr account) -{ - if (auto face = account->getFace(); !face.isNull()) { - return face; - } - - return APPLICATION->getThemedIcon("noaccount"); -} - -void InstanceSettingsPage::changeInstanceAccount(int index) -{ - auto accounts = APPLICATION->accounts(); - if (index != -1 && accounts->at(index) && ui->instanceAccountGroupBox->isChecked()) { - auto account = accounts->at(index); - m_settings->set("InstanceAccountId", account->profileId()); - } -} - -void InstanceSettingsPage::on_maxMemSpinBox_valueChanged([[maybe_unused]] int i) -{ - updateThresholds(); -} - -void InstanceSettingsPage::checkerFinished() -{ - checker.reset(); -} - -void InstanceSettingsPage::retranslate() -{ - ui->retranslateUi(this); - ui->customCommands->retranslate(); // TODO: why is this seperate from the others? - ui->environmentVariables->retranslate(); -} - -void InstanceSettingsPage::updateThresholds() -{ - auto sysMiB = Sys::getSystemRam() / Sys::mebibyte; - unsigned int maxMem = ui->maxMemSpinBox->value(); - unsigned int minMem = ui->minMemSpinBox->value(); - - QString iconName; - - if (maxMem >= sysMiB) { - iconName = "status-bad"; - ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation exceeds your system memory capacity.")); - } else if (maxMem > (sysMiB * 0.9)) { - iconName = "status-yellow"; - ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity.")); - } else if (maxMem < minMem) { - iconName = "status-yellow"; - ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value")); - } else { - iconName = "status-good"; - ui->labelMaxMemIcon->setToolTip(""); - } - - { - auto height = ui->labelMaxMemIcon->fontInfo().pixelSize(); - QIcon icon = APPLICATION->getThemedIcon(iconName); - QPixmap pix = icon.pixmap(height, height); - ui->labelMaxMemIcon->setPixmap(pix); - } -} - -void InstanceSettingsPage::on_serverJoinAddressButton_toggled(bool checked) -{ - ui->serverJoinAddress->setEnabled(checked); -} - -void InstanceSettingsPage::on_worldJoinButton_toggled(bool checked) -{ - ui->worldsCb->setEnabled(checked); -} diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.h b/launcher/ui/pages/instance/InstanceSettingsPage.h index 6499f9e8f..fa1dce3dc 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.h +++ b/launcher/ui/pages/instance/InstanceSettingsPage.h @@ -35,63 +35,29 @@ #pragma once -#include - -#include -#include #include "Application.h" #include "BaseInstance.h" -#include "JavaCommon.h" -#include "java/JavaChecker.h" #include "ui/pages/BasePage.h" +#include "ui/widgets/MinecraftSettingsWidget.h" +#include -class JavaChecker; -namespace Ui { -class InstanceSettingsPage; -} - -class InstanceSettingsPage : public QWidget, public BasePage { +class InstanceSettingsPage : public MinecraftSettingsWidget, public BasePage { Q_OBJECT public: - explicit InstanceSettingsPage(BaseInstance* inst, QWidget* parent = 0); - virtual ~InstanceSettingsPage(); - virtual QString displayName() const override { return tr("Settings"); } - virtual QIcon icon() const override { return APPLICATION->getThemedIcon("instance-settings"); } - virtual QString id() const override { return "settings"; } - virtual bool apply() override; - virtual QString helpPage() const override { return "Instance-settings"; } - void retranslate() override; - - void updateThresholds(); - - private slots: - void on_javaDetectBtn_clicked(); - void on_javaTestBtn_clicked(); - void on_javaBrowseBtn_clicked(); - void on_javaDownloadBtn_clicked(); - void on_maxMemSpinBox_valueChanged(int i); - void on_serverJoinAddressButton_toggled(bool checked); - void on_worldJoinButton_toggled(bool checked); - - void onUseNativeGLFWChanged(bool checked); - void onUseNativeOpenALChanged(bool checked); - - void applySettings(); - void loadSettings(); - - void checkerFinished(); - - void globalSettingsButtonClicked(bool checked); - - void updateAccountsMenu(); - QIcon getFaceForAccount(MinecraftAccountPtr account); - void changeInstanceAccount(int index); - - private: - Ui::InstanceSettingsPage* ui; - BaseInstance* m_instance; - SettingsObjectPtr m_settings; - unique_qobject_ptr checker; - bool m_world_quickplay_supported; + explicit InstanceSettingsPage(MinecraftInstancePtr instance, QWidget* parent = nullptr) : MinecraftSettingsWidget(std::move(instance), parent) + { + connect(APPLICATION, &Application::globalSettingsAboutToOpen, this, &InstanceSettingsPage::saveSettings); + connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceSettingsPage::loadSettings); + } + ~InstanceSettingsPage() override {} + QString displayName() const override { return tr("Settings"); } + QIcon icon() const override { return APPLICATION->getThemedIcon("instance-settings"); } + QString id() const override { return "settings"; } + bool apply() override + { + saveSettings(); + return true; + } + QString helpPage() const override { return "Instance-settings"; } }; diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.ui b/launcher/ui/pages/instance/InstanceSettingsPage.ui deleted file mode 100644 index 4905eae87..000000000 --- a/launcher/ui/pages/instance/InstanceSettingsPage.ui +++ /dev/null @@ -1,814 +0,0 @@ - - - InstanceSettingsPage - - - - 0 - 0 - 691 - 581 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Open Global Settings - - - The settings here are overrides for global settings. - - - - - - - 0 - - - - Java - - - - - - true - - - Java insta&llation - - - true - - - false - - - - - - If enabled, the launcher will not check if an instance is compatible with the selected Java version. - - - Skip Java compatibility checks - - - - - - - - - - - - Browse - - - - - - - - - - - Download Java - - - - - - - Auto-detect... - - - - - - - Test - - - - - - - - - - - - true - - - Memor&y - - - true - - - false - - - - - - PermGen: - - - - - - - Minimum memory allocation: - - - - - - - Maximum memory allocation: - - - - - - - Note: Permgen is set automatically by Java 8 and later - - - - - - - The amount of memory Minecraft is started with. - - - MiB - - - 8 - - - 1048576 - - - 128 - - - 256 - - - - - - - The maximum amount of memory Minecraft is allowed to use. - - - MiB - - - 8 - - - 1048576 - - - 128 - - - 1024 - - - - - - - The amount of memory available to store loaded Java classes. - - - MiB - - - 4 - - - 999999999 - - - 8 - - - 64 - - - - - - - - - - Qt::AlignCenter - - - maxMemSpinBox - - - - - - - - - - true - - - Java argumen&ts - - - true - - - false - - - - - - - - - - - - - Game windows - - - - - - true - - - Game Window - - - true - - - false - - - - - - Start Minecraft maximized - - - - - - - - - Window height: - - - - - - - Window width: - - - - - - - 1 - - - 65536 - - - 1 - - - 854 - - - - - - - 1 - - - 65536 - - - 480 - - - - - - - - - - - - true - - - Conso&le Settings - - - true - - - false - - - - - - Show console while the game is running - - - - - - - Automatically close console when the game quits - - - - - - - Show console when the game crashes - - - - - - - - - - Miscellaneous - - - true - - - false - - - - - - Close the launcher after game window opens - - - - - - - Quit the launcher after game window closes - - - - - - - - - - Qt::Vertical - - - - 88 - 125 - - - - - - - - - Custom commands - - - - - - - - - - Environment variables - - - - - - - - - - Workarounds - - - - - - true - - - Native libraries - - - true - - - false - - - - - - Use system installation of OpenAL - - - - - - - &GLFW library path - - - lineEditGLFWPath - - - - - - - Use system installation of GLFW - - - - - - - false - - - - - - - &OpenAL library path - - - lineEditOpenALPath - - - - - - - false - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - Performance - - - - - - true - - - Performance - - - true - - - false - - - - - - <html><head/><body><p>Enable Feral Interactive's GameMode, to potentially improve gaming performance.</p></body></html> - - - Enable Feral GameMode - - - - - - - <html><head/><body><p>Enable MangoHud's advanced performance overlay.</p></body></html> - - - Enable MangoHud - - - - - - - <html><head/><body><p>Use the discrete GPU instead of the primary GPU.</p></body></html> - - - Use discrete GPU - - - - - - - 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 - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - Miscellaneous - - - - - - Legacy settings - - - true - - - false - - - - - - <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) - - - - - - - - - - true - - - Override global game time settings - - - true - - - false - - - - - - Show time spent playing this instance - - - - - - - Record time spent playing this instance - - - - - - - - - - Set a target to join on launch - - - true - - - false - - - - - - Server address: - - - - - - - - - - Singleplayer world - - - - - - - - - - - - - Override default account - - - true - - - false - - - - - - - - - 0 - 0 - - - - Account: - - - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - CustomCommands - QWidget -
ui/widgets/CustomCommands.h
- 1 -
- - EnvironmentVariables - QWidget -
ui/widgets/EnvironmentVariables.h
- 1 -
-
- - openGlobalJavaSettingsButton - settingsTabs - javaSettingsGroupBox - javaPathTextBox - javaBrowseBtn - javaDownloadBtn - javaDetectBtn - javaTestBtn - skipCompatibilityCheckbox - memoryGroupBox - minMemSpinBox - maxMemSpinBox - permGenSpinBox - javaArgumentsGroupBox - jvmArgsTextBox - windowSizeGroupBox - maximizedCheckBox - windowWidthSpinBox - windowHeightSpinBox - consoleSettingsBox - showConsoleCheck - autoCloseConsoleCheck - showConsoleErrorCheck - nativeWorkaroundsGroupBox - useNativeGLFWCheck - useNativeOpenALCheck - showGameTime - recordGameTime - miscellaneousSettingsBox - closeAfterLaunchCheck - quitAfterGameStopCheck - perfomanceGroupBox - enableFeralGamemodeCheck - enableMangoHud - useDiscreteGpuCheck - gameTimeGroupBox - serverJoinGroupBox - serverJoinAddress - instanceAccountGroupBox - instanceAccountSelector - - - -
diff --git a/launcher/ui/pages/instance/LogPage.cpp b/launcher/ui/pages/instance/LogPage.cpp index 6a4888f9c..4962f90ce 100644 --- a/launcher/ui/pages/instance/LogPage.cpp +++ b/launcher/ui/pages/instance/LogPage.cpp @@ -90,7 +90,7 @@ class LogFormatProxyModel : public QIdentityProxyModel { QModelIndex find(const QModelIndex& start, const QString& value, bool reverse) const { QModelIndex parentIndex = parent(start); - auto compare = [&](int r) -> QModelIndex { + auto compare = [this, start, parentIndex, value](int r) -> QModelIndex { QModelIndex idx = index(r, start.column(), parentIndex); if (!idx.isValid() || idx == start) { return QModelIndex(); diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index f2feb8c7f..95507ac22 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -51,118 +51,60 @@ #include "Application.h" -#include "ui/GuiUtil.h" #include "ui/dialogs/CustomMessageBox.h" -#include "ui/dialogs/ModUpdateDialog.h" #include "ui/dialogs/ResourceDownloadDialog.h" - -#include "DesktopServices.h" +#include "ui/dialogs/ResourceUpdateDialog.h" #include "minecraft/PackProfile.h" #include "minecraft/VersionFilterData.h" #include "minecraft/mod/Mod.h" #include "minecraft/mod/ModFolderModel.h" -#include "modplatform/ModIndex.h" -#include "modplatform/ResourceAPI.h" - -#include "Version.h" #include "tasks/ConcurrentTask.h" #include "tasks/Task.h" #include "ui/dialogs/ProgressDialog.h" -ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr mods, QWidget* parent) - : ExternalResourcesPage(inst, mods, parent), m_model(mods) +ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr model, QWidget* parent) + : ExternalResourcesPage(inst, model, parent), m_model(model) { - // This is structured like that so that these changes - // do not affect the Resource pack and Shader pack tabs - { - ui->actionDownloadItem->setText(tr("Download mods")); - ui->actionDownloadItem->setToolTip(tr("Download mods from online mod platforms")); - ui->actionDownloadItem->setEnabled(true); - ui->actionAddItem->setText(tr("Add file")); - ui->actionAddItem->setToolTip(tr("Add a locally downloaded file")); + ui->actionDownloadItem->setText(tr("Download Mods")); + ui->actionDownloadItem->setToolTip(tr("Download mods from online mod platforms")); + ui->actionDownloadItem->setEnabled(true); + ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem); - ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem); + connect(ui->actionDownloadItem, &QAction::triggered, this, &ModFolderPage::downloadMods); - connect(ui->actionDownloadItem, &QAction::triggered, this, &ModFolderPage::installMods); + ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected mods (all mods if none are selected)")); + connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods); + ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem); - // update menu - auto updateMenu = ui->actionUpdateItem->menu(); - if (updateMenu) { - updateMenu->clear(); - } else { - updateMenu = new QMenu(this); - } + auto updateMenu = new QMenu(this); - auto update = updateMenu->addAction(tr("Check for Updates")); - update->setToolTip(tr("Try to check or update all selected mods (all mods if none are selected)")); - connect(update, &QAction::triggered, this, &ModFolderPage::updateMods); + auto update = updateMenu->addAction(tr("Check for Updates")); + connect(update, &QAction::triggered, this, &ModFolderPage::updateMods); - auto updateWithDeps = updateMenu->addAction(tr("Verify Dependencies")); - updateWithDeps->setToolTip( - tr("Try to update and check for missing dependencies all selected mods (all mods if none are selected)")); - connect(updateWithDeps, &QAction::triggered, this, [this] { updateMods(true); }); + updateMenu->addAction(ui->actionVerifyItemDependencies); + connect(ui->actionVerifyItemDependencies, &QAction::triggered, this, [this] { updateMods(true); }); - auto depsDisabled = APPLICATION->settings()->getSetting("ModDependenciesDisabled"); - updateWithDeps->setVisible(!depsDisabled->get().toBool()); - connect(depsDisabled.get(), &Setting::SettingChanged, this, - [updateWithDeps](const Setting& setting, QVariant value) { updateWithDeps->setVisible(!value.toBool()); }); + auto depsDisabled = APPLICATION->settings()->getSetting("ModDependenciesDisabled"); + ui->actionVerifyItemDependencies->setVisible(!depsDisabled->get().toBool()); + connect(depsDisabled.get(), &Setting::SettingChanged, this, + [this](const Setting& setting, const QVariant& value) { ui->actionVerifyItemDependencies->setVisible(!value.toBool()); }); - auto actionRemoveItemMetadata = updateMenu->addAction(tr("Reset update metadata")); - actionRemoveItemMetadata->setToolTip(tr("Remove mod's metadata")); - connect(actionRemoveItemMetadata, &QAction::triggered, this, &ModFolderPage::deleteModMetadata); - actionRemoveItemMetadata->setEnabled(false); + updateMenu->addAction(ui->actionResetItemMetadata); + connect(ui->actionResetItemMetadata, &QAction::triggered, this, &ModFolderPage::deleteModMetadata); - ui->actionUpdateItem->setMenu(updateMenu); + ui->actionUpdateItem->setMenu(updateMenu); - ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected mods (all mods if none are selected)")); - connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods); - ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem); + ui->actionChangeVersion->setToolTip(tr("Change a mod's version.")); + connect(ui->actionChangeVersion, &QAction::triggered, this, &ModFolderPage::changeModVersion); + ui->actionsToolbar->insertActionAfter(ui->actionUpdateItem, ui->actionChangeVersion); - ui->actionVisitItemPage->setToolTip(tr("Go to mod's home page")); - ui->actionsToolbar->addAction(ui->actionVisitItemPage); - connect(ui->actionVisitItemPage, &QAction::triggered, this, &ModFolderPage::visitModPages); + ui->actionViewHomepage->setToolTip(tr("View the homepages of all selected mods.")); - auto changeVersion = new QAction(tr("Change Version")); - changeVersion->setToolTip(tr("Change mod version")); - changeVersion->setEnabled(false); - ui->actionsToolbar->insertActionAfter(ui->actionUpdateItem, changeVersion); - connect(changeVersion, &QAction::triggered, this, &ModFolderPage::changeModVersion); - - ui->actionsToolbar->insertActionAfter(ui->actionVisitItemPage, ui->actionExportMetadata); - connect(ui->actionExportMetadata, &QAction::triggered, this, &ModFolderPage::exportModMetadata); - - auto check_allow_update = [this] { return ui->treeView->selectionModel()->hasSelection() || !m_model->empty(); }; - - connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, - [this, check_allow_update, actionRemoveItemMetadata, changeVersion] { - ui->actionUpdateItem->setEnabled(check_allow_update()); - - auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - auto mods_list = m_model->selectedMods(selection); - auto selected = std::count_if(mods_list.cbegin(), mods_list.cend(), - [](Mod* v) { return v->metadata() != nullptr || v->homeurl().size() != 0; }); - if (selected <= 1) { - ui->actionVisitItemPage->setText(tr("Visit mod's page")); - ui->actionVisitItemPage->setToolTip(tr("Go to mod's home page")); - } else { - ui->actionVisitItemPage->setText(tr("Visit mods' pages")); - ui->actionVisitItemPage->setToolTip(tr("Go to the pages of the selected mods")); - } - - changeVersion->setEnabled(mods_list.length() == 1 && mods_list[0]->metadata() != nullptr); - ui->actionVisitItemPage->setEnabled(selected != 0); - actionRemoveItemMetadata->setEnabled(selected != 0); - }); - - auto updateButtons = [this, check_allow_update] { ui->actionUpdateItem->setEnabled(check_allow_update()); }; - connect(mods.get(), &ModFolderModel::rowsInserted, this, updateButtons); - - connect(mods.get(), &ModFolderModel::rowsRemoved, this, updateButtons); - - connect(mods.get(), &ModFolderModel::updateFinished, this, updateButtons); - } + ui->actionExportMetadata->setToolTip(tr("Export mod's metadata to text.")); + connect(ui->actionExportMetadata, &QAction::triggered, this, &ModFolderPage::exportModMetadata); + ui->actionsToolbar->insertActionAfter(ui->actionViewHomepage, ui->actionExportMetadata); } bool ModFolderPage::shouldDisplay() const @@ -170,15 +112,12 @@ bool ModFolderPage::shouldDisplay() const return true; } -bool ModFolderPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous) +void ModFolderPage::updateFrame(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous) { auto sourceCurrent = m_filterModel->mapToSource(current); int row = sourceCurrent.row(); - Mod const* m = m_model->at(row); - if (m) - ui->frame->updateWithMod(*m); - - return true; + const Mod& mod = m_model->at(row); + ui->frame->updateWithMod(mod); } void ModFolderPage::removeItems(const QItemSelection& selection) @@ -193,10 +132,10 @@ void ModFolderPage::removeItems(const QItemSelection& selection) if (response != QMessageBox::Yes) return; } - m_model->deleteMods(selection.indexes()); + m_model->deleteResources(selection.indexes()); } -void ModFolderPage::installMods() +void ModFolderPage::downloadMods() { if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance @@ -209,7 +148,7 @@ void ModFolderPage::installMods() ResourceDownload::ModDownloadDialog mdownload(this, m_model, m_instance); if (mdownload.exec()) { - auto tasks = new ConcurrentTask(this, "Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + auto tasks = new ConcurrentTask(tr("Download Mods"), APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); @@ -266,12 +205,12 @@ void ModFolderPage::updateMods(bool includeDeps) } auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - auto mods_list = m_model->selectedMods(selection); + auto mods_list = m_model->selectedResources(selection); bool use_all = mods_list.empty(); if (use_all) - mods_list = m_model->allMods(); + mods_list = m_model->allResources(); - ModUpdateDialog update_dialog(this, m_instance, m_model, mods_list, includeDeps); + ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, includeDeps, true); update_dialog.checkCandidates(); if (update_dialog.aborted()) { @@ -292,7 +231,7 @@ void ModFolderPage::updateMods(bool includeDeps) } if (update_dialog.exec()) { - auto tasks = new ConcurrentTask(this, "Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + auto tasks = new ConcurrentTask("Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); @@ -321,6 +260,90 @@ void ModFolderPage::updateMods(bool includeDeps) } } +void ModFolderPage::deleteModMetadata() +{ + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); + auto selectionCount = m_model->selectedMods(selection).length(); + if (selectionCount == 0) + return; + if (selectionCount > 1) { + auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), + tr("You are about to remove the metadata for %1 mods.\n" + "Are you sure?") + .arg(selectionCount), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); + + if (response != QMessageBox::Yes) + return; + } + + m_model->deleteMetadata(selection); +} + +void ModFolderPage::changeModVersion() +{ + if (m_instance->typeName() != "Minecraft") + return; // this is a null instance or a legacy instance + + auto profile = static_cast(m_instance)->getPackProfile(); + if (!profile->getModLoaders().has_value()) { + QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!")); + return; + } + if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { + QMessageBox::critical(this, tr("Error"), tr("Mod updates are unavailable when metadata is disabled!")); + return; + } + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); + auto mods_list = m_model->selectedMods(selection); + if (mods_list.length() != 1 || mods_list[0]->metadata() == nullptr) + return; + + ResourceDownload::ModDownloadDialog mdownload(this, m_model, m_instance); + mdownload.setResourceMetadata((*mods_list.begin())->metadata()); + if (mdownload.exec()) { + auto tasks = new ConcurrentTask("Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](QString reason) { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::aborted, [this, tasks]() { + CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::succeeded, [this, tasks]() { + QStringList warnings = tasks->warnings(); + if (warnings.count()) + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + + tasks->deleteLater(); + }); + + for (auto& task : mdownload.getTasks()) { + tasks->addTask(task); + } + + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(tasks); + + m_model->update(); + } +} + +void ModFolderPage::exportModMetadata() +{ + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); + auto selectedMods = m_model->selectedMods(selection); + if (selectedMods.length() == 0) + selectedMods = m_model->allMods(); + + std::sort(selectedMods.begin(), selectedMods.end(), [](const Mod* a, const Mod* b) { return a->name() < b->name(); }); + ExportToModListDialog dlg(m_instance->name(), selectedMods, this); + dlg.exec(); +} + CoreModFolderPage::CoreModFolderPage(BaseInstance* inst, std::shared_ptr mods, QWidget* parent) : ModFolderPage(inst, mods, parent) { @@ -369,97 +392,3 @@ bool NilModFolderPage::shouldDisplay() const { return m_model->dir().exists(); } - -void ModFolderPage::visitModPages() -{ - auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - for (auto mod : m_model->selectedMods(selection)) { - auto url = mod->metaurl(); - if (!url.isEmpty()) - DesktopServices::openUrl(url); - } -} - -void ModFolderPage::deleteModMetadata() -{ - auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - auto selectionCount = m_model->selectedMods(selection).length(); - if (selectionCount == 0) - return; - if (selectionCount > 1) { - auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), - tr("You are about to remove the metadata for %1 mods.\n" - "Are you sure?") - .arg(selectionCount), - QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) - ->exec(); - - if (response != QMessageBox::Yes) - return; - } - - m_model->deleteModsMetadata(selection); -} - -void ModFolderPage::changeModVersion() -{ - if (m_instance->typeName() != "Minecraft") - return; // this is a null instance or a legacy instance - - auto profile = static_cast(m_instance)->getPackProfile(); - if (!profile->getModLoaders().has_value()) { - QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!")); - return; - } - if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { - QMessageBox::critical(this, tr("Error"), tr("Mod updates are unavailable when metadata is disabled!")); - return; - } - auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - auto mods_list = m_model->selectedMods(selection); - if (mods_list.length() != 1 || mods_list[0]->metadata() == nullptr) - return; - - ResourceDownload::ModDownloadDialog mdownload(this, m_model, m_instance); - mdownload.setModMetadata((*mods_list.begin())->metadata()); - if (mdownload.exec()) { - auto tasks = new ConcurrentTask(this, "Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { - CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); - tasks->deleteLater(); - }); - connect(tasks, &Task::aborted, [this, tasks]() { - CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); - tasks->deleteLater(); - }); - connect(tasks, &Task::succeeded, [this, tasks]() { - QStringList warnings = tasks->warnings(); - if (warnings.count()) - CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); - - tasks->deleteLater(); - }); - - for (auto& task : mdownload.getTasks()) { - tasks->addTask(task); - } - - ProgressDialog loadDialog(this); - loadDialog.setSkipButton(true, tr("Abort")); - loadDialog.execWithTask(tasks); - - m_model->update(); - } -} - -void ModFolderPage::exportModMetadata() -{ - auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - auto selectedMods = m_model->selectedMods(selection); - if (selectedMods.length() == 0) - selectedMods = m_model->allMods(); - - std::sort(selectedMods.begin(), selectedMods.end(), [](const Mod* a, const Mod* b) { return a->name() < b->name(); }); - ExportToModListDialog dlg(m_instance->name(), selectedMods, this); - dlg.exec(); -} diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index 41b605991..a7d078f50 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -44,7 +44,7 @@ class ModFolderPage : public ExternalResourcesPage { Q_OBJECT public: - explicit ModFolderPage(BaseInstance* inst, std::shared_ptr mods, QWidget* parent = nullptr); + explicit ModFolderPage(BaseInstance* inst, std::shared_ptr model, QWidget* parent = nullptr); virtual ~ModFolderPage() = default; void setFilter(const QString& filter) { m_fileSelectionFilter = filter; } @@ -57,16 +57,15 @@ class ModFolderPage : public ExternalResourcesPage { virtual bool shouldDisplay() const override; public slots: - bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override; + void updateFrame(const QModelIndex& current, const QModelIndex& previous) override; private slots: void removeItems(const QItemSelection& selection) override; + + void downloadMods(); + void updateMods(bool includeDeps = false); void deleteModMetadata(); void exportModMetadata(); - - void installMods(); - void updateMods(bool includeDeps = false); - void visitModPages(); void changeModVersion(); protected: diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index ab5d98289..ed8ef68d9 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -138,7 +138,7 @@ void OtherLogsPage::on_btnReload_clicked() m_currentFile = QString(); QMessageBox::critical(this, tr("Error"), tr("Unable to open %1 for reading: %2").arg(m_currentFile, file.errorString())); } else { - auto setPlainText = [&](const QString& text) { + auto setPlainText = [this](const QString& text) { QString fontFamily = APPLICATION->settings()->get("ConsoleFont").toString(); bool conversionOk = false; int fontSize = APPLICATION->settings()->get("ConsoleFontSize").toInt(&conversionOk); @@ -149,7 +149,7 @@ void OtherLogsPage::on_btnReload_clicked() doc->setDefaultFont(QFont(fontFamily, fontSize)); ui->text->setPlainText(text); }; - auto showTooBig = [&]() { + auto showTooBig = [setPlainText, &file]() { setPlainText(tr("The file (%1) is too big. You may want to open it in a viewer optimized " "for large files.") .arg(file.fileName())); diff --git a/launcher/ui/pages/instance/ResourcePackPage.cpp b/launcher/ui/pages/instance/ResourcePackPage.cpp index 85be64256..79e677765 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.cpp +++ b/launcher/ui/pages/instance/ResourcePackPage.cpp @@ -37,43 +37,56 @@ #include "ResourcePackPage.h" -#include "ResourceDownloadTask.h" - #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/ProgressDialog.h" #include "ui/dialogs/ResourceDownloadDialog.h" +#include "ui/dialogs/ResourceUpdateDialog.h" ResourcePackPage::ResourcePackPage(MinecraftInstance* instance, std::shared_ptr model, QWidget* parent) - : ExternalResourcesPage(instance, model, parent) + : ExternalResourcesPage(instance, model, parent), m_model(model) { - ui->actionDownloadItem->setText(tr("Download packs")); - ui->actionDownloadItem->setToolTip(tr("Download resource packs from online platforms")); + ui->actionDownloadItem->setText(tr("Download Packs")); + ui->actionDownloadItem->setToolTip(tr("Download resource packs from online mod platforms")); ui->actionDownloadItem->setEnabled(true); - connect(ui->actionDownloadItem, &QAction::triggered, this, &ResourcePackPage::downloadRPs); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem); - ui->actionViewConfigs->setVisible(false); + connect(ui->actionDownloadItem, &QAction::triggered, this, &ResourcePackPage::downloadResourcePacks); + + ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected resource packs (all resource packs if none are selected)")); + connect(ui->actionUpdateItem, &QAction::triggered, this, &ResourcePackPage::updateResourcePacks); + ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem); + + auto updateMenu = new QMenu(this); + + auto update = updateMenu->addAction(ui->actionUpdateItem->text()); + connect(update, &QAction::triggered, this, &ResourcePackPage::updateResourcePacks); + + updateMenu->addAction(ui->actionResetItemMetadata); + connect(ui->actionResetItemMetadata, &QAction::triggered, this, &ResourcePackPage::deleteResourcePackMetadata); + + ui->actionUpdateItem->setMenu(updateMenu); + + ui->actionChangeVersion->setToolTip(tr("Change a mod's version.")); + connect(ui->actionChangeVersion, &QAction::triggered, this, &ResourcePackPage::changeResourcePackVersion); + ui->actionsToolbar->insertActionAfter(ui->actionUpdateItem, ui->actionChangeVersion); } -bool ResourcePackPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous) +void ResourcePackPage::updateFrame(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous) { auto sourceCurrent = m_filterModel->mapToSource(current); int row = sourceCurrent.row(); auto& rp = static_cast(m_model->at(row)); ui->frame->updateWithResourcePack(rp); - - return true; } -void ResourcePackPage::downloadRPs() +void ResourcePackPage::downloadResourcePacks() { if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - ResourceDownload::ResourcePackDownloadDialog mdownload(this, std::static_pointer_cast(m_model), m_instance); + ResourceDownload::ResourcePackDownloadDialog mdownload(this, m_model, m_instance); if (mdownload.exec()) { - auto tasks = - new ConcurrentTask(this, "Download Resource Pack", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + auto tasks = new ConcurrentTask("Download Resource Pack", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); @@ -101,3 +114,155 @@ void ResourcePackPage::downloadRPs() m_model->update(); } } + +void ResourcePackPage::updateResourcePacks() +{ + if (m_instance->typeName() != "Minecraft") + return; // this is a null instance or a legacy instance + + auto profile = static_cast(m_instance)->getPackProfile(); + if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { + QMessageBox::critical(this, tr("Error"), tr("Resource pack updates are unavailable when metadata is disabled!")); + return; + } + if (m_instance != nullptr && m_instance->isRunning()) { + auto response = CustomMessageBox::selectable( + this, tr("Confirm Update"), + tr("Updating resource packs while the game is running may cause pack 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) + ->exec(); + + if (response != QMessageBox::Yes) + return; + } + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); + + auto mods_list = m_model->selectedResources(selection); + bool use_all = mods_list.empty(); + if (use_all) + mods_list = m_model->allResources(); + + ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false, false); + update_dialog.checkCandidates(); + + if (update_dialog.aborted()) { + CustomMessageBox::selectable(this, tr("Aborted"), tr("The resource pack updater was aborted!"), QMessageBox::Warning)->show(); + return; + } + if (update_dialog.noUpdates()) { + QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) }; + if (mods_list.size() > 1) { + if (use_all) { + message = tr("All resource packs are up-to-date! :)"); + } else { + message = tr("All selected resource packs are up-to-date! :)"); + } + } + CustomMessageBox::selectable(this, tr("Update checker"), message)->exec(); + return; + } + + if (update_dialog.exec()) { + auto tasks = new ConcurrentTask("Download Resource Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](QString reason) { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::aborted, [this, tasks]() { + CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::succeeded, [this, tasks]() { + QStringList warnings = tasks->warnings(); + if (warnings.count()) { + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + } + tasks->deleteLater(); + }); + + for (auto task : update_dialog.getTasks()) { + tasks->addTask(task); + } + + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(tasks); + + m_model->update(); + } +} + +void ResourcePackPage::deleteResourcePackMetadata() +{ + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); + auto selectionCount = m_model->selectedResourcePacks(selection).length(); + if (selectionCount == 0) + return; + if (selectionCount > 1) { + auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), + tr("You are about to remove the metadata for %1 resource packs.\n" + "Are you sure?") + .arg(selectionCount), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); + + if (response != QMessageBox::Yes) + return; + } + + m_model->deleteMetadata(selection); +} + +void ResourcePackPage::changeResourcePackVersion() +{ + if (m_instance->typeName() != "Minecraft") + return; // this is a null instance or a legacy instance + + if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { + QMessageBox::critical(this, tr("Error"), tr("Resource pack updates are unavailable when metadata is disabled!")); + return; + } + + const QModelIndexList rows = ui->treeView->selectionModel()->selectedRows(); + + if (rows.count() != 1) + return; + + Resource& resource = m_model->at(m_filterModel->mapToSource(rows[0]).row()); + + if (resource.metadata() == nullptr) + return; + + ResourceDownload::ResourcePackDownloadDialog mdownload(this, m_model, m_instance); + mdownload.setResourceMetadata(resource.metadata()); + if (mdownload.exec()) { + auto tasks = new ConcurrentTask("Download Resource Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](QString reason) { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::aborted, [this, tasks]() { + CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::succeeded, [this, tasks]() { + QStringList warnings = tasks->warnings(); + if (warnings.count()) + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + + tasks->deleteLater(); + }); + + for (auto& task : mdownload.getTasks()) { + tasks->addTask(task); + } + + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(tasks); + + m_model->update(); + } +} \ No newline at end of file diff --git a/launcher/ui/pages/instance/ResourcePackPage.h b/launcher/ui/pages/instance/ResourcePackPage.h index cb84ca96d..55abe007c 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.h +++ b/launcher/ui/pages/instance/ResourcePackPage.h @@ -58,6 +58,14 @@ class ResourcePackPage : public ExternalResourcesPage { } public slots: - bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override; - void downloadRPs(); + void updateFrame(const QModelIndex& current, const QModelIndex& previous) override; + + private slots: + void downloadResourcePacks(); + void updateResourcePacks(); + void deleteResourcePackMetadata(); + void changeResourcePackVersion(); + + protected: + std::shared_ptr m_model; }; diff --git a/launcher/ui/pages/instance/ShaderPackPage.cpp b/launcher/ui/pages/instance/ShaderPackPage.cpp index 40366a1be..a287d3edf 100644 --- a/launcher/ui/pages/instance/ShaderPackPage.cpp +++ b/launcher/ui/pages/instance/ShaderPackPage.cpp @@ -45,27 +45,197 @@ #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/ProgressDialog.h" #include "ui/dialogs/ResourceDownloadDialog.h" +#include "ui/dialogs/ResourceUpdateDialog.h" ShaderPackPage::ShaderPackPage(MinecraftInstance* instance, std::shared_ptr model, QWidget* parent) - : ExternalResourcesPage(instance, model, parent) + : ExternalResourcesPage(instance, model, parent), m_model(model) { - ui->actionDownloadItem->setText(tr("Download shaders")); - ui->actionDownloadItem->setToolTip(tr("Download shaders from online platforms")); + ui->actionDownloadItem->setText(tr("Download Packs")); + ui->actionDownloadItem->setToolTip(tr("Download shader packs from online mod platforms")); ui->actionDownloadItem->setEnabled(true); - connect(ui->actionDownloadItem, &QAction::triggered, this, &ShaderPackPage::downloadShaders); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem); - ui->actionViewConfigs->setVisible(false); + connect(ui->actionDownloadItem, &QAction::triggered, this, &ShaderPackPage::downloadShaderPack); + + ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected shader packs (all shader packs if none are selected)")); + connect(ui->actionUpdateItem, &QAction::triggered, this, &ShaderPackPage::updateShaderPacks); + ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem); + + auto updateMenu = new QMenu(this); + + auto update = updateMenu->addAction(ui->actionUpdateItem->text()); + connect(update, &QAction::triggered, this, &ShaderPackPage::updateShaderPacks); + + updateMenu->addAction(ui->actionResetItemMetadata); + connect(ui->actionResetItemMetadata, &QAction::triggered, this, &ShaderPackPage::deleteShaderPackMetadata); + + ui->actionUpdateItem->setMenu(updateMenu); + + ui->actionChangeVersion->setToolTip(tr("Change a shader pack's version.")); + connect(ui->actionChangeVersion, &QAction::triggered, this, &ShaderPackPage::changeShaderPackVersion); + ui->actionsToolbar->insertActionAfter(ui->actionUpdateItem, ui->actionChangeVersion); } -void ShaderPackPage::downloadShaders() +void ShaderPackPage::downloadShaderPack() { if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - ResourceDownload::ShaderPackDownloadDialog mdownload(this, std::static_pointer_cast(m_model), m_instance); + ResourceDownload::ShaderPackDownloadDialog mdownload(this, m_model, m_instance); if (mdownload.exec()) { - auto tasks = new ConcurrentTask(this, "Download Shaders", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + auto tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](QString reason) { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::aborted, [this, tasks]() { + CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::succeeded, [this, tasks]() { + QStringList warnings = tasks->warnings(); + if (warnings.count()) + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + + tasks->deleteLater(); + }); + + for (auto& task : mdownload.getTasks()) { + tasks->addTask(task); + } + + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(tasks); + + m_model->update(); + } +} + +void ShaderPackPage::updateShaderPacks() +{ + if (m_instance->typeName() != "Minecraft") + return; // this is a null instance or a legacy instance + + auto profile = static_cast(m_instance)->getPackProfile(); + if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { + QMessageBox::critical(this, tr("Error"), tr("Shader pack updates are unavailable when metadata is disabled!")); + return; + } + if (m_instance != nullptr && m_instance->isRunning()) { + auto response = + CustomMessageBox::selectable(this, tr("Confirm Update"), + tr("Updating shader packs while the game is running may pack 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) + ->exec(); + + if (response != QMessageBox::Yes) + return; + } + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); + + auto mods_list = m_model->selectedResources(selection); + bool use_all = mods_list.empty(); + if (use_all) + mods_list = m_model->allResources(); + + ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false, false); + update_dialog.checkCandidates(); + + if (update_dialog.aborted()) { + CustomMessageBox::selectable(this, tr("Aborted"), tr("The shader pack updater was aborted!"), QMessageBox::Warning)->show(); + return; + } + if (update_dialog.noUpdates()) { + QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) }; + if (mods_list.size() > 1) { + if (use_all) { + message = tr("All shader packs are up-to-date! :)"); + } else { + message = tr("All selected shader packs are up-to-date! :)"); + } + } + CustomMessageBox::selectable(this, tr("Update checker"), message)->exec(); + return; + } + + if (update_dialog.exec()) { + auto tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](QString reason) { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::aborted, [this, tasks]() { + CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::succeeded, [this, tasks]() { + QStringList warnings = tasks->warnings(); + if (warnings.count()) { + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + } + tasks->deleteLater(); + }); + + for (auto task : update_dialog.getTasks()) { + tasks->addTask(task); + } + + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(tasks); + + m_model->update(); + } +} + +void ShaderPackPage::deleteShaderPackMetadata() +{ + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); + auto selectionCount = m_model->selectedShaderPacks(selection).length(); + if (selectionCount == 0) + return; + if (selectionCount > 1) { + auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), + tr("You are about to remove the metadata for %1 shader packs.\n" + "Are you sure?") + .arg(selectionCount), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); + + if (response != QMessageBox::Yes) + return; + } + + m_model->deleteMetadata(selection); +} + +void ShaderPackPage::changeShaderPackVersion() +{ + if (m_instance->typeName() != "Minecraft") + return; // this is a null instance or a legacy instance + + if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { + QMessageBox::critical(this, tr("Error"), tr("Shader pack updates are unavailable when metadata is disabled!")); + return; + } + + const QModelIndexList rows = ui->treeView->selectionModel()->selectedRows(); + + if (rows.count() != 1) + return; + + Resource& resource = m_model->at(m_filterModel->mapToSource(rows[0]).row()); + + if (resource.metadata() == nullptr) + return; + + ResourceDownload::ShaderPackDownloadDialog mdownload(this, m_model, m_instance); + mdownload.setResourceMetadata(resource.metadata()); + if (mdownload.exec()) { + auto tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); diff --git a/launcher/ui/pages/instance/ShaderPackPage.h b/launcher/ui/pages/instance/ShaderPackPage.h index d134e67ad..ebf7f1d58 100644 --- a/launcher/ui/pages/instance/ShaderPackPage.h +++ b/launcher/ui/pages/instance/ShaderPackPage.h @@ -53,5 +53,11 @@ class ShaderPackPage : public ExternalResourcesPage { bool shouldDisplay() const override { return true; } public slots: - void downloadShaders(); + void downloadShaderPack(); + void updateShaderPacks(); + void deleteShaderPackMetadata(); + void changeShaderPackVersion(); + + private: + std::shared_ptr m_model; }; diff --git a/launcher/ui/pages/instance/TexturePackPage.cpp b/launcher/ui/pages/instance/TexturePackPage.cpp index 7c8d7e061..fd1e0a2fc 100644 --- a/launcher/ui/pages/instance/TexturePackPage.cpp +++ b/launcher/ui/pages/instance/TexturePackPage.cpp @@ -44,38 +44,207 @@ #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/ProgressDialog.h" #include "ui/dialogs/ResourceDownloadDialog.h" +#include "ui/dialogs/ResourceUpdateDialog.h" TexturePackPage::TexturePackPage(MinecraftInstance* instance, std::shared_ptr model, QWidget* parent) - : ExternalResourcesPage(instance, model, parent) + : ExternalResourcesPage(instance, model, parent), m_model(model) { - ui->actionDownloadItem->setText(tr("Download packs")); - ui->actionDownloadItem->setToolTip(tr("Download texture packs from online platforms")); + ui->actionDownloadItem->setText(tr("Download Packs")); + ui->actionDownloadItem->setToolTip(tr("Download texture packs from online mod platforms")); ui->actionDownloadItem->setEnabled(true); - connect(ui->actionDownloadItem, &QAction::triggered, this, &TexturePackPage::downloadTPs); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem); - ui->actionViewConfigs->setVisible(false); + connect(ui->actionDownloadItem, &QAction::triggered, this, &TexturePackPage::downloadTexturePacks); + + ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected texture packs (all texture packs if none are selected)")); + connect(ui->actionUpdateItem, &QAction::triggered, this, &TexturePackPage::updateTexturePacks); + ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem); + + auto updateMenu = new QMenu(this); + + auto update = updateMenu->addAction(ui->actionUpdateItem->text()); + connect(update, &QAction::triggered, this, &TexturePackPage::updateTexturePacks); + + updateMenu->addAction(ui->actionResetItemMetadata); + connect(ui->actionResetItemMetadata, &QAction::triggered, this, &TexturePackPage::deleteTexturePackMetadata); + + ui->actionUpdateItem->setMenu(updateMenu); + + ui->actionChangeVersion->setToolTip(tr("Change a texture pack's version.")); + connect(ui->actionChangeVersion, &QAction::triggered, this, &TexturePackPage::changeTexturePackVersion); + ui->actionsToolbar->insertActionAfter(ui->actionUpdateItem, ui->actionChangeVersion); + + ui->actionViewHomepage->setToolTip(tr("View the homepages of all selected texture packs.")); } -bool TexturePackPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous) +void TexturePackPage::updateFrame(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous) { auto sourceCurrent = m_filterModel->mapToSource(current); int row = sourceCurrent.row(); auto& rp = static_cast(m_model->at(row)); ui->frame->updateWithTexturePack(rp); - - return true; } -void TexturePackPage::downloadTPs() +void TexturePackPage::downloadTexturePacks() { if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - ResourceDownload::TexturePackDownloadDialog mdownload(this, std::static_pointer_cast(m_model), m_instance); + ResourceDownload::TexturePackDownloadDialog mdownload(this, m_model, m_instance); if (mdownload.exec()) { - auto tasks = - new ConcurrentTask(this, "Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + auto tasks = new ConcurrentTask("Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](QString reason) { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::aborted, [this, tasks]() { + CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::succeeded, [this, tasks]() { + QStringList warnings = tasks->warnings(); + if (warnings.count()) + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + + tasks->deleteLater(); + }); + + for (auto& task : mdownload.getTasks()) { + tasks->addTask(task); + } + + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(tasks); + + m_model->update(); + } +} + +void TexturePackPage::updateTexturePacks() +{ + if (m_instance->typeName() != "Minecraft") + return; // this is a null instance or a legacy instance + + auto profile = static_cast(m_instance)->getPackProfile(); + if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { + QMessageBox::critical(this, tr("Error"), tr("Texture pack updates are unavailable when metadata is disabled!")); + return; + } + if (m_instance != nullptr && m_instance->isRunning()) { + auto response = CustomMessageBox::selectable( + this, tr("Confirm Update"), + tr("Updating texture packs while the game is running may cause pack 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) + ->exec(); + + if (response != QMessageBox::Yes) + return; + } + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); + + auto mods_list = m_model->selectedResources(selection); + bool use_all = mods_list.empty(); + if (use_all) + mods_list = m_model->allResources(); + + ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false, false); + update_dialog.checkCandidates(); + + if (update_dialog.aborted()) { + CustomMessageBox::selectable(this, tr("Aborted"), tr("The texture pack updater was aborted!"), QMessageBox::Warning)->show(); + return; + } + if (update_dialog.noUpdates()) { + QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) }; + if (mods_list.size() > 1) { + if (use_all) { + message = tr("All texture packs are up-to-date! :)"); + } else { + message = tr("All selected texture packs are up-to-date! :)"); + } + } + CustomMessageBox::selectable(this, tr("Update checker"), message)->exec(); + return; + } + + if (update_dialog.exec()) { + auto tasks = new ConcurrentTask("Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](QString reason) { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::aborted, [this, tasks]() { + CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::succeeded, [this, tasks]() { + QStringList warnings = tasks->warnings(); + if (warnings.count()) { + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + } + tasks->deleteLater(); + }); + + for (auto task : update_dialog.getTasks()) { + tasks->addTask(task); + } + + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(tasks); + + m_model->update(); + } +} + +void TexturePackPage::deleteTexturePackMetadata() +{ + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); + auto selectionCount = m_model->selectedTexturePacks(selection).length(); + if (selectionCount == 0) + return; + if (selectionCount > 1) { + auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), + tr("You are about to remove the metadata for %1 texture packs.\n" + "Are you sure?") + .arg(selectionCount), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); + + if (response != QMessageBox::Yes) + return; + } + + m_model->deleteMetadata(selection); +} + +void TexturePackPage::changeTexturePackVersion() +{ + if (m_instance->typeName() != "Minecraft") + return; // this is a null instance or a legacy instance + + if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { + QMessageBox::critical(this, tr("Error"), tr("Texture pack updates are unavailable when metadata is disabled!")); + return; + } + + const QModelIndexList rows = ui->treeView->selectionModel()->selectedRows(); + + if (rows.count() != 1) + return; + + Resource& resource = m_model->at(m_filterModel->mapToSource(rows[0]).row()); + + if (resource.metadata() == nullptr) + return; + + ResourceDownload::TexturePackDownloadDialog mdownload(this, m_model, m_instance); + mdownload.setResourceMetadata(resource.metadata()); + if (mdownload.exec()) { + auto tasks = new ConcurrentTask("Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); diff --git a/launcher/ui/pages/instance/TexturePackPage.h b/launcher/ui/pages/instance/TexturePackPage.h index 9c4f24b70..28d7ba209 100644 --- a/launcher/ui/pages/instance/TexturePackPage.h +++ b/launcher/ui/pages/instance/TexturePackPage.h @@ -55,6 +55,12 @@ class TexturePackPage : public ExternalResourcesPage { virtual bool shouldDisplay() const override { return m_instance->traits().contains("texturepacks"); } public slots: - bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override; - void downloadTPs(); + void updateFrame(const QModelIndex& current, const QModelIndex& previous) override; + void downloadTexturePacks(); + void updateTexturePacks(); + void deleteTexturePackMetadata(); + void changeTexturePackVersion(); + + private: + std::shared_ptr m_model; }; diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp index 0c25b4c0c..975c44de2 100644 --- a/launcher/ui/pages/instance/VersionPage.cpp +++ b/launcher/ui/pages/instance/VersionPage.cpp @@ -243,7 +243,7 @@ void VersionPage::updateButtons(int row) ui->actionRemove->setEnabled(patch && patch->isRemovable()); ui->actionMove_down->setEnabled(patch && patch->isMoveable()); ui->actionMove_up->setEnabled(patch && patch->isMoveable()); - ui->actionChange_version->setEnabled(patch && patch->isVersionChangeable()); + ui->actionChange_version->setEnabled(patch && patch->isVersionChangeable(false)); ui->actionEdit->setEnabled(patch && patch->isCustom()); ui->actionCustomize->setEnabled(patch && patch->isCustomizable()); ui->actionRevert->setEnabled(patch && patch->isRevertible()); @@ -252,8 +252,11 @@ void VersionPage::updateButtons(int row) bool VersionPage::reloadPackProfile() { try { - m_profile->reload(Net::Mode::Online); - return true; + auto result = m_profile->reload(Net::Mode::Online); + if (!result) { + QMessageBox::critical(this, tr("Error"), result.error); + } + return result; } catch (const Exception& e) { QMessageBox::critical(this, tr("Error"), e.cause()); return false; @@ -435,7 +438,7 @@ void VersionPage::on_actionDownload_All_triggered() if (updateTasks.isEmpty()) { return; } - auto task = makeShared(this); + auto task = makeShared(); for (auto t : updateTasks) { task->addTask(t); } diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 1f0329321..cfc262b62 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -39,7 +39,9 @@ ResourceAPI::SearchArgs ModModel::createSearchArguments() auto sort = getCurrentSortingMethodByIndex(); - return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, loaders, versions, side, categories }; + return { + ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, loaders, versions, side, categories, m_filter->openSource + }; } ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& entry) diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index c9817cdf7..f0cc2df54 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -99,7 +99,7 @@ void ModPage::triggerSearch() updateSelectionButton(); static_cast(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentData().toUInt(), changed); - m_fetch_progress.watch(m_model->activeSearchJob().get()); + m_fetchProgress.watch(m_model->activeSearchJob().get()); } QMap ModPage::urlHandlers() const diff --git a/launcher/ui/pages/modplatform/OptionalModDialog.cpp b/launcher/ui/pages/modplatform/OptionalModDialog.cpp index fc1c8b3cb..5dc53d9dc 100644 --- a/launcher/ui/pages/modplatform/OptionalModDialog.cpp +++ b/launcher/ui/pages/modplatform/OptionalModDialog.cpp @@ -43,6 +43,9 @@ OptionalModDialog::OptionalModDialog(QWidget* parent, const QStringList& mods) : else item->setCheckState(Qt::Checked); }); + + ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); + ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK")); } OptionalModDialog::~OptionalModDialog() diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index 50a170fff..6b8309fb7 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -337,7 +337,7 @@ std::optional ResourceModel::getIcon(QModelIndex& index, const QUrl& url) auto icon_fetch_action = Net::ApiDownload::makeCached(url, cache_entry); auto full_file_path = cache_entry->getFullPath(); - connect(icon_fetch_action.get(), &Task::succeeded, this, [=] { + connect(icon_fetch_action.get(), &Task::succeeded, this, [this, url, full_file_path, index] { auto icon = QIcon(full_file_path); QPixmapCache::insert(url.toString(), icon.pixmap(icon.actualSize({ 64, 64 }))); @@ -345,7 +345,7 @@ std::optional ResourceModel::getIcon(QModelIndex& index, const QUrl& url) emit dataChanged(index, index, { Qt::DecorationRole }); }); - connect(icon_fetch_action.get(), &Task::failed, this, [=] { + connect(icon_fetch_action.get(), &Task::failed, this, [this, url] { m_currently_running_icon_actions.remove(url); m_failed_icon_actions.insert(url); }); diff --git a/launcher/ui/pages/modplatform/ResourcePackPage.cpp b/launcher/ui/pages/modplatform/ResourcePackPage.cpp index 849ea1111..99039476e 100644 --- a/launcher/ui/pages/modplatform/ResourcePackPage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePackPage.cpp @@ -30,7 +30,7 @@ void ResourcePackResourcePage::triggerSearch() updateSelectionButton(); static_cast(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentData().toUInt()); - m_fetch_progress.watch(m_model->activeSearchJob().get()); + m_fetchProgress.watch(m_model->activeSearchJob().get()); } QMap ResourcePackResourcePage::urlHandlers() const diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index dea28d6d5..2dd5ccf0f 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -39,14 +39,16 @@ #include "ResourcePage.h" #include "modplatform/ModIndex.h" +#include "ui/dialogs/CustomMessageBox.h" #include "ui_ResourcePage.h" +#include #include #include #include "Markdown.h" -#include "StringUtils.h" +#include "Application.h" #include "ui/dialogs/ResourceDownloadDialog.h" #include "ui/pages/modplatform/ResourceModel.h" #include "ui/widgets/ProjectItem.h" @@ -54,7 +56,7 @@ namespace ResourceDownload { ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& base_instance) - : QWidget(parent), m_base_instance(base_instance), m_ui(new Ui::ResourcePage), m_parent_dialog(parent), m_fetch_progress(this, false) + : QWidget(parent), m_baseInstance(base_instance), m_ui(new Ui::ResourcePage), m_parentDialog(parent), m_fetchProgress(this, false) { m_ui->setupUi(this); @@ -63,18 +65,18 @@ ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& base_in m_ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); m_ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); - m_search_timer.setTimerType(Qt::TimerType::CoarseTimer); - m_search_timer.setSingleShot(true); + m_searchTimer.setTimerType(Qt::TimerType::CoarseTimer); + m_searchTimer.setSingleShot(true); - connect(&m_search_timer, &QTimer::timeout, this, &ResourcePage::triggerSearch); + connect(&m_searchTimer, &QTimer::timeout, this, &ResourcePage::triggerSearch); // hide progress bar to prevent weird artifact - m_fetch_progress.hide(); - m_fetch_progress.hideIfInactive(true); - m_fetch_progress.setFixedHeight(24); - m_fetch_progress.progressFormat(""); + m_fetchProgress.hide(); + m_fetchProgress.hideIfInactive(true); + m_fetchProgress.setFixedHeight(24); + m_fetchProgress.progressFormat(""); - m_ui->verticalLayout->insertWidget(1, &m_fetch_progress); + m_ui->verticalLayout->insertWidget(1, &m_fetchProgress); m_ui->packView->setItemDelegate(new ProjectItemDelegate(this)); m_ui->packView->installEventFilter(this); @@ -120,10 +122,10 @@ auto ResourcePage::eventFilter(QObject* watched, QEvent* event) -> bool keyEvent->accept(); return true; } else { - if (m_search_timer.isActive()) - m_search_timer.stop(); + if (m_searchTimer.isActive()) + m_searchTimer.stop(); - m_search_timer.start(350); + m_searchTimer.start(350); } } else if (watched == m_ui->packView) { if (keyEvent->key() == Qt::Key_Return) { @@ -247,7 +249,7 @@ void ResourcePage::updateUi() void ResourcePage::updateSelectionButton() { - if (!isOpened || m_selected_version_index < 0) { + if (!isOpened || m_selectedVersionIndex < 0) { m_ui->resourceSelectionButton->setEnabled(false); return; } @@ -257,7 +259,7 @@ void ResourcePage::updateSelectionButton() if (current_pack->versionsLoaded && current_pack->versions.empty()) { m_ui->resourceSelectionButton->setEnabled(false); qWarning() << tr("No version available for the selected pack"); - } else if (!current_pack->isVersionSelected(m_selected_version_index)) + } else if (!current_pack->isVersionSelected(m_selectedVersionIndex)) m_ui->resourceSelectionButton->setText(tr("Select %1 for download").arg(resourceString())); else m_ui->resourceSelectionButton->setText(tr("Deselect %1 for download").arg(resourceString())); @@ -282,11 +284,15 @@ void ResourcePage::updateVersionList() if (!m_model->checkVersionFilters(version)) continue; - auto release_type = current_pack->versions[i].version_type.isValid() - ? QString(" [%1]").arg(current_pack->versions[i].version_type.toString()) - : ""; + auto versionText = version.version; + if (version.version_type.isValid()) { + versionText += QString(" [%1]").arg(version.version_type.toString()); + } + if (version.fileId == installedVersion) { + versionText += tr(" [installed]", "Mod version select"); + } - m_ui->versionSelectionBox->addItem(QString("%1%2").arg(version.version, release_type), QVariant(i)); + m_ui->versionSelectionBox->addItem(versionText, QVariant(i)); } } if (m_ui->versionSelectionBox->count() == 0) { @@ -326,25 +332,26 @@ void ResourcePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI void ResourcePage::onVersionSelectionChanged(int index) { - m_selected_version_index = index; + m_selectedVersionIndex = m_ui->versionSelectionBox->itemData(index).toInt(); updateSelectionButton(); } void ResourcePage::addResourceToDialog(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version) { - m_parent_dialog->addResource(pack, version); + m_parentDialog->addResource(pack, version); } void ResourcePage::removeResourceFromDialog(const QString& pack_name) { - m_parent_dialog->removeResource(pack_name); + m_parentDialog->removeResource(pack_name); } void ResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& ver, const std::shared_ptr base_model) { - m_model->addPack(pack, ver, base_model); + bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + m_model->addPack(pack, ver, base_model, is_indexed); } void ResourcePage::removeResourceFromPage(const QString& name) @@ -354,14 +361,15 @@ void ResourcePage::removeResourceFromPage(const QString& name) void ResourcePage::onResourceSelected() { - if (m_selected_version_index < 0) + if (m_selectedVersionIndex < 0) return; auto current_pack = getCurrentPack(); - if (!current_pack || !current_pack->versionsLoaded) + if (!current_pack || !current_pack->versionsLoaded || current_pack->versions.size() < m_selectedVersionIndex) return; - auto& version = current_pack->versions[m_selected_version_index]; + auto& version = current_pack->versions[m_selectedVersionIndex]; + Q_ASSERT(!version.downloadUrl.isNull()); if (version.is_currently_selected) removeResourceFromDialog(current_pack->name); else @@ -400,14 +408,14 @@ void ResourcePage::openUrl(const QUrl& url) } } - if (!page.isNull() && !m_do_not_jump_to_mod) { + if (!page.isNull() && !m_doNotJumpToMod) { const QString slug = match.captured(1); // ensure the user isn't opening the same mod if (auto current_pack = getCurrentPack(); current_pack && slug != current_pack->slug) { - m_parent_dialog->selectPage(page); + m_parentDialog->selectPage(page); - auto newPage = m_parent_dialog->selectedPage(); + auto newPage = m_parentDialog->selectedPage(); QLineEdit* searchEdit = newPage->m_ui->searchEdit; auto model = newPage->m_model; @@ -451,7 +459,7 @@ void ResourcePage::openProject(QVariant projectID) m_ui->resourceFilterButton->hide(); m_ui->packView->hide(); m_ui->resourceSelectionButton->hide(); - m_do_not_jump_to_mod = true; + m_doNotJumpToMod = true; auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); @@ -465,20 +473,23 @@ void ResourcePage::openProject(QVariant projectID) auto cancelBtn = buttonBox->button(QDialogButtonBox::Cancel); cancelBtn->setDefault(false); cancelBtn->setAutoDefault(false); + cancelBtn->setText(tr("Cancel")); connect(okBtn, &QPushButton::clicked, this, [this] { onResourceSelected(); - m_parent_dialog->accept(); + m_parentDialog->accept(); }); - connect(cancelBtn, &QPushButton::clicked, m_parent_dialog, &ResourceDownloadDialog::reject); + connect(cancelBtn, &QPushButton::clicked, m_parentDialog, &ResourceDownloadDialog::reject); m_ui->gridLayout_4->addWidget(buttonBox, 1, 2); - auto jump = [this, okBtn] { + connect(m_ui->versionSelectionBox, QOverload::of(&QComboBox::currentIndexChanged), this, + [this, okBtn](int index) { okBtn->setEnabled(m_ui->versionSelectionBox->itemData(index).toInt() >= 0); }); + + auto jump = [this] { for (int row = 0; row < m_model->rowCount({}); row++) { const QModelIndex index = m_model->index(row); m_ui->packView->setCurrentIndex(index); - okBtn->setEnabled(true); return; } m_ui->packDescription->setText(tr("The resource was not found")); diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index b625240eb..09c512df4 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -62,7 +62,7 @@ class ResourcePage : public QWidget, public BasePage { [[nodiscard]] bool setCurrentPack(ModPlatform::IndexedPack::Ptr); [[nodiscard]] auto getCurrentPack() const -> ModPlatform::IndexedPack::Ptr; - [[nodiscard]] auto getDialog() const -> const ResourceDownloadDialog* { return m_parent_dialog; } + [[nodiscard]] auto getDialog() const -> const ResourceDownloadDialog* { return m_parentDialog; } [[nodiscard]] auto getModel() const -> ResourceModel* { return m_model; } protected: @@ -99,22 +99,22 @@ class ResourcePage : public QWidget, public BasePage { virtual void openUrl(const QUrl&); public: - BaseInstance& m_base_instance; + BaseInstance& m_baseInstance; protected: Ui::ResourcePage* m_ui; - ResourceDownloadDialog* m_parent_dialog = nullptr; + ResourceDownloadDialog* m_parentDialog = nullptr; ResourceModel* m_model = nullptr; - int m_selected_version_index = -1; + int m_selectedVersionIndex = -1; - ProgressWidget m_fetch_progress; + ProgressWidget m_fetchProgress; // Used to do instant searching with a delay to cache quick changes - QTimer m_search_timer; + QTimer m_searchTimer; - bool m_do_not_jump_to_mod = false; + bool m_doNotJumpToMod = false; }; } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.cpp b/launcher/ui/pages/modplatform/ShaderPackPage.cpp index ebd8d4ea2..08acf361a 100644 --- a/launcher/ui/pages/modplatform/ShaderPackPage.cpp +++ b/launcher/ui/pages/modplatform/ShaderPackPage.cpp @@ -8,6 +8,7 @@ #include "ShaderPackModel.h" +#include "Application.h" #include "ui/dialogs/ResourceDownloadDialog.h" #include @@ -31,7 +32,7 @@ void ShaderPackResourcePage::triggerSearch() updateSelectionButton(); static_cast(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentData().toUInt()); - m_fetch_progress.watch(m_model->activeSearchJob().get()); + m_fetchProgress.watch(m_model->activeSearchJob().get()); } QMap ShaderPackResourcePage::urlHandlers() const @@ -48,10 +49,11 @@ void ShaderPackResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pac ModPlatform::IndexedVersion& version, const std::shared_ptr base_model) { + bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); QString custom_target_folder; if (version.loaders & ModPlatform::Cauldron) custom_target_folder = QStringLiteral("resourcepacks"); - m_model->addPack(pack, version, base_model, false, custom_target_folder); + m_model->addPack(pack, version, base_model, is_indexed, custom_target_folder); } } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp index cfdb185ff..18a2adc49 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp @@ -189,7 +189,7 @@ void ListModel::performPaginatedSearch() auto netJob = makeShared("Flame::Search", APPLICATION->network()); auto searchUrl = FlameAPI().getSearchURL({ ModPlatform::ResourceType::MODPACK, nextSearchOffset, currentSearchTerm, sort, - m_filter->loaders, m_filter->versions, "", m_filter->categoryIds }); + m_filter->loaders, m_filter->versions, "", m_filter->categoryIds, m_filter->openSource }); netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl.value()), response)); jobPtr = netJob; diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index 9abf4a9c6..de6b3d633 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -273,7 +273,7 @@ void FlamePage::suggestCurrent() void FlamePage::onVersionSelectionChanged(int index) { bool is_blocked = false; - ui->versionSelectionBox->currentData().toInt(&is_blocked); + ui->versionSelectionBox->itemData(index).toInt(&is_blocked); if (index == -1 || is_blocked) { m_selected_version_index = -1; diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.ui b/launcher/ui/pages/modplatform/flame/FlamePage.ui index b028e7569..cf882ef1c 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.ui +++ b/launcher/ui/pages/modplatform/flame/FlamePage.ui @@ -32,16 +32,16 @@ - - - Search and filter... + + + Filter options - - - Filter + + + Search and filter... diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index ce8c03fa1..4e01f3a65 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -209,7 +209,7 @@ auto FlameShaderPackPage::shouldDisplay() const -> bool unique_qobject_ptr FlameModPage::createFilterWidget() { - return ModFilterWidget::create(&static_cast(m_base_instance), false, this); + return ModFilterWidget::create(&static_cast(m_baseInstance), false, this); } void FlameModPage::prepareProviderCategories() diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui index 18c604ca4..337c3e474 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui +++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui @@ -13,6 +13,11 @@ + + + true + + Note: If your FTB instances are not in the default location, select it using the button next to search. diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 417ff4080..416c69d28 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -155,7 +155,7 @@ void ModpackListModel::performPaginatedSearch() ResourceAPI::SortingMethod sort{}; sort.name = currentSort; auto searchUrl = ModrinthAPI().getSearchURL({ ModPlatform::ResourceType::MODPACK, nextSearchOffset, currentSearchTerm, sort, - m_filter->loaders, m_filter->versions, "", m_filter->categoryIds }); + m_filter->loaders, m_filter->versions, "", m_filter->categoryIds, m_filter->openSource }); auto netJob = makeShared("Modrinth::SearchModpack", APPLICATION->network()); netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl.value()), m_allResponse)); diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index 8803c6dd9..7d70abec4 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -375,7 +375,7 @@ void ModrinthPage::onVersionSelectionChanged(int index) selectedVersion = ""; return; } - selectedVersion = ui->versionSelectionBox->currentData().toString(); + selectedVersion = ui->versionSelectionBox->itemData(index).toString(); suggestCurrent(); } diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui index ef44abb52..d6e983929 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui @@ -14,16 +14,16 @@ - - - Search and filter... + + + Filter options - - - Filter + + + Search and filter... diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index 85dcde471..4ee620677 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -144,7 +144,7 @@ auto ModrinthShaderPackPage::shouldDisplay() const -> bool unique_qobject_ptr ModrinthModPage::createFilterWidget() { - return ModFilterWidget::create(&static_cast(m_base_instance), true, this); + return ModFilterWidget::create(&static_cast(m_baseInstance), true, this); } void ModrinthModPage::prepareProviderCategories() diff --git a/launcher/ui/setupwizard/JavaWizardPage.cpp b/launcher/ui/setupwizard/JavaWizardPage.cpp index 8caae173c..6b8ece9f7 100644 --- a/launcher/ui/setupwizard/JavaWizardPage.cpp +++ b/launcher/ui/setupwizard/JavaWizardPage.cpp @@ -14,7 +14,7 @@ #include "JavaCommon.h" -#include "ui/widgets/JavaSettingsWidget.h" +#include "ui/widgets/JavaWizardWidget.h" #include "ui/widgets/VersionSelectWidget.h" JavaWizardPage::JavaWizardPage(QWidget* parent) : BaseWizardPage(parent) @@ -27,7 +27,7 @@ void JavaWizardPage::setupUi() setObjectName(QStringLiteral("javaPage")); QVBoxLayout* layout = new QVBoxLayout(this); - m_java_widget = new JavaSettingsWidget(this); + m_java_widget = new JavaWizardWidget(this); layout->addWidget(m_java_widget); setLayout(layout); @@ -58,13 +58,13 @@ bool JavaWizardPage::validatePage() settings->set("UserAskedAboutAutomaticJavaDownload", true); switch (result) { default: - case JavaSettingsWidget::ValidationStatus::Bad: { + case JavaWizardWidget::ValidationStatus::Bad: { return false; } - case JavaSettingsWidget::ValidationStatus::AllOK: { + case JavaWizardWidget::ValidationStatus::AllOK: { settings->set("JavaPath", m_java_widget->javaPath()); } /* fallthrough */ - case JavaSettingsWidget::ValidationStatus::JavaBad: { + case JavaWizardWidget::ValidationStatus::JavaBad: { // Memory auto s = APPLICATION->settings(); s->set("MinMemAlloc", m_java_widget->minHeapSize()); diff --git a/launcher/ui/setupwizard/JavaWizardPage.h b/launcher/ui/setupwizard/JavaWizardPage.h index 9bf9cfa2b..914630d0b 100644 --- a/launcher/ui/setupwizard/JavaWizardPage.h +++ b/launcher/ui/setupwizard/JavaWizardPage.h @@ -2,7 +2,7 @@ #include "BaseWizardPage.h" -class JavaSettingsWidget; +class JavaWizardWidget; class JavaWizardPage : public BaseWizardPage { Q_OBJECT @@ -21,5 +21,5 @@ class JavaWizardPage : public BaseWizardPage { void retranslate() override; private: /* data */ - JavaSettingsWidget* m_java_widget = nullptr; + JavaWizardWidget* m_java_widget = nullptr; }; diff --git a/launcher/ui/setupwizard/SetupWizard.cpp b/launcher/ui/setupwizard/SetupWizard.cpp index 4e5bd1dca..f2e51ee41 100644 --- a/launcher/ui/setupwizard/SetupWizard.cpp +++ b/launcher/ui/setupwizard/SetupWizard.cpp @@ -57,7 +57,7 @@ void SetupWizard::pageChanged(int id) if (basePagePtr->wantsRefreshButton()) { setButtonLayout({ QWizard::CustomButton1, QWizard::Stretch, QWizard::BackButton, QWizard::NextButton, QWizard::FinishButton }); auto customButton = button(QWizard::CustomButton1); - connect(customButton, &QAbstractButton::clicked, [&]() { + connect(customButton, &QAbstractButton::clicked, [this]() { auto basePagePtr = getCurrentBasePage(); if (basePagePtr) { basePagePtr->refresh(); diff --git a/launcher/ui/themes/CustomTheme.cpp b/launcher/ui/themes/CustomTheme.cpp index 081ba1900..b8c5738b7 100644 --- a/launcher/ui/themes/CustomTheme.cpp +++ b/launcher/ui/themes/CustomTheme.cpp @@ -181,7 +181,7 @@ bool CustomTheme::read(const QString& path, bool& hasCustomLogColors) m_widgets = Json::requireString(root, "widgets", "Qt widget theme"); m_qssFilePath = Json::ensureString(root, "qssFilePath", "themeStyle.css"); - auto readColor = [&](const QJsonObject& colors, const QString& colorName) -> QColor { + auto readColor = [](const QJsonObject& colors, const QString& colorName) -> QColor { auto colorValue = Json::ensureString(colors, colorName, QString()); if (!colorValue.isEmpty()) { QColor color(colorValue); @@ -196,7 +196,7 @@ bool CustomTheme::read(const QString& path, bool& hasCustomLogColors) if (root.contains("colors")) { auto colorsRoot = Json::requireObject(root, "colors"); - auto readAndSetPaletteColor = [&](QPalette::ColorRole role, const QString& colorName) { + auto readAndSetPaletteColor = [this, readColor, colorsRoot](QPalette::ColorRole role, const QString& colorName) { auto color = readColor(colorsRoot, colorName); if (color.isValid()) { m_palette.setColor(role, color); @@ -229,7 +229,7 @@ bool CustomTheme::read(const QString& path, bool& hasCustomLogColors) hasCustomLogColors = true; auto logColorsRoot = Json::requireObject(root, "logColors"); - auto readAndSetLogColor = [&](MessageLevel::Enum level, bool fg, const QString& colorName) { + auto readAndSetLogColor = [this, readColor, logColorsRoot](MessageLevel::Enum level, bool fg, const QString& colorName) { auto color = readColor(logColorsRoot, colorName); if (color.isValid()) { if (fg) diff --git a/launcher/ui/themes/ThemeManager.cpp b/launcher/ui/themes/ThemeManager.cpp index 691a51668..30a1fe7be 100644 --- a/launcher/ui/themes/ThemeManager.cpp +++ b/launcher/ui/themes/ThemeManager.cpp @@ -36,6 +36,9 @@ ThemeManager::ThemeManager() { + QIcon::setFallbackThemeName(QIcon::themeName()); + QIcon::setThemeSearchPaths(QIcon::themeSearchPaths() << m_iconThemeFolder.path()); + themeDebugLog() << "Determining System Widget Theme..."; const auto& style = QApplication::style(); m_defaultStyle = style->objectName(); @@ -93,10 +96,6 @@ void ThemeManager::initializeIcons() // set icon theme search path! themeDebugLog() << "<> Initializing Icon Themes"; - auto searchPaths = QIcon::themeSearchPaths(); - searchPaths.append(m_iconThemeFolder.path()); - QIcon::setThemeSearchPaths(searchPaths); - for (const QString& id : builtinIcons) { IconTheme theme(id, QString(":/icons/%1").arg(id)); if (!theme.load()) { @@ -348,4 +347,4 @@ void ThemeManager::refresh() initializeThemes(); initializeCatPacks(); -} \ No newline at end of file +} diff --git a/launcher/ui/widgets/CheckComboBox.cpp b/launcher/ui/widgets/CheckComboBox.cpp index 41def3ba1..02b629162 100644 --- a/launcher/ui/widgets/CheckComboBox.cpp +++ b/launcher/ui/widgets/CheckComboBox.cpp @@ -40,7 +40,7 @@ class CheckComboModel : public QIdentityProxyModel { { if (role == Qt::CheckStateRole) { auto txt = QIdentityProxyModel::data(index, Qt::DisplayRole).toString(); - return checked.contains(txt) ? Qt::Checked : Qt::Unchecked; + return m_checked.contains(txt) ? Qt::Checked : Qt::Unchecked; } if (role == Qt::DisplayRole) return QIdentityProxyModel::data(index, Qt::DisplayRole); @@ -50,10 +50,10 @@ class CheckComboModel : public QIdentityProxyModel { { if (role == Qt::CheckStateRole) { auto txt = QIdentityProxyModel::data(index, Qt::DisplayRole).toString(); - if (checked.contains(txt)) { - checked.removeOne(txt); + if (m_checked.contains(txt)) { + m_checked.removeOne(txt); } else { - checked.push_back(txt); + m_checked.push_back(txt); } emit dataChanged(index, index); emit checkStateChanged(); @@ -61,13 +61,13 @@ class CheckComboModel : public QIdentityProxyModel { } return QIdentityProxyModel::setData(index, value, role); } - QStringList getChecked() { return checked; } + QStringList getChecked() { return m_checked; } signals: void checkStateChanged(); private: - QStringList checked; + QStringList m_checked; }; CheckComboBox::CheckComboBox(QWidget* parent) : QComboBox(parent), m_separator(", ") @@ -92,7 +92,7 @@ void CheckComboBox::setSourceModel(QAbstractItemModel* new_model) void CheckComboBox::hidePopup() { - if (!containerMousePress) + if (!m_containerMousePress) QComboBox::hidePopup(); } @@ -138,7 +138,7 @@ bool CheckComboBox::eventFilter(QObject* receiver, QEvent* event) } case QEvent::MouseButtonPress: { auto ev = static_cast(event); - containerMousePress = ev && view()->indexAt(ev->pos()).isValid(); + m_containerMousePress = ev && view()->indexAt(ev->pos()).isValid(); break; } case QEvent::Wheel: diff --git a/launcher/ui/widgets/CheckComboBox.h b/launcher/ui/widgets/CheckComboBox.h index 876c6e3e1..469587762 100644 --- a/launcher/ui/widgets/CheckComboBox.h +++ b/launcher/ui/widgets/CheckComboBox.h @@ -60,5 +60,5 @@ class CheckComboBox : public QComboBox { private: QString m_default_text; QString m_separator; - bool containerMousePress; + bool m_containerMousePress = false; }; \ No newline at end of file diff --git a/launcher/ui/widgets/DropLabel.cpp b/launcher/ui/widgets/DropLabel.cpp deleted file mode 100644 index b1473b358..000000000 --- a/launcher/ui/widgets/DropLabel.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "DropLabel.h" - -#include -#include - -DropLabel::DropLabel(QWidget* parent) : QLabel(parent) -{ - setAcceptDrops(true); -} - -void DropLabel::dragEnterEvent(QDragEnterEvent* event) -{ - event->acceptProposedAction(); -} - -void DropLabel::dragMoveEvent(QDragMoveEvent* event) -{ - event->acceptProposedAction(); -} - -void DropLabel::dragLeaveEvent(QDragLeaveEvent* event) -{ - event->accept(); -} - -void DropLabel::dropEvent(QDropEvent* event) -{ - const QMimeData* mimeData = event->mimeData(); - - if (!mimeData) { - return; - } - - if (mimeData->hasUrls()) { - auto urls = mimeData->urls(); - emit droppedURLs(urls); - } - - event->acceptProposedAction(); -} diff --git a/launcher/ui/widgets/DropLabel.h b/launcher/ui/widgets/DropLabel.h deleted file mode 100644 index 0027f48b1..000000000 --- a/launcher/ui/widgets/DropLabel.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include - -class DropLabel : public QLabel { - Q_OBJECT - - public: - explicit DropLabel(QWidget* parent = nullptr); - - signals: - void droppedURLs(QList urls); - - protected: - void dropEvent(QDropEvent* event) override; - void dragEnterEvent(QDragEnterEvent* event) override; - void dragMoveEvent(QDragMoveEvent* event) override; - void dragLeaveEvent(QDragLeaveEvent* event) override; -}; diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp index 44f702659..3ef5dcb88 100644 --- a/launcher/ui/widgets/InfoFrame.cpp +++ b/launcher/ui/widgets/InfoFrame.cpp @@ -84,7 +84,7 @@ void InfoFrame::updateWithMod(Mod const& m) QString text = ""; QString name = ""; - QString link = m.metaurl(); + QString link = m.homepage(); if (m.name().isEmpty()) name = m.internal_id(); else @@ -93,7 +93,7 @@ void InfoFrame::updateWithMod(Mod const& m) if (link.isEmpty()) text = name; else { - text = "" + name + ""; + text = "" + name + ""; } if (!m.authors().isEmpty()) text += " by " + m.authors().join(", "); @@ -145,7 +145,13 @@ void InfoFrame::updateWithMod(Mod const& m) void InfoFrame::updateWithResource(const Resource& resource) { - setName(resource.name()); + const QString homepage = resource.homepage(); + + if (!homepage.isEmpty()) + setName("" + resource.name() + ""); + else + setName(resource.name()); + setImage(); } @@ -209,14 +215,28 @@ QString InfoFrame::renderColorCodes(QString input) void InfoFrame::updateWithResourcePack(ResourcePack& resource_pack) { - setName(renderColorCodes(resource_pack.name())); + QString name = renderColorCodes(resource_pack.name()); + + const QString homepage = resource_pack.homepage(); + if (!homepage.isEmpty()) { + name = "" + name + ""; + } + + setName(name); setDescription(renderColorCodes(resource_pack.description())); setImage(resource_pack.image({ 64, 64 })); } void InfoFrame::updateWithTexturePack(TexturePack& texture_pack) { - setName(renderColorCodes(texture_pack.name())); + QString name = renderColorCodes(texture_pack.name()); + + const QString homepage = texture_pack.homepage(); + if (!homepage.isEmpty()) { + name = "" + name + ""; + } + + setName(name); setDescription(renderColorCodes(texture_pack.description())); setImage(texture_pack.image({ 64, 64 })); } diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index 2b270c482..a255168e9 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -1,559 +1,314 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2022 Jamie Mansfield + * Copyright (C) 2022 Sefa Eyeoglu + * 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. + */ + #include "JavaSettingsWidget.h" #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "DesktopServices.h" -#include "FileSystem.h" -#include "JavaCommon.h" -#include "java/JavaChecker.h" -#include "java/JavaInstall.h" -#include "java/JavaInstallList.h" -#include "java/JavaUtils.h" - -#include "ui/dialogs/CustomMessageBox.h" -#include "ui/java/InstallJavaDialog.h" -#include "ui/widgets/VersionSelectWidget.h" - +#include #include "Application.h" #include "BuildConfig.h" +#include "FileSystem.h" +#include "JavaCommon.h" +#include "java/JavaInstallList.h" +#include "java/JavaUtils.h" +#include "settings/Setting.h" +#include "sys.h" +#include "ui/dialogs/CustomMessageBox.h" +#include "ui/dialogs/VersionSelectDialog.h" +#include "ui/java/InstallJavaDialog.h" -JavaSettingsWidget::JavaSettingsWidget(QWidget* parent) : QWidget(parent) +#include "ui_JavaSettingsWidget.h" + +JavaSettingsWidget::JavaSettingsWidget(InstancePtr instance, QWidget* parent) + : QWidget(parent), m_instance(std::move(instance)), m_ui(new Ui::JavaSettingsWidget) { - m_availableMemory = Sys::getSystemRam() / Sys::mebibyte; + m_ui->setupUi(this); - goodIcon = APPLICATION->getThemedIcon("status-good"); - yellowIcon = APPLICATION->getThemedIcon("status-yellow"); - badIcon = APPLICATION->getThemedIcon("status-bad"); - m_memoryTimer = new QTimer(this); - setupUi(); + if (m_instance == nullptr) { + m_ui->javaDownloadBtn->hide(); + if (BuildConfig.JAVA_DOWNLOADER_ENABLED) { + connect(m_ui->autodetectJavaCheckBox, &QCheckBox::stateChanged, this, [this](bool state) { + m_ui->autodownloadJavaCheckBox->setEnabled(state); + if (!state) + m_ui->autodownloadJavaCheckBox->setChecked(false); + }); + } else { + m_ui->autodownloadJavaCheckBox->hide(); + } + } else { + m_ui->javaDownloadBtn->setVisible(BuildConfig.JAVA_DOWNLOADER_ENABLED); + m_ui->skipWizardCheckBox->hide(); + m_ui->autodetectJavaCheckBox->hide(); + m_ui->autodownloadJavaCheckBox->hide(); - connect(m_minMemSpinBox, SIGNAL(valueChanged(int)), this, SLOT(onSpinBoxValueChanged(int))); - connect(m_maxMemSpinBox, SIGNAL(valueChanged(int)), this, SLOT(onSpinBoxValueChanged(int))); - connect(m_permGenSpinBox, SIGNAL(valueChanged(int)), this, SLOT(onSpinBoxValueChanged(int))); - connect(m_memoryTimer, &QTimer::timeout, this, &JavaSettingsWidget::memoryValueChanged); - connect(m_versionWidget, &VersionSelectWidget::selectedVersionChanged, this, &JavaSettingsWidget::javaVersionSelected); - connect(m_javaBrowseBtn, &QPushButton::clicked, this, &JavaSettingsWidget::on_javaBrowseBtn_clicked); - connect(m_javaPathTextBox, &QLineEdit::textEdited, this, &JavaSettingsWidget::javaPathEdited); - connect(m_javaStatusBtn, &QToolButton::clicked, this, &JavaSettingsWidget::on_javaStatusBtn_clicked); - if (BuildConfig.JAVA_DOWNLOADER_ENABLED) { - connect(m_javaDownloadBtn, &QPushButton::clicked, this, &JavaSettingsWidget::javaDownloadBtn_clicked); - } -} + m_ui->javaInstallationGroupBox->setCheckable(true); + m_ui->memoryGroupBox->setCheckable(true); + m_ui->javaArgumentsGroupBox->setCheckable(true); -void JavaSettingsWidget::setupUi() -{ - setObjectName(QStringLiteral("javaSettingsWidget")); - m_verticalLayout = new QVBoxLayout(this); - m_verticalLayout->setObjectName(QStringLiteral("verticalLayout")); + SettingsObjectPtr settings = m_instance->settings(); - m_versionWidget = new VersionSelectWidget(this); + connect(settings->getSetting("OverrideJavaLocation").get(), &Setting::SettingChanged, m_ui->javaInstallationGroupBox, + [this, settings] { m_ui->javaInstallationGroupBox->setChecked(settings->get("OverrideJavaLocation").toBool()); }); + connect(settings->getSetting("JavaPath").get(), &Setting::SettingChanged, m_ui->javaInstallationGroupBox, + [this, settings] { m_ui->javaPathTextBox->setText(settings->get("JavaPath").toString()); }); - m_horizontalLayout = new QHBoxLayout(); - m_horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); - m_javaPathTextBox = new QLineEdit(this); - m_javaPathTextBox->setObjectName(QStringLiteral("javaPathTextBox")); - - m_horizontalLayout->addWidget(m_javaPathTextBox); - - m_javaBrowseBtn = new QPushButton(this); - m_javaBrowseBtn->setObjectName(QStringLiteral("javaBrowseBtn")); - - m_horizontalLayout->addWidget(m_javaBrowseBtn); - - m_javaStatusBtn = new QToolButton(this); - m_javaStatusBtn->setIcon(yellowIcon); - m_horizontalLayout->addWidget(m_javaStatusBtn); - - m_memoryGroupBox = new QGroupBox(this); - m_memoryGroupBox->setObjectName(QStringLiteral("memoryGroupBox")); - m_gridLayout_2 = new QGridLayout(m_memoryGroupBox); - m_gridLayout_2->setObjectName(QStringLiteral("gridLayout_2")); - m_gridLayout_2->setColumnStretch(0, 1); - - m_labelMinMem = new QLabel(m_memoryGroupBox); - m_labelMinMem->setObjectName(QStringLiteral("labelMinMem")); - m_gridLayout_2->addWidget(m_labelMinMem, 0, 0, 1, 1); - - m_minMemSpinBox = new QSpinBox(m_memoryGroupBox); - m_minMemSpinBox->setObjectName(QStringLiteral("minMemSpinBox")); - m_minMemSpinBox->setSuffix(QStringLiteral(" MiB")); - m_minMemSpinBox->setMinimum(8); - m_minMemSpinBox->setMaximum(1048576); - m_minMemSpinBox->setSingleStep(128); - m_labelMinMem->setBuddy(m_minMemSpinBox); - m_gridLayout_2->addWidget(m_minMemSpinBox, 0, 1, 1, 1); - - m_labelMaxMem = new QLabel(m_memoryGroupBox); - m_labelMaxMem->setObjectName(QStringLiteral("labelMaxMem")); - m_gridLayout_2->addWidget(m_labelMaxMem, 1, 0, 1, 1); - - m_maxMemSpinBox = new QSpinBox(m_memoryGroupBox); - m_maxMemSpinBox->setObjectName(QStringLiteral("maxMemSpinBox")); - m_maxMemSpinBox->setSuffix(QStringLiteral(" MiB")); - m_maxMemSpinBox->setMinimum(8); - m_maxMemSpinBox->setMaximum(1048576); - m_maxMemSpinBox->setSingleStep(128); - m_labelMaxMem->setBuddy(m_maxMemSpinBox); - m_gridLayout_2->addWidget(m_maxMemSpinBox, 1, 1, 1, 1); - - m_labelMaxMemIcon = new QLabel(m_memoryGroupBox); - m_labelMaxMemIcon->setObjectName(QStringLiteral("labelMaxMemIcon")); - m_gridLayout_2->addWidget(m_labelMaxMemIcon, 1, 2, 1, 1); - - m_labelPermGen = new QLabel(m_memoryGroupBox); - m_labelPermGen->setObjectName(QStringLiteral("labelPermGen")); - m_labelPermGen->setText(QStringLiteral("PermGen:")); - m_gridLayout_2->addWidget(m_labelPermGen, 2, 0, 1, 1); - m_labelPermGen->setVisible(false); - - m_permGenSpinBox = new QSpinBox(m_memoryGroupBox); - m_permGenSpinBox->setObjectName(QStringLiteral("permGenSpinBox")); - m_permGenSpinBox->setSuffix(QStringLiteral(" MiB")); - m_permGenSpinBox->setMinimum(4); - m_permGenSpinBox->setMaximum(1048576); - m_permGenSpinBox->setSingleStep(8); - m_gridLayout_2->addWidget(m_permGenSpinBox, 2, 1, 1, 1); - m_permGenSpinBox->setVisible(false); - - m_verticalLayout->addWidget(m_memoryGroupBox); - - m_horizontalBtnLayout = new QHBoxLayout(); - m_horizontalBtnLayout->setObjectName(QStringLiteral("horizontalBtnLayout")); - - if (BuildConfig.JAVA_DOWNLOADER_ENABLED) { - m_javaDownloadBtn = new QPushButton(tr("Download Java"), this); - m_horizontalBtnLayout->addWidget(m_javaDownloadBtn); - } - - m_autoJavaGroupBox = new QGroupBox(this); - m_autoJavaGroupBox->setObjectName(QStringLiteral("autoJavaGroupBox")); - m_veriticalJavaLayout = new QVBoxLayout(m_autoJavaGroupBox); - m_veriticalJavaLayout->setObjectName(QStringLiteral("veriticalJavaLayout")); - - m_autodetectJavaCheckBox = new QCheckBox(m_autoJavaGroupBox); - m_autodetectJavaCheckBox->setObjectName("autodetectJavaCheckBox"); - m_autodetectJavaCheckBox->setChecked(true); - m_veriticalJavaLayout->addWidget(m_autodetectJavaCheckBox); - - if (BuildConfig.JAVA_DOWNLOADER_ENABLED) { - m_autodownloadCheckBox = new QCheckBox(m_autoJavaGroupBox); - m_autodownloadCheckBox->setObjectName("autodownloadCheckBox"); - m_autodownloadCheckBox->setEnabled(m_autodetectJavaCheckBox->isChecked()); - m_veriticalJavaLayout->addWidget(m_autodownloadCheckBox); - connect(m_autodetectJavaCheckBox, &QCheckBox::stateChanged, this, [this] { - m_autodownloadCheckBox->setEnabled(m_autodetectJavaCheckBox->isChecked()); - if (!m_autodetectJavaCheckBox->isChecked()) - m_autodownloadCheckBox->setChecked(false); + connect(m_ui->javaDownloadBtn, &QPushButton::clicked, this, [this] { + auto javaDialog = new Java::InstallDialog({}, m_instance.get(), this); + javaDialog->exec(); }); - - connect(m_autodownloadCheckBox, &QCheckBox::stateChanged, this, [this] { - auto isChecked = m_autodownloadCheckBox->isChecked(); - m_versionWidget->setVisible(!isChecked); - m_javaStatusBtn->setVisible(!isChecked); - m_javaBrowseBtn->setVisible(!isChecked); - m_javaPathTextBox->setVisible(!isChecked); - m_javaDownloadBtn->setVisible(!isChecked); - if (!isChecked) { - m_verticalLayout->removeItem(m_verticalSpacer); - } else { - m_verticalLayout->addSpacerItem(m_verticalSpacer); + connect(m_ui->javaPathTextBox, &QLineEdit::textChanged, [this](QString newValue) { + if (m_instance->settings()->get("JavaPath").toString() != newValue) { + m_instance->settings()->set("AutomaticJava", false); } }); } - m_verticalLayout->addWidget(m_autoJavaGroupBox); - m_verticalLayout->addLayout(m_horizontalBtnLayout); + connect(m_ui->javaTestBtn, &QPushButton::clicked, this, &JavaSettingsWidget::onJavaTest); + connect(m_ui->javaDetectBtn, &QPushButton::clicked, this, &JavaSettingsWidget::onJavaAutodetect); + connect(m_ui->javaBrowseBtn, &QPushButton::clicked, this, &JavaSettingsWidget::onJavaBrowse); - m_verticalLayout->addWidget(m_versionWidget); - m_verticalLayout->addLayout(m_horizontalLayout); - m_verticalSpacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); + connect(m_ui->maxMemSpinBox, QOverload::of(&QSpinBox::valueChanged), this, &JavaSettingsWidget::updateThresholds); + connect(m_ui->minMemSpinBox, QOverload::of(&QSpinBox::valueChanged), this, &JavaSettingsWidget::updateThresholds); - retranslate(); -} - -void JavaSettingsWidget::initialize() -{ - m_versionWidget->initialize(APPLICATION->javalist().get()); - m_versionWidget->selectSearch(); - m_versionWidget->setResizeOn(2); - auto s = APPLICATION->settings(); - // Memory - observedMinMemory = s->get("MinMemAlloc").toInt(); - observedMaxMemory = s->get("MaxMemAlloc").toInt(); - observedPermGenMemory = s->get("PermGen").toInt(); - m_minMemSpinBox->setValue(observedMinMemory); - m_maxMemSpinBox->setValue(observedMaxMemory); - m_permGenSpinBox->setValue(observedPermGenMemory); + loadSettings(); updateThresholds(); - if (BuildConfig.JAVA_DOWNLOADER_ENABLED) { - m_autodownloadCheckBox->setChecked(true); - } -} - -void JavaSettingsWidget::refresh() -{ - if (BuildConfig.JAVA_DOWNLOADER_ENABLED && m_autodownloadCheckBox->isChecked()) { - return; - } - if (JavaUtils::getJavaCheckPath().isEmpty()) { - JavaCommon::javaCheckNotFound(this); - return; - } - m_versionWidget->loadList(); -} - -JavaSettingsWidget::ValidationStatus JavaSettingsWidget::validate() -{ - switch (javaStatus) { - default: - case JavaStatus::NotSet: - /* fallthrough */ - case JavaStatus::DoesNotExist: - /* fallthrough */ - case JavaStatus::DoesNotStart: - /* fallthrough */ - case JavaStatus::ReturnedInvalidData: { - if (!(BuildConfig.JAVA_DOWNLOADER_ENABLED && m_autodownloadCheckBox->isChecked())) { // the java will not be autodownloaded - int button = QMessageBox::No; - if (m_result.mojangPlatform == "32" && maxHeapSize() > 2048) { - button = CustomMessageBox::selectable( - this, tr("32-bit Java detected"), - tr("You selected a 32-bit installation of Java, but allocated more than 2048MiB as maximum memory.\n" - "%1 will not be able to start Minecraft.\n" - "Do you wish to proceed?" - "\n\n" - "You can change the Java version in the settings later.\n") - .arg(BuildConfig.LAUNCHER_DISPLAYNAME), - QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No | QMessageBox::Help, QMessageBox::NoButton) - ->exec(); - - } else { - button = CustomMessageBox::selectable(this, tr("No Java version selected"), - tr("You either didn't select a Java version or selected one that does not work.\n" - "%1 will not be able to start Minecraft.\n" - "Do you wish to proceed without a functional version of Java?" - "\n\n" - "You can change the Java version in the settings later.\n") - .arg(BuildConfig.LAUNCHER_DISPLAYNAME), - QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No | QMessageBox::Help, - QMessageBox::NoButton) - ->exec(); - } - switch (button) { - case QMessageBox::Yes: - return ValidationStatus::JavaBad; - case QMessageBox::Help: - DesktopServices::openUrl(QUrl(BuildConfig.HELP_URL.arg("java-wizard"))); - /* fallthrough */ - case QMessageBox::No: - /* fallthrough */ - default: - return ValidationStatus::Bad; - } - if (button == QMessageBox::No) { - return ValidationStatus::Bad; - } - } - return ValidationStatus::JavaBad; - } break; - case JavaStatus::Pending: { - return ValidationStatus::Bad; - } - case JavaStatus::Good: { - return ValidationStatus::AllOK; - } - } -} - -QString JavaSettingsWidget::javaPath() const -{ - return m_javaPathTextBox->text(); -} - -int JavaSettingsWidget::maxHeapSize() const -{ - auto min = m_minMemSpinBox->value(); - auto max = m_maxMemSpinBox->value(); - if (max < min) - max = min; - return max; -} - -int JavaSettingsWidget::minHeapSize() const -{ - auto min = m_minMemSpinBox->value(); - auto max = m_maxMemSpinBox->value(); - if (min > max) - min = max; - return min; -} - -bool JavaSettingsWidget::permGenEnabled() const -{ - return m_permGenSpinBox->isVisible(); -} - -int JavaSettingsWidget::permGenSize() const -{ - return m_permGenSpinBox->value(); -} - -void JavaSettingsWidget::memoryValueChanged() -{ - bool actuallyChanged = false; - unsigned int min = m_minMemSpinBox->value(); - unsigned int max = m_maxMemSpinBox->value(); - unsigned int permgen = m_permGenSpinBox->value(); - if (min != observedMinMemory) { - observedMinMemory = min; - actuallyChanged = true; - } - if (max != observedMaxMemory) { - observedMaxMemory = max; - actuallyChanged = true; - } - if (permgen != observedPermGenMemory) { - observedPermGenMemory = permgen; - actuallyChanged = true; - } - if (actuallyChanged) { - checkJavaPathOnEdit(m_javaPathTextBox->text()); - updateThresholds(); - } -} - -void JavaSettingsWidget::javaVersionSelected(BaseVersion::Ptr version) -{ - auto java = std::dynamic_pointer_cast(version); - if (!java) { - return; - } - auto visible = java->id.requiresPermGen(); - m_labelPermGen->setVisible(visible); - m_permGenSpinBox->setVisible(visible); - m_javaPathTextBox->setText(java->path); - checkJavaPath(java->path); -} - -void JavaSettingsWidget::on_javaBrowseBtn_clicked() -{ - auto filter = QString("Java (%1)").arg(JavaUtils::javaExecutable); - auto raw_path = QFileDialog::getOpenFileName(this, tr("Find Java executable"), QString(), filter); - if (raw_path.isEmpty()) { - return; - } - auto cooked_path = FS::NormalizePath(raw_path); - m_javaPathTextBox->setText(cooked_path); - checkJavaPath(cooked_path); -} - -void JavaSettingsWidget::javaDownloadBtn_clicked() -{ - auto jdialog = new Java::InstallDialog({}, nullptr, this); - jdialog->exec(); -} - -void JavaSettingsWidget::on_javaStatusBtn_clicked() -{ - QString text; - bool failed = false; - switch (javaStatus) { - case JavaStatus::NotSet: - checkJavaPath(m_javaPathTextBox->text()); - return; - case JavaStatus::DoesNotExist: - text += QObject::tr("The specified file either doesn't exist or is not a proper executable."); - failed = true; - break; - case JavaStatus::DoesNotStart: { - text += QObject::tr("The specified Java binary didn't start properly.
"); - auto htmlError = m_result.errorLog; - if (!htmlError.isEmpty()) { - htmlError.replace('\n', "
"); - text += QString("%1").arg(htmlError); - } - failed = true; - break; - } - case JavaStatus::ReturnedInvalidData: { - text += QObject::tr("The specified Java binary returned unexpected results:
"); - auto htmlOut = m_result.outLog; - if (!htmlOut.isEmpty()) { - htmlOut.replace('\n', "
"); - text += QString("%1").arg(htmlOut); - } - failed = true; - break; - } - case JavaStatus::Good: - text += QObject::tr( - "Java test succeeded!
Platform reported: %1
Java version " - "reported: %2
") - .arg(m_result.realPlatform, m_result.javaVersion.toString()); - break; - case JavaStatus::Pending: - // TODO: abort here? - return; - } - CustomMessageBox::selectable(this, failed ? QObject::tr("Java test failure") : QObject::tr("Java test success"), text, - failed ? QMessageBox::Critical : QMessageBox::Information) - ->show(); -} - -void JavaSettingsWidget::setJavaStatus(JavaSettingsWidget::JavaStatus status) -{ - javaStatus = status; - switch (javaStatus) { - case JavaStatus::Good: - m_javaStatusBtn->setIcon(goodIcon); - break; - case JavaStatus::NotSet: - case JavaStatus::Pending: - m_javaStatusBtn->setIcon(yellowIcon); - break; - default: - m_javaStatusBtn->setIcon(badIcon); - break; - } -} - -void JavaSettingsWidget::javaPathEdited(const QString& path) -{ - checkJavaPathOnEdit(path); -} - -void JavaSettingsWidget::checkJavaPathOnEdit(const QString& path) -{ - auto realPath = FS::ResolveExecutable(path); - QFileInfo pathInfo(realPath); - if (pathInfo.baseName().toLower().contains("java")) { - checkJavaPath(path); - } else { - if (!m_checker) { - setJavaStatus(JavaStatus::NotSet); - } - } -} - -void JavaSettingsWidget::checkJavaPath(const QString& path) -{ - if (m_checker) { - queuedCheck = path; - return; - } - auto realPath = FS::ResolveExecutable(path); - if (realPath.isNull()) { - setJavaStatus(JavaStatus::DoesNotExist); - return; - } - setJavaStatus(JavaStatus::Pending); - m_checker.reset( - new JavaChecker(path, "", minHeapSize(), maxHeapSize(), m_permGenSpinBox->isVisible() ? m_permGenSpinBox->value() : 0, 0, this)); - connect(m_checker.get(), &JavaChecker::checkFinished, this, &JavaSettingsWidget::checkFinished); - m_checker->start(); -} - -void JavaSettingsWidget::checkFinished(const JavaChecker::Result& result) -{ - m_result = result; - switch (result.validity) { - case JavaChecker::Result::Validity::Valid: { - setJavaStatus(JavaStatus::Good); - break; - } - case JavaChecker::Result::Validity::ReturnedInvalidData: { - setJavaStatus(JavaStatus::ReturnedInvalidData); - break; - } - case JavaChecker::Result::Validity::Errored: { - setJavaStatus(JavaStatus::DoesNotStart); - break; - } - } - updateThresholds(); - m_checker.reset(); - if (!queuedCheck.isNull()) { - checkJavaPath(queuedCheck); - queuedCheck.clear(); - } -} - -void JavaSettingsWidget::retranslate() -{ - m_memoryGroupBox->setTitle(tr("Memory")); - m_maxMemSpinBox->setToolTip(tr("The maximum amount of memory Minecraft is allowed to use.")); - m_labelMinMem->setText(tr("Minimum memory allocation:")); - m_labelMaxMem->setText(tr("Maximum memory allocation:")); - m_minMemSpinBox->setToolTip(tr("The amount of memory Minecraft is started with.")); - m_permGenSpinBox->setToolTip(tr("The amount of memory available to store loaded Java classes.")); - m_javaBrowseBtn->setText(tr("Browse")); - if (BuildConfig.JAVA_DOWNLOADER_ENABLED) { - m_autodownloadCheckBox->setText(tr("Auto-download Mojang Java")); - } - m_autodetectJavaCheckBox->setText(tr("Autodetect Java version")); - m_autoJavaGroupBox->setTitle(tr("Autodetect Java")); -} - -void JavaSettingsWidget::updateThresholds() -{ - QString iconName; - - if (observedMaxMemory >= m_availableMemory) { - iconName = "status-bad"; - m_labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation exceeds your system memory capacity.")); - } else if (observedMaxMemory > (m_availableMemory * 0.9)) { - iconName = "status-yellow"; - m_labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity.")); - } else if (observedMaxMemory < observedMinMemory) { - iconName = "status-yellow"; - m_labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value")); - } else if (BuildConfig.JAVA_DOWNLOADER_ENABLED && m_autodownloadCheckBox->isChecked()) { - iconName = "status-good"; - m_labelMaxMemIcon->setToolTip(""); - } else if (observedMaxMemory > 2048 && !m_result.is_64bit) { - iconName = "status-bad"; - m_labelMaxMemIcon->setToolTip(tr("You are exceeding the maximum allocation supported by 32-bit installations of Java.")); - } else { - iconName = "status-good"; - m_labelMaxMemIcon->setToolTip(""); - } - - { - auto height = m_labelMaxMemIcon->fontInfo().pixelSize(); - QIcon icon = APPLICATION->getThemedIcon(iconName); - QPixmap pix = icon.pixmap(height, height); - m_labelMaxMemIcon->setPixmap(pix); - } -} - -bool JavaSettingsWidget::autoDownloadJava() const -{ - return m_autodownloadCheckBox && m_autodownloadCheckBox->isChecked(); -} - -bool JavaSettingsWidget::autoDetectJava() const -{ - return m_autodetectJavaCheckBox->isChecked(); -} - -void JavaSettingsWidget::onSpinBoxValueChanged(int) -{ - m_memoryTimer->start(500); } JavaSettingsWidget::~JavaSettingsWidget() { - delete m_verticalSpacer; -}; \ No newline at end of file + delete m_ui; +} + +void JavaSettingsWidget::loadSettings() +{ + SettingsObjectPtr settings; + + if (m_instance != nullptr) + settings = m_instance->settings(); + else + settings = APPLICATION->settings(); + + // Java Settings + m_ui->javaInstallationGroupBox->setChecked(settings->get("OverrideJavaLocation").toBool()); + m_ui->javaPathTextBox->setText(settings->get("JavaPath").toString()); + + m_ui->skipCompatibilityCheckBox->setChecked(settings->get("IgnoreJavaCompatibility").toBool()); + + m_ui->javaArgumentsGroupBox->setChecked(m_instance == nullptr || settings->get("OverrideJavaArgs").toBool()); + m_ui->jvmArgsTextBox->setPlainText(settings->get("JvmArgs").toString()); + + if (m_instance == nullptr) { + m_ui->skipWizardCheckBox->setChecked(settings->get("IgnoreJavaWizard").toBool()); + m_ui->autodetectJavaCheckBox->setChecked(settings->get("AutomaticJavaSwitch").toBool()); + m_ui->autodetectJavaCheckBox->stateChanged(m_ui->autodetectJavaCheckBox->isChecked()); + m_ui->autodownloadJavaCheckBox->setChecked(settings->get("AutomaticJavaDownload").toBool()); + } + + // Memory + m_ui->memoryGroupBox->setChecked(m_instance == nullptr || settings->get("OverrideMemory").toBool()); + int min = settings->get("MinMemAlloc").toInt(); + int max = settings->get("MaxMemAlloc").toInt(); + if (min < max) { + m_ui->minMemSpinBox->setValue(min); + m_ui->maxMemSpinBox->setValue(max); + } else { + m_ui->minMemSpinBox->setValue(max); + m_ui->maxMemSpinBox->setValue(min); + } + m_ui->permGenSpinBox->setValue(settings->get("PermGen").toInt()); + + // Java arguments + m_ui->javaArgumentsGroupBox->setChecked(m_instance == nullptr || settings->get("OverrideJavaArgs").toBool()); + m_ui->jvmArgsTextBox->setPlainText(settings->get("JvmArgs").toString()); +} + +void JavaSettingsWidget::saveSettings() +{ + SettingsObjectPtr settings; + + if (m_instance != nullptr) + settings = m_instance->settings(); + else + settings = APPLICATION->settings(); + + SettingsObject::Lock lock(settings); + + // Java Install Settings + bool javaInstall = m_instance == nullptr || m_ui->javaInstallationGroupBox->isChecked(); + + if (m_instance != nullptr) + settings->set("OverrideJavaLocation", javaInstall); + + if (javaInstall) { + settings->set("JavaPath", m_ui->javaPathTextBox->text()); + settings->set("IgnoreJavaCompatibility", m_ui->skipCompatibilityCheckBox->isChecked()); + } else { + settings->reset("JavaPath"); + settings->reset("IgnoreJavaCompatibility"); + } + + if (m_instance == nullptr) { + settings->set("IgnoreJavaWizard", m_ui->skipWizardCheckBox->isChecked()); + settings->set("AutomaticJavaSwitch", m_ui->autodetectJavaCheckBox->isChecked()); + settings->set("AutomaticJavaDownload", m_ui->autodownloadJavaCheckBox->isChecked()); + } + + // Memory + bool memory = m_instance == nullptr || m_ui->memoryGroupBox->isChecked(); + + if (m_instance != nullptr) + settings->set("OverrideMemory", memory); + + if (memory) { + int min = m_ui->minMemSpinBox->value(); + int max = m_ui->maxMemSpinBox->value(); + if (min < max) { + settings->set("MinMemAlloc", min); + settings->set("MaxMemAlloc", max); + } else { + settings->set("MinMemAlloc", max); + settings->set("MaxMemAlloc", min); + } + settings->set("PermGen", m_ui->permGenSpinBox->value()); + } else { + settings->reset("MinMemAlloc"); + settings->reset("MaxMemAlloc"); + settings->reset("PermGen"); + } + + // Java arguments + bool javaArgs = m_instance == nullptr || m_ui->javaArgumentsGroupBox->isChecked(); + + if (m_instance != nullptr) + settings->set("OverrideJavaArgs", javaArgs); + + if (javaArgs) { + settings->set("JvmArgs", m_ui->jvmArgsTextBox->toPlainText().replace("\n", " ")); + } else { + settings->reset("JvmArgs"); + } +} + +void JavaSettingsWidget::onJavaBrowse() +{ + QString rawPath = QFileDialog::getOpenFileName(this, tr("Find Java executable")); + + // do not allow current dir - it's dirty. Do not allow dirs that don't exist + if (rawPath.isEmpty()) { + return; + } + + QString cookedPath = FS::NormalizePath(rawPath); + QFileInfo javaInfo(cookedPath); + if (!javaInfo.exists() || !javaInfo.isExecutable()) { + return; + } + m_ui->javaPathTextBox->setText(cookedPath); +} + +void JavaSettingsWidget::onJavaTest() +{ + if (m_checker != nullptr) + return; + + QString jvmArgs; + + if (m_instance == nullptr || m_ui->javaArgumentsGroupBox->isChecked()) + jvmArgs = m_ui->jvmArgsTextBox->toPlainText().replace("\n", " "); + else + jvmArgs = APPLICATION->settings()->get("JvmArgs").toString(); + + m_checker.reset(new JavaCommon::TestCheck(this, m_ui->javaPathTextBox->text(), jvmArgs, m_ui->minMemSpinBox->value(), + m_ui->maxMemSpinBox->value(), m_ui->permGenSpinBox->value())); + connect(m_checker.get(), &JavaCommon::TestCheck::finished, this, [this] { m_checker.reset(); }); + m_checker->run(); +} + +void JavaSettingsWidget::onJavaAutodetect() +{ + if (JavaUtils::getJavaCheckPath().isEmpty()) { + JavaCommon::javaCheckNotFound(this); + return; + } + + VersionSelectDialog versionDialog(APPLICATION->javalist().get(), tr("Select a Java version"), this, true); + versionDialog.setResizeOn(2); + versionDialog.exec(); + + if (versionDialog.result() == QDialog::Accepted && versionDialog.selectedVersion()) { + JavaInstallPtr java = std::dynamic_pointer_cast(versionDialog.selectedVersion()); + m_ui->javaPathTextBox->setText(java->path); + + if (!java->is_64bit && m_ui->maxMemSpinBox->value() > 2048) { + CustomMessageBox::selectable(this, tr("Confirm Selection"), + tr("You selected a 32-bit version of Java.\n" + "This installation does not support more than 2048MiB of RAM.\n" + "Please make sure that the maximum memory value is lower."), + QMessageBox::Warning, QMessageBox::Ok, QMessageBox::Ok) + ->exec(); + } + } +} +void JavaSettingsWidget::updateThresholds() +{ + auto sysMiB = Sys::getSystemRam() / Sys::mebibyte; + unsigned int maxMem = m_ui->maxMemSpinBox->value(); + unsigned int minMem = m_ui->minMemSpinBox->value(); + + QString iconName; + + if (maxMem >= sysMiB) { + iconName = "status-bad"; + m_ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation exceeds your system memory capacity.")); + } else if (maxMem > (sysMiB * 0.9)) { + iconName = "status-yellow"; + m_ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity.")); + } else if (maxMem < minMem) { + iconName = "status-yellow"; + m_ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value")); + } else { + iconName = "status-good"; + m_ui->labelMaxMemIcon->setToolTip(""); + } + + { + auto height = m_ui->labelMaxMemIcon->fontInfo().pixelSize(); + QIcon icon = APPLICATION->getThemedIcon(iconName); + QPixmap pix = icon.pixmap(height, height); + m_ui->labelMaxMemIcon->setPixmap(pix); + } +} diff --git a/launcher/ui/widgets/JavaSettingsWidget.h b/launcher/ui/widgets/JavaSettingsWidget.h index 35be48478..21a71fb8b 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.h +++ b/launcher/ui/widgets/JavaSettingsWidget.h @@ -1,106 +1,68 @@ -#pragma once -#include - -#include -#include -#include -#include -#include - -class QLineEdit; -class VersionSelectWidget; -class QSpinBox; -class QPushButton; -class QVBoxLayout; -class QHBoxLayout; -class QGroupBox; -class QGridLayout; -class QLabel; -class QToolButton; -class QSpacerItem; - -/** - * This is a widget for all the Java settings dialogs and pages. +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2022 Jamie Mansfield + * 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 + +#include +#include "BaseInstance.h" +#include "JavaCommon.h" + +namespace Ui { +class JavaSettingsWidget; +} + class JavaSettingsWidget : public QWidget { Q_OBJECT public: - explicit JavaSettingsWidget(QWidget* parent); - virtual ~JavaSettingsWidget(); + explicit JavaSettingsWidget(QWidget* parent = nullptr) : JavaSettingsWidget(nullptr, nullptr) {} + explicit JavaSettingsWidget(InstancePtr instance, QWidget* parent = nullptr); + ~JavaSettingsWidget() override; - enum class JavaStatus { NotSet, Pending, Good, DoesNotExist, DoesNotStart, ReturnedInvalidData } javaStatus = JavaStatus::NotSet; - - enum class ValidationStatus { Bad, JavaBad, AllOK }; - - void refresh(); - void initialize(); - ValidationStatus validate(); - void retranslate(); - - bool permGenEnabled() const; - int permGenSize() const; - int minHeapSize() const; - int maxHeapSize() const; - QString javaPath() const; - bool autoDetectJava() const; - bool autoDownloadJava() const; + void loadSettings(); + void saveSettings(); + private slots: + void onJavaBrowse(); + void onJavaAutodetect(); + void onJavaTest(); void updateThresholds(); - protected slots: - void onSpinBoxValueChanged(int); - void memoryValueChanged(); - void javaPathEdited(const QString& path); - void javaVersionSelected(BaseVersion::Ptr version); - void on_javaBrowseBtn_clicked(); - void on_javaStatusBtn_clicked(); - void javaDownloadBtn_clicked(); - void checkFinished(const JavaChecker::Result& result); - - protected: /* methods */ - void checkJavaPathOnEdit(const QString& path); - void checkJavaPath(const QString& path); - void setJavaStatus(JavaStatus status); - void setupUi(); - - private: /* data */ - VersionSelectWidget* m_versionWidget = nullptr; - QVBoxLayout* m_verticalLayout = nullptr; - QSpacerItem* m_verticalSpacer = nullptr; - - QLineEdit* m_javaPathTextBox = nullptr; - QPushButton* m_javaBrowseBtn = nullptr; - QToolButton* m_javaStatusBtn = nullptr; - QHBoxLayout* m_horizontalLayout = nullptr; - - QGroupBox* m_memoryGroupBox = nullptr; - QGridLayout* m_gridLayout_2 = nullptr; - QSpinBox* m_maxMemSpinBox = nullptr; - QLabel* m_labelMinMem = nullptr; - QLabel* m_labelMaxMem = nullptr; - QLabel* m_labelMaxMemIcon = nullptr; - QSpinBox* m_minMemSpinBox = nullptr; - QLabel* m_labelPermGen = nullptr; - QSpinBox* m_permGenSpinBox = nullptr; - - QHBoxLayout* m_horizontalBtnLayout = nullptr; - QPushButton* m_javaDownloadBtn = nullptr; - QIcon goodIcon; - QIcon yellowIcon; - QIcon badIcon; - - QGroupBox* m_autoJavaGroupBox = nullptr; - QVBoxLayout* m_veriticalJavaLayout = nullptr; - QCheckBox* m_autodetectJavaCheckBox = nullptr; - QCheckBox* m_autodownloadCheckBox = nullptr; - - unsigned int observedMinMemory = 0; - unsigned int observedMaxMemory = 0; - unsigned int observedPermGenMemory = 0; - QString queuedCheck; - uint64_t m_availableMemory = 0ull; - shared_qobject_ptr m_checker; - JavaChecker::Result m_result; - QTimer* m_memoryTimer; + private: + InstancePtr m_instance; + Ui::JavaSettingsWidget* m_ui; + unique_qobject_ptr m_checker; }; diff --git a/launcher/ui/widgets/JavaSettingsWidget.ui b/launcher/ui/widgets/JavaSettingsWidget.ui new file mode 100644 index 000000000..15ce88f0c --- /dev/null +++ b/launcher/ui/widgets/JavaSettingsWidget.ui @@ -0,0 +1,269 @@ + + + JavaSettingsWidget + + + + 0 + 0 + 500 + 600 + + + + Form + + + + + + true + + + Java Insta&llation + + + false + + + false + + + + + + Auto-&detect Java version + + + + + + + + + + + + Browse + + + + + + + + + + + Download Java + + + + + + + Auto-detect... + + + + + + + Test + + + + + + + + + Automatically downloads and selects the Java build recommended by Mojang. + + + Auto-download &Mojang Java + + + + + + + If enabled, the launcher won't prompt you to choose a Java version if one is not found on startup. + + + Skip Java setup prompt on startup + + + + + + + If enabled, the launcher will not check if an instance is compatible with the selected Java version. + + + Skip Java compatibility checks + + + + + + + + + + true + + + Memor&y + + + false + + + false + + + + + + PermGen (Java 7 and earlier): + + + + + + + Minimum memory allocation: + + + + + + + The amount of memory available to store loaded Java classes. + + + MiB + + + 4 + + + 999999999 + + + 8 + + + 64 + + + + + + + Maximum memory allocation: + + + + + + + + + + Qt::AlignCenter + + + maxMemSpinBox + + + + + + + The maximum amount of memory Minecraft is allowed to use. + + + MiB + + + 8 + + + 1048576 + + + 128 + + + 1024 + + + + + + + The amount of memory Minecraft is started with. + + + MiB + + + 8 + + + 1048576 + + + 128 + + + 256 + + + + + + + + + + true + + + Java Argumen&ts + + + false + + + false + + + + + + + + + + + + javaPathTextBox + javaBrowseBtn + javaDownloadBtn + javaDetectBtn + javaTestBtn + skipCompatibilityCheckBox + skipWizardCheckBox + autodetectJavaCheckBox + autodownloadJavaCheckBox + minMemSpinBox + maxMemSpinBox + permGenSpinBox + jvmArgsTextBox + + + + diff --git a/launcher/ui/widgets/JavaWizardWidget.cpp b/launcher/ui/widgets/JavaWizardWidget.cpp new file mode 100644 index 000000000..02bb57474 --- /dev/null +++ b/launcher/ui/widgets/JavaWizardWidget.cpp @@ -0,0 +1,559 @@ +#include "JavaWizardWidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "DesktopServices.h" +#include "FileSystem.h" +#include "JavaCommon.h" +#include "java/JavaChecker.h" +#include "java/JavaInstall.h" +#include "java/JavaInstallList.h" +#include "java/JavaUtils.h" + +#include "ui/dialogs/CustomMessageBox.h" +#include "ui/java/InstallJavaDialog.h" +#include "ui/widgets/VersionSelectWidget.h" + +#include "Application.h" +#include "BuildConfig.h" + +JavaWizardWidget::JavaWizardWidget(QWidget* parent) : QWidget(parent) +{ + m_availableMemory = Sys::getSystemRam() / Sys::mebibyte; + + goodIcon = APPLICATION->getThemedIcon("status-good"); + yellowIcon = APPLICATION->getThemedIcon("status-yellow"); + badIcon = APPLICATION->getThemedIcon("status-bad"); + m_memoryTimer = new QTimer(this); + setupUi(); + + connect(m_minMemSpinBox, SIGNAL(valueChanged(int)), this, SLOT(onSpinBoxValueChanged(int))); + connect(m_maxMemSpinBox, SIGNAL(valueChanged(int)), this, SLOT(onSpinBoxValueChanged(int))); + connect(m_permGenSpinBox, SIGNAL(valueChanged(int)), this, SLOT(onSpinBoxValueChanged(int))); + connect(m_memoryTimer, &QTimer::timeout, this, &JavaWizardWidget::memoryValueChanged); + connect(m_versionWidget, &VersionSelectWidget::selectedVersionChanged, this, &JavaWizardWidget::javaVersionSelected); + connect(m_javaBrowseBtn, &QPushButton::clicked, this, &JavaWizardWidget::on_javaBrowseBtn_clicked); + connect(m_javaPathTextBox, &QLineEdit::textEdited, this, &JavaWizardWidget::javaPathEdited); + connect(m_javaStatusBtn, &QToolButton::clicked, this, &JavaWizardWidget::on_javaStatusBtn_clicked); + if (BuildConfig.JAVA_DOWNLOADER_ENABLED) { + connect(m_javaDownloadBtn, &QPushButton::clicked, this, &JavaWizardWidget::javaDownloadBtn_clicked); + } +} + +void JavaWizardWidget::setupUi() +{ + setObjectName(QStringLiteral("javaSettingsWidget")); + m_verticalLayout = new QVBoxLayout(this); + m_verticalLayout->setObjectName(QStringLiteral("verticalLayout")); + + m_versionWidget = new VersionSelectWidget(this); + + m_horizontalLayout = new QHBoxLayout(); + m_horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); + m_javaPathTextBox = new QLineEdit(this); + m_javaPathTextBox->setObjectName(QStringLiteral("javaPathTextBox")); + + m_horizontalLayout->addWidget(m_javaPathTextBox); + + m_javaBrowseBtn = new QPushButton(this); + m_javaBrowseBtn->setObjectName(QStringLiteral("javaBrowseBtn")); + + m_horizontalLayout->addWidget(m_javaBrowseBtn); + + m_javaStatusBtn = new QToolButton(this); + m_javaStatusBtn->setIcon(yellowIcon); + m_horizontalLayout->addWidget(m_javaStatusBtn); + + m_memoryGroupBox = new QGroupBox(this); + m_memoryGroupBox->setObjectName(QStringLiteral("memoryGroupBox")); + m_gridLayout_2 = new QGridLayout(m_memoryGroupBox); + m_gridLayout_2->setObjectName(QStringLiteral("gridLayout_2")); + m_gridLayout_2->setColumnStretch(0, 1); + + m_labelMinMem = new QLabel(m_memoryGroupBox); + m_labelMinMem->setObjectName(QStringLiteral("labelMinMem")); + m_gridLayout_2->addWidget(m_labelMinMem, 0, 0, 1, 1); + + m_minMemSpinBox = new QSpinBox(m_memoryGroupBox); + m_minMemSpinBox->setObjectName(QStringLiteral("minMemSpinBox")); + m_minMemSpinBox->setSuffix(QStringLiteral(" MiB")); + m_minMemSpinBox->setMinimum(8); + m_minMemSpinBox->setMaximum(1048576); + m_minMemSpinBox->setSingleStep(128); + m_labelMinMem->setBuddy(m_minMemSpinBox); + m_gridLayout_2->addWidget(m_minMemSpinBox, 0, 1, 1, 1); + + m_labelMaxMem = new QLabel(m_memoryGroupBox); + m_labelMaxMem->setObjectName(QStringLiteral("labelMaxMem")); + m_gridLayout_2->addWidget(m_labelMaxMem, 1, 0, 1, 1); + + m_maxMemSpinBox = new QSpinBox(m_memoryGroupBox); + m_maxMemSpinBox->setObjectName(QStringLiteral("maxMemSpinBox")); + m_maxMemSpinBox->setSuffix(QStringLiteral(" MiB")); + m_maxMemSpinBox->setMinimum(8); + m_maxMemSpinBox->setMaximum(1048576); + m_maxMemSpinBox->setSingleStep(128); + m_labelMaxMem->setBuddy(m_maxMemSpinBox); + m_gridLayout_2->addWidget(m_maxMemSpinBox, 1, 1, 1, 1); + + m_labelMaxMemIcon = new QLabel(m_memoryGroupBox); + m_labelMaxMemIcon->setObjectName(QStringLiteral("labelMaxMemIcon")); + m_gridLayout_2->addWidget(m_labelMaxMemIcon, 1, 2, 1, 1); + + m_labelPermGen = new QLabel(m_memoryGroupBox); + m_labelPermGen->setObjectName(QStringLiteral("labelPermGen")); + m_labelPermGen->setText(QStringLiteral("PermGen:")); + m_gridLayout_2->addWidget(m_labelPermGen, 2, 0, 1, 1); + m_labelPermGen->setVisible(false); + + m_permGenSpinBox = new QSpinBox(m_memoryGroupBox); + m_permGenSpinBox->setObjectName(QStringLiteral("permGenSpinBox")); + m_permGenSpinBox->setSuffix(QStringLiteral(" MiB")); + m_permGenSpinBox->setMinimum(4); + m_permGenSpinBox->setMaximum(1048576); + m_permGenSpinBox->setSingleStep(8); + m_gridLayout_2->addWidget(m_permGenSpinBox, 2, 1, 1, 1); + m_permGenSpinBox->setVisible(false); + + m_verticalLayout->addWidget(m_memoryGroupBox); + + m_horizontalBtnLayout = new QHBoxLayout(); + m_horizontalBtnLayout->setObjectName(QStringLiteral("horizontalBtnLayout")); + + if (BuildConfig.JAVA_DOWNLOADER_ENABLED) { + m_javaDownloadBtn = new QPushButton(tr("Download Java"), this); + m_horizontalBtnLayout->addWidget(m_javaDownloadBtn); + } + + m_autoJavaGroupBox = new QGroupBox(this); + m_autoJavaGroupBox->setObjectName(QStringLiteral("autoJavaGroupBox")); + m_veriticalJavaLayout = new QVBoxLayout(m_autoJavaGroupBox); + m_veriticalJavaLayout->setObjectName(QStringLiteral("veriticalJavaLayout")); + + m_autodetectJavaCheckBox = new QCheckBox(m_autoJavaGroupBox); + m_autodetectJavaCheckBox->setObjectName("autodetectJavaCheckBox"); + m_autodetectJavaCheckBox->setChecked(true); + m_veriticalJavaLayout->addWidget(m_autodetectJavaCheckBox); + + if (BuildConfig.JAVA_DOWNLOADER_ENABLED) { + m_autodownloadCheckBox = new QCheckBox(m_autoJavaGroupBox); + m_autodownloadCheckBox->setObjectName("autodownloadCheckBox"); + m_autodownloadCheckBox->setEnabled(m_autodetectJavaCheckBox->isChecked()); + m_veriticalJavaLayout->addWidget(m_autodownloadCheckBox); + connect(m_autodetectJavaCheckBox, &QCheckBox::stateChanged, this, [this] { + m_autodownloadCheckBox->setEnabled(m_autodetectJavaCheckBox->isChecked()); + if (!m_autodetectJavaCheckBox->isChecked()) + m_autodownloadCheckBox->setChecked(false); + }); + + connect(m_autodownloadCheckBox, &QCheckBox::stateChanged, this, [this] { + auto isChecked = m_autodownloadCheckBox->isChecked(); + m_versionWidget->setVisible(!isChecked); + m_javaStatusBtn->setVisible(!isChecked); + m_javaBrowseBtn->setVisible(!isChecked); + m_javaPathTextBox->setVisible(!isChecked); + m_javaDownloadBtn->setVisible(!isChecked); + if (!isChecked) { + m_verticalLayout->removeItem(m_verticalSpacer); + } else { + m_verticalLayout->addSpacerItem(m_verticalSpacer); + } + }); + } + m_verticalLayout->addWidget(m_autoJavaGroupBox); + + m_verticalLayout->addLayout(m_horizontalBtnLayout); + + m_verticalLayout->addWidget(m_versionWidget); + m_verticalLayout->addLayout(m_horizontalLayout); + m_verticalSpacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); + + retranslate(); +} + +void JavaWizardWidget::initialize() +{ + m_versionWidget->initialize(APPLICATION->javalist().get()); + m_versionWidget->selectSearch(); + m_versionWidget->setResizeOn(2); + auto s = APPLICATION->settings(); + // Memory + observedMinMemory = s->get("MinMemAlloc").toInt(); + observedMaxMemory = s->get("MaxMemAlloc").toInt(); + observedPermGenMemory = s->get("PermGen").toInt(); + m_minMemSpinBox->setValue(observedMinMemory); + m_maxMemSpinBox->setValue(observedMaxMemory); + m_permGenSpinBox->setValue(observedPermGenMemory); + updateThresholds(); + if (BuildConfig.JAVA_DOWNLOADER_ENABLED) { + m_autodownloadCheckBox->setChecked(true); + } +} + +void JavaWizardWidget::refresh() +{ + if (BuildConfig.JAVA_DOWNLOADER_ENABLED && m_autodownloadCheckBox->isChecked()) { + return; + } + if (JavaUtils::getJavaCheckPath().isEmpty()) { + JavaCommon::javaCheckNotFound(this); + return; + } + m_versionWidget->loadList(); +} + +JavaWizardWidget::ValidationStatus JavaWizardWidget::validate() +{ + switch (javaStatus) { + default: + case JavaStatus::NotSet: + /* fallthrough */ + case JavaStatus::DoesNotExist: + /* fallthrough */ + case JavaStatus::DoesNotStart: + /* fallthrough */ + case JavaStatus::ReturnedInvalidData: { + if (!(BuildConfig.JAVA_DOWNLOADER_ENABLED && m_autodownloadCheckBox->isChecked())) { // the java will not be autodownloaded + int button = QMessageBox::No; + if (m_result.mojangPlatform == "32" && maxHeapSize() > 2048) { + button = CustomMessageBox::selectable( + this, tr("32-bit Java detected"), + tr("You selected a 32-bit installation of Java, but allocated more than 2048MiB as maximum memory.\n" + "%1 will not be able to start Minecraft.\n" + "Do you wish to proceed?" + "\n\n" + "You can change the Java version in the settings later.\n") + .arg(BuildConfig.LAUNCHER_DISPLAYNAME), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No | QMessageBox::Help, QMessageBox::NoButton) + ->exec(); + + } else { + button = CustomMessageBox::selectable(this, tr("No Java version selected"), + tr("You either didn't select a Java version or selected one that does not work.\n" + "%1 will not be able to start Minecraft.\n" + "Do you wish to proceed without a functional version of Java?" + "\n\n" + "You can change the Java version in the settings later.\n") + .arg(BuildConfig.LAUNCHER_DISPLAYNAME), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No | QMessageBox::Help, + QMessageBox::NoButton) + ->exec(); + } + switch (button) { + case QMessageBox::Yes: + return ValidationStatus::JavaBad; + case QMessageBox::Help: + DesktopServices::openUrl(QUrl(BuildConfig.HELP_URL.arg("java-wizard"))); + /* fallthrough */ + case QMessageBox::No: + /* fallthrough */ + default: + return ValidationStatus::Bad; + } + if (button == QMessageBox::No) { + return ValidationStatus::Bad; + } + } + return ValidationStatus::JavaBad; + } break; + case JavaStatus::Pending: { + return ValidationStatus::Bad; + } + case JavaStatus::Good: { + return ValidationStatus::AllOK; + } + } +} + +QString JavaWizardWidget::javaPath() const +{ + return m_javaPathTextBox->text(); +} + +int JavaWizardWidget::maxHeapSize() const +{ + auto min = m_minMemSpinBox->value(); + auto max = m_maxMemSpinBox->value(); + if (max < min) + max = min; + return max; +} + +int JavaWizardWidget::minHeapSize() const +{ + auto min = m_minMemSpinBox->value(); + auto max = m_maxMemSpinBox->value(); + if (min > max) + min = max; + return min; +} + +bool JavaWizardWidget::permGenEnabled() const +{ + return m_permGenSpinBox->isVisible(); +} + +int JavaWizardWidget::permGenSize() const +{ + return m_permGenSpinBox->value(); +} + +void JavaWizardWidget::memoryValueChanged() +{ + bool actuallyChanged = false; + unsigned int min = m_minMemSpinBox->value(); + unsigned int max = m_maxMemSpinBox->value(); + unsigned int permgen = m_permGenSpinBox->value(); + if (min != observedMinMemory) { + observedMinMemory = min; + actuallyChanged = true; + } + if (max != observedMaxMemory) { + observedMaxMemory = max; + actuallyChanged = true; + } + if (permgen != observedPermGenMemory) { + observedPermGenMemory = permgen; + actuallyChanged = true; + } + if (actuallyChanged) { + checkJavaPathOnEdit(m_javaPathTextBox->text()); + updateThresholds(); + } +} + +void JavaWizardWidget::javaVersionSelected(BaseVersion::Ptr version) +{ + auto java = std::dynamic_pointer_cast(version); + if (!java) { + return; + } + auto visible = java->id.requiresPermGen(); + m_labelPermGen->setVisible(visible); + m_permGenSpinBox->setVisible(visible); + m_javaPathTextBox->setText(java->path); + checkJavaPath(java->path); +} + +void JavaWizardWidget::on_javaBrowseBtn_clicked() +{ + auto filter = QString("Java (%1)").arg(JavaUtils::javaExecutable); + auto raw_path = QFileDialog::getOpenFileName(this, tr("Find Java executable"), QString(), filter); + if (raw_path.isEmpty()) { + return; + } + auto cooked_path = FS::NormalizePath(raw_path); + m_javaPathTextBox->setText(cooked_path); + checkJavaPath(cooked_path); +} + +void JavaWizardWidget::javaDownloadBtn_clicked() +{ + auto jdialog = new Java::InstallDialog({}, nullptr, this); + jdialog->exec(); +} + +void JavaWizardWidget::on_javaStatusBtn_clicked() +{ + QString text; + bool failed = false; + switch (javaStatus) { + case JavaStatus::NotSet: + checkJavaPath(m_javaPathTextBox->text()); + return; + case JavaStatus::DoesNotExist: + text += QObject::tr("The specified file either doesn't exist or is not a proper executable."); + failed = true; + break; + case JavaStatus::DoesNotStart: { + text += QObject::tr("The specified Java binary didn't start properly.
"); + auto htmlError = m_result.errorLog; + if (!htmlError.isEmpty()) { + htmlError.replace('\n', "
"); + text += QString("%1").arg(htmlError); + } + failed = true; + break; + } + case JavaStatus::ReturnedInvalidData: { + text += QObject::tr("The specified Java binary returned unexpected results:
"); + auto htmlOut = m_result.outLog; + if (!htmlOut.isEmpty()) { + htmlOut.replace('\n', "
"); + text += QString("%1").arg(htmlOut); + } + failed = true; + break; + } + case JavaStatus::Good: + text += QObject::tr( + "Java test succeeded!
Platform reported: %1
Java version " + "reported: %2
") + .arg(m_result.realPlatform, m_result.javaVersion.toString()); + break; + case JavaStatus::Pending: + // TODO: abort here? + return; + } + CustomMessageBox::selectable(this, failed ? QObject::tr("Java test failure") : QObject::tr("Java test success"), text, + failed ? QMessageBox::Critical : QMessageBox::Information) + ->show(); +} + +void JavaWizardWidget::setJavaStatus(JavaWizardWidget::JavaStatus status) +{ + javaStatus = status; + switch (javaStatus) { + case JavaStatus::Good: + m_javaStatusBtn->setIcon(goodIcon); + break; + case JavaStatus::NotSet: + case JavaStatus::Pending: + m_javaStatusBtn->setIcon(yellowIcon); + break; + default: + m_javaStatusBtn->setIcon(badIcon); + break; + } +} + +void JavaWizardWidget::javaPathEdited(const QString& path) +{ + checkJavaPathOnEdit(path); +} + +void JavaWizardWidget::checkJavaPathOnEdit(const QString& path) +{ + auto realPath = FS::ResolveExecutable(path); + QFileInfo pathInfo(realPath); + if (pathInfo.baseName().toLower().contains("java")) { + checkJavaPath(path); + } else { + if (!m_checker) { + setJavaStatus(JavaStatus::NotSet); + } + } +} + +void JavaWizardWidget::checkJavaPath(const QString& path) +{ + if (m_checker) { + queuedCheck = path; + return; + } + auto realPath = FS::ResolveExecutable(path); + if (realPath.isNull()) { + setJavaStatus(JavaStatus::DoesNotExist); + return; + } + setJavaStatus(JavaStatus::Pending); + m_checker.reset( + new JavaChecker(path, "", minHeapSize(), maxHeapSize(), m_permGenSpinBox->isVisible() ? m_permGenSpinBox->value() : 0, 0)); + connect(m_checker.get(), &JavaChecker::checkFinished, this, &JavaWizardWidget::checkFinished); + m_checker->start(); +} + +void JavaWizardWidget::checkFinished(const JavaChecker::Result& result) +{ + m_result = result; + switch (result.validity) { + case JavaChecker::Result::Validity::Valid: { + setJavaStatus(JavaStatus::Good); + break; + } + case JavaChecker::Result::Validity::ReturnedInvalidData: { + setJavaStatus(JavaStatus::ReturnedInvalidData); + break; + } + case JavaChecker::Result::Validity::Errored: { + setJavaStatus(JavaStatus::DoesNotStart); + break; + } + } + updateThresholds(); + m_checker.reset(); + if (!queuedCheck.isNull()) { + checkJavaPath(queuedCheck); + queuedCheck.clear(); + } +} + +void JavaWizardWidget::retranslate() +{ + m_memoryGroupBox->setTitle(tr("Memory")); + m_maxMemSpinBox->setToolTip(tr("The maximum amount of memory Minecraft is allowed to use.")); + m_labelMinMem->setText(tr("Minimum memory allocation:")); + m_labelMaxMem->setText(tr("Maximum memory allocation:")); + m_minMemSpinBox->setToolTip(tr("The amount of memory Minecraft is started with.")); + m_permGenSpinBox->setToolTip(tr("The amount of memory available to store loaded Java classes.")); + m_javaBrowseBtn->setText(tr("Browse")); + if (BuildConfig.JAVA_DOWNLOADER_ENABLED) { + m_autodownloadCheckBox->setText(tr("Auto-download Mojang Java")); + } + m_autodetectJavaCheckBox->setText(tr("Auto-detect Java version")); + m_autoJavaGroupBox->setTitle(tr("Autodetect Java")); +} + +void JavaWizardWidget::updateThresholds() +{ + QString iconName; + + if (observedMaxMemory >= m_availableMemory) { + iconName = "status-bad"; + m_labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation exceeds your system memory capacity.")); + } else if (observedMaxMemory > (m_availableMemory * 0.9)) { + iconName = "status-yellow"; + m_labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity.")); + } else if (observedMaxMemory < observedMinMemory) { + iconName = "status-yellow"; + m_labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value")); + } else if (BuildConfig.JAVA_DOWNLOADER_ENABLED && m_autodownloadCheckBox->isChecked()) { + iconName = "status-good"; + m_labelMaxMemIcon->setToolTip(""); + } else if (observedMaxMemory > 2048 && !m_result.is_64bit) { + iconName = "status-bad"; + m_labelMaxMemIcon->setToolTip(tr("You are exceeding the maximum allocation supported by 32-bit installations of Java.")); + } else { + iconName = "status-good"; + m_labelMaxMemIcon->setToolTip(""); + } + + { + auto height = m_labelMaxMemIcon->fontInfo().pixelSize(); + QIcon icon = APPLICATION->getThemedIcon(iconName); + QPixmap pix = icon.pixmap(height, height); + m_labelMaxMemIcon->setPixmap(pix); + } +} + +bool JavaWizardWidget::autoDownloadJava() const +{ + return m_autodownloadCheckBox && m_autodownloadCheckBox->isChecked(); +} + +bool JavaWizardWidget::autoDetectJava() const +{ + return m_autodetectJavaCheckBox->isChecked(); +} + +void JavaWizardWidget::onSpinBoxValueChanged(int) +{ + m_memoryTimer->start(500); +} + +JavaWizardWidget::~JavaWizardWidget() +{ + delete m_verticalSpacer; +}; \ No newline at end of file diff --git a/launcher/ui/widgets/JavaWizardWidget.h b/launcher/ui/widgets/JavaWizardWidget.h new file mode 100644 index 000000000..69f093000 --- /dev/null +++ b/launcher/ui/widgets/JavaWizardWidget.h @@ -0,0 +1,103 @@ +#pragma once +#include + +#include +#include +#include +#include +#include + +class QLineEdit; +class VersionSelectWidget; +class QSpinBox; +class QPushButton; +class QVBoxLayout; +class QHBoxLayout; +class QGroupBox; +class QGridLayout; +class QLabel; +class QToolButton; +class QSpacerItem; + +class JavaWizardWidget : public QWidget { + Q_OBJECT + + public: + explicit JavaWizardWidget(QWidget* parent); + virtual ~JavaWizardWidget(); + + enum class JavaStatus { NotSet, Pending, Good, DoesNotExist, DoesNotStart, ReturnedInvalidData } javaStatus = JavaStatus::NotSet; + + enum class ValidationStatus { Bad, JavaBad, AllOK }; + + void refresh(); + void initialize(); + ValidationStatus validate(); + void retranslate(); + + bool permGenEnabled() const; + int permGenSize() const; + int minHeapSize() const; + int maxHeapSize() const; + QString javaPath() const; + bool autoDetectJava() const; + bool autoDownloadJava() const; + + void updateThresholds(); + + protected slots: + void onSpinBoxValueChanged(int); + void memoryValueChanged(); + void javaPathEdited(const QString& path); + void javaVersionSelected(BaseVersion::Ptr version); + void on_javaBrowseBtn_clicked(); + void on_javaStatusBtn_clicked(); + void javaDownloadBtn_clicked(); + void checkFinished(const JavaChecker::Result& result); + + protected: /* methods */ + void checkJavaPathOnEdit(const QString& path); + void checkJavaPath(const QString& path); + void setJavaStatus(JavaStatus status); + void setupUi(); + + private: /* data */ + VersionSelectWidget* m_versionWidget = nullptr; + QVBoxLayout* m_verticalLayout = nullptr; + QSpacerItem* m_verticalSpacer = nullptr; + + QLineEdit* m_javaPathTextBox = nullptr; + QPushButton* m_javaBrowseBtn = nullptr; + QToolButton* m_javaStatusBtn = nullptr; + QHBoxLayout* m_horizontalLayout = nullptr; + + QGroupBox* m_memoryGroupBox = nullptr; + QGridLayout* m_gridLayout_2 = nullptr; + QSpinBox* m_maxMemSpinBox = nullptr; + QLabel* m_labelMinMem = nullptr; + QLabel* m_labelMaxMem = nullptr; + QLabel* m_labelMaxMemIcon = nullptr; + QSpinBox* m_minMemSpinBox = nullptr; + QLabel* m_labelPermGen = nullptr; + QSpinBox* m_permGenSpinBox = nullptr; + + QHBoxLayout* m_horizontalBtnLayout = nullptr; + QPushButton* m_javaDownloadBtn = nullptr; + QIcon goodIcon; + QIcon yellowIcon; + QIcon badIcon; + + QGroupBox* m_autoJavaGroupBox = nullptr; + QVBoxLayout* m_veriticalJavaLayout = nullptr; + QCheckBox* m_autodetectJavaCheckBox = nullptr; + QCheckBox* m_autodownloadCheckBox = nullptr; + + unsigned int observedMinMemory = 0; + unsigned int observedMaxMemory = 0; + unsigned int observedPermGenMemory = 0; + QString queuedCheck; + uint64_t m_availableMemory = 0ull; + shared_qobject_ptr m_checker; + JavaChecker::Result m_result; + QTimer* m_memoryTimer; +}; diff --git a/launcher/ui/widgets/MinecraftSettingsWidget.cpp b/launcher/ui/widgets/MinecraftSettingsWidget.cpp new file mode 100644 index 000000000..cec7f267f --- /dev/null +++ b/launcher/ui/widgets/MinecraftSettingsWidget.cpp @@ -0,0 +1,460 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2022 Jamie Mansfield + * Copyright (C) 2022 Sefa Eyeoglu + * 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. + */ + +#include "MinecraftSettingsWidget.h" + +#include "Application.h" +#include "BuildConfig.h" +#include "minecraft/WorldList.h" +#include "minecraft/auth/AccountList.h" +#include "settings/Setting.h" +#include "ui_MinecraftSettingsWidget.h" + +MinecraftSettingsWidget::MinecraftSettingsWidget(MinecraftInstancePtr instance, QWidget* parent) + : QWidget(parent), m_instance(std::move(instance)), m_ui(new Ui::MinecraftSettingsWidget) +{ + m_ui->setupUi(this); + + if (m_instance == nullptr) { + for (int i = m_ui->settingsTabs->count() - 1; i >= 0; --i) { + const QString name = m_ui->settingsTabs->widget(i)->objectName(); + + if (name == "javaPage" || name == "launchPage") + m_ui->settingsTabs->removeTab(i); + } + + m_ui->openGlobalSettingsButton->setVisible(false); + } else { + m_javaSettings = new JavaSettingsWidget(m_instance, this); + m_ui->javaScrollArea->setWidget(m_javaSettings); + + m_ui->showGameTime->setText(tr("Show time &playing this instance")); + m_ui->recordGameTime->setText(tr("&Record time playing this instance")); + m_ui->showGlobalGameTime->hide(); + m_ui->showGameTimeWithoutDays->hide(); + + m_ui->maximizedWarning->setText( + tr("Warning: The maximized option is " + "not fully supported on this Minecraft version.")); + + m_ui->miscellaneousSettingsBox->setCheckable(true); + m_ui->consoleSettingsBox->setCheckable(true); + m_ui->windowSizeGroupBox->setCheckable(true); + m_ui->nativeWorkaroundsGroupBox->setCheckable(true); + m_ui->perfomanceGroupBox->setCheckable(true); + m_ui->gameTimeGroupBox->setCheckable(true); + m_ui->legacySettingsGroupBox->setCheckable(true); + + m_quickPlaySingleplayer = m_instance->traits().contains("feature:is_quick_play_singleplayer"); + if (m_quickPlaySingleplayer) { + auto worlds = m_instance->worldList(); + worlds->update(); + for (const auto& world : worlds->allWorlds()) { + m_ui->worldsCb->addItem(world.folderName()); + } + } else { + m_ui->worldsCb->hide(); + m_ui->worldJoinButton->hide(); + m_ui->serverJoinAddressButton->setChecked(true); + m_ui->serverJoinAddress->setEnabled(true); + m_ui->serverJoinAddressButton->setStyleSheet("QRadioButton::indicator { width: 0px; height: 0px; }"); + } + + connect(m_ui->openGlobalSettingsButton, &QCommandLinkButton::clicked, this, &MinecraftSettingsWidget::openGlobalSettings); + connect(m_ui->serverJoinAddressButton, &QAbstractButton::toggled, m_ui->serverJoinAddress, &QWidget::setEnabled); + connect(m_ui->worldJoinButton, &QAbstractButton::toggled, m_ui->worldsCb, &QWidget::setEnabled); + } + + m_ui->maximizedWarning->hide(); + + connect(m_ui->maximizedCheckBox, &QCheckBox::toggled, this, + [this](const bool value) { m_ui->maximizedWarning->setVisible(value && (m_instance == nullptr || !m_instance->isLegacy())); }); + +#if !defined(Q_OS_LINUX) + m_ui->perfomanceGroupBox->hide(); +#endif + + if (!(APPLICATION->capabilities() & Application::SupportsGameMode)) { + m_ui->enableFeralGamemodeCheck->setDisabled(true); + m_ui->enableFeralGamemodeCheck->setToolTip(tr("Feral Interactive's GameMode could not be found on your system.")); + } + + if (!(APPLICATION->capabilities() & Application::SupportsMangoHud)) { + m_ui->enableMangoHud->setEnabled(false); + m_ui->enableMangoHud->setToolTip(tr("MangoHud could not be found on your system.")); + } + + connect(m_ui->useNativeOpenALCheck, &QAbstractButton::toggled, m_ui->lineEditOpenALPath, &QWidget::setEnabled); + connect(m_ui->useNativeGLFWCheck, &QAbstractButton::toggled, m_ui->lineEditGLFWPath, &QWidget::setEnabled); + + loadSettings(); +} + +MinecraftSettingsWidget::~MinecraftSettingsWidget() +{ + delete m_ui; +} + +void MinecraftSettingsWidget::loadSettings() +{ + SettingsObjectPtr settings; + + if (m_instance != nullptr) + settings = m_instance->settings(); + else + settings = APPLICATION->settings(); + + // Game Window + m_ui->windowSizeGroupBox->setChecked(m_instance == nullptr || settings->get("OverrideWindow").toBool()); + m_ui->windowSizeGroupBox->setChecked(settings->get("OverrideWindow").toBool()); + m_ui->maximizedCheckBox->setChecked(settings->get("LaunchMaximized").toBool()); + m_ui->windowWidthSpinBox->setValue(settings->get("MinecraftWinWidth").toInt()); + m_ui->windowHeightSpinBox->setValue(settings->get("MinecraftWinHeight").toInt()); + + // Game Time + m_ui->gameTimeGroupBox->setChecked(m_instance == nullptr || settings->get("OverrideGameTime").toBool()); + m_ui->showGameTime->setChecked(settings->get("ShowGameTime").toBool()); + m_ui->recordGameTime->setChecked(settings->get("RecordGameTime").toBool()); + m_ui->showGlobalGameTime->setChecked(m_instance == nullptr && settings->get("ShowGlobalGameTime").toBool()); + m_ui->showGameTimeWithoutDays->setChecked(m_instance == nullptr && settings->get("ShowGameTimeWithoutDays").toBool()); + + // Console + m_ui->consoleSettingsBox->setChecked(m_instance == nullptr || settings->get("OverrideConsole").toBool()); + m_ui->showConsoleCheck->setChecked(settings->get("ShowConsole").toBool()); + m_ui->autoCloseConsoleCheck->setChecked(settings->get("AutoCloseConsole").toBool()); + m_ui->showConsoleErrorCheck->setChecked(settings->get("ShowConsoleOnError").toBool()); + + // Miscellaneous + m_ui->miscellaneousSettingsBox->setChecked(settings->get("OverrideMiscellaneous").toBool()); + m_ui->closeAfterLaunchCheck->setChecked(settings->get("CloseAfterLaunch").toBool()); + m_ui->quitAfterGameStopCheck->setChecked(settings->get("QuitAfterGameStop").toBool()); + + if (m_javaSettings != nullptr) + m_javaSettings->loadSettings(); + + // Custom commands + m_ui->customCommands->initialize(m_instance != nullptr, m_instance == nullptr || settings->get("OverrideCommands").toBool(), + settings->get("PreLaunchCommand").toString(), settings->get("WrapperCommand").toString(), + settings->get("PostExitCommand").toString()); + + // Environment variables + m_ui->environmentVariables->initialize(m_instance != nullptr, m_instance == nullptr || settings->get("OverrideEnv").toBool(), + settings->get("Env").toMap()); + + // Legacy Tweaks + m_ui->legacySettingsGroupBox->setChecked(m_instance == nullptr || settings->get("OverrideLegacySettings").toBool()); + m_ui->onlineFixes->setChecked(settings->get("OnlineFixes").toBool()); + + // Native Libraries + m_ui->nativeWorkaroundsGroupBox->setChecked(m_instance == nullptr || settings->get("OverrideNativeWorkarounds").toBool()); + m_ui->useNativeGLFWCheck->setChecked(settings->get("UseNativeGLFW").toBool()); + m_ui->lineEditGLFWPath->setText(settings->get("CustomGLFWPath").toString()); +#ifdef Q_OS_LINUX + m_ui->lineEditGLFWPath->setPlaceholderText(APPLICATION->m_detectedGLFWPath); +#else + m_ui->lineEditGLFWPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.GLFW_LIBRARY_NAME)); +#endif + m_ui->useNativeOpenALCheck->setChecked(settings->get("UseNativeOpenAL").toBool()); + m_ui->lineEditOpenALPath->setText(settings->get("CustomOpenALPath").toString()); +#ifdef Q_OS_LINUX + m_ui->lineEditOpenALPath->setPlaceholderText(APPLICATION->m_detectedOpenALPath); +#else + m_ui->lineEditOpenALPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.OPENAL_LIBRARY_NAME)); +#endif + + // Performance + m_ui->perfomanceGroupBox->setChecked(m_instance == nullptr || settings->get("OverridePerformance").toBool()); + m_ui->enableFeralGamemodeCheck->setChecked(settings->get("EnableFeralGamemode").toBool()); + m_ui->enableMangoHud->setChecked(settings->get("EnableMangoHud").toBool()); + m_ui->useDiscreteGpuCheck->setChecked(settings->get("UseDiscreteGpu").toBool()); + m_ui->useZink->setChecked(settings->get("UseZink").toBool()); + + m_ui->serverJoinGroupBox->setChecked(settings->get("JoinServerOnLaunch").toBool()); + + if (m_instance != nullptr) { + if (auto server = settings->get("JoinServerOnLaunchAddress").toString(); !server.isEmpty()) { + m_ui->serverJoinAddress->setText(server); + m_ui->serverJoinAddressButton->setChecked(true); + m_ui->worldJoinButton->setChecked(false); + m_ui->serverJoinAddress->setEnabled(true); + m_ui->worldsCb->setEnabled(false); + } else if (auto world = settings->get("JoinWorldOnLaunch").toString(); !world.isEmpty() && m_quickPlaySingleplayer) { + m_ui->worldsCb->setCurrentText(world); + m_ui->serverJoinAddressButton->setChecked(false); + m_ui->worldJoinButton->setChecked(true); + m_ui->serverJoinAddress->setEnabled(false); + m_ui->worldsCb->setEnabled(true); + } else { + m_ui->serverJoinAddressButton->setChecked(true); + m_ui->worldJoinButton->setChecked(false); + m_ui->serverJoinAddress->setEnabled(true); + m_ui->worldsCb->setEnabled(false); + } + + m_ui->instanceAccountGroupBox->setChecked(settings->get("UseAccountForInstance").toBool()); + updateAccountsMenu(*settings); + } + + m_ui->legacySettingsGroupBox->setChecked(settings->get("OverrideLegacySettings").toBool()); + m_ui->onlineFixes->setChecked(settings->get("OnlineFixes").toBool()); +} + +void MinecraftSettingsWidget::saveSettings() +{ + SettingsObjectPtr settings; + + if (m_instance != nullptr) + settings = m_instance->settings(); + else + settings = APPLICATION->settings(); + + { + SettingsObject::Lock lock(settings); + + // Miscellaneous + bool miscellaneous = m_instance == nullptr || m_ui->miscellaneousSettingsBox->isChecked(); + + if (m_instance != nullptr) + settings->set("OverrideMiscellaneous", miscellaneous); + + if (miscellaneous) { + settings->set("CloseAfterLaunch", m_ui->closeAfterLaunchCheck->isChecked()); + settings->set("QuitAfterGameStop", m_ui->quitAfterGameStopCheck->isChecked()); + } else { + settings->reset("CloseAfterLaunch"); + settings->reset("QuitAfterGameStop"); + } + + // Console + bool console = m_instance == nullptr || m_ui->consoleSettingsBox->isChecked(); + + if (m_instance != nullptr) + settings->set("OverrideConsole", console); + + if (console) { + settings->set("ShowConsole", m_ui->showConsoleCheck->isChecked()); + settings->set("AutoCloseConsole", m_ui->autoCloseConsoleCheck->isChecked()); + settings->set("ShowConsoleOnError", m_ui->showConsoleErrorCheck->isChecked()); + } else { + settings->reset("ShowConsole"); + settings->reset("AutoCloseConsole"); + settings->reset("ShowConsoleOnError"); + } + + // Window Size + bool window = m_instance == nullptr || m_ui->windowSizeGroupBox->isChecked(); + + if (m_instance != nullptr) + settings->set("OverrideWindow", window); + + if (window) { + settings->set("LaunchMaximized", m_ui->maximizedCheckBox->isChecked()); + settings->set("MinecraftWinWidth", m_ui->windowWidthSpinBox->value()); + settings->set("MinecraftWinHeight", m_ui->windowHeightSpinBox->value()); + } else { + settings->reset("LaunchMaximized"); + settings->reset("MinecraftWinWidth"); + settings->reset("MinecraftWinHeight"); + } + + // Custom Commands + bool custcmd = m_instance == nullptr || m_ui->customCommands->checked(); + + if (m_instance != nullptr) + settings->set("OverrideCommands", custcmd); + + if (custcmd) { + settings->set("PreLaunchCommand", m_ui->customCommands->prelaunchCommand()); + settings->set("WrapperCommand", m_ui->customCommands->wrapperCommand()); + settings->set("PostExitCommand", m_ui->customCommands->postexitCommand()); + } else { + settings->reset("PreLaunchCommand"); + settings->reset("WrapperCommand"); + settings->reset("PostExitCommand"); + } + + // Environment Variables + auto env = m_instance == nullptr || m_ui->environmentVariables->override(); + + if (m_instance != nullptr) + settings->set("OverrideEnv", env); + + if (env) + settings->set("Env", m_ui->environmentVariables->value()); + else + settings->reset("Env"); + + // Workarounds + bool workarounds = m_instance == nullptr || m_ui->nativeWorkaroundsGroupBox->isChecked(); + + if (m_instance != nullptr) + settings->set("OverrideNativeWorkarounds", workarounds); + + if (workarounds) { + settings->set("UseNativeGLFW", m_ui->useNativeGLFWCheck->isChecked()); + settings->set("CustomGLFWPath", m_ui->lineEditGLFWPath->text()); + settings->set("UseNativeOpenAL", m_ui->useNativeOpenALCheck->isChecked()); + settings->set("CustomOpenALPath", m_ui->lineEditOpenALPath->text()); + } else { + settings->reset("UseNativeGLFW"); + settings->reset("CustomGLFWPath"); + settings->reset("UseNativeOpenAL"); + settings->reset("CustomOpenALPath"); + } + + // Performance + bool performance = m_instance == nullptr || m_ui->perfomanceGroupBox->isChecked(); + + if (m_instance != nullptr) + settings->set("OverridePerformance", performance); + + if (performance) { + settings->set("EnableFeralGamemode", m_ui->enableFeralGamemodeCheck->isChecked()); + settings->set("EnableMangoHud", m_ui->enableMangoHud->isChecked()); + settings->set("UseDiscreteGpu", m_ui->useDiscreteGpuCheck->isChecked()); + settings->set("UseZink", m_ui->useZink->isChecked()); + } else { + settings->reset("EnableFeralGamemode"); + settings->reset("EnableMangoHud"); + settings->reset("UseDiscreteGpu"); + settings->reset("UseZink"); + } + + // Game time + bool gameTime = m_instance == nullptr || m_ui->gameTimeGroupBox->isChecked(); + + if (m_instance != nullptr) + settings->set("OverrideGameTime", gameTime); + + if (gameTime) { + settings->set("ShowGameTime", m_ui->showGameTime->isChecked()); + settings->set("RecordGameTime", m_ui->recordGameTime->isChecked()); + } else { + settings->reset("ShowGameTime"); + settings->reset("RecordGameTime"); + } + + if (m_instance == nullptr) { + settings->set("ShowGlobalGameTime", m_ui->showGlobalGameTime->isChecked()); + settings->set("ShowGameTimeWithoutDays", m_ui->showGameTimeWithoutDays->isChecked()); + } + + if (m_instance != nullptr) { + // Join server on launch + bool joinServerOnLaunch = m_ui->serverJoinGroupBox->isChecked(); + settings->set("JoinServerOnLaunch", joinServerOnLaunch); + if (joinServerOnLaunch) { + if (m_ui->serverJoinAddressButton->isChecked() || !m_quickPlaySingleplayer) { + settings->set("JoinServerOnLaunchAddress", m_ui->serverJoinAddress->text()); + settings->reset("JoinWorldOnLaunch"); + } else { + settings->set("JoinWorldOnLaunch", m_ui->worldsCb->currentText()); + settings->reset("JoinServerOnLaunchAddress"); + } + } else { + settings->reset("JoinServerOnLaunchAddress"); + settings->reset("JoinWorldOnLaunch"); + } + + // Use an account for this instance + bool useAccountForInstance = m_ui->instanceAccountGroupBox->isChecked(); + settings->set("UseAccountForInstance", useAccountForInstance); + if (useAccountForInstance) { + int accountIndex = m_ui->instanceAccountSelector->currentIndex(); + + if (accountIndex != -1) { + const MinecraftAccountPtr account = APPLICATION->accounts()->at(accountIndex); + if (account != nullptr) + settings->set("InstanceAccountId", account->profileId()); + } + } else { + settings->reset("InstanceAccountId"); + } + } + + bool overrideLegacySettings = m_instance == nullptr || m_ui->legacySettingsGroupBox->isChecked(); + + if (m_instance != nullptr) + settings->set("OverrideLegacySettings", overrideLegacySettings); + + if (overrideLegacySettings) { + settings->set("OnlineFixes", m_ui->onlineFixes->isChecked()); + } else { + settings->reset("OnlineFixes"); + } + } + + if (m_javaSettings != nullptr) + m_javaSettings->saveSettings(); +} + +void MinecraftSettingsWidget::openGlobalSettings() +{ + const QString id = m_ui->settingsTabs->currentWidget()->objectName(); + + qDebug() << id; + + if (id == "javaPage") + APPLICATION->ShowGlobalSettings(this, "java-settings"); + else // TODO select tab + APPLICATION->ShowGlobalSettings(this, "minecraft-settings"); +} + +void MinecraftSettingsWidget::updateAccountsMenu(const SettingsObject& settings) +{ + m_ui->instanceAccountSelector->clear(); + auto accounts = APPLICATION->accounts(); + int accountIndex = accounts->findAccountByProfileId(settings.get("InstanceAccountId").toString()); + + for (int i = 0; i < accounts->count(); i++) { + MinecraftAccountPtr account = accounts->at(i); + + QIcon face = account->getFace(); + + if (face.isNull()) + face = APPLICATION->getThemedIcon("noaccount"); + + m_ui->instanceAccountSelector->addItem(face, account->profileName(), i); + if (i == accountIndex) + m_ui->instanceAccountSelector->setCurrentIndex(i); + } +} + +bool MinecraftSettingsWidget::isQuickPlaySupported() +{ + return m_instance->traits().contains("feature:is_quick_play_singleplayer"); +} diff --git a/launcher/ui/pages/global/CustomCommandsPage.h b/launcher/ui/widgets/MinecraftSettingsWidget.h similarity index 69% rename from launcher/ui/pages/global/CustomCommandsPage.h rename to launcher/ui/widgets/MinecraftSettingsWidget.h index ec1204ffe..86effb337 100644 --- a/launcher/ui/pages/global/CustomCommandsPage.h +++ b/launcher/ui/widgets/MinecraftSettingsWidget.h @@ -2,6 +2,7 @@ /* * Prism Launcher - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield + * 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 @@ -35,29 +36,29 @@ #pragma once -#include -#include +#include +#include "JavaSettingsWidget.h" +#include "minecraft/MinecraftInstance.h" -#include -#include "ui/pages/BasePage.h" -#include "ui/widgets/CustomCommands.h" - -class CustomCommandsPage : public QWidget, public BasePage { - Q_OBJECT +namespace Ui { +class MinecraftSettingsWidget; +} +class MinecraftSettingsWidget : public QWidget { public: - explicit CustomCommandsPage(QWidget* parent = 0); - ~CustomCommandsPage(); + MinecraftSettingsWidget(MinecraftInstancePtr instance, QWidget* parent = nullptr); + ~MinecraftSettingsWidget() override; - QString displayName() const override { return tr("Custom Commands"); } - QIcon icon() const override { return APPLICATION->getThemedIcon("custom-commands"); } - QString id() const override { return "custom-commands"; } - QString helpPage() const override { return "Custom-commands"; } - bool apply() override; - void retranslate() override; + void loadSettings(); + void saveSettings(); private: - void applySettings(); - void loadSettings(); - CustomCommands* commands; + void openGlobalSettings(); + void updateAccountsMenu(const SettingsObject& settings); + bool isQuickPlaySupported(); + + MinecraftInstancePtr m_instance; + Ui::MinecraftSettingsWidget* m_ui; + JavaSettingsWidget* m_javaSettings = nullptr; + bool m_quickPlaySingleplayer = false; }; diff --git a/launcher/ui/widgets/MinecraftSettingsWidget.ui b/launcher/ui/widgets/MinecraftSettingsWidget.ui new file mode 100644 index 000000000..daa065ac8 --- /dev/null +++ b/launcher/ui/widgets/MinecraftSettingsWidget.ui @@ -0,0 +1,686 @@ + + + MinecraftSettingsWidget + + + + 0 + 0 + 648 + 400 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Open &Global Settings + + + The settings here are overrides for global settings. + + + + + + + 0 + + + + General + + + + + + + 0 + 0 + + + + Qt::ScrollBarAlwaysOff + + + true + + + + + 0 + -253 + 610 + 550 + + + + + + + true + + + Game &Window + + + false + + + false + + + + + + Start Minecraft maximized + + + + + + + The base game only supports resolution. In order to simulate the maximized behaviour the current implementation approximates the maximum display size. + + + <html><head/><body><p><span style=" font-weight:600; color:#f5c211;">Warning</span><span style=" color:#f5c211;">: The maximized option may not be fully supported on all Minecraft versions.</span></p></body></html> + + + + + + + + + Window height: + + + + + + + Window width: + + + + + + + 1 + + + 65536 + + + 1 + + + 854 + + + + + + + 1 + + + 65536 + + + 480 + + + + + + + + + + + + true + + + Game &Time + + + false + + + false + + + + + + Show time spent &playing instances + + + + + + + &Record time spent playing instances + + + + + + + Show the &total time played across instances + + + + + + + Always show durations in &hours + + + + + + + + + + true + + + &Console + + + false + + + false + + + + + + Show console while the game is running + + + + + + + Automatically close console when the game quits + + + + + + + Show console when the game crashes + + + + + + + + + + &Miscellaneous + + + false + + + false + + + + + + Close the launcher after game window opens + + + + + + + Quit the launcher after game window closes + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + Java + + + + + + true + + + + + 0 + 0 + 624 + 297 + + + + + + + + + + Tweaks + + + + + + Qt::ScrollBarAlwaysOff + + + true + + + + + 0 + -101 + 610 + 398 + + + + + + + &Legacy Tweaks + + + false + + + false + + + + + + <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) + + + + + + + + + + true + + + &Native Libraries + + + false + + + false + + + + + + Use system installation of OpenAL + + + + + + + &GLFW library path + + + lineEditGLFWPath + + + + + + + Use system installation of GLFW + + + + + + + false + + + + + + + &OpenAL library path + + + lineEditOpenALPath + + + + + + + false + + + + + + + + + + true + + + &Performance + + + false + + + false + + + + + + <html><head/><body><p>Enable Feral Interactive's GameMode, to potentially improve gaming performance.</p></body></html> + + + Enable Feral GameMode + + + + + + + <html><head/><body><p>Enable MangoHud's advanced performance overlay.</p></body></html> + + + Enable MangoHud + + + + + + + <html><head/><body><p>Use the discrete GPU instead of the primary GPU.</p></body></html> + + + Use discrete GPU + + + + + + + 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 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + Launch + + + + + + true + + + + + 0 + 0 + 624 + 297 + + + + + + + Override default &account + + + true + + + false + + + + + + + + + 0 + 0 + + + + Account: + + + + + + + + + + + + + + + Set a &target to join on launch + + + true + + + false + + + + + + Server address: + + + + + + + + + + Singleplayer world + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + Custom Commands + + + + + + + + + + Environment Variables + + + + + + + + + + + + + + CustomCommands + QWidget +
ui/widgets/CustomCommands.h
+ 1 +
+ + EnvironmentVariables + QWidget +
ui/widgets/EnvironmentVariables.h
+ 1 +
+
+ + openGlobalSettingsButton + settingsTabs + scrollArea + maximizedCheckBox + windowWidthSpinBox + windowHeightSpinBox + showGameTime + recordGameTime + showGlobalGameTime + showGameTimeWithoutDays + showConsoleCheck + autoCloseConsoleCheck + showConsoleErrorCheck + closeAfterLaunchCheck + quitAfterGameStopCheck + javaScrollArea + scrollArea_2 + onlineFixes + useNativeGLFWCheck + lineEditGLFWPath + useNativeOpenALCheck + lineEditOpenALPath + perfomanceGroupBox + enableFeralGamemodeCheck + enableMangoHud + useDiscreteGpuCheck + useZink + scrollArea_3 + instanceAccountGroupBox + instanceAccountSelector + serverJoinGroupBox + serverJoinAddressButton + serverJoinAddress + worldJoinButton + worldsCb + + + +
diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index 68adcdb71..37211693f 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -134,6 +134,7 @@ ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, QWi ui->versions->hide(); ui->showAllVersions->hide(); ui->environmentGroup->hide(); + ui->openSource->hide(); } ui->versions->setStyleSheet("combobox-popup: 0;"); @@ -159,6 +160,12 @@ ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, QWi } connect(ui->hideInstalled, &QCheckBox::stateChanged, this, &ModFilterWidget::onHideInstalledFilterChanged); + connect(ui->openSource, &QCheckBox::stateChanged, this, &ModFilterWidget::onOpenSourceFilterChanged); + + connect(ui->releaseCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onReleaseFilterChanged); + connect(ui->betaCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onReleaseFilterChanged); + connect(ui->alphaCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onReleaseFilterChanged); + connect(ui->unknownCb, &QCheckBox::stateChanged, this, &ModFilterWidget::onReleaseFilterChanged); setHidden(true); loadVersionList(); @@ -208,6 +215,7 @@ void ModFilterWidget::loadVersionList() void ModFilterWidget::prepareBasicFilter() { + m_filter->openSource = false; if (m_instance) { m_filter->hideInstalled = false; m_filter->side = ""; // or "both" @@ -271,16 +279,17 @@ void ModFilterWidget::onSideFilterChanged() { QString side; - if (ui->clientSide->isChecked() != ui->serverSide->isChecked()) { - if (ui->clientSide->isChecked()) - side = "client"; - else - side = "server"; + if (ui->clientSide->isChecked() && !ui->serverSide->isChecked()) { + side = "client"; + } else if (!ui->clientSide->isChecked() && ui->serverSide->isChecked()) { + side = "server"; + } else if (ui->clientSide->isChecked() && ui->serverSide->isChecked()) { + side = "both"; } else { - // both are checked or none are checked; in either case no filtering will happen side = ""; } + m_filter_changed = side != m_filter->side; m_filter->side = side; if (m_filter_changed) @@ -337,4 +346,30 @@ void ModFilterWidget::setCategories(const QList& categori } } -#include "ModFilterWidget.moc" \ No newline at end of file +void ModFilterWidget::onOpenSourceFilterChanged() +{ + auto open = ui->openSource->isChecked(); + m_filter_changed = open != m_filter->openSource; + m_filter->openSource = open; + if (m_filter_changed) + emit filterChanged(); +} + +void ModFilterWidget::onReleaseFilterChanged() +{ + std::list releases; + if (ui->releaseCb->isChecked()) + releases.push_back(ModPlatform::IndexedVersionType(ModPlatform::IndexedVersionType::VersionType::Release)); + if (ui->betaCb->isChecked()) + releases.push_back(ModPlatform::IndexedVersionType(ModPlatform::IndexedVersionType::VersionType::Beta)); + if (ui->alphaCb->isChecked()) + releases.push_back(ModPlatform::IndexedVersionType(ModPlatform::IndexedVersionType::VersionType::Alpha)); + if (ui->unknownCb->isChecked()) + releases.push_back(ModPlatform::IndexedVersionType(ModPlatform::IndexedVersionType::VersionType::Unknown)); + m_filter_changed = releases != m_filter->releases; + m_filter->releases = releases; + if (m_filter_changed) + emit filterChanged(); +} + +#include "ModFilterWidget.moc" diff --git a/launcher/ui/widgets/ModFilterWidget.h b/launcher/ui/widgets/ModFilterWidget.h index 50f0e06c9..41a2f1bbd 100644 --- a/launcher/ui/widgets/ModFilterWidget.h +++ b/launcher/ui/widgets/ModFilterWidget.h @@ -64,11 +64,12 @@ class ModFilterWidget : public QTabWidget { QString side; bool hideInstalled; QStringList categoryIds; + bool openSource; bool operator==(const Filter& other) const { return hideInstalled == other.hideInstalled && side == other.side && loaders == other.loaders && versions == other.versions && - releases == other.releases && categoryIds == other.categoryIds; + releases == other.releases && categoryIds == other.categoryIds && openSource == other.openSource; } bool operator!=(const Filter& other) const { return !(*this == other); } @@ -107,6 +108,8 @@ class ModFilterWidget : public QTabWidget { void onSideFilterChanged(); void onHideInstalledFilterChanged(); void onShowAllVersionsChanged(); + void onOpenSourceFilterChanged(); + void onReleaseFilterChanged(); private: Ui::ModFilterWidget* ui; diff --git a/launcher/ui/widgets/ModFilterWidget.ui b/launcher/ui/widgets/ModFilterWidget.ui index 236847094..807a0019a 100644 --- a/launcher/ui/widgets/ModFilterWidget.ui +++ b/launcher/ui/widgets/ModFilterWidget.ui @@ -63,8 +63,8 @@ 0 0 - 308 - 598 + 294 + 781 @@ -188,6 +188,50 @@
+ + + + Open source only + + + + + + + Release type + + + + + + Release + + + + + + + Beta + + + + + + + Alpha + + + + + + + Unknown + + + + + + diff --git a/launcher/ui/widgets/SubTaskProgressBar.ui b/launcher/ui/widgets/SubTaskProgressBar.ui index 5431eab60..aabb68329 100644 --- a/launcher/ui/widgets/SubTaskProgressBar.ui +++ b/launcher/ui/widgets/SubTaskProgressBar.ui @@ -47,6 +47,9 @@ true + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + @@ -68,6 +71,9 @@ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse +
diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.cpp b/launcher/ui/widgets/ThemeCustomizationWidget.cpp index 7a54bd390..097678b8d 100644 --- a/launcher/ui/widgets/ThemeCustomizationWidget.cpp +++ b/launcher/ui/widgets/ThemeCustomizationWidget.cpp @@ -87,7 +87,7 @@ void ThemeCustomizationWidget::applyIconTheme(int index) { auto settings = APPLICATION->settings(); auto originalIconTheme = settings->get("IconTheme").toString(); - auto newIconTheme = ui->iconsComboBox->currentData().toString(); + auto newIconTheme = ui->iconsComboBox->itemData(index).toString(); if (originalIconTheme != newIconTheme) { settings->set("IconTheme", newIconTheme); APPLICATION->themeManager()->applyCurrentlySelectedTheme(); @@ -100,7 +100,7 @@ void ThemeCustomizationWidget::applyWidgetTheme(int index) { auto settings = APPLICATION->settings(); auto originalAppTheme = settings->get("ApplicationTheme").toString(); - auto newAppTheme = ui->widgetStyleComboBox->currentData().toString(); + auto newAppTheme = ui->widgetStyleComboBox->itemData(index).toString(); if (originalAppTheme != newAppTheme) { settings->set("ApplicationTheme", newAppTheme); APPLICATION->themeManager()->applyCurrentlySelectedTheme(); @@ -113,7 +113,7 @@ void ThemeCustomizationWidget::applyCatTheme(int index) { auto settings = APPLICATION->settings(); auto originalCat = settings->get("BackgroundCat").toString(); - auto newCat = ui->backgroundCatComboBox->currentData().toString(); + auto newCat = ui->backgroundCatComboBox->itemData(index).toString(); if (originalCat != newCat) { settings->set("BackgroundCat", newCat); } diff --git a/launcher/updater/prismupdater/UpdaterDialogs.cpp b/launcher/updater/prismupdater/UpdaterDialogs.cpp index 06dc161b1..eab3e6bbb 100644 --- a/launcher/updater/prismupdater/UpdaterDialogs.cpp +++ b/launcher/updater/prismupdater/UpdaterDialogs.cpp @@ -24,6 +24,7 @@ #include "ui_SelectReleaseDialog.h" +#include #include #include "Markdown.h" #include "StringUtils.h" @@ -55,6 +56,9 @@ SelectReleaseDialog::SelectReleaseDialog(const Version& current_version, const Q connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &SelectReleaseDialog::accept); connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &SelectReleaseDialog::reject); + + ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); + ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK")); } SelectReleaseDialog::~SelectReleaseDialog() diff --git a/libraries/cmark b/libraries/cmark index 8fbf02968..3460cd809 160000 --- a/libraries/cmark +++ b/libraries/cmark @@ -1 +1 @@ -Subproject commit 8fbf029685482827828b5858444157052f1b0a5f +Subproject commit 3460cd809b6dd311b58e92733ece2fc956224fd2 diff --git a/libraries/extra-cmake-modules b/libraries/extra-cmake-modules index bbcbaff78..a3d9394ab 160000 --- a/libraries/extra-cmake-modules +++ b/libraries/extra-cmake-modules @@ -1 +1 @@ -Subproject commit bbcbaff78283270c2beee69afd8d5b91da854af8 +Subproject commit a3d9394aba4b35789293378e04fb7473d65edf97 diff --git a/libraries/filesystem b/libraries/filesystem index 2fc4b4637..076592ce6 160000 --- a/libraries/filesystem +++ b/libraries/filesystem @@ -1 +1 @@ -Subproject commit 2fc4b463759e043476fc0036da094e5877e3dd50 +Subproject commit 076592ce6e64568521b88a11881aa36b3d3f7048 diff --git a/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java b/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java index dc518be64..084fbc849 100644 --- a/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java +++ b/libraries/launcher/org/prismlauncher/launcher/impl/StandardLauncher.java @@ -76,13 +76,10 @@ public final class StandardLauncher extends AbstractLauncher { @Override public void launch() throws Throwable { // window size, title and state - // FIXME doesn't support maximisation - if (!maximize) { - gameArgs.add("--width"); - gameArgs.add(Integer.toString(width)); - gameArgs.add("--height"); - gameArgs.add(Integer.toString(height)); - } + gameArgs.add("--width"); + gameArgs.add(Integer.toString(width)); + gameArgs.add("--height"); + gameArgs.add(Integer.toString(height)); if (serverAddress != null) { if (quickPlayMultiplayerSupported) { diff --git a/libraries/quazip b/libraries/quazip index 9d3aa3ee9..8aeb3f7d8 160000 --- a/libraries/quazip +++ b/libraries/quazip @@ -1 +1 @@ -Subproject commit 9d3aa3ee948c1cde5a9f873ecbc3bb229c1182ee +Subproject commit 8aeb3f7d8254f4bf1f7c6cf2a8f59c2ca141a552 diff --git a/libraries/tomlplusplus b/libraries/tomlplusplus index 7eb2ffcc0..c4369ae1d 160000 --- a/libraries/tomlplusplus +++ b/libraries/tomlplusplus @@ -1 +1 @@ -Subproject commit 7eb2ffcc09f8e9890dc0b77ff8ab00fc53b1f2b8 +Subproject commit c4369ae1d8955cae20c4ab40b9813ef4b60e48be diff --git a/libraries/zlib b/libraries/zlib index 04f42ceca..51b7f2abd 160000 --- a/libraries/zlib +++ b/libraries/zlib @@ -1 +1 @@ -Subproject commit 04f42ceca40f73e2978b50e93806c2a18c1281fc +Subproject commit 51b7f2abdade71cd9bb0e7a373ef2610ec6f9daf diff --git a/nix/README.md b/nix/README.md index 8bb658477..7c43658f9 100644 --- a/nix/README.md +++ b/nix/README.md @@ -4,7 +4,7 @@ Prism Launcher is packaged in [nixpkgs](https://github.com/NixOS/nixpkgs/) since 22.11. -See [Package variants](#package-variants) for a list of available packages. +Check the [NixOS Wiki](https://wiki.nixos.org/wiki/Prism_Launcher) for up-to-date instructions. ## Installing a development release (flake) @@ -223,4 +223,3 @@ The following parameters can be overridden: - `jdks` (default: `[ jdk21 jdk17 jdk8 ]`) Java runtimes added to `PRISMLAUNCHER_JAVA_PATHS` variable - `msaClientID` (default: `null`, requires full rebuild!) Client ID used for Microsoft Authentication - `textToSpeechSupport` (default: `isLinux`) Turn on/off support for text-to-speech on Linux (macOS will always have this) -- `withWaylandGLFW` (default: `isLinux`) Build with support for native Wayland via a custom GLFW diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix index 7fc383ff3..7ba20b68b 100644 --- a/nix/unwrapped.nix +++ b/nix/unwrapped.nix @@ -3,31 +3,31 @@ stdenv, cmake, cmark, - darwin, + apple-sdk_11, extra-cmake-modules, gamemode, ghc_filesystem, jdk17, kdePackages, + libnbtplusplus, ninja, nix-filter, self, stripJavaArchivesHook, tomlplusplus, zlib, + msaClientID ? null, - gamemodeSupport ? stdenv.isLinux, - version, - libnbtplusplus, + gamemodeSupport ? stdenv.hostPlatform.isLinux, }: assert lib.assertMsg ( - gamemodeSupport -> stdenv.isLinux + gamemodeSupport -> stdenv.hostPlatform.isLinux ) "gamemodeSupport is only available on Linux."; stdenv.mkDerivation { pname = "prismlauncher-unwrapped"; - inherit version; + version = self.shortRev or self.dirtyShortRev or "unknown"; src = nix-filter.lib { root = self; @@ -66,20 +66,23 @@ stdenv.mkDerivation { tomlplusplus zlib ] - ++ lib.optionals stdenv.isDarwin [ darwin.apple_sdk.frameworks.Cocoa ] + ++ lib.optionals stdenv.hostPlatform.isDarwin [ apple-sdk_11 ] ++ lib.optional gamemodeSupport gamemode; - hardeningEnable = lib.optionals stdenv.isLinux [ "pie" ]; + hardeningEnable = lib.optionals stdenv.hostPlatform.isLinux [ "pie" ]; cmakeFlags = - [ (lib.cmakeFeature "Launcher_BUILD_PLATFORM" "nixpkgs") ] + [ + # downstream branding + (lib.cmakeFeature "Launcher_BUILD_PLATFORM" "nixpkgs") + ] ++ lib.optionals (msaClientID != null) [ (lib.cmakeFeature "Launcher_MSA_CLIENT_ID" (toString msaClientID)) ] ++ lib.optionals (lib.versionOlder kdePackages.qtbase.version "6") [ (lib.cmakeFeature "Launcher_QT_VERSION_MAJOR" "5") ] - ++ lib.optionals stdenv.isDarwin [ + ++ lib.optionals stdenv.hostPlatform.isDarwin [ # we wrap our binary manually (lib.cmakeFeature "INSTALL_BUNDLE" "nodeps") # disable built-in updater diff --git a/nix/wrapper.nix b/nix/wrapper.nix index 5632d483b..03c1f0421 100644 --- a/nix/wrapper.nix +++ b/nix/wrapper.nix @@ -1,73 +1,65 @@ { - lib, - stdenv, - symlinkJoin, - prismlauncher-unwrapped, - addOpenGLRunpath, + addDriverRunpath, + alsa-lib, flite, gamemode, - glfw, - glfw-wayland-minecraft, - glxinfo, - jdk8, + glfw3-minecraft, jdk17, jdk21, + jdk8, kdePackages, + lib, libGL, + libX11, + libXcursor, + libXext, + libXrandr, + libXxf86vm, + libjack2, libpulseaudio, libusb1, - makeWrapper, + mesa-demos, openal, pciutils, + pipewire, + prismlauncher-unwrapped, + stdenv, + symlinkJoin, udev, vulkan-loader, - xorg, + xrandr, + additionalLibs ? [ ], additionalPrograms ? [ ], - controllerSupport ? stdenv.isLinux, - gamemodeSupport ? stdenv.isLinux, + controllerSupport ? stdenv.hostPlatform.isLinux, + gamemodeSupport ? stdenv.hostPlatform.isLinux, jdks ? [ jdk21 jdk17 jdk8 ], msaClientID ? null, - textToSpeechSupport ? stdenv.isLinux, - # Adds `glfw-wayland-minecraft` to `LD_LIBRARY_PATH` - # when launched on wayland, allowing for the game to be run natively. - # Make sure to enable "Use system installation of GLFW" in instance settings - # for this to take effect - # - # Warning: This build of glfw may be unstable, and the launcher - # itself can take slightly longer to start - withWaylandGLFW ? false, + textToSpeechSupport ? stdenv.hostPlatform.isLinux, }: assert lib.assertMsg ( - controllerSupport -> stdenv.isLinux + controllerSupport -> stdenv.hostPlatform.isLinux ) "controllerSupport only has an effect on Linux."; assert lib.assertMsg ( - textToSpeechSupport -> stdenv.isLinux + textToSpeechSupport -> stdenv.hostPlatform.isLinux ) "textToSpeechSupport only has an effect on Linux."; -assert lib.assertMsg ( - withWaylandGLFW -> stdenv.isLinux -) "withWaylandGLFW is only available on Linux."; - let prismlauncher' = prismlauncher-unwrapped.override { inherit msaClientID gamemodeSupport; }; in + symlinkJoin { name = "prismlauncher-${prismlauncher'.version}"; paths = [ prismlauncher' ]; - nativeBuildInputs = - [ kdePackages.wrapQtAppsHook ] - # purposefully using a shell wrapper here for variable expansion - # see https://github.com/NixOS/nixpkgs/issues/172583 - ++ lib.optional withWaylandGLFW makeWrapper; + nativeBuildInputs = [ kdePackages.wrapQtAppsHook ]; buildInputs = [ @@ -75,45 +67,39 @@ symlinkJoin { kdePackages.qtsvg ] ++ lib.optional ( - lib.versionAtLeast kdePackages.qtbase.version "6" && stdenv.isLinux + lib.versionAtLeast kdePackages.qtbase.version "6" && stdenv.hostPlatform.isLinux ) kdePackages.qtwayland; - env = { - waylandPreExec = lib.optionalString withWaylandGLFW '' - if [ -n "$WAYLAND_DISPLAY" ]; then - export LD_LIBRARY_PATH=${lib.getLib glfw-wayland-minecraft}/lib:"$LD_LIBRARY_PATH" - fi - ''; - }; - - postBuild = - lib.optionalString withWaylandGLFW '' - qtWrapperArgs+=(--run "$waylandPreExec") - '' - + '' - wrapQtAppsHook - ''; + postBuild = '' + wrapQtAppsHook + ''; qtWrapperArgs = let runtimeLibs = [ - # lwjgl - glfw - libpulseaudio - libGL + (lib.getLib stdenv.cc.cc) + ## native versions + glfw3-minecraft openal - stdenv.cc.cc.lib - vulkan-loader # VulkanMod's lwjgl + ## openal + alsa-lib + libjack2 + libpulseaudio + pipewire + + ## glfw + libGL + libX11 + libXcursor + libXext + libXrandr + libXxf86vm udev # oshi - xorg.libX11 - xorg.libXext - xorg.libXcursor - xorg.libXrandr - xorg.libXxf86vm + vulkan-loader # VulkanMod's lwjgl ] ++ lib.optional textToSpeechSupport flite ++ lib.optional gamemodeSupport gamemode.lib @@ -121,14 +107,15 @@ symlinkJoin { ++ additionalLibs; runtimePrograms = [ - glxinfo + mesa-demos pciutils # need lspci - xorg.xrandr # needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128 + xrandr # needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128 ] ++ additionalPrograms; + in [ "--prefix PRISMLAUNCHER_JAVA_PATHS : ${lib.makeSearchPath "bin/java" jdks}" ] - ++ lib.optionals stdenv.isLinux [ - "--set LD_LIBRARY_PATH ${addOpenGLRunpath.driverLink}/lib:${lib.makeLibraryPath runtimeLibs}" + ++ lib.optionals stdenv.hostPlatform.isLinux [ + "--set LD_LIBRARY_PATH ${addDriverRunpath.driverLink}/lib:${lib.makeLibraryPath runtimeLibs}" "--prefix PATH : ${lib.makeBinPath runtimePrograms}" ]; diff --git a/program_info/AdhocSignedApp.entitlements b/program_info/AdhocSignedApp.entitlements new file mode 100644 index 000000000..032308a18 --- /dev/null +++ b/program_info/AdhocSignedApp.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.cs.disable-library-validation + + com.apple.security.device.audio-input + + com.apple.security.device.camera + + + diff --git a/program_info/App.entitlements b/program_info/App.entitlements index b46e8ff2a..73bf832c7 100644 --- a/program_info/App.entitlements +++ b/program_info/App.entitlements @@ -2,10 +2,6 @@ - com.apple.security.cs.disable-library-validation - - com.apple.security.cs.allow-dyld-environment-variables - com.apple.security.device.audio-input com.apple.security.device.camera diff --git a/program_info/CMakeLists.txt b/program_info/CMakeLists.txt index ee2e336b1..e693d757a 100644 --- a/program_info/CMakeLists.txt +++ b/program_info/CMakeLists.txt @@ -57,7 +57,7 @@ Section "Visual Studio Runtime" Pop $0 ${If} $0 == "OK" DetailPrint "Download successful" - ExecWait "$INSTDIR\vc_redist\$vc_redist_exe /install /passive /norestart\" + ExecWait "$INSTDIR\vc_redist\$vc_redist_exe /install /passive /norestart" ${Else} DetailPrint "Download failed with error $0" ${EndIf} diff --git a/program_info/genicons.sh b/program_info/genicons.sh index fe8d2e35e..cad811822 100755 --- a/program_info/genicons.sh +++ b/program_info/genicons.sh @@ -9,16 +9,7 @@ svg2png() { inkscape -w "$width" -h "$height" -o "$output_file" "$input_file" } -sipsresize() { - input_file="$1" - output_file="$2" - width="$3" - height="$4" - - sips -z "$width" "$height" "$input_file" --out "$output_file" -} - -if command -v "inkscape" && command -v "icotool"; then +if command -v "inkscape" && command -v "icotool" && command -v "oxipng"; then # Windows ICO d=$(mktemp -d) @@ -30,6 +21,8 @@ if command -v "inkscape" && command -v "icotool"; then svg2png org.prismlauncher.PrismLauncher.svg "$d/prismlauncher_128.png" 128 128 svg2png org.prismlauncher.PrismLauncher.svg "$d/prismlauncher_256.png" 256 256 + oxipng --opt max --strip all --alpha --interlace 0 "$d/prismlauncher_"*".png" + rm prismlauncher.ico && icotool -o prismlauncher.ico -c \ "$d/prismlauncher_256.png" \ "$d/prismlauncher_128.png" \ @@ -40,10 +33,10 @@ if command -v "inkscape" && command -v "icotool"; then "$d/prismlauncher_16.png" else echo "ERROR: Windows icons were NOT generated!" >&2 - echo "ERROR: requires inkscape and icotool in PATH" + echo "ERROR: requires inkscape, icotool and oxipng in PATH" fi -if command -v "inkscape" && command -v "sips" && command -v "iconutil"; then +if command -v "inkscape" && command -v "iconutil" && command -v "oxipng"; then # macOS ICNS d=$(mktemp -d) @@ -51,19 +44,22 @@ if command -v "inkscape" && command -v "sips" && command -v "iconutil"; then mkdir -p "$d" + svg2png org.prismlauncher.PrismLauncher.bigsur.svg "$d/icon_16x16.png" 16 16 + svg2png org.prismlauncher.PrismLauncher.bigsur.svg "$d/icon_16x16@2.png" 32 32 + svg2png org.prismlauncher.PrismLauncher.bigsur.svg "$d/icon_32x32.png" 32 32 + svg2png org.prismlauncher.PrismLauncher.bigsur.svg "$d/icon_32x32@2.png" 64 64 + svg2png org.prismlauncher.PrismLauncher.bigsur.svg "$d/icon_128x128.png" 128 128 + svg2png org.prismlauncher.PrismLauncher.bigsur.svg "$d/icon_128x128@2.png" 256 256 + svg2png org.prismlauncher.PrismLauncher.bigsur.svg "$d/icon_256x256.png" 256 256 + svg2png org.prismlauncher.PrismLauncher.bigsur.svg "$d/icon_256x256@2.png" 512 512 svg2png org.prismlauncher.PrismLauncher.bigsur.svg "$d/icon_512x512@2x.png" 1024 1024 - sipsresize "$d/icon_512x512@2.png" "$d/icon_16x16.png" 16 16 - sipsresize "$d/icon_512x512@2.png" "$d/icon_16x16@2.png" 32 32 - sipsresize "$d/icon_512x512@2.png" "$d/icon_32x32.png" 32 32 - sipsresize "$d/icon_512x512@2.png" "$d/icon_32x32@2.png" 64 64 - sipsresize "$d/icon_512x512@2.png" "$d/icon_128x128.png" 128 128 - sipsresize "$d/icon_512x512@2.png" "$d/icon_128x128@2.png" 256 256 - sipsresize "$d/icon_512x512@2.png" "$d/icon_256x256.png" 256 256 - sipsresize "$d/icon_512x512@2.png" "$d/icon_256x256@2.png" 512 512 + + oxipng --opt max --strip all --alpha --interlace 0 "$d/icon_"*".png" + iconutil -c icns "$d" else echo "ERROR: macOS icons were NOT generated!" >&2 - echo "ERROR: requires inkscape, sips and iconutil in PATH" + echo "ERROR: requires inkscape, iconutil and oxipng in PATH" fi # replace icon in themes diff --git a/program_info/org.prismlauncher.PrismLauncher.desktop.in b/program_info/org.prismlauncher.PrismLauncher.desktop.in index c0e4e9458..182d02b1d 100644 --- a/program_info/org.prismlauncher.PrismLauncher.desktop.in +++ b/program_info/org.prismlauncher.PrismLauncher.desktop.in @@ -7,7 +7,7 @@ Terminal=false Exec=@Launcher_APP_BINARY_NAME@ %U StartupNotify=true Icon=org.@Launcher_APP_BINARY_NAME@.@Launcher_CommonName@ -Categories=Game;ActionGame;AdventureGame;Simulation; +Categories=Game;ActionGame;AdventureGame;Simulation;PackageManager; Keywords=game;minecraft;mc; StartupWMClass=@Launcher_CommonName@ MimeType=application/zip;application/x-modrinth-modpack+zip;x-scheme-handler/curseforge;x-scheme-handler/@Launcher_APP_BINARY_NAME@; diff --git a/program_info/org.prismlauncher.PrismLauncher.metainfo.xml.in b/program_info/org.prismlauncher.PrismLauncher.metainfo.xml.in index a482f0e38..3c542c060 100644 --- a/program_info/org.prismlauncher.PrismLauncher.metainfo.xml.in +++ b/program_info/org.prismlauncher.PrismLauncher.metainfo.xml.in @@ -4,7 +4,7 @@ org.prismlauncher.PrismLauncher.desktop Prism Launcher Prism Launcher Contributors - A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once + Custom Minecraft Launcher to easily manage multiple Minecraft installations at once CC0-1.0 GPL-3.0-only https://prismlauncher.org/ diff --git a/program_info/prismlauncher.icns b/program_info/prismlauncher.icns index a4c0f7ea4..a5e6a8c3a 100644 Binary files a/program_info/prismlauncher.icns and b/program_info/prismlauncher.icns differ diff --git a/scripts/compress_images.sh b/scripts/compress_images.sh new file mode 100755 index 000000000..1eef9f1c4 --- /dev/null +++ b/scripts/compress_images.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +## If current working dirctory is ./scripts, ask to invoke from one directory up +if [ ! -d "scripts" ]; then + echo "Please run this script from the root directory of the project" + exit 1 +fi + +find . -type f -name '*.png' -not -path '*/libraries/*' -exec oxipng --opt max --strip all --alpha --interlace 0 {} \; diff --git a/tests/DummyResourceAPI.h b/tests/DummyResourceAPI.h index 35de95151..f8ab71e59 100644 --- a/tests/DummyResourceAPI.h +++ b/tests/DummyResourceAPI.h @@ -37,7 +37,7 @@ class DummyResourceAPI : public ResourceAPI { [[nodiscard]] Task::Ptr searchProjects(SearchArgs&&, SearchCallbacks&& callbacks) const override { auto task = makeShared(); - QObject::connect(task.get(), &Task::succeeded, [=] { + QObject::connect(task.get(), &Task::succeeded, [callbacks] { auto json = searchRequestResult(); callbacks.on_succeed(json); }); diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp index 1d3cee85f..ca0313bb4 100644 --- a/tests/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -63,7 +63,7 @@ class LinkTask : public Task { qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks"; qDebug() << "atempting to run with privelage"; - connect(m_lnk, &FS::create_link::finishedPrivileged, this, [&](bool gotResults) { + connect(m_lnk, &FS::create_link::finishedPrivileged, this, [this](bool gotResults) { if (gotResults) { emitSucceeded(); } else { @@ -113,22 +113,12 @@ class FileSystemTest : public QObject { QTest::addColumn("path1"); QTest::addColumn("path2"); - QTest::newRow("qt 1") << "/abc/def/ghi/jkl" - << "/abc/def" - << "ghi/jkl"; - QTest::newRow("qt 2") << "/abc/def/ghi/jkl" - << "/abc/def/" - << "ghi/jkl"; + QTest::newRow("qt 1") << "/abc/def/ghi/jkl" << "/abc/def" << "ghi/jkl"; + QTest::newRow("qt 2") << "/abc/def/ghi/jkl" << "/abc/def/" << "ghi/jkl"; #if defined(Q_OS_WIN) - QTest::newRow("win native, from C:") << "C:/abc" - << "C:" - << "abc"; - QTest::newRow("win native 1") << "C:/abc/def/ghi/jkl" - << "C:\\abc\\def" - << "ghi\\jkl"; - QTest::newRow("win native 2") << "C:/abc/def/ghi/jkl" - << "C:\\abc\\def\\" - << "ghi\\jkl"; + QTest::newRow("win native, from C:") << "C:/abc" << "C:" << "abc"; + QTest::newRow("win native 1") << "C:/abc/def/ghi/jkl" << "C:\\abc\\def" << "ghi\\jkl"; + QTest::newRow("win native 2") << "C:/abc/def/ghi/jkl" << "C:\\abc\\def\\" << "ghi\\jkl"; #endif } @@ -148,39 +138,15 @@ class FileSystemTest : public QObject { QTest::addColumn("path2"); QTest::addColumn("path3"); - QTest::newRow("qt 1") << "/abc/def/ghi/jkl" - << "/abc" - << "def" - << "ghi/jkl"; - QTest::newRow("qt 2") << "/abc/def/ghi/jkl" - << "/abc/" - << "def" - << "ghi/jkl"; - QTest::newRow("qt 3") << "/abc/def/ghi/jkl" - << "/abc" - << "def/" - << "ghi/jkl"; - QTest::newRow("qt 4") << "/abc/def/ghi/jkl" - << "/abc/" - << "def/" - << "ghi/jkl"; + QTest::newRow("qt 1") << "/abc/def/ghi/jkl" << "/abc" << "def" << "ghi/jkl"; + QTest::newRow("qt 2") << "/abc/def/ghi/jkl" << "/abc/" << "def" << "ghi/jkl"; + QTest::newRow("qt 3") << "/abc/def/ghi/jkl" << "/abc" << "def/" << "ghi/jkl"; + QTest::newRow("qt 4") << "/abc/def/ghi/jkl" << "/abc/" << "def/" << "ghi/jkl"; #if defined(Q_OS_WIN) - QTest::newRow("win 1") << "C:/abc/def/ghi/jkl" - << "C:\\abc" - << "def" - << "ghi\\jkl"; - QTest::newRow("win 2") << "C:/abc/def/ghi/jkl" - << "C:\\abc\\" - << "def" - << "ghi\\jkl"; - QTest::newRow("win 3") << "C:/abc/def/ghi/jkl" - << "C:\\abc" - << "def\\" - << "ghi\\jkl"; - QTest::newRow("win 4") << "C:/abc/def/ghi/jkl" - << "C:\\abc\\" - << "def" - << "ghi\\jkl"; + QTest::newRow("win 1") << "C:/abc/def/ghi/jkl" << "C:\\abc" << "def" << "ghi\\jkl"; + QTest::newRow("win 2") << "C:/abc/def/ghi/jkl" << "C:\\abc\\" << "def" << "ghi\\jkl"; + QTest::newRow("win 3") << "C:/abc/def/ghi/jkl" << "C:\\abc" << "def\\" << "ghi\\jkl"; + QTest::newRow("win 4") << "C:/abc/def/ghi/jkl" << "C:\\abc\\" << "def" << "ghi\\jkl"; #endif } @@ -369,11 +335,12 @@ class FileSystemTest : public QObject { LinkTask lnk_tsk(folder, target_dir.path()); lnk_tsk.linkRecursively(false); - QObject::connect(&lnk_tsk, &Task::finished, - [&] { QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been."); }); + QObject::connect(&lnk_tsk, &Task::finished, [&lnk_tsk] { + QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been."); + }); lnk_tsk.start(); - QVERIFY2(QTest::qWaitFor([&]() { return lnk_tsk.isFinished(); }, 100000), "Task didn't finish as it should."); + QVERIFY2(QTest::qWaitFor([&lnk_tsk]() { return lnk_tsk.isFinished(); }, 100000), "Task didn't finish as it should."); for (auto entry : target_dir.entryList()) { qDebug() << entry; @@ -465,11 +432,12 @@ class FileSystemTest : public QObject { RegexpMatcher re("[.]?mcmeta"); lnk_tsk.matcher(&re); lnk_tsk.linkRecursively(true); - QObject::connect(&lnk_tsk, &Task::finished, - [&] { QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been."); }); + QObject::connect(&lnk_tsk, &Task::finished, [&lnk_tsk] { + QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been."); + }); lnk_tsk.start(); - QVERIFY2(QTest::qWaitFor([&]() { return lnk_tsk.isFinished(); }, 100000), "Task didn't finish as it should."); + QVERIFY2(QTest::qWaitFor([&lnk_tsk]() { return lnk_tsk.isFinished(); }, 100000), "Task didn't finish as it should."); for (auto entry : target_dir.entryList()) { qDebug() << entry; @@ -512,11 +480,12 @@ class FileSystemTest : public QObject { lnk_tsk.matcher(&re); lnk_tsk.linkRecursively(true); lnk_tsk.whitelist(true); - QObject::connect(&lnk_tsk, &Task::finished, - [&] { QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been."); }); + QObject::connect(&lnk_tsk, &Task::finished, [&lnk_tsk] { + QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been."); + }); lnk_tsk.start(); - QVERIFY2(QTest::qWaitFor([&]() { return lnk_tsk.isFinished(); }, 100000), "Task didn't finish as it should."); + QVERIFY2(QTest::qWaitFor([&lnk_tsk]() { return lnk_tsk.isFinished(); }, 100000), "Task didn't finish as it should."); for (auto entry : target_dir.entryList()) { qDebug() << entry; @@ -556,11 +525,12 @@ class FileSystemTest : public QObject { LinkTask lnk_tsk(folder, target_dir.path()); lnk_tsk.linkRecursively(true); - QObject::connect(&lnk_tsk, &Task::finished, - [&] { QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been."); }); + QObject::connect(&lnk_tsk, &Task::finished, [&lnk_tsk] { + QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been."); + }); lnk_tsk.start(); - QVERIFY2(QTest::qWaitFor([&]() { return lnk_tsk.isFinished(); }, 100000), "Task didn't finish as it should."); + QVERIFY2(QTest::qWaitFor([&lnk_tsk]() { return lnk_tsk.isFinished(); }, 100000), "Task didn't finish as it should."); auto filter = QDir::Filter::Files | QDir::Filter::Dirs | QDir::Filter::Hidden; @@ -604,11 +574,12 @@ class FileSystemTest : public QObject { qDebug() << target_dir.path(); LinkTask lnk_tsk(file, target_dir.filePath("pack.mcmeta")); - QObject::connect(&lnk_tsk, &Task::finished, - [&] { QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been."); }); + QObject::connect(&lnk_tsk, &Task::finished, [&lnk_tsk] { + QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been."); + }); lnk_tsk.start(); - QVERIFY2(QTest::qWaitFor([&]() { return lnk_tsk.isFinished(); }, 100000), "Task didn't finish as it should."); + QVERIFY2(QTest::qWaitFor([&lnk_tsk]() { return lnk_tsk.isFinished(); }, 100000), "Task didn't finish as it should."); auto filter = QDir::Filter::Files; @@ -639,11 +610,12 @@ class FileSystemTest : public QObject { LinkTask lnk_tsk(folder, target_dir.path()); lnk_tsk.linkRecursively(true); lnk_tsk.setMaxDepth(0); - QObject::connect(&lnk_tsk, &Task::finished, - [&] { QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been."); }); + QObject::connect(&lnk_tsk, &Task::finished, [&lnk_tsk] { + QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been."); + }); lnk_tsk.start(); - QVERIFY2(QTest::qWaitFor([&]() { return lnk_tsk.isFinished(); }, 100000), "Task didn't finish as it should."); + QVERIFY2(QTest::qWaitFor([&lnk_tsk]() { return lnk_tsk.isFinished(); }, 100000), "Task didn't finish as it should."); QVERIFY(!QFileInfo(target_dir.path()).isSymLink()); @@ -689,13 +661,14 @@ class FileSystemTest : public QObject { LinkTask lnk_tsk(folder, target_dir.path()); lnk_tsk.linkRecursively(true); lnk_tsk.setMaxDepth(-1); - QObject::connect(&lnk_tsk, &Task::finished, - [&] { QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been."); }); + QObject::connect(&lnk_tsk, &Task::finished, [&lnk_tsk] { + QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been."); + }); lnk_tsk.start(); - QVERIFY2(QTest::qWaitFor([&]() { return lnk_tsk.isFinished(); }, 100000), "Task didn't finish as it should."); + QVERIFY2(QTest::qWaitFor([&lnk_tsk]() { return lnk_tsk.isFinished(); }, 100000), "Task didn't finish as it should."); - std::function verify_check = [&](QString check_path) { + std::function verify_check = [&verify_check](QString check_path) { QDir check_dir(check_path); auto filter = QDir::Filter::Files | QDir::Filter::Dirs | QDir::Filter::Hidden; for (auto entry : check_dir.entryList(filter)) { diff --git a/tests/ResourceFolderModel_test.cpp b/tests/ResourceFolderModel_test.cpp index 350ab615e..f2201a5e9 100644 --- a/tests/ResourceFolderModel_test.cpp +++ b/tests/ResourceFolderModel_test.cpp @@ -87,7 +87,7 @@ class ResourceFolderModelTest : public QObject { QEventLoop loop; - ModFolderModel m(tempDir.path(), nullptr, true); + ModFolderModel m(tempDir.path(), nullptr, true, true); connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit); @@ -96,7 +96,7 @@ class ResourceFolderModelTest : public QObject { expire_timer.setSingleShot(true); expire_timer.start(4000); - m.installMod(folder); + m.installResource(folder); loop.exec(); @@ -111,7 +111,7 @@ class ResourceFolderModelTest : public QObject { QString folder = source + '/'; QTemporaryDir tempDir; QEventLoop loop; - ModFolderModel m(tempDir.path(), nullptr, true); + ModFolderModel m(tempDir.path(), nullptr, true, true); connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit); @@ -120,7 +120,7 @@ class ResourceFolderModelTest : public QObject { expire_timer.setSingleShot(true); expire_timer.start(4000); - m.installMod(folder); + m.installResource(folder); loop.exec(); @@ -134,7 +134,7 @@ class ResourceFolderModelTest : public QObject { void test_addFromWatch() { QString source = QFINDTESTDATA("testdata/ResourceFolderModel"); - ModFolderModel model(source, nullptr); + ModFolderModel model(source, nullptr, false, true); QCOMPARE(model.size(), 0); @@ -154,7 +154,7 @@ class ResourceFolderModelTest : public QObject { QString file_mod = QFINDTESTDATA("testdata/ResourceFolderModel/supercoolmod.jar"); QTemporaryDir tmp; - ResourceFolderModel model(QDir(tmp.path()), nullptr); + ResourceFolderModel model(QDir(tmp.path()), nullptr, false, false); QCOMPARE(model.size(), 0); @@ -199,7 +199,7 @@ class ResourceFolderModelTest : public QObject { QString file_mod = QFINDTESTDATA("testdata/ResourceFolderModel/supercoolmod.jar"); QTemporaryDir tmp; - ResourceFolderModel model(tmp.path(), nullptr); + ResourceFolderModel model(tmp.path(), nullptr, false, false); QCOMPARE(model.size(), 0); @@ -210,7 +210,7 @@ class ResourceFolderModelTest : public QObject { EXEC_UPDATE_TASK(model.installResource(file_mod), QVERIFY) } - for (auto res : model.all()) + for (auto res : model.allResources()) qDebug() << res->name(); QCOMPARE(model.size(), 2); diff --git a/tests/Task_test.cpp b/tests/Task_test.cpp index 0740ba0a3..8333840c1 100644 --- a/tests/Task_test.cpp +++ b/tests/Task_test.cpp @@ -16,7 +16,7 @@ class BasicTask : public Task { friend class TaskTest; public: - BasicTask(bool show_debug_log = true) : Task(nullptr, show_debug_log) {} + BasicTask(bool show_debug_log = true) : Task(show_debug_log) {} private: void executeTask() override { emitSucceeded(); } @@ -66,7 +66,7 @@ class BigConcurrentTaskThread : public QThread { } connect(&big_task, &Task::finished, this, &QThread::quit); - connect(&m_deadline, &QTimer::timeout, this, [&] { + connect(&m_deadline, &QTimer::timeout, this, [this] { passed_the_deadline = true; quit(); }); @@ -128,10 +128,10 @@ class TaskTest : public QObject { { BasicTask t; QObject::connect(&t, &Task::finished, - [&] { QVERIFY2(t.wasSuccessful(), "Task finished but was not successful when it should have been."); }); + [&t] { QVERIFY2(t.wasSuccessful(), "Task finished but was not successful when it should have been."); }); t.start(); - QVERIFY2(QTest::qWaitFor([&]() { return t.isFinished(); }, 1000), "Task didn't finish as it should."); + QVERIFY2(QTest::qWaitFor([&t]() { return t.isFinished(); }, 1000), "Task didn't finish as it should."); } void test_basicConcurrentRun() @@ -154,7 +154,7 @@ class TaskTest : public QObject { }); t.start(); - QVERIFY2(QTest::qWaitFor([&]() { return t.isFinished(); }, 1000), "Task didn't finish as it should."); + QVERIFY2(QTest::qWaitFor([&t]() { return t.isFinished(); }, 1000), "Task didn't finish as it should."); } // Tests if starting new tasks after the 6 initial ones is working @@ -196,7 +196,7 @@ class TaskTest : public QObject { }); t.start(); - QVERIFY2(QTest::qWaitFor([&]() { return t.isFinished(); }, 1000), "Task didn't finish as it should."); + QVERIFY2(QTest::qWaitFor([&t]() { return t.isFinished(); }, 1000), "Task didn't finish as it should."); } void test_basicSequentialRun() @@ -219,7 +219,7 @@ class TaskTest : public QObject { }); t.start(); - QVERIFY2(QTest::qWaitFor([&]() { return t.isFinished(); }, 1000), "Task didn't finish as it should."); + QVERIFY2(QTest::qWaitFor([&t]() { return t.isFinished(); }, 1000), "Task didn't finish as it should."); } void test_basicMultipleOptionsRun() @@ -242,7 +242,7 @@ class TaskTest : public QObject { }); t.start(); - QVERIFY2(QTest::qWaitFor([&]() { return t.isFinished(); }, 1000), "Task didn't finish as it should."); + QVERIFY2(QTest::qWaitFor([&t]() { return t.isFinished(); }, 1000), "Task didn't finish as it should."); } void test_stackOverflowInConcurrentTask()