diff --git a/.github/actions/package/linux/action.yml b/.github/actions/package/linux/action.yml index b71e62592..c5848b089 100644 --- a/.github/actions/package/linux/action.yml +++ b/.github/actions/package/linux/action.yml @@ -31,6 +31,28 @@ runs: using: composite steps: + - name: Setup build variables + shell: bash + run: | + # Fixup architecture naming for AppImages + dpkg_arch="$(dpkg-architecture -q DEB_HOST_ARCH_CPU)" + case "$dpkg_arch" in + "amd64") + APPIMAGE_ARCH="x86_64" + ;; + "arm64") + APPIMAGE_ARCH="aarch64" + ;; + *) + echo "# 🚨 The Debian architecture \"$deb_arch\" is not recognized!" >> "$GITHUB_STEP_SUMMARY" + exit 1 + ;; + esac + echo "APPIMAGE_ARCH=$APPIMAGE_ARCH" >> "$GITHUB_ENV" + + # Used for the file paths of libraries + echo "DEB_HOST_MULTIARCH=$(dpkg-architecture -q DEB_HOST_MULTIARCH)" >> "$GITHUB_ENV" + - name: Package AppImage shell: bash env: @@ -45,26 +67,26 @@ runs: mv ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.metainfo.xml ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.appdata.xml export "NO_APPSTREAM=1" # we have to skip appstream checking because appstream on ubuntu 20.04 is outdated - export OUTPUT="PrismLauncher-Linux-x86_64.AppImage" + export OUTPUT="PrismLauncher-Linux-$APPIMAGE_ARCH.AppImage" chmod +x linuxdeploy-*.AppImage mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines - cp -r ${{ runner.workspace }}/Qt/${{ inputs.qt-version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines + cp -r ${{ runner.workspace }}/Qt/${{ inputs.qt-version }}/gcc_*64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines - 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/ + cp /usr/lib/"$DEB_HOST_MULTIARCH"/libcrypto.so.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ + cp /usr/lib/"$DEB_HOST_MULTIARCH"/libssl.so.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ + cp /usr/lib/"$DEB_HOST_MULTIARCH"/libOpenGL.so.0* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib" export LD_LIBRARY_PATH - chmod +x AppImageUpdate-x86_64.AppImage - cp AppImageUpdate-x86_64.AppImage ${{ env.INSTALL_APPIMAGE_DIR }}/usr/bin + chmod +x AppImageUpdate-"$APPIMAGE_ARCH".AppImage + cp AppImageUpdate-"$APPIMAGE_ARCH".AppImage ${{ env.INSTALL_APPIMAGE_DIR }}/usr/bin - export UPDATE_INFORMATION="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|PrismLauncher-Linux-x86_64.AppImage.zsync" + export UPDATE_INFORMATION="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|PrismLauncher-Linux-$APPIMAGE_ARCH.AppImage.zsync" if [ '${{ inputs.gpg-private-key-id }}' != '' ]; then export SIGN=1 @@ -76,9 +98,9 @@ runs: echo ":warning: Skipped code signing for Linux AppImage, as gpg key was not present." >> $GITHUB_STEP_SUMMARY fi - ./linuxdeploy-x86_64.AppImage --appdir ${{ env.INSTALL_APPIMAGE_DIR }} --output appimage --plugin qt -i ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/icons/hicolor/scalable/apps/org.prismlauncher.PrismLauncher.svg + ./linuxdeploy-"$APPIMAGE_ARCH".AppImage --appdir ${{ env.INSTALL_APPIMAGE_DIR }} --output appimage --plugin qt -i ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/icons/hicolor/scalable/apps/org.prismlauncher.PrismLauncher.svg - mv "PrismLauncher-Linux-x86_64.AppImage" "PrismLauncher-Linux-${{ env.VERSION }}-${{ inputs.build-type }}-x86_64.AppImage" + mv "PrismLauncher-Linux-$APPIMAGE_ARCH.AppImage" "PrismLauncher-Linux-${{ env.VERSION }}-${{ inputs.build-type }}-$APPIMAGE_ARCH.AppImage" - name: Package portable tarball shell: bash @@ -89,17 +111,8 @@ runs: INSTALL_PORTABLE_DIR: install-portable run: | - cmake --preset "$CMAKE_PRESET" -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_PORTABLE_DIR }} -DINSTALL_BUNDLE=full - cmake --install ${{ env.BUILD_DIR }} - cmake --install ${{ env.BUILD_DIR }} --component portable - - 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.* ${{ 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 + cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} + cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt cd ${{ env.INSTALL_PORTABLE_DIR }} @@ -114,11 +127,11 @@ runs: - name: Upload AppImage uses: actions/upload-artifact@v4 with: - name: PrismLauncher-${{ inputs.artifact-name }}-${{ inputs.version }}-${{ inputs.build-type }}-x86_64.AppImage - path: PrismLauncher-${{ runner.os }}-${{ inputs.version }}-${{ inputs.build-type }}-x86_64.AppImage + name: PrismLauncher-${{ runner.os }}-${{ inputs.version }}-${{ inputs.build-type }}-${{ env.APPIMAGE_ARCH }}.AppImage + path: PrismLauncher-${{ runner.os }}-${{ inputs.version }}-${{ inputs.build-type }}-${{ env.APPIMAGE_ARCH }}.AppImage - name: Upload AppImage Zsync uses: actions/upload-artifact@v4 with: - name: PrismLauncher-${{ inputs.artifact-name }}-${{ inputs.version }}-${{ inputs.build-type }}-x86_64.AppImage.zsync - path: PrismLauncher-Linux-x86_64.AppImage.zsync + name: PrismLauncher-${{ runner.os }}-${{ inputs.version }}-${{ inputs.build-type }}-${{ env.APPIMAGE_ARCH }}.AppImage.zsync + path: PrismLauncher-${{ runner.os }}-${{ env.APPIMAGE_ARCH }}.AppImage.zsync diff --git a/.github/actions/setup-dependencies/action.yml b/.github/actions/setup-dependencies/action.yml index e97abd1df..82395eada 100644 --- a/.github/actions/setup-dependencies/action.yml +++ b/.github/actions/setup-dependencies/action.yml @@ -6,6 +6,9 @@ inputs: description: Type for the build required: true default: Debug + artifact-name: + description: Name of the uploaded artifact + required: true msystem: description: MSYS2 subsystem to use required: false @@ -18,7 +21,7 @@ inputs: qt-version: description: Version of Qt to use required: true - default: 6.8.1 + default: 6.9.1 outputs: build-type: @@ -39,6 +42,8 @@ runs: - name: Setup macOS dependencies if: ${{ runner.os == 'macOS' }} uses: ./.github/actions/setup-dependencies/macos + with: + build-type: ${{ inputs.build-type }} - name: Setup Windows dependencies if: ${{ runner.os == 'Windows' }} @@ -51,18 +56,18 @@ runs: # TODO(@getchoo): Get this working on MSYS2! - name: Setup ccache if: ${{ (runner.os != 'Windows' || inputs.msystem == '') && inputs.build-type == 'Debug' }} - uses: hendrikmuhs/ccache-action@v1.2.18 + uses: hendrikmuhs/ccache-action@v1.2.19 with: - variant: ${{ runner.os == 'Windows' && 'sccache' || 'ccache' }} + variant: sccache create-symlink: ${{ runner.os != 'Windows' }} - key: ${{ runner.os }}-qt${{ inputs.qt_ver }}-${{ inputs.architecture }} + key: ${{ runner.os }}-${{ runner.arch }}-${{ inputs.artifact-name }}-sccache - name: Use ccache on debug builds if: ${{ inputs.build-type == 'Debug' }} shell: bash env: - # Only use sccache on MSVC - CCACHE_VARIANT: ${{ (runner.os == 'Windows' && inputs.msystem == '') && 'sccache' || 'ccache' }} + # Only use ccache on MSYS2 + CCACHE_VARIANT: ${{ (runner.os == 'Windows' && inputs.msystem != '') && 'ccache' || 'sccache' }} run: | echo "CMAKE_C_COMPILER_LAUNCHER=$CCACHE_VARIANT" >> "$GITHUB_ENV" echo "CMAKE_CXX_COMPILER_LAUNCHER=$CCACHE_VARIANT" >> "$GITHUB_ENV" diff --git a/.github/actions/setup-dependencies/linux/action.yml b/.github/actions/setup-dependencies/linux/action.yml index dd0d28364..94c04abe5 100644 --- a/.github/actions/setup-dependencies/linux/action.yml +++ b/.github/actions/setup-dependencies/linux/action.yml @@ -8,15 +8,33 @@ runs: shell: bash run: | sudo apt-get -y update - sudo apt-get -y install ninja-build extra-cmake-modules scdoc appstream libxcb-cursor-dev + sudo apt-get -y install \ + dpkg-dev \ + ninja-build extra-cmake-modules scdoc \ + appstream libxcb-cursor-dev - name: Setup AppImage tooling shell: bash run: | declare -A appimage_deps - appimage_deps["https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20250213-2/linuxdeploy-x86_64.AppImage"]="4648f278ab3ef31f819e67c30d50f462640e5365a77637d7e6f2ad9fd0b4522a linuxdeploy-x86_64.AppImage" - appimage_deps["https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/1-alpha-20250213-1/linuxdeploy-plugin-qt-x86_64.AppImage"]="15106be885c1c48a021198e7e1e9a48ce9d02a86dd0a1848f00bdbf3c1c92724 linuxdeploy-plugin-qt-x86_64.AppImage" - appimage_deps["https://github.com/AppImageCommunity/AppImageUpdate/releases/download/2.0.0-alpha-1-20241225/AppImageUpdate-x86_64.AppImage"]="f1747cf60058e99f1bb9099ee9787d16c10241313b7acec81810ea1b1e568c11 AppImageUpdate-x86_64.AppImage" + + deb_arch="$(dpkg-architecture -q DEB_HOST_ARCH)" + case "$deb_arch" in + "amd64") + appimage_deps["https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20250213-2/linuxdeploy-x86_64.AppImage"]="4648f278ab3ef31f819e67c30d50f462640e5365a77637d7e6f2ad9fd0b4522a linuxdeploy-x86_64.AppImage" + appimage_deps["https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/1-alpha-20250213-1/linuxdeploy-plugin-qt-x86_64.AppImage"]="15106be885c1c48a021198e7e1e9a48ce9d02a86dd0a1848f00bdbf3c1c92724 linuxdeploy-plugin-qt-x86_64.AppImage" + appimage_deps["https://github.com/AppImageCommunity/AppImageUpdate/releases/download/2.0.0-alpha-1-20241225/AppImageUpdate-x86_64.AppImage"]="f1747cf60058e99f1bb9099ee9787d16c10241313b7acec81810ea1b1e568c11 AppImageUpdate-x86_64.AppImage" + ;; + "arm64") + appimage_deps["https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20250213-2/linuxdeploy-aarch64.AppImage"]="06706ac8189797dccd36bd384105892cb5e6e71f784f4df526cc958adc223cd6 linuxdeploy-aarch64.AppImage" + appimage_deps["https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/1-alpha-20250213-1/linuxdeploy-plugin-qt-aarch64.AppImage"]="bf1c24aff6d749b5cf423afad6f15abd4440f81dec1aab95706b25f6667cdcf1 linuxdeploy-plugin-qt-aarch64.AppImage" + appimage_deps["https://github.com/AppImageCommunity/AppImageUpdate/releases/download/2.0.0-alpha-1-20241225/AppImageUpdate-aarch64.AppImage"]="cf27f810dfe5eda41f130769e4a4b562b9d93665371c15ebeffb84ee06a41550 AppImageUpdate-aarch64.AppImage" + ;; + *) + echo "# 🚨 The Debian architecture \"$deb_arch\" is not recognized!" >> "$GITHUB_STEP_SUMMARY" + exit 1 + ;; + esac for url in "${!appimage_deps[@]}"; do curl -LO "$url" diff --git a/.github/actions/setup-dependencies/macos/action.yml b/.github/actions/setup-dependencies/macos/action.yml index dcbb308c2..6fc3ed3bf 100644 --- a/.github/actions/setup-dependencies/macos/action.yml +++ b/.github/actions/setup-dependencies/macos/action.yml @@ -1,5 +1,11 @@ name: Setup macOS dependencies +inputs: + build-type: + description: Type for the build + required: true + default: Debug + runs: using: composite @@ -14,3 +20,29 @@ runs: shell: bash run: | echo "JAVA_HOME=$(/usr/libexec/java_home -v 17)" >> "$GITHUB_ENV" + + - name: Setup vcpkg cache + if: ${{ inputs.build-type == 'Debug' }} + shell: bash + env: + USERNAME: ${{ github.repository_owner }} + GITHUB_TOKEN: ${{ github.token }} + FEED_URL: https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json + run: | + mono `vcpkg fetch nuget | tail -n 1` \ + sources add \ + -Source "$FEED_URL" \ + -StorePasswordInClearText \ + -Name GitHubPackages \ + -UserName "$USERNAME" \ + -Password "$GITHUB_TOKEN" + mono `vcpkg fetch nuget | tail -n 1` \ + setapikey "$GITHUB_TOKEN" \ + -Source "$FEED_URL" + echo "VCPKG_BINARY_SOURCES=clear;nuget,$FEED_URL,readwrite" >> "$GITHUB_ENV" + + - name: Setup vcpkg environment + if: ${{ inputs.build-type == 'Debug' }} + shell: bash + run: | + echo "CMAKE_TOOLCHAIN_FILE=$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" >> "$GITHUB_ENV" diff --git a/.github/actions/setup-dependencies/windows/action.yml b/.github/actions/setup-dependencies/windows/action.yml index 0a643f583..73840771c 100644 --- a/.github/actions/setup-dependencies/windows/action.yml +++ b/.github/actions/setup-dependencies/windows/action.yml @@ -25,6 +25,40 @@ runs: arch: ${{ inputs.vcvars-arch }} vsversion: 2022 + - name: Setup Java (MSVC) + uses: actions/setup-java@v5 + with: + # NOTE(@getchoo): We should probably stay on Zulu. + # Temurin doesn't have Java 17 builds for WoA + distribution: zulu + java-version: 17 + + - name: Setup vcpkg cache (MSVC) + if: ${{ inputs.msystem == '' && inputs.build-type == 'Debug' }} + shell: pwsh + env: + USERNAME: ${{ github.repository_owner }} + GITHUB_TOKEN: ${{ github.token }} + FEED_URL: https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json + run: | + .$(vcpkg fetch nuget) ` + sources add ` + -Source "$env:FEED_URL" ` + -StorePasswordInClearText ` + -Name GitHubPackages ` + -UserName "$env:USERNAME" ` + -Password "$env:GITHUB_TOKEN" + .$(vcpkg fetch nuget) ` + setapikey "$env:GITHUB_TOKEN" ` + -Source "$env:FEED_URL" + "VCPKG_BINARY_SOURCES=clear;nuget,$env:FEED_URL,readwrite" | Out-File -Append $env:GITHUB_ENV + + - name: Setup vcpkg environment (MSVC) + if: ${{ inputs.msystem == '' }} + shell: bash + run: | + echo "CMAKE_TOOLCHAIN_FILE=$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" >> "$GITHUB_ENV" + - name: Setup MSYS2 (MinGW) if: ${{ inputs.msystem != '' }} uses: msys2/setup-msys2@v2 @@ -56,7 +90,7 @@ runs: - name: Retrieve ccache cache (MinGW) if: ${{ inputs.msystem != '' && inputs.build-type == 'Debug' }} - uses: actions/cache@v4.2.3 + uses: actions/cache@v4.2.4 with: path: '${{ github.workspace }}\.ccache' key: ${{ runner.os }}-mingw-w64-ccache-${{ github.run_id }} diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index c46f8e192..34451e61f 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -21,11 +21,11 @@ jobs: if: github.repository_owner == 'PrismLauncher' && github.event.pull_request.merged == true && (github.event_name != 'labeled' || startsWith('backport', github.event.label.name)) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: ref: ${{ github.event.pull_request.head.sha }} - name: Create backport PRs - uses: korthout/backport-action@v3.2.0 + uses: korthout/backport-action@v3.3.0 with: # Config README: https://github.com/korthout/backport-action#backport-action pull_description: |- diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f4cdae97c..dc72e2da0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,21 +2,23 @@ name: Build on: push: - branches-ignore: - - "renovate/**" + branches: + - "develop" + - "release-*" paths: # File types - "**.cpp" - "**.h" - "**.java" + - "**.ui" # Directories - - "buildconfig/" - - "cmake/" - - "launcher/" - - "libraries/" - - "program_info/" - - "tests/" + - "buildconfig/**" + - "cmake/**" + - "launcher/**" + - "libraries/**" + - "program_info/**" + - "tests/**" # Files - "CMakeLists.txt" @@ -24,21 +26,23 @@ on: # Workflows - ".github/workflows/build.yml" - - ".github/actions/package/" - - ".github/actions/setup-dependencies/" + - ".github/actions/package/**" + - ".github/actions/setup-dependencies/**" pull_request: paths: # File types - "**.cpp" - "**.h" + - "**.java" + - "**.ui" # Directories - - "buildconfig/" - - "cmake/" - - "launcher/" - - "libraries/" - - "program_info/" - - "tests/" + - "buildconfig/**" + - "cmake/**" + - "launcher/**" + - "libraries/**" + - "program_info/**" + - "tests/**" # Files - "CMakeLists.txt" @@ -46,8 +50,8 @@ on: # Workflows - ".github/workflows/build.yml" - - ".github/actions/package/" - - ".github/actions/setup-dependencies/" + - ".github/actions/package/**" + - ".github/actions/setup-dependencies/**" workflow_call: inputs: build-type: @@ -65,6 +69,10 @@ jobs: build: name: Build (${{ matrix.artifact-name }}) + permissions: + # Required for vcpkg binary cache + packages: write + strategy: fail-fast: false matrix: @@ -73,6 +81,14 @@ jobs: artifact-name: Linux base-cmake-preset: linux + # NOTE(@getchoo): Yes, we're intentionally using 24.04 here!!! + # + # It's not really documented anywhere AFAICT, but upstream Qt binaries + # *for the same version* are compiled against 24.04 on ARM, and *not* 22.04 like x64 + - os: ubuntu-24.04-arm + artifact-name: Linux-aarch64 + base-cmake-preset: linux + - os: windows-2022 artifact-name: Windows-MinGW-w64 base-cmake-preset: windows_mingw @@ -117,7 +133,7 @@ jobs: ## - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: true @@ -126,6 +142,7 @@ jobs: uses: ./.github/actions/setup-dependencies with: build-type: ${{ inputs.build-type || 'Debug' }} + artifact-name: ${{ matrix.artifact-name }} msystem: ${{ matrix.msystem }} vcvars-arch: ${{ matrix.vcvars-arch }} qt-architecture: ${{ matrix.qt-architecture }} @@ -145,6 +162,9 @@ jobs: - name: Run CMake workflow env: CMAKE_PRESET: ${{ steps.cmake-preset.outputs.preset }} + + ARTIFACT_NAME: ${{ matrix.artifact-name }}-Qt6 + BUILD_PLATFORM: official run: | cmake --workflow --preset "$CMAKE_PRESET" @@ -164,6 +184,7 @@ jobs: with: version: ${{ steps.short-version.outputs.version }} build-type: ${{ steps.setup-dependencies.outputs.build-type }} + artifact-name: ${{ matrix.artifact-name }} cmake-preset: ${{ steps.cmake-preset.outputs.preset }} qt-version: ${{ steps.setup-dependencies.outputs.qt-version }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f8fae8ecf..924b81e5f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -2,50 +2,56 @@ name: "CodeQL Code Scanning" on: push: + branches: + - "develop" + - "release-*" paths: # File types - "**.cpp" - "**.h" - "**.java" + - "**.ui" # Directories - - "buildconfig/" - - "cmake/" - - "launcher/" - - "libraries/" - - "program_info/" - - "tests/" + - "buildconfig/**" + - "cmake/**" + - "launcher/**" + - "libraries/**" + - "program_info/**" + - "tests/**" # Files - "CMakeLists.txt" - "COPYING.md" # Workflows - - ".github/codeql" + - ".github/codeql/**" - ".github/workflows/codeql.yml" - - ".github/actions/setup-dependencies/" + - ".github/actions/setup-dependencies/**" pull_request: paths: # File types - "**.cpp" - "**.h" + - "**.java" + - "**.ui" # Directories - - "buildconfig/" - - "cmake/" - - "launcher/" - - "libraries/" - - "program_info/" - - "tests/" + - "buildconfig/**" + - "cmake/**" + - "launcher/**" + - "libraries/**" + - "program_info/**" + - "tests/**" # Files - "CMakeLists.txt" - "COPYING.md" # Workflows - - ".github/codeql" + - ".github/codeql/**" - ".github/workflows/codeql.yml" - - ".github/actions/setup-dependencies/" + - ".github/actions/setup-dependencies/**" workflow_dispatch: jobs: @@ -54,7 +60,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: "true" diff --git a/.github/workflows/flatpak.yml b/.github/workflows/flatpak.yml index cab0edeb7..c16917869 100644 --- a/.github/workflows/flatpak.yml +++ b/.github/workflows/flatpak.yml @@ -2,6 +2,9 @@ name: Flatpak on: push: + branches: + - "develop" + - "release-*" # We don't do anything with these artifacts on releases. They go to Flathub tags-ignore: - "*" @@ -10,17 +13,18 @@ on: - "**.cpp" - "**.h" - "**.java" + - "**.ui" # Build files - - "flatpak/" + - "flatpak/**" # Directories - - "buildconfig/" - - "cmake/" - - "launcher/" - - "libraries/" - - "program_info/" - - "tests/" + - "buildconfig/**" + - "cmake/**" + - "launcher/**" + - "libraries/**" + - "program_info/**" + - "tests/**" # Files - "CMakeLists.txt" @@ -33,17 +37,19 @@ on: # File types - "**.cpp" - "**.h" + - "**.java" + - "**.ui" # Build files - - "flatpak/" + - "flatpak/**" # Directories - - "buildconfig/" - - "cmake/" - - "launcher/" - - "libraries/" - - "program_info/" - - "tests/" + - "buildconfig/**" + - "cmake/**" + - "launcher/**" + - "libraries/**" + - "program_info/**" + - "tests/**" # Files - "CMakeLists.txt" @@ -78,7 +84,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: true diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 80b41161a..ca3803139 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -2,6 +2,9 @@ name: Nix on: push: + branches: + - "develop" + - "release-*" tags: - "*" paths: @@ -9,19 +12,20 @@ on: - "**.cpp" - "**.h" - "**.java" + - "**.ui" # Build files - "**.nix" - - "nix/" + - "nix/**" - "flake.lock" # Directories - - "buildconfig/" - - "cmake/" - - "launcher/" - - "libraries/" - - "program_info/" - - "tests/" + - "buildconfig/**" + - "cmake/**" + - "launcher/**" + - "libraries/**" + - "program_info/**" + - "tests/**" # Files - "CMakeLists.txt" @@ -34,19 +38,21 @@ on: # File types - "**.cpp" - "**.h" + - "**.java" + - "**.ui" # Build files - "**.nix" - - "nix/" + - "nix/**" - "flake.lock" # Directories - - "buildconfig/" - - "cmake/" - - "launcher/" - - "libraries/" - - "program_info/" - - "tests/" + - "buildconfig/**" + - "cmake/**" + - "launcher/**" + - "libraries/**" + - "program_info/**" + - "tests/**" # Files - "CMakeLists.txt" @@ -99,12 +105,12 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: ${{ steps.merge-commit.outputs.merge-commit-sha || github.sha }} - name: Install Nix - uses: DeterminateSystems/nix-installer-action@v17 + uses: DeterminateSystems/nix-installer-action@v19 with: determinate: ${{ env.USE_DETERMINATE }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6e879cfd7..5fed75215 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,12 +20,12 @@ jobs: upload_url: ${{ steps.create_release.outputs.upload_url }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: "true" path: "PrismLauncher-source" - name: Download artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 - name: Grab and store version run: | tag_name=$(echo ${{ github.ref }} | grep -oE "[^/]+$") @@ -34,8 +34,10 @@ jobs: run: | mv ${{ github.workspace }}/PrismLauncher-source PrismLauncher-${{ env.VERSION }} mv PrismLauncher-Linux-Qt6-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz - mv PrismLauncher-*.AppImage/PrismLauncher-*.AppImage PrismLauncher-Linux-x86_64.AppImage - mv PrismLauncher-*.AppImage.zsync/PrismLauncher-*.AppImage.zsync PrismLauncher-Linux-x86_64.AppImage.zsync + mv PrismLauncher-*.AppImage/PrismLauncher-*-x86_64.AppImage PrismLauncher-Linux-x86_64.AppImage + mv PrismLauncher-*.AppImage.zsync/PrismLauncher-*-x86_64.AppImage.zsync PrismLauncher-Linux-x86_64.AppImage.zsync + mv PrismLauncher-*.AppImage/PrismLauncher-*-aarch64.AppImage PrismLauncher-Linux-aarch64.AppImage + mv PrismLauncher-*.AppImage.zsync/PrismLauncher-*-aarch64.AppImage.zsync PrismLauncher-Linux-aarch64.AppImage.zsync mv PrismLauncher-macOS*/PrismLauncher.zip PrismLauncher-macOS-${{ env.VERSION }}.zip tar --exclude='.git' -czf PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }} @@ -89,7 +91,10 @@ jobs: files: | PrismLauncher-Linux-x86_64.AppImage PrismLauncher-Linux-x86_64.AppImage.zsync + PrismLauncher-Linux-aarch64.AppImage + PrismLauncher-Linux-aarch64.AppImage.zsync PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz + PrismLauncher-Linux-aarch64-Qt6-Portable-${{ env.VERSION }}.tar.gz PrismLauncher-Windows-MinGW-w64-${{ env.VERSION }}.zip PrismLauncher-Windows-MinGW-w64-Portable-${{ env.VERSION }}.zip PrismLauncher-Windows-MinGW-w64-Setup-${{ env.VERSION }}.exe diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 106a7844f..b8f8137d7 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -17,7 +17,7 @@ jobs: pull-requests: write steps: - - uses: actions/stale@v9 + - uses: actions/stale@v10 with: days-before-stale: 60 days-before-close: -1 # Don't close anything diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml index 7480ba46e..f1821646a 100644 --- a/.github/workflows/update-flake.yml +++ b/.github/workflows/update-flake.yml @@ -16,10 +16,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: cachix/install-nix-action@17fe5fb4a23ad6cbbe47d6b3f359611ad276644c # v31 + - uses: actions/checkout@v5 + - uses: cachix/install-nix-action@7be5dee1421f63d07e71ce6e0a9f8a4b07c2a487 # v31 - - uses: DeterminateSystems/update-flake-lock@v25 + - uses: DeterminateSystems/update-flake-lock@v27 with: commit-msg: "chore(nix): update lockfile" pr-title: "chore(nix): update lockfile" diff --git a/CMakeLists.txt b/CMakeLists.txt index e3d60a102..1360b82ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.15) # minimum version required by QuaZip +cmake_minimum_required(VERSION 3.22) # minimum version required by Qt project(Launcher) @@ -24,7 +24,7 @@ set(CMAKE_JAVA_TARGET_OUTPUT_DIR ${PROJECT_BINARY_DIR}/jars) ######## Set compiler flags ######## set(CMAKE_CXX_STANDARD_REQUIRED true) set(CMAKE_C_STANDARD_REQUIRED true) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_C_STANDARD 11) include(GenerateExportHeader) if(MSVC) @@ -79,6 +79,19 @@ else() if(WIN32) set(CMAKE_EXE_LINKER_FLAGS "-Wl,--stack,8388608 ${CMAKE_EXE_LINKER_FLAGS}") + # Emit PDBs for WinDbg, etc. + if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + set(CMAKE_EXE_LINKER_FLAGS "-Wl,--pdb= ${CMAKE_EXE_LINKER_FLAGS}") + + foreach(lang C CXX) + set("CMAKE_${lang}_FLAGS" "-gcodeview ${CMAKE_${lang}_FLAGS}") + + # Force-enabling this to use generator expressions like TARGET_PDB_FILE + # (and because we can actually emit PDBs) + set("CMAKE_${lang}_LINKER_SUPPORTS_PDB" ON) + endforeach() + endif() + # -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 @@ -335,6 +348,14 @@ endif() if(NOT Launcher_FORCE_BUNDLED_LIBS) # Find toml++ find_package(tomlplusplus 3.2.0 QUIET) + # Fallback to pkg-config (if available) if CMake files aren't found + if(NOT tomlplusplus_FOUND) + find_package(PkgConfig) + if(PkgConfig_FOUND) + pkg_check_modules(tomlplusplus IMPORTED_TARGET tomlplusplus>=3.2.0) + endif() + endif() + # Find cmark find_package(cmark QUIET) @@ -372,9 +393,6 @@ if(UNIX AND APPLE) set(RESOURCES_DEST_DIR "${Launcher_Name}.app/Contents/Resources") set(JARS_DEST_DIR "${Launcher_Name}.app/Contents/MacOS/jars") - # Apps to bundle - set(APPS "\${CMAKE_INSTALL_PREFIX}/${Launcher_Name}.app") - # Mac bundle settings set(MACOSX_BUNDLE_BUNDLE_NAME "${Launcher_DisplayName}") set(MACOSX_BUNDLE_INFO_STRING "${Launcher_DisplayName}: A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once.") @@ -391,16 +409,10 @@ if(UNIX AND APPLE) 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 - set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} ${MACOSX_SPARKLE_DIR}) - if(NOT MACOSX_SPARKLE_UPDATE_PUBLIC_KEY STREQUAL "" AND NOT MACOSX_SPARKLE_UPDATE_FEED_URL STREQUAL "") set(Launcher_ENABLE_UPDATER YES) endif() - # install as bundle - set(INSTALL_BUNDLE "full" CACHE STRING "Use fixup_bundle to bundle dependencies") - # Add the icon install(FILES ${Launcher_Branding_ICNS} DESTINATION ${RESOURCES_DEST_DIR} RENAME ${Launcher_Name}.icns) @@ -411,9 +423,6 @@ elseif(UNIX) set(LIBRARY_DEST_DIR "lib${LIB_SUFFIX}") set(JARS_DEST_DIR "share/${Launcher_Name}") - # install as bundle with no dependencies included - set(INSTALL_BUNDLE "nodeps" CACHE STRING "Use fixup_bundle to bundle dependencies") - # Set RPATH SET(Launcher_BINARY_RPATH "$ORIGIN/") @@ -424,17 +433,9 @@ elseif(UNIX) install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/launcher/qtlogging.ini" DESTINATION "share/${Launcher_Name}") - if (INSTALL_BUNDLE STREQUAL full) - set(PLUGIN_DEST_DIR "plugins") - set(BUNDLE_DEST_DIR ".") - set(RESOURCES_DEST_DIR ".") - - # Apps to bundle - set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/${Launcher_APP_BINARY_NAME}") - - # directories to look for dependencies - set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) - endif() + set(PLUGIN_DEST_DIR "plugins") + set(BUNDLE_DEST_DIR ".") + set(RESOURCES_DEST_DIR ".") if(Launcher_ManPage) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_ManPage} DESTINATION "${KDE_INSTALL_MANDIR}/man6") @@ -450,15 +451,6 @@ elseif(WIN32) set(PLUGIN_DEST_DIR ".") set(RESOURCES_DEST_DIR ".") set(JARS_DEST_DIR "jars") - - # Apps to bundle - set(APPS "\${CMAKE_INSTALL_PREFIX}/${Launcher_Name}.exe") - - # directories to look for dependencies - set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) - - # install as bundle - set(INSTALL_BUNDLE "full" CACHE STRING "Use fixup_bundle to bundle dependencies") else() message(FATAL_ERROR "Platform not supported") endif() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5965f4d8e..fdc79faf2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributions Guidelines -## Code formatting +## Code style All files are formatted with `clang-format` using the configuration in `.clang-format`. Ensure it is run on changed files before committing! @@ -15,6 +15,11 @@ Please also follow the project's conventions for C++: - 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`. +- Avoid using `[[nodiscard]]` unless ignoring the return value is likely to cause a bug in cases such as: + - A function allocates memory or another resource and the caller needs to clean it up. + - A function has side effects and an error status is returned. + - A function is likely be mistaken for having side effects. +- A plain getter is unlikely to cause confusion and adding `[[nodiscard]]` can create clutter and inconsistency. Most of these rules are included in the `.clang-tidy` file, so you can run `clang-tidy` to check for any violations. diff --git a/COPYING.md b/COPYING.md index f9b905351..e64bb8760 100644 --- a/COPYING.md +++ b/COPYING.md @@ -412,3 +412,26 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the Software or the use or other dealings in the Software. + +## vcpkg (`cmake/vcpkg-ports`) + + MIT License + + Copyright (c) Microsoft Corporation + + Permission is hereby granted, free of charge, to any person obtaining a copy of this + software and associated documentation files (the "Software"), to deal in the Software + without restriction, including without limitation the rights to use, copy, modify, + merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be included in all copies + or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE + OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 361864dfe..868fa1fe1 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Please understand that these builds are not intended for most users. There may b There are development builds available through: - [GitHub Actions](https://github.com/PrismLauncher/PrismLauncher/actions) (includes builds from pull requests opened by contribuitors) -- [nightly.link](https://nightly.link/PrismLauncher/PrismLauncher/workflows/trigger_builds/develop) (this will always point only to the latest version of develop) +- [nightly.link](https://nightly.link/PrismLauncher/PrismLauncher/workflows/build/develop) (this will always point only to the latest version of develop) These have debug information in the binaries, so their file sizes are relatively larger. diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h index 10c38e3d6..045d987d4 100644 --- a/buildconfig/BuildConfig.h +++ b/buildconfig/BuildConfig.h @@ -166,7 +166,7 @@ class Config { QString DISCORD_URL; QString SUBREDDIT_URL; - QString RESOURCE_BASE = "https://resources.download.minecraft.net/"; + QString DEFAULT_RESOURCE_BASE = "https://resources.download.minecraft.net/"; QString LIBRARY_BASE = "https://libraries.minecraft.net/"; QString IMGUR_BASE_URL = "https://api.imgur.com/3/"; QString FMLLIBS_BASE_URL; diff --git a/cmake/commonPresets.json b/cmake/commonPresets.json index 9cdf51649..9be0fb447 100644 --- a/cmake/commonPresets.json +++ b/cmake/commonPresets.json @@ -8,7 +8,8 @@ "binaryDir": "build", "installDir": "install", "cacheVariables": { - "Launcher_BUILD_PLATFORM": "custom" + "Launcher_BUILD_ARTIFACT": "$penv{ARTIFACT_NAME}", + "Launcher_BUILD_PLATFORM": "$penv{BUILD_PLATFORM}" } }, { @@ -39,7 +40,6 @@ "base_release" ], "cacheVariables": { - "Launcher_BUILD_PLATFORM": "official", "Launcher_FORCE_BUNDLED_LIBS": "ON" } } diff --git a/cmake/linuxPreset.json b/cmake/linuxPreset.json index b8bfe4ff0..984defa5d 100644 --- a/cmake/linuxPreset.json +++ b/cmake/linuxPreset.json @@ -15,7 +15,6 @@ }, "generator": "Ninja", "cacheVariables": { - "Launcher_BUILD_ARTIFACT": "Linux-Qt6", "Launcher_ENABLE_JAVA_DOWNLOADER": "ON" } }, @@ -42,9 +41,6 @@ "linux_base" ], "displayName": "Linux (CI)", - "cacheVariables": { - "Launcher_BUILD_ARTIFACT": "Linux-Qt6" - }, "installDir": "/usr" } ], diff --git a/cmake/macosPreset.json b/cmake/macosPreset.json index 726949934..9098f9a9a 100644 --- a/cmake/macosPreset.json +++ b/cmake/macosPreset.json @@ -23,7 +23,7 @@ ], "cacheVariables": { "CMAKE_OSX_ARCHITECTURES": "x86_64;arm64", - "Launcher_BUILD_ARTIFACT": "macOS-Qt6" + "VCPKG_TARGET_TRIPLET": "universal-osx" } }, { @@ -64,10 +64,7 @@ "base_ci", "macos_universal_base" ], - "displayName": "macOS (CI)", - "cacheVariables": { - "Launcher_BUILD_ARTIFACT": "macOS-Qt6" - } + "displayName": "macOS (CI)" } ], "buildPresets": [ diff --git a/cmake/vcpkg-ports/vcpkg-tool-meson/README.md b/cmake/vcpkg-ports/vcpkg-tool-meson/README.md new file mode 100644 index 000000000..9047c8037 --- /dev/null +++ b/cmake/vcpkg-ports/vcpkg-tool-meson/README.md @@ -0,0 +1,3 @@ +The only difference between this and the upstream vcpkg port is the addition of `universal-osx.patch`. It's very annoying we need to bundle this entire tree to do that. + +-@getchoo diff --git a/cmake/vcpkg-ports/vcpkg-tool-meson/adjust-args.patch b/cmake/vcpkg-ports/vcpkg-tool-meson/adjust-args.patch new file mode 100644 index 000000000..ad800aa66 --- /dev/null +++ b/cmake/vcpkg-ports/vcpkg-tool-meson/adjust-args.patch @@ -0,0 +1,13 @@ +diff --git a/mesonbuild/cmake/toolchain.py b/mesonbuild/cmake/toolchain.py +index 11a00be5d..89ae490ff 100644 +--- a/mesonbuild/cmake/toolchain.py ++++ b/mesonbuild/cmake/toolchain.py +@@ -202,7 +202,7 @@ class CMakeToolchain: + @staticmethod + def is_cmdline_option(compiler: 'Compiler', arg: str) -> bool: + if compiler.get_argument_syntax() == 'msvc': +- return arg.startswith('/') ++ return arg.startswith(('/','-')) + else: + if os.path.basename(compiler.get_exe()) == 'zig' and arg in {'ar', 'cc', 'c++', 'dlltool', 'lib', 'ranlib', 'objcopy', 'rc'}: + return True diff --git a/cmake/vcpkg-ports/vcpkg-tool-meson/adjust-python-dep.patch b/cmake/vcpkg-ports/vcpkg-tool-meson/adjust-python-dep.patch new file mode 100644 index 000000000..0cbfe717d --- /dev/null +++ b/cmake/vcpkg-ports/vcpkg-tool-meson/adjust-python-dep.patch @@ -0,0 +1,45 @@ +diff --git a/mesonbuild/dependencies/python.py b/mesonbuild/dependencies/python.py +index 883a29a..d9a82af 100644 +--- a/mesonbuild/dependencies/python.py ++++ b/mesonbuild/dependencies/python.py +@@ -232,8 +232,10 @@ class _PythonDependencyBase(_Base): + else: + if self.is_freethreaded: + libpath = Path('libs') / f'python{vernum}t.lib' ++ libpath = Path('libs') / f'..' / f'..' / f'..' / f'lib' / f'python{vernum}t.lib' + else: + libpath = Path('libs') / f'python{vernum}.lib' ++ libpath = Path('libs') / f'..' / f'..' / f'..' / f'lib' / f'python{vernum}.lib' + # For a debug build, pyconfig.h may force linking with + # pythonX_d.lib (see meson#10776). This cannot be avoided + # and won't work unless we also have a debug build of +@@ -250,6 +252,8 @@ class _PythonDependencyBase(_Base): + vscrt = self.env.coredata.optstore.get_value('b_vscrt') + if vscrt in {'mdd', 'mtd', 'from_buildtype', 'static_from_buildtype'}: + vscrt_debug = True ++ if is_debug_build: ++ libpath = Path('libs') / f'..' / f'..' / f'..' / f'debug/lib' / f'python{vernum}_d.lib' + if is_debug_build and vscrt_debug and not self.variables.get('Py_DEBUG'): + mlog.warning(textwrap.dedent('''\ + Using a debug build type with MSVC or an MSVC-compatible compiler +@@ -350,9 +354,10 @@ class PythonSystemDependency(SystemDependency, _PythonDependencyBase): + self.is_found = True + + # compile args ++ verdot = self.variables.get('py_version_short') + inc_paths = mesonlib.OrderedSet([ + self.variables.get('INCLUDEPY'), +- self.paths.get('include'), ++ self.paths.get('include') + f'/../../../include/python${verdot}', + self.paths.get('platinclude')]) + + self.compile_args += ['-I' + path for path in inc_paths if path] +@@ -416,7 +421,7 @@ def python_factory(env: 'Environment', for_machine: 'MachineChoice', + candidates.append(functools.partial(wrap_in_pythons_pc_dir, pkg_name, env, kwargs, installation)) + # We only need to check both, if a python install has a LIBPC. It might point to the wrong location, + # e.g. relocated / cross compilation, but the presence of LIBPC indicates we should definitely look for something. +- if pkg_libdir is not None: ++ if True or pkg_libdir is not None: + candidates.append(functools.partial(PythonPkgConfigDependency, pkg_name, env, kwargs, installation)) + else: + candidates.append(functools.partial(PkgConfigDependency, 'python3', env, kwargs)) diff --git a/cmake/vcpkg-ports/vcpkg-tool-meson/fix-libcpp-enable-assertions.patch b/cmake/vcpkg-ports/vcpkg-tool-meson/fix-libcpp-enable-assertions.patch new file mode 100644 index 000000000..394b064dc --- /dev/null +++ b/cmake/vcpkg-ports/vcpkg-tool-meson/fix-libcpp-enable-assertions.patch @@ -0,0 +1,52 @@ +From a16ec8b0fb6d7035b669a13edd4d97ff0c307a0b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Martin=20D=C3=B8rum?= +Date: Fri, 2 May 2025 10:56:28 +0200 +Subject: [PATCH] cpp: fix _LIBCPP_ENABLE_ASSERTIONS warning + +libc++ deprecated _LIBCPP_ENABLE_ASSERTIONS from version 18. +However, the libc++ shipped with Apple Clang backported that +deprecation in version 17 already, +which is the version which Apple currently ships for macOS. +This PR changes the _LIBCPP_ENABLE_ASSERTIONS deprecation check +to use version ">=17" on Apple Clang. +--- + mesonbuild/compilers/cpp.py | 12 ++++++++++-- + 1 file changed, 10 insertions(+), 2 deletions(-) + +diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py +index 01b9bb9fa34f..f7dc150e8608 100644 +--- a/mesonbuild/compilers/cpp.py ++++ b/mesonbuild/compilers/cpp.py +@@ -311,6 +311,9 @@ def get_option_link_args(self, target: 'BuildTarget', env: 'Environment', subpro + return libs + return [] + ++ def is_libcpp_enable_assertions_deprecated(self) -> bool: ++ return version_compare(self.version, ">=18") ++ + def get_assert_args(self, disable: bool, env: 'Environment') -> T.List[str]: + if disable: + return ['-DNDEBUG'] +@@ -323,7 +326,7 @@ def get_assert_args(self, disable: bool, env: 'Environment') -> T.List[str]: + if self.language_stdlib_provider(env) == 'stdc++': + return ['-D_GLIBCXX_ASSERTIONS=1'] + else: +- if version_compare(self.version, '>=18'): ++ if self.is_libcpp_enable_assertions_deprecated(): + return ['-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST'] + elif version_compare(self.version, '>=15'): + return ['-D_LIBCPP_ENABLE_ASSERTIONS=1'] +@@ -343,7 +346,12 @@ class ArmLtdClangCPPCompiler(ClangCPPCompiler): + + + class AppleClangCPPCompiler(AppleCompilerMixin, AppleCPPStdsMixin, ClangCPPCompiler): +- pass ++ def is_libcpp_enable_assertions_deprecated(self) -> bool: ++ # Upstream libc++ deprecated _LIBCPP_ENABLE_ASSERTIONS ++ # in favor of _LIBCPP_HARDENING_MODE from version 18 onwards, ++ # but Apple Clang 17's libc++ has back-ported that change. ++ # See: https://github.com/mesonbuild/meson/issues/14440 ++ return version_compare(self.version, ">=17") + + + class EmscriptenCPPCompiler(EmscriptenMixin, ClangCPPCompiler): diff --git a/cmake/vcpkg-ports/vcpkg-tool-meson/install.cmake b/cmake/vcpkg-ports/vcpkg-tool-meson/install.cmake new file mode 100644 index 000000000..84201aa1a --- /dev/null +++ b/cmake/vcpkg-ports/vcpkg-tool-meson/install.cmake @@ -0,0 +1,5 @@ +file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/tools/meson") +file(INSTALL "${SOURCE_PATH}/meson.py" + "${SOURCE_PATH}/mesonbuild" + DESTINATION "${CURRENT_PACKAGES_DIR}/tools/meson" +) diff --git a/cmake/vcpkg-ports/vcpkg-tool-meson/meson-intl.patch b/cmake/vcpkg-ports/vcpkg-tool-meson/meson-intl.patch new file mode 100644 index 000000000..8f2a029de --- /dev/null +++ b/cmake/vcpkg-ports/vcpkg-tool-meson/meson-intl.patch @@ -0,0 +1,13 @@ +diff --git a/mesonbuild/dependencies/misc.py b/mesonbuild/dependencies/misc.py +--- a/mesonbuild/dependencies/misc.py ++++ b/mesonbuild/dependencies/misc.py +@@ -593,7 +593,8 @@ iconv_factory = DependencyFactory( + + packages['intl'] = intl_factory = DependencyFactory( + 'intl', ++ [DependencyMethods.BUILTIN, DependencyMethods.SYSTEM, DependencyMethods.CMAKE], ++ cmake_name='Intl', +- [DependencyMethods.BUILTIN, DependencyMethods.SYSTEM], + builtin_class=IntlBuiltinDependency, + system_class=IntlSystemDependency, + ) diff --git a/cmake/vcpkg-ports/vcpkg-tool-meson/meson.template.in b/cmake/vcpkg-ports/vcpkg-tool-meson/meson.template.in new file mode 100644 index 000000000..df21b753b --- /dev/null +++ b/cmake/vcpkg-ports/vcpkg-tool-meson/meson.template.in @@ -0,0 +1,43 @@ +[binaries] +cmake = ['@CMAKE_COMMAND@'] +ninja = ['@NINJA@'] +pkg-config = ['@PKGCONFIG@'] +@MESON_MT@ +@MESON_AR@ +@MESON_RC@ +@MESON_C@ +@MESON_C_LD@ +@MESON_CXX@ +@MESON_CXX_LD@ +@MESON_OBJC@ +@MESON_OBJC_LD@ +@MESON_OBJCPP@ +@MESON_OBJCPP_LD@ +@MESON_FC@ +@MESON_FC_LD@ +@MESON_WINDRES@ +@MESON_ADDITIONAL_BINARIES@ +[properties] +cmake_toolchain_file = '@SCRIPTS@/buildsystems/vcpkg.cmake' +@MESON_ADDITIONAL_PROPERTIES@ +[cmake] +CMAKE_BUILD_TYPE = '@MESON_CMAKE_BUILD_TYPE@' +VCPKG_TARGET_TRIPLET = '@TARGET_TRIPLET@' +VCPKG_HOST_TRIPLET = '@_HOST_TRIPLET@' +VCPKG_CHAINLOAD_TOOLCHAIN_FILE = '@VCPKG_CHAINLOAD_TOOLCHAIN_FILE@' +VCPKG_CRT_LINKAGE = '@VCPKG_CRT_LINKAGE@' +_VCPKG_INSTALLED_DIR = '@_VCPKG_INSTALLED_DIR@' +@MESON_HOST_MACHINE@ +@MESON_BUILD_MACHINE@ +[built-in options] +default_library = '@MESON_DEFAULT_LIBRARY@' +werror = false +@MESON_CFLAGS@ +@MESON_CXXFLAGS@ +@MESON_FCFLAGS@ +@MESON_OBJCFLAGS@ +@MESON_OBJCPPFLAGS@ +# b_vscrt +@MESON_VSCRT_LINKAGE@ +# c_winlibs/cpp_winlibs +@MESON_WINLIBS@ \ No newline at end of file diff --git a/cmake/vcpkg-ports/vcpkg-tool-meson/portfile.cmake b/cmake/vcpkg-ports/vcpkg-tool-meson/portfile.cmake new file mode 100644 index 000000000..fdea886a7 --- /dev/null +++ b/cmake/vcpkg-ports/vcpkg-tool-meson/portfile.cmake @@ -0,0 +1,45 @@ +# This port represents a dependency on the Meson build system. +# In the future, it is expected that this port acquires and installs Meson. +# Currently is used in ports that call vcpkg_find_acquire_program(MESON) in order to force rebuilds. + +set(VCPKG_POLICY_CMAKE_HELPER_PORT enabled) + +set(patches + meson-intl.patch + adjust-python-dep.patch + adjust-args.patch + remove-freebsd-pcfile-specialization.patch + fix-libcpp-enable-assertions.patch # https://github.com/mesonbuild/meson/pull/14548, Remove in 1.8.3 + universal-osx.patch # NOTE(@getchoo): THIS IS THE ONLY CHANGE NEEDED FOR PRISM +) +set(scripts + vcpkg-port-config.cmake + vcpkg_configure_meson.cmake + vcpkg_install_meson.cmake + meson.template.in +) +set(to_hash + "${CMAKE_CURRENT_LIST_DIR}/vcpkg.json" + "${CMAKE_CURRENT_LIST_DIR}/portfile.cmake" +) +foreach(file IN LISTS patches scripts) + set(filepath "${CMAKE_CURRENT_LIST_DIR}/${file}") + list(APPEND to_hash "${filepath}") + file(COPY "${filepath}" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") +endforeach() + +set(meson_path_hash "") +foreach(filepath IN LISTS to_hash) + file(SHA1 "${filepath}" to_append) + string(APPEND meson_path_hash "${to_append}") +endforeach() +string(SHA512 meson_path_hash "${meson_path_hash}") + +string(SUBSTRING "${meson_path_hash}" 0 6 MESON_SHORT_HASH) +list(TRANSFORM patches REPLACE [[^(..*)$]] [["${CMAKE_CURRENT_LIST_DIR}/\0"]]) +list(JOIN patches "\n " PATCHES) +configure_file("${CMAKE_CURRENT_LIST_DIR}/vcpkg-port-config.cmake" "${CURRENT_PACKAGES_DIR}/share/${PORT}/vcpkg-port-config.cmake" @ONLY) + +vcpkg_install_copyright(FILE_LIST "${VCPKG_ROOT_DIR}/LICENSE.txt") + +include("${CURRENT_PACKAGES_DIR}/share/${PORT}/vcpkg-port-config.cmake") diff --git a/cmake/vcpkg-ports/vcpkg-tool-meson/remove-freebsd-pcfile-specialization.patch b/cmake/vcpkg-ports/vcpkg-tool-meson/remove-freebsd-pcfile-specialization.patch new file mode 100644 index 000000000..947345ccf --- /dev/null +++ b/cmake/vcpkg-ports/vcpkg-tool-meson/remove-freebsd-pcfile-specialization.patch @@ -0,0 +1,23 @@ +diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py +index cc0450a52..13501466d 100644 +--- a/mesonbuild/modules/pkgconfig.py ++++ b/mesonbuild/modules/pkgconfig.py +@@ -701,16 +701,8 @@ class PkgConfigModule(NewExtensionModule): + pcfile = filebase + '.pc' + pkgroot = pkgroot_name = kwargs['install_dir'] or default_install_dir + if pkgroot is None: +- m = state.environment.machines.host +- if m.is_freebsd(): +- pkgroot = os.path.join(_as_str(state.environment.coredata.optstore.get_value_for(OptionKey('prefix'))), 'libdata', 'pkgconfig') +- pkgroot_name = os.path.join('{prefix}', 'libdata', 'pkgconfig') +- elif m.is_haiku(): +- pkgroot = os.path.join(_as_str(state.environment.coredata.optstore.get_value_for(OptionKey('prefix'))), 'develop', 'lib', 'pkgconfig') +- pkgroot_name = os.path.join('{prefix}', 'develop', 'lib', 'pkgconfig') +- else: +- pkgroot = os.path.join(_as_str(state.environment.coredata.optstore.get_value_for(OptionKey('libdir'))), 'pkgconfig') +- pkgroot_name = os.path.join('{libdir}', 'pkgconfig') ++ pkgroot = os.path.join(_as_str(state.environment.coredata.optstore.get_value_for(OptionKey('libdir'))), 'pkgconfig') ++ pkgroot_name = os.path.join('{libdir}', 'pkgconfig') + relocatable = state.get_option('pkgconfig.relocatable') + self._generate_pkgconfig_file(state, deps, subdirs, name, description, url, + version, pcfile, conflicts, variables, diff --git a/cmake/vcpkg-ports/vcpkg-tool-meson/universal-osx.patch b/cmake/vcpkg-ports/vcpkg-tool-meson/universal-osx.patch new file mode 100644 index 000000000..58b96d5ce --- /dev/null +++ b/cmake/vcpkg-ports/vcpkg-tool-meson/universal-osx.patch @@ -0,0 +1,16 @@ +diff --git a/mesonbuild/compilers/detect.py b/mesonbuild/compilers/detect.py +index f57957f0b..a72e72a0b 100644 +--- a/mesonbuild/compilers/detect.py ++++ b/mesonbuild/compilers/detect.py +@@ -1472,6 +1472,11 @@ def _get_clang_compiler_defines(compiler: T.List[str], lang: str) -> T.Dict[str, + """ + from .mixins.clang import clang_lang_map + ++ # Filter out `-arch` flags passed to the compiler for Universal Binaries ++ # https://github.com/mesonbuild/meson/issues/5290 ++ # https://github.com/mesonbuild/meson/issues/8206 ++ compiler = [arg for i, arg in enumerate(compiler) if not (i > 0 and compiler[i - 1] == "-arch") and not arg == "-arch"] ++ + def _try_obtain_compiler_defines(args: T.List[str]) -> str: + mlog.debug(f'Running command: {join_args(args)}') + p, output, error = Popen_safe(compiler + args, write='', stdin=subprocess.PIPE) diff --git a/cmake/vcpkg-ports/vcpkg-tool-meson/vcpkg-port-config.cmake b/cmake/vcpkg-ports/vcpkg-tool-meson/vcpkg-port-config.cmake new file mode 100644 index 000000000..c0dee3a38 --- /dev/null +++ b/cmake/vcpkg-ports/vcpkg-tool-meson/vcpkg-port-config.cmake @@ -0,0 +1,62 @@ +include("${CURRENT_HOST_INSTALLED_DIR}/share/vcpkg-cmake-get-vars/vcpkg-port-config.cmake") +# Overwrite builtin scripts +include("${CMAKE_CURRENT_LIST_DIR}/vcpkg_configure_meson.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/vcpkg_install_meson.cmake") + +set(meson_short_hash @MESON_SHORT_HASH@) + +# Setup meson: +set(program MESON) +set(program_version @VERSION@) +set(program_name meson) +set(search_names meson meson.py) +set(ref "${program_version}") +set(path_to_search "${DOWNLOADS}/tools/meson-${program_version}-${meson_short_hash}") +set(download_urls "https://github.com/mesonbuild/meson/archive/${ref}.tar.gz") +set(download_filename "meson-${ref}.tar.gz") +set(download_sha512 bd2e65f0863d9cb974e659ff502d773e937b8a60aaddfd7d81e34cd2c296c8e82bf214d790ac089ba441543059dfc2677ba95ed51f676df9da420859f404a907) + +find_program(SCRIPT_MESON NAMES ${search_names} PATHS "${path_to_search}" NO_DEFAULT_PATH) # NO_DEFAULT_PATH due top patching + +if(NOT SCRIPT_MESON) + vcpkg_download_distfile(archive_path + URLS ${download_urls} + SHA512 "${download_sha512}" + FILENAME "${download_filename}" + ) + file(REMOVE_RECURSE "${path_to_search}") + file(REMOVE_RECURSE "${path_to_search}-tmp") + file(MAKE_DIRECTORY "${path_to_search}-tmp") + file(ARCHIVE_EXTRACT INPUT "${archive_path}" + DESTINATION "${path_to_search}-tmp" + #PATTERNS "**/mesonbuild/*" "**/*.py" + ) + z_vcpkg_apply_patches( + SOURCE_PATH "${path_to_search}-tmp/meson-${ref}" + PATCHES + @PATCHES@ + ) + file(MAKE_DIRECTORY "${path_to_search}") + file(RENAME "${path_to_search}-tmp/meson-${ref}/meson.py" "${path_to_search}/meson.py") + file(RENAME "${path_to_search}-tmp/meson-${ref}/mesonbuild" "${path_to_search}/mesonbuild") + file(REMOVE_RECURSE "${path_to_search}-tmp") + set(SCRIPT_MESON "${path_to_search}/meson.py") +endif() + +# Check required python version +vcpkg_find_acquire_program(PYTHON3) +vcpkg_execute_in_download_mode( + COMMAND "${PYTHON3}" --version + OUTPUT_VARIABLE version_contents + WORKING_DIRECTORY "${CURRENT_BUILDTREES_DIR}" +) +string(REGEX MATCH [[[0-9]+\.[0-9]+\.[0-9]+]] python_ver "${version_contents}") + +set(min_required 3.7) +if(python_ver VERSION_LESS "${min_required}") + message(FATAL_ERROR "Found Python version '${python_ver} at ${PYTHON3}' is insufficient for meson. meson requires at least version '${min_required}'") +else() + message(STATUS "Found Python version '${python_ver} at ${PYTHON3}'") +endif() + +message(STATUS "Using meson: ${SCRIPT_MESON}") diff --git a/cmake/vcpkg-ports/vcpkg-tool-meson/vcpkg.json b/cmake/vcpkg-ports/vcpkg-tool-meson/vcpkg.json new file mode 100644 index 000000000..04a0cbbec --- /dev/null +++ b/cmake/vcpkg-ports/vcpkg-tool-meson/vcpkg.json @@ -0,0 +1,11 @@ +{ + "name": "vcpkg-tool-meson", + "version": "1.8.2", + "description": "Meson build system", + "homepage": "https://github.com/mesonbuild/meson", + "license": "Apache-2.0", + "supports": "native", + "dependencies": [ + "vcpkg-cmake-get-vars" + ] +} diff --git a/cmake/vcpkg-ports/vcpkg-tool-meson/vcpkg_configure_meson.cmake b/cmake/vcpkg-ports/vcpkg-tool-meson/vcpkg_configure_meson.cmake new file mode 100644 index 000000000..6b00200d1 --- /dev/null +++ b/cmake/vcpkg-ports/vcpkg-tool-meson/vcpkg_configure_meson.cmake @@ -0,0 +1,480 @@ +function(z_vcpkg_meson_set_proglist_variables config_type) + if(VCPKG_TARGET_IS_WINDOWS) + set(proglist MT AR) + else() + set(proglist AR RANLIB STRIP NM OBJDUMP DLLTOOL MT) + endif() + foreach(prog IN LISTS proglist) + if(VCPKG_DETECTED_CMAKE_${prog}) + if(meson_${prog}) + string(TOUPPER "MESON_${meson_${prog}}" var_to_set) + set("${var_to_set}" "${meson_${prog}} = ['${VCPKG_DETECTED_CMAKE_${prog}}']" PARENT_SCOPE) + elseif(${prog} STREQUAL AR AND VCPKG_COMBINED_STATIC_LINKER_FLAGS_${config_type}) + # Probably need to move AR somewhere else + string(TOLOWER "${prog}" proglower) + z_vcpkg_meson_convert_compiler_flags_to_list(ar_flags "${VCPKG_COMBINED_STATIC_LINKER_FLAGS_${config_type}}") + list(PREPEND ar_flags "${VCPKG_DETECTED_CMAKE_${prog}}") + z_vcpkg_meson_convert_list_to_python_array(ar_flags ${ar_flags}) + set("MESON_AR" "${proglower} = ${ar_flags}" PARENT_SCOPE) + else() + string(TOUPPER "MESON_${prog}" var_to_set) + string(TOLOWER "${prog}" proglower) + set("${var_to_set}" "${proglower} = ['${VCPKG_DETECTED_CMAKE_${prog}}']" PARENT_SCOPE) + endif() + endif() + endforeach() + set(compilers "${arg_LANGUAGES}") + if(VCPKG_TARGET_IS_WINDOWS) + list(APPEND compilers RC) + endif() + set(meson_RC windres) + set(meson_Fortran fortran) + set(meson_CXX cpp) + foreach(prog IN LISTS compilers) + if(VCPKG_DETECTED_CMAKE_${prog}_COMPILER) + string(TOUPPER "MESON_${prog}" var_to_set) + if(meson_${prog}) + if(VCPKG_COMBINED_${prog}_FLAGS_${config_type}) + # Need compiler flags in prog vars for sanity check. + z_vcpkg_meson_convert_compiler_flags_to_list(${prog}flags "${VCPKG_COMBINED_${prog}_FLAGS_${config_type}}") + endif() + list(PREPEND ${prog}flags "${VCPKG_DETECTED_CMAKE_${prog}_COMPILER}") + list(FILTER ${prog}flags EXCLUDE REGEX "(-|/)nologo") # Breaks compiler detection otherwise + z_vcpkg_meson_convert_list_to_python_array(${prog}flags ${${prog}flags}) + set("${var_to_set}" "${meson_${prog}} = ${${prog}flags}" PARENT_SCOPE) + if (DEFINED VCPKG_DETECTED_CMAKE_${prog}_COMPILER_ID + AND NOT VCPKG_DETECTED_CMAKE_${prog}_COMPILER_ID MATCHES "^(GNU|Intel)$" + AND VCPKG_DETECTED_CMAKE_LINKER) + string(TOUPPER "MESON_${prog}_LD" var_to_set) + set(${var_to_set} "${meson_${prog}}_ld = ['${VCPKG_DETECTED_CMAKE_LINKER}']" PARENT_SCOPE) + endif() + else() + if(VCPKG_COMBINED_${prog}_FLAGS_${config_type}) + # Need compiler flags in prog vars for sanity check. + z_vcpkg_meson_convert_compiler_flags_to_list(${prog}flags "${VCPKG_COMBINED_${prog}_FLAGS_${config_type}}") + endif() + list(PREPEND ${prog}flags "${VCPKG_DETECTED_CMAKE_${prog}_COMPILER}") + list(FILTER ${prog}flags EXCLUDE REGEX "(-|/)nologo") # Breaks compiler detection otherwise + z_vcpkg_meson_convert_list_to_python_array(${prog}flags ${${prog}flags}) + string(TOLOWER "${prog}" proglower) + set("${var_to_set}" "${proglower} = ${${prog}flags}" PARENT_SCOPE) + if (DEFINED VCPKG_DETECTED_CMAKE_${prog}_COMPILER_ID + AND NOT VCPKG_DETECTED_CMAKE_${prog}_COMPILER_ID MATCHES "^(GNU|Intel)$" + AND VCPKG_DETECTED_CMAKE_LINKER) + string(TOUPPER "MESON_${prog}_LD" var_to_set) + set(${var_to_set} "${proglower}_ld = ['${VCPKG_DETECTED_CMAKE_LINKER}']" PARENT_SCOPE) + endif() + endif() + endif() + endforeach() +endfunction() + +function(z_vcpkg_meson_convert_compiler_flags_to_list out_var compiler_flags) + separate_arguments(cmake_list NATIVE_COMMAND "${compiler_flags}") + list(TRANSFORM cmake_list REPLACE ";" [[\\;]]) + set("${out_var}" "${cmake_list}" PARENT_SCOPE) +endfunction() + +function(z_vcpkg_meson_convert_list_to_python_array out_var) + z_vcpkg_function_arguments(flag_list 1) + vcpkg_list(REMOVE_ITEM flag_list "") # remove empty elements if any + vcpkg_list(JOIN flag_list "', '" flag_list) + set("${out_var}" "['${flag_list}']" PARENT_SCOPE) +endfunction() + +# Generates the required compiler properties for meson +function(z_vcpkg_meson_set_flags_variables config_type) + if(VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW) + set(libpath_flag /LIBPATH:) + else() + set(libpath_flag -L) + endif() + if(config_type STREQUAL "DEBUG") + set(path_suffix "/debug") + else() + set(path_suffix "") + endif() + + set(includepath "-I${CURRENT_INSTALLED_DIR}/include") + set(libpath "${libpath_flag}${CURRENT_INSTALLED_DIR}${path_suffix}/lib") + + foreach(lang IN LISTS arg_LANGUAGES) + z_vcpkg_meson_convert_compiler_flags_to_list(${lang}flags "${VCPKG_COMBINED_${lang}_FLAGS_${config_type}}") + if(lang MATCHES "^(C|CXX)$") + vcpkg_list(APPEND ${lang}flags "${includepath}") + endif() + z_vcpkg_meson_convert_list_to_python_array(${lang}flags ${${lang}flags}) + set(lang_mapping "${lang}") + if(lang STREQUAL "Fortran") + set(lang_mapping "FC") + endif() + string(TOLOWER "${lang_mapping}" langlower) + if(lang STREQUAL "CXX") + set(langlower cpp) + endif() + set(MESON_${lang_mapping}FLAGS "${langlower}_args = ${${lang}flags}\n") + set(linker_flags "${VCPKG_COMBINED_SHARED_LINKER_FLAGS_${config_type}}") + z_vcpkg_meson_convert_compiler_flags_to_list(linker_flags "${linker_flags}") + vcpkg_list(APPEND linker_flags "${libpath}") + z_vcpkg_meson_convert_list_to_python_array(linker_flags ${linker_flags}) + string(APPEND MESON_${lang_mapping}FLAGS "${langlower}_link_args = ${linker_flags}\n") + set(MESON_${lang_mapping}FLAGS "${MESON_${lang_mapping}FLAGS}" PARENT_SCOPE) + endforeach() +endfunction() + +function(z_vcpkg_get_build_and_host_system build_system host_system is_cross) #https://mesonbuild.com/Cross-compilation.html + set(build_unknown FALSE) + if(CMAKE_HOST_WIN32) + if(DEFINED ENV{PROCESSOR_ARCHITEW6432}) + set(build_arch $ENV{PROCESSOR_ARCHITEW6432}) + else() + set(build_arch $ENV{PROCESSOR_ARCHITECTURE}) + endif() + if(build_arch MATCHES "(amd|AMD)64") + set(build_cpu_fam x86_64) + set(build_cpu x86_64) + elseif(build_arch MATCHES "(x|X)86") + set(build_cpu_fam x86) + set(build_cpu i686) + elseif(build_arch MATCHES "^(ARM|arm)64$") + set(build_cpu_fam aarch64) + set(build_cpu armv8) + elseif(build_arch MATCHES "^(ARM|arm)$") + set(build_cpu_fam arm) + set(build_cpu armv7hl) + else() + if(NOT DEFINED VCPKG_MESON_CROSS_FILE OR NOT DEFINED VCPKG_MESON_NATIVE_FILE) + message(WARNING "Unsupported build architecture ${build_arch}! Please set VCPKG_MESON_(CROSS|NATIVE)_FILE to a meson file containing the build_machine entry!") + endif() + set(build_unknown TRUE) + endif() + elseif(CMAKE_HOST_UNIX) + # at this stage, CMAKE_HOST_SYSTEM_PROCESSOR is not defined + execute_process( + COMMAND uname -m + OUTPUT_VARIABLE MACHINE + OUTPUT_STRIP_TRAILING_WHITESPACE + COMMAND_ERROR_IS_FATAL ANY) + + # Show real machine architecture to visually understand whether we are in a native Apple Silicon terminal or running under Rosetta emulation + debug_message("Machine: ${MACHINE}") + + if(MACHINE MATCHES "arm64|aarch64") + set(build_cpu_fam aarch64) + set(build_cpu armv8) + elseif(MACHINE MATCHES "armv7h?l") + set(build_cpu_fam arm) + set(build_cpu ${MACHINE}) + elseif(MACHINE MATCHES "x86_64|amd64") + set(build_cpu_fam x86_64) + set(build_cpu x86_64) + elseif(MACHINE MATCHES "x86|i686") + set(build_cpu_fam x86) + set(build_cpu i686) + elseif(MACHINE MATCHES "i386") + set(build_cpu_fam x86) + set(build_cpu i386) + elseif(MACHINE MATCHES "loongarch64") + set(build_cpu_fam loongarch64) + set(build_cpu loongarch64) + else() + # https://github.com/mesonbuild/meson/blob/master/docs/markdown/Reference-tables.md#cpu-families + if(NOT DEFINED VCPKG_MESON_CROSS_FILE OR NOT DEFINED VCPKG_MESON_NATIVE_FILE) + message(WARNING "Unhandled machine: ${MACHINE}! Please set VCPKG_MESON_(CROSS|NATIVE)_FILE to a meson file containing the build_machine entry!") + endif() + set(build_unknown TRUE) + endif() + else() + if(NOT DEFINED VCPKG_MESON_CROSS_FILE OR NOT DEFINED VCPKG_MESON_NATIVE_FILE) + message(WARNING "Failed to detect the build architecture! Please set VCPKG_MESON_(CROSS|NATIVE)_FILE to a meson file containing the build_machine entry!") + endif() + set(build_unknown TRUE) + endif() + + set(build "[build_machine]\n") # Machine the build is performed on + string(APPEND build "endian = 'little'\n") + if(CMAKE_HOST_WIN32) + string(APPEND build "system = 'windows'\n") + elseif(CMAKE_HOST_APPLE) + string(APPEND build "system = 'darwin'\n") + elseif(CYGWIN) + string(APPEND build "system = 'cygwin'\n") + elseif(CMAKE_HOST_UNIX) + string(APPEND build "system = 'linux'\n") + else() + set(build_unknown TRUE) + endif() + + if(DEFINED build_cpu_fam) + string(APPEND build "cpu_family = '${build_cpu_fam}'\n") + endif() + if(DEFINED build_cpu) + string(APPEND build "cpu = '${build_cpu}'") + endif() + if(NOT build_unknown) + set(${build_system} "${build}" PARENT_SCOPE) + endif() + + set(host_unkown FALSE) + if(VCPKG_TARGET_ARCHITECTURE MATCHES "(amd|AMD|x|X)64") + set(host_cpu_fam x86_64) + set(host_cpu x86_64) + elseif(VCPKG_TARGET_ARCHITECTURE MATCHES "(x|X)86") + set(host_cpu_fam x86) + set(host_cpu i686) + elseif(VCPKG_TARGET_ARCHITECTURE MATCHES "^(ARM|arm)64$") + set(host_cpu_fam aarch64) + set(host_cpu armv8) + elseif(VCPKG_TARGET_ARCHITECTURE MATCHES "^(ARM|arm)$") + set(host_cpu_fam arm) + set(host_cpu armv7hl) + elseif(VCPKG_TARGET_ARCHITECTURE MATCHES "loongarch64") + set(host_cpu_fam loongarch64) + set(host_cpu loongarch64) + elseif(VCPKG_TARGET_ARCHITECTURE MATCHES "wasm32") + set(host_cpu_fam wasm32) + set(host_cpu wasm32) + else() + if(NOT DEFINED VCPKG_MESON_CROSS_FILE OR NOT DEFINED VCPKG_MESON_NATIVE_FILE) + message(WARNING "Unsupported target architecture ${VCPKG_TARGET_ARCHITECTURE}! Please set VCPKG_MESON_(CROSS|NATIVE)_FILE to a meson file containing the host_machine entry!" ) + endif() + set(host_unkown TRUE) + endif() + + set(host "[host_machine]\n") # host=target in vcpkg. + string(APPEND host "endian = 'little'\n") + if(NOT VCPKG_CMAKE_SYSTEM_NAME OR VCPKG_TARGET_IS_MINGW OR VCPKG_TARGET_IS_UWP) + set(meson_system_name "windows") + else() + string(TOLOWER "${VCPKG_CMAKE_SYSTEM_NAME}" meson_system_name) + endif() + string(APPEND host "system = '${meson_system_name}'\n") + string(APPEND host "cpu_family = '${host_cpu_fam}'\n") + string(APPEND host "cpu = '${host_cpu}'") + if(NOT host_unkown) + set(${host_system} "${host}" PARENT_SCOPE) + endif() + + if(NOT build_cpu_fam MATCHES "${host_cpu_fam}" + OR VCPKG_TARGET_IS_ANDROID OR VCPKG_TARGET_IS_IOS OR VCPKG_TARGET_IS_UWP + OR (VCPKG_TARGET_IS_MINGW AND NOT CMAKE_HOST_WIN32)) + set(${is_cross} TRUE PARENT_SCOPE) + endif() +endfunction() + +function(z_vcpkg_meson_setup_extra_windows_variables config_type) + ## b_vscrt + if(VCPKG_CRT_LINKAGE STREQUAL "static") + set(crt_type "mt") + else() + set(crt_type "md") + endif() + if(config_type STREQUAL "DEBUG") + set(crt_type "${crt_type}d") + endif() + set(MESON_VSCRT_LINKAGE "b_vscrt = '${crt_type}'" PARENT_SCOPE) + ## winlibs + separate_arguments(c_winlibs NATIVE_COMMAND "${VCPKG_DETECTED_CMAKE_C_STANDARD_LIBRARIES}") + separate_arguments(cpp_winlibs NATIVE_COMMAND "${VCPKG_DETECTED_CMAKE_CXX_STANDARD_LIBRARIES}") + z_vcpkg_meson_convert_list_to_python_array(c_winlibs ${c_winlibs}) + z_vcpkg_meson_convert_list_to_python_array(cpp_winlibs ${cpp_winlibs}) + set(MESON_WINLIBS "c_winlibs = ${c_winlibs}\n") + string(APPEND MESON_WINLIBS "cpp_winlibs = ${cpp_winlibs}") + set(MESON_WINLIBS "${MESON_WINLIBS}" PARENT_SCOPE) +endfunction() + +function(z_vcpkg_meson_setup_variables config_type) + set(meson_var_list VSCRT_LINKAGE WINLIBS MT AR RC C C_LD CXX CXX_LD OBJC OBJC_LD OBJCXX OBJCXX_LD FC FC_LD WINDRES CFLAGS CXXFLAGS OBJCFLAGS OBJCXXFLAGS FCFLAGS SHARED_LINKER_FLAGS) + foreach(var IN LISTS meson_var_list) + set(MESON_${var} "") + endforeach() + + if(VCPKG_TARGET_IS_WINDOWS) + z_vcpkg_meson_setup_extra_windows_variables("${config_type}") + endif() + + z_vcpkg_meson_set_proglist_variables("${config_type}") + z_vcpkg_meson_set_flags_variables("${config_type}") + + foreach(var IN LISTS meson_var_list) + set(MESON_${var} "${MESON_${var}}" PARENT_SCOPE) + endforeach() +endfunction() + +function(vcpkg_generate_meson_cmd_args) + cmake_parse_arguments(PARSE_ARGV 0 arg + "" + "OUTPUT;CONFIG" + "OPTIONS;LANGUAGES;ADDITIONAL_BINARIES;ADDITIONAL_PROPERTIES" + ) + + if(NOT arg_LANGUAGES) + set(arg_LANGUAGES C CXX) + endif() + + vcpkg_list(JOIN arg_ADDITIONAL_BINARIES "\n" MESON_ADDITIONAL_BINARIES) + vcpkg_list(JOIN arg_ADDITIONAL_PROPERTIES "\n" MESON_ADDITIONAL_PROPERTIES) + + set(buildtype "${arg_CONFIG}") + + if(NOT VCPKG_CHAINLOAD_TOOLCHAIN_FILE) + z_vcpkg_select_default_vcpkg_chainload_toolchain() + endif() + vcpkg_list(APPEND VCPKG_CMAKE_CONFIGURE_OPTIONS "-DVCPKG_LANGUAGES=${arg_LANGUAGES}") + vcpkg_cmake_get_vars(cmake_vars_file) + debug_message("Including cmake vars from: ${cmake_vars_file}") + include("${cmake_vars_file}") + + vcpkg_list(APPEND arg_OPTIONS --backend ninja --wrap-mode nodownload -Doptimization=plain) + + z_vcpkg_get_build_and_host_system(MESON_HOST_MACHINE MESON_BUILD_MACHINE IS_CROSS) + + if(arg_CONFIG STREQUAL "DEBUG") + set(suffix "dbg") + else() + string(SUBSTRING "${arg_CONFIG}" 0 3 suffix) + string(TOLOWER "${suffix}" suffix) + endif() + set(meson_input_file_${buildtype} "${CURRENT_BUILDTREES_DIR}/meson-${TARGET_TRIPLET}-${suffix}.log") + + if(IS_CROSS) + # VCPKG_CROSSCOMPILING is not used since it regresses a lot of ports in x64-windows-x triplets + # For consistency this should proably be changed in the future? + vcpkg_list(APPEND arg_OPTIONS --native "${SCRIPTS}/buildsystems/meson/none.txt") + vcpkg_list(APPEND arg_OPTIONS --cross "${meson_input_file_${buildtype}}") + else() + vcpkg_list(APPEND arg_OPTIONS --native "${meson_input_file_${buildtype}}") + endif() + + # User provided cross/native files + if(VCPKG_MESON_NATIVE_FILE) + vcpkg_list(APPEND arg_OPTIONS --native "${VCPKG_MESON_NATIVE_FILE}") + endif() + if(VCPKG_MESON_NATIVE_FILE_${buildtype}) + vcpkg_list(APPEND arg_OPTIONS --native "${VCPKG_MESON_NATIVE_FILE_${buildtype}}") + endif() + if(VCPKG_MESON_CROSS_FILE) + vcpkg_list(APPEND arg_OPTIONS --cross "${VCPKG_MESON_CROSS_FILE}") + endif() + if(VCPKG_MESON_CROSS_FILE_${buildtype}) + vcpkg_list(APPEND arg_OPTIONS --cross "${VCPKG_MESON_CROSS_FILE_${buildtype}}") + endif() + + vcpkg_list(APPEND arg_OPTIONS --libdir lib) # else meson install into an architecture describing folder + vcpkg_list(APPEND arg_OPTIONS --pkgconfig.relocatable) + + if(arg_CONFIG STREQUAL "RELEASE") + vcpkg_list(APPEND arg_OPTIONS -Ddebug=false --prefix "${CURRENT_PACKAGES_DIR}") + vcpkg_list(APPEND arg_OPTIONS "--pkg-config-path;['${CURRENT_INSTALLED_DIR}/lib/pkgconfig','${CURRENT_INSTALLED_DIR}/share/pkgconfig']") + if(VCPKG_TARGET_IS_WINDOWS) + vcpkg_list(APPEND arg_OPTIONS "-Dcmake_prefix_path=['${CURRENT_INSTALLED_DIR}','${CURRENT_INSTALLED_DIR}/debug','${CURRENT_INSTALLED_DIR}/share']") + else() + vcpkg_list(APPEND arg_OPTIONS "-Dcmake_prefix_path=['${CURRENT_INSTALLED_DIR}','${CURRENT_INSTALLED_DIR}/debug']") + endif() + elseif(arg_CONFIG STREQUAL "DEBUG") + vcpkg_list(APPEND arg_OPTIONS -Ddebug=true --prefix "${CURRENT_PACKAGES_DIR}/debug" --includedir ../include) + vcpkg_list(APPEND arg_OPTIONS "--pkg-config-path;['${CURRENT_INSTALLED_DIR}/debug/lib/pkgconfig','${CURRENT_INSTALLED_DIR}/share/pkgconfig']") + if(VCPKG_TARGET_IS_WINDOWS) + vcpkg_list(APPEND arg_OPTIONS "-Dcmake_prefix_path=['${CURRENT_INSTALLED_DIR}/debug','${CURRENT_INSTALLED_DIR}','${CURRENT_INSTALLED_DIR}/share']") + else() + vcpkg_list(APPEND arg_OPTIONS "-Dcmake_prefix_path=['${CURRENT_INSTALLED_DIR}/debug','${CURRENT_INSTALLED_DIR}']") + endif() + else() + message(FATAL_ERROR "Unknown configuration. Only DEBUG and RELEASE are valid values.") + endif() + + # Allow overrides / additional configuration variables from triplets + if(DEFINED VCPKG_MESON_CONFIGURE_OPTIONS) + vcpkg_list(APPEND arg_OPTIONS ${VCPKG_MESON_CONFIGURE_OPTIONS}) + endif() + if(DEFINED VCPKG_MESON_CONFIGURE_OPTIONS_${buildtype}) + vcpkg_list(APPEND arg_OPTIONS ${VCPKG_MESON_CONFIGURE_OPTIONS_${buildtype}}) + endif() + + if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic") + set(MESON_DEFAULT_LIBRARY shared) + else() + set(MESON_DEFAULT_LIBRARY static) + endif() + set(MESON_CMAKE_BUILD_TYPE "${cmake_build_type_${buildtype}}") + z_vcpkg_meson_setup_variables(${buildtype}) + configure_file("${CMAKE_CURRENT_FUNCTION_LIST_DIR}/meson.template.in" "${meson_input_file_${buildtype}}" @ONLY) + set("${arg_OUTPUT}" ${arg_OPTIONS} PARENT_SCOPE) +endfunction() + +function(vcpkg_configure_meson) + # parse parameters such that semicolons in options arguments to COMMAND don't get erased + cmake_parse_arguments(PARSE_ARGV 0 arg + "NO_PKG_CONFIG" + "SOURCE_PATH" + "OPTIONS;OPTIONS_DEBUG;OPTIONS_RELEASE;LANGUAGES;ADDITIONAL_BINARIES;ADDITIONAL_NATIVE_BINARIES;ADDITIONAL_CROSS_BINARIES;ADDITIONAL_PROPERTIES" + ) + + if(DEFINED arg_ADDITIONAL_NATIVE_BINARIES OR DEFINED arg_ADDITIONAL_CROSS_BINARIES) + message(WARNING "Options ADDITIONAL_(NATIVE|CROSS)_BINARIES have been deprecated. Only use ADDITIONAL_BINARIES!") + endif() + vcpkg_list(APPEND arg_ADDITIONAL_BINARIES ${arg_ADDITIONAL_NATIVE_BINARIES} ${arg_ADDITIONAL_CROSS_BINARIES}) + vcpkg_list(REMOVE_DUPLICATES arg_ADDITIONAL_BINARIES) + + file(REMOVE_RECURSE "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-rel") + file(REMOVE_RECURSE "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-dbg") + + vcpkg_find_acquire_program(MESON) + + get_filename_component(CMAKE_PATH "${CMAKE_COMMAND}" DIRECTORY) + vcpkg_add_to_path("${CMAKE_PATH}") # Make CMake invokeable for Meson + + vcpkg_find_acquire_program(NINJA) + + if(NOT arg_NO_PKG_CONFIG) + vcpkg_find_acquire_program(PKGCONFIG) + set(ENV{PKG_CONFIG} "${PKGCONFIG}") + endif() + + vcpkg_find_acquire_program(PYTHON3) + get_filename_component(PYTHON3_DIR "${PYTHON3}" DIRECTORY) + vcpkg_add_to_path(PREPEND "${PYTHON3_DIR}") + + set(buildtypes "") + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + set(buildname "DEBUG") + set(cmake_build_type_${buildname} "Debug") + vcpkg_list(APPEND buildtypes "${buildname}") + set(path_suffix_${buildname} "debug/") + set(suffix_${buildname} "dbg") + endif() + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") + set(buildname "RELEASE") + set(cmake_build_type_${buildname} "Release") + vcpkg_list(APPEND buildtypes "${buildname}") + set(path_suffix_${buildname} "") + set(suffix_${buildname} "rel") + endif() + + # configure build + foreach(buildtype IN LISTS buildtypes) + message(STATUS "Configuring ${TARGET_TRIPLET}-${suffix_${buildtype}}") + file(MAKE_DIRECTORY "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-${suffix_${buildtype}}") + + vcpkg_generate_meson_cmd_args( + OUTPUT cmd_args + CONFIG ${buildtype} + LANGUAGES ${arg_LANGUAGES} + OPTIONS ${arg_OPTIONS} ${arg_OPTIONS_${buildtype}} + ADDITIONAL_BINARIES ${arg_ADDITIONAL_BINARIES} + ADDITIONAL_PROPERTIES ${arg_ADDITIONAL_PROPERTIES} + ) + + vcpkg_execute_required_process( + COMMAND ${MESON} setup ${cmd_args} ${arg_SOURCE_PATH} + WORKING_DIRECTORY "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-${suffix_${buildtype}}" + LOGNAME config-${TARGET_TRIPLET}-${suffix_${buildtype}} + SAVE_LOG_FILES + meson-logs/meson-log.txt + meson-info/intro-dependencies.json + meson-logs/install-log.txt + ) + + message(STATUS "Configuring ${TARGET_TRIPLET}-${suffix_${buildtype}} done") + endforeach() +endfunction() diff --git a/cmake/vcpkg-ports/vcpkg-tool-meson/vcpkg_install_meson.cmake b/cmake/vcpkg-ports/vcpkg-tool-meson/vcpkg_install_meson.cmake new file mode 100644 index 000000000..0351f271a --- /dev/null +++ b/cmake/vcpkg-ports/vcpkg-tool-meson/vcpkg_install_meson.cmake @@ -0,0 +1,71 @@ +function(vcpkg_install_meson) + cmake_parse_arguments(PARSE_ARGV 0 arg "ADD_BIN_TO_PATH" "" "") + + vcpkg_find_acquire_program(NINJA) + unset(ENV{DESTDIR}) # installation directory was already specified with '--prefix' option + + if(VCPKG_TARGET_IS_OSX) + vcpkg_backup_env_variables(VARS SDKROOT MACOSX_DEPLOYMENT_TARGET) + set(ENV{SDKROOT} "${VCPKG_DETECTED_CMAKE_OSX_SYSROOT}") + set(ENV{MACOSX_DEPLOYMENT_TARGET} "${VCPKG_DETECTED_CMAKE_OSX_DEPLOYMENT_TARGET}") + endif() + + foreach(buildtype IN ITEMS "debug" "release") + if(DEFINED VCPKG_BUILD_TYPE AND NOT VCPKG_BUILD_TYPE STREQUAL buildtype) + continue() + endif() + + if(buildtype STREQUAL "debug") + set(short_buildtype "dbg") + else() + set(short_buildtype "rel") + endif() + + message(STATUS "Package ${TARGET_TRIPLET}-${short_buildtype}") + if(arg_ADD_BIN_TO_PATH) + vcpkg_backup_env_variables(VARS PATH) + if(buildtype STREQUAL "debug") + vcpkg_add_to_path(PREPEND "${CURRENT_INSTALLED_DIR}/debug/bin") + else() + vcpkg_add_to_path(PREPEND "${CURRENT_INSTALLED_DIR}/bin") + endif() + endif() + vcpkg_execute_required_process( + COMMAND "${NINJA}" install -v + WORKING_DIRECTORY "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-${short_buildtype}" + LOGNAME package-${TARGET_TRIPLET}-${short_buildtype} + ) + if(arg_ADD_BIN_TO_PATH) + vcpkg_restore_env_variables(VARS PATH) + endif() + endforeach() + + vcpkg_list(SET renamed_libs) + if(VCPKG_TARGET_IS_WINDOWS AND VCPKG_LIBRARY_LINKAGE STREQUAL static AND NOT VCPKG_TARGET_IS_MINGW) + # Meson names all static libraries lib.a which basically breaks the world + file(GLOB_RECURSE gen_libraries "${CURRENT_PACKAGES_DIR}*/**/lib*.a") + foreach(gen_library IN LISTS gen_libraries) + get_filename_component(libdir "${gen_library}" DIRECTORY) + get_filename_component(libname "${gen_library}" NAME) + string(REGEX REPLACE ".a$" ".lib" fixed_librawname "${libname}") + string(REGEX REPLACE "^lib" "" fixed_librawname "${fixed_librawname}") + file(RENAME "${gen_library}" "${libdir}/${fixed_librawname}") + # For cmake fixes. + string(REGEX REPLACE ".a$" "" origin_librawname "${libname}") + string(REGEX REPLACE ".lib$" "" fixed_librawname "${fixed_librawname}") + vcpkg_list(APPEND renamed_libs ${fixed_librawname}) + set(${librawname}_old ${origin_librawname}) + set(${librawname}_new ${fixed_librawname}) + endforeach() + file(GLOB_RECURSE cmake_files "${CURRENT_PACKAGES_DIR}*/*.cmake") + foreach(cmake_file IN LISTS cmake_files) + foreach(current_lib IN LISTS renamed_libs) + vcpkg_replace_string("${cmake_file}" "${${current_lib}_old}" "${${current_lib}_new}" IGNORE_UNCHANGED) + endforeach() + endforeach() + endif() + + if(VCPKG_TARGET_IS_OSX) + vcpkg_restore_env_variables(VARS SDKROOT MACOSX_DEPLOYMENT_TARGET) + endif() +endfunction() diff --git a/cmake/vcpkg-triplets/universal-osx.cmake b/cmake/vcpkg-triplets/universal-osx.cmake new file mode 100644 index 000000000..1c91a5650 --- /dev/null +++ b/cmake/vcpkg-triplets/universal-osx.cmake @@ -0,0 +1,8 @@ +# See https://github.com/microsoft/vcpkg/discussions/19454 +# NOTE: Try to keep in sync with default arm64-osx definition +set(VCPKG_TARGET_ARCHITECTURE x64) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE static) + +set(VCPKG_CMAKE_SYSTEM_NAME Darwin) +set(VCPKG_OSX_ARCHITECTURES "arm64;x86_64") diff --git a/cmake/windowsMSVCPreset.json b/cmake/windowsMSVCPreset.json index eb6a38b19..2cb996b81 100644 --- a/cmake/windowsMSVCPreset.json +++ b/cmake/windowsMSVCPreset.json @@ -13,9 +13,7 @@ "lhs": "${hostSystemName}", "rhs": "Windows" }, - "cacheVariables": { - "Launcher_BUILD_ARTIFACT": "Windows-MSVC-Qt6" - } + "generator": "Ninja" }, { "name": "windows_msvc_arm64_cross_base", @@ -23,9 +21,8 @@ "inherits": [ "windows_msvc_base" ], - "architecture": "arm64", "cacheVariables": { - "Launcher_BUILD_ARTIFACT": "Windows-MSVC-arm64-Qt6" + "CMAKE_SYSTEM_NAME": "${hostSystemName}" } }, { @@ -34,8 +31,7 @@ "base_debug", "windows_msvc_base" ], - "displayName": "Windows MSVC (Debug)", - "generator": "Ninja" + "displayName": "Windows MSVC (Debug)" }, { "name": "windows_msvc_release", @@ -67,10 +63,7 @@ "base_ci", "windows_msvc_base" ], - "displayName": "Windows MSVC (CI)", - "cacheVariables": { - "Launcher_BUILD_ARTIFACT": "Windows-MSVC-Qt6" - } + "displayName": "Windows MSVC (CI)" }, { "name": "windows_msvc_arm64_cross_ci", @@ -78,10 +71,7 @@ "base_ci", "windows_msvc_arm64_cross_base" ], - "displayName": "Windows MSVC (ARM64 cross, CI)", - "cacheVariables": { - "Launcher_BUILD_ARTIFACT": "Windows-MSVC-arm64-Qt6" - } + "displayName": "Windows MSVC (ARM64 cross, CI)" } ], "buildPresets": [ @@ -110,11 +100,7 @@ ], "displayName": "Windows MSVC (Release)", "configurePreset": "windows_msvc_release", - "configuration": "Release", - "nativeToolOptions": [ - "/p:UseMultiToolTask=true", - "/p:EnforceProcessCountAcrossBuilds=true" - ] + "configuration": "Release" }, { "name": "windows_msvc_arm64_cross_debug", @@ -123,11 +109,7 @@ ], "displayName": "Windows MSVC (ARM64 cross, Debug)", "configurePreset": "windows_msvc_arm64_cross_debug", - "configuration": "Debug", - "nativeToolOptions": [ - "/p:UseMultiToolTask=true", - "/p:EnforceProcessCountAcrossBuilds=true" - ] + "configuration": "Debug" }, { "name": "windows_msvc_arm64_cross_release", @@ -136,11 +118,7 @@ ], "displayName": "Windows MSVC (ARM64 cross, Release)", "configurePreset": "windows_msvc_arm64_cross_release", - "configuration": "Release", - "nativeToolOptions": [ - "/p:UseMultiToolTask=true", - "/p:EnforceProcessCountAcrossBuilds=true" - ] + "configuration": "Release" }, { "name": "windows_msvc_ci", @@ -149,11 +127,7 @@ ], "displayName": "Windows MSVC (CI)", "configurePreset": "windows_msvc_ci", - "configuration": "Release", - "nativeToolOptions": [ - "/p:UseMultiToolTask=true", - "/p:EnforceProcessCountAcrossBuilds=true" - ] + "configuration": "Release" }, { "name": "windows_msvc_arm64_cross_ci", @@ -162,11 +136,7 @@ ], "displayName": "Windows MSVC (ARM64 cross, CI)", "configurePreset": "windows_msvc_arm64_cross_ci", - "configuration": "Release", - "nativeToolOptions": [ - "/p:UseMultiToolTask=true", - "/p:EnforceProcessCountAcrossBuilds=true" - ] + "configuration": "Release" } ], "testPresets": [ diff --git a/cmake/windowsMinGWPreset.json b/cmake/windowsMinGWPreset.json index 984caadd6..7c4adbcf2 100644 --- a/cmake/windowsMinGWPreset.json +++ b/cmake/windowsMinGWPreset.json @@ -13,10 +13,7 @@ "lhs": "${hostSystemName}", "rhs": "Windows" }, - "generator": "Ninja", - "cacheVariables": { - "Launcher_BUILD_ARTIFACT": "Windows-MinGW-w64-Qt6" - } + "generator": "Ninja" }, { "name": "windows_mingw_debug", @@ -40,10 +37,7 @@ "base_ci", "windows_mingw_base" ], - "displayName": "Windows MinGW (CI)", - "cacheVariables": { - "Launcher_BUILD_ARTIFACT": "Windows-MinGW-w64-Qt6" - } + "displayName": "Windows MinGW (CI)" } ], "buildPresets": [ diff --git a/flake.lock b/flake.lock index 2d2f820f4..70625e923 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1748460289, - "narHash": "sha256-7doLyJBzCllvqX4gszYtmZUToxKvMUrg45EUWaUYmBg=", + "lastModified": 1757745802, + "narHash": "sha256-hLEO2TPj55KcUFUU1vgtHE9UEIOjRcH/4QbmfHNF820=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "96ec055edbe5ee227f28cdbc3f1ddf1df5965102", + "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", "type": "github" }, "original": { diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 99b72870b..dbf55f425 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -46,8 +46,6 @@ #include "DataMigrationTask.h" #include "java/JavaInstallList.h" #include "net/PasteUpload.h" -#include "pathmatcher/MultiMatcher.h" -#include "pathmatcher/SimplePrefixMatcher.h" #include "tasks/Task.h" #include "tools/GenericProfiler.h" #include "ui/InstanceWindow.h" @@ -864,6 +862,15 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) // get rid of invalid meta urls if (!metaUrl.isValid() || (metaUrl.scheme() != "http" && metaUrl.scheme() != "https")) m_settings->reset("MetaURLOverride"); + + // Resource URL + m_settings->registerSetting("ResourceURL", BuildConfig.DEFAULT_RESOURCE_BASE); + + QUrl resourceUrl(m_settings->get("ResourceURL").toString()); + + // get rid of invalid resource urls + if (!resourceUrl.isValid() || (resourceUrl.scheme() != "http" && resourceUrl.scheme() != "https")) + m_settings->reset("ResourceURL"); } m_settings->registerSetting("CloseAfterLaunch", false); @@ -1482,12 +1489,9 @@ std::shared_ptr Application::javalist() return m_javalist; } -QIcon Application::getThemedIcon(const QString& name) +QIcon Application::logo() { - if (name == "logo") { - return QIcon(":/" + BuildConfig.LAUNCHER_SVGFILENAME); - } - return QIcon::fromTheme(name); + return QIcon(":/" + BuildConfig.LAUNCHER_SVGFILENAME); } bool Application::openJsonEditor(const QString& filename) @@ -1976,22 +1980,23 @@ bool Application::handleDataMigration(const QString& currentData, if (!currentExists) { // Migrate! - auto matcher = std::make_shared(); - matcher->add(std::make_shared(configFile)); - matcher->add(std::make_shared( - BuildConfig.LAUNCHER_CONFIGFILE)); // it's possible that we already used that directory before - matcher->add(std::make_shared("logs/")); - matcher->add(std::make_shared("accounts.json")); - matcher->add(std::make_shared("accounts/")); - matcher->add(std::make_shared("assets/")); - matcher->add(std::make_shared("icons/")); - matcher->add(std::make_shared("instances/")); - matcher->add(std::make_shared("libraries/")); - matcher->add(std::make_shared("mods/")); - matcher->add(std::make_shared("themes/")); + using namespace Filters; + + QList filters; + filters.append(equals(configFile)); + filters.append(equals(BuildConfig.LAUNCHER_CONFIGFILE)); // it's possible that we already used that directory before + filters.append(startsWith("logs/")); + filters.append(equals("accounts.json")); + filters.append(startsWith("accounts/")); + filters.append(startsWith("assets/")); + filters.append(startsWith("icons/")); + filters.append(startsWith("instances/")); + filters.append(startsWith("libraries/")); + filters.append(startsWith("mods/")); + filters.append(startsWith("themes/")); ProgressDialog diag; - DataMigrationTask task(oldData, currentData, matcher); + DataMigrationTask task(oldData, currentData, any(std::move(filters))); if (diag.execWithTask(&task)) { qDebug() << "<> Migration succeeded"; setDoNotMigrate(); diff --git a/launcher/Application.h b/launcher/Application.h index 52a84b461..0fd733b50 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -116,7 +116,7 @@ class Application : public QApplication { qint64 timeSinceStart() const { return m_startTime.msecsTo(QDateTime::currentDateTime()); } - QIcon getThemedIcon(const QString& name); + QIcon logo(); ThemeManager* themeManager() { return m_themeManager.get(); } diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 6baac4ce8..a542b76eb 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -52,7 +52,6 @@ #include "BaseVersionList.h" #include "MessageLevel.h" #include "minecraft/auth/MinecraftAccount.h" -#include "pathmatcher/IPathMatcher.h" #include "settings/INIFile.h" #include "net/Mode.h" diff --git a/launcher/BaseVersion.h b/launcher/BaseVersion.h index 2837ff3a9..02a7212e5 100644 --- a/launcher/BaseVersion.h +++ b/launcher/BaseVersion.h @@ -30,21 +30,21 @@ class BaseVersion { * A string used to identify this version in config files. * This should be unique within the version list or shenanigans will occur. */ - virtual QString descriptor() = 0; + virtual QString descriptor() const = 0; /*! * The name of this version as it is displayed to the user. * For example: "1.5.1" */ - virtual QString name() = 0; + virtual QString name() const = 0; /*! * This should return a string that describes * the kind of version this is (Stable, Beta, Snapshot, whatever) */ virtual QString typeString() const = 0; - virtual bool operator<(BaseVersion& a) { return name() < a.name(); } - virtual bool operator>(BaseVersion& a) { return name() > a.name(); } + virtual bool operator<(BaseVersion& a) const { return name() < a.name(); } + virtual bool operator>(BaseVersion& a) const { return name() > a.name(); } }; Q_DECLARE_METATYPE(BaseVersion::Ptr) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 9bbf2f7ca..6d6e2ae7f 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -56,7 +56,6 @@ set(CORE_SOURCES # String filters Filter.h - Filter.cpp # JSON parsing helpers Json.h @@ -108,15 +107,6 @@ if (UNIX AND NOT CYGWIN AND NOT APPLE) ) endif() -set(PATHMATCHER_SOURCES - # Path matchers - pathmatcher/FSTreeMatcher.h - pathmatcher/IPathMatcher.h - pathmatcher/MultiMatcher.h - pathmatcher/RegexpMatcher.h - pathmatcher/SimplePrefixMatcher.h -) - set(NET_SOURCES # network stuffs net/ByteArraySink.h @@ -491,8 +481,11 @@ set(META_SOURCES set(API_SOURCES modplatform/ModIndex.h modplatform/ModIndex.cpp + modplatform/ResourceType.h + modplatform/ResourceType.cpp modplatform/ResourceAPI.h + modplatform/ResourceAPI.cpp modplatform/EnsureMetadataTask.h modplatform/EnsureMetadataTask.cpp @@ -503,8 +496,6 @@ set(API_SOURCES modplatform/flame/FlameAPI.cpp modplatform/modrinth/ModrinthAPI.h modplatform/modrinth/ModrinthAPI.cpp - modplatform/helpers/NetworkResourceAPI.h - modplatform/helpers/NetworkResourceAPI.cpp modplatform/helpers/HashUtils.h modplatform/helpers/HashUtils.cpp modplatform/helpers/OverrideUtils.h @@ -532,8 +523,6 @@ set(FTB_SOURCES set(FLAME_SOURCES # Flame - modplatform/flame/FlamePackIndex.cpp - modplatform/flame/FlamePackIndex.h modplatform/flame/FlameModIndex.cpp modplatform/flame/FlameModIndex.h modplatform/flame/PackManifest.h @@ -551,8 +540,6 @@ set(FLAME_SOURCES set(MODRINTH_SOURCES modplatform/modrinth/ModrinthPackIndex.cpp modplatform/modrinth/ModrinthPackIndex.h - modplatform/modrinth/ModrinthPackManifest.cpp - modplatform/modrinth/ModrinthPackManifest.h modplatform/modrinth/ModrinthCheckUpdate.cpp modplatform/modrinth/ModrinthCheckUpdate.h modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -755,7 +742,6 @@ endif() set(LOGIC_SOURCES ${CORE_SOURCES} - ${PATHMATCHER_SOURCES} ${NET_SOURCES} ${LAUNCH_SOURCES} ${UPDATE_SOURCES} @@ -1039,8 +1025,6 @@ SET(LAUNCHER_SOURCES ui/pages/modplatform/OptionalModDialog.cpp ui/pages/modplatform/OptionalModDialog.h - ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp - ui/pages/modplatform/modrinth/ModrinthResourceModels.h ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp ui/pages/modplatform/modrinth/ModrinthResourcePages.h @@ -1173,6 +1157,13 @@ SET(LAUNCHER_SOURCES ui/instanceview/VisualGroup.h ) +if (APPLE) + set(LAUNCHER_SOURCES + ${LAUNCHER_SOURCES} + ui/themes/ThemeManager.mm + ) +endif() + if (NOT Apple) set(LAUNCHER_SOURCES ${LAUNCHER_SOURCES} @@ -1303,12 +1294,16 @@ target_link_libraries(Launcher_logic Launcher_murmur2 nbt++ ${ZLIB_LIBRARIES} - tomlplusplus::tomlplusplus qdcss BuildConfig Qt${QT_VERSION_MAJOR}::Widgets qrcodegenerator ) +if(TARGET PkgConfig::tomlplusplus) + target_link_libraries(Launcher_logic PkgConfig::tomlplusplus) +else() + target_link_libraries(Launcher_logic tomlplusplus::tomlplusplus) +endif() if (UNIX AND NOT CYGWIN AND NOT APPLE) target_link_libraries(Launcher_logic @@ -1379,12 +1374,18 @@ if(DEFINED Launcher_APP_BINARY_DEFS) endif() install(TARGETS ${Launcher_Name} + RUNTIME_DEPENDENCY_SET LAUNCHER_DEPENDENCY_SET BUNDLE DESTINATION "." COMPONENT Runtime LIBRARY DESTINATION ${LIBRARY_DEST_DIR} COMPONENT Runtime RUNTIME DESTINATION ${BINARY_DEST_DIR} COMPONENT Runtime FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime ) +# Deploy PDBs +if(WIN32 AND (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")) + install(FILES $ DESTINATION ${BINARY_DEST_DIR}) +endif() + if(Launcher_BUILD_UPDATER) # Updater add_library(prism_updater_logic STATIC ${PRISMUPDATER_SOURCES} ${TASKS_SOURCES} ${PRISMUPDATER_UI}) @@ -1418,6 +1419,11 @@ if(Launcher_BUILD_UPDATER) RUNTIME DESTINATION ${BINARY_DEST_DIR} COMPONENT Runtime FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime ) + + # Deploy PDBs + if(WIN32 AND (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")) + install(FILES $ DESTINATION ${BINARY_DEST_DIR}) + endif() endif() if(WIN32 OR (DEFINED Launcher_BUILD_FILELINKER AND Launcher_BUILD_FILELINKER)) @@ -1442,6 +1448,12 @@ if(WIN32 OR (DEFINED Launcher_BUILD_FILELINKER AND Launcher_BUILD_FILELINKER)) add_executable("${Launcher_Name}_filelink" WIN32 filelink/filelink_main.cpp) target_sources("${Launcher_Name}_filelink" PRIVATE filelink/filelink.exe.manifest) + # HACK: Fix manifest issues with Ninja in release mode (and only release mode) and MSVC + # I have no idea why this works or why it's needed. UPDATE THIS IF YOU EDIT THE MANIFEST!!! -@getchoo + # Thank you 2018 CMake mailing list thread https://cmake.cmake.narkive.com/LnotZXus/conflicting-msvc-manifests + if(MSVC) + set_property(TARGET "${Launcher_Name}_filelink" PROPERTY LINK_FLAGS "/MANIFESTUAC:level='requireAdministrator'") + endif() target_link_libraries("${Launcher_Name}_filelink" filelink_logic) @@ -1458,6 +1470,11 @@ if(WIN32 OR (DEFINED Launcher_BUILD_FILELINKER AND Launcher_BUILD_FILELINKER)) RUNTIME DESTINATION ${BINARY_DEST_DIR} COMPONENT Runtime FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime ) + + # Deploy PDBs + if(WIN32 AND (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")) + install(FILES $ DESTINATION ${BINARY_DEST_DIR}) + endif() endif() if (UNIX AND APPLE AND Launcher_ENABLE_UPDATER) @@ -1468,186 +1485,64 @@ if (UNIX AND APPLE AND Launcher_ENABLE_UPDATER) endif() #### The bundle mess! #### -# Bundle utilities are used to complete the portable packages - they add all the libraries that would otherwise be missing on the target system. +# Bundle utilities are used to complete packages for different platforms - they add all the libraries that would otherwise be missing on the target system. # NOTE: it seems that this absolutely has to be here, and nowhere else. -if(INSTALL_BUNDLE STREQUAL "full") +if(WIN32 OR (UNIX AND APPLE)) + if(WIN32) + set(QT_DEPLOY_TOOL_OPTIONS "--no-opengl-sw --no-quick-import --no-system-d3d-compiler --no-system-dxc-compiler --skip-plugin-types generic,networkinformation") + endif() + + qt_generate_deploy_script( + TARGET ${Launcher_Name} + OUTPUT_SCRIPT QT_DEPLOY_SCRIPT + CONTENT " + qt_deploy_runtime_dependencies( + EXECUTABLE ${BINARY_DEST_DIR}/$ + BIN_DIR ${BINARY_DEST_DIR} + LIBEXEC_DIR ${LIBRARY_DEST_DIR} + LIB_DIR ${LIBRARY_DEST_DIR} + PLUGINS_DIR ${PLUGIN_DEST_DIR} + NO_OVERWRITE + NO_TRANSLATIONS + NO_COMPILER_RUNTIME + DEPLOY_TOOL_OPTIONS ${QT_DEPLOY_TOOL_OPTIONS} + )" + ) + + # Bundle our linked dependencies + install( + RUNTIME_DEPENDENCY_SET LAUNCHER_DEPENDENCY_SET + COMPONENT bundle + DIRECTORIES + ${CMAKE_SYSTEM_LIBRARY_PATH} + ${QT_LIBS_DIR} + ${QT_LIBEXECS_DIR} + PRE_EXCLUDE_REGEXES + "^(api-ms-win|ext-ms)-.*\\.dll$" + # FIXME: Why aren't these caught by the below regex??? + "^azure.*\\.dll$" + "^vcruntime.*\\.dll$" + POST_EXCLUDE_REGEXES + "system32" + LIBRARY DESTINATION ${LIBRARY_DEST_DIR} + RUNTIME DESTINATION ${BINARY_DEST_DIR} + FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} + ) + # Deploy Qt plugins + install( + SCRIPT ${QT_DEPLOY_SCRIPT} + COMPONENT bundle + ) + # Add qt.conf - this makes Qt stop looking for things outside the bundle install( CODE "file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${RESOURCES_DEST_DIR}/qt.conf\" \" \")" - COMPONENT Runtime + COMPONENT bundle ) - # add qtlogging.ini as a config file + # Add qtlogging.ini as a config file install( FILES "qtlogging.ini" DESTINATION ${CMAKE_INSTALL_PREFIX}/${RESOURCES_DEST_DIR} - COMPONENT Runtime + COMPONENT bundle ) - # Bundle plugins - # Image formats - install( - DIRECTORY "${QT_PLUGINS_DIR}/imageformats" - CONFIGURATIONS Debug RelWithDebInfo "" - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "tga|tiff|mng" EXCLUDE - ) - install( - DIRECTORY "${QT_PLUGINS_DIR}/imageformats" - CONFIGURATIONS Release MinSizeRel - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "tga|tiff|mng" EXCLUDE - REGEX "d\\." EXCLUDE - REGEX "_debug\\." EXCLUDE - REGEX "\\.dSYM" EXCLUDE - ) - # Icon engines - install( - DIRECTORY "${QT_PLUGINS_DIR}/iconengines" - CONFIGURATIONS Debug RelWithDebInfo "" - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "fontawesome" EXCLUDE - ) - install( - DIRECTORY "${QT_PLUGINS_DIR}/iconengines" - CONFIGURATIONS Release MinSizeRel - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "fontawesome" EXCLUDE - REGEX "d\\." EXCLUDE - REGEX "_debug\\." EXCLUDE - REGEX "\\.dSYM" EXCLUDE - ) - # Platform plugins - install( - DIRECTORY "${QT_PLUGINS_DIR}/platforms" - CONFIGURATIONS Debug RelWithDebInfo "" - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "minimal|linuxfb|offscreen" EXCLUDE - ) - install( - DIRECTORY "${QT_PLUGINS_DIR}/platforms" - CONFIGURATIONS Release MinSizeRel - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "minimal|linuxfb|offscreen" EXCLUDE - REGEX "[^2]d\\." EXCLUDE - REGEX "_debug\\." EXCLUDE - REGEX "\\.dSYM" EXCLUDE - ) - # Style plugins - if(EXISTS "${QT_PLUGINS_DIR}/styles") - install( - DIRECTORY "${QT_PLUGINS_DIR}/styles" - CONFIGURATIONS Debug RelWithDebInfo "" - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - ) - install( - DIRECTORY "${QT_PLUGINS_DIR}/styles" - CONFIGURATIONS Release MinSizeRel - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "d\\." EXCLUDE - REGEX "_debug\\." EXCLUDE - REGEX "\\.dSYM" EXCLUDE - ) - endif() - # TLS plugins (Qt 6 only) - if(EXISTS "${QT_PLUGINS_DIR}/tls") - install( - DIRECTORY "${QT_PLUGINS_DIR}/tls" - CONFIGURATIONS Debug RelWithDebInfo "" - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - PATTERN "*qcertonlybackend*" EXCLUDE - ) - install( - DIRECTORY "${QT_PLUGINS_DIR}/tls" - CONFIGURATIONS Release MinSizeRel - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "dd\\." EXCLUDE - REGEX "_debug\\." EXCLUDE - REGEX "\\.dSYM" EXCLUDE - PATTERN "*qcertonlybackend*" EXCLUDE - ) - endif() - # Wayland support - if(EXISTS "${QT_PLUGINS_DIR}/wayland-graphics-integration-client") - install( - DIRECTORY "${QT_PLUGINS_DIR}/wayland-graphics-integration-client" - CONFIGURATIONS Debug RelWithDebInfo "" - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - ) - install( - DIRECTORY "${QT_PLUGINS_DIR}/wayland-graphics-integration-client" - CONFIGURATIONS Release MinSizeRel - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "dd\\." EXCLUDE - REGEX "_debug\\." EXCLUDE - REGEX "\\.dSYM" EXCLUDE - ) - endif() - if(EXISTS "${QT_PLUGINS_DIR}/wayland-graphics-integration-server") - install( - DIRECTORY "${QT_PLUGINS_DIR}/wayland-graphics-integration-server" - CONFIGURATIONS Debug RelWithDebInfo "" - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - ) - install( - DIRECTORY "${QT_PLUGINS_DIR}/wayland-graphics-integration-server" - CONFIGURATIONS Release MinSizeRel - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "dd\\." EXCLUDE - REGEX "_debug\\." EXCLUDE - REGEX "\\.dSYM" EXCLUDE - ) - endif() - if(EXISTS "${QT_PLUGINS_DIR}/wayland-decoration-client") - install( - DIRECTORY "${QT_PLUGINS_DIR}/wayland-decoration-client" - CONFIGURATIONS Debug RelWithDebInfo "" - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - ) - install( - DIRECTORY "${QT_PLUGINS_DIR}/wayland-decoration-client" - CONFIGURATIONS Release MinSizeRel - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "dd\\." EXCLUDE - REGEX "_debug\\." EXCLUDE - REGEX "\\.dSYM" EXCLUDE - ) - endif() - if(EXISTS "${QT_PLUGINS_DIR}/wayland-shell-integration") - install( - DIRECTORY "${QT_PLUGINS_DIR}/wayland-shell-integration" - CONFIGURATIONS Debug RelWithDebInfo "" - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - ) - install( - DIRECTORY "${QT_PLUGINS_DIR}/wayland-shell-integration" - CONFIGURATIONS Release MinSizeRel - DESTINATION ${PLUGIN_DEST_DIR} - COMPONENT Runtime - REGEX "dd\\." EXCLUDE - REGEX "_debug\\." EXCLUDE - REGEX "\\.dSYM" EXCLUDE - ) - endif() - configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/install_prereqs.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/install_prereqs.cmake" - @ONLY - ) - install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/install_prereqs.cmake" COMPONENT Runtime) endif() diff --git a/launcher/DataMigrationTask.cpp b/launcher/DataMigrationTask.cpp index 18decc7c3..9677f868e 100644 --- a/launcher/DataMigrationTask.cpp +++ b/launcher/DataMigrationTask.cpp @@ -12,7 +12,7 @@ #include -DataMigrationTask::DataMigrationTask(const QString& sourcePath, const QString& targetPath, const IPathMatcher::Ptr pathMatcher) +DataMigrationTask::DataMigrationTask(const QString& sourcePath, const QString& targetPath, Filter pathMatcher) : Task(), m_sourcePath(sourcePath), m_targetPath(targetPath), m_pathMatcher(pathMatcher), m_copy(sourcePath, targetPath) { m_copy.matcher(m_pathMatcher).whitelist(true); diff --git a/launcher/DataMigrationTask.h b/launcher/DataMigrationTask.h index fc613cd5e..9a2b0adb8 100644 --- a/launcher/DataMigrationTask.h +++ b/launcher/DataMigrationTask.h @@ -5,7 +5,7 @@ #pragma once #include "FileSystem.h" -#include "pathmatcher/IPathMatcher.h" +#include "Filter.h" #include "tasks/Task.h" #include @@ -18,7 +18,7 @@ class DataMigrationTask : public Task { Q_OBJECT public: - explicit DataMigrationTask(const QString& sourcePath, const QString& targetPath, IPathMatcher::Ptr pathmatcher); + explicit DataMigrationTask(const QString& sourcePath, const QString& targetPath, Filter pathmatcher); ~DataMigrationTask() override = default; protected: @@ -33,7 +33,7 @@ class DataMigrationTask : public Task { private: const QString& m_sourcePath; const QString& m_targetPath; - const IPathMatcher::Ptr m_pathMatcher; + const Filter m_pathMatcher; FS::copy m_copy; int m_toCopy = 0; diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 5136e7954..e987fa59a 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -331,7 +331,7 @@ bool copy::operator()(const QString& offset, bool dryRun) // Function that'll do the actual copying 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)) + if (m_matcher && (m_matcher(relative_dst_path) != m_whitelist)) return; auto dst_path = PathCombine(dst, relative_dst_path); @@ -418,7 +418,7 @@ void create_link::make_link_list(const QString& offset) // Function that'll do the actual linking auto link_file = [this, dst](QString src_path, QString relative_dst_path) { - if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist)) { + if (m_matcher && (m_matcher(relative_dst_path) != m_whitelist)) { qDebug() << "path" << relative_dst_path << "in black list or not in whitelist"; return; } @@ -897,6 +897,29 @@ QString getApplicationsDir() return QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation); } +QString quoteArgs(const QStringList& args, const QString& wrap, const QString& escapeChar, bool wrapOnlyIfNeeded = false) +{ + QString result; + + auto size = args.size(); + for (int i = 0; i < size; ++i) { + QString arg = args[i]; + arg.replace(wrap, escapeChar); + + bool needsWrapping = !wrapOnlyIfNeeded || arg.contains(' ') || arg.contains('\t') || arg.contains(wrap); + + if (needsWrapping) + result += wrap + arg + wrap; + else + result += arg; + + if (i < size - 1) + result += ' '; + } + + return result; +} + // Cross-platform Shortcut creation QString createShortcut(QString destination, QString target, QStringList args, QString name, QString icon) { @@ -940,9 +963,7 @@ QString createShortcut(QString destination, QString target, QStringList args, QS f.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream stream(&f); - QString argstring; - if (!args.empty()) - argstring = " \"" + args.join("\" \"") + "\""; + auto argstring = quoteArgs(args, "\"", "\\\""); stream << "#!/bin/bash" << "\n"; stream << "\"" << target << "\" " << argstring << "\n"; @@ -984,14 +1005,12 @@ QString createShortcut(QString destination, QString target, QStringList args, QS f.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream stream(&f); - QString argstring; - if (!args.empty()) - argstring = " '" + args.join("' '") + "'"; + auto argstring = quoteArgs(args, "'", "'\\''"); stream << "[Desktop Entry]" << "\n"; stream << "Type=Application" << "\n"; stream << "Categories=Game;ActionGame;AdventureGame;Simulation" << "\n"; - stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n"; + stream << "Exec=\"" << target.toLocal8Bit() << "\" " << argstring.toLocal8Bit() << "\n"; stream << "Name=" << name.toLocal8Bit() << "\n"; if (!icon.isEmpty()) { stream << "Icon=" << icon.toLocal8Bit() << "\n"; @@ -1030,20 +1049,7 @@ QString createShortcut(QString destination, QString target, QStringList args, QS return QString(); } - QString argStr; - int argCount = args.count(); - for (int i = 0; i < argCount; i++) { - if (args[i].contains(' ')) { - argStr.append('"').append(args[i]).append('"'); - } else { - argStr.append(args[i]); - } - - if (i < argCount - 1) { - argStr.append(" "); - } - } - + auto argStr = quoteArgs(args, "\"", "\\\"", true); if (argStr.length() >= MAX_PATH) { qWarning() << "Arguments string is too long!"; return QString(); @@ -1271,7 +1277,7 @@ bool clone::operator()(const QString& offset, bool dryRun) // Function that'll do the actual cloneing auto cloneFile = [this, dryRun, dst, &err](QString src_path, QString relative_dst_path) { - if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist)) + if (m_matcher && (m_matcher(relative_dst_path) != m_whitelist)) return; auto dst_path = PathCombine(dst, relative_dst_path); diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 0e573a09e..b0d9ae2e8 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -38,7 +38,7 @@ #pragma once #include "Exception.h" -#include "pathmatcher/IPathMatcher.h" +#include "Filter.h" #include @@ -115,9 +115,9 @@ class copy : public QObject { m_followSymlinks = follow; return *this; } - copy& matcher(IPathMatcher::Ptr filter) + copy& matcher(Filter filter) { - m_matcher = filter; + m_matcher = std::move(filter); return *this; } copy& whitelist(bool whitelist) @@ -147,7 +147,7 @@ class copy : public QObject { private: bool m_followSymlinks = true; - IPathMatcher::Ptr m_matcher = nullptr; + Filter m_matcher = nullptr; bool m_whitelist = false; bool m_overwrite = false; QDir m_src; @@ -209,9 +209,9 @@ class create_link : public QObject { m_useHardLinks = useHard; return *this; } - create_link& matcher(IPathMatcher::Ptr filter) + create_link& matcher(Filter filter) { - m_matcher = filter; + m_matcher = std::move(filter); return *this; } create_link& whitelist(bool whitelist) @@ -260,7 +260,7 @@ class create_link : public QObject { private: bool m_useHardLinks = false; - IPathMatcher::Ptr m_matcher = nullptr; + Filter m_matcher = nullptr; bool m_whitelist = false; bool m_recursive = true; @@ -492,9 +492,9 @@ class clone : public QObject { m_src.setPath(src); m_dst.setPath(dst); } - clone& matcher(IPathMatcher::Ptr filter) + clone& matcher(Filter filter) { - m_matcher = filter; + m_matcher = std::move(filter); return *this; } clone& whitelist(bool whitelist) @@ -518,7 +518,7 @@ class clone : public QObject { bool operator()(const QString& offset, bool dryRun = false); private: - IPathMatcher::Ptr m_matcher = nullptr; + Filter m_matcher = nullptr; bool m_whitelist = false; QDir m_src; QDir m_dst; diff --git a/launcher/Filter.cpp b/launcher/Filter.cpp deleted file mode 100644 index adeb2209e..000000000 --- a/launcher/Filter.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "Filter.h" - -ContainsFilter::ContainsFilter(const QString& pattern) : pattern(pattern) {} -bool ContainsFilter::accepts(const QString& value) -{ - return value.contains(pattern); -} - -ExactFilter::ExactFilter(const QString& pattern) : pattern(pattern) {} -bool ExactFilter::accepts(const QString& value) -{ - return value == pattern; -} - -ExactIfPresentFilter::ExactIfPresentFilter(const QString& pattern) : pattern(pattern) {} -bool ExactIfPresentFilter::accepts(const QString& value) -{ - return value.isEmpty() || value == pattern; -} - -RegexpFilter::RegexpFilter(const QString& regexp, bool invert) : invert(invert) -{ - pattern.setPattern(regexp); - pattern.optimize(); -} -bool RegexpFilter::accepts(const QString& value) -{ - auto match = pattern.match(value); - bool matched = match.hasMatch(); - return invert ? (!matched) : (matched); -} - -ExactListFilter::ExactListFilter(const QStringList& pattern) : m_pattern(pattern) {} -bool ExactListFilter::accepts(const QString& value) -{ - return m_pattern.isEmpty() || m_pattern.contains(value); -} \ No newline at end of file diff --git a/launcher/Filter.h b/launcher/Filter.h index ae835e724..317f5b067 100644 --- a/launcher/Filter.h +++ b/launcher/Filter.h @@ -3,59 +3,52 @@ #include #include -class Filter { - public: - virtual ~Filter() = default; - virtual bool accepts(const QString& value) = 0; -}; +using Filter = std::function; -class ContainsFilter : public Filter { - public: - ContainsFilter(const QString& pattern); - virtual ~ContainsFilter() = default; - bool accepts(const QString& value) override; +namespace Filters { +inline Filter inverse(Filter filter) +{ + return [filter = std::move(filter)](const QString& src) { return !filter(src); }; +} - private: - QString pattern; -}; +inline Filter any(QList filters) +{ + return [filters = std::move(filters)](const QString& src) { + for (auto& filter : filters) + if (filter(src)) + return true; -class ExactFilter : public Filter { - public: - ExactFilter(const QString& pattern); - virtual ~ExactFilter() = default; - bool accepts(const QString& value) override; + return false; + }; +} - private: - QString pattern; -}; +inline Filter equals(QString pattern) +{ + return [pattern = std::move(pattern)](const QString& src) { return src == pattern; }; +} -class ExactIfPresentFilter : public Filter { - public: - ExactIfPresentFilter(const QString& pattern); - virtual ~ExactIfPresentFilter() override = default; - bool accepts(const QString& value) override; +inline Filter equalsAny(QStringList patterns = {}) +{ + return [patterns = std::move(patterns)](const QString& src) { return patterns.isEmpty() || patterns.contains(src); }; +} - private: - QString pattern; -}; +inline Filter equalsOrEmpty(QString pattern) +{ + return [pattern = std::move(pattern)](const QString& src) { return src.isEmpty() || src == pattern; }; +} -class RegexpFilter : public Filter { - public: - RegexpFilter(const QString& regexp, bool invert); - virtual ~RegexpFilter() = default; - bool accepts(const QString& value) override; +inline Filter contains(QString pattern) +{ + return [pattern = std::move(pattern)](const QString& src) { return src.contains(pattern); }; +} - private: - QRegularExpression pattern; - bool invert = false; -}; +inline Filter startsWith(QString pattern) +{ + return [pattern = std::move(pattern)](const QString& src) { return src.startsWith(pattern); }; +} -class ExactListFilter : public Filter { - public: - ExactListFilter(const QStringList& pattern = {}); - virtual ~ExactListFilter() = default; - bool accepts(const QString& value) override; - - private: - QStringList m_pattern; -}; +inline Filter regexp(QRegularExpression pattern) +{ + return [pattern = std::move(pattern)](const QString& src) { return pattern.match(src).hasMatch(); }; +} +} // namespace Filters diff --git a/launcher/InstanceCopyPrefs.h b/launcher/InstanceCopyPrefs.h index 61c51b3b7..1c3c0c984 100644 --- a/launcher/InstanceCopyPrefs.h +++ b/launcher/InstanceCopyPrefs.h @@ -8,23 +8,23 @@ struct InstanceCopyPrefs { public: - [[nodiscard]] bool allTrue() const; - [[nodiscard]] QString getSelectedFiltersAsRegex() const; - [[nodiscard]] QString getSelectedFiltersAsRegex(const QStringList& additionalFilters) const; + bool allTrue() const; + QString getSelectedFiltersAsRegex() const; + QString getSelectedFiltersAsRegex(const QStringList& additionalFilters) const; // Getters - [[nodiscard]] bool isCopySavesEnabled() const; - [[nodiscard]] bool isKeepPlaytimeEnabled() const; - [[nodiscard]] bool isCopyGameOptionsEnabled() const; - [[nodiscard]] bool isCopyResourcePacksEnabled() const; - [[nodiscard]] bool isCopyShaderPacksEnabled() const; - [[nodiscard]] bool isCopyServersEnabled() const; - [[nodiscard]] bool isCopyModsEnabled() const; - [[nodiscard]] bool isCopyScreenshotsEnabled() const; - [[nodiscard]] bool isUseSymLinksEnabled() const; - [[nodiscard]] bool isLinkRecursivelyEnabled() const; - [[nodiscard]] bool isUseHardLinksEnabled() const; - [[nodiscard]] bool isDontLinkSavesEnabled() const; - [[nodiscard]] bool isUseCloneEnabled() const; + bool isCopySavesEnabled() const; + bool isKeepPlaytimeEnabled() const; + bool isCopyGameOptionsEnabled() const; + bool isCopyResourcePacksEnabled() const; + bool isCopyShaderPacksEnabled() const; + bool isCopyServersEnabled() const; + bool isCopyModsEnabled() const; + bool isCopyScreenshotsEnabled() const; + bool isUseSymLinksEnabled() const; + bool isLinkRecursivelyEnabled() const; + bool isUseHardLinksEnabled() const; + bool isDontLinkSavesEnabled() const; + bool isUseCloneEnabled() const; // Setters void enableCopySaves(bool b); void enableKeepPlaytime(bool b); diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index fb5963532..eba1a1339 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -3,8 +3,8 @@ #include #include #include "FileSystem.h" +#include "Filter.h" #include "NullInstance.h" -#include "pathmatcher/RegexpMatcher.h" #include "settings/INISettingsObject.h" #include "tasks/Task.h" @@ -30,9 +30,8 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyP if (!filters.isEmpty()) { // Set regex filter: // FIXME: get this from the original instance type... - auto matcherReal = new RegexpMatcher(filters); - matcherReal->caseSensitive(false); - m_matcher.reset(matcherReal); + QRegularExpression regexp(filters, QRegularExpression::CaseInsensitiveOption); + m_matcher = Filters::regexp(regexp); } } diff --git a/launcher/InstanceCopyTask.h b/launcher/InstanceCopyTask.h index 3aba13e5c..ef4120bc6 100644 --- a/launcher/InstanceCopyTask.h +++ b/launcher/InstanceCopyTask.h @@ -5,6 +5,7 @@ #include #include "BaseInstance.h" #include "BaseVersion.h" +#include "Filter.h" #include "InstanceCopyPrefs.h" #include "InstanceTask.h" #include "net/NetJob.h" @@ -28,7 +29,7 @@ class InstanceCopyTask : public InstanceTask { InstancePtr m_origInstance; QFuture m_copyFuture; QFutureWatcher m_copyFutureWatcher; - IPathMatcher::Ptr m_matcher; + Filter m_matcher; bool m_keepPlaytime; bool m_useLinks = false; bool m_useHardLinks = false; diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index c489f6112..77298e2ce 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -263,9 +263,9 @@ void InstanceImportTask::extractFinished() } } -bool installIcon(QString root, QString instIcon) +bool installIcon(QString root, QString instIconKey) { - auto importIconPath = IconUtils::findBestIconIn(root, instIcon); + auto importIconPath = IconUtils::findBestIconIn(root, instIconKey); if (importIconPath.isNull() || !QFile::exists(importIconPath)) importIconPath = IconUtils::findBestIconIn(root, "icon.png"); if (importIconPath.isNull() || !QFile::exists(importIconPath)) @@ -273,10 +273,10 @@ bool installIcon(QString root, QString instIcon) if (!importIconPath.isNull() && QFile::exists(importIconPath)) { // import icon auto iconList = APPLICATION->icons(); - if (iconList->iconFileExists(instIcon)) { - iconList->deleteIcon(instIcon); + if (iconList->iconFileExists(instIconKey)) { + iconList->deleteIcon(instIconKey); } - iconList->installIcon(importIconPath, instIcon); + iconList->installIcon(importIconPath, instIconKey + ".png"); return true; } return false; diff --git a/launcher/InstancePageProvider.h b/launcher/InstancePageProvider.h index ebbab0f3a..c683774d2 100644 --- a/launcher/InstancePageProvider.h +++ b/launcher/InstancePageProvider.h @@ -45,7 +45,7 @@ class InstancePageProvider : protected QObject, public BasePageProvider { values.append(new ServersPage(onesix)); values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots"))); values.append(new InstanceSettingsPage(onesix)); - values.append(new OtherLogsPage("logs", tr("Other logs"), "Other-Logs", inst)); + values.append(new OtherLogsPage("logs", tr("Other Logs"), "Other-Logs", inst)); return values; } diff --git a/launcher/InstanceTask.h b/launcher/InstanceTask.h index 7c02160a7..86b4cee68 100644 --- a/launcher/InstanceTask.h +++ b/launcher/InstanceTask.h @@ -14,10 +14,10 @@ struct InstanceName { InstanceName() = default; InstanceName(QString name, QString version) : m_original_name(std::move(name)), m_original_version(std::move(version)) {} - [[nodiscard]] QString modifiedName() const; - [[nodiscard]] QString originalName() const; - [[nodiscard]] QString name() const; - [[nodiscard]] QString version() const; + QString modifiedName() const; + QString originalName() const; + QString name() const; + QString version() const; void setName(QString name) { m_modified_name = name; } void setName(InstanceName& other); @@ -44,12 +44,12 @@ class InstanceTask : public Task, public InstanceName { void setGroup(const QString& group) { m_instGroup = group; } QString group() const { return m_instGroup; } - [[nodiscard]] bool shouldConfirmUpdate() const { return m_confirm_update; } + bool shouldConfirmUpdate() const { return m_confirm_update; } void setConfirmUpdate(bool confirm) { m_confirm_update = confirm; } bool shouldOverride() const { return m_override_existing; } - [[nodiscard]] QString originalInstanceID() const { return m_original_instance_id; }; + QString originalInstanceID() const { return m_original_instance_id; }; protected: void setOverride(bool override, QString instance_id_to_override = {}) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index b1a956b49..26f539e15 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -200,7 +200,7 @@ void LaunchController::login() if ((m_accountToUse->accountType() != AccountType::Offline && m_accountToUse->accountState() == AccountState::Offline) || m_accountToUse->shouldRefresh()) { // Force account refresh on the account used to launch the instance updating the AccountState - // only on first try and if it is not meant to be offline + // only on first try and if it is not meant to be offline m_accountToUse->refresh(); } while (tryagain) { @@ -296,11 +296,15 @@ void LaunchController::login() case AccountState::Working: { // refresh is in progress, we need to wait for it to finish to proceed. ProgressDialog progDialog(m_parentWidget); - if (m_online) { - progDialog.setSkipButton(true, tr("Play Offline")); - } + progDialog.setSkipButton(true, tr("Abort")); + auto task = accountToCheck->currentTask(); progDialog.execWithTask(task.get()); + + // don't retry if aborted + if (task->getState() == Task::State::AbortedByUser) + tryagain = false; + continue; } case AccountState::Expired: { diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 0b1a2b39e..dfe397930 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -51,7 +51,7 @@ namespace MMCZip { // ours -bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet& contained, const FilterFunction& filter) +bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet& contained, const Filter& filter) { QuaZip modZip(from.filePath()); modZip.open(QuaZip::mdUnzip); diff --git a/launcher/MMCZip.h b/launcher/MMCZip.h index fe0c79de2..e23d29d65 100644 --- a/launcher/MMCZip.h +++ b/launcher/MMCZip.h @@ -52,16 +52,16 @@ #if defined(LAUNCHER_APPLICATION) #include "minecraft/mod/Mod.h" #endif +#include "Filter.h" #include "tasks/Task.h" namespace MMCZip { -using FilterFunction = std::function; using FilterFileFunction = std::function; /** * Merge two zip files, using a filter function */ -bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet& contained, const FilterFunction& filter = nullptr); +bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet& contained, const Filter& filter = nullptr); /** * Compress directory, by providing a list of files to compress diff --git a/launcher/MessageLevel.h b/launcher/MessageLevel.h index 4ffd6bfc2..4c840dfc1 100644 --- a/launcher/MessageLevel.h +++ b/launcher/MessageLevel.h @@ -1,7 +1,7 @@ #pragma once +#include #include -#include /** * @brief the MessageLevel Enum diff --git a/launcher/RecursiveFileSystemWatcher.cpp b/launcher/RecursiveFileSystemWatcher.cpp index 5cb3cd0be..b0137fb5c 100644 --- a/launcher/RecursiveFileSystemWatcher.cpp +++ b/launcher/RecursiveFileSystemWatcher.cpp @@ -78,7 +78,7 @@ QStringList RecursiveFileSystemWatcher::scanRecursive(const QDir& directory) } for (const QString& file : directory.entryList(QDir::Files | QDir::Hidden)) { auto relPath = m_root.relativeFilePath(directory.absoluteFilePath(file)); - if (m_matcher->matches(relPath)) { + if (m_matcher(relPath)) { ret.append(relPath); } } diff --git a/launcher/RecursiveFileSystemWatcher.h b/launcher/RecursiveFileSystemWatcher.h index 7f96f5cd0..0a71e64c2 100644 --- a/launcher/RecursiveFileSystemWatcher.h +++ b/launcher/RecursiveFileSystemWatcher.h @@ -2,7 +2,7 @@ #include #include -#include "pathmatcher/IPathMatcher.h" +#include "Filter.h" class RecursiveFileSystemWatcher : public QObject { Q_OBJECT @@ -16,7 +16,7 @@ class RecursiveFileSystemWatcher : public QObject { void setWatchFiles(bool watchFiles); bool watchFiles() const { return m_watchFiles; } - void setMatcher(IPathMatcher::Ptr matcher) { m_matcher = matcher; } + void setMatcher(Filter matcher) { m_matcher = std::move(matcher); } QStringList files() const { return m_files; } @@ -32,7 +32,7 @@ class RecursiveFileSystemWatcher : public QObject { QDir m_root; bool m_watchFiles = false; bool m_isEnabled = false; - IPathMatcher::Ptr m_matcher; + Filter m_matcher; QFileSystemWatcher* m_watcher; diff --git a/launcher/Version.h b/launcher/Version.h index 12e7f0832..4b5ea7119 100644 --- a/launcher/Version.h +++ b/launcher/Version.h @@ -96,8 +96,8 @@ class Version { QString m_fullString; - [[nodiscard]] inline bool isAppendix() const { return m_stringPart.startsWith('+'); } - [[nodiscard]] inline bool isPreRelease() const { return m_stringPart.startsWith('-') && m_stringPart.length() > 1; } + inline bool isAppendix() const { return m_stringPart.startsWith('+'); } + inline bool isPreRelease() const { return m_stringPart.startsWith('-') && m_stringPart.length() > 1; } inline bool operator==(const Section& other) const { diff --git a/launcher/VersionProxyModel.cpp b/launcher/VersionProxyModel.cpp index 165dd4cb7..32048db8e 100644 --- a/launcher/VersionProxyModel.cpp +++ b/launcher/VersionProxyModel.cpp @@ -37,9 +37,9 @@ #include "VersionProxyModel.h" #include #include +#include #include #include -#include "Application.h" class VersionFilterModel : public QSortFilterProxyModel { Q_OBJECT @@ -63,7 +63,7 @@ class VersionFilterModel : public QSortFilterProxyModel { for (auto it = filters.begin(); it != filters.end(); ++it) { auto data = sourceModel()->data(idx, it.key()); auto match = data.toString(); - if (!it.value()->accepts(match)) { + if (!it.value()(match)) { return false; } } @@ -206,11 +206,11 @@ QVariant VersionProxyModel::data(const QModelIndex& index, int role) const if (column == Name && hasRecommended) { auto recommenced = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole); if (recommenced.toBool()) { - return APPLICATION->getThemedIcon("star"); + return QIcon::fromTheme("star"); } else if (hasLatest) { auto latest = sourceModel()->data(parentIndex, BaseVersionList::LatestRole); if (latest.toBool()) { - return APPLICATION->getThemedIcon("bug"); + return QIcon::fromTheme("bug"); } } QPixmap pixmap; @@ -380,9 +380,9 @@ void VersionProxyModel::clearFilters() filterModel->filterChanged(); } -void VersionProxyModel::setFilter(const BaseVersionList::ModelRoles column, Filter* f) +void VersionProxyModel::setFilter(const BaseVersionList::ModelRoles column, Filter f) { - m_filters[column].reset(f); + m_filters[column] = std::move(f); filterModel->filterChanged(); } diff --git a/launcher/VersionProxyModel.h b/launcher/VersionProxyModel.h index 7965af0ad..ddd5d2458 100644 --- a/launcher/VersionProxyModel.h +++ b/launcher/VersionProxyModel.h @@ -10,7 +10,7 @@ class VersionProxyModel : public QAbstractProxyModel { Q_OBJECT public: enum Column { Name, ParentVersion, Branch, Type, CPUArchitecture, Path, Time, JavaName, JavaMajor }; - using FilterMap = QHash>; + using FilterMap = QHash; public: VersionProxyModel(QObject* parent = 0); @@ -28,7 +28,7 @@ class VersionProxyModel : public QAbstractProxyModel { const FilterMap& filters() const; const QString& search() const; - void setFilter(BaseVersionList::ModelRoles column, Filter* filter); + void setFilter(BaseVersionList::ModelRoles column, Filter filter); void setSearch(const QString& search); void clearFilters(); QModelIndex getRecommended() const; diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp index 8a2a482e1..fb80f89da 100644 --- a/launcher/icons/IconList.cpp +++ b/launcher/icons/IconList.cpp @@ -55,7 +55,7 @@ IconList::IconList(const QStringList& builtinPaths, const QString& path, QObject QDir instanceIcons(builtinPath); auto fileInfoList = instanceIcons.entryInfoList(QDir::Files, QDir::Name); for (const auto& fileInfo : fileInfoList) { - builtinNames.insert(fileInfo.baseName()); + builtinNames.insert(fileInfo.completeBaseName()); } } for (const auto& builtinName : builtinNames) { @@ -127,10 +127,11 @@ QStringList IconList::getIconFilePaths() const QString formatName(const QDir& iconsDir, const QFileInfo& iconFile) { if (iconFile.dir() == iconsDir) - return iconFile.baseName(); + return iconFile.completeBaseName(); constexpr auto delimiter = " » "; - QString relativePathWithoutExtension = iconsDir.relativeFilePath(iconFile.dir().path()) + QDir::separator() + iconFile.baseName(); + QString relativePathWithoutExtension = + iconsDir.relativeFilePath(iconFile.dir().path()) + QDir::separator() + iconFile.completeBaseName(); return relativePathWithoutExtension.replace(QDir::separator(), delimiter); } diff --git a/launcher/install_prereqs.cmake.in b/launcher/install_prereqs.cmake.in deleted file mode 100644 index acbce9650..000000000 --- a/launcher/install_prereqs.cmake.in +++ /dev/null @@ -1,26 +0,0 @@ -set(CMAKE_MODULE_PATH "@CMAKE_MODULE_PATH@") -file(GLOB_RECURSE QTPLUGINS "${CMAKE_INSTALL_PREFIX}/@PLUGIN_DEST_DIR@/*@CMAKE_SHARED_LIBRARY_SUFFIX@") -function(gp_resolved_file_type_override resolved_file type_var) - if(resolved_file MATCHES "^/(usr/)?lib/libQt") - set(${type_var} other PARENT_SCOPE) - elseif(resolved_file MATCHES "^/(usr/)?lib(.+)?/libxcb-") - set(${type_var} other PARENT_SCOPE) - elseif(resolved_file MATCHES "^/(usr/)?lib(.+)?/libicu") - set(${type_var} other PARENT_SCOPE) - elseif(resolved_file MATCHES "^/(usr/)?lib(.+)?/libpng") - set(${type_var} other PARENT_SCOPE) - elseif(resolved_file MATCHES "^/(usr/)?lib(.+)?/libproxy") - set(${type_var} other PARENT_SCOPE) - elseif((resolved_file MATCHES "^/(usr/)?lib(.+)?/libstdc\\+\\+") AND (UNIX AND NOT APPLE)) - set(${type_var} other PARENT_SCOPE) - endif() -endfunction() - -set(gp_tool "@CMAKE_GP_TOOL@") -set(gp_cmd_paths ${gp_cmd_paths} - "@CMAKE_GP_CMD_PATHS@" -) - -include(BundleUtilities) -fixup_bundle("@APPS@" "${QTPLUGINS}" "@DIRS@") - diff --git a/launcher/java/JavaInstall.cpp b/launcher/java/JavaInstall.cpp index 8e97e0e14..30cb77e08 100644 --- a/launcher/java/JavaInstall.cpp +++ b/launcher/java/JavaInstall.cpp @@ -21,7 +21,7 @@ #include "BaseVersion.h" #include "StringUtils.h" -bool JavaInstall::operator<(const JavaInstall& rhs) +bool JavaInstall::operator<(const JavaInstall& rhs) const { auto archCompare = StringUtils::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive); if (archCompare != 0) @@ -35,17 +35,17 @@ bool JavaInstall::operator<(const JavaInstall& rhs) return StringUtils::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0; } -bool JavaInstall::operator==(const JavaInstall& rhs) +bool JavaInstall::operator==(const JavaInstall& rhs) const { return arch == rhs.arch && id == rhs.id && path == rhs.path; } -bool JavaInstall::operator>(const JavaInstall& rhs) +bool JavaInstall::operator>(const JavaInstall& rhs) const { return (!operator<(rhs)) && (!operator==(rhs)); } -bool JavaInstall::operator<(BaseVersion& a) +bool JavaInstall::operator<(BaseVersion& a) const { try { return operator<(dynamic_cast(a)); @@ -54,7 +54,7 @@ bool JavaInstall::operator<(BaseVersion& a) } } -bool JavaInstall::operator>(BaseVersion& a) +bool JavaInstall::operator>(BaseVersion& a) const { try { return operator>(dynamic_cast(a)); diff --git a/launcher/java/JavaInstall.h b/launcher/java/JavaInstall.h index 7d8d392fa..d8fd477fd 100644 --- a/launcher/java/JavaInstall.h +++ b/launcher/java/JavaInstall.h @@ -24,17 +24,17 @@ struct JavaInstall : public BaseVersion { JavaInstall() {} JavaInstall(QString id, QString arch, QString path) : id(id), arch(arch), path(path) {} - virtual QString descriptor() override { return id.toString(); } + virtual QString descriptor() const override { return id.toString(); } - virtual QString name() override { return id.toString(); } + virtual QString name() const override { return id.toString(); } virtual QString typeString() const override { return arch; } - virtual bool operator<(BaseVersion& a) override; - virtual bool operator>(BaseVersion& a) override; - bool operator<(const JavaInstall& rhs); - bool operator==(const JavaInstall& rhs); - bool operator>(const JavaInstall& rhs); + virtual bool operator<(BaseVersion& a) const override; + virtual bool operator>(BaseVersion& a) const override; + bool operator<(const JavaInstall& rhs) const; + bool operator==(const JavaInstall& rhs) const; + bool operator>(const JavaInstall& rhs) const; JavaVersion id; QString arch; diff --git a/launcher/java/JavaInstallList.h b/launcher/java/JavaInstallList.h index b77f17b28..c68c2a3be 100644 --- a/launcher/java/JavaInstallList.h +++ b/launcher/java/JavaInstallList.h @@ -35,7 +35,7 @@ class JavaInstallList : public BaseVersionList { public: explicit JavaInstallList(QObject* parent = 0, bool onlyManagedVersions = false); - [[nodiscard]] Task::Ptr getLoadTask() override; + Task::Ptr getLoadTask() override; bool isLoaded() override; const BaseVersion::Ptr at(int i) const override; int count() const override; diff --git a/launcher/java/JavaMetadata.cpp b/launcher/java/JavaMetadata.cpp index 2d68f55c8..d4da95457 100644 --- a/launcher/java/JavaMetadata.cpp +++ b/launcher/java/JavaMetadata.cpp @@ -78,7 +78,7 @@ MetadataPtr parseJavaMeta(const QJsonObject& in) return meta; } -bool Metadata::operator<(const Metadata& rhs) +bool Metadata::operator<(const Metadata& rhs) const { auto id = version; if (id < rhs.version) { @@ -97,17 +97,17 @@ bool Metadata::operator<(const Metadata& rhs) return StringUtils::naturalCompare(m_name, rhs.m_name, Qt::CaseInsensitive) < 0; } -bool Metadata::operator==(const Metadata& rhs) +bool Metadata::operator==(const Metadata& rhs) const { return version == rhs.version && m_name == rhs.m_name; } -bool Metadata::operator>(const Metadata& rhs) +bool Metadata::operator>(const Metadata& rhs) const { return (!operator<(rhs)) && (!operator==(rhs)); } -bool Metadata::operator<(BaseVersion& a) +bool Metadata::operator<(BaseVersion& a) const { try { return operator<(dynamic_cast(a)); @@ -116,7 +116,7 @@ bool Metadata::operator<(BaseVersion& a) } } -bool Metadata::operator>(BaseVersion& a) +bool Metadata::operator>(BaseVersion& a) const { try { return operator>(dynamic_cast(a)); diff --git a/launcher/java/JavaMetadata.h b/launcher/java/JavaMetadata.h index 77a42fd78..2e569ee39 100644 --- a/launcher/java/JavaMetadata.h +++ b/launcher/java/JavaMetadata.h @@ -32,17 +32,17 @@ enum class DownloadType { Manifest, Archive, Unknown }; class Metadata : public BaseVersion { public: - virtual QString descriptor() override { return version.toString(); } + virtual QString descriptor() const override { return version.toString(); } - virtual QString name() override { return m_name; } + virtual QString name() const override { return m_name; } virtual QString typeString() const override { return vendor; } - virtual bool operator<(BaseVersion& a) override; - virtual bool operator>(BaseVersion& a) override; - bool operator<(const Metadata& rhs); - bool operator==(const Metadata& rhs); - bool operator>(const Metadata& rhs); + virtual bool operator<(BaseVersion& a) const override; + virtual bool operator>(BaseVersion& a) const override; + bool operator<(const Metadata& rhs) const; + bool operator==(const Metadata& rhs) const; + bool operator>(const Metadata& rhs) const; QString m_name; QString vendor; diff --git a/launcher/java/JavaVersion.cpp b/launcher/java/JavaVersion.cpp index e9a160ea7..fef573c16 100644 --- a/launcher/java/JavaVersion.cpp +++ b/launcher/java/JavaVersion.cpp @@ -63,7 +63,7 @@ bool JavaVersion::isModular() const return m_parseable && m_major >= 9; } -bool JavaVersion::operator<(const JavaVersion& rhs) +bool JavaVersion::operator<(const JavaVersion& rhs) const { if (m_parseable && rhs.m_parseable) { auto major = m_major; @@ -101,7 +101,7 @@ bool JavaVersion::operator<(const JavaVersion& rhs) return StringUtils::naturalCompare(m_string, rhs.m_string, Qt::CaseSensitive) < 0; } -bool JavaVersion::operator==(const JavaVersion& rhs) +bool JavaVersion::operator==(const JavaVersion& rhs) const { if (m_parseable && rhs.m_parseable) { return m_major == rhs.m_major && m_minor == rhs.m_minor && m_security == rhs.m_security && m_prerelease == rhs.m_prerelease; @@ -109,7 +109,7 @@ bool JavaVersion::operator==(const JavaVersion& rhs) return m_string == rhs.m_string; } -bool JavaVersion::operator>(const JavaVersion& rhs) +bool JavaVersion::operator>(const JavaVersion& rhs) const { return (!operator<(rhs)) && (!operator==(rhs)); } diff --git a/launcher/java/JavaVersion.h b/launcher/java/JavaVersion.h index c070bdeec..143ddd262 100644 --- a/launcher/java/JavaVersion.h +++ b/launcher/java/JavaVersion.h @@ -20,9 +20,9 @@ class JavaVersion { JavaVersion& operator=(const QString& rhs); - bool operator<(const JavaVersion& rhs); - bool operator==(const JavaVersion& rhs); - bool operator>(const JavaVersion& rhs); + bool operator<(const JavaVersion& rhs) const; + bool operator==(const JavaVersion& rhs) const; + bool operator>(const JavaVersion& rhs) const; bool requiresPermGen() const; bool defaultsToUtf8() const; diff --git a/launcher/java/download/ArchiveDownloadTask.h b/launcher/java/download/ArchiveDownloadTask.h index 1db33763a..4cd919543 100644 --- a/launcher/java/download/ArchiveDownloadTask.h +++ b/launcher/java/download/ArchiveDownloadTask.h @@ -28,7 +28,7 @@ class ArchiveDownloadTask : public Task { ArchiveDownloadTask(QUrl url, QString final_path, QString checksumType = "", QString checksumHash = ""); virtual ~ArchiveDownloadTask() = default; - [[nodiscard]] bool canAbort() const override { return true; } + bool canAbort() const override { return true; } void executeTask() override; virtual bool abort() override; diff --git a/launcher/java/download/ManifestDownloadTask.h b/launcher/java/download/ManifestDownloadTask.h index ae9e0d0ed..0f65b343c 100644 --- a/launcher/java/download/ManifestDownloadTask.h +++ b/launcher/java/download/ManifestDownloadTask.h @@ -29,7 +29,7 @@ class ManifestDownloadTask : public Task { ManifestDownloadTask(QUrl url, QString final_path, QString checksumType = "", QString checksumHash = ""); virtual ~ManifestDownloadTask() = default; - [[nodiscard]] bool canAbort() const override { return true; } + bool canAbort() const override { return true; } void executeTask() override; virtual bool abort() override; diff --git a/launcher/meta/Version.cpp b/launcher/meta/Version.cpp index 74e71e91c..ce9a9cc8a 100644 --- a/launcher/meta/Version.cpp +++ b/launcher/meta/Version.cpp @@ -21,11 +21,11 @@ Meta::Version::Version(const QString& uid, const QString& version) : BaseVersion(), m_uid(uid), m_version(version) {} -QString Meta::Version::descriptor() +QString Meta::Version::descriptor() const { return m_version; } -QString Meta::Version::name() +QString Meta::Version::name() const { if (m_data) return m_data->name; @@ -88,7 +88,7 @@ QString Meta::Version::localFilename() const ::Version Meta::Version::toComparableVersion() const { - return { const_cast(this)->descriptor() }; + return { descriptor() }; } void Meta::Version::setType(const QString& type) diff --git a/launcher/meta/Version.h b/launcher/meta/Version.h index 2327879a1..a2bbc6176 100644 --- a/launcher/meta/Version.h +++ b/launcher/meta/Version.h @@ -40,8 +40,8 @@ class Version : public QObject, public BaseVersion, public BaseEntity { explicit Version(const QString& uid, const QString& version); virtual ~Version() = default; - QString descriptor() override; - QString name() override; + QString descriptor() const override; + QString name() const override; QString typeString() const override; QString uid() const { return m_uid; } @@ -60,7 +60,7 @@ class Version : public QObject, public BaseVersion, public BaseEntity { QString localFilename() const override; - [[nodiscard]] ::Version toComparableVersion() const; + ::Version toComparableVersion() const; public: // for usage by format parsers only void setType(const QString& type); diff --git a/launcher/meta/VersionList.h b/launcher/meta/VersionList.h index 21c86b751..18681b8ed 100644 --- a/launcher/meta/VersionList.h +++ b/launcher/meta/VersionList.h @@ -37,7 +37,7 @@ class VersionList : public BaseVersionList, public BaseEntity { enum Roles { UidRole = Qt::UserRole + 100, TimeRole, RequiresRole, VersionPtrRole }; bool isLoaded() override; - [[nodiscard]] Task::Ptr getLoadTask() override; + Task::Ptr getLoadTask() override; const BaseVersion::Ptr at(int i) const override; int count() const override; void sortVersions() override; diff --git a/launcher/minecraft/AssetsUtils.cpp b/launcher/minecraft/AssetsUtils.cpp index 083924dc6..410d1e689 100644 --- a/launcher/minecraft/AssetsUtils.cpp +++ b/launcher/minecraft/AssetsUtils.cpp @@ -298,7 +298,8 @@ QString AssetObject::getLocalPath() QUrl AssetObject::getUrl() { - return BuildConfig.RESOURCE_BASE + getRelPath(); + auto resourceURL = APPLICATION->settings()->get("ResourceURL").toString(); + return resourceURL + getRelPath(); } QString AssetObject::getRelPath() diff --git a/launcher/minecraft/ComponentUpdateTask.cpp b/launcher/minecraft/ComponentUpdateTask.cpp index c77ed248d..56db20205 100644 --- a/launcher/minecraft/ComponentUpdateTask.cpp +++ b/launcher/minecraft/ComponentUpdateTask.cpp @@ -748,7 +748,6 @@ void ComponentUpdateTask::remoteLoadFailed(size_t taskIndex, const QString& msg) d->remoteLoadSuccessful = false; taskSlot.succeeded = false; taskSlot.finished = true; - taskSlot.error = msg; d->remoteTasksInProgress--; checkIfAllFinished(); } @@ -769,7 +768,9 @@ void ComponentUpdateTask::checkIfAllFinished() QStringList allErrorsList; for (auto& item : d->remoteLoadStatusList) { if (!item.succeeded) { - allErrorsList.append(item.error); + const ComponentPtr component = d->m_profile->getComponent(item.PackProfileIndex); + allErrorsList.append(tr("Could not download metadata for %1 %2. Please change the version or try again later.") + .arg(component->getName(), component->m_version)); } } auto allErrors = allErrorsList.join("\n"); diff --git a/launcher/minecraft/ComponentUpdateTask_p.h b/launcher/minecraft/ComponentUpdateTask_p.h index 2fc0b6d9a..8ffb9c71e 100644 --- a/launcher/minecraft/ComponentUpdateTask_p.h +++ b/launcher/minecraft/ComponentUpdateTask_p.h @@ -15,7 +15,6 @@ struct RemoteLoadStatus { size_t PackProfileIndex = 0; bool finished = false; bool succeeded = false; - QString error; Task::Ptr task; }; diff --git a/launcher/minecraft/Library.cpp b/launcher/minecraft/Library.cpp index 0bc462474..026f9c281 100644 --- a/launcher/minecraft/Library.cpp +++ b/launcher/minecraft/Library.cpp @@ -242,13 +242,13 @@ bool Library::isActive(const RuntimeContext& runtimeContext) const if (m_rules.empty()) { result = true; } else { - RuleAction ruleResult = Disallow; + Rule::Action ruleResult = Rule::Disallow; for (auto rule : m_rules) { - RuleAction temp = rule->apply(this, runtimeContext); - if (temp != Defer) + Rule::Action temp = rule.apply(runtimeContext); + if (temp != Rule::Defer) ruleResult = temp; } - result = result && (ruleResult == Allow); + result = result && (ruleResult == Rule::Allow); } if (isNative()) { result = result && !getCompatibleNative(runtimeContext).isNull(); diff --git a/launcher/minecraft/Library.h b/launcher/minecraft/Library.h index d3019e814..d827554aa 100644 --- a/launcher/minecraft/Library.h +++ b/launcher/minecraft/Library.h @@ -129,7 +129,7 @@ class Library { void setHint(const QString& hint) { m_hint = hint; } /// Set the load rules - void setRules(QList> rules) { m_rules = rules; } + void setRules(QList rules) { m_rules = rules; } /// Returns true if the library should be loaded (or extracted, in case of natives) bool isActive(const RuntimeContext& runtimeContext) const; @@ -203,7 +203,7 @@ class Library { bool applyRules = false; /// rules associated with the library - QList> m_rules; + QList m_rules; /// MOJANG: container with Mojang style download info MojangLibraryDownloadInfo::Ptr m_mojangDownloads; diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 403aeb4bb..efac92094 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -53,7 +53,6 @@ #include "FileSystem.h" #include "MMCTime.h" #include "java/JavaVersion.h" -#include "pathmatcher/MultiMatcher.h" #include "launch/LaunchTask.h" #include "launch/TaskStepWrapper.h" diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index d4e0a8626..a5fa2537b 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -103,7 +103,7 @@ class MinecraftInstance : public BaseInstance { QString getLocalLibraryPath() const; /** Returns whether the instance, with its version, has support for demo mode. */ - [[nodiscard]] bool supportsDemo() const; + bool supportsDemo() const; void updateRuntimeContext() override; diff --git a/launcher/minecraft/MojangVersionFormat.cpp b/launcher/minecraft/MojangVersionFormat.cpp index d17a3a21f..42730b12d 100644 --- a/launcher/minecraft/MojangVersionFormat.cpp +++ b/launcher/minecraft/MojangVersionFormat.cpp @@ -319,7 +319,11 @@ LibraryPtr MojangVersionFormat::libraryFromJson(ProblemContainer& problems, cons } if (libObj.contains("rules")) { out->applyRules = true; - out->m_rules = rulesFromJsonV4(libObj); + + QJsonArray rulesArray = requireArray(libObj.value("rules")); + for (auto rule : rulesArray) { + out->m_rules.append(Rule::fromJson(requireObject(rule))); + } } if (libObj.contains("downloads")) { out->m_mojangDownloads = libDownloadInfoFromJson(libObj); @@ -355,7 +359,7 @@ QJsonObject MojangVersionFormat::libraryToJson(Library* library) if (!library->m_rules.isEmpty()) { QJsonArray allRules; for (auto& rule : library->m_rules) { - QJsonObject ruleObj = rule->toJson(); + QJsonObject ruleObj = rule.toJson(); allRules.append(ruleObj); } libRoot.insert("rules", allRules); diff --git a/launcher/minecraft/Rule.cpp b/launcher/minecraft/Rule.cpp index d80aab84d..606776e8a 100644 --- a/launcher/minecraft/Rule.cpp +++ b/launcher/minecraft/Rule.cpp @@ -2,6 +2,7 @@ /* * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2025 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 @@ -38,72 +39,54 @@ #include "Rule.h" -RuleAction RuleAction_fromString(QString name) +Rule Rule::fromJson(const QJsonObject& object) { - if (name == "allow") - return Allow; - if (name == "disallow") - return Disallow; - return Defer; -} + Rule result; -QList> rulesFromJsonV4(const QJsonObject& objectWithRules) -{ - QList> rules; - auto rulesVal = objectWithRules.value("rules"); - if (!rulesVal.isArray()) - return rules; + if (object["action"] == "allow") + result.m_action = Allow; + else if (object["action"] == "disallow") + result.m_action = Disallow; - QJsonArray ruleList = rulesVal.toArray(); - for (auto ruleVal : ruleList) { - std::shared_ptr rule; - if (!ruleVal.isObject()) - continue; - auto ruleObj = ruleVal.toObject(); - auto actionVal = ruleObj.value("action"); - if (!actionVal.isString()) - continue; - auto action = RuleAction_fromString(actionVal.toString()); - if (action == Defer) - continue; - - auto osVal = ruleObj.value("os"); - if (!osVal.isObject()) { - // add a new implicit action rule - rules.append(ImplicitRule::create(action)); - continue; - } - - auto osObj = osVal.toObject(); - auto osNameVal = osObj.value("name"); - if (!osNameVal.isString()) - continue; - QString osName = osNameVal.toString(); - QString versionRegex = osObj.value("version").toString(); - // add a new OS rule - rules.append(OsRule::create(action, osName, versionRegex)); - } - return rules; -} - -QJsonObject ImplicitRule::toJson() -{ - QJsonObject ruleObj; - ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); - return ruleObj; -} - -QJsonObject OsRule::toJson() -{ - QJsonObject ruleObj; - ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); - QJsonObject osObj; - { - osObj.insert("name", m_system); - if (!m_version_regexp.isEmpty()) { - osObj.insert("version", m_version_regexp); + if (auto os = object["os"]; os.isObject()) { + if (auto name = os["name"].toString(); !name.isNull()) { + result.m_os = OS{ + name, + os["version"].toString(), + }; } } - ruleObj.insert("os", osObj); - return ruleObj; + + return result; +} + +QJsonObject Rule::toJson() +{ + QJsonObject result; + + if (m_action == Allow) + result["action"] = "allow"; + else if (m_action == Disallow) + result["action"] = "disallow"; + + if (m_os.has_value()) { + QJsonObject os; + + os["name"] = m_os->name; + + if (!m_os->version.isEmpty()) + os["version"] = m_os->version; + + result["os"] = os; + } + + return result; +} + +Rule::Action Rule::apply(const RuntimeContext& runtimeContext) +{ + if (m_os.has_value() && !runtimeContext.classifierMatches(m_os->name)) + return Defer; + + return m_action; } diff --git a/launcher/minecraft/Rule.h b/launcher/minecraft/Rule.h index c6cdbc43f..b0b689fd7 100644 --- a/launcher/minecraft/Rule.h +++ b/launcher/minecraft/Rule.h @@ -2,6 +2,7 @@ /* * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2025 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 @@ -38,59 +39,27 @@ #include #include #include -#include #include "RuntimeContext.h" class Library; -class Rule; - -enum RuleAction { Allow, Disallow, Defer }; - -QList> rulesFromJsonV4(const QJsonObject& objectWithRules); class Rule { - protected: - RuleAction m_result; - virtual bool applies(const Library* parent, const RuntimeContext& runtimeContext) = 0; - public: - Rule(RuleAction result) : m_result(result) {} - virtual ~Rule() {} - virtual QJsonObject toJson() = 0; - RuleAction apply(const Library* parent, const RuntimeContext& runtimeContext) - { - if (applies(parent, runtimeContext)) - return m_result; - else - return Defer; - } -}; + enum Action { Allow, Disallow, Defer }; + + static Rule fromJson(const QJsonObject& json); + QJsonObject toJson(); + + Action apply(const RuntimeContext& runtimeContext); -class OsRule : public Rule { private: - // the OS - QString m_system; - // the OS version regexp - QString m_version_regexp; + struct OS { + QString name; + // FIXME: unsupported + // retained to avoid information being lost from files + QString version; + }; - protected: - virtual bool applies(const Library*, const RuntimeContext& runtimeContext) { return runtimeContext.classifierMatches(m_system); } - OsRule(RuleAction result, QString system, QString version_regexp) : Rule(result), m_system(system), m_version_regexp(version_regexp) {} - - public: - virtual QJsonObject toJson(); - static std::shared_ptr create(RuleAction result, QString system, QString version_regexp) - { - return std::shared_ptr(new OsRule(result, system, version_regexp)); - } -}; - -class ImplicitRule : public Rule { - protected: - virtual bool applies(const Library*, [[maybe_unused]] const RuntimeContext& runtimeContext) { return true; } - ImplicitRule(RuleAction result) : Rule(result) {} - - public: - virtual QJsonObject toJson(); - static std::shared_ptr create(RuleAction result) { return std::shared_ptr(new ImplicitRule(result)); } + Action m_action = Defer; + std::optional m_os; }; diff --git a/launcher/minecraft/ShortcutUtils.cpp b/launcher/minecraft/ShortcutUtils.cpp index 0336a9512..ec4ebb31a 100644 --- a/launcher/minecraft/ShortcutUtils.cpp +++ b/launcher/minecraft/ShortcutUtils.cpp @@ -123,7 +123,7 @@ bool createInstanceShortcut(const Shortcut& shortcut, const QString& filePath) // part of fix for weird bug involving the window icon being replaced // dunno why it happens, but parent 2-line fix seems to be enough, so w/e - auto appIcon = APPLICATION->getThemedIcon("logo"); + auto appIcon = APPLICATION->logo(); QFile iconFile(iconPath); if (!iconFile.open(QFile::WriteOnly)) { diff --git a/launcher/minecraft/World.h b/launcher/minecraft/World.h index 34d418e79..cca931826 100644 --- a/launcher/minecraft/World.h +++ b/launcher/minecraft/World.h @@ -59,7 +59,7 @@ class World { // WEAK compare operator - used for replacing worlds bool operator==(const World& other) const; - [[nodiscard]] auto isSymLink() const -> bool { return m_containerFile.isSymLink(); } + auto isSymLink() const -> bool { return m_containerFile.isSymLink(); } /** * @brief Take a instance path, checks if the file pointed to by the resource is a symlink or under a symlink in that instance @@ -68,9 +68,9 @@ class World { * @return true * @return false */ - [[nodiscard]] bool isSymLinkUnder(const QString& instPath) const; + bool isSymLinkUnder(const QString& instPath) const; - [[nodiscard]] bool isMoreThanOneHardLink() const; + bool isMoreThanOneHardLink() const; QString canonicalFilePath() const { return m_containerFile.canonicalFilePath(); } diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index 6a821ba60..059feabde 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -36,6 +36,7 @@ #include "WorldList.h" #include +#include #include #include #include @@ -301,50 +302,31 @@ QStringList WorldList::mimeTypes() const return types; } -class WorldMimeData : public QMimeData { - Q_OBJECT - - public: - WorldMimeData(QList worlds) { m_worlds = worlds; } - QStringList formats() const { return QMimeData::formats() << "text/uri-list"; } - - protected: - QVariant retrieveData(const QString& mimetype, QMetaType type) const - { - QList urls; - for (auto& world : m_worlds) { - if (!world.isValid() || !world.isOnFS()) - continue; - QString worldPath = world.container().absoluteFilePath(); - qDebug() << worldPath; - urls.append(QUrl::fromLocalFile(worldPath)); - } - const_cast(this)->setUrls(urls); - return QMimeData::retrieveData(mimetype, type); - } - - private: - QList m_worlds; -}; - QMimeData* WorldList::mimeData(const QModelIndexList& indexes) const { - if (indexes.size() == 0) - return new QMimeData(); + QList urls; - QList worlds_; for (auto idx : indexes) { if (idx.column() != 0) continue; + int row = idx.row(); if (row < 0 || row >= this->m_worlds.size()) continue; - worlds_.append(this->m_worlds[row]); + + const World& world = m_worlds[row]; + + if (!world.isValid() || !world.isOnFS()) + continue; + + QString worldPath = world.container().absoluteFilePath(); + qDebug() << worldPath; + urls.append(QUrl::fromLocalFile(worldPath)); } - if (!worlds_.size()) { - return new QMimeData(); - } - return new WorldMimeData(worlds_); + + auto result = new QMimeData(); + result->setUrls(urls); + return result; } Qt::ItemFlags WorldList::flags(const QModelIndex& index) const @@ -453,5 +435,3 @@ void WorldList::loadWorldsAsync() }); } } - -#include "WorldList.moc" diff --git a/launcher/minecraft/auth/AccountData.cpp b/launcher/minecraft/auth/AccountData.cpp index 161fd968c..29a65e275 100644 --- a/launcher/minecraft/auth/AccountData.cpp +++ b/launcher/minecraft/auth/AccountData.cpp @@ -180,6 +180,7 @@ MinecraftProfile profileFromJSONV3(const QJsonObject& parent, const char* tokenN } out.skin.id = idV.toString(); out.skin.url = urlV.toString(); + out.skin.url.replace("http://textures.minecraft.net", "https://textures.minecraft.net"); out.skin.variant = variantV.toString(); // data for skin is optional @@ -216,6 +217,7 @@ MinecraftProfile profileFromJSONV3(const QJsonObject& parent, const char* tokenN Cape cape; cape.id = idV.toString(); cape.url = urlV.toString(); + cape.url.replace("http://textures.minecraft.net", "https://textures.minecraft.net"); cape.alias = aliasV.toString(); // data for cape is optional. diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index 86e9cc511..ca052c378 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -181,8 +181,10 @@ void MinecraftAccount::authFailed(QString reason) data.validity_ = Validity::None; emit changed(); } break; + case AccountTaskState::STATE_WORKING: { + data.accountState = AccountState::Unchecked; + } break; case AccountTaskState::STATE_CREATED: - case AccountTaskState::STATE_WORKING: case AccountTaskState::STATE_SUCCEEDED: { // Not reachable here, as they are not failures. } diff --git a/launcher/minecraft/auth/MinecraftAccount.h b/launcher/minecraft/auth/MinecraftAccount.h index f6fcfada2..a82d3f134 100644 --- a/launcher/minecraft/auth/MinecraftAccount.h +++ b/launcher/minecraft/auth/MinecraftAccount.h @@ -114,7 +114,7 @@ class MinecraftAccount : public QObject, public Usable { bool isActive() const; - [[nodiscard]] AccountType accountType() const noexcept { return data.type; } + AccountType accountType() const noexcept { return data.type; } bool ownsMinecraft() const { return data.type != AccountType::Offline && data.minecraftEntitlement.ownsMinecraft; } diff --git a/launcher/minecraft/auth/Parsers.cpp b/launcher/minecraft/auth/Parsers.cpp index de1ffda86..ba77d3e31 100644 --- a/launcher/minecraft/auth/Parsers.cpp +++ b/launcher/minecraft/auth/Parsers.cpp @@ -207,6 +207,7 @@ bool parseMinecraftProfile(QByteArray& data, MinecraftProfile& output) if (!getString(capeObj.value("url"), capeOut.url)) { continue; } + capeOut.url.replace("http://textures.minecraft.net", "https://textures.minecraft.net"); if (!getString(capeObj.value("alias"), capeOut.alias)) { continue; } @@ -358,6 +359,7 @@ bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output) qWarning() << "Skin url is not a string"; return false; } + skinOut.url.replace("http://textures.minecraft.net", "https://textures.minecraft.net"); auto maybeMeta = skin.find("metadata"); if (maybeMeta != skin.end() && maybeMeta->isObject()) { @@ -371,6 +373,7 @@ bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output) qWarning() << "Cape url is not a string"; return false; } + capeOut.url.replace("http://textures.minecraft.net", "https://textures.minecraft.net"); // we don't know the cape ID as it is not returned from the session server // so just fake it - changing capes is probably locked anyway :( diff --git a/launcher/minecraft/mod/DataPack.h b/launcher/minecraft/mod/DataPack.h index 4b56cb9d8..2943dd4bc 100644 --- a/launcher/minecraft/mod/DataPack.h +++ b/launcher/minecraft/mod/DataPack.h @@ -40,15 +40,15 @@ class DataPack : public Resource { DataPack(QFileInfo file_info) : Resource(file_info) {} /** Gets the numerical ID of the pack format. */ - [[nodiscard]] int packFormat() const { return m_pack_format; } + int packFormat() const { return m_pack_format; } /** Gets, respectively, the lower and upper versions supported by the set pack format. */ - [[nodiscard]] virtual std::pair compatibleVersions() const; + virtual std::pair compatibleVersions() const; /** Gets the description of the data pack. */ - [[nodiscard]] QString description() const { return m_description; } + QString description() const { return m_description; } /** Gets the image of the data pack, converted to a QPixmap for drawing, and scaled to size. */ - [[nodiscard]] QPixmap image(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const; + QPixmap image(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const; /** Thread-safe. */ void setPackFormat(int new_format_id); @@ -64,8 +64,6 @@ class DataPack : public Resource { [[nodiscard]] int compare(Resource const& other, SortType type) const override; [[nodiscard]] bool applyFilter(QRegularExpression filter) const override; - virtual QString directory() { return "/data"; } - protected: mutable QMutex m_data_lock; diff --git a/launcher/minecraft/mod/DataPackFolderModel.cpp b/launcher/minecraft/mod/DataPackFolderModel.cpp index 45cf1271f..1a2badd77 100644 --- a/launcher/minecraft/mod/DataPackFolderModel.cpp +++ b/launcher/minecraft/mod/DataPackFolderModel.cpp @@ -42,7 +42,6 @@ #include #include -#include "Application.h" #include "Version.h" #include "minecraft/mod/tasks/LocalDataPackParseTask.h" @@ -92,7 +91,7 @@ QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const } case Qt::DecorationRole: { if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink())) - return APPLICATION->getThemedIcon("status-yellow"); + return QIcon::fromTheme("status-yellow"); if (column == ImageColumn) { return at(row).image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding); } diff --git a/launcher/minecraft/mod/DataPackFolderModel.h b/launcher/minecraft/mod/DataPackFolderModel.h index 026ae8b76..2b90e1a2a 100644 --- a/launcher/minecraft/mod/DataPackFolderModel.h +++ b/launcher/minecraft/mod/DataPackFolderModel.h @@ -50,10 +50,10 @@ class DataPackFolderModel : public ResourceFolderModel { virtual QString id() const override { return "datapacks"; } - [[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + 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; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + int columnCount(const QModelIndex& parent) const override; [[nodiscard]] Resource* createResource(const QFileInfo& file) override; [[nodiscard]] Task* createParseTask(Resource&) override; diff --git a/launcher/minecraft/mod/MetadataHandler.h b/launcher/minecraft/mod/MetadataHandler.h index 0b8cb124d..5f12348ae 100644 --- a/launcher/minecraft/mod/MetadataHandler.h +++ b/launcher/minecraft/mod/MetadataHandler.h @@ -19,27 +19,16 @@ #pragma once -#include - #include "modplatform/packwiz/Packwiz.h" -// launcher/minecraft/mod/Mod.h -class Mod; - namespace Metadata { using ModStruct = Packwiz::V1::Mod; -using ModSide = Packwiz::V1::Side; -inline auto create(const QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> ModStruct +inline ModStruct create(const QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) { return Packwiz::V1::createModFormat(index_dir, mod_pack, mod_version); } -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)); -} - inline void update(const QDir& index_dir, ModStruct& mod) { Packwiz::V1::updateModIndex(index_dir, mod); @@ -50,24 +39,14 @@ inline void remove(const QDir& index_dir, QString mod_slug) Packwiz::V1::deleteModIndex(index_dir, mod_slug); } -inline void remove(const QDir& index_dir, QVariant& mod_id) -{ - Packwiz::V1::deleteModIndex(index_dir, mod_id); -} - -inline auto get(const QDir& index_dir, QString mod_slug) -> ModStruct +inline ModStruct get(const QDir& index_dir, QString mod_slug) { return Packwiz::V1::getIndexForMod(index_dir, std::move(mod_slug)); } -inline auto get(const QDir& index_dir, QVariant& mod_id) -> ModStruct +inline ModStruct get(const QDir& index_dir, QVariant& mod_id) { return Packwiz::V1::getIndexForMod(index_dir, mod_id); } -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 99fc39ce0..e9ca2e682 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -179,9 +179,9 @@ auto Mod::loaders() const -> QString auto Mod::side() const -> QString { if (metadata()) - return Metadata::modSideToString(metadata()->side); + return ModPlatform::SideUtils::toString(metadata()->side); - return Metadata::modSideToString(Metadata::ModSide::UniversalSide); + return ModPlatform::SideUtils::toString(ModPlatform::Side::UniversalSide); } auto Mod::mcVersions() const -> QString diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index 553af92f3..eceb8c256 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -76,7 +76,7 @@ class Mod : public Resource { /** Get the intneral path to the mod's icon file*/ QString iconPath() const { return m_local_details.icon_file; } /** Gets the icon of the mod, converted to a QPixmap for drawing, and scaled to size. */ - [[nodiscard]] QPixmap icon(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const; + QPixmap icon(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const; /** Thread-safe. */ QPixmap setIcon(QImage new_image) const; diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index b613e0af1..45ec76f19 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -49,8 +49,6 @@ #include #include -#include "Application.h" - #include "minecraft/mod/tasks/LocalModParseTask.h" ModFolderModel::ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) @@ -132,7 +130,7 @@ QVariant ModFolderModel::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())) - return APPLICATION->getThemedIcon("status-yellow"); + return QIcon::fromTheme("status-yellow"); if (column == ImageColumn) { return at(row).icon({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding); } diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index 16d8c2a89..87bfd4345 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -83,23 +83,23 @@ class Resource : public QObject { void setFile(QFileInfo file_info); void parseFile(); - [[nodiscard]] auto fileinfo() const -> QFileInfo { return m_file_info; } - [[nodiscard]] auto dateTimeChanged() const -> QDateTime { return m_changed_date_time; } - [[nodiscard]] auto internal_id() const -> QString { return m_internal_id; } - [[nodiscard]] auto type() const -> ResourceType { return m_type; } - [[nodiscard]] bool enabled() const { return m_enabled; } - [[nodiscard]] auto getOriginalFileName() const -> QString; - [[nodiscard]] QString sizeStr() const { return m_size_str; } - [[nodiscard]] qint64 sizeInfo() const { return m_size_info; } + auto fileinfo() const -> QFileInfo { return m_file_info; } + auto dateTimeChanged() const -> QDateTime { return m_changed_date_time; } + auto internal_id() const -> QString { return m_internal_id; } + auto type() const -> ResourceType { return m_type; } + bool enabled() const { return m_enabled; } + auto getOriginalFileName() const -> QString; + QString sizeStr() const { return m_size_str; } + qint64 sizeInfo() const { return m_size_info; } - [[nodiscard]] virtual auto name() const -> QString; - [[nodiscard]] virtual bool valid() const { return m_type != ResourceType::UNKNOWN; } + virtual auto name() const -> QString; + 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; + auto status() const -> ResourceStatus { return m_status; }; + auto metadata() -> std::shared_ptr { return m_metadata; } + auto metadata() const -> std::shared_ptr { return m_metadata; } + auto provider() const -> QString; + virtual auto homepage() const -> QString; void setStatus(ResourceStatus status) { m_status = status; } void setMetadata(std::shared_ptr&& metadata); @@ -110,12 +110,12 @@ class Resource : public QObject { * = 0: 'this' is equal to 'other' * < 0: 'this' comes before 'other' */ - [[nodiscard]] virtual int compare(Resource const& other, SortType type = SortType::NAME) const; + virtual int compare(Resource const& other, SortType type = SortType::NAME) const; /** Returns whether the given filter should filter out 'this' (false), * or if such filter includes the Resource (true). */ - [[nodiscard]] virtual bool applyFilter(QRegularExpression filter) const; + virtual bool applyFilter(QRegularExpression filter) const; /** Changes the enabled property, according to 'action'. * @@ -123,10 +123,10 @@ class Resource : public QObject { */ bool enable(EnableAction action); - [[nodiscard]] auto shouldResolve() const -> bool { return !m_is_resolving && !m_is_resolved; } - [[nodiscard]] auto isResolving() const -> bool { return m_is_resolving; } - [[nodiscard]] auto isResolved() const -> bool { return m_is_resolved; } - [[nodiscard]] auto resolutionTicket() const -> int { return m_resolution_ticket; } + auto shouldResolve() const -> bool { return !m_is_resolving && !m_is_resolved; } + auto isResolving() const -> bool { return m_is_resolving; } + auto isResolved() const -> bool { return m_is_resolved; } + auto resolutionTicket() const -> int { return m_resolution_ticket; } void setResolving(bool resolving, int resolutionTicket) { @@ -139,7 +139,7 @@ class Resource : public QObject { // Delete the metadata only. auto destroyMetadata(const QDir& index_dir) -> void; - [[nodiscard]] auto isSymLink() const -> bool { return m_file_info.isSymLink(); } + auto isSymLink() const -> bool { return m_file_info.isSymLink(); } /** * @brief Take a instance path, checks if the file pointed to by the resource is a symlink or under a symlink in that instance @@ -148,11 +148,11 @@ class Resource : public QObject { * @return true * @return false */ - [[nodiscard]] bool isSymLinkUnder(const QString& instPath) const; + bool isSymLinkUnder(const QString& instPath) const; - [[nodiscard]] bool isMoreThanOneHardLink() const; + bool isMoreThanOneHardLink() const; - [[nodiscard]] auto mod_id() const -> QString { return m_mod_id; } + auto mod_id() const -> QString { return m_mod_id; } void setModId(const QString& modId) { m_mod_id = modId; } protected: diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index fbcd1b0bc..2b4f9eb14 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -503,7 +503,7 @@ QVariant ResourceFolderModel::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())) - return APPLICATION->getThemedIcon("status-yellow"); + return QIcon::fromTheme("status-yellow"); return {}; } @@ -709,8 +709,7 @@ SortType ResourceFolderModel::columnToSortKey(size_t column) const } /* Standard Proxy Model for createFilterProxyModel */ -[[nodiscard]] bool ResourceFolderModel::ProxyModel::filterAcceptsRow(int source_row, - [[maybe_unused]] const QModelIndex& source_parent) const +bool ResourceFolderModel::ProxyModel::filterAcceptsRow(int source_row, [[maybe_unused]] const QModelIndex& source_parent) const { auto* model = qobject_cast(sourceModel()); if (!model) @@ -721,7 +720,7 @@ SortType ResourceFolderModel::columnToSortKey(size_t column) const return resource.applyFilter(filterRegularExpression()); } -[[nodiscard]] bool ResourceFolderModel::ProxyModel::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const +bool ResourceFolderModel::ProxyModel::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const { auto* model = qobject_cast(sourceModel()); if (!model || !source_left.isValid() || !source_right.isValid() || source_left.column() != source_right.column()) { diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index 759861e14..0dea3c7f1 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -21,11 +21,11 @@ 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) \ + T& at(int index) \ { \ return *static_cast(m_resources[index].get()); \ } \ - [[nodiscard]] const T& at(int index) const \ + const T& at(int index) const \ { \ return *static_cast(m_resources.at(index).get()); \ } \ @@ -115,24 +115,24 @@ class ResourceFolderModel : public QAbstractListModel { /** Creates a new parse task, if needed, for 'res' and start it.*/ virtual void resolveResource(Resource::Ptr res); - [[nodiscard]] qsizetype size() const { return m_resources.size(); } + qsizetype size() const { return m_resources.size(); } [[nodiscard]] bool empty() const { return size() == 0; } - [[nodiscard]] Resource& at(int index) { return *m_resources[index].get(); } - [[nodiscard]] const Resource& at(int index) const { return *m_resources.at(index).get(); } + Resource& at(int index) { return *m_resources[index].get(); } + 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); + Resource::Ptr find(QString id); - [[nodiscard]] QDir const& dir() const { return m_dir; } + QDir const& dir() const { return m_dir; } /** Checks whether there's any parse tasks being done. * * Since they can be quite expensive, and are usually done in a separate thread, if we were to destroy the model while having * such tasks would introduce an undefined behavior, most likely resulting in a crash. */ - [[nodiscard]] bool hasPendingParseTasks() const; + bool hasPendingParseTasks() const; /* Qt behavior */ @@ -141,22 +141,22 @@ class ResourceFolderModel : public QAbstractListModel { 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()); } - [[nodiscard]] int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NUM_COLUMNS; } + int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast(size()); } + int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NUM_COLUMNS; } - [[nodiscard]] Qt::DropActions supportedDropActions() const override; + Qt::DropActions supportedDropActions() const override; /// flags, mostly to support drag&drop - [[nodiscard]] Qt::ItemFlags flags(const QModelIndex& index) const override; - [[nodiscard]] QStringList mimeTypes() const override; - bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + QStringList mimeTypes() const override; + [[nodiscard]] bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) override; [[nodiscard]] bool validateIndex(const QModelIndex& index) const; - [[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; - [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; void setupHeaderAction(QAction* act, int column); void saveColumns(QTreeView* tree); @@ -169,16 +169,16 @@ class ResourceFolderModel : public QAbstractListModel { */ QSortFilterProxyModel* createFilterProxyModel(QObject* parent = nullptr); - [[nodiscard]] SortType columnToSortKey(size_t column) const; - [[nodiscard]] QList columnResizeModes() const { return m_column_resize_modes; } + SortType columnToSortKey(size_t column) const; + QList columnResizeModes() const { return m_column_resize_modes; } class ProxyModel : public QSortFilterProxyModel { public: explicit ProxyModel(QObject* parent = nullptr) : QSortFilterProxyModel(parent) {} protected: - [[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override; - [[nodiscard]] bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override; + bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override; + bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override; }; QString instDirPath() const; diff --git a/launcher/minecraft/mod/ResourcePack.h b/launcher/minecraft/mod/ResourcePack.h index bd161df87..9345e9c27 100644 --- a/launcher/minecraft/mod/ResourcePack.h +++ b/launcher/minecraft/mod/ResourcePack.h @@ -22,7 +22,5 @@ class ResourcePack : public DataPack { ResourcePack(QFileInfo file_info) : DataPack(file_info) {} /** Gets, respectively, the lower and upper versions supported by the set pack format. */ - [[nodiscard]] std::pair compatibleVersions() const override; - - QString directory() override { return "/assets"; } + std::pair compatibleVersions() const override; }; diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index d9f27a043..df572484b 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -41,7 +41,6 @@ #include #include -#include "Application.h" #include "Version.h" #include "minecraft/mod/tasks/LocalDataPackParseTask.h" @@ -96,7 +95,7 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const } case Qt::DecorationRole: { if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink())) - return APPLICATION->getThemedIcon("status-yellow"); + return QIcon::fromTheme("status-yellow"); if (column == ImageColumn) { return at(row).image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding); } diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.h b/launcher/minecraft/mod/ResourcePackFolderModel.h index 9dbf41b85..b552c324e 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.h +++ b/launcher/minecraft/mod/ResourcePackFolderModel.h @@ -13,10 +13,10 @@ class ResourcePackFolderModel : public ResourceFolderModel { QString id() const override { return "resourcepacks"; } - [[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + 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; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + int columnCount(const QModelIndex& parent) const override; [[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new ResourcePack(file); } [[nodiscard]] Task* createParseTask(Resource&) override; diff --git a/launcher/minecraft/mod/ShaderPack.h b/launcher/minecraft/mod/ShaderPack.h index ec0f9404e..9275ebed8 100644 --- a/launcher/minecraft/mod/ShaderPack.h +++ b/launcher/minecraft/mod/ShaderPack.h @@ -45,7 +45,7 @@ class ShaderPack : public Resource { public: using Ptr = shared_qobject_ptr; - [[nodiscard]] ShaderPackFormat packFormat() const { return m_pack_format; } + ShaderPackFormat packFormat() const { return m_pack_format; } ShaderPack(QObject* parent = nullptr) : Resource(parent) {} ShaderPack(QFileInfo file_info) : Resource(file_info) {} diff --git a/launcher/minecraft/mod/TexturePack.h b/launcher/minecraft/mod/TexturePack.h index bf4b5b6b4..1327e2f61 100644 --- a/launcher/minecraft/mod/TexturePack.h +++ b/launcher/minecraft/mod/TexturePack.h @@ -37,10 +37,10 @@ class TexturePack : public Resource { TexturePack(QFileInfo file_info) : Resource(file_info) {} /** Gets the description of the texture pack. */ - [[nodiscard]] QString description() const { return m_description; } + QString description() const { return m_description; } /** Gets the image of the texture pack, converted to a QPixmap for drawing, and scaled to size. */ - [[nodiscard]] QPixmap image(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const; + QPixmap image(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const; /** Thread-safe. */ void setDescription(QString new_description); diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index 8b89b45cd..57c5f8ee9 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -33,10 +33,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include - -#include "Application.h" - #include "TexturePackFolderModel.h" #include "minecraft/mod/tasks/LocalTexturePackParseTask.h" @@ -98,7 +94,7 @@ 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())) - return APPLICATION->getThemedIcon("status-yellow"); + return QIcon::fromTheme("status-yellow"); if (column == ImageColumn) { return at(row).image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding); } diff --git a/launcher/minecraft/mod/TexturePackFolderModel.h b/launcher/minecraft/mod/TexturePackFolderModel.h index 7a9264e8f..37f78d8d7 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.h +++ b/launcher/minecraft/mod/TexturePackFolderModel.h @@ -50,10 +50,10 @@ class TexturePackFolderModel : public ResourceFolderModel { virtual QString id() const override { return "texturepacks"; } - [[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + 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; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + int columnCount(const QModelIndex& parent) const override; [[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new TexturePack(file); } [[nodiscard]] Task* createParseTask(Resource&) override; diff --git a/launcher/minecraft/mod/WorldSave.h b/launcher/minecraft/mod/WorldSave.h index 5985fc8ad..702a3edf6 100644 --- a/launcher/minecraft/mod/WorldSave.h +++ b/launcher/minecraft/mod/WorldSave.h @@ -38,9 +38,9 @@ class WorldSave : public Resource { WorldSave(QFileInfo file_info) : Resource(file_info) {} /** Gets the format of the save. */ - [[nodiscard]] WorldSaveFormat saveFormat() const { return m_save_format; } + WorldSaveFormat saveFormat() const { return m_save_format; } /** Gets the name of the save dir (first found in multi mode). */ - [[nodiscard]] QString saveDirName() const { return m_save_dir_name; } + QString saveDirName() const { return m_save_dir_name; } /** Thread-safe. */ void setSaveFormat(WorldSaveFormat new_save_format); diff --git a/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp index 21e7c5a2a..75815cb44 100644 --- a/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp +++ b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp @@ -27,12 +27,8 @@ #include "minecraft/mod/MetadataHandler.h" #include "modplatform/ModIndex.h" #include "modplatform/ResourceAPI.h" -#include "modplatform/flame/FlameAPI.h" -#include "modplatform/modrinth/ModrinthAPI.h" #include "tasks/SequentialTask.h" #include "ui/pages/modplatform/ModModel.h" -#include "ui/pages/modplatform/flame/FlameResourceModels.h" -#include "ui/pages/modplatform/modrinth/ModrinthResourceModels.h" static Version mcVersion(BaseInstance* inst) { @@ -55,14 +51,7 @@ static bool checkDependencies(std::shared_ptr> selected) - : SequentialTask(tr("Get dependencies")) - , m_selected(selected) - , m_flame_provider{ ModPlatform::ResourceProvider::FLAME, std::make_shared(*instance), - std::make_shared() } - , m_modrinth_provider{ ModPlatform::ResourceProvider::MODRINTH, std::make_shared(*instance), - std::make_shared() } - , m_version(mcVersion(instance)) - , m_loaderType(mcLoaders(instance)) + : SequentialTask(tr("Get dependencies")), m_selected(selected), m_version(mcVersion(instance)), m_loaderType(mcLoaders(instance)) { for (auto mod : folder->allMods()) { m_mods_file_names << mod->fileinfo().fileName(); @@ -144,9 +133,9 @@ QList GetModDependenciesTask::getDependenciesForVersion Task::Ptr GetModDependenciesTask::getProjectInfoTask(std::shared_ptr pDep) { - auto provider = pDep->pack->provider == m_flame_provider.name ? m_flame_provider : m_modrinth_provider; + auto provider = pDep->pack->provider; auto responseInfo = std::make_shared(); - auto info = provider.api->getProject(pDep->pack->addonId.toString(), responseInfo); + auto info = getAPI(provider)->getProject(pDep->pack->addonId.toString(), responseInfo); connect(info.get(), &NetJob::succeeded, [this, responseInfo, provider, pDep] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*responseInfo, &parse_error); @@ -158,9 +147,10 @@ Task::Ptr GetModDependenciesTask::getProjectInfoTask(std::shared_ptrloadIndexedPack(*pDep->pack, obj); + auto obj = provider == ModPlatform::ResourceProvider::FLAME ? Json::requireObject(Json::requireObject(doc), "data") + : Json::requireObject(doc); + + getAPI(provider)->loadIndexedPack(*pDep->pack, obj); } catch (const JSONValidationError& e) { removePack(pDep->pack->addonId); qDebug() << doc; @@ -181,7 +171,8 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen pDep->pack->provider = providerName; m_pack_dependencies.append(pDep); - auto provider = providerName == m_flame_provider.name ? m_flame_provider : m_modrinth_provider; + + auto provider = providerName; auto tasks = makeShared( QString("DependencyInfo: %1").arg(dep.addonId.toString().isEmpty() ? dep.version : dep.addonId.toString())); @@ -191,46 +182,30 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen } ResourceAPI::DependencySearchArgs args = { dep, m_version, m_loaderType }; - ResourceAPI::DependencySearchCallbacks callbacks; + ResourceAPI::Callback callbacks; callbacks.on_fail = [](QString reason, int) { qCritical() << tr("A network error occurred. Could not load project dependencies:%1").arg(reason); }; - callbacks.on_succeed = [dep, provider, pDep, level, this](auto& doc, [[maybe_unused]] auto& pack) { - try { - QJsonArray arr; - if (dep.version.length() != 0 && doc.isObject()) { - arr.append(doc.object()); - } else { - arr = doc.isObject() ? Json::ensureArray(doc.object(), "data") : doc.array(); - } - pDep->version = provider.mod->loadDependencyVersions(dep, arr); - if (!pDep->version.addonId.isValid()) { - if (m_loaderType & ModPlatform::Quilt) { // falback for quilt - auto overide = ModPlatform::getOverrideDeps(); - auto over = std::find_if(overide.cbegin(), overide.cend(), [dep, provider](const auto& o) { - return o.provider == provider.name && dep.addonId == o.quilt; - }); - if (over != overide.cend()) { - removePack(dep.addonId); - addTask(prepareDependencyTask({ over->fabric, dep.type }, provider.name, level)); - return; - } + callbacks.on_succeed = [dep, provider, pDep, level, this](auto& pack) { + pDep->version = pack; + if (!pDep->version.addonId.isValid()) { + if (m_loaderType & ModPlatform::Quilt) { // falback for quilt + auto overide = ModPlatform::getOverrideDeps(); + auto over = std::find_if(overide.cbegin(), overide.cend(), + [dep, provider](auto o) { return o.provider == provider && dep.addonId == o.quilt; }); + if (over != overide.cend()) { + removePack(dep.addonId); + addTask(prepareDependencyTask({ over->fabric, dep.type }, provider, level)); + return; } - removePack(dep.addonId); - qWarning() << "Error while reading mod version empty "; - qDebug() << doc; - return; } - pDep->version.is_currently_selected = true; - pDep->pack->versions = { pDep->version }; - pDep->pack->versionsLoaded = true; - - } catch (const JSONValidationError& e) { removePack(dep.addonId); - qDebug() << doc; - qWarning() << "Error while reading mod version: " << e.cause(); return; } + pDep->version.is_currently_selected = true; + pDep->pack->versions = { pDep->version }; + pDep->pack->versionsLoaded = true; + if (level == 0) { removePack(dep.addonId); qWarning() << "Dependency cycle exceeded"; @@ -238,10 +213,10 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen } if (dep.addonId.toString().isEmpty() && !pDep->version.addonId.toString().isEmpty()) { pDep->pack->addonId = pDep->version.addonId; - auto dep_ = getOverride({ pDep->version.addonId, pDep->dependency.type }, provider.name); + auto dep_ = getOverride({ pDep->version.addonId, pDep->dependency.type }, provider); if (dep_.addonId != pDep->version.addonId) { removePack(pDep->version.addonId); - addTask(prepareDependencyTask(dep_, provider.name, level)); + addTask(prepareDependencyTask(dep_, provider, level)); } else { addTask(getProjectInfoTask(pDep)); } @@ -250,12 +225,12 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen removePack(pDep->version.addonId); return; } - for (auto dep_ : getDependenciesForVersion(pDep->version, provider.name)) { - addTask(prepareDependencyTask(dep_, provider.name, level - 1)); + for (auto dep_ : getDependenciesForVersion(pDep->version, provider)) { + addTask(prepareDependencyTask(dep_, provider, level - 1)); } }; - auto version = provider.api->getDependencyVersion(std::move(args), std::move(callbacks)); + auto version = getAPI(provider)->getDependencyVersion(std::move(args), std::move(callbacks)); tasks->addTask(version); return tasks; } diff --git a/launcher/minecraft/mod/tasks/GetModDependenciesTask.h b/launcher/minecraft/mod/tasks/GetModDependenciesTask.h index a02ffb4d5..3530d6cc0 100644 --- a/launcher/minecraft/mod/tasks/GetModDependenciesTask.h +++ b/launcher/minecraft/mod/tasks/GetModDependenciesTask.h @@ -28,6 +28,8 @@ #include "minecraft/mod/ModFolderModel.h" #include "modplatform/ModIndex.h" #include "modplatform/ResourceAPI.h" +#include "modplatform/flame/FlameAPI.h" +#include "modplatform/modrinth/ModrinthAPI.h" #include "tasks/SequentialTask.h" #include "tasks/Task.h" #include "ui/pages/modplatform/ModModel.h" @@ -54,17 +56,20 @@ class GetModDependenciesTask : public SequentialTask { QStringList required_by; }; - struct Provider { - ModPlatform::ResourceProvider name; - std::shared_ptr mod; - std::shared_ptr api; - }; - explicit GetModDependenciesTask(BaseInstance* instance, ModFolderModel* folder, QList> selected); auto getDependecies() const -> QList> { return m_pack_dependencies; } QHash getExtraInfo(); + private: + inline ResourceAPI* getAPI(ModPlatform::ResourceProvider provider) + { + if (provider == ModPlatform::ResourceProvider::FLAME) + return &m_flameAPI; + else + return &m_modrinthAPI; + } + protected slots: Task::Ptr prepareDependencyTask(const ModPlatform::Dependency&, ModPlatform::ResourceProvider, int); QList getDependenciesForVersion(const ModPlatform::IndexedVersion&, @@ -82,9 +87,10 @@ class GetModDependenciesTask : public SequentialTask { QList> m_mods; QList> m_selected; QStringList m_mods_file_names; - Provider m_flame_provider; - Provider m_modrinth_provider; Version m_version; ModPlatform::ModLoaderTypes m_loaderType; + + ModrinthAPI m_modrinthAPI; + FlameAPI m_flameAPI; }; diff --git a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp index c37a25c21..c63e0c65f 100644 --- a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp @@ -73,11 +73,6 @@ bool processFolder(DataPack* pack, ProcessingLevel level) return mcmeta_invalid(); // mcmeta file isn't a valid file } - QFileInfo data_dir_info(FS::PathCombine(pack->fileinfo().filePath(), pack->directory())); - if (!data_dir_info.exists() || !data_dir_info.isDir()) { - return false; // data dir does not exists or isn't valid - } - if (level == ProcessingLevel::BasicInfoOnly) { return true; // only need basic info already checked } @@ -141,11 +136,6 @@ bool processZIP(DataPack* pack, ProcessingLevel level) return mcmeta_invalid(); // could not set pack.mcmeta as current file. } - QuaZipDir zipDir(&zip); - if (!zipDir.exists(pack->directory())) { - return false; // data dir does not exists at zip root - } - if (level == ProcessingLevel::BasicInfoOnly) { zip.close(); return true; // only need basic info already checked diff --git a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h index 57591a0f4..6bdf55880 100644 --- a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h @@ -61,7 +61,7 @@ class LocalDataPackParseTask : public Task { void executeTask() override; - [[nodiscard]] int token() const { return m_token; } + int token() const { return m_token; } private: int m_token; diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.h b/launcher/minecraft/mod/tasks/LocalModParseTask.h index 7ce5a84d2..cbe009376 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.h @@ -39,13 +39,13 @@ class LocalModParseTask : public Task { using ResultPtr = std::shared_ptr; ResultPtr result() const { return m_result; } - [[nodiscard]] bool canAbort() const override { return true; } + bool canAbort() const override { return true; } bool abort() override; LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile); void executeTask() override; - [[nodiscard]] int token() const { return m_token; } + int token() const { return m_token; } private: int m_token; diff --git a/launcher/minecraft/mod/tasks/LocalResourceParse.cpp b/launcher/minecraft/mod/tasks/LocalResourceParse.cpp index e309b2105..39e8a321b 100644 --- a/launcher/minecraft/mod/tasks/LocalResourceParse.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourceParse.cpp @@ -28,50 +28,38 @@ #include "LocalShaderPackParseTask.h" #include "LocalTexturePackParseTask.h" #include "LocalWorldSaveParseTask.h" - -static const QMap s_packed_type_names = { { PackedResourceType::ResourcePack, QObject::tr("resource pack") }, - { PackedResourceType::TexturePack, QObject::tr("texture pack") }, - { PackedResourceType::DataPack, QObject::tr("data pack") }, - { PackedResourceType::ShaderPack, QObject::tr("shader pack") }, - { PackedResourceType::WorldSave, QObject::tr("world save") }, - { PackedResourceType::Mod, QObject::tr("mod") }, - { PackedResourceType::UNKNOWN, QObject::tr("unknown") } }; +#include "modplatform/ResourceType.h" namespace ResourceUtils { -PackedResourceType identify(QFileInfo file) +ModPlatform::ResourceType identify(QFileInfo file) { if (file.exists() && file.isFile()) { if (ModUtils::validate(file)) { // mods can contain resource and data packs so they must be tested first qDebug() << file.fileName() << "is a mod"; - return PackedResourceType::Mod; + return ModPlatform::ResourceType::Mod; } else if (DataPackUtils::validateResourcePack(file)) { qDebug() << file.fileName() << "is a resource pack"; - return PackedResourceType::ResourcePack; + return ModPlatform::ResourceType::ResourcePack; } else if (TexturePackUtils::validate(file)) { qDebug() << file.fileName() << "is a pre 1.6 texture pack"; - return PackedResourceType::TexturePack; + return ModPlatform::ResourceType::TexturePack; } else if (DataPackUtils::validate(file)) { qDebug() << file.fileName() << "is a data pack"; - return PackedResourceType::DataPack; + return ModPlatform::ResourceType::DataPack; } else if (WorldSaveUtils::validate(file)) { qDebug() << file.fileName() << "is a world save"; - return PackedResourceType::WorldSave; + return ModPlatform::ResourceType::World; } else if (ShaderPackUtils::validate(file)) { qDebug() << file.fileName() << "is a shader pack"; - return PackedResourceType::ShaderPack; + return ModPlatform::ResourceType::ShaderPack; } else { qDebug() << "Can't Identify" << file.fileName(); } } else { qDebug() << "Can't find" << file.absolutePath(); } - return PackedResourceType::UNKNOWN; -} - -QString getPackedTypeName(PackedResourceType type) -{ - return s_packed_type_names.constFind(type).value(); + return ModPlatform::ResourceType::Unknown; } } // namespace ResourceUtils diff --git a/launcher/minecraft/mod/tasks/LocalResourceParse.h b/launcher/minecraft/mod/tasks/LocalResourceParse.h index 7385d24b0..dc3aeb025 100644 --- a/launcher/minecraft/mod/tasks/LocalResourceParse.h +++ b/launcher/minecraft/mod/tasks/LocalResourceParse.h @@ -21,17 +21,9 @@ #pragma once -#include - -#include #include -#include +#include "modplatform/ResourceType.h" -enum class PackedResourceType { DataPack, ResourcePack, TexturePack, ShaderPack, WorldSave, Mod, UNKNOWN }; namespace ResourceUtils { -static const std::set ValidResourceTypes = { PackedResourceType::DataPack, PackedResourceType::ResourcePack, - PackedResourceType::TexturePack, PackedResourceType::ShaderPack, - PackedResourceType::WorldSave, PackedResourceType::Mod }; -PackedResourceType identify(QFileInfo file); -QString getPackedTypeName(PackedResourceType type); +ModPlatform::ResourceType identify(QFileInfo file); } // namespace ResourceUtils diff --git a/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.h b/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.h index 6be2183cd..55d77f33b 100644 --- a/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.h @@ -46,12 +46,12 @@ class LocalShaderPackParseTask : public Task { public: LocalShaderPackParseTask(int token, ShaderPack& sp); - [[nodiscard]] bool canAbort() const override { return true; } + bool canAbort() const override { return true; } bool abort() override; void executeTask() override; - [[nodiscard]] int token() const { return m_token; } + int token() const { return m_token; } private: int m_token; diff --git a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h index 1341590f2..b9cc1ea54 100644 --- a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h @@ -50,12 +50,12 @@ class LocalTexturePackParseTask : public Task { public: LocalTexturePackParseTask(int token, TexturePack& rp); - [[nodiscard]] bool canAbort() const override { return true; } + bool canAbort() const override { return true; } bool abort() override; void executeTask() override; - [[nodiscard]] int token() const { return m_token; } + int token() const { return m_token; } private: int m_token; diff --git a/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.h b/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.h index 12f677b02..42faf51c5 100644 --- a/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.h @@ -46,12 +46,12 @@ class LocalWorldSaveParseTask : public Task { public: LocalWorldSaveParseTask(int token, WorldSave& save); - [[nodiscard]] bool canAbort() const override { return true; } + bool canAbort() const override { return true; } bool abort() override; void executeTask() override; - [[nodiscard]] int token() const { return m_token; } + int token() const { return m_token; } private: int m_token; diff --git a/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.h b/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.h index 9950345ef..7c872c13d 100644 --- a/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.h @@ -60,7 +60,7 @@ class ResourceFolderLoadTask : public Task { bool clean_orphan, std::function create_function); - [[nodiscard]] bool canAbort() const override { return true; } + bool canAbort() const override { return true; } bool abort() override { m_aborted.store(true); diff --git a/launcher/minecraft/update/AssetUpdateTask.cpp b/launcher/minecraft/update/AssetUpdateTask.cpp index acdddc833..f4a4022e9 100644 --- a/launcher/minecraft/update/AssetUpdateTask.cpp +++ b/launcher/minecraft/update/AssetUpdateTask.cpp @@ -1,5 +1,6 @@ #include "AssetUpdateTask.h" +#include "BuildConfig.h" #include "launch/LaunchStep.h" #include "minecraft/AssetsUtils.h" #include "minecraft/MinecraftInstance.h" @@ -71,7 +72,12 @@ void AssetUpdateTask::assetIndexFinished() auto job = index.getDownloadJob(); if (job) { - setStatus(tr("Getting the assets files from Mojang...")); + QString resourceURL = APPLICATION->settings()->get("ResourceURL").toString(); + QString source = tr("Mojang"); + if (resourceURL != BuildConfig.DEFAULT_RESOURCE_BASE) { + source = QUrl(resourceURL).host(); + } + setStatus(tr("Getting the assets files from %1...").arg(source)); downloadJob = job; connect(downloadJob.get(), &NetJob::succeeded, this, &AssetUpdateTask::emitSucceeded); connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetsFailed); diff --git a/launcher/modplatform/CheckUpdateTask.h b/launcher/modplatform/CheckUpdateTask.h index 1ee820a63..c5beff26c 100644 --- a/launcher/modplatform/CheckUpdateTask.h +++ b/launcher/modplatform/CheckUpdateTask.h @@ -1,9 +1,7 @@ #pragma once -#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; @@ -19,9 +17,9 @@ class CheckUpdateTask : public Task { std::shared_ptr resourceModel) : Task() , m_resources(resources) - , m_game_versions(mcVersions) - , m_loaders_list(std::move(loadersList)) - , m_resource_model(std::move(resourceModel)) + , m_gameVersions(mcVersions) + , m_loadersList(std::move(loadersList)) + , m_resourceModel(std::move(resourceModel)) {} struct Update { @@ -71,9 +69,9 @@ class CheckUpdateTask : public Task { protected: QList& m_resources; - std::list& m_game_versions; - QList m_loaders_list; - std::shared_ptr m_resource_model; + std::list& m_gameVersions; + QList m_loadersList; + std::shared_ptr m_resourceModel; std::vector m_updates; QList> m_deps; diff --git a/launcher/modplatform/ModIndex.cpp b/launcher/modplatform/ModIndex.cpp index 380ff660f..b13087158 100644 --- a/launcher/modplatform/ModIndex.cpp +++ b/launcher/modplatform/ModIndex.cpp @@ -31,7 +31,7 @@ static const QMap s_indexed_version_ty { "alpha", IndexedVersionType::VersionType::Alpha } }; -static const QList loaderList = { NeoForge, Forge, Cauldron, LiteLoader, Quilt, Fabric }; +static const QList loaderList = { NeoForge, Forge, Cauldron, LiteLoader, Quilt, Fabric, Babric, BTA, LegacyFabric, Ornithe, Rift }; QList modLoaderTypesToList(ModLoaderTypes flags) { @@ -122,13 +122,23 @@ auto getModLoaderAsString(ModLoaderType type) -> const QString case Cauldron: return "cauldron"; case LiteLoader: - return "liteloader"; + return "liteloader"; case Fabric: return "fabric"; case Quilt: return "quilt"; case DataPack: return "datapack"; + case Babric: + return "babric"; + case BTA: + return "bta-babric"; + case LegacyFabric: + return "legacy-fabric"; + case Ornithe: + return "ornithe"; + case Rift: + return "rift"; default: break; } @@ -149,7 +159,42 @@ auto getModLoaderFromString(QString type) -> ModLoaderType return Fabric; if (type == "quilt") return Quilt; + if (type == "babric") + return Babric; + if (type == "bta-babric") + return BTA; + if (type == "legacy-fabric") + return LegacyFabric; + if (type == "ornithe") + return Ornithe; + if (type == "rift") + return Rift; return {}; } +QString SideUtils::toString(Side side) +{ + switch (side) { + case Side::ClientSide: + return "client"; + case Side::ServerSide: + return "server"; + case Side::UniversalSide: + return "both"; + case Side::NoSide: + break; + } + return {}; +} + +Side SideUtils::fromString(QString side) +{ + if (side == "client") + return Side::ClientSide; + if (side == "server") + return Side::ServerSide; + if (side == "both") + return Side::UniversalSide; + return Side::UniversalSide; +} } // namespace ModPlatform diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index ad2503ea7..6cff8c622 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -36,17 +36,27 @@ enum ModLoaderType { LiteLoader = 1 << 3, Fabric = 1 << 4, Quilt = 1 << 5, - DataPack = 1 << 6 + DataPack = 1 << 6, + Babric = 1 << 7, + BTA = 1 << 8, + LegacyFabric = 1 << 9, + Ornithe = 1 << 10, + Rift = 1 << 11 }; Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType) QList modLoaderTypesToList(ModLoaderTypes flags); enum class ResourceProvider { MODRINTH, FLAME }; -enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK, MODPACK, DATA_PACK }; - enum class DependencyType { REQUIRED, OPTIONAL, INCOMPATIBLE, EMBEDDED, TOOL, INCLUDE, UNKNOWN }; +enum class Side { NoSide = 0, ClientSide = 1 << 0, ServerSide = 1 << 1, UniversalSide = ClientSide | ServerSide }; + +namespace SideUtils { +QString toString(Side side); +Side fromString(QString side); +} // namespace SideUtils + namespace ProviderCapabilities { const char* name(ResourceProvider); QString readableName(ResourceProvider); @@ -114,10 +124,27 @@ struct IndexedVersion { bool is_preferred = true; QString changelog; QList dependencies; - QString side; // this is for flame API + Side side; // this is for flame API // For internal use, not provided by APIs bool is_currently_selected = false; + + QString getVersionDisplayString() const + { + auto release_type = version_type.isValid() ? QString(" [%1]").arg(version_type.toString()) : ""; + auto versionStr = !version.contains(version_number) ? version_number : ""; + QString gameVersion = ""; + for (auto v : mcVersion) { + if (version.contains(v)) { + gameVersion = ""; + break; + } + if (gameVersion.isEmpty()) { + gameVersion = QObject::tr(" for %1").arg(v); + } + } + return QString("%1%2 — %3%4").arg(version, gameVersion, versionStr, release_type); + } }; struct ExtraPackData { @@ -145,7 +172,7 @@ struct IndexedPack { QString logoName; QString logoUrl; QString websiteUrl; - QString side; + Side side; bool versionsLoaded = false; QList versions; @@ -155,14 +182,14 @@ struct IndexedPack { ExtraPackData extraData; // For internal use, not provided by APIs - [[nodiscard]] bool isVersionSelected(int index) const + bool isVersionSelected(int index) const { if (!versionsLoaded) return false; return versions.at(index).is_currently_selected; } - [[nodiscard]] bool isAnyVersionSelected() const + bool isAnyVersionSelected() const { if (!versionsLoaded) return false; diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.cpp b/launcher/modplatform/ResourceAPI.cpp similarity index 51% rename from launcher/modplatform/helpers/NetworkResourceAPI.cpp rename to launcher/modplatform/ResourceAPI.cpp index d0e1bb912..448efbc24 100644 --- a/launcher/modplatform/helpers/NetworkResourceAPI.cpp +++ b/launcher/modplatform/ResourceAPI.cpp @@ -1,18 +1,14 @@ -// SPDX-FileCopyrightText: 2023 flowln -// -// SPDX-License-Identifier: GPL-3.0-only - -#include "NetworkResourceAPI.h" -#include +#include "modplatform/ResourceAPI.h" #include "Application.h" +#include "Json.h" #include "net/NetJob.h" #include "modplatform/ModIndex.h" #include "net/ApiDownload.h" -Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&& callbacks) const +Task::Ptr ResourceAPI::searchProjects(SearchArgs&& args, Callback>&& callbacks) const { auto search_url_optional = getSearchURL(args); if (!search_url_optional.has_value()) { @@ -40,7 +36,23 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks& return; } - callbacks.on_succeed(doc); + QList newList; + auto packs = documentToArray(doc); + + for (auto packRaw : packs) { + auto packObj = packRaw.toObject(); + + ModPlatform::IndexedPack::Ptr pack = std::make_shared(); + try { + loadIndexedPack(*pack, packObj); + newList << pack; + } catch (const JSONValidationError& e) { + qWarning() << "Error while loading resource from " << debugName() << ": " << e.cause(); + continue; + } + } + + callbacks.on_succeed(newList); }); // Capture a weak_ptr instead of a shared_ptr to avoid circular dependency issues. @@ -60,29 +72,7 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks& return netJob; } -Task::Ptr NetworkResourceAPI::getProjectInfo(ProjectInfoArgs&& args, ProjectInfoCallbacks&& callbacks) const -{ - auto response = std::make_shared(); - auto job = getProject(args.pack.addonId.toString(), response); - - QObject::connect(job.get(), &NetJob::succeeded, [response, callbacks, args] { - 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(); - qWarning() << *response; - return; - } - - callbacks.on_succeed(doc, args.pack); - }); - QObject::connect(job.get(), &NetJob::failed, [callbacks](QString reason) { callbacks.on_fail(reason); }); - QObject::connect(job.get(), &NetJob::aborted, [callbacks] { callbacks.on_abort(); }); - return job; -} - -Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, VersionSearchCallbacks&& callbacks) const +Task::Ptr ResourceAPI::getProjectVersions(VersionSearchArgs&& args, Callback>&& callbacks) const { auto versions_url_optional = getVersionsURL(args); if (!versions_url_optional.has_value()) @@ -95,7 +85,7 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response)); - QObject::connect(netJob.get(), &NetJob::succeeded, [response, callbacks, args] { + QObject::connect(netJob.get(), &NetJob::succeeded, [this, response, callbacks, args] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { @@ -105,7 +95,32 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi return; } - callbacks.on_succeed(doc, args.pack); + QVector unsortedVersions; + try { + auto arr = doc.isObject() ? Json::ensureArray(doc.object(), "data") : doc.array(); + + for (auto versionIter : arr) { + auto obj = versionIter.toObject(); + + auto file = loadIndexedPackVersion(obj, args.resourceType); + if (!file.addonId.isValid()) + file.addonId = args.pack.addonId; + + if (file.fileId.isValid() && !file.downloadUrl.isEmpty()) // Heuristic to check if the returned value is valid + unsortedVersions.append(file); + } + + auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool { + // dates are in RFC 3339 format + return a.date > b.date; + }; + std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate); + } catch (const JSONValidationError& e) { + qDebug() << doc; + qWarning() << "Error while reading " << debugName() << " resource version: " << e.cause(); + } + + callbacks.on_succeed(unsortedVersions); }); // Capture a weak_ptr instead of a shared_ptr to avoid circular dependency issues. @@ -120,11 +135,147 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi } callbacks.on_fail(reason, network_error_code); }); + QObject::connect(netJob.get(), &NetJob::aborted, [callbacks] { callbacks.on_abort(); }); return netJob; } -Task::Ptr NetworkResourceAPI::getProject(QString addonId, std::shared_ptr response) const +Task::Ptr ResourceAPI::getProjectInfo(ProjectInfoArgs&& args, Callback&& callbacks) const +{ + auto response = std::make_shared(); + auto job = getProject(args.pack.addonId.toString(), response); + + QObject::connect(job.get(), &NetJob::succeeded, [this, response, callbacks, args] { + auto pack = args.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(); + qWarning() << *response; + return; + } + try { + auto obj = Json::requireObject(doc); + if (obj.contains("data")) + obj = Json::requireObject(obj, "data"); + loadIndexedPack(pack, obj); + loadExtraPackInfo(pack, obj); + } catch (const JSONValidationError& e) { + qDebug() << doc; + qWarning() << "Error while reading " << debugName() << " resource info: " << e.cause(); + } + callbacks.on_succeed(pack); + }); + // 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 = job.toWeakRef(); + QObject::connect(job.get(), &NetJob::failed, [weak, callbacks](const QString& reason) { + int network_error_code = -1; + if (auto job = weak.lock()) { + if (auto netJob = qSharedPointerDynamicCast(job)) { + 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(job.get(), &NetJob::aborted, [callbacks] { callbacks.on_abort(); }); + return job; +} + +Task::Ptr ResourceAPI::getDependencyVersion(DependencySearchArgs&& args, Callback&& callbacks) const +{ + auto versions_url_optional = getDependencyURL(args); + if (!versions_url_optional.has_value()) + return nullptr; + + auto versions_url = versions_url_optional.value(); + + auto netJob = makeShared(QString("%1::Dependency").arg(args.dependency.addonId.toString()), APPLICATION->network()); + auto response = std::make_shared(); + + netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response)); + + QObject::connect(netJob.get(), &NetJob::succeeded, [this, response, callbacks, args] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response for getting versions at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + return; + } + + QJsonArray arr; + if (args.dependency.version.length() != 0 && doc.isObject()) { + arr.append(doc.object()); + } else { + arr = doc.isObject() ? Json::ensureArray(doc.object(), "data") : doc.array(); + } + + QVector versions; + for (auto versionIter : arr) { + auto obj = versionIter.toObject(); + + auto file = loadIndexedPackVersion(obj, ModPlatform::ResourceType::Mod); + if (!file.addonId.isValid()) + file.addonId = args.dependency.addonId; + + if (file.fileId.isValid() && + (!file.loaders || args.loader & file.loaders)) // Heuristic to check if the returned value is valid + versions.append(file); + } + + auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool { + // dates are in RFC 3339 format + return a.date > b.date; + }; + std::sort(versions.begin(), versions.end(), orderSortPredicate); + auto bestMatch = versions.size() != 0 ? versions.front() : ModPlatform::IndexedVersion(); + callbacks.on_succeed(bestMatch); + }); + + // 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; +} + +QString ResourceAPI::getGameVersionsString(std::list mcVersions) const +{ + QString s; + for (auto& ver : mcVersions) { + s += QString("\"%1\",").arg(mapMCVersionToModrinth(ver)); + } + s.remove(s.length() - 1, 1); // remove last comma + return s; +} + +QString ResourceAPI::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; +} + +Task::Ptr ResourceAPI::getProject(QString addonId, std::shared_ptr response) const { auto project_url_optional = getInfoURL(addonId); if (!project_url_optional.has_value()) @@ -138,44 +289,3 @@ Task::Ptr NetworkResourceAPI::getProject(QString addonId, std::shared_ptr(QString("%1::Dependency").arg(args.dependency.addonId.toString()), APPLICATION->network()); - auto response = std::make_shared(); - - netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response)); - - 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) { - qWarning() << "Error while parsing JSON response for getting versions at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qWarning() << *response; - return; - } - - callbacks.on_succeed(doc, args.dependency); - }); - - // 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/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h index 62a1ff199..211a6e477 100644 --- a/launcher/modplatform/ResourceAPI.h +++ b/launcher/modplatform/ResourceAPI.h @@ -4,7 +4,7 @@ /* * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu - * Copyright (c) 2023 Trial97 + * Copyright (c) 2023-2025 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 @@ -48,6 +48,7 @@ #include "../Version.h" #include "modplatform/ModIndex.h" +#include "modplatform/ResourceType.h" #include "tasks/Task.h" /* Simple class with a common interface for interacting with APIs */ @@ -66,6 +67,13 @@ class ResourceAPI { QString readable_name; }; + template + struct Callback { + std::function on_succeed; + std::function on_fail; + std::function on_abort; + }; + struct SearchArgs { ModPlatform::ResourceType type{}; int offset = 0; @@ -74,45 +82,21 @@ class ResourceAPI { std::optional sorting; std::optional loaders; std::optional> versions; - std::optional side; + std::optional side; std::optional categoryIds; bool openSource; }; - struct SearchCallbacks { - std::function on_succeed; - std::function on_fail; - std::function on_abort; - }; struct VersionSearchArgs { ModPlatform::IndexedPack pack; std::optional> mcVersions; std::optional loaders; - - VersionSearchArgs(VersionSearchArgs const&) = default; - void operator=(VersionSearchArgs other) - { - pack = other.pack; - mcVersions = other.mcVersions; - loaders = other.loaders; - } - }; - struct VersionSearchCallbacks { - std::function on_succeed; - std::function on_fail; + ModPlatform::ResourceType resourceType; }; struct ProjectInfoArgs { ModPlatform::IndexedPack pack; - - ProjectInfoArgs(ProjectInfoArgs const&) = default; - void operator=(ProjectInfoArgs other) { pack = other.pack; } - }; - struct ProjectInfoCallbacks { - std::function on_succeed; - std::function on_fail; - std::function on_abort; }; struct DependencySearchArgs { @@ -121,73 +105,52 @@ class ResourceAPI { ModPlatform::ModLoaderTypes loader; }; - struct DependencySearchCallbacks { - std::function on_succeed; - std::function on_fail; - }; - public: /** Gets a list of available sorting methods for this API. */ - [[nodiscard]] virtual auto getSortingMethods() const -> QList = 0; + virtual auto getSortingMethods() const -> QList = 0; public slots: - [[nodiscard]] virtual Task::Ptr searchProjects(SearchArgs&&, SearchCallbacks&&) const - { - qWarning() << "TODO: ResourceAPI::searchProjects"; - return nullptr; - } - [[nodiscard]] virtual Task::Ptr getProject([[maybe_unused]] QString addonId, - [[maybe_unused]] std::shared_ptr response) const - { - qWarning() << "TODO: ResourceAPI::getProject"; - return nullptr; - } - [[nodiscard]] virtual Task::Ptr getProjects([[maybe_unused]] QStringList addonIds, - [[maybe_unused]] std::shared_ptr response) const - { - qWarning() << "TODO: ResourceAPI::getProjects"; - return nullptr; - } + virtual Task::Ptr searchProjects(SearchArgs&&, Callback>&&) const; - [[nodiscard]] virtual Task::Ptr getProjectInfo(ProjectInfoArgs&&, ProjectInfoCallbacks&&) const - { - qWarning() << "TODO: ResourceAPI::getProjectInfo"; - return nullptr; - } - [[nodiscard]] virtual Task::Ptr getProjectVersions(VersionSearchArgs&&, VersionSearchCallbacks&&) const - { - qWarning() << "TODO: ResourceAPI::getProjectVersions"; - return nullptr; - } + virtual Task::Ptr getProject(QString addonId, std::shared_ptr response) const; + virtual Task::Ptr getProjects(QStringList addonIds, std::shared_ptr response) const = 0; - [[nodiscard]] virtual Task::Ptr getDependencyVersion(DependencySearchArgs&&, DependencySearchCallbacks&&) const - { - qWarning() << "TODO"; - return nullptr; - } + virtual Task::Ptr getProjectInfo(ProjectInfoArgs&&, Callback&&) const; + Task::Ptr getProjectVersions(VersionSearchArgs&& args, Callback>&& callbacks) const; + virtual Task::Ptr getDependencyVersion(DependencySearchArgs&&, Callback&&) const; protected: - [[nodiscard]] inline QString debugName() const { return "External resource API"; } + inline QString debugName() const { return "External resource API"; } - [[nodiscard]] inline QString mapMCVersionToModrinth(Version v) const - { - static const QString preString = " Pre-Release "; - auto verStr = v.toString(); + QString mapMCVersionToModrinth(Version v) const; - if (verStr.contains(preString)) { - verStr.replace(preString, "-pre"); - } - verStr.replace(" ", "-"); - return verStr; - } + QString getGameVersionsString(std::list mcVersions) const; - [[nodiscard]] inline QString getGameVersionsString(std::list mcVersions) const - { - QString s; - for (auto& ver : mcVersions) { - s += QString("\"%1\",").arg(mapMCVersionToModrinth(ver)); - } - s.remove(s.length() - 1, 1); // remove last comma - return s; - } + public: + virtual auto getSearchURL(SearchArgs const& args) const -> std::optional = 0; + virtual auto getInfoURL(QString const& id) const -> std::optional = 0; + virtual auto getVersionsURL(VersionSearchArgs const& args) const -> std::optional = 0; + virtual auto getDependencyURL(DependencySearchArgs const& args) const -> std::optional = 0; + + /** Functions to load data into a pack. + * + * Those are needed for the same reason as documentToArray, and NEED to be re-implemented in the same way. + */ + + virtual void loadIndexedPack(ModPlatform::IndexedPack&, QJsonObject&) const = 0; + virtual ModPlatform::IndexedVersion loadIndexedPackVersion(QJsonObject& obj, ModPlatform::ResourceType) const = 0; + + /** Converts a JSON document to a common array format. + * + * This is needed so that different providers, with different JSON structures, can be parsed + * uniformally. You NEED to re-implement this if you intend on using the default callbacks. + */ + virtual QJsonArray documentToArray(QJsonDocument& obj) const = 0; + + /** Functions to load data into a pack. + * + * Those are needed for the same reason as documentToArray, and NEED to be re-implemented in the same way. + */ + + virtual void loadExtraPackInfo(ModPlatform::IndexedPack&, QJsonObject&) const = 0; }; diff --git a/launcher/modplatform/ResourceType.cpp b/launcher/modplatform/ResourceType.cpp new file mode 100644 index 000000000..2758f113f --- /dev/null +++ b/launcher/modplatform/ResourceType.cpp @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com> +// +// SPDX-License-Identifier: GPL-3.0-only + +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com> + * + * 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 "ResourceType.h" + +namespace ModPlatform { +static const QMap s_packedTypeNames = { { ResourceType::ResourcePack, QObject::tr("resource pack") }, + { ResourceType::TexturePack, QObject::tr("texture pack") }, + { ResourceType::DataPack, QObject::tr("data pack") }, + { ResourceType::ShaderPack, QObject::tr("shader pack") }, + { ResourceType::World, QObject::tr("world save") }, + { ResourceType::Mod, QObject::tr("mod") }, + { ResourceType::Unknown, QObject::tr("unknown") } }; + +namespace ResourceTypeUtils { + +QString getName(ResourceType type) +{ + return s_packedTypeNames.constFind(type).value(); +} + +} // namespace ResourceTypeUtils +} // namespace ModPlatform diff --git a/launcher/modplatform/ResourceType.h b/launcher/modplatform/ResourceType.h new file mode 100644 index 000000000..b9073aa17 --- /dev/null +++ b/launcher/modplatform/ResourceType.h @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com> +// +// SPDX-License-Identifier: GPL-3.0-only + +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com> + * + * 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 +#include + +namespace ModPlatform { + +enum class ResourceType { Mod, ResourcePack, ShaderPack, Modpack, DataPack, World, Screenshots, TexturePack, Unknown }; + +namespace ResourceTypeUtils { +static const std::set VALID_RESOURCES = { ResourceType::DataPack, ResourceType::ResourcePack, ResourceType::TexturePack, + ResourceType::ShaderPack, ResourceType::World, ResourceType::Mod }; +QString getName(ResourceType type); +} // namespace ResourceTypeUtils +} // namespace ModPlatform \ No newline at end of file diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 4f2cca80f..ba3a25aa3 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -434,14 +434,15 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, std::shared QList exempt; for (const auto& componentUid : componentsToInstall.keys()) { auto componentVersion = componentsToInstall.value(componentUid); - - for (const auto& library : componentVersion->data()->libraries) { - GradleSpecifier lib(library->rawName()); - exempt.append(lib); + if (componentVersion->data()) { + for (const auto& library : componentVersion->data()->libraries) { + GradleSpecifier lib(library->rawName()); + exempt.append(lib); + } } } - { + if (minecraftVersion->data()) { for (const auto& library : minecraftVersion->data()->libraries) { GradleSpecifier lib(library->rawName()); exempt.append(lib); @@ -582,10 +583,12 @@ bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr< for (const auto& componentUid : componentsToInstall.keys()) { auto componentVersion = componentsToInstall.value(componentUid); - if (componentVersion->data()->mainClass != QString("")) { - mainClasses.append(componentVersion->data()->mainClass); + if (componentVersion->data()) { + if (componentVersion->data()->mainClass != QString("")) { + mainClasses.append(componentVersion->data()->mainClass); + } + tweakers.append(componentVersion->data()->addTweakers); } - tweakers.append(componentVersion->data()->addTweakers); } auto f = std::make_shared(); diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp index 5f812d219..6dacb43de 100644 --- a/launcher/modplatform/flame/FileResolvingTask.cpp +++ b/launcher/modplatform/flame/FileResolvingTask.cpp @@ -84,18 +84,18 @@ void Flame::FileResolvingTask::executeTask() m_task->start(); } -PackedResourceType getResourceType(int classId) +ModPlatform::ResourceType getResourceType(int classId) { switch (classId) { case 17: // Worlds - return PackedResourceType::WorldSave; + return ModPlatform::ResourceType::World; case 6: // Mods - return PackedResourceType::Mod; + return ModPlatform::ResourceType::Mod; case 12: // Resource Packs - // return PackedResourceType::ResourcePack; // not really a resourcepack + // return ModPlatform::ResourceType::ResourcePack; // not really a resourcepack /* fallthrough */ case 4546: // Customization - // return PackedResourceType::ShaderPack; // not really a shaderPack + // return ModPlatform::ResourceType::ShaderPack; // not really a shaderPack /* fallthrough */ case 4471: // Modpacks /* fallthrough */ @@ -104,7 +104,7 @@ PackedResourceType getResourceType(int classId) case 4559: // Addons /* fallthrough */ default: - return PackedResourceType::UNKNOWN; + return ModPlatform::ResourceType::Unknown; } } @@ -256,7 +256,7 @@ 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) { + if (file->resourceType == ModPlatform::ResourceType::World) { file->targetFolder = "saves"; } } diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index 15eb7a696..b0d9af804 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -5,6 +5,7 @@ #include "FlameAPI.h" #include #include +#include "BuildConfig.h" #include "FlameModIndex.h" #include "Application.h" @@ -29,7 +30,7 @@ Task::Ptr FlameAPI::matchFingerprints(const QList& fingerprints, std::shar QJsonDocument body(body_obj); auto body_raw = body.toJson(); - netJob->addNetAction(Net::ApiUpload::makeByteArray(QString("https://api.curseforge.com/v1/fingerprints"), response, body_raw)); + netJob->addNetAction(Net::ApiUpload::makeByteArray(QString(BuildConfig.FLAME_BASE_URL + "/fingerprints"), response, body_raw)); return netJob; } @@ -42,7 +43,7 @@ QString FlameAPI::getModFileChangelog(int modId, int fileId) auto netJob = makeShared(QString("Flame::FileChangelog"), APPLICATION->network()); auto response = std::make_shared(); netJob->addNetAction(Net::ApiDownload::makeByteArray( - QString("https://api.curseforge.com/v1/mods/%1/files/%2/changelog") + QString(BuildConfig.FLAME_BASE_URL + "/mods/%1/files/%2/changelog") .arg(QString::fromStdString(std::to_string(modId)), QString::fromStdString(std::to_string(fileId))), response)); @@ -77,7 +78,7 @@ QString FlameAPI::getModDescription(int modId) auto netJob = makeShared(QString("Flame::ModDescription"), APPLICATION->network()); auto response = std::make_shared(); netJob->addNetAction(Net::ApiDownload::makeByteArray( - QString("https://api.curseforge.com/v1/mods/%1/description").arg(QString::number(modId)), response)); + QString(BuildConfig.FLAME_BASE_URL + "/mods/%1/description").arg(QString::number(modId)), response)); QObject::connect(netJob.get(), &NetJob::succeeded, [&netJob, response, &description] { QJsonParseError parse_error{}; @@ -117,7 +118,7 @@ Task::Ptr FlameAPI::getProjects(QStringList addonIds, std::shared_ptraddNetAction(Net::ApiUpload::makeByteArray(QString("https://api.curseforge.com/v1/mods"), response, body_raw)); + netJob->addNetAction(Net::ApiUpload::makeByteArray(QString(BuildConfig.FLAME_BASE_URL + "/mods"), response, body_raw)); QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; }); @@ -139,7 +140,7 @@ Task::Ptr FlameAPI::getFiles(const QStringList& fileIds, std::shared_ptraddNetAction(Net::ApiUpload::makeByteArray(QString("https://api.curseforge.com/v1/mods/files"), response, body_raw)); + netJob->addNetAction(Net::ApiUpload::makeByteArray(QString(BuildConfig.FLAME_BASE_URL + "/mods/files"), response, body_raw)); QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; }); @@ -150,7 +151,7 @@ Task::Ptr FlameAPI::getFile(const QString& addonId, const QString& fileId, std:: { auto netJob = makeShared(QString("Flame::GetFile"), APPLICATION->network()); netJob->addNetAction( - Net::ApiDownload::makeByteArray(QUrl(QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(addonId, fileId)), response)); + Net::ApiDownload::makeByteArray(QUrl(QString(BuildConfig.FLAME_BASE_URL + "/mods/%1/files/%2").arg(addonId, fileId)), response)); QObject::connect(netJob.get(), &NetJob::failed, [addonId, fileId] { qDebug() << "Flame API file failure" << addonId << fileId; }); @@ -174,14 +175,14 @@ Task::Ptr FlameAPI::getCategories(std::shared_ptr response, ModPlatf { auto netJob = makeShared(QString("Flame::GetCategories"), APPLICATION->network()); netJob->addNetAction(Net::ApiDownload::makeByteArray( - QUrl(QString("https://api.curseforge.com/v1/categories?gameId=432&classId=%1").arg(getClassId(type))), response)); + QUrl(QString(BuildConfig.FLAME_BASE_URL + "/categories?gameId=432&classId=%1").arg(getClassId(type))), response)); QObject::connect(netJob.get(), &Task::failed, [](QString msg) { qDebug() << "Flame failed to get categories:" << msg; }); return netJob; } Task::Ptr FlameAPI::getModCategories(std::shared_ptr response) { - return getCategories(response, ModPlatform::ResourceType::MOD); + return getCategories(response, ModPlatform::ResourceType::Mod); } QList FlameAPI::loadModCategories(std::shared_ptr response) @@ -217,9 +218,19 @@ QList FlameAPI::loadModCategories(std::shared_ptr FlameAPI::getLatestVersion(QList versions, QList instanceLoaders, - ModPlatform::ModLoaderTypes modLoaders) + ModPlatform::ModLoaderTypes modLoaders, + bool checkLoaders) { static const auto noLoader = ModPlatform::ModLoaderType(0); + if (!checkLoaders) { + std::optional ver; + for (auto file_tmp : versions) { + if (!ver.has_value() || file_tmp.date > ver->date) { + ver = file_tmp; + } + } + return ver; + } QHash bestMatch; auto checkVersion = [&bestMatch](const ModPlatform::IndexedVersion& version, const ModPlatform::ModLoaderType& loader) { if (bestMatch.contains(loader)) { diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 71282f36a..799e142ce 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -6,18 +6,22 @@ #include #include +#include "BuildConfig.h" +#include "Json.h" +#include "Version.h" #include "modplatform/ModIndex.h" #include "modplatform/ResourceAPI.h" -#include "modplatform/helpers/NetworkResourceAPI.h" +#include "modplatform/flame/FlameModIndex.h" -class FlameAPI : public NetworkResourceAPI { +class FlameAPI : public ResourceAPI { public: QString getModFileChangelog(int modId, int fileId); QString getModDescription(int modId); std::optional getLatestVersion(QList versions, QList instanceLoaders, - ModPlatform::ModLoaderTypes fallback); + ModPlatform::ModLoaderTypes fallback, + bool checkLoaders); Task::Ptr getProjects(QStringList addonIds, std::shared_ptr response) const override; Task::Ptr matchFingerprints(const QList& fingerprints, std::shared_ptr response); @@ -28,7 +32,7 @@ class FlameAPI : public NetworkResourceAPI { static Task::Ptr getModCategories(std::shared_ptr response); static QList loadModCategories(std::shared_ptr response); - [[nodiscard]] QList getSortingMethods() const override; + QList getSortingMethods() const override; static inline bool validateModLoaders(ModPlatform::ModLoaderTypes loaders) { @@ -40,15 +44,15 @@ class FlameAPI : public NetworkResourceAPI { { switch (type) { default: - case ModPlatform::ResourceType::MOD: + case ModPlatform::ResourceType::Mod: return 6; - case ModPlatform::ResourceType::RESOURCE_PACK: + case ModPlatform::ResourceType::ResourcePack: return 12; - case ModPlatform::ResourceType::SHADER_PACK: + case ModPlatform::ResourceType::ShaderPack: return 6552; - case ModPlatform::ResourceType::MODPACK: + case ModPlatform::ResourceType::Modpack: return 4471; - case ModPlatform::ResourceType::DATA_PACK: + case ModPlatform::ResourceType::DataPack: return 6945; } } @@ -70,6 +74,11 @@ class FlameAPI : public NetworkResourceAPI { case ModPlatform::NeoForge: return 6; case ModPlatform::DataPack: + case ModPlatform::Babric: + case ModPlatform::BTA: + case ModPlatform::LegacyFabric: + case ModPlatform::Ornithe: + case ModPlatform::Rift: break; // not supported } return 0; @@ -89,7 +98,7 @@ class FlameAPI : public NetworkResourceAPI { static const QString getModLoaderFilters(ModPlatform::ModLoaderTypes types) { return "[" + getModLoaderStrings(types).join(',') + "]"; } public: - [[nodiscard]] std::optional getSearchURL(SearchArgs const& args) const override + std::optional getSearchURL(SearchArgs const& args) const override { QStringList get_arguments; get_arguments.append(QString("classId=%1").arg(getClassId(args.type))); @@ -112,34 +121,54 @@ class FlameAPI : public NetworkResourceAPI { if (args.versions.has_value() && !args.versions.value().empty()) get_arguments.append(QString("gameVersion=%1").arg(args.versions.value().front().toString())); - return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&'); + return BuildConfig.FLAME_BASE_URL + "/mods/search?gameId=432&" + get_arguments.join('&'); } - [[nodiscard]] std::optional getVersionsURL(VersionSearchArgs const& args) const override + std::optional getVersionsURL(VersionSearchArgs const& args) const override { auto addonId = args.pack.addonId.toString(); - QString url = QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000").arg(addonId); + QString url = QString(BuildConfig.FLAME_BASE_URL + "/mods/%1/files?pageSize=10000").arg(addonId); if (args.mcVersions.has_value()) url += QString("&gameVersion=%1").arg(args.mcVersions.value().front().toString()); - if (args.loaders.has_value() && args.loaders.value() != ModPlatform::ModLoaderType::DataPack && ModPlatform::hasSingleModLoaderSelected(args.loaders.value())) { + if (args.loaders.has_value() && args.loaders.value() != ModPlatform::ModLoaderType::DataPack && + ModPlatform::hasSingleModLoaderSelected(args.loaders.value())) { int mappedModLoader = getMappedModLoader(static_cast(static_cast(args.loaders.value()))); url += QString("&modLoaderType=%1").arg(mappedModLoader); } return url; } - private: - [[nodiscard]] std::optional getInfoURL(QString const& id) const override + QJsonArray documentToArray(QJsonDocument& obj) const override { return Json::ensureArray(obj.object(), "data"); } + void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) const override { FlameMod::loadIndexedPack(m, obj); } + ModPlatform::IndexedVersion loadIndexedPackVersion(QJsonObject& obj, ModPlatform::ResourceType resourceType) const override { - return QString("https://api.curseforge.com/v1/mods/%1").arg(id); + auto arr = FlameMod::loadIndexedPackVersion(obj); + if (resourceType != ModPlatform::ResourceType::TexturePack) { + return arr; + } + // FIXME: Client-side version filtering. This won't take into account any user-selected filtering. + auto const& mc_versions = arr.mcVersion; + + if (std::any_of(mc_versions.constBegin(), mc_versions.constEnd(), + [](auto const& mc_version) { return Version(mc_version) <= Version("1.6"); })) { + return arr; + } + return {}; + }; + void loadExtraPackInfo(ModPlatform::IndexedPack& m, [[maybe_unused]] QJsonObject&) const override { FlameMod::loadBody(m); } + + private: + std::optional getInfoURL(QString const& id) const override + { + return QString(BuildConfig.FLAME_BASE_URL + "/mods/%1").arg(id); } - [[nodiscard]] std::optional getDependencyURL(DependencySearchArgs const& args) const override + std::optional getDependencyURL(DependencySearchArgs const& args) const override { auto addonId = args.dependency.addonId.toString(); auto url = - QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&gameVersion=%2").arg(addonId, args.mcVersion.toString()); + QString(BuildConfig.FLAME_BASE_URL + "/mods/%1/files?pageSize=10000&gameVersion=%2").arg(addonId, args.mcVersion.toString()); if (args.loader && ModPlatform::hasSingleModLoaderSelected(args.loader)) { int mappedModLoader = getMappedModLoader(static_cast(static_cast(args.loader))); url += QString("&modLoaderType=%1").arg(mappedModLoader); diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp index 047813675..17d13deda 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.cpp +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -46,12 +46,12 @@ void FlameCheckUpdate::executeTask() connect(netJob, &Task::stepProgress, this, &FlameCheckUpdate::propagateStepProgress); connect(netJob, &Task::details, this, &FlameCheckUpdate::setDetails); for (auto* resource : m_resources) { - auto versions_url_optional = api.getVersionsURL({ { resource->metadata()->project_id.toString() }, m_game_versions }); - if (!versions_url_optional.has_value()) + auto versionsUrlOptional = api.getVersionsURL({ { resource->metadata()->project_id.toString() }, m_gameVersions }); + if (!versionsUrlOptional.has_value()) continue; auto response = std::make_shared(); - auto task = Net::ApiDownload::makeByteArray(versions_url_optional.value(), response); + auto task = Net::ApiDownload::makeByteArray(versionsUrlOptional.value(), response); connect(task.get(), &Task::succeeded, this, [this, resource, response] { getLatestVersionCallback(resource, response); }); netJob->addNetAction(task); @@ -87,7 +87,7 @@ void FlameCheckUpdate::getLatestVersionCallback(Resource* resource, std::shared_ qCritical() << e.what(); qDebug() << doc; } - auto latest_ver = api.getLatestVersion(pack->versions, m_loaders_list, resource->metadata()->loaders); + auto latest_ver = api.getLatestVersion(pack->versions, m_loadersList, resource->metadata()->loaders, !m_loadersList.isEmpty()); setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(resource->name())); @@ -119,7 +119,7 @@ void FlameCheckUpdate::getLatestVersionCallback(Resource* resource, std::shared_ old_version = tr("Unknown"); } - auto download_task = makeShared(pack, latest_ver.value(), m_resource_model); + auto download_task = makeShared(pack, latest_ver.value(), m_resourceModel); 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()); diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index c80187c42..caf75fe6c 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -493,16 +493,35 @@ bool FlameCreationTask::createInstance() void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) { - auto results = m_modIdResolver->getResults(); + auto results = m_modIdResolver->getResults().files; + + QStringList optionalFiles; + for (auto& result : results) { + if (!result.required) { + optionalFiles << FS::PathCombine(result.targetFolder, result.version.fileName); + } + } + + if (!optionalFiles.empty()) { + OptionalModDialog optionalModDialog(m_parent, optionalFiles); + if (optionalModDialog.exec() == QDialog::Rejected) { + emitAborted(); + loop.quit(); + return; + } + + m_selectedOptionalMods = optionalModDialog.getResult(); + } // first check for blocked mods QList blocked_mods; auto anyBlocked = false; - for (const auto& result : results.files.values()) { - if (result.resourceType != PackedResourceType::Mod) { + for (const auto& result : results.values()) { + if (result.resourceType != ModPlatform::ResourceType::Mod) { m_otherResources.append(std::make_pair(result.version.fileName, result.targetFolder)); } + // skip optional mods that were not selected if (result.version.downloadUrl.isEmpty()) { BlockedMod blocked_mod; blocked_mod.name = result.version.fileName; @@ -511,6 +530,10 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) blocked_mod.matched = false; blocked_mod.localPath = ""; blocked_mod.targetFolder = result.targetFolder; + auto fileName = result.version.fileName; + fileName = FS::RemoveInvalidPathChars(fileName); + auto relpath = FS::PathCombine(result.targetFolder, fileName); + blocked_mod.disabled = !result.required && !m_selectedOptionalMods.contains(relpath); blocked_mods.append(blocked_mod); @@ -546,30 +569,12 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop) m_filesJob.reset(new NetJob(tr("Mod Download Flame"), APPLICATION->network())); auto results = m_modIdResolver->getResults().files; - QStringList optionalFiles; - for (auto& result : results) { - if (!result.required) { - optionalFiles << FS::PathCombine(result.targetFolder, result.version.fileName); - } - } - - QStringList selectedOptionalMods; - if (!optionalFiles.empty()) { - OptionalModDialog optionalModDialog(m_parent, optionalFiles); - if (optionalModDialog.exec() == QDialog::Rejected) { - emitAborted(); - loop.quit(); - return; - } - - selectedOptionalMods = optionalModDialog.getResult(); - } for (const auto& result : results) { auto fileName = result.version.fileName; fileName = FS::RemoveInvalidPathChars(fileName); auto relpath = FS::PathCombine(result.targetFolder, fileName); - if (!result.required && !selectedOptionalMods.contains(relpath)) { + if (!result.required && !m_selectedOptionalMods.contains(relpath)) { relpath += ".disabled"; } @@ -617,6 +622,8 @@ void FlameCreationTask::copyBlockedMods(QList const& blocked_mods) } auto destPath = FS::PathCombine(m_stagingPath, "minecraft", mod.targetFolder, mod.name); + if (mod.disabled) + destPath += ".disabled"; setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total))); @@ -680,29 +687,29 @@ void FlameCreationTask::validateOtherResources(QEventLoop& loop) QString worldPath; switch (type) { - case PackedResourceType::Mod: + case ModPlatform::ResourceType::Mod: validatePath(fileName, targetFolder, "mods"); zipMods.push_back(fileName); break; - case PackedResourceType::ResourcePack: + case ModPlatform::ResourceType::ResourcePack: validatePath(fileName, targetFolder, "resourcepacks"); break; - case PackedResourceType::TexturePack: + case ModPlatform::ResourceType::TexturePack: validatePath(fileName, targetFolder, "texturepacks"); break; - case PackedResourceType::DataPack: + case ModPlatform::ResourceType::DataPack: validatePath(fileName, targetFolder, "datapacks"); break; - case PackedResourceType::ShaderPack: + case ModPlatform::ResourceType::ShaderPack: // in theory flame API can't do this but who knows, that *may* change ? // better to handle it if it *does* occur in the future validatePath(fileName, targetFolder, "shaderpacks"); break; - case PackedResourceType::WorldSave: + case ModPlatform::ResourceType::World: worldPath = validatePath(fileName, targetFolder, "saves"); installWorld(worldPath); break; - case PackedResourceType::UNKNOWN: + case ModPlatform::ResourceType::Unknown: /* fallthrough */ default: qDebug() << "Can't Identify" << fileName << "at" << localPath << ", leaving it where it is."; diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.h b/launcher/modplatform/flame/FlameInstanceCreationTask.h index 3e586a416..e41ce742e 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.h +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.h @@ -92,4 +92,6 @@ class FlameCreationTask final : public InstanceCreationTask { QList> m_otherResources; std::optional m_instance; + + QStringList m_selectedOptionalMods; }; diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index c1b9e67af..d92ee729c 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -4,6 +4,7 @@ #include "Json.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" +#include "modplatform/ModIndex.h" #include "modplatform/flame/FlameAPI.h" static FlameAPI api; @@ -57,7 +58,7 @@ void FlameMod::loadURLs(ModPlatform::IndexedPack& pack, QJsonObject& obj) pack.extraDataLoaded = true; } -void FlameMod::loadBody(ModPlatform::IndexedPack& pack, [[maybe_unused]] QJsonObject& obj) +void FlameMod::loadBody(ModPlatform::IndexedPack& pack) { pack.extraData.body = api.getModDescription(pack.addonId.toInt()); @@ -110,6 +111,7 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) -> if (str.contains('.')) file.mcVersion.append(str); + file.side = ModPlatform::Side::NoSide; if (auto loader = str.toLower(); loader == "neoforge") file.loaders |= ModPlatform::NeoForge; else if (loader == "forge") @@ -123,10 +125,10 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) -> else if (loader == "quilt") file.loaders |= ModPlatform::Quilt; else if (loader == "server" || loader == "client") { - if (file.side.isEmpty()) - file.side = loader; - else if (file.side != loader) - file.side = "both"; + if (file.side == ModPlatform::Side::NoSide) + file.side = ModPlatform::SideUtils::fromString(loader); + else if (file.side != ModPlatform::SideUtils::fromString(loader)) + file.side = ModPlatform::Side::UniversalSide; } } @@ -202,31 +204,3 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) -> return file; } - -ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst) -{ - auto profile = (dynamic_cast(inst))->getPackProfile(); - QString mcVersion = profile->getComponentVersion("net.minecraft"); - auto loaders = profile->getSupportedModLoaders(); - QList versions; - for (auto versionIter : arr) { - auto obj = versionIter.toObject(); - - auto file = loadIndexedPackVersion(obj); - if (!file.addonId.isValid()) - file.addonId = m.addonId; - - if (file.fileId.isValid() && - (!loaders.has_value() || !file.loaders || loaders.value() & file.loaders)) // Heuristic to check if the returned value is valid - versions.append(file); - } - - auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool { - // dates are in RFC 3339 format - return a.date > b.date; - }; - std::sort(versions.begin(), versions.end(), orderSortPredicate); - if (versions.size() != 0) - return versions.front(); - return {}; -} diff --git a/launcher/modplatform/flame/FlameModIndex.h b/launcher/modplatform/flame/FlameModIndex.h index f6b4b22be..b583b518f 100644 --- a/launcher/modplatform/flame/FlameModIndex.h +++ b/launcher/modplatform/flame/FlameModIndex.h @@ -12,8 +12,7 @@ namespace FlameMod { void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj); void loadURLs(ModPlatform::IndexedPack& m, QJsonObject& obj); -void loadBody(ModPlatform::IndexedPack& m, QJsonObject& obj); +void loadBody(ModPlatform::IndexedPack& m); void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr); ModPlatform::IndexedVersion loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false); -ModPlatform::IndexedVersion loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst); } // namespace FlameMod \ No newline at end of file diff --git a/launcher/modplatform/flame/FlamePackIndex.cpp b/launcher/modplatform/flame/FlamePackIndex.cpp deleted file mode 100644 index db2061d99..000000000 --- a/launcher/modplatform/flame/FlamePackIndex.cpp +++ /dev/null @@ -1,150 +0,0 @@ -#include "FlamePackIndex.h" -#include -#include - -#include "Json.h" -#include "modplatform/ModIndex.h" - -void Flame::loadIndexedPack(Flame::IndexedPack& pack, QJsonObject& obj) -{ - pack.addonId = Json::requireInteger(obj, "id"); - pack.name = Json::requireString(obj, "name"); - pack.description = Json::ensureString(obj, "summary", ""); - - auto logo = Json::requireObject(obj, "logo"); - pack.logoUrl = Json::requireString(logo, "thumbnailUrl"); - pack.logoName = Json::requireString(obj, "slug") + "." + QFileInfo(QUrl(pack.logoUrl).fileName()).suffix(); - - auto authors = Json::requireArray(obj, "authors"); - for (auto authorIter : authors) { - auto author = Json::requireObject(authorIter); - Flame::ModpackAuthor packAuthor; - packAuthor.name = Json::requireString(author, "name"); - packAuthor.url = Json::requireString(author, "url"); - pack.authors.append(packAuthor); - } - int defaultFileId = Json::requireInteger(obj, "mainFileId"); - - bool found = false; - // check if there are some files before adding the pack - auto files = Json::requireArray(obj, "latestFiles"); - for (auto fileIter : files) { - auto file = Json::requireObject(fileIter); - int id = Json::requireInteger(file, "id"); - - // NOTE: for now, ignore everything that's not the default... - if (id != defaultFileId) { - continue; - } - - auto versionArray = Json::requireArray(file, "gameVersions"); - if (versionArray.size() < 1) { - continue; - } - - found = true; - break; - } - if (!found) { - throw JSONValidationError(QString("Pack with no good file, skipping: %1").arg(pack.name)); - } - - loadIndexedInfo(pack, obj); -} - -void Flame::loadIndexedInfo(IndexedPack& pack, QJsonObject& obj) -{ - auto links_obj = Json::ensureObject(obj, "links"); - - pack.extra.websiteUrl = Json::ensureString(links_obj, "websiteUrl"); - if (pack.extra.websiteUrl.endsWith('/')) - pack.extra.websiteUrl.chop(1); - - pack.extra.issuesUrl = Json::ensureString(links_obj, "issuesUrl"); - if (pack.extra.issuesUrl.endsWith('/')) - pack.extra.issuesUrl.chop(1); - - pack.extra.sourceUrl = Json::ensureString(links_obj, "sourceUrl"); - if (pack.extra.sourceUrl.endsWith('/')) - pack.extra.sourceUrl.chop(1); - - pack.extra.wikiUrl = Json::ensureString(links_obj, "wikiUrl"); - if (pack.extra.wikiUrl.endsWith('/')) - pack.extra.wikiUrl.chop(1); - - pack.extraInfoLoaded = true; -} - -void Flame::loadIndexedPackVersions(Flame::IndexedPack& pack, QJsonArray& arr) -{ - QList unsortedVersions; - for (auto versionIter : arr) { - auto version = Json::requireObject(versionIter); - Flame::IndexedVersion file; - - file.addonId = pack.addonId; - file.fileId = Json::requireInteger(version, "id"); - auto versionArray = Json::requireArray(version, "gameVersions"); - if (versionArray.size() < 1) { - continue; - } - - for (auto mcVer : versionArray) { - auto str = mcVer.toString(); - - if (str.contains('.')) - file.mcVersion.append(str); - - if (auto loader = str.toLower(); loader == "neoforge") - file.loaders |= ModPlatform::NeoForge; - else if (loader == "forge") - file.loaders |= ModPlatform::Forge; - else if (loader == "cauldron") - file.loaders |= ModPlatform::Cauldron; - else if (loader == "liteloader") - file.loaders |= ModPlatform::LiteLoader; - else if (loader == "fabric") - file.loaders |= ModPlatform::Fabric; - else if (loader == "quilt") - file.loaders |= ModPlatform::Quilt; - } - - // pick the latest version supported - file.version = Json::requireString(version, "displayName"); - - ModPlatform::IndexedVersionType::VersionType ver_type; - switch (Json::requireInteger(version, "releaseType")) { - case 1: - ver_type = ModPlatform::IndexedVersionType::VersionType::Release; - break; - case 2: - ver_type = ModPlatform::IndexedVersionType::VersionType::Beta; - break; - case 3: - ver_type = ModPlatform::IndexedVersionType::VersionType::Alpha; - break; - default: - ver_type = ModPlatform::IndexedVersionType::VersionType::Unknown; - } - file.version_type = ModPlatform::IndexedVersionType(ver_type); - file.downloadUrl = Json::ensureString(version, "downloadUrl"); - - // only add if we have a download URL (third party distribution is enabled) - if (!file.downloadUrl.isEmpty()) { - unsortedVersions.append(file); - } - } - - auto orderSortPredicate = [](const IndexedVersion& a, const IndexedVersion& b) -> bool { return a.fileId > b.fileId; }; - std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate); - pack.versions = unsortedVersions; - pack.versionsLoaded = true; -} - -auto Flame::getVersionDisplayString(const IndexedVersion& version) -> QString -{ - auto release_type = version.version_type.isValid() ? QString(" [%1]").arg(version.version_type.toString()) : ""; - auto mcVersion = - !version.mcVersion.isEmpty() && !version.version.contains(version.mcVersion) ? QObject::tr(" for %1").arg(version.mcVersion) : ""; - return QString("%1%2%3").arg(version.version, mcVersion, release_type); -} diff --git a/launcher/modplatform/flame/FlamePackIndex.h b/launcher/modplatform/flame/FlamePackIndex.h deleted file mode 100644 index 22f7d648a..000000000 --- a/launcher/modplatform/flame/FlamePackIndex.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -#include -#include -#include -#include "modplatform/ModIndex.h" - -namespace Flame { - -struct ModpackAuthor { - QString name; - QString url; -}; - -struct IndexedVersion { - int addonId; - int fileId; - QString version; - ModPlatform::IndexedVersionType version_type; - ModPlatform::ModLoaderTypes loaders = {}; - QString mcVersion; - QString downloadUrl; -}; - -struct ModpackExtra { - QString websiteUrl; - QString wikiUrl; - QString issuesUrl; - QString sourceUrl; -}; - -struct IndexedPack { - int addonId; - QString name; - QString description; - QList authors; - QString logoName; - QString logoUrl; - - bool versionsLoaded = false; - QList versions; - - bool extraInfoLoaded = false; - ModpackExtra extra; -}; - -void loadIndexedPack(IndexedPack& m, QJsonObject& obj); -void loadIndexedInfo(IndexedPack&, QJsonObject&); -void loadIndexedPackVersions(IndexedPack& m, QJsonArray& arr); - -auto getVersionDisplayString(const IndexedVersion&) -> QString; -} // namespace Flame - -Q_DECLARE_METATYPE(Flame::IndexedPack) -Q_DECLARE_METATYPE(QList) diff --git a/launcher/modplatform/flame/PackManifest.h b/launcher/modplatform/flame/PackManifest.h index 6b911ffb4..049a99871 100644 --- a/launcher/modplatform/flame/PackManifest.h +++ b/launcher/modplatform/flame/PackManifest.h @@ -40,8 +40,8 @@ #include #include #include -#include "minecraft/mod/tasks/LocalResourceParse.h" #include "modplatform/ModIndex.h" +#include "modplatform/ResourceType.h" namespace Flame { struct File { @@ -55,7 +55,7 @@ struct File { // our QString targetFolder = QStringLiteral("mods"); - PackedResourceType resourceType; + ModPlatform::ResourceType resourceType; }; struct Modloader { diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.h b/launcher/modplatform/helpers/NetworkResourceAPI.h deleted file mode 100644 index b72e82533..000000000 --- a/launcher/modplatform/helpers/NetworkResourceAPI.h +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-FileCopyrightText: 2023 flowln -// -// SPDX-License-Identifier: GPL-3.0-only - -#pragma once - -#include -#include "modplatform/ResourceAPI.h" - -class NetworkResourceAPI : public ResourceAPI { - public: - Task::Ptr searchProjects(SearchArgs&&, SearchCallbacks&&) const override; - - Task::Ptr getProject(QString addonId, std::shared_ptr response) const override; - - Task::Ptr getProjectInfo(ProjectInfoArgs&&, ProjectInfoCallbacks&&) const override; - Task::Ptr getProjectVersions(VersionSearchArgs&&, VersionSearchCallbacks&&) const override; - Task::Ptr getDependencyVersion(DependencySearchArgs&&, DependencySearchCallbacks&&) const override; - - protected: - [[nodiscard]] virtual auto getSearchURL(SearchArgs const& args) const -> std::optional = 0; - [[nodiscard]] virtual auto getInfoURL(QString const& id) const -> std::optional = 0; - [[nodiscard]] virtual auto getVersionsURL(VersionSearchArgs const& args) const -> std::optional = 0; - [[nodiscard]] virtual auto getDependencyURL(DependencySearchArgs const& args) const -> std::optional = 0; -}; diff --git a/launcher/modplatform/import_ftb/PackHelpers.cpp b/launcher/modplatform/import_ftb/PackHelpers.cpp index e523b9d20..089b5fff2 100644 --- a/launcher/modplatform/import_ftb/PackHelpers.cpp +++ b/launcher/modplatform/import_ftb/PackHelpers.cpp @@ -19,6 +19,7 @@ #include "modplatform/import_ftb/PackHelpers.h" #include +#include #include #include @@ -27,6 +28,35 @@ namespace FTBImportAPP { +QIcon loadFTBIcon(const QString& imagePath) +{ + // Map of type byte to image type string + static const QHash imageTypeMap = { { 0x00, "png" }, { 0x01, "jpg" }, { 0x02, "gif" }, { 0x03, "webp" } }; + QFile file(imagePath); + if (!file.exists() || !file.open(QIODevice::ReadOnly)) { + return QIcon(); + } + char type; + if (!file.getChar(&type)) { + qDebug() << "Missing FTB image type header at" << imagePath; + return QIcon(); + } + if (!imageTypeMap.contains(type)) { + qDebug().nospace().noquote() << "Don't recognize FTB image type 0x" << QString::number(type, 16); + return QIcon(); + } + + auto imageType = imageTypeMap[type]; + // Extract actual image data beyond the first byte + QImageReader reader(&file, imageType); + auto pixmap = QPixmap::fromImageReader(&reader); + if (pixmap.isNull()) { + qDebug() << "The FTB image at" << imagePath << "is not valid"; + return QIcon(); + } + return QIcon(pixmap); +} + Modpack parseDirectory(QString path) { Modpack modpack{ path }; @@ -48,9 +78,14 @@ Modpack parseDirectory(QString path) qDebug() << "Couldn't load ftb instance json: " << e.cause(); return {}; } + auto versionsFile = QFileInfo(FS::PathCombine(path, "version.json")); - if (!versionsFile.exists() || !versionsFile.isFile()) + if (!versionsFile.exists() || !versionsFile.isFile()) { + versionsFile = QFileInfo(FS::PathCombine(path, ".ftbapp", "version.json")); + } + if (!versionsFile.exists() || !versionsFile.isFile()) { return {}; + } try { auto doc = Json::requireDocument(versionsFile.absoluteFilePath(), "FTB_APP version JSON file"); const auto root = doc.object(); @@ -85,6 +120,8 @@ Modpack parseDirectory(QString path) auto iconFile = QFileInfo(FS::PathCombine(path, "folder.jpg")); if (iconFile.exists() && iconFile.isFile()) { modpack.icon = QIcon(iconFile.absoluteFilePath()); + } else { // the logo is a file that the first bit denotes the image tipe followed by the actual image data + modpack.icon = loadFTBIcon(FS::PathCombine(path, ".ftbapp", "logo")); } return modpack; } diff --git a/launcher/modplatform/import_ftb/PackInstallTask.cpp b/launcher/modplatform/import_ftb/PackInstallTask.cpp index 7cb8b6ebc..3851e198c 100644 --- a/launcher/modplatform/import_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/import_ftb/PackInstallTask.cpp @@ -91,6 +91,16 @@ void PackInstallTask::copySettings() break; case ModPlatform::DataPack: break; + case ModPlatform::Babric: + break; + case ModPlatform::BTA: + break; + case ModPlatform::LegacyFabric: + break; + case ModPlatform::Ornithe: + break; + case ModPlatform::Rift: + break; } components->saveNow(); diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index 17b23723b..c2714b3c8 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -5,12 +5,14 @@ #pragma once #include "BuildConfig.h" +#include "Json.h" #include "modplatform/ModIndex.h" -#include "modplatform/helpers/NetworkResourceAPI.h" +#include "modplatform/ResourceAPI.h" +#include "modplatform/modrinth/ModrinthPackIndex.h" #include -class ModrinthAPI : public NetworkResourceAPI { +class ModrinthAPI : public ResourceAPI { public: Task::Ptr currentVersion(QString hash, QString hash_format, std::shared_ptr response); @@ -35,7 +37,7 @@ class ModrinthAPI : public NetworkResourceAPI { static QList loadModCategories(std::shared_ptr response); public: - [[nodiscard]] auto getSortingMethods() const -> QList override; + auto getSortingMethods() const -> QList override; inline auto getAuthorURL(const QString& name) const -> QString { return "https://modrinth.com/user/" + name; }; @@ -43,7 +45,7 @@ class ModrinthAPI : public NetworkResourceAPI { { QStringList l; for (auto loader : { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Fabric, ModPlatform::Quilt, ModPlatform::LiteLoader, - ModPlatform::DataPack }) { + ModPlatform::DataPack, ModPlatform::Babric, ModPlatform::BTA, ModPlatform::LegacyFabric, ModPlatform::Ornithe, ModPlatform::Rift }) { if (types & loader) { l << getModLoaderAsString(loader); } @@ -69,21 +71,23 @@ class ModrinthAPI : public NetworkResourceAPI { return l.join(','); } - static auto getSideFilters(QString side) -> const QString + static QString getSideFilters(ModPlatform::Side side) { - if (side.isEmpty()) { - return {}; + switch (side) { + case ModPlatform::Side::ClientSide: + return QString("\"client_side:required\",\"client_side:optional\"],[\"server_side:optional\",\"server_side:unsupported\""); + case ModPlatform::Side::ServerSide: + return QString("\"server_side:required\",\"server_side:optional\"],[\"client_side:optional\",\"client_side:unsupported\""); + case ModPlatform::Side::UniversalSide: + return QString("\"client_side:required\"],[\"server_side:required\""); + case ModPlatform::Side::NoSide: + // fallthrough + default: + return {}; } - if (side == "both") - return QString("\"client_side:required\"],[\"server_side:required\""); - if (side == "client") - 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\"],[\"client_side:optional\",\"client_side:unsupported\""); - return {}; } - [[nodiscard]] static inline QString mapMCVersionFromModrinth(QString v) + static inline QString mapMCVersionFromModrinth(QString v) { static const QString preString = " Pre-Release "; bool pre = false; @@ -99,18 +103,18 @@ class ModrinthAPI : public NetworkResourceAPI { } private: - [[nodiscard]] static QString resourceTypeParameter(ModPlatform::ResourceType type) + static QString resourceTypeParameter(ModPlatform::ResourceType type) { switch (type) { - case ModPlatform::ResourceType::MOD: + case ModPlatform::ResourceType::Mod: return "mod"; - case ModPlatform::ResourceType::RESOURCE_PACK: + case ModPlatform::ResourceType::ResourcePack: return "resourcepack"; - case ModPlatform::ResourceType::SHADER_PACK: + case ModPlatform::ResourceType::ShaderPack: return "shader"; - case ModPlatform::ResourceType::DATA_PACK: + case ModPlatform::ResourceType::DataPack: return "datapack"; - case ModPlatform::ResourceType::MODPACK: + case ModPlatform::ResourceType::Modpack: return "modpack"; default: qWarning() << "Invalid resource type for Modrinth API!"; @@ -120,7 +124,7 @@ class ModrinthAPI : public NetworkResourceAPI { return ""; } - [[nodiscard]] QString createFacets(SearchArgs const& args) const + QString createFacets(SearchArgs const& args) const { QStringList facets_list; @@ -144,7 +148,7 @@ class ModrinthAPI : public NetworkResourceAPI { } public: - [[nodiscard]] inline auto getSearchURL(SearchArgs const& args) const -> std::optional override + inline auto getSearchURL(SearchArgs const& args) const -> std::optional override { if (args.loaders.has_value() && args.loaders.value() != 0) { if (!validateModLoaders(args.loaders.value())) { @@ -200,10 +204,10 @@ class ModrinthAPI : public NetworkResourceAPI { static inline auto validateModLoaders(ModPlatform::ModLoaderTypes loaders) -> bool { return loaders & (ModPlatform::NeoForge | ModPlatform::Forge | ModPlatform::Fabric | ModPlatform::Quilt | ModPlatform::LiteLoader | - ModPlatform::DataPack); + ModPlatform::DataPack | ModPlatform::Babric | ModPlatform::BTA | ModPlatform::LegacyFabric | ModPlatform::Ornithe | ModPlatform::Rift); } - [[nodiscard]] std::optional getDependencyURL(DependencySearchArgs const& args) const override + std::optional getDependencyURL(DependencySearchArgs const& args) const override { return args.dependency.version.length() != 0 ? QString("%1/version/%2").arg(BuildConfig.MODRINTH_PROD_URL, args.dependency.version) : QString("%1/project/%2/version?game_versions=[\"%3\"]&loaders=[\"%4\"]") @@ -212,4 +216,12 @@ class ModrinthAPI : public NetworkResourceAPI { .arg(mapMCVersionToModrinth(args.mcVersion)) .arg(getModLoaderStrings(args.loader).join("\",\"")); }; + + QJsonArray documentToArray(QJsonDocument& obj) const override { return obj.object().value("hits").toArray(); } + void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) const override { Modrinth::loadIndexedPack(m, obj); } + ModPlatform::IndexedVersion loadIndexedPackVersion(QJsonObject& obj, ModPlatform::ResourceType) const override + { + return Modrinth::loadIndexedPackVersion(obj); + }; + void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) const override { Modrinth::loadExtraPackData(m, obj); } }; diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp index aa371f280..6683a0ed5 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp @@ -15,6 +15,26 @@ static ModrinthAPI api; +ModrinthCheckUpdate::ModrinthCheckUpdate(QList& resources, + std::list& mcVersions, + QList loadersList, + std::shared_ptr resourceModel) + : CheckUpdateTask(resources, mcVersions, std::move(loadersList), std::move(resourceModel)) + , m_hashType(ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first()) +{ + if (!m_loadersList.isEmpty()) { // this is for mods so append all the other posible loaders to the initial list + m_initialSize = m_loadersList.length(); + ModPlatform::ModLoaderTypes modLoaders; + for (auto m : resources) { + modLoaders |= m->metadata()->loaders; + } + for (auto l : m_loadersList) { + modLoaders &= ~l; + } + m_loadersList.append(ModPlatform::modLoaderTypesToList(modLoaders)); + } +} + bool ModrinthCheckUpdate::abort() { if (m_job) @@ -30,45 +50,61 @@ bool ModrinthCheckUpdate::abort() void ModrinthCheckUpdate::executeTask() { setStatus(tr("Preparing resources for Modrinth...")); - setProgress(0, (m_loaders_list.isEmpty() ? 1 : m_loaders_list.length()) * 2 + 1); + setProgress(0, (m_loadersList.isEmpty() ? 1 : m_loadersList.length()) * 2 + 1); auto hashing_task = makeShared("MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); + bool startHasing = false; 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 (resource->metadata()->hash_format != m_hash_type) { + if (resource->metadata()->hash_format != m_hashType) { 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); + startHasing = true; } else { m_mappings.insert(hash, resource); } } - connect(hashing_task.get(), &Task::finished, this, &ModrinthCheckUpdate::checkNextLoader); - m_job = hashing_task; - hashing_task->start(); + if (startHasing) { + connect(hashing_task.get(), &Task::finished, this, &ModrinthCheckUpdate::checkNextLoader); + m_job = hashing_task; + hashing_task->start(); + } else { + checkNextLoader(); + } } -void ModrinthCheckUpdate::getUpdateModsForLoader(std::optional loader) +void ModrinthCheckUpdate::getUpdateModsForLoader(std::optional loader, bool forceModLoaderCheck) { 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); + QStringList hashes; + if (forceModLoaderCheck && loader.has_value()) { + for (auto hash : m_mappings.keys()) { + if (m_mappings[hash]->metadata()->loaders & loader.value()) { + hashes.append(hash); + } + } + } else { + hashes = m_mappings.keys(); + } + auto job = api.latestVersions(hashes, m_hashType, m_gameVersions, 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; + m_loaderIdx++; job->start(); } @@ -121,7 +157,7 @@ void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr resp // - The version reported by the JAR is different from the version reported by the indexed version (it's usually the case) // Such is the pain of having arbitrary files for a given version .-. - auto project_ver = Modrinth::loadIndexedPackVersion(project_obj, m_hash_type, loader_filter); + auto project_ver = Modrinth::loadIndexedPackVersion(project_obj, m_hashType, loader_filter); if (project_ver.downloadUrl.isEmpty()) { qCritical() << "Modrinth mod without download url!" << project_ver.fileName; ++iter; @@ -135,7 +171,7 @@ void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr resp pack->addonId = resource->metadata()->project_id; pack->provider = ModPlatform::ResourceProvider::MODRINTH; 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_resourceModel); QString old_version = resource->metadata()->version_number; if (old_version.isEmpty()) { @@ -165,16 +201,11 @@ void ModrinthCheckUpdate::checkNextLoader() emitSucceeded(); return; } - - if (m_loaders_list.isEmpty() && m_loader_idx == 0) { - getUpdateModsForLoader({}); - m_loader_idx++; + if (m_loaderIdx < m_loadersList.size()) { // this are mods so check with loades + getUpdateModsForLoader(m_loadersList.at(m_loaderIdx), m_loaderIdx > m_initialSize); return; - } - - if (m_loader_idx < m_loaders_list.size()) { - getUpdateModsForLoader(m_loaders_list.at(m_loader_idx)); - m_loader_idx++; + } else if (m_loadersList.isEmpty() && m_loaderIdx == 0) { // this are other resources no need to check more than once with empty loader + getUpdateModsForLoader(); return; } @@ -192,4 +223,4 @@ void ModrinthCheckUpdate::checkNextLoader() } emitSucceeded(); -} +} \ No newline at end of file diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.h b/launcher/modplatform/modrinth/ModrinthCheckUpdate.h index 204b24784..eb8057694 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.h +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.h @@ -9,23 +9,21 @@ class ModrinthCheckUpdate : public CheckUpdateTask { ModrinthCheckUpdate(QList& resources, std::list& mcVersions, QList loadersList, - std::shared_ptr resourceModel) - : CheckUpdateTask(resources, mcVersions, std::move(loadersList), std::move(resourceModel)) - , m_hash_type(ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first()) - {} + std::shared_ptr resourceModel); public slots: bool abort() override; protected slots: void executeTask() override; - void getUpdateModsForLoader(std::optional loader); + void getUpdateModsForLoader(std::optional loader = {}, bool forceModLoaderCheck = false); void checkVersionsResponse(std::shared_ptr response, std::optional loader); void checkNextLoader(); private: Task::Ptr m_job = nullptr; QHash m_mappings; - QString m_hash_type; - int m_loader_idx = 0; + QString m_hashType; + int m_loaderIdx = 0; + int m_initialSize = 0; }; diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 374b7681e..18b435106 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -13,7 +13,6 @@ #include "modplatform/EnsureMetadataTask.h" #include "modplatform/helpers/OverrideUtils.h" -#include "modplatform/modrinth/ModrinthPackManifest.h" #include "net/ChecksumValidator.h" #include "net/ApiDownload.h" @@ -85,7 +84,7 @@ bool ModrinthCreationTask::updateInstance() QString old_index_path(FS::PathCombine(old_index_folder, "modrinth.index.json")); QFileInfo old_index_file(old_index_path); if (old_index_file.exists()) { - std::vector old_files; + std::vector old_files; parseManifest(old_index_path, old_files, false, false); // Let's remove all duplicated, identical resources! @@ -356,7 +355,7 @@ bool ModrinthCreationTask::createInstance() } bool ModrinthCreationTask::parseManifest(const QString& index_path, - std::vector& files, + std::vector& files, bool set_internal_data, bool show_optional_dialog) { @@ -377,9 +376,9 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, } auto jsonFiles = Json::requireIsArrayOf(obj, "files", "modrinth.index.json"); - std::vector optionalFiles; + std::vector optionalFiles; for (const auto& modInfo : jsonFiles) { - Modrinth::File file; + File file; file.path = Json::requireString(modInfo, "path").replace("\\", "/"); auto env = Json::ensureObject(modInfo, "env"); diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h index ddfa7ae95..e02a55877 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h @@ -1,13 +1,27 @@ #pragma once #include + +#include +#include +#include +#include +#include +#include + #include "BaseInstance.h" #include "InstanceCreationTask.h" -#include "modplatform/modrinth/ModrinthPackManifest.h" - class ModrinthCreationTask final : public InstanceCreationTask { Q_OBJECT + struct File { + QString path; + + QCryptographicHash::Algorithm hashAlgorithm; + QByteArray hash; + QQueue downloads; + bool required = true; + }; public: ModrinthCreationTask(QString staging_path, @@ -30,7 +44,7 @@ class ModrinthCreationTask final : public InstanceCreationTask { bool createInstance() override; private: - bool parseManifest(const QString&, std::vector&, bool set_internal_data = true, bool show_optional_dialog = true); + bool parseManifest(const QString&, std::vector&, bool set_internal_data = true, bool show_optional_dialog = true); private: QWidget* m_parent = nullptr; @@ -38,7 +52,7 @@ class ModrinthCreationTask final : public InstanceCreationTask { QString m_minecraft_version, m_fabric_version, m_quilt_version, m_forge_version, m_neoForge_version; QString m_managed_id, m_managed_version_id, m_managed_name; - std::vector m_files; + std::vector m_files; Task::Ptr m_task; std::optional m_instance; diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 4b19acd3f..9ee4101e6 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -28,6 +28,7 @@ #include "minecraft/PackProfile.h" #include "minecraft/mod/MetadataHandler.h" #include "minecraft/mod/ModFolderModel.h" +#include "modplatform/ModIndex.h" #include "modplatform/helpers/HashUtils.h" #include "tasks/Task.h" @@ -289,7 +290,7 @@ QByteArray ModrinthPackExportTask::generateIndex() // a server side mod does not imply that the mod does not work on the client // however, if a mrpack mod is marked as server-only it will not install on the client - if (iterator->side == Metadata::ModSide::ClientSide) + if (iterator->side == ModPlatform::Side::ClientSide) env["server"] = "unsupported"; fileOut["env"] = env; diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index ec4730de5..f9b86bbd7 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -23,6 +23,7 @@ #include "BaseInstance.h" #include "MMCZip.h" #include "minecraft/MinecraftInstance.h" +#include "modplatform/ModIndex.h" #include "modplatform/modrinth/ModrinthAPI.h" #include "tasks/Task.h" @@ -45,7 +46,7 @@ class ModrinthPackExportTask : public Task { struct ResolvedFile { QString sha1, sha512, url; qint64 size; - Metadata::ModSide side; + ModPlatform::Side side; }; static const QStringList PREFIXES; diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index 744b058c0..8e0552a48 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -63,11 +63,11 @@ void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) auto server = shouldDownloadOnSide(Json::ensureString(obj, "server_side")); if (server && client) { - pack.side = "both"; + pack.side = ModPlatform::Side::UniversalSide; } else if (server) { - pack.side = "server"; + pack.side = ModPlatform::Side::ServerSide; } else if (client) { - pack.side = "client"; + pack.side = ModPlatform::Side::ClientSide; } // Modrinth can have more data than what's provided by the basic search :) @@ -112,25 +112,6 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob pack.extraDataLoaded = true; } -void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr) -{ - QList unsortedVersions; - for (auto versionIter : arr) { - auto obj = versionIter.toObject(); - auto file = loadIndexedPackVersion(obj); - - if (file.fileId.isValid()) // Heuristic to check if the returned value is valid - unsortedVersions.append(file); - } - auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool { - // dates are in RFC 3339 format - return a.date > b.date; - }; - std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate); - pack.versions = unsortedVersions; - pack.versionsLoaded = true; -} - ModPlatform::IndexedVersion Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_type, QString preferred_file_name) { ModPlatform::IndexedVersion file; @@ -244,28 +225,3 @@ ModPlatform::IndexedVersion Modrinth::loadIndexedPackVersion(QJsonObject& obj, Q return {}; } - -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"); - auto loaders = profile->getSupportedModLoaders(); - - QList versions; - for (auto versionIter : arr) { - auto obj = versionIter.toObject(); - auto file = loadIndexedPackVersion(obj); - - if (file.fileId.isValid() && - (!loaders.has_value() || !file.loaders || loaders.value() & file.loaders)) // Heuristic to check if the returned value is valid - versions.append(file); - } - auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool { - // dates are in RFC 3339 format - return a.date > b.date; - }; - std::sort(versions.begin(), versions.end(), orderSortPredicate); - return versions.length() != 0 ? versions.front() : ModPlatform::IndexedVersion(); -} diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.h b/launcher/modplatform/modrinth/ModrinthPackIndex.h index 16f3d262c..5d852cb6f 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.h +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.h @@ -25,8 +25,6 @@ namespace Modrinth { void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj); void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj); -void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr); auto loadIndexedPackVersion(QJsonObject& obj, QString hash_type = "sha512", QString filename_prefer = "") -> ModPlatform::IndexedVersion; -auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst) -> ModPlatform::IndexedVersion; } // namespace Modrinth diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp deleted file mode 100644 index 1e90f713e..000000000 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp +++ /dev/null @@ -1,196 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (c) 2022 flowln - * - * 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 - * Copyright 2022 kb1000 - * - * 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 "ModrinthPackManifest.h" -#include -#include "Json.h" - -#include "modplatform/modrinth/ModrinthAPI.h" - -#include - -static ModrinthAPI api; - -namespace Modrinth { - -void loadIndexedPack(Modpack& pack, QJsonObject& obj) -{ - pack.id = Json::ensureString(obj, "project_id"); - - pack.name = Json::ensureString(obj, "title"); - pack.description = Json::ensureString(obj, "description"); - auto temp_author_name = Json::ensureString(obj, "author"); - pack.author = std::make_tuple(temp_author_name, api.getAuthorURL(temp_author_name)); - pack.iconUrl = Json::ensureString(obj, "icon_url"); - pack.iconName = QString("modrinth_%1.%2").arg(Json::ensureString(obj, "slug"), QFileInfo(pack.iconUrl.fileName()).suffix()); -} - -void loadIndexedInfo(Modpack& pack, QJsonObject& obj) -{ - pack.extra.body = Json::ensureString(obj, "body"); - pack.extra.projectUrl = QString("https://modrinth.com/modpack/%1").arg(Json::ensureString(obj, "slug")); - - pack.extra.issuesUrl = Json::ensureString(obj, "issues_url"); - if (pack.extra.issuesUrl.endsWith('/')) - pack.extra.issuesUrl.chop(1); - - pack.extra.sourceUrl = Json::ensureString(obj, "source_url"); - if (pack.extra.sourceUrl.endsWith('/')) - pack.extra.sourceUrl.chop(1); - - pack.extra.wikiUrl = Json::ensureString(obj, "wiki_url"); - if (pack.extra.wikiUrl.endsWith('/')) - pack.extra.wikiUrl.chop(1); - - pack.extra.discordUrl = Json::ensureString(obj, "discord_url"); - if (pack.extra.discordUrl.endsWith('/')) - pack.extra.discordUrl.chop(1); - - auto donate_arr = Json::ensureArray(obj, "donation_urls"); - for (auto d : donate_arr) { - auto d_obj = Json::requireObject(d); - - DonationData donate; - - donate.id = Json::ensureString(d_obj, "id"); - donate.platform = Json::ensureString(d_obj, "platform"); - donate.url = Json::ensureString(d_obj, "url"); - - pack.extra.donate.append(donate); - } - - pack.extra.status = Json::ensureString(obj, "status"); - - pack.extraInfoLoaded = true; -} - -void loadIndexedVersions(Modpack& pack, QJsonDocument& doc) -{ - QList unsortedVersions; - - auto arr = Json::requireArray(doc); - - for (auto versionIter : arr) { - auto obj = Json::requireObject(versionIter); - auto file = loadIndexedVersion(obj); - - if (!file.id.isEmpty()) // Heuristic to check if the returned value is valid - unsortedVersions.append(file); - } - auto orderSortPredicate = [](const ModpackVersion& a, const ModpackVersion& b) -> bool { - // dates are in RFC 3339 format - return a.date > b.date; - }; - - std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate); - - pack.versions.swap(unsortedVersions); - - pack.versionsLoaded = true; -} - -auto loadIndexedVersion(QJsonObject& obj) -> ModpackVersion -{ - ModpackVersion file; - - file.name = Json::requireString(obj, "name"); - file.version = Json::requireString(obj, "version_number"); - 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) { - if (loader == "neoforge") - file.loaders |= ModPlatform::NeoForge; - else if (loader == "forge") - file.loaders |= ModPlatform::Forge; - else if (loader == "cauldron") - file.loaders |= ModPlatform::Cauldron; - else if (loader == "liteloader") - file.loaders |= ModPlatform::LiteLoader; - else if (loader == "fabric") - file.loaders |= ModPlatform::Fabric; - else if (loader == "quilt") - file.loaders |= ModPlatform::Quilt; - } - file.version_type = ModPlatform::IndexedVersionType(Json::requireString(obj, "version_type")); - file.changelog = Json::ensureString(obj, "changelog"); - - file.id = Json::requireString(obj, "id"); - file.project_id = Json::requireString(obj, "project_id"); - - file.date = Json::requireString(obj, "date_published"); - - auto files = Json::requireArray(obj, "files"); - - for (auto file_iter : files) { - File indexed_file; - auto parent = Json::requireObject(file_iter); - auto is_primary = Json::ensureBoolean(parent, (const QString)QStringLiteral("primary"), false); - if (!is_primary) { - auto filename = Json::ensureString(parent, "filename"); - // Checking suffix here is fine because it's the response from Modrinth, - // so one would assume it will always be in English. - if (!filename.endsWith("mrpack") && !filename.endsWith("zip")) - continue; - } - - auto url = Json::requireString(parent, "url"); - - file.download_url = url; - if (is_primary) - break; - } - - if (file.download_url.isEmpty()) - return {}; - - return file; -} - -auto getVersionDisplayString(const ModpackVersion& version) -> QString -{ - auto release_type = version.version_type.isValid() ? QString(" [%1]").arg(version.version_type.toString()) : ""; - auto mcVersion = !version.gameVersion.isEmpty() && !version.name.contains(version.gameVersion) - ? QObject::tr(" for %1").arg(version.gameVersion) - : ""; - auto versionStr = !version.name.contains(version.version) ? version.version : ""; - return QString("%1%2 — %3%4").arg(version.name, mcVersion, versionStr, release_type); -} - -} // namespace Modrinth diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.h b/launcher/modplatform/modrinth/ModrinthPackManifest.h deleted file mode 100644 index a990e9a77..000000000 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.h +++ /dev/null @@ -1,129 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (c) 2022 flowln - * - * 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 - * Copyright 2022 kb1000 - * - * 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 -#include -#include -#include -#include -#include - -#include "modplatform/ModIndex.h" - -class MinecraftInstance; - -namespace Modrinth { - -struct File { - QString path; - - QCryptographicHash::Algorithm hashAlgorithm; - QByteArray hash; - QQueue downloads; - bool required = true; -}; - -struct DonationData { - QString id; - QString platform; - QString url; -}; - -struct ModpackExtra { - QString body; - - QString projectUrl; - - QString issuesUrl; - QString sourceUrl; - QString wikiUrl; - QString discordUrl; - - QList donate; - - QString status; -}; - -struct ModpackVersion { - QString name; - QString version; - QString gameVersion; - ModPlatform::IndexedVersionType version_type; - QString changelog; - ModPlatform::ModLoaderTypes loaders = {}; - - QString id; - QString project_id; - - QString date; - - QString download_url; -}; - -struct Modpack { - QString id; - - QString name; - QString description; - std::tuple author; - QString iconName; - QUrl iconUrl; - - bool versionsLoaded = false; - bool extraInfoLoaded = false; - - ModpackExtra extra; - QList versions; -}; - -void loadIndexedPack(Modpack&, QJsonObject&); -void loadIndexedInfo(Modpack&, QJsonObject&); -void loadIndexedVersions(Modpack&, QJsonDocument&); -auto loadIndexedVersion(QJsonObject&) -> ModpackVersion; - -auto validateDownloadUrl(QUrl) -> bool; - -auto getVersionDisplayString(const ModpackVersion&) -> QString; - -} // namespace Modrinth - -Q_DECLARE_METATYPE(Modrinth::Modpack) -Q_DECLARE_METATYPE(Modrinth::ModpackVersion) -Q_DECLARE_METATYPE(QList) diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index a3bb74399..0660d611c 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -28,7 +28,6 @@ #include "FileSystem.h" #include "StringUtils.h" -#include "minecraft/mod/Mod.h" #include "modplatform/ModIndex.h" #include @@ -113,7 +112,7 @@ auto V1::createModFormat([[maybe_unused]] const QDir& index_dir, mod.provider = mod_pack.provider; mod.file_id = mod_version.fileId; mod.project_id = mod_pack.addonId; - mod.side = stringToSide(mod_version.side.isEmpty() ? mod_pack.side : mod_version.side); + mod.side = mod_version.side == ModPlatform::Side::NoSide ? mod_pack.side : mod_version.side; mod.loaders = mod_version.loaders; mod.mcVersions = mod_version.mcVersion; mod.mcVersions.sort(); @@ -126,18 +125,6 @@ auto V1::createModFormat([[maybe_unused]] const QDir& index_dir, return 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) }; - if (mod.isValid()) - return mod; - - qWarning() << QString("Tried to create mod metadata with a Mod without metadata!"); - - return {}; -} - void V1::updateModIndex(const QDir& index_dir, Mod& mod) { if (!mod.isValid()) { @@ -208,7 +195,7 @@ void V1::updateModIndex(const QDir& index_dir, Mod& mod) { auto tbl = toml::table{ { "name", mod.name.toStdString() }, { "filename", mod.filename.toStdString() }, - { "side", sideToString(mod.side).toStdString() }, + { "side", ModPlatform::SideUtils::toString(mod.side).toStdString() }, { "x-prismlauncher-loaders", loaders }, { "x-prismlauncher-mc-versions", mcVersions }, { "x-prismlauncher-release-type", mod.releaseType.toString().toStdString() }, @@ -249,18 +236,6 @@ void V1::deleteModIndex(const QDir& index_dir, QString& mod_slug) } } -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); - - if (mod.mod_id() == mod_id) { - deleteModIndex(index_dir, mod.name); - break; - } - } -} - auto V1::getIndexForMod(const QDir& index_dir, QString slug) -> Mod { Mod mod; @@ -296,7 +271,7 @@ auto V1::getIndexForMod(const QDir& index_dir, QString slug) -> Mod { // Basic info mod.name = stringEntry(table, "name"); mod.filename = stringEntry(table, "filename"); - mod.side = stringToSide(stringEntry(table, "side")); + mod.side = ModPlatform::SideUtils::fromString(stringEntry(table, "side")); 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()) { @@ -371,28 +346,4 @@ auto V1::getIndexForMod(const QDir& index_dir, QVariant& mod_id) -> Mod return {}; } -auto V1::sideToString(Side side) -> QString -{ - switch (side) { - case Side::ClientSide: - return "client"; - case Side::ServerSide: - return "server"; - case Side::UniversalSide: - return "both"; - } - return {}; -} - -auto V1::stringToSide(QString side) -> Side -{ - if (side == "client") - return Side::ClientSide; - if (side == "server") - return Side::ServerSide; - if (side == "both") - return Side::UniversalSide; - return Side::UniversalSide; -} - } // namespace Packwiz diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h index 44896e74c..ba9a0fe75 100644 --- a/launcher/modplatform/packwiz/Packwiz.h +++ b/launcher/modplatform/packwiz/Packwiz.h @@ -27,23 +27,18 @@ class QDir; -// Mod from launcher/minecraft/mod/Mod.h -class Mod; - namespace Packwiz { 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{}; QString filename{}; - Side side{ Side::UniversalSide }; + ModPlatform::Side side{ ModPlatform::Side::UniversalSide }; ModPlatform::ModLoaderTypes loaders; QStringList mcVersions; ModPlatform::IndexedVersionType releaseType; @@ -74,10 +69,6 @@ class V1 { * its common representation in the launcher, when downloading mods. * */ 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(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 @@ -88,9 +79,6 @@ class V1 { /* Deletes the metadata for the mod with the given slug. If the metadata doesn't exist, it does nothing. */ 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(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. * */ @@ -100,9 +88,6 @@ class V1 { * If the mod doesn't have a metadata, it simply returns an empty Mod object. * */ static auto getIndexForMod(const QDir& index_dir, QVariant& mod_id) -> Mod; - - static auto sideToString(Side side) -> QString; - static auto stringToSide(QString side) -> Side; }; } // namespace Packwiz diff --git a/launcher/net/HttpMetaCache.h b/launcher/net/HttpMetaCache.h index 144012ae5..c8b02dae4 100644 --- a/launcher/net/HttpMetaCache.h +++ b/launcher/net/HttpMetaCache.h @@ -66,7 +66,7 @@ class MetaEntry { /* Whether the entry expires after some time (false) or not (true). */ void makeEternal(bool eternal) { m_is_eternal = eternal; } - [[nodiscard]] bool isEternal() const { return m_is_eternal; } + bool isEternal() const { return m_is_eternal; } auto getCurrentAge() -> qint64 { return m_current_age; } void setCurrentAge(qint64 age) { m_current_age = age; } diff --git a/launcher/pathmatcher/FSTreeMatcher.h b/launcher/pathmatcher/FSTreeMatcher.h deleted file mode 100644 index d8d36d2c3..000000000 --- a/launcher/pathmatcher/FSTreeMatcher.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include -#include "IPathMatcher.h" - -class FSTreeMatcher : public IPathMatcher { - public: - virtual ~FSTreeMatcher() {}; - FSTreeMatcher(SeparatorPrefixTree<'/'>& tree) : m_fsTree(tree) {} - - bool matches(const QString& string) const override { return m_fsTree.covers(string); } - - SeparatorPrefixTree<'/'>& m_fsTree; -}; diff --git a/launcher/pathmatcher/IPathMatcher.h b/launcher/pathmatcher/IPathMatcher.h deleted file mode 100644 index f3b01e8cf..000000000 --- a/launcher/pathmatcher/IPathMatcher.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once -#include -#include - -class IPathMatcher { - public: - using Ptr = std::shared_ptr; - - public: - virtual ~IPathMatcher() {} - virtual bool matches(const QString& string) const = 0; -}; diff --git a/launcher/pathmatcher/MultiMatcher.h b/launcher/pathmatcher/MultiMatcher.h deleted file mode 100644 index 3ad07b643..000000000 --- a/launcher/pathmatcher/MultiMatcher.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include -#include "IPathMatcher.h" - -class MultiMatcher : public IPathMatcher { - public: - virtual ~MultiMatcher() {}; - MultiMatcher() {} - MultiMatcher& add(Ptr add) - { - m_matchers.append(add); - return *this; - } - - virtual bool matches(const QString& string) const override - { - for (auto iter : m_matchers) { - if (iter->matches(string)) { - return true; - } - } - return false; - } - - QList m_matchers; -}; diff --git a/launcher/pathmatcher/RegexpMatcher.h b/launcher/pathmatcher/RegexpMatcher.h deleted file mode 100644 index e36516386..000000000 --- a/launcher/pathmatcher/RegexpMatcher.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include -#include "IPathMatcher.h" - -class RegexpMatcher : public IPathMatcher { - public: - virtual ~RegexpMatcher() {} - RegexpMatcher(const QString& regexp) - { - m_regexp.setPattern(regexp); - m_onlyFilenamePart = !regexp.contains('/'); - } - - RegexpMatcher(const QRegularExpression& regex) : m_regexp(regex) { m_onlyFilenamePart = !regex.pattern().contains('/'); } - - RegexpMatcher& caseSensitive(bool cs = true) - { - if (cs) { - m_regexp.setPatternOptions(QRegularExpression::CaseInsensitiveOption); - } else { - m_regexp.setPatternOptions(QRegularExpression::NoPatternOption); - } - return *this; - } - - virtual bool matches(const QString& string) const override - { - if (m_onlyFilenamePart) { - auto slash = string.lastIndexOf('/'); - if (slash != -1) { - auto part = string.mid(slash + 1); - return m_regexp.match(part).hasMatch(); - } - } - return m_regexp.match(string).hasMatch(); - } - QRegularExpression m_regexp; - bool m_onlyFilenamePart = false; -}; diff --git a/launcher/pathmatcher/SimplePrefixMatcher.h b/launcher/pathmatcher/SimplePrefixMatcher.h deleted file mode 100644 index 57bf63a30..000000000 --- a/launcher/pathmatcher/SimplePrefixMatcher.h +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Sefa Eyeoglu -// -// SPDX-License-Identifier: GPL-3.0-only - -#include "IPathMatcher.h" - -class SimplePrefixMatcher : public IPathMatcher { - public: - virtual ~SimplePrefixMatcher() {}; - SimplePrefixMatcher(const QString& prefix) - { - m_prefix = prefix; - m_isPrefix = prefix.endsWith('/'); - } - - virtual bool matches(const QString& string) const override - { - if (m_isPrefix) - return string.startsWith(m_prefix); - return string == m_prefix; - } - QString m_prefix; - bool m_isPrefix = false; -}; diff --git a/launcher/tasks/ConcurrentTask.h b/launcher/tasks/ConcurrentTask.h index cc6256cf8..a65613bf2 100644 --- a/launcher/tasks/ConcurrentTask.h +++ b/launcher/tasks/ConcurrentTask.h @@ -88,7 +88,7 @@ class ConcurrentTask : public Task { protected: // NOTE: This is not thread-safe. - [[nodiscard]] unsigned int totalSize() const { return static_cast(m_queue.size() + m_doing.size() + m_done.size()); } + unsigned int totalSize() const { return static_cast(m_queue.size() + m_doing.size() + m_done.size()); } virtual void updateState(); diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index 75fc93b3b..860e57e54 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -73,7 +73,7 @@ struct Language { if (key == "ja_KANJI") { result = locale.nativeLanguageName() + u8" (漢字)"; } else if (key == "es_UY") { - result = u8"español de Latinoamérica"; + result = u8"Español de Latinoamérica"; } else if (key == "en_NZ") { result = u8"New Zealand English"; // No idea why qt translates this to just english and not to New Zealand English } else if (key == "en@pirate") { diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 9edbda605..3cec0ae53 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -148,7 +148,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi { ui->setupUi(this); - setWindowIcon(APPLICATION->getThemedIcon("logo")); + setWindowIcon(APPLICATION->logo()); setWindowTitle(APPLICATION->applicationDisplayName()); #ifndef QT_NO_ACCESSIBILITY setAccessibleName(BuildConfig.LAUNCHER_DISPLAYNAME); @@ -165,7 +165,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi // qt designer will delete it when you save the file >:( changeIconButton = new LabeledToolButton(this); changeIconButton->setObjectName(QStringLiteral("changeIconButton")); - changeIconButton->setIcon(APPLICATION->getThemedIcon("news")); + changeIconButton->setIcon(QIcon::fromTheme("news")); changeIconButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); connect(changeIconButton, &QToolButton::clicked, this, &MainWindow::on_actionChangeInstIcon_triggered); ui->instanceToolBar->insertWidgetBefore(ui->actionLaunchInstance, changeIconButton); @@ -277,7 +277,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi { m_newsChecker.reset(new NewsChecker(APPLICATION->network(), BuildConfig.NEWS_RSS_URL)); newsLabel = new QToolButton(); - newsLabel->setIcon(APPLICATION->getThemedIcon("news")); + newsLabel->setIcon(QIcon::fromTheme("news")); newsLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); newsLabel->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); newsLabel->setFocusPolicy(Qt::NoFocus); @@ -684,7 +684,7 @@ void MainWindow::repopulateAccountsMenu() if (!face.isNull()) { action->setIcon(face); } else { - action->setIcon(APPLICATION->getThemedIcon("noaccount")); + action->setIcon(QIcon::fromTheme("noaccount")); } const int highestNumberKey = 9; @@ -755,7 +755,7 @@ void MainWindow::defaultAccountChanged() ui->actionAccountsButton->setText(profileLabel); auto face = account->getFace(); if (face.isNull()) { - ui->actionAccountsButton->setIcon(APPLICATION->getThemedIcon("noaccount")); + ui->actionAccountsButton->setIcon(QIcon::fromTheme("noaccount")); } else { ui->actionAccountsButton->setIcon(face); } @@ -763,7 +763,7 @@ void MainWindow::defaultAccountChanged() } // Set the icon to the "no account" icon. - ui->actionAccountsButton->setIcon(APPLICATION->getThemedIcon("noaccount")); + ui->actionAccountsButton->setIcon(QIcon::fromTheme("noaccount")); ui->actionAccountsButton->setText(tr("Accounts")); } @@ -1041,7 +1041,7 @@ void MainWindow::processURLs(QList urls) auto type = ResourceUtils::identify(localFileInfo); - if (ResourceUtils::ValidResourceTypes.count(type) == 0) { // probably instance/modpack + if (ModPlatform::ResourceTypeUtils::VALID_RESOURCES.count(type) == 0) { // probably instance/modpack addInstance(localFileName, extra_info); continue; } @@ -1065,25 +1065,25 @@ void MainWindow::processURLs(QList urls) auto minecraftInst = std::dynamic_pointer_cast(inst); switch (type) { - case PackedResourceType::ResourcePack: + case ModPlatform::ResourceType::ResourcePack: minecraftInst->resourcePackList()->installResourceWithFlameMetadata(localFileName, version); break; - case PackedResourceType::TexturePack: + case ModPlatform::ResourceType::TexturePack: minecraftInst->texturePackList()->installResourceWithFlameMetadata(localFileName, version); break; - case PackedResourceType::DataPack: + case ModPlatform::ResourceType::DataPack: qWarning() << "Importing of Data Packs not supported at this time. Ignoring" << localFileName; break; - case PackedResourceType::Mod: + case ModPlatform::ResourceType::Mod: minecraftInst->loaderModList()->installResourceWithFlameMetadata(localFileName, version); break; - case PackedResourceType::ShaderPack: + case ModPlatform::ResourceType::ShaderPack: minecraftInst->shaderPackList()->installResourceWithFlameMetadata(localFileName, version); break; - case PackedResourceType::WorldSave: + case ModPlatform::ResourceType::World: minecraftInst->worldList()->installWorld(localFileInfo); break; - case PackedResourceType::UNKNOWN: + case ModPlatform::ResourceType::Unknown: default: qDebug() << "Can't Identify" << localFileName << "Ignoring it."; break; diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui index 1d29ff628..ff1b4a25a 100644 --- a/launcher/ui/MainWindow.ui +++ b/launcher/ui/MainWindow.ui @@ -239,7 +239,7 @@ - More news... + More News... Open the development blog to read more news about %1. diff --git a/launcher/ui/ViewLogWindow.cpp b/launcher/ui/ViewLogWindow.cpp index c0c56f3ee..f4390e2f1 100644 --- a/launcher/ui/ViewLogWindow.cpp +++ b/launcher/ui/ViewLogWindow.cpp @@ -8,7 +8,7 @@ ViewLogWindow::ViewLogWindow(QWidget* parent) : QMainWindow(parent), m_page(new OtherLogsPage("launcher-logs", tr("Launcher Logs"), "Launcher-Logs", nullptr, parent)) { setAttribute(Qt::WA_DeleteOnClose); - setWindowIcon(APPLICATION->getThemedIcon("log")); + setWindowIcon(QIcon::fromTheme("log")); setWindowTitle(tr("View Launcher Logs")); setCentralWidget(m_page); setMinimumSize(m_page->size()); diff --git a/launcher/ui/dialogs/AboutDialog.cpp b/launcher/ui/dialogs/AboutDialog.cpp index 5b7d44ff7..0f3067719 100644 --- a/launcher/ui/dialogs/AboutDialog.cpp +++ b/launcher/ui/dialogs/AboutDialog.cpp @@ -147,7 +147,7 @@ AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDia ui->urlLabel->setOpenExternalLinks(true); - ui->icon->setPixmap(APPLICATION->getThemedIcon("logo").pixmap(64)); + ui->icon->setPixmap(APPLICATION->logo().pixmap(64)); ui->title->setText(launcherName); ui->versionLabel->setText(BuildConfig.printableVersionString()); diff --git a/launcher/ui/dialogs/BlockedModsDialog.h b/launcher/ui/dialogs/BlockedModsDialog.h index b24e76bbf..15d4d4770 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 disabled = false; bool move = false; }; diff --git a/launcher/ui/dialogs/CreateShortcutDialog.cpp b/launcher/ui/dialogs/CreateShortcutDialog.cpp index 5cfe33c7f..574881ad0 100644 --- a/launcher/ui/dialogs/CreateShortcutDialog.cpp +++ b/launcher/ui/dialogs/CreateShortcutDialog.cpp @@ -38,7 +38,6 @@ #include #include -#include "Application.h" #include "BuildConfig.h" #include "CreateShortcutDialog.h" #include "ui_CreateShortcutDialog.h" @@ -112,7 +111,7 @@ CreateShortcutDialog::CreateShortcutDialog(InstancePtr instance, QWidget* parent if (account->isInUse()) profileLabel = tr("%1 (in use)").arg(profileLabel); auto face = account->getFace(); - QIcon icon = face.isNull() ? APPLICATION->getThemedIcon("noaccount") : face; + QIcon icon = face.isNull() ? QIcon::fromTheme("noaccount") : face; ui->accountSelectionBox->addItem(profileLabel, account->profileName()); ui->accountSelectionBox->setItemIcon(i, icon); if (defaultAccount == account) diff --git a/launcher/ui/dialogs/ImportResourceDialog.cpp b/launcher/ui/dialogs/ImportResourceDialog.cpp index 97c8f22c5..7cd178130 100644 --- a/launcher/ui/dialogs/ImportResourceDialog.cpp +++ b/launcher/ui/dialogs/ImportResourceDialog.cpp @@ -8,10 +8,11 @@ #include "InstanceList.h" #include +#include "modplatform/ResourceType.h" #include "ui/instanceview/InstanceDelegate.h" #include "ui/instanceview/InstanceProxyModel.h" -ImportResourceDialog::ImportResourceDialog(QString file_path, PackedResourceType type, QWidget* parent) +ImportResourceDialog::ImportResourceDialog(QString file_path, ModPlatform::ResourceType type, QWidget* parent) : QDialog(parent), ui(new Ui::ImportResourceDialog), m_resource_type(type), m_file_path(file_path) { ui->setupUi(this); @@ -42,7 +43,7 @@ ImportResourceDialog::ImportResourceDialog(QString file_path, PackedResourceType connect(contentsWidget->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ImportResourceDialog::selectionChanged); ui->label->setText( - tr("Choose the instance you would like to import this %1 to.").arg(ResourceUtils::getPackedTypeName(m_resource_type))); + tr("Choose the instance you would like to import this %1 to.").arg(ModPlatform::ResourceTypeUtils::getName(m_resource_type))); ui->label_file_path->setText(tr("File: %1").arg(m_file_path)); ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); diff --git a/launcher/ui/dialogs/ImportResourceDialog.h b/launcher/ui/dialogs/ImportResourceDialog.h index bbde1ba7b..d96099661 100644 --- a/launcher/ui/dialogs/ImportResourceDialog.h +++ b/launcher/ui/dialogs/ImportResourceDialog.h @@ -3,7 +3,7 @@ #include #include -#include "minecraft/mod/tasks/LocalResourceParse.h" +#include "modplatform/ResourceType.h" #include "ui/instanceview/InstanceProxyModel.h" namespace Ui { @@ -14,13 +14,13 @@ class ImportResourceDialog : public QDialog { Q_OBJECT public: - explicit ImportResourceDialog(QString file_path, PackedResourceType type, QWidget* parent = nullptr); + explicit ImportResourceDialog(QString file_path, ModPlatform::ResourceType type, QWidget* parent = nullptr); ~ImportResourceDialog() override; QString selectedInstanceKey; private: Ui::ImportResourceDialog* ui; - PackedResourceType m_resource_type; + ModPlatform::ResourceType m_resource_type; QString m_file_path; InstanceProxyModel* proxyModel; diff --git a/launcher/ui/dialogs/InstallLoaderDialog.cpp b/launcher/ui/dialogs/InstallLoaderDialog.cpp index 7082125f2..552b83776 100644 --- a/launcher/ui/dialogs/InstallLoaderDialog.cpp +++ b/launcher/ui/dialogs/InstallLoaderDialog.cpp @@ -53,7 +53,7 @@ class InstallLoaderPage : public VersionSelectWidget, public BasePage { QString id() const override { return uid; } QString displayName() const override { return name; } - QIcon icon() const override { return APPLICATION->getThemedIcon(iconName); } + QIcon icon() const override { return QIcon::fromTheme(iconName); } void openedImpl() override { diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp index 9e74cd7ac..5542f6986 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.cpp +++ b/launcher/ui/dialogs/NewInstanceDialog.cpp @@ -74,7 +74,7 @@ NewInstanceDialog::NewInstanceDialog(const QString& initialGroup, { ui->setupUi(this); - setWindowIcon(APPLICATION->getThemedIcon("new")); + setWindowIcon(QIcon::fromTheme("new")); InstIconKey = "default"; ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey)); diff --git a/launcher/ui/dialogs/ProfileSetupDialog.cpp b/launcher/ui/dialogs/ProfileSetupDialog.cpp index 0b5e1a784..af8b26c66 100644 --- a/launcher/ui/dialogs/ProfileSetupDialog.cpp +++ b/launcher/ui/dialogs/ProfileSetupDialog.cpp @@ -55,9 +55,9 @@ ProfileSetupDialog::ProfileSetupDialog(MinecraftAccountPtr accountToSetup, QWidg ui->setupUi(this); ui->errorLabel->setVisible(false); - goodIcon = APPLICATION->getThemedIcon("status-good"); - yellowIcon = APPLICATION->getThemedIcon("status-yellow"); - badIcon = APPLICATION->getThemedIcon("status-bad"); + goodIcon = QIcon::fromTheme("status-good"); + yellowIcon = QIcon::fromTheme("status-yellow"); + badIcon = QIcon::fromTheme("status-bad"); static const QRegularExpression s_permittedNames("[a-zA-Z0-9_]{3,16}"); auto nameEdit = ui->nameEdit; diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 191feeb88..3015ae6e7 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -59,7 +59,7 @@ ResourceDownloadDialog::ResourceDownloadDialog(QWidget* parent, const std::share resize(static_cast(std::max(0.5 * parent->width(), 400.0)), static_cast(std::max(0.75 * parent->height(), 400.0))); - setWindowIcon(APPLICATION->getThemedIcon("new")); + setWindowIcon(QIcon::fromTheme("new")); // Bonk Qt over its stupid head and make sure it understands which button is the default one... // See: https://stackoverflow.com/questions/24556831/qbuttonbox-set-default-button diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h index 181086d82..a83f3c536 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.h +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -57,7 +57,7 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { void connectButtons(); //: String that gets appended to the download dialog title ("Download " + resourcesString()) - [[nodiscard]] virtual QString resourcesString() const { return tr("resources"); } + virtual QString resourcesString() const { return tr("resources"); } QString dialogTitle() override { return tr("Download %1").arg(resourcesString()); }; @@ -68,7 +68,7 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { void removeResource(const QString&); const QList getTasks(); - [[nodiscard]] const std::shared_ptr getBaseModel() const { return m_base_model; } + const std::shared_ptr getBaseModel() const { return m_base_model; } void setResourceMetadata(const std::shared_ptr& meta); @@ -82,10 +82,10 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { virtual void confirm(); protected: - [[nodiscard]] virtual QString geometrySaveKey() const { return ""; } + virtual QString geometrySaveKey() const { return ""; } void setButtonStatus(); - [[nodiscard]] virtual GetModDependenciesTask::Ptr getModDependenciesTask() { return nullptr; } + virtual GetModDependenciesTask::Ptr getModDependenciesTask() { return nullptr; } protected: const std::shared_ptr m_base_model; @@ -104,8 +104,8 @@ class ModDownloadDialog final : public ResourceDownloadDialog { ~ModDownloadDialog() override = default; //: String that gets appended to the mod download dialog title ("Download " + resourcesString()) - [[nodiscard]] QString resourcesString() const override { return tr("mods"); } - [[nodiscard]] QString geometrySaveKey() const override { return "ModDownloadGeometry"; } + QString resourcesString() const override { return tr("mods"); } + QString geometrySaveKey() const override { return "ModDownloadGeometry"; } QList getPages() override; GetModDependenciesTask::Ptr getModDependenciesTask() override; @@ -124,8 +124,8 @@ class ResourcePackDownloadDialog final : public ResourceDownloadDialog { ~ResourcePackDownloadDialog() override = default; //: String that gets appended to the resource pack download dialog title ("Download " + resourcesString()) - [[nodiscard]] QString resourcesString() const override { return tr("resource packs"); } - [[nodiscard]] QString geometrySaveKey() const override { return "RPDownloadGeometry"; } + QString resourcesString() const override { return tr("resource packs"); } + QString geometrySaveKey() const override { return "RPDownloadGeometry"; } QList getPages() override; @@ -143,8 +143,8 @@ class TexturePackDownloadDialog final : public ResourceDownloadDialog { ~TexturePackDownloadDialog() override = default; //: String that gets appended to the texture pack download dialog title ("Download " + resourcesString()) - [[nodiscard]] QString resourcesString() const override { return tr("texture packs"); } - [[nodiscard]] QString geometrySaveKey() const override { return "TPDownloadGeometry"; } + QString resourcesString() const override { return tr("texture packs"); } + QString geometrySaveKey() const override { return "TPDownloadGeometry"; } QList getPages() override; @@ -160,8 +160,8 @@ class ShaderPackDownloadDialog final : public ResourceDownloadDialog { ~ShaderPackDownloadDialog() override = default; //: String that gets appended to the shader pack download dialog title ("Download " + resourcesString()) - [[nodiscard]] QString resourcesString() const override { return tr("shader packs"); } - [[nodiscard]] QString geometrySaveKey() const override { return "ShaderDownloadGeometry"; } + QString resourcesString() const override { return tr("shader packs"); } + QString geometrySaveKey() const override { return "ShaderDownloadGeometry"; } QList getPages() override; @@ -177,8 +177,8 @@ class DataPackDownloadDialog final : public ResourceDownloadDialog { ~DataPackDownloadDialog() override = default; //: String that gets appended to the data pack download dialog title ("Download " + resourcesString()) - [[nodiscard]] QString resourcesString() const override { return tr("data packs"); } - [[nodiscard]] QString geometrySaveKey() const override { return "DataPackDownloadGeometry"; } + QString resourcesString() const override { return tr("data packs"); } + QString geometrySaveKey() const override { return "DataPackDownloadGeometry"; } QList getPages() override; diff --git a/launcher/ui/dialogs/ResourceUpdateDialog.cpp b/launcher/ui/dialogs/ResourceUpdateDialog.cpp index 774d3a339..eb69a6c6b 100644 --- a/launcher/ui/dialogs/ResourceUpdateDialog.cpp +++ b/launcher/ui/dialogs/ResourceUpdateDialog.cpp @@ -34,17 +34,17 @@ static std::list mcVersions(BaseInstance* inst) ResourceUpdateDialog::ResourceUpdateDialog(QWidget* parent, BaseInstance* instance, - const std::shared_ptr resource_model, - QList& search_for, - bool include_deps, + const std::shared_ptr resourceModel, + QList& searchFor, + bool includeDeps, QList loadersList) : ReviewMessageBox(parent, tr("Confirm resources to update"), "") , m_parent(parent) - , m_resource_model(resource_model) - , m_candidates(search_for) - , m_second_try_metadata(new ConcurrentTask("Second Metadata Search", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())) + , m_resourceModel(resourceModel) + , m_candidates(searchFor) + , m_secondTryMetadata(new ConcurrentTask("Second Metadata Search", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())) , m_instance(instance) - , m_include_deps(include_deps) + , m_includeDeps(includeDeps) , m_loadersList(std::move(loadersList)) { ReviewMessageBox::setGeometry(0, 0, 800, 600); @@ -63,9 +63,9 @@ void ResourceUpdateDialog::checkCandidates() } // Report failed metadata generation - if (!m_failed_metadata.empty()) { + if (!m_failedMetadata.empty()) { QString text; - for (const auto& failed : m_failed_metadata) { + for (const auto& failed : m_failedMetadata) { const auto& mod = std::get<0>(failed); const auto& reason = std::get<1>(failed); text += tr("Mod name: %1
File name: %2
Reason: %3

").arg(mod->name(), mod->fileinfo().fileName(), reason); @@ -84,24 +84,24 @@ void ResourceUpdateDialog::checkCandidates() } auto versions = mcVersions(m_instance); + 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, m_loadersList, m_resource_model)); - connect(m_modrinth_check_task.get(), &CheckUpdateTask::checkFailed, this, + if (!m_modrinthToUpdate.empty()) { + m_modrinthCheckTask.reset(new ModrinthCheckUpdate(m_modrinthToUpdate, versions, m_loadersList, m_resourceModel)); + connect(m_modrinthCheckTask.get(), &CheckUpdateTask::checkFailed, this, [this](Resource* resource, QString reason, QUrl recover_url) { - m_failed_check_update.append({ resource, reason, recover_url }); + m_failedCheckUpdate.append({ resource, reason, recover_url }); }); - check_task.addTask(m_modrinth_check_task); + check_task.addTask(m_modrinthCheckTask); } - if (!m_flame_to_update.empty()) { - m_flame_check_task.reset(new FlameCheckUpdate(m_flame_to_update, versions, m_loadersList, m_resource_model)); - connect(m_flame_check_task.get(), &CheckUpdateTask::checkFailed, this, - [this](Resource* resource, QString reason, QUrl recover_url) { - m_failed_check_update.append({ resource, reason, recover_url }); - }); - check_task.addTask(m_flame_check_task); + if (!m_flameToUpdate.empty()) { + m_flameCheckTask.reset(new FlameCheckUpdate(m_flameToUpdate, versions, m_loadersList, m_resourceModel)); + connect(m_flameCheckTask.get(), &CheckUpdateTask::checkFailed, this, [this](Resource* resource, QString reason, QUrl recover_url) { + m_failedCheckUpdate.append({ resource, reason, recover_url }); + }); + check_task.addTask(m_flameCheckTask); } connect(&check_task, &Task::failed, this, @@ -130,33 +130,33 @@ void ResourceUpdateDialog::checkCandidates() QList> selectedVers; // Add found updates for Modrinth - if (m_modrinth_check_task) { - auto modrinth_updates = m_modrinth_check_task->getUpdates(); + if (m_modrinthCheckTask) { + auto modrinth_updates = m_modrinthCheckTask->getUpdates(); for (auto& updatable : modrinth_updates) { qDebug() << QString("Mod %1 has an update available!").arg(updatable.name); appendResource(updatable); m_tasks.insert(updatable.name, updatable.download); } - selectedVers.append(m_modrinth_check_task->getDependencies()); + selectedVers.append(m_modrinthCheckTask->getDependencies()); } // Add found updated for Flame - if (m_flame_check_task) { - auto flame_updates = m_flame_check_task->getUpdates(); + if (m_flameCheckTask) { + auto flame_updates = m_flameCheckTask->getUpdates(); for (auto& updatable : flame_updates) { qDebug() << QString("Mod %1 has an update available!").arg(updatable.name); appendResource(updatable); m_tasks.insert(updatable.name, updatable.download); } - selectedVers.append(m_flame_check_task->getDependencies()); + selectedVers.append(m_flameCheckTask->getDependencies()); } // Report failed update checking - if (!m_failed_check_update.empty()) { + if (!m_failedCheckUpdate.empty()) { QString text; - for (const auto& failed : m_failed_check_update) { + for (const auto& failed : m_failedCheckUpdate) { const auto& mod = std::get<0>(failed); const auto& reason = std::get<1>(failed); const auto& recover_url = std::get<2>(failed); @@ -185,8 +185,8 @@ void ResourceUpdateDialog::checkCandidates() } } - if (m_include_deps && !APPLICATION->settings()->get("ModDependenciesDisabled").toBool()) { // dependencies - auto* mod_model = dynamic_cast(m_resource_model.get()); + if (m_includeDeps && !APPLICATION->settings()->get("ModDependenciesDisabled").toBool()) { // dependencies + auto* mod_model = dynamic_cast(m_resourceModel.get()); if (mod_model != nullptr) { auto depTask = makeShared(m_instance, mod_model, selectedVers); @@ -224,7 +224,7 @@ void ResourceUpdateDialog::checkCandidates() 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 download_task = makeShared(dep->pack, dep->version, m_resourceModel); 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, @@ -239,7 +239,7 @@ void ResourceUpdateDialog::checkCandidates() // If there's no resource to be updated if (ui->modTreeWidget->topLevelItemCount() == 0) { - m_no_updates = true; + m_noUpdates = true; } else { // FIXME: Find a more efficient way of doing this! @@ -254,7 +254,7 @@ void ResourceUpdateDialog::checkCandidates() } } - if (m_aborted || m_no_updates) + if (m_aborted || m_noUpdates) QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); } @@ -362,7 +362,7 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool seq.addTask(flame_task); } - seq.addTask(m_second_try_metadata); + seq.addTask(m_secondTryMetadata); // execute all the tasks ProgressDialog checking_dialog(m_parent); @@ -381,10 +381,10 @@ void ResourceUpdateDialog::onMetadataEnsured(Resource* resource) switch (resource->metadata()->provider) { case ModPlatform::ResourceProvider::MODRINTH: - m_modrinth_to_update.push_back(resource); + m_modrinthToUpdate.push_back(resource); break; case ModPlatform::ResourceProvider::FLAME: - m_flame_to_update.push_back(resource); + m_flameToUpdate.push_back(resource); break; } } @@ -415,14 +415,14 @@ void ResourceUpdateDialog::onMetadataFailed(Resource* resource, bool try_others, auto seq = makeShared(); seq->addTask(task->getHashingTask()); seq->addTask(task); - m_second_try_metadata->addTask(seq); + m_secondTryMetadata->addTask(seq); } else { - m_second_try_metadata->addTask(task); + m_secondTryMetadata->addTask(task); } } else { QString reason{ tr("Couldn't find a valid version on the selected mod provider(s)") }; - m_failed_metadata.append({ resource, reason }); + m_failedMetadata.append({ resource, reason }); } } diff --git a/launcher/ui/dialogs/ResourceUpdateDialog.h b/launcher/ui/dialogs/ResourceUpdateDialog.h index aef11c90f..be3c19dcc 100644 --- a/launcher/ui/dialogs/ResourceUpdateDialog.h +++ b/launcher/ui/dialogs/ResourceUpdateDialog.h @@ -18,9 +18,9 @@ class ResourceUpdateDialog final : public ReviewMessageBox { public: explicit ResourceUpdateDialog(QWidget* parent, BaseInstance* instance, - std::shared_ptr resource_model, - QList& search_for, - bool include_deps, + std::shared_ptr resourceModel, + QList& searchFor, + bool includeDeps, QList loadersList = {}); void checkCandidates(); @@ -28,9 +28,9 @@ class ResourceUpdateDialog final : public ReviewMessageBox { void appendResource(const CheckUpdateTask::Update& info, QStringList requiredBy = {}); const QList getTasks(); - auto indexDir() const -> QDir { return m_resource_model->indexDir(); } + auto indexDir() const -> QDir { return m_resourceModel->indexDir(); } - auto noUpdates() const -> bool { return m_no_updates; }; + auto noUpdates() const -> bool { return m_noUpdates; }; auto aborted() const -> bool { return m_aborted; }; private: @@ -40,29 +40,29 @@ class ResourceUpdateDialog final : public ReviewMessageBox { void onMetadataEnsured(Resource* resource); void onMetadataFailed(Resource* resource, bool try_others = false, - ModPlatform::ResourceProvider first_choice = ModPlatform::ResourceProvider::MODRINTH); + ModPlatform::ResourceProvider firstChoice = ModPlatform::ResourceProvider::MODRINTH); private: QWidget* m_parent; - shared_qobject_ptr m_modrinth_check_task; - shared_qobject_ptr m_flame_check_task; + shared_qobject_ptr m_modrinthCheckTask; + shared_qobject_ptr m_flameCheckTask; - const std::shared_ptr m_resource_model; + const std::shared_ptr m_resourceModel; QList& m_candidates; - QList m_modrinth_to_update; - QList m_flame_to_update; + QList m_modrinthToUpdate; + QList m_flameToUpdate; - ConcurrentTask::Ptr m_second_try_metadata; - QList> m_failed_metadata; - QList> m_failed_check_update; + ConcurrentTask::Ptr m_secondTryMetadata; + QList> m_failedMetadata; + QList> m_failedCheckUpdate; QHash m_tasks; BaseInstance* m_instance; - bool m_no_updates = false; + bool m_noUpdates = false; bool m_aborted = false; - bool m_include_deps = false; + bool m_includeDeps = false; QList m_loadersList; }; diff --git a/launcher/ui/dialogs/ReviewMessageBox.cpp b/launcher/ui/dialogs/ReviewMessageBox.cpp index 96cc8149f..c5f7b5fbe 100644 --- a/launcher/ui/dialogs/ReviewMessageBox.cpp +++ b/launcher/ui/dialogs/ReviewMessageBox.cpp @@ -1,8 +1,6 @@ #include "ReviewMessageBox.h" #include "ui_ReviewMessageBox.h" -#include "Application.h" - #include ReviewMessageBox::ReviewMessageBox(QWidget* parent, [[maybe_unused]] QString const& title, [[maybe_unused]] QString const& icon) @@ -56,7 +54,7 @@ void ReviewMessageBox::appendResource(ResourceInformation&& info) itemTop->insertChildren(1, { customPathItem }); - itemTop->setIcon(1, QIcon(APPLICATION->getThemedIcon("status-yellow"))); + itemTop->setIcon(1, QIcon(QIcon::fromTheme("status-yellow"))); itemTop->setToolTip( childIndx++, tr("This file will be downloaded to a folder location different from the default, possibly due to its loader requiring it.")); diff --git a/launcher/ui/dialogs/UpdateAvailableDialog.cpp b/launcher/ui/dialogs/UpdateAvailableDialog.cpp index 810a1f089..f288fe760 100644 --- a/launcher/ui/dialogs/UpdateAvailableDialog.cpp +++ b/launcher/ui/dialogs/UpdateAvailableDialog.cpp @@ -22,7 +22,6 @@ #include "UpdateAvailableDialog.h" #include -#include "Application.h" #include "BuildConfig.h" #include "Markdown.h" #include "StringUtils.h" @@ -41,7 +40,7 @@ UpdateAvailableDialog::UpdateAvailableDialog(const QString& currentVersion, ui->headerLabel->setText(tr("A new version of %1 is available!").arg(launcherName)); ui->versionAvailableLabel->setText( tr("Version %1 is now available - you have %2 . Would you like to download it now?").arg(availableVersion).arg(currentVersion)); - ui->icon->setPixmap(APPLICATION->getThemedIcon("checkupdate").pixmap(64)); + ui->icon->setPixmap(QIcon::fromTheme("checkupdate").pixmap(64)); auto releaseNotesHtml = markdownToHTML(releaseNotes); ui->releaseNotes->setHtml(StringUtils::htmlListPatch(releaseNotesHtml)); diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index e7c06d048..5967281c4 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -446,7 +446,7 @@ void SkinManageDialog::on_userBtn_clicked() auto uuidLoop = makeShared(); auto profileLoop = makeShared(); - auto getUUID = Net::Download::makeByteArray("https://api.mojang.com/users/profiles/minecraft/" + user, uuidOut); + auto getUUID = Net::Download::makeByteArray("https://api.minecraftservices.com/minecraft/profile/lookup/name/" + user, uuidOut); auto getProfile = Net::Download::makeByteArray(QUrl(), profileOut); auto downloadSkin = Net::Download::makeFile(QUrl(), path); diff --git a/launcher/ui/java/InstallJavaDialog.cpp b/launcher/ui/java/InstallJavaDialog.cpp index 5f69b9d46..1db4971f4 100644 --- a/launcher/ui/java/InstallJavaDialog.cpp +++ b/launcher/ui/java/InstallJavaDialog.cpp @@ -97,7 +97,7 @@ class InstallJavaPage : public QWidget, public BasePage { QString id() const override { return uid; } QString displayName() const override { return name; } - QIcon icon() const override { return APPLICATION->getThemedIcon(iconName); } + QIcon icon() const override { return QIcon::fromTheme(iconName); } void openedImpl() override { @@ -140,9 +140,9 @@ class InstallJavaPage : public QWidget, public BasePage { void recommendedFilterChanged() { if (m_recommend) { - majorVersionSelect->setFilter(BaseVersionList::ModelRoles::JavaMajorRole, new ExactListFilter(m_recommended_majors)); + majorVersionSelect->setFilter(BaseVersionList::ModelRoles::JavaMajorRole, Filters::equalsAny(m_recommended_majors)); } else { - majorVersionSelect->setFilter(BaseVersionList::ModelRoles::JavaMajorRole, new ExactListFilter()); + majorVersionSelect->setFilter(BaseVersionList::ModelRoles::JavaMajorRole, Filters::equalsAny()); } } diff --git a/launcher/ui/pages/global/APIPage.cpp b/launcher/ui/pages/global/APIPage.cpp index a030bf316..3fedbff72 100644 --- a/launcher/ui/pages/global/APIPage.cpp +++ b/launcher/ui/pages/global/APIPage.cpp @@ -76,6 +76,7 @@ APIPage::APIPage(QWidget* parent) : QWidget(parent), ui(new Ui::APIPage) updateBaseURLPlaceholder(ui->pasteTypeComboBox->currentIndex()); // NOTE: this allows http://, but we replace that with https later anyway ui->metaURL->setValidator(new QRegularExpressionValidator(s_validUrlRegExp, ui->metaURL)); + ui->resourceURL->setValidator(new QRegularExpressionValidator(s_validUrlRegExp, ui->resourceURL)); ui->baseURLEntry->setValidator(new QRegularExpressionValidator(s_validUrlRegExp, ui->baseURLEntry)); ui->msaClientID->setValidator(new QRegularExpressionValidator(s_validMSAClientID, ui->msaClientID)); ui->flameKey->setValidator(new QRegularExpressionValidator(s_validFlameKey, ui->flameKey)); @@ -137,6 +138,8 @@ void APIPage::loadSettings() ui->msaClientID->setText(msaClientID); QString metaURL = s->get("MetaURLOverride").toString(); ui->metaURL->setText(metaURL); + QString resourceURL = s->get("ResourceURL").toString(); + ui->resourceURL->setText(resourceURL); QString flameKey = s->get("FlameKeyOverride").toString(); ui->flameKey->setText(flameKey); QString modrinthToken = s->get("ModrinthToken").toString(); @@ -156,18 +159,31 @@ void APIPage::applySettings() QString msaClientID = ui->msaClientID->text(); s->set("MSAClientIDOverride", msaClientID); QUrl metaURL(ui->metaURL->text()); + QUrl resourceURL(ui->resourceURL->text()); // Add required trailing slash if (!metaURL.isEmpty() && !metaURL.path().endsWith('/')) { QString path = metaURL.path(); path.append('/'); metaURL.setPath(path); } + + if (!resourceURL.isEmpty() && !resourceURL.path().endsWith('/')) { + QString path = resourceURL.path(); + path.append('/'); + resourceURL.setPath(path); + } // Don't allow HTTP, since meta is basically RCE with all the jar files. if (!metaURL.isEmpty() && metaURL.scheme() == "http") { metaURL.setScheme("https"); } + // Also don't allow HTTP + if (!resourceURL.isEmpty() && resourceURL.scheme() == "http") { + resourceURL.setScheme("https"); + } + s->set("MetaURLOverride", metaURL.toString()); + s->set("ResourceURL", resourceURL.toString()); QString flameKey = ui->flameKey->text(); s->set("FlameKeyOverride", flameKey); QString modrinthToken = ui->modrinthToken->text(); diff --git a/launcher/ui/pages/global/APIPage.h b/launcher/ui/pages/global/APIPage.h index 9252a9ab3..7a22aa069 100644 --- a/launcher/ui/pages/global/APIPage.h +++ b/launcher/ui/pages/global/APIPage.h @@ -39,7 +39,6 @@ #include -#include #include "ui/pages/BasePage.h" namespace Ui { @@ -54,7 +53,7 @@ class APIPage : public QWidget, public BasePage { ~APIPage(); QString displayName() const override { return tr("Services"); } - QIcon icon() const override { return APPLICATION->getThemedIcon("worlds"); } + QIcon icon() const override { return QIcon::fromTheme("worlds"); } QString id() const override { return "apis"; } QString helpPage() const override { return "APIs"; } virtual bool apply() override; diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui index c6a4593fc..abc19f006 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -129,6 +129,38 @@ + + + + Assets Server + + + + + + You can set this to another server if you have problems with downloading assets. + + + Qt::RichText + + + true + + + true + + + + + + + Use Default + + + + + + @@ -170,7 +202,7 @@ - &Microsoft Authentation + &Microsoft Authentication Qt::RichText diff --git a/launcher/ui/pages/global/AccountListPage.h b/launcher/ui/pages/global/AccountListPage.h index 7bd5101c0..2841b9456 100644 --- a/launcher/ui/pages/global/AccountListPage.h +++ b/launcher/ui/pages/global/AccountListPage.h @@ -41,7 +41,6 @@ #include "ui/pages/BasePage.h" -#include "Application.h" #include "minecraft/auth/AccountList.h" namespace Ui { @@ -59,9 +58,9 @@ class AccountListPage : public QMainWindow, public BasePage { QString displayName() const override { return tr("Accounts"); } QIcon icon() const override { - auto icon = APPLICATION->getThemedIcon("accounts"); + auto icon = QIcon::fromTheme("accounts"); if (icon.isNull()) { - icon = APPLICATION->getThemedIcon("noaccount"); + icon = QIcon::fromTheme("noaccount"); } return icon; } diff --git a/launcher/ui/pages/global/AppearancePage.h b/launcher/ui/pages/global/AppearancePage.h index 29b2d34bf..2220db2cd 100644 --- a/launcher/ui/pages/global/AppearancePage.h +++ b/launcher/ui/pages/global/AppearancePage.h @@ -37,7 +37,6 @@ #include #include -#include "Application.h" #include "java/JavaChecker.h" #include "translations/TranslationsModel.h" #include "ui/pages/BasePage.h" @@ -53,7 +52,7 @@ class AppearancePage : public AppearanceWidget, public BasePage { explicit AppearancePage(QWidget* parent = nullptr) : AppearanceWidget(false, parent) { layout()->setContentsMargins(0, 0, 6, 0); } QString displayName() const override { return tr("Appearance"); } - QIcon icon() const override { return APPLICATION->getThemedIcon("appearance"); } + QIcon icon() const override { return QIcon::fromTheme("appearance"); } QString id() const override { return "appearance-settings"; } QString helpPage() const override { return "Launcher-settings"; } diff --git a/launcher/ui/pages/global/ExternalToolsPage.h b/launcher/ui/pages/global/ExternalToolsPage.h index 377488ccf..702ace557 100644 --- a/launcher/ui/pages/global/ExternalToolsPage.h +++ b/launcher/ui/pages/global/ExternalToolsPage.h @@ -37,7 +37,6 @@ #include -#include #include "ui/pages/BasePage.h" namespace Ui { @@ -54,9 +53,9 @@ class ExternalToolsPage : public QWidget, public BasePage { QString displayName() const override { return tr("Tools"); } QIcon icon() const override { - auto icon = APPLICATION->getThemedIcon("externaltools"); + auto icon = QIcon::fromTheme("externaltools"); if (icon.isNull()) { - icon = APPLICATION->getThemedIcon("loadermods"); + icon = QIcon::fromTheme("loadermods"); } return icon; } diff --git a/launcher/ui/pages/global/JavaPage.h b/launcher/ui/pages/global/JavaPage.h index b30fa22e3..79a3d1b96 100644 --- a/launcher/ui/pages/global/JavaPage.h +++ b/launcher/ui/pages/global/JavaPage.h @@ -35,7 +35,6 @@ #pragma once -#include #include #include #include @@ -57,7 +56,7 @@ class JavaPage : public QWidget, public BasePage { ~JavaPage(); QString displayName() const override { return tr("Java"); } - QIcon icon() const override { return APPLICATION->getThemedIcon("java"); } + QIcon icon() const override { return QIcon::fromTheme("java"); } QString id() const override { return "java-settings"; } QString helpPage() const override { return "Java-settings"; } void retranslate() override; diff --git a/launcher/ui/pages/global/JavaPage.ui b/launcher/ui/pages/global/JavaPage.ui index a40e38868..3ed28cf30 100644 --- a/launcher/ui/pages/global/JavaPage.ui +++ b/launcher/ui/pages/global/JavaPage.ui @@ -24,7 +24,7 @@ 0 - 0 + 6 0 @@ -50,7 +50,7 @@ 0 0 535 - 606 + 612 diff --git a/launcher/ui/pages/global/LanguagePage.cpp b/launcher/ui/pages/global/LanguagePage.cpp index af6fc1727..f4be75782 100644 --- a/launcher/ui/pages/global/LanguagePage.cpp +++ b/launcher/ui/pages/global/LanguagePage.cpp @@ -37,6 +37,7 @@ #include "LanguagePage.h" #include +#include "Application.h" #include "ui/widgets/LanguageSelectionWidget.h" LanguagePage::LanguagePage(QWidget* parent) : QWidget(parent) diff --git a/launcher/ui/pages/global/LanguagePage.h b/launcher/ui/pages/global/LanguagePage.h index ff7ce7ddc..b376e1cf2 100644 --- a/launcher/ui/pages/global/LanguagePage.h +++ b/launcher/ui/pages/global/LanguagePage.h @@ -36,7 +36,6 @@ #pragma once -#include #include #include #include "ui/pages/BasePage.h" @@ -51,7 +50,7 @@ class LanguagePage : public QWidget, public BasePage { virtual ~LanguagePage(); QString displayName() const override { return tr("Language"); } - QIcon icon() const override { return APPLICATION->getThemedIcon("language"); } + QIcon icon() const override { return QIcon::fromTheme("language"); } QString id() const override { return "language-settings"; } QString helpPage() const override { return "Language-settings"; } bool apply() override; diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 132a2c320..7a0f11c83 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -65,15 +65,6 @@ enum InstSortMode { Sort_LastLaunch }; -enum InstRenamingMode { - // Rename metadata only. - Rename_Always, - // Ask everytime. - Rename_Ask, - // Rename physical directory too. - Rename_Never -}; - LauncherPage::LauncherPage(QWidget* parent) : QWidget(parent), ui(new Ui::LauncherPage) { ui->setupUi(this); @@ -242,18 +233,12 @@ void LauncherPage::applySettings() break; } - auto renamingMode = (InstRenamingMode)ui->renamingBehaviorComboBox->currentIndex(); - switch (renamingMode) { - case Rename_Always: - s->set("InstRenamingMode", "MetadataOnly"); - break; - case Rename_Never: - s->set("InstRenamingMode", "PhysicalDir"); - break; - case Rename_Ask: - default: - s->set("InstRenamingMode", "AskEverytime"); - break; + if (ui->askToRenameDirBtn->isChecked()) { + s->set("InstRenamingMode", "AskEverytime"); + } else if (ui->alwaysRenameDirBtn->isChecked()) { + s->set("InstRenamingMode", "PhysicalDir"); + } else if (ui->neverRenameDirBtn->isChecked()) { + s->set("InstRenamingMode", "MetadataOnly"); } // Mods @@ -300,15 +285,9 @@ void LauncherPage::loadSettings() } QString renamingMode = s->get("InstRenamingMode").toString(); - InstRenamingMode renamingModeEnum; - if (renamingMode == "MetadataOnly") { - renamingModeEnum = Rename_Always; - } else if (renamingMode == "PhysicalDir") { - renamingModeEnum = Rename_Never; - } else { - renamingModeEnum = Rename_Ask; - } - ui->renamingBehaviorComboBox->setCurrentIndex(renamingModeEnum); + ui->askToRenameDirBtn->setChecked(renamingMode == "AskEverytime"); + ui->alwaysRenameDirBtn->setChecked(renamingMode == "PhysicalDir"); + ui->neverRenameDirBtn->setChecked(renamingMode == "MetadataOnly"); // Mods ui->metadataEnableBtn->setChecked(!s->get("ModMetadataDisabled").toBool()); diff --git a/launcher/ui/pages/global/LauncherPage.h b/launcher/ui/pages/global/LauncherPage.h index d76c84b63..263bf08bb 100644 --- a/launcher/ui/pages/global/LauncherPage.h +++ b/launcher/ui/pages/global/LauncherPage.h @@ -38,7 +38,6 @@ #include #include -#include #include #include "java/JavaChecker.h" #include "ui/pages/BasePage.h" @@ -58,7 +57,7 @@ class LauncherPage : public QWidget, public BasePage { ~LauncherPage(); QString displayName() const override { return tr("General"); } - QIcon icon() const override { return APPLICATION->getThemedIcon("settings"); } + QIcon icon() const override { return QIcon::fromTheme("settings"); } QString id() const override { return "launcher-settings"; } QString helpPage() const override { return "Launcher-settings"; } bool apply() override; diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 55478e6a0..0debe3f4d 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -32,7 +32,7 @@ - Qt::ScrollBarAsNeeded + Qt::ScrollBarPolicy::ScrollBarAsNeeded true @@ -41,9 +41,9 @@ 0 - -356 - 742 - 1148 + 0 + 746 + 1194 @@ -86,10 +86,10 @@ - Qt::Vertical + Qt::Orientation::Vertical - QSizePolicy::Fixed + QSizePolicy::Policy::Fixed @@ -107,37 +107,42 @@ - - - - 0 - 0 - + + + Ask what to do - - - Ask what to do with the folder - - - - - Always rename the folder - - - - - Never rename the folder—only the displayed name - - + + renamingBehaviorGroup + + + + + + + Always rename the folder + + + renamingBehaviorGroup + + + + + + + Never rename the folder + + + renamingBehaviorGroup + - Qt::Vertical + Qt::Orientation::Vertical - QSizePolicy::Fixed + QSizePolicy::Policy::Fixed @@ -206,7 +211,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -433,7 +438,7 @@ - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter @@ -602,7 +607,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -618,7 +623,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -636,9 +641,6 @@ scrollArea - sortByNameBtn - sortLastLaunchedBtn - renamingBehaviorComboBox preferMenuBarCheckBox autoUpdateCheckBox updateIntervalSpinBox @@ -670,5 +672,6 @@ + diff --git a/launcher/ui/pages/global/MinecraftPage.h b/launcher/ui/pages/global/MinecraftPage.h index b21862536..c21d59a6b 100644 --- a/launcher/ui/pages/global/MinecraftPage.h +++ b/launcher/ui/pages/global/MinecraftPage.h @@ -38,7 +38,6 @@ #include #include -#include "Application.h" #include "java/JavaChecker.h" #include "ui/pages/BasePage.h" #include "ui/widgets/MinecraftSettingsWidget.h" @@ -53,7 +52,7 @@ class MinecraftPage : public MinecraftSettingsWidget, public BasePage { ~MinecraftPage() override {} QString displayName() const override { return tr("Minecraft"); } - QIcon icon() const override { return APPLICATION->getThemedIcon("minecraft"); } + QIcon icon() const override { return QIcon::fromTheme("minecraft"); } QString id() const override { return "minecraft-settings"; } QString helpPage() const override { return "Minecraft-settings"; } bool apply() override diff --git a/launcher/ui/pages/global/ProxyPage.h b/launcher/ui/pages/global/ProxyPage.h index 26118f181..8689a5c80 100644 --- a/launcher/ui/pages/global/ProxyPage.h +++ b/launcher/ui/pages/global/ProxyPage.h @@ -40,7 +40,6 @@ #include #include -#include #include "ui/pages/BasePage.h" namespace Ui { @@ -55,7 +54,7 @@ class ProxyPage : public QWidget, public BasePage { ~ProxyPage(); QString displayName() const override { return tr("Proxy"); } - QIcon icon() const override { return APPLICATION->getThemedIcon("proxy"); } + QIcon icon() const override { return QIcon::fromTheme("proxy"); } QString id() const override { return "proxy-settings"; } QString helpPage() const override { return "Proxy-settings"; } bool apply() override; diff --git a/launcher/ui/pages/global/ProxyPage.ui b/launcher/ui/pages/global/ProxyPage.ui index dec8d0a26..436a90ad1 100644 --- a/launcher/ui/pages/global/ProxyPage.ui +++ b/launcher/ui/pages/global/ProxyPage.ui @@ -51,7 +51,7 @@ Uses your system's default proxy settings. - Use S&ystem Settings + Use s&ystem settings proxyGroup diff --git a/launcher/ui/pages/instance/DataPackPage.h b/launcher/ui/pages/instance/DataPackPage.h index 6676c165a..bed84540a 100644 --- a/launcher/ui/pages/instance/DataPackPage.h +++ b/launcher/ui/pages/instance/DataPackPage.h @@ -28,8 +28,8 @@ class DataPackPage : public ExternalResourcesPage { public: explicit DataPackPage(BaseInstance* instance, std::shared_ptr model, QWidget* parent = nullptr); - QString displayName() const override { return QObject::tr("Data packs"); } - QIcon icon() const override { return APPLICATION->getThemedIcon("datapacks"); } + QString displayName() const override { return QObject::tr("Data Packs"); } + QIcon icon() const override { return QIcon::fromTheme("datapacks"); } QString id() const override { return "datapacks"; } QString helpPage() const override { return "Data-packs"; } bool shouldDisplay() const override { return true; } diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp index d38d16284..d0fb2347b 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp +++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp @@ -52,7 +52,7 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared { ui->setupUi(this); - ui->actionsToolbar->insertSpacer(ui->actionViewConfigs); + ui->actionsToolbar->insertSpacer(ui->actionViewFolder); m_filterModel = model->createFilterProxyModel(this); m_filterModel->setDynamicSortFilter(true); diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.ui b/launcher/ui/pages/instance/ExternalResourcesPage.ui index 5df8aafa2..c6955d0ce 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.ui +++ b/launcher/ui/pages/instance/ExternalResourcesPage.ui @@ -24,31 +24,7 @@ 0 - - - - - - - - - Filter: - - - - - - - - - - 0 - 0 - - - - - + @@ -60,13 +36,30 @@ true - QAbstractItemView::DropOnly + QAbstractItemView::DragDropMode::DropOnly true + + + + + 0 + 0 + + + + + + + + Search + + + @@ -74,7 +67,7 @@ Actions - Qt::ToolButtonIconOnly + Qt::ToolButtonStyle::ToolButtonIconOnly true @@ -92,7 +85,6 @@ - @@ -179,7 +171,7 @@ Reset Update Metadata
- QAction::NoRole + QAction::MenuRole::NoRole @@ -187,7 +179,7 @@ Verify Dependencies - QAction::NoRole + QAction::MenuRole::NoRole @@ -212,7 +204,7 @@ Change a resource's version. - QAction::NoRole + QAction::MenuRole::NoRole @@ -247,7 +239,6 @@ treeView - filterEdit diff --git a/launcher/ui/pages/instance/GameOptionsPage.h b/launcher/ui/pages/instance/GameOptionsPage.h new file mode 100644 index 000000000..43f91976c --- /dev/null +++ b/launcher/ui/pages/instance/GameOptionsPage.h @@ -0,0 +1,69 @@ +// 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. + */ + +#pragma once + +#include +#include + +#include "ui/pages/BasePage.h" + +namespace Ui { +class GameOptionsPage; +} + +class GameOptions; +class MinecraftInstance; + +class GameOptionsPage : public QWidget, public BasePage { + Q_OBJECT + + public: + explicit GameOptionsPage(MinecraftInstance* inst, QWidget* parent = 0); + virtual ~GameOptionsPage(); + + void openedImpl() override; + void closedImpl() override; + + virtual QString displayName() const override { return tr("Game Options"); } + virtual QIcon icon() const override { return QIcon::fromTheme("settings"); } + virtual QString id() const override { return "gameoptions"; } + virtual QString helpPage() const override { return "Game-Options-management"; } + void retranslate() override; + + private: // data + Ui::GameOptionsPage* ui = nullptr; + std::shared_ptr m_model; +}; diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.h b/launcher/ui/pages/instance/InstanceSettingsPage.h index 2c507e84b..aca47e2c7 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.h +++ b/launcher/ui/pages/instance/InstanceSettingsPage.h @@ -36,7 +36,6 @@ #pragma once #include -#include "Application.h" #include "BaseInstance.h" #include "ui/pages/BasePage.h" #include "ui/widgets/MinecraftSettingsWidget.h" @@ -53,7 +52,7 @@ class InstanceSettingsPage : public MinecraftSettingsWidget, public BasePage { } ~InstanceSettingsPage() override {} QString displayName() const override { return tr("Settings"); } - QIcon icon() const override { return APPLICATION->getThemedIcon("instance-settings"); } + QIcon icon() const override { return QIcon::fromTheme("instance-settings"); } QString id() const override { return "settings"; } bool apply() override { diff --git a/launcher/ui/pages/instance/LogPage.cpp b/launcher/ui/pages/instance/LogPage.cpp index 9a7ce6039..928368236 100644 --- a/launcher/ui/pages/instance/LogPage.cpp +++ b/launcher/ui/pages/instance/LogPage.cpp @@ -131,7 +131,6 @@ QModelIndex LogFormatProxyModel::find(const QModelIndex& start, const QString& v LogPage::LogPage(InstancePtr instance, QWidget* parent) : QWidget(parent), ui(new Ui::LogPage), m_instance(instance) { ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); m_proxy = new LogFormatProxyModel(this); diff --git a/launcher/ui/pages/instance/LogPage.h b/launcher/ui/pages/instance/LogPage.h index b4d74fb9c..636a8b70d 100644 --- a/launcher/ui/pages/instance/LogPage.h +++ b/launcher/ui/pages/instance/LogPage.h @@ -38,7 +38,6 @@ #include #include -#include #include "BaseInstance.h" #include "launch/LaunchTask.h" #include "ui/pages/BasePage.h" @@ -67,7 +66,7 @@ class LogPage : public QWidget, public BasePage { explicit LogPage(InstancePtr instance, QWidget* parent = 0); virtual ~LogPage(); virtual QString displayName() const override { return tr("Minecraft Log"); } - virtual QIcon icon() const override { return APPLICATION->getThemedIcon("log"); } + virtual QIcon icon() const override { return QIcon::fromTheme("log"); } virtual QString id() const override { return "console"; } virtual bool apply() override; virtual QString helpPage() const override { return "Minecraft-Logs"; } diff --git a/launcher/ui/pages/instance/LogPage.ui b/launcher/ui/pages/instance/LogPage.ui index fb8690581..2362e19c0 100644 --- a/launcher/ui/pages/instance/LogPage.ui +++ b/launcher/ui/pages/instance/LogPage.ui @@ -10,161 +10,153 @@ 782 - + 0 0 - - 0 - 0 - - - - 0 + + + + false + + + true + + + + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + false + + + + + + + + + Keep updating + + + true + + + + + + + Wrap lines + + + true + + + + + + + Color lines + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Copy the whole log into the clipboard + + + &Copy + + + + + + + Upload the log to the paste service configured in preferences + + + Upload + + + + + + + Clear the log + + + Clear + + + + + + + + + + 0 + 0 + + + + Find + + + + + + + + 0 + 0 + + + + Scroll all the way to bottom + + + Bottom + + + + + + + Qt::Vertical + + + + + + + Search - - - Tab 1 - - - - - - false - - - true - - - - - - Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - false - - - - - - - - - Keep updating - - - true - - - - - - - Wrap lines - - - true - - - - - - - Color lines - - - true - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Copy the whole log into the clipboard - - - &Copy - - - - - - - Upload the log to the paste service configured in preferences - - - Upload - - - - - - - Clear the log - - - Clear - - - - - - - - - Search: - - - - - - - Find - - - - - - - - - - Scroll all the way to bottom - - - Bottom - - - - - - - Qt::Vertical - - - - - @@ -177,7 +169,6 @@ - tabWidget trackLogCheckbox wrapCheckbox colorCheckbox @@ -185,7 +176,6 @@ btnPaste btnClear text - searchBar findButton diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index a95762b5d..e97935650 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -22,8 +22,6 @@ #include "Markdown.h" #include "StringUtils.h" -#include "modplatform/modrinth/ModrinthPackManifest.h" - #include "ui/InstanceWindow.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/ProgressDialog.h" @@ -168,7 +166,7 @@ QString ManagedPackPage::displayName() const QIcon ManagedPackPage::icon() const { - return APPLICATION->getThemedIcon(m_inst->getManagedPackType()); + return QIcon::fromTheme(m_inst->getManagedPackType()); } QString ManagedPackPage::helpPage() const @@ -213,7 +211,7 @@ bool ManagedPackPage::runUpdateTask(InstanceTask* task) void ManagedPackPage::suggestVersion() { - ui->updateButton->setText(tr("Update pack")); + ui->updateButton->setText(tr("Update Pack")); ui->updateButton->setDisabled(false); } @@ -256,36 +254,13 @@ void ModrinthManagedPackPage::parseManagedPack() if (m_fetch_job && m_fetch_job->isRunning()) m_fetch_job->abort(); - m_fetch_job.reset(new NetJob(QString("Modrinth::PackVersions(%1)").arg(m_inst->getManagedPackName()), APPLICATION->network())); - auto response = std::make_shared(); + ResourceAPI::Callback> callbacks{}; + m_pack = { m_inst->getManagedPackID() }; - QString id = m_inst->getManagedPackID(); - - m_fetch_job->addNetAction( - Net::ApiDownload::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response)); - - connect(m_fetch_job.get(), &NetJob::succeeded, this, [this, response, id] { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from Modrinth at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qWarning() << *response; - - setFailState(); - - return; - } - - try { - Modrinth::loadIndexedVersions(m_pack, doc); - } catch (const JSONValidationError& e) { - qDebug() << *response; - qWarning() << "Error while reading modrinth modpack version: " << e.cause(); - - setFailState(); - return; - } + // Use default if no callbacks are set + callbacks.on_succeed = [this](auto& doc) { + m_pack.versions = doc; + m_pack.versionsLoaded = true; // We block signals here so that suggestVersion() doesn't get called, causing an assertion fail. ui->versionsComboBox->blockSignals(true); @@ -293,22 +268,23 @@ void ModrinthManagedPackPage::parseManagedPack() ui->versionsComboBox->blockSignals(false); for (const auto& version : m_pack.versions) { - QString name = Modrinth::getVersionDisplayString(version); + QString name = version.getVersionDisplayString(); // NOTE: the id from version isn't the same id in the modpack format spec... // e.g. HexMC's 4.4.0 has versionId 4.0.0 in the modpack index.............. if (version.version == m_inst->getManagedPackVersionName()) name = tr("%1 (Current)").arg(name); - ui->versionsComboBox->addItem(name, QVariant(version.id)); + ui->versionsComboBox->addItem(name, version.fileId); } suggestVersion(); m_loaded = true; - }); - connect(m_fetch_job.get(), &NetJob::failed, this, &ModrinthManagedPackPage::setFailState); - connect(m_fetch_job.get(), &NetJob::aborted, this, &ModrinthManagedPackPage::setFailState); + }; + callbacks.on_fail = [this](QString reason, int) { setFailState(); }; + callbacks.on_abort = [this]() { setFailState(); }; + m_fetch_job = m_api.getProjectVersions({ m_pack, {}, {}, ModPlatform::ResourceType::Modpack }, std::move(callbacks)); ui->changelogTextBrowser->setText(tr("Fetching changelogs...")); @@ -370,10 +346,10 @@ void ModrinthManagedPackPage::update() QMap extra_info; // NOTE: Don't use 'm_pack.id' here, since we didn't completely parse all the metadata for the pack, including this field. extra_info.insert("pack_id", m_inst->getManagedPackID()); - extra_info.insert("pack_version_id", version.id); + extra_info.insert("pack_version_id", version.fileId.toString()); extra_info.insert("original_instance_id", m_inst->id()); - auto extracted = new InstanceImportTask(version.download_url, this, std::move(extra_info)); + auto extracted = new InstanceImportTask(version.downloadUrl, this, std::move(extra_info)); InstanceName inst_name(m_inst->getManagedPackName(), version.version); inst_name.setName(m_inst->name().replace(m_inst->getManagedPackVersionName(), version.version)); @@ -449,37 +425,15 @@ void FlameManagedPackPage::parseManagedPack() if (m_fetch_job && m_fetch_job->isRunning()) m_fetch_job->abort(); - m_fetch_job.reset(new NetJob(QString("Flame::PackVersions(%1)").arg(m_inst->getManagedPackName()), APPLICATION->network())); - auto response = std::make_shared(); - QString id = m_inst->getManagedPackID(); + m_pack = { id }; - m_fetch_job->addNetAction(Net::ApiDownload::makeByteArray(QString("%1/mods/%2/files").arg(BuildConfig.FLAME_BASE_URL, id), response)); + ResourceAPI::Callback> callbacks{}; - connect(m_fetch_job.get(), &NetJob::succeeded, this, [this, response, id] { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from Flame at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qWarning() << *response; - - setFailState(); - - return; - } - - try { - auto obj = doc.object(); - auto data = Json::ensureArray(obj, "data"); - Flame::loadIndexedPackVersions(m_pack, data); - } catch (const JSONValidationError& e) { - qDebug() << *response; - qWarning() << "Error while reading flame modpack version: " << e.cause(); - - setFailState(); - return; - } + // Use default if no callbacks are set + callbacks.on_succeed = [this](auto& doc) { + m_pack.versions = doc; + m_pack.versionsLoaded = true; // We block signals here so that suggestVersion() doesn't get called, causing an assertion fail. ui->versionsComboBox->blockSignals(true); @@ -487,7 +441,7 @@ void FlameManagedPackPage::parseManagedPack() ui->versionsComboBox->blockSignals(false); for (const auto& version : m_pack.versions) { - QString name = Flame::getVersionDisplayString(version); + QString name = version.getVersionDisplayString(); if (version.fileId == m_inst->getManagedPackVersionID().toInt()) name = tr("%1 (Current)").arg(name); @@ -498,9 +452,10 @@ void FlameManagedPackPage::parseManagedPack() suggestVersion(); m_loaded = true; - }); - connect(m_fetch_job.get(), &NetJob::failed, this, &FlameManagedPackPage::setFailState); - connect(m_fetch_job.get(), &NetJob::aborted, this, &FlameManagedPackPage::setFailState); + }; + callbacks.on_fail = [this](QString reason, int) { setFailState(); }; + callbacks.on_abort = [this]() { setFailState(); }; + m_fetch_job = m_api.getProjectVersions({ m_pack, {}, {}, ModPlatform::ResourceType::Modpack }, std::move(callbacks)); m_fetch_job->start(); } @@ -521,7 +476,7 @@ void FlameManagedPackPage::suggestVersion() auto version = m_pack.versions.at(index); ui->changelogTextBrowser->setHtml( - StringUtils::htmlListPatch(m_api.getModFileChangelog(m_inst->getManagedPackID().toInt(), version.fileId))); + StringUtils::htmlListPatch(m_api.getModFileChangelog(m_inst->getManagedPackID().toInt(), version.fileId.toInt()))); ManagedPackPage::suggestVersion(); } @@ -537,7 +492,7 @@ void FlameManagedPackPage::update() QMap extra_info; extra_info.insert("pack_id", m_inst->getManagedPackID()); - extra_info.insert("pack_version_id", QString::number(version.fileId)); + extra_info.insert("pack_version_id", version.fileId.toString()); extra_info.insert("original_instance_id", m_inst->id()); auto extracted = new InstanceImportTask(version.downloadUrl, this, std::move(extra_info)); diff --git a/launcher/ui/pages/instance/ManagedPackPage.h b/launcher/ui/pages/instance/ManagedPackPage.h index e8d304c6b..f319ed069 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.h +++ b/launcher/ui/pages/instance/ManagedPackPage.h @@ -6,11 +6,10 @@ #include "BaseInstance.h" +#include "modplatform/ModIndex.h" #include "modplatform/modrinth/ModrinthAPI.h" -#include "modplatform/modrinth/ModrinthPackManifest.h" #include "modplatform/flame/FlameAPI.h" -#include "modplatform/flame/FlamePackIndex.h" #include "net/NetJob.h" @@ -37,11 +36,11 @@ class ManagedPackPage : public QWidget, public BasePage { static ManagedPackPage* createPage(BaseInstance* inst, QString type, QWidget* parent = nullptr); ~ManagedPackPage() override; - [[nodiscard]] QString displayName() const override; - [[nodiscard]] QIcon icon() const override; - [[nodiscard]] QString helpPage() const override; - [[nodiscard]] QString id() const override { return "managed_pack"; } - [[nodiscard]] bool shouldDisplay() const override; + QString displayName() const override; + QIcon icon() const override; + QString helpPage() const override; + QString id() const override { return "managed_pack"; } + bool shouldDisplay() const override; void openedImpl() override; @@ -55,7 +54,7 @@ class ManagedPackPage : public QWidget, public BasePage { /** URL of the managed pack. * Not the version-specific one. */ - [[nodiscard]] virtual QString url() const { return {}; }; + virtual QString url() const { return {}; }; void setInstanceWindow(InstanceWindow* window) { m_instance_window = window; } @@ -109,7 +108,7 @@ class GenericManagedPackPage final : public ManagedPackPage { ~GenericManagedPackPage() override = default; // TODO: We may want to show this page with some useful info at some point. - [[nodiscard]] bool shouldDisplay() const override { return false; }; + bool shouldDisplay() const override { return false; }; }; class ModrinthManagedPackPage final : public ManagedPackPage { @@ -120,8 +119,8 @@ class ModrinthManagedPackPage final : public ManagedPackPage { ~ModrinthManagedPackPage() override = default; void parseManagedPack() override; - [[nodiscard]] QString url() const override; - [[nodiscard]] QString helpPage() const override { return "modrinth-managed-pack"; } + QString url() const override; + QString helpPage() const override { return "modrinth-managed-pack"; } public slots: void suggestVersion() override; @@ -130,9 +129,9 @@ class ModrinthManagedPackPage final : public ManagedPackPage { void updateFromFile() override; private: - NetJob::Ptr m_fetch_job = nullptr; + Task::Ptr m_fetch_job = nullptr; - Modrinth::Modpack m_pack; + ModPlatform::IndexedPack m_pack; ModrinthAPI m_api; }; @@ -144,8 +143,8 @@ class FlameManagedPackPage final : public ManagedPackPage { ~FlameManagedPackPage() override = default; void parseManagedPack() override; - [[nodiscard]] QString url() const override; - [[nodiscard]] QString helpPage() const override { return "curseforge-managed-pack"; } + QString url() const override; + QString helpPage() const override { return "curseforge-managed-pack"; } public slots: void suggestVersion() override; @@ -154,8 +153,8 @@ class FlameManagedPackPage final : public ManagedPackPage { void updateFromFile() override; private: - NetJob::Ptr m_fetch_job = nullptr; + Task::Ptr m_fetch_job = nullptr; - Flame::IndexedPack m_pack; + ModPlatform::IndexedPack m_pack; FlameAPI m_api; }; diff --git a/launcher/ui/pages/instance/ManagedPackPage.ui b/launcher/ui/pages/instance/ManagedPackPage.ui index 54ff08e94..62641bc82 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.ui +++ b/launcher/ui/pages/instance/ManagedPackPage.ui @@ -12,16 +12,16 @@ - 9 + 0 - 9 + 0 - 9 + 6 - 9 + 0 @@ -34,7 +34,7 @@ - Pack information + Pack Information @@ -42,7 +42,7 @@ - Pack name: + Pack Name: @@ -162,7 +162,7 @@ - Update from file + Update From File diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 7b79766ee..198f336f9 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -106,6 +106,8 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr 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); + + ui->actionsToolbar->insertActionAfter(ui->actionViewFolder, ui->actionViewConfigs); } bool ModFolderPage::shouldDisplay() const diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index 8996b1615..b33992470 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -52,7 +52,7 @@ class ModFolderPage : public ExternalResourcesPage { void setFilter(const QString& filter) { m_fileSelectionFilter = filter; } virtual QString displayName() const override { return tr("Mods"); } - virtual QIcon icon() const override { return APPLICATION->getThemedIcon("loadermods"); } + virtual QIcon icon() const override { return QIcon::fromTheme("loadermods"); } virtual QString id() const override { return "mods"; } virtual QString helpPage() const override { return "Loader-mods"; } @@ -82,8 +82,8 @@ class CoreModFolderPage : public ModFolderPage { explicit CoreModFolderPage(BaseInstance* inst, std::shared_ptr mods, QWidget* parent = 0); virtual ~CoreModFolderPage() = default; - virtual QString displayName() const override { return tr("Core mods"); } - virtual QIcon icon() const override { return APPLICATION->getThemedIcon("coremods"); } + virtual QString displayName() const override { return tr("Core Mods"); } + virtual QIcon icon() const override { return QIcon::fromTheme("coremods"); } virtual QString id() const override { return "coremods"; } virtual QString helpPage() const override { return "Core-mods"; } @@ -97,7 +97,7 @@ class NilModFolderPage : public ModFolderPage { virtual ~NilModFolderPage() = default; virtual QString displayName() const override { return tr("Nilmods"); } - virtual QIcon icon() const override { return APPLICATION->getThemedIcon("coremods"); } + virtual QIcon icon() const override { return QIcon::fromTheme("coremods"); } virtual QString id() const override { return "nilmods"; } virtual QString helpPage() const override { return "Nilmods"; } diff --git a/launcher/ui/pages/instance/NotesPage.h b/launcher/ui/pages/instance/NotesPage.h index 3351d25fc..f11e2ad7c 100644 --- a/launcher/ui/pages/instance/NotesPage.h +++ b/launcher/ui/pages/instance/NotesPage.h @@ -37,7 +37,6 @@ #include -#include #include "BaseInstance.h" #include "ui/pages/BasePage.h" @@ -54,9 +53,9 @@ class NotesPage : public QWidget, public BasePage { virtual QString displayName() const override { return tr("Notes"); } virtual QIcon icon() const override { - auto icon = APPLICATION->getThemedIcon("notes"); + auto icon = QIcon::fromTheme("notes"); if (icon.isNull()) - icon = APPLICATION->getThemedIcon("news"); + icon = QIcon::fromTheme("news"); return icon; } virtual QString id() const override { return "notes"; } diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index 6f98db4a8..69e152475 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -61,7 +61,6 @@ OtherLogsPage::OtherLogsPage(QString id, QString displayName, QString helpPage, , m_logSearchPaths(instance ? instance->getLogFileSearchPaths() : QStringList{ "logs" }) { ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); m_proxy = new LogFormatProxyModel(this); if (m_instance) { diff --git a/launcher/ui/pages/instance/OtherLogsPage.h b/launcher/ui/pages/instance/OtherLogsPage.h index 4104d8f3c..9fc0ba3b9 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.h +++ b/launcher/ui/pages/instance/OtherLogsPage.h @@ -38,7 +38,6 @@ #include #include -#include #include #include "LogPage.h" #include "ui/pages/BasePage.h" @@ -58,7 +57,7 @@ class OtherLogsPage : public QWidget, public BasePage { QString id() const override { return m_id; } QString displayName() const override { return m_displayName; } - QIcon icon() const override { return APPLICATION->getThemedIcon("log"); } + QIcon icon() const override { return QIcon::fromTheme("log"); } QString helpPage() const override { return m_helpPage; } void retranslate() override; diff --git a/launcher/ui/pages/instance/OtherLogsPage.ui b/launcher/ui/pages/instance/OtherLogsPage.ui index 7d60de5c4..77076d4ab 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.ui +++ b/launcher/ui/pages/instance/OtherLogsPage.ui @@ -10,7 +10,7 @@ 538 - + 0 @@ -18,194 +18,189 @@ 0 - 0 + 6 0 - - - - 0 + + + + + 0 + 0 + - - - Tab 1 - - - - - - Search: + + &Find + + + + + + + Qt::Vertical + + + + + + + + 0 + 0 + + + + Scroll all the way to bottom + + + &Bottom + + + + + + + false + + + false + + + true + + + + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + false + + + + + + + + + + + + 0 + 0 + - - - - - - - &Find - - - - - - - Qt::Vertical - - - - - + + - Scroll all the way to bottom + Delete the selected log - &Bottom + &Delete Selected - - - - false + + + + Delete all the logs - - false - - - true - - - - - - Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - false + + Delete &All - - - - - - - - - 0 - 0 - - - - - - - - Delete the selected log - - - &Delete Selected - - - - - - - Delete all the logs - - - Delete &All - - - - - - - - - - - Keep updating - - - true - - - - - - - Wrap lines - - - true - - - - - - - Color lines - - - true - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Copy the whole log into the clipboard - - - &Copy - - - - - - - Upload the log to the paste service configured in preferences - - - &Upload - - - - - - - Reload the contents of the log from the disk - - - &Reload - - - - - - - - + + + + + + + Keep updating + + + true + + + + + + + Wrap lines + + + true + + + + + + + Color lines + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Copy the whole log into the clipboard + + + &Copy + + + + + + + Upload the log to the paste service configured in preferences + + + &Upload + + + + + + + Reload the contents of the log from the disk + + + &Reload + + + + + + + + + + + Search + @@ -218,7 +213,6 @@ - tabWidget selectLogBox btnReload btnCopy diff --git a/launcher/ui/pages/instance/ResourcePackPage.h b/launcher/ui/pages/instance/ResourcePackPage.h index e39d417c9..0ad24fc45 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.h +++ b/launcher/ui/pages/instance/ResourcePackPage.h @@ -50,8 +50,8 @@ class ResourcePackPage : public ExternalResourcesPage { public: explicit ResourcePackPage(MinecraftInstance* instance, std::shared_ptr model, QWidget* parent = 0); - QString displayName() const override { return tr("Resource packs"); } - QIcon icon() const override { return APPLICATION->getThemedIcon("resourcepacks"); } + QString displayName() const override { return tr("Resource Packs"); } + QIcon icon() const override { return QIcon::fromTheme("resourcepacks"); } QString id() const override { return "resourcepacks"; } QString helpPage() const override { return "Resource-packs"; } diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp index 082b44308..9aa7ea50a 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.cpp +++ b/launcher/ui/pages/instance/ScreenshotsPage.cpp @@ -134,7 +134,7 @@ class FilterModel : public QIdentityProxyModel { { m_thumbnailingPool.setMaxThreadCount(4); m_thumbnailCache = std::make_shared(); - m_thumbnailCache->add("placeholder", APPLICATION->getThemedIcon("screenshot-placeholder")); + m_thumbnailCache->add("placeholder", QIcon::fromTheme("screenshot-placeholder")); connect(&watcher, &QFileSystemWatcher::fileChanged, this, &FilterModel::fileChanged); } virtual ~FilterModel() diff --git a/launcher/ui/pages/instance/ScreenshotsPage.h b/launcher/ui/pages/instance/ScreenshotsPage.h index bb127b429..b9c750a1f 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.h +++ b/launcher/ui/pages/instance/ScreenshotsPage.h @@ -37,7 +37,6 @@ #include -#include #include "ui/pages/BasePage.h" #include "settings/Setting.h" @@ -67,7 +66,7 @@ class ScreenshotsPage : public QMainWindow, public BasePage { virtual bool eventFilter(QObject*, QEvent*) override; virtual QString displayName() const override { return tr("Screenshots"); } - virtual QIcon icon() const override { return APPLICATION->getThemedIcon("screenshots"); } + virtual QIcon icon() const override { return QIcon::fromTheme("screenshots"); } virtual QString id() const override { return "screenshots"; } virtual QString helpPage() const override { return "Screenshots-management"; } virtual bool apply() override { return !m_uploadActive; } diff --git a/launcher/ui/pages/instance/ScreenshotsPage.ui b/launcher/ui/pages/instance/ScreenshotsPage.ui index 2e2227a29..db55869cd 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.ui +++ b/launcher/ui/pages/instance/ScreenshotsPage.ui @@ -26,11 +26,17 @@ + + false + - QAbstractItemView::ExtendedSelection + QAbstractItemView::SelectionMode::ExtendedSelection - QAbstractItemView::SelectRows + QAbstractItemView::SelectionBehavior::SelectRows + + + QListView::Movement::Static @@ -41,7 +47,7 @@ Actions - Qt::ToolButtonTextOnly + Qt::ToolButtonStyle::ToolButtonTextOnly RightToolBarArea diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index d1f39bb88..f616a5b22 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -36,6 +36,7 @@ */ #include "ServersPage.h" +#include "Application.h" #include "ServerPingTask.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui_ServersPage.h" @@ -319,7 +320,7 @@ class ServersModel : public QAbstractListModel { if (px.loadFromData(bytes)) return QIcon(px); } - return APPLICATION->getThemedIcon("unknown_server"); + return QIcon::fromTheme("unknown_server"); } case 1: return m_servers[row].m_address; diff --git a/launcher/ui/pages/instance/ServersPage.h b/launcher/ui/pages/instance/ServersPage.h index 77710d6cc..49a746245 100644 --- a/launcher/ui/pages/instance/ServersPage.h +++ b/launcher/ui/pages/instance/ServersPage.h @@ -39,7 +39,7 @@ #include #include -#include +#include "BaseInstance.h" #include "ui/pages/BasePage.h" #include "settings/Setting.h" @@ -63,7 +63,7 @@ class ServersPage : public QMainWindow, public BasePage { void closedImpl() override; virtual QString displayName() const override { return tr("Servers"); } - virtual QIcon icon() const override { return APPLICATION->getThemedIcon("server"); } + virtual QIcon icon() const override { return QIcon::fromTheme("server"); } virtual QString id() const override { return "servers"; } virtual QString helpPage() const override { return "Servers-management"; } void retranslate() override; diff --git a/launcher/ui/pages/instance/ShaderPackPage.h b/launcher/ui/pages/instance/ShaderPackPage.h index f2b141329..c6ae3bc24 100644 --- a/launcher/ui/pages/instance/ShaderPackPage.h +++ b/launcher/ui/pages/instance/ShaderPackPage.h @@ -47,8 +47,8 @@ class ShaderPackPage : public ExternalResourcesPage { explicit ShaderPackPage(MinecraftInstance* instance, std::shared_ptr model, QWidget* parent = nullptr); ~ShaderPackPage() override = default; - QString displayName() const override { return tr("Shader packs"); } - QIcon icon() const override { return APPLICATION->getThemedIcon("shaderpacks"); } + QString displayName() const override { return tr("Shader Packs"); } + QIcon icon() const override { return QIcon::fromTheme("shaderpacks"); } QString id() const override { return "shaderpacks"; } QString helpPage() const override { return "shader-packs"; } diff --git a/launcher/ui/pages/instance/TexturePackPage.h b/launcher/ui/pages/instance/TexturePackPage.h index 3ebca3e87..2c92212b9 100644 --- a/launcher/ui/pages/instance/TexturePackPage.h +++ b/launcher/ui/pages/instance/TexturePackPage.h @@ -51,7 +51,7 @@ class TexturePackPage : public ExternalResourcesPage { explicit TexturePackPage(MinecraftInstance* instance, std::shared_ptr model, QWidget* parent = nullptr); QString displayName() const override { return tr("Texture packs"); } - QIcon icon() const override { return APPLICATION->getThemedIcon("resourcepacks"); } + QIcon icon() const override { return QIcon::fromTheme("resourcepacks"); } QString id() const override { return "texturepacks"; } QString helpPage() const override { return "Texture-packs"; } diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp index d355f38fb..ef5427a00 100644 --- a/launcher/ui/pages/instance/VersionPage.cpp +++ b/launcher/ui/pages/instance/VersionPage.cpp @@ -91,12 +91,12 @@ class IconProxy : public QIdentityProxyModel { if (!var.isNull()) { auto string = var.toString(); if (string == "warning") { - return APPLICATION->getThemedIcon("status-yellow"); + return QIcon::fromTheme("status-yellow"); } else if (string == "error") { - return APPLICATION->getThemedIcon("status-bad"); + return QIcon::fromTheme("status-bad"); } } - return APPLICATION->getThemedIcon("status-good"); + return QIcon::fromTheme("status-good"); } return var; } diff --git a/launcher/ui/pages/instance/VersionPage.ui b/launcher/ui/pages/instance/VersionPage.ui index 9be21d499..d525f56a5 100644 --- a/launcher/ui/pages/instance/VersionPage.ui +++ b/launcher/ui/pages/instance/VersionPage.ui @@ -43,18 +43,11 @@ - - - - - - - - Filter: - - - - + + + Search + + diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index 31f3bfd3e..c7eaf94a0 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -48,10 +48,10 @@ #include #include #include +#include #include #include #include -#include #include "FileSystem.h" #include "tools/MCEditTool.h" @@ -77,7 +77,7 @@ class WorldListProxyModel : public QSortFilterProxyModel { auto iconFile = worlds->data(sourceIndex, WorldList::IconFileRole).toString(); if (iconFile.isNull()) { // NOTE: Minecraft uses the same placeholder for servers AND worlds - return APPLICATION->getThemedIcon("unknown_server"); + return QIcon::fromTheme("unknown_server"); } return QIcon(iconFile); } diff --git a/launcher/ui/pages/instance/WorldListPage.h b/launcher/ui/pages/instance/WorldListPage.h index 08cf7dc5f..9b931066d 100644 --- a/launcher/ui/pages/instance/WorldListPage.h +++ b/launcher/ui/pages/instance/WorldListPage.h @@ -37,7 +37,6 @@ #include -#include #include #include "minecraft/MinecraftInstance.h" #include "ui/pages/BasePage.h" @@ -57,7 +56,7 @@ class WorldListPage : public QMainWindow, public BasePage { virtual ~WorldListPage(); virtual QString displayName() const override { return tr("Worlds"); } - virtual QIcon icon() const override { return APPLICATION->getThemedIcon("worlds"); } + virtual QIcon icon() const override { return QIcon::fromTheme("worlds"); } virtual QString id() const override { return "worlds"; } virtual QString helpPage() const override { return "Worlds"; } virtual bool shouldDisplay() const override; diff --git a/launcher/ui/pages/modplatform/CustomPage.cpp b/launcher/ui/pages/modplatform/CustomPage.cpp index ba22bd2e6..87e126fd7 100644 --- a/launcher/ui/pages/modplatform/CustomPage.cpp +++ b/launcher/ui/pages/modplatform/CustomPage.cpp @@ -104,7 +104,7 @@ void CustomPage::filterChanged() if (ui->experimentsFilter->isChecked()) out << "(experiment)"; auto regexp = out.join('|'); - ui->versionList->setFilter(BaseVersionList::TypeRole, new RegexpFilter(regexp, false)); + ui->versionList->setFilter(BaseVersionList::TypeRole, Filters::regexp(QRegularExpression(regexp))); } void CustomPage::loaderFilterChanged() diff --git a/launcher/ui/pages/modplatform/CustomPage.h b/launcher/ui/pages/modplatform/CustomPage.h index c5d6d5af5..2bfb1de29 100644 --- a/launcher/ui/pages/modplatform/CustomPage.h +++ b/launcher/ui/pages/modplatform/CustomPage.h @@ -37,7 +37,7 @@ #include -#include +#include "BaseVersion.h" #include "tasks/Task.h" #include "ui/pages/BasePage.h" @@ -54,7 +54,7 @@ class CustomPage : public QWidget, public BasePage { explicit CustomPage(NewInstanceDialog* dialog, QWidget* parent = 0); virtual ~CustomPage(); virtual QString displayName() const override { return tr("Custom"); } - virtual QIcon icon() const override { return APPLICATION->getThemedIcon("minecraft"); } + virtual QIcon icon() const override { return QIcon::fromTheme("minecraft"); } virtual QString id() const override { return "vanilla"; } virtual QString helpPage() const override { return "Vanilla-platform"; } virtual bool shouldDisplay() const override; diff --git a/launcher/ui/pages/modplatform/DataPackModel.cpp b/launcher/ui/pages/modplatform/DataPackModel.cpp index 085bd2d53..846ef5aa4 100644 --- a/launcher/ui/pages/modplatform/DataPackModel.cpp +++ b/launcher/ui/pages/modplatform/DataPackModel.cpp @@ -9,8 +9,8 @@ namespace ResourceDownload { -DataPackResourceModel::DataPackResourceModel(BaseInstance const& base_inst, ResourceAPI* api) - : ResourceModel(api), m_base_instance(base_inst) +DataPackResourceModel::DataPackResourceModel(BaseInstance const& base_inst, ResourceAPI* api, QString debugName, QString metaEntryBase) + : ResourceModel(api), m_base_instance(base_inst), m_debugName(debugName + " (Model)"), m_metaEntryBase(metaEntryBase) {} /******** Make data requests ********/ @@ -18,7 +18,7 @@ DataPackResourceModel::DataPackResourceModel(BaseInstance const& base_inst, Reso ResourceAPI::SearchArgs DataPackResourceModel::createSearchArguments() { auto sort = getCurrentSortingMethodByIndex(); - return { ModPlatform::ResourceType::DATA_PACK, m_next_search_offset, m_search_term, sort, ModPlatform::ModLoaderType::DataPack }; + return { ModPlatform::ResourceType::DataPack, m_next_search_offset, m_search_term, sort, ModPlatform::ModLoaderType::DataPack }; } ResourceAPI::VersionSearchArgs DataPackResourceModel::createVersionsArguments(const QModelIndex& entry) diff --git a/launcher/ui/pages/modplatform/DataPackModel.h b/launcher/ui/pages/modplatform/DataPackModel.h index 89e83969c..29b11ffd6 100644 --- a/launcher/ui/pages/modplatform/DataPackModel.h +++ b/launcher/ui/pages/modplatform/DataPackModel.h @@ -21,14 +21,13 @@ class DataPackResourceModel : public ResourceModel { Q_OBJECT public: - DataPackResourceModel(BaseInstance const&, ResourceAPI*); + DataPackResourceModel(BaseInstance const&, ResourceAPI*, QString, QString); /* Ask the API for more information */ void searchWithTerm(const QString& term, unsigned int sort); - void loadIndexedPack(ModPlatform::IndexedPack&, QJsonObject&) override = 0; - void loadExtraPackInfo(ModPlatform::IndexedPack&, QJsonObject&) override = 0; - void loadIndexedPackVersions(ModPlatform::IndexedPack&, QJsonArray&) override = 0; + [[nodiscard]] QString debugName() const override { return m_debugName; } + [[nodiscard]] QString metaEntryBase() const override { return m_metaEntryBase; } public slots: ResourceAPI::SearchArgs createSearchArguments() override; @@ -38,7 +37,9 @@ class DataPackResourceModel : public ResourceModel { protected: const BaseInstance& m_base_instance; - auto documentToArray(QJsonDocument& obj) const -> QJsonArray override = 0; + private: + QString m_debugName; + QString m_metaEntryBase; }; } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/DataPackPage.h b/launcher/ui/pages/modplatform/DataPackPage.h index cf78df96c..431fc9a05 100644 --- a/launcher/ui/pages/modplatform/DataPackPage.h +++ b/launcher/ui/pages/modplatform/DataPackPage.h @@ -34,13 +34,13 @@ class DataPackResourcePage : public ResourcePage { } //: The plural version of 'data pack' - [[nodiscard]] inline QString resourcesString() const override { return tr("data packs"); } + inline QString resourcesString() const override { return tr("data packs"); } //: The singular version of 'data packs' - [[nodiscard]] inline QString resourceString() const override { return tr("data pack"); } + inline QString resourceString() const override { return tr("data pack"); } - [[nodiscard]] bool supportsFiltering() const override { return false; }; + bool supportsFiltering() const override { return false; }; - [[nodiscard]] QMap urlHandlers() const override; + QMap urlHandlers() const override; protected: DataPackResourcePage(DataPackDownloadDialog* dialog, BaseInstance& instance); diff --git a/launcher/ui/pages/modplatform/ImportPage.h b/launcher/ui/pages/modplatform/ImportPage.h index 70d7736eb..1119e709a 100644 --- a/launcher/ui/pages/modplatform/ImportPage.h +++ b/launcher/ui/pages/modplatform/ImportPage.h @@ -37,7 +37,6 @@ #include -#include #include "tasks/Task.h" #include "ui/pages/BasePage.h" @@ -54,7 +53,7 @@ class ImportPage : public QWidget, public BasePage { explicit ImportPage(NewInstanceDialog* dialog, QWidget* parent = 0); virtual ~ImportPage(); virtual QString displayName() const override { return tr("Import"); } - virtual QIcon icon() const override { return APPLICATION->getThemedIcon("viewfolder"); } + virtual QIcon icon() const override { return QIcon::fromTheme("viewfolder"); } virtual QString id() const override { return "import"; } virtual QString helpPage() const override { return "Zip-import"; } virtual bool shouldDisplay() const override; diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 6e98a88bc..c5a03e1fd 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -7,13 +7,16 @@ #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" #include "minecraft/mod/ModFolderModel.h" +#include "modplatform/ModIndex.h" #include #include namespace ResourceDownload { -ModModel::ModModel(BaseInstance& base_inst, ResourceAPI* api) : ResourceModel(api), m_base_instance(base_inst) {} +ModModel::ModModel(BaseInstance& base_inst, ResourceAPI* api, QString debugName, QString metaEntryBase) + : ResourceModel(api), m_base_instance(base_inst), m_debugName(debugName + " (Model)"), m_metaEntryBase(metaEntryBase) +{} /******** Make data requests ********/ @@ -40,7 +43,7 @@ ResourceAPI::SearchArgs ModModel::createSearchArguments() auto sort = getCurrentSortingMethodByIndex(); return { - ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, loaders, versions, side, categories, m_filter->openSource + ModPlatform::ResourceType::Mod, m_next_search_offset, m_search_term, sort, loaders, versions, side, categories, m_filter->openSource }; } @@ -59,7 +62,7 @@ ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(const QModelInd if (m_filter->loaders) loaders = m_filter->loaders; - return { pack, versions, loaders }; + return { pack, versions, loaders, ModPlatform::ResourceType::Mod }; } ResourceAPI::ProjectInfoArgs ModModel::createInfoArguments(const QModelIndex& entry) @@ -101,9 +104,10 @@ QVariant ModModel::getInstalledPackVersion(ModPlatform::IndexedPack::Ptr pack) c return {}; } -bool checkSide(QString filter, QString value) +bool checkSide(ModPlatform::Side filter, ModPlatform::Side value) { - return filter.isEmpty() || value.isEmpty() || filter == "both" || value == "both" || filter == value; + return filter == ModPlatform::Side::NoSide || value == ModPlatform::Side::NoSide || filter == ModPlatform::Side::UniversalSide || + value == ModPlatform::Side::UniversalSide || filter == value; } bool ModModel::checkFilters(ModPlatform::IndexedPack::Ptr pack) diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index bb9255cd0..873d4c1f9 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -24,26 +24,23 @@ class ModModel : public ResourceModel { Q_OBJECT public: - ModModel(BaseInstance&, ResourceAPI* api); + ModModel(BaseInstance&, ResourceAPI* api, QString debugName, QString metaEntryBase); /* Ask the API for more information */ void searchWithTerm(const QString& term, unsigned int sort, bool filter_changed); - void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override = 0; - void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override = 0; - void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override = 0; - virtual ModPlatform::IndexedVersion loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) = 0; - void setFilter(std::shared_ptr filter) { m_filter = filter; } virtual QVariant getInstalledPackVersion(ModPlatform::IndexedPack::Ptr) const override; + [[nodiscard]] QString debugName() const override { return m_debugName; } + [[nodiscard]] QString metaEntryBase() const override { return m_metaEntryBase; } + public slots: ResourceAPI::SearchArgs createSearchArguments() override; ResourceAPI::VersionSearchArgs createVersionsArguments(const QModelIndex&) override; ResourceAPI::ProjectInfoArgs createInfoArguments(const QModelIndex&) override; protected: - auto documentToArray(QJsonDocument& obj) const -> QJsonArray override = 0; virtual bool isPackInstalled(ModPlatform::IndexedPack::Ptr) const override; virtual bool checkFilters(ModPlatform::IndexedPack::Ptr) override; @@ -53,6 +50,10 @@ class ModModel : public ResourceModel { BaseInstance& m_base_instance; std::shared_ptr m_filter = nullptr; + + private: + QString m_debugName; + QString m_metaEntryBase; }; } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index fb9f3f9d3..d3b08cbd9 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -43,17 +43,17 @@ class ModPage : public ResourcePage { } //: The plural version of 'mod' - [[nodiscard]] inline QString resourcesString() const override { return tr("mods"); } + inline QString resourcesString() const override { return tr("mods"); } //: The singular version of 'mods' - [[nodiscard]] inline QString resourceString() const override { return tr("mod"); } + inline QString resourceString() const override { return tr("mod"); } - [[nodiscard]] QMap urlHandlers() const override; + QMap urlHandlers() const override; void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, std::shared_ptr) override; virtual std::unique_ptr createFilterWidget() = 0; - [[nodiscard]] bool supportsFiltering() const override { return true; }; + bool supportsFiltering() const override { return true; }; auto getFilter() const -> const std::shared_ptr { return m_filter; } void setFilterWidget(std::unique_ptr&); diff --git a/launcher/ui/pages/modplatform/ModpackProviderBasePage.h b/launcher/ui/pages/modplatform/ModpackProviderBasePage.h index a3daa9a81..6cc0b8e99 100644 --- a/launcher/ui/pages/modplatform/ModpackProviderBasePage.h +++ b/launcher/ui/pages/modplatform/ModpackProviderBasePage.h @@ -25,5 +25,5 @@ class ModpackProviderBasePage : public BasePage { /** Programatically set the term in the search bar. */ virtual void setSearchTerm(QString) = 0; /** Get the current term in the search bar. */ - [[nodiscard]] virtual QString getSerachTerm() const = 0; + virtual QString getSerachTerm() const = 0; }; \ No newline at end of file diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index 8e3be5e8f..eea7af25e 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -15,8 +15,8 @@ #include "Application.h" #include "BuildConfig.h" -#include "Json.h" +#include "modplatform/ResourceAPI.h" #include "net/ApiDownload.h" #include "net/NetJob.h" @@ -65,7 +65,7 @@ auto ResourceModel::data(const QModelIndex& index, int role) const -> QVariant icon_or_none.has_value()) return icon_or_none.value(); - return APPLICATION->getThemedIcon("screenshot-placeholder"); + return QIcon::fromTheme("screenshot-placeholder"); } else { return {}; } @@ -141,9 +141,9 @@ void ResourceModel::search() if (m_search_term.startsWith("#")) { auto projectId = m_search_term.mid(1); if (!projectId.isEmpty()) { - ResourceAPI::ProjectInfoCallbacks callbacks; + ResourceAPI::Callback callbacks; - callbacks.on_fail = [this](QString reason) { + callbacks.on_fail = [this](QString reason, int) { if (!s_running_models.constFind(this).value()) return; searchRequestFailed(reason, -1); @@ -154,10 +154,10 @@ void ResourceModel::search() searchRequestAborted(); }; - callbacks.on_succeed = [this](auto& doc, auto& pack) { + callbacks.on_succeed = [this](auto& pack) { if (!s_running_models.constFind(this).value()) return; - searchRequestForOneSucceeded(doc); + searchRequestForOneSucceeded(pack); }; if (auto job = m_api->getProjectInfo({ projectId }, std::move(callbacks)); job) runSearchJob(job); @@ -166,27 +166,23 @@ void ResourceModel::search() } auto args{ createSearchArguments() }; - auto callbacks{ createSearchCallbacks() }; + ResourceAPI::Callback> callbacks{}; - // Use defaults if no callbacks are set - if (!callbacks.on_succeed) - callbacks.on_succeed = [this](auto& doc) { - if (!s_running_models.constFind(this).value()) - return; - searchRequestSucceeded(doc); - }; - if (!callbacks.on_fail) - callbacks.on_fail = [this](QString reason, int network_error_code) { - if (!s_running_models.constFind(this).value()) - return; - searchRequestFailed(reason, network_error_code); - }; - if (!callbacks.on_abort) - callbacks.on_abort = [this] { - if (!s_running_models.constFind(this).value()) - return; - searchRequestAborted(); - }; + callbacks.on_succeed = [this](auto& doc) { + if (!s_running_models.constFind(this).value()) + return; + searchRequestSucceeded(doc); + }; + callbacks.on_fail = [this](QString reason, int network_error_code) { + if (!s_running_models.constFind(this).value()) + return; + searchRequestFailed(reason, network_error_code); + }; + callbacks.on_abort = [this] { + if (!s_running_models.constFind(this).value()) + return; + searchRequestAborted(); + }; if (auto job = m_api->searchProjects(std::move(args), std::move(callbacks)); job) runSearchJob(job); @@ -201,14 +197,15 @@ void ResourceModel::loadEntry(const QModelIndex& entry) if (!pack->versionsLoaded) { auto args{ createVersionsArguments(entry) }; - auto callbacks{ createVersionsCallbacks(entry) }; + ResourceAPI::Callback> callbacks{}; + auto addonId = pack->addonId; // Use default if no callbacks are set if (!callbacks.on_succeed) - callbacks.on_succeed = [this, entry](auto& doc, auto pack) { + callbacks.on_succeed = [this, entry, addonId](auto& doc) { if (!s_running_models.constFind(this).value()) return; - versionRequestSucceeded(doc, pack, entry); + versionRequestSucceeded(doc, addonId, entry); }; if (!callbacks.on_fail) callbacks.on_fail = [](QString reason, int) { @@ -222,28 +219,23 @@ void ResourceModel::loadEntry(const QModelIndex& entry) if (!pack->extraDataLoaded) { auto args{ createInfoArguments(entry) }; - auto callbacks{ createInfoCallbacks(entry) }; + ResourceAPI::Callback callbacks{}; - // Use default if no callbacks are set - if (!callbacks.on_succeed) - callbacks.on_succeed = [this, entry](auto& doc, auto& newpack) { - if (!s_running_models.constFind(this).value()) - return; - auto pack = newpack; - infoRequestSucceeded(doc, pack, entry); - }; - if (!callbacks.on_fail) - callbacks.on_fail = [this](QString reason) { - if (!s_running_models.constFind(this).value()) - return; - QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project info: %1").arg(reason)); - }; - if (!callbacks.on_abort) - callbacks.on_abort = [this] { - if (!s_running_models.constFind(this).value()) - return; - qCritical() << tr("The request was aborted for an unknown reason"); - }; + callbacks.on_succeed = [this, entry](auto& newpack) { + if (!s_running_models.constFind(this).value()) + return; + infoRequestSucceeded(newpack, entry); + }; + callbacks.on_fail = [this](QString reason, int) { + if (!s_running_models.constFind(this).value()) + return; + QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project info: %1").arg(reason)); + }; + callbacks.on_abort = [this] { + if (!s_running_models.constFind(this).value()) + return; + qCritical() << tr("The request was aborted for an unknown reason"); + }; if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job) runInfoJob(job); @@ -358,68 +350,35 @@ std::optional ResourceModel::getIcon(QModelIndex& index, const QUrl& url) return {}; } -// No 'forgor to implement' shall pass here :blobfox_knife: -#define NEED_FOR_CALLBACK_ASSERT(name) \ - Q_ASSERT_X(0 != 0, #name, "You NEED to re-implement this if you intend on using the default callbacks.") - -QJsonArray ResourceModel::documentToArray([[maybe_unused]] QJsonDocument& doc) const -{ - NEED_FOR_CALLBACK_ASSERT("documentToArray"); - return {}; -} -void ResourceModel::loadIndexedPack(ModPlatform::IndexedPack&, QJsonObject&) -{ - NEED_FOR_CALLBACK_ASSERT("loadIndexedPack"); -} -void ResourceModel::loadExtraPackInfo(ModPlatform::IndexedPack&, QJsonObject&) -{ - NEED_FOR_CALLBACK_ASSERT("loadExtraPackInfo"); -} -void ResourceModel::loadIndexedPackVersions(ModPlatform::IndexedPack&, QJsonArray&) -{ - NEED_FOR_CALLBACK_ASSERT("loadIndexedPackVersions"); -} - /* Default callbacks */ -void ResourceModel::searchRequestSucceeded(QJsonDocument& doc) +void ResourceModel::searchRequestSucceeded(QList& newList) { - QList newList; - auto packs = documentToArray(doc); - - for (auto packRaw : packs) { - auto packObj = packRaw.toObject(); - - ModPlatform::IndexedPack::Ptr pack = std::make_shared(); - try { - loadIndexedPack(*pack, packObj); - if (auto sel = std::find_if(m_selected.begin(), m_selected.end(), - [&pack](const DownloadTaskPtr i) { - const auto ipack = i->getPack(); - return ipack->provider == pack->provider && ipack->addonId == pack->addonId; - }); - sel != m_selected.end()) { - newList.append(sel->get()->getPack()); - } else - newList.append(pack); - } catch (const JSONValidationError& e) { - qWarning() << "Error while loading resource from " << debugName() << ": " << e.cause(); - continue; + QList filteredNewList; + for (auto pack : newList) { + ModPlatform::IndexedPack::Ptr p; + if (auto sel = std::find_if(m_selected.begin(), m_selected.end(), + [&pack](const DownloadTaskPtr i) { + const auto ipack = i->getPack(); + return ipack->provider == pack->provider && ipack->addonId == pack->addonId; + }); + sel != m_selected.end()) { + p = sel->get()->getPack(); + } else { + p = pack; + } + if (checkFilters(p)) { + filteredNewList << p; } } - if (packs.size() < 25) { + if (newList.size() < 25) { m_search_state = SearchState::Finished; } else { m_next_search_offset += 25; m_search_state = SearchState::CanFetchMore; } - QList filteredNewList; - for (auto p : newList) - if (checkFilters(p)) - filteredNewList << p; - // When you have a Qt build with assertions turned on, proceeding here will abort the application if (filteredNewList.size() == 0) return; @@ -429,24 +388,12 @@ void ResourceModel::searchRequestSucceeded(QJsonDocument& doc) endInsertRows(); } -void ResourceModel::searchRequestForOneSucceeded(QJsonDocument& doc) +void ResourceModel::searchRequestForOneSucceeded(ModPlatform::IndexedPack& pack) { - ModPlatform::IndexedPack::Ptr pack = std::make_shared(); - - try { - auto obj = Json::requireObject(doc); - if (obj.contains("data")) - obj = Json::requireObject(obj, "data"); - loadIndexedPack(*pack, obj); - } catch (const JSONValidationError& e) { - qDebug() << doc; - qWarning() << "Error while reading " << debugName() << " resource info: " << e.cause(); - } - m_search_state = SearchState::Finished; beginInsertRows(QModelIndex(), m_packs.size(), m_packs.size() + 1); - m_packs.append(pack); + m_packs.append(std::make_shared(pack)); endInsertRows(); } @@ -479,21 +426,16 @@ void ResourceModel::searchRequestAborted() search(); } -void ResourceModel::versionRequestSucceeded(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index) +void ResourceModel::versionRequestSucceeded(QVector& doc, QVariant pack, const QModelIndex& index) { auto current_pack = data(index, Qt::UserRole).value(); // Check if the index is still valid for this resource or not - if (pack.addonId != current_pack->addonId) + if (pack != current_pack->addonId) return; - try { - auto arr = doc.isObject() ? Json::ensureArray(doc.object(), "data") : doc.array(); - loadIndexedPackVersions(*current_pack, arr); - } catch (const JSONValidationError& e) { - qDebug() << doc; - qWarning() << "Error while reading " << debugName() << " resource version: " << e.cause(); - } + current_pack->versions = doc; + current_pack->versionsLoaded = true; // Cache info :^) QVariant new_pack; @@ -506,7 +448,7 @@ void ResourceModel::versionRequestSucceeded(QJsonDocument& doc, ModPlatform::Ind emit versionListUpdated(index); } -void ResourceModel::infoRequestSucceeded(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index) +void ResourceModel::infoRequestSucceeded(ModPlatform::IndexedPack& pack, const QModelIndex& index) { auto current_pack = data(index, Qt::UserRole).value(); @@ -514,14 +456,7 @@ void ResourceModel::infoRequestSucceeded(QJsonDocument& doc, ModPlatform::Indexe if (pack.addonId != current_pack->addonId) return; - try { - auto obj = Json::requireObject(doc); - loadExtraPackInfo(*current_pack, obj); - } catch (const JSONValidationError& e) { - qDebug() << doc; - qWarning() << "Error while reading " << debugName() << " resource info: " << e.cause(); - } - + *current_pack = pack; // Cache info :^) QVariant new_pack; new_pack.setValue(current_pack); diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h index 3f1e633ec..0b56b2b6a 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.h +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -36,25 +36,22 @@ class ResourceModel : public QAbstractListModel { ResourceModel(ResourceAPI* api); ~ResourceModel() override; - [[nodiscard]] auto data(const QModelIndex&, int role) const -> QVariant override; - [[nodiscard]] auto roleNames() const -> QHash override; + auto data(const QModelIndex&, int role) const -> QVariant override; + auto roleNames() const -> QHash override; bool setData(const QModelIndex& index, const QVariant& value, int role) override; - [[nodiscard]] virtual auto debugName() const -> QString; - [[nodiscard]] virtual auto metaEntryBase() const -> QString = 0; + virtual auto debugName() const -> QString; + virtual auto metaEntryBase() const -> QString = 0; - [[nodiscard]] inline int rowCount(const QModelIndex& parent) const override - { - return parent.isValid() ? 0 : static_cast(m_packs.size()); - } - [[nodiscard]] inline int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : 1; } - [[nodiscard]] inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); } + inline int rowCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : static_cast(m_packs.size()); } + inline int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : 1; } + inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); } - [[nodiscard]] bool hasActiveSearchJob() const { return m_current_search_job && m_current_search_job->isRunning(); } - [[nodiscard]] bool hasActiveInfoJob() const { return m_current_info_job.isRunning(); } - [[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? m_current_search_job : nullptr; } + bool hasActiveSearchJob() const { return m_current_search_job && m_current_search_job->isRunning(); } + bool hasActiveInfoJob() const { return m_current_info_job.isRunning(); } + Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? m_current_search_job : nullptr; } - [[nodiscard]] auto getSortingMethods() const { return m_api->getSortingMethods(); } + auto getSortingMethods() const { return m_api->getSortingMethods(); } virtual QVariant getInstalledPackVersion(ModPlatform::IndexedPack::Ptr) const { return {}; } /** Whether the version is opted out or not. Currently only makes sense in CF. */ @@ -69,7 +66,6 @@ class ResourceModel : public QAbstractListModel { public slots: void fetchMore(const QModelIndex& parent) override; - // NOTE: Can't use [[nodiscard]] here because of https://bugreports.qt.io/browse/QTBUG-58628 on Qt 5.12 inline bool canFetchMore(const QModelIndex& parent) const override { return parent.isValid() ? false : m_search_state == SearchState::CanFetchMore; @@ -78,13 +74,10 @@ class ResourceModel : public QAbstractListModel { void setSearchTerm(QString term) { m_search_term = term; } virtual ResourceAPI::SearchArgs createSearchArguments() = 0; - virtual ResourceAPI::SearchCallbacks createSearchCallbacks() { return {}; } virtual ResourceAPI::VersionSearchArgs createVersionsArguments(const QModelIndex&) = 0; - virtual ResourceAPI::VersionSearchCallbacks createVersionsCallbacks(const QModelIndex&) { return {}; } virtual ResourceAPI::ProjectInfoArgs createInfoArguments(const QModelIndex&) = 0; - virtual ResourceAPI::ProjectInfoCallbacks createInfoCallbacks(const QModelIndex&) { return {}; } /** Requests the API for more entries. */ virtual void search(); @@ -113,23 +106,7 @@ class ResourceModel : public QAbstractListModel { void runSearchJob(Task::Ptr); void runInfoJob(Task::Ptr); - [[nodiscard]] auto getCurrentSortingMethodByIndex() const -> std::optional; - - /** Converts a JSON document to a common array format. - * - * This is needed so that different providers, with different JSON structures, can be parsed - * uniformally. You NEED to re-implement this if you intend on using the default callbacks. - */ - [[nodiscard]] virtual auto documentToArray(QJsonDocument&) const -> QJsonArray; - - /** Functions to load data into a pack. - * - * Those are needed for the same reason as documentToArray, and NEED to be re-implemented in the same way. - */ - - virtual void loadIndexedPack(ModPlatform::IndexedPack&, QJsonObject&); - virtual void loadExtraPackInfo(ModPlatform::IndexedPack&, QJsonObject&); - virtual void loadIndexedPackVersions(ModPlatform::IndexedPack&, QJsonArray&); + auto getCurrentSortingMethodByIndex() const -> std::optional; virtual bool isPackInstalled(ModPlatform::IndexedPack::Ptr) const { return false; } @@ -160,14 +137,14 @@ class ResourceModel : public QAbstractListModel { private: /* Default search request callbacks */ - void searchRequestSucceeded(QJsonDocument&); - void searchRequestForOneSucceeded(QJsonDocument&); + void searchRequestSucceeded(QList&); + void searchRequestForOneSucceeded(ModPlatform::IndexedPack&); void searchRequestFailed(QString reason, int network_error_code); void searchRequestAborted(); - void versionRequestSucceeded(QJsonDocument&, ModPlatform::IndexedPack&, const QModelIndex&); + void versionRequestSucceeded(QVector&, QVariant, const QModelIndex&); - void infoRequestSucceeded(QJsonDocument&, ModPlatform::IndexedPack&, const QModelIndex&); + void infoRequestSucceeded(ModPlatform::IndexedPack&, const QModelIndex&); signals: void versionListUpdated(const QModelIndex& index); diff --git a/launcher/ui/pages/modplatform/ResourcePackModel.cpp b/launcher/ui/pages/modplatform/ResourcePackModel.cpp index 0de980ed8..e774c6f64 100644 --- a/launcher/ui/pages/modplatform/ResourcePackModel.cpp +++ b/launcher/ui/pages/modplatform/ResourcePackModel.cpp @@ -8,8 +8,11 @@ namespace ResourceDownload { -ResourcePackResourceModel::ResourcePackResourceModel(BaseInstance const& base_inst, ResourceAPI* api) - : ResourceModel(api), m_base_instance(base_inst) +ResourcePackResourceModel::ResourcePackResourceModel(BaseInstance const& base_inst, + ResourceAPI* api, + QString debugName, + QString metaEntryBase) + : ResourceModel(api), m_base_instance(base_inst), m_debugName(debugName + " (Model)"), m_metaEntryBase(metaEntryBase) {} /******** Make data requests ********/ @@ -17,13 +20,13 @@ ResourcePackResourceModel::ResourcePackResourceModel(BaseInstance const& base_in ResourceAPI::SearchArgs ResourcePackResourceModel::createSearchArguments() { auto sort = getCurrentSortingMethodByIndex(); - return { ModPlatform::ResourceType::RESOURCE_PACK, m_next_search_offset, m_search_term, sort }; + return { ModPlatform::ResourceType::ResourcePack, m_next_search_offset, m_search_term, sort }; } ResourceAPI::VersionSearchArgs ResourcePackResourceModel::createVersionsArguments(const QModelIndex& entry) { auto& pack = m_packs[entry.row()]; - return { *pack }; + return { *pack, {}, {}, ModPlatform::ResourceType::ResourcePack }; } ResourceAPI::ProjectInfoArgs ResourcePackResourceModel::createInfoArguments(const QModelIndex& entry) diff --git a/launcher/ui/pages/modplatform/ResourcePackModel.h b/launcher/ui/pages/modplatform/ResourcePackModel.h index 4f00808e8..d664ccb05 100644 --- a/launcher/ui/pages/modplatform/ResourcePackModel.h +++ b/launcher/ui/pages/modplatform/ResourcePackModel.h @@ -20,14 +20,13 @@ class ResourcePackResourceModel : public ResourceModel { Q_OBJECT public: - ResourcePackResourceModel(BaseInstance const&, ResourceAPI*); + ResourcePackResourceModel(BaseInstance const&, ResourceAPI*, QString debugName, QString metaEntryBase); /* Ask the API for more information */ void searchWithTerm(const QString& term, unsigned int sort); - void loadIndexedPack(ModPlatform::IndexedPack&, QJsonObject&) override = 0; - void loadExtraPackInfo(ModPlatform::IndexedPack&, QJsonObject&) override = 0; - void loadIndexedPackVersions(ModPlatform::IndexedPack&, QJsonArray&) override = 0; + [[nodiscard]] QString debugName() const override { return m_debugName; } + [[nodiscard]] QString metaEntryBase() const override { return m_metaEntryBase; } public slots: ResourceAPI::SearchArgs createSearchArguments() override; @@ -37,7 +36,9 @@ class ResourcePackResourceModel : public ResourceModel { protected: const BaseInstance& m_base_instance; - auto documentToArray(QJsonDocument& obj) const -> QJsonArray override = 0; + private: + QString m_debugName; + QString m_metaEntryBase; }; } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ResourcePackPage.h b/launcher/ui/pages/modplatform/ResourcePackPage.h index 8d967f73a..f8d4d5bf9 100644 --- a/launcher/ui/pages/modplatform/ResourcePackPage.h +++ b/launcher/ui/pages/modplatform/ResourcePackPage.h @@ -33,15 +33,15 @@ class ResourcePackResourcePage : public ResourcePage { } //: The plural version of 'resource pack' - [[nodiscard]] inline QString resourcesString() const override { return tr("resource packs"); } + inline QString resourcesString() const override { return tr("resource packs"); } //: The singular version of 'resource packs' - [[nodiscard]] inline QString resourceString() const override { return tr("resource pack"); } + inline QString resourceString() const override { return tr("resource pack"); } - [[nodiscard]] bool supportsFiltering() const override { return false; }; + bool supportsFiltering() const override { return false; }; - [[nodiscard]] QMap urlHandlers() const override; + QMap urlHandlers() const override; - [[nodiscard]] inline auto helpPage() const -> QString override { return "resourcepack-platform"; } + inline auto helpPage() const -> QString override { return "resourcepack-platform"; } protected: ResourcePackResourcePage(ResourceDownloadDialog* dialog, BaseInstance& instance); diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index 055db441a..8f4d2c496 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -33,37 +33,37 @@ class ResourcePage : public QWidget, public BasePage { ~ResourcePage() override; /* Affects what the user sees */ - [[nodiscard]] auto displayName() const -> QString override = 0; - [[nodiscard]] auto icon() const -> QIcon override = 0; - [[nodiscard]] auto id() const -> QString override = 0; - [[nodiscard]] auto helpPage() const -> QString override = 0; - [[nodiscard]] bool shouldDisplay() const override = 0; + auto displayName() const -> QString override = 0; + auto icon() const -> QIcon override = 0; + auto id() const -> QString override = 0; + auto helpPage() const -> QString override = 0; + bool shouldDisplay() const override = 0; /* Used internally */ - [[nodiscard]] virtual auto metaEntryBase() const -> QString = 0; - [[nodiscard]] virtual auto debugName() const -> QString = 0; + virtual auto metaEntryBase() const -> QString = 0; + virtual auto debugName() const -> QString = 0; //: The plural version of 'resource' - [[nodiscard]] virtual inline QString resourcesString() const { return tr("resources"); } + virtual inline QString resourcesString() const { return tr("resources"); } //: The singular version of 'resources' - [[nodiscard]] virtual inline QString resourceString() const { return tr("resource"); } + virtual inline QString resourceString() const { return tr("resource"); } /* Features this resource's page supports */ - [[nodiscard]] virtual bool supportsFiltering() const = 0; + virtual bool supportsFiltering() const = 0; void retranslate() override; void openedImpl() override; auto eventFilter(QObject* watched, QEvent* event) -> bool override; /** Get the current term in the search bar. */ - [[nodiscard]] auto getSearchTerm() const -> QString; + auto getSearchTerm() const -> QString; /** Programatically set the term in the search bar. */ void setSearchTerm(QString); - [[nodiscard]] bool setCurrentPack(ModPlatform::IndexedPack::Ptr); - [[nodiscard]] auto getCurrentPack() const -> ModPlatform::IndexedPack::Ptr; - [[nodiscard]] auto getDialog() const -> const ResourceDownloadDialog* { return m_parentDialog; } - [[nodiscard]] auto getModel() const -> ResourceModel* { return m_model; } + bool setCurrentPack(ModPlatform::IndexedPack::Ptr); + auto getCurrentPack() const -> ModPlatform::IndexedPack::Ptr; + auto getDialog() const -> const ResourceDownloadDialog* { return m_parentDialog; } + auto getModel() const -> ResourceModel* { return m_model; } protected: ResourcePage(ResourceDownloadDialog* parent, BaseInstance&); @@ -95,8 +95,6 @@ class ResourcePage : public QWidget, public BasePage { void onResourceSelected(); void onResourceToggle(const QModelIndex& index); - // NOTE: Can't use [[nodiscard]] here because of https://bugreports.qt.io/browse/QTBUG-58628 on Qt 5.12 - /** Associates regex expressions to pages in the order they're given in the map. */ virtual QMap urlHandlers() const = 0; virtual void openUrl(const QUrl&); diff --git a/launcher/ui/pages/modplatform/ShaderPackModel.cpp b/launcher/ui/pages/modplatform/ShaderPackModel.cpp index efc6bfaf9..f54a868db 100644 --- a/launcher/ui/pages/modplatform/ShaderPackModel.cpp +++ b/launcher/ui/pages/modplatform/ShaderPackModel.cpp @@ -8,8 +8,8 @@ namespace ResourceDownload { -ShaderPackResourceModel::ShaderPackResourceModel(BaseInstance const& base_inst, ResourceAPI* api) - : ResourceModel(api), m_base_instance(base_inst) +ShaderPackResourceModel::ShaderPackResourceModel(BaseInstance const& base_inst, ResourceAPI* api, QString debugName, QString metaEntryBase) + : ResourceModel(api), m_base_instance(base_inst), m_debugName(debugName + " (Model)"), m_metaEntryBase(metaEntryBase) {} /******** Make data requests ********/ @@ -17,13 +17,13 @@ ShaderPackResourceModel::ShaderPackResourceModel(BaseInstance const& base_inst, ResourceAPI::SearchArgs ShaderPackResourceModel::createSearchArguments() { auto sort = getCurrentSortingMethodByIndex(); - return { ModPlatform::ResourceType::SHADER_PACK, m_next_search_offset, m_search_term, sort }; + return { ModPlatform::ResourceType::ShaderPack, m_next_search_offset, m_search_term, sort }; } ResourceAPI::VersionSearchArgs ShaderPackResourceModel::createVersionsArguments(const QModelIndex& entry) { auto& pack = m_packs[entry.row()]; - return { *pack }; + return { *pack, {}, {}, ModPlatform::ResourceType::ShaderPack }; } ResourceAPI::ProjectInfoArgs ShaderPackResourceModel::createInfoArguments(const QModelIndex& entry) diff --git a/launcher/ui/pages/modplatform/ShaderPackModel.h b/launcher/ui/pages/modplatform/ShaderPackModel.h index 5bb9e58b1..9856be93e 100644 --- a/launcher/ui/pages/modplatform/ShaderPackModel.h +++ b/launcher/ui/pages/modplatform/ShaderPackModel.h @@ -20,14 +20,13 @@ class ShaderPackResourceModel : public ResourceModel { Q_OBJECT public: - ShaderPackResourceModel(BaseInstance const&, ResourceAPI*); + ShaderPackResourceModel(BaseInstance const&, ResourceAPI*, QString debugName, QString metaEntryBase); /* Ask the API for more information */ void searchWithTerm(const QString& term, unsigned int sort); - void loadIndexedPack(ModPlatform::IndexedPack&, QJsonObject&) override = 0; - void loadExtraPackInfo(ModPlatform::IndexedPack&, QJsonObject&) override = 0; - void loadIndexedPackVersions(ModPlatform::IndexedPack&, QJsonArray&) override = 0; + [[nodiscard]] QString debugName() const override { return m_debugName; } + [[nodiscard]] QString metaEntryBase() const override { return m_metaEntryBase; } public slots: ResourceAPI::SearchArgs createSearchArguments() override; @@ -37,7 +36,9 @@ class ShaderPackResourceModel : public ResourceModel { protected: const BaseInstance& m_base_instance; - auto documentToArray(QJsonDocument& obj) const -> QJsonArray override = 0; + private: + QString m_debugName; + QString m_metaEntryBase; }; } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.h b/launcher/ui/pages/modplatform/ShaderPackPage.h index d436e218a..85d2b16e6 100644 --- a/launcher/ui/pages/modplatform/ShaderPackPage.h +++ b/launcher/ui/pages/modplatform/ShaderPackPage.h @@ -33,17 +33,17 @@ class ShaderPackResourcePage : public ResourcePage { } //: The plural version of 'shader pack' - [[nodiscard]] inline QString resourcesString() const override { return tr("shader packs"); } + inline QString resourcesString() const override { return tr("shader packs"); } //: The singular version of 'shader packs' - [[nodiscard]] inline QString resourceString() const override { return tr("shader pack"); } + inline QString resourceString() const override { return tr("shader pack"); } - [[nodiscard]] bool supportsFiltering() const override { return false; }; + bool supportsFiltering() const override { return false; }; void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, std::shared_ptr) override; - [[nodiscard]] QMap urlHandlers() const override; + QMap urlHandlers() const override; - [[nodiscard]] inline auto helpPage() const -> QString override { return "shaderpack-platform"; } + inline auto helpPage() const -> QString override { return "shaderpack-platform"; } protected: ShaderPackResourcePage(ShaderPackDownloadDialog* dialog, BaseInstance& instance); diff --git a/launcher/ui/pages/modplatform/TexturePackModel.cpp b/launcher/ui/pages/modplatform/TexturePackModel.cpp index d56f9334b..7c1490671 100644 --- a/launcher/ui/pages/modplatform/TexturePackModel.cpp +++ b/launcher/ui/pages/modplatform/TexturePackModel.cpp @@ -12,8 +12,8 @@ static std::list s_availableVersions = {}; namespace ResourceDownload { -TexturePackResourceModel::TexturePackResourceModel(BaseInstance const& inst, ResourceAPI* api) - : ResourcePackResourceModel(inst, api), m_version_list(APPLICATION->metadataIndex()->get("net.minecraft")) +TexturePackResourceModel::TexturePackResourceModel(BaseInstance const& inst, ResourceAPI* api, QString debugName, QString metaEntryBase) + : ResourcePackResourceModel(inst, api, debugName, metaEntryBase), m_version_list(APPLICATION->metadataIndex()->get("net.minecraft")) { if (!m_version_list->isLoaded()) { qDebug() << "Loading version list..."; @@ -73,6 +73,7 @@ ResourceAPI::SearchArgs TexturePackResourceModel::createSearchArguments() ResourceAPI::VersionSearchArgs TexturePackResourceModel::createVersionsArguments(const QModelIndex& entry) { auto args = ResourcePackResourceModel::createVersionsArguments(entry); + args.resourceType = ModPlatform::ResourceType::TexturePack; if (!m_version_list->isLoaded()) { qCritical() << "The version list could not be loaded. Falling back to showing all entries."; return args; diff --git a/launcher/ui/pages/modplatform/TexturePackModel.h b/launcher/ui/pages/modplatform/TexturePackModel.h index 45b5734ee..bb7348b33 100644 --- a/launcher/ui/pages/modplatform/TexturePackModel.h +++ b/launcher/ui/pages/modplatform/TexturePackModel.h @@ -13,9 +13,9 @@ class TexturePackResourceModel : public ResourcePackResourceModel { Q_OBJECT public: - TexturePackResourceModel(BaseInstance const& inst, ResourceAPI* api); + TexturePackResourceModel(BaseInstance const& inst, ResourceAPI* api, QString debugName, QString metaEntryBase); - [[nodiscard]] inline ::Version maximumTexturePackVersion() const { return { "1.6" }; } + inline ::Version maximumTexturePackVersion() const { return { "1.6" }; } ResourceAPI::SearchArgs createSearchArguments() override; ResourceAPI::VersionSearchArgs createVersionsArguments(const QModelIndex&) override; diff --git a/launcher/ui/pages/modplatform/TexturePackPage.h b/launcher/ui/pages/modplatform/TexturePackPage.h index 27fd8bcfc..262004dfd 100644 --- a/launcher/ui/pages/modplatform/TexturePackPage.h +++ b/launcher/ui/pages/modplatform/TexturePackPage.h @@ -35,9 +35,9 @@ class TexturePackResourcePage : public ResourcePackResourcePage { } //: The plural version of 'texture pack' - [[nodiscard]] inline QString resourcesString() const override { return tr("texture packs"); } + inline QString resourcesString() const override { return tr("texture packs"); } //: The singular version of 'texture packs' - [[nodiscard]] inline QString resourceString() const override { return tr("texture pack"); } + inline QString resourceString() const override { return tr("texture pack"); } protected: TexturePackResourcePage(TexturePackDownloadDialog* dialog, BaseInstance& instance) : ResourcePackResourcePage(dialog, instance) {} diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp index 9e2e7a2ca..c5fec48d7 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp @@ -61,7 +61,7 @@ QVariant ListModel::data(const QModelIndex& index, int role) const if (m_logoMap.contains(pack.safeName)) { return (m_logoMap.value(pack.safeName)); } - auto icon = APPLICATION->getThemedIcon("atlauncher-placeholder"); + auto icon = QIcon::fromTheme("atlauncher-placeholder"); auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1").arg(pack.safeName); ((ListModel*)this)->requestLogo(pack.safeName, url); diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.h b/launcher/ui/pages/modplatform/atlauncher/AtlPage.h index 73460232b..8c8bf53b3 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.h +++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.h @@ -41,7 +41,6 @@ #include #include -#include "Application.h" #include "ui/pages/modplatform/ModpackProviderBasePage.h" namespace Ui { @@ -57,7 +56,7 @@ class AtlPage : public QWidget, public ModpackProviderBasePage { explicit AtlPage(NewInstanceDialog* dialog, QWidget* parent = 0); virtual ~AtlPage(); virtual QString displayName() const override { return "ATLauncher"; } - virtual QIcon icon() const override { return APPLICATION->getThemedIcon("atlauncher"); } + virtual QIcon icon() const override { return QIcon::fromTheme("atlauncher"); } virtual QString id() const override { return "atl"; } virtual QString helpPage() const override { return "ATL-platform"; } virtual bool shouldDisplay() const override; @@ -68,7 +67,7 @@ class AtlPage : public QWidget, public ModpackProviderBasePage { /** Programatically set the term in the search bar. */ virtual void setSearchTerm(QString) override; /** Get the current term in the search bar. */ - [[nodiscard]] virtual QString getSerachTerm() const override; + virtual QString getSerachTerm() const override; private: void suggestCurrent(); diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp index 9255ccf1e..e5d880dca 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp @@ -20,7 +20,7 @@ ListModel::~ListModel() {} int ListModel::rowCount(const QModelIndex& parent) const { - return parent.isValid() ? 0 : modpacks.size(); + return parent.isValid() ? 0 : m_modpacks.size(); } int ListModel::columnCount(const QModelIndex& parent) const @@ -31,27 +31,27 @@ int ListModel::columnCount(const QModelIndex& parent) const QVariant ListModel::data(const QModelIndex& index, int role) const { int pos = index.row(); - if (pos >= modpacks.size() || pos < 0 || !index.isValid()) { + if (pos >= m_modpacks.size() || pos < 0 || !index.isValid()) { return QString("INVALID INDEX %1").arg(pos); } - IndexedPack pack = modpacks.at(pos); + auto pack = m_modpacks.at(pos); switch (role) { case Qt::ToolTipRole: { - if (pack.description.length() > 100) { + if (pack->description.length() > 100) { // some magic to prevent to long tooltips and replace html linebreaks - QString edit = pack.description.left(97); + QString edit = pack->description.left(97); edit = edit.left(edit.lastIndexOf("
")).left(edit.lastIndexOf(" ")).append("..."); return edit; } - return pack.description; + return pack->description; } case Qt::DecorationRole: { - if (m_logoMap.contains(pack.logoName)) { - return (m_logoMap.value(pack.logoName)); + if (m_logoMap.contains(pack->logoName)) { + return (m_logoMap.value(pack->logoName)); } - QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); - ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl); + QIcon icon = QIcon::fromTheme("screenshot-placeholder"); + ((ListModel*)this)->requestLogo(pack->logoName, pack->logoUrl); return icon; } case Qt::UserRole: { @@ -62,9 +62,9 @@ QVariant ListModel::data(const QModelIndex& index, int role) const case Qt::SizeHintRole: return QSize(0, 58); case UserDataTypes::TITLE: - return pack.name; + return pack->name; case UserDataTypes::DESCRIPTION: - return pack.description; + return pack->description; case UserDataTypes::INSTALLED: return false; default: @@ -76,11 +76,10 @@ QVariant ListModel::data(const QModelIndex& index, int role) const bool ListModel::setData(const QModelIndex& index, const QVariant& value, [[maybe_unused]] int role) { int pos = index.row(); - if (pos >= modpacks.size() || pos < 0 || !index.isValid()) + if (pos >= m_modpacks.size() || pos < 0 || !index.isValid()) return false; - Q_ASSERT(value.canConvert()); - modpacks[pos] = value.value(); + m_modpacks[pos] = value.value(); return true; } @@ -89,8 +88,8 @@ void ListModel::logoLoaded(QString logo, QIcon out) { m_loadingLogos.removeAll(logo); m_logoMap.insert(logo, out); - for (int i = 0; i < modpacks.size(); i++) { - if (modpacks[i].logoName == logo) { + for (int i = 0; i < m_modpacks.size(); i++) { + if (m_modpacks[i]->logoName == logo) { emit dataChanged(createIndex(i, 0), createIndex(i, 0), { Qt::DecorationRole }); } } @@ -117,8 +116,8 @@ void ListModel::requestLogo(QString logo, QString url) connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] { job->deleteLater(); emit logoLoaded(logo, QIcon(fullPath)); - if (waitingCallbacks.contains(logo)) { - waitingCallbacks.value(logo)(fullPath); + if (m_waitingCallbacks.contains(logo)) { + m_waitingCallbacks.value(logo)(fullPath); } }); @@ -148,14 +147,14 @@ Qt::ItemFlags ListModel::flags(const QModelIndex& index) const bool ListModel::canFetchMore([[maybe_unused]] const QModelIndex& parent) const { - return searchState == CanPossiblyFetchMore; + return m_searchState == CanPossiblyFetchMore; } void ListModel::fetchMore(const QModelIndex& parent) { if (parent.isValid()) return; - if (nextSearchOffset == 0) { + if (m_nextSearchOffset == 0) { qWarning() << "fetchMore with 0 offset is wrong..."; return; } @@ -164,137 +163,106 @@ void ListModel::fetchMore(const QModelIndex& parent) void ListModel::performPaginatedSearch() { - if (currentSearchTerm.startsWith("#")) { - auto projectId = currentSearchTerm.mid(1); + static const FlameAPI api; + if (m_currentSearchTerm.startsWith("#")) { + auto projectId = m_currentSearchTerm.mid(1); if (!projectId.isEmpty()) { - ResourceAPI::ProjectInfoCallbacks callbacks; + ResourceAPI::Callback callbacks; - callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); }; - callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); }; + callbacks.on_fail = [this](QString reason, int) { searchRequestFailed(reason); }; + callbacks.on_succeed = [this](auto& pack) { searchRequestForOneSucceeded(pack); }; callbacks.on_abort = [this] { qCritical() << "Search task aborted by an unknown reason!"; searchRequestFailed("Aborted"); }; - static const FlameAPI api; - if (auto job = api.getProjectInfo({ projectId }, std::move(callbacks)); job) { - jobPtr = job; - jobPtr->start(); + if (auto job = api.getProjectInfo({ { projectId } }, std::move(callbacks)); job) { + m_jobPtr = job; + m_jobPtr->start(); } return; } } ResourceAPI::SortingMethod sort{}; - sort.index = currentSort + 1; + sort.index = m_currentSort + 1; - 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->openSource }); + ResourceAPI::Callback> callbacks{}; - netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl.value()), response)); - jobPtr = netJob; - jobPtr->start(); - connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished); - connect(netJob.get(), &NetJob::failed, this, &ListModel::searchRequestFailed); + callbacks.on_succeed = [this](auto& doc) { searchRequestFinished(doc); }; + callbacks.on_fail = [this](QString reason, int) { searchRequestFailed(reason); }; + + auto netJob = api.searchProjects({ ModPlatform::ResourceType::Modpack, m_nextSearchOffset, m_currentSearchTerm, sort, m_filter->loaders, + m_filter->versions, ModPlatform::Side::NoSide, m_filter->categoryIds, m_filter->openSource }, + std::move(callbacks)); + + m_jobPtr = netJob; + m_jobPtr->start(); } void ListModel::searchWithTerm(const QString& term, int sort, std::shared_ptr filter, bool filterChanged) { - if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort && !filterChanged) { + if (m_currentSearchTerm == term && m_currentSearchTerm.isNull() == term.isNull() && m_currentSort == sort && !filterChanged) { return; } - currentSearchTerm = term; - currentSort = sort; + m_currentSearchTerm = term; + m_currentSort = sort; m_filter = filter; if (hasActiveSearchJob()) { - jobPtr->abort(); - searchState = ResetRequested; + m_jobPtr->abort(); + m_searchState = ResetRequested; return; } beginResetModel(); - modpacks.clear(); + m_modpacks.clear(); endResetModel(); - searchState = None; + m_searchState = None; - nextSearchOffset = 0; + m_nextSearchOffset = 0; performPaginatedSearch(); } -void Flame::ListModel::searchRequestFinished() +void Flame::ListModel::searchRequestFinished(QList& newList) { if (hasActiveSearchJob()) return; - QJsonParseError parse_error; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from CurseForge at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qWarning() << *response; - return; - } - - QList newList; - auto packs = Json::ensureArray(doc.object(), "data"); - for (auto packRaw : packs) { - auto packObj = packRaw.toObject(); - - Flame::IndexedPack pack; - try { - Flame::loadIndexedPack(pack, packObj); - newList.append(pack); - } catch (const JSONValidationError& e) { - qWarning() << "Error while loading pack from CurseForge: " << e.cause(); - continue; - } - } - if (packs.size() < 25) { - searchState = Finished; + if (newList.size() < 25) { + m_searchState = Finished; } else { - nextSearchOffset += 25; - searchState = CanPossiblyFetchMore; + m_nextSearchOffset += 25; + m_searchState = CanPossiblyFetchMore; } // When you have a Qt build with assertions turned on, proceeding here will abort the application if (newList.size() == 0) return; - beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1); - modpacks.append(newList); + beginInsertRows(QModelIndex(), m_modpacks.size(), m_modpacks.size() + newList.size() - 1); + m_modpacks.append(newList); endInsertRows(); } -void Flame::ListModel::searchRequestForOneSucceeded(QJsonDocument& doc) +void Flame::ListModel::searchRequestForOneSucceeded(ModPlatform::IndexedPack& pack) { - jobPtr.reset(); + m_jobPtr.reset(); - auto packObj = Json::ensureObject(doc.object(), "data"); - - Flame::IndexedPack pack; - try { - Flame::loadIndexedPack(pack, packObj); - } catch (const JSONValidationError& e) { - qWarning() << "Error while loading pack from CurseForge: " << e.cause(); - return; - } - - beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + 1); - modpacks.append({ pack }); + beginInsertRows(QModelIndex(), m_modpacks.size(), m_modpacks.size() + 1); + m_modpacks.append(std::make_shared(pack)); endInsertRows(); } void Flame::ListModel::searchRequestFailed(QString reason) { - jobPtr.reset(); + m_jobPtr.reset(); - if (searchState == ResetRequested) { + if (m_searchState == ResetRequested) { beginResetModel(); - modpacks.clear(); + m_modpacks.clear(); endResetModel(); - nextSearchOffset = 0; + m_nextSearchOffset = 0; performPaginatedSearch(); } else { - searchState = Finished; + m_searchState = Finished; } } diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.h b/launcher/ui/pages/modplatform/flame/FlameModel.h index 026f6d1ee..f98e2be96 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.h +++ b/launcher/ui/pages/modplatform/flame/FlameModel.h @@ -16,8 +16,6 @@ #include #include "ui/widgets/ModFilterWidget.h" -#include - namespace Flame { using LogoMap = QMap; @@ -41,8 +39,8 @@ class ListModel : public QAbstractListModel { void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback); void searchWithTerm(const QString& term, int sort, std::shared_ptr filter, bool filterChanged); - [[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); } - [[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; } + bool hasActiveSearchJob() const { return m_jobPtr && m_jobPtr->isRunning(); } + Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? m_jobPtr : nullptr; } private slots: void performPaginatedSearch(); @@ -50,27 +48,26 @@ class ListModel : public QAbstractListModel { void logoFailed(QString logo); void logoLoaded(QString logo, QIcon out); - void searchRequestFinished(); + void searchRequestFinished(QList&); void searchRequestFailed(QString reason); - void searchRequestForOneSucceeded(QJsonDocument&); + void searchRequestForOneSucceeded(ModPlatform::IndexedPack&); private: void requestLogo(QString file, QString url); private: - QList modpacks; + QList m_modpacks; QStringList m_failedLogos; QStringList m_loadingLogos; LogoMap m_logoMap; - QMap waitingCallbacks; + QMap m_waitingCallbacks; - QString currentSearchTerm; - int currentSort = 0; + QString m_currentSearchTerm; + int m_currentSort = 0; std::shared_ptr m_filter; - int nextSearchOffset = 0; - enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None; - Task::Ptr jobPtr; - std::shared_ptr response = std::make_shared(); + int m_nextSearchOffset = 0; + enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } m_searchState = None; + Task::Ptr m_jobPtr; }; } // namespace Flame diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index 6563f3362..059d65438 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -35,7 +35,8 @@ #include "FlamePage.h" #include "Version.h" -#include "modplatform/flame/FlamePackIndex.h" +#include "modplatform/ModIndex.h" +#include "modplatform/ResourceAPI.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/widgets/ModFilterWidget.h" #include "ui_FlamePage.h" @@ -43,29 +44,25 @@ #include #include -#include "Application.h" #include "FlameModel.h" #include "InstanceImportTask.h" -#include "Json.h" #include "StringUtils.h" #include "modplatform/flame/FlameAPI.h" #include "ui/dialogs/NewInstanceDialog.h" #include "ui/widgets/ProjectItem.h" -#include "net/ApiDownload.h" - static FlameAPI api; FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) - : QWidget(parent), ui(new Ui::FlamePage), dialog(dialog), m_fetch_progress(this, false) + : QWidget(parent), m_ui(new Ui::FlamePage), m_dialog(dialog), m_fetch_progress(this, false) { - ui->setupUi(this); - ui->searchEdit->installEventFilter(this); - listModel = new Flame::ListModel(this); - ui->packView->setModel(listModel); + m_ui->setupUi(this); + m_ui->searchEdit->installEventFilter(this); + m_listModel = new Flame::ListModel(this); + m_ui->packView->setModel(m_listModel); - ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); + 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); @@ -76,33 +73,33 @@ FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) m_fetch_progress.setFixedHeight(24); m_fetch_progress.progressFormat(""); - ui->verticalLayout->insertWidget(2, &m_fetch_progress); + m_ui->verticalLayout->insertWidget(2, &m_fetch_progress); // index is used to set the sorting with the curseforge api - ui->sortByBox->addItem(tr("Sort by Featured")); - ui->sortByBox->addItem(tr("Sort by Popularity")); - ui->sortByBox->addItem(tr("Sort by Last Updated")); - ui->sortByBox->addItem(tr("Sort by Name")); - ui->sortByBox->addItem(tr("Sort by Author")); - ui->sortByBox->addItem(tr("Sort by Total Downloads")); + m_ui->sortByBox->addItem(tr("Sort by Featured")); + m_ui->sortByBox->addItem(tr("Sort by Popularity")); + m_ui->sortByBox->addItem(tr("Sort by Last Updated")); + m_ui->sortByBox->addItem(tr("Sort by Name")); + m_ui->sortByBox->addItem(tr("Sort by Author")); + m_ui->sortByBox->addItem(tr("Sort by Total Downloads")); - connect(ui->sortByBox, &QComboBox::currentIndexChanged, this, &FlamePage::triggerSearch); - connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlamePage::onSelectionChanged); - connect(ui->versionSelectionBox, &QComboBox::currentIndexChanged, this, &FlamePage::onVersionSelectionChanged); + connect(m_ui->sortByBox, &QComboBox::currentIndexChanged, this, &FlamePage::triggerSearch); + connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlamePage::onSelectionChanged); + connect(m_ui->versionSelectionBox, &QComboBox::currentIndexChanged, this, &FlamePage::onVersionSelectionChanged); - ui->packView->setItemDelegate(new ProjectItemDelegate(this)); - ui->packDescription->setMetaEntry("FlamePacks"); + m_ui->packView->setItemDelegate(new ProjectItemDelegate(this)); + m_ui->packDescription->setMetaEntry("FlamePacks"); createFilterWidget(); } FlamePage::~FlamePage() { - delete ui; + delete m_ui; } bool FlamePage::eventFilter(QObject* watched, QEvent* event) { - if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) { + if (watched == m_ui->searchEdit && event->type() == QEvent::KeyPress) { QKeyEvent* keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Return) { triggerSearch(); @@ -125,7 +122,7 @@ bool FlamePage::shouldDisplay() const void FlamePage::retranslate() { - ui->retranslateUi(this); + m_ui->retranslateUi(this); } void FlamePage::openedImpl() @@ -136,109 +133,91 @@ void FlamePage::openedImpl() void FlamePage::triggerSearch() { - ui->packView->selectionModel()->setCurrentIndex({}, QItemSelectionModel::SelectionFlag::ClearAndSelect); - ui->packView->clearSelection(); - ui->packDescription->clear(); - ui->versionSelectionBox->clear(); - listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex(), m_filterWidget->getFilter(), - m_filterWidget->changed()); - m_fetch_progress.watch(listModel->activeSearchJob().get()); -} - -bool checkVersionFilters(const Flame::IndexedVersion& v, std::shared_ptr filter) -{ - if (!filter) - return true; - return ((!filter->loaders || !v.loaders || filter->loaders & v.loaders) && // loaders - (filter->releases.empty() || // releases - std::find(filter->releases.cbegin(), filter->releases.cend(), v.version_type) != filter->releases.cend()) && - filter->checkMcVersions({ v.mcVersion })); // mcVersions} + m_ui->packView->selectionModel()->setCurrentIndex({}, QItemSelectionModel::SelectionFlag::ClearAndSelect); + m_ui->packView->clearSelection(); + m_ui->packDescription->clear(); + m_ui->versionSelectionBox->clear(); + bool filterChanged = m_filterWidget->changed(); + m_listModel->searchWithTerm(m_ui->searchEdit->text(), m_ui->sortByBox->currentIndex(), m_filterWidget->getFilter(), filterChanged); + m_fetch_progress.watch(m_listModel->activeSearchJob().get()); } void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev) { - ui->versionSelectionBox->clear(); + m_ui->versionSelectionBox->clear(); if (!curr.isValid()) { if (isOpened) { - dialog->setSuggestedPack(); + m_dialog->setSuggestedPack(); } return; } - QVariant raw = listModel->data(curr, Qt::UserRole); - Q_ASSERT(raw.canConvert()); - current = raw.value(); + m_current = m_listModel->data(curr, Qt::UserRole).value(); - if (!current.versionsLoaded || m_filterWidget->changed()) { + if (!m_current->versionsLoaded || m_filterWidget->changed()) { qDebug() << "Loading flame modpack versions"; - auto netJob = new NetJob(QString("Flame::PackVersions(%1)").arg(current.name), APPLICATION->network()); - auto response = std::make_shared(); - int addonId = current.addonId; - netJob->addNetAction( - Net::ApiDownload::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/files").arg(addonId), response)); - connect(netJob, &NetJob::succeeded, this, [this, response, addonId, curr] { - if (addonId != current.addonId) { + ResourceAPI::Callback > callbacks{}; + + auto addonId = m_current->addonId; + // Use default if no callbacks are set + callbacks.on_succeed = [this, curr, addonId](auto& doc) { + if (addonId != m_current->addonId) { return; // wrong request } - QJsonParseError parse_error; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from CurseForge at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qWarning() << *response; - return; - } - auto arr = Json::ensureArray(doc.object(), "data"); - try { - Flame::loadIndexedPackVersions(current, arr); - } catch (const JSONValidationError& e) { - qDebug() << *response; - qWarning() << "Error while reading flame modpack version: " << e.cause(); - } - auto pred = [this](const Flame::IndexedVersion& v) { return !checkVersionFilters(v, m_filterWidget->getFilter()); }; + m_current->versions = doc; + m_current->versionsLoaded = true; + auto pred = [this](const ModPlatform::IndexedVersion& v) { + if (auto filter = m_filterWidget->getFilter()) + return !filter->checkModpackFilters(v); + return false; + }; #if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0) - current.versions.removeIf(pred); + m_current->versions.removeIf(pred); #else - for (auto it = current.versions.begin(); it != current.versions.end();) - if (pred(*it)) - it = current.versions.erase(it); - else - ++it; + for (auto it = m_current->versions.begin(); it != m_current->versions.end();) + if (pred(*it)) + it = m_current->versions.erase(it); + else + ++it; #endif - for (const auto& version : current.versions) { - ui->versionSelectionBox->addItem(Flame::getVersionDisplayString(version), QVariant(version.downloadUrl)); + for (auto version : m_current->versions) { + m_ui->versionSelectionBox->addItem(version.getVersionDisplayString(), QVariant(version.downloadUrl)); } QVariant current_updated; - current_updated.setValue(current); + current_updated.setValue(m_current); - if (!listModel->setData(curr, current_updated, Qt::UserRole)) + if (!m_listModel->setData(curr, current_updated, Qt::UserRole)) qWarning() << "Failed to cache versions for the current pack!"; // TODO: Check whether it's a connection issue or the project disabled 3rd-party distribution. - if (current.versionsLoaded && ui->versionSelectionBox->count() < 1) { - ui->versionSelectionBox->addItem(tr("No version is available!"), -1); + if (m_current->versionsLoaded && m_ui->versionSelectionBox->count() < 1) { + m_ui->versionSelectionBox->addItem(tr("No version is available!"), -1); } suggestCurrent(); - }); - connect(netJob, &NetJob::finished, this, [response, netJob] { netJob->deleteLater(); }); - connect(netJob, &NetJob::failed, - [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); + }; + callbacks.on_fail = [this](QString reason, int) { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); + }; + + auto netJob = api.getProjectVersions({ *m_current, {}, {}, ModPlatform::ResourceType::Modpack }, std::move(callbacks)); + + m_job = netJob; netJob->start(); } else { - for (auto version : current.versions) { - ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl)); + for (auto version : m_current->versions) { + m_ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl)); } suggestCurrent(); } // TODO: Check whether it's a connection issue or the project disabled 3rd-party distribution. - if (current.versionsLoaded && ui->versionSelectionBox->count() < 1) { - ui->versionSelectionBox->addItem(tr("No version is available!"), -1); + if (m_current->versionsLoaded && m_ui->versionSelectionBox->count() < 1) { + m_ui->versionSelectionBox->addItem(tr("No version is available!"), -1); } updateUi(); @@ -251,26 +230,26 @@ void FlamePage::suggestCurrent() } if (m_selected_version_index == -1) { - dialog->setSuggestedPack(); + m_dialog->setSuggestedPack(); return; } - auto version = current.versions.at(m_selected_version_index); + auto version = m_current->versions.at(m_selected_version_index); QMap extra_info; - extra_info.insert("pack_id", QString::number(current.addonId)); - extra_info.insert("pack_version_id", QString::number(version.fileId)); + extra_info.insert("pack_id", m_current->addonId.toString()); + extra_info.insert("pack_version_id", version.fileId.toString()); - dialog->setSuggestedPack(current.name, new InstanceImportTask(version.downloadUrl, this, std::move(extra_info))); - QString editedLogoName = "curseforge_" + current.logoName; - listModel->getLogo(current.logoName, current.logoUrl, - [this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); }); + m_dialog->setSuggestedPack(m_current->name, new InstanceImportTask(version.downloadUrl, this, std::move(extra_info))); + QString editedLogoName = "curseforge_" + m_current->logoName; + m_listModel->getLogo(m_current->logoName, m_current->logoUrl, + [this, editedLogoName](QString logo) { m_dialog->setSuggestedIconFromFile(logo, editedLogoName); }); } void FlamePage::onVersionSelectionChanged(int index) { bool is_blocked = false; - ui->versionSelectionBox->itemData(index).toInt(&is_blocked); + m_ui->versionSelectionBox->itemData(index).toInt(&is_blocked); if (index == -1 || is_blocked) { m_selected_version_index = -1; @@ -279,7 +258,7 @@ void FlamePage::onVersionSelectionChanged(int index) m_selected_version_index = index; - Q_ASSERT(current.versions.at(m_selected_version_index).downloadUrl == ui->versionSelectionBox->currentData().toString()); + Q_ASSERT(m_current->versions.at(m_selected_version_index).downloadUrl == m_ui->versionSelectionBox->currentData().toString()); suggestCurrent(); } @@ -287,70 +266,71 @@ void FlamePage::onVersionSelectionChanged(int index) void FlamePage::updateUi() { QString text = ""; - QString name = current.name; + QString name = m_current->name; - if (current.extra.websiteUrl.isEmpty()) + if (m_current->websiteUrl.isEmpty()) text = name; else - text = "" + name + ""; - if (!current.authors.empty()) { - auto authorToStr = [](Flame::ModpackAuthor& author) { + text = "websiteUrl + "\">" + name + ""; + if (!m_current->authors.empty()) { + auto authorToStr = [](ModPlatform::ModpackAuthor& author) { if (author.url.isEmpty()) { return author.name; } return QString("%2").arg(author.url, author.name); }; QStringList authorStrs; - for (auto& author : current.authors) { + for (auto& author : m_current->authors) { authorStrs.push_back(authorToStr(author)); } text += "
" + tr(" by ") + authorStrs.join(", "); } - if (current.extraInfoLoaded) { - if (!current.extra.issuesUrl.isEmpty() || !current.extra.sourceUrl.isEmpty() || !current.extra.wikiUrl.isEmpty()) { + if (m_current->extraDataLoaded) { + if (!m_current->extraData.issuesUrl.isEmpty() || !m_current->extraData.sourceUrl.isEmpty() || + !m_current->extraData.wikiUrl.isEmpty()) { text += "

" + tr("External links:") + "
"; } - if (!current.extra.issuesUrl.isEmpty()) - text += "- " + tr("Issues: %1").arg(current.extra.issuesUrl) + "
"; - if (!current.extra.wikiUrl.isEmpty()) - text += "- " + tr("Wiki: %1").arg(current.extra.wikiUrl) + "
"; - if (!current.extra.sourceUrl.isEmpty()) - text += "- " + tr("Source code: %1").arg(current.extra.sourceUrl) + "
"; + if (!m_current->extraData.issuesUrl.isEmpty()) + text += "- " + tr("Issues: %1").arg(m_current->extraData.issuesUrl) + "
"; + if (!m_current->extraData.wikiUrl.isEmpty()) + text += "- " + tr("Wiki: %1").arg(m_current->extraData.wikiUrl) + "
"; + if (!m_current->extraData.sourceUrl.isEmpty()) + text += "- " + tr("Source code: %1").arg(m_current->extraData.sourceUrl) + "
"; } text += "
"; - text += api.getModDescription(current.addonId).toUtf8(); + text += api.getModDescription(m_current->addonId.toInt()).toUtf8(); - ui->packDescription->setHtml(StringUtils::htmlListPatch(text + current.description)); - ui->packDescription->flush(); + m_ui->packDescription->setHtml(StringUtils::htmlListPatch(text + m_current->description)); + m_ui->packDescription->flush(); } QString FlamePage::getSerachTerm() const { - return ui->searchEdit->text(); + return m_ui->searchEdit->text(); } void FlamePage::setSearchTerm(QString term) { - ui->searchEdit->setText(term); + m_ui->searchEdit->setText(term); } void FlamePage::createFilterWidget() { auto widget = ModFilterWidget::create(nullptr, false); m_filterWidget.swap(widget); - auto old = ui->splitter->replaceWidget(0, m_filterWidget.get()); + auto old = m_ui->splitter->replaceWidget(0, m_filterWidget.get()); // because we replaced the widget we also need to delete it if (old) { delete old; } - connect(ui->filterButton, &QPushButton::clicked, this, [this] { m_filterWidget->setHidden(!m_filterWidget->isHidden()); }); + connect(m_ui->filterButton, &QPushButton::clicked, this, [this] { m_filterWidget->setHidden(!m_filterWidget->isHidden()); }); connect(m_filterWidget.get(), &ModFilterWidget::filterChanged, this, &FlamePage::triggerSearch); auto response = std::make_shared(); - m_categoriesTask = FlameAPI::getCategories(response, ModPlatform::ResourceType::MODPACK); + m_categoriesTask = FlameAPI::getCategories(response, ModPlatform::ResourceType::Modpack); connect(m_categoriesTask.get(), &Task::succeeded, [this, response]() { auto categories = FlameAPI::loadModCategories(response); m_filterWidget->setCategories(categories); diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.h b/launcher/ui/pages/modplatform/flame/FlamePage.h index 32b752bbe..eb763229f 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.h +++ b/launcher/ui/pages/modplatform/flame/FlamePage.h @@ -37,9 +37,8 @@ #include -#include -#include #include +#include "modplatform/ModIndex.h" #include "ui/pages/modplatform/ModpackProviderBasePage.h" #include "ui/widgets/ModFilterWidget.h" #include "ui/widgets/ProgressWidget.h" @@ -61,7 +60,7 @@ class FlamePage : public QWidget, public ModpackProviderBasePage { explicit FlamePage(NewInstanceDialog* dialog, QWidget* parent = 0); virtual ~FlamePage(); virtual QString displayName() const override { return "CurseForge"; } - virtual QIcon icon() const override { return APPLICATION->getThemedIcon("flame"); } + virtual QIcon icon() const override { return QIcon::fromTheme("flame"); } virtual QString id() const override { return "flame"; } virtual QString helpPage() const override { return "Flame-platform"; } virtual bool shouldDisplay() const override; @@ -76,7 +75,7 @@ class FlamePage : public QWidget, public ModpackProviderBasePage { /** Programatically set the term in the search bar. */ virtual void setSearchTerm(QString) override; /** Get the current term in the search bar. */ - [[nodiscard]] virtual QString getSerachTerm() const override; + virtual QString getSerachTerm() const override; private: void suggestCurrent(); @@ -88,10 +87,10 @@ class FlamePage : public QWidget, public ModpackProviderBasePage { void createFilterWidget(); private: - Ui::FlamePage* ui = nullptr; - NewInstanceDialog* dialog = nullptr; - Flame::ListModel* listModel = nullptr; - Flame::IndexedPack current; + Ui::FlamePage* m_ui = nullptr; + NewInstanceDialog* m_dialog = nullptr; + Flame::ListModel* m_listModel = nullptr; + ModPlatform::IndexedPack::Ptr m_current; int m_selected_version_index = -1; @@ -102,4 +101,5 @@ class FlamePage : public QWidget, public ModpackProviderBasePage { std::unique_ptr m_filterWidget; Task::Ptr m_categoriesTask; + Task::Ptr m_job; }; diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp index 9917c29e6..a40e6d5a3 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp @@ -8,7 +8,7 @@ #include "minecraft/PackProfile.h" #include "modplatform/flame/FlameAPI.h" -#include "modplatform/flame/FlameModIndex.h" +#include "ui/pages/modplatform/flame/FlameResourcePages.h" namespace ResourceDownload { @@ -17,97 +17,9 @@ static bool isOptedOut(const ModPlatform::IndexedVersion& ver) return ver.downloadUrl.isEmpty(); } -FlameModModel::FlameModModel(BaseInstance& base) : ModModel(base, new FlameAPI) {} - -void FlameModModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - FlameMod::loadIndexedPack(m, obj); -} - -// We already deal with the URLs when initializing the pack, due to the API response's structure -void FlameModModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - FlameMod::loadBody(m, obj); -} - -void FlameModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) -{ - FlameMod::loadIndexedPackVersions(m, arr); -} - -auto FlameModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion -{ - return FlameMod::loadDependencyVersions(m, arr, &m_base_instance); -} - -bool FlameModModel::optedOut(const ModPlatform::IndexedVersion& ver) const -{ - return isOptedOut(ver); -} - -auto FlameModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray -{ - return Json::ensureArray(obj.object(), "data"); -} - -FlameResourcePackModel::FlameResourcePackModel(const BaseInstance& base) : ResourcePackResourceModel(base, new FlameAPI) {} - -void FlameResourcePackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - FlameMod::loadIndexedPack(m, obj); -} - -// We already deal with the URLs when initializing the pack, due to the API response's structure -void FlameResourcePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - FlameMod::loadBody(m, obj); -} - -void FlameResourcePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) -{ - FlameMod::loadIndexedPackVersions(m, arr); -} - -bool FlameResourcePackModel::optedOut(const ModPlatform::IndexedVersion& ver) const -{ - return isOptedOut(ver); -} - -auto FlameResourcePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray -{ - return Json::ensureArray(obj.object(), "data"); -} - -FlameTexturePackModel::FlameTexturePackModel(const BaseInstance& base) : TexturePackResourceModel(base, new FlameAPI) {} - -void FlameTexturePackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - FlameMod::loadIndexedPack(m, obj); -} - -// We already deal with the URLs when initializing the pack, due to the API response's structure -void FlameTexturePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - FlameMod::loadBody(m, obj); -} - -void FlameTexturePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) -{ - FlameMod::loadIndexedPackVersions(m, arr); - - QList filtered_versions(m.versions.size()); - - // FIXME: Client-side version filtering. This won't take into account any user-selected filtering. - for (auto const& version : m.versions) { - auto const& mc_versions = version.mcVersion; - - if (std::any_of(mc_versions.constBegin(), mc_versions.constEnd(), - [this](auto const& mc_version) { return Version(mc_version) <= maximumTexturePackVersion(); })) - filtered_versions.push_back(version); - } - - m.versions = filtered_versions; -} +FlameTexturePackModel::FlameTexturePackModel(const BaseInstance& base) + : TexturePackResourceModel(base, new FlameAPI, Flame::debugName(), Flame::metaEntryBase()) +{} ResourceAPI::SearchArgs FlameTexturePackModel::createSearchArguments() { @@ -137,65 +49,4 @@ bool FlameTexturePackModel::optedOut(const ModPlatform::IndexedVersion& ver) con return isOptedOut(ver); } -auto FlameTexturePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray -{ - return Json::ensureArray(obj.object(), "data"); -} - -FlameShaderPackModel::FlameShaderPackModel(const BaseInstance& base) : ShaderPackResourceModel(base, new FlameAPI) {} - -void FlameShaderPackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - FlameMod::loadIndexedPack(m, obj); -} - -// We already deal with the URLs when initializing the pack, due to the API response's structure -void FlameShaderPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - FlameMod::loadBody(m, obj); -} - -void FlameShaderPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) -{ - FlameMod::loadIndexedPackVersions(m, arr); -} - -bool FlameShaderPackModel::optedOut(const ModPlatform::IndexedVersion& ver) const -{ - return isOptedOut(ver); -} - -auto FlameShaderPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray -{ - return Json::ensureArray(obj.object(), "data"); -} - -FlameDataPackModel::FlameDataPackModel(const BaseInstance& base) : DataPackResourceModel(base, new FlameAPI) {} - -void FlameDataPackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - FlameMod::loadIndexedPack(m, obj); -} - -// We already deal with the URLs when initializing the pack, due to the API response's structure -void FlameDataPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - FlameMod::loadBody(m, obj); -} - -void FlameDataPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) -{ - FlameMod::loadIndexedPackVersions(m, arr); -} - -bool FlameDataPackModel::optedOut(const ModPlatform::IndexedVersion& ver) const -{ - return isOptedOut(ver); -} - -auto FlameDataPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray -{ - return Json::ensureArray(obj.object(), "data"); -} - } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h index 5fffe6361..76062f8e6 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h @@ -5,52 +5,10 @@ #pragma once #include "ui/pages/modplatform/ModModel.h" -#include "ui/pages/modplatform/ResourcePackModel.h" #include "ui/pages/modplatform/flame/FlameResourcePages.h" namespace ResourceDownload { -class FlameModModel : public ModModel { - Q_OBJECT - - public: - FlameModModel(BaseInstance&); - ~FlameModModel() override = default; - - bool optedOut(const ModPlatform::IndexedVersion& ver) const override; - - private: - [[nodiscard]] QString debugName() const override { return Flame::debugName() + " (Model)"; } - [[nodiscard]] QString metaEntryBase() const override { return Flame::metaEntryBase(); } - - void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; - auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion override; - - auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; -}; - -class FlameResourcePackModel : public ResourcePackResourceModel { - Q_OBJECT - - public: - FlameResourcePackModel(const BaseInstance&); - ~FlameResourcePackModel() override = default; - - bool optedOut(const ModPlatform::IndexedVersion& ver) const override; - - private: - [[nodiscard]] QString debugName() const override { return Flame::debugName() + " (Model)"; } - [[nodiscard]] QString metaEntryBase() const override { return Flame::metaEntryBase(); } - - void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; - - auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; -}; - class FlameTexturePackModel : public TexturePackResourceModel { Q_OBJECT @@ -61,55 +19,11 @@ class FlameTexturePackModel : public TexturePackResourceModel { bool optedOut(const ModPlatform::IndexedVersion& ver) const override; private: - [[nodiscard]] QString debugName() const override { return Flame::debugName() + " (Model)"; } - [[nodiscard]] QString metaEntryBase() const override { return Flame::metaEntryBase(); } - - void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; + QString debugName() const override { return Flame::debugName() + " (Model)"; } + QString metaEntryBase() const override { return Flame::metaEntryBase(); } ResourceAPI::SearchArgs createSearchArguments() override; ResourceAPI::VersionSearchArgs createVersionsArguments(const QModelIndex&) override; - - auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; -}; - -class FlameShaderPackModel : public ShaderPackResourceModel { - Q_OBJECT - - public: - FlameShaderPackModel(const BaseInstance&); - ~FlameShaderPackModel() override = default; - - bool optedOut(const ModPlatform::IndexedVersion& ver) const override; - - private: - [[nodiscard]] QString debugName() const override { return Flame::debugName() + " (Model)"; } - [[nodiscard]] QString metaEntryBase() const override { return Flame::metaEntryBase(); } - - void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; - auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; -}; - -class FlameDataPackModel : public DataPackResourceModel { - Q_OBJECT - - public: - FlameDataPackModel(const BaseInstance&); - ~FlameDataPackModel() override = default; - - bool optedOut(const ModPlatform::IndexedVersion& ver) const override; - - private: - [[nodiscard]] QString debugName() const override { return Flame::debugName() + " (Model)"; } - [[nodiscard]] QString metaEntryBase() const override { return Flame::metaEntryBase(); } - - void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; - auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; }; } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index bf421c036..6ff435854 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -40,7 +40,6 @@ #include "FlameResourcePages.h" #include #include -#include "modplatform/ModIndex.h" #include "modplatform/flame/FlameAPI.h" #include "ui_ResourcePage.h" @@ -51,7 +50,7 @@ namespace ResourceDownload { FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ModPage(dialog, instance) { - m_model = new FlameModModel(instance); + m_model = new ModModel(instance, new FlameAPI(), Flame::debugName(), Flame::metaEntryBase()); m_ui->packView->setModel(m_model); addSortings(); @@ -85,7 +84,7 @@ void FlameModPage::openUrl(const QUrl& url) FlameResourcePackPage::FlameResourcePackPage(ResourcePackDownloadDialog* dialog, BaseInstance& instance) : ResourcePackResourcePage(dialog, instance) { - m_model = new FlameResourcePackModel(instance); + m_model = new ResourcePackResourceModel(instance, new FlameAPI(), Flame::debugName(), Flame::metaEntryBase()); m_ui->packView->setModel(m_model); addSortings(); @@ -169,7 +168,7 @@ void FlameDataPackPage::openUrl(const QUrl& url) FlameShaderPackPage::FlameShaderPackPage(ShaderPackDownloadDialog* dialog, BaseInstance& instance) : ShaderPackResourcePage(dialog, instance) { - m_model = new FlameShaderPackModel(instance); + m_model = new ShaderPackResourceModel(instance, new FlameAPI(), Flame::debugName(), Flame::metaEntryBase()); m_ui->packView->setModel(m_model); addSortings(); @@ -184,10 +183,9 @@ FlameShaderPackPage::FlameShaderPackPage(ShaderPackDownloadDialog* dialog, BaseI m_ui->packDescription->setMetaEntry(metaEntryBase()); } -FlameDataPackPage::FlameDataPackPage(DataPackDownloadDialog* dialog, BaseInstance& instance) - : DataPackResourcePage(dialog, instance) +FlameDataPackPage::FlameDataPackPage(DataPackDownloadDialog* dialog, BaseInstance& instance) : DataPackResourcePage(dialog, instance) { - m_model = new FlameDataPackModel(instance); + m_model = new DataPackResourceModel(instance, new FlameAPI(), Flame::debugName(), Flame::metaEntryBase()); m_ui->packView->setModel(m_model); addSortings(); diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h index 309e1e019..d4b697ae0 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h @@ -40,7 +40,6 @@ #pragma once #include -#include "Application.h" #include "modplatform/ResourceAPI.h" @@ -58,7 +57,7 @@ static inline QString displayName() } static inline QIcon icon() { - return APPLICATION->getThemedIcon("flame"); + return QIcon::fromTheme("flame"); } static inline QString id() { @@ -86,15 +85,15 @@ class FlameModPage : public ModPage { FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance); ~FlameModPage() override = default; - [[nodiscard]] bool shouldDisplay() const override; + bool shouldDisplay() const override; - [[nodiscard]] inline auto displayName() const -> QString override { return Flame::displayName(); } - [[nodiscard]] inline auto icon() const -> QIcon override { return Flame::icon(); } - [[nodiscard]] inline auto id() const -> QString override { return Flame::id(); } - [[nodiscard]] inline auto debugName() const -> QString override { return Flame::debugName(); } - [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Flame::metaEntryBase(); } + inline auto displayName() const -> QString override { return Flame::displayName(); } + inline auto icon() const -> QIcon override { return Flame::icon(); } + inline auto id() const -> QString override { return Flame::id(); } + inline auto debugName() const -> QString override { return Flame::debugName(); } + inline auto metaEntryBase() const -> QString override { return Flame::metaEntryBase(); } - [[nodiscard]] inline auto helpPage() const -> QString override { return "Mod-platform"; } + inline auto helpPage() const -> QString override { return "Mod-platform"; } void openUrl(const QUrl& url) override; std::unique_ptr createFilterWidget() override; @@ -118,15 +117,15 @@ class FlameResourcePackPage : public ResourcePackResourcePage { FlameResourcePackPage(ResourcePackDownloadDialog* dialog, BaseInstance& instance); ~FlameResourcePackPage() override = default; - [[nodiscard]] bool shouldDisplay() const override; + bool shouldDisplay() const override; - [[nodiscard]] inline auto displayName() const -> QString override { return Flame::displayName(); } - [[nodiscard]] inline auto icon() const -> QIcon override { return Flame::icon(); } - [[nodiscard]] inline auto id() const -> QString override { return Flame::id(); } - [[nodiscard]] inline auto debugName() const -> QString override { return Flame::debugName(); } - [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Flame::metaEntryBase(); } + inline auto displayName() const -> QString override { return Flame::displayName(); } + inline auto icon() const -> QIcon override { return Flame::icon(); } + inline auto id() const -> QString override { return Flame::id(); } + inline auto debugName() const -> QString override { return Flame::debugName(); } + inline auto metaEntryBase() const -> QString override { return Flame::metaEntryBase(); } - [[nodiscard]] inline auto helpPage() const -> QString override { return ""; } + inline auto helpPage() const -> QString override { return ""; } void openUrl(const QUrl& url) override; }; @@ -143,15 +142,15 @@ class FlameTexturePackPage : public TexturePackResourcePage { FlameTexturePackPage(TexturePackDownloadDialog* dialog, BaseInstance& instance); ~FlameTexturePackPage() override = default; - [[nodiscard]] bool shouldDisplay() const override; + bool shouldDisplay() const override; - [[nodiscard]] inline auto displayName() const -> QString override { return Flame::displayName(); } - [[nodiscard]] inline auto icon() const -> QIcon override { return Flame::icon(); } - [[nodiscard]] inline auto id() const -> QString override { return Flame::id(); } - [[nodiscard]] inline auto debugName() const -> QString override { return Flame::debugName(); } - [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Flame::metaEntryBase(); } + inline auto displayName() const -> QString override { return Flame::displayName(); } + inline auto icon() const -> QIcon override { return Flame::icon(); } + inline auto id() const -> QString override { return Flame::id(); } + inline auto debugName() const -> QString override { return Flame::debugName(); } + inline auto metaEntryBase() const -> QString override { return Flame::metaEntryBase(); } - [[nodiscard]] inline auto helpPage() const -> QString override { return ""; } + inline auto helpPage() const -> QString override { return ""; } void openUrl(const QUrl& url) override; }; @@ -168,21 +167,19 @@ class FlameShaderPackPage : public ShaderPackResourcePage { FlameShaderPackPage(ShaderPackDownloadDialog* dialog, BaseInstance& instance); ~FlameShaderPackPage() override = default; - [[nodiscard]] bool shouldDisplay() const override; + bool shouldDisplay() const override; - [[nodiscard]] inline auto displayName() const -> QString override { return Flame::displayName(); } - [[nodiscard]] inline auto icon() const -> QIcon override { return Flame::icon(); } - [[nodiscard]] inline auto id() const -> QString override { return Flame::id(); } - [[nodiscard]] inline auto debugName() const -> QString override { return Flame::debugName(); } - [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Flame::metaEntryBase(); } + inline auto displayName() const -> QString override { return Flame::displayName(); } + inline auto icon() const -> QIcon override { return Flame::icon(); } + inline auto id() const -> QString override { return Flame::id(); } + inline auto debugName() const -> QString override { return Flame::debugName(); } + inline auto metaEntryBase() const -> QString override { return Flame::metaEntryBase(); } - [[nodiscard]] inline auto helpPage() const -> QString override { return ""; } + inline auto helpPage() const -> QString override { return ""; } void openUrl(const QUrl& url) override; }; - - class FlameDataPackPage : public DataPackResourcePage { Q_OBJECT @@ -195,15 +192,15 @@ class FlameDataPackPage : public DataPackResourcePage { FlameDataPackPage(DataPackDownloadDialog* dialog, BaseInstance& instance); ~FlameDataPackPage() override = default; - [[nodiscard]] bool shouldDisplay() const override; + bool shouldDisplay() const override; - [[nodiscard]] inline auto displayName() const -> QString override { return Flame::displayName(); } - [[nodiscard]] inline auto icon() const -> QIcon override { return Flame::icon(); } - [[nodiscard]] inline auto id() const -> QString override { return Flame::id(); } - [[nodiscard]] inline auto debugName() const -> QString override { return Flame::debugName(); } - [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Flame::metaEntryBase(); } + inline auto displayName() const -> QString override { return Flame::displayName(); } + inline auto icon() const -> QIcon override { return Flame::icon(); } + inline auto id() const -> QString override { return Flame::id(); } + inline auto debugName() const -> QString override { return Flame::debugName(); } + inline auto metaEntryBase() const -> QString override { return Flame::metaEntryBase(); } - [[nodiscard]] inline auto helpPage() const -> QString override { return ""; } + inline auto helpPage() const -> QString override { return ""; } void openUrl(const QUrl& url) override; }; diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp index 35e1dc110..9a2b31768 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp +++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include "FileSystem.h" #include "ListModel.h" @@ -88,6 +89,34 @@ void ImportFTBPage::retranslate() ui->retranslateUi(this); } +QString saveIconToTempFile(const QIcon& icon) +{ + if (icon.isNull()) { + return QString(); + } + + QPixmap pixmap = icon.pixmap(icon.availableSizes().last()); + if (pixmap.isNull()) { + return QString(); + } + + QTemporaryFile tempFile(QDir::tempPath() + "/iconXXXXXX.png"); + tempFile.setAutoRemove(false); + if (!tempFile.open()) { + return QString(); + } + + QString tempPath = tempFile.fileName(); + tempFile.close(); + + if (!pixmap.save(tempPath, "PNG")) { + QFile::remove(tempPath); + return QString(); + } + + return tempPath; // Success +} + void ImportFTBPage::suggestCurrent() { if (!isOpened) @@ -100,7 +129,14 @@ void ImportFTBPage::suggestCurrent() dialog->setSuggestedPack(selected.name, new PackInstallTask(selected)); QString editedLogoName = QString("ftb_%1_%2.jpg").arg(selected.name, QString::number(selected.id)); - dialog->setSuggestedIconFromFile(FS::PathCombine(selected.path, "folder.jpg"), editedLogoName); + auto iconPath = FS::PathCombine(selected.path, "folder.jpg"); + if (!QFileInfo::exists(iconPath)) { + // need to save the icon as that actual logo is not a image on the disk + iconPath = saveIconToTempFile(selected.icon); + } + if (!iconPath.isEmpty() && QFileInfo::exists(iconPath)) { + dialog->setSuggestedIconFromFile(iconPath, editedLogoName); + } } void ImportFTBPage::onPublicPackSelectionChanged(QModelIndex now, QModelIndex) diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h index 7afff5a9d..25b900f97 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h +++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h @@ -23,7 +23,6 @@ #include #include -#include #include "modplatform/import_ftb/PackHelpers.h" #include "ui/pages/modplatform/ModpackProviderBasePage.h" #include "ui/pages/modplatform/import_ftb/ListModel.h" @@ -42,7 +41,7 @@ class ImportFTBPage : public QWidget, public ModpackProviderBasePage { explicit ImportFTBPage(NewInstanceDialog* dialog, QWidget* parent = 0); virtual ~ImportFTBPage(); QString displayName() const override { return tr("FTB App Import"); } - QIcon icon() const override { return APPLICATION->getThemedIcon("ftb_logo"); } + QIcon icon() const override { return QIcon::fromTheme("ftb_logo"); } QString id() const override { return "import_ftb"; } QString helpPage() const override { return "FTB-import"; } bool shouldDisplay() const override { return true; } @@ -52,7 +51,7 @@ class ImportFTBPage : public QWidget, public ModpackProviderBasePage { /** Programatically set the term in the search bar. */ virtual void setSearchTerm(QString) override; /** Get the current term in the search bar. */ - [[nodiscard]] virtual QString getSerachTerm() const override; + virtual QString getSerachTerm() const override; private: void suggestCurrent(); diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp index 8d3beea01..daa2189ad 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp @@ -17,7 +17,6 @@ */ #include "ListModel.h" -#include #include #include #include diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp index efa9bd3bc..eb95b291c 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp @@ -173,7 +173,7 @@ QVariant ListModel::data(const QModelIndex& index, int role) const if (m_logoMap.contains(pack.logo)) { return (m_logoMap.value(pack.logo)); } - QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); + QIcon icon = QIcon::fromTheme("screenshot-placeholder"); ((ListModel*)this)->requestLogo(pack.logo); return icon; } diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.h b/launcher/ui/pages/modplatform/legacy_ftb/Page.h index 818000c05..db70ae79e 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.h +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.h @@ -39,7 +39,6 @@ #include #include -#include #include "QObjectPtr.h" #include "modplatform/legacy_ftb/PackFetchTask.h" #include "modplatform/legacy_ftb/PackHelpers.h" @@ -64,7 +63,7 @@ class Page : public QWidget, public ModpackProviderBasePage { explicit Page(NewInstanceDialog* dialog, QWidget* parent = 0); virtual ~Page(); QString displayName() const override { return "FTB Legacy"; } - QIcon icon() const override { return APPLICATION->getThemedIcon("ftb_logo"); } + QIcon icon() const override { return QIcon::fromTheme("ftb_logo"); } QString id() const override { return "legacy_ftb"; } QString helpPage() const override { return "FTB-legacy"; } bool shouldDisplay() const override; @@ -74,7 +73,7 @@ class Page : public QWidget, public ModpackProviderBasePage { /** Programatically set the term in the search bar. */ virtual void setSearchTerm(QString) override; /** Get the current term in the search bar. */ - [[nodiscard]] virtual QString getSerachTerm() const override; + virtual QString getSerachTerm() const override; private: void suggestCurrent(); diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index e84903cfd..acd321d4b 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -36,8 +36,10 @@ #include "ModrinthModel.h" +#include "Application.h" #include "BuildConfig.h" #include "Json.h" +#include "modplatform/ModIndex.h" #include "modplatform/modrinth/ModrinthAPI.h" #include "net/NetJob.h" #include "ui/widgets/ProjectItem.h" @@ -61,7 +63,7 @@ void ModpackListModel::fetchMore(const QModelIndex& parent) { if (parent.isValid()) return; - if (nextSearchOffset == 0) { + if (m_nextSearchOffset == 0) { qWarning() << "fetchMore with 0 offset is wrong..."; return; } @@ -71,27 +73,27 @@ void ModpackListModel::fetchMore(const QModelIndex& parent) auto ModpackListModel::data(const QModelIndex& index, int role) const -> QVariant { int pos = index.row(); - if (pos >= modpacks.size() || pos < 0 || !index.isValid()) { + if (pos >= m_modpacks.size() || pos < 0 || !index.isValid()) { return QString("INVALID INDEX %1").arg(pos); } - Modrinth::Modpack pack = modpacks.at(pos); + auto pack = m_modpacks.at(pos); switch (role) { case Qt::ToolTipRole: { - if (pack.description.length() > 100) { + if (pack->description.length() > 100) { // some magic to prevent to long tooltips and replace html linebreaks - QString edit = pack.description.left(97); + QString edit = pack->description.left(97); edit = edit.left(edit.lastIndexOf("
")).left(edit.lastIndexOf(" ")).append("..."); return edit; } - return pack.description; + return pack->description; } case Qt::DecorationRole: { - if (m_logoMap.contains(pack.iconName)) - return m_logoMap.value(pack.iconName); + if (m_logoMap.contains(pack->logoName)) + return m_logoMap.value(pack->logoName); - QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); - ((ModpackListModel*)this)->requestLogo(pack.iconName, pack.iconUrl.toString()); + QIcon icon = QIcon::fromTheme("screenshot-placeholder"); + ((ModpackListModel*)this)->requestLogo(pack->logoName, pack->logoUrl); return icon; } case Qt::UserRole: { @@ -103,9 +105,9 @@ auto ModpackListModel::data(const QModelIndex& index, int role) const -> QVarian return QSize(0, 58); // Custom data case UserDataTypes::TITLE: - return pack.name; + return pack->name; case UserDataTypes::DESCRIPTION: - return pack.description; + return pack->description; case UserDataTypes::INSTALLED: return false; default: @@ -118,11 +120,10 @@ auto ModpackListModel::data(const QModelIndex& index, int role) const -> QVarian bool ModpackListModel::setData(const QModelIndex& index, const QVariant& value, [[maybe_unused]] int role) { int pos = index.row(); - if (pos >= modpacks.size() || pos < 0 || !index.isValid()) + if (pos >= m_modpacks.size() || pos < 0 || !index.isValid()) return false; - Q_ASSERT(value.canConvert()); - modpacks[pos] = value.value(); + m_modpacks[pos] = value.value(); return true; } @@ -131,67 +132,56 @@ void ModpackListModel::performPaginatedSearch() { if (hasActiveSearchJob()) return; + static const ModrinthAPI api; - if (currentSearchTerm.startsWith("#")) { - auto projectId = currentSearchTerm.mid(1); + if (m_currentSearchTerm.startsWith("#")) { + auto projectId = m_currentSearchTerm.mid(1); if (!projectId.isEmpty()) { - ResourceAPI::ProjectInfoCallbacks callbacks; + ResourceAPI::Callback callbacks; - callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); }; - callbacks.on_succeed = [this](auto& doc, auto&) { searchRequestForOneSucceeded(doc); }; + callbacks.on_fail = [this](QString reason, int) { searchRequestFailed(reason); }; + callbacks.on_succeed = [this](auto& pack) { searchRequestForOneSucceeded(pack); }; callbacks.on_abort = [this] { qCritical() << "Search task aborted by an unknown reason!"; searchRequestFailed("Aborted"); }; - static const ModrinthAPI api; if (auto job = api.getProjectInfo({ projectId }, std::move(callbacks)); job) { - jobPtr = job; - jobPtr->start(); + m_jobPtr = job; + m_jobPtr->start(); } return; } } // TODO: Move to standalone API 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->openSource }); + sort.name = m_currentSort; - auto netJob = makeShared("Modrinth::SearchModpack", APPLICATION->network()); - netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl.value()), m_allResponse)); + ResourceAPI::Callback> callbacks{}; - connect(netJob.get(), &NetJob::succeeded, this, [this] { - QJsonParseError parseError{}; + callbacks.on_succeed = [this](auto& doc) { searchRequestFinished(doc); }; + callbacks.on_fail = [this](QString reason, int) { searchRequestFailed(reason); }; - QJsonDocument doc = QJsonDocument::fromJson(*m_allResponse, &parseError); - if (parseError.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from " << debugName() << " at " << parseError.offset - << " reason: " << parseError.errorString(); - qWarning() << *m_allResponse; - return; - } + auto netJob = api.searchProjects({ ModPlatform::ResourceType::Modpack, m_nextSearchOffset, m_currentSearchTerm, sort, m_filter->loaders, + m_filter->versions, ModPlatform::Side::NoSide, m_filter->categoryIds, m_filter->openSource }, + std::move(callbacks)); - searchRequestFinished(doc); - }); - connect(netJob.get(), &NetJob::failed, this, &ModpackListModel::searchRequestFailed); - - jobPtr = netJob; - jobPtr->start(); + m_jobPtr = netJob; + m_jobPtr->start(); } void ModpackListModel::refresh() { if (hasActiveSearchJob()) { - jobPtr->abort(); - searchState = ResetRequested; + m_jobPtr->abort(); + m_searchState = ResetRequested; return; } beginResetModel(); - modpacks.clear(); + m_modpacks.clear(); endResetModel(); - searchState = None; + m_searchState = None; - nextSearchOffset = 0; + m_nextSearchOffset = 0; performPaginatedSearch(); } @@ -222,12 +212,12 @@ void ModpackListModel::searchWithTerm(const QString& term, auto sort_str = sortFromIndex(sort); - if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort_str && !filterChanged) { + if (m_currentSearchTerm == term && m_currentSearchTerm.isNull() == term.isNull() && m_currentSort == sort_str && !filterChanged) { return; } - currentSearchTerm = term; - currentSort = sort_str; + m_currentSearchTerm = term; + m_currentSort = sort_str; m_filter = filter; refresh(); @@ -257,8 +247,8 @@ void ModpackListModel::requestLogo(QString logo, QString url) connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] { job->deleteLater(); emit logoLoaded(logo, QIcon(fullPath)); - if (waitingCallbacks.contains(logo)) { - waitingCallbacks.value(logo)(fullPath); + if (m_waitingCallbacks.contains(logo)) { + m_waitingCallbacks.value(logo)(fullPath); } }); @@ -277,8 +267,8 @@ void ModpackListModel::logoLoaded(QString logo, QIcon out) { m_loadingLogos.removeAll(logo); m_logoMap.insert(logo, out); - for (int i = 0; i < modpacks.size(); i++) { - if (modpacks[i].iconName == logo) { + for (int i = 0; i < m_modpacks.size(); i++) { + if (m_modpacks[i]->logoName == logo) { emit dataChanged(createIndex(i, 0), createIndex(i, 0), { Qt::DecorationRole }); } } @@ -290,65 +280,38 @@ void ModpackListModel::logoFailed(QString logo) m_loadingLogos.removeAll(logo); } -void ModpackListModel::searchRequestFinished(QJsonDocument& doc_all) +void ModpackListModel::searchRequestFinished(QList& newList) { - jobPtr.reset(); + m_jobPtr.reset(); - QList newList; - - auto packs_all = doc_all.object().value("hits").toArray(); - for (auto packRaw : packs_all) { - auto packObj = packRaw.toObject(); - - Modrinth::Modpack pack; - try { - Modrinth::loadIndexedPack(pack, packObj); - newList.append(pack); - } catch (const JSONValidationError& e) { - qWarning() << "Error while loading mod from " << m_parent->debugName() << ": " << e.cause(); - continue; - } - } - - if (packs_all.size() < m_modpacks_per_page) { - searchState = Finished; + if (newList.size() < m_modpacks_per_page) { + m_searchState = Finished; } else { - nextSearchOffset += m_modpacks_per_page; - searchState = CanPossiblyFetchMore; + m_nextSearchOffset += m_modpacks_per_page; + m_searchState = CanPossiblyFetchMore; } // When you have a Qt build with assertions turned on, proceeding here will abort the application if (newList.size() == 0) return; - beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1); - modpacks.append(newList); + beginInsertRows(QModelIndex(), m_modpacks.size(), m_modpacks.size() + newList.size() - 1); + m_modpacks.append(newList); endInsertRows(); } -void ModpackListModel::searchRequestForOneSucceeded(QJsonDocument& doc) +void ModpackListModel::searchRequestForOneSucceeded(ModPlatform::IndexedPack& pack) { - jobPtr.reset(); + m_jobPtr.reset(); - auto packObj = doc.object(); - - Modrinth::Modpack pack; - try { - Modrinth::loadIndexedPack(pack, packObj); - pack.id = Json::ensureString(packObj, "id", pack.id); - } catch (const JSONValidationError& e) { - qWarning() << "Error while loading mod from " << m_parent->debugName() << ": " << e.cause(); - return; - } - - beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + 1); - modpacks.append({ pack }); + beginInsertRows(QModelIndex(), m_modpacks.size(), m_modpacks.size() + 1); + m_modpacks.append(std::make_shared(pack)); endInsertRows(); } void ModpackListModel::searchRequestFailed(QString) { - auto failed_action = dynamic_cast(jobPtr.get())->getFailedActions().at(0); + auto failed_action = dynamic_cast(m_jobPtr.get())->getFailedActions().at(0); if (failed_action->replyStatusCode() == -1) { // Network error QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load modpacks.")); @@ -360,17 +323,17 @@ void ModpackListModel::searchRequestFailed(QString) .arg(m_parent->displayName()) .arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_DISPLAYNAME))); } - jobPtr.reset(); + m_jobPtr.reset(); - if (searchState == ResetRequested) { + if (m_searchState == ResetRequested) { beginResetModel(); - modpacks.clear(); + m_modpacks.clear(); endResetModel(); - nextSearchOffset = 0; + m_nextSearchOffset = 0; performPaginatedSearch(); } else { - searchState = Finished; + m_searchState = Finished; } } diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h index 640ddf688..7037f4745 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h @@ -37,7 +37,7 @@ #include -#include "modplatform/modrinth/ModrinthPackManifest.h" +#include "modplatform/ModIndex.h" #include "net/NetJob.h" #include "ui/pages/modplatform/modrinth/ModrinthPage.h" @@ -56,7 +56,7 @@ class ModpackListModel : public QAbstractListModel { ModpackListModel(ModrinthPage* parent); ~ModpackListModel() override = default; - inline auto rowCount(const QModelIndex& parent) const -> int override { return parent.isValid() ? 0 : modpacks.size(); }; + inline auto rowCount(const QModelIndex& parent) const -> int override { return parent.isValid() ? 0 : m_modpacks.size(); }; inline auto columnCount(const QModelIndex& parent) const -> int override { return parent.isValid() ? 0 : 1; }; inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); }; @@ -66,27 +66,27 @@ class ModpackListModel : public QAbstractListModel { auto data(const QModelIndex& index, int role) const -> QVariant override; bool setData(const QModelIndex& index, const QVariant& value, int role) override; - inline void setActiveJob(NetJob::Ptr ptr) { jobPtr = ptr; } + inline void setActiveJob(NetJob::Ptr ptr) { m_jobPtr = ptr; } /* Ask the API for more information */ void fetchMore(const QModelIndex& parent) override; void refresh(); void searchWithTerm(const QString& term, int sort, std::shared_ptr filter, bool filterChanged); - [[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); } - [[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; } + bool hasActiveSearchJob() const { return m_jobPtr && m_jobPtr->isRunning(); } + Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? m_jobPtr : nullptr; } void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback); inline auto canFetchMore(const QModelIndex& parent) const -> bool override { - return parent.isValid() ? false : searchState == CanPossiblyFetchMore; + return parent.isValid() ? false : m_searchState == CanPossiblyFetchMore; }; public slots: - void searchRequestFinished(QJsonDocument& doc_all); + void searchRequestFinished(QList& doc_all); void searchRequestFailed(QString reason); - void searchRequestForOneSucceeded(QJsonDocument&); + void searchRequestForOneSucceeded(ModPlatform::IndexedPack&); protected slots: @@ -103,20 +103,20 @@ class ModpackListModel : public QAbstractListModel { protected: ModrinthPage* m_parent; - QList modpacks; + QList m_modpacks; LogoMap m_logoMap; - QMap waitingCallbacks; + QMap m_waitingCallbacks; QStringList m_failedLogos; QStringList m_loadingLogos; - QString currentSearchTerm; - QString currentSort; + QString m_currentSearchTerm; + QString m_currentSort; std::shared_ptr m_filter; - int nextSearchOffset = 0; - enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None; + int m_nextSearchOffset = 0; + enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } m_searchState = None; - Task::Ptr jobPtr; + Task::Ptr m_jobPtr; std::shared_ptr m_allResponse = std::make_shared(); QByteArray m_specific_response; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index 4586bf70d..768f2f492 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -36,6 +36,7 @@ #include "ModrinthPage.h" #include "Version.h" +#include "modplatform/ModIndex.h" #include "modplatform/modrinth/ModrinthAPI.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui_ModrinthPage.h" @@ -57,17 +58,17 @@ #include ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) - : QWidget(parent), ui(new Ui::ModrinthPage), dialog(dialog), m_fetch_progress(this, false) + : QWidget(parent), m_ui(new Ui::ModrinthPage), m_dialog(dialog), m_fetch_progress(this, false) { - ui->setupUi(this); + m_ui->setupUi(this); createFilterWidget(); - ui->searchEdit->installEventFilter(this); + m_ui->searchEdit->installEventFilter(this); m_model = new Modrinth::ModpackListModel(this); - ui->packView->setModel(m_model); + m_ui->packView->setModel(m_model); - ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); + 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); @@ -78,30 +79,30 @@ ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) m_fetch_progress.setFixedHeight(24); m_fetch_progress.progressFormat(""); - ui->verticalLayout->insertWidget(1, &m_fetch_progress); + m_ui->verticalLayout->insertWidget(1, &m_fetch_progress); - ui->sortByBox->addItem(tr("Sort by Relevance")); - ui->sortByBox->addItem(tr("Sort by Total Downloads")); - ui->sortByBox->addItem(tr("Sort by Follows")); - ui->sortByBox->addItem(tr("Sort by Newest")); - ui->sortByBox->addItem(tr("Sort by Last Updated")); + m_ui->sortByBox->addItem(tr("Sort by Relevance")); + m_ui->sortByBox->addItem(tr("Sort by Total Downloads")); + m_ui->sortByBox->addItem(tr("Sort by Follows")); + m_ui->sortByBox->addItem(tr("Sort by Newest")); + m_ui->sortByBox->addItem(tr("Sort by Last Updated")); - connect(ui->sortByBox, &QComboBox::currentIndexChanged, this, &ModrinthPage::triggerSearch); - connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthPage::onSelectionChanged); - connect(ui->versionSelectionBox, &QComboBox::currentIndexChanged, this, &ModrinthPage::onVersionSelectionChanged); + connect(m_ui->sortByBox, &QComboBox::currentIndexChanged, this, &ModrinthPage::triggerSearch); + connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthPage::onSelectionChanged); + connect(m_ui->versionSelectionBox, &QComboBox::currentIndexChanged, this, &ModrinthPage::onVersionSelectionChanged); - ui->packView->setItemDelegate(new ProjectItemDelegate(this)); - ui->packDescription->setMetaEntry(metaEntryBase()); + m_ui->packView->setItemDelegate(new ProjectItemDelegate(this)); + m_ui->packDescription->setMetaEntry(metaEntryBase()); } ModrinthPage::~ModrinthPage() { - delete ui; + delete m_ui; } void ModrinthPage::retranslate() { - ui->retranslateUi(this); + m_ui->retranslateUi(this); } void ModrinthPage::openedImpl() @@ -113,7 +114,7 @@ void ModrinthPage::openedImpl() bool ModrinthPage::eventFilter(QObject* watched, QEvent* event) { - if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) { + if (watched == m_ui->searchEdit && event->type() == QEvent::KeyPress) { auto* keyEvent = reinterpret_cast(event); if (keyEvent->key() == Qt::Key_Return) { this->triggerSearch(); @@ -129,146 +130,108 @@ bool ModrinthPage::eventFilter(QObject* watched, QEvent* event) return QObject::eventFilter(watched, event); } -bool checkVersionFilters(const Modrinth::ModpackVersion& v, std::shared_ptr filter) -{ - if (!filter) - return true; - return ((!filter->loaders || !v.loaders || filter->loaders & v.loaders) && // loaders - (filter->releases.empty() || // releases - std::find(filter->releases.cbegin(), filter->releases.cend(), v.version_type) != filter->releases.cend()) && - filter->checkMcVersions({ v.gameVersion })); // gameVersion} -} - void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev) { - ui->versionSelectionBox->clear(); + m_ui->versionSelectionBox->clear(); if (!curr.isValid()) { if (isOpened) { - dialog->setSuggestedPack(); + m_dialog->setSuggestedPack(); } return; } - QVariant raw = m_model->data(curr, Qt::UserRole); - Q_ASSERT(raw.canConvert()); - current = raw.value(); - auto name = current.name; + m_current = m_model->data(curr, Qt::UserRole).value(); + auto name = m_current->name; - if (!current.extraInfoLoaded) { + if (!m_current->extraDataLoaded) { qDebug() << "Loading modrinth modpack information"; + ResourceAPI::Callback callbacks; - auto netJob = new NetJob(QString("Modrinth::PackInformation(%1)").arg(current.name), APPLICATION->network()); - auto response = std::make_shared(); - - QString id = current.id; - - netJob->addNetAction(Net::ApiDownload::makeByteArray(QString("%1/project/%2").arg(BuildConfig.MODRINTH_PROD_URL, id), response)); - - connect(netJob, &NetJob::succeeded, this, [this, response, id, curr] { - if (id != current.id) { + auto id = m_current->addonId; + callbacks.on_fail = [this](QString reason, int) { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); + }; + callbacks.on_succeed = [this, id, curr](auto& pack) { + if (id != m_current->addonId) { return; // wrong request? } - QJsonParseError parse_error; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from Modrinth at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qWarning() << *response; - return; - } - - auto obj = Json::requireObject(doc); - - try { - Modrinth::loadIndexedInfo(current, obj); - } catch (const JSONValidationError& e) { - qDebug() << *response; - qWarning() << "Error while reading modrinth modpack version: " << e.cause(); - } - - updateUI(); + *m_current = pack; QVariant current_updated; - current_updated.setValue(current); + current_updated.setValue(m_current); if (!m_model->setData(curr, current_updated, Qt::UserRole)) qWarning() << "Failed to cache extra info for the current pack!"; suggestCurrent(); - }); - connect(netJob, &NetJob::finished, this, [response, netJob] { netJob->deleteLater(); }); - connect(netJob, &NetJob::failed, - [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); - netJob->start(); + updateUI(); + }; + if (auto netJob = m_api.getProjectInfo({ { m_current->addonId } }, std::move(callbacks)); netJob) { + m_job = netJob; + m_job->start(); + } + } else updateUI(); - if (!current.versionsLoaded || m_filterWidget->changed()) { + if (!m_current->versionsLoaded || m_filterWidget->changed()) { qDebug() << "Loading modrinth modpack versions"; - auto netJob = new NetJob(QString("Modrinth::PackVersions(%1)").arg(current.name), APPLICATION->network()); - auto response = std::make_shared(); + ResourceAPI::Callback> callbacks{}; - QString id = current.id; - - netJob->addNetAction( - Net::ApiDownload::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response)); - - connect(netJob, &NetJob::succeeded, this, [this, response, id, curr] { - if (id != current.id) { - return; // wrong request? + auto addonId = m_current->addonId; + // Use default if no callbacks are set + callbacks.on_succeed = [this, curr, addonId](auto& doc) { + if (addonId != m_current->addonId) { + return; // wrong request } - QJsonParseError parse_error; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from Modrinth at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qWarning() << *response; - return; - } - - try { - Modrinth::loadIndexedVersions(current, doc); - } catch (const JSONValidationError& e) { - qDebug() << *response; - qWarning() << "Error while reading modrinth modpack version: " << e.cause(); - } - auto pred = [this](const Modrinth::ModpackVersion& v) { return !checkVersionFilters(v, m_filterWidget->getFilter()); }; + m_current->versions = doc; + m_current->versionsLoaded = true; + auto pred = [this](const ModPlatform::IndexedVersion& v) { + if (auto filter = m_filterWidget->getFilter()) + return !filter->checkModpackFilters(v); + return false; + }; #if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0) - current.versions.removeIf(pred); + m_current->versions.removeIf(pred); #else - for (auto it = current.versions.begin(); it != current.versions.end();) - if (pred(*it)) - it = current.versions.erase(it); - else - ++it; + for (auto it = m_current->versions.begin(); it != m_current->versions.end();) + if (pred(*it)) + it = m_current->versions.erase(it); + else + ++it; #endif - for (const auto& version : current.versions) { - ui->versionSelectionBox->addItem(Modrinth::getVersionDisplayString(version), QVariant(version.id)); + for (const auto& version : m_current->versions) { + m_ui->versionSelectionBox->addItem(version.getVersionDisplayString(), QVariant(version.addonId)); } QVariant current_updated; - current_updated.setValue(current); + current_updated.setValue(m_current); if (!m_model->setData(curr, current_updated, Qt::UserRole)) qWarning() << "Failed to cache versions for the current pack!"; suggestCurrent(); - }); - connect(netJob, &NetJob::finished, this, [response, netJob] { netJob->deleteLater(); }); - connect(netJob, &NetJob::failed, - [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); - netJob->start(); + }; + callbacks.on_fail = [this](QString reason, int) { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); + }; + + auto netJob = m_api.getProjectVersions({ *m_current, {}, {}, ModPlatform::ResourceType::Modpack }, std::move(callbacks)); + + m_job2 = netJob; + m_job2->start(); } else { - for (auto version : current.versions) { - if (!version.name.contains(version.version)) - ui->versionSelectionBox->addItem(QString("%1 - %2").arg(version.name, version.version), QVariant(version.id)); + for (auto version : m_current->versions) { + if (!version.version.contains(version.version)) + m_ui->versionSelectionBox->addItem(QString("%1 - %2").arg(version.version, version.version_number), + QVariant(version.addonId)); else - ui->versionSelectionBox->addItem(version.name, QVariant(version.id)); + m_ui->versionSelectionBox->addItem(version.version, QVariant(version.addonId)); } suggestCurrent(); @@ -279,53 +242,64 @@ void ModrinthPage::updateUI() { QString text = ""; - if (current.extra.projectUrl.isEmpty()) - text = current.name; + if (m_current->websiteUrl.isEmpty()) + text = m_current->name; else - text = "" + current.name + ""; + text = "websiteUrl + "\">" + m_current->name + ""; - // TODO: Implement multiple authors with links - text += "
" + tr(" by ") + QString("%2").arg(std::get<1>(current.author).toString(), std::get<0>(current.author)); + if (!m_current->authors.empty()) { + auto authorToStr = [](ModPlatform::ModpackAuthor& author) { + if (author.url.isEmpty()) { + return author.name; + } + return QString("%2").arg(author.url, author.name); + }; + QStringList authorStrs; + for (auto& author : m_current->authors) { + authorStrs.push_back(authorToStr(author)); + } + text += "
" + tr(" by ") + authorStrs.join(", "); + } - if (current.extraInfoLoaded) { - if (current.extra.status == "archived") { + if (m_current->extraDataLoaded) { + if (m_current->extraData.status == "archived") { text += "

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

" + tr("Donate information: "); - auto donateToStr = [](Modrinth::DonationData& donate) -> QString { + auto donateToStr = [](ModPlatform::DonationData& donate) -> QString { return QString("%2").arg(donate.url, donate.platform); }; QStringList donates; - for (auto& donate : current.extra.donate) { + for (auto& donate : m_current->extraData.donate) { donates.append(donateToStr(donate)); } text += donates.join(", "); } - if (!current.extra.issuesUrl.isEmpty() || !current.extra.sourceUrl.isEmpty() || !current.extra.wikiUrl.isEmpty() || - !current.extra.discordUrl.isEmpty()) { + if (!m_current->extraData.issuesUrl.isEmpty() || !m_current->extraData.sourceUrl.isEmpty() || + !m_current->extraData.wikiUrl.isEmpty() || !m_current->extraData.discordUrl.isEmpty()) { text += "

" + tr("External links:") + "
"; } - if (!current.extra.issuesUrl.isEmpty()) - text += "- " + tr("Issues: %1").arg(current.extra.issuesUrl) + "
"; - if (!current.extra.wikiUrl.isEmpty()) - text += "- " + tr("Wiki: %1").arg(current.extra.wikiUrl) + "
"; - if (!current.extra.sourceUrl.isEmpty()) - text += "- " + tr("Source code: %1").arg(current.extra.sourceUrl) + "
"; - if (!current.extra.discordUrl.isEmpty()) - text += "- " + tr("Discord: %1").arg(current.extra.discordUrl) + "
"; + if (!m_current->extraData.issuesUrl.isEmpty()) + text += "- " + tr("Issues: %1").arg(m_current->extraData.issuesUrl) + "
"; + if (!m_current->extraData.wikiUrl.isEmpty()) + text += "- " + tr("Wiki: %1").arg(m_current->extraData.wikiUrl) + "
"; + if (!m_current->extraData.sourceUrl.isEmpty()) + text += "- " + tr("Source code: %1").arg(m_current->extraData.sourceUrl) + "
"; + if (!m_current->extraData.discordUrl.isEmpty()) + text += "- " + tr("Discord: %1").arg(m_current->extraData.discordUrl) + "
"; } text += "
"; - text += markdownToHTML(current.extra.body.toUtf8()); + text += markdownToHTML(m_current->extraData.body.toUtf8()); - ui->packDescription->setHtml(StringUtils::htmlListPatch(text + current.description)); - ui->packDescription->flush(); + m_ui->packDescription->setHtml(StringUtils::htmlListPatch(text + m_current->description)); + m_ui->packDescription->flush(); } void ModrinthPage::suggestCurrent() @@ -334,21 +308,21 @@ void ModrinthPage::suggestCurrent() return; } - if (selectedVersion.isEmpty()) { - dialog->setSuggestedPack(); + if (m_selectedVersion.isEmpty()) { + m_dialog->setSuggestedPack(); return; } - for (auto& ver : current.versions) { - if (ver.id == selectedVersion) { + for (auto& ver : m_current->versions) { + if (ver.addonId == m_selectedVersion) { QMap extra_info; - extra_info.insert("pack_id", current.id); - extra_info.insert("pack_version_id", ver.id); + extra_info.insert("pack_id", m_current->addonId.toString()); + extra_info.insert("pack_version_id", ver.fileId.toString()); - dialog->setSuggestedPack(current.name, ver.version, new InstanceImportTask(ver.download_url, this, std::move(extra_info))); - auto iconName = current.iconName; - m_model->getLogo(iconName, current.iconUrl.toString(), - [this, iconName](QString logo) { dialog->setSuggestedIconFromFile(logo, iconName); }); + m_dialog->setSuggestedPack(m_current->name, ver.version, new InstanceImportTask(ver.downloadUrl, this, std::move(extra_info))); + auto iconName = m_current->logoName; + m_model->getLogo(iconName, m_current->logoUrl, + [this, iconName](QString logo) { m_dialog->setSuggestedIconFromFile(logo, iconName); }); break; } @@ -357,45 +331,46 @@ void ModrinthPage::suggestCurrent() void ModrinthPage::triggerSearch() { - ui->packView->selectionModel()->setCurrentIndex({}, QItemSelectionModel::SelectionFlag::ClearAndSelect); - ui->packView->clearSelection(); - ui->packDescription->clear(); - ui->versionSelectionBox->clear(); - m_model->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex(), m_filterWidget->getFilter(), m_filterWidget->changed()); + m_ui->packView->selectionModel()->setCurrentIndex({}, QItemSelectionModel::SelectionFlag::ClearAndSelect); + m_ui->packView->clearSelection(); + m_ui->packDescription->clear(); + m_ui->versionSelectionBox->clear(); + bool filterChanged = m_filterWidget->changed(); + m_model->searchWithTerm(m_ui->searchEdit->text(), m_ui->sortByBox->currentIndex(), m_filterWidget->getFilter(), filterChanged); m_fetch_progress.watch(m_model->activeSearchJob().get()); } void ModrinthPage::onVersionSelectionChanged(int index) { if (index == -1) { - selectedVersion = ""; + m_selectedVersion = ""; return; } - selectedVersion = ui->versionSelectionBox->itemData(index).toString(); + m_selectedVersion = m_ui->versionSelectionBox->itemData(index).toString(); suggestCurrent(); } void ModrinthPage::setSearchTerm(QString term) { - ui->searchEdit->setText(term); + m_ui->searchEdit->setText(term); } QString ModrinthPage::getSerachTerm() const { - return ui->searchEdit->text(); + return m_ui->searchEdit->text(); } void ModrinthPage::createFilterWidget() { auto widget = ModFilterWidget::create(nullptr, true); m_filterWidget.swap(widget); - auto old = ui->splitter->replaceWidget(0, m_filterWidget.get()); + auto old = m_ui->splitter->replaceWidget(0, m_filterWidget.get()); // because we replaced the widget we also need to delete it if (old) { delete old; } - connect(ui->filterButton, &QPushButton::clicked, this, [this] { m_filterWidget->setHidden(!m_filterWidget->isHidden()); }); + connect(m_ui->filterButton, &QPushButton::clicked, this, [this] { m_filterWidget->setHidden(!m_filterWidget->isHidden()); }); connect(m_filterWidget.get(), &ModFilterWidget::filterChanged, this, &ModrinthPage::triggerSearch); auto response = std::make_shared(); diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h index d22a72e4e..4ca41a3e0 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h @@ -36,10 +36,10 @@ #pragma once -#include "Application.h" +#include "modplatform/ModIndex.h" +#include "modplatform/modrinth/ModrinthAPI.h" #include "ui/dialogs/NewInstanceDialog.h" -#include "modplatform/modrinth/ModrinthPackManifest.h" #include "ui/pages/modplatform/ModpackProviderBasePage.h" #include "ui/widgets/ModFilterWidget.h" #include "ui/widgets/ProgressWidget.h" @@ -63,14 +63,14 @@ class ModrinthPage : public QWidget, public ModpackProviderBasePage { ~ModrinthPage() override; QString displayName() const override { return tr("Modrinth"); } - QIcon icon() const override { return APPLICATION->getThemedIcon("modrinth"); } + QIcon icon() const override { return QIcon::fromTheme("modrinth"); } QString id() const override { return "modrinth"; } QString helpPage() const override { return "Modrinth-platform"; } - inline auto debugName() const -> QString { return "Modrinth"; } - inline auto metaEntryBase() const -> QString { return "ModrinthModpacks"; }; + inline QString debugName() const { return "Modrinth"; } + inline QString metaEntryBase() const { return "ModrinthModpacks"; }; - auto getCurrent() -> Modrinth::Modpack& { return current; } + ModPlatform::IndexedPack::Ptr getCurrent() { return m_current; } void suggestCurrent(); void updateUI(); @@ -82,7 +82,7 @@ class ModrinthPage : public QWidget, public ModpackProviderBasePage { /** Programatically set the term in the search bar. */ virtual void setSearchTerm(QString) override; /** Get the current term in the search bar. */ - [[nodiscard]] virtual QString getSerachTerm() const override; + virtual QString getSerachTerm() const override; private slots: void onSelectionChanged(QModelIndex first, QModelIndex second); @@ -91,12 +91,12 @@ class ModrinthPage : public QWidget, public ModpackProviderBasePage { void createFilterWidget(); private: - Ui::ModrinthPage* ui; - NewInstanceDialog* dialog; + Ui::ModrinthPage* m_ui; + NewInstanceDialog* m_dialog; Modrinth::ModpackListModel* m_model; - Modrinth::Modpack current; - QString selectedVersion; + ModPlatform::IndexedPack::Ptr m_current; + QString m_selectedVersion; ProgressWidget m_fetch_progress; @@ -105,4 +105,8 @@ class ModrinthPage : public QWidget, public ModpackProviderBasePage { std::unique_ptr m_filterWidget; Task::Ptr m_categoriesTask; + + ModrinthAPI m_api; + Task::Ptr m_job; + Task::Ptr m_job2; }; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp deleted file mode 100644 index 91e9ad791..000000000 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp +++ /dev/null @@ -1,144 +0,0 @@ -// SPDX-FileCopyrightText: 2023 flowln -// -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * 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 . - */ - -#include "ModrinthResourceModels.h" - -#include "modplatform/modrinth/ModrinthAPI.h" -#include "modplatform/modrinth/ModrinthPackIndex.h" - -namespace ResourceDownload { - -ModrinthModModel::ModrinthModModel(BaseInstance& base) : ModModel(base, new ModrinthAPI) {} - -void ModrinthModModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - ::Modrinth::loadIndexedPack(m, obj); -} - -void ModrinthModModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - ::Modrinth::loadExtraPackData(m, obj); -} - -void ModrinthModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) -{ - ::Modrinth::loadIndexedPackVersions(m, arr); -} - -auto ModrinthModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion -{ - return ::Modrinth::loadDependencyVersions(m, arr, &m_base_instance); -} - -auto ModrinthModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray -{ - return obj.object().value("hits").toArray(); -} - -ModrinthResourcePackModel::ModrinthResourcePackModel(const BaseInstance& base) : ResourcePackResourceModel(base, new ModrinthAPI) {} - -void ModrinthResourcePackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - ::Modrinth::loadIndexedPack(m, obj); -} - -void ModrinthResourcePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - ::Modrinth::loadExtraPackData(m, obj); -} - -void ModrinthResourcePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) -{ - ::Modrinth::loadIndexedPackVersions(m, arr); -} - -auto ModrinthResourcePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray -{ - return obj.object().value("hits").toArray(); -} - -ModrinthTexturePackModel::ModrinthTexturePackModel(const BaseInstance& base) : TexturePackResourceModel(base, new ModrinthAPI) {} - -void ModrinthTexturePackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - ::Modrinth::loadIndexedPack(m, obj); -} - -void ModrinthTexturePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - ::Modrinth::loadExtraPackData(m, obj); -} - -void ModrinthTexturePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) -{ - ::Modrinth::loadIndexedPackVersions(m, arr); -} - -auto ModrinthTexturePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray -{ - return obj.object().value("hits").toArray(); -} - -ModrinthShaderPackModel::ModrinthShaderPackModel(const BaseInstance& base) : ShaderPackResourceModel(base, new ModrinthAPI) {} - -void ModrinthShaderPackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - ::Modrinth::loadIndexedPack(m, obj); -} - -void ModrinthShaderPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - ::Modrinth::loadExtraPackData(m, obj); -} - -void ModrinthShaderPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) -{ - ::Modrinth::loadIndexedPackVersions(m, arr); -} - -auto ModrinthShaderPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray -{ - return obj.object().value("hits").toArray(); -} - -ModrinthDataPackModel::ModrinthDataPackModel(const BaseInstance& base) : DataPackResourceModel(base, new ModrinthAPI) {} - -void ModrinthDataPackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - ::Modrinth::loadIndexedPack(m, obj); -} - -void ModrinthDataPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - ::Modrinth::loadExtraPackData(m, obj); -} - -void ModrinthDataPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) -{ - ::Modrinth::loadIndexedPackVersions(m, arr); -} - -auto ModrinthDataPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray -{ - return obj.object().value("hits").toArray(); -} - - -} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h deleted file mode 100644 index 6a5ba0382..000000000 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h +++ /dev/null @@ -1,121 +0,0 @@ -// SPDX-FileCopyrightText: 2023 flowln -// -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * 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 . - */ - -#pragma once - -#include "ui/pages/modplatform/DataPackModel.h" -#include "ui/pages/modplatform/ModModel.h" -#include "ui/pages/modplatform/ResourcePackModel.h" -#include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h" - -namespace ResourceDownload { - -class ModrinthModModel : public ModModel { - Q_OBJECT - - public: - ModrinthModModel(BaseInstance&); - ~ModrinthModModel() override = default; - - private: - [[nodiscard]] QString debugName() const override { return Modrinth::debugName() + " (Model)"; } - [[nodiscard]] QString metaEntryBase() const override { return Modrinth::metaEntryBase(); } - - void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; - auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion override; - - auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; -}; - -class ModrinthResourcePackModel : public ResourcePackResourceModel { - Q_OBJECT - - public: - ModrinthResourcePackModel(const BaseInstance&); - ~ModrinthResourcePackModel() override = default; - - private: - [[nodiscard]] QString debugName() const override { return Modrinth::debugName() + " (Model)"; } - [[nodiscard]] QString metaEntryBase() const override { return Modrinth::metaEntryBase(); } - - void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; - - auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; -}; - -class ModrinthTexturePackModel : public TexturePackResourceModel { - Q_OBJECT - - public: - ModrinthTexturePackModel(const BaseInstance&); - ~ModrinthTexturePackModel() override = default; - - private: - [[nodiscard]] QString debugName() const override { return Modrinth::debugName() + " (Model)"; } - [[nodiscard]] QString metaEntryBase() const override { return Modrinth::metaEntryBase(); } - - void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; - - auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; -}; - -class ModrinthShaderPackModel : public ShaderPackResourceModel { - Q_OBJECT - - public: - ModrinthShaderPackModel(const BaseInstance&); - ~ModrinthShaderPackModel() override = default; - - private: - [[nodiscard]] QString debugName() const override { return Modrinth::debugName() + " (Model)"; } - [[nodiscard]] QString metaEntryBase() const override { return Modrinth::metaEntryBase(); } - - void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; - - auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; -}; - -class ModrinthDataPackModel : public DataPackResourceModel { - Q_OBJECT - - public: - ModrinthDataPackModel(const BaseInstance&); - ~ModrinthDataPackModel() override = default; - - private: - [[nodiscard]] QString debugName() const override { return Modrinth::debugName() + " (Model)"; } - [[nodiscard]] QString metaEntryBase() const override { return Modrinth::metaEntryBase(); } - - void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; - - auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; -}; - -} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index aca71cde5..32296316f 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -37,19 +37,18 @@ */ #include "ModrinthResourcePages.h" +#include "ui/pages/modplatform/DataPackModel.h" #include "ui_ResourcePage.h" #include "modplatform/modrinth/ModrinthAPI.h" #include "ui/dialogs/ResourceDownloadDialog.h" -#include "ui/pages/modplatform/modrinth/ModrinthResourceModels.h" - namespace ResourceDownload { ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ModPage(dialog, instance) { - m_model = new ModrinthModModel(instance); + m_model = new ModModel(instance, new ModrinthAPI(), Modrinth::debugName(), Modrinth::metaEntryBase()); m_ui->packView->setModel(m_model); addSortings(); @@ -67,7 +66,7 @@ ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instan ModrinthResourcePackPage::ModrinthResourcePackPage(ResourcePackDownloadDialog* dialog, BaseInstance& instance) : ResourcePackResourcePage(dialog, instance) { - m_model = new ModrinthResourcePackModel(instance); + m_model = new ResourcePackResourceModel(instance, new ModrinthAPI(), Modrinth::debugName(), Modrinth::metaEntryBase()); m_ui->packView->setModel(m_model); addSortings(); @@ -85,7 +84,7 @@ ModrinthResourcePackPage::ModrinthResourcePackPage(ResourcePackDownloadDialog* d ModrinthTexturePackPage::ModrinthTexturePackPage(TexturePackDownloadDialog* dialog, BaseInstance& instance) : TexturePackResourcePage(dialog, instance) { - m_model = new ModrinthTexturePackModel(instance); + m_model = new TexturePackResourceModel(instance, new ModrinthAPI(), Modrinth::debugName(), Modrinth::metaEntryBase()); m_ui->packView->setModel(m_model); addSortings(); @@ -103,7 +102,7 @@ ModrinthTexturePackPage::ModrinthTexturePackPage(TexturePackDownloadDialog* dial ModrinthShaderPackPage::ModrinthShaderPackPage(ShaderPackDownloadDialog* dialog, BaseInstance& instance) : ShaderPackResourcePage(dialog, instance) { - m_model = new ModrinthShaderPackModel(instance); + m_model = new ShaderPackResourceModel(instance, new ModrinthAPI(), Modrinth::debugName(), Modrinth::metaEntryBase()); m_ui->packView->setModel(m_model); addSortings(); @@ -118,10 +117,9 @@ ModrinthShaderPackPage::ModrinthShaderPackPage(ShaderPackDownloadDialog* dialog, m_ui->packDescription->setMetaEntry(metaEntryBase()); } -ModrinthDataPackPage::ModrinthDataPackPage(DataPackDownloadDialog* dialog, BaseInstance& instance) - : DataPackResourcePage(dialog, instance) +ModrinthDataPackPage::ModrinthDataPackPage(DataPackDownloadDialog* dialog, BaseInstance& instance) : DataPackResourcePage(dialog, instance) { - m_model = new ModrinthDataPackModel(instance); + m_model = new DataPackResourceModel(instance, new ModrinthAPI(), Modrinth::debugName(), Modrinth::metaEntryBase()); m_ui->packView->setModel(m_model); addSortings(); diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h index a4c7344b5..3f41a3d5e 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h @@ -38,8 +38,6 @@ #pragma once -#include "Application.h" - #include "modplatform/ResourceAPI.h" #include "ui/pages/modplatform/DataPackPage.h" @@ -57,7 +55,7 @@ static inline QString displayName() } static inline QIcon icon() { - return APPLICATION->getThemedIcon("modrinth"); + return QIcon::fromTheme("modrinth"); } static inline QString id() { @@ -85,15 +83,15 @@ class ModrinthModPage : public ModPage { ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instance); ~ModrinthModPage() override = default; - [[nodiscard]] bool shouldDisplay() const override; + bool shouldDisplay() const override; - [[nodiscard]] inline auto displayName() const -> QString override { return Modrinth::displayName(); } - [[nodiscard]] inline auto icon() const -> QIcon override { return Modrinth::icon(); } - [[nodiscard]] inline auto id() const -> QString override { return Modrinth::id(); } - [[nodiscard]] inline auto debugName() const -> QString override { return Modrinth::debugName(); } - [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Modrinth::metaEntryBase(); } + inline auto displayName() const -> QString override { return Modrinth::displayName(); } + inline auto icon() const -> QIcon override { return Modrinth::icon(); } + inline auto id() const -> QString override { return Modrinth::id(); } + inline auto debugName() const -> QString override { return Modrinth::debugName(); } + inline auto metaEntryBase() const -> QString override { return Modrinth::metaEntryBase(); } - [[nodiscard]] inline auto helpPage() const -> QString override { return "Mod-platform"; } + inline auto helpPage() const -> QString override { return "Mod-platform"; } std::unique_ptr createFilterWidget() override; @@ -114,15 +112,15 @@ class ModrinthResourcePackPage : public ResourcePackResourcePage { ModrinthResourcePackPage(ResourcePackDownloadDialog* dialog, BaseInstance& instance); ~ModrinthResourcePackPage() override = default; - [[nodiscard]] bool shouldDisplay() const override; + bool shouldDisplay() const override; - [[nodiscard]] inline auto displayName() const -> QString override { return Modrinth::displayName(); } - [[nodiscard]] inline auto icon() const -> QIcon override { return Modrinth::icon(); } - [[nodiscard]] inline auto id() const -> QString override { return Modrinth::id(); } - [[nodiscard]] inline auto debugName() const -> QString override { return Modrinth::debugName(); } - [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Modrinth::metaEntryBase(); } + inline auto displayName() const -> QString override { return Modrinth::displayName(); } + inline auto icon() const -> QIcon override { return Modrinth::icon(); } + inline auto id() const -> QString override { return Modrinth::id(); } + inline auto debugName() const -> QString override { return Modrinth::debugName(); } + inline auto metaEntryBase() const -> QString override { return Modrinth::metaEntryBase(); } - [[nodiscard]] inline auto helpPage() const -> QString override { return ""; } + inline auto helpPage() const -> QString override { return ""; } }; class ModrinthTexturePackPage : public TexturePackResourcePage { @@ -137,15 +135,15 @@ class ModrinthTexturePackPage : public TexturePackResourcePage { ModrinthTexturePackPage(TexturePackDownloadDialog* dialog, BaseInstance& instance); ~ModrinthTexturePackPage() override = default; - [[nodiscard]] bool shouldDisplay() const override; + bool shouldDisplay() const override; - [[nodiscard]] inline auto displayName() const -> QString override { return Modrinth::displayName(); } - [[nodiscard]] inline auto icon() const -> QIcon override { return Modrinth::icon(); } - [[nodiscard]] inline auto id() const -> QString override { return Modrinth::id(); } - [[nodiscard]] inline auto debugName() const -> QString override { return Modrinth::debugName(); } - [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Modrinth::metaEntryBase(); } + inline auto displayName() const -> QString override { return Modrinth::displayName(); } + inline auto icon() const -> QIcon override { return Modrinth::icon(); } + inline auto id() const -> QString override { return Modrinth::id(); } + inline auto debugName() const -> QString override { return Modrinth::debugName(); } + inline auto metaEntryBase() const -> QString override { return Modrinth::metaEntryBase(); } - [[nodiscard]] inline auto helpPage() const -> QString override { return ""; } + inline auto helpPage() const -> QString override { return ""; } }; class ModrinthShaderPackPage : public ShaderPackResourcePage { @@ -160,15 +158,15 @@ class ModrinthShaderPackPage : public ShaderPackResourcePage { ModrinthShaderPackPage(ShaderPackDownloadDialog* dialog, BaseInstance& instance); ~ModrinthShaderPackPage() override = default; - [[nodiscard]] bool shouldDisplay() const override; + bool shouldDisplay() const override; - [[nodiscard]] inline auto displayName() const -> QString override { return Modrinth::displayName(); } - [[nodiscard]] inline auto icon() const -> QIcon override { return Modrinth::icon(); } - [[nodiscard]] inline auto id() const -> QString override { return Modrinth::id(); } - [[nodiscard]] inline auto debugName() const -> QString override { return Modrinth::debugName(); } - [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Modrinth::metaEntryBase(); } + inline auto displayName() const -> QString override { return Modrinth::displayName(); } + inline auto icon() const -> QIcon override { return Modrinth::icon(); } + inline auto id() const -> QString override { return Modrinth::id(); } + inline auto debugName() const -> QString override { return Modrinth::debugName(); } + inline auto metaEntryBase() const -> QString override { return Modrinth::metaEntryBase(); } - [[nodiscard]] inline auto helpPage() const -> QString override { return ""; } + inline auto helpPage() const -> QString override { return ""; } }; class ModrinthDataPackPage : public DataPackResourcePage { @@ -183,15 +181,15 @@ class ModrinthDataPackPage : public DataPackResourcePage { ModrinthDataPackPage(DataPackDownloadDialog* dialog, BaseInstance& instance); ~ModrinthDataPackPage() override = default; - [[nodiscard]] bool shouldDisplay() const override; + bool shouldDisplay() const override; - [[nodiscard]] inline auto displayName() const -> QString override { return Modrinth::displayName(); } - [[nodiscard]] inline auto icon() const -> QIcon override { return Modrinth::icon(); } - [[nodiscard]] inline auto id() const -> QString override { return Modrinth::id(); } - [[nodiscard]] inline auto debugName() const -> QString override { return Modrinth::debugName(); } - [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Modrinth::metaEntryBase(); } + inline auto displayName() const -> QString override { return Modrinth::displayName(); } + inline auto icon() const -> QIcon override { return Modrinth::icon(); } + inline auto id() const -> QString override { return Modrinth::id(); } + inline auto debugName() const -> QString override { return Modrinth::debugName(); } + inline auto metaEntryBase() const -> QString override { return Modrinth::metaEntryBase(); } - [[nodiscard]] inline auto helpPage() const -> QString override { return ""; } + inline auto helpPage() const -> QString override { return ""; } }; } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp index c425044a2..62bad833b 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp @@ -71,7 +71,7 @@ QVariant Technic::ListModel::data(const QModelIndex& index, int role) const if (m_logoMap.contains(pack.logoName)) { return (m_logoMap.value(pack.logoName)); } - QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); + QIcon icon = QIcon::fromTheme("screenshot-placeholder"); ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl); return icon; } diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.h b/launcher/ui/pages/modplatform/technic/TechnicModel.h index 09e9294bb..4979000e9 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicModel.h +++ b/launcher/ui/pages/modplatform/technic/TechnicModel.h @@ -58,8 +58,8 @@ class ListModel : public QAbstractListModel { void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback); void searchWithTerm(const QString& term); - [[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); } - [[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; } + bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); } + Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; } private slots: void searchRequestFinished(); diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.h b/launcher/ui/pages/modplatform/technic/TechnicPage.h index d1f691b22..a131a6db1 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.h +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.h @@ -38,7 +38,6 @@ #include #include -#include #include "TechnicData.h" #include "net/NetJob.h" #include "ui/pages/modplatform/ModpackProviderBasePage.h" @@ -61,7 +60,7 @@ class TechnicPage : public QWidget, public ModpackProviderBasePage { explicit TechnicPage(NewInstanceDialog* dialog, QWidget* parent = 0); virtual ~TechnicPage(); virtual QString displayName() const override { return "Technic"; } - virtual QIcon icon() const override { return APPLICATION->getThemedIcon("technic"); } + virtual QIcon icon() const override { return QIcon::fromTheme("technic"); } virtual QString id() const override { return "technic"; } virtual QString helpPage() const override { return "Technic-platform"; } virtual bool shouldDisplay() const override; @@ -74,7 +73,7 @@ class TechnicPage : public QWidget, public ModpackProviderBasePage { /** Programatically set the term in the search bar. */ virtual void setSearchTerm(QString) override; /** Get the current term in the search bar. */ - [[nodiscard]] virtual QString getSerachTerm() const override; + virtual QString getSerachTerm() const override; private: void suggestCurrent(); diff --git a/launcher/ui/themes/CatPainter.cpp b/launcher/ui/themes/CatPainter.cpp index 7ff24932b..7c152fdc9 100644 --- a/launcher/ui/themes/CatPainter.cpp +++ b/launcher/ui/themes/CatPainter.cpp @@ -22,12 +22,27 @@ CatPainter::CatPainter(const QString& path, QObject* parent) : QObject(parent) { - m_image = QPixmap(path); + // Attempt to load as a movie + m_movie = new QMovie(path, QByteArray(), this); + if (m_movie->isValid()) { + // Start the animation if it's a valid movie file + connect(m_movie, &QMovie::frameChanged, this, &CatPainter::updateFrame); + m_movie->start(); + } else { + // Otherwise, load it as a static image + delete m_movie; + m_movie = nullptr; + + m_image = QPixmap(path); + } } void CatPainter::paint(QPainter* painter, const QRect& viewport) { QPixmap frame = m_image; + if (m_movie && m_movie->isValid()) { + frame = m_movie->currentPixmap(); + } auto fit = APPLICATION->settings()->get("CatFit").toString(); painter->setOpacity(APPLICATION->settings()->get("CatOpacity").toFloat() / 100); diff --git a/launcher/ui/themes/CatPainter.h b/launcher/ui/themes/CatPainter.h index 3b790c640..c36cb7617 100644 --- a/launcher/ui/themes/CatPainter.h +++ b/launcher/ui/themes/CatPainter.h @@ -34,5 +34,6 @@ class CatPainter : public QObject { void updateFrame(); private: + QMovie* m_movie = nullptr; QPixmap m_image; }; diff --git a/launcher/ui/themes/ThemeManager.cpp b/launcher/ui/themes/ThemeManager.cpp index 30a1fe7be..a7076da5a 100644 --- a/launcher/ui/themes/ThemeManager.cpp +++ b/launcher/ui/themes/ThemeManager.cpp @@ -50,6 +50,11 @@ ThemeManager::ThemeManager() initializeCatPacks(); } +ThemeManager::~ThemeManager() +{ + stopSettingNewWindowColorsOnMac(); +} + /// @brief Adds the Theme to the list of themes /// @param theme The Theme to add /// @return Theme ID @@ -174,6 +179,15 @@ void ThemeManager::initializeWidgets() themeDebugLog() << "<> Widget themes initialized."; } +#ifndef Q_OS_MACOS +void ThemeManager::setTitlebarColorOnMac(WId windowId, QColor color) +{} +void ThemeManager::setTitlebarColorOfAllWindowsOnMac(QColor color) +{} +void ThemeManager::stopSettingNewWindowColorsOnMac() +{} +#endif + QList ThemeManager::getValidIconThemes() { QList ret; @@ -247,6 +261,7 @@ void ThemeManager::setApplicationTheme(const QString& name, bool initial) auto& theme = themeIter->second; themeDebugLog() << "applying theme" << theme->name(); theme->apply(initial); + setTitlebarColorOfAllWindowsOnMac(qApp->palette().window().color()); m_logColors = theme->logColorScheme(); } else { diff --git a/launcher/ui/themes/ThemeManager.h b/launcher/ui/themes/ThemeManager.h index 8de7562d1..8baa88627 100644 --- a/launcher/ui/themes/ThemeManager.h +++ b/launcher/ui/themes/ThemeManager.h @@ -39,6 +39,7 @@ inline auto themeWarningLog() class ThemeManager { public: ThemeManager(); + ~ThemeManager(); QList getValidIconThemes(); QList getValidApplicationThemes(); @@ -81,6 +82,17 @@ class ThemeManager { void initializeIcons(); void initializeWidgets(); + // On non-Mac systems, this is a no-op. + void setTitlebarColorOnMac(WId windowId, QColor color); + // This also will set the titlebar color of newly opened windows after this method is called. + // On non-Mac systems, this is a no-op. + void setTitlebarColorOfAllWindowsOnMac(QColor color); + // On non-Mac systems, this is a no-op. + void stopSettingNewWindowColorsOnMac(); +#ifdef Q_OS_MACOS + NSObject* m_windowTitlebarObserver = nullptr; +#endif + const QStringList builtinIcons{ "pe_colored", "pe_light", "pe_dark", "pe_blue", "breeze_light", "breeze_dark", "OSX", "iOS", "flat", "flat_white", "multimc" }; }; diff --git a/launcher/ui/themes/ThemeManager.mm b/launcher/ui/themes/ThemeManager.mm new file mode 100644 index 000000000..d9fc291b6 --- /dev/null +++ b/launcher/ui/themes/ThemeManager.mm @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2025 Kenneth Chew <79120643+kthchew@users.noreply.github.com> + * + * 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 "ThemeManager.h" + +#include + +void ThemeManager::setTitlebarColorOnMac(WId windowId, QColor color) +{ + if (windowId == 0) { + return; + } + + NSView* view = (NSView*)windowId; + NSWindow* window = [view window]; + window.titlebarAppearsTransparent = YES; + window.backgroundColor = [NSColor colorWithRed:color.redF() green:color.greenF() blue:color.blueF() alpha:color.alphaF()]; + + // Unfortunately there seems to be no easy way to set the titlebar text color. + // The closest we can do without dubious hacks is set the dark/light mode state based on the brightness of the + // background color, which should at least make the text readable even if we can't use the theme's text color. + // It's a good idea to set this anyway since it also affects some other UI elements like text shadows (PrismLauncher#3825). + if (color.lightnessF() < 0.5) { + window.appearance = [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua]; + } else { + window.appearance = [NSAppearance appearanceNamed:NSAppearanceNameAqua]; + } +} + +void ThemeManager::setTitlebarColorOfAllWindowsOnMac(QColor color) +{ + NSArray* windows = [NSApp windows]; + for (NSWindow* window : windows) { + setTitlebarColorOnMac((WId)window.contentView, color); + } + + // We want to change the titlebar color of newly opened windows as well. + // There's no notification for when a new window is opened, but we can set the color when a window switches + // from occluded to visible, which also fires on open. + NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; + stopSettingNewWindowColorsOnMac(); + m_windowTitlebarObserver = [center addObserverForName:NSWindowDidChangeOcclusionStateNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification* notification) { + NSWindow* window = notification.object; + setTitlebarColorOnMac((WId)window.contentView, color); + }]; +} + +void ThemeManager::stopSettingNewWindowColorsOnMac() +{ + if (m_windowTitlebarObserver) { + NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; + [center removeObserver:m_windowTitlebarObserver]; + m_windowTitlebarObserver = nil; + } +} diff --git a/launcher/ui/widgets/AppearanceWidget.ui b/launcher/ui/widgets/AppearanceWidget.ui index 99bf4a500..cfe464dd6 100644 --- a/launcher/ui/widgets/AppearanceWidget.ui +++ b/launcher/ui/widgets/AppearanceWidget.ui @@ -203,53 +203,6 @@
- - - - - 0 - 0 - - - - Fit - - - - - - - - 0 - 0 - - - - - 77 - 30 - - - - 0 - - - - Fit - - - - - Fill - - - - - Stretch - - - - @@ -370,6 +323,53 @@ + + + + + 0 + 0 + + + + Cat Scaling + + + + + + + + 0 + 0 + + + + + 77 + 30 + + + + 0 + + + + Fit + + + + + Fill + + + + + Stretch + + + +
diff --git a/launcher/ui/widgets/CustomCommands.ui b/launcher/ui/widgets/CustomCommands.ui index c1b4558a8..6c1366c06 100644 --- a/launcher/ui/widgets/CustomCommands.ui +++ b/launcher/ui/widgets/CustomCommands.ui @@ -86,7 +86,7 @@ P&ost-exit Command - labelPostExitCmd + postExitCmdTextBox diff --git a/launcher/ui/widgets/JavaWizardWidget.cpp b/launcher/ui/widgets/JavaWizardWidget.cpp index 6158dc7a3..955bc5334 100644 --- a/launcher/ui/widgets/JavaWizardWidget.cpp +++ b/launcher/ui/widgets/JavaWizardWidget.cpp @@ -33,9 +33,9 @@ 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"); + goodIcon = QIcon::fromTheme("status-good"); + yellowIcon = QIcon::fromTheme("status-yellow"); + badIcon = QIcon::fromTheme("status-bad"); m_memoryTimer = new QTimer(this); setupUi(); @@ -532,7 +532,7 @@ void JavaWizardWidget::updateThresholds() { auto height = m_labelMaxMemIcon->fontInfo().pixelSize(); - QIcon icon = APPLICATION->getThemedIcon(iconName); + QIcon icon = QIcon::fromTheme(iconName); QPixmap pix = icon.pixmap(height, height); m_labelMaxMemIcon->setPixmap(pix); } diff --git a/launcher/ui/widgets/MinecraftSettingsWidget.cpp b/launcher/ui/widgets/MinecraftSettingsWidget.cpp index f46786518..b738cab74 100644 --- a/launcher/ui/widgets/MinecraftSettingsWidget.cpp +++ b/launcher/ui/widgets/MinecraftSettingsWidget.cpp @@ -36,6 +36,7 @@ */ #include "MinecraftSettingsWidget.h" +#include "modplatform/ModIndex.h" #include "ui_MinecraftSettingsWidget.h" #include @@ -222,9 +223,9 @@ void MinecraftSettingsWidget::loadSettings() 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) { + m_ui->serverJoinGroupBox->setChecked(settings->get("JoinServerOnLaunch").toBool()); + if (auto server = settings->get("JoinServerOnLaunchAddress").toString(); !server.isEmpty()) { m_ui->serverJoinAddress->setText(server); m_ui->serverJoinAddressButton->setChecked(true); @@ -240,7 +241,7 @@ void MinecraftSettingsWidget::loadSettings() } else { m_ui->serverJoinAddressButton->setChecked(true); m_ui->worldJoinButton->setChecked(false); - m_ui->serverJoinAddress->setEnabled(true); + m_ui->serverJoinAddress->setEnabled(m_ui->serverJoinGroupBox->isChecked()); m_ui->worldsCb->setEnabled(false); } @@ -499,7 +500,7 @@ void MinecraftSettingsWidget::updateAccountsMenu(const SettingsObject& settings) QIcon face = account->getFace(); if (face.isNull()) - face = APPLICATION->getThemedIcon("noaccount"); + face = QIcon::fromTheme("noaccount"); m_ui->instanceAccountSelector->addItem(face, account->profileName(), i); if (i == accountIndex) diff --git a/launcher/ui/widgets/MinecraftSettingsWidget.ui b/launcher/ui/widgets/MinecraftSettingsWidget.ui index 4a35addc0..f8ee2f854 100644 --- a/launcher/ui/widgets/MinecraftSettingsWidget.ui +++ b/launcher/ui/widgets/MinecraftSettingsWidget.ui @@ -18,7 +18,7 @@ 0 - 0 + 6 0 @@ -553,8 +553,8 @@ It is most likely you will need to change the path - please refer to the mod's w 0 0 - 624 - 487 + 100 + 30 @@ -577,8 +577,8 @@ It is most likely you will need to change the path - please refer to the mod's w 0 0 - 624 - 487 + 261 + 434 diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index 527efc32a..4fb0aef22 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -114,7 +114,7 @@ ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended) ui->setupUi(this); m_versions_proxy = new VersionProxyModel(this); - m_versions_proxy->setFilter(BaseVersionList::TypeRole, new ExactFilter("release")); + m_versions_proxy->setFilter(BaseVersionList::TypeRole, Filters::equals("release")); QAbstractProxyModel* proxy = new VersionBasicModel(this); proxy->setSourceModel(m_versions_proxy); @@ -149,10 +149,19 @@ ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended) connect(ui->forge, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); connect(ui->fabric, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); connect(ui->quilt, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); - if (extended) - connect(ui->liteLoader, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); - else - ui->liteLoader->setVisible(false); + connect(ui->liteLoader, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); + connect(ui->babric, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); + connect(ui->btaBabric, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); + connect(ui->legacyFabric, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); + connect(ui->ornithe, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); + connect(ui->rift, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); + + connect(ui->showMoreButton, &QPushButton::clicked, this, &ModFilterWidget::onShowMoreClicked); + + if (!extended) { + ui->showMoreButton->setVisible(false); + ui->extendedModLoadersWidget->setVisible(false); + } if (extended) { connect(ui->clientSide, &QCheckBox::stateChanged, this, &ModFilterWidget::onSideFilterChanged); @@ -218,7 +227,7 @@ void ModFilterWidget::prepareBasicFilter() m_filter->openSource = false; if (m_instance) { m_filter->hideInstalled = false; - m_filter->side = ""; // or "both" + m_filter->side = ModPlatform::Side::NoSide; // or "both" ModPlatform::ModLoaderTypes loaders; if (m_instance->settings()->get("OverrideModDownloadLoaders").toBool()) { for (auto loader : Json::toStringList(m_instance->settings()->get("ModDownloadLoaders").toString())) { @@ -247,7 +256,7 @@ void ModFilterWidget::onShowAllVersionsChanged() if (ui->showAllVersions->isChecked()) m_versions_proxy->clearFilters(); else - m_versions_proxy->setFilter(BaseVersionList::TypeRole, new ExactFilter("release")); + m_versions_proxy->setFilter(BaseVersionList::TypeRole, Filters::equals("release")); } void ModFilterWidget::onVersionFilterChanged(int) @@ -279,6 +288,16 @@ void ModFilterWidget::onLoadersFilterChanged() loaders |= ModPlatform::Quilt; if (ui->liteLoader->isChecked()) loaders |= ModPlatform::LiteLoader; + if (ui->babric->isChecked()) + loaders |= ModPlatform::Babric; + if (ui->btaBabric->isChecked()) + loaders |= ModPlatform::BTA; + if (ui->legacyFabric->isChecked()) + loaders |= ModPlatform::LegacyFabric; + if (ui->ornithe->isChecked()) + loaders |= ModPlatform::Ornithe; + if (ui->rift->isChecked()) + loaders |= ModPlatform::Rift; m_filter_changed = loaders != m_filter->loaders; m_filter->loaders = loaders; if (m_filter_changed) @@ -287,16 +306,16 @@ void ModFilterWidget::onLoadersFilterChanged() void ModFilterWidget::onSideFilterChanged() { - QString side; + ModPlatform::Side side; if (ui->clientSide->isChecked() && !ui->serverSide->isChecked()) { - side = "client"; + side = ModPlatform::Side::ClientSide; } else if (!ui->clientSide->isChecked() && ui->serverSide->isChecked()) { - side = "server"; + side = ModPlatform::Side::ServerSide; } else if (ui->clientSide->isChecked() && ui->serverSide->isChecked()) { - side = "both"; + side = ModPlatform::Side::UniversalSide; } else { - side = ""; + side = ModPlatform::Side::NoSide; } m_filter_changed = side != m_filter->side; @@ -381,4 +400,10 @@ void ModFilterWidget::onReleaseFilterChanged() emit filterChanged(); } +void ModFilterWidget::onShowMoreClicked() +{ + ui->extendedModLoadersWidget->setVisible(true); + ui->showMoreButton->setVisible(false); +} + #include "ModFilterWidget.moc" diff --git a/launcher/ui/widgets/ModFilterWidget.h b/launcher/ui/widgets/ModFilterWidget.h index 88f2593dd..f00b98eb0 100644 --- a/launcher/ui/widgets/ModFilterWidget.h +++ b/launcher/ui/widgets/ModFilterWidget.h @@ -61,7 +61,7 @@ class ModFilterWidget : public QTabWidget { std::list versions; std::list releases; ModPlatform::ModLoaderTypes loaders; - QString side; + ModPlatform::Side side; bool hideInstalled; QStringList categoryIds; bool openSource; @@ -81,6 +81,14 @@ class ModFilterWidget : public QTabWidget { return versions.empty(); } + + bool checkModpackFilters(const ModPlatform::IndexedVersion& v) + { + return ((!loaders || !v.loaders || loaders & v.loaders) && // loaders + (releases.empty() || // releases + std::find(releases.cbegin(), releases.cend(), v.version_type) != releases.cend()) && + checkMcVersions({ v.mcVersion })); // gameVersion} + } }; static std::unique_ptr create(MinecraftInstance* instance, bool extended); @@ -110,6 +118,7 @@ class ModFilterWidget : public QTabWidget { void onShowAllVersionsChanged(); void onOpenSourceFilterChanged(); void onReleaseFilterChanged(); + void onShowMoreClicked(); private: Ui::ModFilterWidget* ui; diff --git a/launcher/ui/widgets/ModFilterWidget.ui b/launcher/ui/widgets/ModFilterWidget.ui index 788202714..d29c9752a 100644 --- a/launcher/ui/widgets/ModFilterWidget.ui +++ b/launcher/ui/widgets/ModFilterWidget.ui @@ -122,12 +122,75 @@ - + - LiteLoader + Show More + + + + false + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + LiteLoader + + + + + + + Babric + + + + + + + BTA (Babric) + + + + + + + Legacy Fabric + + + + + + + Ornithe + + + + + + + Rift + + + + + + diff --git a/launcher/ui/widgets/PageContainer.cpp b/launcher/ui/widgets/PageContainer.cpp index e3054c17a..cffda086c 100644 --- a/launcher/ui/widgets/PageContainer.cpp +++ b/launcher/ui/widgets/PageContainer.cpp @@ -228,7 +228,7 @@ void PageContainer::showPage(int row) } else { m_pageStack->setCurrentIndex(0); m_header->setText(QString()); - m_iconHeader->setIcon(APPLICATION->getThemedIcon("bug")); + m_iconHeader->setIcon(QIcon::fromTheme("bug")); } } diff --git a/launcher/ui/widgets/ProgressWidget.cpp b/launcher/ui/widgets/ProgressWidget.cpp index 9181de7f8..69c7e6f17 100644 --- a/launcher/ui/widgets/ProgressWidget.cpp +++ b/launcher/ui/widgets/ProgressWidget.cpp @@ -39,7 +39,7 @@ void ProgressWidget::progressFormat(QString format) m_bar->setFormat(format); } -void ProgressWidget::watch(const Task* task) +void ProgressWidget::watch(Task* task) { if (!task) return; @@ -61,11 +61,11 @@ void ProgressWidget::watch(const Task* task) connect(m_task, &Task::started, this, &ProgressWidget::show); } -void ProgressWidget::start(const Task* task) +void ProgressWidget::start(Task* task) { watch(task); if (!m_task->isRunning()) - QMetaObject::invokeMethod(const_cast(m_task), "start", Qt::QueuedConnection); + QMetaObject::invokeMethod(m_task, "start", Qt::QueuedConnection); } bool ProgressWidget::exec(std::shared_ptr task) diff --git a/launcher/ui/widgets/ProgressWidget.h b/launcher/ui/widgets/ProgressWidget.h index b0458f335..4d9097b8a 100644 --- a/launcher/ui/widgets/ProgressWidget.h +++ b/launcher/ui/widgets/ProgressWidget.h @@ -27,10 +27,10 @@ class ProgressWidget : public QWidget { public slots: /** Watch the progress of a task. */ - void watch(const Task* task); + void watch(Task* task); /** Watch the progress of a task, and start it if needed */ - void start(const Task* task); + void start(Task* task); /** Blocking way of waiting for a task to finish. */ bool exec(std::shared_ptr task); @@ -50,7 +50,7 @@ class ProgressWidget : public QWidget { private: QLabel* m_label = nullptr; QProgressBar* m_bar = nullptr; - const Task* m_task = nullptr; + Task* m_task = nullptr; bool m_hide_if_inactive = false; }; diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp index 91cf0956f..b6701535d 100644 --- a/launcher/ui/widgets/ProjectItem.cpp +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -20,9 +20,6 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o auto isChecked = opt.checkState == Qt::Checked; auto isSelected = option.state & QStyle::State_Selected; - if (!isSelected && !isChecked && isInstalled) { - painter->setOpacity(0.4); // Fade out the entire item - } const QStyle* style = opt.widget == nullptr ? QApplication::style() : opt.widget->style(); auto rect = opt.rect; @@ -39,6 +36,9 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o rect.setX(checkboxOpt.rect.right()); } + if (!isSelected && !isChecked && isInstalled) { + painter->setOpacity(0.4); // Fade out the entire item + } // The default icon size will be a square (and height is usually the lower value). auto icon_width = rect.height(), icon_height = rect.height(); int icon_x_margin = (rect.height() - icon_width) / 2; diff --git a/launcher/ui/widgets/VersionSelectWidget.cpp b/launcher/ui/widgets/VersionSelectWidget.cpp index 2d735d18f..040355f4b 100644 --- a/launcher/ui/widgets/VersionSelectWidget.cpp +++ b/launcher/ui/widgets/VersionSelectWidget.cpp @@ -224,20 +224,20 @@ BaseVersion::Ptr VersionSelectWidget::selectedVersion() const void VersionSelectWidget::setFuzzyFilter(BaseVersionList::ModelRoles role, QString filter) { - m_proxyModel->setFilter(role, new ContainsFilter(filter)); + m_proxyModel->setFilter(role, Filters::contains(filter)); } void VersionSelectWidget::setExactFilter(BaseVersionList::ModelRoles role, QString filter) { - m_proxyModel->setFilter(role, new ExactFilter(filter)); + m_proxyModel->setFilter(role, Filters::equals(filter)); } void VersionSelectWidget::setExactIfPresentFilter(BaseVersionList::ModelRoles role, QString filter) { - m_proxyModel->setFilter(role, new ExactIfPresentFilter(filter)); + m_proxyModel->setFilter(role, Filters::equalsOrEmpty(filter)); } -void VersionSelectWidget::setFilter(BaseVersionList::ModelRoles role, Filter* filter) +void VersionSelectWidget::setFilter(BaseVersionList::ModelRoles role, Filter filter) { m_proxyModel->setFilter(role, filter); } diff --git a/launcher/ui/widgets/VersionSelectWidget.h b/launcher/ui/widgets/VersionSelectWidget.h index c16d4c0dd..c66d7e98e 100644 --- a/launcher/ui/widgets/VersionSelectWidget.h +++ b/launcher/ui/widgets/VersionSelectWidget.h @@ -39,13 +39,13 @@ #include #include #include "BaseVersionList.h" +#include "Filter.h" #include "VersionListView.h" class VersionProxyModel; class VersionListView; class QVBoxLayout; class QProgressBar; -class Filter; class VersionSelectWidget : public QWidget { Q_OBJECT @@ -70,7 +70,7 @@ class VersionSelectWidget : public QWidget { void setFuzzyFilter(BaseVersionList::ModelRoles role, QString filter); void setExactFilter(BaseVersionList::ModelRoles role, QString filter); void setExactIfPresentFilter(BaseVersionList::ModelRoles role, QString filter); - void setFilter(BaseVersionList::ModelRoles role, Filter* filter); + void setFilter(BaseVersionList::ModelRoles role, Filter filter); void setEmptyString(QString emptyString); void setEmptyErrorString(QString emptyErrorString); void setEmptyMode(VersionListView::EmptyMode mode); diff --git a/launcher/ui/widgets/WideBar.cpp b/launcher/ui/widgets/WideBar.cpp index 2940d7ce7..e87c8b4c1 100644 --- a/launcher/ui/widgets/WideBar.cpp +++ b/launcher/ui/widgets/WideBar.cpp @@ -250,7 +250,7 @@ void WideBar::addContextMenuAction(QAction* action) m_context_menu_actions.append(action); } -[[nodiscard]] QByteArray WideBar::getVisibilityState() const +QByteArray WideBar::getVisibilityState() const { QByteArray state; diff --git a/launcher/ui/widgets/WideBar.h b/launcher/ui/widgets/WideBar.h index f4877a89a..68a052a23 100644 --- a/launcher/ui/widgets/WideBar.h +++ b/launcher/ui/widgets/WideBar.h @@ -35,7 +35,7 @@ class WideBar : public QToolBar { // Ideally we would use a QBitArray for this, but it doesn't support string conversion, // so using it in settings is very messy. - [[nodiscard]] QByteArray getVisibilityState() const; + QByteArray getVisibilityState() const; void setVisibilityState(QByteArray&&); void removeAction(QAction* action); @@ -50,8 +50,8 @@ class WideBar : public QToolBar { auto getMatching(QAction* act) -> QList::iterator; /** Used to distinguish between versions of the WideBar with different actions */ - [[nodiscard]] QByteArray getHash() const; - [[nodiscard]] bool checkHash(QByteArray const&) const; + QByteArray getHash() const; + bool checkHash(QByteArray const&) const; private: QList m_entries; diff --git a/libraries/launcher/legacy/org/prismlauncher/legacy/utils/api/MojangApi.java b/libraries/launcher/legacy/org/prismlauncher/legacy/utils/api/MojangApi.java index 41f7f9114..34313e91a 100644 --- a/libraries/launcher/legacy/org/prismlauncher/legacy/utils/api/MojangApi.java +++ b/libraries/launcher/legacy/org/prismlauncher/legacy/utils/api/MojangApi.java @@ -49,7 +49,7 @@ import java.util.Map; @SuppressWarnings("unchecked") public final class MojangApi { public static String getUuid(String username) throws IOException { - try (InputStream in = new URL("https://api.mojang.com/users/profiles/minecraft/" + username).openStream()) { + try (InputStream in = new URL("https://api.minecraftservices.com/minecraft/profile/lookup/name/" + username).openStream()) { Map map = (Map) JsonParser.parse(in); return (String) map.get("id"); } diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix index d9144410f..93ac21de0 100644 --- a/nix/unwrapped.nix +++ b/nix/unwrapped.nix @@ -76,35 +76,33 @@ stdenv.mkDerivation { stripJavaArchivesHook ]; - buildInputs = - [ - cmark - kdePackages.qtbase - kdePackages.qtnetworkauth - kdePackages.quazip - tomlplusplus - zlib - ] - ++ lib.optionals stdenv.hostPlatform.isDarwin [ apple-sdk_11 ] - ++ lib.optional gamemodeSupport gamemode; + buildInputs = [ + cmark + kdePackages.qtbase + kdePackages.qtnetworkauth + kdePackages.quazip + tomlplusplus + zlib + ] + ++ lib.optionals stdenv.hostPlatform.isDarwin [ apple-sdk_11 ] + ++ lib.optional gamemodeSupport gamemode; hardeningEnable = lib.optionals stdenv.hostPlatform.isLinux [ "pie" ]; - cmakeFlags = - [ - # downstream branding - (lib.cmakeFeature "Launcher_BUILD_PLATFORM" "nixpkgs") - ] - ++ lib.optionals (msaClientID != null) [ - (lib.cmakeFeature "Launcher_MSA_CLIENT_ID" (toString msaClientID)) - ] - ++ lib.optionals stdenv.hostPlatform.isDarwin [ - # we wrap our binary manually - (lib.cmakeFeature "INSTALL_BUNDLE" "nodeps") - # disable built-in updater - (lib.cmakeFeature "MACOSX_SPARKLE_UPDATE_FEED_URL" "''") - (lib.cmakeFeature "CMAKE_INSTALL_PREFIX" "${placeholder "out"}/Applications/") - ]; + cmakeFlags = [ + # downstream branding + (lib.cmakeFeature "Launcher_BUILD_PLATFORM" "nixpkgs") + ] + ++ lib.optionals (msaClientID != null) [ + (lib.cmakeFeature "Launcher_MSA_CLIENT_ID" (toString msaClientID)) + ] + ++ lib.optionals stdenv.hostPlatform.isDarwin [ + # we wrap our binary manually + (lib.cmakeFeature "INSTALL_BUNDLE" "nodeps") + # disable built-in updater + (lib.cmakeFeature "MACOSX_SPARKLE_UPDATE_FEED_URL" "''") + (lib.cmakeFeature "CMAKE_INSTALL_PREFIX" "${placeholder "out"}/Applications/") + ]; doCheck = true; diff --git a/nix/wrapper.nix b/nix/wrapper.nix index 03c1f0421..01edd0d9a 100644 --- a/nix/wrapper.nix +++ b/nix/wrapper.nix @@ -61,14 +61,13 @@ symlinkJoin { nativeBuildInputs = [ kdePackages.wrapQtAppsHook ]; - buildInputs = - [ - kdePackages.qtbase - kdePackages.qtsvg - ] - ++ lib.optional ( - lib.versionAtLeast kdePackages.qtbase.version "6" && stdenv.hostPlatform.isLinux - ) kdePackages.qtwayland; + buildInputs = [ + kdePackages.qtbase + kdePackages.qtsvg + ] + ++ lib.optional ( + lib.versionAtLeast kdePackages.qtbase.version "6" && stdenv.hostPlatform.isLinux + ) kdePackages.qtwayland; postBuild = '' wrapQtAppsHook @@ -76,41 +75,41 @@ symlinkJoin { qtWrapperArgs = let - runtimeLibs = - [ - (lib.getLib stdenv.cc.cc) - ## native versions - glfw3-minecraft - openal + runtimeLibs = [ + (lib.getLib stdenv.cc.cc) + ## native versions + glfw3-minecraft + openal - ## openal - alsa-lib - libjack2 - libpulseaudio - pipewire + ## openal + alsa-lib + libjack2 + libpulseaudio + pipewire - ## glfw - libGL - libX11 - libXcursor - libXext - libXrandr - libXxf86vm + ## glfw + libGL + libX11 + libXcursor + libXext + libXrandr + libXxf86vm - udev # oshi + udev # oshi - vulkan-loader # VulkanMod's lwjgl - ] - ++ lib.optional textToSpeechSupport flite - ++ lib.optional gamemodeSupport gamemode.lib - ++ lib.optional controllerSupport libusb1 - ++ additionalLibs; + vulkan-loader # VulkanMod's lwjgl + ] + ++ lib.optional textToSpeechSupport flite + ++ lib.optional gamemodeSupport gamemode.lib + ++ lib.optional controllerSupport libusb1 + ++ additionalLibs; runtimePrograms = [ mesa-demos pciutils # need lspci xrandr # needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128 - ] ++ additionalPrograms; + ] + ++ additionalPrograms; in [ "--prefix PRISMLAUNCHER_JAVA_PATHS : ${lib.makeSearchPath "bin/java" jdks}" ] diff --git a/program_info/org.prismlauncher.PrismLauncher.metainfo.xml.in b/program_info/org.prismlauncher.PrismLauncher.metainfo.xml.in index 95bb86a27..747e1bc24 100644 --- a/program_info/org.prismlauncher.PrismLauncher.metainfo.xml.in +++ b/program_info/org.prismlauncher.PrismLauncher.metainfo.xml.in @@ -1,19 +1,13 @@ @Launcher_AppID@ - @Launcher_AppID@.desktop Prism Launcher - Prism Launcher Contributors Custom Minecraft Launcher to easily manage multiple Minecraft installations at once + + Prism Launcher Contributors + CC0-1.0 GPL-3.0-only - https://prismlauncher.org/ - https://prismlauncher.org/wiki/ - https://github.com/PrismLauncher/PrismLauncher/issues - https://prismlauncher.org/discord - https://github.com/PrismLauncher/PrismLauncher - https://github.com/PrismLauncher/PrismLauncher/blob/develop/CONTRIBUTING.md - https://hosted.weblate.org/projects/prismlauncher/launcher

Prism Launcher is a custom launcher for Minecraft that focuses on predictability, long term stability and simplicity.

Features:

@@ -67,8 +61,18 @@ + https://prismlauncher.org/ + https://github.com/PrismLauncher/PrismLauncher/issues + https://prismlauncher.org/wiki/overview/faq/ + https://prismlauncher.org/wiki/ + https://opencollective.com/prismlauncher + https://hosted.weblate.org/projects/prismlauncher/launcher + https://prismlauncher.org/discord + https://github.com/PrismLauncher/PrismLauncher + https://github.com/PrismLauncher/PrismLauncher/blob/develop/CONTRIBUTING.md moderate intense + @Launcher_AppID@.desktop
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 31b887ff1..2165cd03d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -21,9 +21,6 @@ ecm_add_test(ResourceFolderModel_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_V ecm_add_test(ResourcePackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME ResourcePackParse) -ecm_add_test(ResourceModel_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test - TEST_NAME ResourceModel) - ecm_add_test(TexturePackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME TexturePackParse) diff --git a/tests/DummyResourceAPI.h b/tests/DummyResourceAPI.h deleted file mode 100644 index f8ab71e59..000000000 --- a/tests/DummyResourceAPI.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include - -#include - -class SearchTask : public Task { - Q_OBJECT - - public: - void executeTask() override { emitSucceeded(); } -}; - -class DummyResourceAPI : public ResourceAPI { - public: - static auto searchRequestResult() - { - static QByteArray json_response = - "{\"hits\":[" - "{" - "\"author\":\"flowln\"," - "\"description\":\"the bestest mod\"," - "\"project_id\":\"something\"," - "\"project_type\":\"mod\"," - "\"slug\":\"bip_bop\"," - "\"title\":\"AAAAAAAA\"," - "\"versions\":[\"2.71\"]" - "}" - "]}"; - - return QJsonDocument::fromJson(json_response); - } - - DummyResourceAPI() : ResourceAPI() {} - [[nodiscard]] auto getSortingMethods() const -> QList override { return {}; } - - [[nodiscard]] Task::Ptr searchProjects(SearchArgs&&, SearchCallbacks&& callbacks) const override - { - auto task = makeShared(); - QObject::connect(task.get(), &Task::succeeded, [callbacks] { - auto json = searchRequestResult(); - callbacks.on_succeed(json); - }); - return task; - } -}; diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp index 48da30a25..37e5e1201 100644 --- a/tests/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -12,8 +12,6 @@ #include namespace fs = std::filesystem; -#include - class LinkTask : public Task { Q_OBJECT @@ -27,7 +25,7 @@ class LinkTask : public Task { ~LinkTask() { delete m_lnk; } - void matcher(IPathMatcher::Ptr filter) { m_lnk->matcher(filter); } + void matcher(Filter filter) { m_lnk->matcher(filter); } void linkRecursively(bool recursive) { @@ -190,7 +188,7 @@ class FileSystemTest : public QObject { qDebug() << tempDir.path(); qDebug() << target_dir.path(); FS::copy c(folder, target_dir.path()); - RegexpMatcher::Ptr re = std::make_shared("[.]?mcmeta"); + auto re = Filters::regexp(QRegularExpression("[.]?mcmeta")); c.matcher(re); c(); @@ -223,7 +221,7 @@ class FileSystemTest : public QObject { qDebug() << tempDir.path(); qDebug() << target_dir.path(); FS::copy c(folder, target_dir.path()); - RegexpMatcher::Ptr re = std::make_shared("[.]?mcmeta"); + auto re = Filters::regexp(QRegularExpression("[.]?mcmeta")); c.matcher(re); c.whitelist(true); c(); @@ -415,7 +413,7 @@ class FileSystemTest : public QObject { qDebug() << target_dir.path(); LinkTask lnk_tsk(folder, target_dir.path()); - RegexpMatcher::Ptr re = std::make_shared("[.]?mcmeta"); + auto re = Filters::regexp(QRegularExpression("[.]?mcmeta")); lnk_tsk.matcher(re); lnk_tsk.linkRecursively(true); connect(&lnk_tsk, &Task::finished, @@ -461,7 +459,7 @@ class FileSystemTest : public QObject { qDebug() << target_dir.path(); LinkTask lnk_tsk(folder, target_dir.path()); - RegexpMatcher::Ptr re = std::make_shared("[.]?mcmeta"); + auto re = Filters::regexp(QRegularExpression("[.]?mcmeta")); lnk_tsk.matcher(re); lnk_tsk.linkRecursively(true); lnk_tsk.whitelist(true); diff --git a/tests/Packwiz_test.cpp b/tests/Packwiz_test.cpp index e4abda9f9..1fcb1b9f9 100644 --- a/tests/Packwiz_test.cpp +++ b/tests/Packwiz_test.cpp @@ -19,6 +19,7 @@ #include #include +#include "modplatform/ModIndex.h" #include @@ -42,7 +43,7 @@ class PackwizTest : public QObject { QCOMPARE(metadata.name, "Borderless Mining"); QCOMPARE(metadata.filename, "borderless-mining-1.1.1+1.18.jar"); - QCOMPARE(metadata.side, Packwiz::V1::Side::ClientSide); + QCOMPARE(metadata.side, ModPlatform::Side::ClientSide); QCOMPARE(metadata.url, QUrl("https://cdn.modrinth.com/data/kYq5qkSL/versions/1.1.1+1.18/borderless-mining-1.1.1+1.18.jar")); QCOMPARE(metadata.hash_format, "sha512"); @@ -72,7 +73,7 @@ class PackwizTest : public QObject { QCOMPARE(metadata.name, "Screenshot to Clipboard (Fabric)"); QCOMPARE(metadata.filename, "screenshot-to-clipboard-1.0.7-fabric.jar"); - QCOMPARE(metadata.side, Packwiz::V1::Side::UniversalSide); + QCOMPARE(metadata.side, ModPlatform::Side::UniversalSide); QCOMPARE(metadata.url, QUrl("https://edge.forgecdn.net/files/3509/43/screenshot-to-clipboard-1.0.7-fabric.jar")); QCOMPARE(metadata.hash_format, "murmur2"); diff --git a/tests/ResourceModel_test.cpp b/tests/ResourceModel_test.cpp deleted file mode 100644 index 30bb99fb8..000000000 --- a/tests/ResourceModel_test.cpp +++ /dev/null @@ -1,95 +0,0 @@ -#include -#include -#include - -#include - -#include - -#include "DummyResourceAPI.h" - -using ResourceDownload::ResourceModel; - -#define EXEC_TASK(EXEC) \ - QEventLoop loop; \ - \ - connect(model, &ResourceModel::dataChanged, &loop, &QEventLoop::quit); \ - \ - QTimer expire_timer; \ - expire_timer.callOnTimeout(&loop, &QEventLoop::quit); \ - expire_timer.setSingleShot(true); \ - expire_timer.start(4000); \ - \ - EXEC; \ - if (model->hasActiveSearchJob()) \ - loop.exec(); \ - \ - QVERIFY2(expire_timer.isActive(), "Timer has expired. The search never finished."); \ - expire_timer.stop(); \ - \ - disconnect(model, nullptr, &loop, nullptr) - -class ResourceModelTest; - -class DummyResourceModel : public ResourceModel { - Q_OBJECT - - friend class ResourceModelTest; - - public: - DummyResourceModel() : ResourceModel(new DummyResourceAPI) {} - ~DummyResourceModel() {} - - [[nodiscard]] auto metaEntryBase() const -> QString override { return ""; } - - ResourceAPI::SearchArgs createSearchArguments() override { return {}; } - ResourceAPI::VersionSearchArgs createVersionsArguments(const QModelIndex&) override { return {}; } - ResourceAPI::ProjectInfoArgs createInfoArguments(const QModelIndex&) override { return {}; } - - QJsonArray documentToArray(QJsonDocument& doc) const override { return doc.object().value("hits").toArray(); } - - void loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) override - { - pack.authors.append({ Json::requireString(obj, "author"), "" }); - pack.description = Json::requireString(obj, "description"); - pack.addonId = Json::requireString(obj, "project_id"); - } -}; - -class ResourceModelTest : public QObject { - Q_OBJECT - private slots: - void test_abstract_item_model() - { - auto dummy = DummyResourceModel(); - auto tester = QAbstractItemModelTester(&dummy); - } - - void test_search() - { - auto model = new DummyResourceModel; - - QVERIFY(model->m_packs.isEmpty()); - - EXEC_TASK(model->search()); - - QVERIFY(model->m_packs.size() == 1); - QVERIFY(model->m_search_state == DummyResourceModel::SearchState::Finished); - - auto processed_pack = model->m_packs.at(0); - auto search_json = DummyResourceAPI::searchRequestResult(); - auto processed_response = model->documentToArray(search_json).first().toObject(); - - QVERIFY(processed_pack->addonId.toString() == Json::requireString(processed_response, "project_id")); - QVERIFY(processed_pack->description == Json::requireString(processed_response, "description")); - QVERIFY(processed_pack->authors.first().name == Json::requireString(processed_response, "author")); - - delete model; - } -}; - -QTEST_GUILESS_MAIN(ResourceModelTest) - -#include "ResourceModel_test.moc" - -#include "moc_DummyResourceAPI.cpp" diff --git a/tests/ResourcePackParse_test.cpp b/tests/ResourcePackParse_test.cpp index e9b5244ad..5400d888a 100644 --- a/tests/ResourcePackParse_test.cpp +++ b/tests/ResourcePackParse_test.cpp @@ -69,7 +69,7 @@ class ResourcePackParseTest : public QObject { QVERIFY(pack.packFormat() == 6); QVERIFY(pack.description() == "o quartel pegou fogo, policia deu sinal, acode acode acode a bandeira nacional"); - QVERIFY(valid == false); // no assets dir + QVERIFY(valid == true); // no assets dir but it is still valid based on https://minecraft.wiki/w/Resource_pack } }; diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json new file mode 100644 index 000000000..71db4bcca --- /dev/null +++ b/vcpkg-configuration.json @@ -0,0 +1,20 @@ +{ + "default-registry": { + "kind": "git", + "baseline": "0c4cf19224a049cf82f4521e29e39f7bd680440c", + "repository": "https://github.com/microsoft/vcpkg" + }, + "registries": [ + { + "kind": "artifact", + "location": "https://github.com/microsoft/vcpkg-ce-catalog/archive/refs/heads/main.zip", + "name": "microsoft" + } + ], + "overlay-ports": [ + "./cmake/vcpkg-ports" + ], + "overlay-triplets": [ + "./cmake/vcpkg-triplets" + ] +} diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 000000000..4abf8d1b7 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,10 @@ +{ + "dependencies": [ + "bzip2", + "cmark", + { "name": "ecm", "host": true }, + { "name": "pkgconf", "host": true }, + "tomlplusplus", + "zlib" + ] +}