diff --git a/.clang-format b/.clang-format index 005a1006b..a0b93351f 100644 --- a/.clang-format +++ b/.clang-format @@ -16,4 +16,3 @@ BraceWrapping: BreakBeforeBraces: Custom BreakConstructorInitializers: BeforeComma Cpp11BracedListStyle: false -QualifierAlignment: Left diff --git a/.clang-tidy b/.clang-tidy index c5eb09533..6c489fb50 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,32 +1,23 @@ -FormatStyle: file - Checks: - "bugprone-*,clang-analyzer-*,cppcoreguidelines-*,hicpp-*,misc-*,modernize-*,performance-*,portability-*,readability-*, - -*-magic-numbers, - -*-non-private-member-variables-in-classes, - -*-special-member-functions, - -bugprone-easily-swappable-parameters, - -cppcoreguidelines-owning-memory, - -cppcoreguidelines-pro-type-static-cast-downcast, - -modernize-use-nodiscard, - -modernize-use-trailing-return-type, - -portability-avoid-pragma-once, - -readability-avoid-unconditional-preprocessor-if, - -readability-function-cognitive-complexity, - -readability-identifier-length, - -readability-redundant-access-specifiers" + - modernize-use-using + - readability-avoid-const-params-in-decls + - misc-unused-parameters, + - readability-identifier-naming + +# ^ Without unused-parameters the readability-identifier-naming check doesn't cause any warnings. CheckOptions: - misc-include-cleaner.MissingIncludes: false - readability-identifier-naming.DefaultCase: "camelBack" - readability-identifier-naming.NamespaceCase: "CamelCase" - readability-identifier-naming.ClassCase: "CamelCase" - readability-identifier-naming.ClassConstantCase: "CamelCase" - readability-identifier-naming.EnumCase: "CamelCase" - readability-identifier-naming.EnumConstantCase: "CamelCase" - readability-identifier-naming.MacroDefinitionCase: "UPPER_CASE" - readability-identifier-naming.ClassMemberPrefix: "m_" - readability-identifier-naming.StaticConstantPrefix: "s_" - readability-identifier-naming.StaticVariablePrefix: "s_" - readability-identifier-naming.GlobalConstantPrefix: "g_" - readability-implicit-bool-conversion.AllowPointerConditions: true + - { key: readability-identifier-naming.ClassCase, value: PascalCase } + - { key: readability-identifier-naming.EnumCase, value: PascalCase } + - { key: readability-identifier-naming.FunctionCase, value: camelCase } + - { key: readability-identifier-naming.GlobalVariableCase, value: camelCase } + - { key: readability-identifier-naming.GlobalFunctionCase, value: camelCase } + - { key: readability-identifier-naming.GlobalConstantCase, value: SCREAMING_SNAKE_CASE } + - { key: readability-identifier-naming.MacroDefinitionCase, value: SCREAMING_SNAKE_CASE } + - { key: readability-identifier-naming.ClassMemberCase, value: camelCase } + - { key: readability-identifier-naming.PrivateMemberPrefix, value: m_ } + - { key: readability-identifier-naming.ProtectedMemberPrefix, value: m_ } + - { key: readability-identifier-naming.PrivateStaticMemberPrefix, value: s_ } + - { key: readability-identifier-naming.ProtectedStaticMemberPrefix, value: s_ } + - { key: readability-identifier-naming.PublicStaticConstantCase, value: SCREAMING_SNAKE_CASE } + - { key: readability-identifier-naming.EnumConstantCase, value: PascalCase } diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 3574bf20d..ea1fbfdd9 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,6 +1,6 @@ name: Bug Report description: File a bug report -labels: ["bug: unconfirmed", "status: needs triage"] +labels: [bug] body: - type: markdown attributes: @@ -23,14 +23,14 @@ body: - macOS - Linux - Other -- type: input +- type: textarea attributes: label: Version of Prism Launcher description: The version of Prism Launcher used in the bug report. placeholder: Prism Launcher 5.0 validations: required: true -- type: input +- type: textarea attributes: label: Version of Qt description: The version of Qt used in the bug report. You can find it in Help -> About Prism Launcher -> About Qt. diff --git a/.github/ISSUE_TEMPLATE/rfc.yml b/.github/ISSUE_TEMPLATE/rfc.yml index 5e6d68e65..4d285047e 100644 --- a/.github/ISSUE_TEMPLATE/rfc.yml +++ b/.github/ISSUE_TEMPLATE/rfc.yml @@ -1,7 +1,7 @@ # Template based on https://gitlab.archlinux.org/archlinux/rfcs/-/blob/0ba3b61e987e197f8d1901709409b8564958f78a/rfcs/0000-template.rst name: Request for Comment (RFC) description: Propose a larger change and start a discussion. -labels: ["type: enhancement", "status: needs discussion", "status: needs triage"] +labels: [rfc] body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/suggestion.yml b/.github/ISSUE_TEMPLATE/suggestion.yml index 18a202ae1..ddee86b65 100644 --- a/.github/ISSUE_TEMPLATE/suggestion.yml +++ b/.github/ISSUE_TEMPLATE/suggestion.yml @@ -1,6 +1,6 @@ name: Suggestion description: Make a suggestion -labels: ["type: enhancement", "status: needs triage"] +labels: [enhancement] body: - type: markdown attributes: diff --git a/.github/actions/package/linux/action.yml b/.github/actions/package/linux/action.yml index 2ce6ca955..38e474202 100644 --- a/.github/actions/package/linux/action.yml +++ b/.github/actions/package/linux/action.yml @@ -27,18 +27,6 @@ runs: using: composite steps: - - name: Cleanup Qt installation on Linux - shell: bash - run: | - rm -rf "$QT_PLUGIN_PATH"/printsupport - rm -rf "$QT_PLUGIN_PATH"/sqldrivers - rm -rf "$QT_PLUGIN_PATH"/help - rm -rf "$QT_PLUGIN_PATH"/designer - rm -rf "$QT_PLUGIN_PATH"/qmltooling - rm -rf "$QT_PLUGIN_PATH"/qmlls - rm -rf "$QT_PLUGIN_PATH"/qmllint - rm -rf "$QT_PLUGIN_PATH"/platformthemes/libqgtk3.so - - name: Setup build variables shell: bash run: | @@ -75,7 +63,7 @@ runs: if [ '${{ inputs.gpg-private-key-id }}' != '' ]; then echo "$GPG_PRIVATE_KEY" > privkey.asc gpg --import privkey.asc - gpg --export --armor ${{ inputs.gpg-private-key-id }} > pubkey.asc + gpg --export --armor 9C7A2C9B62603299 > pubkey.asc else echo ":warning: Skipped code signing for Linux AppImage, as gpg key was not present." >> $GITHUB_STEP_SUMMARY fi @@ -90,10 +78,8 @@ runs: # FIXME(@getchoo): gamemode doesn't seem to be very portable with DBus. Find a way to make it work! find "$INSTALL_APPIMAGE_DIR" -name '*gamemode*' -exec rm {} + - #disable OpenGL and Vulkan launcher features until https://github.com/VHSgunzo/sharun/issues/35 - echo "PRISMLAUNCHER_DISABLE_GLVULKAN=1" >> "$INSTALL_APPIMAGE_DIR"/.env #makes the launcher use portals for file picking - echo "QT_QPA_PLATFORMTHEME=xdgdesktopportal" >> "$INSTALL_APPIMAGE_DIR"/.env + echo "QT_QPA_PLATFORMTHEME=xdgdesktopportal" > "$INSTALL_APPIMAGE_DIR"/.env ln -s org.prismlauncher.PrismLauncher.metainfo.xml "$INSTALL_APPIMAGE_DIR"/share/metainfo/org.prismlauncher.PrismLauncher.appdata.xml ln -s share/applications/org.prismlauncher.PrismLauncher.desktop "$INSTALL_APPIMAGE_DIR" ln -s share/icons/hicolor/256x256/apps/org.prismlauncher.PrismLauncher.png "$INSTALL_APPIMAGE_DIR" @@ -135,19 +121,19 @@ runs: tar -czf ../PrismLauncher-portable.tar.gz * - name: Upload binary tarball - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@v6 with: name: PrismLauncher-${{ inputs.artifact-name }}-Qt6-Portable-${{ inputs.version }}-${{ inputs.build-type }} path: PrismLauncher-portable.tar.gz - name: Upload AppImage - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@v6 with: name: PrismLauncher-${{ runner.os }}-${{ inputs.version }}-${{ inputs.build-type }}-${{ env.APPIMAGE_ARCH }}.AppImage path: PrismLauncher-${{ runner.os }}-*${{ env.APPIMAGE_ARCH }}.AppImage - name: Upload AppImage Zsync - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@v6 with: 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/package/macos/action.yml b/.github/actions/package/macos/action.yml index 1af01250f..1693ca21b 100644 --- a/.github/actions/package/macos/action.yml +++ b/.github/actions/package/macos/action.yml @@ -96,36 +96,16 @@ runs: fi ditto -c -k --sequesterRsrc --keepParent "Prism Launcher.app" ../PrismLauncher.zip - - name: Create DMG - shell: bash - env: - INSTALL_DIR: install - run: | - cd ${{ env.INSTALL_DIR }} - - mkdir -p src - cp -R "Prism Launcher.app" src/ - - ln -s /Applications src/ - - hdiutil create \ - -volname "Prism Launcher ${{ inputs.version }}" \ - -srcfolder src \ - -ov -format ULMO \ - "../PrismLauncher.dmg" - - name: Make Sparkle signature shell: bash run: | if [ '${{ inputs.sparkle-ed25519-key }}' != '' ]; then echo '${{ inputs.sparkle-ed25519-key }}' > ed25519-priv.pem - signature_zip=$(/opt/homebrew/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.zip -inkey ed25519-priv.pem | openssl base64 | tr -d \\n) - signature_dmg=$(/opt/homebrew/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.dmg -inkey ed25519-priv.pem | openssl base64 | tr -d \\n) + signature=$(/opt/homebrew/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.zip -inkey ed25519-priv.pem | openssl base64 | tr -d \\n) rm ed25519-priv.pem cat >> $GITHUB_STEP_SUMMARY << EOF ### Artifact Information :information_source: - - :memo: Sparkle Signature (ed25519): \`$signature_zip\` (ZIP) - - :memo: Sparkle Signature (ed25519): \`$signature_dmg\` (DMG) + - :memo: Sparkle Signature (ed25519): \`$signature\` EOF else cat >> $GITHUB_STEP_SUMMARY << EOF @@ -135,13 +115,7 @@ runs: fi - name: Upload binary tarball - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@v6 with: name: PrismLauncher-${{ inputs.artifact-name }}-${{ inputs.version }}-${{ inputs.build-type }} path: PrismLauncher.zip - - - name: Upload disk image - uses: actions/upload-artifact@v7 - with: - name: PrismLauncher-${{ inputs.artifact-name }}-${{ inputs.version }}-${{ inputs.build-type }}.dmg - path: PrismLauncher.dmg diff --git a/.github/actions/package/windows/action.yml b/.github/actions/package/windows/action.yml index 532f3db44..74036154a 100644 --- a/.github/actions/package/windows/action.yml +++ b/.github/actions/package/windows/action.yml @@ -61,7 +61,7 @@ runs: - name: Login to Azure if: ${{ env.CI_HAS_ACCESS_TO_AZURE != '' && inputs.azure-client-id != '' }} - uses: azure/login@v3 + uses: azure/login@v2 with: client-id: ${{ inputs.azure-client-id }} tenant-id: ${{ inputs.azure-tenant-id }} @@ -69,7 +69,7 @@ runs: - name: Sign executables if: ${{ env.CI_HAS_ACCESS_TO_AZURE != '' && inputs.azure-client-id != '' }} - uses: azure/artifact-signing-action@v2 + uses: azure/artifact-signing-action@v1 with: endpoint: https://eus.codesigning.azure.net/ trusted-signing-account-name: PrismLauncher @@ -79,8 +79,8 @@ runs: files-folder-recurse: true files-folder-depth: 2 # recommended in https://github.com/Azure/artifact-signing-action#timestamping-1 - timestamp-rfc3161: 'http://timestamp.acs.microsoft.com' - timestamp-digest: 'SHA256' + timestamp-rfc3161: "http://timestamp.acs.microsoft.com" + timestamp-digest: "SHA256" # TODO(@getchoo): Is this all really needed??? # https://github.com/Azure/trusted-signing-action/blob/fc390cf8ed0f14e248a542af1d838388a47c7a7c/docs/OIDC.md exclude-environment-credential: true @@ -142,7 +142,7 @@ runs: - name: Sign installer if: ${{ env.CI_HAS_ACCESS_TO_AZURE != '' && inputs.azure-client-id != '' }} - uses: azure/artifact-signing-action@v2 + uses: azure/artifact-signing-action@v1 with: endpoint: https://eus.codesigning.azure.net/ trusted-signing-account-name: PrismLauncher @@ -152,8 +152,8 @@ runs: ${{ github.workspace }}\PrismLauncher-Setup.exe # recommended in https://github.com/Azure/artifact-signing-action#timestamping-1 - timestamp-rfc3161: 'http://timestamp.acs.microsoft.com' - timestamp-digest: 'SHA256' + timestamp-rfc3161: "http://timestamp.acs.microsoft.com" + timestamp-digest: "SHA256" # TODO(@getchoo): Is this all really needed??? # https://github.com/Azure/trusted-signing-action/blob/fc390cf8ed0f14e248a542af1d838388a47c7a7c/docs/OIDC.md exclude-environment-credential: true @@ -168,19 +168,19 @@ runs: exclude-interactive-browser-credential: true - name: Upload binary zip - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@v6 with: name: PrismLauncher-${{ inputs.artifact-name }}-${{ inputs.version }}-${{ inputs.build-type }} path: install/** - name: Upload portable zip - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@v6 with: name: PrismLauncher-${{ inputs.artifact-name }}-Portable-${{ inputs.version }}-${{ inputs.build-type }} path: install-portable/** - name: Upload installer - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@v6 with: name: PrismLauncher-${{ inputs.artifact-name }}-Setup-${{ inputs.version }}-${{ inputs.build-type }} path: PrismLauncher-Setup.exe diff --git a/.github/actions/setup-dependencies/action.yml b/.github/actions/setup-dependencies/action.yml index b73c7509a..a8ecba583 100644 --- a/.github/actions/setup-dependencies/action.yml +++ b/.github/actions/setup-dependencies/action.yml @@ -55,7 +55,7 @@ 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.23 + uses: hendrikmuhs/ccache-action@v1.2.20 with: variant: sccache create-symlink: ${{ runner.os != 'Windows' }} diff --git a/.github/actions/setup-dependencies/linux/action.yml b/.github/actions/setup-dependencies/linux/action.yml index fa5af702b..753ea19fe 100644 --- a/.github/actions/setup-dependencies/linux/action.yml +++ b/.github/actions/setup-dependencies/linux/action.yml @@ -1,5 +1,4 @@ name: Setup Linux dependencies -description: Install and setup dependencies for building Prism Launcher runs: using: composite diff --git a/.github/actions/setup-dependencies/windows/action.yml b/.github/actions/setup-dependencies/windows/action.yml index 24ad51d8f..43769f4fe 100644 --- a/.github/actions/setup-dependencies/windows/action.yml +++ b/.github/actions/setup-dependencies/windows/action.yml @@ -1,5 +1,4 @@ name: Setup Windows Dependencies -description: Install and setup dependencies for building Prism Launcher inputs: build-type: @@ -91,7 +90,7 @@ runs: - name: Retrieve ccache cache (MinGW) if: ${{ inputs.msystem != '' && inputs.build-type == 'Debug' }} - uses: actions/cache@v5.0.5 + uses: actions/cache@v5.0.1 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 9cde3307d..899f654fe 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -8,7 +8,8 @@ on: # the GitHub repository. This means that it should not evaluate user input in a # way that allows code injection. -permissions: {} +permissions: + contents: read jobs: backport: @@ -18,13 +19,13 @@ jobs: actions: write # for korthout/backport-action to create PR with workflow changes name: Backport Pull Request if: github.repository_owner == 'PrismLauncher' && github.event.pull_request.merged == true && (github.event_name != 'labeled' || startsWith('backport', github.event.label.name)) - runs-on: ubuntu-slim + runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: ref: ${{ github.event.pull_request.head.sha }} - name: Create backport PRs - uses: korthout/backport-action@v4.5 + uses: korthout/backport-action@v4.0.0 with: # Config README: https://github.com/korthout/backport-action#backport-action pull_description: |- diff --git a/.github/workflows/blocked-prs.yml b/.github/workflows/blocked-prs.yml index 001080154..e6b37f188 100644 --- a/.github/workflows/blocked-prs.yml +++ b/.github/workflows/blocked-prs.yml @@ -14,17 +14,15 @@ on: required: true type: number -permissions: {} - jobs: blocked_status: name: Check Blocked Status - runs-on: ubuntu-slim + runs-on: ubuntu-latest steps: - name: Generate token id: generate-token - uses: actions/create-github-app-token@v3 + uses: actions/create-github-app-token@v2 with: app-id: ${{ vars.PULL_REQUEST_APP_ID }} private-key: ${{ secrets.PULL_REQUEST_APP_PRIVATE_KEY }} @@ -72,7 +70,6 @@ jobs: ' <<< "$PR_JSON")" } >> "$GITHUB_ENV" - - name: Find Blocked/Stacked PRs in body id: pr_ids run: | @@ -152,12 +149,12 @@ jobs: - name: Add 'blocked' Label if Missing id: label_blocked - if: "(fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0) && !contains(fromJSON(env.JOB_DATA).prLabels, 'status: blocked') && !fromJSON(steps.blocking_data.outputs.all_merged)" + if: (fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0) && !contains(fromJSON(env.JOB_DATA).prLabels, 'blocked') && !fromJSON(steps.blocking_data.outputs.all_merged) continue-on-error: true env: GH_TOKEN: ${{ steps.generate-token.outputs.token }} run: | - gh -R ${{ github.repository }} issue edit --add-label 'status: blocked' "$PR_NUMBER" + gh -R ${{ github.repository }} issue edit --add-label 'blocked' "$PR_NUMBER" - name: Remove 'blocked' Label if All Dependencies Are Merged id: unlabel_blocked @@ -166,7 +163,7 @@ jobs: env: GH_TOKEN: ${{ steps.generate-token.outputs.token }} run: | - gh -R ${{ github.repository }} issue edit --remove-label 'status: blocked' "$PR_NUMBER" + gh -R ${{ github.repository }} issue edit --remove-label 'blocked' "$PR_NUMBER" - name: Apply 'blocking' Label to Unmerged Dependencies id: label_blocking @@ -177,7 +174,7 @@ jobs: BLOCKING_ISSUES: ${{ steps.blocking_data.outputs.current_blocking }} run: | while read -r pr ; do - gh -R ${{ github.repository }} issue edit --add-label 'status: blocking' "$pr" || true + gh -R ${{ github.repository }} issue edit --add-label 'blocking' "$pr" || true done < <(jq -c '.[]' <<< "$BLOCKING_ISSUES") - name: Apply Blocking PR Status Check @@ -254,4 +251,3 @@ jobs: --body "$COMMENT_BODY" \ --create-if-none \ --edit-last - diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0596906c8..9d56bb661 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,9 +5,57 @@ concurrency: cancel-in-progress: true on: - merge_group: - types: [checks_requested] + push: + branches: + - "develop" + - "release-*" + paths: + # File types + - "**.cpp" + - "**.h" + - "**.java" + - "**.ui" + + # Directories + - "buildconfig/**" + - "cmake/**" + - "launcher/**" + - "libraries/**" + - "program_info/**" + - "tests/**" + + # Files + - "CMakeLists.txt" + - "COPYING.md" + + # Workflows + - ".github/workflows/build.yml" + - ".github/actions/package/**" + - ".github/actions/setup-dependencies/**" pull_request: + paths: + # File types + - "**.cpp" + - "**.h" + - "**.java" + - "**.ui" + + # Directories + - "buildconfig/**" + - "cmake/**" + - "launcher/**" + - "libraries/**" + - "program_info/**" + - "tests/**" + + # Files + - "CMakeLists.txt" + - "COPYING.md" + + # Workflows + - ".github/workflows/build.yml" + - ".github/actions/package/**" + - ".github/actions/setup-dependencies/**" workflow_call: inputs: build-type: @@ -24,8 +72,6 @@ on: type: string default: Debug -permissions: {} - jobs: build: name: Build (${{ matrix.artifact-name }}) @@ -33,7 +79,6 @@ jobs: environment: ${{ inputs.environment || '' }} permissions: - contents: read # Required for Azure Trusted Signing id-token: write # Required for vcpkg binary cache diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml deleted file mode 100644 index d72994c50..000000000 --- a/.github/workflows/clang-tidy.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Clang-Tidy Code Scanning - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -on: - merge_group: - types: [checks_requested] - pull_request: - -permissions: {} - -jobs: - clang-tidy: - name: Run Clang-Tidy - - runs-on: ubuntu-latest - - permissions: - contents: read - security-events: write - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - fetch-depth: 0 # Required for diffing later on - submodules: "true" - - - name: Install Nix - uses: cachix/install-nix-action@v31 - - - name: Run source generators - # TODO(@getchoo): Figure out how to make this work with PCH - run: | - nix develop --command bash -c ' - cmake -B build -D Launcher_USE_PCH=OFF && cmake --build build --target autogen autorcc - ' - - # TODO: Use SARIF after https://github.com/psastras/sarif-rs/issues/638 is fixed - - name: Run clang-tidy-diff - env: - BASE_REF: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha }} - run: | - nix develop --command bash -c ' - clang-tidy -verify-config && git diff -U0 --no-color "$BASE_REF" | clang-tidy-diff.py -p1 -quiet -only-check-in-db - ' diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f9705bf53..e4830ddd9 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -5,21 +5,63 @@ concurrency: cancel-in-progress: true on: - merge_group: - types: [checks_requested] - pull_request: - workflow_dispatch: + push: + branches: + - "develop" + - "release-*" + paths: + # File types + - "**.cpp" + - "**.h" + - "**.java" + - "**.ui" -permissions: {} + # Directories + - "buildconfig/**" + - "cmake/**" + - "launcher/**" + - "libraries/**" + - "program_info/**" + - "tests/**" + + # Files + - "CMakeLists.txt" + - "COPYING.md" + + # Workflows + - ".github/codeql/**" + - ".github/workflows/codeql.yml" + - ".github/actions/setup-dependencies/**" + pull_request: + paths: + # File types + - "**.cpp" + - "**.h" + - "**.java" + - "**.ui" + + # Directories + - "buildconfig/**" + - "cmake/**" + - "launcher/**" + - "libraries/**" + - "program_info/**" + - "tests/**" + + # Files + - "CMakeLists.txt" + - "COPYING.md" + + # Workflows + - ".github/codeql/**" + - ".github/workflows/codeql.yml" + - ".github/actions/setup-dependencies/**" + workflow_dispatch: jobs: CodeQL: runs-on: ubuntu-latest - permissions: - contents: read - security-events: write - steps: - name: Checkout repository uses: actions/checkout@v6 @@ -41,12 +83,12 @@ jobs: - name: Configure and Build run: | - cmake --preset linux -DLauncher_USE_PCH=OFF + cmake --preset linux cmake --build --preset linux --config Debug - name: Run tests run: | - ctest --preset linux --build-config Debug --extra-verbose --output-on-failure + ctest --preset linux --build-config Debug - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml deleted file mode 100644 index 7af2c1ccb..000000000 --- a/.github/workflows/container.yml +++ /dev/null @@ -1,174 +0,0 @@ -name: Development Container - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -on: - merge_group: - types: [checks_requested] - pull_request: - workflow_dispatch: - -permissions: {} - -env: - REGISTRY: ghcr.io - -jobs: - build: - name: Build (${{ matrix.arch }}) - - permissions: - contents: read - packages: write - - outputs: - image-name: ${{ steps.image-name.outputs.image-name }} - - strategy: - fail-fast: false - matrix: - include: - - arch: arm64 - os: ubuntu-24.04-arm - - arch: amd64 - os: ubuntu-24.04 - - runs-on: ${{ matrix.os }} - - steps: - - name: Set image name - id: image-name - run: | - echo "image-name=${REGISTRY}/${GITHUB_REPOSITORY_OWNER,,}/devcontainer" >> "$GITHUB_OUTPUT" - - - name: Install Podman - uses: redhat-actions/podman-install@main - # TODO(@getchoo): Always use this when the action properly supports ARM - if: ${{ runner.arch == 'X64' || runner.arch == 'X86' }} - with: - github-token: ${{ github.token }} - - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Determine metadata for image - id: image-metadata - uses: docker/metadata-action@v6 - with: - images: | - ${{ steps.image-name.outputs.image-name }} - flavor: | - latest=false - tags: | - type=raw,value=latest,enable=${{ github.event.merge_group.base_ref == 'refs/heads/develop' }} - - type=sha - type=sha,format=long - type=ref,event=branch - type=ref,event=tag - - - name: Build image - id: build-image - uses: redhat-actions/buildah-build@v2 - with: - containerfiles: | - ./Containerfile - tags: ${{ steps.image-metadata.outputs.tags }} - labels: ${{ steps.image-metadata.outputs.labels }} - - - name: Push image - id: push-image - if: ${{ github.event_name != 'pull_request' }} - uses: redhat-actions/push-to-registry@v2 - with: - tags: ${{ steps.build-image.outputs.tags }} - username: ${{ github.repository_owner }} - password: ${{ github.token }} - tls-verify: true - - - name: Export image digest - if: ${{ github.event_name != 'pull_request' }} - env: - DIGEST: ${{ steps.push-image.outputs.digest }} - run: | - mkdir -p "$RUNNER_TEMP"/digests - touch "$RUNNER_TEMP"/digests/"${DIGEST#sha256:}" - - - name: Upload digest artifact - if: ${{ github.event_name != 'pull_request' }} - uses: actions/upload-artifact@v7 - with: - name: digests-${{ matrix.arch }} - path: ${{ runner.temp }}/digests/* - if-no-files-found: error - retention-days: 1 - - manifest: - name: Create manifest - - needs: [ build ] - if: ${{ github.event_name != 'pull_request' }} - - permissions: - contents: read - packages: write - - runs-on: ubuntu-24.04 - - steps: - - name: Download digests - uses: actions/download-artifact@v8 - with: - path: ${{ runner.temp }}/digests - pattern: digests-* - merge-multiple: true - - - name: Install Podman - # TODO(@getchoo): Always use this when the action properly supports ARM - if: ${{ runner.arch == 'X64' || runner.arch == 'X86' }} - uses: redhat-actions/podman-install@main - with: - github-token: ${{ github.token }} - - - name: Login to registry - uses: redhat-actions/podman-login@v1 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.repository_owner }} - password: ${{ github.token }} - - - name: Determine metadata for manifest - id: manifest-metadata - uses: docker/metadata-action@v6 - with: - images: | - ${{ needs.build.outputs.image-name }} - flavor: | - latest=false - tags: | - type=raw,value=latest,enable=${{ github.event.merge_group.base_ref == 'refs/heads/develop' }} - - type=sha - type=sha,format=long - type=ref,event=branch - type=ref,event=tag - - - name: Create manifest list - working-directory: ${{ runner.temp }}/digests - env: - IMAGE_NAME: ${{ needs.build.outputs.image-name }} - run: | - while read -r tag; do - podman manifest create "$tag" \ - $(printf "$IMAGE_NAME@sha256:%s " *) - done <<< "$DOCKER_METADATA_OUTPUT_TAGS" - - - name: Push manifest - uses: redhat-actions/push-to-registry@v2 - with: - tags: ${{ steps.manifest-metadata.outputs.tags }} - username: ${{ github.repository_owner }} - password: ${{ github.token }} - tls-verify: true diff --git a/.github/workflows/merge-blocking-pr.yml b/.github/workflows/merge-blocking-pr.yml index 3542a470e..182e40984 100644 --- a/.github/workflows/merge-blocking-pr.yml +++ b/.github/workflows/merge-blocking-pr.yml @@ -11,21 +11,19 @@ on: required: true type: number -permissions: {} - jobs: update-blocked-status: name: Update Blocked Status - runs-on: ubuntu-slim + runs-on: ubuntu-latest # a pr that was a `blocking:` label was merged. # find the open pr's it was blocked by and trigger a refresh of their state - if: "${{ github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'status: blocking') }}" + if: ${{ github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'blocking') }} steps: - name: Generate token id: generate-token - uses: actions/create-github-app-token@v3 + uses: actions/create-github-app-token@v2 with: app-id: ${{ vars.PULL_REQUEST_APP_ID }} private-key: ${{ secrets.PULL_REQUEST_APP_PRIVATE_KEY }} @@ -37,7 +35,7 @@ jobs: PR_NUMBER: ${{ inputs.pr_id || github.event.pull_request.number }} run: | blocked_prs=$( - gh -R ${{ github.repository }} pr list --label 'status: blocked' --json 'number,body' \ + gh -R ${{ github.repository }} pr list --label 'blocked' --json 'number,body' \ | jq -c --argjson pr "$PR_NUMBER" ' reduce ( .[] | select( .body | diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 58f4d263a..2035668f4 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -17,7 +17,6 @@ on: - "**.h" - "**.java" - "**.ui" - - "**.md" # Build files - "**.nix" @@ -34,6 +33,7 @@ on: # Files - "CMakeLists.txt" + - "COPYING.md" # Workflows - ".github/workflows/nix.yml" @@ -44,7 +44,6 @@ on: - "**.h" - "**.java" - "**.ui" - - "**.md" # Build files - "**.nix" @@ -61,12 +60,14 @@ on: # Files - "CMakeLists.txt" + - "COPYING.md" # Workflows - ".github/workflows/nix.yml" workflow_dispatch: -permissions: {} +permissions: + contents: read env: DEBUG: ${{ github.ref_type != 'tag' }} @@ -75,9 +76,6 @@ jobs: build: name: Build (${{ matrix.system }}) - permissions: - contents: read - strategy: fail-fast: false matrix: @@ -88,7 +86,7 @@ jobs: - os: ubuntu-22.04-arm system: aarch64-linux - - os: macos-26 + - os: macos-14 system: aarch64-darwin runs-on: ${{ matrix.os }} @@ -103,7 +101,7 @@ jobs: # For PRs - name: Setup Nix Magic Cache if: ${{ github.event_name == 'pull_request' }} - uses: DeterminateSystems/magic-nix-cache-action@v14 + uses: DeterminateSystems/magic-nix-cache-action@v13 with: diagnostic-endpoint: "" use-flakehub: false @@ -111,7 +109,7 @@ jobs: # For in-tree builds - name: Setup Cachix if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }} - uses: cachix/cachix-action@v17 + uses: cachix/cachix-action@v16 with: name: prismlauncher authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1bb1c5b50..8a7da812e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,16 +4,14 @@ on: release: types: [ released ] -permissions: {} +permissions: + contents: read jobs: winget: name: Winget - permissions: - contents: read - - runs-on: ubuntu-slim + runs-on: windows-latest steps: - name: Publish on Winget diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e332488c3..bd22fe05c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,18 +5,10 @@ on: tags: - "*" -permissions: {} - jobs: build_release: name: Build Release uses: ./.github/workflows/build.yml - permissions: - contents: read - # Required for Azure Trusted Signing - id-token: write - # Required for vcpkg binary cache - packages: write with: build-type: Release environment: Release @@ -24,9 +16,7 @@ jobs: create_release: needs: build_release - permissions: - contents: write - runs-on: ubuntu-slim + runs-on: ubuntu-latest outputs: upload_url: ${{ steps.create_release.outputs.upload_url }} steps: @@ -36,7 +26,7 @@ jobs: submodules: "true" path: "PrismLauncher-source" - name: Download artifacts - uses: actions/download-artifact@v8 + uses: actions/download-artifact@v7 - name: Grab and store version run: | tag_name=$(echo ${{ github.ref }} | grep -oE "[^/]+$") @@ -51,7 +41,6 @@ jobs: 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 - mv PrismLauncher-macOS*/PrismLauncher.dmg PrismLauncher-macOS-${{ env.VERSION }}.dmg tar --exclude='.git' -czf PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }} @@ -94,7 +83,7 @@ jobs: - name: Create release id: create_release - uses: softprops/action-gh-release@v3 + uses: softprops/action-gh-release@v2 with: token: ${{ secrets.GITHUB_TOKEN }} tag_name: ${{ github.ref }} @@ -122,5 +111,4 @@ jobs: PrismLauncher-Windows-MSVC-Portable-${{ env.VERSION }}.zip PrismLauncher-Windows-MSVC-Setup-${{ env.VERSION }}.exe PrismLauncher-macOS-${{ env.VERSION }}.zip - PrismLauncher-macOS-${{ env.VERSION }}.dmg PrismLauncher-${{ env.VERSION }}.tar.gz diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..b8f8137d7 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,29 @@ +name: Stale + +on: + schedule: + # run weekly on sunday + - cron: "0 0 * * 0" + workflow_dispatch: + +jobs: + label: + name: Label issues and PRs + + runs-on: ubuntu-latest + + permissions: + issues: write + pull-requests: write + + steps: + - uses: actions/stale@v10 + with: + days-before-stale: 60 + days-before-close: -1 # Don't close anything + exempt-issue-labels: rfc,nostale,help wanted + exempt-all-milestones: true + exempt-all-assignees: true + operations-per-run: 1000 + stale-issue-label: inactive + stale-pr-label: inactive diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml index fa3e3b4d3..7f8ca2a0d 100644 --- a/.github/workflows/update-flake.yml +++ b/.github/workflows/update-flake.yml @@ -6,30 +6,25 @@ on: - cron: "0 0 * * 0" workflow_dispatch: -permissions: {} +permissions: + contents: write + pull-requests: write jobs: update-flake: if: github.repository == 'PrismLauncher/PrismLauncher' - - permissions: - contents: write - pull-requests: write - - runs-on: ubuntu-slim + runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31 + - uses: cachix/install-nix-action@4e002c8ec80594ecd40e759629461e26c8abed15 # v31 - uses: DeterminateSystems/update-flake-lock@v28 with: commit-msg: "chore(nix): update lockfile" pr-title: "chore(nix): update lockfile" pr-labels: | - platform: Linux - area: packaging - complexity: low - priority: low - type: robot + Linux + packaging + simple change changelog:omit diff --git a/CMakeLists.txt b/CMakeLists.txt index 12afdefcc..e7126109b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.25) # Required for features like `CMAKE_MSVC_DEBUG_INFORMATION_FORMAT` +cmake_minimum_required(VERSION 3.22) # minimum version required by Qt project(Launcher LANGUAGES C CXX) if(APPLE) @@ -13,10 +13,6 @@ endif() ##################################### Set CMake options ##################################### set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTOGEN_ORIGIN_DEPENDS OFF) -set(CMAKE_GLOBAL_AUTOGEN_TARGET ON) -set(CMAKE_GLOBAL_AUTORCC_TARGET ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/") @@ -34,82 +30,79 @@ set(CMAKE_C_STANDARD_REQUIRED true) set(CMAKE_CXX_STANDARD 20) set(CMAKE_C_STANDARD 11) include(GenerateExportHeader) +if(MSVC) + # /GS Adds buffer security checks, default on but incuded anyway to mirror gcc's fstack-protector flag + # /permissive- specify standards-conforming compiler behavior, also enabled by Qt6, default on with std:c++20 + # Use /W4 as /Wall includes unnesserey warnings such as added padding to structs + set(CMAKE_CXX_FLAGS "/GS /permissive- /W4 ${CMAKE_CXX_FLAGS}") -add_compile_definitions($<$>:QT_NO_DEBUG>) -add_compile_definitions(QT_WARN_DEPRECATED_UP_TO=0x060400) -add_compile_definitions(QT_DISABLE_DEPRECATED_UP_TO=0x060400) + # /EHs Enables stack unwind semantics for standard C++ exceptions to ensure stackframes are unwound + # and object deconstructors are called when an exception is caught. + # without it memory leaks and a warning is printed + # /EHc tells the compiler to assume that functions declared as extern "C" never throw a C++ exception + # This appears to not always be a defualt compiler option in CMAKE + set(CMAKE_CXX_FLAGS "/EHsc ${CMAKE_CXX_FLAGS}") -if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") - add_compile_options( - # /GS Adds buffer security checks, default on but included anyway to mirror gcc's fstack-protector flag - "$<$:/GS>" - - # /Gw helps reduce binary size - # /Gy allows the compiler to package individual functions - # /guard:cf enables control flow guard - "$<$,$>:/Gw;/Gy;/guard:cf>" - ) - - add_link_options( - # /LTCG allows for linking wholy optimizated programs - # /MANIFEST:NO disables generating a manifest file, we instead provide our own - # /STACK sets the stack reserve size, ATL's pack list needs 3-4 MiB as of November 2022, provide 8 MiB - "$<$:/LTCG;/MANIFEST:NO;/STACK:8388608>" - ) + # LINK accepts /SUBSYSTEM whics sets if we are a WINDOWS (gui) or a CONSOLE programs + # This implicitly selects an entrypoint specific to the subsystem selected + # qtmain/QtEntryPointLib provides the correct entrypoint (wWinMain) for gui programs + # Additinaly LINK autodetects we use a GUI so we can omit /SUBSYSTEM + # This allows tests to still use have console without using seperate linker flags + # /LTCG allows for linking wholy optimizated programs + # /MANIFEST:NO disables generating a manifest file, we instead provide our own + # /STACK sets the stack reserve size, ATL's pack list needs 3-4 MiB as of November 2022, provide 8 MiB + set(CMAKE_EXE_LINKER_FLAGS "/LTCG /MANIFEST:NO /STACK:8388608 ${CMAKE_EXE_LINKER_FLAGS}") # /GL enables whole program optimizations - # NOTE: With Clang, this is implemented as regular LTO and only used during linking - if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - add_compile_options("$<$,$>:/GL>") - endif() + # /Gw helps reduce binary size + # /Gy allows the compiler to package individual functions + # /guard:cf enables control flow guard + foreach(lang C CXX) + set("CMAKE_${lang}_FLAGS_RELEASE" "/GL /Gw /Gy /guard:cf") + endforeach() # See https://github.com/ccache/ccache/issues/1040 - # TODO(@getchoo): Is sccache affected by this? Would be nice to use `ProgramDatabase`.... - set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<$:Embedded>") + # Note, CMake 3.25 replaces this with CMAKE_MSVC_DEBUG_INFORMATION_FORMAT + # See https://cmake.org/cmake/help/v3.25/variable/CMAKE_MSVC_DEBUG_INFORMATION_FORMAT.html + foreach(config DEBUG RELWITHDEBINFO) + foreach(lang C CXX) + set(flags_var "CMAKE_${lang}_FLAGS_${config}") + string(REGEX REPLACE "/Z[Ii]" "/Z7" ${flags_var} "${${flags_var}}") + endforeach() + endforeach() if(CMAKE_MSVC_RUNTIME_LIBRARY STREQUAL "MultiThreadedDLL") set(CMAKE_MAP_IMPORTED_CONFIG_DEBUG Release "") set(CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO Release "") endif() else() - add_compile_options("$<$:-fstack-protector-strong;--param=ssp-buffer-size=4>") - - # Avoid re-defining _FORTIFY_SOURCE, as it can cause redefinition errors in setups that use it by default (i.e., package builds) - if(NOT (CMAKE_C_FLAGS MATCHES "-D_FORTIFY_SOURCE" OR CMAKE_CXX_FLAGS MATCHES "-D_FORTIFY_SOURCE")) - # NOTE: _FORTIFY_SOURCE requires optimizations in most newer versions of glibc - add_compile_options("$<$,$>:-D_FORTIFY_SOURCE=2>") - endif() + set(CMAKE_CXX_FLAGS "-Wall -pedantic -fstack-protector-strong --param=ssp-buffer-size=4 ${CMAKE_CXX_FLAGS}") # ATL's pack list needs more than the default 1 Mib stack on windows - if(CMAKE_SYSTEM_NAME STREQUAL "Windows") - add_link_options("$<$:-Wl,--stack,8388608>") + if(WIN32) + set(CMAKE_EXE_LINKER_FLAGS "-Wl,--stack,8388608 ${CMAKE_EXE_LINKER_FLAGS}") # -ffunction-sections and -fdata-sections help reduce binary size # -mguard=cf enables Control Flow Guard # TODO: Look into -gc-sections to further reduce binary size - add_compile_options("$<$,$>:-ffunction-sections;-fdata-sections;-mguard=cf>") + foreach(lang C CXX) + set("CMAKE_${lang}_FLAGS_RELEASE" "-ffunction-sections -fdata-sections -mguard=cf") + endforeach() endif() endif() +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_WARN_DEPRECATED_UP_TO=0x060200") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_DISABLE_DEPRECATED_UP_TO=0x060000") + +# set CXXFLAGS for build targets +set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}") + # Export compile commands for debug builds if we can (useful in LSPs like clangd) # https://cmake.org/cmake/help/v3.31/variable/CMAKE_EXPORT_COMPILE_COMMANDS.html if(CMAKE_GENERATOR STREQUAL "Unix Makefiles" OR CMAKE_GENERATOR MATCHES "^Ninja") set(CMAKE_EXPORT_COMPILE_COMMANDS ON) endif() -option(USE_CLANG_TIDY "Enable the use of clang-tidy during compilation" OFF) - -if(USE_CLANG_TIDY) - find_program(CLANG_TIDY clang-tidy OPTIONAL) - if(CLANG_TIDY) - message(STATUS "Using clang-tidy during compilation") - set(CLANG_TIDY_COMMAND "${CLANG_TIDY}" "--config-file=${CMAKE_SOURCE_DIR}/.clang-tidy") - set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY_COMMAND}) - else() - message(WARNING "Unable to find `clang-tidy`. Not using during compilation") - endif() -endif() - option(DEBUG_ADDRESS_SANITIZER "Enable Address Sanitizer in Debug builds" OFF) # If this is a Debug build turn on address sanitiser @@ -146,9 +139,8 @@ if(ENABLE_LTO) if(ipo_supported) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_MINSIZEREL TRUE) - set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO TRUE) if(CMAKE_BUILD_TYPE) - if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel") message(STATUS "IPO / LTO enabled") else() message(STATUS "Not enabling IPO / LTO on debug builds") @@ -177,15 +169,14 @@ endif() ######## Set URLs ######## set(Launcher_NEWS_RSS_URL "https://prismlauncher.org/feed/feed.xml" CACHE STRING "URL to fetch Prism Launcher's news RSS feed from.") set(Launcher_NEWS_OPEN_URL "https://prismlauncher.org/news" CACHE STRING "URL that gets opened when the user clicks 'More News'") -set(Launcher_WIKI_URL "https://prismlauncher.org/wiki/" CACHE STRING "URL that gets opened when the user clicks 'Launcher Help'") -set(Launcher_HELP_URL "https://prismlauncher.org/wiki/help-pages/%1" CACHE STRING "URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help in a dialog window") +set(Launcher_HELP_URL "https://prismlauncher.org/wiki/help-pages/%1" CACHE STRING "URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help") set(Launcher_LOGIN_CALLBACK_URL "https://prismlauncher.org/successful-login" CACHE STRING "URL that gets opened when the user successfully logins.") -set(Launcher_LEGACY_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" CACHE STRING "URL for legacy (<=1.5.2) FML Libraries.") +set(Launcher_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" CACHE STRING "URL for FML Libraries.") ######## Set version numbers ######## -set(Launcher_VERSION_MAJOR 12) +set(Launcher_VERSION_MAJOR 10) set(Launcher_VERSION_MINOR 0) -set(Launcher_VERSION_PATCH 0) +set(Launcher_VERSION_PATCH 2) set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}") set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}.0") @@ -419,7 +410,7 @@ if(UNIX AND APPLE) COMMAND ${ACTOOL_EXE} "${ICON_SOURCE}" --compile "${ASSETS_OUT_DIR}" --output-partial-info-plist /dev/null - --app-icon ${Launcher_Name} + --app-icon PrismLauncher --enable-on-demand-resources NO --target-device mac --minimum-deployment-target ${CMAKE_OSX_DEPLOYMENT_TARGET} @@ -454,7 +445,7 @@ elseif(UNIX) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_MetaInfo} DESTINATION ${KDE_INSTALL_METAINFODIR}) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION "${KDE_INSTALL_ICONDIR}/hicolor/scalable/apps") install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_PNG_256} DESTINATION "${KDE_INSTALL_ICONDIR}/hicolor/256x256/apps" RENAME "${Launcher_AppID}.png") - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_MIMEInfo} DESTINATION ${KDE_INSTALL_MIMEDIR}) + install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_mrpack_MIMEInfo} DESTINATION ${KDE_INSTALL_MIMEDIR}) install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/launcher/qtlogging.ini" DESTINATION "share/${Launcher_Name}") @@ -492,6 +483,7 @@ option(NBT_USE_ZLIB "Build NBT library with zlib support" OFF) option(NBT_BUILD_TESTS "Build NBT library tests" OFF) #FIXME: fix unit tests. add_subdirectory(libraries/libnbtplusplus) +add_subdirectory(libraries/systeminfo) # system information library add_subdirectory(libraries/launcher) # java based launcher part for Minecraft add_subdirectory(libraries/javacheck) # java compatibility checker diff --git a/CMakePresets.json b/CMakePresets.json index f8496acb6..5e9ee8b39 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -105,7 +105,7 @@ "lhs": "${hostSystemName}", "rhs": "Darwin" }, - "configurePreset": "macos" + "configurePreset": "macos" }, { "name": "macos_universal", @@ -141,8 +141,7 @@ "name": "base", "hidden": true, "output": { - "outputOnFailure": true, - "verbosity": "extra" + "outputOnFailure": true }, "execution": { "noTestsAction": "error" @@ -153,11 +152,11 @@ } } }, - { - "name": "linux", + { + "name": "linux", "displayName": "Linux", "inherits": [ - "base" + "base" ], "condition": { "type": "equals", @@ -166,11 +165,11 @@ }, "configurePreset": "linux" }, - { - "name": "macos", + { + "name": "macos", "displayName": "macOS", "inherits": [ - "base" + "base" ], "condition": { "type": "equals", @@ -179,11 +178,11 @@ }, "configurePreset": "macos" }, - { - "name": "macos_universal", + { + "name": "macos_universal", "displayName": "macOS (Universal Binary)", "inherits": [ - "base" + "base" ], "condition": { "type": "equals", @@ -192,11 +191,11 @@ }, "configurePreset": "macos_universal" }, - { - "name": "windows_mingw", + { + "name": "windows_mingw", "displayName": "Windows (MinGW)", "inherits": [ - "base" + "base" ], "condition": { "type": "equals", @@ -205,11 +204,11 @@ }, "configurePreset": "windows_mingw" }, - { - "name": "windows_msvc", + { + "name": "windows_msvc", "displayName": "Windows (MSVC)", "inherits": [ - "base" + "base" ], "condition": { "type": "equals", diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f4b12d08b..8adfaec0e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,52 +1,5 @@ # Contributions Guidelines -## Restrictions on Generative AI Usage (AI Policy) - -> [!NOTE] -> The following is adapted from [matplotlib's contributing guide](https://matplotlib.org/devdocs/devel/contribute.html#generative-ai) and the [Linux Kernel policy guide](https://www.kernel.org/doc./html/next/process/coding-assistants.html) - -We expect authentic engagement in our community. - -- Do not post output from Large Language Models or similar generative AI as comments on GitHub or our discord server, as such comments tend to be formulaic and low-quality content. -- If you use generative AI tools as an aid in developing code or documentation changes, ensure that you fully understand the proposed changes and can explain why they are the correct approach. - -Make sure you have added value based on your personal competency to your contributions. -Just taking some input, feeding it to an AI and posting the result is not of value to the project. -To preserve precious core developer capacity, we reserve the right to rigorously reject seemingly AI generated low-value contributions. - -### Signed-off-by and Developer Certificate of Origin - -AI agents MUST NOT add Signed-off-by tags. Only humans can legally certify the Developer Certificate of Origin (DCO). The human submitter is responsible for: - -- Reviewing all AI-generated code -- Ensuring compliance with licensing requirements -- Adding their own Signed-off-by tag to certify the DCO -- Taking full responsibility for the contribution - -See [Signing your work](#signing-your-work) for more information. - -### Attribution - -When AI tools contribute to development, proper attribution helps track the evolving role of AI in the development process. Contributions should include an Assisted-by tag in the commit message with the following format: - -```text -Assisted-by: AGENT_NAME:MODEL_VERSION [TOOL1] [TOOL2] -``` - -Where: - -- `AGENT_NAME` is the name of the AI tool or framework -- `MODEL_VERSION` is the specific model version used -- `[TOOL1] [TOOL2]` are optional specialized analysis tools used (e.g., coccinelle, sparse, smatch, clang-tidy) - -Basic development tools (git, gcc, make, editors) should not be listed. - -Example: - -```text -Assisted-by: Claude:claude-3-opus coccinelle sparse -``` - ## Code style All files are formatted with `clang-format` using the configuration in `.clang-format`. Ensure it is run on changed files before committing! diff --git a/Containerfile b/Containerfile deleted file mode 100644 index 59595fe55..000000000 --- a/Containerfile +++ /dev/null @@ -1,74 +0,0 @@ -ARG DEBIAN_VERSION=stable-slim - -FROM docker.io/library/debian:${DEBIAN_VERSION} - -ARG QT_ABI=gcc_64 -ARG QT_ARCH= -ARG QT_HOST=linux -ARG QT_TARGET=desktop -ARG QT_VERSION=6.10.2 - -ARG DEBIAN_FRONTEND=noninteractive - -RUN apt-get update \ - && apt-get --assume-yes upgrade \ - && apt-get --assume-yes autopurge - -# Use Adoptium for Java 17 -RUN apt-get --assume-yes --no-install-recommends install \ - apt-transport-https ca-certificates curl gpg -RUN curl -L https://packages.adoptium.net/artifactory/api/gpg/key/public | gpg --dearmor | tee /etc/apt/trusted.gpg.d/adoptium.gpg -RUN echo "deb https://packages.adoptium.net/artifactory/deb $(awk -F= '/^VERSION_CODENAME/{print$2}' /etc/os-release) main" | tee /etc/apt/sources.list.d/adoptium.list -RUN apt-get update - -# Install base dependencies -RUN apt-get --assume-yes --no-install-recommends install \ - # Compilers - clang lld llvm temurin-17-jdk \ - # Build system - cmake ninja-build extra-cmake-modules pkg-config \ - # Dependencies - cmark gamemode-dev libarchive-dev libcmark-dev libgamemode0 libgl1-mesa-dev libqrencode-dev libtomlplusplus-dev scdoc zlib1g-dev \ - # Tooling - clang-format clang-tidy git - -# Use LLD by default for faster linking -ENV CMAKE_LINKER_TYPE=lld - -# Prepare and install Qt -## Setup UTF-8 locale (required, apparently) -RUN apt-get --assume-yes --no-install-recommends install locales -RUN echo "C.UTF-8 UTF-8" > /etc/locale.gen -RUN locale-gen -ENV LC_ALL=C.UTF-8 - -## Some libraries are required for the official binaries -RUN apt-get --assume-yes --no-install-recommends install \ - libglib2.0-0t64 libxkbcommon0 python3-pip - -RUN pip3 install --break-system-packages aqtinstall -RUN aqt install-qt \ - ${QT_HOST} ${QT_TARGET} ${QT_VERSION} ${QT_ARCH} \ - --outputdir /opt/qt \ - --modules qtimageformats qtnetworkauth - -ENV PATH=/opt/qt/${QT_VERSION}/${QT_ABI}/bin:$PATH -ENV QT_PLUGIN_PATH=/opt/qt/${QT_VERSION}/${QT_ABI}/plugins/ - -## We don't use these. Nuke them -RUN rm -rf \ - "$QT_PLUGIN_PATH"/designer \ - "$QT_PLUGIN_PATH"/help \ - # "$QT_PLUGIN_PATH"/platformthemes/libqgtk3.so \ - "$QT_PLUGIN_PATH"/printsupport \ - "$QT_PLUGIN_PATH"/qmllint \ - "$QT_PLUGIN_PATH"/qmlls \ - "$QT_PLUGIN_PATH"/qmltooling \ - "$QT_PLUGIN_PATH"/sqldrivers - -# Setup workspace -RUN mkdir /work -WORKDIR /work - -ENTRYPOINT ["bash"] -CMD ["-i"] diff --git a/README.md b/README.md index ac6cfd96b..ce19db037 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 contributors) -- [nightly.link](https://prismlauncher.org/nightly) (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.cpp.in b/buildconfig/BuildConfig.cpp.in index 14d8236d8..0a1ac334d 100644 --- a/buildconfig/BuildConfig.cpp.in +++ b/buildconfig/BuildConfig.cpp.in @@ -50,7 +50,6 @@ Config::Config() LAUNCHER_GIT = "@Launcher_Git@"; LAUNCHER_APPID = "@Launcher_AppID@"; LAUNCHER_SVGFILENAME = "@Launcher_SVGFileName@"; - LAUNCHER_ENVNAME = "@Launcher_ENVName@"; USER_AGENT = "@Launcher_UserAgent@"; @@ -106,14 +105,13 @@ Config::Config() NEWS_RSS_URL = "@Launcher_NEWS_RSS_URL@"; NEWS_OPEN_URL = "@Launcher_NEWS_OPEN_URL@"; - WIKI_URL = "@Launcher_WIKI_URL@"; HELP_URL = "@Launcher_HELP_URL@"; LOGIN_CALLBACK_URL = "@Launcher_LOGIN_CALLBACK_URL@"; IMGUR_CLIENT_ID = "@Launcher_IMGUR_CLIENT_ID@"; MSA_CLIENT_ID = "@Launcher_MSA_CLIENT_ID@"; FLAME_API_KEY = "@Launcher_CURSEFORGE_API_KEY@"; META_URL = "@Launcher_META_URL@"; - LEGACY_FMLLIBS_BASE_URL = "@Launcher_LEGACY_FMLLIBS_BASE_URL@"; + FMLLIBS_BASE_URL = "@Launcher_FMLLIBS_BASE_URL@"; GLFW_LIBRARY_NAME = "@Launcher_GLFW_LIBRARY_NAME@"; OPENAL_LIBRARY_NAME = "@Launcher_OPENAL_LIBRARY_NAME@"; diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h index a5851bfba..045d987d4 100644 --- a/buildconfig/BuildConfig.h +++ b/buildconfig/BuildConfig.h @@ -54,7 +54,6 @@ class Config { QString LAUNCHER_GIT; QString LAUNCHER_APPID; QString LAUNCHER_SVGFILENAME; - QString LAUNCHER_ENVNAME; /// The major version number. int VERSION_MAJOR; @@ -129,12 +128,7 @@ class Config { QString NEWS_OPEN_URL; /** - * URL that gets opened when the user clicks 'Launcher Help' - */ - QString WIKI_URL; - - /** - * URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help in a dialog window + * URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help */ QString HELP_URL; @@ -175,10 +169,10 @@ class Config { 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 LEGACY_FMLLIBS_BASE_URL; + QString FMLLIBS_BASE_URL; QString TRANSLATION_FILES_URL; - QString FTB_API_BASE_URL = "https://api.feed-the-beast.com/v1/modpacks/public"; + QString MODPACKSCH_API_BASE_URL = "https://api.modpacks.ch/"; QString LEGACY_FTB_CDN_BASE_URL = "https://dist.creeper.host/FTB2/"; @@ -194,10 +188,8 @@ class Config { QString MODRINTH_STAGING_URL = "https://staging-api.modrinth.com/v2"; QString MODRINTH_PROD_URL = "https://api.modrinth.com/v2"; QStringList MODRINTH_MRPACK_HOSTS{ "cdn.modrinth.com", "github.com", "raw.githubusercontent.com", "gitlab.com" }; - QString MODRINTH_DOWNLOAD_HOST = "cdn.modrinth.com"; QString FLAME_BASE_URL = "https://api.curseforge.com/v1"; - QString FLAME_DOWNLOAD_HOST = "edge.forgecdn.net"; QString versionString() const; /** diff --git a/cmake/CompilerWarnings.cmake b/cmake/CompilerWarnings.cmake new file mode 100644 index 000000000..51d2fb13a --- /dev/null +++ b/cmake/CompilerWarnings.cmake @@ -0,0 +1,163 @@ +# +# Function to set compiler warnings with reasonable defaults at the project level. +# Taken from https://github.com/aminya/project_options/blob/main/src/CompilerWarnings.cmake +# under the folowing license: +# +# MIT License +# +# Copyright (c) 2022-2100 Amin Yahyaabadi +# +# 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. +# + +include_guard() + +function(_set_project_warnings_add_target_link_option TARGET OPTIONS) + target_link_options(${_project_name} INTERFACE ${OPTIONS}) +endfunction() + +# Set the compiler warnings +# +# https://clang.llvm.org/docs/DiagnosticsReference.html +# https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Available.md +function( + set_project_warnings + _project_name + MSVC_WARNINGS + CLANG_WARNINGS + GCC_WARNINGS +) + if("${MSVC_WARNINGS}" STREQUAL "") + set(MSVC_WARNINGS + /W4 # Baseline reasonable warnings + /w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss of data + /w14254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data + /w14263 # 'function': member function does not override any base class virtual member function + /w14265 # 'classname': class has virtual functions, but destructor is not virtual instances of this class may not + # be destructed correctly + /w14287 # 'operator': unsigned/negative constant mismatch + /we4289 # nonstandard extension used: 'variable': loop control variable declared in the for-loop is used outside + # the for-loop scope + /w14296 # 'operator': expression is always 'boolean_value' + /w14311 # 'variable': pointer truncation from 'type1' to 'type2' + /w14545 # expression before comma evaluates to a function which is missing an argument list + /w14546 # function call before comma missing argument list + /w14547 # 'operator': operator before comma has no effect; expected operator with side-effect + /w14549 # 'operator': operator before comma has no effect; did you intend 'operator'? + /w14555 # expression has no effect; expected expression with side- effect + /w14619 # pragma warning: there is no warning number 'number' + /w14640 # Enable warning on thread un-safe static member initialization + /w14826 # Conversion from 'type1' to 'type_2' is sign-extended. This may cause unexpected runtime behavior. + /w14905 # wide string literal cast to 'LPSTR' + /w14906 # string literal cast to 'LPWSTR' + /w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied + /permissive- # standards conformance mode for MSVC compiler. + + /we4062 # forbid omitting a possible value of an enum in a switch statement + ) + endif() + + if("${CLANG_WARNINGS}" STREQUAL "") + set(CLANG_WARNINGS + -Wall + -Wextra # reasonable and standard + -Wshadow # warn the user if a variable declaration shadows one from a parent context + -Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps + # catch hard to track down memory errors + -Wold-style-cast # warn for c-style casts + -Wcast-align # warn for potential performance problem casts + -Wunused # warn on anything being unused + -Woverloaded-virtual # warn if you overload (not override) a virtual function + -Wpedantic # warn if non-standard C++ is used + -Wconversion # warn on type conversions that may lose data + -Wsign-conversion # warn on sign conversions + -Wnull-dereference # warn if a null dereference is detected + -Wdouble-promotion # warn if float is implicit promoted to double + -Wformat=2 # warn on security issues around functions that format output (ie printf) + -Wimplicit-fallthrough # warn on statements that fallthrough without an explicit annotation + # -Wgnu-zero-variadic-macro-arguments (part of -pedantic) is triggered by every qCDebug() call and therefore results + # in a lot of noise. This warning is only notifying us that clang is emulating the GCC behaviour + # instead of the exact standard wording so we can safely ignore it + -Wno-gnu-zero-variadic-macro-arguments + + -Werror=switch # forbid omitting a possible value of an enum in a switch statement + ) + endif() + + if("${GCC_WARNINGS}" STREQUAL "") + set(GCC_WARNINGS + ${CLANG_WARNINGS} + -Wmisleading-indentation # warn if indentation implies blocks where blocks do not exist + -Wduplicated-cond # warn if if / else chain has duplicated conditions + -Wduplicated-branches # warn if if / else branches have duplicated code + -Wlogical-op # warn about logical operations being used where bitwise were probably wanted + -Wuseless-cast # warn if you perform a cast to the same type + + -Werror=switch # forbid omitting a possible value of an enum in a switch statement + ) + endif() + + if(MSVC) + set(PROJECT_WARNINGS_CXX ${MSVC_WARNINGS}) + elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + set(PROJECT_WARNINGS_CXX ${CLANG_WARNINGS}) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(PROJECT_WARNINGS_CXX ${GCC_WARNINGS}) + else() + message(AUTHOR_WARNING "No compiler warnings set for CXX compiler: '${CMAKE_CXX_COMPILER_ID}'") + # TODO support Intel compiler + endif() + + # Add C warnings + set(PROJECT_WARNINGS_C "${PROJECT_WARNINGS_CXX}") + list( + REMOVE_ITEM + PROJECT_WARNINGS_C + -Wnon-virtual-dtor + -Wold-style-cast + -Woverloaded-virtual + -Wuseless-cast + -Wextra-semi + + -Werror=switch # forbid omitting a possible value of an enum in a switch statement + ) + + target_compile_options( + ${_project_name} + INTERFACE # C++ warnings + $<$:${PROJECT_WARNINGS_CXX}> + # C warnings + $<$:${PROJECT_WARNINGS_C}> + ) + + # If we are using the compiler as a linker driver pass the warnings to it + # (most useful when using LTO or warnings as errors) + if(CMAKE_CXX_LINK_EXECUTABLE MATCHES "^") + _set_project_warnings_add_target_link_option( + ${_project_name} "$<$:${PROJECT_WARNINGS_CXX}>" + ) + endif() + + if(CMAKE_C_LINK_EXECUTABLE MATCHES "^") + _set_project_warnings_add_target_link_option( + ${_project_name} "$<$:${PROJECT_WARNINGS_C}>" + ) + endif() + + endfunction() diff --git a/cmake/MacOSXBundleInfo.plist.in b/cmake/MacOSXBundleInfo.plist.in index eb40bacfd..0814f7ef0 100644 --- a/cmake/MacOSXBundleInfo.plist.in +++ b/cmake/MacOSXBundleInfo.plist.in @@ -7,7 +7,7 @@ NSMicrophoneUsageDescription A Minecraft mod wants to access your microphone. NSDownloadsFolderUsageDescription - ${Launcher_DisplayName} uses access to your Downloads folder to help you more quickly add mods that can't be automatically downloaded to your instance. You can change where ${Launcher_DisplayName} scans for downloaded mods in Settings or the prompt that appears. + Prism uses access to your Downloads folder to help you more quickly add mods that can't be automatically downloaded to your instance. You can change where Prism scans for downloaded mods in Settings or the prompt that appears. NSLocalNetworkUsageDescription Minecraft uses the local network to find and connect to LAN servers. NSPrincipalClass @@ -61,7 +61,7 @@ mrpack CFBundleTypeName - ${Launcher_DisplayName} instance + Prism Launcher instance CFBundleTypeOSTypes TEXT @@ -87,11 +87,10 @@ CFBundleURLName - ${Launcher_Name} + Prismlauncher CFBundleURLSchemes prismlauncher - ${MACOSX_BUNDLE_EXECUTABLE_NAME} diff --git a/flake.lock b/flake.lock index 640d2bcf1..730f36a14 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "libnbtplusplus": { "flake": false, "locked": { - "lastModified": 1772016279, - "narHash": "sha256-7itkptyjoRcXfGLwg1/jxajetZ3a4mDc66+w4X6yW8s=", + "lastModified": 1744811532, + "narHash": "sha256-qhmjaRkt+O7A+gu6HjUkl7QzOEb4r8y8vWZMG2R/C6o=", "owner": "PrismLauncher", "repo": "libnbtplusplus", - "rev": "687e43031df0dc641984b4256bcca50d5b3f7de3", + "rev": "531449ba1c930c98e0bcf5d332b237a8566f9d78", "type": "github" }, "original": { @@ -18,15 +18,15 @@ }, "nixpkgs": { "locked": { - "lastModified": 1778443072, - "narHash": "sha256-rNDJzV2JTV5SUTwv1cgKZYMdyoUYU9/YfegSaUf3QfY=", - "rev": "da5ad661ba4e5ef59ba743f0d112cbc30e474f32", + "lastModified": 1766473571, + "narHash": "sha256-QvjEJNgMVuOootbR+DEfbiW+zSK57U32CE0jmVdcNjQ=", + "rev": "76701a179d3a98b07653e2b0409847499b2a07d3", "type": "tarball", - "url": "https://releases.nixos.org/nixos/unstable/nixos-26.05pre995699.da5ad661ba4e/nixexprs.tar.xz" + "url": "https://releases.nixos.org/nixos/25.11/nixos-25.11.2403.76701a179d3a/nixexprs.tar.xz" }, "original": { "type": "tarball", - "url": "https://channels.nixos.org/nixos-unstable/nixexprs.tar.xz" + "url": "https://channels.nixos.org/nixos-25.11/nixexprs.tar.xz" } }, "root": { diff --git a/flake.nix b/flake.nix index 289e0ec1c..6594db522 100644 --- a/flake.nix +++ b/flake.nix @@ -9,7 +9,7 @@ }; inputs = { - nixpkgs.url = "https://channels.nixos.org/nixos-unstable/nixexprs.tar.xz"; + nixpkgs.url = "https://channels.nixos.org/nixos-25.11/nixexprs.tar.xz"; libnbtplusplus = { url = "github:PrismLauncher/libnbtplusplus"; @@ -42,7 +42,7 @@ let pkgs = nixpkgsFor.${system}; - llvm = pkgs.llvmPackages_22; + llvm = pkgs.llvmPackages_19; in { @@ -85,9 +85,7 @@ let pkgs = nixpkgsFor.${system}; - llvm = pkgs.llvmPackages_22; - python = pkgs.python3; - mkShell = pkgs.mkShell.override { inherit (llvm) stdenv; }; + llvm = pkgs.llvmPackages_19; packages' = self.packages.${system}; @@ -133,36 +131,18 @@ in { - default = mkShell { + default = pkgs.mkShell { name = "prism-launcher"; inputsFrom = [ packages'.prismlauncher-unwrapped ]; - packages = [ - pkgs.ccache + packages = with pkgs; [ + ccache llvm.clang-tools - python # NOTE(@getchoo): Required for run-clang-tidy, etc. - - (pkgs.stdenvNoCC.mkDerivation { - pname = "clang-tidy-diff"; - inherit (llvm.clang) version; - - nativeBuildInputs = [ - pkgs.installShellFiles - python.pkgs.wrapPython - ]; - - dontUnpack = true; - dontConfigure = true; - dontBuild = true; - - postInstall = "installBin ${llvm.libclang.python}/share/clang/clang-tidy-diff.py"; - postFixup = "wrapPythonPrograms"; - }) ]; cmakeBuildType = "Debug"; - cmakeFlags = [ "-GNinja" ] ++ packages'.prismlauncher-unwrapped.cmakeFlags; + cmakeFlags = [ "-GNinja" ] ++ packages'.prismlauncher.cmakeFlags; dontFixCmake = true; shellHook = '' @@ -185,25 +165,17 @@ formatter = forAllSystems (system: nixpkgsFor.${system}.nixfmt-rfc-style); - overlays.default = - final: prev: - - let - llvm = final.llvmPackages_22 or prev.llvmPackages_22; - in - - { - prismlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix { - inherit (llvm) stdenv; - inherit - libnbtplusplus - self - ; - }; - - prismlauncher = final.callPackage ./nix/wrapper.nix { }; + overlays.default = final: prev: { + prismlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix { + inherit + libnbtplusplus + self + ; }; + prismlauncher = final.callPackage ./nix/wrapper.nix { }; + }; + packages = forAllSystems ( system: diff --git a/launcher/Application.cpp b/launcher/Application.cpp index ddeb30588..c2519c634 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -126,11 +126,12 @@ #include #include +#include #include "SysInfo.h" #ifdef Q_OS_LINUX #include -#include "LibraryUtils.h" +#include "MangoHud.h" #include "gamemode_client.h" #endif @@ -157,6 +158,7 @@ #endif #include #include +#include "console/WindowsConsole.h" #endif #include "console/Console.h" @@ -290,9 +292,21 @@ std::tuple read_lock_File(const Q Application::Application(int& argc, char** argv) : QApplication(argc, argv) { +#if defined Q_OS_WIN32 + // attach the parent console if stdout not already captured + if (AttachWindowsConsole()) { + consoleAttached = true; + if (auto err = EnableAnsiSupport(); !err) { + isANSIColorConsole = true; + } else { + std::cout << "Error setting up ansi console" << err.message() << std::endl; + } + } +#else if (console::isConsole()) { isANSIColorConsole = true; } +#endif setOrganizationName(BuildConfig.LAUNCHER_NAME); setOrganizationDomain(BuildConfig.LAUNCHER_DOMAIN); @@ -318,7 +332,6 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) { { "a", "profile" }, "Use the account specified by its profile name (only valid in combination with --launch)", "profile" }, { { "o", "offline" }, "Launch offline, with given player name (only valid in combination with --launch)", "offline" }, { "alive", "Write a small '" + liveCheckFile + "' file after the launcher starts" }, - { "show-window", "Show the main launcher window (useful in combination with --launch)" }, { { "I", "import" }, "Import instance or resource from specified local path or URL", "url" }, { "show", "Opens the window for the specified instance (by instance ID)", "show" } }); // Has to be positional for some OS to handle that properly @@ -334,13 +347,12 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_worldToJoin = parser.value("world"); m_profileToUse = parser.value("profile"); if (parser.isSet("offline")) { - m_launchOffline = true; + m_offline = true; m_offlineName = parser.value("offline"); } m_liveCheck = parser.isSet("alive"); m_instanceIdToShowWindowOf = parser.value("show"); - m_showMainWindow = parser.isSet("show-window"); for (auto url : parser.values("import")) { m_urlsToImport.append(normalizeImportUrl(url)); @@ -352,7 +364,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) } // error if --launch is missing with --server or --profile - if ((!m_serverToJoin.isEmpty() || !m_worldToJoin.isEmpty() || !m_profileToUse.isEmpty() || m_launchOffline) && + if ((!m_serverToJoin.isEmpty() || !m_worldToJoin.isEmpty() || !m_profileToUse.isEmpty() || m_offline) && m_instanceIdToLaunch.isEmpty()) { std::cerr << "--server, --profile and --offline can only be used in combination with --launch!" << std::endl; m_status = Application::Failed; @@ -394,7 +406,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) } else { QDir foo; if (DesktopServices::isSnap()) { - foo = QDir(qEnvironmentVariable("SNAP_USER_COMMON")); + foo = QDir(getenv("SNAP_USER_COMMON")); } else { foo = QDir(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "..")); } @@ -480,7 +492,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) if (!m_profileToUse.isEmpty()) { launch.args["profile"] = m_profileToUse; } - if (m_launchOffline) { + if (m_offline) { launch.args["offline_enabled"] = "true"; launch.args["offline_name"] = m_offlineName; } @@ -514,13 +526,12 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) logFile = std::unique_ptr(new QFile(logBase.arg(0))); if (!logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { showFatalErrorMessage("The launcher data folder is not writable!", - QString("The launcher couldn't create a log file - %1.\n" + QString("The launcher couldn't create a log file - the data folder is not writable.\n" "\n" "Make sure you have write permissions to the data folder.\n" - "(%2)\n" + "(%1)\n" "\n" "The launcher cannot continue until you fix this problem.") - .arg(logFile->errorString()) .arg(dataPath)); return; } @@ -578,14 +589,16 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) } { - auto migrated = handleDataMigration( - dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../PolyMC"), "PolyMC", - "polymc.cfg"); - if (!migrated) { - handleDataMigration(dataPath, - FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../multimc"), - "MultiMC", "multimc.cfg"); - } + bool migrated = false; + + if (!migrated) + migrated = handleDataMigration( + dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../PolyMC"), "PolyMC", + "polymc.cfg"); + if (!migrated) + migrated = handleDataMigration( + dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../multimc"), "MultiMC", + "multimc.cfg"); } { @@ -625,11 +638,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) if (check.write(payload) == payload.size()) { check.close(); } else { - qWarning() << "Could not write into" << liveCheckFile << "error:" << check.errorString(); + qWarning() << "Could not write into" << liveCheckFile << "!"; check.remove(); // also closes file! } } else { - qWarning() << "Could not open" << liveCheckFile << "for writing:" << check.errorString(); + qWarning() << "Could not open" << liveCheckFile << "for writing!"; } } @@ -731,9 +744,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) // Memory m_settings->registerSetting({ "MinMemAlloc", "MinMemoryAlloc" }, 512); - m_settings->registerSetting({ "MaxMemAlloc", "MaxMemoryAlloc" }, SysInfo::defaultMaxJvmMem()); + m_settings->registerSetting({ "MaxMemAlloc", "MaxMemoryAlloc" }, SysInfo::suitableMaxMem()); m_settings->registerSetting("PermGen", 128); - m_settings->registerSetting("LowMemWarning", true); // Java Settings m_settings->registerSetting("JavaPath", ""); @@ -776,8 +788,6 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_settings->registerSetting("ModMetadataDisabled", false); m_settings->registerSetting("ModDependenciesDisabled", false); m_settings->registerSetting("SkipModpackUpdatePrompt", false); - m_settings->registerSetting("ShowModIncompat", false); - m_settings->registerSetting("DownloadGameFilesDuringInstanceCreation", true); // Minecraft offline player name m_settings->registerSetting("LastOfflinePlayerName", ""); @@ -818,8 +828,6 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_settings->registerSetting("UpdateDialogGeometry", ""); - m_settings->registerSetting("NewsGeometry", ""); - m_settings->registerSetting("ModDownloadGeometry", ""); m_settings->registerSetting("RPDownloadGeometry", ""); m_settings->registerSetting("TPDownloadGeometry", ""); @@ -854,23 +862,25 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) } } { - auto resetIfInvalid = [this](const Setting* setting) { - if (const QUrl url(setting->get().toString()); !url.isValid() || (url.scheme() != "http" && url.scheme() != "https")) { - m_settings->reset(setting->id()); - } - }; - // Meta URL - resetIfInvalid(m_settings->registerSetting("MetaURLOverride", "").get()); + m_settings->registerSetting("MetaURLOverride", ""); + + QUrl metaUrl(m_settings->get("MetaURLOverride").toString()); + + // get rid of invalid meta urls + if (!metaUrl.isValid() || (metaUrl.scheme() != "http" && metaUrl.scheme() != "https")) + m_settings->reset("MetaURLOverride"); // Resource URL - resetIfInvalid(m_settings->registerSetting({ "ResourceURLOverride", "ResourceURL" }, "").get()); + m_settings->registerSetting("ResourceURL", BuildConfig.DEFAULT_RESOURCE_BASE); - // Legacy FML libs URL - resetIfInvalid(m_settings->registerSetting("LegacyFMLLibsURLOverride", "").get()); + 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("MetaRefreshOnLaunch", true); m_settings->registerSetting("CloseAfterLaunch", false); m_settings->registerSetting("QuitAfterGameStop", false); @@ -890,7 +900,6 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_settings->set("FlameKeyOverride", flameKey); m_settings->reset("CFKeyOverride"); } - m_settings->registerSetting("FallbackMRBlockedMods", true); m_settings->registerSetting("ModrinthToken", ""); m_settings->registerSetting("UserAgentOverride", ""); @@ -902,7 +911,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) // Init page provider { - m_globalSettingsProvider = std::make_unique(tr("Settings")); + m_globalSettingsProvider = std::make_shared(tr("Settings")); m_globalSettingsProvider->addPage(); m_globalSettingsProvider->addPage(); m_globalSettingsProvider->addPage(); @@ -935,6 +944,15 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) qInfo() << "<> Network done."; } + // load translations + { + m_translations.reset(new TranslationsModel("translations")); + auto bcp47Name = m_settings->get("Language").toString(); + m_translations->selectLanguage(bcp47Name); + qInfo() << "Your language is" << bcp47Name; + qInfo() << "<> Translations loaded."; + } + // Instance icons { auto setting = APPLICATION->settings()->getSetting("IconsDir"); @@ -974,7 +992,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) if (FS::checkProblemticPathJava(QDir(instDir))) { qWarning() << "Your instance path contains \'!\' and this is known to cause java problems!"; } - m_instances.reset(new InstanceList(m_settings.get(), instDir, this)); + m_instances.reset(new InstanceList(m_settings, instDir, this)); connect(InstDirSetting.get(), &Setting::SettingChanged, m_instances.get(), &InstanceList::on_InstFolderChanged); qInfo() << "Loading Instances..."; m_instances->loadList(); @@ -1008,30 +1026,24 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_metacache->addBase("translations", QDir("translations").absolutePath()); m_metacache->addBase("meta", QDir("meta").absolutePath()); m_metacache->addBase("java", QDir("cache/java").absolutePath()); - m_metacache->addBase("feed", QDir("cache/feed").absolutePath()); m_metacache->Load(); qInfo() << "<> Cache initialized."; } - // load translations - { - m_translations.reset(new TranslationsModel("translations")); - m_translations->downloadIndex(); - qInfo() << "Your language is" << m_translations->selectedLanguage(); - qInfo() << "<> Translations loaded."; - } + // now we have network, download translation updates + m_translations->downloadIndex(); // FIXME: what to do with these? m_profilers.insert("jprofiler", std::shared_ptr(new JProfilerFactory())); m_profilers.insert("jvisualvm", std::shared_ptr(new JVisualVMFactory())); m_profilers.insert("generic", std::shared_ptr(new GenericProfilerFactory())); for (auto profiler : m_profilers.values()) { - profiler->registerSettings(m_settings.get()); + profiler->registerSettings(m_settings); } // Create the MCEdit thing... why is this here? { - m_mcedit.reset(new MCEditTool(m_settings.get())); + m_mcedit.reset(new MCEditTool(m_settings)); } #ifdef Q_OS_MACOS @@ -1352,11 +1364,8 @@ void Application::performMainStartupAction() qDebug() << " Launching with account" << m_profileToUse; } - launch(inst, m_launchOffline ? LaunchMode::Offline : LaunchMode::Normal, targetToJoin, accountToUse, m_offlineName); - - if (!m_showMainWindow) { - return; - } + launch(inst, !m_offline, false, targetToJoin, accountToUse, m_offlineName); + return; } } if (!m_instanceIdToShowWindowOf.isEmpty()) { @@ -1409,6 +1418,16 @@ Application::~Application() { // Shut down logger by setting the logger function to nothing qInstallMessageHandler(nullptr); + +#if defined Q_OS_WIN32 + // Detach from Windows console + if (consoleAttached) { + fclose(stdout); + fclose(stdin); + fclose(stderr); + FreeConsole(); + } +#endif } void Application::messageReceived(const QByteArray& message) @@ -1450,7 +1469,7 @@ void Application::messageReceived(const QByteArray& message) bool offline = received.args["offline_enabled"] == "true"; QString offlineName = received.args["offline_name"]; - BaseInstance* instance; + InstancePtr instance; if (!id.isEmpty()) { instance = instances()->getInstanceById(id); if (!instance) { @@ -1478,23 +1497,23 @@ void Application::messageReceived(const QByteArray& message) } } - launch(instance, offline ? LaunchMode::Offline : LaunchMode::Normal, serverObject, accountObject, offlineName); + launch(instance, !offline, false, serverObject, accountObject, offlineName); } else { qWarning() << "Received invalid message" << message; } } -TranslationsModel* Application::translations() +std::shared_ptr Application::translations() { - return m_translations.get(); + return m_translations; } -JavaInstallList* Application::javalist() +std::shared_ptr Application::javalist() { if (!m_javalist) { m_javalist.reset(new JavaInstallList()); } - return m_javalist.get(); + return m_javalist; } QIcon Application::logo() @@ -1513,8 +1532,9 @@ bool Application::openJsonEditor(const QString& filename) } } -bool Application::launch(BaseInstance* instance, - LaunchMode mode, +bool Application::launch(InstancePtr instance, + bool online, + bool demo, MinecraftTarget::Ptr targetToJoin, MinecraftAccountPtr accountToUse, const QString& offlineName) @@ -1533,7 +1553,8 @@ bool Application::launch(BaseInstance* instance, auto& controller = extras.controller; controller.reset(new LaunchController()); controller->setInstance(instance); - controller->setLaunchMode(mode); + controller->setOnline(online); + controller->setDemo(demo); controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get()); controller->setTargetToJoin(targetToJoin); controller->setAccountToUse(accountToUse); @@ -1543,7 +1564,9 @@ bool Application::launch(BaseInstance* instance, } else if (m_mainWindow) { controller->setParentWidget(m_mainWindow); } - connect(controller.get(), &LaunchController::finished, this, &Application::controllerFinished); + connect(controller.get(), &LaunchController::succeeded, this, &Application::controllerSucceeded); + connect(controller.get(), &LaunchController::failed, this, &Application::controllerFailed); + connect(controller.get(), &LaunchController::aborted, this, [this] { controllerFailed(tr("Aborted")); }); addRunningInstance(); QMetaObject::invokeMethod(controller.get(), &Task::start, Qt::QueuedConnection); return true; @@ -1557,7 +1580,7 @@ bool Application::launch(BaseInstance* instance, return false; } -bool Application::kill(BaseInstance* instance) +bool Application::kill(InstancePtr instance) { if (!instance->isRunning()) { qWarning() << "Attempted to kill instance" << instance->id() << ", which isn't running."; @@ -1566,7 +1589,7 @@ bool Application::kill(BaseInstance* instance) QMutexLocker locker(&m_instanceExtrasMutex); auto& extras = m_instanceExtras[instance->id()]; // NOTE: copy of the shared pointer keeps it alive - auto& controller = extras.controller; + auto controller = extras.controller; locker.unlock(); if (controller) { return controller->abort(); @@ -1615,7 +1638,7 @@ void Application::updateIsRunning(bool running) m_updateRunning = running; } -void Application::controllerFinished() +void Application::controllerSucceeded() { auto controller = qobject_cast(sender()); if (!controller) @@ -1623,11 +1646,10 @@ void Application::controllerFinished() auto id = controller->id(); QMutexLocker locker(&m_instanceExtrasMutex); - auto& extras = m_instanceExtras.at(id); + auto& extras = m_instanceExtras[id]; - const bool wasSuccessful = controller->wasSuccessful(); // on success, do... - if (wasSuccessful && controller->instance()->settings()->get("AutoCloseConsole").toBool()) { + if (controller->instance()->settings()->get("AutoCloseConsole").toBool()) { if (extras.window) { QMetaObject::invokeMethod(extras.window, &QWidget::close, Qt::QueuedConnection); } @@ -1637,8 +1659,29 @@ void Application::controllerFinished() // quit when there are no more windows. if (shouldExitNow()) { - m_status = wasSuccessful ? Succeeded : Failed; - exit(wasSuccessful ? 0 : 1); + m_status = Status::Succeeded; + exit(0); + } +} + +void Application::controllerFailed(const QString& error) +{ + Q_UNUSED(error); + auto controller = qobject_cast(sender()); + if (!controller) + return; + auto id = controller->id(); + QMutexLocker locker(&m_instanceExtrasMutex); + auto& extras = m_instanceExtras[id]; + + // on failure, do... nothing + extras.controller.reset(); + subRunningInstance(); + + // quit when there are no more windows. + if (shouldExitNow()) { + m_status = Status::Failed; + exit(1); } } @@ -1695,7 +1738,7 @@ ViewLogWindow* Application::showLogWindow() return m_viewLogWindow; } -InstanceWindow* Application::showInstanceWindow(BaseInstance* instance, QString page) +InstanceWindow* Application::showInstanceWindow(InstancePtr instance, QString page) { if (!instance) return nullptr; @@ -1807,22 +1850,22 @@ void Application::updateProxySettings(QString proxyTypeStr, QString addr, int po qDebug() << proxyDesc; } -HttpMetaCache* Application::metacache() +shared_qobject_ptr Application::metacache() { - return m_metacache.get(); + return m_metacache; } -QNetworkAccessManager* Application::network() +shared_qobject_ptr Application::network() { - return m_network.get(); + return m_network; } -Meta::Index* Application::metadataIndex() +shared_qobject_ptr Application::metadataIndex() { if (!m_metadataIndex) { m_metadataIndex.reset(new Meta::Index()); } - return m_metadataIndex.get(); + return m_metadataIndex; } void Application::updateCapabilities() @@ -1837,7 +1880,7 @@ void Application::updateCapabilities() if (gamemode_query_status() >= 0) m_capabilities |= SupportsGameMode; - if (!LibraryUtils::findMangoHud().isEmpty()) + if (!MangoHud::getLibraryString().isEmpty()) m_capabilities |= SupportsMangoHud; #endif } @@ -1845,8 +1888,8 @@ void Application::updateCapabilities() void Application::detectLibraries() { #ifdef Q_OS_LINUX - m_detectedGLFWPath = LibraryUtils::find(BuildConfig.GLFW_LIBRARY_NAME); - m_detectedOpenALPath = LibraryUtils::find(BuildConfig.OPENAL_LIBRARY_NAME); + m_detectedGLFWPath = MangoHud::findLibrary(BuildConfig.GLFW_LIBRARY_NAME); + m_detectedOpenALPath = MangoHud::findLibrary(BuildConfig.OPENAL_LIBRARY_NAME); qDebug() << "Detected native libraries:" << m_detectedGLFWPath << m_detectedOpenALPath; #endif } @@ -1953,7 +1996,7 @@ bool Application::handleDataMigration(const QString& currentData, auto setDoNotMigrate = [&nomigratePath] { QFile file(nomigratePath); if (!file.open(QIODevice::WriteOnly)) { - qWarning() << "setDoNotMigrate failed; Failed to open file" << file.fileName() << "for writing:" << file.errorString(); + qWarning() << "setDoNotMigrate failed; Failed to open file '" << file.fileName() << "' for writing!"; } }; @@ -2007,7 +2050,7 @@ void Application::triggerUpdateCheck() } } -QUrl Application::normalizeImportUrl(const QString& url) +QUrl Application::normalizeImportUrl(QString const& url) { auto local_file = QFileInfo(url); if (local_file.exists()) { diff --git a/launcher/Application.h b/launcher/Application.h index 936e13d71..0fd733b50 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -37,8 +37,6 @@ #pragma once -#include - #include #include #include @@ -46,10 +44,12 @@ #include #include #include +#include -#include "QObjectPtr.h" +#include -#include "minecraft/auth/MinecraftAccount.h" +#include "launch/LogModel.h" +#include "minecraft/launch/MinecraftTarget.h" class LaunchController; class LocalPeer; @@ -74,12 +74,6 @@ class ITheme; class MCEditTool; class ThemeManager; class IconTheme; -class BaseInstance; - -class LogModel; - -struct MinecraftTarget; -class MinecraftAccount; namespace Meta { class Index; @@ -97,6 +91,7 @@ class Index; #define APPLICATION_DYN (dynamic_cast(QCoreApplication::instance())) class Application : public QApplication { + // friends for the purpose of limiting access to deprecated stuff Q_OBJECT public: enum Status { StartingUp, Failed, Succeeded, Initialized }; @@ -117,7 +112,7 @@ class Application : public QApplication { bool event(QEvent* event) override; - SettingsObject* settings() const { return m_settings.get(); } + std::shared_ptr settings() const { return m_settings; } qint64 timeSinceStart() const { return m_startTime.msecsTo(QDateTime::currentDateTime()); } @@ -125,21 +120,21 @@ class Application : public QApplication { ThemeManager* themeManager() { return m_themeManager.get(); } - ExternalUpdater* updater() { return m_updater.get(); } + shared_qobject_ptr updater() { return m_updater; } void triggerUpdateCheck(); - TranslationsModel* translations(); + std::shared_ptr translations(); - JavaInstallList* javalist(); + std::shared_ptr javalist(); - InstanceList* instances() const { return m_instances.get(); } + std::shared_ptr instances() const { return m_instances; } - IconList* icons() const { return m_icons.get(); } + std::shared_ptr icons() const { return m_icons; } MCEditTool* mcedit() const { return m_mcedit.get(); } - AccountList* accounts() const { return m_accounts.get(); } + shared_qobject_ptr accounts() const { return m_accounts; } Status status() const { return m_status; } @@ -147,11 +142,11 @@ class Application : public QApplication { void updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password); - QNetworkAccessManager* network(); + shared_qobject_ptr network(); - HttpMetaCache* metacache(); + shared_qobject_ptr metacache(); - Meta::Index* metadataIndex(); + shared_qobject_ptr metadataIndex(); void updateCapabilities(); @@ -187,7 +182,7 @@ class Application : public QApplication { */ bool openJsonEditor(const QString& filename); - InstanceWindow* showInstanceWindow(BaseInstance* instance, QString page = QString()); + InstanceWindow* showInstanceWindow(InstancePtr instance, QString page = QString()); MainWindow* showMainWindow(bool minimized = false); ViewLogWindow* showLogWindow(); @@ -199,7 +194,7 @@ class Application : public QApplication { bool updaterEnabled(); QString updaterBinaryName(); - QUrl normalizeImportUrl(const QString& url); + QUrl normalizeImportUrl(QString const& url); signals: void updateAllowedChanged(bool status); @@ -214,18 +209,20 @@ class Application : public QApplication { #endif public slots: - bool launch(BaseInstance* instance, - LaunchMode mode = LaunchMode::Normal, - std::shared_ptr targetToJoin = nullptr, - shared_qobject_ptr accountToUse = nullptr, + bool launch(InstancePtr instance, + bool online = true, + bool demo = false, + MinecraftTarget::Ptr targetToJoin = nullptr, + MinecraftAccountPtr accountToUse = nullptr, const QString& offlineName = QString()); - bool kill(BaseInstance* instance); + bool kill(InstancePtr instance); void closeCurrentWindow(); private slots: void on_windowClose(); void messageReceived(const QByteArray& message); - void controllerFinished(); + void controllerSucceeded(); + void controllerFailed(const QString& error); void setupWizardFinished(int status); private: @@ -241,27 +238,23 @@ class Application : public QApplication { void subRunningInstance(); bool shouldExitNow() const; - private: - QHash m_qsaveResources; - mutable QMutex m_qsaveResourcesMutex; - private: QDateTime m_startTime; - std::unique_ptr m_network; + shared_qobject_ptr m_network; - std::unique_ptr m_updater; - std::unique_ptr m_accounts; + shared_qobject_ptr m_updater; + shared_qobject_ptr m_accounts; - std::unique_ptr m_metacache; - std::unique_ptr m_metadataIndex; + shared_qobject_ptr m_metacache; + shared_qobject_ptr m_metadataIndex; - std::unique_ptr m_settings; - std::unique_ptr m_instances; - std::unique_ptr m_icons; - std::unique_ptr m_javalist; - std::unique_ptr m_translations; - std::unique_ptr m_globalSettingsProvider; + std::shared_ptr m_settings; + std::shared_ptr m_instances; + std::shared_ptr m_icons; + std::shared_ptr m_javalist; + std::shared_ptr m_translations; + std::shared_ptr m_globalSettingsProvider; std::unique_ptr m_mcedit; QSet m_features; std::unique_ptr m_themeManager; @@ -278,10 +271,15 @@ class Application : public QApplication { Qt::ApplicationState m_prevAppState = Qt::ApplicationInactive; #endif +#if defined Q_OS_WIN32 + // used on Windows to attach the standard IO streams + bool consoleAttached = false; +#endif + // FIXME: attach to instances instead. struct InstanceXtras { InstanceWindow* window = nullptr; - std::unique_ptr controller; + shared_qobject_ptr controller; }; std::map m_instanceExtras; mutable QMutex m_instanceExtrasMutex; @@ -309,17 +307,20 @@ class Application : public QApplication { QString m_serverToJoin; QString m_worldToJoin; QString m_profileToUse; - bool m_launchOffline = false; + bool m_offline = false; QString m_offlineName; bool m_liveCheck = false; QList m_urlsToImport; QString m_instanceIdToShowWindowOf; - bool m_showMainWindow = false; std::unique_ptr logFile; - std::unique_ptr logModel; + shared_qobject_ptr logModel; public: void addQSavePath(QString); void removeQSavePath(QString); bool checkQSavePath(QString); + + private: + QHash m_qsaveResources; + mutable QMutex m_qsaveResourcesMutex; }; diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 0080cc516..fdbcc11fe 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -45,7 +45,6 @@ #include "Application.h" #include "Json.h" -#include "launch/LaunchTask.h" #include "settings/INISettingsObject.h" #include "settings/OverrideSetting.h" #include "settings/Setting.h" @@ -54,7 +53,7 @@ #include "Commandline.h" #include "FileSystem.h" -int getConsoleMaxLines(SettingsObject* settings) +int getConsoleMaxLines(SettingsObjectPtr settings) { auto lineSetting = settings->getSetting("ConsoleMaxLines"); bool conversionOk = false; @@ -66,14 +65,14 @@ int getConsoleMaxLines(SettingsObject* settings) return maxLines; } -bool shouldStopOnConsoleOverflow(SettingsObject* settings) +bool shouldStopOnConsoleOverflow(SettingsObjectPtr settings) { return settings->get("ConsoleOverflowStop").toBool(); } -BaseInstance::BaseInstance(SettingsObject* globalSettings, std::unique_ptr settings, const QString& rootDir) : QObject() +BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir) : QObject() { - m_settings = std::move(settings); + m_settings = settings; m_global_settings = globalSettings; m_rootDir = rootDir; @@ -123,13 +122,10 @@ BaseInstance::BaseInstance(SettingsObject* globalSettings, std::unique_ptrregisterSetting("ManagedPackName", ""); m_settings->registerSetting("ManagedPackVersionID", ""); m_settings->registerSetting("ManagedPackVersionName", ""); - m_settings->registerSetting("ManagedPackURL", ""); m_settings->registerSetting("Profiler", ""); } -BaseInstance::~BaseInstance() {} - QString BaseInstance::getPreLaunchCommand() { return settings()->get("PreLaunchCommand").toString(); @@ -339,11 +335,11 @@ QString BaseInstance::instanceRoot() const return m_rootDir; } -SettingsObject* BaseInstance::settings() +SettingsObjectPtr BaseInstance::settings() { loadSpecificSettings(); - return m_settings.get(); + return m_settings; } bool BaseInstance::canLaunch() const @@ -471,9 +467,9 @@ QStringList BaseInstance::extraArguments() return Commandline::splitArgs(settings()->get("JvmArgs").toString()); } -LaunchTask* BaseInstance::getLaunchTask() +shared_qobject_ptr BaseInstance::getLaunchTask() { - return m_launchProcess.get(); + return m_launchProcess; } void BaseInstance::updateRuntimeContext() diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 9280d2e1c..a542b76eb 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -64,6 +64,9 @@ class Task; class LaunchTask; class BaseInstance; +// pointer for lazy people +using InstancePtr = std::shared_ptr; + /// Shortcut saving target representations enum class ShortcutTarget { Desktop, Applications, Other }; @@ -75,8 +78,8 @@ struct ShortcutData { }; /// Console settings -int getConsoleMaxLines(SettingsObject* settings); -bool shouldStopOnConsoleOverflow(SettingsObject* settings); +int getConsoleMaxLines(SettingsObjectPtr settings); +bool shouldStopOnConsoleOverflow(SettingsObjectPtr settings); /*! * \brief Base class for instances. @@ -86,11 +89,11 @@ bool shouldStopOnConsoleOverflow(SettingsObject* settings); * To create a new instance type, create a new class inheriting from this class * and implement the pure virtual functions. */ -class BaseInstance : public QObject { +class BaseInstance : public QObject, public std::enable_shared_from_this { Q_OBJECT protected: /// no-touchy! - BaseInstance(SettingsObject* globalSettings, std::unique_ptr settings, const QString& rootDir); + BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir); public: /* types */ enum class Status { @@ -100,7 +103,7 @@ class BaseInstance : public QObject { public: /// virtual destructor to make sure the destruction is COMPLETE - virtual ~BaseInstance(); + virtual ~BaseInstance() {} virtual void saveNow() = 0; @@ -190,7 +193,7 @@ class BaseInstance : public QObject { * * \return A pointer to this instance's settings object. */ - virtual SettingsObject* settings(); + virtual SettingsObjectPtr settings(); /*! * \brief Loads settings specific to an instance type if they're not already loaded. @@ -201,10 +204,10 @@ class BaseInstance : public QObject { virtual QList createUpdateTask() = 0; /// returns a valid launcher (task container) - virtual LaunchTask* createLaunchTask(AuthSessionPtr account, MinecraftTarget::Ptr targetToJoin) = 0; + virtual shared_qobject_ptr createLaunchTask(AuthSessionPtr account, MinecraftTarget::Ptr targetToJoin) = 0; /// returns the current launch task (if any) - LaunchTask* getLaunchTask(); + shared_qobject_ptr getLaunchTask(); /*! * Create envrironment variables for running the instance @@ -283,7 +286,7 @@ class BaseInstance : public QObject { protected: void changeStatus(Status newStatus); - SettingsObject* globalSettings() const { return m_global_settings; } + SettingsObjectPtr globalSettings() const { return m_global_settings.lock(); } bool isSpecificSettingsLoaded() const { return m_specific_settings_loaded; } void setSpecificSettingsLoaded(bool loaded) { m_specific_settings_loaded = loaded; } @@ -294,7 +297,7 @@ class BaseInstance : public QObject { */ void propertiesChanged(BaseInstance* inst); - void launchTaskChanged(LaunchTask*); + void launchTaskChanged(shared_qobject_ptr); void runningStatusChanged(bool running); @@ -307,10 +310,10 @@ class BaseInstance : public QObject { protected: /* data */ QString m_rootDir; - std::unique_ptr m_settings; + SettingsObjectPtr m_settings; // InstanceFlags m_flags; bool m_isRunning = false; - std::unique_ptr m_launchProcess; + shared_qobject_ptr m_launchProcess; QDateTime m_timeStarted; RuntimeContext m_runtimeContext; @@ -320,7 +323,7 @@ class BaseInstance : public QObject { bool m_hasUpdate = false; bool m_hasBrokenVersion = false; - SettingsObject* m_global_settings; + SettingsObjectWeakPtr m_global_settings; bool m_specific_settings_loaded = false; }; diff --git a/launcher/BaseVersion.h b/launcher/BaseVersion.h index e442c60df..02a7212e5 100644 --- a/launcher/BaseVersion.h +++ b/launcher/BaseVersion.h @@ -24,7 +24,6 @@ */ class BaseVersion { public: - // TODO: delete using Ptr = std::shared_ptr; virtual ~BaseVersion() {} /*! diff --git a/launcher/BaseVersionList.h b/launcher/BaseVersionList.h index ba546e955..673d13562 100644 --- a/launcher/BaseVersionList.h +++ b/launcher/BaseVersionList.h @@ -63,7 +63,7 @@ class BaseVersionList : public QAbstractListModel { * The task returned by this function should reset the model when it's done. * \return A pointer to a task that reloads the version list. */ - virtual Task::Ptr getLoadTask(bool forceReload = false) = 0; + virtual Task::Ptr getLoadTask() = 0; //! Checks whether or not the list is loaded. If this returns false, the list should be // loaded. diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 7d4430fd2..de647455a 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -75,6 +75,9 @@ set(CORE_SOURCES # RW lock protected map RWStorage.h + # A variable that has an implicit default value and keeps track of changes + DefaultVariable.h + # a smart pointer wrapper intended for safer use with Qt signal/slot mechanisms QObjectPtr.h @@ -107,9 +110,9 @@ if (UNIX AND NOT CYGWIN AND NOT APPLE) set(CORE_SOURCES ${CORE_SOURCES} - # LibraryUtils - LibraryUtils.h - LibraryUtils.cpp + # MangoHud + MangoHud.h + MangoHud.cpp ) endif() @@ -119,7 +122,6 @@ set(NET_SOURCES net/ChecksumValidator.h net/Download.cpp net/Download.h - net/DummySink.h net/FileSink.cpp net/FileSink.h net/HttpMetaCache.cpp @@ -244,13 +246,15 @@ set(MINECRAFT_SOURCES minecraft/auth/steps/MSAStep.h minecraft/auth/steps/XboxAuthorizationStep.cpp minecraft/auth/steps/XboxAuthorizationStep.h + minecraft/auth/steps/XboxProfileStep.cpp + minecraft/auth/steps/XboxProfileStep.h minecraft/auth/steps/XboxUserStep.cpp minecraft/auth/steps/XboxUserStep.h minecraft/update/AssetUpdateTask.h minecraft/update/AssetUpdateTask.cpp - minecraft/update/LegacyFMLLibrariesTask.cpp - minecraft/update/LegacyFMLLibrariesTask.h + minecraft/update/FMLLibrariesTask.cpp + minecraft/update/FMLLibrariesTask.h minecraft/update/FoldersTask.cpp minecraft/update/FoldersTask.h minecraft/update/LibrariesTask.cpp @@ -260,8 +264,6 @@ set(MINECRAFT_SOURCES minecraft/launch/ClaimAccount.h minecraft/launch/CreateGameFolders.cpp minecraft/launch/CreateGameFolders.h - minecraft/launch/EnsureAvailableMemory.cpp - minecraft/launch/EnsureAvailableMemory.h minecraft/launch/EnsureOfflineLibraries.cpp minecraft/launch/EnsureOfflineLibraries.h minecraft/launch/ModMinecraftJar.cpp @@ -529,11 +531,6 @@ set(FTB_SOURCES modplatform/import_ftb/PackInstallTask.cpp modplatform/import_ftb/PackHelpers.h modplatform/import_ftb/PackHelpers.cpp - - modplatform/ftb/FTBPackInstallTask.h - modplatform/ftb/FTBPackInstallTask.cpp - modplatform/ftb/FTBPackManifest.h - modplatform/ftb/FTBPackManifest.cpp ) set(FLAME_SOURCES @@ -799,8 +796,6 @@ SET(LAUNCHER_SOURCES ApplicationMessage.cpp SysInfo.h SysInfo.cpp - HardwareInfo.cpp - HardwareInfo.h # console utils console/Console.h @@ -817,6 +812,23 @@ SET(LAUNCHER_SOURCES KonamiCode.h KonamiCode.cpp + # Bundled resources + resources/backgrounds/backgrounds.qrc + resources/multimc/multimc.qrc + resources/pe_dark/pe_dark.qrc + resources/pe_light/pe_light.qrc + resources/pe_colored/pe_colored.qrc + resources/pe_blue/pe_blue.qrc + resources/breeze_dark/breeze_dark.qrc + resources/breeze_light/breeze_light.qrc + resources/OSX/OSX.qrc + resources/iOS/iOS.qrc + resources/flat/flat.qrc + resources/flat_white/flat_white.qrc + resources/documents/documents.qrc + resources/shaders/shaders.qrc + "${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_LogoQRC}" + # Icons icons/MMCIcon.h icons/MMCIcon.cpp @@ -886,7 +898,6 @@ SET(LAUNCHER_SOURCES ui/themes/CatPainter.h # Processes - LaunchMode.h LaunchController.h LaunchController.cpp @@ -997,13 +1008,6 @@ SET(LAUNCHER_SOURCES ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h - ui/pages/modplatform/ftb/FtbFilterModel.cpp - ui/pages/modplatform/ftb/FtbFilterModel.h - ui/pages/modplatform/ftb/FtbListModel.cpp - ui/pages/modplatform/ftb/FtbListModel.h - ui/pages/modplatform/ftb/FtbPage.cpp - ui/pages/modplatform/ftb/FtbPage.h - ui/pages/modplatform/legacy_ftb/Page.cpp ui/pages/modplatform/legacy_ftb/Page.h ui/pages/modplatform/legacy_ftb/ListModel.h @@ -1067,8 +1071,6 @@ SET(LAUNCHER_SOURCES ui/dialogs/ImportResourceDialog.h ui/dialogs/MSALoginDialog.cpp ui/dialogs/MSALoginDialog.h - ui/dialogs/NetworkJobFailedDialog.cpp - ui/dialogs/NetworkJobFailedDialog.h ui/dialogs/NewComponentDialog.cpp ui/dialogs/NewComponentDialog.h ui/dialogs/NewInstanceDialog.cpp @@ -1206,6 +1208,76 @@ if(WIN32) ) endif() +qt_wrap_ui(LAUNCHER_UI + ui/MainWindow.ui + ui/setupwizard/PasteWizardPage.ui + ui/setupwizard/AutoJavaWizardPage.ui + ui/setupwizard/LoginWizardPage.ui + ui/pages/global/AccountListPage.ui + ui/pages/global/JavaPage.ui + ui/pages/global/LauncherPage.ui + ui/pages/global/APIPage.ui + ui/pages/global/ProxyPage.ui + ui/pages/global/ExternalToolsPage.ui + ui/pages/instance/ExternalResourcesPage.ui + ui/pages/instance/NotesPage.ui + ui/pages/instance/LogPage.ui + ui/pages/instance/ServersPage.ui + ui/pages/instance/OtherLogsPage.ui + ui/pages/instance/VersionPage.ui + ui/pages/instance/ManagedPackPage.ui + ui/pages/instance/WorldListPage.ui + ui/pages/instance/ScreenshotsPage.ui + ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui + ui/pages/modplatform/atlauncher/AtlPage.ui + ui/pages/modplatform/CustomPage.ui + ui/pages/modplatform/ResourcePage.ui + ui/pages/modplatform/flame/FlamePage.ui + ui/pages/modplatform/legacy_ftb/Page.ui + ui/pages/modplatform/import_ftb/ImportFTBPage.ui + ui/pages/modplatform/ImportPage.ui + ui/pages/modplatform/OptionalModDialog.ui + ui/pages/modplatform/modrinth/ModrinthPage.ui + ui/pages/modplatform/technic/TechnicPage.ui + ui/widgets/CustomCommands.ui + ui/widgets/EnvironmentVariables.ui + ui/widgets/InfoFrame.ui + ui/widgets/ModFilterWidget.ui + ui/widgets/SubTaskProgressBar.ui + ui/widgets/AppearanceWidget.ui + ui/widgets/MinecraftSettingsWidget.ui + ui/widgets/JavaSettingsWidget.ui + ui/dialogs/CopyInstanceDialog.ui + ui/dialogs/CreateShortcutDialog.ui + ui/dialogs/ProfileSetupDialog.ui + ui/dialogs/ProgressDialog.ui + ui/dialogs/NewInstanceDialog.ui + ui/dialogs/NewComponentDialog.ui + ui/dialogs/NewsDialog.ui + ui/dialogs/ProfileSelectDialog.ui + ui/dialogs/ExportInstanceDialog.ui + ui/dialogs/ExportPackDialog.ui + ui/dialogs/ExportToModListDialog.ui + ui/dialogs/IconPickerDialog.ui + ui/dialogs/ImportResourceDialog.ui + ui/dialogs/MSALoginDialog.ui + ui/dialogs/AboutDialog.ui + ui/dialogs/ReviewMessageBox.ui + ui/dialogs/ScrollMessageBox.ui + ui/dialogs/BlockedModsDialog.ui + ui/dialogs/ChooseProviderDialog.ui + ui/dialogs/skins/SkinManageDialog.ui + ui/dialogs/ChooseOfflineNameDialog.ui +) + +qt_wrap_ui(PRISM_UPDATE_UI + ui/dialogs/UpdateAvailableDialog.ui +) + +if (NOT Apple) + set (LAUNCHER_UI ${LAUNCHER_UI} ${PRISM_UPDATE_UI}) +endif() + qt_add_resources(LAUNCHER_RESOURCES resources/backgrounds/backgrounds.qrc resources/multimc/multimc.qrc @@ -1218,17 +1290,24 @@ qt_add_resources(LAUNCHER_RESOURCES resources/OSX/OSX.qrc resources/iOS/iOS.qrc resources/flat/flat.qrc - resources/flat_white/flat_white.qrc resources/documents/documents.qrc resources/shaders/shaders.qrc "${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_LogoQRC}" ) +qt_wrap_ui(PRISMUPDATER_UI + updater/prismupdater/SelectReleaseDialog.ui + ui/widgets/SubTaskProgressBar.ui + ui/dialogs/ProgressDialog.ui +) + ######## Windows resource files ######## if(WIN32) set(LAUNCHER_RCS ${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_WindowsRC}) endif() +include(CompilerWarnings) + ######## Precompiled Headers ########### if(${Launcher_USE_PCH}) @@ -1243,7 +1322,11 @@ endif() ####### Targets ######## # Add executable -add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_RESOURCES}) +add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES}) +set_project_warnings(Launcher_logic + "${Launcher_MSVC_WARNINGS}" + "${Launcher_CLANG_WARNINGS}" + "${Launcher_GCC_WARNINGS}") target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION) @@ -1252,6 +1335,7 @@ if(${Launcher_USE_PCH}) endif() target_link_libraries(Launcher_logic + systeminfo Launcher_murmur2 nbt++ ${ZLIB_LIBRARIES} @@ -1328,6 +1412,8 @@ if(APPLE) endif() endif() +target_link_libraries(Launcher_logic) + add_executable(${Launcher_Name} MACOSX_BUNDLE WIN32 main.cpp ${LAUNCHER_RCS}) if(${Launcher_USE_PCH}) @@ -1363,7 +1449,7 @@ endif() if(Launcher_BUILD_UPDATER) # Updater - add_library(prism_updater_logic STATIC ${PRISMUPDATER_SOURCES} ${TASKS_SOURCES}) + add_library(prism_updater_logic STATIC ${PRISMUPDATER_SOURCES} ${TASKS_SOURCES} ${PRISMUPDATER_UI}) target_include_directories(prism_updater_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) if(${Launcher_USE_PCH}) @@ -1372,6 +1458,7 @@ if(Launcher_BUILD_UPDATER) target_link_libraries(prism_updater_logic ${ZLIB_LIBRARIES} + systeminfo BuildConfig Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Core @@ -1416,6 +1503,10 @@ endif() if(WIN32 OR (DEFINED Launcher_BUILD_FILELINKER AND Launcher_BUILD_FILELINKER)) # File link add_library(filelink_logic STATIC ${LINKEXE_SOURCES}) + set_project_warnings(filelink_logic + "${Launcher_MSVC_WARNINGS}" + "${Launcher_CLANG_WARNINGS}" + "${Launcher_GCC_WARNINGS}") target_include_directories(filelink_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) @@ -1424,6 +1515,7 @@ if(WIN32 OR (DEFINED Launcher_BUILD_FILELINKER AND Launcher_BUILD_FILELINKER)) endif() target_link_libraries(filelink_logic + systeminfo BuildConfig Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Core @@ -1475,28 +1567,6 @@ if (UNIX AND APPLE AND Launcher_ENABLE_UPDATER) install(DIRECTORY ${MACOSX_SPARKLE_DIR}/Sparkle.framework DESTINATION ${FRAMEWORK_DEST_DIR} USE_SOURCE_PERMISSIONS) endif() -# Set basic compiler warning/error flags for all targets -get_property(Launcher_TARGETS DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY BUILDSYSTEM_TARGETS) -foreach(target ${Launcher_TARGETS}) - message(STATUS "Enabling all warnings as errors for target '${target}'") - if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") - target_compile_options(${target} PRIVATE /W4 /WX /permissive-) - else() - target_compile_options(${target} PRIVATE -Wall -Wextra -Wpedantic -Werror) - endif() -endforeach() - -# Disable some warnings in main launcher target due to being present in a lot of places. TODO: Fix them. -if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") - target_compile_options(Launcher_logic PRIVATE /wd4100) # C4100 - unused parameter - target_compile_options(${Launcher_Name} PRIVATE /wd4100) # C4100 - unused parameter -else() - # sfinae-incomplete is a new GCC warning and triggers in Qt headers - # no-unknown-warning-option so that compilers that don't have sfinae-incomplete don't error - target_compile_options(Launcher_logic PRIVATE -Wno-unused-parameter -Wno-missing-field-initializers -Wno-unknown-warning-option -Wno-sfinae-incomplete) - target_compile_options(${Launcher_Name} PRIVATE -Wno-unused-parameter -Wno-missing-field-initializers -Wno-unknown-warning-option -Wno-sfinae-incomplete) -endif() - #### The bundle mess! #### # 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. diff --git a/launcher/DataMigrationTask.cpp b/launcher/DataMigrationTask.cpp index cab22089e..9677f868e 100644 --- a/launcher/DataMigrationTask.cpp +++ b/launcher/DataMigrationTask.cpp @@ -63,7 +63,7 @@ void DataMigrationTask::dryRunFinished() void DataMigrationTask::dryRunAborted() { - emitAborted(); + emitFailed(tr("Aborted")); } void DataMigrationTask::copyFinished() @@ -81,5 +81,5 @@ void DataMigrationTask::copyFinished() void DataMigrationTask::copyAborted() { - emitAborted(); + emitFailed(tr("Aborted")); } diff --git a/launcher/DefaultVariable.h b/launcher/DefaultVariable.h new file mode 100644 index 000000000..b082091c7 --- /dev/null +++ b/launcher/DefaultVariable.h @@ -0,0 +1,23 @@ +#pragma once + +template +class DefaultVariable { + public: + DefaultVariable(const T& value) { defaultValue = value; } + DefaultVariable& operator=(const T& value) + { + currentValue = value; + is_default = currentValue == defaultValue; + is_explicit = true; + return *this; + } + operator const T&() const { return is_default ? defaultValue : currentValue; } + bool isDefault() const { return is_default; } + bool isExplicit() const { return is_explicit; } + + private: + T currentValue; + T defaultValue; + bool is_default = true; + bool is_explicit = false; +}; diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index ef56e3e65..31f630b55 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -36,7 +36,6 @@ */ #include "FileSystem.h" -#include #include #include "BuildConfig.h" @@ -283,9 +282,6 @@ bool copyFileAttributes(QString src, QString dst) if (attrs == INVALID_FILE_ATTRIBUTES) return false; return SetFileAttributesW(dst.toStdWString().c_str(), attrs); -#else - Q_UNUSED(src); - Q_UNUSED(dst); #endif return true; } @@ -596,7 +592,7 @@ void create_link::runPrivileged(const QString& offset) } ExternalLinkFileProcess* linkFileProcess = new ExternalLinkFileProcess(serverName, m_useHardLinks, this); - connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [this, &gotResults]() { emit finishedPrivileged(gotResults); }); + connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [this, gotResults]() { emit finishedPrivileged(gotResults); }); connect(linkFileProcess, &ExternalLinkFileProcess::finished, linkFileProcess, &QObject::deleteLater); linkFileProcess->start(); @@ -684,32 +680,6 @@ bool deletePath(QString path) return err.value() == 0; } -bool deleteContents(const QString& path) -{ - const QFileInfo info(path); - if (!info.exists()) { - return true; - } - if (!info.isDir()) { - qWarning() << "Attempted to delete contents of non-directory path:" << path; - return false; - } - - bool ret = true; - - for (const auto& entry : fs::directory_iterator(StringUtils::toStdString(path))) { - std::error_code err; - - fs::remove_all(entry.path(), err); - if (err.value() != 0) { - qWarning().nospace() << "Could not delete directory entry " << entry.path() << ": " << QString::fromStdString(err.message()); - ret = false; - } - } - - return ret; -} - bool trash(QString path, QString* pathInTrash) { // FIXME: Figure out trash in Flatpak. Qt seemingly doesn't use the Trash portal @@ -823,33 +793,68 @@ QString NormalizePath(QString path) } } -namespace { -const QString g_badChars = "<>:\"|?*\r\n!"; -QString removeChars(QString source, QChar replace, const QString& extraChars = "") -{ - auto badChars = g_badChars; - if (!extraChars.isEmpty()) { - badChars += extraChars; - } +static const QString BAD_WIN_CHARS = "<>:\"|?*\r\n"; +static const QString BAD_NTFS_CHARS = "<>:\"|?*"; +static const QString BAD_HFS_CHARS = ":"; - for (auto& c : source) { - if (c.unicode() < 0x20 || !c.isPrint() || badChars.contains(c)) { - c = replace; - } - } - - return source; -} -} // namespace +static const QString BAD_FILENAME_CHARS = BAD_WIN_CHARS + "\\/"; QString RemoveInvalidFilenameChars(QString string, QChar replaceWith) { - return removeChars(std::move(string), replaceWith, "\\/"); + for (int i = 0; i < string.length(); i++) + if (string.at(i) < ' ' || BAD_FILENAME_CHARS.contains(string.at(i))) + string[i] = replaceWith; + return string; } -QString RemoveInvalidPathChars(QString string, QChar replaceWith) +QString RemoveInvalidPathChars(QString path, QChar replaceWith) { - return removeChars(std::move(string), replaceWith); + QString invalidChars; +#ifdef Q_OS_WIN + invalidChars = BAD_WIN_CHARS; +#endif + + // the null character is ignored in this check as it was not a problem until now + switch (statFS(path).fsType) { + case FilesystemType::FAT: // similar to NTFS + /* fallthrough */ + case FilesystemType::NTFS: + /* fallthrough */ + case FilesystemType::REFS: // similar to NTFS(should be available only on windows) + invalidChars += BAD_NTFS_CHARS; + break; + // case FilesystemType::EXT: + // case FilesystemType::EXT_2_OLD: + // case FilesystemType::EXT_2_3_4: + // case FilesystemType::XFS: + // case FilesystemType::BTRFS: + // case FilesystemType::NFS: + // case FilesystemType::ZFS: + case FilesystemType::APFS: + /* fallthrough */ + case FilesystemType::HFS: + /* fallthrough */ + case FilesystemType::HFSPLUS: + /* fallthrough */ + case FilesystemType::HFSX: + invalidChars += BAD_HFS_CHARS; + break; + // case FilesystemType::FUSEBLK: + // case FilesystemType::F2FS: + // case FilesystemType::UNKNOWN: + default: + break; + } + + if (invalidChars.size() != 0) { + for (int i = 0; i < path.length(); i++) { + if (path.at(i) < ' ' || invalidChars.contains(path.at(i))) { + path[i] = replaceWith; + } + } + } + + return path; } QString DirNameFromString(QString string, QString inDir) @@ -945,10 +950,7 @@ QString createShortcut(QString destination, QString target, QStringList args, QS qWarning() << "Couldn't create directories within application"; return QString(); } - if (!info.open(QIODevice::WriteOnly | QIODevice::Text)) { - qWarning() << "Failed to open file" << info.fileName() << "for writing:" << info.errorString(); - return QString(); - } + info.open(QIODevice::WriteOnly | QIODevice::Text); QFile(icon).rename(resources.path() + "/Icon.icns"); @@ -956,10 +958,7 @@ QString createShortcut(QString destination, QString target, QStringList args, QS QString exec = binaryDir.path() + "/Run.command"; QFile f(exec); - if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { - qWarning() << "Failed to open file" << f.fileName() << "for writing:" << f.errorString(); - return QString(); - } + f.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream stream(&f); auto argstring = quoteArgs(args, "\"", "\\\""); @@ -1002,7 +1001,7 @@ QString createShortcut(QString destination, QString target, QStringList args, QS destination += ".desktop"; QFile f(destination); if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { - qWarning() << "Failed to open file" << f.fileName() << "for writing:" << f.errorString(); + qWarning() << "Failed to open file '" << f.fileName() << "' for writing!"; return QString(); } QTextStream stream(&f); diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 6d9b01178..f2676b147 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -291,13 +291,6 @@ bool move(const QString& source, const QString& dest); */ bool deletePath(QString path); -/** - * Delete a folder's contents recursively but not the folder itself. - * @param path The path to the folder. - * @return Whether the deletion was completely successful. - */ -bool deleteContents(const QString& path); - bool removeFiles(QStringList listFile); /** diff --git a/launcher/GZip.cpp b/launcher/GZip.cpp index 201dcd572..dc786e10e 100644 --- a/launcher/GZip.cpp +++ b/launcher/GZip.cpp @@ -172,8 +172,7 @@ int inf(QFile* source, std::function handleBlock) assert(ret != Z_STREAM_ERROR); /* state not clobbered */ switch (ret) { case Z_NEED_DICT: - ret = Z_DATA_ERROR; - [[fallthrough]]; + ret = Z_DATA_ERROR; /* and fall through */ case Z_DATA_ERROR: case Z_MEM_ERROR: (void)inflateEnd(&strm); diff --git a/launcher/HardwareInfo.cpp b/launcher/HardwareInfo.cpp deleted file mode 100644 index 36b6f7783..000000000 --- a/launcher/HardwareInfo.cpp +++ /dev/null @@ -1,355 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2026 Octol1ttle - * - * 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 "HardwareInfo.h" - -#include -#include - -#if defined(Q_OS_MACOS) || defined(Q_OS_LINUX) -namespace { -QString afterColon(QString str) -{ - return str.remove(0, str.indexOf(':') + 2).trimmed(); -} - -template -bool readFromOutput(const char* command, F function) -{ - FILE* file = popen(command, "r"); // NOLINT(*-command-processor) - if (!file) { - qWarning().nospace() << "Could not execute command '" << command << "': " << strerror(errno); - return false; - } - - constexpr size_t bufferSize = 512; - std::array buffer{}; - while (fgets(buffer.data(), bufferSize, file) != nullptr) { - function(buffer.data()); - } - - const int exitCode = pclose(file); - if (exitCode != 0) { - if (exitCode == -1) { - qWarning().nospace() << "Could not close stream for command '" << command << "': " << strerror(errno); - } else { - qWarning().nospace() << "Command '" << command << "' exited with code " << exitCode; - } - - return false; - } - - return true; -} -} // namespace -#endif - -#ifdef Q_OS_WINDOWS -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif -#include - -#include -#include - -#include -using Microsoft::WRL::ComPtr; - -QString HardwareInfo::cpuInfo() -{ - const QSettings registry(R"(HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor\0)", QSettings::NativeFormat); - return registry.value("ProcessorNameString").toString(); -} - -uint64_t HardwareInfo::totalRamMiB() -{ - MEMORYSTATUSEX status; - status.dwLength = sizeof status; - - if (GlobalMemoryStatusEx(&status) == TRUE) { - // transforming bytes -> mib - return status.ullTotalPhys / 1024 / 1024; - } - - qWarning() << "Could not get total RAM: GlobalMemoryStatusEx"; - return 0; -} - -uint64_t HardwareInfo::availableRamMiB() -{ - MEMORYSTATUSEX status; - status.dwLength = sizeof status; - - if (GlobalMemoryStatusEx(&status) == TRUE) { - // transforming bytes -> mib - return status.ullAvailPhys / 1024 / 1024; - } - - qWarning() << "Could not get available RAM: GlobalMemoryStatusEx"; - return 0; -} - -QStringList HardwareInfo::gpuInfo() -{ - ComPtr factory; - HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(&factory)); - if (FAILED(hr)) { - qWarning() << "Could not create DXGI factory:" << Qt::hex << hr; - return { "GPU discovery failed: could not create DXGI factory" }; - } - - UINT i = 0; - ComPtr adapter; - QStringList out; - while (factory->EnumAdapterByGpuPreference(i, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, IID_PPV_ARGS(&adapter)) != DXGI_ERROR_NOT_FOUND) { - DXGI_ADAPTER_DESC desc; - hr = adapter->GetDesc(&desc); - if (SUCCEEDED(hr)) { - out << "GPU: " + QString::fromWCharArray(desc.Description); // NOLINT(*-pro-bounds-array-to-pointer-decay, *-no-array-decay) - } else { - qWarning() << "Could not get DXGI adapter description:" << Qt::hex << hr; - } - - ++i; - } - - return out; -} - -#elif defined(Q_OS_MACOS) -#include "sys/sysctl.h" - -QString HardwareInfo::cpuInfo() -{ - std::array buffer{}; - size_t bufferSize = buffer.size(); - if (sysctlbyname("machdep.cpu.brand_string", &buffer, &bufferSize, nullptr, 0) == 0) { - return { buffer.data() }; - } - - qWarning() << "Could not get CPU model: sysctlbyname"; - return ""; -} - -uint64_t HardwareInfo::totalRamMiB() -{ - uint64_t memsize = 0; - size_t memsizeSize = sizeof memsize; - if (sysctlbyname("hw.memsize", &memsize, &memsizeSize, nullptr, 0) == 0) { - // transforming bytes -> mib - return memsize / 1024 / 1024; - } - - qWarning() << "Could not get total RAM: sysctlbyname"; - return 0; -} - -uint64_t HardwareInfo::availableRamMiB() -{ - return 0; -} - -MacOSHardwareInfo::MemoryPressureLevel MacOSHardwareInfo::memoryPressureLevel() -{ - uint32_t level = 0; - size_t levelSize = sizeof level; - if (sysctlbyname("kern.memorystatus_vm_pressure_level", &level, &levelSize, nullptr, 0) == 0) { - return static_cast(level); - } - - qWarning() << "Could not get memory pressure level: sysctlbyname"; - return MemoryPressureLevel::Normal; -} - -QString MacOSHardwareInfo::memoryPressureLevelName() -{ - // The names are internal, users refer to levels by their graph colors in Activity Monitor - switch (memoryPressureLevel()) { - case MemoryPressureLevel::Normal: - return "Green"; - case MemoryPressureLevel::Warning: - return "Yellow"; - case MemoryPressureLevel::Critical: - return "Red"; - default: - Q_ASSERT(false); - return ""; - } -} - -QStringList HardwareInfo::gpuInfo() -{ - QStringList out; - const bool success = readFromOutput("system_profiler SPDisplaysDataType", [&](const QString& str) { - // Chipset Model: Intel HD Graphics 620 - if (str.contains("Chipset Model")) { - out << "GPU: " + afterColon(str); - } - }); - if (!success) { - return { "GPU discovery failed: could not read from system_profiler" }; - } - - return out; -} - -#elif defined(Q_OS_LINUX) -#include - -QString HardwareInfo::cpuInfo() -{ - std::ifstream cpuin("/proc/cpuinfo"); - for (std::string line; std::getline(cpuin, line);) { - // model name : AMD Ryzen 7 5800X 8-Core Processor - if (const QString str = QString::fromStdString(line); str.startsWith("model name")) { - return afterColon(str); - } - } - - qWarning() << "Could not get CPU model: /proc/cpuinfo"; - return "unknown"; -} - -namespace { -uint64_t readMemInfo(const QString& searchTarget) -{ - std::ifstream memin("/proc/meminfo"); - for (std::string line; std::getline(memin, line);) { - // MemTotal: 16287480 kB - if (const QString str = QString::fromStdString(line); str.startsWith(searchTarget)) { - bool ok = false; - const uint total = str.simplified().section(' ', 1, 1).toUInt(&ok); - if (!ok) { - qWarning() << "Could not read /proc/meminfo: failed to parse string:" << str; - return 0; - } - - // transforming kib -> mib - return total / 1024; - } - } - - qWarning() << "Could not read /proc/meminfo: search target not found:" << searchTarget; - return 0; -} -} // namespace - -uint64_t HardwareInfo::totalRamMiB() -{ - return readMemInfo("MemTotal"); -} - -uint64_t HardwareInfo::availableRamMiB() -{ - return readMemInfo("MemAvailable"); -} - -QStringList HardwareInfo::gpuInfo() -{ - bool readingGpuInfo = false; - QString gpu; - QString driverInUse = "NONE"; - QString driversAvailable = "NONE"; - QStringList out; - - const bool success = readFromOutput("lspci -k", [&](const QString& str) { - // clang-format off - // 04:00.0 VGA compatible controller: Advanced Micro Devices, Inc. [AMD/ATI] Ellesmere [Radeon RX 470/480/570/570X/580/580X/590] (rev e7) - // Subsystem: Sapphire Technology Limited Radeon RX 580 Pulse 4GB - // Kernel driver in use: amdgpu - // Kernel modules: amdgpu - // clang-format on - if (str.contains("VGA compatible controller") || str.contains("3D controller")) { - readingGpuInfo = true; - } else if (!str.startsWith('\t')) { - if (readingGpuInfo) { - out << QString("GPU: %1 (driver in use: %2; drivers available: %3)").arg(gpu, driverInUse, driversAvailable); - driverInUse = "NONE"; - driversAvailable = "NONE"; - } - readingGpuInfo = false; - } - - if (!readingGpuInfo) { - return; - } - - const QString value = afterColon(str); - if (str.contains("Subsystem")) { - gpu = value; - } - if (str.contains("Kernel driver in use")) { - driverInUse = value; - } - if (str.contains("Kernel modules")) { - driversAvailable = value; - } - }); - if (!success) { - return { "GPU discovery failed: could not read from lspci" }; - } - - return out; -} - -#else - -QString HardwareInfo::cpuInfo() -{ - return "unknown"; -} - -#if defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) -#include - -uint64_t HardwareInfo::totalRamMiB() -{ - uint64_t out = 0; - - const bool success = readFromOutput("sysctl hw.physmem", [&](const QString& str) { - const uint64_t mem = str.mid(12).toULong(); - - // transforming kib -> mib - out = mem / 1024; - }); - if (!success) { - qWarning() << "Could not get total RAM: could not read from sysctl"; - return 0; - } - - return out; -} - -#else -uint64_t HardwareInfo::totalRamMiB() -{ - return 0; -} -#endif - -uint64_t HardwareInfo::availableRamMiB() -{ - return 0; -} - -QStringList HardwareInfo::gpuInfo() -{ - return { "GPU discovery failed: not implemented for this OS" }; -} -#endif diff --git a/launcher/HardwareInfo.h b/launcher/HardwareInfo.h deleted file mode 100644 index 4efd339b6..000000000 --- a/launcher/HardwareInfo.h +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2026 Octol1ttle - * - * 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 - -namespace HardwareInfo { -QString cpuInfo(); -uint64_t totalRamMiB(); -uint64_t availableRamMiB(); -QStringList gpuInfo(); -} // namespace HardwareInfo - -#ifdef Q_OS_MACOS -namespace MacOSHardwareInfo { -enum class MemoryPressureLevel : uint8_t { - Normal = 1, - Warning = 2, - Critical = 4, -}; - -MemoryPressureLevel memoryPressureLevel(); -QString memoryPressureLevelName(); -} // namespace MacOSHardwareInfo -#endif \ No newline at end of file diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index e32cdf095..4bb57bc58 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -8,7 +8,7 @@ #include "settings/INISettingsObject.h" #include "tasks/Task.h" -InstanceCopyTask::InstanceCopyTask(BaseInstance* origInstance, const InstanceCopyPrefs& prefs) +InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs) { m_origInstance = origInstance; m_keepPlaytime = prefs.isKeepPlaytimeEnabled(); @@ -146,9 +146,9 @@ void InstanceCopyTask::copyFinished() } // FIXME: shouldn't this be able to report errors? - auto instanceSettings = std::make_unique(FS::PathCombine(m_stagingPath, "instance.cfg")); + auto instanceSettings = std::make_shared(FS::PathCombine(m_stagingPath, "instance.cfg")); - BaseInstance* inst(new NullInstance(m_globalSettings, std::move(instanceSettings), m_stagingPath)); + InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, m_stagingPath)); inst->setName(name()); inst->setIconKey(m_instIcon); if (!m_keepPlaytime) { diff --git a/launcher/InstanceCopyTask.h b/launcher/InstanceCopyTask.h index a926af8a7..ef4120bc6 100644 --- a/launcher/InstanceCopyTask.h +++ b/launcher/InstanceCopyTask.h @@ -15,7 +15,7 @@ class InstanceCopyTask : public InstanceTask { Q_OBJECT public: - explicit InstanceCopyTask(BaseInstance* origInstance, const InstanceCopyPrefs& prefs); + explicit InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs); protected: //! Entry point for tasks. @@ -26,7 +26,7 @@ class InstanceCopyTask : public InstanceTask { private: /* data */ - BaseInstance* m_origInstance; + InstancePtr m_origInstance; QFuture m_copyFuture; QFutureWatcher m_copyFutureWatcher; Filter m_matcher; diff --git a/launcher/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp index e58926660..94c229128 100644 --- a/launcher/InstanceCreationTask.cpp +++ b/launcher/InstanceCreationTask.cpp @@ -2,25 +2,7 @@ #include #include - -#include "Application.h" -#include "InstanceTask.h" -#include "minecraft/MinecraftLoadAndCheck.h" -#include "tasks/SequentialTask.h" - -bool InstanceCreationTask::abort() -{ - if (!canAbort()) { - return false; - } - - m_abort = true; - if (m_gameFilesTask) { - return m_gameFilesTask->abort(); - } - - return InstanceTask::abort(); -} +#include "FileSystem.h" void InstanceCreationTask::executeTask() { @@ -37,11 +19,9 @@ void InstanceCreationTask::executeTask() return; } - m_instance = createInstance(); - if (!m_instance) { - if (m_abort) { + if (!createInstance()) { + if (m_abort) return; - } qWarning() << "Instance creation failed!"; if (!m_error_message.isEmpty()) { @@ -64,10 +44,9 @@ void InstanceCreationTask::executeTask() setStatus(tr("Removing old conflicting files...")); qDebug() << "Removing old files"; - for (const QString& path : m_filesToRemove) { - if (!QFile::exists(path)) { + for (const QString& path : m_files_to_remove) { + if (!QFile::exists(path)) continue; - } qDebug() << "Removing" << path; @@ -82,61 +61,6 @@ void InstanceCreationTask::executeTask() return; } } - - if (!m_abort) { - if (!APPLICATION->settings()->get("DownloadGameFilesDuringInstanceCreation").toBool()) { - emitSucceeded(); - return; - } - setAbortable(true); - setAbortButtonText(tr("Skip")); - qDebug() << "Downloading game files"; - - auto updateTasks = m_instance->createUpdateTask(); - if (updateTasks.isEmpty()) { - emitSucceeded(); - return; - } - auto task = makeShared(); - task->addTask(makeShared(m_instance.get(), Net::Mode::Online)); - for (const auto& t : updateTasks) { - task->addTask(t); - } - connect(task.get(), &Task::finished, this, [this, task] { - if (task->wasSuccessful() || m_abort) { - emitSucceeded(); - } else { - emitFailed(tr("Could not download game files: %1").arg(task->failReason())); - } - }); - propagateFromOther(task.get()); - setDetails(tr("Downloading game files")); - - m_gameFilesTask = task; - m_gameFilesTask->start(); - } -} - -void InstanceCreationTask::scheduleToDelete(QWidget* parent, const QDir& dir, const QString& path, bool checkDisabled) -{ - if (path.isEmpty()) { - return; - } - if (path.startsWith("saves/")) { - if (m_shouldDeleteSaves == ShouldDeleteSaves::NotAsked) { - m_shouldDeleteSaves = askIfShouldDeleteSaves(parent); - } - if (m_shouldDeleteSaves == ShouldDeleteSaves::No) { - return; - } - } - qDebug() << "Scheduling" << path << "for removal"; - m_filesToRemove.append(dir.absoluteFilePath(path)); - if (checkDisabled) { - if (path.endsWith(".disabled")) { // remove it if it was enabled/disabled by user - m_filesToRemove.append(dir.absoluteFilePath(path.chopped(9))); - } else { - m_filesToRemove.append(dir.absoluteFilePath(path + ".disabled")); - } - } + if (!m_abort) + emitSucceeded(); } diff --git a/launcher/InstanceCreationTask.h b/launcher/InstanceCreationTask.h index 39acaf8b2..84fb2a145 100644 --- a/launcher/InstanceCreationTask.h +++ b/launcher/InstanceCreationTask.h @@ -2,7 +2,6 @@ #include "BaseVersion.h" #include "InstanceTask.h" -#include "minecraft/MinecraftInstance.h" class InstanceCreationTask : public InstanceTask { Q_OBJECT @@ -10,8 +9,6 @@ class InstanceCreationTask : public InstanceTask { InstanceCreationTask() = default; virtual ~InstanceCreationTask() = default; - bool abort() override; - protected: void executeTask() final override; @@ -30,24 +27,20 @@ class InstanceCreationTask : public InstanceTask { /** * Creates a new instance. * - * Returns the instance if it was created or nullptr otherwise. + * Returns whether the instance creation was successful (true) or not (false). */ - virtual std::unique_ptr createInstance() { return nullptr; } + virtual bool createInstance() { return false; }; QString getError() const { return m_error_message; } protected: void setError(const QString& message) { m_error_message = message; }; - void scheduleToDelete(QWidget* parent, const QDir& dir, const QString& path, bool checkDisabled = false); protected: bool m_abort = false; - QStringList m_filesToRemove; - ShouldDeleteSaves m_shouldDeleteSaves; + QStringList m_files_to_remove; private: QString m_error_message; - std::unique_ptr m_instance; - Task::Ptr m_gameFilesTask; }; diff --git a/launcher/InstanceDirUpdate.cpp b/launcher/InstanceDirUpdate.cpp index 75fbdb6c6..8be0dccac 100644 --- a/launcher/InstanceDirUpdate.cpp +++ b/launcher/InstanceDirUpdate.cpp @@ -42,7 +42,7 @@ #include "InstanceList.h" #include "ui/dialogs/CustomMessageBox.h" -QString askToUpdateInstanceDirName(BaseInstance* instance, const QString& oldName, const QString& newName, QWidget* parent) +QString askToUpdateInstanceDirName(InstancePtr instance, const QString& oldName, const QString& newName, QWidget* parent) { if (oldName == newName) return QString(); diff --git a/launcher/InstanceDirUpdate.h b/launcher/InstanceDirUpdate.h index 9da49a9a6..b92a59c4c 100644 --- a/launcher/InstanceDirUpdate.h +++ b/launcher/InstanceDirUpdate.h @@ -37,7 +37,7 @@ #include "BaseInstance.h" /// Update instanceRoot to make it sync with name/id; return newRoot if a directory rename happened -QString askToUpdateInstanceDirName(BaseInstance* instance, const QString& oldName, const QString& newName, QWidget* parent); +QString askToUpdateInstanceDirName(InstancePtr instance, const QString& oldName, const QString& newName, QWidget* parent); /// Check if there are linked instances, and display a warning; return true if the operation should proceed bool checkLinkedInstances(const QString& id, QWidget* parent, const QString& verb); diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 9b04f99b6..cd168dee2 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -322,7 +322,6 @@ void InstanceImportTask::processFlame() connect(inst_creation_task.get(), &Task::aborted, this, &InstanceImportTask::emitAborted); connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable); - connect(inst_creation_task.get(), &Task::abortButtonTextChanged, this, &Task::setAbortButtonText); connect(inst_creation_task.get(), &Task::warningLogged, this, [this](const QString& line) { m_Warnings.append(line); }); @@ -342,9 +341,9 @@ void InstanceImportTask::processTechnic() void InstanceImportTask::processMultiMC() { QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); - auto instanceSettings = std::make_unique(configPath); + auto instanceSettings = std::make_shared(configPath); - NullInstance instance(m_globalSettings, std::move(instanceSettings), m_stagingPath); + NullInstance instance(m_globalSettings, instanceSettings, m_stagingPath); // reset time played on import... because packs. instance.resetTimePlayed(); @@ -422,7 +421,6 @@ void InstanceImportTask::processModrinth() connect(inst_creation_task.get(), &Task::aborted, this, &InstanceImportTask::emitAborted); connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable); - connect(inst_creation_task.get(), &Task::abortButtonTextChanged, this, &Task::setAbortButtonText); connect(inst_creation_task.get(), &Task::warningLogged, this, [this](const QString& line) { m_Warnings.append(line); }); diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 1339499c7..de94db7c3 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -34,37 +34,42 @@ * limitations under the License. */ -#include "InstanceList.h" - #include +#include #include #include #include +#include #include #include #include +#include #include #include +#include +#include #include #include +#include #include "BaseInstance.h" #include "ExponentialSeries.h" #include "FileSystem.h" - +#include "InstanceList.h" #include "InstanceTask.h" #include "NullInstance.h" #include "WatchLock.h" #include "minecraft/MinecraftInstance.h" +#include "minecraft/ShortcutUtils.h" #include "settings/INISettingsObject.h" #ifdef Q_OS_WIN32 -#include +#include #endif const static int GROUP_FILE_FORMAT_VERSION = 1; -InstanceList::InstanceList(SettingsObject* settings, const QString& instDir, QObject* parent) +InstanceList::InstanceList(SettingsObjectPtr settings, const QString& instDir, QObject* parent) : QAbstractListModel(parent), m_globalSettings(settings) { resumeWatch(); @@ -138,7 +143,7 @@ QMimeData* InstanceList::mimeData(const QModelIndexList& indexes) const QStringList InstanceList::getLinkedInstancesById(const QString& id) const { QStringList linkedInstances; - for (auto& inst : m_instances) { + for (auto inst : m_instances) { if (inst->isLinkedToInstanceId(id)) linkedInstances.append(inst->id()); } @@ -148,15 +153,15 @@ QStringList InstanceList::getLinkedInstancesById(const QString& id) const int InstanceList::rowCount(const QModelIndex& parent) const { Q_UNUSED(parent); - return count(); + return m_instances.count(); } QModelIndex InstanceList::index(int row, int column, const QModelIndex& parent) const { Q_UNUSED(parent); - if (row < 0 || row >= count()) + if (row < 0 || row >= m_instances.size()) return QModelIndex(); - return createIndex(row, column, m_instances.at(row).get()); + return createIndex(row, column, (void*)m_instances.at(row).get()); } QVariant InstanceList::data(const QModelIndex& index, int role) const @@ -261,7 +266,7 @@ void InstanceList::setInstanceGroup(const InstanceId& id, GroupId name) if (changed) { increaseGroupCount(name); - auto idx = getInstIndex(inst); + auto idx = getInstIndex(inst.get()); emit dataChanged(index(idx), index(idx), { GroupRole }); saveGroupList(); } @@ -452,7 +457,7 @@ void InstanceList::deleteInstance(const InstanceId& id) } } -static QMap getIdMapping(const std::vector>& list) +static QMap getIdMapping(const QList& list) { QMap out; int i = 0; @@ -461,7 +466,7 @@ static QMap getIdMapping(const std::vector> newList; + QList newList; for (auto& id : discoverInstances()) { if (existingIds.contains(id)) { + auto instPair = existingIds[id]; existingIds.remove(id); qInfo() << "Should keep and soft-reload" << id; } else { - std::unique_ptr instPtr = loadInstance(id); + InstancePtr instPtr = loadInstance(id); if (instPtr) { - newList.push_back(std::move(instPtr)); + newList.append(instPtr); } } } @@ -560,8 +566,8 @@ InstanceList::InstListError InstanceList::loadList() void InstanceList::updateTotalPlayTime() { totalPlayTime = 0; - for (const auto& itr : m_instances) { - totalPlayTime += itr->totalTimePlayed(); + for (auto const& itr : m_instances) { + totalPlayTime += itr.get()->totalTimePlayed(); } } @@ -572,12 +578,12 @@ void InstanceList::saveNow() } } -void InstanceList::add(std::vector>& t) +void InstanceList::add(const QList& t) { - beginInsertRows(QModelIndex(), count(), static_cast(count() + t.size() - 1)); + beginInsertRows(QModelIndex(), m_instances.count(), m_instances.count() + t.size() - 1); + m_instances.append(t); for (auto& ptr : t) { - m_instances.push_back(std::move(ptr)); - connect(m_instances.back().get(), &BaseInstance::propertiesChanged, this, &InstanceList::propertiesChanged); + connect(ptr.get(), &BaseInstance::propertiesChanged, this, &InstanceList::propertiesChanged); } endInsertRows(); } @@ -607,26 +613,26 @@ void InstanceList::providerUpdated() } } -BaseInstance* InstanceList::getInstanceById(QString instId) const +InstancePtr InstanceList::getInstanceById(QString instId) const { if (instId.isEmpty()) - return nullptr; + return InstancePtr(); for (auto& inst : m_instances) { if (inst->id() == instId) { - return inst.get(); + return inst; } } - return nullptr; + return InstancePtr(); } -BaseInstance* InstanceList::getInstanceByManagedName(const QString& managed_name) const +InstancePtr InstanceList::getInstanceByManagedName(const QString& managed_name) const { if (managed_name.isEmpty()) return {}; - for (auto& instance : m_instances) { + for (auto instance : m_instances) { if (instance->getManagedPackName() == managed_name) - return instance.get(); + return instance; } return {}; @@ -634,14 +640,14 @@ BaseInstance* InstanceList::getInstanceByManagedName(const QString& managed_name QModelIndex InstanceList::getInstanceIndexById(const QString& id) const { - return index(getInstIndex(getInstanceById(id))); + return index(getInstIndex(getInstanceById(id).get())); } int InstanceList::getInstIndex(BaseInstance* inst) const { - int count = this->count(); + int count = m_instances.count(); for (int i = 0; i < count; i++) { - if (inst == m_instances.at(i).get()) { + if (inst == m_instances[i].get()) { return i; } } @@ -657,15 +663,15 @@ void InstanceList::propertiesChanged(BaseInstance* inst) } } -std::unique_ptr InstanceList::loadInstance(const InstanceId& id) +InstancePtr InstanceList::loadInstance(const InstanceId& id) { if (!m_groupsLoaded) { loadGroupList(); } auto instanceRoot = FS::PathCombine(m_instDir, id); - auto instanceSettings = std::make_unique(FS::PathCombine(instanceRoot, "instance.cfg")); - std::unique_ptr inst; + auto instanceSettings = std::make_shared(FS::PathCombine(instanceRoot, "instance.cfg")); + InstancePtr inst; instanceSettings->registerSetting("InstanceType", ""); @@ -674,9 +680,9 @@ std::unique_ptr InstanceList::loadInstance(const InstanceId& id) // NOTE: Some launcher versions didn't save the InstanceType properly. We will just bank on the probability that this is probably a // OneSix instance if (inst_type == "OneSix" || inst_type.isEmpty()) { - inst.reset(new MinecraftInstance(m_globalSettings, std::move(instanceSettings), instanceRoot)); + inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot)); } else { - inst.reset(new NullInstance(m_globalSettings, std::move(instanceSettings), instanceRoot)); + inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot)); } qDebug() << "Loaded instance" << inst->name() << "from" << inst->instanceRoot(); @@ -905,20 +911,20 @@ class InstanceStaging : public Task { const unsigned maxBackoff = 16; public: - InstanceStaging(InstanceList* parent, InstanceTask* child, SettingsObject* settings) : m_parent(parent), backoff(minBackoff, maxBackoff) + InstanceStaging(InstanceList* parent, InstanceTask* child, SettingsObjectPtr settings) + : m_parent(parent), backoff(minBackoff, maxBackoff) { m_stagingPath = parent->getStagedInstancePath(); m_child.reset(child); m_child->setStagingPath(m_stagingPath); - m_child->setParentSettings(settings); + m_child->setParentSettings(std::move(settings)); connect(child, &Task::succeeded, this, &InstanceStaging::childSucceeded); connect(child, &Task::failed, this, &InstanceStaging::childFailed); connect(child, &Task::aborted, this, &InstanceStaging::childAborted); connect(child, &Task::abortStatusChanged, this, &InstanceStaging::setAbortable); - connect(child, &Task::abortButtonTextChanged, this, &InstanceStaging::setAbortButtonText); connect(child, &Task::status, this, &InstanceStaging::setStatus); connect(child, &Task::details, this, &InstanceStaging::setDetails); connect(child, &Task::progress, this, &InstanceStaging::setProgress); @@ -926,21 +932,22 @@ class InstanceStaging : public Task { connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceeded); } - ~InstanceStaging() override = default; + virtual ~InstanceStaging() {} // FIXME/TODO: add ability to abort during instance commit retries bool abort() override { - if (!canAbort()) { + if (!canAbort()) return false; - } - return m_child->abort(); + m_child->abort(); + + return Task::abort(); } bool canAbort() const override { return (m_child && m_child->canAbort()); } protected: - void executeTask() override + virtual void executeTask() override { if (m_stagingPath.isNull()) { emitFailed(tr("Could not create staging folder")); @@ -955,14 +962,12 @@ class InstanceStaging : public Task { void childSucceeded() { unsigned sleepTime = backoff(); - if (m_parent->commitStagedInstance(m_stagingPath, *m_child, m_child->group(), *m_child)) { - m_backoffTimer.stop(); + if (m_parent->commitStagedInstance(m_stagingPath, *m_child.get(), m_child->group(), *m_child.get())) { emitSucceeded(); return; } // we actually failed, retry? if (sleepTime == maxBackoff) { - m_backoffTimer.stop(); emitFailed(tr("Failed to commit instance, even after multiple retries. It is being blocked by something.")); return; } @@ -971,14 +976,12 @@ class InstanceStaging : public Task { } void childFailed(const QString& reason) { - m_backoffTimer.stop(); m_parent->destroyStagingPath(m_stagingPath); emitFailed(reason); } void childAborted() { - m_backoffTimer.stop(); m_parent->destroyStagingPath(m_stagingPath); emitAborted(); } @@ -992,7 +995,7 @@ class InstanceStaging : public Task { */ ExponentialSeries backoff; QString m_stagingPath; - std::unique_ptr m_child; + unique_qobject_ptr m_child; QTimer m_backoffTimer; }; @@ -1025,14 +1028,15 @@ QString InstanceList::getStagedInstancePath() } bool InstanceList::commitStagedInstance(const QString& path, - const InstanceName& instanceName, + InstanceName const& instanceName, QString groupName, - const InstanceTask& commiting) + InstanceTask const& commiting) { if (groupName.isEmpty() && !groupName.isNull()) groupName = QString(); QString instID; + InstancePtr inst; auto should_override = commiting.shouldOverride(); diff --git a/launcher/InstanceList.h b/launcher/InstanceList.h index f0a92d273..fc4fa9a39 100644 --- a/launcher/InstanceList.h +++ b/launcher/InstanceList.h @@ -50,7 +50,7 @@ struct InstanceName; using InstanceId = QString; using GroupId = QString; -using InstanceLocator = std::pair; +using InstanceLocator = std::pair; enum class InstCreateError { NoCreateError = 0, NoSuchVersion, UnknownCreateError, InstExists, CantCreateDir }; @@ -73,7 +73,7 @@ class InstanceList : public QAbstractListModel { Q_OBJECT public: - explicit InstanceList(SettingsObject* settings, const QString& instDir, QObject* parent = 0); + explicit InstanceList(SettingsObjectPtr settings, const QString& instDir, QObject* parent = 0); virtual ~InstanceList(); public: @@ -96,17 +96,17 @@ class InstanceList : public QAbstractListModel { */ enum InstListError { NoError = 0, UnknownError }; - BaseInstance* at(int i) const { return m_instances.at(i).get(); } + InstancePtr at(int i) const { return m_instances.at(i); } - int count() const { return static_cast(m_instances.size()); } + int count() const { return m_instances.count(); } InstListError loadList(); void saveNow(); /* O(n) */ - BaseInstance* getInstanceById(QString id) const; + InstancePtr getInstanceById(QString id) const; /* O(n) */ - BaseInstance* getInstanceByManagedName(const QString& managed_name) const; + InstancePtr getInstanceByManagedName(const QString& managed_name) const; QModelIndex getInstanceIndexById(const QString& id) const; QStringList getGroups(); bool isGroupCollapsed(const QString& groupName); @@ -179,11 +179,11 @@ class InstanceList : public QAbstractListModel { void updateTotalPlayTime(); void suspendWatch(); void resumeWatch(); - void add(std::vector>& list); + void add(const QList& list); void loadGroupList(); void saveGroupList(); QList discoverInstances(); - std::unique_ptr loadInstance(const InstanceId& id); + InstancePtr loadInstance(const InstanceId& id); void increaseGroupCount(const QString& group); void decreaseGroupCount(const QString& group); @@ -192,11 +192,11 @@ class InstanceList : public QAbstractListModel { int m_watchLevel = 0; int totalPlayTime = 0; bool m_dirty = false; - std::vector> m_instances; + QList m_instances; // id -> refs QMap m_groupNameCache; - SettingsObject* m_globalSettings; + SettingsObjectPtr m_globalSettings; QString m_instDir; QFileSystemWatcher* m_watcher; // FIXME: this is so inefficient that looking at it is almost painful. diff --git a/launcher/InstancePageProvider.h b/launcher/InstancePageProvider.h index 134fb8f24..c683774d2 100644 --- a/launcher/InstancePageProvider.h +++ b/launcher/InstancePageProvider.h @@ -21,26 +21,26 @@ class InstancePageProvider : protected QObject, public BasePageProvider { Q_OBJECT public: - explicit InstancePageProvider(BaseInstance* parent) { inst = parent; } + explicit InstancePageProvider(InstancePtr parent) { inst = parent; } virtual ~InstancePageProvider() = default; virtual QList getPages() override { QList values; values.append(new LogPage(inst)); - MinecraftInstance* onesix = dynamic_cast(inst); - values.append(new VersionPage(onesix)); - values.append(ManagedPackPage::createPage(onesix)); - auto modsPage = new ModFolderPage(onesix, onesix->loaderModList()); + std::shared_ptr onesix = std::dynamic_pointer_cast(inst); + values.append(new VersionPage(onesix.get())); + values.append(ManagedPackPage::createPage(onesix.get())); + auto modsPage = new ModFolderPage(onesix.get(), onesix->loaderModList()); modsPage->setFilter("%1 (*.zip *.jar *.litemod *.nilmod)"); values.append(modsPage); - values.append(new CoreModFolderPage(onesix, onesix->coreModList())); - values.append(new NilModFolderPage(onesix, onesix->nilModList())); - values.append(new ResourcePackPage(onesix, onesix->resourcePackList())); - values.append(new GlobalDataPackPage(onesix)); - values.append(new TexturePackPage(onesix, onesix->texturePackList())); - values.append(new ShaderPackPage(onesix, onesix->shaderPackList())); - values.append(new NotesPage(onesix)); + values.append(new CoreModFolderPage(onesix.get(), onesix->coreModList())); + values.append(new NilModFolderPage(onesix.get(), onesix->nilModList())); + values.append(new ResourcePackPage(onesix.get(), onesix->resourcePackList())); + values.append(new GlobalDataPackPage(onesix.get())); + values.append(new TexturePackPage(onesix.get(), onesix->texturePackList())); + values.append(new ShaderPackPage(onesix.get(), onesix->shaderPackList())); + values.append(new NotesPage(onesix.get())); values.append(new WorldListPage(onesix, onesix->worldList())); values.append(new ServersPage(onesix)); values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots"))); @@ -52,5 +52,5 @@ class InstancePageProvider : protected QObject, public BasePageProvider { virtual QString dialogTitle() override { return tr("Edit Instance (%1)").arg(inst->name()); } protected: - BaseInstance* inst; + InstancePtr inst; }; diff --git a/launcher/InstanceTask.cpp b/launcher/InstanceTask.cpp index 01998a7aa..be10bbe07 100644 --- a/launcher/InstanceTask.cpp +++ b/launcher/InstanceTask.cpp @@ -1,5 +1,4 @@ #include "InstanceTask.h" -#include #include "Application.h" #include "settings/SettingsObject.h" @@ -83,13 +82,3 @@ void InstanceName::setName(InstanceName& other) } InstanceTask::InstanceTask() : Task(), InstanceName() {} - -ShouldDeleteSaves askIfShouldDeleteSaves(QWidget* parent) -{ - auto dialog = CustomMessageBox::selectable(parent, QObject::tr("Delete Existing Save Files"), - QObject::tr("An earlier version of this mod pack installed save files.\n" - "Would you like to remove those existing saves as part of this update?"), - QMessageBox::Question, QMessageBox::No | QMessageBox::Yes); - auto result = dialog->exec(); - return result == QMessageBox::Yes ? ShouldDeleteSaves::Yes : ShouldDeleteSaves::No; -} diff --git a/launcher/InstanceTask.h b/launcher/InstanceTask.h index 125930a27..86b4cee68 100644 --- a/launcher/InstanceTask.h +++ b/launcher/InstanceTask.h @@ -8,8 +8,6 @@ enum class InstanceNameChange { ShouldChange, ShouldKeep }; [[nodiscard]] InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& old_name, const QString& new_name); enum class ShouldUpdate { Update, SkipUpdating, Cancel }; [[nodiscard]] ShouldUpdate askIfShouldUpdate(QWidget* parent, QString original_version_name); -enum class ShouldDeleteSaves { NotAsked, Yes, No }; -[[nodiscard]] ShouldDeleteSaves askIfShouldDeleteSaves(QWidget* parent); struct InstanceName { public: @@ -37,7 +35,7 @@ class InstanceTask : public Task, public InstanceName { InstanceTask(); ~InstanceTask() override = default; - void setParentSettings(SettingsObject* settings) { m_globalSettings = settings; } + void setParentSettings(SettingsObjectPtr settings) { m_globalSettings = settings; } void setStagingPath(const QString& stagingPath) { m_stagingPath = stagingPath; } @@ -62,7 +60,7 @@ class InstanceTask : public Task, public InstanceName { } protected: /* data */ - SettingsObject* m_globalSettings; + SettingsObjectPtr m_globalSettings; QString m_instIcon; QString m_instGroup; QString m_stagingPath; diff --git a/launcher/Json.cpp b/launcher/Json.cpp index 2d3372e2e..688f9dae7 100644 --- a/launcher/Json.cpp +++ b/launcher/Json.cpp @@ -303,7 +303,7 @@ QStringList toStringList(const QString& jsonString) return {}; try { return requireIsArrayOf(doc); - } catch (Json::JsonException&) { + } catch (Json::JsonException& e) { return {}; } } diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 2b882338c..183afefaa 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -40,18 +40,22 @@ #include "minecraft/auth/AccountData.h" #include "minecraft/auth/AccountList.h" -#include "net/NetUtils.h" #include "ui/InstanceWindow.h" +#include "ui/MainWindow.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/MSALoginDialog.h" #include "ui/dialogs/ProfileSelectDialog.h" #include "ui/dialogs/ProfileSetupDialog.h" #include "ui/dialogs/ProgressDialog.h" +#include +#include #include +#include #include #include -#include +#include +#include #include "BuildConfig.h" #include "JavaCommon.h" @@ -59,7 +63,7 @@ #include "tasks/Task.h" #include "ui/dialogs/ChooseOfflineNameDialog.h" -LaunchController::LaunchController() = default; +LaunchController::LaunchController() : Task() {} void LaunchController::executeTask() { @@ -82,17 +86,9 @@ void LaunchController::decideAccount() return; } - // Select the account to use. If the instance has a specific account set, that will be used. Otherwise, the default account will be used - auto* accounts = APPLICATION->accounts(); - const auto instanceAccountId = m_instance->settings()->get("InstanceAccountId").toString(); - const auto instanceAccountIndex = accounts->findAccountByProfileId(instanceAccountId); - if (instanceAccountIndex == -1 || instanceAccountId.isEmpty()) { - m_accountToUse = accounts->defaultAccount(); - } else { - m_accountToUse = accounts->at(instanceAccountIndex); - } - - if (!accounts->anyAccountIsValid()) { + // Find an account to use. + auto accounts = APPLICATION->accounts(); + if (accounts->count() <= 0 || !accounts->anyAccountIsValid()) { // Tell the user they need to log in at least one account in order to play. auto reply = CustomMessageBox::selectable(m_parentWidget, tr("No Accounts"), tr("In order to play Minecraft, you must have at least one Microsoft " @@ -110,7 +106,16 @@ void LaunchController::decideAccount() } } - if (!m_accountToUse && accounts->anyAccountIsValid()) { + // Select the account to use. If the instance has a specific account set, that will be used. Otherwise, the default account will be used + auto instanceAccountId = m_instance->settings()->get("InstanceAccountId").toString(); + auto instanceAccountIndex = accounts->findAccountByProfileId(instanceAccountId); + if (instanceAccountIndex == -1 || instanceAccountId.isEmpty()) { + m_accountToUse = accounts->defaultAccount(); + } else { + m_accountToUse = accounts->at(instanceAccountIndex); + } + + if (!m_accountToUse) { // If no default account is set, ask the user which one to use. ProfileSelectDialog selectDialog(tr("Which account would you like to use?"), ProfileSelectDialog::GlobalDefaultCheckbox, m_parentWidget); @@ -127,146 +132,46 @@ void LaunchController::decideAccount() } } -LaunchDecision LaunchController::decideLaunchMode() -{ - if (!m_accountToUse || m_wantedLaunchMode == LaunchMode::Demo) { - m_actualLaunchMode = LaunchMode::Demo; - return LaunchDecision::Continue; - } - - const auto* accounts = APPLICATION->accounts(); - MinecraftAccountPtr accountToCheck = nullptr; - - if (m_accountToUse->accountType() != AccountType::Offline) { - accountToCheck = m_accountToUse->ownsMinecraft() ? m_accountToUse : nullptr; - } else if (const auto defaultAccount = accounts->defaultAccount(); defaultAccount && defaultAccount->ownsMinecraft()) { - accountToCheck = defaultAccount; - } else { - for (int i = 0; i < accounts->count(); i++) { - if (const auto account = accounts->at(i); account->ownsMinecraft()) { - accountToCheck = account; - break; - } - } - } - - if (!accountToCheck) { - m_actualLaunchMode = LaunchMode::Demo; - return LaunchDecision::Continue; - } - - auto state = accountToCheck->accountState(); - const bool needsRefresh = - m_wantedLaunchMode == LaunchMode::Normal && (state == AccountState::Offline || accountToCheck->shouldRefresh()); - if (state == AccountState::Unchecked || state == AccountState::Errored || needsRefresh) { - accountToCheck->refresh(); - state = AccountState::Working; - } - - if (state == AccountState::Working) { - // refresh is in progress, we need to wait for it to finish to proceed. - ProgressDialog progDialog(m_parentWidget); - progDialog.setSkipButton(true, tr("Abort")); - - // TODO: this relies on tasks' synchronous signal dispatching nature - // TODO: meaning currentTask can't complete and become null while this code is running - // TODO: this code will produce a race condition when tasks become fully async - auto task = accountToCheck->currentTask(); - progDialog.execWithTask(task.get()); - - if (task->getState() == State::AbortedByUser) { - return LaunchDecision::Abort; - } - - state = accountToCheck->accountState(); - } - - QString reauthReason; - switch (state) { - case AccountState::Errored: - reauthReason = tr("An error occurred while refreshing '%1'").arg(accountToCheck->profileName()); - break; - case AccountState::Expired: - reauthReason = tr("'%1' has expired and needs to be reauthenticated").arg(accountToCheck->profileName()); - break; - case AccountState::Disabled: - reauthReason = tr("The launcher's client identification has changed"); - break; - case AccountState::Gone: - reauthReason = tr("'%1' no longer exists on the servers").arg(accountToCheck->profileName()); - break; - default: - m_actualLaunchMode = - state == AccountState::Online && m_wantedLaunchMode == LaunchMode::Normal ? LaunchMode::Normal : LaunchMode::Offline; - return LaunchDecision::Continue; // All good to go - } - - if (reauthenticateAccount(accountToCheck, reauthReason)) { - return LaunchDecision::Undecided; - } - - return LaunchDecision::Abort; -} - -bool LaunchController::askPlayDemo() const +bool LaunchController::askPlayDemo() { QMessageBox box(m_parentWidget); box.setWindowTitle(tr("Play demo?")); - QString text = m_accountToUse - ? tr("This account does not own Minecraft.\nYou need to purchase the game first to play the full version.") - : tr("No account was selected for launch."); - text += tr("\n\nDo you want to play the demo?"); - box.setText(text); + box.setText( + tr("This account does not own Minecraft.\nYou need to purchase the game first to play it.\n\nDo you want to play " + "the demo?")); box.setIcon(QMessageBox::Warning); - const auto* demoButton = box.addButton(tr("Play Demo"), QMessageBox::ButtonRole::YesRole); - auto* cancelButton = box.addButton(tr("Cancel"), QMessageBox::ButtonRole::NoRole); + auto demoButton = box.addButton(tr("Play Demo"), QMessageBox::ButtonRole::YesRole); + auto cancelButton = box.addButton(tr("Cancel"), QMessageBox::ButtonRole::NoRole); box.setDefaultButton(cancelButton); box.exec(); return box.clickedButton() == demoButton; } -QString LaunchController::askOfflineName(const QString& playerName, bool* ok) +QString LaunchController::askOfflineName(QString playerName, bool demo, bool* ok) { if (ok != nullptr) { *ok = false; } - QString title, message; - title = tr("Player name"); - switch (m_actualLaunchMode) { - case LaunchMode::Normal: - Q_ASSERT(false); - return ""; - case LaunchMode::Demo: - message = tr("Choose your demo mode player name"); - break; - case LaunchMode::Offline: - if (m_wantedLaunchMode == LaunchMode::Normal) { - auto netErr = m_accountToUse->accountData()->networkError; - if (Net::isServerError(netErr)) { - title = tr("Auth servers offline"); - message = tr("The Minecraft authentication servers are currently unavailable, launching in offline mode.\n\n"); - } else { - title = tr("No internet connection"); - message = tr("You are not connected to the Internet, launching in offline mode.\n\n"); - } - } - message += tr("Choose your offline mode player name"); - break; + // we ask the user for a player name + QString message = tr("Choose your offline mode player name."); + if (demo) { + message = tr("Choose your demo mode player name."); } - const QString lastOfflinePlayerName = APPLICATION->settings()->get("LastOfflinePlayerName").toString(); + QString lastOfflinePlayerName = APPLICATION->settings()->get("LastOfflinePlayerName").toString(); QString usedname = lastOfflinePlayerName.isEmpty() ? playerName : lastOfflinePlayerName; ChooseOfflineNameDialog dialog(message, m_parentWidget); - dialog.setWindowTitle(title); + dialog.setWindowTitle(tr("Player name")); dialog.setUsername(usedname); if (dialog.exec() != QDialog::Accepted) { return {}; } - usedname = dialog.getUsername(); + const QString name = dialog.getUsername(); + usedname = name; APPLICATION->settings()->set("LastOfflinePlayerName", usedname); if (ok != nullptr) { @@ -279,79 +184,192 @@ void LaunchController::login() { decideAccount(); - LaunchDecision decision = decideLaunchMode(); - while (decision == LaunchDecision::Undecided) { - decision = decideLaunchMode(); - } - if (decision == LaunchDecision::Abort) { - emitAborted(); - return; - } - - if (m_actualLaunchMode == LaunchMode::Demo) { - if (m_wantedLaunchMode == LaunchMode::Demo || askPlayDemo()) { + if (!m_accountToUse) { + // if no account is selected, ask about demo + if (!m_demo) { + m_demo = askPlayDemo(); + } + if (m_demo) { + // we ask the user for a player name bool ok = false; - auto name = askOfflineName("Player", &ok); + auto name = askOfflineName("Player", m_demo, &ok); if (ok) { m_session = std::make_shared(); - m_session->MakeDemo(name, MinecraftAccount::uuidFromUsername(name).toString(QUuid::Id128)); + static const QRegularExpression s_removeChars("[{}-]"); + m_session->MakeDemo(name, MinecraftAccount::uuidFromUsername(name).toString().remove(s_removeChars)); launchInstance(); return; } } - - emitFailed(tr("No account selected for launch")); + // if no account is selected, we bail + emitFailed(tr("No account selected for launch.")); return; } - m_session = std::make_shared(); - m_session->launchMode = m_actualLaunchMode; - m_accountToUse->fillSession(m_session); + // we loop until the user succeeds in logging in or gives up + bool tryagain = true; + unsigned int tries = 0; - if (m_accountToUse->accountType() != AccountType::Offline) { - if (m_actualLaunchMode == LaunchMode::Normal && !m_accountToUse->hasProfile()) { - // Now handle setting up a profile name here... - if (ProfileSetupDialog dialog(m_accountToUse, m_parentWidget); dialog.exec() != QDialog::Accepted) { + 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 + m_accountToUse->refresh(); + } + while (tryagain) { + if (tries > 0 && tries % 3 == 0) { + auto result = + QMessageBox::question(m_parentWidget, tr("Continue launch?"), + tr("It looks like we couldn't launch after %1 tries. Usually this can be fixed by logging out and " + "logging back in your Microsoft account. If that doesn't work, Minecraft authentication servers " + "may be having an outage or you may need a VPN in your region. Do you want to continue trying?") + .arg(tries)); + + if (result == QMessageBox::No) { emitAborted(); return; } } + tries++; + m_session = std::make_shared(); + m_session->wants_online = m_online; + m_session->demo = m_demo; + m_accountToUse->fillSession(m_session); - if (m_actualLaunchMode == LaunchMode::Offline && m_accountToUse->accountType() != AccountType::Offline) { - bool ok = false; - QString name = m_offlineName; - if (name.isEmpty()) { - name = askOfflineName(m_session->player_name, &ok); - if (!ok) { - emitAborted(); - return; - } + MinecraftAccountPtr accountToCheck; + + if (m_accountToUse->ownsMinecraft()) + accountToCheck = m_accountToUse; + else if (const MinecraftAccountPtr defaultAccount = APPLICATION->accounts()->defaultAccount(); + defaultAccount != nullptr && defaultAccount->ownsMinecraft()) { + accountToCheck = defaultAccount; + } else { + for (int i = 0; i < APPLICATION->accounts()->count(); i++) { + MinecraftAccountPtr account = APPLICATION->accounts()->at(i); + if (account->ownsMinecraft()) + accountToCheck = account; + } + } + + if (accountToCheck == nullptr) { + if (!m_session->demo) + m_session->demo = askPlayDemo(); + + if (m_session->demo) + launchInstance(); + else + emitFailed(tr("Launch cancelled - account does not own Minecraft.")); + + return; + } + + switch (accountToCheck->accountState()) { + case AccountState::Offline: { + m_session->wants_online = false; + } + /* fallthrough */ + case AccountState::Online: { + if (!m_session->wants_online && m_accountToUse->accountType() != AccountType::Offline) { + // we ask the user for a player name + bool ok = false; + QString name; + if (m_offlineName.isEmpty()) { + name = askOfflineName(m_session->player_name, m_session->demo, &ok); + if (!ok) { + tryagain = false; + break; + } + } else { + name = m_offlineName; + } + m_session->MakeOffline(name); + // offline flavored game from here :3 + } else if (m_accountToUse == accountToCheck && !m_accountToUse->hasProfile()) { + // Now handle setting up a profile name here... + ProfileSetupDialog dialog(m_accountToUse, m_parentWidget); + if (dialog.exec() == QDialog::Accepted) { + tryagain = true; + continue; + } else { + emitFailed(tr("Received undetermined session status during login.")); + return; + } + } + + if (m_accountToUse->accountType() == AccountType::Offline) + m_session->wants_online = false; + + // we own Minecraft, there is a profile, it's all ready to go! + launchInstance(); + return; + } + case AccountState::Errored: + // This means some sort of soft error that we can fix with a refresh ... so let's refresh. + case AccountState::Unchecked: { + accountToCheck->refresh(); + } + /* fallthrough */ + case AccountState::Working: { + // refresh is in progress, we need to wait for it to finish to proceed. + ProgressDialog progDialog(m_parentWidget); + 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: { + if (reauthenticateAccount(accountToCheck)) + continue; + return; + } + case AccountState::Disabled: { + auto errorString = tr("The launcher's client identification has changed. Please remove '%1' and try again.") + .arg(accountToCheck->profileName()); + + QMessageBox::warning(m_parentWidget, tr("Client identification changed"), errorString, QMessageBox::StandardButton::Ok, + QMessageBox::StandardButton::Ok); + emitFailed(errorString); + return; + } + case AccountState::Gone: { + auto errorString = + tr("'%1' no longer exists on the servers. It may have been migrated, in which case please add the new account " + "you migrated this one to.") + .arg(accountToCheck->profileName()); + QMessageBox::warning(m_parentWidget, tr("Account gone"), errorString, QMessageBox::StandardButton::Ok, + QMessageBox::StandardButton::Ok); + emitFailed(errorString); + return; } - m_session->MakeOffline(name); } } - - launchInstance(); + emitFailed(tr("Failed to launch.")); } -bool LaunchController::reauthenticateAccount(const MinecraftAccountPtr& account, const QString& reason) +bool LaunchController::reauthenticateAccount(MinecraftAccountPtr account) { auto button = QMessageBox::warning( - m_parentWidget, tr("Account refresh failed"), tr("%1. Do you want to reauthenticate this account?").arg(reason), + m_parentWidget, tr("Account refresh failed"), + tr("'%1' has expired and needs to be reauthenticated. Do you want to reauthenticate this account?").arg(account->profileName()), QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, QMessageBox::StandardButton::Yes); if (button == QMessageBox::StandardButton::Yes) { - auto* accounts = APPLICATION->accounts(); - const bool isDefault = accounts->defaultAccount() == account; + auto accounts = APPLICATION->accounts(); + bool isDefault = accounts->defaultAccount() == account; + accounts->removeAccount(accounts->index(accounts->findAccountByProfileId(account->profileId()))); if (account->accountType() == AccountType::MSA) { auto newAccount = MSALoginDialog::newAccount(m_parentWidget); if (newAccount != nullptr) { - accounts->removeAccount(accounts->index(accounts->findAccountByProfileId(account->profileId()))); accounts->addAccount(newAccount); - if (isDefault) { + if (isDefault) accounts->setDefaultAccount(newAccount); - } if (m_accountToUse == account) { m_accountToUse = nullptr; @@ -362,13 +380,14 @@ bool LaunchController::reauthenticateAccount(const MinecraftAccountPtr& account, } } + emitFailed(tr("The account has expired and needs to be reauthenticated")); return false; } void LaunchController::launchInstance() { - Q_ASSERT(m_instance != nullptr); - Q_ASSERT(m_session.get() != nullptr); + Q_ASSERT_X(m_instance != NULL, "launchInstance", "instance is NULL"); + Q_ASSERT_X(m_session.get() != nullptr, "launchInstance", "session is NULL"); if (!m_instance->reloadSettings()) { QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Couldn't load the instance profile.")); @@ -382,36 +401,37 @@ void LaunchController::launchInstance() return; } - const auto* console = qobject_cast(m_parentWidget); - const auto showConsole = m_instance->settings()->get("ShowConsole").toBool(); + auto console = qobject_cast(m_parentWidget); + auto showConsole = m_instance->settings()->get("ShowConsole").toBool(); if (!console && showConsole) { APPLICATION->showInstanceWindow(m_instance); } - connect(m_launcher, &LaunchTask::readyForLaunch, this, &LaunchController::readyForLaunch); - connect(m_launcher, &LaunchTask::succeeded, this, &LaunchController::onSucceeded); - connect(m_launcher, &LaunchTask::failed, this, &LaunchController::onFailed); - connect(m_launcher, &LaunchTask::requestProgress, this, &LaunchController::onProgressRequested); + connect(m_launcher.get(), &LaunchTask::readyForLaunch, this, &LaunchController::readyForLaunch); + connect(m_launcher.get(), &LaunchTask::succeeded, this, &LaunchController::onSucceeded); + connect(m_launcher.get(), &LaunchTask::failed, this, &LaunchController::onFailed); + connect(m_launcher.get(), &LaunchTask::requestProgress, this, &LaunchController::onProgressRequested); // Prepend Online and Auth Status QString online_mode; - if (m_actualLaunchMode == LaunchMode::Normal) { + if (m_session->wants_online) { online_mode = "online"; // Prepend Server Status - const QStringList servers = { "login.microsoftonline.com", "session.minecraft.net", "textures.minecraft.net", "api.mojang.com" }; + QStringList servers = { "login.microsoftonline.com", "session.minecraft.net", "textures.minecraft.net", "api.mojang.com" }; - m_launcher->prependStep(makeShared(m_launcher, servers)); + m_launcher->prependStep(makeShared(m_launcher.get(), servers)); } else { - online_mode = m_actualLaunchMode == LaunchMode::Demo ? "demo" : "offline"; + online_mode = m_demo ? "demo" : "offline"; } - m_launcher->prependStep(makeShared(m_launcher, "Launched instance in " + online_mode + " mode\n", MessageLevel::Launcher)); + m_launcher->prependStep( + makeShared(m_launcher.get(), "Launched instance in " + online_mode + " mode\n", MessageLevel::Launcher)); // Prepend Version { auto versionString = QString("%1 version: %2 (%3)") .arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString(), BuildConfig.BUILD_PLATFORM); - m_launcher->prependStep(makeShared(m_launcher, versionString + "\n", MessageLevel::Launcher)); + m_launcher->prependStep(makeShared(m_launcher.get(), versionString + "\n\n", MessageLevel::Launcher)); } m_launcher->start(); } @@ -468,10 +488,10 @@ void LaunchController::onFailed(QString reason) if (m_instance->settings()->get("ShowConsoleOnError").toBool()) { APPLICATION->showInstanceWindow(m_instance, "console"); } - emitFailed(std::move(reason)); + emitFailed(reason); } -void LaunchController::onProgressRequested(Task* task) const +void LaunchController::onProgressRequested(Task* task) { ProgressDialog progDialog(m_parentWidget); progDialog.setSkipButton(true, tr("Abort")); diff --git a/launcher/LaunchController.h b/launcher/LaunchController.h index bc0d14e0f..50b72eb18 100644 --- a/launcher/LaunchController.h +++ b/launcher/LaunchController.h @@ -36,30 +36,30 @@ #pragma once #include #include +#include #include "minecraft/auth/MinecraftAccount.h" #include "minecraft/launch/MinecraftTarget.h" class InstanceWindow; - -enum class LaunchDecision { Undecided, Continue, Abort }; - class LaunchController : public Task { Q_OBJECT public: void executeTask() override; LaunchController(); - ~LaunchController() override = default; + virtual ~LaunchController() = default; - void setInstance(BaseInstance* instance) { m_instance = instance; } + void setInstance(InstancePtr instance) { m_instance = instance; } - BaseInstance* instance() const { return m_instance; } + InstancePtr instance() { return m_instance; } - void setLaunchMode(const LaunchMode mode) { m_wantedLaunchMode = mode; } + void setOnline(bool online) { m_online = online; } void setOfflineName(const QString& offlineName) { m_offlineName = offlineName; } + void setDemo(bool demo) { m_demo = demo; } + void setProfiler(BaseProfilerFactory* profiler) { m_profiler = profiler; } void setParentWidget(QWidget* widget) { m_parentWidget = widget; } @@ -68,7 +68,7 @@ class LaunchController : public Task { void setAccountToUse(MinecraftAccountPtr accountToUse) { m_accountToUse = std::move(accountToUse); } - QString id() const { return m_instance->id(); } + QString id() { return m_instance->id(); } bool abort() override; @@ -76,28 +76,27 @@ class LaunchController : public Task { void login(); void launchInstance(); void decideAccount(); - LaunchDecision decideLaunchMode(); - bool askPlayDemo() const; - QString askOfflineName(const QString& playerName, bool* ok = nullptr); - bool reauthenticateAccount(const MinecraftAccountPtr& account, const QString& reason); + bool askPlayDemo(); + QString askOfflineName(QString playerName, bool demo, bool* ok = nullptr); + bool reauthenticateAccount(MinecraftAccountPtr account); private slots: void readyForLaunch(); void onSucceeded(); void onFailed(QString reason); - void onProgressRequested(Task* task) const; + void onProgressRequested(Task* task); private: - LaunchMode m_wantedLaunchMode = LaunchMode::Normal; - LaunchMode m_actualLaunchMode = LaunchMode::Normal; BaseProfilerFactory* m_profiler = nullptr; + bool m_online = true; QString m_offlineName; - BaseInstance* m_instance = nullptr; + bool m_demo = false; + InstancePtr m_instance; QWidget* m_parentWidget = nullptr; InstanceWindow* m_console = nullptr; MinecraftAccountPtr m_accountToUse = nullptr; - AuthSessionPtr m_session = nullptr; - LaunchTask* m_launcher = nullptr; - MinecraftTarget::Ptr m_targetToJoin = nullptr; + AuthSessionPtr m_session; + shared_qobject_ptr m_launcher; + MinecraftTarget::Ptr m_targetToJoin; }; diff --git a/launcher/LaunchMode.h b/launcher/LaunchMode.h deleted file mode 100644 index 45cfe50ce..000000000 --- a/launcher/LaunchMode.h +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2026 Octol1ttle - * - * 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 - -enum class LaunchMode { - Normal, - Offline, - Demo, -}; diff --git a/launcher/Launcher.in b/launcher/Launcher.in index 28ba32bf8..0e84bdd9d 100755 --- a/launcher/Launcher.in +++ b/launcher/Launcher.in @@ -15,18 +15,12 @@ fi LAUNCHER_NAME=@Launcher_APP_BINARY_NAME@ -LAUNCHER_ENVNAME=@Launcher_ENVName@ LAUNCHER_DIR="$(dirname "$(readlink -f "$0")")" echo "Launcher Dir: ${LAUNCHER_DIR}" # Makes the launcher use portals for file picking export QT_QPA_PLATFORMTHEME=xdgdesktopportal -# disable OpenGL and Vulkan launcher features on sharun until https://github.com/VHSgunzo/sharun/issues/35 -if [[ -f "${LAUNCHER_DIR}/sharun" ]]; then - export ${LAUNCHER_ENVNAME}_DISABLE_GLVULKAN=1 -fi - # Just to be sure... chmod +x "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" diff --git a/launcher/LoggedProcess.cpp b/launcher/LoggedProcess.cpp index c5ef47d7b..bae45ad88 100644 --- a/launcher/LoggedProcess.cpp +++ b/launcher/LoggedProcess.cpp @@ -114,7 +114,7 @@ void LoggedProcess::on_error(QProcess::ProcessError error) { switch (error) { case QProcess::FailedToStart: { - emit log({ tr("The process failed to start: %1").arg(errorString()) }, MessageLevel::Fatal); + emit log({ tr("The process failed to start.") }, MessageLevel::Fatal); changeState(LoggedProcess::FailedToStart); break; } diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 64be89643..5e962d12a 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -233,7 +233,7 @@ std::optional extractSubDir(ArchiveReader* zip, const QString& subd << target; return false; } - if (!f->writeFile(ext, target_file_path, target)) { + if (!f->writeFile(ext, target_file_path)) { qWarning() << "Failed to extract file" << original_name << "to" << target_file_path; return false; } diff --git a/launcher/MTPixmapCache.h b/launcher/MTPixmapCache.h index 97db598de..0ba9c5ac8 100644 --- a/launcher/MTPixmapCache.h +++ b/launcher/MTPixmapCache.h @@ -14,26 +14,26 @@ else \ type = Qt::DirectConnection; -#define DEFINE_FUNC_NO_PARAM(NAME, RET_TYPE, RET_DEF) \ +#define DEFINE_FUNC_NO_PARAM(NAME, RET_TYPE) \ static RET_TYPE NAME() \ { \ - RET_TYPE ret = RET_DEF; \ + RET_TYPE ret; \ GET_TYPE() \ QMetaObject::invokeMethod(s_instance, "_" #NAME, type, Q_RETURN_ARG(RET_TYPE, ret)); \ return ret; \ } -#define DEFINE_FUNC_ONE_PARAM(NAME, RET_TYPE, RET_DEF, PARAM_1_TYPE) \ +#define DEFINE_FUNC_ONE_PARAM(NAME, RET_TYPE, PARAM_1_TYPE) \ static RET_TYPE NAME(PARAM_1_TYPE p1) \ { \ - RET_TYPE ret = RET_DEF; \ + RET_TYPE ret; \ GET_TYPE() \ QMetaObject::invokeMethod(s_instance, "_" #NAME, type, Q_RETURN_ARG(RET_TYPE, ret), Q_ARG(PARAM_1_TYPE, p1)); \ return ret; \ } -#define DEFINE_FUNC_TWO_PARAM(NAME, RET_TYPE, RET_DEF, PARAM_1_TYPE, PARAM_2_TYPE) \ +#define DEFINE_FUNC_TWO_PARAM(NAME, RET_TYPE, PARAM_1_TYPE, PARAM_2_TYPE) \ static RET_TYPE NAME(PARAM_1_TYPE p1, PARAM_2_TYPE p2) \ { \ - RET_TYPE ret = RET_DEF; \ + RET_TYPE ret; \ GET_TYPE() \ QMetaObject::invokeMethod(s_instance, "_" #NAME, type, Q_RETURN_ARG(RET_TYPE, ret), Q_ARG(PARAM_1_TYPE, p1), \ Q_ARG(PARAM_2_TYPE, p2)); \ @@ -53,18 +53,18 @@ class PixmapCache final : public QObject { static void setInstance(PixmapCache* i) { s_instance = i; } public: - DEFINE_FUNC_NO_PARAM(cacheLimit, int, -1) - DEFINE_FUNC_NO_PARAM(clear, bool, false) - DEFINE_FUNC_TWO_PARAM(find, bool, false, const QString&, QPixmap*) - DEFINE_FUNC_TWO_PARAM(find, bool, false, const QPixmapCache::Key&, QPixmap*) - DEFINE_FUNC_TWO_PARAM(insert, bool, false, const QString&, const QPixmap&) - DEFINE_FUNC_ONE_PARAM(insert, QPixmapCache::Key, {}, const QPixmap&) - DEFINE_FUNC_ONE_PARAM(remove, bool, false, const QString&) - DEFINE_FUNC_ONE_PARAM(remove, bool, false, const QPixmapCache::Key&) - DEFINE_FUNC_TWO_PARAM(replace, bool, false, const QPixmapCache::Key&, const QPixmap&) - DEFINE_FUNC_ONE_PARAM(setCacheLimit, bool, false, int) - DEFINE_FUNC_NO_PARAM(markCacheMissByEviciton, bool, false) - DEFINE_FUNC_ONE_PARAM(setFastEvictionThreshold, bool, false, int) + DEFINE_FUNC_NO_PARAM(cacheLimit, int) + DEFINE_FUNC_NO_PARAM(clear, bool) + DEFINE_FUNC_TWO_PARAM(find, bool, const QString&, QPixmap*) + DEFINE_FUNC_TWO_PARAM(find, bool, const QPixmapCache::Key&, QPixmap*) + DEFINE_FUNC_TWO_PARAM(insert, bool, const QString&, const QPixmap&) + DEFINE_FUNC_ONE_PARAM(insert, QPixmapCache::Key, const QPixmap&) + DEFINE_FUNC_ONE_PARAM(remove, bool, const QString&) + DEFINE_FUNC_ONE_PARAM(remove, bool, const QPixmapCache::Key&) + DEFINE_FUNC_TWO_PARAM(replace, bool, const QPixmapCache::Key&, const QPixmap&) + DEFINE_FUNC_ONE_PARAM(setCacheLimit, bool, int) + DEFINE_FUNC_NO_PARAM(markCacheMissByEviciton, bool) + DEFINE_FUNC_ONE_PARAM(setFastEvictionThreshold, bool, int) // NOTE: Every function returns something non-void to simplify the macros. private slots: diff --git a/launcher/LibraryUtils.cpp b/launcher/MangoHud.cpp similarity index 94% rename from launcher/LibraryUtils.cpp rename to launcher/MangoHud.cpp index 4ac038114..d85100207 100644 --- a/launcher/LibraryUtils.cpp +++ b/launcher/MangoHud.cpp @@ -25,7 +25,7 @@ #include "FileSystem.h" #include "Json.h" -#include "LibraryUtils.h" +#include "MangoHud.h" #ifdef __GLIBC__ #ifndef _GNU_SOURCE @@ -36,9 +36,9 @@ #include #endif -namespace LibraryUtils { +namespace MangoHud { -QString findMangoHud() +QString getLibraryString() { /** * Guess MangoHud install location by searching for vulkan layers in this order: @@ -123,7 +123,7 @@ QString findMangoHud() #ifdef __GLIBC__ // Check whether mangohud is usable on a glibc based system - QString libraryPath = find(libraryName); + QString libraryPath = findLibrary(libraryName); if (!libraryPath.isEmpty()) { return libraryPath; } @@ -138,7 +138,7 @@ QString findMangoHud() return {}; } -QString find(QString libName) +QString findLibrary(QString libName) { #ifdef __GLIBC__ const char* library = libName.toLocal8Bit().constData(); @@ -161,11 +161,11 @@ QString find(QString libName) dlclose(handle); return fullPath; #else - qWarning() << "LibraryUtils::find is not implemented on this platform"; + qWarning() << "MangoHud::findLibrary is not implemented on this platform"; return {}; #endif } -} // namespace LibraryUtils +} // namespace MangoHud #ifdef UNDEF_GNU_SOURCE #undef _GNU_SOURCE diff --git a/launcher/LibraryUtils.h b/launcher/MangoHud.h similarity index 87% rename from launcher/LibraryUtils.h rename to launcher/MangoHud.h index 6832a9627..5361999b4 100644 --- a/launcher/LibraryUtils.h +++ b/launcher/MangoHud.h @@ -21,9 +21,9 @@ #include #include -namespace LibraryUtils { +namespace MangoHud { -QString findMangoHud(); +QString getLibraryString(); -QString find(QString libName); -} // namespace LibraryUtils +QString findLibrary(QString libName); +} // namespace MangoHud diff --git a/launcher/NullInstance.h b/launcher/NullInstance.h index 1c2425e16..13ad0b2c5 100644 --- a/launcher/NullInstance.h +++ b/launcher/NullInstance.h @@ -41,8 +41,8 @@ class NullInstance : public BaseInstance { Q_OBJECT public: - NullInstance(SettingsObject* globalSettings, std::unique_ptr settings, const QString& rootDir) - : BaseInstance(globalSettings, std::move(settings), rootDir) + NullInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir) + : BaseInstance(globalSettings, settings, rootDir) { setVersionBroken(true); } @@ -52,7 +52,7 @@ class NullInstance : public BaseInstance { QString getStatusbarDescription() override { return tr("Unknown instance type"); }; QSet traits() const override { return {}; }; QString instanceConfigFolder() const override { return instanceRoot(); }; - LaunchTask* createLaunchTask(AuthSessionPtr, MinecraftTarget::Ptr) override { return nullptr; } + shared_qobject_ptr createLaunchTask(AuthSessionPtr, MinecraftTarget::Ptr) override { return nullptr; } QList createUpdateTask() override { return {}; } QProcessEnvironment createEnvironment() override { return QProcessEnvironment(); } QProcessEnvironment createLaunchEnvironment() override { return QProcessEnvironment(); } diff --git a/launcher/PSaveFile.h b/launcher/PSaveFile.h index 533195e94..67a57a1a2 100644 --- a/launcher/PSaveFile.h +++ b/launcher/PSaveFile.h @@ -67,5 +67,5 @@ class PSaveFile : public QSaveFile { QString m_absoluteFilePath; }; #else -using PSaveFile = QSaveFile; +#define PSaveFile QSaveFile #endif diff --git a/launcher/ResourceDownloadTask.cpp b/launcher/ResourceDownloadTask.cpp index bb44d2495..2919ddd19 100644 --- a/launcher/ResourceDownloadTask.cpp +++ b/launcher/ResourceDownloadTask.cpp @@ -19,52 +19,23 @@ #include "ResourceDownloadTask.h" -#include - #include "Application.h" #include "FileSystem.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" #include "minecraft/mod/ResourceFolderModel.h" #include "minecraft/mod/ShaderPackFolderModel.h" -#include "modplatform/ModIndex.h" #include "modplatform/helpers/HashUtils.h" #include "net/ApiDownload.h" #include "net/ChecksumValidator.h" -namespace { -Net::ModrinthDownloadMeta createModrinthMeta(BaseInstance* instance, QString reason) -{ - auto* mcInstance = dynamic_cast(instance); - if (!mcInstance) { - return {}; - } - - auto* profile = mcInstance->getPackProfile(); - if (!profile) { - return {}; - } - - auto loaders = profile->getModLoadersList(); - - return { - .reason = std::move(reason), - .gameVersion = profile->getComponentVersion("net.minecraft"), - .loader = !loaders.isEmpty() ? ModPlatform::getModLoaderAsString(loaders.first()) : "", - }; -} -} // namespace - ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion version, - ResourceFolderModel* packs, - bool isIndexed, - QString downloadReason) + const std::shared_ptr packs, + bool is_indexed) : m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs) { - if (isIndexed) { + if (is_indexed) { m_update_task.reset(new LocalResourceUpdateTask(m_pack_model->indexDir(), *m_pack, m_pack_version)); connect(m_update_task.get(), &LocalResourceUpdateTask::hasOldResource, this, &ResourceDownloadTask::hasOldResource); @@ -74,9 +45,7 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack, m_filesNetJob.reset(new NetJob(tr("Resource download"), APPLICATION->network())); m_filesNetJob->setStatus(tr("Downloading resource:\n%1").arg(m_pack_version.downloadUrl)); - auto action = Net::ApiDownload::makeFile(m_pack_version.downloadUrl, m_pack_model->dir().absoluteFilePath(getFilename()), - Net::Download::Option::NoOptions, - createModrinthMeta(m_pack_model->instance(), std::move(downloadReason))); + auto action = Net::ApiDownload::makeFile(m_pack_version.downloadUrl, m_pack_model->dir().absoluteFilePath(getFilename())); if (!m_pack_version.hash_type.isEmpty() && !m_pack_version.hash.isEmpty()) { switch (Hashing::algorithmFromString(m_pack_version.hash_type)) { case Hashing::Algorithm::Md4: @@ -113,23 +82,21 @@ void ResourceDownloadTask::downloadSucceeded() auto oldName = std::get<0>(to_delete); auto oldFilename = std::get<1>(to_delete); - if (oldName.isEmpty() || oldFilename == m_pack_version.fileName) { + if (oldName.isEmpty() || oldFilename == m_pack_version.fileName) return; - } m_pack_model->uninstallResource(oldFilename, true); // also rename the shader config file - if (dynamic_cast(m_pack_model) != nullptr) { + if (dynamic_cast(m_pack_model.get()) != nullptr) { QFileInfo oldConfig(m_pack_model->dir(), oldFilename + ".txt"); QFileInfo newConfig(m_pack_model->dir(), getFilename() + ".txt"); if (oldConfig.exists() && !newConfig.exists()) { bool success = FS::move(oldConfig.filePath(), newConfig.filePath()); - if (!success) { + if (!success) emit logWarning(tr("Failed to rename shader config from '%1' to '%2'").arg(oldConfig.fileName(), newConfig.fileName())); - } } } } @@ -137,7 +104,7 @@ void ResourceDownloadTask::downloadSucceeded() void ResourceDownloadTask::downloadFailed(QString reason) { m_filesNetJob.reset(); - emitFailed(std::move(reason)); + emitFailed(reason); } void ResourceDownloadTask::downloadProgressChanged(qint64 current, qint64 total) @@ -147,7 +114,7 @@ void ResourceDownloadTask::downloadProgressChanged(qint64 current, qint64 total) // This indirection is done so that we don't delete a mod before being sure it was // downloaded successfully! -void ResourceDownloadTask::hasOldResource(const QString& name, const QString& filename) +void ResourceDownloadTask::hasOldResource(QString name, QString filename) { to_delete = { name, filename }; } diff --git a/launcher/ResourceDownloadTask.h b/launcher/ResourceDownloadTask.h index 84324e99a..78fe8efc7 100644 --- a/launcher/ResourceDownloadTask.h +++ b/launcher/ResourceDownloadTask.h @@ -32,9 +32,8 @@ class ResourceDownloadTask : public SequentialTask { public: explicit ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion version, - ResourceFolderModel* packs, - bool isIndexed = true, - QString downloadReason = "standalone"); + std::shared_ptr packs, + bool is_indexed = true); const QString& getFilename() const { return m_pack_version.fileName; } const QVariant& getVersionID() const { return m_pack_version.fileId; } const ModPlatform::IndexedVersion& getVersion() const { return m_pack_version; } @@ -45,7 +44,7 @@ class ResourceDownloadTask : public SequentialTask { private: ModPlatform::IndexedPack::Ptr m_pack; ModPlatform::IndexedVersion m_pack_version; - ResourceFolderModel* m_pack_model; + const std::shared_ptr m_pack_model; NetJob::Ptr m_filesNetJob; LocalResourceUpdateTask::Ptr m_update_task; @@ -57,5 +56,5 @@ class ResourceDownloadTask : public SequentialTask { std::tuple to_delete{ "", "" }; private slots: - void hasOldResource(const QString& name, const QString& filename); + void hasOldResource(QString name, QString filename); }; diff --git a/launcher/RuntimeContext.h b/launcher/RuntimeContext.h index 84a56a892..85304a5bc 100644 --- a/launcher/RuntimeContext.h +++ b/launcher/RuntimeContext.h @@ -41,7 +41,7 @@ struct RuntimeContext { return javaRealArchitecture; } - void updateFromInstanceSettings(SettingsObject* instanceSettings) + void updateFromInstanceSettings(SettingsObjectPtr instanceSettings) { javaArchitecture = instanceSettings->get("JavaArchitecture").toString(); javaRealArchitecture = instanceSettings->get("JavaRealArchitecture").toString(); diff --git a/launcher/SysInfo.cpp b/launcher/SysInfo.cpp index d02b1d854..cfcf63805 100644 --- a/launcher/SysInfo.cpp +++ b/launcher/SysInfo.cpp @@ -1,54 +1,20 @@ - -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 r58Playz - * Copyright (C) 2024 timoreo - * Copyright (C) 2024 Trial97 - * 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 - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - +#include #include - -#include "HardwareInfo.h" - +#include "sys.h" #ifdef Q_OS_MACOS #include +#endif +#include +#include +#include +#include +#ifdef Q_OS_MACOS bool rosettaDetect() { int ret = 0; size_t size = sizeof(ret); - if (sysctlbyname("sysctl.proc_translated", &ret, &size, nullptr, 0) == -1) { + if (sysctlbyname("sysctl.proc_translated", &ret, &size, NULL, 0) == -1) { return false; } return ret == 1; @@ -85,13 +51,18 @@ QString useQTForArch() return QSysInfo::currentCpuArchitecture(); } -int defaultMaxJvmMem() +int suitableMaxMem() { + float totalRAM = (float)Sys::getSystemRam() / (float)Sys::mebibyte; + int maxMemoryAlloc; + // If totalRAM < 6GB, use (totalRAM / 1.5), else 4GB - if (const uint64_t totalRAM = HardwareInfo::totalRamMiB(); totalRAM < (4096 * 1.5)) - return totalRAM / 1.5; + if (totalRAM < (4096 * 1.5)) + maxMemoryAlloc = (int)(totalRAM / 1.5); else - return 4096; + maxMemoryAlloc = 4096; + + return maxMemoryAlloc; } QString getSupportedJavaArchitecture() diff --git a/launcher/SysInfo.h b/launcher/SysInfo.h index da23c5be1..f6c04d702 100644 --- a/launcher/SysInfo.h +++ b/launcher/SysInfo.h @@ -1,12 +1,9 @@ #pragma once - -#include - #include namespace SysInfo { QString currentSystem(); QString useQTForArch(); QString getSupportedJavaArchitecture(); -int defaultMaxJvmMem(); +int suitableMaxMem(); } // namespace SysInfo diff --git a/launcher/Usable.h b/launcher/Usable.h index 8cef29868..b0ecd4018 100644 --- a/launcher/Usable.h +++ b/launcher/Usable.h @@ -36,7 +36,7 @@ class Usable { */ class UseLock { public: - UseLock(Usable* usable) : m_usable(usable) + UseLock(shared_qobject_ptr usable) : m_usable(usable) { // this doesn't use shared pointer use count, because that wouldn't be correct. this count is separate. m_usable->incrementUses(); @@ -44,5 +44,5 @@ class UseLock { ~UseLock() { m_usable->decrementUses(); } private: - Usable* m_usable; + shared_qobject_ptr m_usable; }; diff --git a/launcher/Version.cpp b/launcher/Version.cpp index d5496ce1c..bffe5d58a 100644 --- a/launcher/Version.cpp +++ b/launcher/Version.cpp @@ -1,42 +1,124 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2023 flowln - * Copyright (C) 2022 Sefa Eyeoglu - * Copyright (c) 2026 Trial97 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ #include "Version.h" #include #include #include -#include + +Version::Version(QString str) : m_string(std::move(str)) +{ + parse(); +} + +#define VERSION_OPERATOR(return_on_different) \ + bool exclude_our_sections = false; \ + bool exclude_their_sections = false; \ + \ + const auto size = qMax(m_sections.size(), other.m_sections.size()); \ + for (int i = 0; i < size; ++i) { \ + Section sec1 = (i >= m_sections.size()) ? Section() : m_sections.at(i); \ + Section sec2 = (i >= other.m_sections.size()) ? Section() : other.m_sections.at(i); \ + \ + { /* Don't include appendixes in the comparison */ \ + if (sec1.isAppendix()) \ + exclude_our_sections = true; \ + if (sec2.isAppendix()) \ + exclude_their_sections = true; \ + \ + if (exclude_our_sections) { \ + sec1 = Section(); \ + if (sec2.m_isNull) \ + break; \ + } \ + \ + if (exclude_their_sections) { \ + sec2 = Section(); \ + if (sec1.m_isNull) \ + break; \ + } \ + } \ + \ + if (sec1 != sec2) \ + return return_on_different; \ + } + +bool Version::operator<(const Version& other) const +{ + VERSION_OPERATOR(sec1 < sec2) + + return false; +} +bool Version::operator==(const Version& other) const +{ + VERSION_OPERATOR(false) + + return true; +} +bool Version::operator!=(const Version& other) const +{ + return !operator==(other); +} +bool Version::operator<=(const Version& other) const +{ + return *this < other || *this == other; +} +bool Version::operator>(const Version& other) const +{ + return !(*this <= other); +} +bool Version::operator>=(const Version& other) const +{ + return !(*this < other); +} + +void Version::parse() +{ + m_sections.clear(); + QString currentSection; + + if (m_string.isEmpty()) + return; + + auto classChange = [¤tSection](QChar lastChar, QChar currentChar) { + if (lastChar.isNull()) + return false; + if (lastChar.isDigit() != currentChar.isDigit()) + return true; + + const QList s_separators{ '.', '-', '+' }; + if (s_separators.contains(currentChar) && currentSection.at(0) != currentChar) + return true; + + return false; + }; + + currentSection += m_string.at(0); + for (int i = 1; i < m_string.size(); ++i) { + const auto& current_char = m_string.at(i); + if (classChange(m_string.at(i - 1), current_char)) { + if (!currentSection.isEmpty()) + m_sections.append(Section(currentSection)); + currentSection = ""; + } + + currentSection += current_char; + } + + if (!currentSection.isEmpty()) + m_sections.append(Section(currentSection)); +} /// qDebug print support for the Version class QDebug operator<<(QDebug debug, const Version& v) { - const QDebugStateSaver saver(debug); + QDebugStateSaver saver(debug); debug.nospace() << "Version{ string: " << v.toString() << ", sections: [ "; bool first = true; - for (const auto& s : v.m_sections) { - if (!first) { + for (auto s : v.m_sections) { + if (!first) debug.nospace() << ", "; - } - debug.nospace() << s.value; + debug.nospace() << s.m_fullString; first = false; } @@ -44,114 +126,3 @@ QDebug operator<<(QDebug debug, const Version& v) return debug; } - -std::strong_ordering Version::Section::operator<=>(const Section& other) const -{ - // If both components are numeric, compare numerically (codepoint-wise) - if (this->t == Type::Numeric && other.t == Type::Numeric) { - auto aLen = this->value.size(); - if (aLen != other.value.size()) { - // Lengths differ; compare by length - return aLen <=> other.value.size(); - } - // Compare by digits - auto cmp = QString::compare(this->value, other.value); - if (cmp < 0) { - return std::strong_ordering::less; - } - if (cmp > 0) { - return std::strong_ordering::greater; - } - return std::strong_ordering::equal; - } - // One or both are null - if (this->t == Type::Null) { - if (other.t == Type::PreRelease) { - return std::strong_ordering::greater; - } - return std::strong_ordering::less; - } - if (other.t == Type::Null) { - if (this->t == Type::PreRelease) { - return std::strong_ordering::less; - } - return std::strong_ordering::greater; - } - // Textual comparison (differing type, or both textual/pre-release) - auto minLen = qMin(this->value.size(), other.value.size()); - for (int i = 0; i < minLen; i++) { - auto a = this->value.at(i); - auto b = other.value.at(i); - if (a != b) { - // Compare by rune - return a.unicode() <=> b.unicode(); - } - } - // Compare by length - return this->value.size() <=> other.value.size(); -} - -namespace { -void removeLeadingZeros(QString& s) -{ - s.remove(0, std::distance(s.begin(), std::ranges::find_if_not(s, [](QChar c) { return c == '0'; }))); -} -} // namespace - -void Version::parse() -{ - auto len = m_string.size(); - for (int i = 0; i < len;) { - Section cur(Section::Type::Textual); - auto c = m_string.at(i); - if (c == '+') { - break; // Ignore appendices - } - // custom: the space is special to handle the strings like "1.20 Pre-Release 1" - // this is needed to support Modrinth versions - if (c == '-' || c == ' ') { - // Add dash to component - cur.value += c; - i++; - // If the next rune is non-digit, mark as pre-release (requires >= 1 non-digit after dash so the component has length > 1) - if (i < len && !m_string.at(i).isDigit()) { - cur.t = Section::Type::PreRelease; - } - } else if (c.isDigit()) { - // Mark as numeric - cur.t = Section::Type::Numeric; - } - for (; i < len; i++) { - auto r = m_string.at(i); - if ((r.isDigit() != (cur.t == Section::Type::Numeric)) // starts a new section - || (r == ' ' && cur.t == Section::Type::Numeric) // custom: numeric section then a space is a pre-release - || (r == '-' && cur.t != Section::Type::PreRelease) // "---" is a valid pre-release component - || r == '+') { - // Run completed (do not consume this rune) - break; - } - // Add rune to current run - cur.value += r; - } - if (!cur.value.isEmpty()) { - if (cur.t == Section::Type::Numeric) { - removeLeadingZeros(cur.value); - } - m_sections.append(cur); - } - } -} - -std::strong_ordering Version::operator<=>(const Version& other) const -{ - const auto size = qMax(m_sections.size(), other.m_sections.size()); - for (int i = 0; i < size; ++i) { - auto sec1 = (i >= m_sections.size()) ? Section() : m_sections.at(i); - auto sec2 = (i >= other.m_sections.size()) ? Section() : other.m_sections.at(i); - - if (auto cmp = sec1 <=> sec2; cmp != std::strong_ordering::equal) { - return cmp; - } - } - return std::strong_ordering::equal; -} diff --git a/launcher/Version.h b/launcher/Version.h index c0f70f487..4b5ea7119 100644 --- a/launcher/Version.h +++ b/launcher/Version.h @@ -3,7 +3,6 @@ * Prism Launcher - Minecraft Launcher * Copyright (C) 2023 flowln * Copyright (C) 2022 Sefa Eyeoglu - * Copyright (c) 2026 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 @@ -16,6 +15,23 @@ * * 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 @@ -25,36 +41,115 @@ #include #include -// this implements the FlexVer -// https://git.sleeping.town/exa/FlexVer +class QUrl; + class Version { public: - Version(QString str) : m_string(std::move(str)) { parse(); } // NOLINT(hicpp-explicit-conversions) + Version(QString str); Version() = default; - private: - struct Section { - enum class Type : std::uint8_t { Null, Textual, Numeric, PreRelease }; - explicit Section(Type t = Type::Null, QString value = "") : t(t), value(std::move(value)) {} - Type t; - QString value; - bool operator==(const Section& other) const = default; - std::strong_ordering operator<=>(const Section& other) const; - }; + bool operator<(const Version& other) const; + bool operator<=(const Version& other) const; + bool operator>(const Version& other) const; + bool operator>=(const Version& other) const; + bool operator==(const Version& other) const; + bool operator!=(const Version& other) const; - private: - void parse(); - - public: QString toString() const { return m_string; } bool isEmpty() const { return m_string.isEmpty(); } friend QDebug operator<<(QDebug debug, const Version& v); - bool operator==(const Version& other) const { return (*this <=> other) == std::strong_ordering::equal; } - std::strong_ordering operator<=>(const Version& other) const; + private: + struct Section { + explicit Section(QString fullString) : m_fullString(std::move(fullString)) + { + qsizetype cutoff = m_fullString.size(); + for (int i = 0; i < m_fullString.size(); i++) { + if (!m_fullString[i].isDigit()) { + cutoff = i; + break; + } + } + + auto numPart = QStringView{ m_fullString }.left(cutoff); + + if (!numPart.isEmpty()) { + m_isNull = false; + m_numPart = numPart.toInt(); + } + + auto stringPart = QStringView{ m_fullString }.mid(cutoff); + + if (!stringPart.isEmpty()) { + m_isNull = false; + m_stringPart = stringPart.toString(); + } + } + + explicit Section() = default; + + bool m_isNull = true; + + int m_numPart = 0; + QString m_stringPart; + + QString m_fullString; + + 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 + { + if (m_isNull && !other.m_isNull) + return false; + if (!m_isNull && other.m_isNull) + return false; + + if (!m_isNull && !other.m_isNull) { + return (m_numPart == other.m_numPart) && (m_stringPart == other.m_stringPart); + } + + return true; + } + + inline bool operator<(const Section& other) const + { + static auto unequal_is_less = [](Section const& non_null) -> bool { + if (non_null.m_stringPart.isEmpty()) + return non_null.m_numPart == 0; + return (non_null.m_stringPart != QLatin1Char('.')) && non_null.isPreRelease(); + }; + + if (!m_isNull && other.m_isNull) + return unequal_is_less(*this); + if (m_isNull && !other.m_isNull) + return !unequal_is_less(other); + + if (!m_isNull && !other.m_isNull) { + if (m_numPart < other.m_numPart) + return true; + if (m_numPart == other.m_numPart && m_stringPart < other.m_stringPart) + return true; + + if (!m_stringPart.isEmpty() && other.m_stringPart.isEmpty()) + return false; + if (m_stringPart.isEmpty() && !other.m_stringPart.isEmpty()) + return true; + + return false; + } + + return m_fullString < other.m_fullString; + } + + inline bool operator!=(const Section& other) const { return !(*this == other); } + inline bool operator>(const Section& other) const { return !(*this < other || *this == other); } + }; private: QString m_string; QList
m_sections; -}; \ No newline at end of file + + void parse(); +}; diff --git a/launcher/VersionProxyModel.cpp b/launcher/VersionProxyModel.cpp index aaab7e8e0..32048db8e 100644 --- a/launcher/VersionProxyModel.cpp +++ b/launcher/VersionProxyModel.cpp @@ -198,8 +198,9 @@ QVariant VersionProxyModel::data(const QModelIndex& index, int role) const return tr("Latest"); } } + } else { + return sourceModel()->data(parentIndex, BaseVersionList::VersionIdRole); } - return sourceModel()->data(parentIndex, BaseVersionList::VersionIdRole); } case Qt::DecorationRole: { if (column == Name && hasRecommended) { diff --git a/launcher/archive/ArchiveReader.cpp b/launcher/archive/ArchiveReader.cpp index 764063de3..1f87d8237 100644 --- a/launcher/archive/ArchiveReader.cpp +++ b/launcher/archive/ArchiveReader.cpp @@ -23,10 +23,7 @@ #include #include #include -#include -#include #include -#include namespace MMCZip { QStringList ArchiveReader::getFiles() @@ -37,36 +34,25 @@ QStringList ArchiveReader::getFiles() bool ArchiveReader::collectFiles(bool onlyFiles) { return parse([this, onlyFiles](File* f) { - if (!onlyFiles || f->isFile()) { + if (!onlyFiles || f->isFile()) m_fileNames << f->filename(); - } return f->skip(); }); } -using getPathFunc = std::function; -static QString decodeLibArchivePath(archive_entry* entry, const getPathFunc& getUtf8Path, const getPathFunc& getPath) -{ - auto fileName = QString::fromUtf8(getUtf8Path(entry)); - if (fileName.isEmpty()) { - fileName = QString::fromLocal8Bit(getPath(entry)); - } - return fileName; -} - QString ArchiveReader::File::filename() { - return decodeLibArchivePath(m_entry, archive_entry_pathname_utf8, archive_entry_pathname); + return QString::fromUtf8(archive_entry_pathname_utf8(m_entry)); } QByteArray ArchiveReader::File::readAll(int* outStatus) { QByteArray data; - const void* buff = nullptr; - size_t size = 0; - la_int64_t offset = 0; + const void* buff; + size_t size; + la_int64_t offset; - int status = 0; + int status; while ((status = archive_read_data_block(m_archive.get(), &buff, &size, &offset)) == ARCHIVE_OK) { data.append(static_cast(buff), static_cast(size)); } @@ -92,10 +78,10 @@ int ArchiveReader::File::readNextHeader() return archive_read_next_header(m_archive.get(), &m_entry); } -auto ArchiveReader::goToFile(const QString& filename) -> std::unique_ptr +auto ArchiveReader::goToFile(QString filename) -> std::unique_ptr { auto f = std::make_unique(); - auto* a = f->m_archive.get(); + auto a = f->m_archive.get(); archive_read_support_format_all(a); archive_read_support_filter_all(a); auto fileName = m_archivePath.toStdWString(); @@ -117,16 +103,15 @@ auto ArchiveReader::goToFile(const QString& filename) -> std::unique_ptr static int copy_data(struct archive* ar, struct archive* aw, bool notBlock = false) { - int r = 0; - const void* buff = nullptr; - size_t size = 0; - la_int64_t offset = 0; + int r; + const void* buff; + size_t size; + la_int64_t offset; for (;;) { r = archive_read_data_block(ar, &buff, &size, &offset); - if (r == ARCHIVE_EOF) { - return ARCHIVE_OK; - } + if (r == ARCHIVE_EOF) + return (ARCHIVE_OK); if (r < ARCHIVE_OK) { qCritical() << "Failed reading data block:" << archive_error_string(ar); return (r); @@ -143,43 +128,9 @@ static int copy_data(struct archive* ar, struct archive* aw, bool notBlock = fal } } -static bool willEscapeRoot(const QDir& root, archive_entry* entry) +bool ArchiveReader::File::writeFile(archive* out, QString targetFileName, bool notBlock) { - auto entryPath = decodeLibArchivePath(entry, archive_entry_pathname_utf8, archive_entry_pathname); - auto linkTarget = decodeLibArchivePath(entry, archive_entry_symlink_utf8, archive_entry_symlink); - auto hardLink = decodeLibArchivePath(entry, archive_entry_hardlink_utf8, archive_entry_hardlink); - - if (entryPath.isEmpty() || (linkTarget.isEmpty() && hardLink.isEmpty())) { - return false; - } - - bool isHardLink = false; - if (isHardLink = linkTarget.isEmpty(); isHardLink) { - linkTarget = hardLink; - } - - QString linkFullPath = root.filePath(entryPath); - auto rootDir = QUrl::fromLocalFile(root.absolutePath()); - - if (!rootDir.isParentOf(QUrl::fromLocalFile(linkFullPath))) { - return true; - } - - QDir linkDir = QFileInfo(linkFullPath).dir(); - if (!QDir::isAbsolutePath(linkTarget)) { - linkTarget = (!isHardLink ? linkDir : root).filePath(linkTarget); - } - return !rootDir.isParentOf(QUrl::fromLocalFile(QDir::cleanPath(linkTarget))); -} - -bool ArchiveReader::File::writeFile(archive* out, const QString& targetFileName, bool notBlock) -{ - return writeFile(out, targetFileName, {}, notBlock); -}; - -bool ArchiveReader::File::writeFile(archive* out, const QString& targetFileName, std::optional root, bool notBlock) -{ - auto* entry = m_entry; + auto entry = m_entry; std::unique_ptr entryClone(nullptr, &archive_entry_free); if (!targetFileName.isEmpty()) { entryClone.reset(archive_entry_clone(m_entry)); @@ -187,34 +138,26 @@ bool ArchiveReader::File::writeFile(archive* out, const QString& targetFileName, auto nameUtf8 = targetFileName.toUtf8(); archive_entry_set_pathname_utf8(entry, nameUtf8.constData()); } - if (root.has_value() && willEscapeRoot(root.value(), entry)) { - qCritical() << "Failed to write header to entry:" << filename() << "-" << "file outside root"; - return false; - } if (archive_write_header(out, entry) < ARCHIVE_OK) { - qCritical() << "Failed to write header to entry:" << filename() << "-" << archive_error_string(out) << targetFileName; + qCritical() << "Failed to write header to entry:" << filename() << "-" << archive_error_string(out); return false; - } - if (archive_entry_size(m_entry) > 0) { + } else if (archive_entry_size(m_entry) > 0) { auto r = copy_data(m_archive.get(), out, notBlock); - if (r < ARCHIVE_OK) { + if (r < ARCHIVE_OK) qCritical() << "Failed reading data block:" << archive_error_string(out); - } - if (r < ARCHIVE_WARN) { + if (r < ARCHIVE_WARN) return false; - } } auto r = archive_write_finish_entry(out); - if (r < ARCHIVE_OK) { + if (r < ARCHIVE_OK) qCritical() << "Failed to finish writing entry:" << archive_error_string(out); - } return (r >= ARCHIVE_WARN); } -bool ArchiveReader::parse(const std::function& doStuff) +bool ArchiveReader::parse(std::function doStuff) { auto f = std::make_unique(); - auto* a = f->m_archive.get(); + auto a = f->m_archive.get(); archive_read_support_format_all(a); archive_read_support_filter_all(a); auto fileName = m_archivePath.toStdWString(); @@ -238,7 +181,7 @@ bool ArchiveReader::parse(const std::function& doStuff) return true; } -bool ArchiveReader::parse(const std::function& doStuff) +bool ArchiveReader::parse(std::function doStuff) { return parse([doStuff](File* f, bool&) { return doStuff(f); }); } @@ -262,32 +205,26 @@ QString ArchiveReader::getZipName() bool ArchiveReader::exists(const QString& filePath) const { - if (filePath == QLatin1String("/") || filePath.isEmpty()) { + if (filePath == QLatin1String("/") || filePath.isEmpty()) return true; - } // Normalize input path (remove trailing slash, if any) QString normalizedPath = QDir::cleanPath(filePath); - if (normalizedPath.startsWith('/')) { + if (normalizedPath.startsWith('/')) normalizedPath.remove(0, 1); - } - if (normalizedPath == QLatin1String(".")) { + if (normalizedPath == QLatin1String(".")) return true; - } - if (normalizedPath == QLatin1String("..")) { + if (normalizedPath == QLatin1String("..")) return false; // root only - } // Check for exact file match - if (m_fileNames.contains(normalizedPath, Qt::CaseInsensitive)) { + if (m_fileNames.contains(normalizedPath, Qt::CaseInsensitive)) return true; - } // Check for directory existence by seeing if any file starts with that path QString dirPath = normalizedPath + QLatin1Char('/'); for (const QString& f : m_fileNames) { - if (f.startsWith(dirPath, Qt::CaseInsensitive)) { + if (f.startsWith(dirPath, Qt::CaseInsensitive)) return true; - } } return false; diff --git a/launcher/archive/ArchiveReader.h b/launcher/archive/ArchiveReader.h index 4f11d2e06..aaeba6095 100644 --- a/launcher/archive/ArchiveReader.h +++ b/launcher/archive/ArchiveReader.h @@ -19,11 +19,8 @@ #include #include -#include #include #include -#include -#include struct archive; struct archive_entry; @@ -31,7 +28,7 @@ namespace MMCZip { class ArchiveReader { public: using ArchivePtr = std::unique_ptr; - explicit ArchiveReader(QString fileName) : m_archivePath(std::move(fileName)) {} + ArchiveReader(QString fileName) : m_archivePath(fileName) {} virtual ~ArchiveReader() = default; QStringList getFiles(); @@ -51,8 +48,7 @@ class ArchiveReader { QByteArray readAll(int* outStatus = nullptr); bool skip(); - bool writeFile(archive* out, const QString& targetFileName = "", bool notBlock = false); - bool writeFile(archive* out, const QString& targetFileName, std::optional root, bool notBlock = false); + bool writeFile(archive* out, QString targetFileName = "", bool notBlock = false); private: int readNextHeader(); @@ -63,14 +59,14 @@ class ArchiveReader { archive_entry* m_entry; }; - std::unique_ptr goToFile(const QString& filename); - bool parse(const std::function&); - bool parse(const std::function&); + std::unique_ptr goToFile(QString filename); + bool parse(std::function); + bool parse(std::function); private: QString m_archivePath; size_t m_blockSize = 10240; - QStringList m_fileNames; + QStringList m_fileNames = {}; }; } // namespace MMCZip diff --git a/launcher/archive/ArchiveWriter.cpp b/launcher/archive/ArchiveWriter.cpp index 43dbe4dbd..8436e5486 100644 --- a/launcher/archive/ArchiveWriter.cpp +++ b/launcher/archive/ArchiveWriter.cpp @@ -174,7 +174,7 @@ bool ArchiveWriter::addFile(const QString& fileName, const QString& fileDest) if (fileInfo.isFile() && !fileInfo.isSymLink()) { QFile file(fileInfo.absoluteFilePath()); if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open file:" << fileInfo.filePath() << "error:" << file.errorString(); + qCritical() << "Failed to open file:" << fileInfo.filePath(); return false; } diff --git a/launcher/archive/ExtractZipTask.cpp b/launcher/archive/ExtractZipTask.cpp index 35dc39d90..acbcd39d5 100644 --- a/launcher/archive/ExtractZipTask.cpp +++ b/launcher/archive/ExtractZipTask.cpp @@ -95,7 +95,7 @@ auto ExtractZipTask::extractZip() -> ZipResult return false; } - if (!f->writeFile(ext, target_file_path, target)) { + if (!f->writeFile(ext, target_file_path)) { result = ZipResult(tr("Failed to extract file %1 to %2").arg(original_name, target_file_path)); return false; } diff --git a/launcher/console/WindowsConsole.cpp b/launcher/console/WindowsConsole.cpp index e12183624..4a0eb3d3d 100644 --- a/launcher/console/WindowsConsole.cpp +++ b/launcher/console/WindowsConsole.cpp @@ -24,16 +24,12 @@ #endif #include -#include #include -#include #include #include #include #include -namespace console { - void RedirectHandle(DWORD handle, FILE* stream, const char* mode) { HANDLE stdHandle = GetStdHandle(handle); @@ -161,31 +157,3 @@ std::error_code EnableAnsiSupport() return {}; } - -void FreeWindowsConsole() -{ - fclose(stdout); - fclose(stdin); - fclose(stderr); - FreeConsole(); -} - -WindowsConsoleGuard::WindowsConsoleGuard() : m_consoleAttached(false) -{ - if (console::AttachWindowsConsole()) { - m_consoleAttached = true; - if (auto err = console::EnableAnsiSupport(); err) { - std::cout << "Error setting up ansi console" << err.message() << std::endl; - } - } -} - -WindowsConsoleGuard::~WindowsConsoleGuard() -{ - // Detach from Windows console - if (m_consoleAttached) { - console::FreeWindowsConsole(); - } -} - -} // namespace console diff --git a/launcher/console/WindowsConsole.h b/launcher/console/WindowsConsole.h index 52102217d..4c1f3ee28 100644 --- a/launcher/console/WindowsConsole.h +++ b/launcher/console/WindowsConsole.h @@ -21,24 +21,8 @@ #pragma once -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif - #include -namespace console { + void BindCrtHandlesToStdHandles(bool bindStdIn, bool bindStdOut, bool bindStdErr); bool AttachWindowsConsole(); std::error_code EnableAnsiSupport(); -void FreeWindowsConsole(); - -class WindowsConsoleGuard { - public: - WindowsConsoleGuard(); - ~WindowsConsoleGuard(); - - private: - bool m_consoleAttached; -}; - -} // namespace console diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp index d87a1078b..343f31eaa 100644 --- a/launcher/filelink/FileLink.cpp +++ b/launcher/filelink/FileLink.cpp @@ -34,11 +34,26 @@ #include +#include + +#if defined Q_OS_WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include "console/WindowsConsole.h" +#endif + #include namespace fs = std::filesystem; FileLinkApp::FileLinkApp(int& argc, char** argv) : QCoreApplication(argc, argv), socket(new QLocalSocket(this)) { +#if defined Q_OS_WIN32 + // attach the parent console + if (AttachWindowsConsole()) { + consoleAttached = true; + } +#endif setOrganizationName(BuildConfig.LAUNCHER_NAME); setOrganizationDomain(BuildConfig.LAUNCHER_DOMAIN); setApplicationName(BuildConfig.LAUNCHER_NAME + "FileLink"); @@ -221,4 +236,13 @@ FileLinkApp::~FileLinkApp() qDebug() << "link program shutting down"; // Shut down logger by setting the logger function to nothing qInstallMessageHandler(nullptr); + +#if defined Q_OS_WIN32 + // Detach from Windows console + if (consoleAttached) { + fclose(stdout); + fclose(stdin); + fclose(stderr); + } +#endif } diff --git a/launcher/filelink/FileLink.h b/launcher/filelink/FileLink.h index 25fdb71fc..583d0d43a 100644 --- a/launcher/filelink/FileLink.h +++ b/launcher/filelink/FileLink.h @@ -38,6 +38,7 @@ #include "FileSystem.h" class FileLinkApp : public QCoreApplication { + // friends for the purpose of limiting access to deprecated stuff Q_OBJECT public: enum Status { Starting, Failed, Succeeded, Initialized }; @@ -63,4 +64,8 @@ class FileLinkApp : public QCoreApplication { QList m_links_to_make; QList m_path_results; +#if defined Q_OS_WIN32 + // used on Windows to attach the standard IO streams + bool consoleAttached = false; +#endif }; diff --git a/launcher/filelink/filelink_main.cpp b/launcher/filelink/filelink_main.cpp index d34844370..2a8bcb703 100644 --- a/launcher/filelink/filelink_main.cpp +++ b/launcher/filelink/filelink_main.cpp @@ -22,17 +22,8 @@ #include "FileLink.h" -#if defined Q_OS_WIN32 -#include "console/WindowsConsole.h" -#endif - int main(int argc, char* argv[]) { -#if defined Q_OS_WIN32 - // attach the parent console - console::WindowsConsoleGuard _consoleGuard; -#endif - FileLinkApp ldh(argc, argv); switch (ldh.status()) { diff --git a/launcher/include/base.pch.hpp b/launcher/include/base.pch.hpp index ecaf41fdd..c858857f9 100644 --- a/launcher/include/base.pch.hpp +++ b/launcher/include/base.pch.hpp @@ -3,7 +3,6 @@ #define PRISM_PRECOMPILED_BASE_HEADERS_H #include -#include #include #include #include @@ -13,5 +12,6 @@ #include #include #include +#include #endif // PRISM_PRECOMPILED_BASE_HEADERS_H diff --git a/launcher/include/qtcore.pch.hpp b/launcher/include/qtcore.pch.hpp index b8836618a..d7c24ddb6 100644 --- a/launcher/include/qtcore.pch.hpp +++ b/launcher/include/qtcore.pch.hpp @@ -5,15 +5,9 @@ #include #include #include - -#include -#include - #include -#include #include -#include #include diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp index 3a04756fa..23bd1b73e 100644 --- a/launcher/java/JavaChecker.cpp +++ b/launcher/java/JavaChecker.cpp @@ -163,7 +163,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) auto os_arch = results["os.arch"]; auto java_version = results["java.version"]; auto java_vendor = results["java.vendor"]; - bool is_64 = os_arch == "x86_64" || os_arch == "amd64" || os_arch == "aarch64" || os_arch == "arm64" || os_arch == "riscv64" || os_arch == "ppc64le" || os_arch == "ppc64"; + bool is_64 = os_arch == "x86_64" || os_arch == "amd64" || os_arch == "aarch64" || os_arch == "arm64" || os_arch == "riscv64"; result.validity = Result::Validity::Valid; result.is_64bit = is_64; @@ -179,20 +179,13 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) void JavaChecker::error(QProcess::ProcessError err) { if (err == QProcess::FailedToStart) { - qDebug() << "Java checker has failed to start:" << process->errorString(); + qDebug() << "Java checker has failed to start."; qDebug() << "Process environment:"; qDebug() << process->environment(); qDebug() << "Native environment:"; qDebug() << QProcessEnvironment::systemEnvironment().toStringList(); killTimer.stop(); - - Result result = { - m_path, - m_id, - }; - result.errorLog = process->errorString(); - result.validity = Result::Validity::Errored; - emit checkFinished(result); + emit checkFinished({ m_path, m_id }); } emitSucceeded(); } diff --git a/launcher/java/JavaInstall.cpp b/launcher/java/JavaInstall.cpp index 98aac5cab..30cb77e08 100644 --- a/launcher/java/JavaInstall.cpp +++ b/launcher/java/JavaInstall.cpp @@ -49,7 +49,7 @@ bool JavaInstall::operator<(BaseVersion& a) const { try { return operator<(dynamic_cast(a)); - } catch (const std::bad_cast&) { + } catch (const std::bad_cast& e) { return BaseVersion::operator<(a); } } @@ -58,7 +58,7 @@ bool JavaInstall::operator>(BaseVersion& a) const { try { return operator>(dynamic_cast(a)); - } catch (const std::bad_cast&) { + } catch (const std::bad_cast& e) { return BaseVersion::operator>(a); } } diff --git a/launcher/java/JavaInstall.h b/launcher/java/JavaInstall.h index 5899964f0..d8fd477fd 100644 --- a/launcher/java/JavaInstall.h +++ b/launcher/java/JavaInstall.h @@ -39,6 +39,7 @@ struct JavaInstall : public BaseVersion { JavaVersion id; QString arch; QString path; + bool recommended = false; bool is_64bit = false; }; diff --git a/launcher/java/JavaInstallList.cpp b/launcher/java/JavaInstallList.cpp index 1d7b9cdff..aa7fab8a0 100644 --- a/launcher/java/JavaInstallList.cpp +++ b/launcher/java/JavaInstallList.cpp @@ -41,7 +41,6 @@ #include #include "Application.h" -#include "settings/SettingsObject.h" #include "java/JavaChecker.h" #include "java/JavaInstallList.h" #include "java/JavaUtils.h" @@ -51,9 +50,8 @@ JavaInstallList::JavaInstallList(QObject* parent, bool onlyManagedVersions) : BaseVersionList(parent), m_only_managed_versions(onlyManagedVersions) {} -Task::Ptr JavaInstallList::getLoadTask(bool forceReload) +Task::Ptr JavaInstallList::getLoadTask() { - Q_UNUSED(forceReload) load(); return getCurrentTask(); } @@ -109,7 +107,7 @@ QVariant JavaInstallList::data(const QModelIndex& index, int role) const case VersionRole: return version->id.toString(); case RecommendedRole: - return false; + return version->recommended; case PathRole: return version->path; case CPUArchitectureRole: @@ -129,6 +127,10 @@ void JavaInstallList::updateListData(QList versions) beginResetModel(); m_vlist = versions; sortVersions(); + if (m_vlist.size()) { + auto best = std::dynamic_pointer_cast(m_vlist[0]); + best->recommended = true; + } endResetModel(); m_status = Status::Done; m_load_task.reset(); diff --git a/launcher/java/JavaInstallList.h b/launcher/java/JavaInstallList.h index 58d1ad8ca..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); - Task::Ptr getLoadTask(bool forceReload = false) 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 3647c963f..115baa9e5 100644 --- a/launcher/java/JavaMetadata.cpp +++ b/launcher/java/JavaMetadata.cpp @@ -111,7 +111,7 @@ bool Metadata::operator<(BaseVersion& a) const { try { return operator<(dynamic_cast(a)); - } catch (const std::bad_cast&) { + } catch (const std::bad_cast& e) { return BaseVersion::operator<(a); } } @@ -120,7 +120,7 @@ bool Metadata::operator>(BaseVersion& a) const { try { return operator>(dynamic_cast(a)); - } catch (const std::bad_cast&) { + } catch (const std::bad_cast& e) { return BaseVersion::operator>(a); } } diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index c58fe5601..25fe1caa8 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -42,7 +42,6 @@ #include #include "Application.h" -#include "BuildConfig.h" #include "FileSystem.h" #include "java/JavaInstallList.h" #include "java/JavaUtils.h" @@ -156,7 +155,7 @@ JavaInstallPtr JavaUtils::GetDefaultJava() QStringList addJavasFromEnv(QList javas) { - auto env = QProcessEnvironment::systemEnvironment().value(QStringLiteral("%1_JAVA_PATHS").arg(BuildConfig.LAUNCHER_ENVNAME)); + auto env = qEnvironmentVariable("PRISMLAUNCHER_JAVA_PATHS"); // FIXME: use launcher name from buildconfig #if defined(Q_OS_WIN32) QList javaPaths = env.replace("\\", "/").split(QLatin1String(";")); diff --git a/launcher/java/download/ManifestDownloadTask.cpp b/launcher/java/download/ManifestDownloadTask.cpp index 0a51741a2..28c6a1831 100644 --- a/launcher/java/download/ManifestDownloadTask.cpp +++ b/launcher/java/download/ManifestDownloadTask.cpp @@ -39,8 +39,9 @@ void ManifestDownloadTask::executeTask() { setStatus(tr("Downloading Java")); auto download = makeShared(QString("JRE::DownloadJava"), APPLICATION->network()); + auto files = std::make_shared(); - auto [action, files] = Net::Download::makeByteArray(m_url); + auto action = Net::Download::makeByteArray(m_url, files); if (!m_checksum_hash.isEmpty() && !m_checksum_type.isEmpty()) { auto hashType = QCryptographicHash::Algorithm::Sha1; if (m_checksum_type == "sha256") { diff --git a/launcher/launch/LaunchTask.cpp b/launcher/launch/LaunchTask.cpp index 26b2b582d..eb1a8b37a 100644 --- a/launcher/launch/LaunchTask.cpp +++ b/launcher/launch/LaunchTask.cpp @@ -51,14 +51,14 @@ void LaunchTask::init() m_instance->setRunning(true); } -std::unique_ptr LaunchTask::create(MinecraftInstance* inst) +shared_qobject_ptr LaunchTask::create(MinecraftInstancePtr inst) { - auto task = std::unique_ptr(new LaunchTask(inst)); - task->init(); - return task; + shared_qobject_ptr proc(new LaunchTask(inst)); + proc->init(); + return proc; } -LaunchTask::LaunchTask(MinecraftInstance* instance) : m_instance(instance) {} +LaunchTask::LaunchTask(MinecraftInstancePtr instance) : m_instance(instance) {} void LaunchTask::appendStep(shared_qobject_ptr step) { @@ -180,7 +180,7 @@ bool LaunchTask::abort() return true; case LaunchTask::NotStarted: { state = LaunchTask::Aborted; - emitAborted(); + emitFailed("Aborted"); return true; } case LaunchTask::Running: diff --git a/launcher/launch/LaunchTask.h b/launcher/launch/LaunchTask.h index c52273a9e..db7e453e4 100644 --- a/launcher/launch/LaunchTask.h +++ b/launcher/launch/LaunchTask.h @@ -39,6 +39,7 @@ #include #include #include +#include "BaseInstance.h" #include "LaunchStep.h" #include "LogModel.h" #include "MessageLevel.h" @@ -47,21 +48,21 @@ class LaunchTask : public Task { Q_OBJECT protected: - explicit LaunchTask(MinecraftInstance* instance); + explicit LaunchTask(MinecraftInstancePtr instance); void init(); public: enum State { NotStarted, Running, Waiting, Failed, Aborted, Finished }; public: /* methods */ - static std::unique_ptr create(MinecraftInstance* inst); + static shared_qobject_ptr create(MinecraftInstancePtr inst); virtual ~LaunchTask() = default; void appendStep(shared_qobject_ptr step); void prependStep(shared_qobject_ptr step); void setCensorFilter(QMap filter); - MinecraftInstance* instance() { return m_instance; } + MinecraftInstancePtr instance() { return m_instance; } void setPid(qint64 pid) { m_pid = pid; } @@ -118,7 +119,7 @@ class LaunchTask : public Task { bool parseXmlLogs(QString const& line, MessageLevel level); protected: /* data */ - MinecraftInstance* m_instance; + MinecraftInstancePtr m_instance; shared_qobject_ptr m_logModel; QList> m_steps; QMap m_censorFilter; diff --git a/launcher/launch/TaskStepWrapper.cpp b/launcher/launch/TaskStepWrapper.cpp index acf790a8f..db9e8fad2 100644 --- a/launcher/launch/TaskStepWrapper.cpp +++ b/launcher/launch/TaskStepWrapper.cpp @@ -23,7 +23,10 @@ void TaskStepWrapper::executeTask() return; } connect(m_task.get(), &Task::finished, this, &TaskStepWrapper::updateFinished); - propagateFromOther(m_task.get()); + connect(m_task.get(), &Task::progress, this, &TaskStepWrapper::setProgress); + connect(m_task.get(), &Task::stepProgress, this, &TaskStepWrapper::propagateStepProgress); + connect(m_task.get(), &Task::status, this, &TaskStepWrapper::setStatus); + connect(m_task.get(), &Task::details, this, &TaskStepWrapper::setDetails); emit progressReportingRequest(); } @@ -56,7 +59,9 @@ bool TaskStepWrapper::canAbort() const bool TaskStepWrapper::abort() { if (m_task && m_task->canAbort()) { - return m_task->abort(); + auto status = m_task->abort(); + emitFailed("Aborted."); + return status; } return Task::abort(); } diff --git a/launcher/launch/steps/CheckJava.cpp b/launcher/launch/steps/CheckJava.cpp index 1afd5cd1d..0f8d27e94 100644 --- a/launcher/launch/steps/CheckJava.cpp +++ b/launcher/launch/steps/CheckJava.cpp @@ -36,6 +36,7 @@ #include "CheckJava.h" #include #include +#include #include #include #include @@ -67,7 +68,7 @@ void CheckJava::executeTask() emitFailed(QString("Java path is not valid.")); return; } else { - emit logLine("Java path is:\n " + m_javaPath, MessageLevel::Launcher); + emit logLine("Java path is:\n" + m_javaPath + "\n\n", MessageLevel::Launcher); } if (JavaUtils::getJavaCheckPath().isEmpty()) { @@ -146,6 +147,6 @@ void CheckJava::checkJavaFinished(const JavaChecker::Result& result) void CheckJava::printJavaInfo(const QString& version, const QString& architecture, const QString& realArchitecture, const QString& vendor) { emit logLine( - QString("Java is version %1, using %2 (%3) architecture, from %4").arg(version, architecture, realArchitecture, vendor), + QString("Java is version %1, using %2 (%3) architecture, from %4.\n\n").arg(version, architecture, realArchitecture, vendor), MessageLevel::Launcher); } diff --git a/launcher/launch/steps/LookupServerAddress.cpp b/launcher/launch/steps/LookupServerAddress.cpp index cb2f5d7de..fdd9fc545 100644 --- a/launcher/launch/steps/LookupServerAddress.cpp +++ b/launcher/launch/steps/LookupServerAddress.cpp @@ -38,7 +38,7 @@ void LookupServerAddress::setOutputAddressPtr(MinecraftTarget::Ptr output) bool LookupServerAddress::abort() { m_dnsLookup->abort(); - emitAborted(); + emitFailed("Aborted"); return true; } diff --git a/launcher/launch/steps/PrintServers.cpp b/launcher/launch/steps/PrintServers.cpp index ac0e4bf83..ba96d37b9 100644 --- a/launcher/launch/steps/PrintServers.cpp +++ b/launcher/launch/steps/PrintServers.cpp @@ -34,7 +34,7 @@ void PrintServers::executeTask() void PrintServers::resolveServer(const QHostInfo& host_info) { QString server = host_info.hostName(); - QString addresses = server + " resolves to:\n "; + QString addresses = server + " resolves to:\n ["; if (!host_info.addresses().isEmpty()) { for (QHostAddress address : host_info.addresses()) { @@ -46,7 +46,7 @@ void PrintServers::resolveServer(const QHostInfo& host_info) } else { addresses += "N/A"; } - addresses += "\n"; + addresses += "]\n\n"; m_server_to_address.insert(server, addresses); diff --git a/launcher/launch/steps/TextPrint.cpp b/launcher/launch/steps/TextPrint.cpp index f96d11343..53aff807e 100644 --- a/launcher/launch/steps/TextPrint.cpp +++ b/launcher/launch/steps/TextPrint.cpp @@ -24,6 +24,6 @@ bool TextPrint::canAbort() const bool TextPrint::abort() { - emitAborted(); + emitFailed("Aborted."); return true; } diff --git a/launcher/main.cpp b/launcher/main.cpp index 9378387bb..2bce655d2 100644 --- a/launcher/main.cpp +++ b/launcher/main.cpp @@ -33,23 +33,13 @@ * limitations under the License. */ -#include - #include "Application.h" -#if defined Q_OS_WIN32 -#include "console/WindowsConsole.h" -#endif - int main(int argc, char* argv[]) { -#if defined Q_OS_WIN32 - // used on Windows to attach the standard IO streams - console::WindowsConsoleGuard _consoleGuard; -#endif - // initialize Qt Application app(argc, argv); + switch (app.status()) { case Application::StartingUp: case Application::Initialized: { diff --git a/launcher/meta/BaseEntity.cpp b/launcher/meta/BaseEntity.cpp index 1869e14d3..b0e754ada 100644 --- a/launcher/meta/BaseEntity.cpp +++ b/launcher/meta/BaseEntity.cpp @@ -26,7 +26,6 @@ #include "net/NetJob.h" #include "Application.h" -#include "settings/SettingsObject.h" #include "BuildConfig.h" #include "tasks/Task.h" @@ -82,12 +81,12 @@ QUrl BaseEntity::url() const return QUrl(metaOverride).resolved(localFilename()); } -Task::Ptr BaseEntity::loadTask(Net::Mode mode, bool forceReload) +Task::Ptr BaseEntity::loadTask(Net::Mode mode) { if (m_task && m_task->isRunning()) { return m_task; } - m_task.reset(new BaseEntityLoadTask(this, mode, forceReload)); + m_task.reset(new BaseEntityLoadTask(this, mode)); return m_task; } @@ -107,9 +106,7 @@ BaseEntity::LoadStatus BaseEntity::status() const return m_load_status; } -BaseEntityLoadTask::BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode, bool forceReload) - : m_entity(parent), m_mode(mode), m_force_reload(forceReload) -{} +BaseEntityLoadTask::BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode) : m_entity(parent), m_mode(mode) {} void BaseEntityLoadTask::executeTask() { @@ -127,11 +124,9 @@ void BaseEntityLoadTask::executeTask() } // on online the hash needs to match - const auto& expected = m_entity->m_sha256; - const auto& actual = m_entity->m_file_sha256; - hashMatches = expected == actual; + hashMatches = m_entity->m_sha256 == m_entity->m_file_sha256; if (m_mode == Net::Mode::Online && !m_entity->m_sha256.isEmpty() && !hashMatches) { - throw Exception(QString("Checksum mismatch, expected sha256: %1, got: %2").arg(expected, actual)); + throw Exception("mismatched checksum"); } // load local file @@ -153,18 +148,13 @@ void BaseEntityLoadTask::executeTask() auto wasLoadedOffline = m_entity->m_load_status != BaseEntity::LoadStatus::NotLoaded && m_mode == Net::Mode::Offline; // if has is not present allways fetch from remote(e.g. the main index file), else only fetch if hash doesn't match auto wasLoadedRemote = m_entity->m_sha256.isEmpty() ? m_entity->m_load_status == BaseEntity::LoadStatus::Remote : hashMatches; - if (wasLoadedOffline || (wasLoadedRemote && !m_force_reload)) { + if (wasLoadedOffline || wasLoadedRemote) { emitSucceeded(); return; } m_task.reset(new NetJob(QObject::tr("Download of meta file %1").arg(m_entity->localFilename()), APPLICATION->network())); auto url = m_entity->url(); auto entry = APPLICATION->metacache()->resolveEntry("meta", m_entity->localFilename()); - if (m_force_reload) { - // clear validators so manual refreshes fetch a fresh body - entry->setETag({}); - entry->setRemoteChangedTimestamp({}); - } entry->setStale(true); auto dl = Net::ApiDownload::makeCached(url, entry); /* diff --git a/launcher/meta/BaseEntity.h b/launcher/meta/BaseEntity.h index 32d8bdbb8..17aa0cb87 100644 --- a/launcher/meta/BaseEntity.h +++ b/launcher/meta/BaseEntity.h @@ -43,7 +43,7 @@ class BaseEntity { void setSha256(QString sha256); virtual void parse(const QJsonObject& obj) = 0; - [[nodiscard]] Task::Ptr loadTask(Net::Mode loadType = Net::Mode::Online, bool forceReload = false); + [[nodiscard]] Task::Ptr loadTask(Net::Mode loadType = Net::Mode::Online); protected: QString m_sha256; // the expected sha256 @@ -58,7 +58,7 @@ class BaseEntityLoadTask : public Task { Q_OBJECT public: - explicit BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode, bool forceReload); + explicit BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode); ~BaseEntityLoadTask() override = default; virtual void executeTask() override; @@ -68,7 +68,6 @@ class BaseEntityLoadTask : public Task { private: BaseEntity* m_entity; Net::Mode m_mode; - bool m_force_reload = false; NetJob::Ptr m_task; }; } // namespace Meta diff --git a/launcher/meta/Index.cpp b/launcher/meta/Index.cpp index bd58215c4..d0c7075cd 100644 --- a/launcher/meta/Index.cpp +++ b/launcher/meta/Index.cpp @@ -15,7 +15,6 @@ #include "Index.h" -#include "Application.h" #include "JsonFormat.h" #include "QObjectPtr.h" #include "VersionList.h" @@ -136,7 +135,7 @@ void Index::connectVersionList(const int row, const VersionList::Ptr& list) Task::Ptr Index::loadVersion(const QString& uid, const QString& version, Net::Mode mode, bool force) { - if (mode == Net::Mode::Offline || !APPLICATION->settings()->get("MetaRefreshOnLaunch").toBool()) { + if (mode == Net::Mode::Offline) { return get(uid, version)->loadTask(mode); } diff --git a/launcher/meta/VersionList.cpp b/launcher/meta/VersionList.cpp index fa3a271a6..dfca52d87 100644 --- a/launcher/meta/VersionList.cpp +++ b/launcher/meta/VersionList.cpp @@ -32,11 +32,11 @@ VersionList::VersionList(const QString& uid, QObject* parent) : BaseVersionList( setObjectName("Version list: " + uid); } -Task::Ptr VersionList::getLoadTask(bool forceReload) +Task::Ptr VersionList::getLoadTask() { auto loadTask = makeShared(tr("Load meta for %1", "This is for the task name that loads the meta index.").arg(m_uid)); - loadTask->addTask(APPLICATION->metadataIndex()->loadTask(Net::Mode::Online, forceReload)); - loadTask->addTask(this->loadTask(Net::Mode::Online, forceReload)); + loadTask->addTask(APPLICATION->metadataIndex()->loadTask(Net::Mode::Online)); + loadTask->addTask(this->loadTask(Net::Mode::Online)); return loadTask; } diff --git a/launcher/meta/VersionList.h b/launcher/meta/VersionList.h index 709c361de..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; - Task::Ptr getLoadTask(bool forceReload = false) 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/Agent.h b/launcher/minecraft/Agent.h index 2432679da..bc385a74e 100644 --- a/launcher/minecraft/Agent.h +++ b/launcher/minecraft/Agent.h @@ -4,10 +4,26 @@ #include "Library.h" -struct Agent { +class Agent; + +using AgentPtr = std::shared_ptr; + +class Agent { + public: + Agent(LibraryPtr library, const QString& argument) + { + m_library = library; + m_argument = argument; + } + + public: /* methods */ + LibraryPtr library() { return m_library; } + QString argument() { return m_argument; } + + protected: /* data */ /// The library pointing to the jar this Java agent is contained within - LibraryPtr library; + LibraryPtr m_library; /// The argument to the Java agent, passed after an = if present - QString argument; + QString m_argument; }; diff --git a/launcher/minecraft/AssetsUtils.cpp b/launcher/minecraft/AssetsUtils.cpp index 37d02b5c1..410d1e689 100644 --- a/launcher/minecraft/AssetsUtils.cpp +++ b/launcher/minecraft/AssetsUtils.cpp @@ -52,7 +52,6 @@ #include "Application.h" #include "net/NetRequest.h" -#include "update/AssetUpdateTask.h" namespace { QSet collectPathsFromDir(QString dirPath) @@ -104,7 +103,7 @@ bool loadAssetsIndexJson(const QString& assetsId, const QString& path, AssetsInd // Try to open the file and fail if we can't. // TODO: We should probably report this error to the user. if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to read assets index file" << path << "error:" << file.errorString(); + qCritical() << "Failed to read assets index file" << path; return false; } index.id = assetsId; @@ -299,7 +298,7 @@ QString AssetObject::getLocalPath() QUrl AssetObject::getUrl() { - auto resourceURL = AssetUpdateTask::resourceUrl(); + auto resourceURL = APPLICATION->settings()->get("ResourceURL").toString(); return resourceURL + getRelPath(); } diff --git a/launcher/minecraft/ComponentUpdateTask.cpp b/launcher/minecraft/ComponentUpdateTask.cpp index 73203f74b..bdfdcfbcc 100644 --- a/launcher/minecraft/ComponentUpdateTask.cpp +++ b/launcher/minecraft/ComponentUpdateTask.cpp @@ -38,9 +38,6 @@ * If the component list changes, start over. */ -/* - * TODO: This task launches multiple other tasks. As such it should be converted to a ConcurrentTask - */ ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list) : Task() { d.reset(new ComponentUpdateTaskData); @@ -51,38 +48,9 @@ ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfi ComponentUpdateTask::~ComponentUpdateTask() {} -bool ComponentUpdateTask::canAbort() const -{ - for (const auto& status : d->remoteLoadStatusList) { - if (status.task && !status.task->canAbort()) { - return false; - } - } - - return true; -} - -bool ComponentUpdateTask::abort() -{ - bool aborted = true; - for (const auto& status : d->remoteLoadStatusList) { - if (status.task && !status.task->abort()) { - aborted = false; - } - } - - return aborted; -} - -Net::Mode ComponentUpdateTask::netMode() -{ - return d->netmode; -} - void ComponentUpdateTask::executeTask() { qCDebug(instanceProfileResolveC) << "Loading components"; - setStatus(tr("Loading components")); loadComponents(); } @@ -228,13 +196,12 @@ void ComponentUpdateTask::loadComponents() componentIndex++; } d->remoteTasksInProgress = taskIndex; - m_progressTotal = static_cast(taskIndex); switch (result) { case LoadResult::LoadedLocal: { // Everything got loaded. Advance to dependency resolution. performUpdateActions(); resolveDependencies(d->mode == Mode::Launch || d->netmode == Net::Mode::Offline); - return; + break; } case LoadResult::RequiresRemote: { // we wait for signals. @@ -242,11 +209,9 @@ void ComponentUpdateTask::loadComponents() } case LoadResult::Failed: { emitFailed(tr("Some component metadata load tasks failed.")); - return; + break; } } - - setDetails(tr("Downloading metadata for %1 components").arg(taskIndex)); } namespace { @@ -789,7 +754,6 @@ void ComponentUpdateTask::remoteLoadFailed(size_t taskIndex, const QString& msg) void ComponentUpdateTask::checkIfAllFinished() { - setProgress(m_progress + 1, m_progressTotal); if (d->remoteTasksInProgress) { // not yet... return; diff --git a/launcher/minecraft/ComponentUpdateTask.h b/launcher/minecraft/ComponentUpdateTask.h index 2ef9737ba..64c55877b 100644 --- a/launcher/minecraft/ComponentUpdateTask.h +++ b/launcher/minecraft/ComponentUpdateTask.h @@ -17,12 +17,8 @@ class ComponentUpdateTask : public Task { explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list); virtual ~ComponentUpdateTask(); - bool canAbort() const override; - bool abort() override; - Net::Mode netMode(); - protected: - void executeTask() override; + void executeTask(); private: void loadComponents(); diff --git a/launcher/minecraft/GradleSpecifier.h b/launcher/minecraft/GradleSpecifier.h index 65297abed..a2588064f 100644 --- a/launcher/minecraft/GradleSpecifier.h +++ b/launcher/minecraft/GradleSpecifier.h @@ -38,10 +38,12 @@ #include #include #include +#include "DefaultVariable.h" struct GradleSpecifier { GradleSpecifier() { m_valid = false; } - GradleSpecifier(const QString& value) + GradleSpecifier(QString value) { operator=(value); } + GradleSpecifier& operator=(const QString& value) { /* org.gradle.test.classifiers : service : 1.0 : jdk15 @ jar @@ -60,7 +62,7 @@ struct GradleSpecifier { m_valid = match.hasMatch(); if (!m_valid) { m_invalidValue = value; - return; + return *this; } auto elements = match.captured(); m_groupId = match.captured(1); @@ -70,6 +72,7 @@ struct GradleSpecifier { if (match.lastCapturedIndex() >= 5) { m_extension = match.captured(5); } + return *this; } QString serialize() const { @@ -80,8 +83,8 @@ struct GradleSpecifier { if (!m_classifier.isEmpty()) { retval += ":" + m_classifier; } - if (m_extension.has_value()) { - retval += "@" + m_extension.value(); + if (m_extension.isExplicit()) { + retval += "@" + m_extension; } return retval; } @@ -94,7 +97,7 @@ struct GradleSpecifier { if (!m_classifier.isEmpty()) { filename += "-" + m_classifier; } - filename += "." + m_extension.value_or("jar"); + filename += "." + m_extension; return filename; } QString toPath(const QString& filenameOverride = QString()) const @@ -119,13 +122,26 @@ struct GradleSpecifier { inline QString artifactId() const { return m_artifactId; } inline void setClassifier(const QString& classifier) { m_classifier = classifier; } inline QString classifier() const { return m_classifier; } - inline std::optional extension() const { return m_extension; } + inline QString extension() const { return m_extension; } inline QString artifactPrefix() const { return m_groupId + ":" + m_artifactId; } bool matchName(const GradleSpecifier& other) const { return other.artifactId() == artifactId() && other.groupId() == groupId() && other.classifier() == classifier(); } - bool operator ==(const GradleSpecifier &other) const = default; + bool operator==(const GradleSpecifier& other) const + { + if (m_groupId != other.m_groupId) + return false; + if (m_artifactId != other.m_artifactId) + return false; + if (m_version != other.m_version) + return false; + if (m_classifier != other.m_classifier) + return false; + if (m_extension != other.m_extension) + return false; + return true; + } private: QString m_invalidValue; @@ -133,6 +149,6 @@ struct GradleSpecifier { QString m_artifactId; QString m_version; QString m_classifier; - std::optional m_extension; + DefaultVariable m_extension = DefaultVariable("jar"); bool m_valid = false; }; diff --git a/launcher/minecraft/LaunchProfile.cpp b/launcher/minecraft/LaunchProfile.cpp index fb74d4a9a..c11a0f915 100644 --- a/launcher/minecraft/LaunchProfile.cpp +++ b/launcher/minecraft/LaunchProfile.cpp @@ -213,9 +213,9 @@ void LaunchProfile::applyMavenFile(LibraryPtr mavenFile, const RuntimeContext& r m_mavenFiles.append(Library::limitedCopy(mavenFile)); } -void LaunchProfile::applyAgent(const Agent& agent, const RuntimeContext& runtimeContext) +void LaunchProfile::applyAgent(AgentPtr agent, const RuntimeContext& runtimeContext) { - auto lib = agent.library; + auto lib = agent->library(); if (!lib->isActive(runtimeContext)) { return; } @@ -330,7 +330,7 @@ const QList& LaunchProfile::getMavenFiles() const return m_mavenFiles; } -const QList& LaunchProfile::getAgents() const +const QList& LaunchProfile::getAgents() const { return m_agents; } @@ -349,8 +349,7 @@ void LaunchProfile::getLibraryFiles(const RuntimeContext& runtimeContext, QStringList& jars, QStringList& nativeJars, const QString& overridePath, - const QString& tempPath, - bool addJarMods) const + const QString& tempPath) const { QStringList native32, native64; jars.clear(); @@ -361,7 +360,7 @@ void LaunchProfile::getLibraryFiles(const RuntimeContext& runtimeContext, // NOTE: order is important here, add main jar last to the lists if (m_mainJar) { // FIXME: HACK!! jar modding is weird and unsystematic! - if (m_jarMods.size() && addJarMods) { + if (m_jarMods.size()) { QDir tempDir(tempPath); jars.append(tempDir.absoluteFilePath("minecraft.jar")); } else { diff --git a/launcher/minecraft/LaunchProfile.h b/launcher/minecraft/LaunchProfile.h index 6dc3d9aeb..f1be6fee0 100644 --- a/launcher/minecraft/LaunchProfile.h +++ b/launcher/minecraft/LaunchProfile.h @@ -57,7 +57,7 @@ class LaunchProfile : public ProblemProvider { void applyMods(const QList& jarMods); void applyLibrary(LibraryPtr library, const RuntimeContext& runtimeContext); void applyMavenFile(LibraryPtr library, const RuntimeContext& runtimeContext); - void applyAgent(const Agent& agent, const RuntimeContext& runtimeContext); + void applyAgent(AgentPtr agent, const RuntimeContext& runtimeContext); void applyCompatibleJavaMajors(QList& javaMajor); void applyCompatibleJavaName(QString javaName); void applyMainJar(LibraryPtr jar); @@ -79,7 +79,7 @@ class LaunchProfile : public ProblemProvider { const QList& getLibraries() const; const QList& getNativeLibraries() const; const QList& getMavenFiles() const; - const QList& getAgents() const; + const QList& getAgents() const; const QList& getCompatibleJavaMajors() const; const QString getCompatibleJavaName() const; const LibraryPtr getMainJar() const; @@ -87,8 +87,7 @@ class LaunchProfile : public ProblemProvider { QStringList& jars, QStringList& nativeJars, const QString& overridePath, - const QString& tempPath, - bool addJarMods = true) const; + const QString& tempPath) const; bool hasTrait(const QString& trait) const; ProblemSeverity getProblemSeverity() const override; const QList getProblems() const override; @@ -133,7 +132,7 @@ class LaunchProfile : public ProblemProvider { QList m_mavenFiles; /// the list of java agents to add to JVM arguments - QList m_agents; + QList m_agents; /// the main jar LibraryPtr m_mainJar; diff --git a/launcher/minecraft/Library.cpp b/launcher/minecraft/Library.cpp index 2b43d4389..026f9c281 100644 --- a/launcher/minecraft/Library.cpp +++ b/launcher/minecraft/Library.cpp @@ -149,7 +149,7 @@ QList Library::getDownloads(const RuntimeContext& runtimeC if (sha1.size()) { auto dl = Net::ApiDownload::makeCached(url, entry, options); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, sha1)); - qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url << "expected sha1:" << sha1; + qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url; out.append(dl); } else { out.append(Net::ApiDownload::makeCached(url, entry, options)); diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 8e98a2efe..f8924579a 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -59,7 +59,6 @@ #include "minecraft/launch/AutoInstallJava.h" #include "minecraft/launch/ClaimAccount.h" #include "minecraft/launch/CreateGameFolders.h" -#include "minecraft/launch/EnsureAvailableMemory.h" #include "minecraft/launch/EnsureOfflineLibraries.h" #include "minecraft/launch/ExtractNatives.h" #include "minecraft/launch/LauncherPartLaunch.h" @@ -70,8 +69,8 @@ #include "minecraft/launch/VerifyJavaInstall.h" #include "minecraft/update/AssetUpdateTask.h" +#include "minecraft/update/FMLLibrariesTask.h" #include "minecraft/update/FoldersTask.h" -#include "minecraft/update/LegacyFMLLibrariesTask.h" #include "minecraft/update/LibrariesTask.h" #include "java/JavaUtils.h" @@ -98,7 +97,7 @@ #include #ifdef Q_OS_LINUX -#include "LibraryUtils.h" +#include "MangoHud.h" #endif #ifdef WITH_QTDBUS @@ -131,8 +130,7 @@ for (const auto& gpu : gpus) { QString name = qvariant_cast(gpu[QStringLiteral("Name")]); bool defaultGpu = qvariant_cast(gpu[QStringLiteral("Default")]); - bool discrete = qvariant_cast(gpu.value(QStringLiteral("Discrete"), !defaultGpu)); - if (discrete) { + if (!defaultGpu) { QStringList envList = qvariant_cast(gpu[QStringLiteral("Environment")]); for (int i = 0; i + 1 < envList.size(); i += 2) { env.insert(envList[i], envList[i + 1]); @@ -164,14 +162,12 @@ class OrSetting : public Setting { std::shared_ptr m_b; }; -MinecraftInstance::MinecraftInstance(SettingsObject* globalSettings, std::unique_ptr settings, const QString& rootDir) - : BaseInstance(globalSettings, std::move(settings), rootDir) +MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir) + : BaseInstance(globalSettings, settings, rootDir) { m_components.reset(new PackProfile(this)); } -MinecraftInstance::~MinecraftInstance() {} - void MinecraftInstance::saveNow() { m_components->saveNow(); @@ -210,7 +206,6 @@ void MinecraftInstance::loadSpecificSettings() m_settings->registerOverride(global_settings->getSetting("MinMemAlloc"), memorySetting); m_settings->registerOverride(global_settings->getSetting("MaxMemAlloc"), memorySetting); m_settings->registerOverride(global_settings->getSetting("PermGen"), memorySetting); - m_settings->registerOverride(global_settings->getSetting("LowMemWarning"), memorySetting); // Native library workarounds auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false); @@ -276,7 +271,7 @@ void MinecraftInstance::loadSpecificSettings() void MinecraftInstance::updateRuntimeContext() { - m_runtimeContext.updateFromInstanceSettings(m_settings.get()); + m_runtimeContext.updateFromInstanceSettings(m_settings); m_components->invalidateLaunchProfile(); } @@ -285,9 +280,9 @@ QString MinecraftInstance::typeName() const return "Minecraft"; } -PackProfile* MinecraftInstance::getPackProfile() const +std::shared_ptr MinecraftInstance::getPackProfile() const { - return m_components.get(); + return m_components; } QSet MinecraftInstance::traits() const @@ -315,9 +310,9 @@ void MinecraftInstance::populateLaunchMenu(QMenu* menu) normalLaunchDemo->setEnabled(supportsDemo()); - connect(normalLaunch, &QAction::triggered, [this] { APPLICATION->launch(this); }); - connect(normalLaunchOffline, &QAction::triggered, [this] { APPLICATION->launch(this, LaunchMode::Offline); }); - connect(normalLaunchDemo, &QAction::triggered, [this] { APPLICATION->launch(this, LaunchMode::Demo); }); + connect(normalLaunch, &QAction::triggered, [this] { APPLICATION->launch(shared_from_this()); }); + connect(normalLaunchOffline, &QAction::triggered, [this] { APPLICATION->launch(shared_from_this(), false, false); }); + connect(normalLaunchDemo, &QAction::triggered, [this] { APPLICATION->launch(shared_from_this(), false, true); }); QString profilersTitle = tr("Profilers"); menu->addSeparator()->setText(profilersTitle); @@ -532,10 +527,10 @@ QStringList MinecraftInstance::extraArguments() } } auto agents = m_components->getProfile()->getAgents(); - for (const auto& agent : agents) { + for (auto agent : agents) { QStringList jar, temp1, temp2, temp3; - agent.library->getApplicableFiles(runtimeContext(), jar, temp1, temp2, temp3, getLocalLibraryPath()); - list.append("-javaagent:" + jar[0] + (agent.argument.isEmpty() ? "" : "=" + agent.argument)); + agent->library()->getApplicableFiles(runtimeContext(), jar, temp1, temp2, temp3, getLocalLibraryPath()); + list.append("-javaagent:" + jar[0] + (agent->argument().isEmpty() ? "" : "=" + agent->argument())); } { @@ -571,8 +566,6 @@ QStringList MinecraftInstance::javaArguments() { QStringList args; - args << "-Duser.language=en"; - // custom args go first. we want to override them if we have our own here. args.append(extraArguments()); @@ -626,6 +619,8 @@ QStringList MinecraftInstance::javaArguments() } } + args << "-Duser.language=en"; + if (javaVersion.isModular() && shouldApplyOnlineFixes()) // allow reflective access to java.net - required by the skin fix args << "--add-opens" << "java.base/java.net=ALL-UNNAMED"; @@ -654,7 +649,7 @@ QMap MinecraftInstance::getVariables() out.insert("INST_ID", id()); out.insert("INST_DIR", QDir::toNativeSeparators(QDir(instanceRoot()).absolutePath())); out.insert("INST_MC_DIR", QDir::toNativeSeparators(QDir(gameRoot()).absolutePath())); - out.insert("INST_JAVA", QDir::toNativeSeparators(QDir(settings()->get("JavaPath").toString()).absolutePath())); + out.insert("INST_JAVA", settings()->get("JavaPath").toString()); out.insert("INST_JAVA_ARGS", javaArguments().join(' ')); out.insert("NO_COLOR", "1"); #ifdef Q_OS_MACOS @@ -688,7 +683,12 @@ QProcessEnvironment MinecraftInstance::createEnvironment() env.insert(iter.key(), iter.value().toString()); }; - insertEnv(settings()->get("Env").toString()); + bool overrideEnv = settings()->get("OverrideEnv").toBool(); + + if (!overrideEnv) + insertEnv(APPLICATION->settings()->get("Env").toString()); + else + insertEnv(settings()->get("Env").toString()); return env; } @@ -703,7 +703,7 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment() if (auto value = env.value("LD_PRELOAD"); !value.isEmpty()) preloadList = value.split(QLatin1String(":")); - auto mangoHudLibString = LibraryUtils::findMangoHud(); + auto mangoHudLibString = MangoHud::getLibraryString(); if (!mangoHudLibString.isEmpty()) { QFileInfo mangoHudLib(mangoHudLibString); QString libPath = mangoHudLib.absolutePath(); @@ -739,7 +739,6 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment() env.insert("__GLX_VENDOR_LIBRARY_NAME", "mesa"); env.insert("MESA_LOADER_DRIVER_OVERRIDE", "zink"); env.insert("GALLIUM_DRIVER", "zink"); - env.insert("LIBGL_KOPPER_DRI2", "1"); } #endif return env; @@ -748,21 +747,21 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment() QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) const { auto profile = m_components->getProfile(); - auto args = profile->getMinecraftArguments().split(' ', Qt::SkipEmptyParts); + QString args_pattern = profile->getMinecraftArguments(); for (auto tweaker : profile->getTweakers()) { - args << "--tweakClass" << tweaker; + args_pattern += " --tweakClass " + tweaker; } if (targetToJoin) { if (!targetToJoin->address.isEmpty()) { if (profile->hasTrait("feature:is_quick_play_multiplayer")) { - args << "--quickPlayMultiplayer" << targetToJoin->address + ':' + QString::number(targetToJoin->port); + args_pattern += " --quickPlayMultiplayer " + targetToJoin->address + ':' + QString::number(targetToJoin->port); } else { - args << "--server" << targetToJoin->address; - args << "--port" << QString::number(targetToJoin->port); + args_pattern += " --server " + targetToJoin->address; + args_pattern += " --port " + QString::number(targetToJoin->port); } } else if (!targetToJoin->world.isEmpty() && profile->hasTrait("feature:is_quick_play_singleplayer")) { - args << "--quickPlaySingleplayer" << targetToJoin->world; + args_pattern += " --quickPlaySingleplayer " + targetToJoin->world; } } @@ -778,15 +777,16 @@ QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session, Mine tokenMapping["user_properties"] = session->serializeUserProperties(); tokenMapping["user_type"] = session->user_type; - if (session->launchMode == LaunchMode::Demo) { - args << "--demo"; + if (session->demo) { + args_pattern += " --demo"; } } - for (int i = 0; i < args.length(); i++) { - args[i] = replaceTokensIn(args[i], tokenMapping); + QStringList parts = args_pattern.split(' ', Qt::SkipEmptyParts); + for (int i = 0; i < parts.length(); i++) { + parts[i] = replaceTokensIn(parts[i], tokenMapping); } - return args; + return parts; } QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) @@ -888,24 +888,50 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftT QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) { - constexpr auto indent = " "; - constexpr auto emptyLine = ""; - QStringList out; - - out << "Components:"; - for (int i = 0; i < m_components->rowCount(); ++i) { - const auto& component = m_components->getComponent(i); - out << indent + - QString("%1) %2 (%3) %4").arg(QString::number(i + 1), component->getName(), component->getID(), component->getVersion()); - } - out << emptyLine; - - out << "Launcher: " + getLauncher(); - out << "Main class: " + getMainClass() << emptyLine; + out << "Main Class:" << " " + getMainClass() << ""; + out << "Native path:" << " " + getNativePath() << ""; auto profile = m_components->getProfile(); + // traits + auto alltraits = traits(); + if (alltraits.size()) { + out << "Traits:"; + for (auto trait : alltraits) { + out << "traits " + trait; + } + out << ""; + } + + // native libraries + auto settings = this->settings(); + bool nativeOpenAL = settings->get("UseNativeOpenAL").toBool(); + bool nativeGLFW = settings->get("UseNativeGLFW").toBool(); + if (nativeOpenAL || nativeGLFW) { + if (nativeOpenAL) + out << "Using system OpenAL."; + if (nativeGLFW) + out << "Using system GLFW."; + out << ""; + } + + // libraries and class path. + { + out << "Libraries:"; + QStringList jars, nativeJars; + profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot()); + for (auto file : jars) { + out << " " + file; + } + out << ""; + out << "Native libraries:"; + for (auto file : nativeJars) { + out << " " + file; + } + out << ""; + } + // mods and core mods auto printModList = [&out](const QString& label, ModFolderModel& model) { if (model.size()) { @@ -928,12 +954,12 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr out << u8" [✘] " + mod->fileinfo().completeBaseName() + " (disabled)"; } } - out << emptyLine; + out << ""; } }; - printModList("Mods", *loaderModList()); - printModList("Core Mods", *coreModList()); + printModList("Mods", *(loaderModList().get())); + printModList("Core Mods", *(coreModList().get())); // jar mods auto& jarMods = profile->getJarMods(); @@ -943,59 +969,19 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr auto displayname = jarmod->displayName(runtimeContext()); auto realname = jarmod->filename(runtimeContext()); if (displayname != realname) { - out << indent + displayname + " (" + realname + ")"; + out << " " + displayname + " (" + realname + ")"; } else { - out << indent + realname; + out << " " + realname; } } - out << emptyLine; + out << ""; } - // traits - auto alltraits = traits(); - if (alltraits.size()) { - out << "Traits:"; - for (auto trait : alltraits) { - out << indent + trait; - } - out << emptyLine; - } - - // native libraries - auto settings = this->settings(); - bool nativeOpenAL = settings->get("UseNativeOpenAL").toBool(); - bool nativeGLFW = settings->get("UseNativeGLFW").toBool(); - if (nativeOpenAL || nativeGLFW) { - if (nativeOpenAL) - out << "Using system OpenAL."; - if (nativeGLFW) - out << "Using system GLFW."; - out << emptyLine; - } - - // libraries and class path. - { - out << "Libraries:"; - QStringList jars, nativeJars; - profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot()); - for (auto file : jars) { - out << indent + file; - } - out << emptyLine; - out << "Native libraries:"; - for (auto file : nativeJars) { - out << indent + file; - } - out << emptyLine; - } - - out << "Natives path:" << indent + getNativePath() << emptyLine; - // minecraft arguments auto params = processMinecraftArgs(nullptr, targetToJoin); - out << "Minecraft arguments:"; - out << indent + params.join(' '); - out << emptyLine; + out << "Params:"; + out << " " + params.join(' '); + out << ""; // window size QString windowParams; @@ -1006,18 +992,9 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr auto height = settings->get("MinecraftWinHeight").toInt(); out << "Window size: " + QString::number(width) + " x " + QString::number(height); } - out << emptyLine; - - // environment variables - const QString env = settings->get("Env").toString(); - if (auto envMap = Json::toMap(env); !envMap.isEmpty()) { - out << "Custom environment variables:"; - for (auto [key, value] : envMap.asKeyValueRange()) { - out << indent + key + "=" + value.toString(); - } - out << emptyLine; - } - + out << ""; + out << "Launcher: " + getLauncher(); + out << ""; return out; } @@ -1117,23 +1094,24 @@ QList MinecraftInstance::createUpdateTask() // libraries download makeShared(this), // FML libraries download and copy into the instance - makeShared(this), + makeShared(this), // assets update makeShared(this), }; } -LaunchTask* MinecraftInstance::createLaunchTask(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) +shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) { updateRuntimeContext(); - auto process = LaunchTask::create(this); + // FIXME: get rid of shared_from_this ... + auto process = LaunchTask::create(std::dynamic_pointer_cast(shared_from_this())); auto pptr = process.get(); APPLICATION->icons()->saveIcon(iconKey(), FS::PathCombine(gameRoot(), "icon.png"), "PNG"); // print a header { - process->appendStep(makeShared(pptr, "Minecraft folder is:\n " + gameRoot() + "\n", MessageLevel::Launcher)); + process->appendStep(makeShared(pptr, "Minecraft folder is:\n" + gameRoot() + "\n\n", MessageLevel::Launcher)); } // create the .minecraft folder and server-resource-packs (workaround for Minecraft bug MCL-3732) @@ -1163,7 +1141,7 @@ LaunchTask* MinecraftInstance::createLaunchTask(AuthSessionPtr session, Minecraf // load meta { - auto mode = session->launchMode != LaunchMode::Offline ? Net::Mode::Online : Net::Mode::Offline; + auto mode = session->status != AuthSession::PlayableOffline ? Net::Mode::Online : Net::Mode::Offline; process->appendStep(makeShared(pptr, makeShared(this, mode))); } @@ -1171,8 +1149,6 @@ LaunchTask* MinecraftInstance::createLaunchTask(AuthSessionPtr session, Minecraf { process->appendStep(makeShared(pptr)); process->appendStep(makeShared(pptr)); - // verify that minimum Java requirements are met - process->appendStep(makeShared(pptr)); } // run pre-launch command if that's needed @@ -1182,9 +1158,11 @@ LaunchTask* MinecraftInstance::createLaunchTask(AuthSessionPtr session, Minecraf process->appendStep(step); } - // if we aren't in offline mode - if (session->launchMode != LaunchMode::Offline) { - process->appendStep(makeShared(pptr, session)); + // if we aren't in offline mode,. + if (session->status != AuthSession::PlayableOffline) { + if (!session->demo) { + process->appendStep(makeShared(pptr, session)); + } for (auto t : createUpdateTask()) { process->appendStep(makeShared(pptr, t)); } @@ -1202,11 +1180,6 @@ LaunchTask* MinecraftInstance::createLaunchTask(AuthSessionPtr session, Minecraf process->appendStep(makeShared(pptr)); } - // make sure we have enough RAM, warn the user if we don't - { - process->appendStep(makeShared(pptr, this)); - } - // print some instance info here... { process->appendStep(makeShared(pptr, session, targetToJoin)); @@ -1222,6 +1195,11 @@ LaunchTask* MinecraftInstance::createLaunchTask(AuthSessionPtr session, Minecraf process->appendStep(makeShared(pptr)); } + // verify that minimum Java requirements are met + { + process->appendStep(makeShared(pptr)); + } + { // actually launch the game auto step = makeShared(pptr); @@ -1243,9 +1221,9 @@ LaunchTask* MinecraftInstance::createLaunchTask(AuthSessionPtr session, Minecraf if (m_settings->get("QuitAfterGameStop").toBool()) { process->appendStep(makeShared(pptr)); } - m_launchProcess = std::move(process); - emit launchTaskChanged(m_launchProcess.get()); - return m_launchProcess.get(); + m_launchProcess = process; + emit launchTaskChanged(m_launchProcess); + return m_launchProcess; } JavaVersion MinecraftInstance::getJavaVersion() @@ -1253,80 +1231,80 @@ JavaVersion MinecraftInstance::getJavaVersion() return JavaVersion(settings()->get("JavaVersion").toString()); } -ModFolderModel* MinecraftInstance::loaderModList() +std::shared_ptr MinecraftInstance::loaderModList() { if (!m_loader_mod_list) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); m_loader_mod_list.reset(new ModFolderModel(modsRoot(), this, is_indexed, true)); } - return m_loader_mod_list.get(); + return m_loader_mod_list; } -ModFolderModel* MinecraftInstance::coreModList() +std::shared_ptr MinecraftInstance::coreModList() { if (!m_core_mod_list) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); m_core_mod_list.reset(new ModFolderModel(coreModsDir(), this, is_indexed, true)); } - return m_core_mod_list.get(); + return m_core_mod_list; } -ModFolderModel* MinecraftInstance::nilModList() +std::shared_ptr MinecraftInstance::nilModList() { if (!m_nil_mod_list) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); m_nil_mod_list.reset(new ModFolderModel(nilModsDir(), this, is_indexed, false)); } - return m_nil_mod_list.get(); + return m_nil_mod_list; } -ResourcePackFolderModel* MinecraftInstance::resourcePackList() +std::shared_ptr MinecraftInstance::resourcePackList() { if (!m_resource_pack_list) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), this, is_indexed, true)); } - return m_resource_pack_list.get(); + return m_resource_pack_list; } -TexturePackFolderModel* MinecraftInstance::texturePackList() +std::shared_ptr MinecraftInstance::texturePackList() { if (!m_texture_pack_list) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), this, is_indexed, true)); } - return m_texture_pack_list.get(); + return m_texture_pack_list; } -ShaderPackFolderModel* MinecraftInstance::shaderPackList() +std::shared_ptr MinecraftInstance::shaderPackList() { if (!m_shader_pack_list) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), this, is_indexed, true)); } - return m_shader_pack_list.get(); + return m_shader_pack_list; } -DataPackFolderModel* MinecraftInstance::dataPackList() +std::shared_ptr MinecraftInstance::dataPackList() { if (!m_data_pack_list && settings()->get("GlobalDataPacksEnabled").toBool()) { bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); m_data_pack_list.reset(new DataPackFolderModel(dataPacksDir(), this, isIndexed, true)); } - return m_data_pack_list.get(); + return m_data_pack_list; } -QList MinecraftInstance::resourceLists() +QList> MinecraftInstance::resourceLists() { return { loaderModList(), coreModList(), nilModList(), resourcePackList(), texturePackList(), shaderPackList(), dataPackList() }; } -WorldList* MinecraftInstance::worldList() +std::shared_ptr MinecraftInstance::worldList() { if (!m_world_list) { m_world_list.reset(new WorldList(worldDir(), this)); } - return m_world_list.get(); + return m_world_list; } QList MinecraftInstance::getJarMods() const diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index 909962d5e..ecddef69c 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -56,8 +56,8 @@ class PackProfile; class MinecraftInstance : public BaseInstance { Q_OBJECT public: - MinecraftInstance(SettingsObject* globalSettings, std::unique_ptr settings, const QString& rootDir); - virtual ~MinecraftInstance(); + MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir); + virtual ~MinecraftInstance() = default; virtual void saveNow() override; void loadSpecificSettings() override; @@ -109,22 +109,22 @@ class MinecraftInstance : public BaseInstance { void updateRuntimeContext() override; ////// Profile management ////// - PackProfile* getPackProfile() const; + std::shared_ptr getPackProfile() const; ////// Mod Lists ////// - ModFolderModel* loaderModList(); - ModFolderModel* coreModList(); - ModFolderModel* nilModList(); - ResourcePackFolderModel* resourcePackList(); - TexturePackFolderModel* texturePackList(); - ShaderPackFolderModel* shaderPackList(); - DataPackFolderModel* dataPackList(); - QList resourceLists(); - WorldList* worldList(); + std::shared_ptr loaderModList(); + std::shared_ptr coreModList(); + std::shared_ptr nilModList(); + std::shared_ptr resourcePackList(); + std::shared_ptr texturePackList(); + std::shared_ptr shaderPackList(); + std::shared_ptr dataPackList(); + QList> resourceLists(); + std::shared_ptr worldList(); ////// Launch stuff ////// QList createUpdateTask() override; - LaunchTask* createLaunchTask(AuthSessionPtr account, MinecraftTarget::Ptr targetToJoin) override; + shared_qobject_ptr createLaunchTask(AuthSessionPtr account, MinecraftTarget::Ptr targetToJoin) override; QStringList extraArguments() override; QStringList verboseDescription(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) override; QList getJarMods() const; @@ -162,13 +162,15 @@ class MinecraftInstance : public BaseInstance { QMap makeProfileVarMapping(std::shared_ptr profile) const; protected: // data - std::unique_ptr m_components; - std::unique_ptr m_loader_mod_list; - std::unique_ptr m_core_mod_list; - std::unique_ptr m_nil_mod_list; - std::unique_ptr m_resource_pack_list; - std::unique_ptr m_shader_pack_list; - std::unique_ptr m_texture_pack_list; - std::unique_ptr m_data_pack_list; - std::unique_ptr m_world_list; + std::shared_ptr m_components; + mutable std::shared_ptr m_loader_mod_list; + mutable std::shared_ptr m_core_mod_list; + mutable std::shared_ptr m_nil_mod_list; + mutable std::shared_ptr m_resource_pack_list; + mutable std::shared_ptr m_shader_pack_list; + mutable std::shared_ptr m_texture_pack_list; + mutable std::shared_ptr m_data_pack_list; + mutable std::shared_ptr m_world_list; }; + +using MinecraftInstancePtr = std::shared_ptr; diff --git a/launcher/minecraft/MinecraftLoadAndCheck.cpp b/launcher/minecraft/MinecraftLoadAndCheck.cpp index c26fb8b60..149e7cf19 100644 --- a/launcher/minecraft/MinecraftLoadAndCheck.cpp +++ b/launcher/minecraft/MinecraftLoadAndCheck.cpp @@ -20,8 +20,11 @@ void MinecraftLoadAndCheck::executeTask() } connect(m_task.get(), &Task::succeeded, this, &MinecraftLoadAndCheck::emitSucceeded); connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::emitFailed); - connect(m_task.get(), &Task::aborted, this, &MinecraftLoadAndCheck::emitAborted); - propagateFromOther(m_task.get()); + connect(m_task.get(), &Task::aborted, this, [this] { emitFailed(tr("Aborted")); }); + connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::setProgress); + connect(m_task.get(), &Task::stepProgress, this, &MinecraftLoadAndCheck::propagateStepProgress); + connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus); + connect(m_task.get(), &Task::details, this, &MinecraftLoadAndCheck::setDetails); } bool MinecraftLoadAndCheck::canAbort() const @@ -35,7 +38,9 @@ bool MinecraftLoadAndCheck::canAbort() const bool MinecraftLoadAndCheck::abort() { if (m_task && m_task->canAbort()) { - return m_task->abort(); + auto status = m_task->abort(); + emitFailed("Aborted."); + return status; } return Task::abort(); } diff --git a/launcher/minecraft/OneSixVersionFormat.cpp b/launcher/minecraft/OneSixVersionFormat.cpp index 95f4c5ef6..48ea3b894 100644 --- a/launcher/minecraft/OneSixVersionFormat.cpp +++ b/launcher/minecraft/OneSixVersionFormat.cpp @@ -209,7 +209,8 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument& doc QString arg = ""; readString(agentObj, "argument", arg); - out->agents.append(Agent{ lib, arg }); + AgentPtr agent(new Agent(lib, arg)); + out->agents.append(agent); } } @@ -304,10 +305,10 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr& patch writeStringList(root, "+jvmArgs", patch->addnJvmArguments); if (!patch->agents.isEmpty()) { QJsonArray array; - for (const auto& value : patch->agents) { - QJsonObject agentOut = OneSixVersionFormat::libraryToJson(value.library.get()); - if (!value.argument.isEmpty()) - agentOut.insert("argument", value.argument); + for (auto value : patch->agents) { + QJsonObject agentOut = OneSixVersionFormat::libraryToJson(value->library().get()); + if (!value->argument().isEmpty()) + agentOut.insert("argument", value->argument()); array.append(agentOut); } @@ -369,7 +370,8 @@ LibraryPtr OneSixVersionFormat::plusJarModFromJson([[maybe_unused]] ProblemConta } // just make up something unique on the spot for the library name. - QString id = QUuid::createUuid().toString(QUuid::WithoutBraces); + auto uuid = QUuid::createUuid(); + QString id = uuid.toString().remove('{').remove('}'); out->setRawName(GradleSpecifier("org.multimc.jarmods:" + id + ":1")); // filename override is the old name diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index f0cff7f0e..7ffebddb5 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -326,15 +326,7 @@ PackProfile::Result PackProfile::reload(Net::Mode netmode) { // Do not reload when the update/resolve task is running. It is in control. if (d->m_updateTask) { - if (d->m_updateTask->netMode() == netmode) { - return Result::Success(); - } - - // https://github.com/PrismLauncher/PrismLauncher/issues/5209 - // FIXME: HACK HACK HACK - disconnect(d->m_updateTask.get(), &ComponentUpdateTask::aborted, nullptr, nullptr); - d->m_updateTask->abort(); - d->m_updateTask.reset(); + return Result::Success(); } // flush any scheduled saves to not lose state @@ -925,7 +917,7 @@ bool PackProfile::installAgents_internal(QStringList filepaths) agent->setDisplayName(sourceInfo.completeBaseName()); agent->setHint("local"); - versionFile->agents.append(Agent{agent, QString()}); + versionFile->agents.append(std::make_shared(agent, QString())); versionFile->name = targetName; versionFile->uid = targetId; diff --git a/launcher/minecraft/PackProfile_p.h b/launcher/minecraft/PackProfile_p.h index feb825904..4fb3621f0 100644 --- a/launcher/minecraft/PackProfile_p.h +++ b/launcher/minecraft/PackProfile_p.h @@ -22,7 +22,7 @@ struct PackProfileData { ComponentIndex componentIndex; bool dirty = false; QTimer m_saveTimer; - shared_qobject_ptr m_updateTask; + Task::Ptr m_updateTask; bool loaded = false; bool interactionDisabled = true; }; diff --git a/launcher/minecraft/ProfileUtils.cpp b/launcher/minecraft/ProfileUtils.cpp index ae6326953..b0b9122e3 100644 --- a/launcher/minecraft/ProfileUtils.cpp +++ b/launcher/minecraft/ProfileUtils.cpp @@ -144,13 +144,13 @@ bool saveJsonFile(const QJsonDocument& doc, const QString& filename) auto data = doc.toJson(); QSaveFile jsonFile(filename); if (!jsonFile.open(QIODevice::WriteOnly)) { - qWarning() << "Couldn't open" << filename << "for writing:" << jsonFile.errorString(); jsonFile.cancelWriting(); + qWarning() << "Couldn't open" << filename << "for writing"; return false; } jsonFile.write(data); if (!jsonFile.commit()) { - qWarning() << "Couldn't save" << filename << "error:" << jsonFile.errorString(); + qWarning() << "Couldn't save" << filename; return false; } return true; diff --git a/launcher/minecraft/ShortcutUtils.cpp b/launcher/minecraft/ShortcutUtils.cpp index b719e3142..ec4ebb31a 100644 --- a/launcher/minecraft/ShortcutUtils.cpp +++ b/launcher/minecraft/ShortcutUtils.cpp @@ -71,7 +71,7 @@ bool createInstanceShortcut(const Shortcut& shortcut, const QString& filePath) QFile iconFile(iconPath); if (!iconFile.open(QFile::WriteOnly)) { - QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for application: %1").arg(iconFile.errorString())); + QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for application.")); return false; } @@ -101,7 +101,7 @@ bool createInstanceShortcut(const Shortcut& shortcut, const QString& filePath) QFile iconFile(iconPath); if (!iconFile.open(QFile::WriteOnly)) { - QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut: %1").arg(iconFile.errorString())); + QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut.")); return false; } bool success = icon->icon().pixmap(64, 64).save(&iconFile, "PNG"); @@ -127,7 +127,7 @@ bool createInstanceShortcut(const Shortcut& shortcut, const QString& filePath) QFile iconFile(iconPath); if (!iconFile.open(QFile::WriteOnly)) { - QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut: %1").arg(iconFile.errorString())); + QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut.")); return false; } bool success = icon->icon().pixmap(64, 64).save(&iconFile, "ICO"); diff --git a/launcher/minecraft/ShortcutUtils.h b/launcher/minecraft/ShortcutUtils.h index 5cf31f9b2..b995c36bd 100644 --- a/launcher/minecraft/ShortcutUtils.h +++ b/launcher/minecraft/ShortcutUtils.h @@ -37,7 +37,6 @@ #pragma once #include "Application.h" -#include "BaseInstance.h" #include #include diff --git a/launcher/minecraft/VanillaInstanceCreationTask.cpp b/launcher/minecraft/VanillaInstanceCreationTask.cpp index e646e2c52..ccbd8c677 100644 --- a/launcher/minecraft/VanillaInstanceCreationTask.cpp +++ b/launcher/minecraft/VanillaInstanceCreationTask.cpp @@ -7,27 +7,32 @@ #include "minecraft/PackProfile.h" #include "settings/INISettingsObject.h" -VanillaCreationTask::VanillaCreationTask(BaseVersion::Ptr version, QString loader, BaseVersion::Ptr loaderVersion) - : m_version(std::move(version)), m_using_loader(true), m_loader(std::move(loader)), m_loader_version(std::move(loaderVersion)) +VanillaCreationTask::VanillaCreationTask(BaseVersion::Ptr version, QString loader, BaseVersion::Ptr loader_version) + : InstanceCreationTask() + , m_version(std::move(version)) + , m_using_loader(true) + , m_loader(std::move(loader)) + , m_loader_version(std::move(loader_version)) {} -std::unique_ptr VanillaCreationTask::createInstance() +bool VanillaCreationTask::createInstance() { setStatus(tr("Creating instance from version %1").arg(m_version->name())); - auto inst = std::make_unique( - m_globalSettings, std::make_unique(FS::PathCombine(m_stagingPath, "instance.cfg")), m_stagingPath); - SettingsObject::Lock lock(inst->settings()); + auto instance_settings = std::make_shared(FS::PathCombine(m_stagingPath, "instance.cfg")); + instance_settings->suspendSave(); + { + MinecraftInstance inst(m_globalSettings, instance_settings, m_stagingPath); + auto components = inst.getPackProfile(); + components->buildingFromScratch(); + components->setComponentVersion("net.minecraft", m_version->descriptor(), true); + if (m_using_loader) + components->setComponentVersion(m_loader, m_loader_version->descriptor()); - auto* components = inst->getPackProfile(); - components->buildingFromScratch(); - components->setComponentVersion("net.minecraft", m_version->descriptor(), true); - if (m_using_loader) { - components->setComponentVersion(m_loader, m_loader_version->descriptor()); + inst.setName(name()); + inst.setIconKey(m_instIcon); } + instance_settings->resumeSave(); - inst->setName(name()); - inst->setIconKey(m_instIcon); - components->saveNow(); - return inst; + return true; } diff --git a/launcher/minecraft/VanillaInstanceCreationTask.h b/launcher/minecraft/VanillaInstanceCreationTask.h index c1a69ab62..d1b816824 100644 --- a/launcher/minecraft/VanillaInstanceCreationTask.h +++ b/launcher/minecraft/VanillaInstanceCreationTask.h @@ -7,10 +7,10 @@ class VanillaCreationTask final : public InstanceCreationTask { Q_OBJECT public: - explicit VanillaCreationTask(BaseVersion::Ptr version) : m_version(std::move(version)) {} - VanillaCreationTask(BaseVersion::Ptr version, QString loader, BaseVersion::Ptr loaderVersion); + VanillaCreationTask(BaseVersion::Ptr version) : InstanceCreationTask(), m_version(std::move(version)) {} + VanillaCreationTask(BaseVersion::Ptr version, QString loader, BaseVersion::Ptr loader_version); - std::unique_ptr createInstance() override; + bool createInstance() override; private: // Version to update to / create of the instance. diff --git a/launcher/minecraft/VersionFile.h b/launcher/minecraft/VersionFile.h index 32a7504ac..40f49aaa4 100644 --- a/launcher/minecraft/VersionFile.h +++ b/launcher/minecraft/VersionFile.h @@ -126,7 +126,7 @@ class VersionFile : public ProblemContainer { QList mavenFiles; /// Prism Launcher: list of agents to add to JVM arguments - QList agents; + QList agents; /// The main jar (Minecraft version library, normally) LibraryPtr mainJar; diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp index 0deecb042..fca45fb99 100644 --- a/launcher/minecraft/World.cpp +++ b/launcher/minecraft/World.cpp @@ -153,23 +153,18 @@ QByteArray serializeLevelDat(nbt::tag_compound* levelInfo) return val; } -QString getDatFromFS(const QFileInfo& root, QString file) -{ - QDir worldDir(root.filePath()); - if (!root.isDir() || !worldDir.exists(file)) { - return QString(); - } - return worldDir.absoluteFilePath(file); -} - QString getLevelDatFromFS(const QFileInfo& file) { - return getDatFromFS(file, "level.dat"); + QDir worldDir(file.filePath()); + if (!file.isDir() || !worldDir.exists("level.dat")) { + return QString(); + } + return worldDir.absoluteFilePath("level.dat"); } -QByteArray getDatDataFromFS(const QFileInfo& root, QString file) +QByteArray getLevelDatDataFromFS(const QFileInfo& file) { - auto fullFilePath = getDatFromFS(root, file); + auto fullFilePath = getLevelDatFromFS(file); if (fullFilePath.isNull()) { return QByteArray(); } @@ -180,16 +175,6 @@ QByteArray getDatDataFromFS(const QFileInfo& root, QString file) return f.readAll(); } -QByteArray getLevelDatDataFromFS(const QFileInfo& file) -{ - return getDatDataFromFS(file, "level.dat"); -} - -QByteArray getWorldGenDataFromFS(const QFileInfo& file) -{ - return getDatDataFromFS(file, "data/minecraft/world_gen_settings.dat"); -} - bool putLevelDatDataToFS(const QFileInfo& file, QByteArray& data) { auto fullFilePath = getLevelDatFromFS(file); @@ -244,8 +229,6 @@ bool World::resetIcon() return false; } -int64_t loadSeed(QByteArray data); - void World::readFromFS(const QFileInfo& file) { auto bytes = getLevelDatDataFromFS(file); @@ -255,12 +238,6 @@ void World::readFromFS(const QFileInfo& file) } loadFromLevelDat(bytes); m_levelDatTime = file.lastModified(); - if (m_randomSeed == 0) { - bytes = getWorldGenDataFromFS(file); - if (!bytes.isEmpty()) { - m_randomSeed = loadSeed(bytes); - } - } } void World::readFromZip(const QFileInfo& file) @@ -274,6 +251,9 @@ void World::readFromZip(const QFileInfo& file) QFileInfo fi(filePath); if (fi.fileName().compare(levelDat, Qt::CaseInsensitive) == 0) { m_containerOffsetPath = filePath.chopped(levelDat.length()); + if (!m_containerOffsetPath.isEmpty()) { + return false; + } m_levelDatTime = file->dateTime(); loadFromLevelDat(file->readAll()); m_isValid = true; @@ -413,28 +393,6 @@ GameType read_gametype(nbt::value& parent, const char* name) } // namespace -int64_t loadSeed(QByteArray data) -{ - auto levelData = parseLevelDat(data); - if (!levelData) { - return 0; - } - - nbt::value* valPtr = nullptr; - try { - valPtr = &levelData->at("data"); - } catch (const std::out_of_range&) { - return 0; - } - nbt::value& val = *valPtr; - - try { - return read_long(val, "seed").value_or(0); - } catch (const std::out_of_range&) { - } - return 0; -} - void World::loadFromLevelDat(QByteArray data) { auto levelData = parseLevelDat(data); diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index ca8ac1aa8..4aa0f7532 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -157,8 +157,7 @@ bool WorldList::resetIcon(int row) return false; World& m = m_worlds[row]; if (m.resetIcon()) { - QModelIndex modelIndex = index(row, NameColumn); - emit dataChanged(modelIndex, modelIndex, { WorldList::IconFileRole }); + emit dataChanged(index(row), index(row), { WorldList::IconFileRole }); return true; } return false; @@ -427,7 +426,7 @@ void WorldList::loadWorldsAsync() m_worlds[row].setSize(size); // Notify views - QModelIndex modelIndex = index(row, SizeColumn); + QModelIndex modelIndex = index(row); emit dataChanged(modelIndex, modelIndex, { SizeRole }); } }, diff --git a/launcher/minecraft/auth/AccountData.cpp b/launcher/minecraft/auth/AccountData.cpp index bfb350a63..29a65e275 100644 --- a/launcher/minecraft/auth/AccountData.cpp +++ b/launcher/minecraft/auth/AccountData.cpp @@ -303,6 +303,7 @@ bool AccountData::resumeStateFromV3(QJsonObject data) } // leave msaClientID empty if it doesn't exist or isn't a string msaToken = tokenFromJSONV3(data, "msa"); userToken = tokenFromJSONV3(data, "utoken"); + xboxApiToken = tokenFromJSONV3(data, "xrp-main"); mojangservicesToken = tokenFromJSONV3(data, "xrp-mc"); } @@ -332,6 +333,7 @@ QJsonObject AccountData::saveState() const output["msa-client-id"] = msaClientID; tokenToJSONV3(output, msaToken, "msa"); tokenToJSONV3(output, userToken, "utoken"); + tokenToJSONV3(output, xboxApiToken, "xrp-main"); tokenToJSONV3(output, mojangservicesToken, "xrp-mc"); } else if (type == AccountType::Offline) { output["type"] = "Offline"; @@ -356,10 +358,28 @@ QString AccountData::profileId() const QString AccountData::profileName() const { if (minecraftProfile.name.size() == 0) { - return QObject::tr("No Minecraft profile"); + return QObject::tr("No profile (%1)").arg(accountDisplayString()); + } else { + return minecraftProfile.name; } +} - return minecraftProfile.name; +QString AccountData::accountDisplayString() const +{ + switch (type) { + case AccountType::Offline: { + return QObject::tr(""); + } + case AccountType::MSA: { + if (xboxApiToken.extra.contains("gtg")) { + return xboxApiToken.extra["gtg"].toString(); + } + return "Xbox profile missing"; + } + default: { + return "Invalid Account"; + } + } } QString AccountData::lastError() const diff --git a/launcher/minecraft/auth/AccountData.h b/launcher/minecraft/auth/AccountData.h index 96ef94f30..3b76012cb 100644 --- a/launcher/minecraft/auth/AccountData.h +++ b/launcher/minecraft/auth/AccountData.h @@ -41,7 +41,6 @@ #include #include -#include #include enum class Validity { None, Assumed, Certain }; @@ -96,6 +95,9 @@ struct AccountData { QJsonObject saveState() const; bool resumeStateFromV3(QJsonObject data); + //! userName for Mojang accounts, gamertag for MSA + QString accountDisplayString() const; + //! Yggdrasil access token, as passed to the game. QString accessToken() const; @@ -109,6 +111,7 @@ struct AccountData { QString msaClientID; Token msaToken; Token userToken; + Token xboxApiToken; Token mojangservicesToken; Token yggdrasilToken; @@ -119,6 +122,5 @@ struct AccountData { // runtime only information (not saved with the account) QString internalId; QString errorString; - QNetworkReply::NetworkError networkError = QNetworkReply::NoError; AccountState accountState = AccountState::Unchecked; }; diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp index 418ba98d2..d86014a34 100644 --- a/launcher/minecraft/auth/AccountList.cpp +++ b/launcher/minecraft/auth/AccountList.cpp @@ -39,7 +39,6 @@ #include #include -#include #include #include #include @@ -169,26 +168,6 @@ void AccountList::removeAccount(QModelIndex index) } } -void AccountList::moveAccount(QModelIndex index, int delta) -{ - const int row = index.row(); - const int newRow = row + delta; - if (index.isValid() && row < m_accounts.size() && newRow >= 0 && newRow < m_accounts.size()) { - // Qt is stupid, https://doc.qt.io/qt-6/qabstractitemmodel.html#beginMoveRows - const int modelDestinationRow = (newRow > row) ? newRow + 1 : newRow; - - if (beginMoveRows(QModelIndex(), row, row, QModelIndex(), modelDestinationRow)) { - m_accounts.move(row, newRow); - endMoveRows(); - - onListChanged(); - } else { - qCritical().noquote() << "AccountList: failed to move account from" << row << "to" << newRow - << QString("(%1 accounts in total)").arg(this->count()); - } - } -} - MinecraftAccountPtr AccountList::defaultAccount() const { return m_defaultAccount; @@ -316,28 +295,12 @@ QVariant AccountList::data(const QModelIndex& index, int role) const MinecraftAccountPtr account = at(index.row()); switch (role) { - case Qt::SizeHintRole: - if (index.column() == ProfileNameColumn) { - return QSize(0, 30); - } - - return QVariant(); - case Qt::DecorationRole: - if (index.column() == ProfileNameColumn) { - auto face = account->getFace(24, 24); - - if (!face.isNull()) { - return face; - } else { - return QIcon::fromTheme("noaccount").pixmap(24, 24); - } - } - - return QVariant(); case Qt::DisplayRole: switch (index.column()) { case ProfileNameColumn: return account->profileName(); + case NameColumn: + return account->accountDisplayString(); case TypeColumn: { switch (account->accountType()) { case AccountType::MSA: { @@ -355,6 +318,9 @@ QVariant AccountList::data(const QModelIndex& index, int role) const return QVariant(); } + case Qt::ToolTipRole: + return account->accountDisplayString(); + case PointerRole: return QVariant::fromValue(account); @@ -375,6 +341,8 @@ QVariant AccountList::headerData(int section, [[maybe_unused]] Qt::Orientation o switch (section) { case ProfileNameColumn: return tr("Username"); + case NameColumn: + return tr("Account"); case TypeColumn: return tr("Type"); case StatusColumn: @@ -387,6 +355,8 @@ QVariant AccountList::headerData(int section, [[maybe_unused]] Qt::Orientation o switch (section) { case ProfileNameColumn: return tr("Minecraft username associated with the account."); + case NameColumn: + return tr("User name of the account."); case TypeColumn: return tr("Type of the account (MSA or Offline)"); case StatusColumn: @@ -450,7 +420,7 @@ bool AccountList::loadList() // Try to open the file and fail if we can't. // TODO: We should probably report this error to the user. if (!file.open(QIODevice::ReadOnly)) { - qCritical() << QString("Failed to read the account list file %1 (%2).").arg(m_listFilePath).arg(file.errorString()).toUtf8(); + qCritical() << QString("Failed to read the account list file (%1).").arg(m_listFilePath).toUtf8(); return false; } @@ -567,7 +537,7 @@ bool AccountList::saveList() // Try to open the file and fail if we can't. // TODO: We should probably report this error to the user. if (!file.open(QIODevice::WriteOnly)) { - qCritical() << QString("Failed to save the account list file %1 (%2).").arg(m_listFilePath).arg(file.errorString()).toUtf8(); + qCritical() << QString("Failed to read the account list file (%1).").arg(m_listFilePath).toUtf8(); return false; } @@ -578,7 +548,7 @@ bool AccountList::saveList() qDebug() << "Saved account list to" << m_listFilePath; return true; } else { - qDebug() << "Failed to save accounts to" << m_listFilePath << "error:" << file.errorString(); + qDebug() << "Failed to save accounts to" << m_listFilePath; return false; } } @@ -648,32 +618,21 @@ void AccountList::tryNext() while (m_refreshQueue.length()) { auto accountId = m_refreshQueue.front(); m_refreshQueue.pop_front(); - bool found = false; for (int i = 0; i < count(); i++) { auto account = at(i); if (account->internalId() == accountId) { - found = true; - if (!account->shouldRefresh()) { - // Account no longer needs refreshing, skip it. - qDebug() << "RefreshSchedule: Skipping account" << account->profileName() << "with internal ID" - << accountId << "(no longer needs refresh)"; - break; - } m_currentTask = account->refresh(); if (m_currentTask) { connect(m_currentTask.get(), &Task::succeeded, this, &AccountList::authSucceeded); connect(m_currentTask.get(), &Task::failed, this, &AccountList::authFailed); m_currentTask->start(); - qDebug() << "RefreshSchedule: Processing account" << account->profileName() << "with internal ID" + qDebug() << "RefreshSchedule: Processing account" << account->accountDisplayString() << "with internal ID" << accountId; return; } - break; } } - if (!found) { - qDebug() << "RefreshSchedule: Account with internal ID" << accountId << "not found."; - } + qDebug() << "RefreshSchedule: Account with internal ID" << accountId << "not found."; } // if we get here, no account needed refreshing. Schedule refresh in an hour. m_refreshTimer->start(1000 * 3600); diff --git a/launcher/minecraft/auth/AccountList.h b/launcher/minecraft/auth/AccountList.h index 916f23341..2f1276312 100644 --- a/launcher/minecraft/auth/AccountList.h +++ b/launcher/minecraft/auth/AccountList.h @@ -55,6 +55,7 @@ class AccountList : public QAbstractListModel { enum VListColumns { // TODO: Add icon column. ProfileNameColumn = 0, + NameColumn, TypeColumn, StatusColumn, @@ -77,7 +78,6 @@ class AccountList : public QAbstractListModel { void addAccount(MinecraftAccountPtr account); void removeAccount(QModelIndex index); - void moveAccount(QModelIndex index, int delta); int findAccountByProfileId(const QString& profileId) const; MinecraftAccountPtr getAccountByProfileName(const QString& profileName) const; QStringList profileNames() const; diff --git a/launcher/minecraft/auth/AuthFlow.cpp b/launcher/minecraft/auth/AuthFlow.cpp index 5b8f98122..cea171b33 100644 --- a/launcher/minecraft/auth/AuthFlow.cpp +++ b/launcher/minecraft/auth/AuthFlow.cpp @@ -10,6 +10,7 @@ #include "minecraft/auth/steps/MSAStep.h" #include "minecraft/auth/steps/MinecraftProfileStep.h" #include "minecraft/auth/steps/XboxAuthorizationStep.h" +#include "minecraft/auth/steps/XboxProfileStep.h" #include "minecraft/auth/steps/XboxUserStep.h" #include "tasks/Task.h" @@ -31,9 +32,11 @@ AuthFlow::AuthFlow(AccountData* data, Action action) : Task(), m_data(data) m_steps.append(oauthStep); } m_steps.append(makeShared(m_data)); + m_steps.append(makeShared(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox")); m_steps.append( makeShared(m_data, &m_data->mojangservicesToken, "rp://api.minecraftservices.com/", "Mojang")); m_steps.append(makeShared(m_data)); + m_steps.append(makeShared(m_data)); m_steps.append(makeShared(m_data)); m_steps.append(makeShared(m_data)); m_steps.append(makeShared(m_data)); diff --git a/launcher/minecraft/auth/AuthFlow.h b/launcher/minecraft/auth/AuthFlow.h index d881a7691..710509d8e 100644 --- a/launcher/minecraft/auth/AuthFlow.h +++ b/launcher/minecraft/auth/AuthFlow.h @@ -2,6 +2,7 @@ #include #include +#include #include #include diff --git a/launcher/minecraft/auth/AuthSession.cpp b/launcher/minecraft/auth/AuthSession.cpp index 85d77be9c..a74eabb7a 100644 --- a/launcher/minecraft/auth/AuthSession.cpp +++ b/launcher/minecraft/auth/AuthSession.cpp @@ -20,17 +20,23 @@ QString AuthSession::serializeUserProperties() bool AuthSession::MakeOffline(QString offline_playername) { + if (status != PlayableOffline && status != PlayableOnline) { + return false; + } session = "-"; access_token = "0"; player_name = offline_playername; + status = PlayableOffline; return true; } void AuthSession::MakeDemo(QString name, QString u) { + wants_online = false; + demo = true; uuid = u; session = "-"; access_token = "0"; player_name = name; - launchMode = LaunchMode::Demo; + status = PlayableOnline; // needs online to download the assets }; diff --git a/launcher/minecraft/auth/AuthSession.h b/launcher/minecraft/auth/AuthSession.h index 07db54213..cbe604805 100644 --- a/launcher/minecraft/auth/AuthSession.h +++ b/launcher/minecraft/auth/AuthSession.h @@ -3,8 +3,6 @@ #include #include -#include "LaunchMode.h" - class MinecraftAccount; struct AuthSession { @@ -13,6 +11,16 @@ struct AuthSession { QString serializeUserProperties(); + enum Status { + Undetermined, + RequiresOAuth, + RequiresPassword, + RequiresProfileSetup, + PlayableOffline, + PlayableOnline, + GoneOrMigrated + } status = Undetermined; + // combined session ID QString session; // volatile auth token @@ -21,10 +29,15 @@ struct AuthSession { QString player_name; // profile ID QString uuid; - // 'msa' or 'offline', depending on account type + // 'legacy' or 'mojang', depending on account type QString user_type; - // the actual launch mode for this session - LaunchMode launchMode; + // Did the auth server reply? + bool auth_server_online = false; + // Did the user request online mode? + bool wants_online = true; + + // Is this a demo session? + bool demo = false; }; using AuthSessionPtr = std::shared_ptr; diff --git a/launcher/minecraft/auth/AuthStep.h b/launcher/minecraft/auth/AuthStep.h index f8131509f..aaaec6e7f 100644 --- a/launcher/minecraft/auth/AuthStep.h +++ b/launcher/minecraft/auth/AuthStep.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include "QObjectPtr.h" diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index e346f015b..ca052c378 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -55,7 +55,8 @@ MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent) { - data.internalId = QUuid::createUuid().toString(QUuid::Id128); + static const QRegularExpression s_removeChars("[{}-]"); + data.internalId = QUuid::createUuid().toString().remove(s_removeChars); } MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json) @@ -76,14 +77,15 @@ MinecraftAccountPtr MinecraftAccount::createBlankMSA() MinecraftAccountPtr MinecraftAccount::createOffline(const QString& username) { + static const QRegularExpression s_removeChars("[{}-]"); auto account = makeShared(); account->data.type = AccountType::Offline; account->data.yggdrasilToken.token = "0"; account->data.yggdrasilToken.validity = Validity::Certain; account->data.yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc(); account->data.yggdrasilToken.extra["userName"] = username; - account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString(QUuid::Id128); - account->data.minecraftProfile.id = uuidFromUsername(username).toString(QUuid::Id128); + account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(s_removeChars); + account->data.minecraftProfile.id = uuidFromUsername(username).toString().remove(s_removeChars); account->data.minecraftProfile.name = username; account->data.minecraftProfile.validity = Validity::Certain; return account; @@ -99,7 +101,7 @@ AccountState MinecraftAccount::accountState() const return data.accountState; } -QPixmap MinecraftAccount::getFace(int width, int height) const +QPixmap MinecraftAccount::getFace() const { QPixmap skinTexture; if (!skinTexture.loadFromData(data.minecraftProfile.skin.data, "PNG")) { @@ -110,7 +112,7 @@ QPixmap MinecraftAccount::getFace(int width, int height) const QPainter painter(&skin); painter.drawPixmap(0, 0, skinTexture.copy(8, 8, 8, 8)); painter.drawPixmap(0, 0, skinTexture.copy(40, 8, 8, 8)); - return skin.scaled(width, height, Qt::KeepAspectRatio); + return skin.scaled(64, 64, Qt::KeepAspectRatio); } shared_qobject_ptr MinecraftAccount::login(bool useDeviceCode) @@ -191,14 +193,6 @@ void MinecraftAccount::authFailed(QString reason) emit activityChanged(false); } -QString MinecraftAccount::displayName() const -{ - if (const QList validStates{ AccountState::Unchecked, AccountState::Working, AccountState::Offline, AccountState::Online }; !validStates.contains(accountState())) { - return QString("⚠ %1").arg(profileName()); - } - return profileName(); -} - bool MinecraftAccount::isActive() const { return !m_currentTask.isNull(); @@ -241,6 +235,17 @@ bool MinecraftAccount::shouldRefresh() const void MinecraftAccount::fillSession(AuthSessionPtr session) { + static const QRegularExpression s_removeChars("[{}-]"); + if (ownsMinecraft() && !hasProfile()) { + session->status = AuthSession::RequiresProfileSetup; + } else { + if (session->wants_online) { + session->status = AuthSession::PlayableOnline; + } else { + session->status = AuthSession::PlayableOffline; + } + } + // volatile auth token session->access_token = data.accessToken(); // profile name @@ -248,7 +253,7 @@ void MinecraftAccount::fillSession(AuthSessionPtr session) // profile ID session->uuid = data.profileId(); if (session->uuid.isEmpty()) - session->uuid = uuidFromUsername(session->player_name).toString(QUuid::Id128); + session->uuid = uuidFromUsername(session->player_name).toString().remove(s_removeChars); // 'legacy' or 'mojang', depending on account type session->user_type = typeString(); if (!session->access_token.isEmpty()) { @@ -286,12 +291,12 @@ QUuid MinecraftAccount::uuidFromUsername(QString username) // basically a reimplementation of Java's UUID#nameUUIDFromBytes QByteArray digest = QCryptographicHash::hash(input, QCryptographicHash::Md5); - auto bOr = [](QByteArray& array, qsizetype index, uint8_t value) { array[index] |= value; }; - auto bAnd = [](QByteArray& array, qsizetype index, uint8_t value) { array[index] &= value; }; - bAnd(digest, 6, 0x0f); // clear version - bOr(digest, 6, 0x30); // set to version 3 - bAnd(digest, 8, 0x3f); // clear variant - bOr(digest, 8, 0x80); // set to IETF variant + auto bOr = [](QByteArray& array, qsizetype index, char value) { array[index] |= value; }; + auto bAnd = [](QByteArray& array, qsizetype index, char value) { array[index] &= value; }; + bAnd(digest, 6, (char)0x0f); // clear version + bOr(digest, 6, (char)0x30); // set to version 3 + bAnd(digest, 8, (char)0x3f); // clear variant + bOr(digest, 8, (char)0x80); // set to IETF variant return QUuid::fromRfc4122(digest); } diff --git a/launcher/minecraft/auth/MinecraftAccount.h b/launcher/minecraft/auth/MinecraftAccount.h index 24608701d..a82d3f134 100644 --- a/launcher/minecraft/auth/MinecraftAccount.h +++ b/launcher/minecraft/auth/MinecraftAccount.h @@ -104,14 +104,14 @@ class MinecraftAccount : public QObject, public Usable { public: /* queries */ QString internalId() const { return data.internalId; } + QString accountDisplayString() const { return data.accountDisplayString(); } + QString accessToken() const { return data.accessToken(); } QString profileId() const { return data.profileId(); } QString profileName() const { return data.profileName(); } - QString displayName() const; - bool isActive() const; AccountType accountType() const noexcept { return data.type; } @@ -135,7 +135,7 @@ class MinecraftAccount : public QObject, public Usable { } } - QPixmap getFace(int width = 64, int height = 64) const; + QPixmap getFace() const; //! Returns the current state of the account AccountState accountState() const; diff --git a/launcher/minecraft/auth/steps/EntitlementsStep.cpp b/launcher/minecraft/auth/steps/EntitlementsStep.cpp index 1a4e9aa74..5b9809c52 100644 --- a/launcher/minecraft/auth/steps/EntitlementsStep.cpp +++ b/launcher/minecraft/auth/steps/EntitlementsStep.cpp @@ -23,35 +23,35 @@ QString EntitlementsStep::describe() void EntitlementsStep::perform() { - m_entitlements_request_id = QUuid::createUuid().toString(QUuid::WithoutBraces); + auto uuid = QUuid::createUuid(); + m_entitlements_request_id = uuid.toString().remove('{').remove('}'); QUrl url("https://api.minecraftservices.com/entitlements/license?requestId=" + m_entitlements_request_id); auto headers = QList{ { "Content-Type", "application/json" }, { "Accept", "application/json" }, { "Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8() } }; - auto [request, response] = Net::Download::makeByteArray(url); - m_request = request; - m_request->addHeaderProxy(std::make_unique(headers)); - m_request->enableAutoRetry(true); + m_response.reset(new QByteArray()); + m_request = Net::Download::makeByteArray(url, m_response); + m_request->addHeaderProxy(new Net::RawHeaderProxy(headers)); m_task.reset(new NetJob("EntitlementsStep", APPLICATION->network())); m_task->setAskRetry(false); m_task->addNetAction(m_request); - connect(m_task.get(), &Task::finished, this, [this, response] { onRequestDone(response); }); + connect(m_task.get(), &Task::finished, this, &EntitlementsStep::onRequestDone); m_task->start(); qDebug() << "Getting entitlements..."; } -void EntitlementsStep::onRequestDone(QByteArray* response) +void EntitlementsStep::onRequestDone() { - qCDebug(authCredentials()) << *response; + qCDebug(authCredentials()) << *m_response; // TODO: check presence of same entitlementsRequestId? // TODO: validate JWTs? - Parsers::parseMinecraftEntitlements(*response, m_data->minecraftEntitlement); + Parsers::parseMinecraftEntitlements(*m_response, m_data->minecraftEntitlement); emit finished(AccountTaskState::STATE_WORKING, tr("Got entitlements")); } diff --git a/launcher/minecraft/auth/steps/EntitlementsStep.h b/launcher/minecraft/auth/steps/EntitlementsStep.h index 72f77dabe..f20fcac08 100644 --- a/launcher/minecraft/auth/steps/EntitlementsStep.h +++ b/launcher/minecraft/auth/steps/EntitlementsStep.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include "minecraft/auth/AuthStep.h" #include "net/Download.h" @@ -17,10 +18,11 @@ class EntitlementsStep : public AuthStep { QString describe() override; private slots: - void onRequestDone(QByteArray* response); + void onRequestDone(); private: QString m_entitlements_request_id; + std::shared_ptr m_response; Net::Download::Ptr m_request; NetJob::Ptr m_task; }; diff --git a/launcher/minecraft/auth/steps/GetSkinStep.cpp b/launcher/minecraft/auth/steps/GetSkinStep.cpp index 7b26ca468..e067bc34c 100644 --- a/launcher/minecraft/auth/steps/GetSkinStep.cpp +++ b/launcher/minecraft/auth/steps/GetSkinStep.cpp @@ -16,22 +16,21 @@ void GetSkinStep::perform() { QUrl url(m_data->minecraftProfile.skin.url); - auto [request, response] = Net::Download::makeByteArray(url); - m_request = request; - m_request->enableAutoRetry(true); + m_response.reset(new QByteArray()); + m_request = Net::Download::makeByteArray(url, m_response); m_task.reset(new NetJob("GetSkinStep", APPLICATION->network())); m_task->setAskRetry(false); m_task->addNetAction(m_request); - connect(m_task.get(), &Task::finished, this, [this, response] { onRequestDone(response); }); + connect(m_task.get(), &Task::finished, this, &GetSkinStep::onRequestDone); m_task->start(); } -void GetSkinStep::onRequestDone(QByteArray* response) +void GetSkinStep::onRequestDone() { if (m_request->error() == QNetworkReply::NoError) - m_data->minecraftProfile.skin.data = *response; + m_data->minecraftProfile.skin.data = *m_response; emit finished(AccountTaskState::STATE_WORKING, tr("Got skin")); } diff --git a/launcher/minecraft/auth/steps/GetSkinStep.h b/launcher/minecraft/auth/steps/GetSkinStep.h index 2cd74ab92..c598f05d9 100644 --- a/launcher/minecraft/auth/steps/GetSkinStep.h +++ b/launcher/minecraft/auth/steps/GetSkinStep.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include "minecraft/auth/AuthStep.h" #include "net/Download.h" @@ -17,9 +18,10 @@ class GetSkinStep : public AuthStep { QString describe() override; private slots: - void onRequestDone(QByteArray* response); + void onRequestDone(); private: + std::shared_ptr m_response; Net::Download::Ptr m_request; NetJob::Ptr m_task; }; diff --git a/launcher/minecraft/auth/steps/LauncherLoginStep.cpp b/launcher/minecraft/auth/steps/LauncherLoginStep.cpp index 4ceed8586..cc108f4de 100644 --- a/launcher/minecraft/auth/steps/LauncherLoginStep.cpp +++ b/launcher/minecraft/auth/steps/LauncherLoginStep.cpp @@ -36,37 +36,35 @@ void LauncherLoginStep::perform() { "Accept", "application/json" }, }; - auto [request, response] = Net::Upload::makeByteArray(url, requestBody.toUtf8()); - m_request = request; - m_request->addHeaderProxy(std::make_unique(headers)); - m_request->enableAutoRetry(true); + m_response.reset(new QByteArray()); + m_request = Net::Upload::makeByteArray(url, m_response, requestBody.toUtf8()); + m_request->addHeaderProxy(new Net::RawHeaderProxy(headers)); m_task.reset(new NetJob("LauncherLoginStep", APPLICATION->network())); m_task->setAskRetry(false); m_task->addNetAction(m_request); - connect(m_task.get(), &Task::finished, this, [this, response] { onRequestDone(response); }); + connect(m_task.get(), &Task::finished, this, &LauncherLoginStep::onRequestDone); m_task->start(); qDebug() << "Getting Minecraft access token..."; } -void LauncherLoginStep::onRequestDone(QByteArray* response) +void LauncherLoginStep::onRequestDone() { - qCDebug(authCredentials()) << *response; + qCDebug(authCredentials()) << *m_response; if (m_request->error() != QNetworkReply::NoError) { qWarning() << "Reply error:" << m_request->error(); - if (Net::isApplicationError(m_request->error()) && !Net::isServerError(m_request->error())) { + if (Net::isApplicationError(m_request->error())) { emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to get Minecraft access token: %1").arg(m_request->errorString())); } else { - m_data->networkError = m_request->error(); emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to get Minecraft access token: %1").arg(m_request->errorString())); } return; } - if (!Parsers::parseMojangResponse(*response, m_data->yggdrasilToken)) { + if (!Parsers::parseMojangResponse(*m_response, m_data->yggdrasilToken)) { qWarning() << "Could not parse login_with_xbox response..."; emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to parse the Minecraft access token response.")); return; diff --git a/launcher/minecraft/auth/steps/LauncherLoginStep.h b/launcher/minecraft/auth/steps/LauncherLoginStep.h index 2501f5707..0b5969f2b 100644 --- a/launcher/minecraft/auth/steps/LauncherLoginStep.h +++ b/launcher/minecraft/auth/steps/LauncherLoginStep.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include "minecraft/auth/AuthStep.h" #include "net/NetJob.h" @@ -17,9 +18,10 @@ class LauncherLoginStep : public AuthStep { QString describe() override; private slots: - void onRequestDone(QByteArray* response); + void onRequestDone(); private: + std::shared_ptr m_response; Net::Upload::Ptr m_request; NetJob::Ptr m_task; }; diff --git a/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp b/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp index 78762a32a..0a7e12fc4 100644 --- a/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp +++ b/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp @@ -66,16 +66,15 @@ void MSADeviceCodeStep::perform() { "Content-Type", "application/x-www-form-urlencoded" }, { "Accept", "application/json" }, }; - auto [request, response] = Net::Upload::makeByteArray(url, payload); - m_request = request; - m_request->addHeaderProxy(std::make_unique(headers)); - m_request->enableAutoRetry(true); + m_response.reset(new QByteArray()); + m_request = Net::Upload::makeByteArray(url, m_response, payload); + m_request->addHeaderProxy(new Net::RawHeaderProxy(headers)); m_task.reset(new NetJob("MSADeviceCodeStep", APPLICATION->network())); m_task->setAskRetry(false); m_task->addNetAction(m_request); - connect(m_task.get(), &Task::finished, this, [this, response] { deviceAuthorizationFinished(response); }); + connect(m_task.get(), &Task::finished, this, &MSADeviceCodeStep::deviceAuthorizationFinished); m_task->start(); } @@ -111,21 +110,21 @@ DeviceAuthorizationResponse parseDeviceAuthorizationResponse(const QByteArray& d }; } -void MSADeviceCodeStep::deviceAuthorizationFinished(QByteArray* response) +void MSADeviceCodeStep::deviceAuthorizationFinished() { - if (!m_request->wasSuccessful() || m_request->error() != QNetworkReply::NoError) { - qWarning() << "Device authorization failed:" << m_request->error() << m_request->errorString(); - emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Device authorization failed: %1").arg(m_request->errorString())); - return; - } - - auto rsp = parseDeviceAuthorizationResponse(*response); + auto rsp = parseDeviceAuthorizationResponse(*m_response); if (!rsp.error.isEmpty() || !rsp.error_description.isEmpty()) { qWarning() << "Device authorization failed:" << rsp.error; emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Device authorization failed: %1").arg(rsp.error_description.isEmpty() ? rsp.error : rsp.error_description)); return; } + if (!m_request->wasSuccessful() || m_request->error() != QNetworkReply::NoError) { + qWarning() << "Device authorization failed:" << *m_response; + emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Failed to retrieve device authorization")); + return; + } + if (rsp.device_code.isEmpty() || rsp.user_code.isEmpty() || rsp.verification_uri.isEmpty() || rsp.expires_in == 0) { qWarning() << "Device authorization failed: required fields missing"; emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Device authorization failed: required fields missing")); @@ -182,11 +181,11 @@ void MSADeviceCodeStep::authenticateUser() { "Content-Type", "application/x-www-form-urlencoded" }, { "Accept", "application/json" }, }; - auto [request, response] = Net::Upload::makeByteArray(url, payload); - m_request = request; - m_request->addHeaderProxy(std::make_unique(headers)); + m_response.reset(new QByteArray()); + m_request = Net::Upload::makeByteArray(url, m_response, payload); + m_request->addHeaderProxy(new Net::RawHeaderProxy(headers)); - connect(m_request.get(), &Task::finished, this, [this, response] { authenticationFinished(response); }); + connect(m_request.get(), &Task::finished, this, &MSADeviceCodeStep::authenticationFinished); m_request->setNetwork(APPLICATION->network()); m_request->start(); @@ -227,7 +226,7 @@ AuthenticationResponse parseAuthenticationResponse(const QByteArray& data) obj.toVariantMap() }; } -void MSADeviceCodeStep::authenticationFinished(QByteArray* response) +void MSADeviceCodeStep::authenticationFinished() { if (m_request->error() == QNetworkReply::TimeoutError) { // rfc8628#section-3.5 @@ -239,7 +238,7 @@ void MSADeviceCodeStep::authenticationFinished(QByteArray* response) startPoolTimer(); return; } - auto rsp = parseAuthenticationResponse(*response); + auto rsp = parseAuthenticationResponse(*m_response); if (rsp.error == "slow_down") { // rfc8628#section-3.5 // "A variant of 'authorization_pending', the authorization request is diff --git a/launcher/minecraft/auth/steps/MSADeviceCodeStep.h b/launcher/minecraft/auth/steps/MSADeviceCodeStep.h index cfb8270d4..7f755563f 100644 --- a/launcher/minecraft/auth/steps/MSADeviceCodeStep.h +++ b/launcher/minecraft/auth/steps/MSADeviceCodeStep.h @@ -58,10 +58,10 @@ class MSADeviceCodeStep : public AuthStep { void authorizeWithBrowser(QString url, QString code, int expiresIn); private slots: - void deviceAuthorizationFinished(QByteArray* response); + void deviceAuthorizationFinished(); void startPoolTimer(); void authenticateUser(); - void authenticationFinished(QByteArray* response); + void authenticationFinished(); private: QString m_clientId; @@ -72,6 +72,7 @@ class MSADeviceCodeStep : public AuthStep { QTimer m_pool_timer; QTimer m_expiration_timer; + std::shared_ptr m_response; Net::Upload::Ptr m_request; NetJob::Ptr m_task; }; diff --git a/launcher/minecraft/auth/steps/MSAStep.cpp b/launcher/minecraft/auth/steps/MSAStep.cpp index 51a5e5ce0..29bab69d5 100644 --- a/launcher/minecraft/auth/steps/MSAStep.cpp +++ b/launcher/minecraft/auth/steps/MSAStep.cpp @@ -37,7 +37,6 @@ #include #include -#include #include #include @@ -136,7 +135,7 @@ MSAStep::MSAStep(AccountData* data, bool silent) : AuthStep(data), m_silent(sile m_oauth2.setAccessTokenUrl(QUrl("https://login.microsoftonline.com/consumers/oauth2/v2.0/token")); m_oauth2.setScope("XboxLive.SignIn XboxLive.offline_access"); m_oauth2.setClientIdentifier(m_clientId); - m_oauth2.setNetworkAccessManager(APPLICATION->network()); + m_oauth2.setNetworkAccessManager(APPLICATION->network().get()); connect(&m_oauth2, &QOAuth2AuthorizationCodeFlow::granted, this, [this] { m_data->msaClientID = m_oauth2.clientIdentifier(); diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp index b95682b2b..9de5422a8 100644 --- a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp +++ b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp @@ -21,21 +21,20 @@ void MinecraftProfileStep::perform() { "Accept", "application/json" }, { "Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8() } }; - auto [request, response] = Net::Download::makeByteArray(url); - m_request = request; - m_request->addHeaderProxy(std::make_unique(headers)); - m_request->enableAutoRetry(true); + m_response.reset(new QByteArray()); + m_request = Net::Download::makeByteArray(url, m_response); + m_request->addHeaderProxy(new Net::RawHeaderProxy(headers)); m_task.reset(new NetJob("MinecraftProfileStep", APPLICATION->network())); m_task->setAskRetry(false); m_task->addNetAction(m_request); - connect(m_task.get(), &Task::finished, this, [this, response] { onRequestDone(response); }); + connect(m_task.get(), &Task::finished, this, &MinecraftProfileStep::onRequestDone); m_task->start(); } -void MinecraftProfileStep::onRequestDone(QByteArray* response) +void MinecraftProfileStep::onRequestDone() { if (m_request->error() == QNetworkReply::ContentNotFoundError) { // NOTE: Succeed even if we do not have a profile. This is a valid account state. @@ -50,19 +49,18 @@ void MinecraftProfileStep::onRequestDone(QByteArray* response) qWarning() << " Error string :" << m_request->errorString(); qWarning() << " Response:"; - qWarning() << QString::fromUtf8(*response); + qWarning() << QString::fromUtf8(*m_response); - if (Net::isApplicationError(m_request->error()) && !Net::isServerError(m_request->error())) { + if (Net::isApplicationError(m_request->error())) { emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile acquisition failed: %1").arg(m_request->errorString())); } else { - m_data->networkError = m_request->error(); emit finished(AccountTaskState::STATE_OFFLINE, tr("Minecraft Java profile acquisition failed: %1").arg(m_request->errorString())); } return; } - if (!Parsers::parseMinecraftProfile(*response, m_data->minecraftProfile)) { + if (!Parsers::parseMinecraftProfile(*m_response, m_data->minecraftProfile)) { m_data->minecraftProfile = MinecraftProfile(); emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile response could not be parsed")); return; diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStep.h b/launcher/minecraft/auth/steps/MinecraftProfileStep.h index 5348f5ba1..e8b35b875 100644 --- a/launcher/minecraft/auth/steps/MinecraftProfileStep.h +++ b/launcher/minecraft/auth/steps/MinecraftProfileStep.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include "minecraft/auth/AuthStep.h" #include "net/Download.h" @@ -17,9 +18,10 @@ class MinecraftProfileStep : public AuthStep { QString describe() override; private slots: - void onRequestDone(QByteArray* response); + void onRequestDone(); private: + std::shared_ptr m_response; Net::Download::Ptr m_request; NetJob::Ptr m_task; }; diff --git a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp index 422aa1a54..21f97d6aa 100644 --- a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp +++ b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include "Application.h" #include "Logging.h" @@ -12,7 +12,7 @@ #include "net/Upload.h" XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, Token* token, QString relyingParty, QString authorizationKind) - : AuthStep(data), m_token(token), m_relyingParty(std::move(relyingParty)), m_authorizationKind(std::move(authorizationKind)) + : AuthStep(data), m_token(token), m_relyingParty(relyingParty), m_authorizationKind(authorizationKind) {} QString XboxAuthorizationStep::describe() @@ -22,7 +22,7 @@ QString XboxAuthorizationStep::describe() void XboxAuthorizationStep::perform() { - const QString xboxAuthTemplate = R"XXX( + QString xbox_auth_template = R"XXX( { "Properties": { "SandboxId": "RETAIL", @@ -34,40 +34,41 @@ void XboxAuthorizationStep::perform() "TokenType": "JWT" } )XXX"; - const auto xboxAuthData = xboxAuthTemplate.arg(m_data->userToken.token, m_relyingParty); + auto xbox_auth_data = xbox_auth_template.arg(m_data->userToken.token, m_relyingParty); // http://xboxlive.com - const QUrl url("https://xsts.auth.xboxlive.com/xsts/authorize"); - auto headers = QList{ { .headerName = "Content-Type", .headerValue = "application/json" }, - { .headerName = "Accept", .headerValue = "application/json" }, - { .headerName = "x-xbl-contract-version", .headerValue = "1" } }; - auto [request, response] = Net::Upload::makeByteArray(url, xboxAuthData.toUtf8()); - m_request = request; - m_request->addHeaderProxy(std::make_unique(headers)); - m_request->enableAutoRetry(true); + QUrl url("https://xsts.auth.xboxlive.com/xsts/authorize"); + auto headers = QList{ + { "Content-Type", "application/json" }, + { "Accept", "application/json" }, + }; + m_response.reset(new QByteArray()); + m_request = Net::Upload::makeByteArray(url, m_response, xbox_auth_data.toUtf8()); + m_request->addHeaderProxy(new Net::RawHeaderProxy(headers)); m_task.reset(new NetJob("XboxAuthorizationStep", APPLICATION->network())); m_task->setAskRetry(false); m_task->addNetAction(m_request); - connect(m_task.get(), &Task::finished, this, [this, response] { onRequestDone(response); }); + connect(m_task.get(), &Task::finished, this, &XboxAuthorizationStep::onRequestDone); m_task->start(); qDebug() << "Getting authorization token for" << m_relyingParty; } -void XboxAuthorizationStep::onRequestDone(QByteArray* response) +void XboxAuthorizationStep::onRequestDone() { - qCDebug(authCredentials()) << *response; + qCDebug(authCredentials()) << *m_response; if (m_request->error() != QNetworkReply::NoError) { qWarning() << "Reply error:" << m_request->error(); - if (Net::isApplicationError(m_request->error()) && !Net::isServerError(m_request->error())) { - if (processSTSError(*response)) { - return; + if (Net::isApplicationError(m_request->error())) { + if (!processSTSError()) { + emit finished(AccountTaskState::STATE_FAILED_SOFT, + tr("Failed to get authorization for %1 services. Error %2.").arg(m_authorizationKind, m_request->error())); + } else { + emit finished(AccountTaskState::STATE_FAILED_SOFT, + tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, m_request->errorString())); } - emit finished(AccountTaskState::STATE_FAILED_SOFT, - tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, m_request->errorString())); } else { - m_data->networkError = m_request->error(); emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, m_request->errorString())); } @@ -75,7 +76,7 @@ void XboxAuthorizationStep::onRequestDone(QByteArray* response) } Token temp; - if (!Parsers::parseXTokenResponse(*response, temp, m_authorizationKind)) { + if (!Parsers::parseXTokenResponse(*m_response, temp, m_authorizationKind)) { emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Could not parse authorization response for access to %1 services.").arg(m_authorizationKind)); return; @@ -92,12 +93,12 @@ void XboxAuthorizationStep::onRequestDone(QByteArray* response) emit finished(AccountTaskState::STATE_WORKING, tr("Got authorization to access %1").arg(m_relyingParty)); } -bool XboxAuthorizationStep::processSTSError(const QByteArray& response) +bool XboxAuthorizationStep::processSTSError() { if (m_request->error() == QNetworkReply::AuthenticationRequiredError) { QJsonParseError jsonError; - const QJsonDocument doc = QJsonDocument::fromJson(response, &jsonError); - if (jsonError.error != QJsonParseError::NoError) { + QJsonDocument doc = QJsonDocument::fromJson(*m_response, &jsonError); + if (jsonError.error) { qWarning() << "Cannot parse error XSTS response as JSON:" << jsonError.errorString(); emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Cannot parse %1 authorization error response as JSON: %2").arg(m_authorizationKind, jsonError.errorString())); diff --git a/launcher/minecraft/auth/steps/XboxAuthorizationStep.h b/launcher/minecraft/auth/steps/XboxAuthorizationStep.h index 9f424c0c3..8418727c4 100644 --- a/launcher/minecraft/auth/steps/XboxAuthorizationStep.h +++ b/launcher/minecraft/auth/steps/XboxAuthorizationStep.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include "minecraft/auth/AuthStep.h" #include "net/NetJob.h" @@ -17,16 +18,17 @@ class XboxAuthorizationStep : public AuthStep { QString describe() override; private: - bool processSTSError(const QByteArray& response); + bool processSTSError(); private slots: - void onRequestDone(QByteArray* response); + void onRequestDone(); private: Token* m_token; QString m_relyingParty; QString m_authorizationKind; + std::shared_ptr m_response; Net::Upload::Ptr m_request; NetJob::Ptr m_task; }; diff --git a/launcher/minecraft/auth/steps/XboxProfileStep.cpp b/launcher/minecraft/auth/steps/XboxProfileStep.cpp new file mode 100644 index 000000000..ef127caca --- /dev/null +++ b/launcher/minecraft/auth/steps/XboxProfileStep.cpp @@ -0,0 +1,66 @@ +#include "XboxProfileStep.h" + +#include +#include + +#include "Application.h" +#include "Logging.h" +#include "net/NetUtils.h" +#include "net/RawHeaderProxy.h" + +XboxProfileStep::XboxProfileStep(AccountData* data) : AuthStep(data) {} + +QString XboxProfileStep::describe() +{ + return tr("Fetching Xbox profile."); +} + +void XboxProfileStep::perform() +{ + QUrl url("https://profile.xboxlive.com/users/me/profile/settings"); + QUrlQuery q; + q.addQueryItem("settings", + "GameDisplayName,AppDisplayName,AppDisplayPicRaw,GameDisplayPicRaw," + "PublicGamerpic,ShowUserAsAvatar,Gamerscore,Gamertag,ModernGamertag,ModernGamertagSuffix," + "UniqueModernGamertag,AccountTier,TenureLevel,XboxOneRep," + "PreferredColor,Location,Bio,Watermarks," + "RealName,RealNameOverride,IsQuarantined"); + url.setQuery(q); + auto headers = QList{ + { "Content-Type", "application/json" }, + { "Accept", "application/json" }, + { "x-xbl-contract-version", "3" }, + { "Authorization", QString("XBL3.0 x=%1;%2").arg(m_data->userToken.extra["uhs"].toString(), m_data->xboxApiToken.token).toUtf8() } + }; + + m_response.reset(new QByteArray()); + m_request = Net::Download::makeByteArray(url, m_response); + m_request->addHeaderProxy(new Net::RawHeaderProxy(headers)); + + m_task.reset(new NetJob("XboxProfileStep", APPLICATION->network())); + m_task->setAskRetry(false); + m_task->addNetAction(m_request); + + connect(m_task.get(), &Task::finished, this, &XboxProfileStep::onRequestDone); + + m_task->start(); + qDebug() << "Getting Xbox profile..."; +} + +void XboxProfileStep::onRequestDone() +{ + if (m_request->error() != QNetworkReply::NoError) { + qWarning() << "Reply error:" << m_request->error(); + qCDebug(authCredentials()) << *m_response; + if (Net::isApplicationError(m_request->error())) { + emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to retrieve the Xbox profile: %1").arg(m_request->errorString())); + } else { + emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to retrieve the Xbox profile: %1").arg(m_request->errorString())); + } + return; + } + + qCDebug(authCredentials()) << "Xbox profile:" << *m_response; + + emit finished(AccountTaskState::STATE_WORKING, tr("Got Xbox profile")); +} diff --git a/launcher/minecraft/auth/steps/XboxProfileStep.h b/launcher/minecraft/auth/steps/XboxProfileStep.h new file mode 100644 index 000000000..f2ab874f2 --- /dev/null +++ b/launcher/minecraft/auth/steps/XboxProfileStep.h @@ -0,0 +1,27 @@ +#pragma once +#include +#include + +#include "minecraft/auth/AuthStep.h" +#include "net/Download.h" +#include "net/NetJob.h" + +class XboxProfileStep : public AuthStep { + Q_OBJECT + + public: + explicit XboxProfileStep(AccountData* data); + virtual ~XboxProfileStep() noexcept = default; + + void perform() override; + + QString describe() override; + + private slots: + void onRequestDone(); + + private: + std::shared_ptr m_response; + Net::Download::Ptr m_request; + NetJob::Ptr m_task; +}; diff --git a/launcher/minecraft/auth/steps/XboxUserStep.cpp b/launcher/minecraft/auth/steps/XboxUserStep.cpp index 382750218..b1fbb1f7a 100644 --- a/launcher/minecraft/auth/steps/XboxUserStep.cpp +++ b/launcher/minecraft/auth/steps/XboxUserStep.cpp @@ -37,36 +37,34 @@ void XboxUserStep::perform() // https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/live/rest/additional/httpstandardheaders { "x-xbl-contract-version", "1" } }; - auto [request, response] = Net::Upload::makeByteArray(url, xbox_auth_data.toUtf8()); - m_request = request; - m_request->addHeaderProxy(std::make_unique(headers)); - m_request->enableAutoRetry(true); + m_response.reset(new QByteArray()); + m_request = Net::Upload::makeByteArray(url, m_response, xbox_auth_data.toUtf8()); + m_request->addHeaderProxy(new Net::RawHeaderProxy(headers)); m_task.reset(new NetJob("XboxUserStep", APPLICATION->network())); m_task->setAskRetry(false); m_task->addNetAction(m_request); - connect(m_task.get(), &Task::finished, this, [this, response] { onRequestDone(response); }); + connect(m_task.get(), &Task::finished, this, &XboxUserStep::onRequestDone); m_task->start(); qDebug() << "First layer of Xbox auth ... commencing."; } -void XboxUserStep::onRequestDone(QByteArray* response) +void XboxUserStep::onRequestDone() { if (m_request->error() != QNetworkReply::NoError) { qWarning() << "Reply error:" << m_request->error(); - if (Net::isApplicationError(m_request->error()) && !Net::isServerError(m_request->error())) { + if (Net::isApplicationError(m_request->error())) { emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Xbox user authentication failed: %1").arg(m_request->errorString())); } else { - m_data->networkError = m_request->error(); emit finished(AccountTaskState::STATE_OFFLINE, tr("Xbox user authentication failed: %1").arg(m_request->errorString())); } return; } Token temp; - if (!Parsers::parseXTokenResponse(*response, temp, "UToken")) { + if (!Parsers::parseXTokenResponse(*m_response, temp, "UToken")) { qWarning() << "Could not parse user authentication response..."; emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Xbox user authentication response could not be understood.")); return; diff --git a/launcher/minecraft/auth/steps/XboxUserStep.h b/launcher/minecraft/auth/steps/XboxUserStep.h index b6330a499..f6cc822f2 100644 --- a/launcher/minecraft/auth/steps/XboxUserStep.h +++ b/launcher/minecraft/auth/steps/XboxUserStep.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include "minecraft/auth/AuthStep.h" #include "net/NetJob.h" @@ -17,9 +18,10 @@ class XboxUserStep : public AuthStep { QString describe() override; private slots: - void onRequestDone(QByteArray* response); + void onRequestDone(); private: + std::shared_ptr m_response; Net::Upload::Ptr m_request; NetJob::Ptr m_task; }; diff --git a/launcher/minecraft/launch/AutoInstallJava.cpp b/launcher/minecraft/launch/AutoInstallJava.cpp index f60780f1b..6a2bcb311 100644 --- a/launcher/minecraft/launch/AutoInstallJava.cpp +++ b/launcher/minecraft/launch/AutoInstallJava.cpp @@ -244,7 +244,7 @@ bool AutoInstallJava::abort() { if (m_current_task && m_current_task->canAbort()) { auto status = m_current_task->abort(); - emitAborted(); + emitFailed("Aborted."); return status; } return Task::abort(); diff --git a/launcher/minecraft/launch/AutoInstallJava.h b/launcher/minecraft/launch/AutoInstallJava.h index a4ffdff29..cbfcf5ee7 100644 --- a/launcher/minecraft/launch/AutoInstallJava.h +++ b/launcher/minecraft/launch/AutoInstallJava.h @@ -59,7 +59,7 @@ class AutoInstallJava : public LaunchStep { void tryNextMajorJava(); private: - MinecraftInstance* m_instance; + MinecraftInstancePtr m_instance; Task::Ptr m_current_task; qsizetype m_majorJavaVersionIndex = 0; diff --git a/launcher/minecraft/launch/ClaimAccount.cpp b/launcher/minecraft/launch/ClaimAccount.cpp index 1b375abb3..a3de1516a 100644 --- a/launcher/minecraft/launch/ClaimAccount.cpp +++ b/launcher/minecraft/launch/ClaimAccount.cpp @@ -6,7 +6,7 @@ ClaimAccount::ClaimAccount(LaunchTask* parent, AuthSessionPtr session) : LaunchStep(parent) { - if (session->launchMode == LaunchMode::Normal) { + if (session->status == AuthSession::Status::PlayableOnline && !session->demo) { auto accounts = APPLICATION->accounts(); m_account = accounts->getAccountByProfileName(session->player_name); } @@ -15,9 +15,9 @@ ClaimAccount::ClaimAccount(LaunchTask* parent, AuthSessionPtr session) : LaunchS void ClaimAccount::executeTask() { if (m_account) { - lock.reset(new UseLock(m_account.get())); + lock.reset(new UseLock(m_account)); + emitSucceeded(); } - emitSucceeded(); } void ClaimAccount::finalize() diff --git a/launcher/minecraft/launch/EnsureAvailableMemory.cpp b/launcher/minecraft/launch/EnsureAvailableMemory.cpp deleted file mode 100644 index 0f349674a..000000000 --- a/launcher/minecraft/launch/EnsureAvailableMemory.cpp +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2026 Octol1ttle - * - * 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 "EnsureAvailableMemory.h" - -#include "HardwareInfo.h" -#include "ui/dialogs/CustomMessageBox.h" - -EnsureAvailableMemory::EnsureAvailableMemory(LaunchTask* parent, MinecraftInstance* instance) : LaunchStep(parent), m_instance(instance) {} - -void EnsureAvailableMemory::executeTask() -{ -#ifdef Q_OS_MACOS - QString text; - switch (MacOSHardwareInfo::memoryPressureLevel()) { - case MacOSHardwareInfo::MemoryPressureLevel::Normal: - emitSucceeded(); - return; - case MacOSHardwareInfo::MemoryPressureLevel::Warning: - text = - tr("The system is under increased memory pressure.\n" - "This may lead to lag or slowdowns.\n" - "If possible, close other applications before continuing.\n\n" - "Launch anyway?"); - break; - case MacOSHardwareInfo::MemoryPressureLevel::Critical: - text = - tr("Your system is under critical memory pressure.\n" - "This may lead to severe slowdowns, crashes or system instability.\n" - "It is recommended to close other applications or restart your system.\n\n" - "Launch anyway?"); - break; - } - - bool shouldAbort = false; - - if (m_instance->settings()->get("LowMemWarning").toBool()) { - auto* dialog = CustomMessageBox::selectable(nullptr, tr("High memory pressure"), text, QMessageBox::Icon::Warning, - QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, - QMessageBox::StandardButton::No); - - shouldAbort = dialog->exec() == QMessageBox::No; - dialog->deleteLater(); - } - - const auto message = tr("The system is under high memory pressure"); - if (shouldAbort) { - emit logLine(message, MessageLevel::Fatal); - emitFailed(message); - return; - } - - emit logLine(message, MessageLevel::Warning); - emitSucceeded(); -#else - const uint64_t available = HardwareInfo::availableRamMiB(); - if (available == 0) { - // could not read - emitSucceeded(); - return; - } - - const uint64_t settingMin = m_instance->settings()->get("MinMemAlloc").toUInt(); - const uint64_t settingMax = m_instance->settings()->get("MaxMemAlloc").toUInt(); - const uint64_t max = std::max(settingMin, settingMax); - - if (static_cast(max) * 0.9 > static_cast(available)) { - bool shouldAbort = false; - - if (m_instance->settings()->get("LowMemWarning").toBool()) { - auto* dialog = CustomMessageBox::selectable( - nullptr, tr("Low free memory"), - tr("There might not be enough free RAM to launch this instance with the current memory settings.\n\n" - "Maximum allocated: %1 MiB\nFree: %2 MiB (out of %3 MiB total)\n\n" - "Launch anyway? This may cause slowdowns in the game and your system.") - .arg(max) - .arg(available) - .arg(HardwareInfo::totalRamMiB()), - QMessageBox::Icon::Warning, QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, - QMessageBox::StandardButton::No); - - shouldAbort = dialog->exec() == QMessageBox::No; - dialog->deleteLater(); - } - - const auto message = tr("Not enough RAM available to launch this instance"); - if (shouldAbort) { - emit logLine(message, MessageLevel::Fatal); - emitFailed(message); - return; - } - - emit logLine(message, MessageLevel::Warning); - } - - emitSucceeded(); -#endif -} diff --git a/launcher/minecraft/launch/EnsureAvailableMemory.h b/launcher/minecraft/launch/EnsureAvailableMemory.h deleted file mode 100644 index 3074a3f9a..000000000 --- a/launcher/minecraft/launch/EnsureAvailableMemory.h +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2026 Octol1ttle - * - * 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 "launch/LaunchStep.h" -#include "minecraft/MinecraftInstance.h" - -class EnsureAvailableMemory : public LaunchStep { - Q_OBJECT - - public: - explicit EnsureAvailableMemory(LaunchTask* parent, MinecraftInstance* instance); - ~EnsureAvailableMemory() override = default; - - void executeTask() override; - bool canAbort() const override { return false; } - - private: - MinecraftInstance* m_instance; -}; diff --git a/launcher/minecraft/launch/EnsureOfflineLibraries.cpp b/launcher/minecraft/launch/EnsureOfflineLibraries.cpp index 0165fbdf9..59801a1d0 100644 --- a/launcher/minecraft/launch/EnsureOfflineLibraries.cpp +++ b/launcher/minecraft/launch/EnsureOfflineLibraries.cpp @@ -27,27 +27,16 @@ void EnsureOfflineLibraries::executeTask() { const auto profile = m_instance->getPackProfile()->getProfile(); QStringList allJars; - profile->getLibraryFiles(m_instance->runtimeContext(), allJars, allJars, m_instance->getLocalLibraryPath(), m_instance->binRoot(), - false); - - QStringList missing; + profile->getLibraryFiles(m_instance->runtimeContext(), allJars, allJars, m_instance->getLocalLibraryPath(), m_instance->binRoot()); for (const auto& jar : allJars) { if (!QFileInfo::exists(jar)) { - missing.append(jar); + emit logLine(tr("This instance cannot be launched because some libraries are missing or have not been downloaded yet. Please " + "try again in online mode with a working Internet connection"), + MessageLevel::Fatal); + emitFailed("Required libraries are missing"); + return; } } - if (missing.isEmpty()) { - emitSucceeded(); - return; - } - - emit logLine("Missing libraries:", MessageLevel::Error); - for (const auto& jar : missing) { - emit logLine(" " + jar, MessageLevel::Error); - } - emit logLine(tr("\nThis instance cannot be launched because some libraries are missing or have not been downloaded yet. Please " - "try again in online mode with a working Internet connection"), - MessageLevel::Fatal); - emitFailed("Required libraries are missing"); + emitSucceeded(); } diff --git a/launcher/minecraft/launch/ExtractNatives.cpp b/launcher/minecraft/launch/ExtractNatives.cpp index 17d1a8cda..6d54d9b6a 100644 --- a/launcher/minecraft/launch/ExtractNatives.cpp +++ b/launcher/minecraft/launch/ExtractNatives.cpp @@ -53,7 +53,7 @@ static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibH name = replaceSuffix(name, ".jnilib", ".dylib"); } QString absFilePath = directory.absoluteFilePath(name); - return f->writeFile(ext, absFilePath, directory); + return f->writeFile(ext, absFilePath); }); } @@ -65,6 +65,7 @@ void ExtractNatives::executeTask() emitSucceeded(); return; } + auto settings = instance->settings(); auto outputPath = instance->getNativePath(); FS::ensureFolderPathExists(outputPath); diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp index e4d3ec1ef..d966287af 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.cpp +++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp @@ -94,8 +94,8 @@ void LauncherPartLaunch::executeTask() m_launchScript = instance->createLaunchScript(m_session, m_targetToJoin); QStringList args = instance->javaArguments(); - QString allArgs = args.join(" "); - emit logLine("Java arguments:\n " + m_parent->censorPrivateInfo(allArgs) + "\n", MessageLevel::Launcher); + QString allArgs = args.join(", "); + emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::Launcher); auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString()); @@ -164,9 +164,9 @@ void LauncherPartLaunch::on_state(LoggedProcess::State state) switch (state) { case LoggedProcess::FailedToStart: { //: Error message displayed if instace can't start - const char* reason = QT_TR_NOOP("Could not launch Minecraft: %1"); - emit logLine(QString(reason).arg(m_process.errorString()), MessageLevel::Fatal); - emitFailed(tr(reason).arg(m_process.errorString())); + const char* reason = QT_TR_NOOP("Could not launch Minecraft!"); + emit logLine(reason, MessageLevel::Fatal); + emitFailed(tr(reason)); return; } case LoggedProcess::Aborted: diff --git a/launcher/minecraft/launch/PrintInstanceInfo.cpp b/launcher/minecraft/launch/PrintInstanceInfo.cpp index df8c28c7b..e44d09839 100644 --- a/launcher/minecraft/launch/PrintInstanceInfo.cpp +++ b/launcher/minecraft/launch/PrintInstanceInfo.cpp @@ -19,10 +19,49 @@ #include #include "PrintInstanceInfo.h" -#include "HardwareInfo.h" - -#if defined(Q_OS_FREEBSD) +#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) namespace { +#if defined(Q_OS_LINUX) +void probeProcCpuinfo(QStringList& log) +{ + std::ifstream cpuin("/proc/cpuinfo"); + for (std::string line; std::getline(cpuin, line);) { + if (strncmp(line.c_str(), "model name", 10) == 0) { + log << QString::fromStdString(line.substr(13, std::string::npos)); + break; + } + } +} + +void runLspci(QStringList& log) +{ + // FIXME: fixed size buffers... + char buff[512]; + int gpuline = -1; + int cline = 0; + FILE* lspci = popen("lspci -k", "r"); + + if (!lspci) + return; + + while (fgets(buff, 512, lspci) != NULL) { + std::string str(buff); + if (str.length() < 9) + continue; + if (str.substr(8, 3) == "VGA") { + gpuline = cline; + log << QString::fromStdString(str.substr(35, std::string::npos)); + } + if (gpuline > -1 && gpuline != cline) { + if (cline - gpuline < 3) { + log << QString::fromStdString(str.substr(1, std::string::npos)); + } + } + cline++; + } + pclose(lspci); +} +#elif defined(Q_OS_FREEBSD) void runSysctlHwModel(QStringList& log) { char buff[512]; @@ -53,6 +92,24 @@ void runPciconf(QStringList& log) } pclose(pciconf); } +#endif +void runGlxinfo(QStringList& log) +{ + // FIXME: fixed size buffers... + char buff[512]; + FILE* glxinfo = popen("glxinfo", "r"); + if (!glxinfo) + return; + + while (fgets(buff, 512, glxinfo) != NULL) { + if (strncmp(buff, "OpenGL version string:", 22) == 0) { + log << QString::fromUtf8(buff); + break; + } + } + pclose(glxinfo); +} + } // namespace #endif @@ -61,23 +118,16 @@ void PrintInstanceInfo::executeTask() auto instance = m_parent->instance(); QStringList log; - log << ""; - log << "OS: " + QString("%1 | %2 | %3").arg(QSysInfo::prettyProductName(), QSysInfo::kernelType(), QSysInfo::kernelVersion()); -#ifdef Q_OS_FREEBSD +#if defined(Q_OS_LINUX) + ::probeProcCpuinfo(log); + ::runLspci(log); + ::runGlxinfo(log); +#elif defined(Q_OS_FREEBSD) ::runSysctlHwModel(log); ::runPciconf(log); -#else - log << "CPU: " + HardwareInfo::cpuInfo(); -#ifdef Q_OS_MACOS - log << "Memory pressure level: " + MacOSHardwareInfo::memoryPressureLevelName(); -#else - log << QString("RAM: %1 MiB (available: %2 MiB)").arg(HardwareInfo::totalRamMiB()).arg(HardwareInfo::availableRamMiB()); + ::runGlxinfo(log); #endif -#endif - log.append(HardwareInfo::gpuInfo()); - log << ""; - logLines(log, MessageLevel::Launcher); logLines(instance->verboseDescription(m_session, m_targetToJoin), MessageLevel::Launcher); emitSucceeded(); diff --git a/launcher/minecraft/launch/ScanModFolders.cpp b/launcher/minecraft/launch/ScanModFolders.cpp index cbe1599cb..1a2ddf194 100644 --- a/launcher/minecraft/launch/ScanModFolders.cpp +++ b/launcher/minecraft/launch/ScanModFolders.cpp @@ -45,19 +45,19 @@ void ScanModFolders::executeTask() auto m_inst = m_parent->instance(); auto loaders = m_inst->loaderModList(); - connect(loaders, &ModFolderModel::updateFinished, this, &ScanModFolders::modsDone); + connect(loaders.get(), &ModFolderModel::updateFinished, this, &ScanModFolders::modsDone); if (!loaders->update()) { m_modsDone = true; } auto cores = m_inst->coreModList(); - connect(cores, &ModFolderModel::updateFinished, this, &ScanModFolders::coreModsDone); + connect(cores.get(), &ModFolderModel::updateFinished, this, &ScanModFolders::coreModsDone); if (!cores->update()) { m_coreModsDone = true; } auto nils = m_inst->nilModList(); - connect(nils, &ModFolderModel::updateFinished, this, &ScanModFolders::nilModsDone); + connect(nils.get(), &ModFolderModel::updateFinished, this, &ScanModFolders::nilModsDone); if (!nils->update()) { m_nilModsDone = true; } diff --git a/launcher/minecraft/launch/VerifyJavaInstall.cpp b/launcher/minecraft/launch/VerifyJavaInstall.cpp index 9a1d14433..bc950d673 100644 --- a/launcher/minecraft/launch/VerifyJavaInstall.cpp +++ b/launcher/minecraft/launch/VerifyJavaInstall.cpp @@ -71,7 +71,7 @@ void VerifyJavaInstall::executeTask() } if (ignoreCompatibility) { - emit logLine(tr("Java major version is incompatible. Things might break.\n"), MessageLevel::Warning); + emit logLine(tr("Java major version is incompatible. Things might break."), MessageLevel::Warning); emitSucceeded(); return; } diff --git a/launcher/minecraft/mod/DataPack.cpp b/launcher/minecraft/mod/DataPack.cpp index c2b477d45..e089f2f6e 100644 --- a/launcher/minecraft/mod/DataPack.cpp +++ b/launcher/minecraft/mod/DataPack.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include "MTPixmapCache.h" #include "Version.h" @@ -32,121 +31,61 @@ // Values taken from: // https://minecraft.wiki/w/Pack_format#List_of_data_pack_formats -static const QMap, std::pair> s_pack_format_versions = { - { { 4, 0 }, { Version("1.13"), Version("1.14.4") } }, - { { 5, 0 }, { Version("1.15"), Version("1.16.1") } }, - { { 6, 0 }, { Version("1.16.2"), Version("1.16.5") } }, - { { 7, 0 }, { Version("1.17"), Version("1.17.1") } }, - { { 8, 0 }, { Version("1.18"), Version("1.18.1") } }, - { { 9, 0 }, { Version("1.18.2"), Version("1.18.2") } }, - { { 10, 0 }, { Version("1.19"), Version("1.19.3") } }, - { { 11, 0 }, { Version("23w03a"), Version("23w05a") } }, - { { 12, 0 }, { Version("1.19.4"), Version("1.19.4") } }, - { { 13, 0 }, { Version("23w12a"), Version("23w14a") } }, - { { 14, 0 }, { Version("23w16a"), Version("23w17a") } }, - { { 15, 0 }, { Version("1.20"), Version("1.20.1") } }, - { { 16, 0 }, { Version("23w31a"), Version("23w31a") } }, - { { 17, 0 }, { Version("23w32a"), Version("23w35a") } }, - { { 18, 0 }, { Version("1.20.2"), Version("1.20.2") } }, - { { 19, 0 }, { Version("23w40a"), Version("23w40a") } }, - { { 20, 0 }, { Version("23w41a"), Version("23w41a") } }, - { { 21, 0 }, { Version("23w42a"), Version("23w42a") } }, - { { 22, 0 }, { Version("23w43a"), Version("23w43b") } }, - { { 23, 0 }, { Version("23w44a"), Version("23w44a") } }, - { { 24, 0 }, { Version("23w45a"), Version("23w45a") } }, - { { 25, 0 }, { Version("23w46a"), Version("23w46a") } }, - { { 26, 0 }, { Version("1.20.3"), Version("1.20.4") } }, - { { 27, 0 }, { Version("23w51a"), Version("23w51b") } }, - { { 28, 0 }, { Version("24w03a"), Version("24w03b") } }, - { { 29, 0 }, { Version("24w04a"), Version("24w04a") } }, - { { 30, 0 }, { Version("24w05a"), Version("24w05b") } }, - { { 31, 0 }, { Version("24w06a"), Version("24w06a") } }, - { { 32, 0 }, { Version("24w07a"), Version("24w07a") } }, - { { 33, 0 }, { Version("24w09a"), Version("24w09a") } }, - { { 34, 0 }, { Version("24w10a"), Version("24w10a") } }, - { { 35, 0 }, { Version("24w11a"), Version("24w11a") } }, - { { 36, 0 }, { Version("24w12a"), Version("24w12a") } }, - { { 37, 0 }, { Version("24w13a"), Version("24w13a") } }, - { { 38, 0 }, { Version("24w14a"), Version("24w14a") } }, - { { 39, 0 }, { Version("1.20.5-pre1"), Version("1.20.5-pre1") } }, - { { 40, 0 }, { Version("1.20.5-pre2"), Version("1.20.5-pre2") } }, - { { 41, 0 }, { Version("1.20.5"), Version("1.20.6") } }, - { { 42, 0 }, { Version("24w18a"), Version("24w18a") } }, - { { 43, 0 }, { Version("24w19a"), Version("24w19b") } }, - { { 44, 0 }, { Version("24w20a"), Version("24w20a") } }, - { { 45, 0 }, { Version("24w21a"), Version("24w21b") } }, - { { 46, 0 }, { Version("1.21-pre1"), Version("1.21-pre1") } }, - { { 47, 0 }, { Version("1.21-pre2"), Version("1.21-pre2") } }, - { { 48, 0 }, { Version("1.21"), Version("1.21.1") } }, - { { 49, 0 }, { Version("24w33a"), Version("24w33a") } }, - { { 50, 0 }, { Version("24w34a"), Version("24w34a") } }, - { { 51, 0 }, { Version("24w35a"), Version("24w35a") } }, - { { 52, 0 }, { Version("24w36a"), Version("24w36a") } }, - { { 53, 0 }, { Version("24w37a"), Version("24w37a") } }, - { { 54, 0 }, { Version("24w38a"), Version("24w38a") } }, - { { 55, 0 }, { Version("24w39a"), Version("24w39a") } }, - { { 56, 0 }, { Version("24w40a"), Version("24w40a") } }, - { { 57, 0 }, { Version("1.21.2"), Version("1.21.3") } }, - { { 58, 0 }, { Version("24w44a"), Version("24w44a") } }, - { { 59, 0 }, { Version("24w45a"), Version("24w45a") } }, - { { 60, 0 }, { Version("24w46a"), Version("1.21.4-pre1") } }, - { { 61, 0 }, { Version("1.21.4"), Version("1.21.4") } }, - { { 62, 0 }, { Version("25w02a"), Version("25w02a") } }, - { { 63, 0 }, { Version("25w03a"), Version("25w03a") } }, - { { 64, 0 }, { Version("25w04a"), Version("25w04a") } }, - { { 65, 0 }, { Version("25w05a"), Version("25w05a") } }, - { { 66, 0 }, { Version("25w06a"), Version("25w06a") } }, - { { 67, 0 }, { Version("25w07a"), Version("25w07a") } }, - { { 68, 0 }, { Version("25w08a"), Version("25w08a") } }, - { { 69, 0 }, { Version("25w09a"), Version("25w09b") } }, - { { 70, 0 }, { Version("25w10a"), Version("1.21.5-pre1") } }, - { { 71, 0 }, { Version("1.21.5"), Version("1.21.5") } }, - { { 72, 0 }, { Version("25w15a"), Version("25w15a") } }, - { { 73, 0 }, { Version("25w16a"), Version("25w16a") } }, - { { 74, 0 }, { Version("25w17a"), Version("25w17a") } }, - { { 75, 0 }, { Version("25w18a"), Version("25w18a") } }, - { { 76, 0 }, { Version("25w19a"), Version("25w19a") } }, - { { 77, 0 }, { Version("25w20a"), Version("25w20a") } }, - { { 78, 0 }, { Version("25w21a"), Version("25w21a") } }, - { { 79, 0 }, { Version("1.21.6-pre1"), Version("1.21.6-pre2") } }, - { { 80, 0 }, { Version("1.21.6"), Version("1.21.6") } }, - { { 81, 0 }, { Version("1.21.7"), Version("1.21.8") } }, - { { 82, 0 }, { Version("25w31a"), Version("25w31a") } }, - { { 83, 0 }, { Version("25w32a"), Version("25w32a") } }, - { { 83, 1 }, { Version("25w33a"), Version("25w33a") } }, - { { 84, 0 }, { Version("25w34a"), Version("25w34b") } }, - { { 85, 0 }, { Version("25w35a"), Version("25w35a") } }, - { { 86, 0 }, { Version("25w36a"), Version("25w36b") } }, - { { 87, 0 }, { Version("25w37a"), Version("1.21.9-pre1") } }, - { { 87, 1 }, { Version("1.21.9-pre1"), Version("1.21.9-pre1") } }, - { { 88, 0 }, { Version("1.21.9"), Version("1.21.10") } }, - { { 89, 0 }, { Version("25w41a"), Version("25w41a") } }, - { { 90, 0 }, { Version("25w42a"), Version("25w42a") } }, - { { 91, 0 }, { Version("25w43a"), Version("25w43a") } }, - { { 92, 0 }, { Version("25w44a"), Version("25w44a") } }, - { { 93, 0 }, { Version("25w45a"), Version("25w45a") } }, - { { 93, 1 }, { Version("25w46a"), Version("25w46a") } }, - { { 94, 0 }, { Version("1.21.11-pre1"), Version("1.21.11-pre3") } }, - { { 94, 1 }, { Version("1.21.11-pre4"), Version("1.21.11") } }, - { { 95, 0 }, { Version("26.1-snap1"), Version("26.1-snap1") } }, - { { 96, 0 }, { Version("26.1-snap2"), Version("26.1-snap2") } }, - { { 97, 0 }, { Version("26.1-snap3"), Version("26.1-snap3") } }, - { { 97, 1 }, { Version("26.1-snap4"), Version("26.1-snap4") } }, - { { 98, 0 }, { Version("26.1-snap5"), Version("26.1-snap5") } }, - { { 99, 0 }, { Version("26.1-snap6"), Version("26.1-snap6") } }, - { { 99, 1 }, { Version("26.1-snap7"), Version("26.1-snap7") } }, - { { 99, 2 }, { Version("26.1-snap8"), Version("26.1-snap9") } }, - { { 99, 3 }, { Version("26.1-snap10"), Version("26.1-snap10") } }, - { { 100, 0 }, { Version("26.1-snap11"), Version("26.1-snap11") } }, -}; +static const QMap> s_pack_format_versions = { { 4, { Version("1.13"), Version("1.14.4") } }, + { 5, { Version("1.15"), Version("1.16.1") } }, + { 6, { Version("1.16.2"), Version("1.16.5") } }, + { 7, { Version("1.17"), Version("1.17.1") } }, + { 8, { Version("1.18"), Version("1.18.1") } }, + { 9, { Version("1.18.2"), Version("1.18.2") } }, + { 10, { Version("1.19"), Version("1.19.3") } }, + { 11, { Version("23w03a"), Version("23w05a") } }, + { 12, { Version("1.19.4"), Version("1.19.4") } }, + { 13, { Version("23w12a"), Version("23w14a") } }, + { 14, { Version("23w16a"), Version("23w17a") } }, + { 15, { Version("1.20"), Version("1.20.1") } }, + { 16, { Version("23w31a"), Version("23w31a") } }, + { 17, { Version("23w32a"), Version("23w35a") } }, + { 18, { Version("1.20.2"), Version("1.20.2") } }, + { 19, { Version("23w40a"), Version("23w40a") } }, + { 20, { Version("23w41a"), Version("23w41a") } }, + { 21, { Version("23w42a"), Version("23w42a") } }, + { 22, { Version("23w43a"), Version("23w43b") } }, + { 23, { Version("23w44a"), Version("23w44a") } }, + { 24, { Version("23w45a"), Version("23w45a") } }, + { 25, { Version("23w46a"), Version("23w46a") } }, + { 26, { Version("1.20.3"), Version("1.20.4") } }, + { 27, { Version("23w51a"), Version("23w51b") } }, + { 28, { Version("24w05a"), Version("24w05b") } }, + { 29, { Version("24w04a"), Version("24w04a") } }, + { 30, { Version("24w05a"), Version("24w05b") } }, + { 31, { Version("24w06a"), Version("24w06a") } }, + { 32, { Version("24w07a"), Version("24w07a") } }, + { 33, { Version("24w09a"), Version("24w09a") } }, + { 34, { Version("24w10a"), Version("24w10a") } }, + { 35, { Version("24w11a"), Version("24w11a") } }, + { 36, { Version("24w12a"), Version("24w12a") } }, + { 37, { Version("24w13a"), Version("24w13a") } }, + { 38, { Version("24w14a"), Version("24w14a") } }, + { 39, { Version("1.20.5-pre1"), Version("1.20.5-pre1") } }, + { 40, { Version("1.20.5-pre2"), Version("1.20.5-pre2") } }, + { 41, { Version("1.20.5"), Version("1.20.6") } }, + { 42, { Version("24w18a"), Version("24w18a") } }, + { 43, { Version("24w19a"), Version("24w19b") } }, + { 44, { Version("24w20a"), Version("24w20a") } }, + { 45, { Version("21w21a"), Version("21w21b") } }, + { 46, { Version("1.21-pre1"), Version("1.21-pre1") } }, + { 47, { Version("1.21-pre2"), Version("1.21-pre2") } }, + { 48, { Version("1.21"), Version("1.21") } } }; -void DataPack::setPackFormat(int new_format_id, std::pair min_format, std::pair max_format) +void DataPack::setPackFormat(int new_format_id) { QMutexLocker locker(&m_data_lock); + if (!s_pack_format_versions.contains(new_format_id)) { + qWarning() << "Pack format '" << new_format_id << "' is not a recognized data pack id!"; + } + m_pack_format = new_format_id; - m_min_format = min_format; - m_max_format = max_format; } void DataPack::setDescription(QString new_description) @@ -201,22 +140,19 @@ QPixmap DataPack::image(QSize size, Qt::AspectRatioMode mode) const return image(size); } -static std::pair map(std::pair format, const QMap, std::pair>& versions) +std::pair DataPack::compatibleVersions() const { - if (format.first == 0 || !versions.contains(format)) { + if (!s_pack_format_versions.contains(m_pack_format)) { return { {}, {} }; } - return versions.constFind(format).value(); -} -static std::pair map(int format, const QMap, std::pair>& versions) -{ - return map({ format, 0 }, versions); + + return s_pack_format_versions.constFind(m_pack_format).value(); } int DataPack::compare(const Resource& other, SortType type) const { - const auto& cast_other = static_cast(other); - if (type == SortType::PackFormat) { + auto const& cast_other = static_cast(other); + if (type == SortType::PACK_FORMAT) { auto this_ver = packFormat(); auto other_ver = cast_other.packFormat(); @@ -232,64 +168,21 @@ int DataPack::compare(const Resource& other, SortType type) const bool DataPack::applyFilter(QRegularExpression filter) const { - if (filter.match(description()).hasMatch()) { + if (filter.match(description()).hasMatch()) return true; - } - if (filter.match(QString::number(packFormat())).hasMatch()) { + if (filter.match(QString::number(packFormat())).hasMatch()) + return true; + + if (filter.match(compatibleVersions().first.toString()).hasMatch()) + return true; + if (filter.match(compatibleVersions().second.toString()).hasMatch()) return true; - } - auto versions = { map(m_pack_format, mappings()), map(m_min_format, mappings()), map(m_max_format, mappings()) }; - for (const auto& version : versions) { - if (!version.first.isEmpty()) { - if (filter.match(version.first.toString()).hasMatch()) { - return true; - } - if (filter.match(version.second.toString()).hasMatch()) { - return true; - } - } - } return Resource::applyFilter(filter); } bool DataPack::valid() const { - return m_pack_format != 0 || (m_min_format.first != 0 && m_max_format.first != 0); -} - -QMap, std::pair> DataPack::mappings() const -{ - return s_pack_format_versions; -} - -QString DataPack::packFormatStr() const -{ - if (m_pack_format != 0) { - auto version_bounds = map(m_pack_format, mappings()); - if (version_bounds.first.toString().isEmpty()) { - return QString::number(m_pack_format); - } - return QString("%1 (%2 - %3)") - .arg(QString::number(m_pack_format), version_bounds.first.toString(), version_bounds.second.toString()); - } - auto min_bound = map(m_min_format, mappings()); - auto max_bound = map(m_max_format, mappings()); - auto min_version = min_bound.first; - auto max_version = max_bound.second; - if (min_version.isEmpty() || max_version.isEmpty()) { - return tr("Unrecognized"); - } - auto str = QString("[") + QString::number(m_min_format.first); - if (m_min_format.second != 0) { - str += "." + QString::number(m_min_format.second); - } - - str += QString(" - ") + QString::number(m_max_format.first); - if (m_max_format.second != 0) { - str += "." + QString::number(m_max_format.second); - } - - return str + QString(" (%2 - %3)").arg(min_version.toString(), max_version.toString()); + return m_pack_format != 0; } diff --git a/launcher/minecraft/mod/DataPack.h b/launcher/minecraft/mod/DataPack.h index 89da0178a..2943dd4bc 100644 --- a/launcher/minecraft/mod/DataPack.h +++ b/launcher/minecraft/mod/DataPack.h @@ -25,7 +25,6 @@ #include #include -#include class Version; @@ -42,6 +41,8 @@ class DataPack : public Resource { /** Gets the numerical ID of the pack format. */ int packFormat() const { return m_pack_format; } + /** Gets, respectively, the lower and upper versions supported by the set pack format. */ + virtual std::pair compatibleVersions() const; /** Gets the description of the data pack. */ QString description() const { return m_description; } @@ -50,7 +51,7 @@ class DataPack : public Resource { QPixmap image(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const; /** Thread-safe. */ - void setPackFormat(int new_format_id, std::pair min_format, std::pair max_format); + void setPackFormat(int new_format_id); /** Thread-safe. */ void setDescription(QString new_description); @@ -60,14 +61,9 @@ class DataPack : public Resource { bool valid() const override; - [[nodiscard]] int compare(const Resource& other, SortType type) const override; + [[nodiscard]] int compare(Resource const& other, SortType type) const override; [[nodiscard]] bool applyFilter(QRegularExpression filter) const override; - QString packFormatStr() const; - - protected: - virtual QMap, std::pair> mappings() const; - protected: mutable QMutex m_data_lock; @@ -75,8 +71,6 @@ class DataPack : public Resource { * See https://minecraft.wiki/w/Data_pack#pack.mcmeta */ int m_pack_format = 0; - std::pair m_min_format; - std::pair m_max_format; /** The data pack's description, as defined in the pack.mcmeta file. */ diff --git a/launcher/minecraft/mod/DataPackFolderModel.cpp b/launcher/minecraft/mod/DataPackFolderModel.cpp index 350ae60e1..7abab7a06 100644 --- a/launcher/minecraft/mod/DataPackFolderModel.cpp +++ b/launcher/minecraft/mod/DataPackFolderModel.cpp @@ -40,95 +40,94 @@ #include #include +#include "Version.h" + #include "minecraft/mod/tasks/LocalDataPackParseTask.h" -DataPackFolderModel::DataPackFolderModel(const QString& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent) - : ResourceFolderModel(QDir(dir), instance, isIndexed, createDir, parent) +DataPackFolderModel::DataPackFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) + : ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent) { - m_columnNames = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Size", "File Name" }); - m_columnNamesTranslated = - QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Size"), tr("File Name") }); - m_columnSortKeys = { SortType::Enabled, SortType::Name, SortType::Name, SortType::PackFormat, - SortType::Date, SortType::Size, SortType::Filename }; - m_columnResizeModes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, - QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; - m_columnsHideable = { false, true, false, true, true, true, true }; + m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified" }); + m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified") }); + m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE }; + m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, + QHeaderView::Interactive }; + m_columnsHideable = { false, true, false, true, true }; } QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const { - if (!validateIndex(index)) { + if (!validateIndex(index)) return {}; - } int row = index.row(); int column = index.column(); switch (role) { - case Qt::BackgroundRole: - return rowBackground(row); case Qt::DisplayRole: - if (column == PackFormatColumn) { - const auto& resource = at(row); - return resource.packFormatStr(); + switch (column) { + case NameColumn: + return m_resources[row]->name(); + case PackFormatColumn: { + auto& resource = at(row); + auto pack_format = resource.packFormat(); + if (pack_format == 0) + return tr("Unrecognized"); + + auto version_bounds = resource.compatibleVersions(); + if (version_bounds.first.toString().isEmpty()) + return QString::number(pack_format); + + return QString("%1 (%2 - %3)") + .arg(QString::number(pack_format), version_bounds.first.toString(), version_bounds.second.toString()); + } + case DateColumn: + return m_resources[row]->dateTimeChanged(); + + default: + return {}; } - if (column == SizeColumn) { - const auto& resource = at(row); - return resource.sizeStr(); - } - break; case Qt::DecorationRole: { + if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink())) + return QIcon::fromTheme("status-yellow"); if (column == ImageColumn) { return at(row).image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding); } - break; + return {}; } case Qt::ToolTipRole: { if (column == PackFormatColumn) { //: The string being explained by this is in the format: ID (Lower version - Upper version) return tr("The data pack format ID, as well as the Minecraft versions it was designed for."); } - break; + if (column == NameColumn) { + if (at(row).isSymLinkUnder(instDirPath())) { + return m_resources[row]->internal_id() + + tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." + "\nCanonical Path: %1") + .arg(at(row).fileinfo().canonicalFilePath()); + ; + } + if (at(row).isMoreThanOneHardLink()) { + return m_resources[row]->internal_id() + + tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original."); + } + } + return m_resources[row]->internal_id(); } case Qt::SizeHintRole: if (column == ImageColumn) { return QSize(32, 32); } - break; + return {}; + case Qt::CheckStateRole: + if (column == ActiveColumn) + return at(row).enabled() ? Qt::Checked : Qt::Unchecked; + else + return {}; default: - break; + return {}; } - - // map the columns to the base equivilents - QModelIndex mappedIndex; - switch (column) { - case ActiveColumn: - mappedIndex = index.siblingAtColumn(ResourceFolderModel::ActiveColumn); - break; - case NameColumn: - mappedIndex = index.siblingAtColumn(ResourceFolderModel::NameColumn); - break; - case DateColumn: - mappedIndex = index.siblingAtColumn(ResourceFolderModel::DateColumn); - break; - case ProviderColumn: - mappedIndex = index.siblingAtColumn(ResourceFolderModel::ProviderColumn); - break; - case FileNameColumn: - mappedIndex = index.siblingAtColumn(ResourceFolderModel::FileNameColumn); - break; - case SizeColumn: - mappedIndex = index.siblingAtColumn(ResourceFolderModel::SizeColumn); - break; - default: - break; - } - - if (mappedIndex.isValid()) { - return ResourceFolderModel::data(mappedIndex, role); - } - - return {}; } QVariant DataPackFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientation orientation, int role) const @@ -141,8 +140,6 @@ QVariant DataPackFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien case PackFormatColumn: case DateColumn: case ImageColumn: - case SizeColumn: - case FileNameColumn: return columnNames().at(section); default: return {}; @@ -159,10 +156,6 @@ QVariant DataPackFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien return tr("The data pack format ID, as well as the Minecraft versions it was designed for."); case DateColumn: return tr("The date and time this data pack was last changed (or added)."); - case SizeColumn: - return tr("The size of the data pack."); - case FileNameColumn: - return tr("The file name of the data pack."); default: return {}; } @@ -178,7 +171,7 @@ QVariant DataPackFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien int DataPackFolderModel::columnCount(const QModelIndex& parent) const { - return parent.isValid() ? 0 : NumColumns; + return parent.isValid() ? 0 : NUM_COLUMNS; } Resource* DataPackFolderModel::createResource(const QFileInfo& file) @@ -188,5 +181,5 @@ Resource* DataPackFolderModel::createResource(const QFileInfo& file) Task* DataPackFolderModel::createParseTask(Resource& resource) { - return new LocalDataPackParseTask(m_nextResolutionTicket, static_cast(&resource)); + return new LocalDataPackParseTask(m_next_resolution_ticket, static_cast(&resource)); } diff --git a/launcher/minecraft/mod/DataPackFolderModel.h b/launcher/minecraft/mod/DataPackFolderModel.h index 24133354a..2b90e1a2a 100644 --- a/launcher/minecraft/mod/DataPackFolderModel.h +++ b/launcher/minecraft/mod/DataPackFolderModel.h @@ -39,24 +39,16 @@ #include "ResourceFolderModel.h" #include "DataPack.h" +#include "ResourcePack.h" class DataPackFolderModel : public ResourceFolderModel { Q_OBJECT public: - enum Columns : std::uint8_t { - ActiveColumn = 0, - ImageColumn, - NameColumn, - PackFormatColumn, - DateColumn, - SizeColumn, - FileNameColumn, - NumColumns - }; + enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, NUM_COLUMNS }; - explicit DataPackFolderModel(const QString& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent = nullptr); + explicit DataPackFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); - QString id() const override { return "datapacks"; } + virtual QString id() const override { return "datapacks"; } QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; @@ -64,7 +56,7 @@ class DataPackFolderModel : public ResourceFolderModel { int columnCount(const QModelIndex& parent) const override; [[nodiscard]] Resource* createResource(const QFileInfo& file) override; - [[nodiscard]] Task* createParseTask(Resource& /*unused*/) override; + [[nodiscard]] Task* createParseTask(Resource&) override; RESOURCE_HELPERS(DataPack) }; diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index f0cdfaff6..32b940c5b 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -40,7 +40,6 @@ #include #include #include -#include #include "MTPixmapCache.h" #include "MetadataHandler.h" @@ -50,34 +49,6 @@ #include "minecraft/mod/tasks/LocalModParseTask.h" #include "modplatform/ModIndex.h" -namespace { - -int compareVersionLists(const QStringList& leftVersions, const QStringList& rightVersions) -{ - const qsizetype commonSize = std::min(leftVersions.size(), rightVersions.size()); - - for (qsizetype i = 0; i < commonSize; i++) { - const auto leftVersion = Version(leftVersions.at(i).trimmed()); - const auto rightVersion = Version(rightVersions.at(i).trimmed()); - - if (leftVersion > rightVersion) - return 1; - - if (leftVersion < rightVersion) - return -1; - } - - if (leftVersions.size() > rightVersions.size()) - return 1; - - if (leftVersions.size() < rightVersions.size()) - return -1; - - return 0; -} - -} // namespace - Mod::Mod(const QFileInfo& file) : Resource(file), m_local_details() { m_enabled = (file.suffix() != "disabled"); @@ -90,18 +61,18 @@ void Mod::setDetails(const ModDetails& details) int Mod::compare(const Resource& other, SortType type) const { - auto cast_other = dynamic_cast(&other); + auto cast_other = dynamic_cast(&other); if (!cast_other) return Resource::compare(other, type); switch (type) { default: - case SortType::Enabled: - case SortType::Name: - case SortType::Date: - case SortType::Size: + case SortType::ENABLED: + case SortType::NAME: + case SortType::DATE: + case SortType::SIZE: return Resource::compare(other, type); - case SortType::Version: { + case SortType::VERSION: { auto this_ver = Version(version()); auto other_ver = Version(cast_other->version()); if (this_ver > other_ver) @@ -110,44 +81,30 @@ int Mod::compare(const Resource& other, SortType type) const return -1; break; } - case SortType::Side: { + case SortType::SIDE: { auto compare_result = QString::compare(side(), cast_other->side(), Qt::CaseInsensitive); if (compare_result != 0) return compare_result; break; } - case SortType::McVersions: { - auto compare_result = compareVersionLists(mcVersions(), cast_other->mcVersions()); + case SortType::MC_VERSIONS: { + auto compare_result = QString::compare(mcVersions(), cast_other->mcVersions(), Qt::CaseInsensitive); if (compare_result != 0) return compare_result; break; } - case SortType::Loaders: { + case SortType::LOADERS: { auto compare_result = QString::compare(loaders(), cast_other->loaders(), Qt::CaseInsensitive); if (compare_result != 0) return compare_result; break; } - case SortType::ReleaseType: { + case SortType::RELEASE_TYPE: { auto compare_result = QString::compare(releaseType(), cast_other->releaseType(), Qt::CaseInsensitive); if (compare_result != 0) return compare_result; break; } - case SortType::RequiredBy: { - if (requiredByCount() > cast_other->requiredByCount()) - return 1; - if (requiredByCount() < cast_other->requiredByCount()) - return -1; - break; - } - case SortType::Requires: { - if (requiresCount() > cast_other->requiresCount()) - return 1; - if (requiresCount() < cast_other->requiresCount()) - return -1; - break; - } } return 0; } @@ -226,19 +183,14 @@ auto Mod::side() const -> QString return ModPlatform::SideUtils::toString(ModPlatform::Side::UniversalSide); } -auto Mod::mcVersions() const -> QStringList +auto Mod::mcVersions() const -> QString { if (metadata()) - return metadata()->mcVersions; + return metadata()->mcVersions.join(", "); return {}; } -auto Mod::mcVersionsString() const -> QString -{ - return mcVersions().join(", "); -} - auto Mod::releaseType() const -> QString { if (metadata()) @@ -331,25 +283,3 @@ bool Mod::valid() const { return !m_local_details.mod_id.isEmpty(); } - -QStringList Mod::dependencies() const -{ - return details().dependencies; -} - -int Mod::requiredByCount() const -{ - return m_requiredByCount; -} -int Mod::requiresCount() const -{ - return m_requiresCount; -} -void Mod::setRequiredByCount(int value) -{ - m_requiredByCount = value; -} -void Mod::setRequiresCount(int value) -{ - m_requiresCount = value; -} diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index 27768ae2c..c548f5350 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -68,16 +68,8 @@ class Mod : public Resource { auto issueTracker() const -> QString; auto side() const -> QString; auto loaders() const -> QString; - auto mcVersions() const -> QStringList; - auto mcVersionsString() const -> QString; + auto mcVersions() const -> QString; auto releaseType() const -> QString; - QStringList dependencies() const; - - int requiredByCount() const; - int requiresCount() const; - - void setRequiredByCount(int value); - void setRequiresCount(int value); /** Get the intneral path to the mod's icon file*/ QString iconPath() const { return m_local_details.icon_file; } @@ -110,7 +102,4 @@ class Mod : public Resource { bool wasEverUsed = false; bool wasReadAttempt = false; } mutable m_packImageCacheKey; - - int m_requiredByCount = 0; - int m_requiresCount = 0; }; diff --git a/launcher/minecraft/mod/ModDetails.h b/launcher/minecraft/mod/ModDetails.h index 02cf42a38..9b81f561f 100644 --- a/launcher/minecraft/mod/ModDetails.h +++ b/launcher/minecraft/mod/ModDetails.h @@ -138,8 +138,6 @@ struct ModDetails { /* Path of mod logo */ QString icon_file = {}; - QStringList dependencies = {}; - ModDetails() = default; /** Metadata should be handled manually to properly set the mod status. */ @@ -154,7 +152,6 @@ struct ModDetails { , issue_tracker(other.issue_tracker) , licenses(other.licenses) , icon_file(other.icon_file) - , dependencies(other.dependencies) {} ModDetails& operator=(const ModDetails& other) = default; diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index a03410fd3..45ec76f19 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -38,9 +38,7 @@ #include "ModFolderModel.h" #include -#include #include -#include #include #include #include @@ -50,49 +48,38 @@ #include #include #include -#include -#include -#include "minecraft/mod/Resource.h" -#include "minecraft/mod/ResourceFolderModel.h" #include "minecraft/mod/tasks/LocalModParseTask.h" -#include "modplatform/ModIndex.h" -#include "ui/dialogs/CustomMessageBox.h" -ModFolderModel::ModFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent) - : ResourceFolderModel(QDir(dir), instance, isIndexed, createDir, parent) +ModFolderModel::ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) + : ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent) { - m_columnNames = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Size", "Side", "Loaders", - "Minecraft Versions", "Release Type", "Requires", "Required By", "File Name" }); - m_columnNamesTranslated = - QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider"), tr("Size"), tr("Side"), - tr("Loaders"), tr("Minecraft Versions"), tr("Release Type"), tr("Requires"), tr("Required By"), tr("File Name") }); - m_columnSortKeys = { SortType::Enabled, SortType::Name, SortType::Name, SortType::Version, SortType::Date, - SortType::Provider, SortType::Size, SortType::Side, SortType::Loaders, SortType::McVersions, - SortType::ReleaseType, SortType::Requires, SortType::RequiredBy, SortType::Filename }; - m_columnResizeModes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, - QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, - QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, - QHeaderView::Interactive, QHeaderView::Interactive }; - m_columnsHideable = { false, true, false, true, true, true, true, true, true, true, true, true, true, true }; - - connect(this, &ModFolderModel::parseFinished, this, &ModFolderModel::onParseFinished); + m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Size", "Side", "Loaders", + "Minecraft Versions", "Release Type" }); + m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider"), + tr("Size"), tr("Side"), tr("Loaders"), tr("Minecraft Versions"), tr("Release Type") }); + m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION, + SortType::DATE, SortType::PROVIDER, SortType::SIZE, SortType::SIDE, + SortType::LOADERS, SortType::MC_VERSIONS, SortType::RELEASE_TYPE }; + m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, + QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, + QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; + m_columnsHideable = { false, true, false, true, true, true, true, true, true, true, true }; } QVariant ModFolderModel::data(const QModelIndex& index, int role) const { - if (!validateIndex(index)) { + if (!validateIndex(index)) return {}; - } int row = index.row(); int column = index.column(); switch (role) { - case Qt::BackgroundRole: - return rowBackground(row); case Qt::DisplayRole: switch (column) { + case NameColumn: + return m_resources[row]->name(); case VersionColumn: { switch (at(row).type()) { case ResourceType::FOLDER: @@ -100,8 +87,14 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const case ResourceType::SINGLEFILE: return tr("File"); default: - return at(row).version(); + break; } + return at(row).version(); + } + case DateColumn: + return at(row).dateTimeChanged(); + case ProviderColumn: { + return at(row).provider(); } case SideColumn: { return at(row).side(); @@ -110,66 +103,51 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const return at(row).loaders(); } case McVersionsColumn: { - return at(row).mcVersionsString(); + return at(row).mcVersions(); } case ReleaseTypeColumn: { return at(row).releaseType(); } - case RequiredByColumn: { - return at(row).requiredByCount(); - } - case RequiresColumn: { - return at(row).requiresCount(); - } + case SizeColumn: + return at(row).sizeStr(); default: - break; + return QVariant(); } - break; + + case Qt::ToolTipRole: + if (column == NameColumn) { + if (at(row).isSymLinkUnder(instDirPath())) { + return m_resources[row]->internal_id() + + tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." + "\nCanonical Path: %1") + .arg(at(row).fileinfo().canonicalFilePath()); + } + if (at(row).isMoreThanOneHardLink()) { + return m_resources[row]->internal_id() + + tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original."); + } + } + return m_resources[row]->internal_id(); case Qt::DecorationRole: { + if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink())) + return QIcon::fromTheme("status-yellow"); if (column == ImageColumn) { return at(row).icon({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding); } - break; + return {}; } case Qt::SizeHintRole: if (column == ImageColumn) { return QSize(32, 32); } - break; + return {}; + case Qt::CheckStateRole: + if (column == ActiveColumn) + return at(row).enabled() ? Qt::Checked : Qt::Unchecked; + return QVariant(); default: - break; + return QVariant(); } - - // map the columns to the base equivilents - QModelIndex mappedIndex; - switch (column) { - case ActiveColumn: - mappedIndex = index.siblingAtColumn(ResourceFolderModel::ActiveColumn); - break; - case NameColumn: - mappedIndex = index.siblingAtColumn(ResourceFolderModel::NameColumn); - break; - case DateColumn: - mappedIndex = index.siblingAtColumn(ResourceFolderModel::DateColumn); - break; - case ProviderColumn: - mappedIndex = index.siblingAtColumn(ResourceFolderModel::ProviderColumn); - break; - case SizeColumn: - mappedIndex = index.siblingAtColumn(ResourceFolderModel::SizeColumn); - break; - case FileNameColumn: - mappedIndex = index.siblingAtColumn(ResourceFolderModel::FileNameColumn); - break; - default: - break; - } - - if (mappedIndex.isValid()) { - return ResourceFolderModel::data(mappedIndex, role); - } - - return {}; } QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientation orientation, int role) const @@ -188,9 +166,6 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio case McVersionsColumn: case ReleaseTypeColumn: case SizeColumn: - case RequiredByColumn: - case RequiresColumn: - case FileNameColumn: return columnNames().at(section); default: return QVariant(); @@ -218,28 +193,23 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio return tr("The release type."); case SizeColumn: return tr("The size of the mod."); - case RequiredByColumn: - return tr("For each mod, the number of other mods which depend on it."); - case RequiresColumn: - return tr("For each mod, the number of other mods it depends on."); - case FileNameColumn: - return tr("The file name of the mod."); default: return QVariant(); } default: return QVariant(); } + return QVariant(); } int ModFolderModel::columnCount(const QModelIndex& parent) const { - return parent.isValid() ? 0 : NumColumns; + return parent.isValid() ? 0 : NUM_COLUMNS; } Task* ModFolderModel::createParseTask(Resource& resource) { - return new LocalModParseTask(m_nextResolutionTicket, resource.type(), resource.fileinfo()); + return new LocalModParseTask(m_next_resolution_ticket, resource.type(), resource.fileinfo()); } bool ModFolderModel::isValid() @@ -247,288 +217,24 @@ bool ModFolderModel::isValid() return m_dir.exists() && m_dir.isReadable(); } -void ModFolderModel::onParseSucceeded(int ticket, const QString& resourceId) +void ModFolderModel::onParseSucceeded(int ticket, QString mod_id) { - auto iter = m_activeParseTasks.constFind(ticket); - if (iter == m_activeParseTasks.constEnd()) { + auto iter = m_active_parse_tasks.constFind(ticket); + if (iter == m_active_parse_tasks.constEnd()) return; - } - int row = m_resourcesIndex[resourceId]; + int row = m_resources_index[mod_id]; - const auto& parseTask = *iter; - auto* castTask = static_cast(parseTask.get()); + auto parse_task = *iter; + auto cast_task = static_cast(parse_task.get()); - Q_ASSERT(castTask->token() == ticket); + Q_ASSERT(cast_task->token() == ticket); - auto resource = find(resourceId); + auto resource = find(mod_id); - auto result = castTask->result(); - if (result && resource) { - auto* mod = static_cast(resource.get()); - mod->finishResolvingWithDetails(std::move(result->details)); - } - emit dataChanged(index(row, RequiresColumn), index(row, RequiredByColumn)); -} - -namespace { -Mod* findById(QSet mods, const QString& resourceId) -{ - auto found = std::ranges::find_if(mods, [resourceId](Mod* m) { return m->mod_id() == resourceId; }); - return found != mods.end() ? *found : nullptr; -} -} // namespace - -void ModFolderModel::onParseFinished() -{ - if (hasPendingParseTasks()) { - return; - } - auto modsList = allMods(); - auto mods = QSet(modsList.begin(), modsList.end()); - - m_requires.clear(); - m_requiredBy.clear(); - - auto findByProjectID = [mods](const QVariant& modId, ModPlatform::ResourceProvider provider) -> Mod* { - auto found = std::ranges::find_if(mods, [modId, provider](Mod* m) { - return m->metadata() && m->metadata()->provider == provider && m->metadata()->project_id == modId; - }); - return found != mods.end() ? *found : nullptr; - }; - for (auto* mod : mods) { - auto id = mod->mod_id(); - for (const auto& dep : mod->dependencies()) { - auto* d = findById(mods, dep); - if (d) { - m_requires[id] << d; - m_requiredBy[d->mod_id()] << mod; - } - } - if (mod->metadata()) { - for (const auto& dep : mod->metadata()->dependencies) { - if (dep.type == ModPlatform::DependencyType::REQUIRED) { - auto* d = findByProjectID(dep.addonId, mod->metadata()->provider); - if (d) { - m_requires[id] << d; - m_requiredBy[d->mod_id()] << mod; - } - } - } - } - } - for (auto* mod : mods) { - auto id = mod->mod_id(); - if (mod->requiredByCount() != m_requiredBy[id].count() || mod->requiresCount() != m_requires[id].count()) { - mod->setRequiredByCount(m_requiredBy[id].count()); - mod->setRequiresCount(m_requires[id].count()); - int row = m_resourcesIndex[mod->internalId()]; - emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1)); - } - } -} - -namespace { - -QSet collectMods(const QSet& mods, QHash> relation, std::set& seen, bool shouldBeEnabled) -{ - QSet affectedList = {}; - QSet needToCheck = {}; - for (auto* mod : mods) { - auto id = mod->mod_id(); - if (!seen.contains(id)) { - seen.insert(id); - for (auto* affected : relation[id]) { - auto affectedId = affected->mod_id(); - - if (findById(mods, affectedId) == nullptr && !seen.contains(affectedId)) { - if (shouldBeEnabled != affected->enabled()) { - affectedList << affected; - } - needToCheck << affected; - } - } - } - } - // collect the affected mods until all of them are included in the list - if (!needToCheck.isEmpty()) { - affectedList += collectMods(needToCheck, relation, seen, shouldBeEnabled); - } - return affectedList; -} -} // namespace - -QModelIndexList ModFolderModel::getAffectedMods(const QModelIndexList& indexes, EnableAction action) -{ - if (indexes.isEmpty()) { - return {}; - } - - QModelIndexList affectedList = {}; - auto affectedModsList = selectedMods(indexes); - auto affectedMods = QSet(affectedModsList.begin(), affectedModsList.end()); - std::set seen; - - switch (action) { - case EnableAction::ENABLE: { - affectedMods = collectMods(affectedMods, m_requires, seen, true); - break; - } - case EnableAction::DISABLE: { - affectedMods = collectMods(affectedMods, m_requiredBy, seen, false); - break; - } - case EnableAction::TOGGLE: { - return {}; // this function should not be called with TOGGLE - } - } - for (auto* affected : affectedMods) { - auto affectedId = affected->mod_id(); - auto row = m_resourcesIndex[affected->internalId()]; - affectedList << index(row, 0); - } - return affectedList; -} - -bool ModFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAction action) -{ - if (indexes.isEmpty()) { - return {}; - } - - auto indexedModsList = selectedMods(indexes); - auto indexedMods = QSet(indexedModsList.begin(), indexedModsList.end()); - - QSet toEnable = {}; - QSet toDisable = {}; - std::set seen; - - switch (action) { - case EnableAction::ENABLE: { - toEnable = indexedMods; - break; - } - case EnableAction::DISABLE: { - toDisable = indexedMods; - break; - } - case EnableAction::TOGGLE: { - for (auto* mod : indexedMods) { - if (mod->enabled()) { - toDisable << mod; - } else { - toEnable << mod; - } - } - break; - } - } - - auto requiredToEnable = collectMods(toEnable, m_requires, seen, true); - auto requiredToDisable = collectMods(toDisable, m_requiredBy, seen, false); - - toDisable.removeIf([toEnable](Mod* m) { return toEnable.contains(m); }); - auto toList = [this](const QSet& mods) { - QModelIndexList list; - for (auto* mod : mods) { - auto row = m_resourcesIndex[mod->internalId()]; - list << index(row, 0); - } - return list; - }; - - if (requiredToEnable.size() > 0 || requiredToDisable.size() > 0) { - QString title; - QString message; - QString noButton; - QString yesButton; - if (requiredToEnable.size() > 0 && requiredToDisable.size() > 0) { - title = tr("Confirm toggle"); - message = tr("Toggling these mod(s) will cause changes to other mods.\n") + - tr("%n mod(s) will be enabled\n", "", requiredToEnable.size()) + - tr("%n mod(s) will be disabled\n", "", requiredToDisable.size()) + - tr("Do you want to automatically apply these related changes?\nIgnoring them may break the game."); - noButton = tr("Only Toggle Selected"); - yesButton = tr("Toggle Required Mods"); - } else if (requiredToEnable.size() > 0) { - title = tr("Confirm enable"); - message = tr("The enabled mod(s) require %n mod(s).\n", "", requiredToEnable.size()) + - tr("Would you like to enable them as well?\nIgnoring them may break the game."); - noButton = tr("Only Enable Selected"); - yesButton = tr("Enable Required"); - } else { - title = tr("Confirm disable"); - message = tr("The disabled mod(s) are required by %n mod(s).\n", "", requiredToDisable.size()) + - tr("Would you like to disable them as well?\nIgnoring them may break the game."); - noButton = tr("Only Disable Selected"); - yesButton = tr("Disable Required"); - } - - auto* box = CustomMessageBox::selectable(nullptr, title, message, QMessageBox::Warning, - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::No); - box->button(QMessageBox::No)->setText(noButton); - box->button(QMessageBox::Yes)->setText(yesButton); - auto response = box->exec(); - - if (response == QMessageBox::Yes) { - toEnable |= requiredToEnable; - toDisable |= requiredToDisable; - } else if (response == QMessageBox::Cancel) { - return false; - } - } - - auto disableStatus = ResourceFolderModel::setResourceEnabled(toList(toDisable), EnableAction::DISABLE); - auto enableStatus = ResourceFolderModel::setResourceEnabled(toList(toEnable), EnableAction::ENABLE); - return disableStatus && enableStatus; -} - -namespace { -QStringList reqToList(const QSet& l) -{ - QStringList req; - for (auto* m : l) { - req << m->name(); - } - return req; -} -} // namespace - -QStringList ModFolderModel::requiresList(const QString& id) -{ - return reqToList(m_requires[id]); -} - -QStringList ModFolderModel::requiredByList(const QString& id) -{ - return reqToList(m_requiredBy[id]); -} - -bool ModFolderModel::deleteResources(const QModelIndexList& indexes) -{ - auto deleteInvalid = [](QSet& mods) { - for (auto it = mods.begin(); it != mods.end();) { - auto* mod = *it; - // the QFileInfo::exists is used instead of mod->fileinfo().exists - // because the later somehow caches that the file exists - if (!mod || !QFileInfo::exists(mod->fileinfo().absoluteFilePath())) { - it = mods.erase(it); - } else { - ++it; - } - } - }; - auto rsp = ResourceFolderModel::deleteResources(indexes); - for (auto* mod : allMods()) { - auto id = mod->mod_id(); - deleteInvalid(m_requiredBy[id]); - deleteInvalid(m_requires[id]); - if (mod->requiredByCount() != m_requiredBy[id].count() || mod->requiresCount() != m_requires[id].count()) { - mod->setRequiredByCount(m_requiredBy[id].count()); - mod->setRequiresCount(m_requires[id].count()); - int row = m_resourcesIndex[mod->internalId()]; - emit dataChanged(index(row, RequiresColumn), index(row, RequiredByColumn)); - } - } - return rsp; + auto result = cast_task->result(); + if (result && resource) + static_cast(resource.get())->finishResolvingWithDetails(std::move(result->details)); + + emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1)); } diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index a5c6ba483..42868dc91 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -39,14 +39,13 @@ #include #include -#include +#include #include #include #include #include "Mod.h" #include "ResourceFolderModel.h" -#include "minecraft/mod/Resource.h" class BaseInstance; class QFileSystemWatcher; @@ -58,7 +57,7 @@ class QFileSystemWatcher; class ModFolderModel : public ResourceFolderModel { Q_OBJECT public: - enum Columns : std::uint8_t { + enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, @@ -70,14 +69,11 @@ class ModFolderModel : public ResourceFolderModel { LoadersColumn, McVersionsColumn, ReleaseTypeColumn, - RequiresColumn, - RequiredByColumn, - FileNameColumn, - NumColumns + NUM_COLUMNS }; - ModFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent = nullptr); + ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); - QString id() const override { return "mods"; } + virtual QString id() const override { return "mods"; } QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; @@ -85,26 +81,12 @@ class ModFolderModel : public ResourceFolderModel { int columnCount(const QModelIndex& parent) const override; [[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new Mod(file); } - [[nodiscard]] Task* createParseTask(Resource& /*unused*/) override; + [[nodiscard]] Task* createParseTask(Resource&) override; bool isValid(); - bool setResourceEnabled(const QModelIndexList& indexes, EnableAction action) override; - bool deleteResources(const QModelIndexList& indexes) override; - - QModelIndexList getAffectedMods(const QModelIndexList& indexes, EnableAction action); - RESOURCE_HELPERS(Mod) - public: - QStringList requiresList(const QString& id); - QStringList requiredByList(const QString& id); - private slots: - void onParseSucceeded(int ticket, const QString& resourceId) override; - void onParseFinished(); - - private: - QHash> m_requiredBy; - QHash> m_requires; + void onParseSucceeded(int ticket, QString resource_id) override; }; diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index 06d9a0af7..8f67d5d94 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -4,75 +4,69 @@ #include #include #include -#include #include "FileSystem.h" #include "StringUtils.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" -Resource::Resource(QObject* parent) : QObject(parent), m_size_info(0) {} +Resource::Resource(QObject* parent) : QObject(parent) {} -Resource::Resource(QFileInfo fileInfo) : m_size_info(0) +Resource::Resource(QFileInfo file_info) : QObject() { - setFile(fileInfo); + setFile(file_info); } -void Resource::setFile(QFileInfo fileInfo) +void Resource::setFile(QFileInfo file_info) { - m_file_info = std::move(fileInfo); + m_file_info = file_info; parseFile(); } -namespace { -std::tuple calculateFileSize(const QFileInfo& file) +static std::tuple calculateFileSize(const QFileInfo& file) { if (file.isDir()) { auto dir = QDir(file.absoluteFilePath()); dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot); auto count = dir.count(); auto str = QObject::tr("item"); - if (count != 1) { + if (count != 1) str = QObject::tr("items"); - } return { QString("%1 %2").arg(QString::number(count), str), count }; } return { StringUtils::humanReadableFileSize(file.size(), true), file.size() }; } -} // namespace void Resource::parseFile() { - QString fileName{ m_file_info.fileName() }; + QString file_name{ m_file_info.fileName() }; m_type = ResourceType::UNKNOWN; - m_internal_id = fileName; + m_internal_id = file_name; std::tie(m_size_str, m_size_info) = calculateFileSize(m_file_info); if (m_file_info.isDir()) { m_type = ResourceType::FOLDER; - m_name = fileName; + m_name = file_name; } else if (m_file_info.isFile()) { - if (fileName.endsWith(".disabled")) { - fileName.chop(9); + if (file_name.endsWith(".disabled")) { + file_name.chop(9); m_enabled = false; } - if (fileName.endsWith(".zip") || fileName.endsWith(".jar")) { + if (file_name.endsWith(".zip") || file_name.endsWith(".jar")) { m_type = ResourceType::ZIPFILE; - fileName.chop(4); - } else if (fileName.endsWith(".nilmod")) { + file_name.chop(4); + } else if (file_name.endsWith(".nilmod")) { m_type = ResourceType::ZIPFILE; - fileName.chop(7); - } else if (fileName.endsWith(".litemod")) { + file_name.chop(7); + } else if (file_name.endsWith(".litemod")) { m_type = ResourceType::LITEMOD; - fileName.chop(8); + file_name.chop(8); } else { m_type = ResourceType::SINGLEFILE; } - m_name = fileName; + m_name = file_name; } m_changed_date_time = m_file_info.lastModified(); @@ -80,140 +74,87 @@ void Resource::parseFile() auto Resource::name() const -> QString { - if (metadata()) { + if (metadata()) return metadata()->name; - } return m_name; } -namespace { -void removeThePrefix(QString& string) +static void removeThePrefix(QString& string) { static const QRegularExpression s_regex(QStringLiteral("^(?:the|teh) +"), QRegularExpression::CaseInsensitiveOption); string.remove(s_regex); string = string.trimmed(); } -} // namespace auto Resource::provider() const -> QString { - if (metadata()) { + if (metadata()) return ModPlatform::ProviderCapabilities::readableName(metadata()->provider); - } return tr("Unknown"); } auto Resource::homepage() const -> QString { - if (metadata()) { + if (metadata()) return ModPlatform::getMetaURL(metadata()->provider, metadata()->project_id); - } return {}; } void Resource::setMetadata(std::shared_ptr&& metadata) { - if (status() == ResourceStatus::NoMetadata) { - setStatus(ResourceStatus::Installed); - } + if (status() == ResourceStatus::NO_METADATA) + setStatus(ResourceStatus::INSTALLED); m_metadata = metadata; } -QStringList Resource::issues() const -{ - QStringList result; - result.reserve(m_issues.length()); - - for (const char* issue : m_issues) { - result.append(tr(issue)); - } - - return result; -} - -void Resource::updateIssues(const BaseInstance* inst) -{ - m_issues.clear(); - - if (m_metadata == nullptr) { - return; - } - - const auto* mcInst = dynamic_cast(inst); - if (mcInst == nullptr) { - return; - } - - auto* profile = mcInst->getPackProfile(); - QString mcVersion = profile->getComponentVersion("net.minecraft"); - - if (!m_metadata->mcVersions.empty() && !m_metadata->mcVersions.contains(mcVersion)) { - // delay translation until issues() is called - m_issues.append(QT_TR_NOOP("Not marked as compatible with the instance's game version.")); - } -} - int Resource::compare(const Resource& other, SortType type) const { switch (type) { default: - case SortType::Enabled: - if (enabled() && !other.enabled()) { + case SortType::ENABLED: + if (enabled() && !other.enabled()) return 1; - } - if (!enabled() && other.enabled()) { + if (!enabled() && other.enabled()) return -1; - } break; - case SortType::Name: { - QString thisName{ name() }; - QString otherName{ other.name() }; + case SortType::NAME: { + QString this_name{ name() }; + QString other_name{ other.name() }; // TODO do we need this? it could result in 0 being returned - removeThePrefix(thisName); - removeThePrefix(otherName); + removeThePrefix(this_name); + removeThePrefix(other_name); - return QString::compare(thisName, otherName, Qt::CaseInsensitive); + return QString::compare(this_name, other_name, Qt::CaseInsensitive); } - case SortType::Date: - if (dateTimeChanged() > other.dateTimeChanged()) { + case SortType::DATE: + if (dateTimeChanged() > other.dateTimeChanged()) return 1; - } - if (dateTimeChanged() < other.dateTimeChanged()) { + if (dateTimeChanged() < other.dateTimeChanged()) return -1; - } break; - case SortType::Filename: - return fileinfo().fileName().localeAwareCompare(other.fileinfo().fileName()); - - case SortType::Size: { + case SortType::SIZE: { if (this->type() != other.type()) { - if (this->type() == ResourceType::FOLDER) { + if (this->type() == ResourceType::FOLDER) return -1; - } - if (other.type() == ResourceType::FOLDER) { + if (other.type() == ResourceType::FOLDER) return 1; - } } - if (sizeInfo() > other.sizeInfo()) { + if (sizeInfo() > other.sizeInfo()) return 1; - } - if (sizeInfo() < other.sizeInfo()) { + if (sizeInfo() < other.sizeInfo()) return -1; - } break; } - - case SortType::Provider: { - auto compareResult = QString::compare(provider(), other.provider(), Qt::CaseInsensitive); - if (compareResult != 0) { - return compareResult; - } + case SortType::PROVIDER: { + auto compare_result = QString::compare(provider(), other.provider(), Qt::CaseInsensitive); + if (compare_result != 0) + return compare_result; break; } } @@ -223,20 +164,13 @@ int Resource::compare(const Resource& other, SortType type) const bool Resource::applyFilter(QRegularExpression filter) const { - if (filter.match(name()).hasMatch()) { - return true; - } - if (filter.match(fileinfo().fileName()).hasMatch()) { - return true; - } - return false; + return filter.match(name()).hasMatch(); } bool Resource::enable(EnableAction action) { - if (m_type == ResourceType::UNKNOWN || m_type == ResourceType::FOLDER) { + if (m_type == ResourceType::UNKNOWN || m_type == ResourceType::FOLDER) return false; - } QString path = m_file_info.absoluteFilePath(); QFile file(path); @@ -255,16 +189,14 @@ bool Resource::enable(EnableAction action) break; } - if (m_enabled == enable) { + if (m_enabled == enable) return false; - } if (enable) { // m_enabled is false, but there's no '.disabled' suffix. // TODO: Report error? - if (!path.endsWith(".disabled")) { + if (!path.endsWith(".disabled")) return false; - } path.chop(9); } else { path += ".disabled"; @@ -272,9 +204,8 @@ bool Resource::enable(EnableAction action) path = FS::getUniqueResourceName(path); } } - if (!file.rename(path)) { + if (!file.rename(path)) return false; - } setFile(QFileInfo(path)); @@ -282,34 +213,33 @@ bool Resource::enable(EnableAction action) return true; } -auto Resource::destroy(const QDir& indexDir, bool preserveMetadata, bool attemptTrash) -> bool +auto Resource::destroy(const QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool { m_type = ResourceType::UNKNOWN; - if (!preserveMetadata) { + if (!preserve_metadata) { qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name()); - destroyMetadata(indexDir); + destroyMetadata(index_dir); } - return (attemptTrash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath()); + return (attempt_trash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath()); } -auto Resource::destroyMetadata(const QDir& indexDir) -> void +auto Resource::destroyMetadata(const QDir& index_dir) -> void { if (metadata()) { - Metadata::remove(indexDir, metadata()->slug); + Metadata::remove(index_dir, metadata()->slug); } else { auto n = name(); - Metadata::remove(indexDir, n); + Metadata::remove(index_dir, n); } m_metadata = nullptr; } bool Resource::isSymLinkUnder(const QString& instPath) const { - if (isSymLink()) { + if (isSymLink()) return true; - } auto instDir = QDir(instPath); @@ -327,51 +257,7 @@ bool Resource::isMoreThanOneHardLink() const auto Resource::getOriginalFileName() const -> QString { auto fileName = m_file_info.fileName(); - if (!m_enabled) { + if (!m_enabled) fileName.chop(9); - } return fileName; } - -QDebug operator<<(QDebug debug, ResourceType type) -{ - switch (type) { - case ResourceType::ZIPFILE: - debug << "ZIPFILE"; - break; - case ResourceType::SINGLEFILE: - debug << "SINGLEFILE"; - break; - case ResourceType::FOLDER: - debug << "FOLDER"; - break; - case ResourceType::LITEMOD: - debug << "LITEMOD"; - break; - case ResourceType::UNKNOWN: - default: - debug << "UNKNOWN"; - break; - }; - return debug; -} - -QDebug operator<<(QDebug debug, ResourceStatus status) -{ - switch (status) { - case ResourceStatus::Installed: - debug << "INSTALLED"; - break; - case ResourceStatus::NotInstalled: - debug << "NOT_INSTALLED"; - break; - case ResourceStatus::NoMetadata: - debug << "NO_METADATA"; - break; - case ResourceStatus::Unknown: - default: - debug << "UNKNOWN"; - break; - }; - return debug; -} diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index 694d74ec0..87bfd4345 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -43,9 +43,7 @@ #include "MetadataHandler.h" #include "QObjectPtr.h" -class BaseInstance; - -enum class ResourceType : std::uint8_t { +enum class ResourceType { UNKNOWN, //!< Indicates an unspecified resource type. ZIPFILE, //!< The resource is a zip file containing the resource's class files. SINGLEFILE, //!< The resource is a single file (not a zip file). @@ -53,35 +51,16 @@ enum class ResourceType : std::uint8_t { LITEMOD, //!< The resource is a litemod }; -QDebug operator<<(QDebug debug, ResourceType type); - -enum class ResourceStatus : std::uint8_t { - Installed, // Both JAR and Metadata are present - NotInstalled, // Only the Metadata is present - NoMetadata, // Only the JAR is present - Unknown, // Default status +enum class ResourceStatus { + INSTALLED, // Both JAR and Metadata are present + NOT_INSTALLED, // Only the Metadata is present + NO_METADATA, // Only the JAR is present + UNKNOWN, // Default status }; -QDebug operator<<(QDebug debug, ResourceStatus status); +enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER, SIZE, SIDE, MC_VERSIONS, LOADERS, RELEASE_TYPE }; -enum class SortType : std::uint8_t { - Name, - Date, - Version, - Enabled, - PackFormat, - Provider, - Size, - Side, - McVersions, - Loaders, - ReleaseType, - Requires, - RequiredBy, - Filename, -}; - -enum class EnableAction : std::uint8_t { ENABLE, DISABLE, TOGGLE }; +enum class EnableAction { ENABLE, DISABLE, TOGGLE }; /** General class for managed resources. It mirrors a file in disk, with some more info * for display and house-keeping purposes. @@ -93,19 +72,20 @@ class Resource : public QObject { Q_DISABLE_COPY(Resource) public: using Ptr = shared_qobject_ptr; + using WeakPtr = QPointer; Resource(QObject* parent = nullptr); - Resource(QFileInfo fileInfo); - Resource(const QString& filePath) : Resource(QFileInfo(filePath)) {} + Resource(QFileInfo file_info); + Resource(QString file_path) : Resource(QFileInfo(file_path)) {} ~Resource() override = default; - void setFile(QFileInfo fileInfo); + void setFile(QFileInfo file_info); void parseFile(); auto fileinfo() const -> QFileInfo { return m_file_info; } auto dateTimeChanged() const -> QDateTime { return m_changed_date_time; } - auto internalId() const -> QString { return m_internal_id; } + 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; @@ -125,20 +105,12 @@ class Resource : public QObject { void setMetadata(std::shared_ptr&& metadata); void setMetadata(const Metadata::ModStruct& metadata) { setMetadata(std::make_shared(metadata)); } - /** - * Returns compatibility issues with the resource and the instance. - * This is initially empty, and may be updated when calling updateIssues. - */ - QStringList issues() const; - void updateIssues(const BaseInstance* inst); - bool hasIssues() const { return !m_issues.empty(); } - /** Compares two Resources, for sorting purposes, considering a ascending order, returning: * > 0: 'this' comes after 'other' * = 0: 'this' is equal to 'other' * < 0: 'this' comes before 'other' */ - virtual int compare(const Resource& 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). @@ -163,9 +135,9 @@ class Resource : public QObject { } // Delete all files of this resource. - auto destroy(const QDir& indexDir, bool preserveMetadata = false, bool attemptTrash = true) -> bool; + auto destroy(const QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool; // Delete the metadata only. - auto destroyMetadata(const QDir& indexDir) -> void; + auto destroyMetadata(const QDir& index_dir) -> void; auto isSymLink() const -> bool { return m_file_info.isSymLink(); } @@ -180,6 +152,9 @@ class Resource : public QObject { bool isMoreThanOneHardLink() const; + auto mod_id() const -> QString { return m_mod_id; } + void setModId(const QString& modId) { m_mod_id = modId; } + protected: /* The file corresponding to this resource. */ QFileInfo m_file_info; @@ -190,20 +165,19 @@ class Resource : public QObject { QString m_internal_id; /* Name as reported via the file name. In the absence of a better name, this is shown to the user. */ QString m_name; + QString m_mod_id; /* The type of file we're dealing with. */ ResourceType m_type = ResourceType::UNKNOWN; /* Installation status of the resource. */ - ResourceStatus m_status = ResourceStatus::Unknown; + ResourceStatus m_status = ResourceStatus::UNKNOWN; std::shared_ptr m_metadata = nullptr; /* Whether the resource is enabled (e.g. shows up in the game) or not. */ bool m_enabled = true; - QList m_issues; - /* Used to keep trach of pending / concluded actions on the resource. */ bool m_is_resolving = false; bool m_is_resolved = false; diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index ddc132089..1d5c7ca79 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include "Application.h" @@ -28,10 +27,10 @@ #include "tasks/Task.h" #include "ui/dialogs/CustomMessageBox.h" -ResourceFolderModel::ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent) - : QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this), m_isIndexed(isIndexed) +ResourceFolderModel::ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) + : QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this), m_is_indexed(is_indexed) { - if (createDir) { + if (create_dir) { FS::ensureFolderPathExists(m_dir.absolutePath()); } @@ -50,75 +49,70 @@ ResourceFolderModel::ResourceFolderModel(const QDir& dir, BaseInstance* instance ResourceFolderModel::~ResourceFolderModel() { - while (!QThreadPool::globalInstance()->waitForDone(100)) { + while (!QThreadPool::globalInstance()->waitForDone(100)) QCoreApplication::processEvents(); - } } bool ResourceFolderModel::startWatching(const QStringList& paths) { // Remove orphaned metadata next time - m_firstFolderLoad = true; + m_first_folder_load = true; - if (m_isWatching) { + if (m_is_watching) return false; - } - auto couldntBeWatched = m_watcher.addPaths(paths); - for (const auto& path : paths) { - if (couldntBeWatched.contains(path)) { + auto couldnt_be_watched = m_watcher.addPaths(paths); + for (auto path : paths) { + if (couldnt_be_watched.contains(path)) qDebug() << "Failed to start watching" << path; - } else { + else qDebug() << "Started watching" << path; - } } update(); - m_isWatching = !m_isWatching; - return m_isWatching; + m_is_watching = !m_is_watching; + return m_is_watching; } bool ResourceFolderModel::stopWatching(const QStringList& paths) { - if (!m_isWatching) { + if (!m_is_watching) return false; - } - auto couldntBeStopped = m_watcher.removePaths(paths); - for (const auto& path : paths) { - if (couldntBeStopped.contains(path)) { + auto couldnt_be_stopped = m_watcher.removePaths(paths); + for (auto path : paths) { + if (couldnt_be_stopped.contains(path)) qDebug() << "Failed to stop watching" << path; - } else { + else qDebug() << "Stopped watching" << path; - } } - m_isWatching = !m_isWatching; - return !m_isWatching; + m_is_watching = !m_is_watching; + return !m_is_watching; } -bool ResourceFolderModel::installResource(QString originalPath) +bool ResourceFolderModel::installResource(QString original_path) { // NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName - originalPath = FS::NormalizePath(originalPath); - QFileInfo fileInfo(originalPath); + original_path = FS::NormalizePath(original_path); + QFileInfo file_info(original_path); - if (!fileInfo.exists() || !fileInfo.isReadable()) { - qWarning() << "Caught attempt to install non-existing file or file-like object:" << originalPath; + if (!file_info.exists() || !file_info.isReadable()) { + qWarning() << "Caught attempt to install non-existing file or file-like object:" << original_path; return false; } - qDebug() << "Installing:" << fileInfo.absoluteFilePath(); + qDebug() << "Installing:" << file_info.absoluteFilePath(); - Resource resource(fileInfo); + Resource resource(file_info); if (!resource.valid()) { - qWarning() << originalPath << "is not a valid resource. Ignoring it."; + qWarning() << original_path << "is not a valid resource. Ignoring it."; return false; } - auto newPath = FS::NormalizePath(m_dir.filePath(fileInfo.fileName())); - if (originalPath == newPath) { - qWarning() << "Overwriting the mod (" << originalPath << ") with itself makes no sense..."; + auto new_path = FS::NormalizePath(m_dir.filePath(file_info.fileName())); + if (original_path == new_path) { + qWarning() << "Overwriting the mod (" << original_path << ") with itself makes no sense..."; return false; } @@ -126,47 +120,45 @@ bool ResourceFolderModel::installResource(QString originalPath) case ResourceType::SINGLEFILE: case ResourceType::ZIPFILE: case ResourceType::LITEMOD: { - if (QFile::exists(newPath) || QFile::exists(newPath + QString(".disabled"))) { - if (!FS::deletePath(newPath)) { - qCritical() << "Cleaning up new location (" << newPath << ") was unsuccessful!"; + if (QFile::exists(new_path) || QFile::exists(new_path + QString(".disabled"))) { + if (!FS::deletePath(new_path)) { + qCritical() << "Cleaning up new location (" << new_path << ") was unsuccessful!"; return false; } - qDebug() << newPath << "has been deleted."; + qDebug() << new_path << "has been deleted."; } - if (!QFile::copy(originalPath, newPath)) { - qCritical() << "Copy from" << originalPath << "to" << newPath << "has failed."; + if (!QFile::copy(original_path, new_path)) { + qCritical() << "Copy from" << original_path << "to" << new_path << "has failed."; return false; } - FS::updateTimestamp(newPath); + FS::updateTimestamp(new_path); - QFileInfo newPathFileInfo(newPath); - resource.setFile(newPathFileInfo); + QFileInfo new_path_file_info(new_path); + resource.setFile(new_path_file_info); - if (!m_isWatching) { + if (!m_is_watching) return update(); - } return true; } case ResourceType::FOLDER: { - if (QFile::exists(newPath)) { - qDebug() << "Ignoring folder '" << originalPath << "', it would merge with" << newPath; + if (QFile::exists(new_path)) { + qDebug() << "Ignoring folder '" << original_path << "', it would merge with" << new_path; return false; } - if (!FS::copy(originalPath, newPath)()) { - qWarning() << "Copy of folder from" << originalPath << "to" << newPath << "has (potentially partially) failed."; + if (!FS::copy(original_path, new_path)()) { + qWarning() << "Copy of folder from" << original_path << "to" << new_path << "has (potentially partially) failed."; return false; } - QFileInfo newpathInfo(newPath); + QFileInfo newpathInfo(new_path); resource.setFile(newpathInfo); - if (!m_isWatching) { + if (!m_is_watching) return update(); - } return true; } @@ -176,24 +168,25 @@ bool ResourceFolderModel::installResource(QString originalPath) return false; } -void ResourceFolderModel::installResourceWithFlameMetadata(const QString& path, ModPlatform::IndexedVersion& vers) +void ResourceFolderModel::installResourceWithFlameMetadata(QString path, ModPlatform::IndexedVersion& vers) { - auto install = [this, path] { installResource(path); }; + auto install = [this, path] { installResource(std::move(path)); }; if (vers.addonId.isValid()) { ModPlatform::IndexedPack pack{ - .addonId = vers.addonId, - .provider = ModPlatform::ResourceProvider::FLAME, + vers.addonId, + ModPlatform::ResourceProvider::FLAME, }; - auto [job, response] = FlameAPI().getProject(vers.addonId.toString()); + auto response = std::make_shared(); + auto job = FlameAPI().getProject(vers.addonId.toString(), response); connect(job.get(), &Task::failed, this, install); connect(job.get(), &Task::aborted, this, install); connect(job.get(), &Task::succeeded, [response, this, &vers, install, &pack] { - QJsonParseError parseError{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parseError); - if (parseError.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response for mod info at" << parseError.offset - << "reason:" << parseError.errorString(); + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response for mod info at" << parse_error.offset + << "reason:" << parse_error.errorString(); qDebug() << *response; return; } @@ -204,9 +197,9 @@ void ResourceFolderModel::installResourceWithFlameMetadata(const QString& path, qDebug() << doc; qWarning() << "Error while reading mod info:" << e.cause(); } - LocalResourceUpdateTask updateMetadata(indexDir(), pack, vers); - connect(&updateMetadata, &Task::finished, this, install); - updateMetadata.start(); + LocalResourceUpdateTask update_metadata(indexDir(), pack, vers); + connect(&update_metadata, &Task::finished, this, install); + update_metadata.start(); }); job->start(); @@ -215,7 +208,7 @@ void ResourceFolderModel::installResourceWithFlameMetadata(const QString& path, } } -bool ResourceFolderModel::uninstallResource(const QString& fileName, bool preserveMetadata) +bool ResourceFolderModel::uninstallResource(const QString& file_name, bool preserve_metadata) { for (auto& resource : m_resources) { auto resourceFileInfo = resource->fileinfo(); @@ -224,8 +217,8 @@ bool ResourceFolderModel::uninstallResource(const QString& fileName, bool preser resourceFileName.chop(9); } - if (resourceFileName == fileName) { - auto res = resource->destroy(indexDir(), preserveMetadata, false); + if (resourceFileName == file_name) { + auto res = resource->destroy(indexDir(), preserve_metadata, false); update(); @@ -237,16 +230,14 @@ bool ResourceFolderModel::uninstallResource(const QString& fileName, bool preser bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes) { - if (indexes.isEmpty()) { + if (indexes.isEmpty()) return true; - } for (auto i : indexes) { - if (i.column() != 0) { + if (i.column() != 0) continue; - } - const auto& resource = m_resources.at(i.row()); + auto& resource = m_resources.at(i.row()); resource->destroy(indexDir()); } @@ -257,16 +248,14 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes) void ResourceFolderModel::deleteMetadata(const QModelIndexList& indexes) { - if (indexes.isEmpty()) { + if (indexes.isEmpty()) return; - } for (auto i : indexes) { - if (i.column() != 0) { + if (i.column() != 0) continue; - } - const auto& resource = m_resources.at(i.row()); + auto& resource = m_resources.at(i.row()); resource->destroyMetadata(indexDir()); } @@ -283,36 +272,33 @@ bool ResourceFolderModel::setResourceEnabled(const QModelIndexList& indexes, Ena QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) { + if (response != QMessageBox::Yes) return false; - } } - if (indexes.isEmpty()) { + if (indexes.isEmpty()) return true; - } bool succeeded = true; - for (const auto& idx : indexes) { - if (!validateIndex(idx) || idx.column() != 0) { + for (auto const& idx : indexes) { + if (!validateIndex(idx) || idx.column() != 0) continue; - } int row = idx.row(); auto& resource = m_resources[row]; // Preserve the row, but change its ID - auto oldId = resource->internalId(); + auto old_id = resource->internal_id(); if (!resource->enable(action)) { succeeded = false; continue; } - auto newId = resource->internalId(); + auto new_id = resource->internal_id(); - m_resourcesIndex.remove(oldId); - m_resourcesIndex[newId] = row; + m_resources_index.remove(old_id); + m_resources_index[new_id] = row; emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); } @@ -327,25 +313,24 @@ bool ResourceFolderModel::update() QMutexLocker lock(&s_update_task_mutex); // Already updating, so we schedule a future update and return. - if (m_currentUpdateTask) { - m_scheduledUpdate = true; + if (m_current_update_task) { + m_scheduled_update = true; return false; } - m_currentUpdateTask.reset(createUpdateTask()); - if (!m_currentUpdateTask) { + m_current_update_task.reset(createUpdateTask()); + if (!m_current_update_task) return false; - } - connect(m_currentUpdateTask.get(), &Task::succeeded, this, &ResourceFolderModel::onUpdateSucceeded, + connect(m_current_update_task.get(), &Task::succeeded, this, &ResourceFolderModel::onUpdateSucceeded, Qt::ConnectionType::QueuedConnection); - connect(m_currentUpdateTask.get(), &Task::failed, this, &ResourceFolderModel::onUpdateFailed, Qt::ConnectionType::QueuedConnection); + connect(m_current_update_task.get(), &Task::failed, this, &ResourceFolderModel::onUpdateFailed, Qt::ConnectionType::QueuedConnection); connect( - m_currentUpdateTask.get(), &Task::finished, this, + m_current_update_task.get(), &Task::finished, this, [this] { - m_currentUpdateTask.reset(); - if (m_scheduledUpdate) { - m_scheduledUpdate = false; + m_current_update_task.reset(); + if (m_scheduled_update) { + m_scheduled_update = false; update(); } else { emit updateFinished(); @@ -356,16 +341,16 @@ bool ResourceFolderModel::update() Task::Ptr preUpdate{ createPreUpdateTask() }; if (preUpdate != nullptr) { - auto* task = new SequentialTask("ResourceFolderModel::update"); + auto task = new SequentialTask("ResourceFolderModel::update"); task->addTask(preUpdate); - task->addTask(m_currentUpdateTask); + task->addTask(m_current_update_task); connect(task, &Task::finished, [task] { task->deleteLater(); }); QThreadPool::globalInstance()->start(task); } else { - QThreadPool::globalInstance()->start(m_currentUpdateTask.get()); + QThreadPool::globalInstance()->start(m_current_update_task.get()); } return true; @@ -378,25 +363,24 @@ void ResourceFolderModel::resolveResource(Resource::Ptr res) } Task::Ptr task{ createParseTask(*res) }; - if (!task) { + if (!task) return; - } - int ticket = m_nextResolutionTicket.fetch_add(1); + int ticket = m_next_resolution_ticket.fetch_add(1); res->setResolving(true, ticket); - m_activeParseTasks.insert(ticket, task); + m_active_parse_tasks.insert(ticket, task); connect( - task.get(), &Task::succeeded, this, [this, ticket, res] { onParseSucceeded(ticket, res->internalId()); }, + task.get(), &Task::succeeded, this, [this, ticket, res] { onParseSucceeded(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection); connect( - task.get(), &Task::failed, this, [this, ticket, res] { onParseFailed(ticket, res->internalId()); }, + task.get(), &Task::failed, this, [this, ticket, res] { onParseFailed(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection); connect( task.get(), &Task::finished, this, [this, ticket] { - m_activeParseTasks.remove(ticket); + m_active_parse_tasks.remove(ticket); emit parseFinished(); }, Qt::ConnectionType::QueuedConnection); @@ -411,45 +395,44 @@ void ResourceFolderModel::resolveResource(Resource::Ptr res) void ResourceFolderModel::onUpdateSucceeded() { - auto updateResults = static_cast(m_currentUpdateTask.get())->result(); + auto update_results = static_cast(m_current_update_task.get())->result(); - auto& newResources = updateResults->resources; + auto& new_resources = update_results->resources; - auto currentList = m_resourcesIndex.keys(); - QSet currentSet(currentList.begin(), currentList.end()); + auto current_list = m_resources_index.keys(); + QSet current_set(current_list.begin(), current_list.end()); - auto newList = newResources.keys(); - QSet newSet(newList.begin(), newList.end()); + auto new_list = new_resources.keys(); + QSet new_set(new_list.begin(), new_list.end()); - applyUpdates(currentSet, newSet, newResources); + applyUpdates(current_set, new_set, new_resources); } -void ResourceFolderModel::onParseSucceeded(int ticket, const QString& resourceId) +void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id) { - auto iter = m_activeParseTasks.constFind(ticket); - if (iter == m_activeParseTasks.constEnd() || !m_resourcesIndex.contains(resourceId)) { + auto iter = m_active_parse_tasks.constFind(ticket); + if (iter == m_active_parse_tasks.constEnd() || !m_resources_index.contains(resource_id)) return; - } - int row = m_resourcesIndex[resourceId]; + int row = m_resources_index[resource_id]; emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1)); } Task* ResourceFolderModel::createUpdateTask() { - auto indexDir2 = indexDir(); - auto* task = new ResourceFolderLoadTask(dir(), indexDir2, m_isIndexed, m_firstFolderLoad, - [this](const QFileInfo& file) { return createResource(file); }); - m_firstFolderLoad = false; + auto index_dir = indexDir(); + auto task = new ResourceFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load, + [this](const QFileInfo& file) { return createResource(file); }); + m_first_folder_load = false; return task; } bool ResourceFolderModel::hasPendingParseTasks() const { - return !m_activeParseTasks.isEmpty(); + return !m_active_parse_tasks.isEmpty(); } -void ResourceFolderModel::directoryChanged(const QString& /*path*/) +void ResourceFolderModel::directoryChanged(QString path) { update(); } @@ -464,9 +447,8 @@ Qt::ItemFlags ResourceFolderModel::flags(const QModelIndex& index) const { Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); auto flags = defaultFlags | Qt::ItemIsDropEnabled; - if (index.isValid()) { + if (index.isValid()) flags |= Qt::ItemIsUserCheckable; - } return flags; } @@ -477,25 +459,21 @@ QStringList ResourceFolderModel::mimeTypes() const return types; } -bool ResourceFolderModel::dropMimeData(const QMimeData* data, - Qt::DropAction action, - int /*row*/, - int /*column*/, - const QModelIndex& /*parent*/) +bool ResourceFolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&) { if (action == Qt::IgnoreAction) { return true; } // check if the action is supported - if ((data == nullptr) || !(action & supportedDropActions())) { + if (!data || !(action & supportedDropActions())) { return false; } // files dropped from outside? if (data->hasUrls()) { auto urls = data->urls(); - for (const auto& url : urls) { + for (auto url : urls) { // only local files may be dropped... if (!url.isLocalFile()) { continue; @@ -511,36 +489,25 @@ bool ResourceFolderModel::dropMimeData(const QMimeData* data, bool ResourceFolderModel::validateIndex(const QModelIndex& index) const { - if (!index.isValid()) { + if (!index.isValid()) return false; - } int row = index.row(); - return row >= 0 && row < m_resources.size(); -} + if (row < 0 || row >= m_resources.size()) + return false; -// HACK: all subclasses need to call this to have the whole row painted -// and they only delegate to the superclass for compatible columns -QBrush ResourceFolderModel::rowBackground(int row) const -{ - if (APPLICATION->settings()->get("ShowModIncompat").toBool() && m_resources[row]->hasIssues()) { - return { QColor(255, 0, 0, 40) }; - } - return {}; + return true; } QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const { - if (!validateIndex(index)) { + if (!validateIndex(index)) return {}; - } int row = index.row(); int column = index.column(); switch (role) { - case Qt::BackgroundRole: - return rowBackground(row); case Qt::DisplayRole: switch (column) { case NameColumn: @@ -551,52 +518,34 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const return m_resources[row]->provider(); case SizeColumn: return m_resources[row]->sizeStr(); - case FileNameColumn: - return m_resources[row]->fileinfo().fileName(); default: return {}; } - case Qt::ToolTipRole: { - QString tooltip = m_resources[row]->internalId(); - + case Qt::ToolTipRole: if (column == NameColumn) { - if (APPLICATION->settings()->get("ShowModIncompat").toBool()) { - for (const QString& issue : at(row).issues()) { - tooltip += "\n" + issue; - } - } - if (at(row).isSymLinkUnder(instDirPath())) { - tooltip += - m_resources[row]->internalId() + - tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." - "\nCanonical Path: %1") - .arg(at(row).fileinfo().canonicalFilePath()); + return m_resources[row]->internal_id() + + tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." + "\nCanonical Path: %1") + .arg(at(row).fileinfo().canonicalFilePath()); + ; } - if (at(row).isMoreThanOneHardLink()) { - tooltip += tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original."); + return m_resources[row]->internal_id() + + tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original."); } } - return tooltip; - } + return m_resources[row]->internal_id(); case Qt::DecorationRole: { - if (column == NameColumn) { - if (APPLICATION->settings()->get("ShowModIncompat").toBool() && at(row).hasIssues()) { - return QIcon::fromTheme("status-bad"); - } - if (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()) { - return QIcon::fromTheme("status-yellow"); - } - } + if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink())) + return QIcon::fromTheme("status-yellow"); return {}; } case Qt::CheckStateRole: - if (column == ActiveColumn) { + if (column == ActiveColumn) return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked; - } return {}; default: return {}; @@ -606,9 +555,8 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const bool ResourceFolderModel::setData(const QModelIndex& index, [[maybe_unused]] const QVariant& value, int role) { int row = index.row(); - if (row < 0 || row >= rowCount(index.parent()) || !index.isValid()) { + if (row < 0 || row >= rowCount(index.parent()) || !index.isValid()) return false; - } if (role == Qt::CheckStateRole) { return setResourceEnabled({ index }, EnableAction::TOGGLE); @@ -627,7 +575,6 @@ QVariant ResourceFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien case DateColumn: case ProviderColumn: case SizeColumn: - case FileNameColumn: return columnNames().at(section); default: return {}; @@ -645,8 +592,6 @@ QVariant ResourceFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien return tr("The source provider of the resource."); case SizeColumn: return tr("The size of the resource."); - case FileNameColumn: - return tr("The file name of the resource."); default: return {}; } @@ -667,22 +612,22 @@ void ResourceFolderModel::setupHeaderAction(QAction* act, int column) void ResourceFolderModel::saveColumns(QTreeView* tree) { - const auto stateSettingName = QString("UI/%1_Page/Columns").arg(id()); - const auto overrideSettingName = QString("UI/%1_Page/ColumnsOverride").arg(id()); - const auto visibilitySettingName = QString("UI/%1_Page/ColumnsVisibility").arg(id()); + auto const stateSettingName = QString("UI/%1_Page/Columns").arg(id()); + auto const overrideSettingName = QString("UI/%1_Page/ColumnsOverride").arg(id()); + auto const visibilitySettingName = QString("UI/%1_Page/ColumnsVisibility").arg(id()); auto stateSetting = m_instance->settings()->getSetting(stateSettingName); stateSetting->set(QString::fromUtf8(tree->header()->saveState().toBase64())); // neither passthrough nor override settings works for this usecase as I need to only set the global when the gate is false - auto* settings = m_instance->settings(); + auto settings = m_instance->settings(); if (!settings->get(overrideSettingName).toBool()) { settings = APPLICATION->settings(); } auto visibility = Json::toMap(settings->get(visibilitySettingName).toString()); - for (auto i = 0; i < m_columnNames.size(); ++i) { + for (auto i = 0; i < m_column_names.size(); ++i) { if (m_columnsHideable[i]) { - auto name = m_columnNames[i]; + auto name = m_column_names[i]; visibility[name] = !tree->isColumnHidden(i); } } @@ -691,24 +636,24 @@ void ResourceFolderModel::saveColumns(QTreeView* tree) void ResourceFolderModel::loadColumns(QTreeView* tree) { - const auto stateSettingName = QString("UI/%1_Page/Columns").arg(id()); - const auto overrideSettingName = QString("UI/%1_Page/ColumnsOverride").arg(id()); - const auto visibilitySettingName = QString("UI/%1_Page/ColumnsVisibility").arg(id()); + auto const stateSettingName = QString("UI/%1_Page/Columns").arg(id()); + auto const overrideSettingName = QString("UI/%1_Page/ColumnsOverride").arg(id()); + auto const visibilitySettingName = QString("UI/%1_Page/ColumnsVisibility").arg(id()); auto stateSetting = m_instance->settings()->getOrRegisterSetting(stateSettingName, ""); tree->header()->restoreState(QByteArray::fromBase64(stateSetting->get().toString().toUtf8())); - auto setVisible = [this, tree](const QVariant& value) { + auto setVisible = [this, tree](QVariant value) { auto visibility = Json::toMap(value.toString()); - for (auto i = 0; i < m_columnNames.size(); ++i) { + for (auto i = 0; i < m_column_names.size(); ++i) { if (m_columnsHideable[i]) { - auto name = m_columnNames[i]; + auto name = m_column_names[i]; tree->setColumnHidden(i, !visibility.value(name, false).toBool()); } } }; - const auto defaultValue = Json::fromMap({ + auto const defaultValue = Json::fromMap({ { "Image", true }, { "Version", true }, { "Last Modified", true }, @@ -716,7 +661,7 @@ void ResourceFolderModel::loadColumns(QTreeView* tree) { "Pack Format", true }, }); // neither passthrough nor override settings works for this usecase as I need to only set the global when the gate is false - auto* settings = m_instance->settings(); + auto settings = m_instance->settings(); if (!settings->getOrRegisterSetting(overrideSettingName, false)->get().toBool()) { settings = APPLICATION->settings(); } @@ -725,7 +670,7 @@ void ResourceFolderModel::loadColumns(QTreeView* tree) // allways connect the signal in case the setting is toggled on and off auto gSetting = APPLICATION->settings()->getOrRegisterSetting(visibilitySettingName, defaultValue); - connect(gSetting.get(), &Setting::SettingChanged, tree, [this, setVisible, overrideSettingName](const Setting&, const QVariant& value) { + connect(gSetting.get(), &Setting::SettingChanged, tree, [this, setVisible, overrideSettingName](const Setting&, QVariant value) { if (!m_instance->settings()->get(overrideSettingName).toBool()) { setVisible(value); } @@ -734,11 +679,11 @@ void ResourceFolderModel::loadColumns(QTreeView* tree) QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree) { - auto* menu = new QMenu(tree); + auto menu = new QMenu(tree); { // action to decide if the visibility is per instance or not - auto* act = new QAction(tr("Override Columns Visibility"), menu); - const auto overrideSettingName = QString("UI/%1_Page/ColumnsOverride").arg(id()); + auto act = new QAction(tr("Override Columns Visibility"), menu); + auto const overrideSettingName = QString("UI/%1_Page/ColumnsOverride").arg(id()); act->setCheckable(true); act->setChecked(m_instance->settings()->getOrRegisterSetting(overrideSettingName, false)->get().toBool()); @@ -754,10 +699,9 @@ QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree) for (int col = 0; col < columnCount(); ++col) { // Skip creating actions for columns that should not be hidden - if (!m_columnsHideable.at(col)) { + if (!m_columnsHideable.at(col)) continue; - } - auto* act = new QAction(menu); + auto act = new QAction(menu); setupHeaderAction(act, col); act->setCheckable(true); @@ -766,9 +710,8 @@ QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree) connect(act, &QAction::toggled, tree, [this, col, tree](bool toggled) { tree->setColumnHidden(col, !toggled); for (int c = 0; c < columnCount(); ++c) { - if (m_columnResizeModes.at(c) == QHeaderView::ResizeToContents) { + if (m_column_resize_modes.at(c) == QHeaderView::ResizeToContents) tree->resizeColumnToContents(c); - } } saveColumns(tree); }); @@ -786,43 +729,41 @@ QSortFilterProxyModel* ResourceFolderModel::createFilterProxyModel(QObject* pare SortType ResourceFolderModel::columnToSortKey(size_t column) const { - Q_ASSERT(m_columnSortKeys.size() == columnCount()); - return m_columnSortKeys.at(column); + Q_ASSERT(m_column_sort_keys.size() == columnCount()); + return m_column_sort_keys.at(column); } /* Standard Proxy Model for createFilterProxyModel */ -bool ResourceFolderModel::ProxyModel::filterAcceptsRow(int sourceRow, [[maybe_unused]] const QModelIndex& sourceParent) const +bool ResourceFolderModel::ProxyModel::filterAcceptsRow(int source_row, [[maybe_unused]] const QModelIndex& source_parent) const { auto* model = qobject_cast(sourceModel()); - if (!model) { + if (!model) return true; - } - const auto& resource = model->at(sourceRow); + const auto& resource = model->at(source_row); return resource.applyFilter(filterRegularExpression()); } -bool ResourceFolderModel::ProxyModel::lessThan(const QModelIndex& sourceLeft, const QModelIndex& sourceRight) const +bool ResourceFolderModel::ProxyModel::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const { auto* model = qobject_cast(sourceModel()); - if (!model || !sourceLeft.isValid() || !sourceRight.isValid() || sourceLeft.column() != sourceRight.column()) { - return QSortFilterProxyModel::lessThan(sourceLeft, sourceRight); + if (!model || !source_left.isValid() || !source_right.isValid() || source_left.column() != source_right.column()) { + return QSortFilterProxyModel::lessThan(source_left, source_right); } // we are now guaranteed to have two valid indexes in the same column... we love the provided invariants unconditionally and // proceed. - auto columnSortKey = model->columnToSortKey(sourceLeft.column()); - const auto& resourceLeft = model->at(sourceLeft.row()); - const auto& resourceRight = model->at(sourceRight.row()); + auto column_sort_key = model->columnToSortKey(source_left.column()); + auto const& resource_left = model->at(source_left.row()); + auto const& resource_right = model->at(source_right.row()); - auto compareResult = resourceLeft.compare(resourceRight, columnSortKey); - if (compareResult == 0) { - return QSortFilterProxyModel::lessThan(sourceLeft, sourceRight); - } + auto compare_result = resource_left.compare(resource_right, column_sort_key); + if (compare_result == 0) + return QSortFilterProxyModel::lessThan(source_left, source_right); - return compareResult < 0; + return compare_result < 0; } QString ResourceFolderModel::instDirPath() const @@ -830,69 +771,60 @@ QString ResourceFolderModel::instDirPath() const return QFileInfo(m_instance->instanceRoot()).absoluteFilePath(); } -void ResourceFolderModel::onParseFailed(int ticket, const QString& resourceId) +void ResourceFolderModel::onParseFailed(int ticket, QString resource_id) { - auto iter = m_activeParseTasks.constFind(ticket); - if (iter == m_activeParseTasks.constEnd() || !m_resourcesIndex.contains(resourceId)) { + auto iter = m_active_parse_tasks.constFind(ticket); + if (iter == m_active_parse_tasks.constEnd() || !m_resources_index.contains(resource_id)) return; - } - auto removedIndex = m_resourcesIndex[resourceId]; - auto removedIt = m_resources.begin() + removedIndex; - Q_ASSERT(removedIt != m_resources.end()); + auto removed_index = m_resources_index[resource_id]; + auto removed_it = m_resources.begin() + removed_index; + Q_ASSERT(removed_it != m_resources.end()); - beginRemoveRows(QModelIndex(), removedIndex, removedIndex); - m_resources.erase(removedIt); + beginRemoveRows(QModelIndex(), removed_index, removed_index); + m_resources.erase(removed_it); // update index - m_resourcesIndex.clear(); + m_resources_index.clear(); int idx = 0; - for (const auto& mod : qAsConst(m_resources)) { - m_resourcesIndex[mod->internalId()] = idx; + for (auto const& mod : qAsConst(m_resources)) { + m_resources_index[mod->internal_id()] = idx; idx++; } endRemoveRows(); } -void ResourceFolderModel::applyUpdates(QSet& currentSet, QSet& newSet, QMap& newResources) +void ResourceFolderModel::applyUpdates(QSet& current_set, QSet& new_set, QMap& new_resources) { // see if the kept resources changed in some way { - QSet keptSet = currentSet; - keptSet.intersect(newSet); + QSet kept_set = current_set; + kept_set.intersect(new_set); - for (const auto& kept : keptSet) { - auto rowIt = m_resourcesIndex.constFind(kept); - Q_ASSERT(rowIt != m_resourcesIndex.constEnd()); - auto row = rowIt.value(); + for (auto const& kept : kept_set) { + auto row_it = m_resources_index.constFind(kept); + Q_ASSERT(row_it != m_resources_index.constEnd()); + auto row = row_it.value(); - auto& newResource = newResources[kept]; - const auto& currentResource = m_resources.at(row); + auto& new_resource = new_resources[kept]; + auto const& current_resource = m_resources.at(row); - if (newResource->dateTimeChanged() == currentResource->dateTimeChanged()) { - // no significant change - bool hadIssues = !currentResource->hasIssues(); - currentResource->updateIssues(m_instance); - - if (hadIssues != currentResource->hasIssues()) { - emit dataChanged(index(row, 0), index(row, columnCount({}) - 1)); - } + if (new_resource->dateTimeChanged() == current_resource->dateTimeChanged()) { + // no significant change, ignore... continue; } // If the resource is resolving, but something about it changed, we don't want to // continue the resolving. - if (currentResource->isResolving()) { - auto ticket = currentResource->resolutionTicket(); - if (m_activeParseTasks.contains(ticket)) { - auto* task = (*m_activeParseTasks.find(ticket)).get(); + if (current_resource->isResolving()) { + auto ticket = current_resource->resolutionTicket(); + if (m_active_parse_tasks.contains(ticket)) { + auto task = (*m_active_parse_tasks.find(ticket)).get(); task->abort(); } } - m_resources[row].reset(newResource); - newResource->updateIssues(m_instance); - + m_resources[row].reset(new_resource); resolveResource(m_resources.at(row)); emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); } @@ -900,48 +832,46 @@ void ResourceFolderModel::applyUpdates(QSet& currentSet, QSet& // remove resources no longer present { - QSet removedSet = currentSet; - removedSet.subtract(newSet); + QSet removed_set = current_set; + removed_set.subtract(new_set); - QList removedRows; - for (const auto& removed : removedSet) { - removedRows.append(m_resourcesIndex[removed]); - } + QList removed_rows; + for (auto& removed : removed_set) + removed_rows.append(m_resources_index[removed]); - std::ranges::sort(removedRows, std::greater()); + std::sort(removed_rows.begin(), removed_rows.end(), std::greater()); - for (auto& removedIndex : removedRows) { - auto removedIt = m_resources.begin() + removedIndex; + for (auto& removed_index : removed_rows) { + auto removed_it = m_resources.begin() + removed_index; - Q_ASSERT(removedIt != m_resources.end()); + Q_ASSERT(removed_it != m_resources.end()); - if ((*removedIt)->isResolving()) { - auto ticket = (*removedIt)->resolutionTicket(); - if (m_activeParseTasks.contains(ticket)) { - auto* task = (*m_activeParseTasks.find(ticket)).get(); + if ((*removed_it)->isResolving()) { + auto ticket = (*removed_it)->resolutionTicket(); + if (m_active_parse_tasks.contains(ticket)) { + auto task = (*m_active_parse_tasks.find(ticket)).get(); task->abort(); } } - beginRemoveRows(QModelIndex(), removedIndex, removedIndex); - m_resources.erase(removedIt); + beginRemoveRows(QModelIndex(), removed_index, removed_index); + m_resources.erase(removed_it); endRemoveRows(); } } // add new resources to the end { - QSet addedSet = newSet; - addedSet.subtract(currentSet); + QSet added_set = new_set; + added_set.subtract(current_set); // When you have a Qt build with assertions turned on, proceeding here will abort the application - if (addedSet.size() > 0) { + if (added_set.size() > 0) { beginInsertRows(QModelIndex(), static_cast(m_resources.size()), - static_cast(m_resources.size() + addedSet.size() - 1)); + static_cast(m_resources.size() + added_set.size() - 1)); - for (const auto& added : addedSet) { - auto res = newResources[added]; - res->updateIssues(m_instance); + for (auto& added : added_set) { + auto res = new_resources[added]; m_resources.append(res); resolveResource(m_resources.last()); } @@ -952,10 +882,10 @@ void ResourceFolderModel::applyUpdates(QSet& currentSet, QSet& // update index { - m_resourcesIndex.clear(); + m_resources_index.clear(); int idx = 0; - for (const auto& mod : qAsConst(m_resources)) { - m_resourcesIndex[mod->internalId()] = idx; + for (auto const& mod : qAsConst(m_resources)) { + m_resources_index[mod->internal_id()] = idx; idx++; } } @@ -963,29 +893,25 @@ void ResourceFolderModel::applyUpdates(QSet& currentSet, QSet& Resource::Ptr ResourceFolderModel::find(QString id) { auto iter = - std::find_if(m_resources.constBegin(), m_resources.constEnd(), [&](const Resource::Ptr& r) { return r->internalId() == id; }); - if (iter == m_resources.constEnd()) { + std::find_if(m_resources.constBegin(), m_resources.constEnd(), [&](Resource::Ptr const& r) { return r->internal_id() == id; }); + if (iter == m_resources.constEnd()) return nullptr; - } return *iter; } QList ResourceFolderModel::allResources() { QList result; result.reserve(m_resources.size()); - for (const Resource ::Ptr& resource : m_resources) { + for (const Resource ::Ptr& resource : m_resources) result.append((resource.get())); - } return result; } - QList ResourceFolderModel::selectedResources(const QModelIndexList& indexes) { QList result; for (const QModelIndex& index : indexes) { - if (index.column() != 0) { + if (index.column() != 0) continue; - } result.append(&at(index.row())); } return result; diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index 7699394ab..b6343a807 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -61,7 +61,7 @@ class QSortFilterProxyModel; class ResourceFolderModel : public QAbstractListModel { Q_OBJECT public: - ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent = nullptr); + ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); ~ResourceFolderModel() override; virtual QString id() const { return "resource"; } @@ -93,13 +93,13 @@ class ResourceFolderModel : public QAbstractListModel { */ virtual bool installResource(QString path); - virtual void installResourceWithFlameMetadata(const QString& path, ModPlatform::IndexedVersion& vers); + virtual void installResourceWithFlameMetadata(QString path, ModPlatform::IndexedVersion& vers); /** Uninstall (i.e. remove all data about it) a resource, given its file name. * * Returns whether the removal was successful. */ - virtual bool uninstallResource(const QString& fileName, bool preserveMetadata = false); + virtual bool uninstallResource(const QString& file_name, bool preserve_metadata = false); virtual bool deleteResources(const QModelIndexList&); virtual void deleteMetadata(const QModelIndexList&); @@ -125,7 +125,7 @@ class ResourceFolderModel : public QAbstractListModel { Resource::Ptr find(QString id); - const QDir& dir() const { return m_dir; } + QDir const& dir() const { return m_dir; } /** Checks whether there's any parse tasks being done. * @@ -137,12 +137,12 @@ class ResourceFolderModel : public QAbstractListModel { /* Qt behavior */ /* Basic columns */ - enum Columns : std::uint8_t { ActiveColumn = 0, NameColumn, DateColumn, ProviderColumn, SizeColumn, FileNameColumn, NumColumns }; + enum Columns { ActiveColumn = 0, NameColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS }; - QStringList columnNames(bool translated = true) const { return translated ? m_columnNamesTranslated : m_columnNames; } + QStringList columnNames(bool translated = true) const { return translated ? m_column_names_translated : m_column_names; } 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 : NumColumns; } + int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NUM_COLUMNS; } Qt::DropActions supportedDropActions() const override; @@ -153,7 +153,6 @@ class ResourceFolderModel : public QAbstractListModel { [[nodiscard]] bool validateIndex(const QModelIndex& index) const; - QBrush rowBackground(int row) const; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; @@ -171,19 +170,18 @@ class ResourceFolderModel : public QAbstractListModel { QSortFilterProxyModel* createFilterProxyModel(QObject* parent = nullptr); SortType columnToSortKey(size_t column) const; - QList columnResizeModes() const { return m_columnResizeModes; } + QList columnResizeModes() const { return m_column_resize_modes; } class ProxyModel : public QSortFilterProxyModel { public: explicit ProxyModel(QObject* parent = nullptr) : QSortFilterProxyModel(parent) {} protected: - bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; - bool lessThan(const QModelIndex& sourceLeft, const QModelIndex& sourceRight) 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; - BaseInstance* instance() const { return m_instance; } signals: void updateFinished(); @@ -207,7 +205,7 @@ class ResourceFolderModel : public QAbstractListModel { * This task should load and parse all heavy info needed by a resource, such as parsing a manifest. It gets executed * in the background, so it slowly updates the UI as tasks get done. */ - [[nodiscard]] virtual Task* createParseTask(Resource& /*unused*/) { return nullptr; } + [[nodiscard]] virtual Task* createParseTask(Resource&) { return nullptr; } /** Standard implementation of the model update logic. * @@ -215,10 +213,10 @@ class ResourceFolderModel : public QAbstractListModel { * to act only on those disparities. * */ - void applyUpdates(QSet& currentSet, QSet& newSet, QMap& newResources); + void applyUpdates(QSet& current_set, QSet& new_set, QMap& new_resources); protected slots: - void directoryChanged(const QString&); + void directoryChanged(QString); /** Called when the update task is successful. * @@ -234,40 +232,39 @@ class ResourceFolderModel : public QAbstractListModel { * This is just a simple reference implementation. You probably want to override it with your own logic in a subclass * if the resource is complex and has more stuff to parse. */ - virtual void onParseSucceeded(int ticket, const QString& resourceId); - virtual void onParseFailed(int ticket, const QString& resourceId); + virtual void onParseSucceeded(int ticket, QString resource_id); + virtual void onParseFailed(int ticket, QString resource_id); protected: // Represents the relationship between a column's index (represented by the list index), and it's sorting key. // As such, the order in with they appear is very important! - QList m_columnSortKeys = { SortType::Enabled, SortType::Name, SortType::Date, - SortType::Provider, SortType::Size, SortType::Filename }; - QStringList m_columnNames = { "Enable", "Name", "Last Modified", "Provider", "Size", "File Name" }; - QStringList m_columnNamesTranslated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size"), tr("File Name") }; - QList m_columnResizeModes = { QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, - QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; - QList m_columnsHideable = { false, false, true, true, true, true }; + QList m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE, SortType::PROVIDER, SortType::SIZE }; + QStringList m_column_names = { "Enable", "Name", "Last Modified", "Provider", "Size" }; + QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size") }; + QList m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, + QHeaderView::Interactive, QHeaderView::Interactive }; + QList m_columnsHideable = { false, false, true, true, true }; QDir m_dir; BaseInstance* m_instance; QFileSystemWatcher m_watcher; - bool m_isWatching = false; + bool m_is_watching = false; - bool m_isIndexed; - bool m_firstFolderLoad = true; + bool m_is_indexed; + bool m_first_folder_load = true; - Task::Ptr m_currentUpdateTask = nullptr; - bool m_scheduledUpdate = false; + Task::Ptr m_current_update_task = nullptr; + bool m_scheduled_update = false; QList m_resources; // Represents the relationship between a resource's internal ID and it's row position on the model. - QMap m_resourcesIndex; + QMap m_resources_index; // Runs off-thread ConcurrentTask m_resourceResolver; bool m_resourceResolverRunning = false; - QMap m_activeParseTasks; - std::atomic m_nextResolutionTicket = 0; + QMap m_active_parse_tasks; + std::atomic m_next_resolution_ticket = 0; }; diff --git a/launcher/minecraft/mod/ResourcePack.cpp b/launcher/minecraft/mod/ResourcePack.cpp index 2d4295b1b..ccf52e8e4 100644 --- a/launcher/minecraft/mod/ResourcePack.cpp +++ b/launcher/minecraft/mod/ResourcePack.cpp @@ -3,101 +3,35 @@ #include #include #include -#include #include "MTPixmapCache.h" #include "Version.h" // Values taken from: // https://minecraft.wiki/w/Pack_format#List_of_resource_pack_formats -static const QMap, std::pair> s_pack_format_versions = { - { { 1, 0 }, { Version("1.6.1"), Version("1.8.9") } }, - { { 2, 0 }, { Version("1.9"), Version("1.10.2") } }, - { { 3, 0 }, { Version("1.11"), Version("1.12.2") } }, - { { 4, 0 }, { Version("1.13"), Version("1.14.4") } }, - { { 5, 0 }, { Version("1.15"), Version("1.16.1") } }, - { { 6, 0 }, { Version("1.16.2"), Version("1.16.5") } }, - { { 7, 0 }, { Version("1.17"), Version("1.17.1") } }, - { { 8, 0 }, { Version("1.18"), Version("1.18.2") } }, - { { 9, 0 }, { Version("1.19"), Version("1.19.2") } }, - { { 11, 0 }, { Version("22w42a"), Version("22w44a") } }, - { { 12, 0 }, { Version("1.19.3"), Version("1.19.3") } }, - { { 13, 0 }, { Version("1.19.4"), Version("1.19.4") } }, - { { 14, 0 }, { Version("23w14a"), Version("23w16a") } }, - { { 15, 0 }, { Version("1.20"), Version("1.20.1") } }, - { { 16, 0 }, { Version("23w31a"), Version("23w31a") } }, - { { 17, 0 }, { Version("23w32a"), Version("1.20.2-pre1") } }, - { { 18, 0 }, { Version("1.20.2"), Version("1.20.2") } }, - { { 19, 0 }, { Version("23w42a"), Version("23w42a") } }, - { { 20, 0 }, { Version("23w43a"), Version("23w44a") } }, - { { 21, 0 }, { Version("23w45a"), Version("23w46a") } }, - { { 22, 0 }, { Version("1.20.3"), Version("1.20.4") } }, - { { 24, 0 }, { Version("24w03a"), Version("24w04a") } }, - { { 25, 0 }, { Version("24w05a"), Version("24w05b") } }, - { { 26, 0 }, { Version("24w06a"), Version("24w07a") } }, - { { 28, 0 }, { Version("24w09a"), Version("24w10a") } }, - { { 29, 0 }, { Version("24w11a"), Version("24w11a") } }, - { { 30, 0 }, { Version("24w12a"), Version("24w12a") } }, - { { 31, 0 }, { Version("24w13a"), Version("1.20.5-pre3") } }, - { { 32, 0 }, { Version("1.20.5"), Version("1.20.6") } }, - { { 33, 0 }, { Version("24w18a"), Version("24w20a") } }, - { { 34, 0 }, { Version("1.21"), Version("1.21.1") } }, - { { 35, 0 }, { Version("24w33a"), Version("24w33a") } }, - { { 36, 0 }, { Version("24w34a"), Version("24w35a") } }, - { { 37, 0 }, { Version("24w36a"), Version("24w36a") } }, - { { 38, 0 }, { Version("24w37a"), Version("24w37a") } }, - { { 39, 0 }, { Version("24w38a"), Version("24w39a") } }, - { { 40, 0 }, { Version("24w40a"), Version("24w40a") } }, - { { 41, 0 }, { Version("1.21.2-pre1"), Version("1.21.2-pre2") } }, - { { 42, 0 }, { Version("1.21.2"), Version("1.21.3") } }, - { { 43, 0 }, { Version("24w44a"), Version("24w44a") } }, - { { 44, 0 }, { Version("24w45a"), Version("24w45a") } }, - { { 45, 0 }, { Version("24w46a"), Version("24w46a") } }, - { { 46, 0 }, { Version("1.21.4"), Version("1.21.4") } }, - { { 47, 0 }, { Version("25w02a"), Version("25w02a") } }, - { { 48, 0 }, { Version("25w03a"), Version("25w03a") } }, - { { 49, 0 }, { Version("25w04a"), Version("25w04a") } }, - { { 50, 0 }, { Version("25w05a"), Version("25w05a") } }, - { { 51, 0 }, { Version("25w06a"), Version("25w06a") } }, - { { 52, 0 }, { Version("25w07a"), Version("25w07a") } }, - { { 53, 0 }, { Version("25w08a"), Version("25w09b") } }, - { { 54, 0 }, { Version("25w10a"), Version("25w10a") } }, - { { 55, 0 }, { Version("1.21.5"), Version("1.21.5") } }, - { { 56, 0 }, { Version("25w15a"), Version("25w15a") } }, - { { 57, 0 }, { Version("25w16a"), Version("25w16a") } }, - { { 58, 0 }, { Version("25w17a"), Version("25w17a") } }, - { { 59, 0 }, { Version("25w18a"), Version("25w18a") } }, - { { 60, 0 }, { Version("25w19a"), Version("25w19a") } }, - { { 61, 0 }, { Version("25w20a"), Version("25w20a") } }, - { { 62, 0 }, { Version("25w21a"), Version("25w21a") } }, - { { 63, 0 }, { Version("1.21.6"), Version("1.21.6") } }, - { { 64, 0 }, { Version("1.21.7"), Version("1.21.8") } }, - { { 65, 0 }, { Version("25w31a"), Version("25w31a") } }, - { { 65, 1 }, { Version("25w32a"), Version("25w32a") } }, - { { 65, 2 }, { Version("25w33a"), Version("25w33a") } }, - { { 66, 0 }, { Version("25w34a"), Version("25w34b") } }, - { { 67, 0 }, { Version("25w35a"), Version("25w35a") } }, - { { 68, 0 }, { Version("25w36a"), Version("25w36b") } }, - { { 69, 0 }, { Version("1.21.9"), Version("1.21.10") } }, - { { 70, 0 }, { Version("25w41a"), Version("25w41a") } }, - { { 70, 1 }, { Version("25w42a"), Version("25w42a") } }, - { { 71, 0 }, { Version("25w43a"), Version("25w43a") } }, - { { 72, 0 }, { Version("25w44a"), Version("25w44a") } }, - { { 73, 0 }, { Version("25w45a"), Version("25w45a") } }, - { { 74, 0 }, { Version("25w46a"), Version("25w46a") } }, - { { 75, 0 }, { Version("1.21.11"), Version("1.21.11") } }, - { { 76, 0 }, { Version("26.1-snap1"), Version("26.1-snap1") } }, - { { 77, 0 }, { Version("26.1-snap2"), Version("26.1-snap2") } }, - { { 78, 0 }, { Version("26.1-snap3"), Version("26.1-snap3") } }, - { { 78, 1 }, { Version("26.1-snap4"), Version("26.1-snap4") } }, - { { 79, 0 }, { Version("26.1-snap5"), Version("26.1-snap5") } }, - { { 80, 0 }, { Version("26.1-snap6"), Version("26.1-snap6") } }, - { { 81, 0 }, { Version("26.1-snap7"), Version("26.1-snap7") } }, - { { 81, 1 }, { Version("26.1-snap8"), Version("26.1-snap9") } }, - { { 82, 0 }, { Version("26.1-snap10"), Version("26.1-snap10") } }, - { { 83, 0 }, { Version("26.1-snap11"), Version("26.1-snap11") } }, +static const QMap> s_pack_format_versions = { + { 1, { Version("1.6.1"), Version("1.8.9") } }, { 2, { Version("1.9"), Version("1.10.2") } }, + { 3, { Version("1.11"), Version("1.12.2") } }, { 4, { Version("1.13"), Version("1.14.4") } }, + { 5, { Version("1.15"), Version("1.16.1") } }, { 6, { Version("1.16.2"), Version("1.16.5") } }, + { 7, { Version("1.17"), Version("1.17.1") } }, { 8, { Version("1.18"), Version("1.18.2") } }, + { 9, { Version("1.19"), Version("1.19.2") } }, { 11, { Version("22w42a"), Version("22w44a") } }, + { 12, { Version("1.19.3"), Version("1.19.3") } }, { 13, { Version("1.19.4"), Version("1.19.4") } }, + { 14, { Version("23w14a"), Version("23w16a") } }, { 15, { Version("1.20"), Version("1.20.1") } }, + { 16, { Version("23w31a"), Version("23w31a") } }, { 17, { Version("23w32a"), Version("23w35a") } }, + { 18, { Version("1.20.2"), Version("23w16a") } }, { 19, { Version("23w42a"), Version("23w42a") } }, + { 20, { Version("23w43a"), Version("23w44a") } }, { 21, { Version("23w45a"), Version("23w46a") } }, + { 22, { Version("1.20.3-pre1"), Version("23w51b") } }, { 24, { Version("24w03a"), Version("24w04a") } }, + { 25, { Version("24w05a"), Version("24w05b") } }, { 26, { Version("24w06a"), Version("24w07a") } }, + { 28, { Version("24w09a"), Version("24w10a") } }, { 29, { Version("24w11a"), Version("24w11a") } }, + { 30, { Version("24w12a"), Version("23w12a") } }, { 31, { Version("24w13a"), Version("1.20.5-pre3") } }, + { 32, { Version("1.20.5-pre4"), Version("1.20.6") } }, { 33, { Version("24w18a"), Version("24w20a") } }, + { 34, { Version("24w21a"), Version("1.21") } } }; -QMap, std::pair> ResourcePack::mappings() const +std::pair ResourcePack::compatibleVersions() const { - return s_pack_format_versions; + if (!s_pack_format_versions.contains(m_pack_format)) { + return { {}, {} }; + } + + return s_pack_format_versions.constFind(m_pack_format).value(); } diff --git a/launcher/minecraft/mod/ResourcePack.h b/launcher/minecraft/mod/ResourcePack.h index 43aa5e1da..9345e9c27 100644 --- a/launcher/minecraft/mod/ResourcePack.h +++ b/launcher/minecraft/mod/ResourcePack.h @@ -22,5 +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. */ - QMap, std::pair> mappings() const override; + std::pair compatibleVersions() const override; }; diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index 4dd8e314c..5680f4c2d 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -39,92 +39,98 @@ #include #include +#include "Version.h" + #include "minecraft/mod/tasks/LocalDataPackParseTask.h" -ResourcePackFolderModel::ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent) - : ResourceFolderModel(dir, instance, isIndexed, createDir, parent) +ResourcePackFolderModel::ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) + : ResourceFolderModel(dir, instance, is_indexed, create_dir, parent) { - m_columnNames = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Provider", "Size", "File Name" }); - m_columnNamesTranslated = QStringList( - { tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Provider"), tr("Size"), tr("File Name") }); - m_columnSortKeys = { SortType::Enabled, SortType::Name, SortType::Name, SortType::PackFormat, - SortType::Date, SortType::Provider, SortType::Size, SortType::Filename }; - m_columnResizeModes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, - QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; - m_columnsHideable = { false, true, false, true, true, true, true, true }; + m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Provider", "Size" }); + m_column_names_translated = + QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Provider"), tr("Size") }); + m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, + SortType::DATE, SortType::PROVIDER, SortType::SIZE }; + m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, + QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; + m_columnsHideable = { false, true, false, true, true, true, true }; } QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const { - if (!validateIndex(index)) { + if (!validateIndex(index)) return {}; - } int row = index.row(); int column = index.column(); switch (role) { - case Qt::BackgroundRole: - return rowBackground(row); - case Qt::DisplayRole: { - if (column == PackFormatColumn) { - const auto& resource = at(row); - return resource.packFormatStr(); + case Qt::DisplayRole: + switch (column) { + case NameColumn: + return m_resources[row]->name(); + case PackFormatColumn: { + auto& resource = at(row); + auto pack_format = resource.packFormat(); + if (pack_format == 0) + return tr("Unrecognized"); + + auto version_bounds = resource.compatibleVersions(); + if (version_bounds.first.toString().isEmpty()) + return QString::number(pack_format); + + return QString("%1 (%2 - %3)") + .arg(QString::number(pack_format), version_bounds.first.toString(), version_bounds.second.toString()); + } + case DateColumn: + return m_resources[row]->dateTimeChanged(); + case ProviderColumn: + return m_resources[row]->provider(); + case SizeColumn: + return m_resources[row]->sizeStr(); + default: + return {}; } - break; - } case Qt::DecorationRole: { + if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink())) + return QIcon::fromTheme("status-yellow"); if (column == ImageColumn) { return at(row).image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding); } - break; + return {}; } case Qt::ToolTipRole: { if (column == PackFormatColumn) { //: The string being explained by this is in the format: ID (Lower version - Upper version) return tr("The resource pack format ID, as well as the Minecraft versions it was designed for."); } - break; + if (column == NameColumn) { + if (at(row).isSymLinkUnder(instDirPath())) { + return m_resources[row]->internal_id() + + tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." + "\nCanonical Path: %1") + .arg(at(row).fileinfo().canonicalFilePath()); + ; + } + if (at(row).isMoreThanOneHardLink()) { + return m_resources[row]->internal_id() + + tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original."); + } + } + return m_resources[row]->internal_id(); } case Qt::SizeHintRole: if (column == ImageColumn) { return QSize(32, 32); } - break; + return {}; + case Qt::CheckStateRole: + if (column == ActiveColumn) + return at(row).enabled() ? Qt::Checked : Qt::Unchecked; + return {}; default: - break; + return {}; } - - // map the columns to the base equivilents - QModelIndex mappedIndex; - switch (column) { - case ActiveColumn: - mappedIndex = index.siblingAtColumn(ResourceFolderModel::ActiveColumn); - break; - case NameColumn: - mappedIndex = index.siblingAtColumn(ResourceFolderModel::NameColumn); - break; - case DateColumn: - mappedIndex = index.siblingAtColumn(ResourceFolderModel::DateColumn); - break; - case ProviderColumn: - mappedIndex = index.siblingAtColumn(ResourceFolderModel::ProviderColumn); - break; - case SizeColumn: - mappedIndex = index.siblingAtColumn(ResourceFolderModel::SizeColumn); - break; - case FileNameColumn: - mappedIndex = index.siblingAtColumn(ResourceFolderModel::FileNameColumn); - break; - default: - break; - } - - if (mappedIndex.isValid()) { - return ResourceFolderModel::data(mappedIndex, role); - } - - return {}; } QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientation orientation, int role) const @@ -139,7 +145,6 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O case ImageColumn: case ProviderColumn: case SizeColumn: - case FileNameColumn: return columnNames().at(section); default: return {}; @@ -160,8 +165,6 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O return tr("The source provider of the resource pack."); case SizeColumn: return tr("The size of the resource pack."); - case FileNameColumn: - return tr("The file name of the resource pack."); default: return {}; } @@ -177,10 +180,10 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const { - return parent.isValid() ? 0 : NumColumns; + return parent.isValid() ? 0 : NUM_COLUMNS; } Task* ResourcePackFolderModel::createParseTask(Resource& resource) { - return new LocalDataPackParseTask(m_nextResolutionTicket, dynamic_cast(&resource)); + return new LocalDataPackParseTask(m_next_resolution_ticket, dynamic_cast(&resource)); } diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.h b/launcher/minecraft/mod/ResourcePackFolderModel.h index 186bbb75d..b552c324e 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.h +++ b/launcher/minecraft/mod/ResourcePackFolderModel.h @@ -7,19 +7,9 @@ class ResourcePackFolderModel : public ResourceFolderModel { Q_OBJECT public: - enum Columns : std::uint8_t { - ActiveColumn = 0, - ImageColumn, - NameColumn, - PackFormatColumn, - DateColumn, - ProviderColumn, - SizeColumn, - FileNameColumn, - NumColumns - }; + enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS }; - explicit ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent = nullptr); + explicit ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); QString id() const override { return "resourcepacks"; } @@ -29,7 +19,7 @@ class ResourcePackFolderModel : public ResourceFolderModel { int columnCount(const QModelIndex& parent) const override; [[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new ResourcePack(file); } - [[nodiscard]] Task* createParseTask(Resource& /*unused*/) override; + [[nodiscard]] Task* createParseTask(Resource&) override; RESOURCE_HELPERS(ResourcePack) }; diff --git a/launcher/minecraft/mod/ShaderPackFolderModel.h b/launcher/minecraft/mod/ShaderPackFolderModel.h index ded641770..9b0180180 100644 --- a/launcher/minecraft/mod/ShaderPackFolderModel.h +++ b/launcher/minecraft/mod/ShaderPackFolderModel.h @@ -18,7 +18,7 @@ class ShaderPackFolderModel : public ResourceFolderModel { [[nodiscard]] Task* createParseTask(Resource& resource) override { - return new LocalShaderPackParseTask(m_nextResolutionTicket, static_cast(resource)); + return new LocalShaderPackParseTask(m_next_resolution_ticket, static_cast(resource)); } QDir indexDir() const override { return m_dir; } diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index 976bf3854..57c5f8ee9 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -36,82 +36,83 @@ #include "TexturePackFolderModel.h" #include "minecraft/mod/tasks/LocalTexturePackParseTask.h" +#include "minecraft/mod/tasks/ResourceFolderLoadTask.h" -TexturePackFolderModel::TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent) - : ResourceFolderModel(QDir(dir), instance, isIndexed, createDir, parent) +TexturePackFolderModel::TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) + : ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent) { - m_columnNames = QStringList({ "Enable", "Image", "Name", "Last Modified", "Provider", "Size", "File Name" }); - m_columnNamesTranslated = - QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size"), tr("File Name") }); - m_columnSortKeys = { SortType::Enabled, SortType::Name, SortType::Name, SortType::Date, - SortType::Provider, SortType::Size, SortType::Filename }; - m_columnResizeModes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, - QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; - m_columnsHideable = { false, true, false, true, true, true, true }; + m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified", "Provider", "Size" }); + m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size") }); + m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE, SortType::PROVIDER, SortType::SIZE }; + m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, + QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; + m_columnsHideable = { false, true, false, true, true, true }; } Task* TexturePackFolderModel::createParseTask(Resource& resource) { - return new LocalTexturePackParseTask(m_nextResolutionTicket, static_cast(resource)); + return new LocalTexturePackParseTask(m_next_resolution_ticket, static_cast(resource)); } QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const { - if (!validateIndex(index)) { + if (!validateIndex(index)) return {}; - } int row = index.row(); int column = index.column(); switch (role) { - case Qt::BackgroundRole: - return rowBackground(row); + case Qt::DisplayRole: + switch (column) { + case NameColumn: + return m_resources[row]->name(); + case DateColumn: + return m_resources[row]->dateTimeChanged(); + case ProviderColumn: + return m_resources[row]->provider(); + case SizeColumn: + return m_resources[row]->sizeStr(); + default: + return {}; + } + case Qt::ToolTipRole: + if (column == NameColumn) { + if (at(row).isSymLinkUnder(instDirPath())) { + return m_resources[row]->internal_id() + + tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." + "\nCanonical Path: %1") + .arg(at(row).fileinfo().canonicalFilePath()); + ; + } + if (at(row).isMoreThanOneHardLink()) { + return m_resources[row]->internal_id() + + tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original."); + } + } + + return m_resources[row]->internal_id(); case Qt::DecorationRole: { + if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink())) + return QIcon::fromTheme("status-yellow"); if (column == ImageColumn) { return at(row).image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding); } - break; + return {}; } case Qt::SizeHintRole: if (column == ImageColumn) { return QSize(32, 32); } - break; + return {}; + case Qt::CheckStateRole: + if (column == ActiveColumn) { + return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked; + } + return {}; default: - break; + return {}; } - - // map the columns to the base equivilents - QModelIndex mappedIndex; - switch (column) { - case ActiveColumn: - mappedIndex = index.siblingAtColumn(ResourceFolderModel::ActiveColumn); - break; - case NameColumn: - mappedIndex = index.siblingAtColumn(ResourceFolderModel::NameColumn); - break; - case DateColumn: - mappedIndex = index.siblingAtColumn(ResourceFolderModel::DateColumn); - break; - case ProviderColumn: - mappedIndex = index.siblingAtColumn(ResourceFolderModel::ProviderColumn); - break; - case SizeColumn: - mappedIndex = index.siblingAtColumn(ResourceFolderModel::SizeColumn); - break; - case FileNameColumn: - mappedIndex = index.siblingAtColumn(ResourceFolderModel::FileNameColumn); - break; - default: - break; - } - - if (mappedIndex.isValid()) { - return ResourceFolderModel::data(mappedIndex, role); - } - - return {}; } QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientation orientation, int role) const @@ -125,7 +126,6 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or case ImageColumn: case ProviderColumn: case SizeColumn: - case FileNameColumn: return columnNames().at(section); default: return {}; @@ -142,8 +142,6 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or return tr("The source provider of the texture pack."); case SizeColumn: return tr("The size of the texture pack."); - case FileNameColumn: - return tr("The file name of the texture pack."); default: return {}; } @@ -157,5 +155,5 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or int TexturePackFolderModel::columnCount(const QModelIndex& parent) const { - return parent.isValid() ? 0 : NumColumns; + return parent.isValid() ? 0 : NUM_COLUMNS; } diff --git a/launcher/minecraft/mod/TexturePackFolderModel.h b/launcher/minecraft/mod/TexturePackFolderModel.h index 3e7343092..37f78d8d7 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.h +++ b/launcher/minecraft/mod/TexturePackFolderModel.h @@ -44,20 +44,11 @@ class TexturePackFolderModel : public ResourceFolderModel { Q_OBJECT public: - enum Columns : std::uint8_t { - ActiveColumn = 0, - ImageColumn, - NameColumn, - DateColumn, - ProviderColumn, - SizeColumn, - FileNameColumn, - NumColumns - }; + enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS }; - explicit TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent = nullptr); + explicit TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); - QString id() const override { return "texturepacks"; } + virtual QString id() const override { return "texturepacks"; } QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; @@ -65,7 +56,7 @@ class TexturePackFolderModel : public ResourceFolderModel { int columnCount(const QModelIndex& parent) const override; [[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new TexturePack(file); } - [[nodiscard]] Task* createParseTask(Resource& /*unused*/) override; + [[nodiscard]] Task* createParseTask(Resource&) override; RESOURCE_HELPERS(TexturePack) }; diff --git a/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp index 0859c9880..f2ab390e9 100644 --- a/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp +++ b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp @@ -74,13 +74,13 @@ void GetModDependenciesTask::prepare() ModPlatform::Dependency GetModDependenciesTask::getOverride(const ModPlatform::Dependency& dep, const ModPlatform::ResourceProvider providerName) { - if (auto isQuilt = (m_loaderType & ModPlatform::Quilt) != 0U; isQuilt || (m_loaderType & ModPlatform::Fabric) != 0U) { + if (auto isQuilt = m_loaderType & ModPlatform::Quilt; isQuilt || m_loaderType & ModPlatform::Fabric) { auto overide = ModPlatform::getOverrideDeps(); auto over = std::find_if(overide.cbegin(), overide.cend(), [dep, providerName, isQuilt](const auto& o) { return o.provider == providerName && dep.addonId == (isQuilt ? o.fabric : o.quilt); }); if (over != overide.cend()) { - return { .addonId = isQuilt ? over->quilt : over->fabric, .type = dep.type, .version = "" }; + return { isQuilt ? over->quilt : over->fabric, dep.type }; } } return dep; @@ -91,45 +91,40 @@ QList GetModDependenciesTask::getDependenciesForVersion { QList c_dependencies; for (auto ver_dep : version.dependencies) { - if (ver_dep.type != ModPlatform::DependencyType::REQUIRED) { + if (ver_dep.type != ModPlatform::DependencyType::REQUIRED) continue; - } ver_dep = getOverride(ver_dep, providerName); auto isOnlyVersion = providerName == ModPlatform::ResourceProvider::MODRINTH && ver_dep.addonId.toString().isEmpty(); if (auto dep = std::find_if(c_dependencies.begin(), c_dependencies.end(), [&ver_dep, isOnlyVersion](const ModPlatform::Dependency& i) { return isOnlyVersion ? i.version == ver_dep.version : i.addonId == ver_dep.addonId; }); - dep != c_dependencies.end()) { + dep != c_dependencies.end()) continue; // check the current dependency list - } if (auto dep = std::find_if(m_selected.begin(), m_selected.end(), - [&ver_dep, providerName, isOnlyVersion](const std::shared_ptr& i) { + [&ver_dep, providerName, isOnlyVersion](std::shared_ptr i) { return i->pack->provider == providerName && (isOnlyVersion ? i->version.version == ver_dep.version : i->pack->addonId == ver_dep.addonId); }); - dep != m_selected.end()) { + dep != m_selected.end()) continue; // check the selected versions - } if (auto dep = std::find_if(m_mods.begin(), m_mods.end(), - [&ver_dep, providerName, isOnlyVersion](const std::shared_ptr& i) { + [&ver_dep, providerName, isOnlyVersion](std::shared_ptr i) { return i->provider == providerName && (isOnlyVersion ? i->file_id == ver_dep.version : i->project_id == ver_dep.addonId); }); - dep != m_mods.end()) { + dep != m_mods.end()) continue; // check the existing mods - } if (auto dep = std::find_if(m_pack_dependencies.begin(), m_pack_dependencies.end(), - [&ver_dep, providerName, isOnlyVersion](const std::shared_ptr& i) { + [&ver_dep, providerName, isOnlyVersion](std::shared_ptr i) { return i->pack->provider == providerName && (isOnlyVersion ? i->version.version == ver_dep.addonId : i->pack->addonId == ver_dep.addonId); }); - dep != m_pack_dependencies.end()) { // check loaded dependencies + dep != m_pack_dependencies.end()) // check loaded dependencies continue; - } c_dependencies.append(ver_dep); } @@ -139,7 +134,8 @@ QList GetModDependenciesTask::getDependenciesForVersion Task::Ptr GetModDependenciesTask::getProjectInfoTask(std::shared_ptr pDep) { auto provider = pDep->pack->provider; - auto [info, responseInfo] = getAPI(provider)->getProject(pDep->pack->addonId.toString()); + auto responseInfo = std::make_shared(); + 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); @@ -185,11 +181,9 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen tasks->addTask(getProjectInfoTask(pDep)); } - ResourceAPI::DependencySearchArgs args = { - .dependency = dep, .mcVersion = m_version, .loader = m_loaderType, .includeChangelog = true - }; + ResourceAPI::DependencySearchArgs args = { dep, m_version, m_loaderType }; ResourceAPI::Callback callbacks; - callbacks.on_fail = [](const QString& reason, int) { + 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& pack) { @@ -198,10 +192,10 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen 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 && dep.addonId == o.quilt; }); + [dep, provider](auto o) { return o.provider == provider && dep.addonId == o.quilt; }); if (over != overide.cend()) { removePack(dep.addonId); - addTask(prepareDependencyTask({ .addonId = over->fabric, .type = dep.type, .version = "" }, provider, level)); + addTask(prepareDependencyTask({ over->fabric, dep.type }, provider, level)); return; } } @@ -219,7 +213,7 @@ 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({ .addonId = pDep->version.addonId, .type = pDep->dependency.type, .version = "" }, provider); + auto dep_ = getOverride({ pDep->version.addonId, pDep->dependency.type }, provider); if (dep_.addonId != pDep->version.addonId) { removePack(pDep->version.addonId); addTask(prepareDependencyTask(dep_, provider, level)); @@ -231,7 +225,7 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen removePack(pDep->version.addonId); return; } - for (const auto& dep_ : getDependenciesForVersion(pDep->version, provider)) { + for (auto dep_ : getDependenciesForVersion(pDep->version, provider)) { addTask(prepareDependencyTask(dep_, provider, level - 1)); } }; diff --git a/launcher/minecraft/mod/tasks/GetModDependenciesTask.h b/launcher/minecraft/mod/tasks/GetModDependenciesTask.h index d6c2985b0..3530d6cc0 100644 --- a/launcher/minecraft/mod/tasks/GetModDependenciesTask.h +++ b/launcher/minecraft/mod/tasks/GetModDependenciesTask.h @@ -23,7 +23,6 @@ #include #include #include -#include #include "minecraft/mod/MetadataHandler.h" #include "minecraft/mod/ModFolderModel.h" @@ -45,11 +44,15 @@ class GetModDependenciesTask : public SequentialTask { ModPlatform::IndexedPack::Ptr pack; ModPlatform::IndexedVersion version; PackDependency() = default; - PackDependency(ModPlatform::IndexedPack::Ptr p, ModPlatform::IndexedVersion v) : pack(std::move(p)), version(std::move(v)) {} + PackDependency(const ModPlatform::IndexedPack::Ptr p, const ModPlatform::IndexedVersion& v) + { + pack = p; + version = v; + } }; struct PackDependencyExtraInfo { - bool maybe_installed{}; + bool maybe_installed; QStringList required_by; }; @@ -59,12 +62,12 @@ class GetModDependenciesTask : public SequentialTask { QHash getExtraInfo(); private: - ResourceAPI* getAPI(ModPlatform::ResourceProvider provider) + inline ResourceAPI* getAPI(ModPlatform::ResourceProvider provider) { - if (provider == ModPlatform::ResourceProvider::FLAME) { + if (provider == ModPlatform::ResourceProvider::FLAME) return &m_flameAPI; - } - return &m_modrinthAPI; + else + return &m_modrinthAPI; } protected slots: diff --git a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp index 1bf9a5c02..054ae78c0 100644 --- a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp @@ -27,7 +27,6 @@ #include "minecraft/mod/ResourcePack.h" #include -#include namespace DataPackUtils { @@ -164,25 +163,6 @@ bool processZIP(DataPack* pack, ProcessingLevel level) return true; } -std::pair parseVersion(const QJsonValue& value) -{ - if (value.isDouble()) { - // Single integer -> [major, 0] - return std::make_pair(value.toInt(), 0); - } - std::pair version; - if (value.isArray()) { - QJsonArray arr = value.toArray(); - if (arr.size() >= 1) { - version.first = arr.at(0).toInt(); - } - if (arr.size() >= 2) { - version.second = arr.at(1).toInt(); - } - } - return version; -} - // https://minecraft.wiki/w/Data_pack#pack.mcmeta // https://minecraft.wiki/w/Raw_JSON_text_format // https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta @@ -197,20 +177,7 @@ bool processMCMeta(DataPack* pack, QByteArray&& raw_data) try { auto pack_obj = Json::requireObject(json_doc.object(), "pack", {}); - - int pack_format = 0; - std::pair min_format; - std::pair max_format; - if (pack_obj.contains("pack_format")) { - pack_format = pack_obj.value("pack_format").toInt(); - } - if (pack_obj.contains("min_format")) { - min_format = parseVersion(pack_obj.value("min_format")); - } - if (pack_obj.contains("max_format")) { - max_format = parseVersion(pack_obj.value("max_format")); - } - pack->setPackFormat(pack_format, min_format, max_format); + pack->setPackFormat(pack_obj["pack_format"].toInt()); pack->setDescription(DataPackUtils::processComponent(pack_obj.value("description"))); } catch (Json::JsonException& e) { qWarning() << "JsonException:" << e.what() << e.cause(); diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index 74c311676..59d3876b3 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -61,36 +61,6 @@ ModDetails ReadMCModInfo(QByteArray contents) for (auto author : authors) { details.authors.append(author.toString()); } - - if (details.mod_id.startsWith("mod_")) { - details.mod_id = details.mod_id.mid(4); - } - - auto addDep = [&details](QString dep) { - if (dep == "mod_MinecraftForge" || dep == "Forge") - return; - if (dep.contains(":")) { - dep = dep.section(":", 1); - } - if (dep.contains("@")) { - dep = dep.section("@", 0, 0); - } - if (dep.startsWith("mod_")) { - dep = dep.mid(4); - } - details.dependencies.append(dep); - }; - - if (firstObj.contains("requiredMods")) { - for (auto dep : firstObj.value("requiredMods").toArray()) { - addDep(dep.toString()); - } - } else if (firstObj.contains("dependencies")) { - for (auto dep : firstObj.value("dependencies").toArray()) { - addDep(dep.toString()); - } - } - return details; }; QJsonParseError jsonError; @@ -228,51 +198,6 @@ ModDetails ReadMCModTOML(QByteArray contents) } details.icon_file = logoFile; - auto parseDep = [&details](toml::array* dependencies) { - static const QStringList ignoreModIds = { "", "forge", "neoforge", "minecraft" }; - if (!dependencies) { - return; - } - auto isNeoForgeDep = [](toml::table* t) { - auto type = (*t)["type"].as_string(); - return type && type->get() == "required"; - }; - auto isForgeDep = [](toml::table* t) { - auto mandatory = (*t)["mandatory"].as_boolean(); - return mandatory && mandatory->get(); - }; - for (auto& dep : *dependencies) { - auto dep_table = dep.as_table(); - if (!dep_table) { - continue; - } - auto modId = (*dep_table)["modId"].as_string(); - if (!modId || ignoreModIds.contains(QString::fromStdString(modId->get()))) { - continue; - } - if (isNeoForgeDep(dep_table) || isForgeDep(dep_table)) { - details.dependencies.append(QString::fromStdString(modId->get())); - } - } - }; - - if (tomlData.contains("dependencies")) { - auto depValue = tomlData["dependencies"]; - if (auto array = depValue.as_array()) { - parseDep(array); - } else if (auto depTable = depValue.as_table()) { - auto expectedKey = details.mod_id.toStdString(); - if (!depTable->contains(expectedKey)) { - if (auto it = depTable->begin(); it != depTable->end()) { - expectedKey = it->first; - } - } - if ((array = (*depTable)[expectedKey].as_array())) { - parseDep(array); - } - } - } - return details; } @@ -351,26 +276,15 @@ ModDetails ReadFabricModInfo(QByteArray contents) details.icon_file = obj.value(key).toString(); } else { // parsing the sizes failed // take the first - if (auto it = obj.begin(); it != obj.end()) { - details.icon_file = it->toString(); + for (auto i : obj) { + details.icon_file = i.toString(); + break; } } } else if (icon.isString()) { details.icon_file = icon.toString(); } } - - if (object.contains("depends")) { - auto depends = object.value("depends"); - if (depends.isObject()) { - auto obj = depends.toObject(); - for (auto key : obj.keys()) { - if (key != "fabricloader" && key != "minecraft" && !key.startsWith("fabric-")) { - details.dependencies.append(key); - } - } - } - } } return details; } @@ -449,37 +363,15 @@ ModDetails ReadQuiltModInfo(QByteArray contents) details.icon_file = obj.value(key).toString(); } else { // parsing the sizes failed // take the first - if (auto it = obj.begin(); it != obj.end()) { - details.icon_file = it->toString(); + for (auto i : obj) { + details.icon_file = i.toString(); + break; } } } else if (icon.isString()) { details.icon_file = icon.toString(); } } - if (object.contains("depends")) { - auto depends = object.value("depends"); - if (depends.isArray()) { - auto array = depends.toArray(); - for (auto obj : array) { - QString modId; - if (obj.isString()) { - modId = obj.toString(); - } else if (obj.isObject()) { - auto objValue = obj.toObject(); - modId = objValue.value("id").toString(); - if (objValue.contains("optional") && objValue.value("optional").toBool()) { - continue; - } - } else { - continue; - } - if (modId != "minecraft" && !modId.startsWith("quilt_")) { - details.dependencies.append(modId); - } - } - } - } } } catch (const Exception& e) { @@ -568,7 +460,7 @@ bool process(Mod& mod, ProcessingLevel level) case ResourceType::LITEMOD: return processLitemod(mod); default: - qWarning() << "Invalid type" << mod.type() << "for mod parse task!"; + qWarning() << "Invalid type for mod parse task!"; return false; } } @@ -751,7 +643,7 @@ bool loadIconFile(const Mod& mod, QPixmap* pixmap) if (icon_info.exists() && icon_info.isFile()) { QFile icon(icon_info.filePath()); if (!icon.open(QIODevice::ReadOnly)) { - return png_invalid("failed to open file " + icon_info.filePath() + " " + icon.errorString()); + return png_invalid("failed to open file " + icon_info.filePath()); } auto data = icon.readAll(); diff --git a/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.cpp index a90e9ca5e..3b98e053b 100644 --- a/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.cpp @@ -41,28 +41,26 @@ #include "minecraft/mod/MetadataHandler.h" #include -#include -ResourceFolderLoadTask::ResourceFolderLoadTask(const QDir& resourceDir, - const QDir& indexDir, - bool isIndexed, - bool cleanOrphan, - std::function createFunction) +ResourceFolderLoadTask::ResourceFolderLoadTask(const QDir& resource_dir, + const QDir& index_dir, + bool is_indexed, + bool clean_orphan, + std::function create_function) : Task(false) - , m_resource_dir(resourceDir) - , m_index_dir(indexDir) - , m_is_indexed(isIndexed) - , m_clean_orphan(cleanOrphan) - , m_create_func(std::move(createFunction)) + , m_resource_dir(resource_dir) + , m_index_dir(index_dir) + , m_is_indexed(is_indexed) + , m_clean_orphan(clean_orphan) + , m_create_func(create_function) , m_result(new Result()) , m_thread_to_spawn_into(thread()) {} void ResourceFolderLoadTask::executeTask() { - if (thread() != m_thread_to_spawn_into) { + if (thread() != m_thread_to_spawn_into) connect(this, &Task::finished, this->thread(), &QThread::quit); - } if (m_is_indexed) { // Read metadata first @@ -73,7 +71,7 @@ void ResourceFolderLoadTask::executeTask() m_resource_dir.refresh(); for (auto entry : m_resource_dir.entryInfoList()) { auto filePath = entry.absoluteFilePath(); - if (auto* app = APPLICATION_DYN; (app != nullptr) && app->checkQSavePath(filePath)) { + if (auto app = APPLICATION_DYN; app && app->checkQSavePath(filePath)) { continue; } auto newFilePath = FS::getUniqueResourceName(filePath); @@ -85,29 +83,29 @@ void ResourceFolderLoadTask::executeTask() Resource* resource = m_create_func(entry); if (resource->enabled()) { - if (m_result->resources.contains(resource->internalId())) { - m_result->resources[resource->internalId()]->setStatus(ResourceStatus::Installed); + if (m_result->resources.contains(resource->internal_id())) { + m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::INSTALLED); // Delete the object we just created, since a valid one is already in the mods list. delete resource; } else { - m_result->resources[resource->internalId()].reset(resource); - m_result->resources[resource->internalId()]->setStatus(ResourceStatus::NoMetadata); + m_result->resources[resource->internal_id()].reset(resource); + m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::NO_METADATA); } } else { - QString choppedId = resource->internalId().chopped(9); - if (m_result->resources.contains(choppedId)) { - m_result->resources[resource->internalId()].reset(resource); + QString chopped_id = resource->internal_id().chopped(9); + if (m_result->resources.contains(chopped_id)) { + m_result->resources[resource->internal_id()].reset(resource); - auto metadata = m_result->resources[choppedId]->metadata(); + auto metadata = m_result->resources[chopped_id]->metadata(); if (metadata) { resource->setMetadata(*metadata); - m_result->resources[resource->internalId()]->setStatus(ResourceStatus::Installed); - m_result->resources.remove(choppedId); + m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::INSTALLED); + m_result->resources.remove(chopped_id); } } else { - m_result->resources[resource->internalId()].reset(resource); - m_result->resources[resource->internalId()]->setStatus(ResourceStatus::NoMetadata); + m_result->resources[resource->internal_id()].reset(resource); + m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::NO_METADATA); } } } @@ -118,41 +116,38 @@ void ResourceFolderLoadTask::executeTask() QMutableMapIterator iter(m_result->resources); while (iter.hasNext()) { auto resource = iter.next().value(); - if (resource->status() == ResourceStatus::NotInstalled) { + if (resource->status() == ResourceStatus::NOT_INSTALLED) { resource->destroy(m_index_dir, false, false); iter.remove(); } } } - for (const auto& mod : m_result->resources) { + for (auto mod : m_result->resources) mod->moveToThread(m_thread_to_spawn_into); - } - if (m_aborted) { + if (m_aborted) emit finished(); - } else { + else emitSucceeded(); - } } void ResourceFolderLoadTask::getFromMetadata() { m_index_dir.refresh(); - for (const auto& entry : m_index_dir.entryList(QDir::Files)) { + for (auto entry : m_index_dir.entryList(QDir::Files)) { if (!entry.endsWith(".pw.toml")) { continue; } auto metadata = Metadata::get(m_index_dir, entry); - if (!metadata.isValid()) { + if (!metadata.isValid()) continue; - } auto* resource = m_create_func(QFileInfo(m_resource_dir.filePath(metadata.filename))); resource->setMetadata(metadata); - resource->setStatus(ResourceStatus::NotInstalled); - m_result->resources[resource->internalId()].reset(resource); + resource->setStatus(ResourceStatus::NOT_INSTALLED); + m_result->resources[resource->internal_id()].reset(resource); } } diff --git a/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.h b/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.h index 6489176b7..7c872c13d 100644 --- a/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.h @@ -41,7 +41,7 @@ #include #include #include -#include "minecraft/mod/Resource.h" +#include "minecraft/mod/Mod.h" #include "tasks/Task.h" class ResourceFolderLoadTask : public Task { @@ -54,11 +54,11 @@ class ResourceFolderLoadTask : public Task { ResultPtr result() const { return m_result; } public: - ResourceFolderLoadTask(const QDir& resourceDir, - const QDir& indexDir, - bool isIndexed, - bool cleanOrphan, - std::function createFunction); + ResourceFolderLoadTask(const QDir& resource_dir, + const QDir& index_dir, + bool is_indexed, + bool clean_orphan, + std::function create_function); bool canAbort() const override { return true; } bool abort() override @@ -76,7 +76,7 @@ class ResourceFolderLoadTask : public Task { QDir m_resource_dir, m_index_dir; bool m_is_indexed; bool m_clean_orphan; - std::function m_create_func; + std::function m_create_func; ResultPtr m_result; std::atomic m_aborted = false; diff --git a/launcher/minecraft/skins/CapeChange.cpp b/launcher/minecraft/skins/CapeChange.cpp index c955a1622..abbaa0b67 100644 --- a/launcher/minecraft/skins/CapeChange.cpp +++ b/launcher/minecraft/skins/CapeChange.cpp @@ -36,8 +36,9 @@ #include "CapeChange.h" -#include #include + +#include "net/ByteArraySink.h" #include "net/RawHeaderProxy.h" CapeChange::CapeChange(QString cape) : NetRequest(), m_capeId(cape) @@ -61,8 +62,8 @@ CapeChange::Ptr CapeChange::make(QString token, QString capeId) auto up = makeShared(capeId); up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/capes/active"); up->setObjectName(QString("BYTES:") + up->m_url.toString()); - up->m_sink.reset(new Net::DummySink()); - up->addHeaderProxy(std::make_unique(QList{ + up->m_sink.reset(new Net::ByteArraySink(std::make_shared())); + up->addHeaderProxy(new Net::RawHeaderProxy(QList{ { "Authorization", QString("Bearer %1").arg(token).toLocal8Bit() }, })); return up; diff --git a/launcher/minecraft/skins/SkinDelete.cpp b/launcher/minecraft/skins/SkinDelete.cpp index 9c98e3faa..94aca62ca 100644 --- a/launcher/minecraft/skins/SkinDelete.cpp +++ b/launcher/minecraft/skins/SkinDelete.cpp @@ -36,7 +36,7 @@ #include "SkinDelete.h" -#include +#include "net/ByteArraySink.h" #include "net/RawHeaderProxy.h" SkinDelete::SkinDelete() : NetRequest() @@ -54,8 +54,8 @@ SkinDelete::Ptr SkinDelete::make(QString token) { auto up = makeShared(); up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/skins/active"); - up->m_sink.reset(new Net::DummySink()); - up->addHeaderProxy(std::make_unique(QList{ + up->m_sink.reset(new Net::ByteArraySink(std::make_shared())); + up->addHeaderProxy(new Net::RawHeaderProxy(QList{ { "Authorization", QString("Bearer %1").arg(token).toLocal8Bit() }, })); return up; diff --git a/launcher/minecraft/skins/SkinList.cpp b/launcher/minecraft/skins/SkinList.cpp index c960b9493..b47f17db5 100644 --- a/launcher/minecraft/skins/SkinList.cpp +++ b/launcher/minecraft/skins/SkinList.cpp @@ -121,7 +121,7 @@ bool SkinList::update() auto folderContents = m_dir.entryInfoList(); // if there are any untracked files... for (QFileInfo entry : folderContents) { - if (!entry.isFile() || entry.suffix() != "png") + if (!entry.isFile() && entry.suffix() != "png") continue; SkinModel w(entry.absoluteFilePath()); diff --git a/launcher/minecraft/skins/SkinModel.cpp b/launcher/minecraft/skins/SkinModel.cpp index e2c41f17b..ca2545e05 100644 --- a/launcher/minecraft/skins/SkinModel.cpp +++ b/launcher/minecraft/skins/SkinModel.cpp @@ -58,7 +58,7 @@ static QImage improveSkin(QImage skin) // It seems some older skins may use this format, which can't be drawn onto // https://github.com/PrismLauncher/PrismLauncher/issues/4032 // https://doc.qt.io/qt-6/qpainter.html#begin - if (skin.format() <= QImage::Format_Indexed8 || !skin.hasAlphaChannel()) { + if (skin.format() == QImage::Format_Indexed8) { skin = skin.convertToFormat(QImage::Format_ARGB32); } diff --git a/launcher/minecraft/skins/SkinUpload.cpp b/launcher/minecraft/skins/SkinUpload.cpp index 8399f1f7d..ccc29d281 100644 --- a/launcher/minecraft/skins/SkinUpload.cpp +++ b/launcher/minecraft/skins/SkinUpload.cpp @@ -39,7 +39,7 @@ #include #include "FileSystem.h" -#include "net/DummySink.h" +#include "net/ByteArraySink.h" #include "net/RawHeaderProxy.h" SkinUpload::SkinUpload(QString path, QString variant) : NetRequest(), m_path(path), m_variant(variant) @@ -72,8 +72,8 @@ SkinUpload::Ptr SkinUpload::make(QString token, QString path, QString variant) auto up = makeShared(path, variant); up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/skins"); up->setObjectName(QString("BYTES:") + up->m_url.toString()); - up->m_sink.reset(new Net::DummySink()); - up->addHeaderProxy(std::make_unique(QList{ + up->m_sink.reset(new Net::ByteArraySink(std::make_shared())); + up->addHeaderProxy(new Net::RawHeaderProxy(QList{ { "Authorization", QString("Bearer %1").arg(token).toLocal8Bit() }, })); return up; diff --git a/launcher/minecraft/update/AssetUpdateTask.cpp b/launcher/minecraft/update/AssetUpdateTask.cpp index 22eea47b2..bfce11151 100644 --- a/launcher/minecraft/update/AssetUpdateTask.cpp +++ b/launcher/minecraft/update/AssetUpdateTask.cpp @@ -39,7 +39,7 @@ void AssetUpdateTask::executeTask() connect(downloadJob.get(), &NetJob::succeeded, this, &AssetUpdateTask::assetIndexFinished); connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetIndexFailed); - connect(downloadJob.get(), &NetJob::aborted, this, &AssetUpdateTask::emitAborted); + connect(downloadJob.get(), &NetJob::aborted, this, [this] { emitFailed(tr("Aborted")); }); connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress); connect(downloadJob.get(), &NetJob::stepProgress, this, &AssetUpdateTask::propagateStepProgress); @@ -73,7 +73,7 @@ void AssetUpdateTask::assetIndexFinished() auto job = index.getDownloadJob(); if (job) { - QString resourceURL = resourceUrl(); + QString resourceURL = APPLICATION->settings()->get("ResourceURL").toString(); QString source = tr("Mojang"); if (resourceURL != BuildConfig.DEFAULT_RESOURCE_BASE) { source = QUrl(resourceURL).host(); @@ -82,7 +82,7 @@ void AssetUpdateTask::assetIndexFinished() downloadJob = job; connect(downloadJob.get(), &NetJob::succeeded, this, &AssetUpdateTask::emitSucceeded); connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetsFailed); - connect(downloadJob.get(), &NetJob::aborted, this, &AssetUpdateTask::emitAborted); + connect(downloadJob.get(), &NetJob::aborted, this, [this] { emitFailed(tr("Aborted")); }); connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress); connect(downloadJob.get(), &NetJob::stepProgress, this, &AssetUpdateTask::propagateStepProgress); downloadJob->start(); @@ -111,12 +111,3 @@ bool AssetUpdateTask::abort() } return true; } - -QString AssetUpdateTask::resourceUrl() -{ - if (const QString urlOverride = APPLICATION->settings()->get("ResourceURLOverride").toString(); !urlOverride.isEmpty()) { - return urlOverride; - } - - return BuildConfig.DEFAULT_RESOURCE_BASE; -} diff --git a/launcher/minecraft/update/AssetUpdateTask.h b/launcher/minecraft/update/AssetUpdateTask.h index 56aecc293..88fac0ac5 100644 --- a/launcher/minecraft/update/AssetUpdateTask.h +++ b/launcher/minecraft/update/AssetUpdateTask.h @@ -13,9 +13,6 @@ class AssetUpdateTask : public Task { bool canAbort() const override; - public: - static QString resourceUrl(); - private slots: void assetIndexFinished(); void assetIndexFailed(QString reason); diff --git a/launcher/minecraft/update/LegacyFMLLibrariesTask.cpp b/launcher/minecraft/update/FMLLibrariesTask.cpp similarity index 73% rename from launcher/minecraft/update/LegacyFMLLibrariesTask.cpp rename to launcher/minecraft/update/FMLLibrariesTask.cpp index a3bcc145f..ce0c9a723 100644 --- a/launcher/minecraft/update/LegacyFMLLibrariesTask.cpp +++ b/launcher/minecraft/update/FMLLibrariesTask.cpp @@ -1,4 +1,4 @@ -#include "LegacyFMLLibrariesTask.h" +#include "FMLLibrariesTask.h" #include "FileSystem.h" #include "minecraft/MinecraftInstance.h" @@ -10,11 +10,11 @@ #include "net/ApiDownload.h" -LegacyFMLLibrariesTask::LegacyFMLLibrariesTask(MinecraftInstance* inst) +FMLLibrariesTask::FMLLibrariesTask(MinecraftInstance* inst) { m_inst = inst; } -void LegacyFMLLibrariesTask::executeTask() +void FMLLibrariesTask::executeTask() { // Get the mod list MinecraftInstance* inst = (MinecraftInstance*)m_inst; @@ -61,28 +61,27 @@ void LegacyFMLLibrariesTask::executeTask() NetJob::Ptr dljob{ new NetJob("FML libraries", APPLICATION->network()) }; auto metacache = APPLICATION->metacache(); Net::Download::Options options = Net::Download::Option::MakeEternal; - const QString base = baseUrl(); for (auto& lib : fmlLibsToProcess) { auto entry = metacache->resolveEntry("fmllibs", lib.filename); - QString urlString = base + lib.filename; + QString urlString = BuildConfig.FMLLIBS_BASE_URL + lib.filename; dljob->addNetAction(Net::ApiDownload::makeCached(QUrl(urlString), entry, options)); } - connect(dljob.get(), &NetJob::succeeded, this, &LegacyFMLLibrariesTask::fmllibsFinished); - connect(dljob.get(), &NetJob::failed, this, &LegacyFMLLibrariesTask::fmllibsFailed); - connect(dljob.get(), &NetJob::aborted, this, &LegacyFMLLibrariesTask::emitAborted); - connect(dljob.get(), &NetJob::progress, this, &LegacyFMLLibrariesTask::progress); - connect(dljob.get(), &NetJob::stepProgress, this, &LegacyFMLLibrariesTask::propagateStepProgress); + connect(dljob.get(), &NetJob::succeeded, this, &FMLLibrariesTask::fmllibsFinished); + connect(dljob.get(), &NetJob::failed, this, &FMLLibrariesTask::fmllibsFailed); + connect(dljob.get(), &NetJob::aborted, this, [this] { emitFailed(tr("Aborted")); }); + connect(dljob.get(), &NetJob::progress, this, &FMLLibrariesTask::progress); + connect(dljob.get(), &NetJob::stepProgress, this, &FMLLibrariesTask::propagateStepProgress); downloadJob.reset(dljob); downloadJob->start(); } -bool LegacyFMLLibrariesTask::canAbort() const +bool FMLLibrariesTask::canAbort() const { return true; } -void LegacyFMLLibrariesTask::fmllibsFinished() +void FMLLibrariesTask::fmllibsFinished() { downloadJob.reset(); if (!fmlLibsToProcess.isEmpty()) { @@ -108,28 +107,19 @@ void LegacyFMLLibrariesTask::fmllibsFinished() } emitSucceeded(); } -void LegacyFMLLibrariesTask::fmllibsFailed(QString reason) +void FMLLibrariesTask::fmllibsFailed(QString reason) { QStringList failed = downloadJob->getFailedFiles(); QString failed_all = failed.join("\n"); emitFailed(tr("Failed to download the following files:\n%1\n\nReason:%2\nPlease try again.").arg(failed_all, reason)); } -bool LegacyFMLLibrariesTask::abort() +bool FMLLibrariesTask::abort() { if (downloadJob) { return downloadJob->abort(); } else { - qWarning() << "Prematurely aborted LegacyFMLLibrariesTask"; + qWarning() << "Prematurely aborted FMLLibrariesTask"; } return true; } - -QString LegacyFMLLibrariesTask::baseUrl() -{ - if (const QString urlOverride = APPLICATION->settings()->get("LegacyFMLLibsURLOverride").toString(); !urlOverride.isEmpty()) { - return urlOverride; - } - - return BuildConfig.LEGACY_FMLLIBS_BASE_URL; -} diff --git a/launcher/minecraft/update/LegacyFMLLibrariesTask.h b/launcher/minecraft/update/FMLLibrariesTask.h similarity index 71% rename from launcher/minecraft/update/LegacyFMLLibrariesTask.h rename to launcher/minecraft/update/FMLLibrariesTask.h index 2591f7c9f..4fe2648e8 100644 --- a/launcher/minecraft/update/LegacyFMLLibrariesTask.h +++ b/launcher/minecraft/update/FMLLibrariesTask.h @@ -5,11 +5,11 @@ class MinecraftInstance; -class LegacyFMLLibrariesTask : public Task { +class FMLLibrariesTask : public Task { Q_OBJECT public: - LegacyFMLLibrariesTask(MinecraftInstance* inst); - virtual ~LegacyFMLLibrariesTask() = default; + FMLLibrariesTask(MinecraftInstance* inst); + virtual ~FMLLibrariesTask() = default; void executeTask() override; @@ -22,9 +22,6 @@ class LegacyFMLLibrariesTask : public Task { public slots: bool abort() override; - private: - static QString baseUrl(); - private: MinecraftInstance* m_inst; NetJob::Ptr downloadJob; diff --git a/launcher/minecraft/update/LibrariesTask.cpp b/launcher/minecraft/update/LibrariesTask.cpp index f725af18d..e64691d51 100644 --- a/launcher/minecraft/update/LibrariesTask.cpp +++ b/launcher/minecraft/update/LibrariesTask.cpp @@ -31,7 +31,7 @@ void LibrariesTask::executeTask() emitFailed(tr("Null jar is specified in the metadata, aborting.")); return false; } - auto dls = lib->getDownloads(inst->runtimeContext(), metacache, errors, localPath); + auto dls = lib->getDownloads(inst->runtimeContext(), metacache.get(), errors, localPath); for (auto dl : dls) { downloadJob->addNetAction(dl); } @@ -44,8 +44,8 @@ void LibrariesTask::executeTask() libArtifactPool.append(profile->getLibraries()); libArtifactPool.append(profile->getNativeLibraries()); libArtifactPool.append(profile->getMavenFiles()); - for (const auto& agent : profile->getAgents()) { - libArtifactPool.append(agent.library); + for (auto agent : profile->getAgents()) { + libArtifactPool.append(agent->library()); } libArtifactPool.append(profile->getMainJar()); processArtifactPool(libArtifactPool, failedLocalLibraries, inst->getLocalLibraryPath()); @@ -64,7 +64,7 @@ void LibrariesTask::executeTask() connect(downloadJob.get(), &NetJob::succeeded, this, &LibrariesTask::emitSucceeded); connect(downloadJob.get(), &NetJob::failed, this, &LibrariesTask::jarlibFailed); - connect(downloadJob.get(), &NetJob::aborted, this, &LibrariesTask::emitAborted); + connect(downloadJob.get(), &NetJob::aborted, this, [this] { emitFailed(tr("Aborted")); }); connect(downloadJob.get(), &NetJob::progress, this, &LibrariesTask::progress); connect(downloadJob.get(), &NetJob::stepProgress, this, &LibrariesTask::propagateStepProgress); diff --git a/launcher/modplatform/CheckUpdateTask.h b/launcher/modplatform/CheckUpdateTask.h index ad68fd963..c5beff26c 100644 --- a/launcher/modplatform/CheckUpdateTask.h +++ b/launcher/modplatform/CheckUpdateTask.h @@ -12,18 +12,22 @@ class CheckUpdateTask : public Task { public: CheckUpdateTask(QList& resources, - std::vector& mcVersions, + std::list& mcVersions, QList loadersList, - ResourceFolderModel* resourceModel) - : m_resources(resources), m_gameVersions(mcVersions), m_loadersList(std::move(loadersList)), m_resourceModel(resourceModel) + std::shared_ptr resourceModel) + : Task() + , m_resources(resources) + , m_gameVersions(mcVersions) + , m_loadersList(std::move(loadersList)) + , m_resourceModel(std::move(resourceModel)) {} struct Update { QString name; - QString oldHash; - QString oldVersion; - QString newVersion; - std::optional newVersionType; + QString old_hash; + QString old_version; + QString new_version; + std::optional new_version_type; QString changelog; ModPlatform::ResourceProvider provider; shared_qobject_ptr download; @@ -31,19 +35,19 @@ class CheckUpdateTask : public Task { public: Update(QString name, - QString oldH, - QString oldV, - QString newV, - std::optional newVType, + QString old_h, + QString old_v, + QString new_v, + std::optional new_v_type, QString changelog, ModPlatform::ResourceProvider p, shared_qobject_ptr t, bool enabled = true) : name(std::move(name)) - , oldHash(std::move(oldH)) - , oldVersion(std::move(oldV)) - , newVersion(std::move(newV)) - , newVersionType(newVType) + , old_hash(std::move(old_h)) + , old_version(std::move(old_v)) + , new_version(std::move(new_v)) + , new_version_type(std::move(new_v_type)) , changelog(std::move(changelog)) , provider(p) , download(std::move(t)) @@ -54,17 +58,20 @@ class CheckUpdateTask : public Task { auto getUpdates() -> std::vector&& { return std::move(m_updates); } auto getDependencies() -> QList>&& { return std::move(m_deps); } + public slots: + bool abort() override = 0; + protected slots: void executeTask() override = 0; signals: - void checkFailed(Resource* failed, QString reason, QUrl recoverUrl = {}); + void checkFailed(Resource* failed, QString reason, QUrl recover_url = {}); protected: QList& m_resources; - std::vector& m_gameVersions; + std::list& m_gameVersions; QList m_loadersList; - ResourceFolderModel* m_resourceModel; + std::shared_ptr m_resourceModel; std::vector m_updates; QList> m_deps; diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp index 2c7e485e5..3ade13481 100644 --- a/launcher/modplatform/EnsureMetadataTask.cpp +++ b/launcher/modplatform/EnsureMetadataTask.cpp @@ -99,7 +99,7 @@ void EnsureMetadataTask::executeTask() } // They already have the right metadata :o - if (resource->status() != ResourceStatus::NoMetadata && resource->metadata() && resource->metadata()->provider == m_provider) { + if (resource->status() != ResourceStatus::NO_METADATA && resource->metadata() && resource->metadata()->provider == m_provider) { qDebug() << "Resource" << resource->name() << "already has metadata!"; emitReady(resource); continue; @@ -215,7 +215,8 @@ Task::Ptr EnsureMetadataTask::modrinthVersionsTask() { auto hash_type = ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first(); - auto [ver_task, response] = modrinth_api.currentVersions(m_resources.keys(), hash_type); + auto response = std::make_shared(); + auto ver_task = modrinth_api.currentVersions(m_resources.keys(), hash_type, response); // Prevents unfortunate timings when aborting the task if (!ver_task) @@ -263,18 +264,18 @@ Task::Ptr EnsureMetadataTask::modrinthVersionsTask() Task::Ptr EnsureMetadataTask::modrinthProjectsTask() { QHash addonIds; - for (const auto& data : m_tempVersions) + for (auto const& data : m_tempVersions) addonIds.insert(data.addonId.toString(), data.hash); + auto response = std::make_shared(); Task::Ptr proj_task; - QByteArray* response; if (addonIds.isEmpty()) { qWarning() << "No addonId found!"; } else if (addonIds.size() == 1) { - std::tie(proj_task, response) = modrinth_api.getProject(*addonIds.keyBegin()); + proj_task = modrinth_api.getProject(*addonIds.keyBegin(), response); } else { - std::tie(proj_task, response) = modrinth_api.getProjects(addonIds.keys()); + proj_task = modrinth_api.getProjects(addonIds.keys(), response); } // Prevents unfortunate timings when aborting the task @@ -340,12 +341,14 @@ Task::Ptr EnsureMetadataTask::modrinthProjectsTask() // Flame Task::Ptr EnsureMetadataTask::flameVersionsTask() { + auto response = std::make_shared(); + QList fingerprints; for (auto& murmur : m_resources.keys()) { fingerprints.push_back(murmur.toUInt()); } - auto [ver_task, response] = flame_api.matchFingerprints(fingerprints); + auto ver_task = flame_api.matchFingerprints(fingerprints, response); connect(ver_task.get(), &Task::succeeded, this, [this, response] { QJsonParseError parse_error{}; @@ -404,7 +407,7 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask() Task::Ptr EnsureMetadataTask::flameProjectsTask() { QHash addonIds; - for (const auto& hash : m_resources.keys()) { + for (auto const& hash : m_resources.keys()) { if (m_tempVersions.contains(hash)) { auto data = m_tempVersions.find(hash).value(); @@ -414,15 +417,15 @@ Task::Ptr EnsureMetadataTask::flameProjectsTask() } } + auto response = std::make_shared(); Task::Ptr proj_task; - QByteArray* response; if (addonIds.isEmpty()) { qWarning() << "No addonId found!"; } else if (addonIds.size() == 1) { - std::tie(proj_task, response) = flame_api.getProject(*addonIds.keyBegin()); + proj_task = flame_api.getProject(*addonIds.keyBegin(), response); } else { - std::tie(proj_task, response) = flame_api.getProjects(addonIds.keys()); + proj_task = flame_api.getProjects(addonIds.keys(), response); } // Prevents unfortunate timings when aborting the task diff --git a/launcher/modplatform/ModIndex.cpp b/launcher/modplatform/ModIndex.cpp index 635b2ae92..7b20d37ec 100644 --- a/launcher/modplatform/ModIndex.cpp +++ b/launcher/modplatform/ModIndex.cpp @@ -25,11 +25,6 @@ namespace ModPlatform { -ModLoaderType operator|(ModLoaderType lhs, ModLoaderType rhs) -{ - return static_cast(static_cast(lhs) | static_cast(rhs)); -} - static const QMap s_indexed_version_type_names = { { "release", IndexedVersionType::Release }, { "beta", IndexedVersionType::Beta }, { "alpha", IndexedVersionType::Alpha } }; @@ -183,40 +178,4 @@ Side SideUtils::fromString(QString side) return Side::UniversalSide; return Side::UniversalSide; } - -QString DependencyTypeUtils::toString(DependencyType type) -{ - switch (type) { - case DependencyType::REQUIRED: - return "REQUIRED"; - case DependencyType::OPTIONAL: - return "OPTIONAL"; - case DependencyType::INCOMPATIBLE: - return "INCOMPATIBLE"; - case DependencyType::EMBEDDED: - return "EMBEDDED"; - case DependencyType::TOOL: - return "TOOL"; - case DependencyType::INCLUDE: - return "INCLUDE"; - case DependencyType::UNKNOWN: - return "UNKNOWN"; - } - return "UNKNOWN"; -} - -DependencyType DependencyTypeUtils::fromString(const QString& str) -{ - static const QHash map = { - { "REQUIRED", DependencyType::REQUIRED }, - { "OPTIONAL", DependencyType::OPTIONAL }, - { "INCOMPATIBLE", DependencyType::INCOMPATIBLE }, - { "EMBEDDED", DependencyType::EMBEDDED }, - { "TOOL", DependencyType::TOOL }, - { "INCLUDE", DependencyType::INCLUDE }, - { "UNKNOWN", DependencyType::UNKNOWN }, - }; - - return map.value(str.toUpper(), DependencyType::UNKNOWN); -} } // namespace ModPlatform diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index 8984e575e..5cde2274f 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -24,52 +24,40 @@ #include #include #include -#include #include class QIODevice; namespace ModPlatform { -enum class ModLoaderType : std::uint16_t { - None = 0U, - NeoForge = 1U << 0U, - Forge = 1U << 1U, - Cauldron = 1U << 2U, - LiteLoader = 1U << 3U, - Fabric = 1U << 4U, - Quilt = 1U << 5U, - DataPack = 1U << 6U, - Babric = 1U << 7U, - BTA = 1U << 8U, - LegacyFabric = 1U << 9U, - Ornithe = 1U << 10U, - Rift = 1U << 11U +enum ModLoaderType { + NeoForge = 1 << 0, + Forge = 1 << 1, + Cauldron = 1 << 2, + LiteLoader = 1 << 3, + Fabric = 1 << 4, + Quilt = 1 << 5, + DataPack = 1 << 6, + Babric = 1 << 7, + BTA = 1 << 8, + LegacyFabric = 1 << 9, + Ornithe = 1 << 10, + Rift = 1 << 11 }; - -ModLoaderType operator|(ModLoaderType lhs, ModLoaderType rhs); - -using enum ModLoaderType; - Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType) QList modLoaderTypesToList(ModLoaderTypes flags); -enum class ResourceProvider : std::uint8_t { MODRINTH, FLAME }; +enum class ResourceProvider { MODRINTH, FLAME }; -enum class DependencyType : std::uint8_t { REQUIRED, OPTIONAL, INCOMPATIBLE, EMBEDDED, TOOL, INCLUDE, UNKNOWN }; +enum class DependencyType { REQUIRED, OPTIONAL, INCOMPATIBLE, EMBEDDED, TOOL, INCLUDE, UNKNOWN }; -enum class Side : std::uint8_t { NoSide = 0, ClientSide = 1U << 0U, ServerSide = 1U << 1U, UniversalSide = ClientSide | ServerSide }; +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 DependencyTypeUtils { -QString toString(DependencyType type); -DependencyType fromString(const QString& str); -} // namespace DependencyTypeUtils - namespace ProviderCapabilities { const char* name(ResourceProvider); QString readableName(ResourceProvider); @@ -88,11 +76,11 @@ struct DonationData { }; struct IndexedVersionType { - enum class Enum : std::uint8_t { Unknown = 0, Release = 1, Beta = 2, Alpha = 3 }; + enum class Enum { Unknown, Release = 1, Beta, Alpha }; using enum Enum; - constexpr IndexedVersionType(Enum e = Unknown) : m_type(e) {} // NOLINT(hicpp-explicit-conversions) + constexpr IndexedVersionType(Enum e = Unknown) : m_type(e) {} static IndexedVersionType fromString(const QString& type); - bool isValid() const { return m_type != Unknown; } + inline bool isValid() const { return m_type != Unknown; } std::strong_ordering operator<=>(const IndexedVersionType& other) const = default; std::strong_ordering operator<=>(const IndexedVersionType::Enum& other) const { return m_type <=> other; } QString toString() const; @@ -113,19 +101,19 @@ struct IndexedVersion { QVariant addonId; QVariant fileId; QString version; - QString version_number; + QString version_number = {}; IndexedVersionType version_type; QStringList mcVersion; QString downloadUrl; QString date; QString fileName; - ModLoaderTypes loaders; + ModLoaderTypes loaders = {}; QString hash_type; QString hash; bool is_preferred = true; QString changelog; QList dependencies; - Side side = Side::NoSide; // this is for flame API + Side side; // this is for flame API // For internal use, not provided by APIs bool is_currently_selected = false; @@ -135,7 +123,7 @@ struct IndexedVersion { auto release_type = version_type.isValid() ? QString(" [%1]").arg(version_type.toString()) : ""; auto versionStr = !version.contains(version_number) ? version_number : ""; QString gameVersion = ""; - for (const auto& v : mcVersion) { + for (auto v : mcVersion) { if (version.contains(v)) { gameVersion = ""; break; @@ -173,7 +161,7 @@ struct IndexedPack { QString logoName; QString logoUrl; QString websiteUrl; - Side side = Side::NoSide; + Side side; bool versionsLoaded = false; QList versions; @@ -185,19 +173,17 @@ struct IndexedPack { // For internal use, not provided by APIs bool isVersionSelected(int index) const { - if (!versionsLoaded) { + if (!versionsLoaded) return false; - } return versions.at(index).is_currently_selected; } bool isAnyVersionSelected() const { - if (!versionsLoaded) { + if (!versionsLoaded) return false; - } - return std::any_of(versions.constBegin(), versions.constEnd(), [](const auto& v) { return v.is_currently_selected; }); + return std::any_of(versions.constBegin(), versions.constEnd(), [](auto const& v) { return v.is_currently_selected; }); } }; @@ -210,13 +196,11 @@ struct OverrideDep { inline auto getOverrideDeps() -> QList { - return { - { .quilt = "634179", .fabric = "306612", .slug = "API", .provider = ModPlatform::ResourceProvider::FLAME }, - { .quilt = "720410", .fabric = "308769", .slug = "KotlinLibraries", .provider = ModPlatform::ResourceProvider::FLAME }, + return { { "634179", "306612", "API", ModPlatform::ResourceProvider::FLAME }, + { "720410", "308769", "KotlinLibraries", ModPlatform::ResourceProvider::FLAME }, - { .quilt = "qvIfYCYJ", .fabric = "P7dR8mSH", .slug = "API", .provider = ModPlatform::ResourceProvider::MODRINTH }, - { .quilt = "lwVhp9o5", .fabric = "Ha28R6CL", .slug = "KotlinLibraries", .provider = ModPlatform::ResourceProvider::MODRINTH } - }; + { "qvIfYCYJ", "P7dR8mSH", "API", ModPlatform::ResourceProvider::MODRINTH }, + { "lwVhp9o5", "Ha28R6CL", "KotlinLibraries", ModPlatform::ResourceProvider::MODRINTH } }; } QString getMetaURL(ResourceProvider provider, QVariant projectID); @@ -226,8 +210,8 @@ auto getModLoaderFromString(QString type) -> ModLoaderType; constexpr bool hasSingleModLoaderSelected(ModLoaderTypes l) noexcept { - auto x = static_cast(l); - return (x != 0U) && ((x & (x - 1U)) == 0U); + auto x = static_cast(l); + return x && !(x & (x - 1)); } struct Category { diff --git a/launcher/modplatform/ResourceAPI.cpp b/launcher/modplatform/ResourceAPI.cpp index bd683ed21..d3a4d8e31 100644 --- a/launcher/modplatform/ResourceAPI.cpp +++ b/launcher/modplatform/ResourceAPI.cpp @@ -18,10 +18,10 @@ Task::Ptr ResourceAPI::searchProjects(SearchArgs&& args, Callback(); auto netJob = makeShared(QString("%1::Search").arg(debugName()), APPLICATION->network()); - auto [action, response] = Net::ApiDownload::makeByteArray(QUrl(search_url)); - netJob->addNetAction(action); + netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(search_url), response)); QObject::connect(netJob.get(), &NetJob::succeeded, [this, response, callbacks] { QJsonParseError parse_error{}; @@ -84,9 +84,9 @@ Task::Ptr ResourceAPI::getProjectVersions(VersionSearchArgs&& args, Callback(QString("%1::Versions").arg(args.pack->name), APPLICATION->network()); + auto response = std::make_shared(); - auto [action, response] = Net::ApiDownload::makeByteArray(versions_url); - netJob->addNetAction(action); + netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response)); QObject::connect(netJob.get(), &NetJob::succeeded, [this, response, callbacks, args] { QJsonParseError parse_error{}; @@ -106,13 +106,11 @@ Task::Ptr ResourceAPI::getProjectVersions(VersionSearchArgs&& args, CallbackaddonId; - } - if (file.fileId.isValid() && !file.downloadUrl.isEmpty()) { // Heuristic to check if the returned value is valid + 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 { @@ -148,9 +146,10 @@ Task::Ptr ResourceAPI::getProjectVersions(VersionSearchArgs&& args, Callback&& callbacks, bool askRetry) const +Task::Ptr ResourceAPI::getProjectInfo(ProjectInfoArgs&& args, Callback&& callbacks) const { - auto [job, response] = getProject(args.pack->addonId.toString(), askRetry); + 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; @@ -205,8 +204,9 @@ Task::Ptr ResourceAPI::getDependencyVersion(DependencySearchArgs&& args, Callbac auto versions_url = versions_url_optional.value(); auto netJob = makeShared(QString("%1::Dependency").arg(args.dependency.addonId.toString()), APPLICATION->network()); - auto [action, response] = Net::ApiDownload::makeByteArray(versions_url); - netJob->addNetAction(action); + 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{}; @@ -262,7 +262,7 @@ Task::Ptr ResourceAPI::getDependencyVersion(DependencySearchArgs&& args, Callbac return netJob; } -QString ResourceAPI::getGameVersionsString(std::vector mcVersions) const +QString ResourceAPI::getGameVersionsString(std::list mcVersions) const { QString s; for (auto& ver : mcVersions) { @@ -284,19 +284,17 @@ QString ResourceAPI::mapMCVersionToModrinth(Version v) const return verStr; } -std::pair ResourceAPI::getProject(QString addonId, bool askRetry) const +Task::Ptr ResourceAPI::getProject(QString addonId, std::shared_ptr response) const { auto project_url_optional = getInfoURL(addonId); if (!project_url_optional.has_value()) - return { nullptr, nullptr }; + return nullptr; auto project_url = project_url_optional.value(); auto netJob = makeShared(QString("%1::GetProject").arg(addonId), APPLICATION->network()); - netJob->setAskRetry(askRetry); - auto [action, response] = Net::ApiDownload::makeByteArray(QUrl(project_url)); - netJob->addNetAction(action); + netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(project_url), response)); - return { netJob, response }; + return netJob; } diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h index 51b6d4b50..ca77dc8a7 100644 --- a/launcher/modplatform/ResourceAPI.h +++ b/launcher/modplatform/ResourceAPI.h @@ -44,7 +44,6 @@ #include #include -#include #include "../Version.h" @@ -71,7 +70,7 @@ class ResourceAPI { template struct Callback { std::function on_succeed; - std::function on_fail; + std::function on_fail; std::function on_abort; }; @@ -82,19 +81,18 @@ class ResourceAPI { std::optional search; std::optional sorting; std::optional loaders; - std::optional> versions; + std::optional> versions; std::optional side; std::optional categoryIds; - bool openSource{}; + bool openSource; }; struct VersionSearchArgs { ModPlatform::IndexedPack::Ptr pack; - std::optional> mcVersions; + std::optional> mcVersions; std::optional loaders; ModPlatform::ResourceType resourceType; - bool includeChangelog{}; }; struct ProjectInfoArgs { @@ -105,7 +103,6 @@ class ResourceAPI { ModPlatform::Dependency dependency; Version mcVersion; ModPlatform::ModLoaderTypes loader; - bool includeChangelog{}; }; public: @@ -115,10 +112,10 @@ class ResourceAPI { public slots: virtual Task::Ptr searchProjects(SearchArgs&&, Callback>&&) const; - virtual std::pair getProject(QString addonId, bool askRetry = true) const; - virtual std::pair getProjects(QStringList addonIds) const = 0; + virtual Task::Ptr getProject(QString addonId, std::shared_ptr response) const; + virtual Task::Ptr getProjects(QStringList addonIds, std::shared_ptr response) const = 0; - virtual Task::Ptr getProjectInfo(ProjectInfoArgs&&, Callback&&, bool askRetry = true) const; + virtual Task::Ptr getProjectInfo(ProjectInfoArgs&&, Callback&&) const; Task::Ptr getProjectVersions(VersionSearchArgs&& args, Callback>&& callbacks) const; virtual Task::Ptr getDependencyVersion(DependencySearchArgs&&, Callback&&) const; @@ -127,13 +124,13 @@ class ResourceAPI { QString mapMCVersionToModrinth(Version v) const; - QString getGameVersionsString(std::vector mcVersions) const; + QString getGameVersionsString(std::list mcVersions) const; public: - virtual auto getSearchURL(const SearchArgs& args) const -> std::optional = 0; - virtual auto getInfoURL(const QString& id) const -> std::optional = 0; - virtual auto getVersionsURL(const VersionSearchArgs& args) const -> std::optional = 0; - virtual auto getDependencyURL(const DependencySearchArgs& args) const -> std::optional = 0; + 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. * diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index f3f66997b..851473365 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -38,7 +38,6 @@ #include #include -#include #include "FileSystem.h" #include "Json.h" @@ -60,28 +59,18 @@ #include "BuildConfig.h" #include "ui/dialogs/BlockedModsDialog.h" -namespace { -bool isPathTraversal(const QString& basePath, const QString& entryName) -{ - auto safeName = FS::RemoveInvalidPathChars(entryName); - auto fullPath = FS::PathCombine(basePath, safeName); - auto baseUrl = QUrl::fromLocalFile(basePath); - return !baseUrl.isParentOf(QUrl::fromLocalFile(fullPath)); -} - -Meta::Version::Ptr getComponentVersion(const QString& uid, const QString& version) -{ - return APPLICATION->metadataIndex()->getLoadedVersion(uid, version); -} -} // namespace - namespace ATLauncher { +static Meta::Version::Ptr getComponentVersion(const QString& uid, const QString& version); + PackInstallTask::PackInstallTask(UserInteractionSupport* support, QString packName, QString version, InstallMode installMode) - : m_support(support), m_install_mode(installMode), m_pack_name(packName), m_version_name(std::move(version)) { + m_support = support; + m_pack_name = packName; static const QRegularExpression s_regex("[^A-Za-z0-9]"); m_pack_safe_name = packName.replace(s_regex, ""); + m_version_name = version; + m_install_mode = installMode; } bool PackInstallTask::abort() @@ -98,11 +87,9 @@ void PackInstallTask::executeTask() NetJob::Ptr netJob{ new NetJob("ATLauncher::VersionFetch", APPLICATION->network()) }; auto searchUrl = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json").arg(m_pack_safe_name).arg(m_version_name); + netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response)); - auto [action, response] = Net::ApiDownload::makeByteArray(QUrl(searchUrl)); - netJob->addNetAction(action); - - connect(netJob.get(), &NetJob::succeeded, this, [this, response] { onDownloadSucceeded(response); }); + connect(netJob.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded); connect(netJob.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed); connect(netJob.get(), &NetJob::aborted, this, &PackInstallTask::onDownloadAborted); @@ -110,19 +97,17 @@ void PackInstallTask::executeTask() jobPtr->start(); } -void PackInstallTask::onDownloadSucceeded(QByteArray* responsePtr) +void PackInstallTask::onDownloadSucceeded() { qDebug() << "PackInstallTask::onDownloadSucceeded:" << QThread::currentThreadId(); - - // NOTE(TheKodeToad): moving the response out to avoid it from being destroyed by jobPtr.reset() - QByteArray response = std::move(*responsePtr); jobPtr.reset(); - QJsonParseError parseError{}; - QJsonDocument doc = QJsonDocument::fromJson(response, &parseError); - if (parseError.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from ATLauncher at" << parseError.offset << "reason:" << parseError.errorString(); - qWarning() << response; + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from ATLauncher at" << parse_error.offset + << "reason:" << parse_error.errorString(); + qWarning() << *response.get(); return; } auto obj = doc.object(); @@ -138,7 +123,7 @@ void PackInstallTask::onDownloadSucceeded(QByteArray* responsePtr) // Derived from the installation mode QString message; - bool resetDirectory = false; + bool resetDirectory; switch (m_install_mode) { case InstallMode::Reinstall: @@ -158,9 +143,8 @@ void PackInstallTask::onDownloadSucceeded(QByteArray* responsePtr) } // Display message if one exists - if (!message.isEmpty()) { + if (!message.isEmpty()) m_support->displayMessage(message); - } auto ver = getComponentVersion("net.minecraft", m_version.minecraft); if (!ver) { @@ -184,7 +168,7 @@ void PackInstallTask::onDownloadFailed(QString reason) { qDebug() << "PackInstallTask::onDownloadFailed:" << QThread::currentThreadId(); jobPtr.reset(); - emitFailed(std::move(reason)); + emitFailed(reason); } void PackInstallTask::onDownloadAborted() @@ -213,30 +197,26 @@ void PackInstallTask::deleteExistingFiles() keeps.files.append(VersionKeep{ "root", "servers.dat" }); // Merge with version deletes and keeps - for (const auto& item : m_version.deletes.files) { + for (const auto& item : m_version.deletes.files) deletes.files.append(item); - } - for (const auto& item : m_version.deletes.folders) { + for (const auto& item : m_version.deletes.folders) deletes.folders.append(item); - } - for (const auto& item : m_version.keeps.files) { + for (const auto& item : m_version.keeps.files) keeps.files.append(item); - } - for (const auto& item : m_version.keeps.folders) { + for (const auto& item : m_version.keeps.folders) keeps.folders.append(item); - } auto getPathForBase = [this](const QString& base) { auto minecraftPath = FS::PathCombine(m_stagingPath, "minecraft"); if (base == "root") { return minecraftPath; - } - if (base == "config") { + } else if (base == "config") { return FS::PathCombine(minecraftPath, "config"); + } else { + qWarning() << "Unrecognised base path" << base; + return minecraftPath; } - qWarning() << "Unrecognised base path" << base; - return minecraftPath; }; auto convertToSystemPath = [](const QString& path) { @@ -246,22 +226,24 @@ void PackInstallTask::deleteExistingFiles() }; auto shouldKeep = [keeps, getPathForBase, convertToSystemPath](const QString& fullPath) { - if (std::ranges::any_of(keeps.files, [&fullPath, &getPathForBase, &convertToSystemPath](const auto& item) { - auto basePath = getPathForBase(item.base); - auto targetPath = convertToSystemPath(item.target); - auto path = FS::PathCombine(basePath, targetPath); - return fullPath == path; - })) { - return true; + for (const auto& item : keeps.files) { + auto basePath = getPathForBase(item.base); + auto targetPath = convertToSystemPath(item.target); + auto path = FS::PathCombine(basePath, targetPath); + + if (fullPath == path) { + return true; + } } - if (std::ranges::any_of(keeps.folders, [&fullPath, &getPathForBase, &convertToSystemPath](const auto& item) { - auto basePath = getPathForBase(item.base); - auto targetPath = convertToSystemPath(item.target); - auto path = FS::PathCombine(basePath, targetPath); - return fullPath.startsWith(path); - })) { - return true; + for (const auto& item : keeps.folders) { + auto basePath = getPathForBase(item.base); + auto targetPath = convertToSystemPath(item.target); + auto path = FS::PathCombine(basePath, targetPath); + + if (fullPath.startsWith(path)) { + return true; + } } return false; @@ -275,9 +257,8 @@ void PackInstallTask::deleteExistingFiles() auto targetPath = convertToSystemPath(item.target); auto fullPath = FS::PathCombine(basePath, targetPath); - if (shouldKeep(fullPath)) { + if (shouldKeep(fullPath)) continue; - } filesToDelete.insert(fullPath); } @@ -291,9 +272,8 @@ void PackInstallTask::deleteExistingFiles() while (it.hasNext()) { auto path = it.next(); - if (shouldKeep(path)) { + if (shouldKeep(path)) continue; - } filesToDelete.insert(path); } @@ -305,7 +285,7 @@ void PackInstallTask::deleteExistingFiles() } } -QString PackInstallTask::getDirForModType(ModType type, const QString& raw) +QString PackInstallTask::getDirForModType(ModType type, QString raw) { switch (type) { // Mod types that can either be ignored at this stage, or ignored @@ -353,7 +333,7 @@ QString PackInstallTask::getDirForModType(ModType type, const QString& raw) return Q_NULLPTR; } -QString PackInstallTask::getVersionForLoader(const QString& uid) +QString PackInstallTask::getVersionForLoader(QString uid) { if (m_version.loader.recommended || m_version.loader.latest || m_version.loader.choose) { auto vlist = APPLICATION->metadataIndex()->get(uid); @@ -374,19 +354,16 @@ QString PackInstallTask::getVersionForLoader(const QString& uid) // filtering for those loaders. if (m_version.loader.type != "fabric") { auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Meta::Require& req) { return req.uid == "net.minecraft"; }); - if (iter == reqs.end()) { + if (iter == reqs.end()) continue; - } - if (iter->equalsVersion != m_version.minecraft) { + if (iter->equalsVersion != m_version.minecraft) continue; - } } if (m_version.loader.recommended) { // first recommended build we find, we use. - if (!version->isRecommended()) { + if (!version->isRecommended()) continue; - } } return version->descriptor(); @@ -394,8 +371,7 @@ QString PackInstallTask::getVersionForLoader(const QString& uid) emitFailed(tr("Failed to find version for %1 loader").arg(m_version.loader.type)); return Q_NULLPTR; - } - if (m_version.loader.choose) { + } else if (m_version.loader.choose) { // Fabric Loader doesn't depend on a given Minecraft version. if (m_version.loader.type == "fabric") { return m_support->chooseVersion(vlist, Q_NULLPTR); @@ -439,8 +415,7 @@ QString PackInstallTask::detectLibrary(const VersionLibrary& library) if (name == QString("guava")) { return "com.google.guava:guava:" + version; - } - if (name == QString("commons-lang3")) { + } else if (name == QString("commons-lang3")) { return "org.apache.commons:commons-lang3:" + version; } } @@ -448,7 +423,7 @@ QString PackInstallTask::detectLibrary(const VersionLibrary& library) return "org.multimc.atlauncher:" + library.md5 + ":1"; } -bool PackInstallTask::createLibrariesComponent(const QString& instanceRoot, PackProfile* profile) +bool PackInstallTask::createLibrariesComponent(QString instanceRoot, std::shared_ptr profile) { if (m_version.libraries.isEmpty()) { return true; @@ -472,19 +447,20 @@ bool PackInstallTask::createLibrariesComponent(const QString& instanceRoot, Pack } } - auto id = QUuid::createUuid().toString(QUuid::WithoutBraces); - auto targetId = "org.multimc.atlauncher." + id; + auto uuid = QUuid::createUuid(); + auto id = uuid.toString().remove('{').remove('}'); + auto target_id = "org.multimc.atlauncher." + id; auto patchDir = FS::PathCombine(instanceRoot, "patches"); if (!FS::ensureFolderPathExists(patchDir)) { return false; } - auto patchFileName = FS::PathCombine(patchDir, targetId + ".json"); + auto patchFileName = FS::PathCombine(patchDir, target_id + ".json"); auto f = std::make_shared(); f->name = m_pack_name + " " + m_version_name + " (libraries)"; - const static QMap s_liteLoaderMap = { + const static QMap liteLoaderMap = { { "61179803bcd5fb7790789b790908663d", "1.12-SNAPSHOT" }, { "1420785ecbfed5aff4a586c5c9dd97eb", "1.12.2-SNAPSHOT" }, { "073f68e2fcb518b91fd0d99462441714", "1.6.2_03" }, { "10a15b52fc59b1bfb9c05b56de1097d6", "1.6.2_02" }, { "b52f90f08303edd3d4c374e268a5acf1", "1.6.2_04" }, { "ea747e24e03e24b7cad5bc8a246e0319", "1.6.2_01" }, @@ -504,8 +480,8 @@ bool PackInstallTask::createLibrariesComponent(const QString& instanceRoot, Pack for (const auto& lib : m_version.libraries) { // If the library is LiteLoader, we need to ignore it and handle it separately. - if (s_liteLoaderMap.contains(lib.md5)) { - auto ver = getComponentVersion("com.mumfrey.liteloader", s_liteLoaderMap.value(lib.md5)); + if (liteLoaderMap.contains(lib.md5)) { + auto ver = getComponentVersion("com.mumfrey.liteloader", liteLoaderMap.value(lib.md5)); if (ver) { componentsToInstall.insert("com.mumfrey.liteloader", ver); continue; @@ -522,9 +498,8 @@ bool PackInstallTask::createLibrariesComponent(const QString& instanceRoot, Pack libExempt = Version(libSpecifier.version()) >= Version(existingLib.version()); } } - if (libExempt) { + if (libExempt) continue; - } auto library = std::make_shared(); library->setRawName(libName); @@ -557,11 +532,11 @@ bool PackInstallTask::createLibrariesComponent(const QString& instanceRoot, Pack file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); file.close(); - profile->appendComponent(ComponentPtr{ new Component(profile, targetId, f) }); + profile->appendComponent(ComponentPtr{ new Component(profile.get(), target_id, f) }); return true; } -bool PackInstallTask::createPackComponent(const QString& instanceRoot, PackProfile* profile) +bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr profile) { if (m_version.mainClass.mainClass.isEmpty() && m_version.extraArguments.arguments.isEmpty()) { return true; @@ -591,14 +566,15 @@ bool PackInstallTask::createPackComponent(const QString& instanceRoot, PackProfi return true; } - auto id = QUuid::createUuid().toString(QUuid::WithoutBraces); - auto targetId = "org.multimc.atlauncher." + id; + auto uuid = QUuid::createUuid(); + auto id = uuid.toString().remove('{').remove('}'); + auto target_id = "org.multimc.atlauncher." + id; auto patchDir = FS::PathCombine(instanceRoot, "patches"); if (!FS::ensureFolderPathExists(patchDir)) { return false; } - auto patchFileName = FS::PathCombine(patchDir, targetId + ".json"); + auto patchFileName = FS::PathCombine(patchDir, target_id + ".json"); QStringList mainClasses; QStringList tweakers; @@ -625,9 +601,8 @@ bool PackInstallTask::createPackComponent(const QString& instanceRoot, PackProfi for (auto arg : args) { if (arg.startsWith("--tweakClass=") || previous == "--tweakClass") { auto tweakClass = arg.remove("--tweakClass="); - if (tweakers.contains(tweakClass)) { + if (tweakers.contains(tweakClass)) continue; - } f->addTweakers.append(tweakClass); } @@ -640,13 +615,13 @@ bool PackInstallTask::createPackComponent(const QString& instanceRoot, PackProfi QFile file(patchFileName); if (!file.open(QFile::WriteOnly)) { - qCritical() << "Error opening" << file.fileName() << "for writing:" << file.errorString(); + qCritical() << "Error opening" << file.fileName() << "for reading:" << file.errorString(); return false; } file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); file.close(); - profile->appendComponent(ComponentPtr{ new Component(profile, targetId, f) }); + profile->appendComponent(ComponentPtr{ new Component(profile.get(), target_id, f) }); return true; } @@ -676,7 +651,7 @@ void PackInstallTask::installConfigs() connect(jobPtr.get(), &NetJob::failed, [this](QString reason) { abortable = false; jobPtr.reset(); - emitFailed(std::move(reason)); + emitFailed(reason); }); connect(jobPtr.get(), &NetJob::progress, [this](qint64 current, qint64 total) { abortable = true; @@ -733,17 +708,15 @@ void PackInstallTask::downloadMods() jarmods.clear(); jobPtr.reset(new NetJob(tr("Mod download"), APPLICATION->network())); - QList blockedMods; + QList blocked_mods; for (const auto& mod : m_version.mods) { // skip non-client mods - if (!mod.client) { + if (!mod.client) continue; - } // skip optional mods that were not selected - if (mod.optional && !selectedMods.contains(mod.name)) { + if (mod.optional && !selectedMods.contains(mod.name)) continue; - } QString url; switch (mod.download) { @@ -751,7 +724,7 @@ void PackInstallTask::downloadMods() url = BuildConfig.ATL_DOWNLOAD_SERVER_URL + mod.url; break; case DownloadType::Browser: { - blockedMods.append(mod); + blocked_mods.append(mod); continue; } case DownloadType::Direct: @@ -787,9 +760,8 @@ void PackInstallTask::downloadMods() jobPtr->addNetAction(dl); } else { auto relpath = getDirForModType(mod.type, mod.type_raw); - if (relpath == Q_NULLPTR) { + if (relpath == Q_NULLPTR) continue; - } auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", cacheName); entry->setStale(true); @@ -823,51 +795,49 @@ void PackInstallTask::downloadMods() modsToCopy[entry->getFullPath()] = path; } } - if (!blockedMods.isEmpty()) { + if (!blocked_mods.isEmpty()) { QList mods; - for (const auto& mod : blockedMods) { - BlockedMod blockedMod; - blockedMod.name = mod.file; - blockedMod.websiteUrl = mod.url; - blockedMod.hash = mod.md5; - blockedMod.matched = false; - blockedMod.localPath = ""; + for (auto mod : blocked_mods) { + BlockedMod blocked_mod; + blocked_mod.name = mod.file; + blocked_mod.websiteUrl = mod.url; + blocked_mod.hash = mod.md5; + blocked_mod.matched = false; + blocked_mod.localPath = ""; - mods.append(blockedMod); + mods.append(blocked_mod); } qWarning() << "Blocked mods found, displaying mod list"; - BlockedModsDialog messageDialog(nullptr, tr("Blocked mods found"), - tr("The following files are not available for download in third party launchers.
" - "You will need to manually download them and add them to the instance."), - mods, "md5"); + BlockedModsDialog message_dialog(nullptr, tr("Blocked mods found"), + tr("The following files are not available for download in third party launchers.
" + "You will need to manually download them and add them to the instance."), + mods, "md5"); - messageDialog.setModal(true); + message_dialog.setModal(true); - if (messageDialog.exec() != 0) { + if (message_dialog.exec()) { qDebug() << "Post dialog blocked mods list:" << mods; - for (const auto& blocked : mods) { + for (auto blocked : mods) { if (!blocked.matched) { qDebug() << blocked.name << "was not matched to a local file, skipping copy"; continue; } - auto modIter = - std::ranges::find_if(blockedMods, [blocked](const VersionMod& mod) { return mod.url == blocked.websiteUrl; }); - if (modIter == blockedMods.end()) { + auto modIter = std::find_if(blocked_mods.begin(), blocked_mods.end(), + [blocked](const VersionMod& mod) { return mod.url == blocked.websiteUrl; }); + if (modIter == blocked_mods.end()) continue; - } - const auto& mod = *modIter; + auto mod = *modIter; if (mod.type == ModType::Extract || mod.type == ModType::TexturePackExtract || mod.type == ModType::ResourcePackExtract) { modsToExtract.insert(blocked.localPath, mod); } else if (mod.type == ModType::Decomp) { modsToDecomp.insert(blocked.localPath, mod); } else { auto relpath = getDirForModType(mod.type, mod.type_raw); - if (relpath == Q_NULLPTR) { + if (relpath == Q_NULLPTR) continue; - } auto path = FS::PathCombine(m_stagingPath, "minecraft", relpath, mod.file); @@ -945,8 +915,8 @@ bool PackInstallTask::extractMods(const QMap& toExtract, setStatus(tr("Extracting mods...")); for (auto iter = toExtract.begin(); iter != toExtract.end(); iter++) { - const auto& modPath = iter.key(); - const auto& mod = iter.value(); + auto& modPath = iter.key(); + auto& mod = iter.value(); QString extractToDir; if (mod.type == ModType::Extract) { @@ -965,10 +935,6 @@ bool PackInstallTask::extractMods(const QMap& toExtract, folderToExtract = mod.extractFolder; static const QRegularExpression s_regex("^/"); folderToExtract.remove(s_regex); - if (isPathTraversal(extractToPath, folderToExtract)) { - qWarning() << "Blocked path traversal in" << mod.extractFolder; - return false; - } } qDebug() << "Extracting " + mod.file + " to " + extractToDir; @@ -979,18 +945,13 @@ bool PackInstallTask::extractMods(const QMap& toExtract, } for (auto iter = toDecomp.begin(); iter != toDecomp.end(); iter++) { - const auto& modPath = iter.key(); - const auto& mod = iter.value(); + auto& modPath = iter.key(); + auto& mod = iter.value(); auto extractToDir = getDirForModType(mod.decompType, mod.decompType_raw); QDir extractDir(m_stagingPath); auto extractToPath = FS::PathCombine(extractDir.absolutePath(), "minecraft", extractToDir, mod.decompFile); - if (isPathTraversal(extractToPath, mod.decompFile)) { - qWarning() << "Blocked path traversal in decompFile" << mod.decompFile; - return false; - } - qDebug() << "Extracting " + mod.decompFile + " to " + extractToDir; if (!MMCZip::extractFile(modPath, mod.decompFile, extractToPath)) { qWarning() << "Failed to extract" << mod.decompFile; @@ -999,8 +960,8 @@ bool PackInstallTask::extractMods(const QMap& toExtract, } for (auto iter = toCopy.begin(); iter != toCopy.end(); iter++) { - const auto& from = iter.key(); - const auto& to = iter.value(); + auto& from = iter.key(); + auto& to = iter.value(); // If the file already exists, assume the mod is the correct copy - and remove // the copy from the Configs.zip @@ -1027,71 +988,74 @@ void PackInstallTask::install() setStatus(tr("Installing modpack")); auto instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg"); - MinecraftInstance instance(m_globalSettings, std::make_unique(instanceConfigPath), m_stagingPath); - { - SettingsObject::Lock lock(instance.settings()); - auto* components = instance.getPackProfile(); - components->buildingFromScratch(); + auto instanceSettings = std::make_shared(instanceConfigPath); + instanceSettings->suspendSave(); - // Use a component to add libraries BEFORE Minecraft - if (!createLibrariesComponent(instance.instanceRoot(), components)) { - emitFailed(tr("Failed to create libraries component")); - return; - } + MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); + auto components = instance.getPackProfile(); + components->buildingFromScratch(); - // Minecraft - components->setComponentVersion("net.minecraft", m_version.minecraft, true); - - // Loader - if (m_version.loader.type == QString("forge")) { - auto version = getVersionForLoader("net.minecraftforge"); - if (version == Q_NULLPTR) { - return; - } - - components->setComponentVersion("net.minecraftforge", version); - } else if (m_version.loader.type == QString("neoforge")) { - auto version = getVersionForLoader("net.neoforged"); - if (version == Q_NULLPTR) { - return; - } - - components->setComponentVersion("net.neoforged", version); - } else if (m_version.loader.type == QString("fabric")) { - auto version = getVersionForLoader("net.fabricmc.fabric-loader"); - if (version == Q_NULLPTR) { - return; - } - - components->setComponentVersion("net.fabricmc.fabric-loader", version); - } else if (m_version.loader.type != QString()) { - emitFailed(tr("Unknown loader type: ") + m_version.loader.type); - return; - } - - for (const auto& componentUid : componentsToInstall.keys()) { - auto version = componentsToInstall.value(componentUid); - components->setComponentVersion(componentUid, version->version()); - } - - components->installJarMods(jarmods); - - // Use a component to fill in the rest of the data - // todo: use more detection - if (!createPackComponent(instance.instanceRoot(), components)) { - emitFailed(tr("Failed to create pack component")); - return; - } - - components->saveNow(); - - instance.setName(name()); - instance.setIconKey(m_instIcon); - instance.setManagedPack("atlauncher", m_pack_safe_name, m_pack_name, m_version_name, m_version_name); - - jarmods.clear(); + // Use a component to add libraries BEFORE Minecraft + if (!createLibrariesComponent(instance.instanceRoot(), components)) { + emitFailed(tr("Failed to create libraries component")); + return; } + + // Minecraft + components->setComponentVersion("net.minecraft", m_version.minecraft, true); + + // Loader + if (m_version.loader.type == QString("forge")) { + auto version = getVersionForLoader("net.minecraftforge"); + if (version == Q_NULLPTR) + return; + + components->setComponentVersion("net.minecraftforge", version); + } else if (m_version.loader.type == QString("neoforge")) { + auto version = getVersionForLoader("net.neoforged"); + if (version == Q_NULLPTR) + return; + + components->setComponentVersion("net.neoforged", version); + } else if (m_version.loader.type == QString("fabric")) { + auto version = getVersionForLoader("net.fabricmc.fabric-loader"); + if (version == Q_NULLPTR) + return; + + components->setComponentVersion("net.fabricmc.fabric-loader", version); + } else if (m_version.loader.type != QString()) { + emitFailed(tr("Unknown loader type: ") + m_version.loader.type); + return; + } + + for (const auto& componentUid : componentsToInstall.keys()) { + auto version = componentsToInstall.value(componentUid); + components->setComponentVersion(componentUid, version->version()); + } + + components->installJarMods(jarmods); + + // Use a component to fill in the rest of the data + // todo: use more detection + if (!createPackComponent(instance.instanceRoot(), components)) { + emitFailed(tr("Failed to create pack component")); + return; + } + + components->saveNow(); + + instance.setName(name()); + instance.setIconKey(m_instIcon); + instance.setManagedPack("atlauncher", m_pack_safe_name, m_pack_name, m_version_name, m_version_name); + instanceSettings->resumeSave(); + + jarmods.clear(); emitSucceeded(); } +static Meta::Version::Ptr getComponentVersion(const QString& uid, const QString& version) +{ + return APPLICATION->metadataIndex()->getLoadedVersion(uid, version); +} + } // namespace ATLauncher diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.h b/launcher/modplatform/atlauncher/ATLPackInstallTask.h index e8df132be..8024286e8 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.h +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.h @@ -44,13 +44,14 @@ #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" #include "net/NetJob.h" +#include "settings/INISettingsObject.h" -#include +#include #include namespace ATLauncher { -enum class InstallMode : std::uint8_t { +enum class InstallMode { Install, Reinstall, Update, @@ -85,16 +86,16 @@ class PackInstallTask : public InstanceTask { QString packName, QString version, InstallMode installMode = InstallMode::Install); - ~PackInstallTask() override { delete m_support; } + virtual ~PackInstallTask() { delete m_support; } bool canAbort() const override { return true; } bool abort() override; protected: - void executeTask() override; + virtual void executeTask() override; private slots: - void onDownloadSucceeded(QByteArray* responsePtr); + void onDownloadSucceeded(); void onDownloadFailed(QString reason); void onDownloadAborted(); @@ -102,12 +103,12 @@ class PackInstallTask : public InstanceTask { void onModsExtracted(); private: - QString getDirForModType(ModType type, const QString& raw); - QString getVersionForLoader(const QString& uid); - static QString detectLibrary(const VersionLibrary& library); + QString getDirForModType(ModType type, QString raw); + QString getVersionForLoader(QString uid); + QString detectLibrary(const VersionLibrary& library); - bool createLibrariesComponent(const QString& instanceRoot, PackProfile* profile); - bool createPackComponent(const QString& instanceRoot, PackProfile* profile); + bool createLibrariesComponent(QString instanceRoot, std::shared_ptr profile); + bool createPackComponent(QString instanceRoot, std::shared_ptr profile); void deleteExistingFiles(); void installConfigs(); @@ -124,6 +125,7 @@ class PackInstallTask : public InstanceTask { bool abortable = false; NetJob::Ptr jobPtr; + std::shared_ptr response = std::make_shared(); InstallMode m_install_mode; QString m_pack_name; diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp index 9cd85aef7..3fe71dbc4 100644 --- a/launcher/modplatform/flame/FileResolvingTask.cpp +++ b/launcher/modplatform/flame/FileResolvingTask.cpp @@ -29,8 +29,6 @@ #include "net/NetJob.h" #include "tasks/Task.h" -#include "Application.h" - static const FlameAPI flameAPI; static ModrinthAPI modrinthAPI; @@ -53,19 +51,19 @@ void Flame::FileResolvingTask::executeTask() } setStatus(tr("Resolving mod IDs...")); setProgress(0, 3); + m_result.reset(new QByteArray()); QStringList fileIds; for (auto file : m_manifest.files) { fileIds.push_back(QString::number(file.fileId)); } - auto [task, response] = flameAPI.getFiles(fileIds); - m_task = task; + m_task = flameAPI.getFiles(fileIds, m_result); auto step_progress = std::make_shared(); - connect(m_task.get(), &Task::succeeded, this, [this, response, step_progress]() { + connect(m_task.get(), &Task::finished, this, [this, step_progress]() { step_progress->state = TaskStepState::Succeeded; stepProgress(*step_progress); - netJobFinished(response); + netJobFinished(); }); connect(m_task.get(), &Task::failed, this, [this, step_progress](QString reason) { step_progress->state = TaskStepState::Failed; @@ -110,7 +108,7 @@ ModPlatform::ResourceType getResourceType(int classId) } } -void Flame::FileResolvingTask::netJobFinished(QByteArray* response) +void Flame::FileResolvingTask::netJobFinished() { setProgress(1, 3); // job to check modrinth for blocked projects @@ -118,7 +116,7 @@ void Flame::FileResolvingTask::netJobFinished(QByteArray* response) QJsonArray array; try { - doc = Json::requireDocument(*response); + doc = Json::requireDocument(*m_result); array = Json::requireArray(doc.object()["data"]); } catch (Json::JsonException& e) { qCritical() << "Non-JSON data returned from the CF API"; @@ -155,53 +153,56 @@ void Flame::FileResolvingTask::netJobFinished(QByteArray* response) getFlameProjects(); return; } - auto [modrinthTask, modrinthResponse] = modrinthAPI.currentVersions(hashes, "sha1"); - m_task = modrinthTask; + m_result.reset(new QByteArray()); + m_task = modrinthAPI.currentVersions(hashes, "sha1", m_result); (dynamic_cast(m_task.get()))->setAskRetry(false); auto step_progress = std::make_shared(); - connect(m_task.get(), &Task::succeeded, this, [this, modrinthResponse, step_progress]() { + connect(m_task.get(), &Task::finished, this, [this, step_progress]() { step_progress->state = TaskStepState::Succeeded; stepProgress(*step_progress); QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*modrinthResponse, &parse_error); + QJsonDocument doc = QJsonDocument::fromJson(*m_result, &parse_error); if (parse_error.error != QJsonParseError::NoError) { qWarning() << "Error while parsing JSON response from Modrinth::CurrentVersions at" << parse_error.offset << "reason:" << parse_error.errorString(); - qWarning() << *modrinthResponse; + qWarning() << *m_result; getFlameProjects(); return; - } - if (APPLICATION->settings()->get("FallbackMRBlockedMods").toBool()){ - try { - auto entries = Json::requireObject(doc); - for (auto& out : m_manifest.files) { - auto url = QUrl(out.version.downloadUrl, QUrl::TolerantMode); - if (!url.isValid() && "sha1" == out.version.hash_type && !out.version.hash.isEmpty()) { - try { - auto entry = Json::requireObject(entries, out.version.hash); + } - auto file = Modrinth::loadIndexedPackVersion(entry); + try { + auto entries = Json::requireObject(doc); + for (auto& out : m_manifest.files) { + auto url = QUrl(out.version.downloadUrl, QUrl::TolerantMode); + if (!url.isValid() && "sha1" == out.version.hash_type && !out.version.hash.isEmpty()) { + try { + auto entry = Json::requireObject(entries, out.version.hash); + auto file = Modrinth::loadIndexedPackVersion(entry); + + // If there's more than one mod loader for this version, we can't know for sure + // which file is relative to each loader, so it's best to not use any one and + // let the user download it manually. + if (!file.loaders || hasSingleModLoaderSelected(file.loaders)) { out.version.downloadUrl = file.downloadUrl; qDebug() << "Found alternative on modrinth" << out.version.fileName; - } catch (Json::JsonException& e) { - qDebug() << e.cause(); - qDebug() << entries; } + } catch (Json::JsonException& e) { + qDebug() << e.cause(); + qDebug() << entries; } } - } catch (Json::JsonException& e) { - qDebug() << e.cause(); - qDebug() << doc; } + } catch (Json::JsonException& e) { + qDebug() << e.cause(); + qDebug() << doc; } getFlameProjects(); }); connect(m_task.get(), &Task::failed, this, [this, step_progress](QString reason) { step_progress->state = TaskStepState::Failed; stepProgress(*step_progress); - getFlameProjects(); }); connect(m_task.get(), &Task::stepProgress, this, &FileResolvingTask::propagateStepProgress); connect(m_task.get(), &Task::progress, this, [this, step_progress](qint64 current, qint64 total) { @@ -213,28 +214,29 @@ void Flame::FileResolvingTask::netJobFinished(QByteArray* response) step_progress->status = status; stepProgress(*step_progress); }); + m_task->start(); } void Flame::FileResolvingTask::getFlameProjects() { setProgress(2, 3); + m_result.reset(new QByteArray()); QStringList addonIds; for (auto file : m_manifest.files) { addonIds.push_back(QString::number(file.projectId)); } - auto [task, response] = flameAPI.getProjects(addonIds); - m_task = task; + m_task = flameAPI.getProjects(addonIds, m_result); auto step_progress = std::make_shared(); - connect(m_task.get(), &Task::succeeded, this, [this, response, step_progress] { + connect(m_task.get(), &Task::succeeded, this, [this, step_progress] { QJsonParseError parse_error{}; - auto doc = QJsonDocument::fromJson(*response, &parse_error); + auto doc = QJsonDocument::fromJson(*m_result, &parse_error); if (parse_error.error != QJsonParseError::NoError) { qWarning() << "Error while parsing JSON response from Modrinth projects task at" << parse_error.offset << "reason:" << parse_error.errorString(); - qWarning() << *response; + qWarning() << *m_result; return; } diff --git a/launcher/modplatform/flame/FileResolvingTask.h b/launcher/modplatform/flame/FileResolvingTask.h index 21fa53d2d..3fe8dfb1a 100644 --- a/launcher/modplatform/flame/FileResolvingTask.h +++ b/launcher/modplatform/flame/FileResolvingTask.h @@ -36,13 +36,14 @@ class FileResolvingTask : public Task { virtual void executeTask() override; protected slots: - void netJobFinished(QByteArray* response); + void netJobFinished(); private: void getFlameProjects(); private: /* data */ Flame::Manifest m_manifest; + std::shared_ptr m_result; Task::Ptr m_task; }; } // namespace Flame diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index b9b5c2207..34b8d0a03 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -15,7 +15,7 @@ #include "net/ApiUpload.h" #include "net/NetJob.h" -std::pair FlameAPI::matchFingerprints(const QList& fingerprints) +Task::Ptr FlameAPI::matchFingerprints(const QList& fingerprints, std::shared_ptr response) { auto netJob = makeShared(QString("Flame::MatchFingerprints"), APPLICATION->network()); @@ -29,10 +29,10 @@ std::pair FlameAPI::matchFingerprints(const QList& QJsonDocument body(body_obj); auto body_raw = body.toJson(); - auto [action, response] = Net::ApiUpload::makeByteArray(QString(BuildConfig.FLAME_BASE_URL + "/fingerprints"), body_raw); - netJob->addNetAction(action); - return { netJob, response }; + netJob->addNetAction(Net::ApiUpload::makeByteArray(QString(BuildConfig.FLAME_BASE_URL + "/fingerprints"), response, body_raw)); + + return netJob; } QString FlameAPI::getModFileChangelog(int modId, int fileId) @@ -41,10 +41,11 @@ QString FlameAPI::getModFileChangelog(int modId, int fileId) QString changelog; auto netJob = makeShared(QString("Flame::FileChangelog"), APPLICATION->network()); - auto [action, response] = Net::ApiDownload::makeByteArray( + auto response = std::make_shared(); + netJob->addNetAction(Net::ApiDownload::makeByteArray( QString(BuildConfig.FLAME_BASE_URL + "/mods/%1/files/%2/changelog") - .arg(QString::fromStdString(std::to_string(modId)), QString::fromStdString(std::to_string(fileId)))); - netJob->addNetAction(action); + .arg(QString::fromStdString(std::to_string(modId)), QString::fromStdString(std::to_string(fileId))), + response)); QObject::connect(netJob.get(), &NetJob::succeeded, [&netJob, response, &changelog] { QJsonParseError parse_error{}; @@ -75,9 +76,9 @@ QString FlameAPI::getModDescription(int modId) QString description; auto netJob = makeShared(QString("Flame::ModDescription"), APPLICATION->network()); - auto [action, response] = - Net::ApiDownload::makeByteArray(QString(BuildConfig.FLAME_BASE_URL + "/mods/%1/description").arg(QString::number(modId))); - netJob->addNetAction(action); + auto response = std::make_shared(); + netJob->addNetAction(Net::ApiDownload::makeByteArray( + 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{}; @@ -102,7 +103,7 @@ QString FlameAPI::getModDescription(int modId) return description; } -std::pair FlameAPI::getProjects(QStringList addonIds) const +Task::Ptr FlameAPI::getProjects(QStringList addonIds, std::shared_ptr response) const { auto netJob = makeShared(QString("Flame::GetProjects"), APPLICATION->network()); @@ -116,15 +117,15 @@ std::pair FlameAPI::getProjects(QStringList addonIds) co QJsonDocument body(body_obj); auto body_raw = body.toJson(); - auto [action, response] = Net::ApiUpload::makeByteArray(QString(BuildConfig.FLAME_BASE_URL + "/mods"), body_raw); - netJob->addNetAction(action); + + 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; }); - return { netJob, response }; + return netJob; } -std::pair FlameAPI::getFiles(const QStringList& fileIds) const +Task::Ptr FlameAPI::getFiles(const QStringList& fileIds, std::shared_ptr response) const { auto netJob = makeShared(QString("Flame::GetFiles"), APPLICATION->network()); @@ -139,24 +140,22 @@ std::pair FlameAPI::getFiles(const QStringList& fileIds) QJsonDocument body(body_obj); auto body_raw = body.toJson(); - auto [action, response] = Net::ApiUpload::makeByteArray(QString(BuildConfig.FLAME_BASE_URL + "/mods/files"), body_raw); - netJob->addNetAction(action); + 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; }); - return { netJob, response }; + return netJob; } -std::pair FlameAPI::getFile(const QString& addonId, const QString& fileId) const +Task::Ptr FlameAPI::getFile(const QString& addonId, const QString& fileId, std::shared_ptr response) const { auto netJob = makeShared(QString("Flame::GetFile"), APPLICATION->network()); - auto [action, response] = - Net::ApiDownload::makeByteArray(QUrl(QString(BuildConfig.FLAME_BASE_URL + "/mods/%1/files/%2").arg(addonId, fileId))); - netJob->addNetAction(action); + netJob->addNetAction( + 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; }); - return { netJob, response }; + return netJob; } QList FlameAPI::getSortingMethods() const @@ -172,26 +171,25 @@ QList FlameAPI::getSortingMethods() const { 8, "GameVersion", QObject::tr("Sort by Game Version") } }; } -std::pair FlameAPI::getCategories(ModPlatform::ResourceType type) +Task::Ptr FlameAPI::getCategories(std::shared_ptr response, ModPlatform::ResourceType type) { auto netJob = makeShared(QString("Flame::GetCategories"), APPLICATION->network()); - auto [action, response] = Net::ApiDownload::makeByteArray( - QUrl(QString(BuildConfig.FLAME_BASE_URL + "/categories?gameId=432&classId=%1").arg(getClassId(type)))); - netJob->addNetAction(action); + netJob->addNetAction(Net::ApiDownload::makeByteArray( + 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, response }; + return netJob; } -std::pair FlameAPI::getModCategories() +Task::Ptr FlameAPI::getModCategories(std::shared_ptr response) { - return getCategories(ModPlatform::ResourceType::Mod); + return getCategories(response, ModPlatform::ResourceType::Mod); } -QList FlameAPI::loadModCategories(const QByteArray& response) +QList FlameAPI::loadModCategories(std::shared_ptr response) { QList categories; QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { qWarning() << "Error while parsing JSON response from categories at" << parse_error.offset << "reason:" << parse_error.errorString(); diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index e77d53a4b..8bcb3ff46 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -5,7 +5,7 @@ #pragma once #include -#include +#include #include "BuildConfig.h" #include "Json.h" #include "Version.h" @@ -23,14 +23,14 @@ class FlameAPI : public ResourceAPI { ModPlatform::ModLoaderTypes fallback, bool checkLoaders); - std::pair getProjects(QStringList addonIds) const override; - std::pair matchFingerprints(const QList& fingerprints); - std::pair getFiles(const QStringList& fileIds) const; - std::pair getFile(const QString& addonId, const QString& fileId) const; + Task::Ptr getProjects(QStringList addonIds, std::shared_ptr response) const override; + Task::Ptr matchFingerprints(const QList& fingerprints, std::shared_ptr response); + Task::Ptr getFiles(const QStringList& fileIds, std::shared_ptr response) const; + Task::Ptr getFile(const QString& addonId, const QString& fileId, std::shared_ptr response) const; - static std::pair getCategories(ModPlatform::ResourceType type); - static std::pair getModCategories(); - static QList loadModCategories(const QByteArray& response); + static Task::Ptr getCategories(std::shared_ptr response, ModPlatform::ResourceType type); + static Task::Ptr getModCategories(std::shared_ptr response); + static QList loadModCategories(std::shared_ptr response); QList getSortingMethods() const override; @@ -79,7 +79,6 @@ class FlameAPI : public ResourceAPI { case ModPlatform::LegacyFabric: case ModPlatform::Ornithe: case ModPlatform::Rift: - case ModPlatform::None: break; // not supported } return 0; @@ -99,7 +98,7 @@ class FlameAPI : public ResourceAPI { static const QString getModLoaderFilters(ModPlatform::ModLoaderTypes types) { return "[" + getModLoaderStrings(types).join(',') + "]"; } public: - std::optional getSearchURL(const SearchArgs& 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,7 +111,7 @@ class FlameAPI : public ResourceAPI { get_arguments.append("sortOrder=desc"); if (args.loaders.has_value()) { ModPlatform::ModLoaderTypes loaders = args.loaders.value(); - loaders &= ~static_cast(ModPlatform::ModLoaderType::DataPack); + loaders &= ~ModPlatform::ModLoaderType::DataPack; if (loaders != 0) get_arguments.append(QString("modLoaderTypes=%1").arg(getModLoaderFilters(loaders))); } @@ -125,7 +124,7 @@ class FlameAPI : public ResourceAPI { return BuildConfig.FLAME_BASE_URL + "/mods/search?gameId=432&" + get_arguments.join('&'); } - std::optional getVersionsURL(const VersionSearchArgs& args) const override + std::optional getVersionsURL(VersionSearchArgs const& args) const override { auto addonId = args.pack->addonId.toString(); QString url = QString(BuildConfig.FLAME_BASE_URL + "/mods/%1/files?pageSize=10000").arg(addonId); @@ -150,10 +149,10 @@ class FlameAPI : public ResourceAPI { return arr; } // FIXME: Client-side version filtering. This won't take into account any user-selected filtering. - const auto& mc_versions = arr.mcVersion; + auto const& mc_versions = arr.mcVersion; if (std::any_of(mc_versions.constBegin(), mc_versions.constEnd(), - [](const auto& mc_version) { return Version(mc_version) <= Version("1.6"); })) { + [](auto const& mc_version) { return Version(mc_version) <= Version("1.6"); })) { return arr; } return {}; @@ -161,8 +160,8 @@ class FlameAPI : public ResourceAPI { void loadExtraPackInfo(ModPlatform::IndexedPack& m, [[maybe_unused]] QJsonObject&) const override { FlameMod::loadBody(m); } private: - std::optional getInfoURL(const QString& id) const override { return QString(BuildConfig.FLAME_BASE_URL + "/mods/%1").arg(id); } - std::optional getDependencyURL(const DependencySearchArgs& args) const override + std::optional getInfoURL(QString const& id) const override { return QString(BuildConfig.FLAME_BASE_URL + "/mods/%1").arg(id); } + std::optional getDependencyURL(DependencySearchArgs const& args) const override { auto addonId = args.dependency.addonId.toString(); auto url = diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp index 577f9967a..d6d61e213 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.cpp +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -18,6 +18,8 @@ #include "net/NetJob.h" #include "tasks/Task.h" +static FlameAPI api; + bool FlameCheckUpdate::abort() { bool result = false; @@ -37,7 +39,7 @@ void FlameCheckUpdate::executeTask() { setStatus(tr("Preparing resources for CurseForge...")); - auto* netJob = new NetJob("Get latest versions", APPLICATION->network()); + auto netJob = new NetJob("Get latest versions", APPLICATION->network()); connect(netJob, &Task::finished, this, &FlameCheckUpdate::collectBlockedMods); connect(netJob, &Task::progress, this, &FlameCheckUpdate::setProgress); @@ -46,12 +48,12 @@ void FlameCheckUpdate::executeTask() for (auto* resource : m_resources) { auto project = std::make_shared(); project->addonId = resource->metadata()->project_id.toString(); - auto versionsUrlOptional = FlameAPI().getVersionsURL({ .pack = project, .mcVersions = m_gameVersions }); - if (!versionsUrlOptional.has_value()) { + auto versionsUrlOptional = api.getVersionsURL({ project, m_gameVersions }); + if (!versionsUrlOptional.has_value()) continue; - } - auto [task, response] = Net::ApiDownload::makeByteArray(versionsUrlOptional.value()); + auto response = std::make_shared(); + auto task = Net::ApiDownload::makeByteArray(versionsUrlOptional.value(), response); connect(task.get(), &Task::succeeded, this, [this, resource, response] { getLatestVersionCallback(resource, response); }); netJob->addNetAction(task); @@ -60,13 +62,13 @@ void FlameCheckUpdate::executeTask() m_task->start(); } -void FlameCheckUpdate::getLatestVersionCallback(Resource* resource, QByteArray* response) +void FlameCheckUpdate::getLatestVersionCallback(Resource* resource, std::shared_ptr response) { - QJsonParseError parseError{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parseError); - if (parseError.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from latest mod version at" << parseError.offset - << "reason:" << parseError.errorString(); + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from latest mod version at" << parse_error.offset + << "reason:" << parse_error.errorString(); qWarning() << *response; return; } @@ -87,104 +89,100 @@ void FlameCheckUpdate::getLatestVersionCallback(Resource* resource, QByteArray* qCritical() << e.what(); qDebug() << doc; } - auto latestVer = FlameAPI().getLatestVersion(pack->versions, m_loadersList, resource->metadata()->loaders, !m_loadersList.isEmpty()); + 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())); - if (!latestVer.has_value() || !latestVer->addonId.isValid()) { + if (!latest_ver.has_value() || !latest_ver->addonId.isValid()) { QString reason; - if (dynamic_cast(resource) != nullptr) { + if (dynamic_cast(resource) != nullptr) reason = tr("No valid version found for this resource. It's probably unavailable for the current game " "version / mod loader."); - } else { + else reason = tr("No valid version found for this resource. It's probably unavailable for the current game version."); - } emit checkFailed(resource, reason); return; } - if (latestVer->downloadUrl.isEmpty() && latestVer->fileId != resource->metadata()->file_id) { - m_blocked[resource] = latestVer->fileId.toString(); + if (latest_ver->downloadUrl.isEmpty() && latest_ver->fileId != resource->metadata()->file_id) { + m_blocked[resource] = latest_ver->fileId.toString(); return; } - if (!latestVer->hash.isEmpty() && - (resource->metadata()->hash != latestVer->hash || resource->status() == ResourceStatus::NotInstalled)) { - auto oldVersion = resource->metadata()->version_number; - if (oldVersion.isEmpty()) { - if (resource->status() == ResourceStatus::NotInstalled) { - oldVersion = tr("Not installed"); - } else { - oldVersion = tr("Unknown"); - } + if (!latest_ver->hash.isEmpty() && + (resource->metadata()->hash != latest_ver->hash || resource->status() == ResourceStatus::NOT_INSTALLED)) { + auto old_version = resource->metadata()->version_number; + if (old_version.isEmpty()) { + if (resource->status() == ResourceStatus::NOT_INSTALLED) + old_version = tr("Not installed"); + else + old_version = tr("Unknown"); } - auto downloadTask = makeShared(pack, latestVer.value(), m_resourceModel, true, "update"); - m_updates.emplace_back(pack->name, resource->metadata()->hash, oldVersion, latestVer->version, latestVer->version_type, - FlameAPI().getModFileChangelog(latestVer->addonId.toInt(), latestVer->fileId.toInt()), - ModPlatform::ResourceProvider::FLAME, downloadTask, resource->enabled()); + 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()); } - m_deps.append(std::make_shared(pack, latestVer.value())); + m_deps.append(std::make_shared(pack, latest_ver.value())); } void FlameCheckUpdate::collectBlockedMods() { QStringList addonIds; QHash quickSearch; - for (const auto& resource : m_blocked.keys()) { + for (auto const& resource : m_blocked.keys()) { auto addonId = resource->metadata()->project_id.toString(); addonIds.append(addonId); quickSearch[addonId] = resource; } + auto response = std::make_shared(); Task::Ptr projTask; - QByteArray* response = nullptr; if (addonIds.isEmpty()) { emitSucceeded(); return; - } - if (addonIds.size() == 1) { - std::tie(projTask, response) = FlameAPI().getProject(*addonIds.begin()); + } else if (addonIds.size() == 1) { + projTask = api.getProject(*addonIds.begin(), response); } else { - std::tie(projTask, response) = FlameAPI().getProjects(addonIds); + projTask = api.getProjects(addonIds, response); } connect(projTask.get(), &Task::succeeded, this, [this, response, addonIds, quickSearch] { - QJsonParseError parseError{}; - auto doc = QJsonDocument::fromJson(*response, &parseError); - if (parseError.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from Flame projects task at" << parseError.offset - << "reason:" << parseError.errorString(); + QJsonParseError parse_error{}; + auto doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from Flame projects task at" << parse_error.offset + << "reason:" << parse_error.errorString(); qWarning() << *response; return; } try { QJsonArray entries; - if (addonIds.size() == 1) { + if (addonIds.size() == 1) entries = { Json::requireObject(Json::requireObject(doc), "data") }; - } else { + else entries = Json::requireArray(Json::requireObject(doc), "data"); - } for (auto entry : entries) { - auto entryObj = Json::requireObject(entry); + auto entry_obj = Json::requireObject(entry); - auto id = QString::number(Json::requireInteger(entryObj, "id")); + auto id = QString::number(Json::requireInteger(entry_obj, "id")); - auto* resource = quickSearch.find(id).value(); + auto resource = quickSearch.find(id).value(); ModPlatform::IndexedPack pack; try { setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(resource->name())); - FlameMod::loadIndexedPack(pack, entryObj); - auto recoverUrl = QString("%1/download/%2").arg(pack.websiteUrl, m_blocked[resource]); + FlameMod::loadIndexedPack(pack, entry_obj); + auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, m_blocked[resource]); emit checkFailed(resource, tr("Resource has a new update available, but is not downloadable using CurseForge."), - recoverUrl); + recover_url); } catch (Json::JsonException& e) { qDebug() << e.cause(); qDebug() << entries; @@ -202,4 +200,4 @@ void FlameCheckUpdate::collectBlockedMods() connect(projTask.get(), &Task::details, this, &FlameCheckUpdate::setDetails); m_task.reset(projTask); m_task->start(); -} +} \ No newline at end of file diff --git a/launcher/modplatform/flame/FlameCheckUpdate.h b/launcher/modplatform/flame/FlameCheckUpdate.h index c2b3c9c35..eb80ce47c 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.h +++ b/launcher/modplatform/flame/FlameCheckUpdate.h @@ -7,10 +7,10 @@ class FlameCheckUpdate : public CheckUpdateTask { public: FlameCheckUpdate(QList& resources, - std::vector& mcVersions, + std::list& mcVersions, QList loadersList, - ResourceFolderModel* resourceModel) - : CheckUpdateTask(resources, mcVersions, std::move(loadersList), resourceModel) + std::shared_ptr resourceModel) + : CheckUpdateTask(resources, mcVersions, std::move(loadersList), std::move(resourceModel)) {} public slots: @@ -19,7 +19,7 @@ class FlameCheckUpdate : public CheckUpdateTask { protected slots: void executeTask() override; private slots: - void getLatestVersionCallback(Resource* resource, QByteArray* response); + void getLatestVersionCallback(Resource* resource, std::shared_ptr response); void collectBlockedMods(); private: diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 534132a6e..6a3215275 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -35,7 +35,6 @@ #include "FlameInstanceCreationTask.h" -#include "InstanceTask.h" #include "QObjectPtr.h" #include "minecraft/mod/tasks/LocalResourceUpdateTask.h" #include "modplatform/flame/FileResolvingTask.h" @@ -55,7 +54,7 @@ #include "settings/INISettingsObject.h" -#include "SysInfo.h" +#include "sys.h" #include "tasks/ConcurrentTask.h" #include "ui/dialogs/BlockedModsDialog.h" #include "ui/dialogs/CustomMessageBox.h" @@ -63,7 +62,6 @@ #include #include -#include "HardwareInfo.h" #include "meta/Index.h" #include "minecraft/World.h" #include "minecraft/mod/tasks/LocalResourceParse.h" @@ -77,6 +75,7 @@ bool FlameCreationTask::abort() if (!canAbort()) return false; + m_abort = true; if (m_processUpdateFileInfoJob) m_processUpdateFileInfoJob->abort(); if (m_filesJob) @@ -84,7 +83,7 @@ bool FlameCreationTask::abort() if (m_modIdResolver) m_modIdResolver->abort(); - return InstanceCreationTask::abort(); + return Task::abort(); } bool FlameCreationTask::updateInstance() @@ -92,7 +91,7 @@ bool FlameCreationTask::updateInstance() auto instance_list = APPLICATION->instances(); // FIXME: How to handle situations when there's more than one install already for a given modpack? - BaseInstance* inst; + InstancePtr inst; if (auto original_id = originalInstanceID(); !original_id.isEmpty()) { inst = instance_list->getInstanceById(original_id); Q_ASSERT(inst); @@ -172,7 +171,10 @@ bool FlameCreationTask::updateInstance() // FIXME: We may want to do something about disabled mods. auto old_overrides = Override::readOverrides("overrides", old_index_folder); for (const auto& entry : old_overrides) { - scheduleToDelete(m_parent, old_minecraft_dir, entry); + if (entry.isEmpty()) + continue; + qDebug() << "Scheduling" << entry << "for removal"; + m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(entry)); } // Remove remaining old files (we need to do an API request to know which ids are which files...) @@ -182,7 +184,8 @@ bool FlameCreationTask::updateInstance() fileIds.append(QString::number(file.fileId)); } - auto [job, raw_response] = api.getFiles(fileIds); + auto raw_response = std::make_shared(); + auto job = api.getFiles(fileIds, raw_response); QEventLoop loop; @@ -223,7 +226,13 @@ bool FlameCreationTask::updateInstance() continue; QString relative_path(FS::PathCombine(file.targetFolder, file.version.fileName)); - scheduleToDelete(m_parent, old_minecraft_dir, relative_path, true); + qDebug() << "Scheduling" << relative_path << "for removal"; + m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(relative_path)); + if (relative_path.endsWith(".disabled")) { // remove it if it was enabled/disabled by user + m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(relative_path.chopped(9))); + } else { + m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(relative_path + ".disabled")); + } } }); connect(job.get(), &Task::failed, this, [](QString reason) { qCritical() << "Failed to get files:" << reason; }); @@ -307,7 +316,7 @@ QString FlameCreationTask::getVersionForLoader(QString uid, QString loaderType, return loaderVersion; } -std::unique_ptr FlameCreationTask::createInstance() +bool FlameCreationTask::createInstance() { QEventLoop loop; @@ -325,7 +334,7 @@ std::unique_ptr FlameCreationTask::createInstance() } catch (const JSONValidationError& e) { setError(tr("Could not understand pack manifest:\n") + e.cause()); - return nullptr; + return false; } if (!m_pack.overrides.isEmpty()) { @@ -337,7 +346,7 @@ std::unique_ptr FlameCreationTask::createInstance() QString mcPath = FS::PathCombine(m_stagingPath, "minecraft"); if (!FS::move(overridePath, mcPath)) { setError(tr("Could not rename the overrides folder:\n") + m_pack.overrides); - return nullptr; + return false; } } else { logWarning( @@ -377,8 +386,8 @@ std::unique_ptr FlameCreationTask::createInstance() } QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); - auto instanceSettings = std::make_unique(configPath); - auto instance = std::make_unique(m_globalSettings, std::move(instanceSettings), m_stagingPath); + auto instanceSettings = std::make_shared(configPath); + MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); auto mcVersion = m_pack.minecraft.version; // Hack to correct some 'special sauce'... @@ -388,33 +397,33 @@ std::unique_ptr FlameCreationTask::createInstance() logWarning(tr("Mysterious trailing dots removed from Minecraft version while importing pack.")); } - auto components = instance->getPackProfile(); + auto components = instance.getPackProfile(); components->buildingFromScratch(); components->setComponentVersion("net.minecraft", mcVersion, true); if (!loaderType.isEmpty()) { auto version = getVersionForLoader(loaderUid, loaderType, loaderVersion, mcVersion); if (version.isEmpty()) - return nullptr; + return false; components->setComponentVersion(loaderUid, version); } if (m_instIcon != "default") { - instance->setIconKey(m_instIcon); + instance.setIconKey(m_instIcon); } else { if (m_pack.name.contains("Direwolf20")) { - instance->setIconKey("steve"); + instance.setIconKey("steve"); } else if (m_pack.name.contains("FTB") || m_pack.name.contains("Feed The Beast")) { - instance->setIconKey("ftb_logo"); + instance.setIconKey("ftb_logo"); } else { - instance->setIconKey("flame"); + instance.setIconKey("flame"); } } int recommendedRAM = m_pack.minecraft.recommendedRAM; // only set memory if this is a fresh instance - if (!m_instance && recommendedRAM > 0) { - const uint64_t sysMiB = HardwareInfo::totalRamMiB(); + if (m_instance == nullptr && recommendedRAM > 0) { + const uint64_t sysMiB = Sys::getSystemRam() / Sys::mebibyte; const uint64_t max = sysMiB * 0.9; if (static_cast(recommendedRAM) > max) { @@ -424,8 +433,8 @@ std::unique_ptr FlameCreationTask::createInstance() recommendedRAM = max; } - instance->settings()->set("OverrideMemory", true); - instance->settings()->set("MaxMemAlloc", recommendedRAM); + instance.settings()->set("OverrideMemory", true); + instance.settings()->set("MaxMemAlloc", recommendedRAM); } QString jarmodsPath = FS::PathCombine(m_stagingPath, "minecraft", "jarmods"); @@ -439,7 +448,7 @@ std::unique_ptr FlameCreationTask::createInstance() qDebug() << info.fileName(); jarMods.push_back(info.absoluteFilePath()); } - auto profile = instance->getPackProfile(); + auto profile = instance.getPackProfile(); profile->installJarMods(jarMods); // nuke the original files FS::deletePath(jarmodsPath); @@ -447,11 +456,11 @@ std::unique_ptr FlameCreationTask::createInstance() // Don't add managed info to packs without an ID (most likely imported from ZIP) if (!m_managedId.isEmpty()) - instance->setManagedPack("flame", m_managedId, m_pack.name, m_managedVersionId, m_pack.version); + instance.setManagedPack("flame", m_managedId, m_pack.name, m_managedVersionId, m_pack.version); else - instance->setManagedPack("flame", "", name(), "", ""); + instance.setManagedPack("flame", "", name(), "", ""); - instance->setName(name()); + instance.setName(name()); m_modIdResolver.reset(new Flame::FileResolvingTask(m_pack)); connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, this, [this, &loop] { idResolverSucceeded(loop); }); @@ -476,13 +485,10 @@ std::unique_ptr FlameCreationTask::createInstance() setAbortable(false); auto inst = m_instance.value(); - inst->copyManagedPack(*instance); + inst->copyManagedPack(instance); } - if (did_succeed) { - return instance; - } - return nullptr; + return did_succeed; } void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.h b/launcher/modplatform/flame/FlameInstanceCreationTask.h index 221ceaf22..e41ce742e 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.h +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.h @@ -52,7 +52,7 @@ class FlameCreationTask final : public InstanceCreationTask { public: FlameCreationTask(const QString& staging_path, - SettingsObject* global_settings, + SettingsObjectPtr global_settings, QWidget* parent, QString id, QString version_id, @@ -68,7 +68,7 @@ class FlameCreationTask final : public InstanceCreationTask { bool abort() override; bool updateInstance() override; - std::unique_ptr createInstance() override; + bool createInstance() override; private slots: void idResolverSucceeded(QEventLoop&); @@ -91,7 +91,7 @@ class FlameCreationTask final : public InstanceCreationTask { QList> m_otherResources; - std::optional m_instance; + std::optional m_instance; QStringList m_selectedOptionalMods; }; diff --git a/launcher/modplatform/flame/FlamePackExportTask.cpp b/launcher/modplatform/flame/FlamePackExportTask.cpp index 8be4fe94a..621443985 100644 --- a/launcher/modplatform/flame/FlamePackExportTask.cpp +++ b/launcher/modplatform/flame/FlamePackExportTask.cpp @@ -67,8 +67,8 @@ void FlamePackExportTask::collectFiles() setAbortable(false); QCoreApplication::processEvents(); - m_files.clear(); - if (!MMCZip::collectFileListRecursively(m_options.instance->gameRoot(), nullptr, &m_files, m_options.filter)) { + files.clear(); + if (!MMCZip::collectFileListRecursively(m_options.instance->gameRoot(), nullptr, &files, m_options.filter)) { emitFailed(tr("Could not search for files")); return; } @@ -77,7 +77,7 @@ void FlamePackExportTask::collectFiles() resolvedFiles.clear(); m_options.instance->loaderModList()->update(); - connect(m_options.instance->loaderModList(), &ModFolderModel::updateFinished, this, &FlamePackExportTask::collectHashes); + connect(m_options.instance->loaderModList().get(), &ModFolderModel::updateFinished, this, &FlamePackExportTask::collectHashes); } void FlamePackExportTask::collectHashes() @@ -88,7 +88,7 @@ void FlamePackExportTask::collectHashes() auto allMods = m_options.instance->loaderModList()->allMods(); ConcurrentTask::Ptr hashingTask(new ConcurrentTask("MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())); task.reset(hashingTask); - for (const QFileInfo& file : m_files) { + for (const QFileInfo& file : files) { const QString relative = m_gameRoot.relativeFilePath(file.absoluteFilePath()); // require sensible file types if (!std::any_of(FILE_EXTENSIONS.begin(), FILE_EXTENSIONS.end(), [&relative](const QString& extension) { @@ -167,14 +167,14 @@ void FlamePackExportTask::makeApiRequest() setStatus(tr("Finding versions for hashes...")); setProgress(2, 5); + auto response = std::make_shared(); QList fingerprints; for (auto& murmur : pendingHashes.keys()) { fingerprints.push_back(murmur.toUInt()); } - auto [matchTask, response] = api.matchFingerprints(fingerprints); - task = matchTask; + task.reset(api.matchFingerprints(fingerprints, response)); connect(task.get(), &Task::succeeded, this, [this, response] { QJsonParseError parseError{}; @@ -245,16 +245,16 @@ void FlamePackExportTask::getProjectsInfo() } } + auto response = std::make_shared(); Task::Ptr projTask; - QByteArray* response; if (addonIds.isEmpty()) { buildZip(); return; } else if (addonIds.size() == 1) { - std::tie(projTask, response) = api.getProject(*addonIds.begin()); + projTask = api.getProject(*addonIds.begin(), response); } else { - std::tie(projTask, response) = api.getProjects(addonIds); + projTask = api.getProjects(addonIds, response); } connect(projTask.get(), &Task::succeeded, this, [this, response, addonIds] { @@ -319,7 +319,7 @@ void FlamePackExportTask::buildZip() setStatus(tr("Adding files...")); setProgress(4, 5); - auto zipTask = makeShared(m_options.output, m_gameRoot, m_files, "overrides/", true); + auto zipTask = makeShared(m_options.output, m_gameRoot, files, "overrides/", true); zipTask->addExtraFile("manifest.json", generateIndex()); zipTask->addExtraFile("modlist.html", generateHTML()); diff --git a/launcher/modplatform/flame/FlamePackExportTask.h b/launcher/modplatform/flame/FlamePackExportTask.h index f6a90241d..e3d4c74a7 100644 --- a/launcher/modplatform/flame/FlamePackExportTask.h +++ b/launcher/modplatform/flame/FlamePackExportTask.h @@ -29,7 +29,7 @@ struct FlamePackExportOptions { QString version; QString author; bool optionalFiles; - MinecraftInstance* instance; + MinecraftInstancePtr instance; QString output; MMCZip::FilterFileFunction filter; int recommendedRAM; @@ -72,7 +72,7 @@ class FlamePackExportTask : public Task { FlameAPI api; - QFileInfoList m_files; + QFileInfoList files; QMap pendingHashes{}; QMap resolvedFiles{}; Task::Ptr task; diff --git a/launcher/modplatform/ftb/FTBPackInstallTask.cpp b/launcher/modplatform/ftb/FTBPackInstallTask.cpp deleted file mode 100644 index 6081807cf..000000000 --- a/launcher/modplatform/ftb/FTBPackInstallTask.cpp +++ /dev/null @@ -1,390 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 flowln - * Copyright (c) 2022 Jamie Mansfield - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2020-2021 Jamie Mansfield - * Copyright 2020-2021 Petr Mrazek - * - * 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 "FTBPackInstallTask.h" - -#include "FileSystem.h" -#include "Json.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" -#include "modplatform/flame/FileResolvingTask.h" -#include "modplatform/flame/PackManifest.h" -#include "net/ChecksumValidator.h" -#include "settings/INISettingsObject.h" - -#include "Application.h" -#include "BuildConfig.h" -#include "ui/dialogs/BlockedModsDialog.h" - -namespace FTB { - -PackInstallTask::PackInstallTask(Modpack pack, QString version, QWidget* parent) - : m_pack(std::move(pack)), m_versionName(std::move(version)), m_parent(parent) -{} - -bool PackInstallTask::abort() -{ - if (!canAbort()) - return false; - - bool aborted = true; - - if (m_net_job) - aborted &= m_net_job->abort(); - if (m_modIdResolverTask) - aborted &= m_modIdResolverTask->abort(); - - return aborted ? InstanceTask::abort() : false; -} - -void PackInstallTask::executeTask() -{ - setStatus(tr("Getting the manifest...")); - setAbortable(false); - - // Find pack version - auto version_it = std::find_if(m_pack.versions.constBegin(), m_pack.versions.constEnd(), - [this](const FTB::VersionInfo& a) { return a.name == m_versionName; }); - - if (version_it == m_pack.versions.constEnd()) { - emitFailed(tr("Failed to find pack version %1").arg(m_versionName)); - return; - } - - auto version = *version_it; - - auto netJob = makeShared("FTB::VersionFetch", APPLICATION->network()); - - auto searchUrl = QString(BuildConfig.FTB_API_BASE_URL + "/modpack/%1/%2").arg(m_pack.id).arg(version.id); - - auto [action, response] = Net::Download::makeByteArray(QUrl(searchUrl)); - netJob->addNetAction(action); - - QObject::connect(netJob.get(), &NetJob::succeeded, this, [this, response] { onManifestDownloadSucceeded(response); }); - QObject::connect(netJob.get(), &NetJob::failed, this, &PackInstallTask::onManifestDownloadFailed); - QObject::connect(netJob.get(), &NetJob::aborted, this, &PackInstallTask::abort); - QObject::connect(netJob.get(), &NetJob::progress, this, &PackInstallTask::setProgress); - - m_net_job = netJob; - - setAbortable(true); - netJob->start(); -} - -void PackInstallTask::onManifestDownloadSucceeded(QByteArray* responsePtr) -{ - // NOTE(TheKodeToad): moving the response out to avoid it from being destroyed by m_net_job.reset() - QByteArray response = std::move(*responsePtr); - m_net_job.reset(); - - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString(); - qWarning() << response; - return; - } - - FTB::Version version; - try { - auto obj = Json::requireObject(doc); - FTB::loadVersion(version, obj); - } catch (const JSONValidationError& e) { - emitFailed(tr("Could not understand pack manifest:\n") + e.cause()); - return; - } - - m_version = version; - - resolveMods(); -} - -void PackInstallTask::resolveMods() -{ - setStatus(tr("Resolving mods...")); - setAbortable(false); - setProgress(0, 100); - - m_fileIds.clear(); - - Flame::Manifest manifest; - for (const auto& file : m_version.files) { - if (!file.serverOnly && file.url.isEmpty()) { - if (file.curseforge.file_id <= 0) { - emitFailed(tr("Invalid manifest: There's no information available to download the file '%1'!").arg(file.name)); - return; - } - - Flame::File flameFile; - flameFile.projectId = file.curseforge.project_id; - flameFile.fileId = file.curseforge.file_id; - - manifest.files.insert(flameFile.fileId, flameFile); - m_fileIds.append(flameFile.fileId); - } else { - m_fileIds.append(-1); - } - } - - m_modIdResolverTask.reset(new Flame::FileResolvingTask(manifest)); - - connect(m_modIdResolverTask.get(), &Flame::FileResolvingTask::succeeded, this, &PackInstallTask::onResolveModsSucceeded); - connect(m_modIdResolverTask.get(), &Flame::FileResolvingTask::failed, this, &PackInstallTask::onResolveModsFailed); - connect(m_modIdResolverTask.get(), &Flame::FileResolvingTask::aborted, this, &PackInstallTask::abort); - connect(m_modIdResolverTask.get(), &Flame::FileResolvingTask::progress, this, &PackInstallTask::setProgress); - - setAbortable(true); - - m_modIdResolverTask->start(); -} - -void PackInstallTask::onResolveModsSucceeded() -{ - auto anyBlocked = false; - - Flame::Manifest results = m_modIdResolverTask->getResults(); - for (int index = 0; index < m_fileIds.size(); index++) { - const auto file_id = m_fileIds.at(index); - if (file_id < 0) - continue; - - Flame::File resultsFile = results.files[file_id]; - VersionFile& localFile = m_version.files[index]; - - // First check for blocked mods - if (resultsFile.version.downloadUrl.isEmpty()) { - BlockedMod blocked_mod; - blocked_mod.name = resultsFile.version.fileName; - blocked_mod.websiteUrl = QString("%1/download/%2").arg(resultsFile.pack.websiteUrl, QString::number(resultsFile.fileId)); - blocked_mod.hash = resultsFile.version.hash; - blocked_mod.matched = false; - blocked_mod.localPath = ""; - blocked_mod.targetFolder = resultsFile.targetFolder; - - m_blockedMods.append(blocked_mod); - - anyBlocked = true; - } else { - localFile.url = resultsFile.version.downloadUrl; - } - } - - m_modIdResolverTask.reset(); - - if (anyBlocked) { - qDebug() << "Blocked files found, displaying file list"; - - BlockedModsDialog message_dialog(m_parent, tr("Blocked files found"), - tr("The following files are not available for download in third party launchers.
" - "You will need to manually download them and add them to the instance."), - m_blockedMods); - - message_dialog.setModal(true); - - if (message_dialog.exec() == QDialog::Accepted) { - qDebug() << "Post dialog blocked mods list: " << m_blockedMods; - createInstance(); - } else { - abort(); - } - - } else { - createInstance(); - } -} - -void PackInstallTask::createInstance() -{ - setAbortable(false); - - setStatus(tr("Creating the instance...")); - QCoreApplication::processEvents(); - - auto instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg"); - auto instanceSettings = std::make_unique(instanceConfigPath); - - MinecraftInstance instance(m_globalSettings, std::move(instanceSettings), m_stagingPath); - auto components = instance.getPackProfile(); - components->buildingFromScratch(); - - for (auto target : m_version.targets) { - if (target.type == "game" && target.name == "minecraft") { - components->setComponentVersion("net.minecraft", target.version, true); - break; - } - } - - for (auto target : m_version.targets) { - if (target.type != "modloader") - continue; - - if (target.name == "forge") { - components->setComponentVersion("net.minecraftforge", target.version); - } else if (target.name == "fabric") { - components->setComponentVersion("net.fabricmc.fabric-loader", target.version); - } else if (target.name == "neoforge") { - components->setComponentVersion("net.neoforged", target.version); - } else if (target.name == "quilt") { - components->setComponentVersion("org.quiltmc.quilt-loader", target.version); - } - } - - // install any jar mods - QDir jarModsDir(FS::PathCombine(m_stagingPath, "minecraft", "jarmods")); - if (jarModsDir.exists()) { - QStringList jarMods; - - for (const auto& info : jarModsDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) { - jarMods.push_back(info.absoluteFilePath()); - } - - components->installJarMods(jarMods); - } - - components->saveNow(); - - instance.setName(name()); - instance.setIconKey(m_instIcon); - instance.setManagedPack("ftb", QString::number(m_pack.id), m_pack.name, QString::number(m_version.id), m_version.name); - - instance.saveNow(); - - onCreateInstanceSucceeded(); -} - -void PackInstallTask::onCreateInstanceSucceeded() -{ - downloadPack(); -} - -void PackInstallTask::downloadPack() -{ - setStatus(tr("Downloading mods...")); - setAbortable(false); - - auto jobPtr = makeShared(tr("Mod download"), APPLICATION->network()); - for (const auto& file : m_version.files) { - if (file.serverOnly || file.url.isEmpty()) - continue; - - auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path, file.name); - qDebug() << "Will try to download" << file.url << "to" << path; - - QFileInfo file_info(file.name); - - auto dl = Net::Download::makeFile(file.url, path); - if (!file.sha1.isEmpty()) { - dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, file.sha1)); - } - - jobPtr->addNetAction(dl); - } - - jobPtr->setMaxConcurrent(1); // FTB blocks multiple requests at a time - connect(jobPtr.get(), &NetJob::succeeded, this, &PackInstallTask::onModDownloadSucceeded); - connect(jobPtr.get(), &NetJob::failed, this, &PackInstallTask::onModDownloadFailed); - connect(jobPtr.get(), &NetJob::aborted, this, &PackInstallTask::abort); - connect(jobPtr.get(), &NetJob::progress, this, &PackInstallTask::setProgress); - - m_net_job = jobPtr; - - setAbortable(true); - jobPtr->start(); -} - -void PackInstallTask::onModDownloadSucceeded() -{ - m_net_job.reset(); - if (!m_blockedMods.isEmpty()) { - copyBlockedMods(); - } - emitSucceeded(); -} - -void PackInstallTask::onManifestDownloadFailed(QString reason) -{ - m_net_job.reset(); - emitFailed(reason); -} -void PackInstallTask::onResolveModsFailed(QString reason) -{ - m_net_job.reset(); - emitFailed(reason); -} -void PackInstallTask::onCreateInstanceFailed(QString reason) -{ - emitFailed(reason); -} -void PackInstallTask::onModDownloadFailed(QString reason) -{ - m_net_job.reset(); - emitFailed(reason); -} - -/// @brief copy the matched blocked mods to the instance staging area -void PackInstallTask::copyBlockedMods() -{ - setStatus(tr("Copying Blocked Mods...")); - setAbortable(false); - int i = 0; - int total = m_blockedMods.length(); - setProgress(i, total); - for (const auto& mod : m_blockedMods) { - if (!mod.matched) { - qDebug() << mod.name << "was not matched to a local file, skipping copy"; - continue; - } - - auto dest_path = FS::PathCombine(m_stagingPath, ".minecraft", mod.targetFolder, mod.name); - - setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total))); - - qDebug() << "Will try to copy" << mod.localPath << "to" << dest_path; - - if (!FS::copy(mod.localPath, dest_path)()) { - qDebug() << "Copy of" << mod.localPath << "to" << dest_path << "Failed"; - } - - i++; - setProgress(i, total); - } - - setAbortable(true); -} - -} // namespace FTB diff --git a/launcher/modplatform/ftb/FTBPackInstallTask.h b/launcher/modplatform/ftb/FTBPackInstallTask.h deleted file mode 100644 index 49d2bb991..000000000 --- a/launcher/modplatform/ftb/FTBPackInstallTask.h +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 flowln - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2020-2021 Jamie Mansfield - * Copyright 2020-2021 Petr Mrazek - * - * 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 "FTBPackManifest.h" - -#include "InstanceTask.h" -#include "QObjectPtr.h" -#include "modplatform/flame/FileResolvingTask.h" -#include "net/NetJob.h" -#include "ui/dialogs/BlockedModsDialog.h" - -#include - -namespace FTB { - -class PackInstallTask final : public InstanceTask { - Q_OBJECT - - public: - explicit PackInstallTask(Modpack pack, QString version, QWidget* parent = nullptr); - ~PackInstallTask() override = default; - - bool abort() override; - - protected: - void executeTask() override; - - private slots: - void onManifestDownloadSucceeded(QByteArray* responsePtr); - void onResolveModsSucceeded(); - void onCreateInstanceSucceeded(); - void onModDownloadSucceeded(); - - void onManifestDownloadFailed(QString reason); - void onResolveModsFailed(QString reason); - void onCreateInstanceFailed(QString reason); - void onModDownloadFailed(QString reason); - - private: - void resolveMods(); - void createInstance(); - void downloadPack(); - void copyBlockedMods(); - - private: - NetJob::Ptr m_net_job = nullptr; - shared_qobject_ptr m_modIdResolverTask = nullptr; - - QList m_fileIds; - - Modpack m_pack; - QString m_versionName; - Version m_version; - - QMap m_filesToCopy; - QList m_blockedMods; - - // FIXME: nuke - QWidget* m_parent; -}; - -} // namespace FTB diff --git a/launcher/modplatform/ftb/FTBPackManifest.cpp b/launcher/modplatform/ftb/FTBPackManifest.cpp deleted file mode 100644 index da633a117..000000000 --- a/launcher/modplatform/ftb/FTBPackManifest.cpp +++ /dev/null @@ -1,184 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - 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 . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2020 Jamie Mansfield - * Copyright 2020-2021 Petr Mrazek - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "FTBPackManifest.h" - -#include "Json.h" - -static void loadSpecs(FTB::Specs& s, QJsonObject& obj) -{ - s.id = Json::requireInteger(obj, "id"); - s.minimum = Json::requireInteger(obj, "minimum"); - s.recommended = Json::requireInteger(obj, "recommended"); -} - -static void loadTag(FTB::Tag& t, QJsonObject& obj) -{ - t.id = Json::requireInteger(obj, "id"); - t.name = Json::requireString(obj, "name"); -} - -static void loadArt(FTB::Art& a, QJsonObject& obj) -{ - a.id = Json::requireInteger(obj, "id"); - a.url = Json::requireString(obj, "url"); - a.type = Json::requireString(obj, "type"); - a.width = Json::requireInteger(obj, "width"); - a.height = Json::requireInteger(obj, "height"); - a.compressed = Json::requireBoolean(obj, "compressed"); - a.sha1 = Json::requireString(obj, "sha1"); - a.size = obj["size"].toInt(); - a.updated = Json::requireInteger(obj, "updated"); -} - -static void loadAuthor(FTB::Author& a, QJsonObject& obj) -{ - a.id = Json::requireInteger(obj, "id"); - a.name = Json::requireString(obj, "name"); - a.type = Json::requireString(obj, "type"); - a.website = Json::requireString(obj, "website"); - a.updated = Json::requireInteger(obj, "updated"); -} - -static void loadVersionInfo(FTB::VersionInfo& v, QJsonObject& obj) -{ - v.id = Json::requireInteger(obj, "id"); - v.name = Json::requireString(obj, "name"); - v.type = Json::requireString(obj, "type"); - v.updated = Json::requireInteger(obj, "updated"); - auto specs = Json::requireObject(obj, "specs"); - loadSpecs(v.specs, specs); -} - -void FTB::loadModpack(FTB::Modpack& m, QJsonObject& obj) -{ - m.id = Json::requireInteger(obj, "id"); - m.name = Json::requireString(obj, "name"); - m.safeName = Json::requireString(obj, "name").replace(QRegularExpression("[^A-Za-z0-9]"), "").toLower() + ".png"; - m.synopsis = Json::requireString(obj, "synopsis"); - m.description = Json::requireString(obj, "description"); - m.type = Json::requireString(obj, "type"); - m.featured = Json::requireBoolean(obj, "featured"); - m.installs = Json::requireInteger(obj, "installs"); - m.plays = Json::requireInteger(obj, "plays"); - m.updated = Json::requireInteger(obj, "updated"); - m.refreshed = obj["refreshed"].toInt(); - auto artArr = Json::requireArray(obj, "art"); - for (QJsonValueRef artRaw : artArr) { - auto artObj = Json::requireObject(artRaw); - FTB::Art art; - loadArt(art, artObj); - m.art.append(art); - } - auto authorArr = Json::requireArray(obj, "authors"); - for (QJsonValueRef authorRaw : authorArr) { - auto authorObj = Json::requireObject(authorRaw); - FTB::Author author; - loadAuthor(author, authorObj); - m.authors.append(author); - } - auto versionArr = Json::requireArray(obj, "versions"); - for (QJsonValueRef versionRaw : versionArr) { - auto versionObj = Json::requireObject(versionRaw); - FTB::VersionInfo version; - loadVersionInfo(version, versionObj); - m.versions.append(version); - } - auto tagArr = Json::requireArray(obj, "tags"); - for (QJsonValueRef tagRaw : tagArr) { - auto tagObj = Json::requireObject(tagRaw); - FTB::Tag tag; - loadTag(tag, tagObj); - m.tags.append(tag); - } - m.updated = Json::requireInteger(obj, "updated"); -} - -static void loadVersionTarget(FTB::VersionTarget& a, QJsonObject& obj) -{ - a.id = Json::requireInteger(obj, "id"); - a.name = Json::requireString(obj, "name"); - a.type = Json::requireString(obj, "type"); - a.version = Json::requireString(obj, "version"); - a.updated = Json::requireInteger(obj, "updated"); -} - -static void loadVersionFile(FTB::VersionFile& a, QJsonObject& obj) -{ - a.id = Json::requireInteger(obj, "id"); - a.type = Json::requireString(obj, "type"); - a.path = Json::requireString(obj, "path"); - a.name = Json::requireString(obj, "name"); - a.version = Json::requireString(obj, "version"); - a.url = obj["url"].toString(); // optional - a.sha1 = Json::requireString(obj, "sha1"); - a.size = obj["size"].toInt(); - a.clientOnly = Json::requireBoolean(obj, "clientonly"); - a.serverOnly = Json::requireBoolean(obj, "serveronly"); - a.optional = Json::requireBoolean(obj, "optional"); - a.updated = Json::requireInteger(obj, "updated"); - auto curseforgeObj = obj["curseforge"].toObject(); // optional - a.curseforge.project_id = curseforgeObj["project"].toInt(); - a.curseforge.file_id = curseforgeObj["file"].toInt(); -} - -void FTB::loadVersion(FTB::Version& m, QJsonObject& obj) -{ - m.id = Json::requireInteger(obj, "id"); - m.parent = Json::requireInteger(obj, "parent"); - m.name = Json::requireString(obj, "name"); - m.type = Json::requireString(obj, "type"); - m.installs = Json::requireInteger(obj, "installs"); - m.plays = Json::requireInteger(obj, "plays"); - m.updated = Json::requireInteger(obj, "updated"); - m.refreshed = obj["refreshed"].toInt(); - auto specs = Json::requireObject(obj, "specs"); - loadSpecs(m.specs, specs); - auto targetArr = Json::requireArray(obj, "targets"); - for (QJsonValueRef targetRaw : targetArr) { - auto versionObj = Json::requireObject(targetRaw); - FTB::VersionTarget target; - loadVersionTarget(target, versionObj); - m.targets.append(target); - } - auto fileArr = Json::requireArray(obj, "files"); - for (QJsonValueRef fileRaw : fileArr) { - auto fileObj = Json::requireObject(fileRaw); - FTB::VersionFile file; - loadVersionFile(file, fileObj); - m.files.append(file); - } -} diff --git a/launcher/modplatform/ftb/FTBPackManifest.h b/launcher/modplatform/ftb/FTBPackManifest.h deleted file mode 100644 index 704bde3e5..000000000 --- a/launcher/modplatform/ftb/FTBPackManifest.h +++ /dev/null @@ -1,157 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - 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 . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2020-2021 Jamie Mansfield - * Copyright 2020 Petr Mrazek - * - * 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 - -namespace FTB { - -struct Specs { - int id; - int minimum; - int recommended; -}; - -struct Tag { - int id; - QString name; -}; - -struct Art { - int id; - QString url; - QString type; - int width; - int height; - bool compressed; - QString sha1; - int size; - int64_t updated; -}; - -struct Author { - int id; - QString name; - QString type; - QString website; - int64_t updated; -}; - -struct VersionInfo { - int id; - QString name; - QString type; - int64_t updated; - Specs specs; -}; - -struct Modpack { - int id; - QString name; - QString synopsis; - QString description; - QString type; - bool featured; - int installs; - int plays; - int64_t updated; - int64_t refreshed; - QVector art; - QVector authors; - QVector versions; - QVector tags; - QString safeName; -}; - -struct VersionTarget { - int id; - QString type; - QString name; - QString version; - int64_t updated; -}; - -struct VersionFileCurseForge { - int project_id; - int file_id; -}; - -struct VersionFile { - int id; - QString type; - QString path; - QString name; - QString version; - QString url; - QString sha1; - int size; - bool clientOnly; - bool serverOnly; - bool optional; - int64_t updated; - VersionFileCurseForge curseforge; -}; - -struct Version { - int id; - int parent; - QString name; - QString type; - int installs; - int plays; - int64_t updated; - int64_t refreshed; - Specs specs; - QVector targets; - QVector files; -}; - -struct VersionChangelog { - QString content; - int64_t updated; -}; - -void loadModpack(Modpack& m, QJsonObject& obj); - -void loadVersion(Version& m, QJsonObject& obj); -} // namespace FTB - -Q_DECLARE_METATYPE(FTB::Modpack) diff --git a/launcher/modplatform/helpers/ExportToModList.h b/launcher/modplatform/helpers/ExportToModList.h index 7cbe730f2..ab7797fe6 100644 --- a/launcher/modplatform/helpers/ExportToModList.h +++ b/launcher/modplatform/helpers/ExportToModList.h @@ -23,9 +23,7 @@ namespace ExportToModList { enum Formats { HTML, MARKDOWN, PLAINTXT, JSON, CSV, CUSTOM }; -enum OptionalDataValue { None = 0, Authors = 1 << 0, Url = 1 << 1, Version = 1 << 2, FileName = 1 << 3 }; -Q_DECLARE_FLAGS(OptionalData, OptionalDataValue) - +enum OptionalData { Authors = 1 << 0, Url = 1 << 1, Version = 1 << 2, FileName = 1 << 3 }; QString exportToModList(QList mods, Formats format, OptionalData extraData); QString exportToModList(QList mods, QString lineTemplate); } // namespace ExportToModList diff --git a/launcher/modplatform/helpers/OverrideUtils.cpp b/launcher/modplatform/helpers/OverrideUtils.cpp index e64b30ffe..d5958a59d 100644 --- a/launcher/modplatform/helpers/OverrideUtils.cpp +++ b/launcher/modplatform/helpers/OverrideUtils.cpp @@ -16,7 +16,7 @@ void createOverrides(const QString& name, const QString& parent_folder, const QS QFile file(file_path); if (!file.open(QFile::WriteOnly)) { - qWarning() << "Failed to open file" << file.fileName() << "for writing:" << file.errorString(); + qWarning() << "Failed to open file '" << file.fileName() << "' for writing!"; return; } @@ -47,7 +47,7 @@ QStringList readOverrides(const QString& name, const QString& parent_folder) QStringList previous_overrides; if (!file.open(QFile::ReadOnly)) { - qWarning() << "Failed to open file" << file.fileName() << "for reading:" << file.errorString(); + qWarning() << "Failed to open file '" << file.fileName() << "' for reading!"; return previous_overrides; } diff --git a/launcher/modplatform/import_ftb/PackInstallTask.cpp b/launcher/modplatform/import_ftb/PackInstallTask.cpp index 659c5d2ed..c9e8be34b 100644 --- a/launcher/modplatform/import_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/import_ftb/PackInstallTask.cpp @@ -49,55 +49,66 @@ void PackInstallTask::copySettings() { setStatus(tr("Copying settings...")); progress(2, 2); - QString instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg"); - MinecraftInstance instance(m_globalSettings, std::make_unique(instanceConfigPath), m_stagingPath); + auto instanceSettings = std::make_shared(instanceConfigPath); + instanceSettings->suspendSave(); + MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); + instance.settings()->set("InstanceType", "OneSix"); + instance.settings()->set("totalTimePlayed", m_pack.totalPlayTime / 1000); - { - SettingsObject::Lock lock(instance.settings()); - instance.settings()->set("InstanceType", "OneSix"); - instance.settings()->set("totalTimePlayed", m_pack.totalPlayTime / 1000); - - if (m_pack.jvmArgs.isValid() && !m_pack.jvmArgs.toString().isEmpty()) { - instance.settings()->set("OverrideJavaArgs", true); - instance.settings()->set("JvmArgs", m_pack.jvmArgs.toString()); - } - - auto* components = instance.getPackProfile(); - components->buildingFromScratch(); - components->setComponentVersion("net.minecraft", m_pack.mcVersion, true); - - auto modloader = m_pack.loaderType; - if (modloader.has_value()) { - switch (modloader.value()) { - case ModPlatform::NeoForge: { - components->setComponentVersion("net.neoforged", m_pack.loaderVersion, true); - break; - } - case ModPlatform::Forge: { - components->setComponentVersion("net.minecraftforge", m_pack.loaderVersion, true); - break; - } - case ModPlatform::Fabric: { - components->setComponentVersion("net.fabricmc.fabric-loader", m_pack.loaderVersion, true); - break; - } - case ModPlatform::Quilt: { - components->setComponentVersion("org.quiltmc.quilt-loader", m_pack.loaderVersion, true); - break; - } - default: - break; - } - } - components->saveNow(); - - instance.setName(name()); - if (m_instIcon == "default") { - m_instIcon = "ftb_logo"; - } - instance.setIconKey(m_instIcon); + if (m_pack.jvmArgs.isValid() && !m_pack.jvmArgs.toString().isEmpty()) { + instance.settings()->set("OverrideJavaArgs", true); + instance.settings()->set("JvmArgs", m_pack.jvmArgs.toString()); } + + auto components = instance.getPackProfile(); + components->buildingFromScratch(); + components->setComponentVersion("net.minecraft", m_pack.mcVersion, true); + + auto modloader = m_pack.loaderType; + if (modloader.has_value()) + switch (modloader.value()) { + case ModPlatform::NeoForge: { + components->setComponentVersion("net.neoforged", m_pack.loaderVersion, true); + break; + } + case ModPlatform::Forge: { + components->setComponentVersion("net.minecraftforge", m_pack.loaderVersion, true); + break; + } + case ModPlatform::Fabric: { + components->setComponentVersion("net.fabricmc.fabric-loader", m_pack.loaderVersion, true); + break; + } + case ModPlatform::Quilt: { + components->setComponentVersion("org.quiltmc.quilt-loader", m_pack.loaderVersion, true); + break; + } + case ModPlatform::Cauldron: + break; + case ModPlatform::LiteLoader: + 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(); + + instance.setName(name()); + if (m_instIcon == "default") + m_instIcon = "ftb_logo"; + instance.setIconKey(m_instIcon); + instanceSettings->resumeSave(); + emitSucceeded(); } diff --git a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp index 7d1807ab3..c5701da3c 100644 --- a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp @@ -53,18 +53,13 @@ void PackFetchTask::fetch() QUrl publicPacksUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/modpacks.xml"); qDebug() << "Downloading public version info from" << publicPacksUrl.toString(); - - auto [publicAction, publicResponse] = Net::ApiDownload::makeByteArray(publicPacksUrl); - jobPtr->addNetAction(publicAction); + jobPtr->addNetAction(Net::ApiDownload::makeByteArray(publicPacksUrl, publicModpacksXmlFileData)); QUrl thirdPartyUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/thirdparty.xml"); qDebug() << "Downloading thirdparty version info from" << thirdPartyUrl.toString(); + jobPtr->addNetAction(Net::Download::makeByteArray(thirdPartyUrl, thirdPartyModpacksXmlFileData)); - auto [thirdPartyAction, thirdPartyResponse] = Net::Download::makeByteArray(thirdPartyUrl); - jobPtr->addNetAction(thirdPartyAction); - - connect(jobPtr.get(), &NetJob::succeeded, this, - [this, publicResponse, thirdPartyResponse] { fileDownloadFinished(publicResponse, thirdPartyResponse); }); + connect(jobPtr.get(), &NetJob::succeeded, this, &PackFetchTask::fileDownloadFinished); connect(jobPtr.get(), &NetJob::failed, this, &PackFetchTask::fileDownloadFailed); connect(jobPtr.get(), &NetJob::aborted, this, &PackFetchTask::fileDownloadAborted); @@ -76,10 +71,9 @@ void PackFetchTask::fetchPrivate(const QStringList& toFetch) QString privatePackBaseUrl = BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/%1.xml"; for (auto& packCode : toFetch) { + auto data = std::make_shared(); NetJob* job = new NetJob("Fetching private pack", m_network); - - auto [action, data] = Net::ApiDownload::makeByteArray(privatePackBaseUrl.arg(packCode)); - job->addNetAction(action); + job->addNetAction(Net::ApiDownload::makeByteArray(privatePackBaseUrl.arg(packCode), data)); job->setAskRetry(false); connect(job, &NetJob::succeeded, this, [this, job, data, packCode] { @@ -91,15 +85,20 @@ void PackFetchTask::fetchPrivate(const QStringList& toFetch) } job->deleteLater(); + + data->clear(); }); - connect(job, &NetJob::failed, this, [this, job, packCode](QString reason) { + connect(job, &NetJob::failed, this, [this, job, packCode, data](QString reason) { emit privateFileDownloadFailed(reason, packCode); job->deleteLater(); + + data->clear(); }); - connect(job, &NetJob::aborted, this, [this, job] { + connect(job, &NetJob::aborted, this, [this, job, data] { job->deleteLater(); + data->clear(); emit aborted(); }); @@ -108,21 +107,20 @@ void PackFetchTask::fetchPrivate(const QStringList& toFetch) } } -void PackFetchTask::fileDownloadFinished(QByteArray* publicPtr, QByteArray* thirdPartyPtr) +void PackFetchTask::fileDownloadFinished() { + jobPtr.reset(); + QStringList failedLists; - if (!parseAndAddPacks(*publicPtr, PackType::Public, publicPacks)) { + if (!parseAndAddPacks(*publicModpacksXmlFileData, PackType::Public, publicPacks)) { failedLists.append(tr("Public Packs")); } - if (!parseAndAddPacks(*thirdPartyPtr, PackType::ThirdParty, thirdPartyPacks)) { + if (!parseAndAddPacks(*thirdPartyModpacksXmlFileData, PackType::ThirdParty, thirdPartyPacks)) { failedLists.append(tr("Third Party Packs")); } - // NOTE(TheKodeToad): we don't want to reset the jobPtr earlier as it may invalidate the responses! - jobPtr.reset(); - if (failedLists.size() > 0) { emit failed(tr("Failed to download some pack lists: %1").arg(failedLists.join("\n- "))); } else { @@ -141,6 +139,7 @@ bool PackFetchTask::parseAndAddPacks(QByteArray& data, PackType packType, Modpac if (!doc.setContent(data, false, &errorMsg, &errorLine, &errorCol)) { auto fullErrMsg = QString("Failed to fetch modpack data: %1 %2:%3!").arg(errorMsg).arg(errorLine).arg(errorCol); qWarning() << fullErrMsg; + data.clear(); return false; } diff --git a/launcher/modplatform/legacy_ftb/PackFetchTask.h b/launcher/modplatform/legacy_ftb/PackFetchTask.h index 3e1035b79..4c7a8f6aa 100644 --- a/launcher/modplatform/legacy_ftb/PackFetchTask.h +++ b/launcher/modplatform/legacy_ftb/PackFetchTask.h @@ -3,6 +3,7 @@ #include #include #include +#include #include "PackHelpers.h" #include "net/NetJob.h" @@ -12,22 +13,25 @@ class PackFetchTask : public QObject { Q_OBJECT public: - PackFetchTask(QNetworkAccessManager* network) : QObject(nullptr), m_network(network) {}; + PackFetchTask(shared_qobject_ptr network) : QObject(nullptr), m_network(network) {}; virtual ~PackFetchTask() = default; void fetch(); void fetchPrivate(const QStringList& toFetch); private: - QNetworkAccessManager* m_network; + shared_qobject_ptr m_network; NetJob::Ptr jobPtr; + std::shared_ptr publicModpacksXmlFileData = std::make_shared(); + std::shared_ptr thirdPartyModpacksXmlFileData = std::make_shared(); + bool parseAndAddPacks(QByteArray& data, PackType packType, ModpackList& list); ModpackList publicPacks; ModpackList thirdPartyPacks; protected slots: - void fileDownloadFinished(QByteArray* publicResponse, QByteArray* thirdPartyResponse); + void fileDownloadFinished(); void fileDownloadFailed(QString reason); void fileDownloadAborted(); diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp index 8220676fc..33c0c38b6 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp @@ -52,7 +52,7 @@ namespace LegacyFTB { -PackInstallTask::PackInstallTask(QNetworkAccessManager* network, const Modpack& pack, QString version) +PackInstallTask::PackInstallTask(shared_qobject_ptr network, const Modpack& pack, QString version) { m_pack = pack; m_version = version; @@ -133,79 +133,79 @@ void PackInstallTask::install() } QString instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg"); - MinecraftInstance instance(m_globalSettings, std::make_unique(instanceConfigPath), m_stagingPath); - { - SettingsObject::Lock lock(instance.settings()); + auto instanceSettings = std::make_shared(instanceConfigPath); + instanceSettings->suspendSave(); - auto components = instance.getPackProfile(); - components->buildingFromScratch(); - components->setComponentVersion("net.minecraft", m_pack.mcVersion, true); + MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); + auto components = instance.getPackProfile(); + components->buildingFromScratch(); + components->setComponentVersion("net.minecraft", m_pack.mcVersion, true); - bool fallback = true; + bool fallback = true; - // handle different versions - QFile packJson(m_stagingPath + "/minecraft/pack.json"); - QDir jarmodDir = QDir(m_stagingPath + "/unzip/instMods"); - if (packJson.exists()) { - if (packJson.open(QIODevice::ReadOnly | QIODevice::Text)) { - QJsonDocument doc = QJsonDocument::fromJson(packJson.readAll()); - packJson.close(); + // handle different versions + QFile packJson(m_stagingPath + "/minecraft/pack.json"); + QDir jarmodDir = QDir(m_stagingPath + "/unzip/instMods"); + if (packJson.exists()) { + if (packJson.open(QIODevice::ReadOnly | QIODevice::Text)) { + QJsonDocument doc = QJsonDocument::fromJson(packJson.readAll()); + packJson.close(); - // we only care about the libs - QJsonArray libs = doc.object().value("libraries").toArray(); + // we only care about the libs + QJsonArray libs = doc.object().value("libraries").toArray(); - for (const auto& value : libs) { - QString nameValue = value.toObject().value("name").toString(); - if (!nameValue.startsWith("net.minecraftforge")) { - continue; - } - - GradleSpecifier forgeVersion(nameValue); - - components->setComponentVersion("net.minecraftforge", - forgeVersion.version().replace(m_pack.mcVersion, "").replace("-", "")); - packJson.remove(); - fallback = false; - break; + for (const auto& value : libs) { + QString nameValue = value.toObject().value("name").toString(); + if (!nameValue.startsWith("net.minecraftforge")) { + continue; } - } else { - qWarning() << "Failed to open file" << packJson.fileName() << "for reading:" << packJson.errorString(); + + GradleSpecifier forgeVersion(nameValue); + + components->setComponentVersion("net.minecraftforge", + forgeVersion.version().replace(m_pack.mcVersion, "").replace("-", "")); + packJson.remove(); + fallback = false; + break; } + } else { + qWarning() << "Failed to open file '" << packJson.fileName() << "' for reading!"; } - - if (jarmodDir.exists()) { - qDebug() << "Found jarmods, installing..."; - - QStringList jarmods; - for (auto info : jarmodDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) { - qDebug() << "Jarmod:" << info.fileName(); - jarmods.push_back(info.absoluteFilePath()); - } - - components->installJarMods(jarmods); - fallback = false; - } - - // just nuke unzip directory, it s not needed anymore - FS::deletePath(m_stagingPath + "/unzip"); - - if (fallback) { - // TODO: Some fallback mechanism... or just keep failing! - emitFailed(tr("No installation method found!")); - return; - } - - components->saveNow(); - - progress(4, 4); - - instance.setName(name()); - if (m_instIcon == "default") { - m_instIcon = "ftb_logo"; - } - instance.setIconKey(m_instIcon); } + if (jarmodDir.exists()) { + qDebug() << "Found jarmods, installing..."; + + QStringList jarmods; + for (auto info : jarmodDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) { + qDebug() << "Jarmod:" << info.fileName(); + jarmods.push_back(info.absoluteFilePath()); + } + + components->installJarMods(jarmods); + fallback = false; + } + + // just nuke unzip directory, it s not needed anymore + FS::deletePath(m_stagingPath + "/unzip"); + + if (fallback) { + // TODO: Some fallback mechanism... or just keep failing! + emitFailed(tr("No installation method found!")); + return; + } + + components->saveNow(); + + progress(4, 4); + + instance.setName(name()); + if (m_instIcon == "default") { + m_instIcon = "ftb_logo"; + } + instance.setIconKey(m_instIcon); + instanceSettings->resumeSave(); + emitSucceeded(); } diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.h b/launcher/modplatform/legacy_ftb/PackInstallTask.h index 98777214f..6db6cb712 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.h +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.h @@ -14,7 +14,7 @@ class PackInstallTask : public InstanceTask { Q_OBJECT public: - explicit PackInstallTask(QNetworkAccessManager* network, const Modpack& pack, QString version); + explicit PackInstallTask(shared_qobject_ptr network, const Modpack& pack, QString version); virtual ~PackInstallTask() {} bool canAbort() const override { return true; } @@ -35,7 +35,7 @@ class PackInstallTask : public InstanceTask { void onUnzipCanceled(); private: /* data */ - QNetworkAccessManager* m_network; + shared_qobject_ptr m_network; bool abortable = false; QFuture> m_extractFuture; QFutureWatcher> m_extractFutureWatcher; diff --git a/launcher/modplatform/modrinth/ModrinthAPI.cpp b/launcher/modplatform/modrinth/ModrinthAPI.cpp index d5bea52bc..83d7521f0 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.cpp +++ b/launcher/modplatform/modrinth/ModrinthAPI.cpp @@ -9,19 +9,19 @@ #include "net/ApiDownload.h" #include "net/ApiUpload.h" #include "net/NetJob.h" +#include "net/Upload.h" -std::pair ModrinthAPI::currentVersion(const QString& hash, const QString& hash_format) const +Task::Ptr ModrinthAPI::currentVersion(QString hash, QString hash_format, std::shared_ptr response) { auto netJob = makeShared(QString("Modrinth::GetCurrentVersion"), APPLICATION->network()); - auto [action, response] = - Net::ApiDownload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1?algorithm=%2").arg(hash, hash_format)); - netJob->addNetAction(action); + netJob->addNetAction(Net::ApiDownload::makeByteArray( + QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1?algorithm=%2").arg(hash, hash_format), response)); - return { netJob, response }; + return netJob; } -std::pair ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_format) const +Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_format, std::shared_ptr response) { auto netJob = makeShared(QString("Modrinth::GetCurrentVersions"), APPLICATION->network()); @@ -33,24 +33,23 @@ std::pair ModrinthAPI::currentVersions(const QStringList QJsonDocument body(body_obj); auto body_raw = body.toJson(); - auto [action, response] = Net::ApiUpload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files"), body_raw); - netJob->addNetAction(action); + netJob->addNetAction(Net::ApiUpload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files"), response, body_raw)); netJob->setAskRetry(false); - return { netJob, response }; + return netJob; } -std::pair ModrinthAPI::latestVersion(const QString& hash, - const QString& hash_format, - std::optional> mcVersions, - std::optional loaders) const +Task::Ptr ModrinthAPI::latestVersion(QString hash, + QString hash_format, + std::optional> mcVersions, + std::optional loaders, + std::shared_ptr response) { auto netJob = makeShared(QString("Modrinth::GetLatestVersion"), APPLICATION->network()); QJsonObject body_obj; - if (loaders.has_value()) { + if (loaders.has_value()) Json::writeStringList(body_obj, "loaders", getModLoaderStrings(loaders.value())); - } if (mcVersions.has_value()) { QStringList game_versions; @@ -63,17 +62,17 @@ std::pair ModrinthAPI::latestVersion(const QString& hash QJsonDocument body(body_obj); auto body_raw = body.toJson(); - auto [action, response] = Net::ApiUpload::makeByteArray( - QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1/update?algorithm=%2").arg(hash, hash_format), body_raw); - netJob->addNetAction(action); + netJob->addNetAction(Net::ApiUpload::makeByteArray( + QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1/update?algorithm=%2").arg(hash, hash_format), response, body_raw)); - return { netJob, response }; + return netJob; } -std::pair ModrinthAPI::latestVersions(const QStringList& hashes, - const QString& hash_format, - std::optional> mcVersions, - std::optional loaders) const +Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes, + QString hash_format, + std::optional> mcVersions, + std::optional loaders, + std::shared_ptr response) { auto netJob = makeShared(QString("Modrinth::GetLatestVersions"), APPLICATION->network()); @@ -82,9 +81,8 @@ std::pair ModrinthAPI::latestVersions(const QStringList& Json::writeStringList(body_obj, "hashes", hashes); Json::writeString(body_obj, "algorithm", hash_format); - if (loaders.has_value()) { + if (loaders.has_value()) Json::writeStringList(body_obj, "loaders", getModLoaderStrings(loaders.value())); - } if (mcVersions.has_value()) { QStringList game_versions; @@ -96,48 +94,46 @@ std::pair ModrinthAPI::latestVersions(const QStringList& QJsonDocument body(body_obj); auto body_raw = body.toJson(); - auto [action, response] = Net::ApiUpload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files/update"), body_raw); - netJob->addNetAction(action); - return { netJob, response }; + netJob->addNetAction( + Net::ApiUpload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files/update"), response, body_raw)); + + return netJob; } -std::pair ModrinthAPI::getProjects(QStringList addonIds) const +Task::Ptr ModrinthAPI::getProjects(QStringList addonIds, std::shared_ptr response) const { auto netJob = makeShared(QString("Modrinth::GetProjects"), APPLICATION->network()); auto searchUrl = getMultipleModInfoURL(addonIds); - auto [action, response] = Net::ApiDownload::makeByteArray(QUrl(searchUrl)); - netJob->addNetAction(action); + netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response)); - return { netJob, response }; + return netJob; } QList ModrinthAPI::getSortingMethods() const { // https://docs.modrinth.com/api-spec/#tag/projects/operation/searchProjects - return { { .index = 1, .name = "relevance", .readable_name = QObject::tr("Sort by Relevance") }, - { .index = 2, .name = "downloads", .readable_name = QObject::tr("Sort by Downloads") }, - { .index = 3, .name = "follows", .readable_name = QObject::tr("Sort by Follows") }, - { .index = 4, .name = "newest", .readable_name = QObject::tr("Sort by Newest") }, - { .index = 5, .name = "updated", .readable_name = QObject::tr("Sort by Last Updated") } }; + return { { 1, "relevance", QObject::tr("Sort by Relevance") }, + { 2, "downloads", QObject::tr("Sort by Downloads") }, + { 3, "follows", QObject::tr("Sort by Follows") }, + { 4, "newest", QObject::tr("Sort by Newest") }, + { 5, "updated", QObject::tr("Sort by Last Updated") } }; } -std::pair ModrinthAPI::getModCategories() +Task::Ptr ModrinthAPI::getModCategories(std::shared_ptr response) { auto netJob = makeShared(QString("Modrinth::GetCategories"), APPLICATION->network()); - auto [action, response] = Net::ApiDownload::makeByteArray(QUrl(BuildConfig.MODRINTH_PROD_URL + "/tag/category")); - netJob->addNetAction(action); - QObject::connect(netJob.get(), &Task::failed, [](const QString& msg) { qDebug() << "Modrinth failed to get categories:" << msg; }); - - return { netJob, response }; + netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(BuildConfig.MODRINTH_PROD_URL + "/tag/category"), response)); + QObject::connect(netJob.get(), &Task::failed, [](QString msg) { qDebug() << "Modrinth failed to get categories:" << msg; }); + return netJob; } -QList ModrinthAPI::loadCategories(const QByteArray& response, const QString& projectType) +QList ModrinthAPI::loadCategories(std::shared_ptr response, QString projectType) { QList categories; QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { qWarning() << "Error while parsing JSON response from categories at" << parse_error.offset << "reason:" << parse_error.errorString(); @@ -151,9 +147,8 @@ QList ModrinthAPI::loadCategories(const QByteArray& respo for (auto val : arr) { auto cat = Json::requireObject(val); auto name = Json::requireString(cat, "name"); - if (cat["project_type"].toString() == projectType) { - categories.push_back({ .name = name, .id = name }); - } + if (cat["project_type"].toString() == projectType) + categories.push_back({ name, name }); } } catch (Json::JsonException& e) { @@ -164,7 +159,7 @@ QList ModrinthAPI::loadCategories(const QByteArray& respo return categories; } -QList ModrinthAPI::loadModCategories(const QByteArray& response) +QList ModrinthAPI::loadModCategories(std::shared_ptr response) { return loadCategories(response, "mod"); }; diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index 731ac1b09..1990350d4 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -5,66 +5,68 @@ #pragma once #include "BuildConfig.h" +#include "Json.h" #include "modplatform/ModIndex.h" #include "modplatform/ResourceAPI.h" #include "modplatform/modrinth/ModrinthPackIndex.h" #include -#include class ModrinthAPI : public ResourceAPI { public: - std::pair currentVersion(const QString& hash, const QString& hash_format) const; + Task::Ptr currentVersion(QString hash, QString hash_format, std::shared_ptr response); - std::pair currentVersions(const QStringList& hashes, QString hash_format) const; + Task::Ptr currentVersions(const QStringList& hashes, QString hash_format, std::shared_ptr response); - std::pair latestVersion(const QString& hash, - const QString& hash_format, - std::optional> mcVersions, - std::optional loaders) const; + Task::Ptr latestVersion(QString hash, + QString hash_format, + std::optional> mcVersions, + std::optional loaders, + std::shared_ptr response); - std::pair latestVersions(const QStringList& hashes, - const QString& hash_format, - std::optional> mcVersions, - std::optional loaders) const; + Task::Ptr latestVersions(const QStringList& hashes, + QString hash_format, + std::optional> mcVersions, + std::optional loaders, + std::shared_ptr response); - std::pair getProjects(QStringList addonIds) const override; + Task::Ptr getProjects(QStringList addonIds, std::shared_ptr response) const override; - static std::pair getModCategories(); - static QList loadCategories(const QByteArray& response, const QString& projectType); - static QList loadModCategories(const QByteArray& response); + static Task::Ptr getModCategories(std::shared_ptr response); + static QList loadCategories(std::shared_ptr response, QString projectType); + static QList loadModCategories(std::shared_ptr response); public: auto getSortingMethods() const -> QList override; - static auto getAuthorURL(const QString& name) -> QString { return "https://modrinth.com/user/" + name; }; + inline auto getAuthorURL(const QString& name) const -> QString { return "https://modrinth.com/user/" + name; }; - static auto getModLoaderStrings(const ModPlatform::ModLoaderTypes types) -> QStringList + static auto getModLoaderStrings(const ModPlatform::ModLoaderTypes types) -> const QStringList { QStringList l; for (auto loader : { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Fabric, ModPlatform::Quilt, ModPlatform::LiteLoader, ModPlatform::DataPack, ModPlatform::Babric, ModPlatform::BTA, ModPlatform::LegacyFabric, ModPlatform::Ornithe, ModPlatform::Rift }) { - if ((types & loader) != 0U) { + if (types & loader) { l << getModLoaderAsString(loader); } } return l; } - static auto getModLoaderFilters(ModPlatform::ModLoaderTypes types) -> QString + static auto getModLoaderFilters(ModPlatform::ModLoaderTypes types) -> const QString { QStringList l; - for (const auto& loader : getModLoaderStrings(types)) { + for (auto loader : getModLoaderStrings(types)) { l << QString("\"categories:%1\"").arg(loader); } return l.join(','); } - static auto getCategoriesFilters(const QStringList& categories) -> QString + static auto getCategoriesFilters(QStringList categories) -> const QString { QStringList l; - for (const auto& cat : categories) { + for (auto cat : categories) { l << QString("\"categories:%1\"").arg(cat); } return l.join(','); @@ -74,11 +76,11 @@ class ModrinthAPI : public ResourceAPI { { switch (side) { case ModPlatform::Side::ClientSide: - return { R"("client_side:required","client_side:optional"],["server_side:optional","server_side:unsupported")" }; + return QString("\"client_side:required\",\"client_side:optional\"],[\"server_side:optional\",\"server_side:unsupported\""); case ModPlatform::Side::ServerSide: - return { R"("server_side:required","server_side:optional"],["client_side:optional","client_side:unsupported")" }; + return QString("\"server_side:required\",\"server_side:optional\"],[\"client_side:optional\",\"client_side:unsupported\""); case ModPlatform::Side::UniversalSide: - return { R"("client_side:required"],["server_side:required")" }; + return QString("\"client_side:required\"],[\"server_side:required\""); case ModPlatform::Side::NoSide: // fallthrough default: @@ -86,17 +88,17 @@ class ModrinthAPI : public ResourceAPI { } } - static QString mapMCVersionFromModrinth(QString v) + static inline QString mapMCVersionFromModrinth(QString v) { - static const QString s_preString = " Pre-Release "; + static const QString preString = " Pre-Release "; bool pre = false; if (v.contains("-pre")) { pre = true; - v.replace("-pre", s_preString); + v.replace("-pre", preString); } v.replace("-", " "); if (pre) { - v.replace(" Pre Release ", s_preString); + v.replace(" Pre Release ", preString); } return v; } @@ -123,28 +125,23 @@ class ModrinthAPI : public ResourceAPI { return ""; } - QString createFacets(const SearchArgs& args) const + QString createFacets(SearchArgs const& args) const { QStringList facets_list; - if (args.loaders.has_value() && args.loaders.value() != 0) { + if (args.loaders.has_value() && args.loaders.value() != 0) facets_list.append(QString("[%1]").arg(getModLoaderFilters(args.loaders.value()))); - } - if (args.versions.has_value() && !args.versions.value().empty()) { + if (args.versions.has_value() && !args.versions.value().empty()) facets_list.append(QString("[%1]").arg(getGameVersionsArray(args.versions.value()))); - } if (args.side.has_value()) { auto side = getSideFilters(args.side.value()); - if (!side.isEmpty()) { + if (!side.isEmpty()) facets_list.append(QString("[%1]").arg(side)); - } } - if (args.categoryIds.has_value() && !args.categoryIds->empty()) { + if (args.categoryIds.has_value() && !args.categoryIds->empty()) facets_list.append(QString("[%1]").arg(getCategoriesFilters(args.categoryIds.value()))); - } - if (args.openSource) { + if (args.openSource) facets_list.append("[\"open_source:true\"]"); - } facets_list.append(QString("[\"project_type:%1\"]").arg(resourceTypeParameter(args.type))); @@ -152,7 +149,7 @@ class ModrinthAPI : public ResourceAPI { } public: - auto getSearchURL(const SearchArgs& 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())) { @@ -164,74 +161,67 @@ class ModrinthAPI : public ResourceAPI { QStringList get_arguments; get_arguments.append(QString("offset=%1").arg(args.offset)); get_arguments.append(QString("limit=25")); - if (args.search.has_value()) { + if (args.search.has_value()) get_arguments.append(QString("query=%1").arg(args.search.value())); - } - if (args.sorting.has_value()) { + if (args.sorting.has_value()) get_arguments.append(QString("index=%1").arg(args.sorting.value().name)); - } get_arguments.append(QString("facets=%1").arg(createFacets(args))); return BuildConfig.MODRINTH_PROD_URL + "/search?" + get_arguments.join('&'); }; - auto getInfoURL(const QString& id) const -> std::optional override + inline auto getInfoURL(QString const& id) const -> std::optional override { return BuildConfig.MODRINTH_PROD_URL + "/project/" + id; }; - auto getMultipleModInfoURL(const QStringList& ids) const -> QString + inline auto getMultipleModInfoURL(QStringList ids) const -> QString { return BuildConfig.MODRINTH_PROD_URL + QString("/projects?ids=[\"%1\"]").arg(ids.join("\",\"")); }; - auto getVersionsURL(const VersionSearchArgs& args) const -> std::optional override + inline auto getVersionsURL(VersionSearchArgs const& args) const -> std::optional override { QStringList get_arguments; - if (args.mcVersions.has_value()) { + if (args.mcVersions.has_value()) get_arguments.append(QString("game_versions=[%1]").arg(getGameVersionsString(args.mcVersions.value()))); - } - if (args.loaders.has_value()) { + if (args.loaders.has_value()) get_arguments.append(QString("loaders=[\"%1\"]").arg(getModLoaderStrings(args.loaders.value()).join("\",\""))); - } - get_arguments.append(QString("include_changelog=%1").arg(args.includeChangelog ? "true" : "false")); return QString("%1/project/%2/version%3%4") .arg(BuildConfig.MODRINTH_PROD_URL, args.pack->addonId.toString(), get_arguments.isEmpty() ? "" : "?", get_arguments.join('&')); }; - QString getGameVersionsArray(const std::vector& mcVersions) const + QString getGameVersionsArray(std::list mcVersions) const { QString s; - for (const auto& ver : mcVersions) { - s += QString(R"("versions:%1",)").arg(mapMCVersionToModrinth(ver)); + for (auto& ver : mcVersions) { + s += QString("\"versions:%1\",").arg(mapMCVersionToModrinth(ver)); } s.remove(s.length() - 1, 1); // remove last comma return s.isEmpty() ? QString() : s; } - static auto validateModLoaders(ModPlatform::ModLoaderTypes loaders) -> bool + static inline auto validateModLoaders(ModPlatform::ModLoaderTypes loaders) -> bool { - return (loaders & (ModPlatform::NeoForge | ModPlatform::Forge | ModPlatform::Fabric | ModPlatform::Quilt | ModPlatform::LiteLoader | - ModPlatform::DataPack | ModPlatform::Babric | ModPlatform::BTA | ModPlatform::LegacyFabric | - ModPlatform::Ornithe | ModPlatform::Rift)) != 0; + return loaders & (ModPlatform::NeoForge | ModPlatform::Forge | ModPlatform::Fabric | ModPlatform::Quilt | ModPlatform::LiteLoader | + ModPlatform::DataPack | ModPlatform::Babric | ModPlatform::BTA | ModPlatform::LegacyFabric | + ModPlatform::Ornithe | ModPlatform::Rift); } - std::optional getDependencyURL(const DependencySearchArgs& 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(R"(%1/project/%2/version?game_versions=["%3"]&loaders=["%4"]&include_changelog=%5)") - .arg(BuildConfig.MODRINTH_PROD_URL) - .arg(args.dependency.addonId.toString()) - .arg(mapMCVersionToModrinth(args.mcVersion)) - .arg(getModLoaderStrings(args.loader).join("\",\"")) - .arg(args.includeChangelog ? "true" : "false"); + 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\"]") + .arg(BuildConfig.MODRINTH_PROD_URL) + .arg(args.dependency.addonId.toString()) + .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 /*unused*/) const override + ModPlatform::IndexedVersion loadIndexedPackVersion(QJsonObject& obj, ModPlatform::ResourceType) const override { return Modrinth::loadIndexedPackVersion(obj); }; diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp index 0fda37e57..381261a26 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp @@ -13,23 +13,23 @@ #include "tasks/ConcurrentTask.h" -static const ModrinthAPI g_api; +static ModrinthAPI api; ModrinthCheckUpdate::ModrinthCheckUpdate(QList& resources, - std::vector& mcVersions, + std::list& mcVersions, QList loadersList, - ResourceFolderModel* resourceModel) - : CheckUpdateTask(resources, mcVersions, std::move(loadersList), resourceModel) + 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) { + for (auto m : resources) { modLoaders |= m->metadata()->loaders; } for (auto l : m_loadersList) { - modLoaders &= ~static_cast(l); + modLoaders &= ~l; } m_loadersList.append(ModPlatform::modLoaderTypesToList(modLoaders)); } @@ -37,9 +37,8 @@ ModrinthCheckUpdate::ModrinthCheckUpdate(QList& resources, bool ModrinthCheckUpdate::abort() { - if (m_job) { + if (m_job) return m_job->abort(); - } return true; } @@ -51,9 +50,9 @@ bool ModrinthCheckUpdate::abort() void ModrinthCheckUpdate::executeTask() { setStatus(tr("Preparing resources for Modrinth...")); - setProgress(0, ((m_loadersList.isEmpty() ? 1 : m_loadersList.length()) * 2) + 1); + setProgress(0, (m_loadersList.isEmpty() ? 1 : m_loadersList.length()) * 2 + 1); - auto hashingTask = + auto hashing_task = makeShared("MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); bool startHasing = false; for (auto* resource : m_resources) { @@ -63,11 +62,10 @@ void ModrinthCheckUpdate::executeTask() // 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_hashType) { - auto hashTask = Hashing::createHasher(resource->fileinfo().absoluteFilePath(), ModPlatform::ResourceProvider::MODRINTH); - connect(hashTask.get(), &Hashing::Hasher::resultsReady, - [this, resource](const QString& hash) { m_mappings.insert(hash, resource); }); - connect(hashTask.get(), &Task::failed, [this] { failed("Failed to generate hash"); }); - hashingTask->addTask(hashTask); + 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); @@ -75,9 +73,9 @@ void ModrinthCheckUpdate::executeTask() } if (startHasing) { - connect(hashingTask.get(), &Task::finished, this, &ModrinthCheckUpdate::checkNextLoader); - m_job = hashingTask; - hashingTask->start(); + connect(hashing_task.get(), &Task::finished, this, &ModrinthCheckUpdate::checkNextLoader); + m_job = hashing_task; + hashing_task->start(); } else { checkNextLoader(); } @@ -85,15 +83,14 @@ void ModrinthCheckUpdate::executeTask() void ModrinthCheckUpdate::getUpdateModsForLoader(std::optional loader, bool forceModLoaderCheck) { - m_loaderIdx++; - setStatus(tr("Waiting for the API response from Modrinth...")); setProgress(m_progress + 1, m_progressTotal); + auto response = std::make_shared(); QStringList hashes; if (forceModLoaderCheck && loader.has_value()) { - for (const auto& hash : m_mappings.keys()) { - if ((m_mappings.value(hash)->metadata()->loaders & loader.value()) != 0) { + for (auto hash : m_mappings.keys()) { + if (m_mappings.value(hash)->metadata()->loaders & loader.value()) { hashes.append(hash); } } @@ -106,29 +103,30 @@ void ModrinthCheckUpdate::getUpdateModsForLoader(std::optionalstart(); } -void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optional loader) +void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr response, std::optional loader) { setStatus(tr("Parsing the API response from Modrinth...")); setProgress(m_progress + 1, m_progressTotal); - QJsonParseError parseError{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parseError); - if (parseError.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from ModrinthCheckUpdate at" << parseError.offset - << "reason:" << parseError.errorString(); + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from ModrinthCheckUpdate at" << parse_error.offset + << "reason:" << parse_error.errorString(); qWarning() << *response; - emitFailed(parseError.errorString()); + emitFailed(parse_error.errorString()); return; } @@ -139,11 +137,11 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio const QString hash = iter.key(); Resource* resource = iter.value(); - auto projectObj = doc[hash].toObject(); + auto project_obj = doc[hash].toObject(); // If the returned project is empty, but we have Modrinth metadata, // it means this specific version is not available - if (projectObj.isEmpty()) { + if (project_obj.isEmpty()) { qDebug() << "Mod" << m_mappings.find(hash).value()->name() << "got an empty response. Hash:" << hash; ++iter; continue; @@ -151,11 +149,11 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio // Sometimes a version may have multiple files, one with "forge" and one with "fabric", // so we may want to filter it - QString loaderFilter; - if (loader.has_value() && loader != 0) { - auto modLoaders = ModPlatform::modLoaderTypesToList(*loader); - if (!modLoaders.isEmpty()) { - loaderFilter = ModPlatform::getModLoaderAsString(modLoaders.first()); + QString loader_filter; + if (loader.has_value()) { + for (auto flag : ModPlatform::modLoaderTypesToList(*loader)) { + loader_filter = ModPlatform::getModLoaderAsString(flag); + break; } } @@ -165,9 +163,9 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio // - 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 projectVer = Modrinth::loadIndexedPackVersion(projectObj, m_hashType, loaderFilter); - if (projectVer.downloadUrl.isEmpty()) { - qCritical() << "Modrinth mod without download url!" << projectVer.fileName; + 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; continue; } @@ -178,22 +176,21 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio pack->slug = resource->metadata()->slug; pack->addonId = resource->metadata()->project_id; pack->provider = ModPlatform::ResourceProvider::MODRINTH; - if ((projectVer.hash != hash && projectVer.is_preferred) || (resource->status() == ResourceStatus::NotInstalled)) { - auto downloadTask = makeShared(pack, projectVer, m_resourceModel, true, "update"); + if ((project_ver.hash != hash && project_ver.is_preferred) || (resource->status() == ResourceStatus::NOT_INSTALLED)) { + auto download_task = makeShared(pack, project_ver, m_resourceModel); - QString oldVersion = resource->metadata()->version_number; - if (oldVersion.isEmpty()) { - if (resource->status() == ResourceStatus::NotInstalled) { - oldVersion = tr("Not installed"); - } else { - oldVersion = tr("Unknown"); - } + QString old_version = resource->metadata()->version_number; + if (old_version.isEmpty()) { + if (resource->status() == ResourceStatus::NOT_INSTALLED) + old_version = tr("Not installed"); + else + old_version = tr("Unknown"); } - m_updates.emplace_back(pack->name, hash, oldVersion, projectVer.version_number, projectVer.version_type, - projectVer.changelog, ModPlatform::ResourceProvider::MODRINTH, downloadTask, resource->enabled()); + m_updates.emplace_back(pack->name, hash, old_version, project_ver.version_number, project_ver.version_type, + project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task, resource->enabled()); } - m_deps.append(std::make_shared(pack, projectVer)); + m_deps.append(std::make_shared(pack, project_ver)); iter = m_mappings.erase(iter); } @@ -213,25 +210,23 @@ void ModrinthCheckUpdate::checkNextLoader() 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_loadersList.isEmpty() && m_loaderIdx == 0) { // this are other resources no need to check more than once with empty loader + } else if (m_loadersList.isEmpty() && m_loaderIdx == 0) { // this are other resources no need to check more than once with empty loader getUpdateModsForLoader(); return; } - for (auto* resource : m_mappings) { + for (auto resource : m_mappings) { QString reason; - if (dynamic_cast(resource) != nullptr) { + if (dynamic_cast(resource) != nullptr) reason = tr("No valid version found for this resource. It's probably unavailable for the current game " "version / mod loader."); - } else { + else reason = tr("No valid version found for this resource. It's probably unavailable for the current game version."); - } emit checkFailed(resource, reason); } emitSucceeded(); -} +} \ No newline at end of file diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.h b/launcher/modplatform/modrinth/ModrinthCheckUpdate.h index c0407bef8..eb8057694 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.h +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.h @@ -7,9 +7,9 @@ class ModrinthCheckUpdate : public CheckUpdateTask { public: ModrinthCheckUpdate(QList& resources, - std::vector& mcVersions, + std::list& mcVersions, QList loadersList, - ResourceFolderModel* resourceModel); + std::shared_ptr resourceModel); public slots: bool abort() override; @@ -17,7 +17,7 @@ class ModrinthCheckUpdate : public CheckUpdateTask { protected slots: void executeTask() override; void getUpdateModsForLoader(std::optional loader = {}, bool forceModLoaderCheck = false); - void checkVersionsResponse(QByteArray* response, std::optional loader); + void checkVersionsResponse(std::shared_ptr response, std::optional loader); void checkNextLoader(); private: @@ -25,5 +25,5 @@ class ModrinthCheckUpdate : public CheckUpdateTask { QHash m_mappings; QString m_hashType; int m_loaderIdx = 0; - qsizetype m_initialSize = 0; + int m_initialSize = 0; }; diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 0cb2c547d..767bb003f 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -16,10 +16,7 @@ #include "net/ChecksumValidator.h" #include "net/ApiDownload.h" -#include "net/ApiHeaderProxy.h" #include "net/NetJob.h" - -#include "modplatform/ModIndex.h" #include "settings/INISettingsObject.h" #include "ui/dialogs/CustomMessageBox.h" @@ -32,119 +29,129 @@ bool ModrinthCreationTask::abort() { - if (!canAbort()) { + if (!canAbort()) return false; - } - if (m_task) { + m_abort = true; + if (m_task) m_task->abort(); - } - return InstanceCreationTask::abort(); + return Task::abort(); } bool ModrinthCreationTask::updateInstance() { - auto* instanceList = APPLICATION->instances(); + auto instance_list = APPLICATION->instances(); // FIXME: How to handle situations when there's more than one install already for a given modpack? - BaseInstance* inst = nullptr; - if (auto originalId = originalInstanceID(); !originalId.isEmpty()) { - inst = instanceList->getInstanceById(originalId); + InstancePtr inst; + if (auto original_id = originalInstanceID(); !original_id.isEmpty()) { + inst = instance_list->getInstanceById(original_id); Q_ASSERT(inst); } else { - inst = instanceList->getInstanceByManagedName(originalName()); + inst = instance_list->getInstanceByManagedName(originalName()); if (!inst) { - inst = instanceList->getInstanceById(originalName()); + inst = instance_list->getInstanceById(originalName()); - if (!inst) { + if (!inst) return false; - } } } - QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json"); - if (!parseManifest(indexPath, m_files, true, false)) { + QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json"); + if (!parseManifest(index_path, m_files, true, false)) return false; - } - auto versionName = inst->getManagedPackVersionName(); + auto version_name = inst->getManagedPackVersionName(); m_root_path = QFileInfo(inst->gameRoot()).fileName(); - auto versionStr = !versionName.isEmpty() ? tr(" (version %1)").arg(versionName) : ""; + auto version_str = !version_name.isEmpty() ? tr(" (version %1)").arg(version_name) : ""; if (shouldConfirmUpdate()) { - auto shouldUpdate = askIfShouldUpdate(m_parent, versionStr); - if (shouldUpdate == ShouldUpdate::SkipUpdating) { + auto should_update = askIfShouldUpdate(m_parent, version_str); + if (should_update == ShouldUpdate::SkipUpdating) return false; - } - if (shouldUpdate == ShouldUpdate::Cancel) { + if (should_update == ShouldUpdate::Cancel) { m_abort = true; return false; } } // Remove repeated files, we don't need to download them! - QDir oldInstDir(inst->instanceRoot()); + QDir old_inst_dir(inst->instanceRoot()); - QString oldIndexFolder(FS::PathCombine(oldInstDir.absolutePath(), "mrpack")); + QString old_index_folder(FS::PathCombine(old_inst_dir.absolutePath(), "mrpack")); - QString oldIndexPath(FS::PathCombine(oldIndexFolder, "modrinth.index.json")); - QFileInfo oldIndexFile(oldIndexPath); - if (oldIndexFile.exists()) { - std::vector oldFiles; - parseManifest(oldIndexPath, oldFiles, false, false); + 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; + parseManifest(old_index_path, old_files, false, false); // Let's remove all duplicated, identical resources! - auto filesIterator = m_files.begin(); + auto files_iterator = m_files.begin(); begin: - while (filesIterator != m_files.end()) { - const auto& file = *filesIterator; + while (files_iterator != m_files.end()) { + auto const& file = *files_iterator; - auto oldFilesIterator = oldFiles.begin(); - while (oldFilesIterator != oldFiles.end()) { - const auto& oldFile = *oldFilesIterator; + auto old_files_iterator = old_files.begin(); + while (old_files_iterator != old_files.end()) { + auto const& old_file = *old_files_iterator; - if (oldFile.hash == file.hash) { + if (old_file.hash == file.hash) { qDebug() << "Removed file at" << file.path << "from list of downloads"; - filesIterator = m_files.erase(filesIterator); - oldFilesIterator = oldFiles.erase(oldFilesIterator); + files_iterator = m_files.erase(files_iterator); + old_files_iterator = old_files.erase(old_files_iterator); goto begin; // Sorry :c } - oldFilesIterator++; + old_files_iterator++; } - filesIterator++; + files_iterator++; } - QDir oldMinecraftDir(inst->gameRoot()); + QDir old_minecraft_dir(inst->gameRoot()); // Some files were removed from the old version, and some will be downloaded in an updated version, // so we're fine removing them! - if (!oldFiles.empty()) { - for (const auto& file : oldFiles) { - scheduleToDelete(m_parent, oldMinecraftDir, file.path, true); + if (!old_files.empty()) { + for (auto const& file : old_files) { + if (file.path.isEmpty()) + continue; + qDebug() << "Scheduling" << file.path << "for removal"; + m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(file.path)); + if (file.path.endsWith(".disabled")) { // remove it if it was enabled/disabled by user + m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(file.path.chopped(9))); + } else { + m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(file.path + ".disabled")); + } } } // We will remove all the previous overrides, to prevent duplicate files! // TODO: Currently 'overrides' will always override the stuff on update. How do we preserve unchanged overrides? // FIXME: We may want to do something about disabled mods. - auto oldOverrides = Override::readOverrides("overrides", oldIndexFolder); - for (const auto& entry : oldOverrides) { - scheduleToDelete(m_parent, oldMinecraftDir, entry); + auto old_overrides = Override::readOverrides("overrides", old_index_folder); + for (const auto& entry : old_overrides) { + if (entry.isEmpty()) + continue; + qDebug() << "Scheduling" << entry << "for removal"; + m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(entry)); } - auto oldClientOverrides = Override::readOverrides("client-overrides", oldIndexFolder); - for (const auto& entry : oldClientOverrides) { - scheduleToDelete(m_parent, oldMinecraftDir, entry); + auto old_client_overrides = Override::readOverrides("client-overrides", old_index_folder); + for (const auto& entry : old_client_overrides) { + if (entry.isEmpty()) + continue; + qDebug() << "Scheduling" << entry << "for removal"; + m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(entry)); } } else { // We don't have an old index file, so we may duplicate stuff! - auto* dialog = CustomMessageBox::selectable(m_parent, tr("No index file."), - tr("We couldn't find a suitable index file for the older version. This may cause some " - "of the files to be duplicated. Do you want to continue?"), - QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel); + auto dialog = CustomMessageBox::selectable(m_parent, tr("No index file."), + tr("We couldn't find a suitable index file for the older version. This may cause some " + "of the files to be duplicated. Do you want to continue?"), + QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel); if (dialog->exec() == QDialog::DialogCode::Rejected) { m_abort = true; @@ -162,151 +169,130 @@ bool ModrinthCreationTask::updateInstance() } // https://docs.modrinth.com/docs/modpacks/format_definition/ -std::unique_ptr ModrinthCreationTask::createInstance() +bool ModrinthCreationTask::createInstance() { QEventLoop loop; - QString parentFolder(FS::PathCombine(m_stagingPath, "mrpack")); + QString parent_folder(FS::PathCombine(m_stagingPath, "mrpack")); - QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json"); - if (m_files.empty() && !parseManifest(indexPath, m_files, true, true)) { - return nullptr; - } + QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json"); + if (m_files.empty() && !parseManifest(index_path, m_files, true, true)) + return false; // Keep index file in case we need it some other time (like when changing versions) - QString newIndexPlace(FS::PathCombine(parentFolder, "modrinth.index.json")); - FS::ensureFilePathExists(newIndexPlace); - FS::move(indexPath, newIndexPlace); + QString new_index_place(FS::PathCombine(parent_folder, "modrinth.index.json")); + FS::ensureFilePathExists(new_index_place); + FS::move(index_path, new_index_place); auto mcPath = FS::PathCombine(m_stagingPath, m_root_path); - auto overridePath = FS::PathCombine(m_stagingPath, "overrides"); - if (QFile::exists(overridePath)) { + auto override_path = FS::PathCombine(m_stagingPath, "overrides"); + if (QFile::exists(override_path)) { // Create a list of overrides in "overrides.txt" inside mrpack/ - Override::createOverrides("overrides", parentFolder, overridePath); + Override::createOverrides("overrides", parent_folder, override_path); // Apply the overrides - if (!FS::move(overridePath, mcPath)) { + if (!FS::move(override_path, mcPath)) { setError(tr("Could not rename the overrides folder:\n") + "overrides"); - return nullptr; + return false; } } // Do client overrides - auto clientOverridePath = FS::PathCombine(m_stagingPath, "client-overrides"); - if (QFile::exists(clientOverridePath)) { + auto client_override_path = FS::PathCombine(m_stagingPath, "client-overrides"); + if (QFile::exists(client_override_path)) { // Create a list of overrides in "client-overrides.txt" inside mrpack/ - Override::createOverrides("client-overrides", parentFolder, clientOverridePath); + Override::createOverrides("client-overrides", parent_folder, client_override_path); // Apply the overrides - if (!FS::overrideFolder(mcPath, clientOverridePath)) { + if (!FS::overrideFolder(mcPath, client_override_path)) { setError(tr("Could not rename the client overrides folder:\n") + "client overrides"); - return nullptr; + return false; } } QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); - auto instanceSettings = std::make_unique(configPath); - auto instance = std::make_unique(m_globalSettings, std::move(instanceSettings), m_stagingPath); + auto instanceSettings = std::make_shared(configPath); + MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); - auto* components = instance->getPackProfile(); + auto components = instance.getPackProfile(); components->buildingFromScratch(); components->setComponentVersion("net.minecraft", m_minecraft_version, true); - QString loader; - if (!m_fabric_version.isEmpty()) { + if (!m_fabric_version.isEmpty()) components->setComponentVersion("net.fabricmc.fabric-loader", m_fabric_version); - loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::Fabric); - } - if (!m_quilt_version.isEmpty()) { + if (!m_quilt_version.isEmpty()) components->setComponentVersion("org.quiltmc.quilt-loader", m_quilt_version); - loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::Quilt); - } - if (!m_forge_version.isEmpty()) { + if (!m_forge_version.isEmpty()) components->setComponentVersion("net.minecraftforge", m_forge_version); - loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::Forge); - } - if (!m_neoForge_version.isEmpty()) { + if (!m_neoForge_version.isEmpty()) components->setComponentVersion("net.neoforged", m_neoForge_version); - loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::NeoForge); - } if (m_instIcon != "default") { - instance->setIconKey(m_instIcon); + instance.setIconKey(m_instIcon); } else if (!m_managed_id.isEmpty()) { - instance->setIconKey("modrinth"); + instance.setIconKey("modrinth"); } // Don't add managed info to packs without an ID (most likely imported from ZIP) - if (!m_managed_id.isEmpty()) { - instance->setManagedPack("modrinth", m_managed_id, m_managed_name, m_managed_version_id, version()); - } else { - instance->setManagedPack("modrinth", "", name(), "", ""); - } + if (!m_managed_id.isEmpty()) + instance.setManagedPack("modrinth", m_managed_id, m_managed_name, m_managed_version_id, version()); + else + instance.setManagedPack("modrinth", "", name(), "", ""); - instance->setName(name()); - instance->saveNow(); + instance.setName(name()); + instance.saveNow(); auto downloadMods = makeShared(tr("Mod Download Modrinth"), APPLICATION->network()); - auto rootModpackPath = FS::PathCombine(m_stagingPath, m_root_path); - auto rootModpackUrl = QUrl::fromLocalFile(rootModpackPath); + auto root_modpack_path = FS::PathCombine(m_stagingPath, m_root_path); + auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path); // TODO make this work with other sorts of resource QHash resources; for (auto& file : m_files) { auto fileName = file.path; fileName = FS::RemoveInvalidPathChars(fileName); - auto filePath = FS::PathCombine(rootModpackPath, fileName); - if (!rootModpackUrl.isParentOf(QUrl::fromLocalFile(filePath))) { + auto file_path = FS::PathCombine(root_modpack_path, fileName); + if (!root_modpack_url.isParentOf(QUrl::fromLocalFile(file_path))) { // This means we somehow got out of the root folder, so abort here to prevent exploits setError(tr("One of the files has a path that leads to an arbitrary location (%1). This is a security risk and isn't allowed.") .arg(fileName)); - return nullptr; + return false; } if (fileName.startsWith("mods/")) { - auto* mod = new Mod(filePath); + auto mod = new Mod(file_path); ModDetails d; - d.mod_id = filePath; + d.mod_id = file_path; mod->setDetails(d); resources[file.hash.toHex()] = mod; } if (file.downloads.empty()) { setError(tr("The file '%1' is missing a download link. This is invalid in the pack format.").arg(fileName)); - return nullptr; + return false; } - qDebug() << "Will try to download" << file.downloads.front() << "to" << filePath; - - Net::ModrinthDownloadMeta meta{ - .reason = m_instance.has_value() ? "update" : "modpack", - .gameVersion = m_minecraft_version, - .loader = loader, - }; - - QUrl downloadUrl = file.downloads.dequeue(); - auto dl = Net::ApiDownload::makeFile(downloadUrl, filePath, Net::Download::Option::NoOptions, meta); + qDebug() << "Will try to download" << file.downloads.front() << "to" << file_path; + auto dl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path); dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); downloadMods->addNetAction(dl); if (!file.downloads.empty()) { // FIXME: This really needs to be put into a ConcurrentTask of // MultipleOptionsTask's , once those exist :) auto param = dl.toWeakRef(); - connect(dl.get(), &Task::failed, [&file, filePath, param, downloadMods, meta] { - QUrl fallbackUrl = file.downloads.dequeue(); - auto ndl = Net::ApiDownload::makeFile(fallbackUrl, filePath, Net::Download::Option::NoOptions, meta); + connect(dl.get(), &Task::failed, [&file, file_path, param, downloadMods] { + auto ndl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path); ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); downloadMods->addNetAction(ndl); - if (auto shared = param.lock()) { + if (auto shared = param.lock()) shared->succeeded(); - } }); } } - bool endedWell = false; + bool ended_well = false; - connect(downloadMods.get(), &NetJob::succeeded, this, [&endedWell]() { endedWell = true; }); - connect(downloadMods.get(), &NetJob::failed, [this, &endedWell](const QString& reason) { - endedWell = false; + connect(downloadMods.get(), &NetJob::succeeded, this, [&ended_well]() { ended_well = true; }); + connect(downloadMods.get(), &NetJob::failed, [this, &ended_well](const QString& reason) { + ended_well = false; setError(reason); }); connect(downloadMods.get(), &NetJob::finished, &loop, &QEventLoop::quit); @@ -322,17 +308,17 @@ std::unique_ptr ModrinthCreationTask::createInstance() loop.exec(); - if (!endedWell) { - for (auto* resource : resources) { + if (!ended_well) { + for (auto resource : resources) { delete resource; } - return nullptr; + return ended_well; } QEventLoop ensureMetaLoop; - QDir folder = FS::PathCombine(instance->modsRoot(), ".index"); + QDir folder = FS::PathCombine(instance.modsRoot(), ".index"); auto ensureMetadataTask = makeShared(resources, folder, ModPlatform::ResourceProvider::MODRINTH); - connect(ensureMetadataTask.get(), &Task::succeeded, this, [&endedWell]() { endedWell = true; }); + connect(ensureMetadataTask.get(), &Task::succeeded, this, [&ended_well]() { ended_well = true; }); connect(ensureMetadataTask.get(), &Task::finished, &ensureMetaLoop, &QEventLoop::quit); connect(ensureMetadataTask.get(), &Task::progress, [this](qint64 current, qint64 total) { setDetails(tr("%1 out of %2 complete").arg(current).arg(total)); @@ -344,38 +330,37 @@ std::unique_ptr ModrinthCreationTask::createInstance() m_task = ensureMetadataTask; ensureMetaLoop.exec(); - for (auto* resource : resources) { + for (auto resource : resources) { delete resource; } resources.clear(); // Update information of the already installed instance, if any. - if (m_instance && endedWell) { + if (m_instance && ended_well) { setAbortable(false); - auto* inst = m_instance.value(); + auto inst = m_instance.value(); // Only change the name if it didn't use a custom name, so that the previous custom name // is preserved, but if we're using the original one, we update the version string. // NOTE: This needs to come before the copyManagedPack call! - if (inst->name().contains(inst->getManagedPackVersionName()) && inst->name() != instance->name()) { - if (askForChangingInstanceName(m_parent, inst->name(), instance->name()) == InstanceNameChange::ShouldChange) { - inst->setName(instance->name()); - } + if (inst->name().contains(inst->getManagedPackVersionName()) && inst->name() != instance.name()) { + if (askForChangingInstanceName(m_parent, inst->name(), instance.name()) == InstanceNameChange::ShouldChange) + inst->setName(instance.name()); } - inst->copyManagedPack(*instance); + inst->copyManagedPack(instance); } - if (endedWell) { - return instance; - } - return nullptr; + return ended_well; } -bool ModrinthCreationTask::parseManifest(const QString& indexPath, std::vector& files, bool setInternalData, bool showOptionalDialog) +bool ModrinthCreationTask::parseManifest(const QString& index_path, + std::vector& files, + bool set_internal_data, + bool show_optional_dialog) { try { - auto doc = Json::requireDocument(indexPath); + auto doc = Json::requireDocument(index_path); auto obj = Json::requireObject(doc, "modrinth.index.json"); int formatVersion = Json::requireInteger(obj, "formatVersion", "modrinth.index.json"); if (formatVersion == 1) { @@ -384,10 +369,9 @@ bool ModrinthCreationTask::parseManifest(const QString& indexPath, std::vector createInstance() override; + bool createInstance() override; private: - bool parseManifest(const QString&, std::vector&, bool setInternalData = true, bool showOptionalDialog = true); + bool parseManifest(const QString&, std::vector&, bool set_internal_data = true, bool show_optional_dialog = true); private: QWidget* m_parent = nullptr; @@ -55,7 +55,7 @@ class ModrinthCreationTask final : public InstanceCreationTask { std::vector m_files; Task::Ptr m_task; - std::optional m_instance; + std::optional m_instance; QString m_root_path = "minecraft"; }; diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 9a972bc85..c638e8db0 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -40,7 +40,7 @@ ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, const QString& version, const QString& summary, bool optionalFiles, - BaseInstance* instance, + InstancePtr instance, const QString& output, MMCZip::FilterFileFunction filter) : name(name) @@ -48,7 +48,7 @@ ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, , summary(summary) , optionalFiles(optionalFiles) , instance(instance) - , mcInstance(dynamic_cast(instance)) + , mcInstance(dynamic_cast(instance.get())) , gameRoot(instance->gameRoot()) , output(output) , filter(filter) @@ -86,7 +86,7 @@ void ModrinthPackExportTask::collectFiles() if (mcInstance) { mcInstance->loaderModList()->update(); - connect(mcInstance->loaderModList(), &ModFolderModel::updateFinished, this, &ModrinthPackExportTask::collectHashes); + connect(mcInstance->loaderModList().get(), &ModFolderModel::updateFinished, this, &ModrinthPackExportTask::collectHashes); } else collectHashes(); } @@ -108,13 +108,13 @@ void ModrinthPackExportTask::collectHashes() QFile openFile(file.absoluteFilePath()); if (!openFile.open(QFile::ReadOnly)) { - qWarning() << "Could not open" << file << "for hashing:" << openFile.errorString(); + qWarning() << "Could not open" << file << "for hashing"; continue; } const QByteArray data = openFile.readAll(); if (openFile.error() != QFileDevice::NoError) { - qWarning() << "Could not read" << file << "error:" << openFile.errorString(); + qWarning() << "Could not read" << file; continue; } auto sha512 = Hashing::hash(data, Hashing::Algorithm::Sha512); @@ -155,8 +155,8 @@ void ModrinthPackExportTask::makeApiRequest() buildZip(); else { setStatus(tr("Finding versions for hashes...")); - auto [versionsTask, response] = api.currentVersions(pendingHashes.values(), "sha512"); - task = versionsTask; + auto response = std::make_shared(); + task = api.currentVersions(pendingHashes.values(), "sha512", response); connect(task.get(), &Task::succeeded, [this, response]() { parseApiResponse(response); }); connect(task.get(), &Task::failed, this, &ModrinthPackExportTask::emitFailed); connect(task.get(), &Task::aborted, this, &ModrinthPackExportTask::emitAborted); @@ -164,7 +164,7 @@ void ModrinthPackExportTask::makeApiRequest() } } -void ModrinthPackExportTask::parseApiResponse(QByteArray* response) +void ModrinthPackExportTask::parseApiResponse(const std::shared_ptr response) { task = nullptr; diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index 5aca657a9..f9b86bbd7 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -34,7 +34,7 @@ class ModrinthPackExportTask : public Task { const QString& version, const QString& summary, bool optionalFiles, - BaseInstance* instance, + InstancePtr instance, const QString& output, MMCZip::FilterFileFunction filter); @@ -55,7 +55,7 @@ class ModrinthPackExportTask : public Task { // inputs const QString name, version, summary; const bool optionalFiles; - const BaseInstance* instance; + const InstancePtr instance; MinecraftInstance* mcInstance; const QDir gameRoot; const QString output; @@ -70,7 +70,7 @@ class ModrinthPackExportTask : public Task { void collectFiles(); void collectHashes(); void makeApiRequest(); - void parseApiResponse(QByteArray* response); + void parseApiResponse(std::shared_ptr response); void buildZip(); QByteArray generateIndex(); diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index 48d28feee..699e12b40 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -26,7 +26,9 @@ #include "minecraft/PackProfile.h" #include "modplatform/ModIndex.h" -bool shouldDownloadOnSide(const QString& side) +static ModrinthAPI api; + +bool shouldDownloadOnSide(QString side) { return side == "required" || side == "optional"; } @@ -35,19 +37,17 @@ bool shouldDownloadOnSide(const QString& side) void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) { pack.addonId = obj["project_id"].toString(); - if (pack.addonId.toString().isEmpty()) { + if (pack.addonId.toString().isEmpty()) pack.addonId = Json::requireString(obj, "id"); - } pack.provider = ModPlatform::ResourceProvider::MODRINTH; pack.name = Json::requireString(obj, "title"); pack.slug = obj["slug"].toString(""); - if (!pack.slug.isEmpty()) { + if (!pack.slug.isEmpty()) pack.websiteUrl = "https://modrinth.com/mod/" + pack.slug; - } else { + else pack.websiteUrl = ""; - } pack.description = obj["description"].toString(""); @@ -57,7 +57,7 @@ void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) if (obj.contains("author")) { ModPlatform::ModpackAuthor modAuthor; modAuthor.name = obj["author"].toString(); - modAuthor.url = ModrinthAPI::getAuthorURL(modAuthor.name); + modAuthor.url = api.getAuthorURL(modAuthor.name); pack.authors = { modAuthor }; } @@ -91,9 +91,8 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob pack.extraData.wikiUrl.chop(1); pack.extraData.discordUrl = obj["discord_url"].toString(); - if (pack.extraData.discordUrl.endsWith('/')) { + if (pack.extraData.discordUrl.endsWith('/')) pack.extraData.discordUrl.chop(1); - } auto donate_arr = obj["donation_urls"].toArray(); for (auto d : donate_arr) { @@ -115,9 +114,7 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob pack.extraDataLoaded = true; } -ModPlatform::IndexedVersion Modrinth::loadIndexedPackVersion(QJsonObject& obj, - const QString& preferred_hash_type, - const QString& preferred_file_name) +ModPlatform::IndexedVersion Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_type, QString preferred_file_name) { ModPlatform::IndexedVersion file; @@ -134,27 +131,24 @@ ModPlatform::IndexedVersion Modrinth::loadIndexedPackVersion(QJsonObject& obj, } auto loaders = Json::requireArray(obj, "loaders"); for (auto loader : loaders) { - if (loader == "neoforge") { + if (loader == "neoforge") file.loaders |= ModPlatform::NeoForge; - } else if (loader == "forge") { + else if (loader == "forge") file.loaders |= ModPlatform::Forge; - } else if (loader == "cauldron") { + else if (loader == "cauldron") file.loaders |= ModPlatform::Cauldron; - } else if (loader == "liteloader") { + else if (loader == "liteloader") file.loaders |= ModPlatform::LiteLoader; - } else if (loader == "fabric") { + else if (loader == "fabric") file.loaders |= ModPlatform::Fabric; - } else if (loader == "quilt") { + else if (loader == "quilt") file.loaders |= ModPlatform::Quilt; - } } file.version = Json::requireString(obj, "name"); file.version_number = Json::requireString(obj, "version_number"); file.version_type = ModPlatform::IndexedVersionType::fromString(Json::requireString(obj, "version_type")); - if (obj.contains("changelog")) { - file.changelog = Json::requireString(obj, "changelog"); - } + file.changelog = Json::requireString(obj, "changelog"); auto dependencies = obj["dependencies"].toArray(); for (auto d : dependencies) { @@ -164,17 +158,16 @@ ModPlatform::IndexedVersion Modrinth::loadIndexedPackVersion(QJsonObject& obj, dependency.version = dep["version_id"].toString(); auto depType = Json::requireString(dep, "dependency_type"); - if (depType == "required") { + if (depType == "required") dependency.type = ModPlatform::DependencyType::REQUIRED; - } else if (depType == "optional") { + else if (depType == "optional") dependency.type = ModPlatform::DependencyType::OPTIONAL; - } else if (depType == "incompatible") { + else if (depType == "incompatible") dependency.type = ModPlatform::DependencyType::INCOMPATIBLE; - } else if (depType == "embedded") { + else if (depType == "embedded") dependency.type = ModPlatform::DependencyType::EMBEDDED; - } else { + else dependency.type = ModPlatform::DependencyType::UNKNOWN; - } file.dependencies.append(dependency); } @@ -202,9 +195,8 @@ ModPlatform::IndexedVersion Modrinth::loadIndexedPackVersion(QJsonObject& obj, } // Grab the primary file, if available - if (Json::requireBoolean(parent, "primary")) { + if (Json::requireBoolean(parent, "primary")) break; - } i++; } diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.h b/launcher/modplatform/modrinth/ModrinthPackIndex.h index b38cd9ce6..5d852cb6f 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.h +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.h @@ -23,9 +23,8 @@ namespace Modrinth { -void loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj); -void loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& obj); -auto loadIndexedPackVersion(QJsonObject& obj, const QString& preferred_hash_type = "sha512", const QString& preferred_file_name = "") - -> ModPlatform::IndexedVersion; +void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj); +void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj); +auto loadIndexedPackVersion(QJsonObject& obj, QString hash_type = "sha512", QString filename_prefer = "") -> ModPlatform::IndexedVersion; } // namespace Modrinth diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index 4d162a90d..a9d75b4ae 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -22,89 +22,73 @@ #include #include #include -#include -#include #include #include -#include #include "FileSystem.h" #include "StringUtils.h" -#include "Version.h" #include "modplatform/ModIndex.h" #include namespace Packwiz { -namespace { -auto getRealIndexName(const QDir& indexDir, const QString& normalizedFname, bool shouldFindMatch = false) -> QString +auto getRealIndexName(const QDir& index_dir, QString normalized_fname, bool should_find_match) -> QString { - const QFile indexFile(indexDir.absoluteFilePath(normalizedFname)); + QFile index_file(index_dir.absoluteFilePath(normalized_fname)); - QString realFname = normalizedFname; - if (!indexFile.exists()) { + QString real_fname = normalized_fname; + if (!index_file.exists()) { // Tries to get similar entries - for (auto& fileName : indexDir.entryList(QDir::Filter::Files)) { - if (QString::compare(normalizedFname, fileName, Qt::CaseInsensitive) == 0) { - realFname = fileName; + for (auto& file_name : index_dir.entryList(QDir::Filter::Files)) { + if (!QString::compare(normalized_fname, file_name, Qt::CaseInsensitive)) { + real_fname = file_name; break; } } - if (shouldFindMatch && (QString::compare(normalizedFname, realFname, Qt::CaseSensitive) == 0)) { + if (should_find_match && !QString::compare(normalized_fname, real_fname, Qt::CaseSensitive)) { qCritical() << "Could not find a match for a valid metadata file!"; - qCritical() << "File:" << normalizedFname; + qCritical() << "File:" << normalized_fname; return {}; } } - return realFname; + return real_fname; } // Helpers -auto indexFileName(const QString& modSlug) -> QString +static inline auto indexFileName(QString const& mod_slug) -> QString { - if (modSlug.endsWith(".pw.toml")) { - return modSlug; - } - return QString("%1.pw.toml").arg(modSlug); + if (mod_slug.endsWith(".pw.toml")) + return mod_slug; + return QString("%1.pw.toml").arg(mod_slug); } // Helper functions for extracting data from the TOML file -auto stringEntry(toml::table table, const QString& entryName) -> QString +auto stringEntry(toml::table table, QString entry_name) -> QString { - auto* node = table.get(StringUtils::toStdString(entryName)); + auto node = table[StringUtils::toStdString(entry_name)]; if (!node) { - qDebug() << "Failed to read str property '" + entryName + "' in mod metadata."; + qDebug() << "Failed to read str property '" + entry_name + "' in mod metadata."; return {}; } - return node->value_or(""); + return node.value_or(""); } -auto intEntry(toml::table table, const QString& entryName) -> int +auto intEntry(toml::table table, QString entry_name) -> int { - auto* node = table.get(StringUtils::toStdString(entryName)); + auto node = table[StringUtils::toStdString(entry_name)]; if (!node) { - qDebug() << "Failed to read int property '" + entryName + "' in mod metadata."; + qDebug() << "Failed to read int property '" + entry_name + "' in mod metadata."; return {}; } - return node->value_or(0); + return node.value_or(0); } -bool sortMCVersions(const QString& a, const QString& b) -{ - auto cmp = Version(a) <=> Version(b); - if (cmp == std::strong_ordering::equal) { - return a < b; - } - return cmp == std::strong_ordering::less; -} - -} // namespace auto V1::createModFormat([[maybe_unused]] const QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod @@ -131,15 +115,13 @@ auto V1::createModFormat([[maybe_unused]] const QDir& index_dir, 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.removeDuplicates(); - std::ranges::sort(mod.mcVersions, sortMCVersions); + mod.mcVersions.sort(); mod.releaseType = mod_version.version_type; mod.version_number = mod_version.version_number; if (mod.version_number.isNull()) // on CurseForge, there is only a version name - not a version number mod.version_number = mod_version.version; - mod.dependencies = mod_version.dependencies; return mod; } @@ -204,20 +186,10 @@ void V1::updateModIndex(const QDir& index_dir, Mod& mod) } if (!index_file.open(QIODevice::ReadWrite)) { - qCritical() << "Could not open file" << normalized_fname << "error:" << index_file.errorString(); + qCritical() << QString("Could not open file %1!").arg(normalized_fname); return; } - toml::array deps; - for (auto dep : mod.dependencies) { - auto tbl = toml::table{ { "addonId", dep.addonId.toString().toStdString() }, - { "type", ModPlatform::DependencyTypeUtils::toString(dep.type).toStdString() } }; - if (!dep.version.isEmpty()) { - tbl.emplace("version", dep.version.toStdString()); - } - deps.push_back(tbl); - } - // Put TOML data into the file QTextStream in_stream(&index_file); { @@ -228,7 +200,6 @@ void V1::updateModIndex(const QDir& index_dir, Mod& mod) { "x-prismlauncher-mc-versions", mcVersions }, { "x-prismlauncher-release-type", mod.releaseType.toString().toStdString() }, { "x-prismlauncher-version-number", mod.version_number.toStdString() }, - { "x-prismlauncher-dependencies", deps }, { "download", toml::table{ { "mode", mod.mode.toStdString() }, @@ -318,8 +289,7 @@ auto V1::getIndexForMod(const QDir& index_dir, QString slug) -> Mod } } } - mod.mcVersions.removeDuplicates(); - std::ranges::sort(mod.mcVersions, sortMCVersions); + mod.mcVersions.sort(); } } mod.version_number = table["x-prismlauncher-version-number"].value_or(""); @@ -360,23 +330,6 @@ auto V1::getIndexForMod(const QDir& index_dir, QString slug) -> Mod return {}; } } - { // dependencies - auto deps = table["x-prismlauncher-dependencies"].as_array(); - if (deps) { - for (auto&& depNode : *deps) { - auto dep = depNode.as_table(); - if (dep) { - ModPlatform::Dependency d; - d.addonId = stringEntry(*dep, "addonId"); - if (dep->contains("version")) { - d.version = stringEntry(*dep, "version"); - } - d.type = ModPlatform::DependencyTypeUtils::fromString(stringEntry(*dep, "type")); - mod.dependencies << d; - } - } - } - } return mod; } diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h index b5b817755..ba9a0fe75 100644 --- a/launcher/modplatform/packwiz/Packwiz.h +++ b/launcher/modplatform/packwiz/Packwiz.h @@ -29,6 +29,8 @@ class QDir; namespace Packwiz { +auto getRealIndexName(const QDir& index_dir, QString normalized_index_name, bool should_match = false) -> QString; + class V1 { public: // can also represent other resources beside loader mods - but this is what packwiz calls it @@ -53,8 +55,6 @@ class V1 { QVariant project_id{}; QString version_number{}; - QList dependencies; - public: // This is a totally heuristic, but should work for now. auto isValid() const -> bool { return !slug.isEmpty() && !project_id.isNull(); } diff --git a/launcher/modplatform/technic/SolderPackInstallTask.cpp b/launcher/modplatform/technic/SolderPackInstallTask.cpp index e7f4b7a55..852f27241 100644 --- a/launcher/modplatform/technic/SolderPackInstallTask.cpp +++ b/launcher/modplatform/technic/SolderPackInstallTask.cpp @@ -45,7 +45,7 @@ #include "net/ApiDownload.h" #include "net/ChecksumValidator.h" -Technic::SolderPackInstallTask::SolderPackInstallTask(QNetworkAccessManager* network, +Technic::SolderPackInstallTask::SolderPackInstallTask(shared_qobject_ptr network, const QUrl& solderUrl, const QString& pack, const QString& version, @@ -72,25 +72,24 @@ void Technic::SolderPackInstallTask::executeTask() m_filesNetJob.reset(new NetJob(tr("Resolving modpack files"), m_network)); auto sourceUrl = QString("%1/modpack/%2/%3").arg(m_solderUrl.toString(), m_pack, m_version); - auto [action, response] = Net::ApiDownload::makeByteArray(sourceUrl); - m_filesNetJob->addNetAction(action); + m_filesNetJob->addNetAction(Net::ApiDownload::makeByteArray(sourceUrl, m_response)); auto job = m_filesNetJob.get(); - connect(job, &NetJob::succeeded, this, [this, response] { fileListSucceeded(response); }); + connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::fileListSucceeded); connect(job, &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed); connect(job, &NetJob::aborted, this, &Technic::SolderPackInstallTask::downloadAborted); m_filesNetJob->start(); } -void Technic::SolderPackInstallTask::fileListSucceeded(QByteArray* response) +void Technic::SolderPackInstallTask::fileListSucceeded() { setStatus(tr("Downloading modpack")); QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + QJsonDocument doc = QJsonDocument::fromJson(*m_response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { qWarning() << "Error while parsing JSON response from Solder at" << parse_error.offset << "reason:" << parse_error.errorString(); - qWarning() << *response; + qWarning() << *m_response; return; } auto obj = doc.object(); diff --git a/launcher/modplatform/technic/SolderPackInstallTask.h b/launcher/modplatform/technic/SolderPackInstallTask.h index 07cce4644..2ea701e23 100644 --- a/launcher/modplatform/technic/SolderPackInstallTask.h +++ b/launcher/modplatform/technic/SolderPackInstallTask.h @@ -40,12 +40,13 @@ #include #include +#include namespace Technic { class SolderPackInstallTask : public InstanceTask { Q_OBJECT public: - explicit SolderPackInstallTask(QNetworkAccessManager* network, + explicit SolderPackInstallTask(shared_qobject_ptr network, const QUrl& solderUrl, const QString& pack, const QString& version, @@ -59,7 +60,7 @@ class SolderPackInstallTask : public InstanceTask { virtual void executeTask() override; private slots: - void fileListSucceeded(QByteArray* response); + void fileListSucceeded(); void downloadSucceeded(); void downloadFailed(QString reason); void downloadProgressChanged(qint64 current, qint64 total); @@ -70,13 +71,14 @@ class SolderPackInstallTask : public InstanceTask { private: bool m_abortable = false; - QNetworkAccessManager* m_network; + shared_qobject_ptr m_network; NetJob::Ptr m_filesNetJob; QUrl m_solderUrl; QString m_pack; QString m_version; QString m_minecraftVersion; + std::shared_ptr m_response = std::make_shared(); QTemporaryDir m_outputDir; int m_modCount; QFuture m_extractFuture; diff --git a/launcher/modplatform/technic/TechnicPackProcessor.cpp b/launcher/modplatform/technic/TechnicPackProcessor.cpp index 858f4ae6b..4c40ddf73 100644 --- a/launcher/modplatform/technic/TechnicPackProcessor.cpp +++ b/launcher/modplatform/technic/TechnicPackProcessor.cpp @@ -24,7 +24,7 @@ #include #include "archive/ArchiveReader.h" -void Technic::TechnicPackProcessor::run(SettingsObject* globalSettings, +void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, const QString& instName, const QString& instIcon, const QString& stagingPath, @@ -33,8 +33,8 @@ void Technic::TechnicPackProcessor::run(SettingsObject* globalSettings, { QString minecraftPath = FS::PathCombine(stagingPath, "minecraft"); QString configPath = FS::PathCombine(stagingPath, "instance.cfg"); - auto instanceSettings = std::make_unique(configPath); - MinecraftInstance instance(globalSettings, std::move(instanceSettings), stagingPath); + auto instanceSettings = std::make_shared(configPath); + MinecraftInstance instance(globalSettings, instanceSettings, stagingPath); instance.setName(instName); @@ -117,7 +117,7 @@ void Technic::TechnicPackProcessor::run(SettingsObject* globalSettings, } else if (QFile::exists(versionJson)) { QFile file(versionJson); if (!file.open(QIODevice::ReadOnly)) { - emit failed(tr("Unable to open \"version.json\": %1").arg(file.errorString())); + emit failed(tr("Unable to open \"version.json\"!")); return; } data = file.readAll(); diff --git a/launcher/modplatform/technic/TechnicPackProcessor.h b/launcher/modplatform/technic/TechnicPackProcessor.h index 0d2dabc93..08e117fd8 100644 --- a/launcher/modplatform/technic/TechnicPackProcessor.h +++ b/launcher/modplatform/technic/TechnicPackProcessor.h @@ -28,7 +28,7 @@ class TechnicPackProcessor : public QObject { void failed(QString reason); public: - void run(SettingsObject* globalSettings, + void run(SettingsObjectPtr globalSettings, const QString& instName, const QString& instIcon, const QString& stagingPath, diff --git a/launcher/net/ApiDownload.cpp b/launcher/net/ApiDownload.cpp index 54d3e746e..78eb1f851 100644 --- a/launcher/net/ApiDownload.cpp +++ b/launcher/net/ApiDownload.cpp @@ -18,30 +18,28 @@ */ #include "net/ApiDownload.h" - -#include #include "net/ApiHeaderProxy.h" namespace Net { Download::Ptr ApiDownload::makeCached(QUrl url, MetaEntryPtr entry, Download::Options options) { - auto dl = Download::makeCached(std::move(url), std::move(entry), options); - dl->addHeaderProxy(std::make_unique()); + auto dl = Download::makeCached(url, entry, options); + dl->addHeaderProxy(new ApiHeaderProxy()); return dl; } -std::pair ApiDownload::makeByteArray(QUrl url, Download::Options options) +Download::Ptr ApiDownload::makeByteArray(QUrl url, std::shared_ptr output, Download::Options options) { - auto [dl, response] = Download::makeByteArray(std::move(url), options); - dl->addHeaderProxy(std::make_unique()); - return { dl, response }; + auto dl = Download::makeByteArray(url, output, options); + dl->addHeaderProxy(new ApiHeaderProxy()); + return dl; } -Download::Ptr ApiDownload::makeFile(QUrl url, QString path, Download::Options options, ModrinthDownloadMeta meta) +Download::Ptr ApiDownload::makeFile(QUrl url, QString path, Download::Options options) { - auto dl = Download::makeFile(std::move(url), std::move(path), options); - dl->addHeaderProxy(std::make_unique(std::move(meta))); + auto dl = Download::makeFile(url, path, options); + dl->addHeaderProxy(new ApiHeaderProxy()); return dl; } diff --git a/launcher/net/ApiDownload.h b/launcher/net/ApiDownload.h index 033dd0e12..842c25c56 100644 --- a/launcher/net/ApiDownload.h +++ b/launcher/net/ApiDownload.h @@ -20,17 +20,13 @@ #pragma once #include "Download.h" -#include "net/ApiHeaderProxy.h" namespace Net { namespace ApiDownload { Download::Ptr makeCached(QUrl url, MetaEntryPtr entry, Download::Options options = Download::Option::NoOptions); -std::pair makeByteArray(QUrl url, Download::Options options = Download::Option::NoOptions); -Download::Ptr makeFile(QUrl url, - QString path, - Download::Options options = Download::Option::NoOptions, - ModrinthDownloadMeta meta = ModrinthDownloadMeta()); +Download::Ptr makeByteArray(QUrl url, std::shared_ptr output, Download::Options options = Download::Option::NoOptions); +Download::Ptr makeFile(QUrl url, QString path, Download::Options options = Download::Option::NoOptions); }; // namespace ApiDownload } // namespace Net diff --git a/launcher/net/ApiHeaderProxy.h b/launcher/net/ApiHeaderProxy.h index d12287a4e..789a6fada 100644 --- a/launcher/net/ApiHeaderProxy.h +++ b/launcher/net/ApiHeaderProxy.h @@ -23,64 +23,27 @@ #include "BuildConfig.h" #include "net/HeaderProxy.h" -#include -#include - namespace Net { -struct ModrinthDownloadMeta { - QString reason; - QString gameVersion; - QString loader; - - bool isEmpty() const { return reason.isEmpty(); } - - QByteArray toJson() const - { - QJsonObject obj; - if (!reason.isEmpty()) { - obj["reason"] = reason; - } - if (!gameVersion.isEmpty()) { - obj["game_version"] = gameVersion; - } - if (!loader.isEmpty()) { - obj["loader"] = loader; - } - return QJsonDocument(obj).toJson(QJsonDocument::Compact); - } -}; - class ApiHeaderProxy : public HeaderProxy { public: - ApiHeaderProxy() = default; - explicit ApiHeaderProxy(ModrinthDownloadMeta meta) : m_meta(std::move(meta)) {} - ~ApiHeaderProxy() override = default; + ApiHeaderProxy() : HeaderProxy() {} + virtual ~ApiHeaderProxy() = default; public: - QList headers(const QNetworkRequest& request) const override + virtual QList headers(const QNetworkRequest& request) const override { QList hdrs; - const auto host = request.url().host(); - - if (APPLICATION->capabilities() & Application::SupportsFlame && - (host == QUrl(BuildConfig.FLAME_BASE_URL).host() || host == BuildConfig.FLAME_DOWNLOAD_HOST)) { - hdrs.append({ .headerName = "x-api-key", .headerValue = APPLICATION->getFlameAPIKey().toUtf8() }); - } else if (host == QUrl(BuildConfig.MODRINTH_PROD_URL).host() || host == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) { + if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host() == QUrl(BuildConfig.FLAME_BASE_URL).host()) { + hdrs.append({ "x-api-key", APPLICATION->getFlameAPIKey().toUtf8() }); + } else if (request.url().host() == QUrl(BuildConfig.MODRINTH_PROD_URL).host() || + request.url().host() == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) { QString token = APPLICATION->getModrinthAPIToken(); - if (!token.isNull()) { - hdrs.append({ .headerName = "Authorization", .headerValue = token.toUtf8() }); - } - } - - if (host == BuildConfig.MODRINTH_DOWNLOAD_HOST && !m_meta.isEmpty()) { - hdrs.append({ .headerName = "modrinth-download-meta", .headerValue = m_meta.toJson() }); + if (!token.isNull()) + hdrs.append({ "Authorization", token.toUtf8() }); } return hdrs; }; - - private: - ModrinthDownloadMeta m_meta; }; } // namespace Net diff --git a/launcher/net/ApiUpload.cpp b/launcher/net/ApiUpload.cpp index 261558130..a2b8f357b 100644 --- a/launcher/net/ApiUpload.cpp +++ b/launcher/net/ApiUpload.cpp @@ -22,11 +22,11 @@ namespace Net { -std::pair ApiUpload::makeByteArray(QUrl url, QByteArray m_post_data) +Upload::Ptr ApiUpload::makeByteArray(QUrl url, std::shared_ptr output, QByteArray m_post_data) { - auto [up, response] = Upload::makeByteArray(url, m_post_data); - up->addHeaderProxy(std::make_unique()); - return { up, response }; + auto up = Upload::makeByteArray(url, output, m_post_data); + up->addHeaderProxy(new ApiHeaderProxy()); + return up; } } // namespace Net diff --git a/launcher/net/ApiUpload.h b/launcher/net/ApiUpload.h index 3aa4adeab..674a3b93f 100644 --- a/launcher/net/ApiUpload.h +++ b/launcher/net/ApiUpload.h @@ -24,7 +24,7 @@ namespace Net { namespace ApiUpload { -std::pair makeByteArray(QUrl url, QByteArray m_post_data); +Upload::Ptr makeByteArray(QUrl url, std::shared_ptr output, QByteArray m_post_data); }; } // namespace Net diff --git a/launcher/net/ByteArraySink.h b/launcher/net/ByteArraySink.h index b03d7192a..f68230838 100644 --- a/launcher/net/ByteArraySink.h +++ b/launcher/net/ByteArraySink.h @@ -41,16 +41,21 @@ namespace Net { /* - * Sink object for downloads that uses an owned QByteArray as a target. + * Sink object for downloads that uses an external QByteArray it doesn't own as a target. */ class ByteArraySink : public Sink { public: + ByteArraySink(std::shared_ptr output) : m_output(output) {}; + virtual ~ByteArraySink() = default; public: auto init(QNetworkRequest& request) -> Task::State override { - m_output.clear(); + if (m_output) + m_output->clear(); + else + qWarning() << "ByteArraySink did not initialize the buffer because it's not addressable"; if (initAllValidators(request)) return Task::State::Running; m_fail_reason = "Failed to initialize validators"; @@ -59,7 +64,10 @@ class ByteArraySink : public Sink { auto write(QByteArray& data) -> Task::State override { - m_output.append(data); + if (m_output) + m_output->append(data); + else + qWarning() << "ByteArraySink did not write the buffer because it's not addressable"; if (writeAllValidators(data)) return Task::State::Running; m_fail_reason = "Failed to write validators"; @@ -83,9 +91,7 @@ class ByteArraySink : public Sink { auto hasLocalData() -> bool override { return false; } - QByteArray* output() { return &m_output; } - protected: - QByteArray m_output; + std::shared_ptr m_output; }; } // namespace Net diff --git a/launcher/net/ChecksumValidator.h b/launcher/net/ChecksumValidator.h index c7906cc13..7663d5d12 100644 --- a/launcher/net/ChecksumValidator.h +++ b/launcher/net/ChecksumValidator.h @@ -38,6 +38,7 @@ #include "Validator.h" #include +#include namespace Net { class ChecksumValidator : public Validator { @@ -68,10 +69,10 @@ class ChecksumValidator : public Validator { return true; } - auto validate(QNetworkReply& reply) -> bool override + auto validate(QNetworkReply&) -> bool override { - if (!m_expected.isEmpty() && m_expected != hash()) { - qWarning() << "Checksum mismatch for URL:" << reply.url().toString() << "expected:" << m_expected << "got:" << hash(); + if (m_expected.size() && m_expected != hash()) { + qWarning() << "Checksum mismatch, download is bad."; return false; } return true; diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index 9a22c87e8..49686db98 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -63,18 +63,14 @@ auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Down } #endif -auto Download::makeByteArray(QUrl url, Options options) -> std::pair +auto Download::makeByteArray(QUrl url, std::shared_ptr output, Options options) -> Download::Ptr { auto dl = makeShared(); dl->m_url = url; dl->setObjectName(QString("BYTES:") + url.toString()); dl->m_options = options; - - auto sink = std::make_unique(); - QByteArray* response = sink->output(); - dl->m_sink = std::move(sink); - - return { dl, response }; + dl->m_sink.reset(new ByteArraySink(output)); + return dl; } auto Download::makeFile(QUrl url, QString path, Options options) -> Download::Ptr diff --git a/launcher/net/Download.h b/launcher/net/Download.h index 60a5b5b64..5f6a5caf1 100644 --- a/launcher/net/Download.h +++ b/launcher/net/Download.h @@ -38,16 +38,12 @@ #pragma once -#include - #include "HttpMetaCache.h" #include "QObjectPtr.h" #include "net/NetRequest.h" namespace Net { -class ByteArraySink; - class Download : public NetRequest { Q_OBJECT public: @@ -58,11 +54,7 @@ class Download : public NetRequest { static auto makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions) -> Download::Ptr; #endif - /** - * Creates a request downloading to the returned QByteArray,. - * The QByteArray will live as long as the Download object. - */ - static auto makeByteArray(QUrl url, Options options = Option::NoOptions) -> std::pair; + static auto makeByteArray(QUrl url, std::shared_ptr output, Options options = Option::NoOptions) -> Download::Ptr; static auto makeFile(QUrl url, QString path, Options options = Option::NoOptions) -> Download::Ptr; protected: diff --git a/launcher/net/DummySink.h b/launcher/net/DummySink.h deleted file mode 100644 index fa540fd2d..000000000 --- a/launcher/net/DummySink.h +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (c) 2025 Octol1ttle - * - * 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 - -namespace Net { - -class DummySink : public Sink { - public: - explicit DummySink() {} - ~DummySink() override {} - auto init(QNetworkRequest& request) -> Task::State override { return Task::State::Running; } - auto write(QByteArray& data) -> Task::State override { return Task::State::Succeeded; } - auto abort() -> Task::State override { return Task::State::AbortedByUser; } - auto finalize(QNetworkReply& reply) -> Task::State override { return Task::State::Succeeded; } - auto hasLocalData() -> bool override { return false; } -}; - -} // namespace Net diff --git a/launcher/net/FileSink.cpp b/launcher/net/FileSink.cpp index 47838f62c..f61dd527d 100644 --- a/launcher/net/FileSink.cpp +++ b/launcher/net/FileSink.cpp @@ -58,9 +58,8 @@ Task::State FileSink::init(QNetworkRequest& request) m_wroteAnyData = false; m_output_file.reset(new PSaveFile(m_filename)); if (!m_output_file->open(QIODevice::WriteOnly)) { - const auto error = QString("Could not open %1 for writing: %2").arg(m_filename).arg(m_output_file->errorString()); - qCCritical(taskNetLogC) << error; - m_fail_reason = error; + qCCritical(taskNetLogC) << "Could not open " + m_filename + " for writing"; + m_fail_reason = "Could not open file"; return Task::State::Failed; } @@ -73,17 +72,11 @@ Task::State FileSink::init(QNetworkRequest& request) Task::State FileSink::write(QByteArray& data) { if (!writeAllValidators(data) || m_output_file->write(data) != data.size()) { - QString error = QString("Failed writing into %1: %2").arg(m_filename); - if (m_output_file->error() == QFileDevice::NoError) { - error = error.arg("Validators failed"); - } else { - error = error.arg(m_output_file->errorString()); - } - qCCritical(taskNetLogC) << error; - m_fail_reason = error; + qCCritical(taskNetLogC) << "Failed writing into " + m_filename; m_output_file->cancelWriting(); m_output_file.reset(); m_wroteAnyData = false; + m_fail_reason = "Failed to write validators"; return Task::State::Failed; } @@ -123,10 +116,9 @@ Task::State FileSink::finalize(QNetworkReply& reply) // nothing went wrong... if (!m_output_file->commit()) { - const auto error = QString("Failed to commit changes to %1: %2").arg(m_filename).arg(m_output_file->errorString()); - qCCritical(taskNetLogC) << error; - m_fail_reason = error; + qCCritical(taskNetLogC) << "Failed to commit changes to" << m_filename; m_output_file->cancelWriting(); + m_fail_reason = "Failed to commit changes"; return Task::State::Failed; } } diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp index 5c1e47dfd..9ad9fdf43 100644 --- a/launcher/net/HttpMetaCache.cpp +++ b/launcher/net/HttpMetaCache.cpp @@ -113,7 +113,7 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex if (file_last_changed != entry->m_local_changed_timestamp) { QFile input(real_path); if (!input.open(QIODevice::ReadOnly)) { - qWarning() << "Failed to open file" << input.fileName() << "for reading:" << input.errorString(); + qWarning() << "Failed to open file '" << input.fileName() << "' for reading!"; return staleEntry(base, resource_path); } QString md5sum = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Md5).toHex().constData(); @@ -182,7 +182,7 @@ auto HttpMetaCache::evictAll() -> bool } map.entry_list.clear(); // AND all return codes together so the result is true iff all runs of deletePath() are true - ret &= FS::deleteContents(map.base_path); + ret &= FS::deletePath(map.base_path); } return ret; } diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index 3dd1c09cf..335e360b2 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -41,11 +41,11 @@ #include "tasks/ConcurrentTask.h" #if defined(LAUNCHER_APPLICATION) #include "Application.h" -#include "settings/SettingsObject.h" -#include "ui/dialogs/NetworkJobFailedDialog.h" +#include "ui/dialogs/CustomMessageBox.h" #endif -NetJob::NetJob(QString job_name, QNetworkAccessManager* network, int max_concurrent) : ConcurrentTask(job_name), m_network(network) +NetJob::NetJob(QString job_name, shared_qobject_ptr network, int max_concurrent) + : ConcurrentTask(job_name), m_network(network) { #if defined(LAUNCHER_APPLICATION) if (APPLICATION_DYN && max_concurrent < 0) @@ -69,15 +69,11 @@ void NetJob::executeNextSubTask() // We're finished, check for failures and retry if we can (up to 3 times) if (isRunning() && m_queue.isEmpty() && m_doing.isEmpty() && !m_failed.isEmpty() && m_try < 3) { m_try += 1; - m_failed.removeIf([this](QHash::iterator task) { - // there is no point in retying on 404 Not Found - if (static_cast(task->get())->replyStatusCode() == 404) { - return false; - } - m_done.remove(task->get()); - m_queue.enqueue(*task); - return true; - }); + while (!m_failed.isEmpty()) { + auto task = m_failed.take(*m_failed.keyBegin()); + m_done.remove(task.get()); + m_queue.enqueue(task); + } } ConcurrentTask::executeNextSubTask(); } @@ -104,18 +100,13 @@ auto NetJob::canAbort() const -> bool auto NetJob::abort() -> bool { + bool fullyAborted = true; + // fail all downloads on the queue for (auto task : m_queue) m_failed.insert(task.get(), task); m_queue.clear(); - if (m_doing.isEmpty()) { - // no downloads to abort, NetJob is not running - return true; - } - - bool fullyAborted = true; - // abort active downloads auto toKill = m_doing.values(); for (auto part : toKill) { @@ -173,29 +164,23 @@ void NetJob::emitFailed(QString reason) if (APPLICATION_DYN && m_ask_retry && m_manual_try < APPLICATION->settings()->get("NumberOfManualRetries").toInt() && isOnline()) { m_manual_try++; - auto failed = getFailedActions(); - auto dialog = new NetworkJobFailedDialog(objectName(), m_try, m_done.size(), failed.size(), nullptr); - dialog->setAttribute(Qt::WA_DeleteOnClose); + auto response = CustomMessageBox::selectable(nullptr, "Confirm retry", + "The tasks failed.\n" + "Failed urls\n" + + getFailedFiles().join("\n\t") + + ".\n" + "If this continues to happen please check the logs of the application.\n" + "Do you want to retry?", + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); - for (const auto& request : failed) { - dialog->addFailedRequest(request->url(), request->errorString()); + if (response == QMessageBox::Yes) { + m_try = 0; + executeNextSubTask(); + return; } - - dialog->open(); - - connect(dialog, &QDialog::finished, this, [this, reason = std::move(reason)](int result) { - if (result == QDialog::Accepted) { - m_try = 0; - executeNextSubTask(); - } else { - ConcurrentTask::emitFailed(reason); - } - }); - - return; } #endif - ConcurrentTask::emitFailed(reason); } diff --git a/launcher/net/NetJob.h b/launcher/net/NetJob.h index e8351f686..59213ba15 100644 --- a/launcher/net/NetJob.h +++ b/launcher/net/NetJob.h @@ -50,10 +50,9 @@ class NetJob : public ConcurrentTask { Q_OBJECT public: - // TODO: delete using Ptr = shared_qobject_ptr; - explicit NetJob(QString job_name, QNetworkAccessManager* network, int max_concurrent = -1); + explicit NetJob(QString job_name, shared_qobject_ptr network, int max_concurrent = -1); ~NetJob() override = default; auto size() const -> int; @@ -78,7 +77,7 @@ class NetJob : public ConcurrentTask { bool isOnline(); private: - QNetworkAccessManager* m_network; + shared_qobject_ptr m_network; int m_try = 1; bool m_ask_retry = true; diff --git a/launcher/net/NetRequest.cpp b/launcher/net/NetRequest.cpp index 7faeffbe0..957b2d1e5 100644 --- a/launcher/net/NetRequest.cpp +++ b/launcher/net/NetRequest.cpp @@ -41,15 +41,12 @@ #include #include -#include #include #include -#include #include #if defined(LAUNCHER_APPLICATION) #include "Application.h" -#include "settings/SettingsObject.h" #endif #include "BuildConfig.h" @@ -58,11 +55,6 @@ namespace Net { -NetRequest::NetRequest() : Task() -{ - connect(&m_retryTimer, &QTimer::timeout, this, &NetRequest::executeTask); -} - void NetRequest::addValidator(Validator* v) { m_sink->addValidator(v); @@ -169,20 +161,6 @@ void NetRequest::downloadError(QNetworkReply::NetworkError error) if (error == QNetworkReply::OperationCanceledError) { qCCritical(logCat) << getUid().toString() << "Aborted" << m_url.toString(); m_state = State::Failed; - } else if (replyStatusCode() == 429 /* HTTP Too Many Requests*/ && m_options & Option::AutoRetry) { - qCDebug(logCat) << getUid().toString() << "Rate Limited!"; - int64_t delay = 10 * std::pow(2, m_retryCount); - if (m_reply->hasRawHeader("Retry-After")) { - auto retryAfter = m_reply->rawHeader("Retry-After"); - if (retryAfter.trimmed().endsWith("GMT")) /* HTTP Date format */ { - auto afterTimestamp = QDateTime::fromString(QString::fromUtf8(retryAfter.trimmed()), "ddd, dd MMM yyyy HH:mm:ss 'GMT'"); - auto now = QDateTime::currentDateTime(); - delay = now.secsTo(afterTimestamp); - } else { - delay = retryAfter.toLong(); - } - } - handleAutoRetry(delay); } else { if (m_options & Option::AcceptLocalFiles) { if (m_sink->hasLocalData()) { @@ -204,8 +182,7 @@ void NetRequest::sslErrors(const QList& errors) { int i = 1; for (auto error : errors) { - qCCritical(logCat).nospace() << getUid().toString() << " Request " << m_url.toString() << " SSL Error #" << i << ": " - << error.errorString(); + qCCritical(logCat).nospace() << getUid().toString() << " Request " << m_url.toString() << " SSL Error #" << i << ": " << error.errorString(); auto cert = error.certificate(); qCCritical(logCat) << getUid().toString() << "Certificate in question:\n" << cert.toText(); i++; @@ -266,33 +243,8 @@ auto NetRequest::handleRedirect() -> bool return true; } -void NetRequest::handleAutoRetry(int64_t delay) -{ - m_retryCount++; - if (delay > 60 || m_retryCount > 4) { - /* 1 minute is too long to wait for retry, fail for now */ - m_state = State::Failed; - auto retryAfter = QDateTime::currentDateTime().addSecs(delay); - emitFailed(tr("Request Rate Limited for %n second(s): Retry After %1", "seconds", delay) - .arg(retryAfter.toLocalTime().toString(QLocale::system().dateTimeFormat(QLocale::ShortFormat)))); - return; - } else { - qCDebug(logCat) << getUid().toString() << "Retyring Request in" << delay << "seconds"; - setStatus(tr("Rate Limited: Waiting %n second(s)", "seconds", delay)); - m_retryTimer.setTimerType(Qt::VeryCoarseTimer); - m_retryTimer.setSingleShot(true); - m_retryTimer.setInterval(delay * 1000); - m_retryTimer.start(); - } -} - void NetRequest::downloadFinished() { - // currently waiting for retry - if (m_retryTimer.isActive()) { - return; - } - // handle HTTP redirection first if (handleRedirect()) { qCDebug(logCat) << getUid().toString() << "Request redirected:" << m_url.toString(); @@ -399,14 +351,4 @@ QString NetRequest::errorString() const { return m_reply ? m_reply->errorString() : ""; } - -void NetRequest::enableAutoRetry(bool enable) -{ - if (enable) { - m_options |= Option::AutoRetry; - } else { - m_options &= ~static_cast(Option::AutoRetry); - } -} - } // namespace Net diff --git a/launcher/net/NetRequest.h b/launcher/net/NetRequest.h index e38152b83..714459810 100644 --- a/launcher/net/NetRequest.h +++ b/launcher/net/NetRequest.h @@ -41,7 +41,6 @@ #include #include -#include #include #include "HeaderProxy.h" @@ -56,11 +55,11 @@ namespace Net { class NetRequest : public Task { Q_OBJECT protected: - explicit NetRequest(); + explicit NetRequest() : Task() {} public: using Ptr = shared_qobject_ptr; - enum class Option { NoOptions = 0, AcceptLocalFiles = 1, MakeEternal = 2, AutoRetry = 4 }; + enum class Option { NoOptions = 0, AcceptLocalFiles = 1, MakeEternal = 2 }; Q_DECLARE_FLAGS(Options, Option) public: @@ -69,11 +68,8 @@ class NetRequest : public Task { auto abort() -> bool override; auto canAbort() const -> bool override { return true; } - void setNetwork(QNetworkAccessManager* network) { m_network = network; } - void addHeaderProxy(std::unique_ptr proxy) { m_headerProxies.push_back(std::move(proxy)); } - - // automatically handle HTTP 429 Too Many Requests errors and retry - void enableAutoRetry(bool enable); + void setNetwork(shared_qobject_ptr network) { m_network = network; } + void addHeaderProxy(Net::HeaderProxy* proxy) { m_headerProxies.push_back(std::shared_ptr(proxy)); } QUrl url() const; void setUrl(QUrl url) { m_url = url; } @@ -83,7 +79,6 @@ class NetRequest : public Task { private: auto handleRedirect() -> bool; - void handleAutoRetry(int64_t delay); virtual QNetworkReply* getReply(QNetworkRequest&) = 0; protected slots: @@ -105,18 +100,15 @@ class NetRequest : public Task { std::chrono::time_point m_last_progress_time; qint64 m_last_progress_bytes; - QNetworkAccessManager* m_network; + shared_qobject_ptr m_network; /// the network reply - std::unique_ptr m_reply; + unique_qobject_ptr m_reply; QByteArray m_errorResponse; /// source URL QUrl m_url; - std::vector> m_headerProxies; - - int m_retryCount = 0; - QTimer m_retryTimer; + std::vector> m_headerProxies; }; } // namespace Net diff --git a/launcher/net/NetUtils.h b/launcher/net/NetUtils.h index 67ebc9685..cd517bcca 100644 --- a/launcher/net/NetUtils.h +++ b/launcher/net/NetUtils.h @@ -40,18 +40,4 @@ inline bool isApplicationError(QNetworkReply::NetworkError x) QNetworkReply::UnknownContentError }; return errors.contains(x); } - -// 500 class errors, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/500 -// microsoft may send these error codes when services (auth) are down. -// We treat this as a reason to launch in offline mode. -inline bool isServerError(QNetworkReply::NetworkError x) -{ - static QSet errors = { QNetworkReply::InternalServerError, - QNetworkReply::OperationNotImplementedError, - QNetworkReply::ServiceUnavailableError, // 503 | seen in logs in 2026 - //QNetworkReply::GatewayTimeoutError, // 504 | seen in logs in 2024 - // Qt doesn't have it mapped. Unknown covers it - QNetworkReply::UnknownServerError }; - return errors.contains(x); -} } // namespace Net diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index ecbf4e201..a72d63f29 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -119,11 +119,11 @@ auto PasteUpload::Sink::finalize(QNetworkReply& reply) -> Task::State switch (m_d->m_paste_type) { case PasteUpload::NullPointer: - m_d->m_pasteLink = QString::fromUtf8(*output()).trimmed(); + m_d->m_pasteLink = QString::fromUtf8(*m_output).trimmed(); break; case PasteUpload::Hastebin: { QJsonParseError jsonError; - auto doc = QJsonDocument::fromJson(*output(), &jsonError); + auto doc = QJsonDocument::fromJson(*m_output, &jsonError); if (jsonError.error != QJsonParseError::NoError) { qDebug() << "hastebin server did not reply with JSON" << jsonError.errorString(); m_fail_reason = @@ -144,7 +144,7 @@ auto PasteUpload::Sink::finalize(QNetworkReply& reply) -> Task::State } case PasteUpload::Mclogs: { QJsonParseError jsonError; - auto doc = QJsonDocument::fromJson(*output(), &jsonError); + auto doc = QJsonDocument::fromJson(*m_output, &jsonError); if (jsonError.error != QJsonParseError::NoError) { qDebug() << "mclogs server did not reply with JSON" << jsonError.errorString(); m_fail_reason = @@ -171,7 +171,7 @@ auto PasteUpload::Sink::finalize(QNetworkReply& reply) -> Task::State } case PasteUpload::PasteGG: QJsonParseError jsonError; - auto doc = QJsonDocument::fromJson(*output(), &jsonError); + auto doc = QJsonDocument::fromJson(*m_output, &jsonError); if (jsonError.error != QJsonParseError::NoError) { qDebug() << "pastegg server did not reply with JSON" << jsonError.errorString(); m_fail_reason = diff --git a/launcher/net/PasteUpload.h b/launcher/net/PasteUpload.h index d22a9ba47..7f43779c4 100644 --- a/launcher/net/PasteUpload.h +++ b/launcher/net/PasteUpload.h @@ -45,7 +45,6 @@ #include #include -#include class PasteUpload : public Net::NetRequest { public: @@ -72,7 +71,7 @@ class PasteUpload : public Net::NetRequest { class Sink : public Net::ByteArraySink { public: - Sink(PasteUpload* p) : m_d(p) {}; + Sink(PasteUpload* p) : Net::ByteArraySink(std::make_shared()), m_d(p) {}; virtual ~Sink() = default; public: diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp index 60cf6d3ec..623ec80f4 100644 --- a/launcher/net/Upload.cpp +++ b/launcher/net/Upload.cpp @@ -51,16 +51,12 @@ QNetworkReply* Upload::getReply(QNetworkRequest& request) return m_network->post(request, m_post_data); } -std::pair Upload::makeByteArray(QUrl url, QByteArray m_post_data) +Upload::Ptr Upload::makeByteArray(QUrl url, std::shared_ptr output, QByteArray m_post_data) { auto up = makeShared(); up->m_url = std::move(url); - - auto sink = std::make_unique(); - QByteArray* response = sink->output(); - up->m_sink = std::move(sink); - + up->m_sink.reset(new ByteArraySink(output)); up->m_post_data = std::move(m_post_data); - return { up, response }; + return up; } } // namespace Net diff --git a/launcher/net/Upload.h b/launcher/net/Upload.h index 0610426a1..f920e5561 100644 --- a/launcher/net/Upload.h +++ b/launcher/net/Upload.h @@ -37,8 +37,6 @@ #pragma once -#include - #include "net/NetRequest.h" namespace Net { @@ -49,11 +47,7 @@ class Upload : public NetRequest { using Ptr = shared_qobject_ptr; explicit Upload() : NetRequest() { logCat = taskUploadLogC; }; - /** - * Creates a request downloading to the returned QByteArray,. - * The QByteArray will live as long as the Upload object. - */ - static std::pair makeByteArray(QUrl url, QByteArray m_post_data); + static Upload::Ptr makeByteArray(QUrl url, std::shared_ptr output, QByteArray m_post_data); protected: virtual QNetworkReply* getReply(QNetworkRequest&) override; diff --git a/launcher/news/NewsChecker.cpp b/launcher/news/NewsChecker.cpp index 35173dd7b..dc4447aba 100644 --- a/launcher/news/NewsChecker.cpp +++ b/launcher/news/NewsChecker.cpp @@ -39,9 +39,8 @@ #include #include -#include "Application.h" -NewsChecker::NewsChecker(QNetworkAccessManager* network, const QString& feedUrl) +NewsChecker::NewsChecker(shared_qobject_ptr network, const QString& feedUrl) { m_network = network; m_feedUrl = feedUrl; @@ -55,12 +54,10 @@ void NewsChecker::reloadNews() return; } - m_entry = APPLICATION->metacache()->resolveEntry("feed", "feed.xml"); - qDebug() << "Reloading news."; NetJob::Ptr job{ new NetJob("News RSS Feed", m_network) }; - job->addNetAction(Net::Download::makeCached(m_feedUrl, m_entry)); + job->addNetAction(Net::Download::makeByteArray(m_feedUrl, newsData)); job->setAskRetry(false); connect(job.get(), &NetJob::succeeded, this, &NewsChecker::rssDownloadFinished); connect(job.get(), &NetJob::failed, this, &NewsChecker::rssDownloadFailed); @@ -81,16 +78,14 @@ void NewsChecker::rssDownloadFinished() int errorLine = -1; int errorCol = -1; - QFile feed(m_entry->getFullPath()); - - if (feed.open(QFile::ReadOnly | QFile::Text)) { - QTextStream in(&feed); - // Parse the XML. - if (!doc.setContent(in.readAll(), false, &errorMsg, &errorLine, &errorCol)) { - fail(QString("Error parsing RSS feed XML. %1 at %2:%3.").arg(errorMsg).arg(errorLine).arg(errorCol)); - return; - } + // Parse the XML. + if (!doc.setContent(*newsData, false, &errorMsg, &errorLine, &errorCol)) { + QString fullErrorMsg = QString("Error parsing RSS feed XML. %1 at %2:%3.").arg(errorMsg).arg(errorLine).arg(errorCol); + fail(fullErrorMsg); + newsData->clear(); + return; } + newsData->clear(); } // If the parsing succeeded, read it. diff --git a/launcher/news/NewsChecker.h b/launcher/news/NewsChecker.h index 497ae2319..cdd621a20 100644 --- a/launcher/news/NewsChecker.h +++ b/launcher/news/NewsChecker.h @@ -29,7 +29,7 @@ class NewsChecker : public QObject { /*! * Constructs a news reader to read from the given RSS feed URL. */ - NewsChecker(QNetworkAccessManager* network, const QString& feedUrl); + NewsChecker(shared_qobject_ptr network, const QString& feedUrl); /*! * Returns the error message for the last time the news was loaded. @@ -84,8 +84,7 @@ class NewsChecker : public QObject { //! True if news has been loaded. bool m_loadedNews; - //! The cache entry for the feed. - MetaEntryPtr m_entry; + std::shared_ptr newsData = std::make_shared(); /*! * Gets the error message that was given last time the news was loaded. @@ -93,7 +92,7 @@ class NewsChecker : public QObject { */ QString m_lastLoadError; - QNetworkAccessManager* m_network; + shared_qobject_ptr m_network; protected slots: /// Emits newsLoaded() and sets m_lastLoadError to empty string. diff --git a/launcher/resources/multimc/16x16/about.png b/launcher/resources/multimc/16x16/about.png new file mode 100644 index 000000000..ed7e56dd6 Binary files /dev/null and b/launcher/resources/multimc/16x16/about.png differ diff --git a/launcher/resources/multimc/16x16/checkupdate.png b/launcher/resources/multimc/16x16/checkupdate.png new file mode 100644 index 000000000..9d08c56f0 Binary files /dev/null and b/launcher/resources/multimc/16x16/checkupdate.png differ diff --git a/launcher/resources/multimc/16x16/new.png b/launcher/resources/multimc/16x16/new.png new file mode 100644 index 000000000..dfde06f61 Binary files /dev/null and b/launcher/resources/multimc/16x16/new.png differ diff --git a/launcher/resources/multimc/22x22/about.png b/launcher/resources/multimc/22x22/about.png new file mode 100644 index 000000000..fbf18726f Binary files /dev/null and b/launcher/resources/multimc/22x22/about.png differ diff --git a/launcher/resources/multimc/22x22/checkupdate.png b/launcher/resources/multimc/22x22/checkupdate.png new file mode 100644 index 000000000..a44d47fe0 Binary files /dev/null and b/launcher/resources/multimc/22x22/checkupdate.png differ diff --git a/launcher/resources/multimc/22x22/new.png b/launcher/resources/multimc/22x22/new.png new file mode 100644 index 000000000..41edf3ef0 Binary files /dev/null and b/launcher/resources/multimc/22x22/new.png differ diff --git a/launcher/resources/multimc/32x32/about.png b/launcher/resources/multimc/32x32/about.png new file mode 100644 index 000000000..261d2a44c Binary files /dev/null and b/launcher/resources/multimc/32x32/about.png differ diff --git a/launcher/resources/multimc/32x32/checkupdate.png b/launcher/resources/multimc/32x32/checkupdate.png new file mode 100644 index 000000000..c60f965b2 Binary files /dev/null and b/launcher/resources/multimc/32x32/checkupdate.png differ diff --git a/launcher/resources/multimc/32x32/new.png b/launcher/resources/multimc/32x32/new.png new file mode 100644 index 000000000..8fc4be64a Binary files /dev/null and b/launcher/resources/multimc/32x32/new.png differ diff --git a/launcher/resources/multimc/48x48/about.png b/launcher/resources/multimc/48x48/about.png new file mode 100644 index 000000000..f6d4d11cb Binary files /dev/null and b/launcher/resources/multimc/48x48/about.png differ diff --git a/launcher/resources/multimc/48x48/checkupdate.png b/launcher/resources/multimc/48x48/checkupdate.png new file mode 100644 index 000000000..b181736a8 Binary files /dev/null and b/launcher/resources/multimc/48x48/checkupdate.png differ diff --git a/launcher/resources/multimc/48x48/new.png b/launcher/resources/multimc/48x48/new.png new file mode 100644 index 000000000..a81ccf1b2 Binary files /dev/null and b/launcher/resources/multimc/48x48/new.png differ diff --git a/launcher/resources/multimc/64x64/about.png b/launcher/resources/multimc/64x64/about.png new file mode 100644 index 000000000..b9be9abef Binary files /dev/null and b/launcher/resources/multimc/64x64/about.png differ diff --git a/launcher/resources/multimc/64x64/checkupdate.png b/launcher/resources/multimc/64x64/checkupdate.png new file mode 100644 index 000000000..a4002a61e Binary files /dev/null and b/launcher/resources/multimc/64x64/checkupdate.png differ diff --git a/launcher/resources/multimc/64x64/new.png b/launcher/resources/multimc/64x64/new.png new file mode 100644 index 000000000..289a6ad0b Binary files /dev/null and b/launcher/resources/multimc/64x64/new.png differ diff --git a/launcher/resources/multimc/multimc.qrc b/launcher/resources/multimc/multimc.qrc index 2a4736a93..2937cc34b 100644 --- a/launcher/resources/multimc/multimc.qrc +++ b/launcher/resources/multimc/multimc.qrc @@ -49,6 +49,13 @@ 48x48/minecraft.png 256x256/minecraft.png + + 16x16/about.png + 22x22/about.png + 32x32/about.png + 48x48/about.png + 64x64/about.png + scalable/bug.svg 16x16/bug.png @@ -85,6 +92,14 @@ 48x48/centralmods.png 64x64/centralmods.png + + scalable/checkupdate.svg + 16x16/checkupdate.png + 22x22/checkupdate.png + 32x32/checkupdate.png + 48x48/checkupdate.png + 64x64/checkupdate.png + 16x16/copy.png 22x22/copy.png @@ -99,6 +114,13 @@ 48x48/help.png 64x64/help.png + + 16x16/new.png + 22x22/new.png + 32x32/new.png + 48x48/new.png + 64x64/new.png + scalable/news.svg 16x16/news.png @@ -315,18 +337,16 @@ scalable/instances/fox_legacy.svg scalable/instances/bee_legacy.svg - + scalable/delete.svg scalable/tag.svg scalable/rename.svg scalable/shortcut.svg + scalable/export.svg scalable/launch.svg scalable/server.svg scalable/appearance.svg - scalable/about.svg - scalable/new.svg - scalable/checkupdate.svg scalable/instances/quiltmc.svg scalable/instances/fabricmc.svg @@ -338,7 +358,6 @@ scalable/adoptium.svg scalable/azul.svg scalable/mojang.svg - scalable/openj9_hex_custom.svg diff --git a/launcher/resources/multimc/scalable/about.svg b/launcher/resources/multimc/scalable/about.svg deleted file mode 100644 index b97c79d89..000000000 --- a/launcher/resources/multimc/scalable/about.svg +++ /dev/null @@ -1,3928 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -image/svg+xmlimage/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/launcher/resources/multimc/scalable/about.svg.license b/launcher/resources/multimc/scalable/about.svg.license deleted file mode 100644 index 92494ca5c..000000000 --- a/launcher/resources/multimc/scalable/about.svg.license +++ /dev/null @@ -1,3 +0,0 @@ -SPDX-FileCopyrightText: 2007 KDE Community - -SPDX-License-Identifier: LGPL-3.0-or-later diff --git a/launcher/resources/multimc/scalable/checkupdate.svg b/launcher/resources/multimc/scalable/checkupdate.svg index 4ab18c929..fc09cb4c7 100644 --- a/launcher/resources/multimc/scalable/checkupdate.svg +++ b/launcher/resources/multimc/scalable/checkupdate.svg @@ -1,1566 +1,167 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + unsorted + + + + + Open Clip Art Library, Source: GNOME-Colors, Source: GNOME-Colors, Source: GNOME-Colors, Source: GNOME-Colors, Source: GNOME-Colors + + + + + + + + + + + + + image/svg+xml - + + + en - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/launcher/resources/multimc/scalable/checkupdate.svg.license b/launcher/resources/multimc/scalable/checkupdate.svg.license deleted file mode 100644 index 92494ca5c..000000000 --- a/launcher/resources/multimc/scalable/checkupdate.svg.license +++ /dev/null @@ -1,3 +0,0 @@ -SPDX-FileCopyrightText: 2007 KDE Community - -SPDX-License-Identifier: LGPL-3.0-or-later diff --git a/launcher/resources/multimc/scalable/new.svg b/launcher/resources/multimc/scalable/new.svg index 8b9f1a8ab..c9cff3589 100644 --- a/launcher/resources/multimc/scalable/new.svg +++ b/launcher/resources/multimc/scalable/new.svg @@ -1,602 +1,127 @@ - - - - - - - - - - - - - - - - - - - - - - - - - -image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + New Document + + + + regular + plaintext + text + document + + + + + Source: GNOME Icon Theme, Source: GNOME Icon Theme, Source: GNOME Icon Theme, Source: GNOME Icon Theme, Source: GNOME Icon Theme + + + + + Jakub Steiner + + + + + Jakub Steiner + + + + image/svg+xml + + + en + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/launcher/resources/multimc/scalable/new.svg.license b/launcher/resources/multimc/scalable/new.svg.license deleted file mode 100644 index 92494ca5c..000000000 --- a/launcher/resources/multimc/scalable/new.svg.license +++ /dev/null @@ -1,3 +0,0 @@ -SPDX-FileCopyrightText: 2007 KDE Community - -SPDX-License-Identifier: LGPL-3.0-or-later diff --git a/launcher/resources/multimc/scalable/openj9_hex_custom.svg b/launcher/resources/multimc/scalable/openj9_hex_custom.svg deleted file mode 100644 index 27064b79a..000000000 --- a/launcher/resources/multimc/scalable/openj9_hex_custom.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/launcher/resources/multimc/scalable/openj9_hex_custom.svg.license b/launcher/resources/multimc/scalable/openj9_hex_custom.svg.license deleted file mode 100644 index 289f8006f..000000000 --- a/launcher/resources/multimc/scalable/openj9_hex_custom.svg.license +++ /dev/null @@ -1,4 +0,0 @@ -SPDX-FileCopyrightText: 2017-2026 Ronald Servant -SPDX-FileCopyrightText: 2026 Ludgie - -SPDX-License-Identifier: Apache-2.0 diff --git a/launcher/screenshots/ImgurAlbumCreation.cpp b/launcher/screenshots/ImgurAlbumCreation.cpp index 411e4f793..010beb124 100644 --- a/launcher/screenshots/ImgurAlbumCreation.cpp +++ b/launcher/screenshots/ImgurAlbumCreation.cpp @@ -54,7 +54,7 @@ Net::NetRequest::Ptr ImgurAlbumCreation::make(std::shared_ptrm_url = BuildConfig.IMGUR_BASE_URL + "album"; up->m_sink.reset(new Sink(output)); up->m_screenshots = screenshots; - up->addHeaderProxy(std::make_unique( + up->addHeaderProxy(new Net::RawHeaderProxy( QList{ { "Content-Type", "application/x-www-form-urlencoded" }, { "Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toUtf8() }, { "Accept", "application/json" } })); diff --git a/launcher/screenshots/ImgurUpload.cpp b/launcher/screenshots/ImgurUpload.cpp index dd41fc340..835a1ab81 100644 --- a/launcher/screenshots/ImgurUpload.cpp +++ b/launcher/screenshots/ImgurUpload.cpp @@ -52,7 +52,7 @@ QNetworkReply* ImgurUpload::getReply(QNetworkRequest& request) auto file = new QFile(m_fileInfo.absoluteFilePath(), this); if (!file->open(QFile::ReadOnly)) { - emitFailed(tr("Could not open file %1 for reading: %2").arg(m_fileInfo.absoluteFilePath()).arg(file->errorString())); + emitFailed(); return nullptr; } @@ -118,9 +118,9 @@ auto ImgurUpload::Sink::finalize(QNetworkReply&) -> Task::State Net::NetRequest::Ptr ImgurUpload::make(ScreenShot::Ptr m_shot) { auto up = makeShared(m_shot->m_file); - up->m_url = BuildConfig.IMGUR_BASE_URL + "image"; + up->m_url = std::move(BuildConfig.IMGUR_BASE_URL + "image"); up->m_sink.reset(new Sink(m_shot)); - up->addHeaderProxy(std::make_unique(QList{ + up->addHeaderProxy(new Net::RawHeaderProxy(QList{ { "Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toUtf8() }, { "Accept", "application/json" } })); return up; } diff --git a/launcher/settings/INIFile.cpp b/launcher/settings/INIFile.cpp index a1d1c01d9..75e888938 100644 --- a/launcher/settings/INIFile.cpp +++ b/launcher/settings/INIFile.cpp @@ -35,8 +35,6 @@ */ #include "settings/INIFile.h" - -#include #include #include @@ -64,10 +62,11 @@ bool INIFile::saveFile(QString fileName) _settings_obj.sync(); if (auto status = _settings_obj.status(); status != QSettings::Status::NoError) { + // Shouldn't be possible! + Q_ASSERT(status != QSettings::Status::FormatError); + if (status == QSettings::Status::AccessError) - qCritical() << "An access error occurred while saving INI file" << fileName << "(is the file read-only?)"; - if (ASSERT_NEVER(status == QSettings::Status::FormatError)) - qCritical() << "A format error occurred while saving INI file" << fileName << "(this shouldn't be possible!)"; + qCritical() << "An access error occurred (e.g. trying to write to a read-only file)."; return false; } @@ -179,9 +178,9 @@ bool INIFile::loadFile(QString fileName) if (auto status = _settings_obj.status(); status != QSettings::Status::NoError) { if (status == QSettings::Status::AccessError) - qCritical() << "An access error occurred while loading INI file" << fileName; + qCritical() << "An access error occurred (e.g. trying to write to a read-only file)."; if (status == QSettings::Status::FormatError) - qCritical() << "A format error occurred while loading INI file" << fileName << "(is the file malformed or corrupted?)"; + qCritical() << "A format error occurred (e.g. loading a malformed INI file)."; return false; } if (!_settings_obj.value("ConfigVersion").isValid()) { diff --git a/launcher/settings/SettingsObject.h b/launcher/settings/SettingsObject.h index 5743bee54..abd6c29c5 100644 --- a/launcher/settings/SettingsObject.h +++ b/launcher/settings/SettingsObject.h @@ -30,6 +30,9 @@ class Setting; class SettingsObject; +using SettingsObjectPtr = std::shared_ptr; +using SettingsObjectWeakPtr = std::weak_ptr; + /*! * \brief The SettingsObject handles communicating settings between the application and a *settings file. @@ -47,11 +50,11 @@ class SettingsObject : public QObject { public: class Lock { public: - Lock(SettingsObject* locked) : m_locked(locked) { m_locked->suspendSave(); } + Lock(SettingsObjectPtr locked) : m_locked(locked) { m_locked->suspendSave(); } ~Lock() { m_locked->resumeSave(); } private: - SettingsObject* m_locked; + SettingsObjectPtr m_locked; }; public: diff --git a/launcher/tasks/Task.cpp b/launcher/tasks/Task.cpp index 5857a0663..fe5d84bb9 100644 --- a/launcher/tasks/Task.cpp +++ b/launcher/tasks/Task.cpp @@ -48,13 +48,6 @@ Task::Task(bool show_debug) : m_show_debug(show_debug) setAutoDelete(false); } -Task::~Task() -{ - if (isRunning()) { - qCWarning(taskLogC) << "Task" << describe() << "disposed while running!"; - } -} - void Task::setStatus(const QString& new_status) { if (m_status != new_status) { @@ -138,7 +131,7 @@ void Task::emitAborted() return; } m_state = State::AbortedByUser; - m_failReason = tr("Aborted"); + m_failReason = "Aborted."; if (m_show_debug) qCDebug(taskLogC) << "Task" << describe() << "aborted."; emit aborted(); @@ -201,22 +194,6 @@ QString Task::failReason() const return m_failReason; } -void Task::propagateFromOther(Task* other) -{ - Q_ASSERT(other); - connect(other, &Task::status, this, &Task::setStatus); - connect(other, &Task::details, this, &Task::setDetails); - connect(other, &Task::progress, this, &Task::setProgress); - connect(other, &Task::stepProgress, this, &Task::propagateStepProgress); - - setStatus(other->getStatus()); - setDetails(other->getDetails()); - setProgress(other->getProgress(), other->getTotalProgress()); - for (const auto& progress : other->getStepProgress()) { - propagateStepProgress(*progress); - } -} - void Task::logWarning(const QString& line) { qWarning() << line; diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index 38c09da90..43e71c8ab 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -94,7 +94,7 @@ class Task : public QObject, public QRunnable { public: explicit Task(bool show_debug_log = true); - ~Task() override; + virtual ~Task() = default; bool isRunning() const; bool isFinished() const; @@ -127,9 +127,6 @@ class Task : public QObject, public QRunnable { QUuid getUid() { return m_uid; } - // Copies the other task's status, details, progress, and step progress to this task; and sets up connections for future propagation - void propagateFromOther(Task* other); - protected: void logWarning(const QString& line); @@ -154,8 +151,6 @@ class Task : public QObject, public QRunnable { //! Emitted when the canAbort() status has changed. */ void abortStatusChanged(bool can_abort); - void abortButtonTextChanged(QString text); - public slots: // QRunnable's interface void run() override { start(); } @@ -165,7 +160,7 @@ class Task : public QObject, public QRunnable { //! used by external code to ask the task to abort virtual bool abort() { - if (canAbort() && isRunning()) + if (canAbort()) emitAborted(); return canAbort(); } @@ -176,11 +171,6 @@ class Task : public QObject, public QRunnable { emit abortStatusChanged(can_abort); } - void setAbortButtonText(QString text) - { - emit abortButtonTextChanged(text); - } - protected: //! The task subclass must implement this method. This method is called to start to run the task. //! The task is not finished when this method returns. the subclass must manually call emitSucceeded() or emitFailed() instead. diff --git a/launcher/tools/BaseExternalTool.cpp b/launcher/tools/BaseExternalTool.cpp index dd1a683b4..9e4b91cd8 100644 --- a/launcher/tools/BaseExternalTool.cpp +++ b/launcher/tools/BaseExternalTool.cpp @@ -9,13 +9,13 @@ #include "BaseInstance.h" -BaseExternalTool::BaseExternalTool(SettingsObject* settings, BaseInstance* instance, QObject* parent) +BaseExternalTool::BaseExternalTool(SettingsObjectPtr settings, InstancePtr instance, QObject* parent) : QObject(parent), m_instance(instance), globalSettings(settings) {} BaseExternalTool::~BaseExternalTool() {} -BaseDetachedTool::BaseDetachedTool(SettingsObject* settings, BaseInstance* instance, QObject* parent) +BaseDetachedTool::BaseDetachedTool(SettingsObjectPtr settings, InstancePtr instance, QObject* parent) : BaseExternalTool(settings, instance, parent) {} @@ -26,7 +26,7 @@ void BaseDetachedTool::run() BaseExternalToolFactory::~BaseExternalToolFactory() {} -BaseDetachedTool* BaseDetachedToolFactory::createDetachedTool(BaseInstance* instance, QObject* parent) +BaseDetachedTool* BaseDetachedToolFactory::createDetachedTool(InstancePtr instance, QObject* parent) { return qobject_cast(createTool(instance, parent)); } diff --git a/launcher/tools/BaseExternalTool.h b/launcher/tools/BaseExternalTool.h index 0890c8e5f..eb2d07e1e 100644 --- a/launcher/tools/BaseExternalTool.h +++ b/launcher/tools/BaseExternalTool.h @@ -10,18 +10,18 @@ class QProcess; class BaseExternalTool : public QObject { Q_OBJECT public: - explicit BaseExternalTool(SettingsObject* settings, BaseInstance* instance, QObject* parent = 0); + explicit BaseExternalTool(SettingsObjectPtr settings, InstancePtr instance, QObject* parent = 0); virtual ~BaseExternalTool(); protected: - BaseInstance* m_instance; - SettingsObject* globalSettings; + InstancePtr m_instance; + SettingsObjectPtr globalSettings; }; class BaseDetachedTool : public BaseExternalTool { Q_OBJECT public: - explicit BaseDetachedTool(SettingsObject* settings, BaseInstance* instance, QObject* parent = 0); + explicit BaseDetachedTool(SettingsObjectPtr settings, InstancePtr instance, QObject* parent = 0); public slots: void run(); @@ -36,18 +36,18 @@ class BaseExternalToolFactory { virtual QString name() const = 0; - virtual void registerSettings(SettingsObject* settings) = 0; + virtual void registerSettings(SettingsObjectPtr settings) = 0; - virtual BaseExternalTool* createTool(BaseInstance* instance, QObject* parent = 0) = 0; + virtual BaseExternalTool* createTool(InstancePtr instance, QObject* parent = 0) = 0; virtual bool check(QString* error) = 0; virtual bool check(const QString& path, QString* error) = 0; protected: - SettingsObject* globalSettings; + SettingsObjectPtr globalSettings; }; class BaseDetachedToolFactory : public BaseExternalToolFactory { public: - virtual BaseDetachedTool* createDetachedTool(BaseInstance* instance, QObject* parent = 0); + virtual BaseDetachedTool* createDetachedTool(InstancePtr instance, QObject* parent = 0); }; diff --git a/launcher/tools/BaseProfiler.cpp b/launcher/tools/BaseProfiler.cpp index f7a30fa2c..2ab1254e9 100644 --- a/launcher/tools/BaseProfiler.cpp +++ b/launcher/tools/BaseProfiler.cpp @@ -3,10 +3,10 @@ #include -BaseProfiler::BaseProfiler(SettingsObject* settings, BaseInstance* instance, QObject* parent) : BaseExternalTool(settings, instance, parent) +BaseProfiler::BaseProfiler(SettingsObjectPtr settings, InstancePtr instance, QObject* parent) : BaseExternalTool(settings, instance, parent) {} -void BaseProfiler::beginProfiling(LaunchTask* process) +void BaseProfiler::beginProfiling(shared_qobject_ptr process) { beginProfilingImpl(process); } @@ -27,7 +27,7 @@ void BaseProfiler::abortProfilingImpl() emit abortLaunch(tr("Profiler aborted")); } -BaseProfiler* BaseProfilerFactory::createProfiler(BaseInstance* instance, QObject* parent) +BaseProfiler* BaseProfilerFactory::createProfiler(InstancePtr instance, QObject* parent) { return qobject_cast(createTool(instance, parent)); } diff --git a/launcher/tools/BaseProfiler.h b/launcher/tools/BaseProfiler.h index b84a591d7..ac0f3a786 100644 --- a/launcher/tools/BaseProfiler.h +++ b/launcher/tools/BaseProfiler.h @@ -11,16 +11,16 @@ class QProcess; class BaseProfiler : public BaseExternalTool { Q_OBJECT public: - explicit BaseProfiler(SettingsObject* settings, BaseInstance* instance, QObject* parent = 0); + explicit BaseProfiler(SettingsObjectPtr settings, InstancePtr instance, QObject* parent = 0); public slots: - void beginProfiling(LaunchTask* process); + void beginProfiling(shared_qobject_ptr process); void abortProfiling(); protected: QProcess* m_profilerProcess; - virtual void beginProfilingImpl(LaunchTask* process) = 0; + virtual void beginProfilingImpl(shared_qobject_ptr process) = 0; virtual void abortProfilingImpl(); signals: @@ -30,5 +30,5 @@ class BaseProfiler : public BaseExternalTool { class BaseProfilerFactory : public BaseExternalToolFactory { public: - virtual BaseProfiler* createProfiler(BaseInstance* instance, QObject* parent = 0); + virtual BaseProfiler* createProfiler(InstancePtr instance, QObject* parent = 0); }; diff --git a/launcher/tools/GenericProfiler.cpp b/launcher/tools/GenericProfiler.cpp index 66c63d01a..f7b37b2b8 100644 --- a/launcher/tools/GenericProfiler.cpp +++ b/launcher/tools/GenericProfiler.cpp @@ -24,22 +24,22 @@ class GenericProfiler : public BaseProfiler { Q_OBJECT public: - GenericProfiler(SettingsObject* settings, BaseInstance* instance, QObject* parent = 0); + GenericProfiler(SettingsObjectPtr settings, InstancePtr instance, QObject* parent = 0); protected: - void beginProfilingImpl(LaunchTask* process); + void beginProfilingImpl(shared_qobject_ptr process); }; -GenericProfiler::GenericProfiler(SettingsObject* settings, BaseInstance* instance, QObject* parent) +GenericProfiler::GenericProfiler(SettingsObjectPtr settings, InstancePtr instance, QObject* parent) : BaseProfiler(settings, instance, parent) {} -void GenericProfiler::beginProfilingImpl(LaunchTask* process) +void GenericProfiler::beginProfilingImpl(shared_qobject_ptr process) { emit readyToLaunch(tr("Started process: %1").arg(process->pid())); } -BaseExternalTool* GenericProfilerFactory::createTool(BaseInstance* instance, QObject* parent) +BaseExternalTool* GenericProfilerFactory::createTool(InstancePtr instance, QObject* parent) { return new GenericProfiler(globalSettings, instance, parent); } diff --git a/launcher/tools/GenericProfiler.h b/launcher/tools/GenericProfiler.h index 49ce7271c..7868990ea 100644 --- a/launcher/tools/GenericProfiler.h +++ b/launcher/tools/GenericProfiler.h @@ -22,8 +22,8 @@ class GenericProfilerFactory : public BaseProfilerFactory { public: QString name() const override { return "Generic"; } - void registerSettings([[maybe_unused]] SettingsObject* settings) override {}; - BaseExternalTool* createTool(BaseInstance* instance, QObject* parent = 0) override; + void registerSettings([[maybe_unused]] SettingsObjectPtr settings) override {}; + BaseExternalTool* createTool(InstancePtr instance, QObject* parent = 0) override; bool check([[maybe_unused]] QString* error) override { return true; }; bool check([[maybe_unused]] const QString& path, [[maybe_unused]] QString* error) override { return true; }; }; diff --git a/launcher/tools/JProfiler.cpp b/launcher/tools/JProfiler.cpp index 5d51cde97..8550038d2 100644 --- a/launcher/tools/JProfiler.cpp +++ b/launcher/tools/JProfiler.cpp @@ -9,20 +9,20 @@ class JProfiler : public BaseProfiler { Q_OBJECT public: - JProfiler(SettingsObject* settings, BaseInstance* instance, QObject* parent = 0); + JProfiler(SettingsObjectPtr settings, InstancePtr instance, QObject* parent = 0); private slots: void profilerStarted(); void profilerFinished(int exit, QProcess::ExitStatus status); protected: - void beginProfilingImpl(LaunchTask* process); + void beginProfilingImpl(shared_qobject_ptr process); private: int listeningPort = 0; }; -JProfiler::JProfiler(SettingsObject* settings, BaseInstance* instance, QObject* parent) : BaseProfiler(settings, instance, parent) {} +JProfiler::JProfiler(SettingsObjectPtr settings, InstancePtr instance, QObject* parent) : BaseProfiler(settings, instance, parent) {} void JProfiler::profilerStarted() { @@ -40,7 +40,7 @@ void JProfiler::profilerFinished([[maybe_unused]] int exit, QProcess::ExitStatus } } -void JProfiler::beginProfilingImpl(LaunchTask* process) +void JProfiler::beginProfilingImpl(shared_qobject_ptr process) { listeningPort = globalSettings->get("JProfilerPort").toInt(); QProcess* profiler = new QProcess(this); @@ -63,14 +63,14 @@ void JProfiler::beginProfilingImpl(LaunchTask* process) profiler->start(); } -void JProfilerFactory::registerSettings(SettingsObject* settings) +void JProfilerFactory::registerSettings(SettingsObjectPtr settings) { settings->registerSetting("JProfilerPath"); settings->registerSetting("JProfilerPort", 42042); globalSettings = settings; } -BaseExternalTool* JProfilerFactory::createTool(BaseInstance* instance, QObject* parent) +BaseExternalTool* JProfilerFactory::createTool(InstancePtr instance, QObject* parent) { return new JProfiler(globalSettings, instance, parent); } diff --git a/launcher/tools/JProfiler.h b/launcher/tools/JProfiler.h index 4e6975c25..55715df32 100644 --- a/launcher/tools/JProfiler.h +++ b/launcher/tools/JProfiler.h @@ -5,8 +5,8 @@ class JProfilerFactory : public BaseProfilerFactory { public: QString name() const override { return "JProfiler"; } - void registerSettings(SettingsObject* settings) override; - BaseExternalTool* createTool(BaseInstance* instance, QObject* parent = 0) override; + void registerSettings(SettingsObjectPtr settings) override; + BaseExternalTool* createTool(InstancePtr instance, QObject* parent = 0) override; bool check(QString* error) override; bool check(const QString& path, QString* error) override; }; diff --git a/launcher/tools/JVisualVM.cpp b/launcher/tools/JVisualVM.cpp index 9155a1832..2e1cf69f7 100644 --- a/launcher/tools/JVisualVM.cpp +++ b/launcher/tools/JVisualVM.cpp @@ -10,17 +10,17 @@ class JVisualVM : public BaseProfiler { Q_OBJECT public: - JVisualVM(SettingsObject* settings, BaseInstance* instance, QObject* parent = 0); + JVisualVM(SettingsObjectPtr settings, InstancePtr instance, QObject* parent = 0); private slots: void profilerStarted(); void profilerFinished(int exit, QProcess::ExitStatus status); protected: - void beginProfilingImpl(LaunchTask* process); + void beginProfilingImpl(shared_qobject_ptr process); }; -JVisualVM::JVisualVM(SettingsObject* settings, BaseInstance* instance, QObject* parent) : BaseProfiler(settings, instance, parent) {} +JVisualVM::JVisualVM(SettingsObjectPtr settings, InstancePtr instance, QObject* parent) : BaseProfiler(settings, instance, parent) {} void JVisualVM::profilerStarted() { @@ -38,7 +38,7 @@ void JVisualVM::profilerFinished([[maybe_unused]] int exit, QProcess::ExitStatus } } -void JVisualVM::beginProfilingImpl(LaunchTask* process) +void JVisualVM::beginProfilingImpl(shared_qobject_ptr process) { QProcess* profiler = new QProcess(this); QStringList profilerArgs = { "--openpid", QString::number(process->pid()) }; @@ -54,7 +54,7 @@ void JVisualVM::beginProfilingImpl(LaunchTask* process) m_profilerProcess = profiler; } -void JVisualVMFactory::registerSettings(SettingsObject* settings) +void JVisualVMFactory::registerSettings(SettingsObjectPtr settings) { QString defaultValue = QStandardPaths::findExecutable("jvisualvm"); if (defaultValue.isNull()) { @@ -64,7 +64,7 @@ void JVisualVMFactory::registerSettings(SettingsObject* settings) globalSettings = settings; } -BaseExternalTool* JVisualVMFactory::createTool(BaseInstance* instance, QObject* parent) +BaseExternalTool* JVisualVMFactory::createTool(InstancePtr instance, QObject* parent) { return new JVisualVM(globalSettings, instance, parent); } diff --git a/launcher/tools/JVisualVM.h b/launcher/tools/JVisualVM.h index dfb09caf4..c152aecdb 100644 --- a/launcher/tools/JVisualVM.h +++ b/launcher/tools/JVisualVM.h @@ -5,8 +5,8 @@ class JVisualVMFactory : public BaseProfilerFactory { public: QString name() const override { return "VisualVM"; } - void registerSettings(SettingsObject* settings) override; - BaseExternalTool* createTool(BaseInstance* instance, QObject* parent = 0) override; + void registerSettings(SettingsObjectPtr settings) override; + BaseExternalTool* createTool(InstancePtr instance, QObject* parent = 0) override; bool check(QString* error) override; bool check(const QString& path, QString* error) override; }; diff --git a/launcher/tools/MCEditTool.cpp b/launcher/tools/MCEditTool.cpp index 12db12e42..e006a1411 100644 --- a/launcher/tools/MCEditTool.cpp +++ b/launcher/tools/MCEditTool.cpp @@ -8,7 +8,7 @@ #include "minecraft/MinecraftInstance.h" #include "settings/SettingsObject.h" -MCEditTool::MCEditTool(SettingsObject* settings) +MCEditTool::MCEditTool(SettingsObjectPtr settings) { settings->registerSetting("MCEditPath"); m_settings = settings; diff --git a/launcher/tools/MCEditTool.h b/launcher/tools/MCEditTool.h index edc9ffa27..fd2de1b6d 100644 --- a/launcher/tools/MCEditTool.h +++ b/launcher/tools/MCEditTool.h @@ -5,12 +5,12 @@ class MCEditTool { public: - MCEditTool(SettingsObject* settings); + MCEditTool(SettingsObjectPtr settings); void setPath(QString& path); QString path() const; bool check(const QString& toolPath, QString& error); QString getProgramPath(); private: - SettingsObject* m_settings; + SettingsObjectPtr m_settings; }; diff --git a/launcher/translations/POTranslator.cpp b/launcher/translations/POTranslator.cpp index 0bd3638e6..e4e2573c6 100644 --- a/launcher/translations/POTranslator.cpp +++ b/launcher/translations/POTranslator.cpp @@ -133,7 +133,7 @@ void POTranslatorPrivate::reload() { QFile file(filename); if (!file.open(QFile::OpenMode::enum_type::ReadOnly | QFile::OpenMode::enum_type::Text)) { - qDebug() << "Failed to open PO file:" << filename << "error:" << file.errorString(); + qDebug() << "Failed to open PO file:" << filename; return; } @@ -183,7 +183,8 @@ void POTranslatorPrivate::reload() nextFuzzy = true; } } else if (line.startsWith('"')) { - QByteArray* out = nullptr; + QByteArray temp; + QByteArray* out = &temp; switch (mode) { case Mode::First: diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index 7f905608b..ee2e240ab 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -36,9 +36,13 @@ #include "TranslationsModel.h" +#include #include -#include -#include +#include +#include +#include +#include +#include #include "BuildConfig.h" #include "FileSystem.h" @@ -49,28 +53,19 @@ #include "POTranslator.h" #include "Application.h" -#include "settings/SettingsObject.h" -static constexpr QLatin1String g_defaultLangCode("en_US"); +const static QLatin1String defaultLangCode("en_US"); -namespace { -enum class FileType : std::uint8_t { None, Qm, Po }; - -QString getSystemLocaleName() -{ - return QLocale::system().name(); -} - -QString getSystemLanguage() -{ - return getSystemLocaleName().split('_').front(); -} -} // namespace +enum class FileType { NONE, QM, PO }; struct Language { - Language() : updated(true) {} - - explicit Language(QString _key) : key(std::move(_key)), updated(key == g_defaultLangCode) { locale = QLocale(key); } + Language() { updated = true; } + Language(const QString& _key) + { + key = _key; + locale = QLocale(key); + updated = (key == defaultLangCode); + } QString languageName() const { @@ -102,12 +97,12 @@ struct Language { float percentTranslated() const { if (total == 0) { - return 100.0F; + return 100.0f; } - return 100.0F * static_cast(translated) / static_cast(total); + return 100.0f * float(translated) / float(total); } - void setTranslationStats(const unsigned _translated, const unsigned _untranslated, const unsigned _fuzzy) + void setTranslationStats(unsigned _translated, unsigned _untranslated, unsigned _fuzzy) { translated = _translated; untranslated = _untranslated; @@ -119,18 +114,18 @@ struct Language { bool isIdenticalTo(const Language& other) const { - return (key == other.key && fileName == other.fileName && fileSize == other.fileSize && fileSha1 == other.fileSha1 && + return (key == other.key && file_name == other.file_name && file_size == other.file_size && file_sha1 == other.file_sha1 && translated == other.translated && fuzzy == other.fuzzy && total == other.fuzzy && localFileType == other.localFileType); } - Language& apply(const Language& other) + Language& apply(Language& other) { if (!isOfSameNameAs(other)) { return *this; } - fileName = other.fileName; - fileSize = other.fileSize; - fileSha1 = other.fileSha1; + file_name = other.file_name; + file_size = other.file_size; + file_sha1 = other.file_sha1; translated = other.translated; fuzzy = other.fuzzy; total = other.total; @@ -142,44 +137,47 @@ struct Language { QLocale locale; bool updated; - QString fileName = QString(); - std::size_t fileSize = 0; - QString fileSha1 = QString(); + QString file_name = QString(); + std::size_t file_size = 0; + QString file_sha1 = QString(); unsigned translated = 0; unsigned untranslated = 0; unsigned fuzzy = 0; unsigned total = 0; - FileType localFileType = FileType::None; + FileType localFileType = FileType::NONE; }; struct TranslationsModel::Private { QDir m_dir; // initial state is just english - QList m_languages = { Language(g_defaultLangCode) }; + QList m_languages = { Language(defaultLangCode) }; - QString m_selectedLanguage = g_defaultLangCode; - std::unique_ptr m_qtTranslator; - std::unique_ptr m_appTranslator; + QString m_selectedLanguage = defaultLangCode; + std::unique_ptr m_qt_translator; + std::unique_ptr m_app_translator; - Net::Download* m_indexTask = nullptr; + Net::Download* m_index_task; QString m_downloadingTranslation; - NetJob::Ptr m_downloadJob; - NetJob::Ptr m_indexJob; + NetJob::Ptr m_dl_job; + NetJob::Ptr m_index_job; QString m_nextDownload; - QFileSystemWatcher* watcher = nullptr; + std::unique_ptr m_po_translator; + QFileSystemWatcher* watcher; - bool m_noLanguageSet = false; + const QString m_system_locale = QLocale::system().name(); + const QString m_system_language = m_system_locale.split('_').front(); + + bool no_language_set = false; }; -TranslationsModel::TranslationsModel(const QString& path, QObject* parent) : QAbstractListModel(parent) +TranslationsModel::TranslationsModel(QString path, QObject* parent) : QAbstractListModel(parent) { - d = std::make_unique(); + d.reset(new Private); d->m_dir.setPath(path); - d->m_selectedLanguage = APPLICATION->settings()->get("Language").toString(); FS::ensureFolderPathExists(path); reloadLocalFiles(); @@ -188,12 +186,12 @@ TranslationsModel::TranslationsModel(const QString& path, QObject* parent) : QAb d->watcher->addPath(d->m_dir.canonicalPath()); } -TranslationsModel::~TranslationsModel() = default; +TranslationsModel::~TranslationsModel() {} void TranslationsModel::translationDirChanged(const QString& path) { qDebug() << "Dir changed:" << path; - if (!d->m_noLanguageSet) { + if (!d->no_language_set) { reloadLocalFiles(); } selectLanguage(selectedLanguage()); @@ -202,21 +200,25 @@ void TranslationsModel::translationDirChanged(const QString& path) void TranslationsModel::indexReceived() { qDebug() << "Got translations index!"; - d->m_indexJob.reset(); - reloadLocalFiles(); + d->m_index_job.reset(); - if (d->m_noLanguageSet) { - auto language = getSystemLocaleName(); + if (d->no_language_set) { + reloadLocalFiles(); + + auto language = d->m_system_locale; if (!findLanguageAsOptional(language).has_value()) { - language = getSystemLanguage(); + language = d->m_system_language; } selectLanguage(language); + if (selectedLanguage() != defaultLangCode) { + updateLanguage(selectedLanguage()); + } APPLICATION->settings()->set("Language", selectedLanguage()); - d->m_noLanguageSet = false; + d->no_language_set = false; } - if (selectedLanguage() != g_defaultLangCode) { - updateLanguage(selectedLanguage()); + else if (d->m_selectedLanguage != defaultLangCode) { + downloadTranslation(d->m_selectedLanguage); } } @@ -232,27 +234,27 @@ void readIndex(const QString& path, QMap& languages) } try { - auto toplevelDoc = Json::requireDocument(data); - auto doc = Json::requireObject(toplevelDoc); - auto fileType = Json::requireString(doc, "file_type"); - if (fileType != "MMC-TRANSLATION-INDEX") { - qCritical() << "Translations Download Failed: index file is of unknown file type" << fileType; + auto toplevel_doc = Json::requireDocument(data); + auto doc = Json::requireObject(toplevel_doc); + auto file_type = Json::requireString(doc, "file_type"); + if (file_type != "MMC-TRANSLATION-INDEX") { + qCritical() << "Translations Download Failed: index file is of unknown file type" << file_type; return; } auto version = Json::requireInteger(doc, "version"); if (version > 2) { - qCritical() << "Translations Download Failed: index file is of unknown format version" << fileType; + qCritical() << "Translations Download Failed: index file is of unknown format version" << file_type; return; } auto langObjs = Json::requireObject(doc, "languages"); - for (auto iter = langObjs.begin(); iter != langObjs.end(); ++iter) { + for (auto iter = langObjs.begin(); iter != langObjs.end(); iter++) { Language lang(iter.key()); auto langObj = Json::requireObject(iter.value()); lang.setTranslationStats(langObj["translated"].toInt(), langObj["untranslated"].toInt(), langObj["fuzzy"].toInt()); - lang.fileName = Json::requireString(langObj, "file"); - lang.fileSha1 = Json::requireString(langObj, "sha1"); - lang.fileSize = Json::requireInteger(langObj, "size"); + lang.file_name = Json::requireString(langObj, "file"); + lang.file_sha1 = Json::requireString(langObj, "sha1"); + lang.file_size = Json::requireInteger(langObj, "size"); languages.insert(lang.key, lang); } @@ -264,25 +266,20 @@ void readIndex(const QString& path, QMap& languages) void TranslationsModel::reloadLocalFiles() { - QMap languages = { { g_defaultLangCode, Language(g_defaultLangCode) } }; + QMap languages = { { defaultLangCode, Language(defaultLangCode) } }; - const auto indexPath = d->m_dir.absoluteFilePath("index_v2.json"); - if (!QFileInfo::exists(indexPath)) { - downloadIndex(); - return; - } - readIndex(indexPath, languages); + readIndex(d->m_dir.absoluteFilePath("index_v2.json"), languages); auto entries = d->m_dir.entryInfoList({ "mmc_*.qm", "*.po" }, QDir::Files | QDir::NoDotAndDotDot); for (auto& entry : entries) { auto completeSuffix = entry.completeSuffix(); QString langCode; - FileType fileType = FileType::None; + FileType fileType = FileType::NONE; if (completeSuffix == "qm") { langCode = entry.baseName().remove(0, 4); - fileType = FileType::Qm; + fileType = FileType::QM; } else if (completeSuffix == "po") { langCode = entry.baseName(); - fileType = FileType::Po; + fileType = FileType::PO; } else { continue; } @@ -290,14 +287,13 @@ void TranslationsModel::reloadLocalFiles() auto langIter = languages.find(langCode); if (langIter != languages.end()) { auto& language = *langIter; - // TODO: use std::to_underlying in C++23 - if (static_cast(fileType) > static_cast(language.localFileType)) { + if (int(fileType) > int(language.localFileType)) { language.localFileType = fileType; } } else { - if (fileType == FileType::Po) { + if (fileType == FileType::PO) { Language localFound(langCode); - localFound.localFileType = FileType::Po; + localFound.localFileType = FileType::PO; languages.insert(langCode, localFound); } } @@ -317,7 +313,7 @@ void TranslationsModel::reloadLocalFiles() emit dataChanged(index(row), index(row)); languages.remove(language.key); } - ++iter; + iter++; } else { beginRemoveRows(QModelIndex(), row, row); iter = d->m_languages.erase(iter); @@ -332,38 +328,34 @@ void TranslationsModel::reloadLocalFiles() for (auto& language : languages) { d->m_languages.append(language); } - - const auto comp = [systemLocale = getSystemLocaleName(), systemLanguage = getSystemLanguage()](const Language& a, const Language& b) { + std::sort(d->m_languages.begin(), d->m_languages.end(), [this](const Language& a, const Language& b) { if (a.key != b.key) { - if (a.key == systemLocale || a.key == systemLanguage) { + if (a.key == d->m_system_locale || a.key == d->m_system_language) { return true; } - if (b.key == systemLocale || b.key == systemLanguage) { + if (b.key == d->m_system_locale || b.key == d->m_system_language) { return false; } } return a.languageName().toLower() < b.languageName().toLower(); - }; - std::ranges::sort(d->m_languages, comp); + }); endInsertRows(); } namespace { -enum class Column : std::uint8_t { Language, Completeness }; +enum class Column { Language, Completeness }; } -QVariant TranslationsModel::data(const QModelIndex& index, const int role) const +QVariant TranslationsModel::data(const QModelIndex& index, int role) const { - if (!index.isValid()) { - return {}; - } + if (!index.isValid()) + return QVariant(); - const int row = index.row(); - const auto column = static_cast(index.column()); + int row = index.row(); + auto column = static_cast(index.column()); - if (row < 0 || row >= d->m_languages.size()) { - return {}; - } + if (row < 0 || row >= d->m_languages.size()) + return QVariant(); auto& lang = d->m_languages[row]; switch (role) { @@ -385,11 +377,11 @@ QVariant TranslationsModel::data(const QModelIndex& index, const int role) const case Qt::UserRole: return lang.key; default: - return {}; + return QVariant(); } } -QVariant TranslationsModel::headerData(int section, const Qt::Orientation orientation, const int role) const +QVariant TranslationsModel::headerData(int section, Qt::Orientation orientation, int role) const { auto column = static_cast(section); if (role == Qt::DisplayRole) { @@ -424,50 +416,49 @@ int TranslationsModel::columnCount([[maybe_unused]] const QModelIndex& parent) c return 2; } -QList::Iterator TranslationsModel::findLanguage(const QString& key) const +QList::Iterator TranslationsModel::findLanguage(const QString& key) { - return std::ranges::find_if(d->m_languages, [key](const Language& lang) { return lang.key == key; }); + return std::find_if(d->m_languages.begin(), d->m_languages.end(), [key](Language& lang) { return lang.key == key; }); } -std::optional TranslationsModel::findLanguageAsOptional(const QString& key) const +std::optional TranslationsModel::findLanguageAsOptional(const QString& key) { auto found = findLanguage(key); - if (found != d->m_languages.end()) { + if (found != d->m_languages.end()) return *found; - } return {}; } -void TranslationsModel::setUseSystemLocale(const bool useSystemLocale) const +void TranslationsModel::setUseSystemLocale(bool useSystemLocale) { APPLICATION->settings()->set("UseSystemLocale", useSystemLocale); - QLocale::setDefault(useSystemLocale ? QLocale::system() : QLocale(selectedLanguage())); + QLocale::setDefault(QLocale(useSystemLocale ? QString::fromStdString(std::locale().name()) : defaultLangCode)); } -bool TranslationsModel::selectLanguage(QString key) const +bool TranslationsModel::selectLanguage(QString key) { QString& langCode = key; auto langPtr = findLanguageAsOptional(key); if (langCode.isEmpty()) { - d->m_noLanguageSet = true; + d->no_language_set = true; } if (!langPtr.has_value()) { - qWarning() << "Selected invalid language" << key << ", defaulting to" << g_defaultLangCode; - langCode = g_defaultLangCode; + qWarning() << "Selected invalid language" << key << ", defaulting to" << defaultLangCode; + langCode = defaultLangCode; } else { langCode = langPtr->key; } // uninstall existing translators if there are any - if (d->m_appTranslator) { - QCoreApplication::removeTranslator(d->m_appTranslator.get()); - d->m_appTranslator.reset(); + if (d->m_app_translator) { + QCoreApplication::removeTranslator(d->m_app_translator.get()); + d->m_app_translator.reset(); } - if (d->m_qtTranslator) { - QCoreApplication::removeTranslator(d->m_qtTranslator.get()); - d->m_qtTranslator.reset(); + if (d->m_qt_translator) { + QCoreApplication::removeTranslator(d->m_qt_translator.get()); + d->m_qt_translator.reset(); } /* @@ -475,11 +466,11 @@ bool TranslationsModel::selectLanguage(QString key) const * In a multithreaded application, the default locale should be set at application startup, before any non-GUI threads are created. * This function is not reentrant. */ - const bool useSystemLocale = APPLICATION->settings()->get("UseSystemLocale").toBool(); - QLocale::setDefault(useSystemLocale ? QLocale::system() : QLocale(langCode)); + QLocale::setDefault( + QLocale(APPLICATION->settings()->get("UseSystemLocale").toBool() ? QString::fromStdString(std::locale().name()) : langCode)); // if it's the default UI language, finish - if (langCode == g_defaultLangCode) { + if (langCode == defaultLangCode) { d->m_selectedLanguage = langCode; return true; } @@ -487,88 +478,89 @@ bool TranslationsModel::selectLanguage(QString key) const // otherwise install new translations bool successful = false; // FIXME: this is likely never present. FIX IT. - d->m_qtTranslator = std::make_unique(); - if (d->m_qtTranslator->load("qt_" + langCode, QLibraryInfo::path(QLibraryInfo::TranslationsPath))) { + d->m_qt_translator.reset(new QTranslator()); + if (d->m_qt_translator->load("qt_" + langCode, QLibraryInfo::path(QLibraryInfo::TranslationsPath))) { qDebug() << "Loading Qt Language File for" << langCode.toLocal8Bit().constData() << "..."; - if (!QCoreApplication::installTranslator(d->m_qtTranslator.get())) { + if (!QCoreApplication::installTranslator(d->m_qt_translator.get())) { qCritical() << "Loading Qt Language File failed."; - d->m_qtTranslator.reset(); + d->m_qt_translator.reset(); } else { successful = true; } } else { - d->m_qtTranslator.reset(); + d->m_qt_translator.reset(); } - if (langPtr->localFileType == FileType::Po) { + if (langPtr->localFileType == FileType::PO) { qDebug() << "Loading Application Language File for" << langCode.toLocal8Bit().constData() << "..."; - d->m_appTranslator = std::make_unique(FS::PathCombine(d->m_dir.path(), langCode + ".po")); - if (!d->m_appTranslator->isEmpty()) { - if (!QCoreApplication::installTranslator(d->m_appTranslator.get())) { + auto poTranslator = new POTranslator(FS::PathCombine(d->m_dir.path(), langCode + ".po")); + if (!poTranslator->isEmpty()) { + if (!QCoreApplication::installTranslator(poTranslator)) { + delete poTranslator; qCritical() << "Installing Application Language File failed."; - d->m_appTranslator.reset(); } else { + d->m_app_translator.reset(poTranslator); successful = true; } } else { qCritical() << "Loading Application Language File failed."; - d->m_appTranslator.reset(); + d->m_app_translator.reset(); } - } else if (langPtr->localFileType == FileType::Qm) { - d->m_appTranslator = std::make_unique(); - if (d->m_appTranslator->load("mmc_" + langCode, d->m_dir.path())) { + } else if (langPtr->localFileType == FileType::QM) { + d->m_app_translator.reset(new QTranslator()); + if (d->m_app_translator->load("mmc_" + langCode, d->m_dir.path())) { qDebug() << "Loading Application Language File for" << langCode.toLocal8Bit().constData() << "..."; - if (!QCoreApplication::installTranslator(d->m_appTranslator.get())) { + if (!QCoreApplication::installTranslator(d->m_app_translator.get())) { qCritical() << "Installing Application Language File failed."; - d->m_appTranslator.reset(); + d->m_app_translator.reset(); } else { successful = true; } } else { - d->m_appTranslator.reset(); + d->m_app_translator.reset(); } } else { - d->m_appTranslator.reset(); + d->m_app_translator.reset(); } d->m_selectedLanguage = langCode; return successful; } -QModelIndex TranslationsModel::selectedIndex() const +QModelIndex TranslationsModel::selectedIndex() { auto found = findLanguage(d->m_selectedLanguage); if (found != d->m_languages.end()) { return index(std::distance(d->m_languages.begin(), found), 0, QModelIndex()); } - return {}; + return QModelIndex(); } -QString TranslationsModel::selectedLanguage() const +QString TranslationsModel::selectedLanguage() { return d->m_selectedLanguage; } void TranslationsModel::downloadIndex() { - if (d->m_indexJob || d->m_downloadJob) { + if (d->m_index_job || d->m_dl_job) { return; } qDebug() << "Downloading Translations Index..."; - d->m_indexJob.reset(new NetJob("Translations Index", APPLICATION->network())); - const MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("translations", "index_v2.json"); + d->m_index_job.reset(new NetJob("Translations Index", APPLICATION->network())); + MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("translations", "index_v2.json"); entry->setStale(true); auto task = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATION_FILES_URL + "index_v2.json"), entry); - d->m_indexTask = task.get(); - d->m_indexJob->addNetAction(task); - d->m_indexJob->setAskRetry(false); - connect(d->m_indexJob.get(), &NetJob::failed, this, &TranslationsModel::indexFailed); - connect(d->m_indexJob.get(), &NetJob::succeeded, this, &TranslationsModel::indexReceived); - d->m_indexJob->start(); + d->m_index_task = task.get(); + d->m_index_job->addNetAction(task); + d->m_index_job->setAskRetry(false); + connect(d->m_index_job.get(), &NetJob::failed, this, &TranslationsModel::indexFailed); + connect(d->m_index_job.get(), &NetJob::succeeded, this, &TranslationsModel::indexReceived); + d->m_index_job->start(); } -void TranslationsModel::updateLanguage(const QString& key) +void TranslationsModel::updateLanguage(QString key) { - if (key == g_defaultLangCode) { + if (key == defaultLangCode) { qWarning() << "Cannot update builtin language" << key; return; } @@ -582,9 +574,9 @@ void TranslationsModel::updateLanguage(const QString& key) } } -void TranslationsModel::downloadTranslation(const QString& key) +void TranslationsModel::downloadTranslation(QString key) { - if (d->m_downloadJob) { + if (d->m_dl_job) { d->m_nextDownload = key; return; } @@ -595,21 +587,21 @@ void TranslationsModel::downloadTranslation(const QString& key) } d->m_downloadingTranslation = key; - const MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("translations", "mmc_" + key + ".qm"); + MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("translations", "mmc_" + key + ".qm"); entry->setStale(true); - auto dl = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATION_FILES_URL + lang->fileName), entry); - dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, lang->fileSha1)); - dl->setProgress(dl->getProgress(), lang->fileSize); + auto dl = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATION_FILES_URL + lang->file_name), entry); + dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, lang->file_sha1)); + dl->setProgress(dl->getProgress(), lang->file_size); - d->m_downloadJob.reset(new NetJob("Translation for " + key, APPLICATION->network())); - d->m_downloadJob->addNetAction(dl); - d->m_downloadJob->setAskRetry(false); + d->m_dl_job.reset(new NetJob("Translation for " + key, APPLICATION->network())); + d->m_dl_job->addNetAction(dl); + d->m_dl_job->setAskRetry(false); - connect(d->m_downloadJob.get(), &NetJob::succeeded, this, &TranslationsModel::dlGood); - connect(d->m_downloadJob.get(), &NetJob::failed, this, &TranslationsModel::dlFailed); + connect(d->m_dl_job.get(), &NetJob::succeeded, this, &TranslationsModel::dlGood); + connect(d->m_dl_job.get(), &NetJob::failed, this, &TranslationsModel::dlFailed); - d->m_downloadJob->start(); + d->m_dl_job->start(); } void TranslationsModel::downloadNext() @@ -620,10 +612,10 @@ void TranslationsModel::downloadNext() } } -void TranslationsModel::dlFailed(const QString& reason) +void TranslationsModel::dlFailed(QString reason) { qCritical() << "Translations Download Failed:" << reason; - d->m_downloadJob.reset(); + d->m_dl_job.reset(); downloadNext(); } @@ -634,12 +626,12 @@ void TranslationsModel::dlGood() if (d->m_downloadingTranslation == d->m_selectedLanguage) { selectLanguage(d->m_selectedLanguage); } - d->m_downloadJob.reset(); + d->m_dl_job.reset(); downloadNext(); } -void TranslationsModel::indexFailed(const QString& reason) const +void TranslationsModel::indexFailed(QString reason) { qCritical() << "Translations Index Download Failed:" << reason; - d->m_indexJob.reset(); + d->m_index_job.reset(); } diff --git a/launcher/translations/TranslationsModel.h b/launcher/translations/TranslationsModel.h index 1dd0b601c..945e689fc 100644 --- a/launcher/translations/TranslationsModel.h +++ b/launcher/translations/TranslationsModel.h @@ -24,38 +24,38 @@ struct Language; class TranslationsModel : public QAbstractListModel { Q_OBJECT public: - explicit TranslationsModel(const QString& path, QObject* parent = nullptr); - ~TranslationsModel() override; + explicit TranslationsModel(QString path, QObject* parent = 0); + virtual ~TranslationsModel(); + + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent) const override; + + bool selectLanguage(QString key); + void updateLanguage(QString key); + QModelIndex selectedIndex(); + QString selectedLanguage(); + + void downloadIndex(); + void setUseSystemLocale(bool useSystemLocale); + + private: + QList::Iterator findLanguage(const QString& key); + std::optional findLanguageAsOptional(const QString& key); + void reloadLocalFiles(); + void downloadTranslation(QString key); + void downloadNext(); // hide copy constructor TranslationsModel(const TranslationsModel&) = delete; // hide assign op TranslationsModel& operator=(const TranslationsModel&) = delete; - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; // NOLINT(*-default-arguments) - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - int rowCount(const QModelIndex& parent = QModelIndex()) const override; // NOLINT(*-default-arguments) - bool selectLanguage(QString key) const; - void updateLanguage(const QString& key); - QModelIndex selectedIndex() const; - QString selectedLanguage() const; - - void downloadIndex(); - void setUseSystemLocale(bool useSystemLocale) const; - - private: - int columnCount(const QModelIndex& parent) const override; - - QList::Iterator findLanguage(const QString& key) const; - std::optional findLanguageAsOptional(const QString& key) const; - void reloadLocalFiles(); - void downloadTranslation(const QString& key); - void downloadNext(); - private slots: void indexReceived(); - void indexFailed(const QString& reason) const; - void dlFailed(const QString& reason); + void indexFailed(QString reason); + void dlFailed(QString reason); void dlGood(); void translationDirChanged(const QString& path); diff --git a/launcher/ui/InstanceWindow.cpp b/launcher/ui/InstanceWindow.cpp index a164351b0..93da1d10c 100644 --- a/launcher/ui/InstanceWindow.cpp +++ b/launcher/ui/InstanceWindow.cpp @@ -49,7 +49,7 @@ #include "icons/IconList.h" -InstanceWindow::InstanceWindow(BaseInstance* instance, QWidget* parent) : QMainWindow(parent), m_instance(instance) +InstanceWindow::InstanceWindow(InstancePtr instance, QWidget* parent) : QMainWindow(parent), m_instance(instance) { setAttribute(Qt::WA_DeleteOnClose); @@ -109,7 +109,7 @@ InstanceWindow::InstanceWindow(BaseInstance* instance, QWidget* parent) : QMainW m_container->addButtons(horizontalLayout); - connect(m_instance, &BaseInstance::profilerChanged, this, &InstanceWindow::updateButtons); + connect(m_instance.get(), &BaseInstance::profilerChanged, this, &InstanceWindow::updateButtons); connect(APPLICATION, &Application::globalSettingsApplied, this, &InstanceWindow::updateButtons); } @@ -125,13 +125,13 @@ InstanceWindow::InstanceWindow(BaseInstance* instance, QWidget* parent) : QMainW { auto launchTask = m_instance->getLaunchTask(); instanceLaunchTaskChanged(launchTask); - connect(m_instance, &BaseInstance::launchTaskChanged, this, &InstanceWindow::instanceLaunchTaskChanged); - connect(m_instance, &BaseInstance::runningStatusChanged, this, &InstanceWindow::runningStateChanged); + connect(m_instance.get(), &BaseInstance::launchTaskChanged, this, &InstanceWindow::instanceLaunchTaskChanged); + connect(m_instance.get(), &BaseInstance::runningStatusChanged, this, &InstanceWindow::runningStateChanged); } // set up instance destruction detection { - connect(m_instance, &BaseInstance::statusChanged, this, &InstanceWindow::on_instanceStatusChanged); + connect(m_instance.get(), &BaseInstance::statusChanged, this, &InstanceWindow::on_instanceStatusChanged); } // add ourself as the modpack page's instance window @@ -164,7 +164,7 @@ void InstanceWindow::updateButtons() m_launchButton->setMenu(launchMenu); } -void InstanceWindow::instanceLaunchTaskChanged(LaunchTask* proc) +void InstanceWindow::instanceLaunchTaskChanged(shared_qobject_ptr proc) { m_proc = proc; } diff --git a/launcher/ui/InstanceWindow.h b/launcher/ui/InstanceWindow.h index 7f66a8b9c..e5bc24d44 100644 --- a/launcher/ui/InstanceWindow.h +++ b/launcher/ui/InstanceWindow.h @@ -53,7 +53,7 @@ class InstanceWindow : public QMainWindow, public BasePageContainer { Q_OBJECT public: - explicit InstanceWindow(BaseInstance* proc, QWidget* parent = 0); + explicit InstanceWindow(InstancePtr proc, QWidget* parent = 0); virtual ~InstanceWindow() = default; bool selectPage(QString pageId) override; @@ -72,7 +72,7 @@ class InstanceWindow : public QMainWindow, public BasePageContainer { void isClosing(); private slots: - void instanceLaunchTaskChanged(LaunchTask* proc); + void instanceLaunchTaskChanged(shared_qobject_ptr proc); void runningStateChanged(bool running); void on_instanceStatusChanged(BaseInstance::Status, BaseInstance::Status newStatus); @@ -83,8 +83,8 @@ class InstanceWindow : public QMainWindow, public BasePageContainer { void updateButtons(); private: - LaunchTask* m_proc; - BaseInstance* m_instance; + shared_qobject_ptr m_proc; + InstancePtr m_instance; bool m_doNotSave = false; PageContainer* m_container = nullptr; QPushButton* m_closeButton = nullptr; diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 1bdcd3f68..77a480e70 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -48,7 +48,6 @@ #include #include #include -#include #include #include @@ -105,7 +104,6 @@ #include "ui/dialogs/NewInstanceDialog.h" #include "ui/dialogs/NewsDialog.h" #include "ui/dialogs/ProgressDialog.h" -#include "ui/dialogs/skins/SkinManageDialog.h" #include "ui/instanceview/InstanceDelegate.h" #include "ui/instanceview/InstanceProxyModel.h" #include "ui/instanceview/InstanceView.h" @@ -181,7 +179,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi ui->instanceToolBar->insertSeparator(ui->actionLaunchInstance); // restore the instance toolbar settings - const auto setting_name = QString("WideBarVisibility_%1").arg(ui->instanceToolBar->objectName()); + auto const setting_name = QString("WideBarVisibility_%1").arg(ui->instanceToolBar->objectName()); instanceToolbarSetting = APPLICATION->settings()->getOrRegisterSetting(setting_name); ui->instanceToolBar->setVisibilityState(QByteArray::fromBase64(instanceToolbarSetting->get().toString().toUtf8())); @@ -324,14 +322,14 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi connect(view, &InstanceView::droppedURLs, this, &MainWindow::processURLs, Qt::QueuedConnection); proxymodel = new InstanceProxyModel(this); - proxymodel->setSourceModel(APPLICATION->instances()); + proxymodel->setSourceModel(APPLICATION->instances().get()); proxymodel->sort(0); connect(proxymodel, &InstanceProxyModel::dataChanged, this, &MainWindow::instanceDataChanged); view->setModel(proxymodel); view->setSourceOfGroupCollapseStatus( [](const QString& groupName) -> bool { return APPLICATION->instances()->isGroupCollapsed(groupName); }); - connect(view, &InstanceView::groupStateChanged, APPLICATION->instances(), &InstanceList::on_GroupStateChanged); + connect(view, &InstanceView::groupStateChanged, APPLICATION->instances().get(), &InstanceList::on_GroupStateChanged); ui->horizontalLayout->addWidget(view); } // The cat background @@ -367,13 +365,13 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi connect(view->selectionModel(), &QItemSelectionModel::currentChanged, this, &MainWindow::instanceChanged); // track icon changes and update the toolbar! - connect(APPLICATION->icons(), &IconList::iconUpdated, this, &MainWindow::iconUpdated); + connect(APPLICATION->icons().get(), &IconList::iconUpdated, this, &MainWindow::iconUpdated); // model reset -> selection is invalid. All the instance pointers are wrong. - connect(APPLICATION->instances(), &InstanceList::dataIsInvalid, this, &MainWindow::selectionBad); + connect(APPLICATION->instances().get(), &InstanceList::dataIsInvalid, this, &MainWindow::selectionBad); // handle newly added instances - connect(APPLICATION->instances(), &InstanceList::instanceSelectRequest, this, &MainWindow::instanceSelectRequest); + connect(APPLICATION->instances().get(), &InstanceList::instanceSelectRequest, this, &MainWindow::instanceSelectRequest); // When the global settings page closes, we want to know about it and update our state connect(APPLICATION, &Application::globalSettingsApplied, this, &MainWindow::globalSettingsClosed); @@ -396,9 +394,8 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi // Update the menu when the active account changes. // Shouldn't have to use lambdas here like this, but if I don't, the compiler throws a fit. // Template hell sucks... - connect(APPLICATION->accounts(), &AccountList::defaultAccountChanged, [this] { defaultAccountChanged(); }); - connect(APPLICATION->accounts(), &AccountList::listActivityChanged, [this] { defaultAccountChanged(); }); - connect(APPLICATION->accounts(), &AccountList::listChanged, [this] { defaultAccountChanged(); }); + connect(APPLICATION->accounts().get(), &AccountList::defaultAccountChanged, [this] { defaultAccountChanged(); }); + connect(APPLICATION->accounts().get(), &AccountList::listChanged, [this] { defaultAccountChanged(); }); // Show initial account defaultAccountChanged(); @@ -422,7 +419,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi auto updater = APPLICATION->updater(); if (updater) { - connect(updater, &ExternalUpdater::canCheckForUpdatesChanged, this, &MainWindow::updatesAllowedChanged); + connect(updater.get(), &ExternalUpdater::canCheckForUpdatesChanged, this, &MainWindow::updatesAllowedChanged); } } @@ -460,7 +457,7 @@ void MainWindow::retranslateUi() MinecraftAccountPtr defaultAccount = APPLICATION->accounts()->defaultAccount(); if (defaultAccount) { - auto profileLabel = profileInUseFilter(defaultAccount->displayName(), defaultAccount->isInUse()); + auto profileLabel = profileInUseFilter(defaultAccount->profileName(), defaultAccount->isInUse()); ui->actionAccountsButton->setText(profileLabel); } @@ -655,15 +652,12 @@ void MainWindow::repopulateAccountsMenu() auto accounts = APPLICATION->accounts(); MinecraftAccountPtr defaultAccount = accounts->defaultAccount(); - - bool canChangeSkin = defaultAccount && (defaultAccount->accountType() == AccountType::MSA) && !defaultAccount->isActive(); - ui->actionManageSkins->setEnabled(canChangeSkin); QString active_profileId = ""; if (defaultAccount) { // this can be called before accountMenuButton exists if (ui->actionAccountsButton) { - auto profileLabel = profileInUseFilter(defaultAccount->displayName(), defaultAccount->isInUse()); + auto profileLabel = profileInUseFilter(defaultAccount->profileName(), defaultAccount->isInUse()); ui->actionAccountsButton->setText(profileLabel); } } @@ -677,7 +671,7 @@ void MainWindow::repopulateAccountsMenu() // TODO: Nicer way to iterate? for (int i = 0; i < accounts->count(); i++) { MinecraftAccountPtr account = accounts->at(i); - auto profileLabel = profileInUseFilter(account->displayName(), account->isInUse()); + auto profileLabel = profileInUseFilter(account->profileName(), account->isInUse()); QAction* action = new QAction(profileLabel, this); action->setData(i); action->setCheckable(true); @@ -714,7 +708,6 @@ void MainWindow::repopulateAccountsMenu() connect(ui->actionNoDefaultAccount, &QAction::triggered, this, &MainWindow::changeActiveAccount); ui->accountsMenu->addSeparator(); - ui->accountsMenu->addAction(ui->actionManageSkins); ui->accountsMenu->addAction(ui->actionManageAccounts); accountsButtonMenu->addActions(ui->accountsMenu->actions()); @@ -758,7 +751,7 @@ void MainWindow::defaultAccountChanged() // FIXME: this needs adjustment for MSA if (account && account->profileName() != "") { - auto profileLabel = profileInUseFilter(account->displayName(), account->isInUse()); + auto profileLabel = profileInUseFilter(account->profileName(), account->isInUse()); ui->actionAccountsButton->setText(profileLabel); auto face = account->getFace(); if (face.isNull()) { @@ -937,9 +930,6 @@ void MainWindow::processURLs(QList urls) { // NOTE: This loop only processes one dropped file! for (auto& url : urls) { - if (url.isEmpty() || url.toString().trimmed().isEmpty()) - continue; - qDebug() << "Processing" << url; // The isLocalFile() check below doesn't work as intended without an explicit scheme. @@ -950,25 +940,12 @@ void MainWindow::processURLs(QList urls) QMap extra_info; QUrl local_url; if (!url.isLocalFile()) { // download the remote resource and identify - - const bool isExternalURLImport = (url.host().toLower() == "import") || (url.path().startsWith("/import", Qt::CaseInsensitive)); - QUrl dl_url; - if (url.scheme() == "curseforge" || (url.scheme() == BuildConfig.LAUNCHER_APP_BINARY_NAME && url.host() == "install")) { + if (url.scheme() == "curseforge") { // need to find the download link for the modpack / resource // format of url curseforge://install?addonId=IDHERE&fileId=IDHERE - // format of url binaryname://install?platform=curseforge&addonId=IDHERE&fileId=IDHERE QUrlQuery query(url); - // check if this is a binaryname:// url - if (url.scheme() == BuildConfig.LAUNCHER_APP_BINARY_NAME) { - // check this is an curseforge platform request - if (query.queryItemValue("platform").toLower() != "curseforge") { - qDebug() << "Invalid mod distribution platform:" << query.queryItemValue("platform"); - continue; - } - } - if (query.allQueryItemValues("addonId").isEmpty() || query.allQueryItemValues("fileId").isEmpty()) { qDebug() << "Invalid curseforge link:" << url; continue; @@ -980,8 +957,10 @@ void MainWindow::processURLs(QList urls) extra_info.insert("pack_id", addonId); extra_info.insert("pack_version_id", fileId); + auto array = std::make_shared(); + auto api = FlameAPI(); - auto [job, array] = api.getFile(addonId, fileId); + auto job = api.getFile(addonId, fileId, array); connect(job.get(), &Task::failed, this, [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); }); @@ -1014,7 +993,7 @@ void MainWindow::processURLs(QList urls) dlUrlDialod.execWithTask(job.get()); } - } else if (url.scheme() == BuildConfig.LAUNCHER_APP_BINARY_NAME && !isExternalURLImport) { + } else if (url.scheme() == BuildConfig.LAUNCHER_APP_BINARY_NAME) { QVariantMap receivedData; const QUrlQuery query(url.query()); const auto items = query.queryItems(); @@ -1022,65 +1001,6 @@ void MainWindow::processURLs(QList urls) receivedData.insert(it->first, it->second); emit APPLICATION->oauthReplyRecieved(receivedData); continue; - } else if ((url.scheme() == "prismlauncher" || url.scheme() == BuildConfig.LAUNCHER_APP_BINARY_NAME) && isExternalURLImport) { - // PrismLauncher URL protocol modpack import - // works for any prism fork - // preferred import format: prismlauncher://import?url=ENCODED - const auto host = url.host().toLower(); - const auto path = url.path(); - - QString encodedTarget; - - { - QUrlQuery query(url); - const auto values = query.allQueryItemValues("url"); - if (!values.isEmpty()) { - encodedTarget = values.first(); - } - } - - // alternative import format: prismlauncher://import/ENCODED - if (encodedTarget.isEmpty()) { - QString p = path; - - if (p.startsWith("/import/", Qt::CaseInsensitive)) { - p = p.mid(QString("/import/").size()); - } else if (host == "import" && p.startsWith("/")) { - p = p.mid(1); - } - - if (!p.isEmpty() && p != "/import") { - encodedTarget = p; - } - } - - if (encodedTarget.isEmpty()) { - CustomMessageBox::selectable(this, tr("Error"), tr("Invalid import link: missing 'url' parameter."), - QMessageBox::Critical) - ->show(); - continue; - } - - const QString decodedStr = QUrl::fromPercentEncoding(encodedTarget.toUtf8()).trimmed(); - - QUrl target = QUrl::fromUserInput(decodedStr); - - // Validate: only allow http(s) - if (!target.isValid() || (target.scheme() != "https" && target.scheme() != "http")) { - CustomMessageBox::selectable(this, tr("Error"), tr("Invalid import link: URL must be http(s)."), QMessageBox::Critical) - ->show(); - continue; - } - - const auto res = QMessageBox::question( - this, tr("Install modpack"), - tr("Do you want to download and import a modpack from:\n%1\n\nURL:\n%2").arg(target.host(), target.toString()), - QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); - if (res != QMessageBox::Yes) { - continue; - } - - dl_url = target; } else { dl_url = url; } @@ -1119,11 +1039,6 @@ void MainWindow::processURLs(QList urls) auto localFileName = QDir::toNativeSeparators(local_url.toLocalFile()); QFileInfo localFileInfo(localFileName); - if (localFileName.isEmpty() || !localFileInfo.exists()) { - qDebug() << "Ignoring invalid path" << localFileName; - continue; - } - auto type = ResourceUtils::identify(localFileInfo); if (ModPlatform::ResourceTypeUtils::VALID_RESOURCES.count(type) == 0) { // probably instance/modpack @@ -1147,7 +1062,7 @@ void MainWindow::processURLs(QList urls) qDebug() << "Adding resource" << localFileName << "to" << dlg.selectedInstanceKey; auto inst = APPLICATION->instances()->getInstanceById(dlg.selectedInstanceKey); - auto minecraftInst = dynamic_cast(inst); + auto minecraftInst = std::dynamic_pointer_cast(inst); switch (type) { case ModPlatform::ResourceType::ResourcePack: @@ -1395,16 +1310,6 @@ void MainWindow::on_actionEditInstance_triggered() } } -void MainWindow::on_actionManageSkins_triggered() -{ - auto account = APPLICATION->accounts()->defaultAccount(); - - if (account && (account->accountType() == AccountType::MSA) && !account->isActive()) { - SkinManageDialog dialog(this, account); - dialog.exec(); - } -} - void MainWindow::on_actionManageAccounts_triggered() { APPLICATION->ShowGlobalSettings(this, "accounts"); @@ -1454,7 +1359,7 @@ void MainWindow::on_actionAddToPATH_triggered() void MainWindow::on_actionOpenWiki_triggered() { - DesktopServices::openUrl(QUrl(BuildConfig.WIKI_URL)); + DesktopServices::openUrl(QUrl(BuildConfig.HELP_URL.arg(""))); } void MainWindow::on_actionMoreNews_triggered() @@ -1537,7 +1442,7 @@ void MainWindow::on_actionExportInstanceZip_triggered() void MainWindow::on_actionExportInstanceMrPack_triggered() { if (m_selectedInstance) { - auto instance = dynamic_cast(m_selectedInstance); + auto instance = std::dynamic_pointer_cast(m_selectedInstance); if (instance != nullptr) { ExportPackDialog dlg(instance, this); dlg.exec(); @@ -1548,7 +1453,7 @@ void MainWindow::on_actionExportInstanceMrPack_triggered() void MainWindow::on_actionExportInstanceFlamePack_triggered() { if (m_selectedInstance) { - auto instance = dynamic_cast(m_selectedInstance); + auto instance = std::dynamic_pointer_cast(m_selectedInstance); if (instance) { if (auto cmp = instance->getPackProfile()->getComponent("net.minecraft"); cmp && cmp->getVersionFile() && cmp->getVersionFile()->type == "snapshot") { @@ -1601,7 +1506,7 @@ void MainWindow::instanceActivated(QModelIndex index) if (!index.isValid()) return; QString id = index.data(InstanceList::InstanceIDRole).toString(); - BaseInstance* inst = APPLICATION->instances()->getInstanceById(id); + InstancePtr inst = APPLICATION->instances()->getInstanceById(id); if (!inst) return; @@ -1615,7 +1520,7 @@ void MainWindow::on_actionLaunchInstance_triggered() } } -void MainWindow::activateInstance(BaseInstance* instance) +void MainWindow::activateInstance(InstancePtr instance) { APPLICATION->launch(instance); } @@ -1662,8 +1567,8 @@ void MainWindow::instanceChanged(const QModelIndex& current, [[maybe_unused]] co return; } if (m_selectedInstance) { - disconnect(m_selectedInstance, &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance); - disconnect(m_selectedInstance, &BaseInstance::profilerChanged, this, &MainWindow::refreshCurrentInstance); + disconnect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance); + disconnect(m_selectedInstance.get(), &BaseInstance::profilerChanged, this, &MainWindow::refreshCurrentInstance); } QString id = current.data(InstanceList::InstanceIDRole).toString(); m_selectedInstance = APPLICATION->instances()->getInstanceById(id); @@ -1683,8 +1588,8 @@ void MainWindow::instanceChanged(const QModelIndex& current, [[maybe_unused]] co APPLICATION->settings()->set("SelectedInstance", m_selectedInstance->id()); - connect(m_selectedInstance, &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance); - connect(m_selectedInstance, &BaseInstance::profilerChanged, this, &MainWindow::refreshCurrentInstance); + connect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance); + connect(m_selectedInstance.get(), &BaseInstance::profilerChanged, this, &MainWindow::refreshCurrentInstance); } else { APPLICATION->settings()->set("SelectedInstance", QString()); selectionBad(); diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 80860deef..0e692eda7 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -130,8 +130,6 @@ class MainWindow : public QMainWindow { void on_actionSettings_triggered(); - void on_actionManageSkins_triggered(); - void on_actionManageAccounts_triggered(); void on_actionReportBug_triggered(); @@ -222,7 +220,7 @@ class MainWindow : public QMainWindow { void retranslateUi(); void addInstance(const QString& url = QString(), const QMap& extra_info = {}); - void activateInstance(BaseInstance* instance); + void activateInstance(InstancePtr instance); void setCatBackground(bool enabled); void updateInstanceToolIcon(QString new_icon); void setSelectedInstanceById(const QString& id); @@ -249,7 +247,7 @@ class MainWindow : public QMainWindow { unique_qobject_ptr m_newsChecker; - BaseInstance* m_selectedInstance = nullptr; + InstancePtr m_selectedInstance; QString m_currentInstIcon; // managed by the application object diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui index e9b9aa442..ff1b4a25a 100644 --- a/launcher/ui/MainWindow.ui +++ b/launcher/ui/MainWindow.ui @@ -322,14 +322,6 @@ QAction::PreferencesRole - - - - - - Manage &Skins... - - @@ -528,7 +520,7 @@ Close the current window - QAction::MenuRole::NoRole + QAction::QuitRole @@ -729,7 +721,7 @@ - %1 &Wiki + %1 &Help Open the %1 wiki diff --git a/launcher/ui/dialogs/AboutDialog.cpp b/launcher/ui/dialogs/AboutDialog.cpp index da42ae2b4..6052e94a9 100644 --- a/launcher/ui/dialogs/AboutDialog.cpp +++ b/launcher/ui/dialogs/AboutDialog.cpp @@ -48,7 +48,7 @@ QString getCreditsHtml() { QFile dataFile(":/documents/credits.html"); if (!dataFile.open(QIODevice::ReadOnly)) { - qWarning() << "Failed to open file" << dataFile.fileName() << "for reading:" << dataFile.errorString(); + qWarning() << "Failed to open file '" << dataFile.fileName() << "' for reading!"; return {}; } QString fileContent = QString::fromUtf8(dataFile.readAll()); @@ -66,7 +66,7 @@ QString getLicenseHtml() dataFile.close(); return output; } else { - qWarning() << "Failed to open file" << dataFile.fileName() << "for reading:" << dataFile.errorString(); + qWarning() << "Failed to open file '" << dataFile.fileName() << "' for reading!"; return QString(); } } diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp index 4abaf6eb5..2a96b7f94 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.cpp +++ b/launcher/ui/dialogs/BlockedModsDialog.cpp @@ -27,7 +27,6 @@ #include "ui_BlockedModsDialog.h" #include "Application.h" -#include "settings/SettingsObject.h" #include "modplatform/helpers/HashUtils.h" #include diff --git a/launcher/ui/dialogs/ChooseProviderDialog.h b/launcher/ui/dialogs/ChooseProviderDialog.h index 3d602de93..51e7c98c6 100644 --- a/launcher/ui/dialogs/ChooseProviderDialog.h +++ b/launcher/ui/dialogs/ChooseProviderDialog.h @@ -2,14 +2,13 @@ #include #include -#include namespace Ui { class ChooseProviderDialog; } namespace ModPlatform { -enum class ResourceProvider : std::uint8_t; +enum class ResourceProvider; } class Mod; diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index 74fab3407..e5c2c301b 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -51,7 +51,7 @@ #include "InstanceList.h" #include "icons/IconList.h" -CopyInstanceDialog::CopyInstanceDialog(BaseInstance* original, QWidget* parent) +CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget* parent) : QDialog(parent), ui(new Ui::CopyInstanceDialog), m_original(original) { ui->setupUi(this); diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h index 5f150cf5f..698c6e939 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.h +++ b/launcher/ui/dialogs/CopyInstanceDialog.h @@ -30,7 +30,7 @@ class CopyInstanceDialog : public QDialog { Q_OBJECT public: - explicit CopyInstanceDialog(BaseInstance* original, QWidget* parent = 0); + explicit CopyInstanceDialog(InstancePtr original, QWidget* parent = 0); ~CopyInstanceDialog(); void updateDialogState(); @@ -71,7 +71,7 @@ class CopyInstanceDialog : public QDialog { /* data */ Ui::CopyInstanceDialog* ui; QString InstIconKey; - BaseInstance* m_original; + InstancePtr m_original; InstanceCopyPrefs m_selectedOptions; bool m_cloneSupported = false; bool m_linkSupported = false; diff --git a/launcher/ui/dialogs/CreateShortcutDialog.cpp b/launcher/ui/dialogs/CreateShortcutDialog.cpp index 7d4199f13..574881ad0 100644 --- a/launcher/ui/dialogs/CreateShortcutDialog.cpp +++ b/launcher/ui/dialogs/CreateShortcutDialog.cpp @@ -55,7 +55,7 @@ #include "minecraft/WorldList.h" #include "minecraft/auth/AccountList.h" -CreateShortcutDialog::CreateShortcutDialog(BaseInstance* instance, QWidget* parent) +CreateShortcutDialog::CreateShortcutDialog(InstancePtr instance, QWidget* parent) : QDialog(parent), ui(new Ui::CreateShortcutDialog), m_instance(instance) { ui->setupUi(this); @@ -64,7 +64,7 @@ CreateShortcutDialog::CreateShortcutDialog(BaseInstance* instance, QWidget* pare ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey)); ui->instNameTextBox->setPlaceholderText(instance->name()); - auto mInst = dynamic_cast(instance); + auto mInst = std::dynamic_pointer_cast(instance); m_QuickJoinSupported = mInst && mInst->traits().contains("feature:is_quick_play_singleplayer"); auto worldList = mInst->worldList(); worldList->update(); @@ -212,7 +212,7 @@ void CreateShortcutDialog::createShortcut() if (ui->overrideAccountCheckbox->isChecked()) extraArgs.append({ "--profile", ui->accountSelectionBox->currentData().toString() }); - ShortcutUtils::Shortcut args{ m_instance, name, targetString, this, extraArgs, InstIconKey, target }; + ShortcutUtils::Shortcut args{ m_instance.get(), name, targetString, this, extraArgs, InstIconKey, target }; if (target == ShortcutTarget::Desktop) ShortcutUtils::createInstanceShortcutOnDesktop(args); else if (target == ShortcutTarget::Applications) diff --git a/launcher/ui/dialogs/CreateShortcutDialog.h b/launcher/ui/dialogs/CreateShortcutDialog.h index 8d666ef63..29e68a787 100644 --- a/launcher/ui/dialogs/CreateShortcutDialog.h +++ b/launcher/ui/dialogs/CreateShortcutDialog.h @@ -28,7 +28,7 @@ class CreateShortcutDialog : public QDialog { Q_OBJECT public: - explicit CreateShortcutDialog(BaseInstance* instance, QWidget* parent = nullptr); + explicit CreateShortcutDialog(InstancePtr instance, QWidget* parent = nullptr); ~CreateShortcutDialog(); void createShortcut(); @@ -51,7 +51,7 @@ class CreateShortcutDialog : public QDialog { // Data Ui::CreateShortcutDialog* ui; QString InstIconKey; - BaseInstance* m_instance; + InstancePtr m_instance; bool m_QuickJoinSupported = false; // Functions diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp index 96dab97e3..21c16f01a 100644 --- a/launcher/ui/dialogs/ExportInstanceDialog.cpp +++ b/launcher/ui/dialogs/ExportInstanceDialog.cpp @@ -60,7 +60,7 @@ #include "Application.h" #include "SeparatorPrefixTree.h" -ExportInstanceDialog::ExportInstanceDialog(BaseInstance* instance, QWidget* parent) +ExportInstanceDialog::ExportInstanceDialog(InstancePtr instance, QWidget* parent) : QDialog(parent), m_ui(new Ui::ExportInstanceDialog), m_instance(instance) { m_ui->setupUi(this); @@ -98,7 +98,7 @@ ExportInstanceDialog::~ExportInstanceDialog() } /// Save icon to instance's folder is needed -void SaveIcon(BaseInstance* m_instance) +void SaveIcon(InstancePtr m_instance) { auto iconKey = m_instance->iconKey(); auto iconList = APPLICATION->icons(); diff --git a/launcher/ui/dialogs/ExportInstanceDialog.h b/launcher/ui/dialogs/ExportInstanceDialog.h index c1f8559cc..989e1635a 100644 --- a/launcher/ui/dialogs/ExportInstanceDialog.h +++ b/launcher/ui/dialogs/ExportInstanceDialog.h @@ -43,6 +43,7 @@ #include "FileIgnoreProxy.h" class BaseInstance; +using InstancePtr = std::shared_ptr; namespace Ui { class ExportInstanceDialog; @@ -52,7 +53,7 @@ class ExportInstanceDialog : public QDialog { Q_OBJECT public: - explicit ExportInstanceDialog(BaseInstance* instance, QWidget* parent = 0); + explicit ExportInstanceDialog(InstancePtr instance, QWidget* parent = 0); ~ExportInstanceDialog(); virtual void done(int result); @@ -63,7 +64,7 @@ class ExportInstanceDialog : public QDialog { private: Ui::ExportInstanceDialog* m_ui; - BaseInstance* m_instance; + InstancePtr m_instance; FileIgnoreProxy* m_proxyModel; FastFileIconProvider m_icons; diff --git a/launcher/ui/dialogs/ExportPackDialog.cpp b/launcher/ui/dialogs/ExportPackDialog.cpp index d0a9f0914..17b3ba703 100644 --- a/launcher/ui/dialogs/ExportPackDialog.cpp +++ b/launcher/ui/dialogs/ExportPackDialog.cpp @@ -33,7 +33,7 @@ #include "MMCZip.h" #include "modplatform/modrinth/ModrinthPackExportTask.h" -ExportPackDialog::ExportPackDialog(MinecraftInstance* instance, QWidget* parent, ModPlatform::ResourceProvider provider) +ExportPackDialog::ExportPackDialog(MinecraftInstancePtr instance, QWidget* parent, ModPlatform::ResourceProvider provider) : QDialog(parent), m_instance(instance), m_ui(new Ui::ExportPackDialog), m_provider(provider) { Q_ASSERT(m_provider == ModPlatform::ResourceProvider::MODRINTH || m_provider == ModPlatform::ResourceProvider::FLAME); @@ -101,20 +101,23 @@ ExportPackDialog::ExportPackDialog(MinecraftInstance* instance, QWidget* parent, const QDir::Filters filter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Hidden); - for (auto resourceModel : instance->resourceLists()) { - if (resourceModel == nullptr) { - continue; - } + MinecraftInstance* mcInstance = dynamic_cast(instance.get()); + if (mcInstance) { + for (auto resourceModel : mcInstance->resourceLists()) { + if (resourceModel == nullptr) { + continue; + } - if (!resourceModel->indexDir().exists()) { - continue; - } + if (!resourceModel->indexDir().exists()) { + continue; + } - if (resourceModel->dir() == resourceModel->indexDir()) { - continue; - } + if (resourceModel->dir() == resourceModel->indexDir()) { + continue; + } - m_proxy->ignoreFilesWithPath().insert(instanceRoot.relativeFilePath(resourceModel->indexDir().absolutePath())); + m_proxy->ignoreFilesWithPath().insert(instanceRoot.relativeFilePath(resourceModel->indexDir().absolutePath())); + } } m_ui->files->setModel(m_proxy); diff --git a/launcher/ui/dialogs/ExportPackDialog.h b/launcher/ui/dialogs/ExportPackDialog.h index 81e657aa0..e93055d8d 100644 --- a/launcher/ui/dialogs/ExportPackDialog.h +++ b/launcher/ui/dialogs/ExportPackDialog.h @@ -33,7 +33,7 @@ class ExportPackDialog : public QDialog { Q_OBJECT public: - explicit ExportPackDialog(MinecraftInstance* instance, + explicit ExportPackDialog(MinecraftInstancePtr instance, QWidget* parent = nullptr, ModPlatform::ResourceProvider provider = ModPlatform::ResourceProvider::MODRINTH); ~ExportPackDialog(); @@ -45,7 +45,7 @@ class ExportPackDialog : public QDialog { QString ignoreFileName(); private: - MinecraftInstance* m_instance; + const MinecraftInstancePtr m_instance; Ui::ExportPackDialog* m_ui; FileIgnoreProxy* m_proxy; FastFileIconProvider m_icons; diff --git a/launcher/ui/dialogs/ExportToModListDialog.cpp b/launcher/ui/dialogs/ExportToModListDialog.cpp index a782a9190..e8873f9b4 100644 --- a/launcher/ui/dialogs/ExportToModListDialog.cpp +++ b/launcher/ui/dialogs/ExportToModListDialog.cpp @@ -214,8 +214,6 @@ void ExportToModListDialog::addExtra(ExportToModList::OptionalData option) case ExportToModList::FileName: ui->templateText->insertPlainText("{filename}"); break; - case ExportToModList::None: - break; } } void ExportToModListDialog::enableCustom(bool enabled) diff --git a/launcher/ui/dialogs/IconPickerDialog.cpp b/launcher/ui/dialogs/IconPickerDialog.cpp index b2269cc70..c1b79a904 100644 --- a/launcher/ui/dialogs/IconPickerDialog.cpp +++ b/launcher/ui/dialogs/IconPickerDialog.cpp @@ -30,106 +30,26 @@ #include "icons/IconList.h" #include "icons/IconUtils.h" -class IconProxyModel : public QSortFilterProxyModel -{ -public: - explicit IconProxyModel(QObject* parent = nullptr) : QSortFilterProxyModel(parent) - { - } - - void setCategory(IconPickerDialog::IconPickerCategory category) - { - if (m_category == category) - return; - m_category = category; - invalidateFilter(); - } - -protected: - bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override - { - if (!QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent)) - return false; - - if (m_category == IconPickerDialog::Any) - return true; - - auto model = static_cast(sourceModel()); - QModelIndex index = model->index(source_row, 0, source_parent); - QString key = model->data(index, Qt::UserRole).toString(); - const MMCIcon* icon = model->icon(key); - - if (!icon) - return false; - - bool isModpack = false; - bool isBuiltin = icon->isBuiltIn(); - bool isLegacy = isBuiltin && icon->name().endsWith("_legacy", Qt::CaseInsensitive); - - if (!isBuiltin) { - const QString& name = icon->name(); - if (name.startsWith("curseforge_", Qt::CaseInsensitive) || - name.startsWith("modrinth_", Qt::CaseInsensitive) || - name.startsWith("ftb_", Qt::CaseInsensitive) || - name.startsWith("technic_", Qt::CaseInsensitive) || - name.startsWith("atl_", Qt::CaseInsensitive)) { - isModpack = true; - } - } - - switch (m_category) { - case IconPickerDialog::Legacy: - return isBuiltin && isLegacy; - case IconPickerDialog::Modpacks: - return isModpack; - case IconPickerDialog::Modern: - return isBuiltin && !isLegacy; - case IconPickerDialog::Custom: - return !isBuiltin && !isModpack; - default: - return true; - } - } - -private: - IconPickerDialog::IconPickerCategory m_category = IconPickerDialog::Any; -}; - IconPickerDialog::IconPickerDialog(QWidget* parent) : QDialog(parent), ui(new Ui::IconPickerDialog) { ui->setupUi(this); setWindowModality(Qt::WindowModal); - static const QString context_text[] = { - tr("All"), - tr("Modern"), - tr("Legacy"), - tr("Modpacks"), - tr("Custom"), - }; - static const IconPickerCategory context_id[] = { - Any, - Modern, - Legacy, - Modpacks, - Custom, - }; - const int cnt = sizeof(context_text) / sizeof(context_text[0]); - for (int i = 0; i < cnt; ++i) { - ui->contextCombo->addItem(context_text[i], context_id[i]); - if (i == 0) { - ui->contextCombo->insertSeparator(i + 1); - } - } + searchBar = new QLineEdit(this); + searchBar->setPlaceholderText(tr("Search...")); + ui->verticalLayout->insertWidget(0, searchBar); - proxyModel = new IconProxyModel(this); - proxyModel->setSourceModel(APPLICATION->icons()); + proxyModel = new QSortFilterProxyModel(this); + proxyModel->setSourceModel(APPLICATION->icons().get()); proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); ui->iconView->setModel(proxyModel); auto contentsWidget = ui->iconView; + contentsWidget->setViewMode(QListView::IconMode); contentsWidget->setFlow(QListView::LeftToRight); contentsWidget->setIconSize(QSize(48, 48)); + contentsWidget->setMovement(QListView::Static); + contentsWidget->setResizeMode(QListView::Adjust); contentsWidget->setSelectionMode(QAbstractItemView::SingleSelection); contentsWidget->setSpacing(5); contentsWidget->setWordWrap(false); @@ -166,13 +86,9 @@ IconPickerDialog::IconPickerDialog(QWidget* parent) : QDialog(parent), ui(new Ui auto buttonFolder = ui->buttonBox->addButton(tr("Open Folder"), QDialogButtonBox::ResetRole); connect(buttonFolder, &QPushButton::clicked, this, &IconPickerDialog::openFolder); - connect(ui->searchLine, &QLineEdit::textChanged, this, &IconPickerDialog::filterIcons); - connect(ui->contextCombo, &QComboBox::currentIndexChanged, this, [this](int index) { - IconPickerCategory category = static_cast(ui->contextCombo->itemData(index).toInt()); - filterIconsByCategory(category); - }); + connect(searchBar, &QLineEdit::textChanged, this, &IconPickerDialog::filterIcons); // Prevent incorrect indices from e.g. filesystem changes - connect(APPLICATION->icons(), &IconList::iconUpdated, this, [this]() { proxyModel->invalidate(); }); + connect(APPLICATION->icons().get(), &IconList::iconUpdated, this, [this]() { proxyModel->invalidate(); }); } bool IconPickerDialog::eventFilter(QObject* obj, QEvent* evt) @@ -266,8 +182,3 @@ void IconPickerDialog::filterIcons(const QString& query) { proxyModel->setFilterFixedString(query); } - -void IconPickerDialog::filterIconsByCategory(IconPickerCategory category) -{ - static_cast(proxyModel)->setCategory(category); -} diff --git a/launcher/ui/dialogs/IconPickerDialog.h b/launcher/ui/dialogs/IconPickerDialog.h index 063f9b905..db1315338 100644 --- a/launcher/ui/dialogs/IconPickerDialog.h +++ b/launcher/ui/dialogs/IconPickerDialog.h @@ -32,15 +32,6 @@ class IconPickerDialog : public QDialog { int execWithSelection(QString selection); QString selectedIconKey; - enum IconPickerCategory { - Any, - Modern, - Legacy, - Modpacks, - Custom, - }; - Q_ENUM(IconPickerCategory) - protected: virtual bool eventFilter(QObject*, QEvent*); @@ -58,5 +49,4 @@ class IconPickerDialog : public QDialog { void removeSelectedIcon(); void openFolder(); void filterIcons(const QString& text); - void filterIconsByCategory(IconPickerCategory); }; diff --git a/launcher/ui/dialogs/IconPickerDialog.ui b/launcher/ui/dialogs/IconPickerDialog.ui index 948e7043f..c548edfb7 100644 --- a/launcher/ui/dialogs/IconPickerDialog.ui +++ b/launcher/ui/dialogs/IconPickerDialog.ui @@ -15,64 +15,7 @@ - - - - - - - Icon category - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Search Icons... - - - true - - - - - - - - - - - - 60 - 60 - - - - QListView::Static - - - QListView::Adjust - - - QListView::IconMode - - - true - - + diff --git a/launcher/ui/dialogs/ImportResourceDialog.cpp b/launcher/ui/dialogs/ImportResourceDialog.cpp index 80096ed01..7cd178130 100644 --- a/launcher/ui/dialogs/ImportResourceDialog.cpp +++ b/launcher/ui/dialogs/ImportResourceDialog.cpp @@ -35,7 +35,7 @@ ImportResourceDialog::ImportResourceDialog(QString file_path, ModPlatform::Resou contentsWidget->setItemDelegate(new ListViewDelegate()); proxyModel = new InstanceProxyModel(this); - proxyModel->setSourceModel(APPLICATION->instances()); + proxyModel->setSourceModel(APPLICATION->instances().get()); proxyModel->sort(0); contentsWidget->setModel(proxyModel); diff --git a/launcher/ui/dialogs/InstallLoaderDialog.cpp b/launcher/ui/dialogs/InstallLoaderDialog.cpp index deb5358fb..c811e01eb 100644 --- a/launcher/ui/dialogs/InstallLoaderDialog.cpp +++ b/launcher/ui/dialogs/InstallLoaderDialog.cpp @@ -33,7 +33,11 @@ class InstallLoaderPage : public VersionSelectWidget, public BasePage { Q_OBJECT public: - InstallLoaderPage(const QString& id, const QString& iconName, const QString& name, const Version& oldestVersion, PackProfile* profile) + InstallLoaderPage(const QString& id, + const QString& iconName, + const QString& name, + const Version& oldestVersion, + const std::shared_ptr profile) : VersionSelectWidget(nullptr), uid(id), iconName(iconName), name(name) { const QString minecraftVersion = profile->getComponentVersion("net.minecraft"); @@ -84,29 +88,25 @@ static InstallLoaderPage* pageCast(BasePage* page) return result; } -InstallLoaderDialog::InstallLoaderDialog(PackProfile* profile, const QString& uid, QWidget* parent) - : QDialog(parent), profile(profile), container(new PageContainer(this, QString(), this)), buttons(new QDialogButtonBox(this)) +InstallLoaderDialog::InstallLoaderDialog(std::shared_ptr profile, const QString& uid, QWidget* parent) + : QDialog(parent), profile(std::move(profile)), container(new PageContainer(this, QString(), this)), buttons(new QDialogButtonBox(this)) { auto layout = new QVBoxLayout(this); - // small margins look ugly on macOS on modal windows - #ifndef Q_OS_MACOS layout->setContentsMargins(0, 0, 0, 0); - #endif + container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); layout->addWidget(container); auto buttonLayout = new QHBoxLayout(this); - // small margins look ugly on macOS on modal windows - #ifndef Q_OS_MACOS buttonLayout->setContentsMargins(0, 0, 6, 6); - #endif + auto refreshButton = new QPushButton(tr("&Refresh"), this); - connect(refreshButton, &QPushButton::clicked, this, [this] { pageCast(container->selectedPage())->loadList(true); }); + connect(refreshButton, &QPushButton::clicked, this, [this] { pageCast(container->selectedPage())->loadList(); }); buttonLayout->addWidget(refreshButton); buttons->setOrientation(Qt::Horizontal); buttons->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); - buttons->button(QDialogButtonBox::Ok)->setText(tr("OK")); + buttons->button(QDialogButtonBox::Ok)->setText(tr("Ok")); buttons->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); diff --git a/launcher/ui/dialogs/InstallLoaderDialog.h b/launcher/ui/dialogs/InstallLoaderDialog.h index 501f136e2..86cb3bdd2 100644 --- a/launcher/ui/dialogs/InstallLoaderDialog.h +++ b/launcher/ui/dialogs/InstallLoaderDialog.h @@ -30,7 +30,7 @@ class InstallLoaderDialog final : public QDialog, protected BasePageProvider { Q_OBJECT public: - explicit InstallLoaderDialog(PackProfile* instance, const QString& uid = QString(), QWidget* parent = nullptr); + explicit InstallLoaderDialog(std::shared_ptr instance, const QString& uid = QString(), QWidget* parent = nullptr); QList getPages() override; QString dialogTitle() override; @@ -39,7 +39,7 @@ class InstallLoaderDialog final : public QDialog, protected BasePageProvider { void done(int result) override; private: - PackProfile* profile; + std::shared_ptr profile; PageContainer* container; QDialogButtonBox* buttons; }; diff --git a/launcher/ui/dialogs/MSALoginDialog.cpp b/launcher/ui/dialogs/MSALoginDialog.cpp index e238a54eb..e6b53d292 100644 --- a/launcher/ui/dialogs/MSALoginDialog.cpp +++ b/launcher/ui/dialogs/MSALoginDialog.cpp @@ -35,7 +35,6 @@ #include "MSALoginDialog.h" #include "Application.h" -#include "settings/SettingsObject.h" #include "ui_MSALoginDialog.h" diff --git a/launcher/ui/dialogs/NetworkJobFailedDialog.cpp b/launcher/ui/dialogs/NetworkJobFailedDialog.cpp deleted file mode 100644 index e0d3a2c83..000000000 --- a/launcher/ui/dialogs/NetworkJobFailedDialog.cpp +++ /dev/null @@ -1,86 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2025 Octol1ttle - * - * 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 "NetworkJobFailedDialog.h" - -#include -#include -#include -#include - -#include "ui_NetworkJobFailedDialog.h" - -NetworkJobFailedDialog::NetworkJobFailedDialog(const QString& jobName, const int attempts, const int requests, const int failed, QWidget* parent) - : QDialog(parent), m_ui(new Ui::NetworkJobFailedDialog) -{ - m_ui->setupUi(this); - m_ui->failLabel->setText(m_ui->failLabel->text().arg(jobName)); - if (failed == requests) { - m_ui->requestCountLabel->setText(tr("All %1 requests have failed after %2 attempts").arg(failed).arg(attempts)); - } else if (failed < requests / 2) { - m_ui->requestCountLabel->setText( - tr("Out of %1 requests, %2 have failed after %3 attempts").arg(requests).arg(failed).arg(attempts)); - } else { - m_ui->requestCountLabel->setText( - tr("Out of %1 requests, only %2 succeeded after %3 attempts").arg(requests).arg(requests - failed).arg(attempts)); - } - - m_ui->detailsTable->header()->setSectionResizeMode(0, QHeaderView::Stretch); - m_ui->detailsTable->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); - - m_ui->detailsTable->setSelectionMode(QAbstractItemView::ExtendedSelection); - - const auto* copyShortcut = new QShortcut(QKeySequence::Copy, m_ui->detailsTable); - connect(copyShortcut, &QShortcut::activated, this, &NetworkJobFailedDialog::copyUrl); - - const auto* copyButton = m_ui->dialogButtonBox->addButton(tr("Copy URL"), QDialogButtonBox::ActionRole); - connect(copyButton, &QPushButton::clicked, this, &NetworkJobFailedDialog::copyUrl); - - connect(m_ui->dialogButtonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(m_ui->dialogButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); -} - -NetworkJobFailedDialog::~NetworkJobFailedDialog() -{ - delete m_ui; -} - -void NetworkJobFailedDialog::addFailedRequest(const QUrl& url, QString error) const -{ - auto* item = new QTreeWidgetItem(m_ui->detailsTable, { url.toString(), std::move(error) }); - m_ui->detailsTable->addTopLevelItem(item); - if (m_ui->detailsTable->selectedItems().isEmpty()) { - m_ui->detailsTable->setCurrentItem(item); - } -} - -void NetworkJobFailedDialog::copyUrl() const -{ - auto items = m_ui->detailsTable->selectedItems(); - if (items.isEmpty()) { - return; - } - - QString urls = items.first()->text(0); - for (auto& item : items.sliced(1)) { - urls += "\n" + item->text(0); - } - - auto* clipboard = QGuiApplication::clipboard(); - clipboard->setText(urls); -} diff --git a/launcher/ui/dialogs/NetworkJobFailedDialog.h b/launcher/ui/dialogs/NetworkJobFailedDialog.h deleted file mode 100644 index 9bfb7c439..000000000 --- a/launcher/ui/dialogs/NetworkJobFailedDialog.h +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2025 Octol1ttle - * - * 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 - -QT_BEGIN_NAMESPACE -namespace Ui { -class NetworkJobFailedDialog; -} -QT_END_NAMESPACE - -class NetworkJobFailedDialog : public QDialog { - Q_OBJECT - - public: - explicit NetworkJobFailedDialog(const QString& jobName, int attempts, int requests, int failed, QWidget* parent = nullptr); - ~NetworkJobFailedDialog() override; - - void addFailedRequest(const QUrl& url, QString error) const; - - private slots: - void copyUrl() const; - - private: - Ui::NetworkJobFailedDialog* m_ui; -}; diff --git a/launcher/ui/dialogs/NetworkJobFailedDialog.ui b/launcher/ui/dialogs/NetworkJobFailedDialog.ui deleted file mode 100644 index b133052eb..000000000 --- a/launcher/ui/dialogs/NetworkJobFailedDialog.ui +++ /dev/null @@ -1,99 +0,0 @@ - - - NetworkJobFailedDialog - - - - 0 - 0 - 450 - 350 - - - - Network error - - - - - - - - - - 0 - 0 - - - - A network operation has failed: %1 - - - - - - - - 0 - 0 - - - - (request count) - - - - - - - QAbstractItemView::EditTrigger::NoEditTriggers - - - true - - - QAbstractItemView::ScrollMode::ScrollPerPixel - - - 0 - - - false - - - - URL - - - - - Error - - - - - - - - - 0 - 0 - - - - What would you like to do? - - - - - - - QDialogButtonBox::StandardButton::Abort|QDialogButtonBox::StandardButton::Retry - - - - - - - - diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp index 8cf094527..8d80966ac 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.cpp +++ b/launcher/ui/dialogs/NewInstanceDialog.cpp @@ -61,7 +61,6 @@ #include "ui/pages/modplatform/ImportPage.h" #include "ui/pages/modplatform/atlauncher/AtlPage.h" #include "ui/pages/modplatform/flame/FlamePage.h" -#include "ui/pages/modplatform/ftb/FtbPage.h" #include "ui/pages/modplatform/legacy_ftb/Page.h" #include "ui/pages/modplatform/modrinth/ModrinthPage.h" #include "ui/pages/modplatform/technic/TechnicPage.h" @@ -178,7 +177,6 @@ QList NewInstanceDialog::getPages() pages.append(new AtlPage(this)); if (APPLICATION->capabilities() & Application::SupportsFlame) pages.append(new FlamePage(this)); - pages.append(new FtbPage(this)); pages.append(new LegacyFTB::Page(this)); pages.append(new FTBImportAPP::ImportFTBPage(this)); pages.append(new ModrinthPage(this)); diff --git a/launcher/ui/dialogs/NewsDialog.cpp b/launcher/ui/dialogs/NewsDialog.cpp index 0657c8967..b646e3918 100644 --- a/launcher/ui/dialogs/NewsDialog.cpp +++ b/launcher/ui/dialogs/NewsDialog.cpp @@ -1,8 +1,4 @@ #include "NewsDialog.h" - -#include "Application.h" -#include "settings/SettingsObject.h" - #include "ui_NewsDialog.h" NewsDialog::NewsDialog(QList entries, QWidget* parent) : QDialog(parent), ui(new Ui::NewsDialog()) @@ -27,12 +23,6 @@ NewsDialog::NewsDialog(QList entries, QWidget* parent) : QDialog(p ui->currentArticleContentBrowser->setText(article_entry->content); ui->currentArticleContentBrowser->flush(); - - connect(this, &QDialog::finished, this, [this] { - APPLICATION->settings()->set("NewsGeometry", QString::fromUtf8(saveGeometry().toBase64())); - }); - const QByteArray base64Geometry = APPLICATION->settings()->get("NewsGeometry").toString().toUtf8(); - restoreGeometry(QByteArray::fromBase64(base64Geometry)); } NewsDialog::~NewsDialog() diff --git a/launcher/ui/dialogs/ProfileSelectDialog.cpp b/launcher/ui/dialogs/ProfileSelectDialog.cpp index 4c4995fea..90588ce05 100644 --- a/launcher/ui/dialogs/ProfileSelectDialog.cpp +++ b/launcher/ui/dialogs/ProfileSelectDialog.cpp @@ -17,26 +17,12 @@ #include "ui_ProfileSelectDialog.h" #include -#include #include #include #include "Application.h" -// HACK: hide checkboxes from AccountList -class HideCheckboxProxyModel : public QIdentityProxyModel { - public: - using QIdentityProxyModel::QIdentityProxyModel; - - QVariant data(const QModelIndex& index, int role) const override - { - if (role == Qt::CheckStateRole) { - return {}; - } - - return QIdentityProxyModel::data(index, role); - } -}; +#include "ui/dialogs/ProgressDialog.h" ProfileSelectDialog::ProfileSelectDialog(const QString& message, int flags, QWidget* parent) : QDialog(parent), ui(new Ui::ProfileSelectDialog) @@ -44,10 +30,33 @@ ProfileSelectDialog::ProfileSelectDialog(const QString& message, int flags, QWid ui->setupUi(this); m_accounts = APPLICATION->accounts(); - - auto proxy = new HideCheckboxProxyModel(ui->view); - proxy->setSourceModel(m_accounts); - ui->view->setModel(proxy); + auto view = ui->listView; + // view->setModel(m_accounts.get()); + // view->hideColumn(AccountList::ActiveColumn); + view->setColumnCount(1); + view->setRootIsDecorated(false); + // FIXME: use a real model, not this + if (QTreeWidgetItem* header = view->headerItem()) { + header->setText(0, tr("Name")); + } else { + view->setHeaderLabel(tr("Name")); + } + QList items; + for (int i = 0; i < m_accounts->count(); i++) { + MinecraftAccountPtr account = m_accounts->at(i); + QString profileLabel; + if (account->isInUse()) { + profileLabel = tr("%1 (in use)").arg(account->profileName()); + } else { + profileLabel = account->profileName(); + } + auto item = new QTreeWidgetItem(view); + item->setText(0, profileLabel); + item->setIcon(0, account->getFace()); + item->setData(0, AccountList::PointerRole, QVariant::fromValue(account)); + items.append(item); + } + view->addTopLevelItems(items); // Set the message label. ui->msgLabel->setVisible(!message.isEmpty()); @@ -59,9 +68,9 @@ ProfileSelectDialog::ProfileSelectDialog(const QString& message, int flags, QWid qDebug() << flags; // Select the first entry in the list. - ui->view->setCurrentIndex(ui->view->model()->index(0, 0)); + ui->listView->setCurrentIndex(ui->listView->model()->index(0, 0)); - connect(ui->view, &QAbstractItemView::doubleClicked, this, &ProfileSelectDialog::on_buttonBox_accepted); + connect(ui->listView, &QAbstractItemView::doubleClicked, this, &ProfileSelectDialog::on_buttonBox_accepted); ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK")); @@ -89,7 +98,7 @@ bool ProfileSelectDialog::useAsInstDefaullt() const void ProfileSelectDialog::on_buttonBox_accepted() { - QModelIndexList selection = ui->view->selectionModel()->selectedIndexes(); + QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); if (selection.size() > 0) { QModelIndex selected = selection.first(); m_selected = selected.data(AccountList::PointerRole).value(); diff --git a/launcher/ui/dialogs/ProfileSelectDialog.h b/launcher/ui/dialogs/ProfileSelectDialog.h index a44e82d55..e56ba0527 100644 --- a/launcher/ui/dialogs/ProfileSelectDialog.h +++ b/launcher/ui/dialogs/ProfileSelectDialog.h @@ -76,7 +76,7 @@ class ProfileSelectDialog : public QDialog { void on_buttonBox_rejected(); protected: - AccountList* m_accounts; + shared_qobject_ptr m_accounts; //! The account that was selected when the user clicked OK. MinecraftAccountPtr m_selected; diff --git a/launcher/ui/dialogs/ProfileSelectDialog.ui b/launcher/ui/dialogs/ProfileSelectDialog.ui index a72b3e2e0..e779b51bf 100644 --- a/launcher/ui/dialogs/ProfileSelectDialog.ui +++ b/launcher/ui/dialogs/ProfileSelectDialog.ui @@ -22,7 +22,13 @@ - + + + + 1 + + + @@ -45,7 +51,7 @@ - QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + QDialogButtonBox::Cancel|QDialogButtonBox::Ok diff --git a/launcher/ui/dialogs/ProfileSetupDialog.cpp b/launcher/ui/dialogs/ProfileSetupDialog.cpp index 291827b05..af8b26c66 100644 --- a/launcher/ui/dialogs/ProfileSetupDialog.cpp +++ b/launcher/ui/dialogs/ProfileSetupDialog.cpp @@ -159,23 +159,22 @@ void ProfileSetupDialog::checkName(const QString& name) { "Accept", "application/json" }, { "Authorization", QString("Bearer %1").arg(m_accountToSetup->accessToken()).toUtf8() } }; + m_check_response.reset(new QByteArray()); if (m_check_task) disconnect(m_check_task.get(), nullptr, this, nullptr); - auto [task, response] = Net::Download::makeByteArray(url); + m_check_task = Net::Download::makeByteArray(url, m_check_response); + m_check_task->addHeaderProxy(new Net::RawHeaderProxy(headers)); - m_check_task = task; - m_check_task->addHeaderProxy(std::make_unique(headers)); - - connect(m_check_task.get(), &Task::finished, this, [this, response] { checkFinished(response); }); + connect(m_check_task.get(), &Task::finished, this, &ProfileSetupDialog::checkFinished); m_check_task->setNetwork(APPLICATION->network()); m_check_task->start(); } -void ProfileSetupDialog::checkFinished(QByteArray* response) +void ProfileSetupDialog::checkFinished() { if (m_check_task->error() == QNetworkReply::NoError) { - auto doc = QJsonDocument::fromJson(*response); + auto doc = QJsonDocument::fromJson(*m_check_response); auto root = doc.object(); auto statusValue = root.value("status").toString("INVALID"); if (statusValue == "AVAILABLE") { @@ -206,11 +205,11 @@ void ProfileSetupDialog::setupProfile(const QString& profileName) { "Accept", "application/json" }, { "Authorization", QString("Bearer %1").arg(m_accountToSetup->accessToken()).toUtf8() } }; - auto [task, response] = Net::Upload::makeByteArray(url, payloadTemplate.arg(profileName).toUtf8()); - m_profile_task = task; - m_profile_task->addHeaderProxy(std::make_unique(headers)); + m_profile_response.reset(new QByteArray()); + m_profile_task = Net::Upload::makeByteArray(url, m_profile_response, payloadTemplate.arg(profileName).toUtf8()); + m_profile_task->addHeaderProxy(new Net::RawHeaderProxy(headers)); - connect(m_profile_task.get(), &Task::finished, this, [this, response] { setupProfileFinished(response); }); + connect(m_profile_task.get(), &Task::finished, this, &ProfileSetupDialog::setupProfileFinished); m_profile_task->setNetwork(APPLICATION->network()); m_profile_task->start(); @@ -253,7 +252,7 @@ struct MojangError { } // namespace -void ProfileSetupDialog::setupProfileFinished(QByteArray* response) +void ProfileSetupDialog::setupProfileFinished() { isWorking = false; if (m_profile_task->error() == QNetworkReply::NoError) { @@ -263,7 +262,7 @@ void ProfileSetupDialog::setupProfileFinished(QByteArray* response) */ accept(); } else { - auto parsedError = MojangError::fromJSON(*response); + auto parsedError = MojangError::fromJSON(*m_profile_response); ui->errorLabel->setVisible(true); QString errorMessage = diff --git a/launcher/ui/dialogs/ProfileSetupDialog.h b/launcher/ui/dialogs/ProfileSetupDialog.h index 1da9c1164..c005a4138 100644 --- a/launcher/ui/dialogs/ProfileSetupDialog.h +++ b/launcher/ui/dialogs/ProfileSetupDialog.h @@ -21,6 +21,7 @@ #include #include +#include #include "net/Download.h" #include "net/Upload.h" @@ -43,8 +44,8 @@ class ProfileSetupDialog : public QDialog { void nameEdited(const QString& name); void startCheck(); - void checkFinished(QByteArray* response); - void setupProfileFinished(QByteArray* response); + void checkFinished(); + void setupProfileFinished(); protected: void scheduleCheck(const QString& name); @@ -69,6 +70,9 @@ class ProfileSetupDialog : public QDialog { QTimer checkStartTimer; + std::shared_ptr m_check_response; Net::Download::Ptr m_check_task; + + std::shared_ptr m_profile_response; Net::Upload::Ptr m_profile_task; }; diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index 0a453bda5..aa2f67bdb 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -152,7 +152,6 @@ int ProgressDialog::execWithTask(Task* task) this->m_taskConnections.push_back(connect(task, &Task::progress, this, &ProgressDialog::changeProgress)); this->m_taskConnections.push_back(connect(task, &Task::aborted, this, &ProgressDialog::hide)); this->m_taskConnections.push_back(connect(task, &Task::abortStatusChanged, ui->skipButton, &QPushButton::setEnabled)); - this->m_taskConnections.push_back(connect(task, &Task::abortButtonTextChanged, ui->skipButton, &QPushButton::setText)); m_is_multi_step = task->isMultiStep(); ui->taskProgressScrollArea->setHidden(!m_is_multi_step); @@ -252,7 +251,10 @@ void ProgressDialog::changeStepProgress(TaskStepProgress const& task_progress) task_bar->setValue(mapped_current); task_bar->setStatus(task_progress.status); task_bar->setDetails(task_progress.details); - task_bar->setVisible(!task_progress.isDone()); + + if (task_progress.isDone()) { + task_bar->setVisible(false); + } } void ProgressDialog::changeProgress(qint64 current, qint64 total) diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index bcb30c761..2fd0b8c7c 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -22,7 +22,6 @@ #include #include -#include #include "Application.h" #include "ResourceDownloadTask.h" @@ -50,12 +49,11 @@ namespace ResourceDownload { -ResourceDownloadDialog::ResourceDownloadDialog(QWidget* parent, ResourceFolderModel* baseModel, bool suppressInitialSearch) +ResourceDownloadDialog::ResourceDownloadDialog(QWidget* parent, const std::shared_ptr base_model) : QDialog(parent) - , m_base_model(baseModel) + , m_base_model(base_model) , m_buttons(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel) , m_vertical_layout(this) - , m_suppressInitialSearch(suppressInitialSearch) { setObjectName(QStringLiteral("ResourceDownloadDialog")); @@ -63,35 +61,32 @@ ResourceDownloadDialog::ResourceDownloadDialog(QWidget* parent, ResourceFolderMo setWindowIcon(QIcon::fromTheme("new")); -// small margins look ugly on macOS on modal windows -#ifndef Q_OS_MACOS m_buttons.setContentsMargins(0, 0, 6, 6); -#endif + // 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 - auto* okButton = m_buttons.button(QDialogButtonBox::Ok); - okButton->setEnabled(false); - okButton->setDefault(true); - okButton->setAutoDefault(true); - okButton->setText(tr("Review and confirm")); - okButton->setShortcut(tr("Ctrl+Return")); + auto OkButton = m_buttons.button(QDialogButtonBox::Ok); + OkButton->setEnabled(false); + OkButton->setDefault(true); + OkButton->setAutoDefault(true); + OkButton->setText(tr("Review and confirm")); + OkButton->setShortcut(tr("Ctrl+Return")); - auto* cancelButton = m_buttons.button(QDialogButtonBox::Cancel); - cancelButton->setDefault(false); - cancelButton->setAutoDefault(false); + auto CancelButton = m_buttons.button(QDialogButtonBox::Cancel); + CancelButton->setDefault(false); + CancelButton->setAutoDefault(false); - auto* helpButton = m_buttons.button(QDialogButtonBox::Help); - helpButton->setDefault(false); - helpButton->setAutoDefault(false); + auto HelpButton = m_buttons.button(QDialogButtonBox::Help); + HelpButton->setDefault(false); + HelpButton->setAutoDefault(false); setWindowModality(Qt::WindowModal); } void ResourceDownloadDialog::accept() { - if (!geometrySaveKey().isEmpty()) { + if (!geometrySaveKey().isEmpty()) APPLICATION->settings()->set(geometrySaveKey(), QString::fromUtf8(saveGeometry().toBase64())); - } QDialog::accept(); } @@ -111,9 +106,8 @@ void ResourceDownloadDialog::reject() } } - if (!geometrySaveKey().isEmpty()) { + if (!geometrySaveKey().isEmpty()) APPLICATION->settings()->set(geometrySaveKey(), QString::fromUtf8(saveGeometry().toBase64())); - } QDialog::reject(); } @@ -122,10 +116,7 @@ void ResourceDownloadDialog::reject() // won't work with subclasses if we put it in this ctor. void ResourceDownloadDialog::initializeContainer() { -// small margins look ugly on macOS on modal windows -#ifndef Q_OS_MACOS layout()->setContentsMargins(0, 0, 0, 0); -#endif m_container = new PageContainer(this, {}, this); m_container->setSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Expanding); @@ -139,28 +130,28 @@ void ResourceDownloadDialog::initializeContainer() void ResourceDownloadDialog::connectButtons() { - auto* okButton = m_buttons.button(QDialogButtonBox::Ok); - okButton->setToolTip( + auto OkButton = m_buttons.button(QDialogButtonBox::Ok); + OkButton->setToolTip( tr("Opens a new popup to review your selected %1 and confirm your selection. Shortcut: Ctrl+Return").arg(resourcesString())); - connect(okButton, &QPushButton::clicked, this, &ResourceDownloadDialog::confirm); + connect(OkButton, &QPushButton::clicked, this, &ResourceDownloadDialog::confirm); - auto* cancelButton = m_buttons.button(QDialogButtonBox::Cancel); - connect(cancelButton, &QPushButton::clicked, this, &ResourceDownloadDialog::reject); + auto CancelButton = m_buttons.button(QDialogButtonBox::Cancel); + connect(CancelButton, &QPushButton::clicked, this, &ResourceDownloadDialog::reject); - auto* helpButton = m_buttons.button(QDialogButtonBox::Help); - connect(helpButton, &QPushButton::clicked, m_container, &PageContainer::help); + auto HelpButton = m_buttons.button(QDialogButtonBox::Help); + connect(HelpButton, &QPushButton::clicked, m_container, &PageContainer::help); } void ResourceDownloadDialog::confirm() { - auto* confirmDialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourcesString())); - confirmDialog->retranslateUi(resourcesString()); + auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourcesString())); + confirm_dialog->retranslateUi(resourcesString()); QHash dependencyExtraInfo; QStringList depNames; if (auto task = getModDependenciesTask(); task) { connect(task.get(), &Task::failed, this, - [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); + [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); auto weak = task.toWeakRef(); connect(task.get(), &Task::succeeded, this, [this, weak]() { @@ -174,77 +165,72 @@ void ResourceDownloadDialog::confirm() }); // Check for updates - ProgressDialog progressDialog(this); - progressDialog.setSkipButton(true, tr("Abort")); - progressDialog.setWindowTitle(tr("Checking for dependencies...")); - auto ret = progressDialog.execWithTask(task.get()); + ProgressDialog progress_dialog(this); + progress_dialog.setSkipButton(true, tr("Abort")); + progress_dialog.setWindowTitle(tr("Checking for dependencies...")); + auto ret = progress_dialog.execWithTask(task.get()); // If the dialog was skipped / some download error happened if (ret == QDialog::DialogCode::Rejected) { QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); return; + } else { + for (auto dep : task->getDependecies()) { + addResource(dep->pack, dep->version); + depNames << dep->pack->name; + } + dependencyExtraInfo = task->getExtraInfo(); } - for (const auto& dep : task->getDependecies()) { - addResource(dep->pack, dep->version, "dependency"); - depNames << dep->pack->name; - } - dependencyExtraInfo = task->getExtraInfo(); } auto selected = getTasks(); - std::ranges::sort(selected, [](const DownloadTaskPtr& a, const DownloadTaskPtr& b) { + std::sort(selected.begin(), selected.end(), [](const DownloadTaskPtr& a, const DownloadTaskPtr& b) { return QString::compare(a->getName(), b->getName(), Qt::CaseInsensitive) < 0; }); for (auto& task : selected) { auto extraInfo = dependencyExtraInfo.value(task->getPack()->addonId.toString()); - confirmDialog->appendResource({ .name = task->getName(), - .filename = task->getFilename(), - .provider = ModPlatform::ProviderCapabilities::name(task->getProvider()), - .required_by = extraInfo.required_by, - .version_type = task->getVersion().version_type.toString(), - .enabled = !extraInfo.maybe_installed }); + confirm_dialog->appendResource({ task->getName(), task->getFilename(), ModPlatform::ProviderCapabilities::name(task->getProvider()), + extraInfo.required_by, task->getVersion().version_type.toString(), !extraInfo.maybe_installed }); } - if (confirmDialog->exec() != 0) { - auto deselected = confirmDialog->deselectedResources(); - for (auto* page : m_container->getPages()) { - auto* res = static_cast(page); - for (const auto& name : deselected) { + if (confirm_dialog->exec()) { + auto deselected = confirm_dialog->deselectedResources(); + for (auto page : m_container->getPages()) { + auto res = static_cast(page); + for (auto name : deselected) res->removeResourceFromPage(name); - } } this->accept(); } else { - for (const auto& name : depNames) { + for (auto name : depNames) removeResource(name); - } } } bool ResourceDownloadDialog::selectPage(QString pageId) { - return m_container->selectPage(std::move(pageId)); + return m_container->selectPage(pageId); } ResourcePage* ResourceDownloadDialog::selectedPage() { - auto* result = dynamic_cast(m_container->selectedPage()); + ResourcePage* result = dynamic_cast(m_container->selectedPage()); Q_ASSERT(result != nullptr); return result; } -void ResourceDownloadDialog::addResource(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& ver, QString downloadReason) +void ResourceDownloadDialog::addResource(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& ver) { removeResource(pack->name); - selectedPage()->addResourceToPage(pack, ver, getBaseModel(), std::move(downloadReason)); + selectedPage()->addResourceToPage(pack, ver, getBaseModel()); setButtonStatus(); } -void ResourceDownloadDialog::removeResource(const QString& packName) +void ResourceDownloadDialog::removeResource(const QString& pack_name) { - for (auto* page : m_container->getPages()) { - static_cast(page)->removeResourceFromPage(packName); + for (auto page : m_container->getPages()) { + static_cast(page)->removeResourceFromPage(pack_name); } setButtonStatus(); } @@ -252,18 +238,18 @@ void ResourceDownloadDialog::removeResource(const QString& packName) void ResourceDownloadDialog::setButtonStatus() { auto selected = false; - for (auto* page : m_container->getPages()) { - auto* res = static_cast(page); + for (auto page : m_container->getPages()) { + auto res = static_cast(page); selected = selected || res->hasSelectedPacks(); } m_buttons.button(QDialogButtonBox::Ok)->setEnabled(selected); } -QList ResourceDownloadDialog::getTasks() +const QList ResourceDownloadDialog::getTasks() { QList selected; - for (auto* page : m_container->getPages()) { - auto* res = static_cast(page); + for (auto page : m_container->getPages()) { + auto res = static_cast(page); selected.append(res->selectedPacks()); } return selected; @@ -271,34 +257,28 @@ QList ResourceDownloadDialog::getTasks( void ResourceDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* selected) { - // If previous is null (first selection), nothing to sync - if (!previous) { - return; - } - - auto* prevPage = dynamic_cast(previous); - if (!prevPage) { + auto* prev_page = dynamic_cast(previous); + if (!prev_page) { qCritical() << "Page '" << previous->displayName() << "' in ResourceDownloadDialog is not a ResourcePage!"; return; } // Same effect as having a global search bar - auto* result = dynamic_cast(selected); + ResourcePage* result = dynamic_cast(selected); Q_ASSERT(result != nullptr); - result->setSearchTerm(prevPage->getSearchTerm()); + result->setSearchTerm(prev_page->getSearchTerm()); } -ModDownloadDialog::ModDownloadDialog(QWidget* parent, ModFolderModel* mods, BaseInstance* instance, bool suppressInitialSearch) - : ResourceDownloadDialog(parent, mods, suppressInitialSearch), m_instance(instance) +ModDownloadDialog::ModDownloadDialog(QWidget* parent, const std::shared_ptr& mods, BaseInstance* instance) + : ResourceDownloadDialog(parent, mods), m_instance(instance) { setWindowTitle(dialogTitle()); initializeContainer(); connectButtons(); - if (!geometrySaveKey().isEmpty()) { + if (!geometrySaveKey().isEmpty()) restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get(geometrySaveKey()).toString().toUtf8())); - } } QList ModDownloadDialog::getPages() @@ -307,16 +287,10 @@ QList ModDownloadDialog::getPages() auto loaders = static_cast(m_instance)->getPackProfile()->getSupportedModLoaders().value(); - if (ModrinthAPI::validateModLoaders(loaders)) { - auto* page = ModrinthModPage::create(this, *m_instance); - page->setSuppressInitialSearch(m_suppressInitialSearch); - pages.append(page); - } - if (APPLICATION->capabilities() & Application::SupportsFlame && FlameAPI::validateModLoaders(loaders)) { - auto* page = FlameModPage::create(this, *m_instance); - page->setSuppressInitialSearch(m_suppressInitialSearch); - pages.append(page); - } + if (ModrinthAPI::validateModLoaders(loaders)) + pages.append(ModrinthModPage::create(this, *m_instance)); + if (APPLICATION->capabilities() & Application::SupportsFlame && FlameAPI::validateModLoaders(loaders)) + pages.append(FlameModPage::create(this, *m_instance)); return pages; } @@ -324,9 +298,9 @@ QList ModDownloadDialog::getPages() GetModDependenciesTask::Ptr ModDownloadDialog::getModDependenciesTask() { if (!APPLICATION->settings()->get("ModDependenciesDisabled").toBool()) { // dependencies - if (auto* model = dynamic_cast(getBaseModel()); model) { + if (auto model = dynamic_cast(getBaseModel().get()); model) { QList> selectedVers; - for (const auto& selected : getTasks()) { + for (auto& selected : getTasks()) { selectedVers.append(std::make_shared(selected->getPack(), selected->getVersion())); } @@ -337,96 +311,75 @@ GetModDependenciesTask::Ptr ModDownloadDialog::getModDependenciesTask() } ResourcePackDownloadDialog::ResourcePackDownloadDialog(QWidget* parent, - ResourcePackFolderModel* resourcePacks, - BaseInstance* instance, - bool suppressInitialSearch) - : ResourceDownloadDialog(parent, resourcePacks, suppressInitialSearch), m_instance(instance) + const std::shared_ptr& resource_packs, + BaseInstance* instance) + : ResourceDownloadDialog(parent, resource_packs), m_instance(instance) { setWindowTitle(dialogTitle()); initializeContainer(); connectButtons(); - if (!geometrySaveKey().isEmpty()) { + if (!geometrySaveKey().isEmpty()) restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get(geometrySaveKey()).toString().toUtf8())); - } } QList ResourcePackDownloadDialog::getPages() { QList pages; - auto* modrinthPage = ModrinthResourcePackPage::create(this, *m_instance); - modrinthPage->setSuppressInitialSearch(m_suppressInitialSearch); - pages.append(modrinthPage); - if (APPLICATION->capabilities() & Application::SupportsFlame) { - auto* flamePage = FlameResourcePackPage::create(this, *m_instance); - flamePage->setSuppressInitialSearch(m_suppressInitialSearch); - pages.append(flamePage); - } + pages.append(ModrinthResourcePackPage::create(this, *m_instance)); + if (APPLICATION->capabilities() & Application::SupportsFlame) + pages.append(FlameResourcePackPage::create(this, *m_instance)); return pages; } TexturePackDownloadDialog::TexturePackDownloadDialog(QWidget* parent, - TexturePackFolderModel* resourcePacks, - BaseInstance* instance, - bool suppressInitialSearch) - : ResourceDownloadDialog(parent, resourcePacks, suppressInitialSearch), m_instance(instance) + const std::shared_ptr& resource_packs, + BaseInstance* instance) + : ResourceDownloadDialog(parent, resource_packs), m_instance(instance) { setWindowTitle(dialogTitle()); initializeContainer(); connectButtons(); - if (!geometrySaveKey().isEmpty()) { + if (!geometrySaveKey().isEmpty()) restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get(geometrySaveKey()).toString().toUtf8())); - } } QList TexturePackDownloadDialog::getPages() { QList pages; - auto* modrinthPage = ModrinthTexturePackPage::create(this, *m_instance); - modrinthPage->setSuppressInitialSearch(m_suppressInitialSearch); - pages.append(modrinthPage); - if (APPLICATION->capabilities() & Application::SupportsFlame) { - auto* flamePage = FlameTexturePackPage::create(this, *m_instance); - flamePage->setSuppressInitialSearch(m_suppressInitialSearch); - pages.append(flamePage); - } + pages.append(ModrinthTexturePackPage::create(this, *m_instance)); + if (APPLICATION->capabilities() & Application::SupportsFlame) + pages.append(FlameTexturePackPage::create(this, *m_instance)); return pages; } ShaderPackDownloadDialog::ShaderPackDownloadDialog(QWidget* parent, - ShaderPackFolderModel* shaders, - BaseInstance* instance, - bool suppressInitialSearch) - : ResourceDownloadDialog(parent, shaders, suppressInitialSearch), m_instance(instance) + const std::shared_ptr& shaders, + BaseInstance* instance) + : ResourceDownloadDialog(parent, shaders), m_instance(instance) { setWindowTitle(dialogTitle()); initializeContainer(); connectButtons(); - if (!geometrySaveKey().isEmpty()) { + if (!geometrySaveKey().isEmpty()) restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get(geometrySaveKey()).toString().toUtf8())); - } } QList ShaderPackDownloadDialog::getPages() { QList pages; - auto* modrinthPage = ModrinthShaderPackPage::create(this, *m_instance); - modrinthPage->setSuppressInitialSearch(m_suppressInitialSearch); - pages.append(modrinthPage); - if (APPLICATION->capabilities() & Application::SupportsFlame) { - auto* flamePage = FlameShaderPackPage::create(this, *m_instance); - flamePage->setSuppressInitialSearch(m_suppressInitialSearch); - pages.append(flamePage); - } + pages.append(ModrinthShaderPackPage::create(this, *m_instance)); + if (APPLICATION->capabilities() & Application::SupportsFlame) + pages.append(FlameShaderPackPage::create(this, *m_instance)); return pages; } @@ -440,41 +393,33 @@ void ResourceDownloadDialog::setResourceMetadata(const std::shared_ptrname)); m_container->hidePageList(); m_buttons.hide(); - auto* page = selectedPage(); + auto page = selectedPage(); page->openProject(meta->project_id); } DataPackDownloadDialog::DataPackDownloadDialog(QWidget* parent, - DataPackFolderModel* dataPacks, - BaseInstance* instance, - bool suppressInitialSearch) - : ResourceDownloadDialog(parent, dataPacks, suppressInitialSearch), m_instance(instance) + const std::shared_ptr& data_packs, + BaseInstance* instance) + : ResourceDownloadDialog(parent, data_packs), m_instance(instance) { setWindowTitle(dialogTitle()); initializeContainer(); connectButtons(); - if (!geometrySaveKey().isEmpty()) { + if (!geometrySaveKey().isEmpty()) restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get(geometrySaveKey()).toByteArray())); - } } QList DataPackDownloadDialog::getPages() { QList pages; - auto* modrinthPage = ModrinthDataPackPage::create(this, *m_instance); - modrinthPage->setSuppressInitialSearch(m_suppressInitialSearch); - pages.append(modrinthPage); - if (APPLICATION->capabilities() & Application::SupportsFlame) { - auto* flamePage = FlameDataPackPage::create(this, *m_instance); - flamePage->setSuppressInitialSearch(m_suppressInitialSearch); - pages.append(flamePage); - } + pages.append(ModrinthDataPackPage::create(this, *m_instance)); + if (APPLICATION->capabilities() & Application::SupportsFlame) + pages.append(FlameDataPackPage::create(this, *m_instance)); return pages; } diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h index bea6c7689..a83f3c536 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.h +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -51,7 +51,7 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { public: using DownloadTaskPtr = shared_qobject_ptr; - ResourceDownloadDialog(QWidget* parent, ResourceFolderModel* baseModel, bool suppressInitialSearch = false); + ResourceDownloadDialog(QWidget* parent, std::shared_ptr base_model); void initializeContainer(); void connectButtons(); @@ -64,11 +64,11 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { bool selectPage(QString pageId); ResourcePage* selectedPage(); - void addResource(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, QString downloadReason = "standalone"); + void addResource(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&); void removeResource(const QString&); - QList getTasks(); - ResourceFolderModel* getBaseModel() const { return m_base_model; } + const QList getTasks(); + const std::shared_ptr getBaseModel() const { return m_base_model; } void setResourceMetadata(const std::shared_ptr& meta); @@ -88,22 +88,19 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { virtual GetModDependenciesTask::Ptr getModDependenciesTask() { return nullptr; } protected: - ResourceFolderModel* m_base_model; + const std::shared_ptr m_base_model; PageContainer* m_container = nullptr; QDialogButtonBox m_buttons; QVBoxLayout m_vertical_layout; - - protected: - bool m_suppressInitialSearch = false; }; class ModDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit ModDownloadDialog(QWidget* parent, ModFolderModel* mods, BaseInstance* instance, bool suppressInitialSearch = false); + explicit ModDownloadDialog(QWidget* parent, const std::shared_ptr& mods, BaseInstance* instance); ~ModDownloadDialog() override = default; //: String that gets appended to the mod download dialog title ("Download " + resourcesString()) @@ -122,9 +119,8 @@ class ResourcePackDownloadDialog final : public ResourceDownloadDialog { public: explicit ResourcePackDownloadDialog(QWidget* parent, - ResourcePackFolderModel* resourcePacks, - BaseInstance* instance, - bool suppressInitialSearch = false); + const std::shared_ptr& resource_packs, + BaseInstance* instance); ~ResourcePackDownloadDialog() override = default; //: String that gets appended to the resource pack download dialog title ("Download " + resourcesString()) @@ -142,9 +138,8 @@ class TexturePackDownloadDialog final : public ResourceDownloadDialog { public: explicit TexturePackDownloadDialog(QWidget* parent, - TexturePackFolderModel* resourcePacks, - BaseInstance* instance, - bool suppressInitialSearch = false); + const std::shared_ptr& resource_packs, + BaseInstance* instance); ~TexturePackDownloadDialog() override = default; //: String that gets appended to the texture pack download dialog title ("Download " + resourcesString()) @@ -161,10 +156,7 @@ class ShaderPackDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit ShaderPackDownloadDialog(QWidget* parent, - ShaderPackFolderModel* shaders, - BaseInstance* instance, - bool suppressInitialSearch = false); + explicit ShaderPackDownloadDialog(QWidget* parent, const std::shared_ptr& shader_packs, BaseInstance* instance); ~ShaderPackDownloadDialog() override = default; //: String that gets appended to the shader pack download dialog title ("Download " + resourcesString()) @@ -181,10 +173,7 @@ class DataPackDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit DataPackDownloadDialog(QWidget* parent, - DataPackFolderModel* dataPacks, - BaseInstance* instance, - bool suppressInitialSearch = false); + explicit DataPackDownloadDialog(QWidget* parent, const std::shared_ptr& data_packs, BaseInstance* instance); ~DataPackDownloadDialog() override = default; //: String that gets appended to the data pack download dialog title ("Download " + resourcesString()) diff --git a/launcher/ui/dialogs/ResourceUpdateDialog.cpp b/launcher/ui/dialogs/ResourceUpdateDialog.cpp index e9776c660..494c17e3f 100644 --- a/launcher/ui/dialogs/ResourceUpdateDialog.cpp +++ b/launcher/ui/dialogs/ResourceUpdateDialog.cpp @@ -29,16 +29,14 @@ #include -namespace { -std::vector mcVersions(BaseInstance* inst) +static std::list mcVersions(BaseInstance* inst) { return { static_cast(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion() }; } -} // namespace ResourceUpdateDialog::ResourceUpdateDialog(QWidget* parent, BaseInstance* instance, - ResourceFolderModel* resourceModel, + const std::shared_ptr resourceModel, QList& searchFor, bool includeDeps, QList loadersList) @@ -60,8 +58,8 @@ ResourceUpdateDialog::ResourceUpdateDialog(QWidget* parent, void ResourceUpdateDialog::checkCandidates() { // Ensure mods have valid metadata - auto wentWell = ensureMetadata(); - if (!wentWell) { + auto went_well = ensureMetadata(); + if (!went_well) { m_aborted = true; return; } @@ -75,12 +73,12 @@ void ResourceUpdateDialog::checkCandidates() text += tr("Mod name: %1
File name: %2
Reason: %3

").arg(mod->name(), mod->fileinfo().fileName(), reason); } - ScrollMessageBox messageDialog(m_parent, tr("Metadata generation failed"), - tr("Could not generate metadata for the following resources:
" - "Do you wish to proceed without those resources?"), - text); - messageDialog.setModal(true); - if (messageDialog.exec() == QDialog::Rejected) { + ScrollMessageBox message_dialog(m_parent, tr("Metadata generation failed"), + tr("Could not generate metadata for the following resources:
" + "Do you wish to proceed without those resources?"), + text); + message_dialog.setModal(true); + if (message_dialog.exec() == QDialog::Rejected) { m_aborted = true; QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); return; @@ -89,41 +87,40 @@ void ResourceUpdateDialog::checkCandidates() auto versions = mcVersions(m_instance); - SequentialTask checkTask(tr("Checking for updates")); + SequentialTask check_task(tr("Checking for updates")); 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, const QString& reason, const QUrl& recoverUrl) { - m_failedCheckUpdate.append({ resource, reason, recoverUrl }); + [this](Resource* resource, QString reason, QUrl recover_url) { + m_failedCheckUpdate.append({ resource, reason, recover_url }); }); - checkTask.addTask(m_modrinthCheckTask); + check_task.addTask(m_modrinthCheckTask); } 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, const QString& reason, const QUrl& recoverUrl) { - m_failedCheckUpdate.append({ resource, reason, recoverUrl }); - }); - checkTask.addTask(m_flameCheckTask); + 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(&checkTask, &Task::failed, this, - [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); + connect(&check_task, &Task::failed, this, + [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); - connect(&checkTask, &Task::succeeded, this, [this, &checkTask]() { - QStringList warnings = checkTask.warnings(); + connect(&check_task, &Task::succeeded, this, [this, &check_task]() { + QStringList warnings = check_task.warnings(); if (warnings.count()) { CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->exec(); } }); // Check for updates - ProgressDialog progressDialog(m_parent); - progressDialog.setSkipButton(true, tr("Abort")); - progressDialog.setWindowTitle(tr("Checking for updates...")); - auto ret = progressDialog.execWithTask(&checkTask); + ProgressDialog progress_dialog(m_parent); + progress_dialog.setSkipButton(true, tr("Abort")); + progress_dialog.setWindowTitle(tr("Checking for updates...")); + auto ret = progress_dialog.execWithTask(&check_task); // If the dialog was skipped / some download error happened if (ret == QDialog::DialogCode::Rejected) { @@ -136,8 +133,8 @@ void ResourceUpdateDialog::checkCandidates() // Add found updates for Modrinth if (m_modrinthCheckTask) { - auto modrinthUpdates = m_modrinthCheckTask->getUpdates(); - for (auto& updatable : modrinthUpdates) { + auto modrinth_updates = m_modrinthCheckTask->getUpdates(); + for (auto& updatable : modrinth_updates) { qDebug() << QString("Mod %1 has an update available!").arg(updatable.name); appendResource(updatable); @@ -148,8 +145,8 @@ void ResourceUpdateDialog::checkCandidates() // Add found updated for Flame if (m_flameCheckTask) { - auto flameUpdates = m_flameCheckTask->getUpdates(); - for (auto& updatable : flameUpdates) { + auto flame_updates = m_flameCheckTask->getUpdates(); + for (auto& updatable : flame_updates) { qDebug() << QString("Mod %1 has an update available!").arg(updatable.name); appendResource(updatable); @@ -164,47 +161,37 @@ void ResourceUpdateDialog::checkCandidates() for (const auto& failed : m_failedCheckUpdate) { const auto& mod = std::get<0>(failed); const auto& reason = std::get<1>(failed); - const auto& recoverUrl = std::get<2>(failed); + const auto& recover_url = std::get<2>(failed); qDebug() << mod->name() << "failed to check for updates!"; text += tr("Mod name: %1").arg(mod->name()) + "
"; - if (!reason.isEmpty()) { + if (!reason.isEmpty()) text += tr("Reason: %1").arg(reason) + "
"; - } - if (!recoverUrl.isEmpty()) { + if (!recover_url.isEmpty()) //: %1 is the link to download it manually text += tr("Possible solution: Getting the latest version manually:
%1
") - .arg(QString("%1").arg(recoverUrl.toString())); - } + .arg(QString("%1").arg(recover_url.toString())); text += "
"; } - ScrollMessageBox messageDialog(m_parent, tr("Failed to check for updates"), - tr("Could not check or get the following resources for updates:
" - "Do you wish to proceed without those resources?"), - text, "Disable unavailable mods"); - messageDialog.setModal(true); - if (messageDialog.exec() == QDialog::Rejected) { + ScrollMessageBox message_dialog(m_parent, tr("Failed to check for updates"), + tr("Could not check or get the following resources for updates:
" + "Do you wish to proceed without those resources?"), + text); + message_dialog.setModal(true); + if (message_dialog.exec() == QDialog::Rejected) { m_aborted = true; QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); return; } - - // Disable unavailable mods - if (messageDialog.isOptionChecked()) { - for (const auto& failed : m_failedCheckUpdate) { - const auto& mod = std::get<0>(failed); - mod->enable(EnableAction::DISABLE); - } - } } if (m_includeDeps && !APPLICATION->settings()->get("ModDependenciesDisabled").toBool()) { // dependencies - auto* modModel = dynamic_cast(m_resourceModel); + auto* mod_model = dynamic_cast(m_resourceModel.get()); - if (modModel != nullptr) { - auto depTask = makeShared(m_instance, modModel, selectedVers); + if (mod_model != nullptr) { + auto depTask = makeShared(m_instance, mod_model, selectedVers); connect(depTask.get(), &Task::failed, this, [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); @@ -220,10 +207,10 @@ void ResourceUpdateDialog::checkCandidates() } }); - ProgressDialog progressDialogDeps(m_parent); - progressDialogDeps.setSkipButton(true, tr("Abort")); - progressDialogDeps.setWindowTitle(tr("Checking for dependencies...")); - auto dret = progressDialogDeps.execWithTask(depTask.get()); + ProgressDialog progress_dialog_deps(m_parent); + progress_dialog_deps.setSkipButton(true, tr("Abort")); + progress_dialog_deps.setWindowTitle(tr("Checking for dependencies...")); + auto dret = progress_dialog_deps.execWithTask(depTask.get()); // If the dialog was skipped / some download error happened if (dret == QDialog::DialogCode::Rejected) { @@ -231,20 +218,19 @@ void ResourceUpdateDialog::checkCandidates() QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); return; } - static FlameAPI s_api; + static FlameAPI api; auto dependencyExtraInfo = depTask->getExtraInfo(); for (const auto& dep : depTask->getDependecies()) { auto changelog = dep->version.changelog; - if (dep->pack->provider == ModPlatform::ResourceProvider::FLAME) { - changelog = s_api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt()); - } - auto downloadTask = makeShared(dep->pack, dep->version, m_resourceModel, true, "dependency"); + 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_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, - changelog, dep->pack->provider, downloadTask, !extraInfo.maybe_installed + changelog, dep->pack->provider, download_task, !extraInfo.maybe_installed }; appendResource(updatable, extraInfo.required_by); @@ -270,58 +256,56 @@ void ResourceUpdateDialog::checkCandidates() } } - if (m_aborted || m_noUpdates) { + if (m_aborted || m_noUpdates) QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); - } } // Part 1: Ensure we have a valid metadata auto ResourceUpdateDialog::ensureMetadata() -> bool { - auto indexDir2 = indexDir(); + auto index_dir = indexDir(); SequentialTask seq(tr("Looking for metadata")); // A better use of data structures here could remove the need for this QHash - QHash shouldTryOthers; - QList modrinthTmp; - QList flameTmp; + QHash should_try_others; + QList modrinth_tmp; + QList flame_tmp; - bool confirmRest = false; - bool tryOthersRest = false; - bool skipRest = false; - ModPlatform::ResourceProvider providerRest = ModPlatform::ResourceProvider::MODRINTH; + bool confirm_rest = false; + bool try_others_rest = false; + bool skip_rest = false; + ModPlatform::ResourceProvider provider_rest = ModPlatform::ResourceProvider::MODRINTH; // adds resource to list based on provider - auto addToTmp = [&modrinthTmp, &flameTmp](Resource* resource, ModPlatform::ResourceProvider p) { + auto addToTmp = [&modrinth_tmp, &flame_tmp](Resource* resource, ModPlatform::ResourceProvider p) { switch (p) { case ModPlatform::ResourceProvider::MODRINTH: - modrinthTmp.push_back(resource); + modrinth_tmp.push_back(resource); break; case ModPlatform::ResourceProvider::FLAME: - flameTmp.push_back(resource); + flame_tmp.push_back(resource); break; } }; // ask the user on what provider to seach for the mod first - for (auto* candidate : m_candidates) { - if (candidate->status() != ResourceStatus::NoMetadata) { + for (auto candidate : m_candidates) { + if (candidate->status() != ResourceStatus::NO_METADATA) { onMetadataEnsured(candidate); continue; } - if (skipRest) { + if (skip_rest) continue; - } if (candidate->type() == ResourceType::FOLDER) { continue; } - if (confirmRest) { - addToTmp(candidate, providerRest); - shouldTryOthers.insert(candidate->internalId(), tryOthersRest); + if (confirm_rest) { + addToTmp(candidate, provider_rest); + should_try_others.insert(candidate->internal_id(), try_others_rest); continue; } @@ -334,73 +318,68 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool auto response = chooser.getResponse(); - if (response.skip_all) { - skipRest = true; - } + if (response.skip_all) + skip_rest = true; if (response.confirm_all) { - confirmRest = true; - providerRest = response.chosen; - tryOthersRest = response.try_others; + confirm_rest = true; + provider_rest = response.chosen; + try_others_rest = response.try_others; } - shouldTryOthers.insert(candidate->internalId(), response.try_others); + should_try_others.insert(candidate->internal_id(), response.try_others); - if (confirmed) { + if (confirmed) addToTmp(candidate, response.chosen); - } } // prepare task for the modrinth mods - if (!modrinthTmp.empty()) { - auto modrinthTask = makeShared(modrinthTmp, indexDir2, ModPlatform::ResourceProvider::MODRINTH); - connect(modrinthTask.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); - connect(modrinthTask.get(), &EnsureMetadataTask::metadataFailed, [this, &shouldTryOthers](Resource* candidate) { - onMetadataFailed(candidate, shouldTryOthers.find(candidate->internalId()).value(), ModPlatform::ResourceProvider::MODRINTH); + if (!modrinth_tmp.empty()) { + auto modrinth_task = makeShared(modrinth_tmp, index_dir, ModPlatform::ResourceProvider::MODRINTH); + connect(modrinth_task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); + connect(modrinth_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Resource* candidate) { + onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::MODRINTH); }); - connect(modrinthTask.get(), &EnsureMetadataTask::failed, - [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); + connect(modrinth_task.get(), &EnsureMetadataTask::failed, + [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); - if (modrinthTask->getHashingTask()) { - seq.addTask(modrinthTask->getHashingTask()); - } + if (modrinth_task->getHashingTask()) + seq.addTask(modrinth_task->getHashingTask()); - seq.addTask(modrinthTask); + seq.addTask(modrinth_task); } // prepare task for the flame mods - if (!flameTmp.empty()) { - auto flameTask = makeShared(flameTmp, indexDir2, ModPlatform::ResourceProvider::FLAME); - connect(flameTask.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); - connect(flameTask.get(), &EnsureMetadataTask::metadataFailed, [this, &shouldTryOthers](Resource* candidate) { - onMetadataFailed(candidate, shouldTryOthers.find(candidate->internalId()).value(), ModPlatform::ResourceProvider::FLAME); + if (!flame_tmp.empty()) { + auto flame_task = makeShared(flame_tmp, index_dir, ModPlatform::ResourceProvider::FLAME); + connect(flame_task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); + connect(flame_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Resource* candidate) { + onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::FLAME); }); - connect(flameTask.get(), &EnsureMetadataTask::failed, - [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); + connect(flame_task.get(), &EnsureMetadataTask::failed, + [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); - if (flameTask->getHashingTask()) { - seq.addTask(flameTask->getHashingTask()); - } + if (flame_task->getHashingTask()) + seq.addTask(flame_task->getHashingTask()); - seq.addTask(flameTask); + seq.addTask(flame_task); } seq.addTask(m_secondTryMetadata); // execute all the tasks - ProgressDialog checkingDialog(m_parent); - checkingDialog.setSkipButton(true, tr("Abort")); - checkingDialog.setWindowTitle(tr("Generating metadata...")); - auto retMetadata = checkingDialog.execWithTask(&seq); + ProgressDialog checking_dialog(m_parent); + checking_dialog.setSkipButton(true, tr("Abort")); + checking_dialog.setWindowTitle(tr("Generating metadata...")); + auto ret_metadata = checking_dialog.execWithTask(&seq); - return (retMetadata != QDialog::DialogCode::Rejected); + return (ret_metadata != QDialog::DialogCode::Rejected); } void ResourceUpdateDialog::onMetadataEnsured(Resource* resource) { // When the mod is a folder, for instance - if (!resource->metadata()) { + if (!resource->metadata()) return; - } switch (resource->metadata()->provider) { case ModPlatform::ResourceProvider::MODRINTH: @@ -424,12 +403,12 @@ ModPlatform::ResourceProvider next(ModPlatform::ResourceProvider p) return ModPlatform::ResourceProvider::FLAME; } -void ResourceUpdateDialog::onMetadataFailed(Resource* resource, bool tryOthers, ModPlatform::ResourceProvider firstChoice) +void ResourceUpdateDialog::onMetadataFailed(Resource* resource, bool try_others, ModPlatform::ResourceProvider first_choice) { - if (tryOthers) { - auto indexDir2 = indexDir(); + if (try_others) { + auto index_dir = indexDir(); - auto task = makeShared(resource, indexDir2, next(firstChoice)); + auto task = makeShared(resource, index_dir, next(first_choice)); connect(task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); connect(task.get(), &EnsureMetadataTask::metadataFailed, [this](Resource* candidate) { onMetadataFailed(candidate, false); }); connect(task.get(), &EnsureMetadataTask::failed, @@ -449,57 +428,57 @@ void ResourceUpdateDialog::onMetadataFailed(Resource* resource, bool tryOthers, } } -void ResourceUpdateDialog::appendResource(const CheckUpdateTask::Update& info, QStringList requiredBy) +void ResourceUpdateDialog::appendResource(CheckUpdateTask::Update const& info, QStringList requiredBy) { - auto* itemTop = new QTreeWidgetItem(ui->modTreeWidget); - itemTop->setCheckState(0, info.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); + auto item_top = new QTreeWidgetItem(ui->modTreeWidget); + item_top->setCheckState(0, info.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); if (!info.enabled) { - itemTop->setToolTip(0, tr("Mod was disabled as it may be already installed.")); + item_top->setToolTip(0, tr("Mod was disabled as it may be already installed.")); } - itemTop->setText(0, info.name); - itemTop->setExpanded(true); + item_top->setText(0, info.name); + item_top->setExpanded(true); - auto* providerItem = new QTreeWidgetItem(itemTop); - QString providerName = ModPlatform::ProviderCapabilities::readableName(info.provider); - providerItem->setText(0, tr("Provider: %1").arg(providerName)); - providerItem->setData(0, Qt::UserRole, providerName); + auto provider_item = new QTreeWidgetItem(item_top); + QString provider_name = ModPlatform::ProviderCapabilities::readableName(info.provider); + provider_item->setText(0, tr("Provider: %1").arg(provider_name)); + provider_item->setData(0, Qt::UserRole, provider_name); - auto* oldVersionItem = new QTreeWidgetItem(itemTop); - oldVersionItem->setText(0, tr("Old version: %1").arg(info.oldVersion)); - oldVersionItem->setData(0, Qt::UserRole, info.oldVersion); + auto old_version_item = new QTreeWidgetItem(item_top); + old_version_item->setText(0, tr("Old version: %1").arg(info.old_version)); + old_version_item->setData(0, Qt::UserRole, info.old_version); - auto* newVersionItem = new QTreeWidgetItem(itemTop); - newVersionItem->setText(0, tr("New version: %1").arg(info.newVersion)); - newVersionItem->setData(0, Qt::UserRole, info.newVersion); + auto new_version_item = new QTreeWidgetItem(item_top); + new_version_item->setText(0, tr("New version: %1").arg(info.new_version)); + new_version_item->setData(0, Qt::UserRole, info.new_version); - if (info.newVersionType.has_value()) { - auto* newVersionTypeItem = new QTreeWidgetItem(itemTop); - newVersionTypeItem->setText(0, tr("New Version Type: %1").arg(info.newVersionType.value().toString())); - newVersionTypeItem->setData(0, Qt::UserRole, info.newVersionType.value().toString()); + if (info.new_version_type.has_value()) { + auto new_version_type_item = new QTreeWidgetItem(item_top); + new_version_type_item->setText(0, tr("New Version Type: %1").arg(info.new_version_type.value().toString())); + new_version_type_item->setData(0, Qt::UserRole, info.new_version_type.value().toString()); } if (!requiredBy.isEmpty()) { - auto* requiredByItem = new QTreeWidgetItem(itemTop); + auto requiredByItem = new QTreeWidgetItem(item_top); if (requiredBy.length() == 1) { requiredByItem->setText(0, tr("Required by: %1").arg(requiredBy.back())); requiredByItem->setData(0, Qt::UserRole, requiredBy.back()); } else { requiredByItem->setText(0, tr("Required by:")); - for (const auto& req : requiredBy) { - auto* reqItem = new QTreeWidgetItem(requiredByItem); + for (auto req : requiredBy) { + auto reqItem = new QTreeWidgetItem(requiredByItem); reqItem->setText(0, req); } } ui->toggleDepsButton->show(); - m_deps << itemTop; + m_deps << item_top; } - auto* changelogItem = new QTreeWidgetItem(itemTop); - changelogItem->setText(0, tr("Changelog of the latest version")); + auto changelog_item = new QTreeWidgetItem(item_top); + changelog_item->setText(0, tr("Changelog of the latest version")); - auto* changelog = new QTreeWidgetItem(changelogItem); - auto* changelogArea = new QTextBrowser(); + auto changelog = new QTreeWidgetItem(changelog_item); + auto changelog_area = new QTextBrowser(); QString text = info.changelog; changelog->setData(0, Qt::UserRole, text); @@ -507,14 +486,14 @@ void ResourceUpdateDialog::appendResource(const CheckUpdateTask::Update& info, Q text = markdownToHTML(info.changelog.toUtf8()); } - changelogArea->setHtml(StringUtils::htmlListPatch(text)); - changelogArea->setOpenExternalLinks(true); - changelogArea->setLineWrapMode(QTextBrowser::LineWrapMode::WidgetWidth); - changelogArea->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); + changelog_area->setHtml(StringUtils::htmlListPatch(text)); + changelog_area->setOpenExternalLinks(true); + changelog_area->setLineWrapMode(QTextBrowser::LineWrapMode::WidgetWidth); + changelog_area->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); - ui->modTreeWidget->setItemWidget(changelog, 0, changelogArea); + ui->modTreeWidget->setItemWidget(changelog, 0, changelog_area); - ui->modTreeWidget->addTopLevelItem(itemTop); + ui->modTreeWidget->addTopLevelItem(item_top); } auto ResourceUpdateDialog::getTasks() -> const QList diff --git a/launcher/ui/dialogs/ResourceUpdateDialog.h b/launcher/ui/dialogs/ResourceUpdateDialog.h index 9de1ee246..be3c19dcc 100644 --- a/launcher/ui/dialogs/ResourceUpdateDialog.h +++ b/launcher/ui/dialogs/ResourceUpdateDialog.h @@ -18,7 +18,7 @@ class ResourceUpdateDialog final : public ReviewMessageBox { public: explicit ResourceUpdateDialog(QWidget* parent, BaseInstance* instance, - ResourceFolderModel* resourceModel, + std::shared_ptr resourceModel, QList& searchFor, bool includeDeps, QList loadersList = {}); @@ -39,7 +39,7 @@ class ResourceUpdateDialog final : public ReviewMessageBox { private slots: void onMetadataEnsured(Resource* resource); void onMetadataFailed(Resource* resource, - bool tryOthers = false, + bool try_others = false, ModPlatform::ResourceProvider firstChoice = ModPlatform::ResourceProvider::MODRINTH); private: @@ -48,7 +48,7 @@ class ResourceUpdateDialog final : public ReviewMessageBox { shared_qobject_ptr m_modrinthCheckTask; shared_qobject_ptr m_flameCheckTask; - ResourceFolderModel* m_resourceModel; + const std::shared_ptr m_resourceModel; QList& m_candidates; QList m_modrinthToUpdate; diff --git a/launcher/ui/dialogs/ScrollMessageBox.cpp b/launcher/ui/dialogs/ScrollMessageBox.cpp index 361b610be..1cfb848f4 100644 --- a/launcher/ui/dialogs/ScrollMessageBox.cpp +++ b/launcher/ui/dialogs/ScrollMessageBox.cpp @@ -2,7 +2,7 @@ #include #include "ui_ScrollMessageBox.h" -ScrollMessageBox::ScrollMessageBox(QWidget* parent, const QString& title, const QString& text, const QString& body, const QString& option) +ScrollMessageBox::ScrollMessageBox(QWidget* parent, const QString& title, const QString& text, const QString& body) : QDialog(parent), ui(new Ui::ScrollMessageBox) { ui->setupUi(this); @@ -10,11 +10,6 @@ ScrollMessageBox::ScrollMessageBox(QWidget* parent, const QString& title, const ui->label->setText(text); ui->textBrowser->setText(body); - if (!option.isEmpty()) { - ui->optionCheckBox->setVisible(true); - ui->optionCheckBox->setText(option); - } - ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK")); } @@ -23,8 +18,3 @@ ScrollMessageBox::~ScrollMessageBox() { delete ui; } - -bool ScrollMessageBox::isOptionChecked() const -{ - return ui->optionCheckBox->isChecked(); -} diff --git a/launcher/ui/dialogs/ScrollMessageBox.h b/launcher/ui/dialogs/ScrollMessageBox.h index f91c90273..8fd6769c4 100644 --- a/launcher/ui/dialogs/ScrollMessageBox.h +++ b/launcher/ui/dialogs/ScrollMessageBox.h @@ -12,12 +12,10 @@ class ScrollMessageBox : public QDialog { Q_OBJECT public: - ScrollMessageBox(QWidget* parent, const QString& title, const QString& text, const QString& body, const QString& option = {}); + ScrollMessageBox(QWidget* parent, const QString& title, const QString& text, const QString& body); ~ScrollMessageBox() override; - bool isOptionChecked() const; - private: Ui::ScrollMessageBox* ui; }; diff --git a/launcher/ui/dialogs/ScrollMessageBox.ui b/launcher/ui/dialogs/ScrollMessageBox.ui index 2ebe86074..e684185f2 100644 --- a/launcher/ui/dialogs/ScrollMessageBox.ui +++ b/launcher/ui/dialogs/ScrollMessageBox.ui @@ -25,25 +25,14 @@ - - - - - false - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + @@ -92,4 +81,4 @@ - \ No newline at end of file + diff --git a/launcher/ui/dialogs/VersionSelectDialog.cpp b/launcher/ui/dialogs/VersionSelectDialog.cpp index db9b08096..30377288b 100644 --- a/launcher/ui/dialogs/VersionSelectDialog.cpp +++ b/launcher/ui/dialogs/VersionSelectDialog.cpp @@ -69,7 +69,7 @@ VersionSelectDialog::VersionSelectDialog(BaseVersionList* vlist, QString title, m_buttonBox->setOrientation(Qt::Horizontal); m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); - m_buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK")); + m_buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Ok")); m_buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); m_horizontalLayout->addWidget(m_buttonBox); @@ -144,7 +144,7 @@ BaseVersion::Ptr VersionSelectDialog::selectedVersion() const void VersionSelectDialog::on_refreshButton_clicked() { - m_versionWidget->loadList(true); + m_versionWidget->loadList(); } void VersionSelectDialog::setExactFilter(BaseVersionList::ModelRoles role, QString filter) diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index 4dfeeaa34..3416c0b2d 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -28,13 +28,11 @@ #include #include #include -#include #include #include #include #include "Application.h" -#include "settings/SettingsObject.h" #include "DesktopServices.h" #include "Json.h" #include "QObjectPtr.h" @@ -472,11 +470,14 @@ void SkinManageDialog::on_userBtn_clicked() NetJob::Ptr job{ new NetJob(tr("Download user skin"), APPLICATION->network(), 1) }; job->setAskRetry(false); + auto uuidOut = std::make_shared(); + auto profileOut = std::make_shared(); + auto uuidLoop = makeShared(); auto profileLoop = makeShared(); - auto [getUUID, uuidOut] = Net::Download::makeByteArray("https://api.minecraftservices.com/minecraft/profile/lookup/name/" + user); - auto [getProfile, profileOut] = Net::Download::makeByteArray(QUrl()); + 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); QString failReason; diff --git a/launcher/ui/dialogs/skins/draw/BoxGeometry.cpp b/launcher/ui/dialogs/skins/draw/BoxGeometry.cpp index f1f771303..4404442a4 100644 --- a/launcher/ui/dialogs/skins/draw/BoxGeometry.cpp +++ b/launcher/ui/dialogs/skins/draw/BoxGeometry.cpp @@ -247,11 +247,11 @@ void BoxGeometry::initGeometry(float u, float v, float width, float height, floa // Transfer vertex data to VBO 0 m_vertexBuf.bind(); - m_vertexBuf.allocate(verticesData.constData(), static_cast(verticesData.size() * sizeof(VertexData))); + m_vertexBuf.allocate(verticesData.constData(), verticesData.size() * sizeof(VertexData)); // Transfer index data to VBO 1 m_indexBuf.bind(); - m_indexBuf.allocate(indices.constData(), static_cast(indices.size() * sizeof(GLushort))); + m_indexBuf.allocate(indices.constData(), indices.size() * sizeof(GLushort)); m_indecesCount = indices.size(); } @@ -266,11 +266,11 @@ BoxGeometry* BoxGeometry::Plane() // Transfer vertex data to VBO 0 b->m_vertexBuf.bind(); - b->m_vertexBuf.allocate(planeVertices.constData(), static_cast(planeVertices.size() * sizeof(VertexData))); + b->m_vertexBuf.allocate(planeVertices.constData(), planeVertices.size() * sizeof(VertexData)); // Transfer index data to VBO 1 b->m_indexBuf.bind(); - b->m_indexBuf.allocate(planeIndices.constData(), static_cast(planeIndices.size() * sizeof(GLushort))); + b->m_indexBuf.allocate(planeIndices.constData(), planeIndices.size() * sizeof(GLushort)); b->m_indecesCount = planeIndices.size(); return b; diff --git a/launcher/ui/dialogs/skins/draw/Scene.cpp b/launcher/ui/dialogs/skins/draw/Scene.cpp index 1d06c694f..7e84a3a49 100644 --- a/launcher/ui/dialogs/skins/draw/Scene.cpp +++ b/launcher/ui/dialogs/skins/draw/Scene.cpp @@ -34,9 +34,9 @@ Scene::Scene(const QImage& skin, bool slim, const QImage& cape) : QOpenGLFunctio // body new opengl::BoxGeometry(QVector3D(8, 12, 4), QVector3D(0, -6, 0), QPoint(16, 16), QVector3D(8, 12, 4)), // right leg - new opengl::BoxGeometry(QVector3D(4, 12, 4), QVector3D(-1.9f, -18, -0.1f), QPoint(0, 16), QVector3D(4, 12, 4)), + new opengl::BoxGeometry(QVector3D(4, 12, 4), QVector3D(-1.9, -18, -0.1), QPoint(0, 16), QVector3D(4, 12, 4)), // left leg - new opengl::BoxGeometry(QVector3D(4, 12, 4), QVector3D(1.9f, -18, -0.1f), QPoint(16, 48), QVector3D(4, 12, 4)), + new opengl::BoxGeometry(QVector3D(4, 12, 4), QVector3D(1.9, -18, -0.1), QPoint(16, 48), QVector3D(4, 12, 4)), }; m_staticComponentsOverlay = { @@ -45,9 +45,9 @@ Scene::Scene(const QImage& skin, bool slim, const QImage& cape) : QOpenGLFunctio // body new opengl::BoxGeometry(QVector3D(8.5, 12.5, 4.5), QVector3D(0, -6, 0), QPoint(16, 32), QVector3D(8, 12, 4)), // right leg - new opengl::BoxGeometry(QVector3D(4.5f, 12.5f, 4.5f), QVector3D(-1.9f, -18, -0.1f), QPoint(0, 32), QVector3D(4, 12, 4)), + new opengl::BoxGeometry(QVector3D(4.5, 12.5, 4.5), QVector3D(-1.9, -18, -0.1), QPoint(0, 32), QVector3D(4, 12, 4)), // left leg - new opengl::BoxGeometry(QVector3D(4.5f, 12.5f, 4.5f), QVector3D(1.9f, -18, -0.1f), QPoint(0, 48), QVector3D(4, 12, 4)), + new opengl::BoxGeometry(QVector3D(4.5, 12.5, 4.5), QVector3D(1.9, -18, -0.1), QPoint(0, 48), QVector3D(4, 12, 4)), }; m_normalArms = { @@ -79,7 +79,7 @@ Scene::Scene(const QImage& skin, bool slim, const QImage& cape) : QOpenGLFunctio }; m_cape = new opengl::BoxGeometry(QVector3D(10, 16, 1), QVector3D(0, -8, 2.5), QPoint(0, 0), QVector3D(10, 16, 1), QSize(64, 32)); - m_cape->rotate(10.8f, QVector3D(1, 0, 0)); + m_cape->rotate(10.8, QVector3D(1, 0, 0)); m_cape->rotate(180, QVector3D(0, 1, 0)); auto leftWing = @@ -138,9 +138,11 @@ void Scene::draw(QOpenGLShaderProgram* program) if (!m_elytraVisible) { m_cape->draw(program); } else { + glDisable(GL_CULL_FACE); for (auto e : m_elytra) { e->draw(program); } + glEnable(GL_CULL_FACE); } m_capeTexture->release(); } diff --git a/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.cpp b/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.cpp index ca6d6ad27..e4d1eae8d 100644 --- a/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.cpp +++ b/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.cpp @@ -20,13 +20,11 @@ #include #include -#include #include #include #include #include -#include "BuildConfig.h" #include "minecraft/skins/SkinModel.h" #include "rainbow.h" #include "ui/dialogs/skins/draw/BoxGeometry.h" @@ -219,6 +217,9 @@ void SkinOpenGLWindow::paintGL() glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); + // Enable back face culling + glEnable(GL_CULL_FACE); + glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -272,10 +273,10 @@ QColor calculateContrastingColor(const QColor& color) { auto luma = Rainbow::luma(color); if (luma < 0.5) { - constexpr float contrast = 0.05f; + constexpr float contrast = 0.05; return Rainbow::lighten(color, contrast); } else { - constexpr float contrast = 0.2f; + constexpr float contrast = 0.2; return Rainbow::darken(color, contrast); } } @@ -332,12 +333,6 @@ void SkinOpenGLWindow::setElytraVisible(bool visible) bool SkinOpenGLWindow::hasOpenGL() { - if (!QProcessEnvironment::systemEnvironment() - .value(QStringLiteral("%1_DISABLE_GLVULKAN").arg(BuildConfig.LAUNCHER_ENVNAME)) - .isEmpty()) { - return false; - } - QOpenGLContext ctx; return ctx.create(); } diff --git a/launcher/ui/instanceview/InstanceProxyModel.cpp b/launcher/ui/instanceview/InstanceProxyModel.cpp index f28149a56..ab6bef696 100644 --- a/launcher/ui/instanceview/InstanceProxyModel.cpp +++ b/launcher/ui/instanceview/InstanceProxyModel.cpp @@ -62,11 +62,6 @@ bool InstanceProxyModel::subSortLessThan(const QModelIndex& left, const QModelIn QString sortMode = APPLICATION->settings()->get("InstSortMode").toString(); if (sortMode == "LastLaunch") { return pdataLeft->lastLaunch() > pdataRight->lastLaunch(); - } else if (sortMode == "Playtime") { - if (pdataLeft->totalTimePlayed() == pdataRight->totalTimePlayed()) { - return m_naturalSort.compare(pdataLeft->name(), pdataRight->name()) < 0; - } - return pdataLeft->totalTimePlayed() > pdataRight->totalTimePlayed(); } else { return m_naturalSort.compare(pdataLeft->name(), pdataRight->name()) < 0; } diff --git a/launcher/ui/instanceview/InstanceView.cpp b/launcher/ui/instanceview/InstanceView.cpp index 9a24b7990..8eb82bbae 100644 --- a/launcher/ui/instanceview/InstanceView.cpp +++ b/launcher/ui/instanceview/InstanceView.cpp @@ -511,7 +511,8 @@ void InstanceView::paintEvent([[maybe_unused]] QPaintEvent* event) int wpWidth = viewport()->width(); option.rect.setWidth(wpWidth); - for (auto* category : m_groups) { + for (int i = 0; i < m_groups.size(); ++i) { + VisualGroup* category = m_groups.at(i); int y = category->verticalPosition(); y -= verticalOffset(); QRect backup = option.rect; @@ -521,6 +522,7 @@ void InstanceView::paintEvent([[maybe_unused]] QPaintEvent* event) option.rect.setLeft(m_leftMargin); option.rect.setRight(wpWidth - m_rightMargin); category->drawHeader(&painter, option); + y += category->totalHeight() + m_categoryMargin; option.rect = backup; } @@ -642,7 +644,7 @@ void InstanceView::dropEvent(QDropEvent* event) return; } auto instanceId = QString::fromUtf8(mimedata->data("application/x-instanceid")); - auto instanceList = APPLICATION->instances(); + auto instanceList = APPLICATION->instances().get(); instanceList->setInstanceGroup(instanceId, group->text); event->setDropAction(Qt::MoveAction); event->accept(); diff --git a/launcher/ui/instanceview/VisualGroup.cpp b/launcher/ui/instanceview/VisualGroup.cpp index b68c09171..4f7a61eb5 100644 --- a/launcher/ui/instanceview/VisualGroup.cpp +++ b/launcher/ui/instanceview/VisualGroup.cpp @@ -151,7 +151,7 @@ void VisualGroup::drawHeader(QPainter* painter, const QStyleOptionViewItem& opti QPen pen; pen.setWidth(2); QColor penColor = option.palette.text().color(); - penColor.setAlphaF(0.6f); + penColor.setAlphaF(0.6); pen.setColor(penColor); painter->setPen(pen); painter->setRenderHint(QPainter::Antialiasing); @@ -194,7 +194,7 @@ void VisualGroup::drawHeader(QPainter* painter, const QStyleOptionViewItem& opti // BEGIN: horizontal line { - penColor.setAlphaF(0.05f); + penColor.setAlphaF(0.05); pen.setColor(penColor); painter->setPen(pen); // startPoint is left + arrow + text + space diff --git a/launcher/ui/java/InstallJavaDialog.cpp b/launcher/ui/java/InstallJavaDialog.cpp index fd8e43398..938befe13 100644 --- a/launcher/ui/java/InstallJavaDialog.cpp +++ b/launcher/ui/java/InstallJavaDialog.cpp @@ -54,6 +54,7 @@ class InstallJavaPage : public QWidget, public BasePage { horizontalLayout = new QHBoxLayout(this); horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); horizontalLayout->setContentsMargins(0, 0, 0, 0); + majorVersionSelect = new VersionSelectWidget(this); majorVersionSelect->selectCurrent(); majorVersionSelect->setEmptyString(tr("No Java versions are currently available in the meta.")); @@ -121,8 +122,8 @@ class InstallJavaPage : public QWidget, public BasePage { void selectSearch() { javaVersionSelect->selectSearch(); } void loadList() { - majorVersionSelect->loadList(true); - javaVersionSelect->loadList(true); + majorVersionSelect->loadList(); + javaVersionSelect->loadList(); } public slots: @@ -186,18 +187,13 @@ InstallDialog::InstallDialog(const QString& uid, BaseInstance* instance, QWidget : QDialog(parent), container(new PageContainer(this, QString(), this)), buttons(new QDialogButtonBox(this)) { auto layout = new QVBoxLayout(this); - // small margins look ugly on macOS on modal windows - #ifndef Q_OS_MACOS layout->setContentsMargins(0, 0, 0, 0); - #endif + container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); layout->addWidget(container); auto buttonLayout = new QHBoxLayout(this); - // small margins look ugly on macOS on modal windows - #ifndef Q_OS_MACOS buttonLayout->setContentsMargins(0, 0, 6, 6); - #endif auto refreshLayout = new QHBoxLayout(this); @@ -288,11 +284,6 @@ QList InstallDialog::getPages() new InstallJavaPage("net.adoptium.java", "adoptium", tr("Adoptium")), // Azul new InstallJavaPage("com.azul.java", "azul", tr("Azul Zulu")), - // IBM - /* Must watch out in case the AdoptOpenJDK infrastructure is deprecated. - In case of happening, IBM does not seem to provide as of today (03/2026) an API like Adoptium does and rather uses GitHub directly in its website: `developer.ibm.com`. - GitHub is known for rate limiting requests that do not use an API key from an account. */ - new InstallJavaPage("com.ibm.java", "openj9_hex_custom", tr("IBM Semeru Open")), }; } diff --git a/launcher/ui/java/VersionList.cpp b/launcher/ui/java/VersionList.cpp index 7a4e84f33..f958f064f 100644 --- a/launcher/ui/java/VersionList.cpp +++ b/launcher/ui/java/VersionList.cpp @@ -33,9 +33,9 @@ VersionList::VersionList(Meta::Version::Ptr version, QObject* parent) : BaseVers sortVersions(); } -Task::Ptr VersionList::getLoadTask(bool forceReload) +Task::Ptr VersionList::getLoadTask() { - auto task = m_version->loadTask(Net::Mode::Online, forceReload); + auto task = m_version->loadTask(Net::Mode::Online); connect(task.get(), &Task::finished, this, &VersionList::sortVersions); return task; } diff --git a/launcher/ui/java/VersionList.h b/launcher/ui/java/VersionList.h index cf8a7448d..d334ed564 100644 --- a/launcher/ui/java/VersionList.h +++ b/launcher/ui/java/VersionList.h @@ -30,7 +30,7 @@ class VersionList : public BaseVersionList { public: explicit VersionList(Meta::Version::Ptr m_version, QObject* parent = 0); - Task::Ptr getLoadTask(bool forceReload = false) override; + Task::Ptr getLoadTask() override; bool isLoaded() override; const BaseVersion::Ptr at(int i) const override; int count() const override; diff --git a/launcher/ui/pagedialog/PageDialog.cpp b/launcher/ui/pagedialog/PageDialog.cpp index 0cd80521f..7e52b5f65 100644 --- a/launcher/ui/pagedialog/PageDialog.cpp +++ b/launcher/ui/pagedialog/PageDialog.cpp @@ -21,7 +21,6 @@ #include #include "Application.h" -#include "settings/SettingsObject.h" #include "ui/widgets/PageContainer.h" diff --git a/launcher/ui/pages/global/APIPage.cpp b/launcher/ui/pages/global/APIPage.cpp index 4ed05f98e..ebe93e5b4 100644 --- a/launcher/ui/pages/global/APIPage.cpp +++ b/launcher/ui/pages/global/APIPage.cpp @@ -77,12 +77,9 @@ APIPage::APIPage(QWidget* parent) : QWidget(parent), ui(new Ui::APIPage) 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->legacyFMLLibsURL->setValidator(new QRegularExpressionValidator(s_validUrlRegExp, ui->legacyFMLLibsURL)); ui->msaClientID->setValidator(new QRegularExpressionValidator(s_validMSAClientID, ui->msaClientID)); ui->metaURL->setPlaceholderText(BuildConfig.META_URL); - ui->resourceURL->setPlaceholderText(BuildConfig.DEFAULT_RESOURCE_BASE); - ui->legacyFMLLibsURL->setPlaceholderText(BuildConfig.LEGACY_FMLLIBS_BASE_URL); ui->userAgentLineEdit->setPlaceholderText(BuildConfig.USER_AGENT); loadSettings(); @@ -135,19 +132,12 @@ void APIPage::loadSettings() ui->pasteTypeComboBox->setCurrentIndex(pasteTypeIndex); - if (bool fallbackMRBlockedMods = s->get("FallbackMRBlockedMods").toBool()) { - ui->FallbackMRBlockedMods->setChecked(fallbackMRBlockedMods); - } - QString msaClientID = s->get("MSAClientIDOverride").toString(); ui->msaClientID->setText(msaClientID); QString metaURL = s->get("MetaURLOverride").toString(); ui->metaURL->setText(metaURL); - ui->metaRefreshOnLaunchCB->setCheckState(s->get("MetaRefreshOnLaunch").toBool() ? Qt::Checked : Qt::Unchecked); - QString resourceURL = s->get("ResourceURLOverride").toString(); + QString resourceURL = s->get("ResourceURL").toString(); ui->resourceURL->setText(resourceURL); - QString fmlLibsURL = s->get("LegacyFMLLibsURLOverride").toString(); - ui->legacyFMLLibsURL->setText(fmlLibsURL); QString flameKey = s->get("FlameKeyOverride").toString(); ui->flameKey->setText(flameKey); QString modrinthToken = s->get("ModrinthToken").toString(); @@ -168,36 +158,34 @@ void APIPage::applySettings() s->set("MSAClientIDOverride", msaClientID); QUrl metaURL(ui->metaURL->text()); QUrl resourceURL(ui->resourceURL->text()); - QUrl fmlLibsURL(ui->legacyFMLLibsURL->text()); + // Add required trailing slash + if (!metaURL.isEmpty() && !metaURL.path().endsWith('/')) { + QString path = metaURL.path(); + path.append('/'); + metaURL.setPath(path); + } - auto addRequiredTrailingSlash = [](QUrl& url) { - if (!url.isEmpty() && !url.path().endsWith('/')) { - QString path = url.path(); - path.append('/'); - url.setPath(path); - } - }; - addRequiredTrailingSlash(metaURL); - addRequiredTrailingSlash(resourceURL); - addRequiredTrailingSlash(fmlLibsURL); + if (!resourceURL.isEmpty() && !resourceURL.path().endsWith('/')) { + QString path = resourceURL.path(); + path.append('/'); + resourceURL.setPath(path); + } auto isLocalhost = [](const QUrl& url) { return url.host() == "localhost" || url.host() == "127.0.0.1" || url.host() == "::1"; }; auto isUnsafe = [isLocalhost](const QUrl& url) { return !url.isEmpty() && url.scheme() == "http" && !isLocalhost(url); }; - auto upgradeToHTTPS = [isUnsafe](QUrl& url) { - if (isUnsafe(url)) { - url.setScheme("https"); - } - }; - upgradeToHTTPS(metaURL); - upgradeToHTTPS(resourceURL); - upgradeToHTTPS(fmlLibsURL); + // Don't allow HTTP, since meta is basically RCE with all the jar files. + if (isUnsafe(metaURL)) { + metaURL.setScheme("https"); + } + + // Also don't allow HTTP + if (isUnsafe(resourceURL)) { + resourceURL.setScheme("https"); + } - s->set("FallbackMRBlockedMods", ui->FallbackMRBlockedMods->checkState()); s->set("MetaURLOverride", metaURL.toString()); - s->set("MetaRefreshOnLaunch", ui->metaRefreshOnLaunchCB->checkState() == Qt::Checked); - s->set("ResourceURLOverride", resourceURL.toString()); - s->set("LegacyFMLLibsURLOverride", fmlLibsURL.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.ui b/launcher/ui/pages/global/APIPage.ui index 25d78a04d..834eebb96 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -32,9 +32,9 @@ 0 - 0 - 825 - 1236 + -216 + 816 + 832 @@ -126,13 +126,6 @@ - - - - Refresh on launch - - - @@ -168,34 +161,6 @@ - - - - Legacy FML Libraries Server - - - - - - You can set this to another server if you have problems with downloading legacy FML libraries (Minecraft 1.5.2 and earlier). - - - Qt::RichText - - - true - - - true - - - - - - - - - @@ -386,13 +351,6 @@ - - - - Enable fallback to Modrinth for blocked mods - - - diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp index 06c40f117..7e7490d34 100644 --- a/launcher/ui/pages/global/AccountListPage.cpp +++ b/launcher/ui/pages/global/AccountListPage.cpp @@ -61,8 +61,9 @@ AccountListPage::AccountListPage(QWidget* parent) : QMainWindow(parent), ui(new m_accounts = APPLICATION->accounts(); - ui->listView->setModel(m_accounts); + ui->listView->setModel(m_accounts.get()); ui->listView->header()->setSectionResizeMode(AccountList::VListColumns::ProfileNameColumn, QHeaderView::Stretch); + ui->listView->header()->setSectionResizeMode(AccountList::VListColumns::NameColumn, QHeaderView::Stretch); ui->listView->header()->setSectionResizeMode(AccountList::VListColumns::TypeColumn, QHeaderView::ResizeToContents); ui->listView->header()->setSectionResizeMode(AccountList::VListColumns::StatusColumn, QHeaderView::ResizeToContents); ui->listView->setSelectionMode(QAbstractItemView::SingleSelection); @@ -77,9 +78,9 @@ AccountListPage::AccountListPage(QWidget* parent) : QMainWindow(parent), ui(new connect(ui->listView, &VersionListView::activated, this, [this](const QModelIndex& index) { m_accounts->setDefaultAccount(m_accounts->at(index.row())); }); - connect(m_accounts, &AccountList::listChanged, this, &AccountListPage::listChanged); - connect(m_accounts, &AccountList::listActivityChanged, this, &AccountListPage::listChanged); - connect(m_accounts, &AccountList::defaultAccountChanged, this, &AccountListPage::listChanged); + connect(m_accounts.get(), &AccountList::listChanged, this, &AccountListPage::listChanged); + connect(m_accounts.get(), &AccountList::listActivityChanged, this, &AccountListPage::listChanged); + connect(m_accounts.get(), &AccountList::defaultAccountChanged, this, &AccountListPage::listChanged); updateButtonStates(); @@ -209,17 +210,11 @@ void AccountListPage::updateButtonStates() bool hasSelection = !selection.empty(); bool accountIsReady = false; bool accountIsOnline = false; - bool accountCanMoveUp = false; - bool accountCanMoveDown = false; if (hasSelection) { QModelIndex selected = selection.first(); MinecraftAccountPtr account = selected.data(AccountList::PointerRole).value(); accountIsReady = !account->isActive(); accountIsOnline = account->accountType() != AccountType::Offline; - - accountCanMoveUp = selected.row() > 0; - int indexOfLast = m_accounts->count() - 1; - accountCanMoveDown = selected.row() < indexOfLast; } ui->actionRemove->setEnabled(accountIsReady); ui->actionSetDefault->setEnabled(accountIsReady); @@ -233,8 +228,6 @@ void AccountListPage::updateButtonStates() ui->actionNoDefault->setEnabled(true); ui->actionNoDefault->setChecked(false); } - ui->actionMoveUp->setEnabled(accountCanMoveUp); - ui->actionMoveDown->setEnabled(accountCanMoveDown); ui->listView->resizeColumnToContents(3); } @@ -248,21 +241,3 @@ void AccountListPage::on_actionManageSkins_triggered() dialog.exec(); } } - -void AccountListPage::on_actionMoveUp_triggered() -{ - QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); - if (selection.size() > 0) { - QModelIndex selected = selection.first(); - m_accounts->moveAccount(selected, -1); - } -} - -void AccountListPage::on_actionMoveDown_triggered() -{ - QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); - if (selection.size() > 0) { - QModelIndex selected = selection.first(); - m_accounts->moveAccount(selected, 1); - } -} diff --git a/launcher/ui/pages/global/AccountListPage.h b/launcher/ui/pages/global/AccountListPage.h index bee56cb58..2841b9456 100644 --- a/launcher/ui/pages/global/AccountListPage.h +++ b/launcher/ui/pages/global/AccountListPage.h @@ -76,8 +76,6 @@ class AccountListPage : public QMainWindow, public BasePage { void on_actionSetDefault_triggered(); void on_actionNoDefault_triggered(); void on_actionManageSkins_triggered(); - void on_actionMoveUp_triggered(); - void on_actionMoveDown_triggered(); void listChanged(); @@ -90,6 +88,6 @@ class AccountListPage : public QMainWindow, public BasePage { private: void changeEvent(QEvent* event) override; QMenu* createPopupMenu() override; - AccountList* m_accounts; + shared_qobject_ptr m_accounts; Ui::AccountListPage* ui; }; diff --git a/launcher/ui/pages/global/AccountListPage.ui b/launcher/ui/pages/global/AccountListPage.ui index 6fa004ed7..c9b770ab2 100644 --- a/launcher/ui/pages/global/AccountListPage.ui +++ b/launcher/ui/pages/global/AccountListPage.ui @@ -58,8 +58,6 @@ - - @@ -107,16 +105,6 @@ Remo&ve - - - Move &Up - - - - - Move &Down - - diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index d780ad542..6a44c9290 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -55,6 +55,7 @@ #include "java/JavaUtils.h" #include +#include #include "Application.h" #include "settings/SettingsObject.h" diff --git a/launcher/ui/pages/global/LanguagePage.cpp b/launcher/ui/pages/global/LanguagePage.cpp index 94c582775..f4be75782 100644 --- a/launcher/ui/pages/global/LanguagePage.cpp +++ b/launcher/ui/pages/global/LanguagePage.cpp @@ -38,7 +38,6 @@ #include #include "Application.h" -#include "settings/SettingsObject.h" #include "ui/widgets/LanguageSelectionWidget.h" LanguagePage::LanguagePage(QWidget* parent) : QWidget(parent) diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 1e1fed8f8..7a0f11c83 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -62,9 +62,7 @@ enum InstSortMode { // Sort alphabetically by name. Sort_Name, // Sort by which instance was launched most recently. - Sort_LastLaunch, - // Sort by which instance has the most playtime. - Sort_Playtime, + Sort_LastLaunch }; LauncherPage::LauncherPage(QWidget* parent) : QWidget(parent), ui(new Ui::LauncherPage) @@ -73,7 +71,6 @@ LauncherPage::LauncherPage(QWidget* parent) : QWidget(parent), ui(new Ui::Launch ui->sortingModeGroup->setId(ui->sortByNameBtn, Sort_Name); ui->sortingModeGroup->setId(ui->sortLastLaunchedBtn, Sort_LastLaunch); - ui->sortingModeGroup->setId(ui->sortByPlaytimeBtn, Sort_Playtime); loadSettings(); @@ -93,12 +90,12 @@ bool LauncherPage::apply() void LauncherPage::on_instDirBrowseBtn_clicked() { - QString rawDir = QFileDialog::getExistingDirectory(this, tr("Instance Folder"), ui->instDirTextBox->text()); + QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Instance Folder"), ui->instDirTextBox->text()); // do not allow current dir - it's dirty. Do not allow dirs that don't exist - if (!rawDir.isEmpty() && QDir(rawDir).exists()) { - QString cookedDir = FS::NormalizePath(rawDir); - if (FS::checkProblemticPathJava(QDir(cookedDir))) { + if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) { + QString cooked_dir = FS::NormalizePath(raw_dir); + if (FS::checkProblemticPathJava(QDir(cooked_dir))) { QMessageBox warning; warning.setText( tr("You're trying to specify an instance folder which\'s path " @@ -111,9 +108,9 @@ void LauncherPage::on_instDirBrowseBtn_clicked() warning.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); int result = warning.exec(); if (result == QMessageBox::Ok) { - ui->instDirTextBox->setText(cookedDir); + ui->instDirTextBox->setText(cooked_dir); } - } else if (DesktopServices::isFlatpak() && rawDir.startsWith("/run/user")) { + } else if (DesktopServices::isFlatpak() && raw_dir.startsWith("/run/user")) { QMessageBox warning; warning.setText(tr("You're trying to specify an instance folder " "which was granted temporarily via Flatpak.\n" @@ -126,64 +123,64 @@ void LauncherPage::on_instDirBrowseBtn_clicked() warning.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); int result = warning.exec(); if (result == QMessageBox::Ok) { - ui->instDirTextBox->setText(cookedDir); + ui->instDirTextBox->setText(cooked_dir); } } else { - ui->instDirTextBox->setText(cookedDir); + ui->instDirTextBox->setText(cooked_dir); } } } void LauncherPage::on_iconsDirBrowseBtn_clicked() { - QString rawDir = QFileDialog::getExistingDirectory(this, tr("Icons Folder"), ui->iconsDirTextBox->text()); + QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Icons Folder"), ui->iconsDirTextBox->text()); // do not allow current dir - it's dirty. Do not allow dirs that don't exist - if (!rawDir.isEmpty() && QDir(rawDir).exists()) { - QString cookedDir = FS::NormalizePath(rawDir); - ui->iconsDirTextBox->setText(cookedDir); + if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) { + QString cooked_dir = FS::NormalizePath(raw_dir); + ui->iconsDirTextBox->setText(cooked_dir); } } void LauncherPage::on_modsDirBrowseBtn_clicked() { - QString rawDir = QFileDialog::getExistingDirectory(this, tr("Mods Folder"), ui->modsDirTextBox->text()); + QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Mods Folder"), ui->modsDirTextBox->text()); // do not allow current dir - it's dirty. Do not allow dirs that don't exist - if (!rawDir.isEmpty() && QDir(rawDir).exists()) { - QString cookedDir = FS::NormalizePath(rawDir); - ui->modsDirTextBox->setText(cookedDir); + if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) { + QString cooked_dir = FS::NormalizePath(raw_dir); + ui->modsDirTextBox->setText(cooked_dir); } } void LauncherPage::on_downloadsDirBrowseBtn_clicked() { - QString rawDir = QFileDialog::getExistingDirectory(this, tr("Downloads Folder"), ui->downloadsDirTextBox->text()); + QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Downloads Folder"), ui->downloadsDirTextBox->text()); - if (!rawDir.isEmpty() && QDir(rawDir).exists()) { - QString cookedDir = FS::NormalizePath(rawDir); - ui->downloadsDirTextBox->setText(cookedDir); + if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) { + QString cooked_dir = FS::NormalizePath(raw_dir); + ui->downloadsDirTextBox->setText(cooked_dir); } } void LauncherPage::on_javaDirBrowseBtn_clicked() { - QString rawDir = QFileDialog::getExistingDirectory(this, tr("Java Folder"), ui->javaDirTextBox->text()); + QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Java Folder"), ui->javaDirTextBox->text()); - if (!rawDir.isEmpty() && QDir(rawDir).exists()) { - QString cookedDir = FS::NormalizePath(rawDir); - ui->javaDirTextBox->setText(cookedDir); + if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) { + QString cooked_dir = FS::NormalizePath(raw_dir); + ui->javaDirTextBox->setText(cooked_dir); } } void LauncherPage::on_skinsDirBrowseBtn_clicked() { - QString rawDir = QFileDialog::getExistingDirectory(this, tr("Skins Folder"), ui->skinsDirTextBox->text()); + QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Skins Folder"), ui->skinsDirTextBox->text()); // do not allow current dir - it's dirty. Do not allow dirs that don't exist - if (!rawDir.isEmpty() && QDir(rawDir).exists()) { - QString cookedDir = FS::NormalizePath(rawDir); - ui->skinsDirTextBox->setText(cookedDir); + if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) { + QString cooked_dir = FS::NormalizePath(raw_dir); + ui->skinsDirTextBox->setText(cooked_dir); } } @@ -194,7 +191,7 @@ void LauncherPage::on_metadataEnableBtn_clicked() void LauncherPage::applySettings() { - auto* s = APPLICATION->settings(); + auto s = APPLICATION->settings(); // Updates if (APPLICATION->updater()) { @@ -230,9 +227,6 @@ void LauncherPage::applySettings() case Sort_LastLaunch: s->set("InstSortMode", "LastLaunch"); break; - case Sort_Playtime: - s->set("InstSortMode", "Playtime"); - break; case Sort_Name: default: s->set("InstSortMode", "Name"); @@ -250,13 +244,11 @@ void LauncherPage::applySettings() // Mods s->set("ModMetadataDisabled", !ui->metadataEnableBtn->isChecked()); s->set("ModDependenciesDisabled", !ui->dependenciesEnableBtn->isChecked()); - s->set("ShowModIncompat", ui->showModIncompatCheckBox->isChecked()); s->set("SkipModpackUpdatePrompt", !ui->modpackUpdatePromptBtn->isChecked()); - s->set("DownloadGameFilesDuringInstanceCreation", ui->downloadGameFilesBtn->isChecked()); } void LauncherPage::loadSettings() { - auto* s = APPLICATION->settings(); + auto s = APPLICATION->settings(); // Updates if (APPLICATION->updater()) { ui->autoUpdateCheckBox->setChecked(APPLICATION->updater()->getAutomaticallyChecksForUpdates()); @@ -288,8 +280,6 @@ void LauncherPage::loadSettings() QString sortMode = s->get("InstSortMode").toString(); if (sortMode == "LastLaunch") { ui->sortLastLaunchedBtn->setChecked(true); - } else if (sortMode == "Playtime"){ - ui->sortByPlaytimeBtn->setChecked(true); } else { ui->sortByNameBtn->setChecked(true); } @@ -303,9 +293,7 @@ void LauncherPage::loadSettings() ui->metadataEnableBtn->setChecked(!s->get("ModMetadataDisabled").toBool()); ui->metadataWarningLabel->setHidden(ui->metadataEnableBtn->isChecked()); ui->dependenciesEnableBtn->setChecked(!s->get("ModDependenciesDisabled").toBool()); - ui->showModIncompatCheckBox->setChecked(s->get("ShowModIncompat").toBool()); ui->modpackUpdatePromptBtn->setChecked(!s->get("SkipModpackUpdatePrompt").toBool()); - ui->downloadGameFilesBtn->setChecked(s->get("DownloadGameFilesDuringInstanceCreation").toBool()); } void LauncherPage::retranslate() diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index c98cb1032..0debe3f4d 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -41,9 +41,9 @@ 0 - -149 + 0 746 - 1222 + 1194 @@ -83,16 +83,6 @@ - - - - By total time &played - - - sortingModeGroup - - - @@ -261,11 +251,8 @@ - - Folder where Prism Launcher stores automatically downloaded Java versions. Do NOT set this to your system Java installation. - - &Auto Java Download: + &Java: javaDirTextBox @@ -431,16 +418,6 @@ - - - - Currently this just shows mods which are not marked as compatible with the current Minecraft version. - - - Detect and show mod incompatibilities (experimental) - - - @@ -454,25 +431,6 @@ - - - - Instance Creation - - - - - - Downloads required game files while creating the instance. Disable this to skip the initial download and fetch files when the instance is launched instead. - - - Download game files during instance creation - - - - - - @@ -683,12 +641,6 @@ scrollArea - sortByNameBtn - sortLastLaunchedBtn - sortByPlaytimeBtn - askToRenameDirBtn - alwaysRenameDirBtn - neverRenameDirBtn preferMenuBarCheckBox autoUpdateCheckBox updateIntervalSpinBox @@ -708,9 +660,7 @@ downloadsDirMoveCheckBox metadataEnableBtn dependenciesEnableBtn - showModIncompatCheckBox modpackUpdatePromptBtn - downloadGameFilesBtn lineLimitSpinBox checkStopLogging numberOfConcurrentTasksSpinBox @@ -721,7 +671,7 @@ - + diff --git a/launcher/ui/pages/instance/DataPackPage.cpp b/launcher/ui/pages/instance/DataPackPage.cpp index eb59fbb1e..a56cc9b79 100644 --- a/launcher/ui/pages/instance/DataPackPage.cpp +++ b/launcher/ui/pages/instance/DataPackPage.cpp @@ -25,7 +25,7 @@ #include "ui/dialogs/ResourceDownloadDialog.h" #include "ui/dialogs/ResourceUpdateDialog.h" -DataPackPage::DataPackPage(BaseInstance* instance, DataPackFolderModel* model, QWidget* parent) +DataPackPage::DataPackPage(BaseInstance* instance, std::shared_ptr model, QWidget* parent) : ExternalResourcesPage(instance, model, parent), m_model(model) { ui->actionDownloadItem->setText(tr("Download Packs")); @@ -39,9 +39,9 @@ DataPackPage::DataPackPage(BaseInstance* instance, DataPackFolderModel* model, Q connect(ui->actionUpdateItem, &QAction::triggered, this, &DataPackPage::updateDataPacks); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem); - auto* updateMenu = new QMenu(this); + auto updateMenu = new QMenu(this); - auto* update = updateMenu->addAction(ui->actionUpdateItem->text()); + auto update = updateMenu->addAction(ui->actionUpdateItem->text()); connect(update, &QAction::triggered, this, &DataPackPage::updateDataPacks); updateMenu->addAction(ui->actionResetItemMetadata); @@ -64,9 +64,10 @@ void DataPackPage::updateFrame(const QModelIndex& current, [[maybe_unused]] cons void DataPackPage::downloadDataPacks() { - if (m_instance->typeName() != "Minecraft") { + if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - } + + auto profile = static_cast(m_instance)->getPackProfile(); m_downloadDialog = new ResourceDownload::DataPackDownloadDialog(this, m_model, m_instance); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); @@ -77,9 +78,9 @@ void DataPackPage::downloadDataPacks() void DataPackPage::downloadDialogFinished(int result) { - if (result != 0) { - auto* tasks = new ConcurrentTask(tr("Download Data Packs"), APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](const QString& reason) { + if (result) { + auto tasks = new ConcurrentTask(tr("Download Data Packs"), APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -89,9 +90,8 @@ void DataPackPage::downloadDialogFinished(int result) }); connect(tasks, &Task::succeeded, [this, tasks]() { QStringList warnings = tasks->warnings(); - if (warnings.count()) { + if (warnings.count()) CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); - } tasks->deleteLater(); }); @@ -110,17 +110,16 @@ void DataPackPage::downloadDialogFinished(int result) m_model->update(); } - if (m_downloadDialog) { + if (m_downloadDialog) m_downloadDialog->deleteLater(); - } } void DataPackPage::updateDataPacks() { - if (m_instance->typeName() != "Minecraft") { + if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - } + auto profile = static_cast(m_instance)->getPackProfile(); if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Data pack updates are unavailable when metadata is disabled!")); return; @@ -134,29 +133,27 @@ void DataPackPage::updateDataPacks() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) { + if (response != QMessageBox::Yes) return; - } } auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - auto modsList = m_model->selectedResources(selection); - bool useAll = modsList.empty(); - if (useAll) { - modsList = m_model->allResources(); - } + auto mods_list = m_model->selectedResources(selection); + bool use_all = mods_list.empty(); + if (use_all) + mods_list = m_model->allResources(); - ResourceUpdateDialog updateDialog(this, m_instance, m_model, modsList, false, { ModPlatform::ModLoaderType::DataPack }); - updateDialog.checkCandidates(); + ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false, { ModPlatform::ModLoaderType::DataPack }); + update_dialog.checkCandidates(); - if (updateDialog.aborted()) { + if (update_dialog.aborted()) { CustomMessageBox::selectable(this, tr("Aborted"), tr("The data pack updater was aborted!"), QMessageBox::Warning)->show(); return; } - if (updateDialog.noUpdates()) { - QString message{ tr("'%1' is up-to-date! :)").arg(modsList.front()->name()) }; - if (modsList.size() > 1) { - if (useAll) { + if (update_dialog.noUpdates()) { + QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) }; + if (mods_list.size() > 1) { + if (use_all) { message = tr("All data packs are up-to-date! :)"); } else { message = tr("All selected data packs are up-to-date! :)"); @@ -166,9 +163,9 @@ void DataPackPage::updateDataPacks() return; } - if (updateDialog.exec() != 0) { - auto* tasks = new ConcurrentTask("Download Data Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](const QString& reason) { + if (update_dialog.exec()) { + auto tasks = new ConcurrentTask("Download Data Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -184,7 +181,7 @@ void DataPackPage::updateDataPacks() tasks->deleteLater(); }); - for (const auto& task : updateDialog.getTasks()) { + for (auto task : update_dialog.getTasks()) { tasks->addTask(task); } @@ -200,9 +197,8 @@ void DataPackPage::deleteDataPackMetadata() { auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); auto selectionCount = m_model->selectedDataPacks(selection).length(); - if (selectionCount == 0) { + if (selectionCount == 0) return; - } if (selectionCount > 1) { auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), tr("You are about to remove the metadata for %1 data packs.\n" @@ -211,9 +207,8 @@ void DataPackPage::deleteDataPackMetadata() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) { + if (response != QMessageBox::Yes) return; - } } m_model->deleteMetadata(selection); @@ -221,9 +216,8 @@ void DataPackPage::deleteDataPackMetadata() void DataPackPage::changeDataPackVersion() { - if (m_instance->typeName() != "Minecraft") { + if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Data pack updates are unavailable when metadata is disabled!")); @@ -232,21 +226,19 @@ void DataPackPage::changeDataPackVersion() const QModelIndexList rows = ui->treeView->selectionModel()->selectedRows(); - if (rows.count() != 1) { + if (rows.count() != 1) return; - } Resource& resource = m_model->at(m_filterModel->mapToSource(rows[0]).row()); - if (resource.metadata() == nullptr) { + if (resource.metadata() == nullptr) return; - } - ResourceDownload::DataPackDownloadDialog mdownload(this, m_model, m_instance, true); + ResourceDownload::DataPackDownloadDialog mdownload(this, m_model, m_instance); mdownload.setResourceMetadata(resource.metadata()); - if (mdownload.exec() != 0) { - auto* tasks = new ConcurrentTask("Download Data Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](const QString& reason) { + if (mdownload.exec()) { + auto tasks = new ConcurrentTask("Download Data Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -256,9 +248,8 @@ void DataPackPage::changeDataPackVersion() }); connect(tasks, &Task::succeeded, [this, tasks]() { QStringList warnings = tasks->warnings(); - if (warnings.count()) { + if (warnings.count()) CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); - } tasks->deleteLater(); }); @@ -277,15 +268,14 @@ void DataPackPage::changeDataPackVersion() GlobalDataPackPage::GlobalDataPackPage(MinecraftInstance* instance, QWidget* parent) : QWidget(parent), m_instance(instance) { - auto* layout = new QVBoxLayout(this); + auto layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); setLayout(layout); connect(instance->settings()->getSetting("GlobalDataPacksEnabled").get(), &Setting::SettingChanged, this, [this] { updateContent(); - if (m_container != nullptr) { + if (m_container != nullptr) m_container->refreshContainer(); - } }); connect(instance->settings()->getSetting("GlobalDataPacksPath").get(), &Setting::SettingChanged, this, @@ -294,27 +284,24 @@ GlobalDataPackPage::GlobalDataPackPage(MinecraftInstance* instance, QWidget* par QString GlobalDataPackPage::displayName() const { - if (m_underlyingPage == nullptr) { + if (m_underlyingPage == nullptr) return {}; - } return m_underlyingPage->displayName(); } QIcon GlobalDataPackPage::icon() const { - if (m_underlyingPage == nullptr) { + if (m_underlyingPage == nullptr) return {}; - } return m_underlyingPage->icon(); } QString GlobalDataPackPage::helpPage() const { - if (m_underlyingPage == nullptr) { + if (m_underlyingPage == nullptr) return {}; - } return m_underlyingPage->helpPage(); } @@ -331,24 +318,21 @@ bool GlobalDataPackPage::apply() void GlobalDataPackPage::openedImpl() { - if (m_underlyingPage != nullptr) { + if (m_underlyingPage != nullptr) m_underlyingPage->openedImpl(); - } } void GlobalDataPackPage::closedImpl() { - if (m_underlyingPage != nullptr) { + if (m_underlyingPage != nullptr) m_underlyingPage->closedImpl(); - } } void GlobalDataPackPage::updateContent() { if (m_underlyingPage != nullptr) { - if (m_container->selectedPage() == this) { + if (m_container->selectedPage() == this) m_underlyingPage->closedImpl(); - } m_underlyingPage->apply(); @@ -363,9 +347,8 @@ void GlobalDataPackPage::updateContent() m_underlyingPage->setParentContainer(m_container); m_underlyingPage->updateExtraInfo = [this](QString id, QString value) { updateExtraInfo(std::move(id), std::move(value)); }; - if (m_container->selectedPage() == this) { + if (m_container->selectedPage() == this) m_underlyingPage->openedImpl(); - } layout()->addWidget(m_underlyingPage); } diff --git a/launcher/ui/pages/instance/DataPackPage.h b/launcher/ui/pages/instance/DataPackPage.h index a3e6627d4..4ca81bdd1 100644 --- a/launcher/ui/pages/instance/DataPackPage.h +++ b/launcher/ui/pages/instance/DataPackPage.h @@ -26,7 +26,7 @@ class DataPackPage : public ExternalResourcesPage { Q_OBJECT public: - explicit DataPackPage(BaseInstance* instance, DataPackFolderModel* model, QWidget* parent = nullptr); + 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 QIcon::fromTheme("datapacks"); } @@ -43,7 +43,7 @@ class DataPackPage : public ExternalResourcesPage { void changeDataPackVersion(); private: - DataPackFolderModel* m_model; + std::shared_ptr m_model; QPointer m_downloadDialog; }; diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp index da7fa3ee0..9174d6ee4 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp +++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp @@ -47,7 +47,7 @@ #include #include -ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, ResourceFolderModel* model, QWidget* parent) +ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared_ptr model, QWidget* parent) : QMainWindow(parent), m_instance(instance), ui(new Ui::ExternalResourcesPage), m_model(model) { ui->setupUi(this); @@ -58,7 +58,7 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, ResourceFol m_filterModel->setDynamicSortFilter(true); m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive); - m_filterModel->setSourceModel(m_model); + m_filterModel->setSourceModel(m_model.get()); m_filterModel->setFilterKeyColumn(-1); ui->treeView->setModel(m_filterModel); // must come after setModel @@ -98,12 +98,12 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, ResourceFol }; connect(selection_model, &QItemSelectionModel::selectionChanged, this, updateExtra); - connect(model, &ResourceFolderModel::updateFinished, this, updateExtra); - connect(model, &ResourceFolderModel::parseFinished, this, updateExtra); + connect(model.get(), &ResourceFolderModel::updateFinished, this, updateExtra); + connect(model.get(), &ResourceFolderModel::parseFinished, this, updateExtra); connect(selection_model, &QItemSelectionModel::selectionChanged, this, [this] { updateActions(); }); - connect(m_model, &ResourceFolderModel::rowsInserted, this, [this] { updateActions(); }); - connect(m_model, &ResourceFolderModel::rowsRemoved, this, [this] { updateActions(); }); + connect(m_model.get(), &ResourceFolderModel::rowsInserted, this, [this] { updateActions(); }); + connect(m_model.get(), &ResourceFolderModel::rowsRemoved, this, [this] { updateActions(); }); auto viewHeader = ui->treeView->header(); viewHeader->setContextMenuPolicy(Qt::CustomContextMenu); diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.h b/launcher/ui/pages/instance/ExternalResourcesPage.h index 7f4320648..00bb5d17d 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.h +++ b/launcher/ui/pages/instance/ExternalResourcesPage.h @@ -20,7 +20,7 @@ class ExternalResourcesPage : public QMainWindow, public BasePage { Q_OBJECT public: - explicit ExternalResourcesPage(BaseInstance* instance, ResourceFolderModel* model, QWidget* parent = nullptr); + explicit ExternalResourcesPage(BaseInstance* instance, std::shared_ptr model, QWidget* parent = nullptr); virtual ~ExternalResourcesPage(); virtual QString displayName() const override = 0; @@ -68,7 +68,7 @@ class ExternalResourcesPage : public QMainWindow, public BasePage { BaseInstance* m_instance = nullptr; Ui::ExternalResourcesPage* ui = nullptr; - ResourceFolderModel* m_model; + std::shared_ptr m_model; QSortFilterProxyModel* m_filterModel = nullptr; QString m_fileSelectionFilter; diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.h b/launcher/ui/pages/instance/InstanceSettingsPage.h index 79d5944eb..aca47e2c7 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.h +++ b/launcher/ui/pages/instance/InstanceSettingsPage.h @@ -44,7 +44,8 @@ class InstanceSettingsPage : public MinecraftSettingsWidget, public BasePage { Q_OBJECT public: - explicit InstanceSettingsPage(MinecraftInstance* instance, QWidget* parent = nullptr) : MinecraftSettingsWidget(instance, parent) + explicit InstanceSettingsPage(MinecraftInstancePtr instance, QWidget* parent = nullptr) + : MinecraftSettingsWidget(std::move(instance), parent) { connect(APPLICATION, &Application::globalSettingsAboutToOpen, this, &InstanceSettingsPage::saveSettings); connect(APPLICATION, &Application::globalSettingsApplied, this, &InstanceSettingsPage::loadSettings); diff --git a/launcher/ui/pages/instance/LogPage.cpp b/launcher/ui/pages/instance/LogPage.cpp index 770698961..9f978ecb7 100644 --- a/launcher/ui/pages/instance/LogPage.cpp +++ b/launcher/ui/pages/instance/LogPage.cpp @@ -128,7 +128,7 @@ QModelIndex LogFormatProxyModel::find(const QModelIndex& start, const QString& v return QModelIndex(); } -LogPage::LogPage(BaseInstance* instance, QWidget* parent) : QWidget(parent), ui(new Ui::LogPage), m_instance(instance) +LogPage::LogPage(InstancePtr instance, QWidget* parent) : QWidget(parent), ui(new Ui::LogPage), m_instance(instance) { ui->setupUi(this); @@ -153,7 +153,7 @@ LogPage::LogPage(BaseInstance* instance, QWidget* parent) : QWidget(parent), ui( if (launchTask) { setInstanceLaunchTaskChanged(launchTask, true); } - connect(m_instance, &BaseInstance::launchTaskChanged, this, &LogPage::onInstanceLaunchTaskChanged); + connect(m_instance.get(), &BaseInstance::launchTaskChanged, this, &LogPage::onInstanceLaunchTaskChanged); } auto findShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this); @@ -203,7 +203,7 @@ void LogPage::UIToModelState() m_model->suspend(ui->trackLogCheckbox->checkState() != Qt::Checked); } -void LogPage::setInstanceLaunchTaskChanged(LaunchTask* proc, bool initial) +void LogPage::setInstanceLaunchTaskChanged(shared_qobject_ptr proc, bool initial) { m_process = proc; if (m_process) { @@ -220,7 +220,7 @@ void LogPage::setInstanceLaunchTaskChanged(LaunchTask* proc, bool initial) } } -void LogPage::onInstanceLaunchTaskChanged(LaunchTask* proc) +void LogPage::onInstanceLaunchTaskChanged(shared_qobject_ptr proc) { setInstanceLaunchTaskChanged(proc, false); } diff --git a/launcher/ui/pages/instance/LogPage.h b/launcher/ui/pages/instance/LogPage.h index ef93f2cc0..636a8b70d 100644 --- a/launcher/ui/pages/instance/LogPage.h +++ b/launcher/ui/pages/instance/LogPage.h @@ -63,7 +63,7 @@ class LogPage : public QWidget, public BasePage { Q_OBJECT public: - explicit LogPage(BaseInstance* instance, QWidget* parent = 0); + explicit LogPage(InstancePtr instance, QWidget* parent = 0); virtual ~LogPage(); virtual QString displayName() const override { return tr("Minecraft Log"); } virtual QIcon icon() const override { return QIcon::fromTheme("log"); } @@ -88,17 +88,17 @@ class LogPage : public QWidget, public BasePage { void findNextActivated(); void findPreviousActivated(); - void onInstanceLaunchTaskChanged(LaunchTask* proc); + void onInstanceLaunchTaskChanged(shared_qobject_ptr proc); private: void modelStateToUI(); void UIToModelState(); - void setInstanceLaunchTaskChanged(LaunchTask* proc, bool initial); + void setInstanceLaunchTaskChanged(shared_qobject_ptr proc, bool initial); private: Ui::LogPage* ui; - BaseInstance* m_instance; - LaunchTask* m_process; + InstancePtr m_instance; + shared_qobject_ptr m_process; LogFormatProxyModel* m_proxy; shared_qobject_ptr m_model; diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index d2683fa92..230cf1bdf 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -4,7 +4,6 @@ #include "ManagedPackPage.h" #include -#include #include #include #include "modplatform/ModIndex.h" @@ -103,9 +102,6 @@ ManagedPackPage::ManagedPackPage(BaseInstance* inst, InstanceWindow* instance_wi ui->versionsComboBox->setStyle(comboStyle); } - ui->versionsComboBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - ui->versionsComboBox->view()->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); - ui->reloadButton->setVisible(false); connect(ui->reloadButton, &QPushButton::clicked, this, [this](bool) { ui->reloadButton->setVisible(false); @@ -127,8 +123,6 @@ ManagedPackPage::ManagedPackPage(BaseInstance* inst, InstanceWindow* instance_wi } QDesktopServices::openUrl(url); }); - - connect(ui->urlLine, &QLineEdit::textChanged, this, [this](QString text) { m_inst->settings()->set("ManagedPackURL", text.trimmed()); }); } ManagedPackPage::~ManagedPackPage() @@ -144,20 +138,16 @@ void ManagedPackPage::openedImpl() ui->packOrigin->hide(); ui->packOriginLabel->hide(); ui->versionsComboBox->hide(); - ui->updateToVersionLabel->setText(tr("URL:")); - ui->updateButton->setText(tr("Update Pack")); - ui->updateButton->setDisabled(false); - ui->urlLine->setText(m_inst->settings()->get("ManagedPackURL").toString().trimmed()); + ui->updateButton->hide(); + ui->updateToVersionLabel->hide(); + ui->updateFromFileButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); ui->packName->setText(m_inst->name()); ui->changelogTextBrowser->setText(tr("This is a local modpack.\n" - "This can be updated either using a file in %1 format or an URL.\n" - "Do not use a different format than the one mentioned as it may break the instance.\n" - "Make sure you also trust the URL.\n") + "This can be updated only using a file in %1 format\n") .arg(displayName())); return; } - ui->urlLine->hide(); ui->packName->setText(m_inst->getManagedPackName()); ui->packVersion->setText(m_inst->getManagedPackVersionName()); ui->packOrigin->setText(tr("Website: %2 | Pack ID: %3 | Version ID: %4") @@ -202,24 +192,23 @@ bool ManagedPackPage::runUpdateTask(InstanceTask* task) unique_qobject_ptr wrapped_task(APPLICATION->instances()->wrapInstanceTask(task)); - connect(wrapped_task.get(), &Task::failed, - [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); }); - connect(wrapped_task.get(), &Task::succeeded, [this, task]() { + connect(task, &Task::failed, + [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); }); + connect(task, &Task::succeeded, [this, task]() { QStringList warnings = task->warnings(); - if (warnings.count()) { + if (warnings.count()) CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); - } }); - connect(wrapped_task.get(), &Task::aborted, [this] { + connect(task, &Task::aborted, [this] { CustomMessageBox::selectable(this, tr("Task aborted"), tr("The task has been aborted by the user."), QMessageBox::Information) ->show(); }); ProgressDialog loadDialog(this); loadDialog.setSkipButton(true, tr("Abort")); - loadDialog.execWithTask(wrapped_task.get()); + loadDialog.execWithTask(task); - return wrapped_task->wasSuccessful(); + return task->wasSuccessful(); } void ManagedPackPage::suggestVersion() @@ -261,16 +250,14 @@ void ModrinthManagedPackPage::parseManagedPack() qDebug() << "Parsing Modrinth pack"; // No need for the extra work because we already have everything we need. - if (m_loaded) { + if (m_loaded) return; - } - if (m_fetch_job && m_fetch_job->isRunning()) { + if (m_fetch_job && m_fetch_job->isRunning()) m_fetch_job->abort(); - } ResourceAPI::Callback> callbacks{}; - m_pack = { .addonId = m_inst->getManagedPackID() }; + m_pack = { m_inst->getManagedPackID() }; // Use default if no callbacks are set callbacks.on_succeed = [this](auto& doc) { @@ -287,9 +274,8 @@ void ModrinthManagedPackPage::parseManagedPack() // 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()) { + if (version.version == m_inst->getManagedPackVersionName()) name = tr("%1 (Current)").arg(name); - } ui->versionsComboBox->addItem(name, version.fileId); } @@ -298,14 +284,10 @@ void ModrinthManagedPackPage::parseManagedPack() m_loaded = true; }; - callbacks.on_fail = [this](const QString& /*reason*/, int) { setFailState(); }; + callbacks.on_fail = [this](QString reason, int) { setFailState(); }; callbacks.on_abort = [this]() { setFailState(); }; - m_fetch_job = m_api.getProjectVersions({ .pack = std::make_shared(m_pack), - .mcVersions = {}, - .loaders = {}, - .resourceType = ModPlatform::ResourceType::Modpack, - .includeChangelog = true }, - std::move(callbacks)); + m_fetch_job = m_api.getProjectVersions( + { std::make_shared(m_pack), {}, {}, ModPlatform::ResourceType::Modpack }, std::move(callbacks)); ui->changelogTextBrowser->setText(tr("Fetching changelogs...")); @@ -357,11 +339,6 @@ void ManagedPackPage::onUpdateTaskCompleted(bool did_succeed) const void ModrinthManagedPackPage::update() { - auto customURL = m_inst->settings()->get("ManagedPackURL").toString().trimmed(); - if (m_inst->getManagedPackID().isEmpty() && !customURL.isEmpty()) { - updatePack(customURL); - return; - } auto index = ui->versionsComboBox->currentIndex(); if (m_pack.versions.length() == 0) { setFailState(); @@ -369,7 +346,25 @@ void ModrinthManagedPackPage::update() } auto version = m_pack.versions.at(index); - updatePack(version.downloadUrl, version.fileId.toString(), version.version); + 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.fileId.toString()); + extra_info.insert("original_instance_id", m_inst->id()); + + 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)); + extracted->setName(inst_name); + + extracted->setGroup(APPLICATION->instances()->getInstanceGroup(m_inst->id())); + extracted->setIcon(m_inst->iconKey()); + extracted->setConfirmUpdate(false); + + // Run our task then handle the result + auto did_succeed = runUpdateTask(extracted); + onUpdateTaskCompleted(did_succeed); } void ModrinthManagedPackPage::updateFromFile() @@ -377,8 +372,21 @@ void ModrinthManagedPackPage::updateFromFile() auto output = QFileDialog::getOpenFileUrl(this, tr("Choose update file"), QDir::homePath(), tr("Modrinth pack") + " (*.mrpack *.zip)"); if (output.isEmpty()) return; + QMap extra_info; + extra_info.insert("pack_id", m_inst->getManagedPackID()); + extra_info.insert("pack_version_id", QString()); + extra_info.insert("original_instance_id", m_inst->id()); - updatePack(output); + auto extracted = new InstanceImportTask(output, this, std::move(extra_info)); + + extracted->setName(m_inst->name()); + extracted->setGroup(APPLICATION->instances()->getInstanceGroup(m_inst->id())); + extracted->setIcon(m_inst->iconKey()); + extracted->setConfirmUpdate(false); + + // Run our task then handle the result + auto did_succeed = runUpdateTask(extracted); + onUpdateTaskCompleted(did_succeed); } // FLAME @@ -414,16 +422,14 @@ void FlameManagedPackPage::parseManagedPack() } // No need for the extra work because we already have everything we need. - if (m_loaded) { + if (m_loaded) return; - } - if (m_fetch_job && m_fetch_job->isRunning()) { + if (m_fetch_job && m_fetch_job->isRunning()) m_fetch_job->abort(); - } QString id = m_inst->getManagedPackID(); - m_pack = { .addonId = id }; + m_pack = { id }; ResourceAPI::Callback> callbacks{}; @@ -440,9 +446,8 @@ void FlameManagedPackPage::parseManagedPack() for (const auto& version : m_pack.versions) { QString name = version.getVersionDisplayString(); - if (version.fileId == m_inst->getManagedPackVersionID().toInt()) { + if (version.fileId == m_inst->getManagedPackVersionID().toInt()) name = tr("%1 (Current)").arg(name); - } ui->versionsComboBox->addItem(name, QVariant(version.fileId)); } @@ -451,14 +456,10 @@ void FlameManagedPackPage::parseManagedPack() m_loaded = true; }; - callbacks.on_fail = [this](const QString& /*reason*/, int) { setFailState(); }; + callbacks.on_fail = [this](QString reason, int) { setFailState(); }; callbacks.on_abort = [this]() { setFailState(); }; - m_fetch_job = m_api.getProjectVersions({ .pack = std::make_shared(m_pack), - .mcVersions = {}, - .loaders = {}, - .resourceType = ModPlatform::ResourceType::Modpack, - .includeChangelog = true }, - std::move(callbacks)); + m_fetch_job = m_api.getProjectVersions( + { std::make_shared(m_pack), {}, {}, ModPlatform::ResourceType::Modpack }, std::move(callbacks)); m_fetch_job->start(); } @@ -486,11 +487,6 @@ void FlameManagedPackPage::suggestVersion() void FlameManagedPackPage::update() { - auto customURL = m_inst->settings()->get("ManagedPackURL").toString().trimmed(); - if (m_inst->getManagedPackID().isEmpty() && !customURL.isEmpty()) { - updatePack(customURL); - return; - } auto index = ui->versionsComboBox->currentIndex(); if (m_pack.versions.length() == 0) { setFailState(); @@ -498,7 +494,20 @@ void FlameManagedPackPage::update() } auto version = m_pack.versions.at(index); - updatePack(version.downloadUrl, version.fileId.toString()); + QMap extra_info; + extra_info.insert("pack_id", m_inst->getManagedPackID()); + 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)); + + extracted->setName(m_inst->name()); + extracted->setGroup(APPLICATION->instances()->getInstanceGroup(m_inst->id())); + extracted->setIcon(m_inst->iconKey()); + extracted->setConfirmUpdate(false); + + auto did_succeed = runUpdateTask(extracted); + onUpdateTaskCompleted(did_succeed); } void FlameManagedPackPage::updateFromFile() @@ -507,33 +516,19 @@ void FlameManagedPackPage::updateFromFile() if (output.isEmpty()) return; - updatePack(output); -} - -void ManagedPackPage::updatePack(const QUrl& url, QString versionID, QString versionName) -{ 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", versionID); + extra_info.insert("pack_version_id", QString()); extra_info.insert("original_instance_id", m_inst->id()); - auto extracted = new InstanceImportTask(url, this, std::move(extra_info)); + auto extracted = new InstanceImportTask(output, this, std::move(extra_info)); - if (versionName.isEmpty()) { - extracted->setName(m_inst->name()); - } else { - InstanceName inst_name(m_inst->getManagedPackName(), versionName); - inst_name.setName(m_inst->name().replace(m_inst->getManagedPackVersionName(), versionName)); - extracted->setName(inst_name); - } + extracted->setName(m_inst->name()); extracted->setGroup(APPLICATION->instances()->getInstanceGroup(m_inst->id())); extracted->setIcon(m_inst->iconKey()); extracted->setConfirmUpdate(false); - // Run our task then handle the result auto did_succeed = runUpdateTask(extracted); onUpdateTaskCompleted(did_succeed); } - #include "ManagedPackPage.moc" diff --git a/launcher/ui/pages/instance/ManagedPackPage.h b/launcher/ui/pages/instance/ManagedPackPage.h index 4b7332896..f319ed069 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.h +++ b/launcher/ui/pages/instance/ManagedPackPage.h @@ -86,8 +86,6 @@ class ManagedPackPage : public QWidget, public BasePage { */ bool runUpdateTask(InstanceTask*); - void updatePack(const QUrl& url, QString versionID = {}, QString versionName = {}); - protected: InstanceWindow* m_instance_window = nullptr; diff --git a/launcher/ui/pages/instance/ManagedPackPage.ui b/launcher/ui/pages/instance/ManagedPackPage.ui index 5ed80400a..62641bc82 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.ui +++ b/launcher/ui/pages/instance/ManagedPackPage.ui @@ -137,9 +137,6 @@ - - - diff --git a/launcher/ui/pages/instance/McClient.cpp b/launcher/ui/pages/instance/McClient.cpp index 0a719431d..915df55d4 100644 --- a/launcher/ui/pages/instance/McClient.cpp +++ b/launcher/ui/pages/instance/McClient.cpp @@ -1,17 +1,18 @@ -#include "McClient.h" - #include #include #include #include -#include -#include "Exception.h" +#include #include "Json.h" +#include "McClient.h" -McClient::McClient(QObject* parent, QString domain, QString ip, const uint16_t port) - : QObject(parent), m_domain(std::move(domain)), m_ip(std::move(ip)), m_port(port) -{} +// 7 first bits +#define SEGMENT_BITS 0x7F +// last bit +#define CONTINUE_BIT 0x80 + +McClient::McClient(QObject* parent, QString domain, QString ip, short port) : QObject(parent), m_domain(domain), m_ip(ip), m_port(port) {} void McClient::getStatusData() { @@ -32,12 +33,13 @@ void McClient::getStatusData() void McClient::sendRequest() { QByteArray data; - writeVarInt(data, 0x00); // packet ID - writeVarInt(data, 763); // hardcoded protocol version (763 = 1.20.1) - writeString(data, m_domain); // server address - writeUInt16(data, m_port); // server port - writeVarInt(data, 0x01); // next state - writePacketToSocket(data); // send handshake packet + writeVarInt(data, 0x00); // packet ID + writeVarInt(data, 763); // hardcoded protocol version (763 = 1.20.1) + writeVarInt(data, m_domain.size()); // server address length + writeString(data, m_domain.toStdString()); // server address + writeFixedInt(data, m_port, 2); // server port + writeVarInt(data, 0x01); // next state + writePacketToSocket(data); // send handshake packet writeVarInt(data, 0x00); // packet ID writePacketToSocket(data); // send status packet @@ -45,27 +47,23 @@ void McClient::sendRequest() void McClient::readRawResponse() { - if (m_responseReadState == ResponseReadState::Finished) { + if (m_responseReadState == 2) { return; } m_resp.append(m_socket.readAll()); - if (m_responseReadState == ResponseReadState::Waiting && m_resp.size() >= 5) { + if (m_responseReadState == 0 && m_resp.size() >= 5) { m_wantedRespLength = readVarInt(m_resp); - m_responseReadState = ResponseReadState::GotLength; + m_responseReadState = 1; } - if (m_responseReadState == ResponseReadState::GotLength && m_resp.size() >= m_wantedRespLength) { + if (m_responseReadState == 1 && m_resp.size() >= m_wantedRespLength) { if (m_resp.size() > m_wantedRespLength) { qDebug().nospace() << "Warning: Packet length doesn't match actual packet size (" << m_wantedRespLength << " expected vs " - << m_resp.size() << " received)"; + << m_resp.size() << " received)"; } - try { - parseResponse(); - } catch (const Exception& e) { - emitFail(e.cause()); - } - m_responseReadState = ResponseReadState::Finished; + parseResponse(); + m_responseReadState = 2; } } @@ -73,7 +71,7 @@ void McClient::parseResponse() { qDebug() << "Received response successfully"; - const int packetID = readVarInt(m_resp); + int packetID = readVarInt(m_resp); if (packetID != 0x00) { throw Exception(QString("Packet ID doesn't match expected value (0x00 vs 0x%1)").arg(packetID, 0, 16)); } @@ -82,7 +80,7 @@ void McClient::parseResponse() // 'resp' should now be the JSON string QJsonParseError parseError; - const QJsonDocument doc = Json::parseUntilGarbage(m_resp, &parseError); + QJsonDocument doc = Json::parseUntilGarbage(m_resp, &parseError); if (parseError.error != QJsonParseError::NoError) { qDebug() << "Failed to parse JSON:" << parseError.errorString(); emitFail(parseError.errorString()); @@ -91,23 +89,18 @@ void McClient::parseResponse() emitSucceed(doc.object()); } -// NOLINTBEGIN(*-signed-bitwise) - // From https://wiki.vg/Protocol#VarInt_and_VarLong -constexpr uint8_t g_varIntValueMask = 0x7F; -constexpr uint8_t g_varIntContinue = 0x80; - void McClient::writeVarInt(QByteArray& data, int value) { - while ((value & ~g_varIntValueMask) != 0) { // check if the value is too big to fit in 7 bits + while ((value & ~SEGMENT_BITS)) { // check if the value is too big to fit in 7 bits // Write 7 bits - data.append(static_cast((value & ~g_varIntValueMask) | g_varIntContinue)); // NOLINT(*-narrowing-conversions) + data.append((value & SEGMENT_BITS) | CONTINUE_BIT); // Erase theses 7 bits from the value to write // Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone value >>= 7; } - data.append(static_cast(value)); // NOLINT(*-narrowing-conversions) + data.append(value); } // From https://wiki.vg/Protocol#VarInt_and_VarLong @@ -115,56 +108,53 @@ int McClient::readVarInt(QByteArray& data) { int value = 0; int position = 0; + char currentByte; while (position < 32) { - const uint8_t currentByte = readByte(data); - value |= (currentByte & g_varIntValueMask) << position; + currentByte = readByte(data); + value |= (currentByte & SEGMENT_BITS) << position; - if ((currentByte & g_varIntContinue) == 0) { + if ((currentByte & CONTINUE_BIT) == 0) break; - } position += 7; } - if (position >= 32) { + if (position >= 32) throw Exception("VarInt is too big"); - } return value; } -// NOLINTEND(*-signed-bitwise) - -uint8_t McClient::readByte(QByteArray& data) +char McClient::readByte(QByteArray& data) { if (data.isEmpty()) { throw Exception("No more bytes to read"); } - const uint8_t byte = data.at(0); + char byte = data.at(0); data.remove(0, 1); return byte; } -void McClient::writeUInt16(QByteArray& data, const uint16_t value) +// write number with specified size in big endian format +void McClient::writeFixedInt(QByteArray& data, int value, int size) { - QDataStream stream(&data, QIODeviceBase::Append); - stream.setByteOrder(QDataStream::BigEndian); - stream << value; + for (int i = size - 1; i >= 0; i--) { + data.append((value >> (i * 8)) & 0xFF); + } } -void McClient::writeString(QByteArray& data, const QString& value) +void McClient::writeString(QByteArray& data, const std::string& value) { - writeVarInt(data, static_cast(value.size())); - data.append(value.toUtf8()); + data.append(value.c_str()); } void McClient::writePacketToSocket(QByteArray& data) { // we prefix the packet with its length QByteArray dataWithSize; - writeVarInt(dataWithSize, static_cast(data.size())); + writeVarInt(dataWithSize, data.size()); dataWithSize.append(data); // write it to the socket @@ -174,7 +164,7 @@ void McClient::writePacketToSocket(QByteArray& data) data.clear(); } -void McClient::emitFail(const QString& error) +void McClient::emitFail(QString error) { qDebug() << "Minecraft server ping for status error:" << error; emit failed(error); @@ -183,6 +173,6 @@ void McClient::emitFail(const QString& error) void McClient::emitSucceed(QJsonObject data) { - emit succeeded(std::move(data)); + emit succeeded(data); emit finished(); } diff --git a/launcher/ui/pages/instance/McClient.h b/launcher/ui/pages/instance/McClient.h index c1cb3d748..633e7aaed 100644 --- a/launcher/ui/pages/instance/McClient.h +++ b/launcher/ui/pages/instance/McClient.h @@ -1,54 +1,53 @@ #pragma once - #include +#include #include #include #include +#include + // Client for the Minecraft protocol class McClient : public QObject { Q_OBJECT + QString m_domain; + QString m_ip; + short m_port; + QTcpSocket m_socket; + + // 0: did not start reading the response yet + // 1: read the response length, still reading the response + // 2: finished reading the response + unsigned m_responseReadState = 0; + unsigned m_wantedRespLength = 0; + QByteArray m_resp; + public: - explicit McClient(QObject* parent, QString domain, QString ip, uint16_t port); + explicit McClient(QObject* parent, QString domain, QString ip, short port); //! Read status data of the server, and calls the succeeded() signal with the parsed JSON data void getStatusData(); - signals: - void succeeded(QJsonObject data); - void failed(QString error); - void finished(); - - private: - static uint8_t readByte(QByteArray& data); - static int readVarInt(QByteArray& data); - static void writeUInt16(QByteArray& data, uint16_t value); - static void writeString(QByteArray& data, const QString& value); - static void writeVarInt(QByteArray& data, int value); - private: void sendRequest(); //! Accumulate data until we have a full response, then call parseResponse() once void readRawResponse(); void parseResponse(); + + void writeVarInt(QByteArray& data, int value); + int readVarInt(QByteArray& data); + char readByte(QByteArray& data); + //! write number with specified size in big endian format + void writeFixedInt(QByteArray& data, int value, int size); + void writeString(QByteArray& data, const std::string& value); + void writePacketToSocket(QByteArray& data); - void emitFail(const QString& error); + void emitFail(QString error); void emitSucceed(QJsonObject data); - private: - enum class ResponseReadState : uint8_t { - Waiting, - GotLength, - Finished - }; - - QString m_domain; - QString m_ip; - uint16_t m_port; - QTcpSocket m_socket; - - ResponseReadState m_responseReadState = ResponseReadState::Waiting; - int32_t m_wantedRespLength = 0; - QByteArray m_resp; + signals: + void succeeded(QJsonObject data); + void failed(QString error); + void finished(); }; diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 99c78647c..e3fa9bf6b 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -37,7 +37,6 @@ */ #include "ModFolderPage.h" -#include "minecraft/mod/Resource.h" #include "ui/dialogs/ExportToModListDialog.h" #include "ui/dialogs/InstallLoaderDialog.h" #include "ui_ExternalResourcesPage.h" @@ -67,7 +66,7 @@ #include "tasks/Task.h" #include "ui/dialogs/ProgressDialog.h" -ModFolderPage::ModFolderPage(BaseInstance* inst, ModFolderModel* model, QWidget* parent) +ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr model, QWidget* parent) : ExternalResourcesPage(inst, model, parent), m_model(model) { ui->actionDownloadItem->setText(tr("Download Mods")); @@ -81,9 +80,9 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, ModFolderModel* model, QWidget* connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem); - auto* updateMenu = new QMenu(this); + auto updateMenu = new QMenu(this); - auto* update = updateMenu->addAction(tr("Check for Updates")); + auto update = updateMenu->addAction(tr("Check for Updates")); connect(update, &QAction::triggered, this, &ModFolderPage::updateMods); updateMenu->addAction(ui->actionVerifyItemDependencies); @@ -92,7 +91,7 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, ModFolderModel* model, QWidget* auto depsDisabled = APPLICATION->settings()->getSetting("ModDependenciesDisabled"); ui->actionVerifyItemDependencies->setVisible(!depsDisabled->get().toBool()); connect(depsDisabled.get(), &Setting::SettingChanged, this, - [this](const Setting&, const QVariant& value) { ui->actionVerifyItemDependencies->setVisible(!value.toBool()); }); + [this](const Setting& setting, const QVariant& value) { ui->actionVerifyItemDependencies->setVisible(!value.toBool()); }); updateMenu->addAction(ui->actionResetItemMetadata); connect(ui->actionResetItemMetadata, &QAction::triggered, this, &ModFolderPage::deleteModMetadata); @@ -134,39 +133,18 @@ void ModFolderPage::removeItems(const QItemSelection& selection) QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) { + if (response != QMessageBox::Yes) return; - } } - - auto indexes = selection.indexes(); - auto affected = m_model->getAffectedMods(indexes, EnableAction::DISABLE); - if (!affected.isEmpty()) { - auto response = CustomMessageBox::selectable(this, tr("Confirm Disable"), - tr("The mods you are trying to delete are required by %1 mods.\n" - "Do you want to disable them?") - .arg(affected.length()), - QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, - QMessageBox::Cancel) - ->exec(); - - if (response == QMessageBox::Cancel) { - return; - } - if (response == QMessageBox::Yes) { - m_model->setResourceEnabled(affected, EnableAction::DISABLE); - } - } - m_model->deleteResources(indexes); + m_model->deleteResources(selection.indexes()); } void ModFolderPage::downloadMods() { - if (m_instance->typeName() != "Minecraft") { + if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - } - auto* profile = static_cast(m_instance)->getPackProfile(); + auto profile = static_cast(m_instance)->getPackProfile(); if (!profile->getModLoaders().has_value()) { if (handleNoModLoader()) { return; @@ -182,9 +160,9 @@ void ModFolderPage::downloadMods() void ModFolderPage::downloadDialogFinished(int result) { - if (result != 0) { - auto* tasks = new ConcurrentTask(tr("Download Mods"), APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](const QString& reason) { + if (result) { + auto tasks = new ConcurrentTask(tr("Download Mods"), APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -194,9 +172,8 @@ void ModFolderPage::downloadDialogFinished(int result) }); connect(tasks, &Task::succeeded, [this, tasks]() { QStringList warnings = tasks->warnings(); - if (warnings.count()) { + if (warnings.count()) CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); - } tasks->deleteLater(); }); @@ -215,18 +192,16 @@ void ModFolderPage::downloadDialogFinished(int result) m_model->update(); } - if (m_downloadDialog) { + if (m_downloadDialog) m_downloadDialog->deleteLater(); - } } void ModFolderPage::updateMods(bool includeDeps) { - if (m_instance->typeName() != "Minecraft") { + if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - } - auto* profile = static_cast(m_instance)->getPackProfile(); + auto profile = static_cast(m_instance)->getPackProfile(); if (!profile->getModLoaders().has_value()) { if (handleNoModLoader()) { return; @@ -245,29 +220,27 @@ void ModFolderPage::updateMods(bool includeDeps) QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) { + if (response != QMessageBox::Yes) return; - } } auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - auto modsList = m_model->selectedResources(selection); - bool useAll = modsList.empty(); - if (useAll) { - modsList = m_model->allResources(); - } + auto mods_list = m_model->selectedResources(selection); + bool use_all = mods_list.empty(); + if (use_all) + mods_list = m_model->allResources(); - ResourceUpdateDialog updateDialog(this, m_instance, m_model, modsList, includeDeps, profile->getModLoadersList()); - updateDialog.checkCandidates(); + ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, includeDeps, profile->getModLoadersList()); + update_dialog.checkCandidates(); - if (updateDialog.aborted()) { + if (update_dialog.aborted()) { CustomMessageBox::selectable(this, tr("Aborted"), tr("The mod updater was aborted!"), QMessageBox::Warning)->show(); return; } - if (updateDialog.noUpdates()) { - QString message{ tr("'%1' is up-to-date! :)").arg(modsList.front()->name()) }; - if (modsList.size() > 1) { - if (useAll) { + if (update_dialog.noUpdates()) { + QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) }; + if (mods_list.size() > 1) { + if (use_all) { message = tr("All mods are up-to-date! :)"); } else { message = tr("All selected mods are up-to-date! :)"); @@ -277,9 +250,9 @@ void ModFolderPage::updateMods(bool includeDeps) return; } - if (updateDialog.exec() != 0) { - auto* tasks = new ConcurrentTask("Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](const QString& reason) { + if (update_dialog.exec()) { + auto tasks = new ConcurrentTask("Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -295,7 +268,7 @@ void ModFolderPage::updateMods(bool includeDeps) tasks->deleteLater(); }); - for (const auto& task : updateDialog.getTasks()) { + for (auto task : update_dialog.getTasks()) { tasks->addTask(task); } @@ -311,9 +284,8 @@ void ModFolderPage::deleteModMetadata() { auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); auto selectionCount = m_model->selectedMods(selection).length(); - if (selectionCount == 0) { + if (selectionCount == 0) return; - } if (selectionCount > 1) { auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), tr("You are about to remove the metadata for %1 mods.\n" @@ -322,9 +294,8 @@ void ModFolderPage::deleteModMetadata() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) { + if (response != QMessageBox::Yes) return; - } } m_model->deleteMetadata(selection); @@ -332,11 +303,10 @@ void ModFolderPage::deleteModMetadata() void ModFolderPage::changeModVersion() { - if (m_instance->typeName() != "Minecraft") { + if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - } - auto* profile = static_cast(m_instance)->getPackProfile(); + auto profile = static_cast(m_instance)->getPackProfile(); if (!profile->getModLoaders().has_value()) { if (handleNoModLoader()) { return; @@ -347,16 +317,15 @@ void ModFolderPage::changeModVersion() return; } auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - auto modsList = m_model->selectedMods(selection); - if (modsList.length() != 1 || modsList[0]->metadata() == nullptr) { + auto mods_list = m_model->selectedMods(selection); + if (mods_list.length() != 1 || mods_list[0]->metadata() == nullptr) return; - } - m_downloadDialog = new ResourceDownload::ModDownloadDialog(this, m_model, m_instance, true); + m_downloadDialog = new ResourceDownload::ModDownloadDialog(this, m_model, m_instance); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); connect(m_downloadDialog, &QDialog::finished, this, &ModFolderPage::downloadDialogFinished); - m_downloadDialog->setResourceMetadata((*modsList.begin())->metadata()); + m_downloadDialog->setResourceMetadata((*mods_list.begin())->metadata()); m_downloadDialog->open(); } @@ -364,21 +333,21 @@ void ModFolderPage::exportModMetadata() { auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); auto selectedMods = m_model->selectedMods(selection); - if (selectedMods.length() == 0) { + if (selectedMods.length() == 0) selectedMods = m_model->allMods(); - } - std::ranges::sort(selectedMods, [](const Mod* a, const Mod* b) { return a->name() < b->name(); }); + std::sort(selectedMods.begin(), selectedMods.end(), [](const Mod* a, const Mod* b) { return a->name() < b->name(); }); ExportToModListDialog dlg(m_instance->name(), selectedMods, this); dlg.exec(); } -CoreModFolderPage::CoreModFolderPage(BaseInstance* inst, ModFolderModel* mods, QWidget* parent) : ModFolderPage(inst, mods, parent) +CoreModFolderPage::CoreModFolderPage(BaseInstance* inst, std::shared_ptr mods, QWidget* parent) + : ModFolderPage(inst, mods, parent) { - auto* mcInst = dynamic_cast(m_instance); + auto mcInst = dynamic_cast(m_instance); if (mcInst) { - auto* version = mcInst->getPackProfile(); - if ((version != nullptr) && version->getComponent("net.minecraftforge") && version->getComponent("net.minecraft")) { + auto version = mcInst->getPackProfile(); + if (version && version->getComponent("net.minecraftforge") && version->getComponent("net.minecraft")) { auto minecraftCmp = version->getComponent("net.minecraft"); if (!minecraftCmp->m_loaded) { version->reload(Net::Mode::Offline); @@ -389,9 +358,7 @@ CoreModFolderPage::CoreModFolderPage(BaseInstance* inst, ModFolderModel* mods, Q m_container->refreshContainer(); } }); - if (!update->isRunning()) { - update->start(); - } + update->start(); } } } @@ -401,22 +368,22 @@ CoreModFolderPage::CoreModFolderPage(BaseInstance* inst, ModFolderModel* mods, Q bool CoreModFolderPage::shouldDisplay() const { if (ModFolderPage::shouldDisplay()) { - auto* inst = dynamic_cast(m_instance); - if (!inst) { + auto inst = dynamic_cast(m_instance); + if (!inst) return true; - } - auto* version = inst->getPackProfile(); - if ((version == nullptr) || !version->getComponent("net.minecraftforge") || !version->getComponent("net.minecraft")) { + auto version = inst->getPackProfile(); + if (!version || !version->getComponent("net.minecraftforge") || !version->getComponent("net.minecraft")) return false; - } auto minecraftCmp = version->getComponent("net.minecraft"); return minecraftCmp->m_loaded && minecraftCmp->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate; } return false; } -NilModFolderPage::NilModFolderPage(BaseInstance* inst, ModFolderModel* mods, QWidget* parent) : ModFolderPage(inst, mods, parent) {} +NilModFolderPage::NilModFolderPage(BaseInstance* inst, std::shared_ptr mods, QWidget* parent) + : ModFolderPage(inst, mods, parent) +{} bool NilModFolderPage::shouldDisplay() const { @@ -426,22 +393,31 @@ bool NilModFolderPage::shouldDisplay() const // Helper function so this doesn't need to be duplicated 3 times inline bool ModFolderPage::handleNoModLoader() { - int resp = QMessageBox::question( - this, ModFolderPage::tr("Missing Mod Loader"), - ModFolderPage::tr("You need to install a compatible mod loader before installing mods. Would you like to do so?"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); - if (resp == QMessageBox::Yes) { - // Should be safe - auto* profile = static_cast(this->m_instance)->getPackProfile(); - InstallLoaderDialog dialog(profile, QString(), this); - bool ret = dialog.exec() != 0; - this->m_container->refreshContainer(); + int resp = + QMessageBox::question(this, this->tr("Missing Mod Loader"), + this->tr("You need to install a compatible mod loader before installing mods. Would you like to do so?"), + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + switch (resp) { + case QMessageBox::Yes: { + // Should be safe + auto profile = static_cast(this->m_instance)->getPackProfile(); + InstallLoaderDialog dialog(profile, QString(), this); + bool ret = dialog.exec(); + this->m_container->refreshContainer(); - // returning negation of dialog.exec which'll be true if the install loader dialog got canceled/closed - // and false if the user went through and installed a loader - return !ret; + // returning negation of dialog.exec which'll be true if the install loader dialog got canceled/closed + // and false if the user went through and installed a loader + return !ret; + } + case QMessageBox::No: { + // Nothing happens the dialog is already closing + // returning true so the caller doesn't go and continue with opening it's dialog without a mod loader + return true; + } + default: { + // Unreachable + // returning true as a safety measure + return true; + } } - // Nothing happens the dialog is already closing - // returning true so the caller doesn't go and continue with opening it's dialog without a mod loader - return true; } diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index 62db9fad8..aadeecb20 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -48,7 +48,7 @@ class ModFolderPage : public ExternalResourcesPage { inline bool handleNoModLoader(); public: - explicit ModFolderPage(BaseInstance* inst, ModFolderModel* model, QWidget* parent = nullptr); + explicit ModFolderPage(BaseInstance* inst, std::shared_ptr model, QWidget* parent = nullptr); virtual ~ModFolderPage() = default; void setFilter(const QString& filter) { m_fileSelectionFilter = filter; } @@ -74,14 +74,14 @@ class ModFolderPage : public ExternalResourcesPage { void changeModVersion(); protected: - ModFolderModel* m_model; + std::shared_ptr m_model; QPointer m_downloadDialog; }; class CoreModFolderPage : public ModFolderPage { Q_OBJECT public: - explicit CoreModFolderPage(BaseInstance* inst, ModFolderModel* mods, QWidget* parent = 0); + explicit CoreModFolderPage(BaseInstance* inst, std::shared_ptr mods, QWidget* parent = 0); virtual ~CoreModFolderPage() = default; virtual QString displayName() const override { return tr("Core Mods"); } @@ -95,7 +95,7 @@ class CoreModFolderPage : public ModFolderPage { class NilModFolderPage : public ModFolderPage { Q_OBJECT public: - explicit NilModFolderPage(BaseInstance* inst, ModFolderModel* mods, QWidget* parent = 0); + explicit NilModFolderPage(BaseInstance* inst, std::shared_ptr mods, QWidget* parent = 0); virtual ~NilModFolderPage() = default; virtual QString displayName() const override { return tr("Nilmods"); } diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index b9f943777..a3914832b 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -50,7 +50,7 @@ #include #include -OtherLogsPage::OtherLogsPage(QString id, QString displayName, QString helpPage, BaseInstance* instance, QWidget* parent) +OtherLogsPage::OtherLogsPage(QString id, QString displayName, QString helpPage, InstancePtr instance, QWidget* parent) : QWidget(parent) , m_id(id) , m_displayName(displayName) @@ -64,10 +64,10 @@ OtherLogsPage::OtherLogsPage(QString id, QString displayName, QString helpPage, m_proxy = new LogFormatProxyModel(this); if (m_instance) { - m_model = new LogModel(this); + m_model.reset(new LogModel(this)); ui->trackLogCheckbox->hide(); } else { - m_model = APPLICATION->logModel.get(); + m_model = APPLICATION->logModel; } // set up fonts in the log proxy @@ -90,7 +90,7 @@ OtherLogsPage::OtherLogsPage(QString id, QString displayName, QString helpPage, } else { modelStateToUI(); } - m_proxy->setSourceModel(m_model); + m_proxy->setSourceModel(m_model.get()); connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &OtherLogsPage::populateSelectLogBox); @@ -182,7 +182,7 @@ void OtherLogsPage::populateSelectLogBox() ui->selectLogBox->blockSignals(true); ui->selectLogBox->clear(); if (!m_instance) - ui->selectLogBox->addItem(tr("Current logs")); + ui->selectLogBox->addItem("Current logs"); ui->selectLogBox->addItems(getPaths()); ui->selectLogBox->blockSignals(false); @@ -243,8 +243,8 @@ void OtherLogsPage::reload() if (m_instance) { setControlsEnabled(false); } else { - m_model = APPLICATION->logModel.get(); - m_proxy->setSourceModel(m_model); + m_model = APPLICATION->logModel; + m_proxy->setSourceModel(m_model.get()); ui->text->setModel(m_proxy); ui->text->scrollToBottom(); UIToModelState(); @@ -277,15 +277,10 @@ void OtherLogsPage::reload() MessageLevel last = MessageLevel::Unknown; auto handleLine = [this, &last](QString line) { - if (!line.isEmpty() && line.back() == '\n') { - line.resize(line.size() - 1); - } - if (!line.isEmpty() && line.back() == '\r') { - line.resize(line.size() - 1); - } - if (line.isEmpty()) { + if (line.isEmpty()) return false; - } + if (line.back() == '\n') + line = line.remove(line.size() - 1, 1); MessageLevel level = MessageLevel::Unknown; QString lineTemp = line; // don't edit out the time and level for clarity @@ -304,7 +299,7 @@ void OtherLogsPage::reload() ui->text->clear(); ui->text->setModel(nullptr); if (!m_instance) { - m_model = new LogModel(this); + m_model.reset(new LogModel(this)); m_model->setMaxLines(getConsoleMaxLines(APPLICATION->settings())); m_model->setStopOnOverflow(shouldStopOnConsoleOverflow(APPLICATION->settings())); m_model->setOverflowMessage(tr("Cannot display this log since the log length surpassed %1 lines.").arg(m_model->getMaxLines())); @@ -343,7 +338,7 @@ void OtherLogsPage::reload() ui->text->setModel(m_proxy); ui->text->scrollToBottom(); } else { - m_proxy->setSourceModel(m_model); + m_proxy->setSourceModel(m_model.get()); ui->text->setModel(m_proxy); ui->text->scrollToBottom(); UIToModelState(); @@ -477,14 +472,14 @@ void OtherLogsPage::setControlsEnabled(const bool enabled) ui->btnDelete->setEnabled(enabled); ui->btnClean->setEnabled(enabled); } else if (!m_currentFile.isEmpty()) { - ui->btnReload->setText(tr("&Reload")); - ui->btnReload->setToolTip(tr("Reload the contents of the log from the disk")); + ui->btnReload->setText("&Reload"); + ui->btnReload->setToolTip("Reload the contents of the log from the disk"); ui->btnDelete->setEnabled(enabled); ui->btnClean->setEnabled(enabled); ui->trackLogCheckbox->setEnabled(false); } else { - ui->btnReload->setText(tr("Clear")); - ui->btnReload->setToolTip(tr("Clear the log")); + ui->btnReload->setText("Clear"); + ui->btnReload->setToolTip("Clear the log"); ui->btnDelete->setEnabled(false); ui->btnClean->setEnabled(false); ui->trackLogCheckbox->setEnabled(enabled); diff --git a/launcher/ui/pages/instance/OtherLogsPage.h b/launcher/ui/pages/instance/OtherLogsPage.h index cd2fe6439..9fc0ba3b9 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.h +++ b/launcher/ui/pages/instance/OtherLogsPage.h @@ -52,7 +52,7 @@ class OtherLogsPage : public QWidget, public BasePage { Q_OBJECT public: - explicit OtherLogsPage(QString id, QString displayName, QString helpPage, BaseInstance* instance = nullptr, QWidget* parent = 0); + explicit OtherLogsPage(QString id, QString displayName, QString helpPage, InstancePtr instance = nullptr, QWidget* parent = 0); ~OtherLogsPage(); QString id() const override { return m_id; } @@ -97,7 +97,7 @@ class OtherLogsPage : public QWidget, public BasePage { QString m_helpPage; Ui::OtherLogsPage* ui; - BaseInstance* m_instance; + InstancePtr m_instance; /** Path to display log paths relative to. */ QString m_basePath; QStringList m_logSearchPaths; @@ -105,5 +105,5 @@ class OtherLogsPage : public QWidget, public BasePage { QFileSystemWatcher m_watcher; LogFormatProxyModel* m_proxy; - LogModel* m_model; + shared_qobject_ptr m_model; }; diff --git a/launcher/ui/pages/instance/ResourcePackPage.cpp b/launcher/ui/pages/instance/ResourcePackPage.cpp index e4709ab2b..2b5a5a86c 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.cpp +++ b/launcher/ui/pages/instance/ResourcePackPage.cpp @@ -42,7 +42,7 @@ #include "ui/dialogs/ResourceDownloadDialog.h" #include "ui/dialogs/ResourceUpdateDialog.h" -ResourcePackPage::ResourcePackPage(MinecraftInstance* instance, ResourcePackFolderModel* model, QWidget* parent) +ResourcePackPage::ResourcePackPage(MinecraftInstance* instance, std::shared_ptr model, QWidget* parent) : ExternalResourcesPage(instance, model, parent), m_model(model) { ui->actionDownloadItem->setText(tr("Download Packs")); @@ -56,9 +56,9 @@ ResourcePackPage::ResourcePackPage(MinecraftInstance* instance, ResourcePackFold connect(ui->actionUpdateItem, &QAction::triggered, this, &ResourcePackPage::updateResourcePacks); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem); - auto* updateMenu = new QMenu(this); + auto updateMenu = new QMenu(this); - auto* update = updateMenu->addAction(ui->actionUpdateItem->text()); + auto update = updateMenu->addAction(ui->actionUpdateItem->text()); connect(update, &QAction::triggered, this, &ResourcePackPage::updateResourcePacks); updateMenu->addAction(ui->actionResetItemMetadata); @@ -75,15 +75,14 @@ void ResourcePackPage::updateFrame(const QModelIndex& current, [[maybe_unused]] { auto sourceCurrent = m_filterModel->mapToSource(current); int row = sourceCurrent.row(); - auto& rp = m_model->at(row); + auto& rp = static_cast(m_model->at(row)); ui->frame->updateWithResourcePack(rp); } void ResourcePackPage::downloadResourcePacks() { - if (m_instance->typeName() != "Minecraft") { + if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - } m_downloadDialog = new ResourceDownload::ResourcePackDownloadDialog(this, m_model, m_instance); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); @@ -94,9 +93,9 @@ void ResourcePackPage::downloadResourcePacks() void ResourcePackPage::downloadDialogFinished(int result) { - if (result != 0) { - auto* tasks = new ConcurrentTask("Download Resource Pack", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](const QString& reason) { + if (result) { + auto tasks = new ConcurrentTask("Download Resource Pack", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -106,9 +105,8 @@ void ResourcePackPage::downloadDialogFinished(int result) }); connect(tasks, &Task::succeeded, [this, tasks]() { QStringList warnings = tasks->warnings(); - if (warnings.count()) { + if (warnings.count()) CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); - } tasks->deleteLater(); }); @@ -127,17 +125,16 @@ void ResourcePackPage::downloadDialogFinished(int result) m_model->update(); } - if (m_downloadDialog) { + if (m_downloadDialog) m_downloadDialog->deleteLater(); - } } void ResourcePackPage::updateResourcePacks() { - if (m_instance->typeName() != "Minecraft") { + if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - } + auto profile = static_cast(m_instance)->getPackProfile(); if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Resource pack updates are unavailable when metadata is disabled!")); return; @@ -151,29 +148,27 @@ void ResourcePackPage::updateResourcePacks() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) { + if (response != QMessageBox::Yes) return; - } } auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - auto modsList = m_model->selectedResources(selection); - bool useAll = modsList.empty(); - if (useAll) { - modsList = m_model->allResources(); - } + auto mods_list = m_model->selectedResources(selection); + bool use_all = mods_list.empty(); + if (use_all) + mods_list = m_model->allResources(); - ResourceUpdateDialog updateDialog(this, m_instance, m_model, modsList, false); - updateDialog.checkCandidates(); + ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false); + update_dialog.checkCandidates(); - if (updateDialog.aborted()) { + if (update_dialog.aborted()) { CustomMessageBox::selectable(this, tr("Aborted"), tr("The resource pack updater was aborted!"), QMessageBox::Warning)->show(); return; } - if (updateDialog.noUpdates()) { - QString message{ tr("'%1' is up-to-date! :)").arg(modsList.front()->name()) }; - if (modsList.size() > 1) { - if (useAll) { + if (update_dialog.noUpdates()) { + QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) }; + if (mods_list.size() > 1) { + if (use_all) { message = tr("All resource packs are up-to-date! :)"); } else { message = tr("All selected resource packs are up-to-date! :)"); @@ -183,9 +178,9 @@ void ResourcePackPage::updateResourcePacks() return; } - if (updateDialog.exec() != 0) { - auto* tasks = new ConcurrentTask("Download Resource Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](const QString& reason) { + if (update_dialog.exec()) { + auto tasks = new ConcurrentTask("Download Resource Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -201,7 +196,7 @@ void ResourcePackPage::updateResourcePacks() tasks->deleteLater(); }); - for (const auto& task : updateDialog.getTasks()) { + for (auto task : update_dialog.getTasks()) { tasks->addTask(task); } @@ -217,9 +212,8 @@ void ResourcePackPage::deleteResourcePackMetadata() { auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); auto selectionCount = m_model->selectedResourcePacks(selection).length(); - if (selectionCount == 0) { + if (selectionCount == 0) return; - } if (selectionCount > 1) { auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), tr("You are about to remove the metadata for %1 resource packs.\n" @@ -228,9 +222,8 @@ void ResourcePackPage::deleteResourcePackMetadata() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) { + if (response != QMessageBox::Yes) return; - } } m_model->deleteMetadata(selection); @@ -238,9 +231,8 @@ void ResourcePackPage::deleteResourcePackMetadata() void ResourcePackPage::changeResourcePackVersion() { - if (m_instance->typeName() != "Minecraft") { + if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Resource pack updates are unavailable when metadata is disabled!")); @@ -249,17 +241,15 @@ void ResourcePackPage::changeResourcePackVersion() const QModelIndexList rows = ui->treeView->selectionModel()->selectedRows(); - if (rows.count() != 1) { + if (rows.count() != 1) return; - } Resource& resource = m_model->at(m_filterModel->mapToSource(rows[0]).row()); - if (resource.metadata() == nullptr) { + if (resource.metadata() == nullptr) return; - } - m_downloadDialog = new ResourceDownload::ResourcePackDownloadDialog(this, m_model, m_instance, true); + m_downloadDialog = new ResourceDownload::ResourcePackDownloadDialog(this, m_model, m_instance); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); connect(m_downloadDialog, &QDialog::finished, this, &ResourcePackPage::downloadDialogFinished); diff --git a/launcher/ui/pages/instance/ResourcePackPage.h b/launcher/ui/pages/instance/ResourcePackPage.h index 4e673e98c..0ad24fc45 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.h +++ b/launcher/ui/pages/instance/ResourcePackPage.h @@ -48,7 +48,7 @@ class ResourcePackPage : public ExternalResourcesPage { Q_OBJECT public: - explicit ResourcePackPage(MinecraftInstance* instance, ResourcePackFolderModel* model, QWidget* parent = 0); + explicit ResourcePackPage(MinecraftInstance* instance, std::shared_ptr model, QWidget* parent = 0); QString displayName() const override { return tr("Resource Packs"); } QIcon icon() const override { return QIcon::fromTheme("resourcepacks"); } @@ -71,6 +71,6 @@ class ResourcePackPage : public ExternalResourcesPage { void changeResourcePackVersion(); protected: - ResourcePackFolderModel* m_model; + std::shared_ptr m_model; QPointer m_downloadDialog; }; diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp index 71dc7218e..dc0290e1b 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.cpp +++ b/launcher/ui/pages/instance/ScreenshotsPage.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -52,11 +53,8 @@ #include #include #include -#include -#include #include -#include "settings/SettingsObject.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/ProgressDialog.h" @@ -70,36 +68,14 @@ #include #include "RWStorage.h" -class ScreenshotsFSModel : public QFileSystemModel { - public: - bool canDropMimeData(const QMimeData* data, - const Qt::DropAction action, - const int row, - const int column, - const QModelIndex& parent) const override - { - const QUrl root = QUrl::fromLocalFile(rootPath()); - // this disables reordering items inside the model - // by rejecting drops if the file is already inside the folder - if (data->hasUrls()) { - for (auto& url : data->urls()) { - if (root.isParentOf(url)) { - return false; - } - } - } - return QFileSystemModel::canDropMimeData(data, action, row, column, parent); - } -}; - using SharedIconCache = RWStorage; using SharedIconCachePtr = std::shared_ptr; class ThumbnailingResult : public QObject { Q_OBJECT public slots: - void emitResultsReady(const QString& path) { emit resultsReady(path); } - void emitResultsFailed(const QString& path) { emit resultsFailed(path); } + inline void emitResultsReady(const QString& path) { emit resultsReady(path); } + inline void emitResultsFailed(const QString& path) { emit resultsFailed(path); } signals: void resultsReady(const QString& path); void resultsFailed(const QString& path); @@ -107,32 +83,32 @@ class ThumbnailingResult : public QObject { class ThumbnailRunnable : public QRunnable { public: - ThumbnailRunnable(QString path, SharedIconCachePtr cache) : m_path(std::move(path)), m_cache(std::move(cache)) {} - void run() override + ThumbnailRunnable(QString path, SharedIconCachePtr cache) { - const QFileInfo info(m_path); - if (info.isDir()) { + m_path = path; + m_cache = cache; + } + void run() + { + QFileInfo info(m_path); + if (info.isDir()) return; - } - if (info.suffix().compare("png", Qt::CaseInsensitive) != 0) { + if ((info.suffix().compare("png", Qt::CaseInsensitive) != 0)) return; - } - if (!m_cache->stale(m_path)) { + if (!m_cache->stale(m_path)) return; - } - const QImage image(m_path); + QImage image(m_path); if (image.isNull()) { m_resultEmitter.emitResultsFailed(m_path); qDebug() << "Error loading screenshot (perhaps too large?):" + m_path; return; } QImage small; - if (image.width() > image.height()) { + if (image.width() > image.height()) small = image.scaledToWidth(512).scaledToWidth(256, Qt::SmoothTransformation); - } else { + else small = image.scaledToHeight(512).scaledToHeight(256, Qt::SmoothTransformation); - } - const QPoint offset((256 - small.width()) / 2, (256 - small.height()) / 2); + QPoint offset((256 - small.width()) / 2, (256 - small.height()) / 2); QImage square(QSize(256, 256), QImage::Format_ARGB32); square.fill(Qt::transparent); @@ -140,7 +116,7 @@ class ThumbnailRunnable : public QRunnable { painter.drawImage(offset, small); painter.end(); - const QIcon icon(QPixmap::fromImage(square)); + QIcon icon(QPixmap::fromImage(square)); m_cache->add(m_path, icon); m_resultEmitter.emitResultsReady(m_path); } @@ -154,62 +130,59 @@ class ThumbnailRunnable : public QRunnable { class FilterModel : public QIdentityProxyModel { Q_OBJECT public: - explicit FilterModel(QObject* parent = nullptr) : QIdentityProxyModel(parent) + explicit FilterModel(QObject* parent = 0) : QIdentityProxyModel(parent) { m_thumbnailingPool.setMaxThreadCount(4); m_thumbnailCache = std::make_shared(); m_thumbnailCache->add("placeholder", QIcon::fromTheme("screenshot-placeholder")); connect(&watcher, &QFileSystemWatcher::fileChanged, this, &FilterModel::fileChanged); } - ~FilterModel() override + virtual ~FilterModel() { m_thumbnailingPool.clear(); - if (!m_thumbnailingPool.waitForDone(500)) { + if (!m_thumbnailingPool.waitForDone(500)) qDebug() << "Thumbnail pool took longer than 500ms to finish"; - } } - QVariant data(const QModelIndex& proxyIndex, const int role = Qt::DisplayRole) const override // NOLINT(*-default-arguments) + virtual QVariant data(const QModelIndex& proxyIndex, int role = Qt::DisplayRole) const { - const auto* model = sourceModel(); - if (!model) { - return {}; - } + auto model = sourceModel(); + if (!model) + return QVariant(); if (role == Qt::DisplayRole || role == Qt::EditRole) { - const QVariant result = model->data(mapToSource(proxyIndex), role); + QVariant result = sourceModel()->data(mapToSource(proxyIndex), role); static const QRegularExpression s_removeChars("\\.png$"); return result.toString().remove(s_removeChars); } if (role == Qt::DecorationRole) { - const QVariant result = model->data(mapToSource(proxyIndex), QFileSystemModel::FilePathRole); - const QString filePath = result.toString(); + QVariant result = sourceModel()->data(mapToSource(proxyIndex), QFileSystemModel::FilePathRole); + QString filePath = result.toString(); + QIcon temp; if (!watched.contains(filePath)) { - const_cast(watcher).addPath(filePath); - const_cast&>(watched).insert(filePath); + ((QFileSystemWatcher&)watcher).addPath(filePath); + ((QSet&)watched).insert(filePath); } - if (QIcon temp; m_thumbnailCache->get(filePath, temp)) { + if (m_thumbnailCache->get(filePath, temp)) { return temp; } if (!m_failed.contains(filePath)) { - const_cast(this)->thumbnailImage(filePath); + ((FilterModel*)this)->thumbnailImage(filePath); } return (m_thumbnailCache->get("placeholder")); } - return model->data(mapToSource(proxyIndex), role); + return sourceModel()->data(mapToSource(proxyIndex), role); } - bool setData(const QModelIndex& index, const QVariant& value, const int role = Qt::EditRole) override // NOLINT(*-default-arguments) + virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) { - auto* model = sourceModel(); - if (!model) { + auto model = sourceModel(); + if (!model) return false; - } - if (role != Qt::EditRole) { + if (role != Qt::EditRole) return false; - } // FIXME: this is a workaround for a bug in QFileSystemModel, where it doesn't // sort after renames { - static_cast(model)->setNameFilterDisables(true); - static_cast(model)->setNameFilterDisables(false); + ((QFileSystemModel*)model)->setNameFilterDisables(true); + ((QFileSystemModel*)model)->setNameFilterDisables(false); } return model->setData(mapToSource(index), value.toString() + ".png", role); } @@ -217,15 +190,15 @@ class FilterModel : public QIdentityProxyModel { private: void thumbnailImage(QString path) { - auto* runnable = new ThumbnailRunnable(std::move(path), m_thumbnailCache); + auto runnable = new ThumbnailRunnable(path, m_thumbnailCache); connect(&runnable->m_resultEmitter, &ThumbnailingResult::resultsReady, this, &FilterModel::thumbnailReady); connect(&runnable->m_resultEmitter, &ThumbnailingResult::resultsFailed, this, &FilterModel::thumbnailFailed); m_thumbnailingPool.start(runnable); } private slots: - void thumbnailReady(const QString& /*path*/) { emit layoutChanged(); } - void thumbnailFailed(const QString& path) { m_failed.insert(path); } - void fileChanged(const QString& filepath) + void thumbnailReady(QString path) { emit layoutChanged(); } + void thumbnailFailed(QString path) { m_failed.insert(path); } + void fileChanged(QString filepath) { m_thumbnailCache->setStale(filepath); // reinsert the path... @@ -246,12 +219,13 @@ class FilterModel : public QIdentityProxyModel { class CenteredEditingDelegate : public QStyledItemDelegate { public: - explicit CenteredEditingDelegate(QObject* parent = nullptr) : QStyledItemDelegate(parent) {} - ~CenteredEditingDelegate() override = default; - QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override + explicit CenteredEditingDelegate(QObject* parent = 0) : QStyledItemDelegate(parent) {} + virtual ~CenteredEditingDelegate() {} + virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { - auto* widget = QStyledItemDelegate::createEditor(parent, option, index); - if (auto* foo = dynamic_cast(widget)) { + auto widget = QStyledItemDelegate::createEditor(parent, option, index); + auto foo = dynamic_cast(widget); + if (foo) { foo->setAlignment(Qt::AlignHCenter); foo->setFrame(true); foo->setMaximumWidth(192); @@ -260,11 +234,10 @@ class CenteredEditingDelegate : public QStyledItemDelegate { } }; -ScreenshotsPage::ScreenshotsPage(QString path, QWidget* parent) - : QMainWindow(parent), ui(new Ui::ScreenshotsPage), m_folder(std::move(path)) +ScreenshotsPage::ScreenshotsPage(QString path, QWidget* parent) : QMainWindow(parent), ui(new Ui::ScreenshotsPage) { - m_model = std::make_shared(); - m_filterModel = std::make_shared(); + m_model.reset(new QFileSystemModel()); + m_filterModel.reset(new FilterModel()); m_filterModel->setSourceModel(m_model.get()); m_model->setFilter(QDir::Files); m_model->setReadOnly(false); @@ -275,6 +248,7 @@ ScreenshotsPage::ScreenshotsPage(QString path, QWidget* parent) constexpr int file_modified_column_index = 3; m_model->sort(file_modified_column_index, Qt::DescendingOrder); + m_folder = path; m_valid = FS::ensureFolderPathExists(m_folder); ui->setupUi(this); @@ -291,19 +265,18 @@ ScreenshotsPage::ScreenshotsPage(QString path, QWidget* parent) ui->listView->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->listView->setItemDelegate(new CenteredEditingDelegate(this)); ui->listView->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->listView, &QListView::customContextMenuRequested, this, &ScreenshotsPage::showContextMenu); + connect(ui->listView, &QListView::customContextMenuRequested, this, &ScreenshotsPage::ShowContextMenu); connect(ui->listView, &QAbstractItemView::activated, this, &ScreenshotsPage::onItemActivated); } bool ScreenshotsPage::eventFilter(QObject* obj, QEvent* evt) { - if (obj != ui->listView) { + if (obj != ui->listView) return QWidget::eventFilter(obj, evt); - } if (evt->type() != QEvent::KeyPress) { return QWidget::eventFilter(obj, evt); } - const auto* keyEvent = static_cast(evt); + QKeyEvent* keyEvent = static_cast(evt); if (keyEvent->matches(QKeySequence::Copy)) { on_actionCopy_File_s_triggered(); @@ -333,11 +306,11 @@ ScreenshotsPage::~ScreenshotsPage() delete ui; } -void ScreenshotsPage::showContextMenu(const QPoint& pos) +void ScreenshotsPage::ShowContextMenu(const QPoint& pos) { - auto* menu = ui->toolBar->createContextMenu(this, tr("Context menu")); + auto menu = ui->toolBar->createContextMenu(this, tr("Context menu")); - if (ui->listView->selectionModel()->selectedIndexes().size() > 1) { + if (ui->listView->selectionModel()->selectedRows().size() > 1) { menu->removeAction(ui->actionCopy_Image); } @@ -352,75 +325,66 @@ QMenu* ScreenshotsPage::createPopupMenu() return filteredMenu; } -void ScreenshotsPage::onItemActivated(QModelIndex index) const +void ScreenshotsPage::onItemActivated(QModelIndex index) { - if (!index.isValid()) { + if (!index.isValid()) return; - } - const auto info = m_model->fileInfo(index); + auto info = m_model->fileInfo(index); DesktopServices::openPath(info); } -void ScreenshotsPage::onCurrentSelectionChanged(const QItemSelection& /*selected*/) const +void ScreenshotsPage::onCurrentSelectionChanged(const QItemSelection& selected) { - const auto selected = ui->listView->selectionModel()->selectedIndexes(); - bool allReadable = !selected.isEmpty(); bool allWritable = !selected.isEmpty(); - for (auto index : selected) { - if (!index.isValid()) { + for (auto index : selected.indexes()) { + if (!index.isValid()) break; - } auto info = m_model->fileInfo(index); - if (!info.isReadable()) { + if (!info.isReadable()) allReadable = false; - } - if (!info.isWritable()) { + if (!info.isWritable()) allWritable = false; - } } ui->actionUpload->setEnabled(allReadable); - ui->actionCopy_Image->setEnabled(allReadable && selected.size() == 1); + ui->actionCopy_Image->setEnabled(allReadable); ui->actionCopy_File_s->setEnabled(allReadable); ui->actionDelete->setEnabled(allWritable); ui->actionRename->setEnabled(allWritable); } -void ScreenshotsPage::on_actionView_Folder_triggered() const +void ScreenshotsPage::on_actionView_Folder_triggered() { DesktopServices::openPath(m_folder, true); } void ScreenshotsPage::on_actionUpload_triggered() { - auto selection = ui->listView->selectionModel()->selectedIndexes(); - if (selection.isEmpty()) { + auto selection = ui->listView->selectionModel()->selectedRows(); + if (selection.isEmpty()) return; - } QString text; - const QUrl baseUrl(BuildConfig.IMGUR_BASE_URL); - if (selection.size() > 1) { + QUrl baseUrl(BuildConfig.IMGUR_BASE_URL); + if (selection.size() > 1) text = tr("You are about to upload %1 screenshots to %2.\n" "You should double-check for personal information.\n\n" "Are you sure?") .arg(QString::number(selection.size()), baseUrl.host()); - } else { + else text = tr("You are about to upload the selected screenshot to %1.\n" "You should double-check for personal information.\n\n" "Are you sure?") .arg(baseUrl.host()); - } auto response = CustomMessageBox::selectable(this, "Confirm Upload", text, QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) { + if (response != QMessageBox::Yes) return; - } QList uploaded; auto job = NetJob::Ptr(new NetJob("Screenshot Upload", APPLICATION->network())); @@ -434,7 +398,7 @@ void ScreenshotsPage::on_actionUpload_triggered() auto screenshot = std::make_shared(info); job->addNetAction(ImgurUpload::make(screenshot)); - connect(job.get(), &Task::failed, [this](const QString& reason) { + connect(job.get(), &Task::failed, [this](QString reason) { CustomMessageBox::selectable(this, tr("Failed to upload screenshots!"), reason, QMessageBox::Critical)->show(); }); connect(job.get(), &Task::aborted, [this] { @@ -475,7 +439,7 @@ void ScreenshotsPage::on_actionUpload_triggered() task.addTask(job); task.addTask(albumTask); - connect(&task, &Task::failed, [this](const QString& reason) { + connect(&task, &Task::failed, [this](QString reason) { CustomMessageBox::selectable(this, tr("Failed to upload screenshots!"), reason, QMessageBox::Critical)->show(); }); connect(&task, &Task::aborted, [this] { @@ -503,24 +467,24 @@ void ScreenshotsPage::on_actionUpload_triggered() m_uploadActive = false; } -void ScreenshotsPage::on_actionCopy_Image_triggered() const +void ScreenshotsPage::on_actionCopy_Image_triggered() { - auto selection = ui->listView->selectionModel()->selectedIndexes(); + auto selection = ui->listView->selectionModel()->selectedRows(); if (selection.size() < 1) { return; } // You can only copy one image to the clipboard. In the case of multiple selected files, only the first one gets copied. - const auto item = selection.first(); - const auto info = m_model->fileInfo(item); - const QImage image(info.absoluteFilePath()); + auto item = selection[0]; + auto info = m_model->fileInfo(item); + QImage image(info.absoluteFilePath()); Q_ASSERT(!image.isNull()); QApplication::clipboard()->setImage(image, QClipboard::Clipboard); } -void ScreenshotsPage::on_actionCopy_File_s_triggered() const +void ScreenshotsPage::on_actionCopy_File_s_triggered() { - auto selection = ui->listView->selectionModel()->selectedIndexes(); + auto selection = ui->listView->selectionModel()->selectedRows(); if (selection.size() < 1) { // Don't do anything so we don't empty the users clipboard return; @@ -531,7 +495,7 @@ void ScreenshotsPage::on_actionCopy_File_s_triggered() const auto info = m_model->fileInfo(item); buf += "file:///" + info.absoluteFilePath() + "\r\n"; } - auto* mimeData = new QMimeData(); + QMimeData* mimeData = new QMimeData(); mimeData->setData("text/uri-list", buf.toLocal8Bit()); QApplication::clipboard()->setMimeData(mimeData); } @@ -540,43 +504,39 @@ void ScreenshotsPage::on_actionDelete_triggered() { auto selected = ui->listView->selectionModel()->selectedIndexes(); - const qsizetype count = selected.size(); + int count = ui->listView->selectionModel()->selectedRows().size(); QString text; - if (count > 1) { + if (count > 1) text = tr("You are about to delete %1 screenshots.\n" "This may be permanent and they will be gone from the folder.\n\n" "Are you sure?") .arg(count); - } else { - text = - tr("You are about to delete the selected screenshot.\n" - "This may be permanent and it will be gone from the folder.\n\n" - "Are you sure?"); - } + else + text = tr("You are about to delete the selected screenshot.\n" + "This may be permanent and it will be gone from the folder.\n\n" + "Are you sure?") + .arg(count); - const auto response = + auto response = CustomMessageBox::selectable(this, tr("Confirm Deletion"), text, QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No)->exec(); - if (response != QMessageBox::Yes) { + if (response != QMessageBox::Yes) return; - } for (auto item : selected) { - if (FS::trash(m_model->filePath(item))) { + if (FS::trash(m_model->filePath(item))) continue; - } m_model->remove(item); } } -void ScreenshotsPage::on_actionRename_triggered() const +void ScreenshotsPage::on_actionRename_triggered() { auto selection = ui->listView->selectionModel()->selectedIndexes(); - if (selection.isEmpty()) { + if (selection.isEmpty()) return; - } - ui->listView->edit(selection.first()); + ui->listView->edit(selection[0]); // TODO: mass renaming } @@ -586,8 +546,8 @@ void ScreenshotsPage::openedImpl() m_valid = FS::ensureFolderPathExists(m_folder); } if (m_valid) { - const QString path = QDir(m_folder).absolutePath(); - const auto idx = m_model->setRootPath(path); + QString path = QDir(m_folder).absolutePath(); + auto idx = m_model->setRootPath(path); if (idx.isValid()) { ui->listView->setModel(m_filterModel.get()); connect(ui->listView->selectionModel(), &QItemSelectionModel::selectionChanged, this, @@ -599,7 +559,7 @@ void ScreenshotsPage::openedImpl() } } - const auto setting_name = QString("WideBarVisibility_%1").arg(id()); + auto const setting_name = QString("WideBarVisibility_%1").arg(id()); m_wide_bar_setting = APPLICATION->settings()->getOrRegisterSetting(setting_name); ui->toolBar->setVisibilityState(QByteArray::fromBase64(m_wide_bar_setting->get().toString().toUtf8())); diff --git a/launcher/ui/pages/instance/ScreenshotsPage.h b/launcher/ui/pages/instance/ScreenshotsPage.h index 7d1cf4fcc..b9c750a1f 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.h +++ b/launcher/ui/pages/instance/ScreenshotsPage.h @@ -41,14 +41,13 @@ #include "settings/Setting.h" +class QFileSystemModel; class QIdentityProxyModel; class QItemSelection; namespace Ui { class ScreenshotsPage; } -class ScreenshotsFSModel; - struct ScreenShot; class ScreenshotList; class ImgurAlbumCreation; @@ -78,18 +77,18 @@ class ScreenshotsPage : public QMainWindow, public BasePage { private slots: void on_actionUpload_triggered(); - void on_actionCopy_Image_triggered() const; - void on_actionCopy_File_s_triggered() const; + void on_actionCopy_Image_triggered(); + void on_actionCopy_File_s_triggered(); void on_actionDelete_triggered(); - void on_actionRename_triggered() const; - void on_actionView_Folder_triggered() const; - void onItemActivated(QModelIndex) const; - void onCurrentSelectionChanged(const QItemSelection& selected) const; - void showContextMenu(const QPoint& pos); + void on_actionRename_triggered(); + void on_actionView_Folder_triggered(); + void onItemActivated(QModelIndex); + void onCurrentSelectionChanged(const QItemSelection& selected); + void ShowContextMenu(const QPoint& pos); private: Ui::ScreenshotsPage* ui; - std::shared_ptr m_model; + std::shared_ptr m_model; std::shared_ptr m_filterModel; QString m_folder; bool m_valid = false; diff --git a/launcher/ui/pages/instance/ScreenshotsPage.ui b/launcher/ui/pages/instance/ScreenshotsPage.ui index 4ed92c4b6..db55869cd 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.ui +++ b/launcher/ui/pages/instance/ScreenshotsPage.ui @@ -33,13 +33,10 @@ QAbstractItemView::SelectionMode::ExtendedSelection - QAbstractItemView::SelectionBehavior::SelectItems - - - QAbstractItemView::ScrollMode::ScrollPerPixel + QAbstractItemView::SelectionBehavior::SelectRows - QListView::Movement::Snap + QListView::Movement::Static diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 9012ebd5b..59ae6e43f 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -548,7 +548,7 @@ class ServersModel : public QAbstractListModel { ConcurrentTask::Ptr m_currentQueryTask = nullptr; }; -ServersPage::ServersPage(BaseInstance* inst, QWidget* parent) : QMainWindow(parent), ui(new Ui::ServersPage) +ServersPage::ServersPage(InstancePtr inst, QWidget* parent) : QMainWindow(parent), ui(new Ui::ServersPage) { ui->setupUi(this); m_inst = inst; @@ -568,7 +568,7 @@ ServersPage::ServersPage(BaseInstance* inst, QWidget* parent) : QMainWindow(pare auto selectionModel = ui->serversView->selectionModel(); connect(selectionModel, &QItemSelectionModel::currentChanged, this, &ServersPage::currentChanged); - connect(m_inst, &MinecraftInstance::runningStatusChanged, this, &ServersPage::runningStateChanged); + connect(m_inst.get(), &MinecraftInstance::runningStatusChanged, this, &ServersPage::runningStateChanged); connect(ui->nameLine, &QLineEdit::textEdited, this, &ServersPage::nameEdited); connect(ui->addressLine, &QLineEdit::textEdited, this, &ServersPage::addressEdited); connect(ui->resourceComboBox, &QComboBox::currentIndexChanged, this, &ServersPage::resourceIndexChanged); @@ -757,7 +757,7 @@ void ServersPage::on_actionMove_Down_triggered() void ServersPage::on_actionJoin_triggered() { const auto& address = m_model->at(currentServer)->m_address; - APPLICATION->launch(m_inst, LaunchMode::Normal, std::make_shared(MinecraftTarget::parse(address, false))); + APPLICATION->launch(m_inst, true, false, std::make_shared(MinecraftTarget::parse(address, false))); } void ServersPage::on_actionRefresh_triggered() diff --git a/launcher/ui/pages/instance/ServersPage.h b/launcher/ui/pages/instance/ServersPage.h index 0eec57de1..49a746245 100644 --- a/launcher/ui/pages/instance/ServersPage.h +++ b/launcher/ui/pages/instance/ServersPage.h @@ -56,7 +56,7 @@ class ServersPage : public QMainWindow, public BasePage { Q_OBJECT public: - explicit ServersPage(BaseInstance* inst, QWidget* parent = 0); + explicit ServersPage(InstancePtr inst, QWidget* parent = 0); virtual ~ServersPage(); void openedImpl() override; @@ -100,7 +100,7 @@ class ServersPage : public QMainWindow, public BasePage { bool m_locked = true; Ui::ServersPage* ui = nullptr; ServersModel* m_model = nullptr; - BaseInstance* m_inst = nullptr; + InstancePtr m_inst = nullptr; std::shared_ptr m_wide_bar_setting = nullptr; }; diff --git a/launcher/ui/pages/instance/ServersPage.ui b/launcher/ui/pages/instance/ServersPage.ui index 727b64e49..d330835c8 100644 --- a/launcher/ui/pages/instance/ServersPage.ui +++ b/launcher/ui/pages/instance/ServersPage.ui @@ -53,9 +53,6 @@ false - - QAbstractItemView::ScrollPerPixel - false diff --git a/launcher/ui/pages/instance/ShaderPackPage.cpp b/launcher/ui/pages/instance/ShaderPackPage.cpp index a29564abc..baf18da52 100644 --- a/launcher/ui/pages/instance/ShaderPackPage.cpp +++ b/launcher/ui/pages/instance/ShaderPackPage.cpp @@ -47,7 +47,7 @@ #include "ui/dialogs/ResourceDownloadDialog.h" #include "ui/dialogs/ResourceUpdateDialog.h" -ShaderPackPage::ShaderPackPage(MinecraftInstance* instance, ShaderPackFolderModel* model, QWidget* parent) +ShaderPackPage::ShaderPackPage(MinecraftInstance* instance, std::shared_ptr model, QWidget* parent) : ExternalResourcesPage(instance, model, parent), m_model(model) { ui->actionDownloadItem->setText(tr("Download Packs")); @@ -61,9 +61,9 @@ ShaderPackPage::ShaderPackPage(MinecraftInstance* instance, ShaderPackFolderMode connect(ui->actionUpdateItem, &QAction::triggered, this, &ShaderPackPage::updateShaderPacks); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem); - auto* updateMenu = new QMenu(this); + auto updateMenu = new QMenu(this); - auto* update = updateMenu->addAction(ui->actionUpdateItem->text()); + auto update = updateMenu->addAction(ui->actionUpdateItem->text()); connect(update, &QAction::triggered, this, &ShaderPackPage::updateShaderPacks); updateMenu->addAction(ui->actionResetItemMetadata); @@ -78,9 +78,8 @@ ShaderPackPage::ShaderPackPage(MinecraftInstance* instance, ShaderPackFolderMode void ShaderPackPage::downloadShaderPack() { - if (m_instance->typeName() != "Minecraft") { + if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - } m_downloadDialog = new ResourceDownload::ShaderPackDownloadDialog(this, m_model, m_instance); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); @@ -91,9 +90,9 @@ void ShaderPackPage::downloadShaderPack() void ShaderPackPage::downloadDialogFinished(int result) { - if (result != 0) { - auto* tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](const QString& reason) { + if (result) { + auto tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -103,9 +102,8 @@ void ShaderPackPage::downloadDialogFinished(int result) }); connect(tasks, &Task::succeeded, [this, tasks]() { QStringList warnings = tasks->warnings(); - if (warnings.count()) { + if (warnings.count()) CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); - } tasks->deleteLater(); }); @@ -124,17 +122,16 @@ void ShaderPackPage::downloadDialogFinished(int result) m_model->update(); } - if (m_downloadDialog) { + if (m_downloadDialog) m_downloadDialog->deleteLater(); - } } void ShaderPackPage::updateShaderPacks() { - if (m_instance->typeName() != "Minecraft") { + if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - } + auto profile = static_cast(m_instance)->getPackProfile(); if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Shader pack updates are unavailable when metadata is disabled!")); return; @@ -148,29 +145,27 @@ void ShaderPackPage::updateShaderPacks() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) { + if (response != QMessageBox::Yes) return; - } } auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - auto modsList = m_model->selectedResources(selection); - bool useAll = modsList.empty(); - if (useAll) { - modsList = m_model->allResources(); - } + auto mods_list = m_model->selectedResources(selection); + bool use_all = mods_list.empty(); + if (use_all) + mods_list = m_model->allResources(); - ResourceUpdateDialog updateDialog(this, m_instance, m_model, modsList, false); - updateDialog.checkCandidates(); + ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false); + update_dialog.checkCandidates(); - if (updateDialog.aborted()) { + if (update_dialog.aborted()) { CustomMessageBox::selectable(this, tr("Aborted"), tr("The shader pack updater was aborted!"), QMessageBox::Warning)->show(); return; } - if (updateDialog.noUpdates()) { - QString message{ tr("'%1' is up-to-date! :)").arg(modsList.front()->name()) }; - if (modsList.size() > 1) { - if (useAll) { + if (update_dialog.noUpdates()) { + QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) }; + if (mods_list.size() > 1) { + if (use_all) { message = tr("All shader packs are up-to-date! :)"); } else { message = tr("All selected shader packs are up-to-date! :)"); @@ -180,9 +175,9 @@ void ShaderPackPage::updateShaderPacks() return; } - if (updateDialog.exec() != 0) { - auto* tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](const QString& reason) { + if (update_dialog.exec()) { + auto tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -198,7 +193,7 @@ void ShaderPackPage::updateShaderPacks() tasks->deleteLater(); }); - for (const auto& task : updateDialog.getTasks()) { + for (auto task : update_dialog.getTasks()) { tasks->addTask(task); } @@ -214,9 +209,8 @@ void ShaderPackPage::deleteShaderPackMetadata() { auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); auto selectionCount = m_model->selectedShaderPacks(selection).length(); - if (selectionCount == 0) { + if (selectionCount == 0) return; - } if (selectionCount > 1) { auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), tr("You are about to remove the metadata for %1 shader packs.\n" @@ -225,9 +219,8 @@ void ShaderPackPage::deleteShaderPackMetadata() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) { + if (response != QMessageBox::Yes) return; - } } m_model->deleteMetadata(selection); @@ -235,9 +228,8 @@ void ShaderPackPage::deleteShaderPackMetadata() void ShaderPackPage::changeShaderPackVersion() { - if (m_instance->typeName() != "Minecraft") { + if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Shader pack updates are unavailable when metadata is disabled!")); @@ -246,17 +238,15 @@ void ShaderPackPage::changeShaderPackVersion() const QModelIndexList rows = ui->treeView->selectionModel()->selectedRows(); - if (rows.count() != 1) { + if (rows.count() != 1) return; - } Resource& resource = m_model->at(m_filterModel->mapToSource(rows[0]).row()); - if (resource.metadata() == nullptr) { + if (resource.metadata() == nullptr) return; - } - m_downloadDialog = new ResourceDownload::ShaderPackDownloadDialog(this, m_model, m_instance, true); + m_downloadDialog = new ResourceDownload::ShaderPackDownloadDialog(this, m_model, m_instance); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); connect(m_downloadDialog, &QDialog::finished, this, &ShaderPackPage::downloadDialogFinished); diff --git a/launcher/ui/pages/instance/ShaderPackPage.h b/launcher/ui/pages/instance/ShaderPackPage.h index cc53a01e1..c6ae3bc24 100644 --- a/launcher/ui/pages/instance/ShaderPackPage.h +++ b/launcher/ui/pages/instance/ShaderPackPage.h @@ -44,7 +44,7 @@ class ShaderPackPage : public ExternalResourcesPage { Q_OBJECT public: - explicit ShaderPackPage(MinecraftInstance* instance, ShaderPackFolderModel* model, QWidget* parent = nullptr); + explicit ShaderPackPage(MinecraftInstance* instance, std::shared_ptr model, QWidget* parent = nullptr); ~ShaderPackPage() override = default; QString displayName() const override { return tr("Shader Packs"); } @@ -62,6 +62,6 @@ class ShaderPackPage : public ExternalResourcesPage { void changeShaderPackVersion(); private: - ShaderPackFolderModel* m_model; + std::shared_ptr m_model; QPointer m_downloadDialog; }; diff --git a/launcher/ui/pages/instance/TexturePackPage.cpp b/launcher/ui/pages/instance/TexturePackPage.cpp index 01325e3f6..163a14c86 100644 --- a/launcher/ui/pages/instance/TexturePackPage.cpp +++ b/launcher/ui/pages/instance/TexturePackPage.cpp @@ -46,7 +46,7 @@ #include "ui/dialogs/ResourceDownloadDialog.h" #include "ui/dialogs/ResourceUpdateDialog.h" -TexturePackPage::TexturePackPage(MinecraftInstance* instance, TexturePackFolderModel* model, QWidget* parent) +TexturePackPage::TexturePackPage(MinecraftInstance* instance, std::shared_ptr model, QWidget* parent) : ExternalResourcesPage(instance, model, parent), m_model(model) { ui->actionDownloadItem->setText(tr("Download Packs")); @@ -60,9 +60,9 @@ TexturePackPage::TexturePackPage(MinecraftInstance* instance, TexturePackFolderM connect(ui->actionUpdateItem, &QAction::triggered, this, &TexturePackPage::updateTexturePacks); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem); - auto* updateMenu = new QMenu(this); + auto updateMenu = new QMenu(this); - auto* update = updateMenu->addAction(ui->actionUpdateItem->text()); + auto update = updateMenu->addAction(ui->actionUpdateItem->text()); connect(update, &QAction::triggered, this, &TexturePackPage::updateTexturePacks); updateMenu->addAction(ui->actionResetItemMetadata); @@ -81,15 +81,14 @@ void TexturePackPage::updateFrame(const QModelIndex& current, [[maybe_unused]] c { auto sourceCurrent = m_filterModel->mapToSource(current); int row = sourceCurrent.row(); - auto& rp = m_model->at(row); + auto& rp = static_cast(m_model->at(row)); ui->frame->updateWithTexturePack(rp); } void TexturePackPage::downloadTexturePacks() { - if (m_instance->typeName() != "Minecraft") { + if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - } m_downloadDialog = new ResourceDownload::TexturePackDownloadDialog(this, m_model, m_instance); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); @@ -99,9 +98,9 @@ void TexturePackPage::downloadTexturePacks() void TexturePackPage::downloadDialogFinished(int result) { - if (result != 0) { - auto* tasks = new ConcurrentTask("Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](const QString& reason) { + if (result) { + auto tasks = new ConcurrentTask("Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -111,9 +110,8 @@ void TexturePackPage::downloadDialogFinished(int result) }); connect(tasks, &Task::succeeded, [this, tasks]() { QStringList warnings = tasks->warnings(); - if (warnings.count()) { + if (warnings.count()) CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); - } tasks->deleteLater(); }); @@ -132,17 +130,16 @@ void TexturePackPage::downloadDialogFinished(int result) m_model->update(); } - if (m_downloadDialog) { + if (m_downloadDialog) m_downloadDialog->deleteLater(); - } } void TexturePackPage::updateTexturePacks() { - if (m_instance->typeName() != "Minecraft") { + if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - } + auto profile = static_cast(m_instance)->getPackProfile(); if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Texture pack updates are unavailable when metadata is disabled!")); return; @@ -156,29 +153,27 @@ void TexturePackPage::updateTexturePacks() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) { + if (response != QMessageBox::Yes) return; - } } auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - auto modsList = m_model->selectedResources(selection); - bool useAll = modsList.empty(); - if (useAll) { - modsList = m_model->allResources(); - } + auto mods_list = m_model->selectedResources(selection); + bool use_all = mods_list.empty(); + if (use_all) + mods_list = m_model->allResources(); - ResourceUpdateDialog updateDialog(this, m_instance, m_model, modsList, false); - updateDialog.checkCandidates(); + ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false); + update_dialog.checkCandidates(); - if (updateDialog.aborted()) { + if (update_dialog.aborted()) { CustomMessageBox::selectable(this, tr("Aborted"), tr("The texture pack updater was aborted!"), QMessageBox::Warning)->show(); return; } - if (updateDialog.noUpdates()) { - QString message{ tr("'%1' is up-to-date! :)").arg(modsList.front()->name()) }; - if (modsList.size() > 1) { - if (useAll) { + if (update_dialog.noUpdates()) { + QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) }; + if (mods_list.size() > 1) { + if (use_all) { message = tr("All texture packs are up-to-date! :)"); } else { message = tr("All selected texture packs are up-to-date! :)"); @@ -188,9 +183,9 @@ void TexturePackPage::updateTexturePacks() return; } - if (updateDialog.exec() != 0) { - auto* tasks = new ConcurrentTask("Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](const QString& reason) { + if (update_dialog.exec()) { + auto tasks = new ConcurrentTask("Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -206,7 +201,7 @@ void TexturePackPage::updateTexturePacks() tasks->deleteLater(); }); - for (const auto& task : updateDialog.getTasks()) { + for (auto task : update_dialog.getTasks()) { tasks->addTask(task); } @@ -222,9 +217,8 @@ void TexturePackPage::deleteTexturePackMetadata() { auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); auto selectionCount = m_model->selectedTexturePacks(selection).length(); - if (selectionCount == 0) { + if (selectionCount == 0) return; - } if (selectionCount > 1) { auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), tr("You are about to remove the metadata for %1 texture packs.\n" @@ -233,9 +227,8 @@ void TexturePackPage::deleteTexturePackMetadata() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) { + if (response != QMessageBox::Yes) return; - } } m_model->deleteMetadata(selection); @@ -243,9 +236,8 @@ void TexturePackPage::deleteTexturePackMetadata() void TexturePackPage::changeTexturePackVersion() { - if (m_instance->typeName() != "Minecraft") { + if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Texture pack updates are unavailable when metadata is disabled!")); @@ -254,17 +246,15 @@ void TexturePackPage::changeTexturePackVersion() const QModelIndexList rows = ui->treeView->selectionModel()->selectedRows(); - if (rows.count() != 1) { + if (rows.count() != 1) return; - } Resource& resource = m_model->at(m_filterModel->mapToSource(rows[0]).row()); - if (resource.metadata() == nullptr) { + if (resource.metadata() == nullptr) return; - } - m_downloadDialog = new ResourceDownload::TexturePackDownloadDialog(this, m_model, m_instance, true); + m_downloadDialog = new ResourceDownload::TexturePackDownloadDialog(this, m_model, m_instance); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); connect(m_downloadDialog, &QDialog::finished, this, &TexturePackPage::downloadDialogFinished); diff --git a/launcher/ui/pages/instance/TexturePackPage.h b/launcher/ui/pages/instance/TexturePackPage.h index dad0affa4..2c92212b9 100644 --- a/launcher/ui/pages/instance/TexturePackPage.h +++ b/launcher/ui/pages/instance/TexturePackPage.h @@ -48,7 +48,7 @@ class TexturePackPage : public ExternalResourcesPage { Q_OBJECT public: - explicit TexturePackPage(MinecraftInstance* instance, TexturePackFolderModel* model, QWidget* parent = nullptr); + explicit TexturePackPage(MinecraftInstance* instance, std::shared_ptr model, QWidget* parent = nullptr); QString displayName() const override { return tr("Texture packs"); } QIcon icon() const override { return QIcon::fromTheme("resourcepacks"); } @@ -66,6 +66,6 @@ class TexturePackPage : public ExternalResourcesPage { void changeTexturePackVersion(); private: - TexturePackFolderModel* m_model; + std::shared_ptr m_model; QPointer m_downloadDialog; }; diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp index fea759bb2..ef5427a00 100644 --- a/launcher/ui/pages/instance/VersionPage.cpp +++ b/launcher/ui/pages/instance/VersionPage.cpp @@ -151,7 +151,7 @@ VersionPage::VersionPage(MinecraftInstance* inst, QWidget* parent) : QMainWindow reloadPackProfile(); auto proxy = new IconProxy(ui->packageView); - proxy->setSourceModel(m_profile); + proxy->setSourceModel(m_profile.get()); m_filterModel = new QSortFilterProxyModel(this); m_filterModel->setDynamicSortFilter(true); @@ -168,7 +168,7 @@ VersionPage::VersionPage(MinecraftInstance* inst, QWidget* parent) : QMainWindow auto smodel = ui->packageView->selectionModel(); connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent); connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::packageCurrent); - connect(m_profile, &PackProfile::minecraftChanged, this, &VersionPage::updateVersionControls); + connect(m_profile.get(), &PackProfile::minecraftChanged, this, &VersionPage::updateVersionControls); updateVersionControls(); preselect(0); connect(ui->packageView, &ModListView::customContextMenuRequested, this, &VersionPage::showContextMenu); diff --git a/launcher/ui/pages/instance/VersionPage.h b/launcher/ui/pages/instance/VersionPage.h index 493e3b8c8..602d09206 100644 --- a/launcher/ui/pages/instance/VersionPage.h +++ b/launcher/ui/pages/instance/VersionPage.h @@ -105,7 +105,7 @@ class VersionPage : public QMainWindow, public BasePage { private: Ui::VersionPage* ui; QSortFilterProxyModel* m_filterModel; - PackProfile* m_profile; + std::shared_ptr m_profile; MinecraftInstance* m_inst; int currentIdx = 0; diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index 71c370ab2..c7eaf94a0 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -86,7 +86,7 @@ class WorldListProxyModel : public QSortFilterProxyModel { } }; -WorldListPage::WorldListPage(MinecraftInstance* inst, WorldList* worlds, QWidget* parent) +WorldListPage::WorldListPage(MinecraftInstancePtr inst, std::shared_ptr worlds, QWidget* parent) : QMainWindow(parent), m_inst(inst), ui(new Ui::WorldListPage), m_worlds(worlds) { ui->setupUi(this); @@ -95,7 +95,7 @@ WorldListPage::WorldListPage(MinecraftInstance* inst, WorldList* worlds, QWidget WorldListProxyModel* proxy = new WorldListProxyModel(this); proxy->setSortCaseSensitivity(Qt::CaseInsensitive); - proxy->setSourceModel(m_worlds); + proxy->setSourceModel(m_worlds.get()); proxy->setSortRole(Qt::UserRole); ui->worldTreeView->setSortingEnabled(true); ui->worldTreeView->setModel(proxy); @@ -117,11 +117,12 @@ void WorldListPage::openedImpl() { m_worlds->startWatching(); - if (!m_inst || !m_inst->traits().contains("feature:is_quick_play_singleplayer")) { + auto mInst = std::dynamic_pointer_cast(m_inst); + if (!mInst || !mInst->traits().contains("feature:is_quick_play_singleplayer")) { ui->toolBar->removeAction(ui->actionJoin); } - const auto setting_name = QString("WideBarVisibility_%1").arg(id()); + auto const setting_name = QString("WideBarVisibility_%1").arg(id()); m_wide_bar_setting = APPLICATION->settings()->getOrRegisterSetting(setting_name); ui->toolBar->setVisibilityState(QByteArray::fromBase64(m_wide_bar_setting->get().toString().toUtf8())); @@ -236,10 +237,11 @@ void WorldListPage::on_actionData_Packs_triggered() GenericPageProvider provider(dialog->windowTitle()); - bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_datapackModel.reset(new DataPackFolderModel(folder, m_inst, isIndexed, true)); - - provider.addPageCreator([this] { return new DataPackPage(m_inst, m_datapackModel.get(), this); }); + provider.addPageCreator([this, folder] { + bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + auto model = std::make_shared(folder, m_inst.get(), isIndexed, true); + return new DataPackPage(m_inst.get(), std::move(model)); + }); auto layout = new QVBoxLayout(dialog); @@ -259,12 +261,9 @@ void WorldListPage::on_actionData_Packs_triggered() dialog->setLayout(layout); - dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->exec(); - connect(dialog, &QDialog::finished, this, - [dialog]() { APPLICATION->settings()->set("DataPackDownloadGeometry", dialog->saveGeometry().toBase64()); }); - - dialog->open(); + APPLICATION->settings()->set("DataPackDownloadGeometry", dialog->saveGeometry().toBase64()); } void WorldListPage::on_actionReset_Icon_triggered() @@ -381,7 +380,8 @@ void WorldListPage::worldChanged([[maybe_unused]] const QModelIndex& current, [[ bool hasIcon = !index.data(WorldList::IconFileRole).isNull(); ui->actionReset_Icon->setEnabled(enable && hasIcon); - auto supportsJoin = m_inst && m_inst->traits().contains("feature:is_quick_play_singleplayer"); + auto mInst = std::dynamic_pointer_cast(m_inst); + auto supportsJoin = mInst && mInst->traits().contains("feature:is_quick_play_singleplayer"); ui->actionJoin->setEnabled(enable && supportsJoin); if (!supportsJoin) { @@ -474,7 +474,7 @@ void WorldListPage::on_actionJoin_triggered() } auto worldVariant = m_worlds->data(index, WorldList::ObjectRole); auto world = (World*)worldVariant.value(); - APPLICATION->launch(m_inst, LaunchMode::Normal, std::make_shared(MinecraftTarget::parse(world->folderName(), true))); + APPLICATION->launch(m_inst, true, false, std::make_shared(MinecraftTarget::parse(world->folderName(), true))); } #include "WorldListPage.moc" diff --git a/launcher/ui/pages/instance/WorldListPage.h b/launcher/ui/pages/instance/WorldListPage.h index 0afb9883b..9b931066d 100644 --- a/launcher/ui/pages/instance/WorldListPage.h +++ b/launcher/ui/pages/instance/WorldListPage.h @@ -52,7 +52,7 @@ class WorldListPage : public QMainWindow, public BasePage { Q_OBJECT public: - explicit WorldListPage(MinecraftInstance* inst, WorldList* worlds, QWidget* parent = 0); + explicit WorldListPage(MinecraftInstancePtr inst, std::shared_ptr worlds, QWidget* parent = 0); virtual ~WorldListPage(); virtual QString displayName() const override { return tr("Worlds"); } @@ -71,7 +71,7 @@ class WorldListPage : public QMainWindow, public BasePage { QMenu* createPopupMenu() override; protected: - MinecraftInstance* m_inst; + MinecraftInstancePtr m_inst; private: QModelIndex getSelectedWorld(); @@ -81,12 +81,11 @@ class WorldListPage : public QMainWindow, public BasePage { private: Ui::WorldListPage* ui; - WorldList* m_worlds; + std::shared_ptr m_worlds; unique_qobject_ptr m_mceditProcess; bool m_mceditStarting = false; std::shared_ptr m_wide_bar_setting = nullptr; - std::unique_ptr m_datapackModel; private slots: void on_actionCopy_Seed_triggered(); diff --git a/launcher/ui/pages/instance/WorldListPage.ui b/launcher/ui/pages/instance/WorldListPage.ui index 2c19b9783..22c93256c 100644 --- a/launcher/ui/pages/instance/WorldListPage.ui +++ b/launcher/ui/pages/instance/WorldListPage.ui @@ -53,9 +53,6 @@ true - - QAbstractItemView::ScrollPerPixel - false diff --git a/launcher/ui/pages/modplatform/CustomPage.cpp b/launcher/ui/pages/modplatform/CustomPage.cpp index f24abf9fb..87e126fd7 100644 --- a/launcher/ui/pages/modplatform/CustomPage.cpp +++ b/launcher/ui/pages/modplatform/CustomPage.cpp @@ -80,14 +80,14 @@ void CustomPage::openedImpl() void CustomPage::refresh() { - ui->versionList->loadList(true); + ui->versionList->loadList(); } void CustomPage::loaderRefresh() { if (ui->noneFilter->isChecked()) return; - ui->loaderVersionList->loadList(true); + ui->loaderVersionList->loadList(); } void CustomPage::filterChanged() diff --git a/launcher/ui/pages/modplatform/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp index 6e783014f..c43d3e1fa 100644 --- a/launcher/ui/pages/modplatform/ImportPage.cpp +++ b/launcher/ui/pages/modplatform/ImportPage.cpp @@ -131,9 +131,10 @@ void ImportPage::updateState() } auto addonId = query.allQueryItemValues("addonId")[0]; auto fileId = query.allQueryItemValues("fileId")[0]; + auto array = std::make_shared(); auto api = FlameAPI(); - auto [job, array] = api.getFile(addonId, fileId); + auto job = api.getFile(addonId, fileId, array); connect(job.get(), &NetJob::failed, this, [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); }); diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index c0768b9c3..ef38a595d 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -27,7 +27,7 @@ ResourceAPI::SearchArgs ModModel::createSearchArguments() Q_ASSERT(profile); Q_ASSERT(m_filter); - std::optional> versions{}; + std::optional> versions{}; std::optional categories{}; auto loaders = profile->getSupportedModLoaders(); @@ -55,7 +55,7 @@ ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(const QModelInd Q_ASSERT(profile); Q_ASSERT(m_filter); - std::optional> versions{}; + std::optional> versions{}; auto loaders = profile->getSupportedModLoaders(); if (!m_filter->versions.empty()) versions = m_filter->versions; diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 1cdce5e33..803ba6d5c 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -50,6 +50,7 @@ #include "ResourceDownloadTask.h" #include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" #include "ui/dialogs/ResourceDownloadDialog.h" @@ -62,14 +63,14 @@ ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ResourcePa void ModPage::setFilterWidget(std::unique_ptr& widget) { - if (m_filter_widget) { + if (m_filter_widget) disconnect(m_filter_widget.get(), nullptr, nullptr, nullptr); - } - auto* old = m_ui->splitter->replaceWidget(0, widget.get()); + auto old = m_ui->splitter->replaceWidget(0, widget.get()); // because we replaced the widget we also need to delete it - - delete old; + if (old) { + delete old; + } m_filter_widget.swap(widget); @@ -113,11 +114,10 @@ QMap ModPage::urlHandlers() const void ModPage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version, - ResourceFolderModel* baseModel, - QString downloadReason) + const std::shared_ptr base_model) { - bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_model->addPack(pack, version, baseModel, isIndexed, downloadReason); + bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + m_model->addPack(pack, version, base_model, is_indexed); } } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index 7f75995a3..d3b08cbd9 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -29,10 +29,10 @@ class ModPage : public ResourcePage { static T* create(ModDownloadDialog* dialog, BaseInstance& instance) { auto page = new T(dialog, instance); - auto* model = static_cast(page->getModel()); + auto model = static_cast(page->getModel()); - auto filterWidget = page->createFilterWidget(); - page->setFilterWidget(filterWidget); + auto filter_widget = page->createFilterWidget(); + page->setFilterWidget(filter_widget); model->setFilter(page->getFilter()); connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated); @@ -43,21 +43,18 @@ class ModPage : public ResourcePage { } //: The plural version of 'mod' - QString resourcesString() const override { return tr("mods"); } + inline QString resourcesString() const override { return tr("mods"); } //: The singular version of 'mods' - QString resourceString() const override { return tr("mod"); } + inline QString resourceString() const override { return tr("mod"); } QMap urlHandlers() const override; - void addResourceToPage(ModPlatform::IndexedPack::Ptr /*unused*/, - ModPlatform::IndexedVersion& /*unused*/, - ResourceFolderModel* /*unused*/, - QString downloadReason = "standalone") override; + void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, std::shared_ptr) override; virtual std::unique_ptr createFilterWidget() = 0; bool supportsFiltering() const override { return true; }; - auto getFilter() const -> std::shared_ptr { return m_filter; } + auto getFilter() const -> const std::shared_ptr { return m_filter; } void setFilterWidget(std::unique_ptr&); protected: diff --git a/launcher/ui/pages/modplatform/OptionalModDialog.ui b/launcher/ui/pages/modplatform/OptionalModDialog.ui index 3ac9b5b13..0b809d2cb 100644 --- a/launcher/ui/pages/modplatform/OptionalModDialog.ui +++ b/launcher/ui/pages/modplatform/OptionalModDialog.ui @@ -24,9 +24,6 @@ true - - QAbstractItemView::ScrollPerPixel - diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index edd8564b6..30d3dbdf4 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -12,11 +12,9 @@ #include #include #include -#include #include "Application.h" #include "BuildConfig.h" -#include "settings/SettingsObject.h" #include "modplatform/ResourceAPI.h" #include "net/ApiDownload.h" @@ -30,7 +28,7 @@ namespace ResourceDownload { QHash ResourceModel::s_running_models; -ResourceModel::ResourceModel(ResourceAPI* api) : m_api(api) +ResourceModel::ResourceModel(ResourceAPI* api) : QAbstractListModel(), m_api(api) { s_running_models.insert(this, true); if (APPLICATION_DYN) { @@ -63,14 +61,14 @@ auto ResourceModel::data(const QModelIndex& index, int role) const -> QVariant } case Qt::DecorationRole: { if (APPLICATION_DYN) { - if (auto iconOrNone = const_cast(this)->getIcon(const_cast(index), pack->logoUrl); - iconOrNone.has_value()) { - return iconOrNone.value(); - } + if (auto icon_or_none = const_cast(this)->getIcon(const_cast(index), pack->logoUrl); + icon_or_none.has_value()) + return icon_or_none.value(); return QIcon::fromTheme("screenshot-placeholder"); + } else { + return {}; } - return {}; } case Qt::SizeHintRole: return QSize(0, 58); @@ -113,9 +111,8 @@ QHash ResourceModel::roleNames() const bool ResourceModel::setData(const QModelIndex& index, const QVariant& value, [[maybe_unused]] int role) { int pos = index.row(); - if (pos >= m_packs.size() || pos < 0 || !index.isValid()) { + if (pos >= m_packs.size() || pos < 0 || !index.isValid()) return false; - } m_packs[pos] = value.value(); emit dataChanged(index, index); @@ -130,51 +127,42 @@ QString ResourceModel::debugName() const void ResourceModel::fetchMore(const QModelIndex& parent) { - if (parent.isValid() || m_search_state == SearchState::Finished) { + if (parent.isValid() || m_search_state == SearchState::Finished) return; - } search(); } void ResourceModel::search() { - if (hasActiveSearchJob()) { + if (hasActiveSearchJob()) return; - } - if (m_search_state != SearchState::ResetRequested && m_search_term.startsWith("#")) { + if (m_search_term.startsWith("#")) { auto projectId = m_search_term.mid(1); if (!projectId.isEmpty()) { ResourceAPI::Callback callbacks; - callbacks.on_fail = [this](QString reason, int networkErrorCode) { - if (!s_running_models.constFind(this).value()) { + callbacks.on_fail = [this](QString reason, int) { + if (!s_running_models.constFind(this).value()) return; - } - if (networkErrorCode == 404) { - m_search_state = SearchState::ResetRequested; - } - searchRequestFailed(std::move(reason), networkErrorCode); + searchRequestFailed(reason, -1); }; callbacks.on_abort = [this] { - if (!s_running_models.constFind(this).value()) { + if (!s_running_models.constFind(this).value()) return; - } searchRequestAborted(); }; callbacks.on_succeed = [this](auto& pack) { - if (!s_running_models.constFind(this).value()) { + if (!s_running_models.constFind(this).value()) return; - } searchRequestForOneSucceeded(pack); }; auto project = std::make_shared(); project->addonId = projectId; - if (auto job = m_api->getProjectInfo({ project }, std::move(callbacks), false); job) { + if (auto job = m_api->getProjectInfo({ project }, std::move(callbacks)); job) runSearchJob(job); - } return; } } @@ -183,36 +171,31 @@ void ResourceModel::search() ResourceAPI::Callback> callbacks{}; callbacks.on_succeed = [this](auto& doc) { - if (!s_running_models.constFind(this).value()) { + if (!s_running_models.constFind(this).value()) return; - } searchRequestSucceeded(doc); }; - callbacks.on_fail = [this](QString reason, int networkErrorCode) { - if (!s_running_models.constFind(this).value()) { + callbacks.on_fail = [this](QString reason, int network_error_code) { + if (!s_running_models.constFind(this).value()) return; - } - searchRequestFailed(std::move(reason), networkErrorCode); + searchRequestFailed(reason, network_error_code); }; callbacks.on_abort = [this] { - if (!s_running_models.constFind(this).value()) { + if (!s_running_models.constFind(this).value()) return; - } searchRequestAborted(); }; - if (auto job = m_api->searchProjects(std::move(args), std::move(callbacks)); job) { + if (auto job = m_api->searchProjects(std::move(args), std::move(callbacks)); job) runSearchJob(job); - } } void ResourceModel::loadEntry(const QModelIndex& entry) { - const auto& pack = m_packs[entry.row()]; + auto const& pack = m_packs[entry.row()]; - if (!hasActiveInfoJob()) { + if (!hasActiveInfoJob()) m_current_info_job.clear(); - } if (!pack->versionsLoaded) { auto args{ createVersionsArguments(entry) }; @@ -220,24 +203,20 @@ void ResourceModel::loadEntry(const QModelIndex& entry) auto addonId = pack->addonId; // Use default if no callbacks are set - if (!callbacks.on_succeed) { + if (!callbacks.on_succeed) callbacks.on_succeed = [this, entry, addonId](auto& doc) { - if (!s_running_models.constFind(this).value()) { + if (!s_running_models.constFind(this).value()) return; - } versionRequestSucceeded(doc, addonId, entry); }; - } - if (!callbacks.on_fail) { - callbacks.on_fail = [](const QString& reason, int) { + if (!callbacks.on_fail) + callbacks.on_fail = [](QString reason, int) { QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project versions: %1").arg(reason)); }; - } - if (auto job = m_api->getProjectVersions(std::move(args), std::move(callbacks)); job) { + if (auto job = m_api->getProjectVersions(std::move(args), std::move(callbacks)); job) runInfoJob(job); - } } if (!pack->extraDataLoaded) { @@ -245,45 +224,41 @@ void ResourceModel::loadEntry(const QModelIndex& entry) ResourceAPI::Callback callbacks{}; callbacks.on_succeed = [this, entry](auto& newpack) { - if (!s_running_models.constFind(this).value()) { + if (!s_running_models.constFind(this).value()) return; - } infoRequestSucceeded(newpack, entry); }; - callbacks.on_fail = [this](const QString& reason, int) { - if (!s_running_models.constFind(this).value()) { + 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()) { + 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) { + if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job) runInfoJob(job); - } } } void ResourceModel::refresh() { - bool resetRequested = false; + bool reset_requested = false; if (hasActiveInfoJob()) { m_current_info_job.abort(); - resetRequested = true; + reset_requested = true; } if (hasActiveSearchJob()) { m_current_search_job->abort(); - resetRequested = true; + reset_requested = true; } - if (resetRequested) { + if (reset_requested) { m_search_state = SearchState::ResetRequested; return; } @@ -309,15 +284,13 @@ void ResourceModel::runSearchJob(Task::Ptr ptr) } void ResourceModel::runInfoJob(Task::Ptr ptr) { - if (!m_current_info_job.isRunning()) { + if (!m_current_info_job.isRunning()) m_current_info_job.clear(); - } - m_current_info_job.addTask(std::move(ptr)); + m_current_info_job.addTask(ptr); - if (!m_current_info_job.isRunning()) { + if (!m_current_info_job.isRunning()) m_current_info_job.run(); - } } std::optional ResourceModel::getCurrentSortingMethodByIndex() const @@ -325,12 +298,11 @@ std::optional ResourceModel::getCurrentSortingMethod std::optional sort{}; { // Find sorting method by ID - auto sortingMethods = getSortingMethods(); - auto method = std::find_if(sortingMethods.constBegin(), sortingMethods.constEnd(), - [this](const auto& e) { return m_current_sort_index == e.index; }); - if (method != sortingMethods.constEnd()) { + auto sorting_methods = getSortingMethods(); + auto method = std::find_if(sorting_methods.constBegin(), sorting_methods.constEnd(), + [this](auto const& e) { return m_current_sort_index == e.index; }); + if (method != sorting_methods.constEnd()) sort = *method; - } } return sort; @@ -339,47 +311,43 @@ std::optional ResourceModel::getCurrentSortingMethod std::optional ResourceModel::getIcon(QModelIndex& index, const QUrl& url) { QPixmap pixmap; - if (QPixmapCache::find(url.toString(), &pixmap)) { + if (QPixmapCache::find(url.toString(), &pixmap)) return { pixmap }; - } if (!m_current_icon_job) { m_current_icon_job.reset(new NetJob("IconJob", APPLICATION->network())); m_current_icon_job->setAskRetry(false); } - if (m_currently_running_icon_actions.contains(url)) { + if (m_currently_running_icon_actions.contains(url)) return {}; - } - if (m_failed_icon_actions.contains(url)) { + if (m_failed_icon_actions.contains(url)) return {}; - } - auto cacheEntry = APPLICATION->metacache()->resolveEntry( + auto cache_entry = APPLICATION->metacache()->resolveEntry( metaEntryBase(), QString("logos/%1").arg(QString(QCryptographicHash::hash(url.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex()))); - auto iconFetchAction = Net::ApiDownload::makeCached(url, cacheEntry); + auto icon_fetch_action = Net::ApiDownload::makeCached(url, cache_entry); - auto fullFilePath = cacheEntry->getFullPath(); - connect(iconFetchAction.get(), &Task::succeeded, this, [this, url, fullFilePath, index] { - auto icon = QIcon(fullFilePath); + auto full_file_path = cache_entry->getFullPath(); + connect(icon_fetch_action.get(), &Task::succeeded, this, [this, url, full_file_path, index] { + auto icon = QIcon(full_file_path); QPixmapCache::insert(url.toString(), icon.pixmap(icon.actualSize({ 64, 64 }))); m_currently_running_icon_actions.remove(url); emit dataChanged(index, index, { Qt::DecorationRole }); }); - connect(iconFetchAction.get(), &Task::failed, this, [this, url] { + connect(icon_fetch_action.get(), &Task::failed, this, [this, url] { m_currently_running_icon_actions.remove(url); m_failed_icon_actions.insert(url); }); m_currently_running_icon_actions.insert(url); - m_current_icon_job->addNetAction(iconFetchAction); - if (!m_current_icon_job->isRunning()) { + m_current_icon_job->addNetAction(icon_fetch_action); + if (!m_current_icon_job->isRunning()) QMetaObject::invokeMethod(m_current_icon_job.get(), &NetJob::start); - } return {}; } @@ -391,11 +359,11 @@ void ResourceModel::searchRequestSucceeded(QList& QList filteredNewList; for (auto pack : newList) { ModPlatform::IndexedPack::Ptr p; - if (auto sel = std::ranges::find_if(m_selected, - [&pack](const DownloadTaskPtr& i) { - const auto ipack = i->getPack(); - return ipack->provider == pack->provider && ipack->addonId == pack->addonId; - }); + 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 { @@ -414,9 +382,8 @@ void ResourceModel::searchRequestSucceeded(QList& } // When you have a Qt build with assertions turned on, proceeding here will abort the application - if (filteredNewList.size() == 0) { + if (filteredNewList.size() == 0) return; - } beginInsertRows(QModelIndex(), m_packs.size(), m_packs.size() + filteredNewList.size() - 1); m_packs.append(filteredNewList); @@ -432,16 +399,13 @@ void ResourceModel::searchRequestForOneSucceeded(ModPlatform::IndexedPack::Ptr p endInsertRows(); } -void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int networkErrorCode) +void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int network_error_code) { - switch (networkErrorCode) { + switch (network_error_code) { default: // Network error QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load mods.")); break; - case 404: - // 404 Not Found, some APIs return this when nothing is found, no need to bother the user - break; case 409: // 409 Gone, notify user to update QMessageBox::critical(nullptr, tr("Error"), @@ -449,21 +413,13 @@ void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int net break; } - if (m_search_state == SearchState::ResetRequested) { - clearData(); - - m_next_search_offset = 0; - search(); - } else { - m_search_state = SearchState::Finished; - } + m_search_state = SearchState::Finished; } void ResourceModel::searchRequestAborted() { - if (m_search_state != SearchState::ResetRequested) { + if (m_search_state != SearchState::ResetRequested) qCritical() << "Search task in" << debugName() << "aborted by an unknown reason!"; - } // Retry fetching clearData(); @@ -474,20 +430,19 @@ void ResourceModel::searchRequestAborted() void ResourceModel::versionRequestSucceeded(QVector& doc, QVariant pack, const QModelIndex& index) { - auto currentPack = data(index, Qt::UserRole).value(); + auto current_pack = data(index, Qt::UserRole).value(); // Check if the index is still valid for this resource or not - if (pack != currentPack->addonId) { + if (pack != current_pack->addonId) return; - } - currentPack->versions = doc; - currentPack->versionsLoaded = true; + current_pack->versions = doc; + current_pack->versionsLoaded = true; // Cache info :^) - QVariant newPack; - newPack.setValue(currentPack); - if (!setData(index, newPack, Qt::UserRole)) { + QVariant new_pack; + new_pack.setValue(current_pack); + if (!setData(index, new_pack, Qt::UserRole)) { qWarning() << "Failed to cache resource versions!"; return; } @@ -497,17 +452,16 @@ void ResourceModel::versionRequestSucceeded(QVector void ResourceModel::infoRequestSucceeded(ModPlatform::IndexedPack::Ptr pack, const QModelIndex& index) { - auto currentPack = data(index, Qt::UserRole).value(); + auto current_pack = data(index, Qt::UserRole).value(); // Check if the index is still valid for this resource or not - if (pack->addonId != currentPack->addonId) { + if (pack->addonId != current_pack->addonId) return; - } // Cache info :^) - QVariant newPack; - newPack.setValue(pack); - if (!setData(index, newPack, Qt::UserRole)) { + QVariant new_pack; + new_pack.setValue(pack); + if (!setData(index, new_pack, Qt::UserRole)) { qWarning() << "Failed to cache resource info!"; return; } @@ -517,17 +471,16 @@ void ResourceModel::infoRequestSucceeded(ModPlatform::IndexedPack::Ptr pack, con void ResourceModel::addPack(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version, - ResourceFolderModel* packs, - bool isIndexed, - QString downloadReason) + const std::shared_ptr packs, + bool is_indexed) { version.is_currently_selected = true; - m_selected.append(makeShared(std::move(pack), version, packs, isIndexed, std::move(downloadReason))); + m_selected.append(makeShared(pack, version, packs, is_indexed)); } void ResourceModel::removePack(const QString& rem) { - auto pred = [&rem](const DownloadTaskPtr& i) { return rem == i->getName(); }; + auto pred = [&rem](const DownloadTaskPtr i) { return rem == i->getName(); }; #if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0) m_selected.removeIf(pred); #else @@ -539,16 +492,15 @@ void ResourceModel::removePack(const QString& rem) ++it; } #endif - auto pack = std::ranges::find_if(m_packs, [&rem](const ModPlatform::IndexedPack::Ptr& i) { return rem == i->name; }); + auto pack = std::find_if(m_packs.begin(), m_packs.end(), [&rem](const ModPlatform::IndexedPack::Ptr i) { return rem == i->name; }); if (pack == m_packs.end()) { // ignore it if is not in the current search return; } if (!pack->get()->versionsLoaded) { return; } - for (auto& ver : pack->get()->versions) { + for (auto& ver : pack->get()->versions) ver.is_currently_selected = false; - } } bool ResourceModel::checkVersionFilters(const ModPlatform::IndexedVersion& v) diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h index 9124c0e66..8a0b7e06a 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.h +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -36,16 +36,16 @@ class ResourceModel : public QAbstractListModel { ResourceModel(ResourceAPI* api); ~ResourceModel() override; - auto data(const QModelIndex& /*index*/, int role) const -> QVariant 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; virtual auto debugName() const -> QString; virtual auto metaEntryBase() const -> QString = 0; - int rowCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : static_cast(m_packs.size()); } - int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : 1; } - 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); } bool hasActiveSearchJob() const { return m_current_search_job && m_current_search_job->isRunning(); } bool hasActiveInfoJob() const { return m_current_info_job.isRunning(); } @@ -66,7 +66,7 @@ class ResourceModel : public QAbstractListModel { public slots: void fetchMore(const QModelIndex& parent) override; - bool canFetchMore(const QModelIndex& parent) const override + inline bool canFetchMore(const QModelIndex& parent) const override { return parent.isValid() ? false : m_search_state == SearchState::CanFetchMore; } @@ -93,9 +93,8 @@ class ResourceModel : public QAbstractListModel { void addPack(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version, - ResourceFolderModel* packs, - bool isIndexed = false, - QString downloadReason = "standalone"); + std::shared_ptr packs, + bool is_indexed = false); void removePack(const QString& rem); QList selectedPacks() { return m_selected; } diff --git a/launcher/ui/pages/modplatform/ResourcePackModel.cpp b/launcher/ui/pages/modplatform/ResourcePackModel.cpp index ac5121f2a..53a40faa2 100644 --- a/launcher/ui/pages/modplatform/ResourcePackModel.cpp +++ b/launcher/ui/pages/modplatform/ResourcePackModel.cpp @@ -5,15 +5,14 @@ #include "ResourcePackModel.h" #include -#include namespace ResourceDownload { -ResourcePackResourceModel::ResourcePackResourceModel(const BaseInstance& base_inst, +ResourcePackResourceModel::ResourcePackResourceModel(BaseInstance const& base_inst, ResourceAPI* api, - const QString& debugName, + QString debugName, QString metaEntryBase) - : ResourceModel(api), m_base_instance(base_inst), m_debugName(debugName + " (Model)"), m_metaEntryBase(std::move(metaEntryBase)) + : ResourceModel(api), m_base_instance(base_inst), m_debugName(debugName + " (Model)"), m_metaEntryBase(metaEntryBase) {} /******** Make data requests ********/ @@ -21,29 +20,19 @@ ResourcePackResourceModel::ResourcePackResourceModel(const BaseInstance& base_in ResourceAPI::SearchArgs ResourcePackResourceModel::createSearchArguments() { auto sort = getCurrentSortingMethodByIndex(); - return { - .type = ModPlatform::ResourceType::ResourcePack, - .offset = m_next_search_offset, - .search = m_search_term, - .sorting = sort, - .loaders = {}, - .versions = {}, - .side = {}, - .categoryIds = {}, - .openSource = {}, - }; + 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 = pack, .mcVersions = {}, .loaders = {}, .resourceType = ModPlatform::ResourceType::ResourcePack }; + return { pack, {}, {}, ModPlatform::ResourceType::ResourcePack }; } ResourceAPI::ProjectInfoArgs ResourcePackResourceModel::createInfoArguments(const QModelIndex& entry) { auto pack = m_packs[entry.row()]; - return { .pack = pack }; + return { pack }; } void ResourcePackResourceModel::searchWithTerm(const QString& term, unsigned int sort) diff --git a/launcher/ui/pages/modplatform/ResourcePackModel.h b/launcher/ui/pages/modplatform/ResourcePackModel.h index 92e3c4d37..d664ccb05 100644 --- a/launcher/ui/pages/modplatform/ResourcePackModel.h +++ b/launcher/ui/pages/modplatform/ResourcePackModel.h @@ -8,6 +8,8 @@ #include "BaseInstance.h" +#include "modplatform/ModIndex.h" + #include "ui/pages/modplatform/ResourceModel.h" class Version; @@ -18,7 +20,7 @@ class ResourcePackResourceModel : public ResourceModel { Q_OBJECT public: - ResourcePackResourceModel(const BaseInstance&, ResourceAPI*, const QString& debugName, QString metaEntryBase); + ResourcePackResourceModel(BaseInstance const&, ResourceAPI*, QString debugName, QString metaEntryBase); /* Ask the API for more information */ void searchWithTerm(const QString& term, unsigned int sort); diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 8baa0bbec..061e96491 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -39,14 +39,12 @@ #include "ResourcePage.h" #include "modplatform/ModIndex.h" +#include "ui/dialogs/CustomMessageBox.h" #include "ui_ResourcePage.h" #include #include #include -#include -#include -#include #include "Markdown.h" @@ -57,15 +55,14 @@ namespace ResourceDownload { -ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& baseInstance) - : QWidget(parent), m_baseInstance(baseInstance), m_ui(new Ui::ResourcePage), m_parentDialog(parent), m_fetchProgress(this, false) +ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& base_instance) + : QWidget(parent), m_baseInstance(base_instance), m_ui(new Ui::ResourcePage), m_parentDialog(parent), m_fetchProgress(this, false) { m_ui->setupUi(this); m_ui->searchEdit->installEventFilter(this); m_ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - m_ui->versionSelectionBox->view()->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); m_ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); m_searchTimer.setTimerType(Qt::TimerType::CoarseTimer); @@ -81,7 +78,7 @@ ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& baseIns m_ui->verticalLayout->insertWidget(1, &m_fetchProgress); - auto* delegate = new ProjectItemDelegate(this); + auto delegate = new ProjectItemDelegate(this); m_ui->packView->setItemDelegate(delegate); m_ui->packView->installEventFilter(this); m_ui->packView->viewport()->installEventFilter(this); @@ -95,7 +92,8 @@ ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& baseIns ResourcePage::~ResourcePage() { delete m_ui; - delete m_model; + if (m_model) + delete m_model; } void ResourcePage::retranslate() @@ -115,19 +113,10 @@ void ResourcePage::openedImpl() m_ui->resourceSelectionButton->setText(tr("Select %1 for download").arg(resourceString())); updateSelectionButton(); - if (!m_suppressInitialSearch) { - triggerSearch(); - } else { - m_suppressInitialSearch = false; - } + triggerSearch(); m_ui->searchEdit->setFocus(); } -void ResourcePage::setSuppressInitialSearch(bool suppress) -{ - m_suppressInitialSearch = suppress; -} - auto ResourcePage::eventFilter(QObject* watched, QEvent* event) -> bool { if (event->type() == QEvent::KeyPress) { @@ -137,13 +126,12 @@ auto ResourcePage::eventFilter(QObject* watched, QEvent* event) -> bool triggerSearch(); keyEvent->accept(); return true; - } - if (m_searchTimer.isActive()) { - m_searchTimer.stop(); - } - - m_searchTimer.start(350); + } else { + if (m_searchTimer.isActive()) + m_searchTimer.stop(); + m_searchTimer.start(350); + } } else if (watched == m_ui->packView) { // stop the event from going to the confirm button if (keyEvent->key() == Qt::Key_Return) { @@ -169,7 +157,7 @@ QString ResourcePage::getSearchTerm() const return m_ui->searchEdit->text(); } -void ResourcePage::setSearchTerm(const QString& term) +void ResourcePage::setSearchTerm(QString term) { m_ui->searchEdit->setText(term); } @@ -179,11 +167,10 @@ void ResourcePage::addSortings() Q_ASSERT(m_model); auto sorts = m_model->getSortingMethods(); - std::ranges::sort(sorts, [](const auto& l, const auto& r) { return l.index < r.index; }); + std::sort(sorts.begin(), sorts.end(), [](auto const& l, auto const& r) { return l.index < r.index; }); - for (auto&& sorting : sorts) { + for (auto&& sorting : sorts) m_ui->sortByBox->addItem(sorting.readable_name, QVariant(sorting.index)); - } } bool ResourcePage::setCurrentPack(ModPlatform::IndexedPack::Ptr pack) @@ -200,26 +187,24 @@ ModPlatform::IndexedPack::Ptr ResourcePage::getCurrentPack() const void ResourcePage::updateUi(const QModelIndex& index) { - if (index != m_ui->packView->currentIndex()) { + if (index != m_ui->packView->currentIndex()) return; - } - auto currentPack = getCurrentPack(); - if (!currentPack) { + auto current_pack = getCurrentPack(); + if (!current_pack) { m_ui->packDescription->setHtml({}); m_ui->packDescription->flush(); return; } QString text = ""; - QString name = currentPack->name; + QString name = current_pack->name; - if (currentPack->websiteUrl.isEmpty()) { + if (current_pack->websiteUrl.isEmpty()) text = name; - } else { - text = "websiteUrl + "\">" + name + ""; - } + else + text = "websiteUrl + "\">" + name + ""; - if (!currentPack->authors.empty()) { + if (!current_pack->authors.empty()) { auto authorToStr = [](ModPlatform::ModpackAuthor& author) -> QString { if (author.url.isEmpty()) { return author.name; @@ -227,53 +212,49 @@ void ResourcePage::updateUi(const QModelIndex& index) return QString("%2").arg(author.url, author.name); }; QStringList authorStrs; - for (auto& author : currentPack->authors) { + for (auto& author : current_pack->authors) { authorStrs.push_back(authorToStr(author)); } text += "
" + tr(" by ") + authorStrs.join(", "); } - if (currentPack->extraDataLoaded) { - if (currentPack->extraData.status == "archived") { + if (current_pack->extraDataLoaded) { + if (current_pack->extraData.status == "archived") { text += "

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

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

" + tr("External links:") + "
"; } - if (!currentPack->extraData.issuesUrl.isEmpty()) { - text += "- " + tr("Issues: %1").arg(currentPack->extraData.issuesUrl) + "
"; - } - if (!currentPack->extraData.wikiUrl.isEmpty()) { - text += "- " + tr("Wiki: %1").arg(currentPack->extraData.wikiUrl) + "
"; - } - if (!currentPack->extraData.sourceUrl.isEmpty()) { - text += "- " + tr("Source code: %1").arg(currentPack->extraData.sourceUrl) + "
"; - } - if (!currentPack->extraData.discordUrl.isEmpty()) { - text += "- " + tr("Discord: %1").arg(currentPack->extraData.discordUrl) + "
"; - } + if (!current_pack->extraData.issuesUrl.isEmpty()) + text += "- " + tr("Issues: %1").arg(current_pack->extraData.issuesUrl) + "
"; + if (!current_pack->extraData.wikiUrl.isEmpty()) + text += "- " + tr("Wiki: %1").arg(current_pack->extraData.wikiUrl) + "
"; + if (!current_pack->extraData.sourceUrl.isEmpty()) + text += "- " + tr("Source code: %1").arg(current_pack->extraData.sourceUrl) + "
"; + if (!current_pack->extraData.discordUrl.isEmpty()) + text += "- " + tr("Discord: %1").arg(current_pack->extraData.discordUrl) + "
"; } text += "
"; m_ui->packDescription->setHtml(StringUtils::htmlListPatch( - text + (currentPack->extraData.body.isEmpty() ? currentPack->description : markdownToHTML(currentPack->extraData.body)))); + text + (current_pack->extraData.body.isEmpty() ? current_pack->description : markdownToHTML(current_pack->extraData.body)))); m_ui->packDescription->flush(); } @@ -285,15 +266,14 @@ void ResourcePage::updateSelectionButton() } m_ui->resourceSelectionButton->setEnabled(true); - if (auto currentPack = getCurrentPack(); currentPack) { - if (currentPack->versionsLoaded && currentPack->versions.empty()) { + if (auto current_pack = getCurrentPack(); current_pack) { + if (current_pack->versionsLoaded && current_pack->versions.empty()) { m_ui->resourceSelectionButton->setEnabled(false); qWarning() << tr("No version available for the selected pack"); - } else if (!currentPack->isVersionSelected(m_selectedVersionIndex)) { + } else if (!current_pack->isVersionSelected(m_selectedVersionIndex)) m_ui->resourceSelectionButton->setText(tr("Select %1 for download").arg(resourceString())); - } else { + else m_ui->resourceSelectionButton->setText(tr("Deselect %1 for download").arg(resourceString())); - } } else { qWarning() << "Tried to update the selected button but there is not a pack selected"; } @@ -302,20 +282,19 @@ void ResourcePage::updateSelectionButton() void ResourcePage::versionListUpdated(const QModelIndex& index) { if (index == m_ui->packView->currentIndex()) { - auto currentPack = getCurrentPack(); + auto current_pack = getCurrentPack(); m_ui->versionSelectionBox->blockSignals(true); m_ui->versionSelectionBox->clear(); m_ui->versionSelectionBox->blockSignals(false); - if (currentPack) { - auto installedVersion = m_model->getInstalledPackVersion(currentPack); + if (current_pack) { + auto installedVersion = m_model->getInstalledPackVersion(current_pack); - for (int i = 0; i < currentPack->versions.size(); i++) { - auto& version = currentPack->versions[i]; - if (!m_model->checkVersionFilters(version)) { + for (int i = 0; i < current_pack->versions.size(); i++) { + auto& version = current_pack->versions[i]; + if (!m_model->checkVersionFilters(version)) continue; - } auto versionText = version.version; if (version.version_type.isValid()) { @@ -336,9 +315,8 @@ void ResourcePage::versionListUpdated(const QModelIndex& index) if (m_enableQueue.contains(index.row())) { m_enableQueue.remove(index.row()); onResourceToggle(index); - } else { + } else updateSelectionButton(); - } } else if (m_enableQueue.contains(index.row())) { m_enableQueue.remove(index.row()); onResourceToggle(index); @@ -351,30 +329,27 @@ void ResourcePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI return; } - auto currentPack = getCurrentPack(); + auto current_pack = getCurrentPack(); - bool requestLoad = false; - if (!currentPack || !currentPack->versionsLoaded) { + bool request_load = false; + if (!current_pack || !current_pack->versionsLoaded) { m_ui->resourceSelectionButton->setText(tr("Loading versions...")); m_ui->resourceSelectionButton->setEnabled(false); - requestLoad = true; + request_load = true; } else { versionListUpdated(curr); } - if (currentPack && !currentPack->extraDataLoaded) { - requestLoad = true; - } + if (current_pack && !current_pack->extraDataLoaded) + request_load = true; // we are already requesting this - if (m_enableQueue.contains(curr.row())) { - requestLoad = false; - } + if (m_enableQueue.contains(curr.row())) + request_load = false; - if (requestLoad) { + if (request_load) m_model->loadEntry(curr); - } updateUi(curr); } @@ -387,21 +362,20 @@ void ResourcePage::onVersionSelectionChanged(int index) void ResourcePage::addResourceToDialog(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version) { - m_parentDialog->addResource(std::move(pack), version); + m_parentDialog->addResource(pack, version); } -void ResourcePage::removeResourceFromDialog(const QString& packName) +void ResourcePage::removeResourceFromDialog(const QString& pack_name) { - m_parentDialog->removeResource(packName); + m_parentDialog->removeResource(pack_name); } void ResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& ver, - ResourceFolderModel* baseModel, - QString downloadReason) + const std::shared_ptr base_model) { - bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_model->addPack(std::move(pack), ver, baseModel, isIndexed, std::move(downloadReason)); + bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + m_model->addPack(pack, ver, base_model, is_indexed); } void ResourcePage::modelReset() @@ -416,25 +390,22 @@ void ResourcePage::removeResourceFromPage(const QString& name) void ResourcePage::onResourceSelected() { - if (m_selectedVersionIndex < 0) { + if (m_selectedVersionIndex < 0) return; - } - auto currentPack = getCurrentPack(); - if (!currentPack || !currentPack->versionsLoaded || currentPack->versions.size() < m_selectedVersionIndex) { + auto current_pack = getCurrentPack(); + if (!current_pack || !current_pack->versionsLoaded || current_pack->versions.size() < m_selectedVersionIndex) return; - } - auto& version = currentPack->versions[m_selectedVersionIndex]; + auto& version = current_pack->versions[m_selectedVersionIndex]; Q_ASSERT(!version.downloadUrl.isNull()); - if (version.is_currently_selected) { - removeResourceFromDialog(currentPack->name); - } else { - addResourceToDialog(currentPack, version); - } + if (version.is_currently_selected) + removeResourceFromDialog(current_pack->name); + else + addResourceToDialog(current_pack, version); // Save the modified pack (and prevent warning in release build) - [[maybe_unused]] bool set = setCurrentPack(currentPack); + [[maybe_unused]] bool set = setCurrentPack(current_pack); Q_ASSERT(set); updateSelectionButton(); @@ -449,28 +420,26 @@ void ResourcePage::onResourceToggle(const QModelIndex& index) auto pack = m_model->data(index, Qt::UserRole).value(); if (pack->versionsLoaded) { - if (pack->isAnyVersionSelected()) { + if (pack->isAnyVersionSelected()) removeResourceFromDialog(pack->name); - } else { + else { auto version = std::find_if(pack->versions.begin(), pack->versions.end(), [this](const ModPlatform::IndexedVersion& version) { return m_model->checkVersionFilters(version); }); if (version == pack->versions.end()) { - auto* errorMessage = new QMessageBox( + auto errorMessage = new QMessageBox( QMessageBox::Warning, tr("No versions available"), tr("No versions for '%1' are available.\nThe author likely blocked third-party launchers.").arg(pack->name), QMessageBox::Ok, this); errorMessage->open(); - } else { + } else addResourceToDialog(pack, *version); - } } - if (isSelected) { + if (isSelected) updateSelectionButton(); - } // force update QVariant variant; @@ -482,9 +451,8 @@ void ResourcePage::onResourceToggle(const QModelIndex& index) // we can't be sure that this hasn't already been requested... // but this does the job well enough and there's not much point preventing edgecases - if (!isSelected) { + if (!isSelected) m_model->loadEntry(index); - } } } @@ -515,13 +483,13 @@ void ResourcePage::openUrl(const QUrl& url) const QString slug = match.captured(1); // ensure the user isn't opening the same mod - if (auto currentPack = getCurrentPack(); currentPack && slug != currentPack->slug) { + if (auto current_pack = getCurrentPack(); current_pack && slug != current_pack->slug) { m_parentDialog->selectPage(page); - auto* newPage = m_parentDialog->selectedPage(); + auto newPage = m_parentDialog->selectedPage(); QLineEdit* searchEdit = newPage->m_ui->searchEdit; - auto* model = newPage->m_model; + auto model = newPage->m_model; QListView* view = newPage->m_ui->packView; auto jump = [url, slug, model, view] { @@ -542,11 +510,10 @@ void ResourcePage::openUrl(const QUrl& url) searchEdit->setText(slug); newPage->triggerSearch(); - if (model->hasActiveSearchJob()) { + if (model->hasActiveSearchJob()) connect(model->activeSearchJob().get(), &Task::finished, jump); - } else { + else jump(); - } return; } @@ -556,7 +523,7 @@ void ResourcePage::openUrl(const QUrl& url) QDesktopServices::openUrl(url); } -void ResourcePage::openProject(const QVariant& projectID) +void ResourcePage::openProject(QVariant projectID) { m_ui->sortByBox->hide(); m_ui->searchEdit->hide(); @@ -565,16 +532,16 @@ void ResourcePage::openProject(const QVariant& projectID) m_ui->resourceSelectionButton->hide(); m_doNotJumpToMod = true; - auto* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); - auto* okBtn = buttonBox->button(QDialogButtonBox::Ok); + auto okBtn = buttonBox->button(QDialogButtonBox::Ok); okBtn->setDefault(true); okBtn->setAutoDefault(true); okBtn->setText(tr("Reinstall")); okBtn->setShortcut(tr("Ctrl+Return")); okBtn->setEnabled(false); - auto* cancelBtn = buttonBox->button(QDialogButtonBox::Cancel); + auto cancelBtn = buttonBox->button(QDialogButtonBox::Cancel); cancelBtn->setDefault(false); cancelBtn->setAutoDefault(false); cancelBtn->setText(tr("Cancel")); @@ -591,8 +558,9 @@ void ResourcePage::openProject(const QVariant& projectID) [this, okBtn](int index) { okBtn->setEnabled(m_ui->versionSelectionBox->itemData(index).toInt() >= 0); }); auto jump = [this] { - if (m_model->rowCount({}) > 0) { - m_ui->packView->setCurrentIndex(m_model->index(0)); + for (int row = 0; row < m_model->rowCount({}); row++) { + const QModelIndex index = m_model->index(row); + m_ui->packView->setCurrentIndex(index); return; } m_ui->packDescription->setText(tr("The resource was not found")); @@ -601,10 +569,9 @@ void ResourcePage::openProject(const QVariant& projectID) m_ui->searchEdit->setText("#" + projectID.toString()); triggerSearch(); - if (m_model->hasActiveSearchJob()) { + if (m_model->hasActiveSearchJob()) connect(m_model->activeSearchJob().get(), &Task::finished, jump); - } else { + else jump(); - } } } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index 03895dcd4..8f4d2c496 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -9,6 +9,7 @@ #include "ResourceDownloadTask.h" #include "modplatform/ModIndex.h" +#include "modplatform/ResourceAPI.h" #include "ui/pages/BasePage.h" #include "ui/pages/modplatform/ResourceModel.h" @@ -43,9 +44,9 @@ class ResourcePage : public QWidget, public BasePage { virtual auto debugName() const -> QString = 0; //: The plural version of 'resource' - virtual QString resourcesString() const { return tr("resources"); } + virtual inline QString resourcesString() const { return tr("resources"); } //: The singular version of 'resources' - virtual QString resourceString() const { return tr("resource"); } + virtual inline QString resourceString() const { return tr("resource"); } /* Features this resource's page supports */ virtual bool supportsFiltering() const = 0; @@ -57,7 +58,7 @@ class ResourcePage : public QWidget, public BasePage { /** Get the current term in the search bar. */ auto getSearchTerm() const -> QString; /** Programatically set the term in the search bar. */ - void setSearchTerm(const QString&); + void setSearchTerm(QString); bool setCurrentPack(ModPlatform::IndexedPack::Ptr); auto getCurrentPack() const -> ModPlatform::IndexedPack::Ptr; @@ -75,26 +76,21 @@ class ResourcePage : public QWidget, public BasePage { virtual void versionListUpdated(const QModelIndex& index); void addResourceToDialog(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&); - void removeResourceFromDialog(const QString& packName); + void removeResourceFromDialog(const QString& pack_name); virtual void removeResourceFromPage(const QString& name); - virtual void addResourceToPage(ModPlatform::IndexedPack::Ptr, - ModPlatform::IndexedVersion&, - ResourceFolderModel*, - QString downloadReason = "standalone"); + virtual void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, std::shared_ptr); virtual void modelReset(); QList selectedPacks() { return m_model->selectedPacks(); } bool hasSelectedPacks() { return !(m_model->selectedPacks().isEmpty()); } - virtual void openProject(const QVariant& projectID); - - void setSuppressInitialSearch(bool suppress); + virtual void openProject(QVariant projectID); protected slots: virtual void triggerSearch() = 0; - void onSelectionChanged(QModelIndex curr, QModelIndex prev); + void onSelectionChanged(QModelIndex first, QModelIndex second); void onVersionSelectionChanged(int index); void onResourceSelected(); void onResourceToggle(const QModelIndex& index); @@ -122,9 +118,6 @@ class ResourcePage : public QWidget, public BasePage { bool m_doNotJumpToMod = false; QSet m_enableQueue; - - private: - bool m_suppressInitialSearch = false; }; } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ResourcePage.ui b/launcher/ui/pages/modplatform/ResourcePage.ui index a0eb40864..491e7d9f0 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.ui +++ b/launcher/ui/pages/modplatform/ResourcePage.ui @@ -47,9 +47,6 @@ 48 - - QAbstractItemView::ScrollPerPixel - diff --git a/launcher/ui/pages/modplatform/ShaderPackModel.cpp b/launcher/ui/pages/modplatform/ShaderPackModel.cpp index e8a6bcaf5..5349b69ab 100644 --- a/launcher/ui/pages/modplatform/ShaderPackModel.cpp +++ b/launcher/ui/pages/modplatform/ShaderPackModel.cpp @@ -5,15 +5,11 @@ #include "ShaderPackModel.h" #include -#include namespace ResourceDownload { -ShaderPackResourceModel::ShaderPackResourceModel(const BaseInstance& base_inst, - ResourceAPI* api, - const QString& debugName, - QString metaEntryBase) - : ResourceModel(api), m_base_instance(base_inst), m_debugName(debugName + " (Model)"), m_metaEntryBase(std::move(metaEntryBase)) +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 ********/ @@ -21,29 +17,19 @@ ShaderPackResourceModel::ShaderPackResourceModel(const BaseInstance& base_inst, ResourceAPI::SearchArgs ShaderPackResourceModel::createSearchArguments() { auto sort = getCurrentSortingMethodByIndex(); - return { - .type = ModPlatform::ResourceType::ShaderPack, - .offset = m_next_search_offset, - .search = m_search_term, - .sorting = sort, - .loaders = {}, - .versions = {}, - .side = {}, - .categoryIds = {}, - .openSource = {}, - }; + 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 = pack, .mcVersions = {}, .loaders = {}, .resourceType = ModPlatform::ResourceType::ShaderPack }; + return { pack, {}, {}, ModPlatform::ResourceType::ShaderPack }; } ResourceAPI::ProjectInfoArgs ShaderPackResourceModel::createInfoArguments(const QModelIndex& entry) { auto pack = m_packs[entry.row()]; - return { .pack = pack }; + return { pack }; } void ShaderPackResourceModel::searchWithTerm(const QString& term, unsigned int sort) diff --git a/launcher/ui/pages/modplatform/ShaderPackModel.h b/launcher/ui/pages/modplatform/ShaderPackModel.h index cadaf17ee..9856be93e 100644 --- a/launcher/ui/pages/modplatform/ShaderPackModel.h +++ b/launcher/ui/pages/modplatform/ShaderPackModel.h @@ -20,7 +20,7 @@ class ShaderPackResourceModel : public ResourceModel { Q_OBJECT public: - ShaderPackResourceModel(const BaseInstance&, ResourceAPI*, const QString& debugName, QString metaEntryBase); + ShaderPackResourceModel(BaseInstance const&, ResourceAPI*, QString debugName, QString metaEntryBase); /* Ask the API for more information */ void searchWithTerm(const QString& term, unsigned int sort); diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.cpp b/launcher/ui/pages/modplatform/ShaderPackPage.cpp index ec5fe7967..fda05b038 100644 --- a/launcher/ui/pages/modplatform/ShaderPackPage.cpp +++ b/launcher/ui/pages/modplatform/ShaderPackPage.cpp @@ -36,18 +36,18 @@ QMap ShaderPackResourcePage::urlHandlers() const { QMap map; map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?modrinth\\.com\\/shaders\\/([^\\/]+)\\/?"), "modrinth"); - map.insert(QRegularExpression::anchoredPattern(R"((?:www\.)?curseforge\.com\/minecraft\/customization\/([^\/]+)\/?)"), "curseforge"); - map.insert(QRegularExpression::anchoredPattern(R"(minecraft\.curseforge\.com\/projects\/([^\/]+)\/?)"), "curseforge"); + map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?curseforge\\.com\\/minecraft\\/customization\\/([^\\/]+)\\/?"), + "curseforge"); + map.insert(QRegularExpression::anchoredPattern("minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)\\/?"), "curseforge"); return map; } void ShaderPackResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version, - ResourceFolderModel* baseModel, - QString downloadReason) + const std::shared_ptr base_model) { - bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_model->addPack(pack, version, baseModel, isIndexed, downloadReason); + bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + m_model->addPack(pack, version, base_model, is_indexed); } } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.h b/launcher/ui/pages/modplatform/ShaderPackPage.h index 37e3cadef..85d2b16e6 100644 --- a/launcher/ui/pages/modplatform/ShaderPackPage.h +++ b/launcher/ui/pages/modplatform/ShaderPackPage.h @@ -23,7 +23,7 @@ class ShaderPackResourcePage : public ResourcePage { static T* create(ShaderPackDownloadDialog* dialog, BaseInstance& instance) { auto page = new T(dialog, instance); - auto* model = static_cast(page->getModel()); + auto model = static_cast(page->getModel()); connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated); connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); @@ -33,20 +33,17 @@ class ShaderPackResourcePage : public ResourcePage { } //: The plural version of 'shader pack' - QString resourcesString() const override { return tr("shader packs"); } + inline QString resourcesString() const override { return tr("shader packs"); } //: The singular version of 'shader packs' - QString resourceString() const override { return tr("shader pack"); } + inline QString resourceString() const override { return tr("shader pack"); } bool supportsFiltering() const override { return false; }; - void addResourceToPage(ModPlatform::IndexedPack::Ptr /*unused*/, - ModPlatform::IndexedVersion& /*unused*/, - ResourceFolderModel* /*unused*/, - QString downloadReason = "standalone") override; + void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, std::shared_ptr) override; QMap urlHandlers() const override; - 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 32ec488ab..7c1490671 100644 --- a/launcher/ui/pages/modplatform/TexturePackModel.cpp +++ b/launcher/ui/pages/modplatform/TexturePackModel.cpp @@ -4,22 +4,16 @@ #include "TexturePackModel.h" -#include - #include "Application.h" #include "meta/Index.h" #include "meta/Version.h" -static std::vector s_availableVersions = {}; +static std::list s_availableVersions = {}; namespace ResourceDownload { -TexturePackResourceModel::TexturePackResourceModel(const BaseInstance& inst, - ResourceAPI* api, - const QString& debugName, - QString metaEntryBase) - : ResourcePackResourceModel(inst, api, debugName, std::move(metaEntryBase)) - , 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..."; diff --git a/launcher/ui/pages/modplatform/TexturePackModel.h b/launcher/ui/pages/modplatform/TexturePackModel.h index 0e1e3f3fb..bb7348b33 100644 --- a/launcher/ui/pages/modplatform/TexturePackModel.h +++ b/launcher/ui/pages/modplatform/TexturePackModel.h @@ -13,7 +13,7 @@ class TexturePackResourceModel : public ResourcePackResourceModel { Q_OBJECT public: - TexturePackResourceModel(const BaseInstance& inst, ResourceAPI* api, const QString& debugName, QString metaEntryBase); + TexturePackResourceModel(BaseInstance const& inst, ResourceAPI* api, QString debugName, QString metaEntryBase); inline ::Version maximumTexturePackVersion() const { return { "1.6" }; } diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp index 91668fb84..798da888b 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp @@ -99,26 +99,23 @@ void ListModel::request() auto netJob = makeShared("Atl::Request", APPLICATION->network()); auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/json/packsnew.json"); - auto [action, response] = Net::ApiDownload::makeByteArray(QUrl(url)); - netJob->addNetAction(action); + netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(url), response)); jobPtr = netJob; jobPtr->start(); - connect(netJob.get(), &NetJob::succeeded, this, [this, response] { requestFinished(response); }); + connect(netJob.get(), &NetJob::succeeded, this, &ListModel::requestFinished); connect(netJob.get(), &NetJob::failed, this, &ListModel::requestFailed); } -void ListModel::requestFinished(QByteArray* responsePtr) +void ListModel::requestFinished() { - // NOTE(TheKodeToad): moving the response out to avoid it from being destroyed by jobPtr.reset() - QByteArray response = std::move(*responsePtr); jobPtr.reset(); QJsonParseError parse_error; - QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { qWarning() << "Error while parsing JSON response from ATL at" << parse_error.offset << "reason:" << parse_error.errorString(); - qWarning() << response; + qWarning() << *response; return; } @@ -133,7 +130,7 @@ void ListModel::requestFinished(QByteArray* responsePtr) try { ATLauncher::loadIndexedPack(pack, packObj); } catch (const JSONValidationError& e) { - qDebug() << QString::fromUtf8(response); + qDebug() << QString::fromUtf8(*response); qWarning() << "Error while reading pack manifest from ATLauncher:" << e.cause(); return; } diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.h b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.h index 51c5c782d..bcadd7c91 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.h +++ b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.h @@ -43,7 +43,7 @@ class ListModel : public QAbstractListModel { void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback); private slots: - void requestFinished(QByteArray* responsePtr); + void requestFinished(); void requestFailed(QString reason); void logoFailed(QString logo); @@ -61,6 +61,7 @@ class ListModel : public QAbstractListModel { QMap waitingCallbacks; NetJob::Ptr jobPtr; + std::shared_ptr response = std::make_shared(); }; } // namespace Atl diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp index 421060e85..3e072d441 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp @@ -159,26 +159,23 @@ void AtlOptionalModListModel::useShareCode(const QString& code) { m_jobPtr.reset(new NetJob("Atl::Request", APPLICATION->network())); auto url = QString(BuildConfig.ATL_API_BASE_URL + "share-codes/" + code); - auto [action, response] = Net::ApiDownload::makeByteArray(QUrl(url)); - m_jobPtr->addNetAction(action); + m_jobPtr->addNetAction(Net::ApiDownload::makeByteArray(QUrl(url), m_response)); - connect(m_jobPtr.get(), &NetJob::succeeded, this, [this, response] { shareCodeSuccess(response); }); + connect(m_jobPtr.get(), &NetJob::succeeded, this, &AtlOptionalModListModel::shareCodeSuccess); connect(m_jobPtr.get(), &NetJob::failed, this, &AtlOptionalModListModel::shareCodeFailure); m_jobPtr->start(); } -void AtlOptionalModListModel::shareCodeSuccess(QByteArray* responsePtr) +void AtlOptionalModListModel::shareCodeSuccess() { - // NOTE(TheKodeToad): moving the response out to avoid it from being destroyed by jobPtr.reset() - QByteArray responseData = *std::move(responsePtr); m_jobPtr.reset(); QJsonParseError parse_error{}; - auto doc = QJsonDocument::fromJson(responseData, &parse_error); + auto doc = QJsonDocument::fromJson(*m_response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { qWarning() << "Error while parsing JSON response from ATL at" << parse_error.offset << "reason:" << parse_error.errorString(); - qWarning() << responseData; + qWarning() << *m_response; return; } auto obj = doc.object(); @@ -187,7 +184,7 @@ void AtlOptionalModListModel::shareCodeSuccess(QByteArray* responsePtr) try { ATLauncher::loadShareCodeResponse(response, obj); } catch (const JSONValidationError& e) { - qDebug() << QString::fromUtf8(responseData); + qDebug() << QString::fromUtf8(*m_response); qWarning() << "Error while reading response from ATLauncher:" << e.cause(); return; } diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h index 8c36320e0..fa39e997c 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h +++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h @@ -71,7 +71,7 @@ class AtlOptionalModListModel : public QAbstractListModel { void useShareCode(const QString& code); public slots: - void shareCodeSuccess(QByteArray* responsePtr); + void shareCodeSuccess(); void shareCodeFailure(const QString& reason); void selectRecommended(); @@ -83,6 +83,7 @@ class AtlOptionalModListModel : public QAbstractListModel { private: NetJob::Ptr m_jobPtr; + std::shared_ptr m_response = std::make_shared(); ATLauncher::PackVersion m_version; QList m_mods; diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui index 717d0cca0..d9496142a 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui +++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui @@ -49,11 +49,7 @@
- - - QAbstractItemView::ScrollPerPixel - - + diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp index 14267bb1c..ad49d940e 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp @@ -61,7 +61,6 @@ AtlPage::AtlPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui->packView->setIndentation(0); ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - ui->versionSelectionBox->view()->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); for (int i = 0; i < filterModel->getAvailableSortings().size(); i++) { diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.ui b/launcher/ui/pages/modplatform/atlauncher/AtlPage.ui index 3fc0e55a4..0b1411b96 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.ui +++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.ui @@ -52,9 +52,6 @@ 48 - - QAbstractItemView::ScrollPerPixel - diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp index 861dd9f22..5d968d65a 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp @@ -165,20 +165,12 @@ void ListModel::fetchMore(const QModelIndex& parent) void ListModel::performPaginatedSearch() { static const FlameAPI api; - - // activate search by id only for numerical values because all CurseForge ids are numerical - static const QRegularExpression s_projectIdExpr("^\\#[0-9]+$"); - if (m_searchState != ResetRequested && s_projectIdExpr.match(m_currentSearchTerm).hasMatch()) { + if (m_currentSearchTerm.startsWith("#")) { auto projectId = m_currentSearchTerm.mid(1); if (!projectId.isEmpty()) { ResourceAPI::Callback callbacks; - callbacks.on_fail = [this](QString reason, int network_error_code) { - if (network_error_code == 404) { - m_searchState = ResetRequested; - } - searchRequestFailed(reason); - }; + 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!"; @@ -186,7 +178,7 @@ void ListModel::performPaginatedSearch() }; auto project = std::make_shared(); project->addonId = projectId; - if (auto job = api.getProjectInfo({ project }, std::move(callbacks), false); job) { + if (auto job = api.getProjectInfo({ project }, std::move(callbacks)); job) { m_jobPtr = job; m_jobPtr->start(); } diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index 336133819..79faccbc1 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -62,7 +62,6 @@ FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) m_ui->packView->setModel(m_listModel); m_ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - m_ui->versionSelectionBox->view()->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); m_ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); m_search_timer.setTimerType(Qt::TimerType::CoarseTimer); @@ -330,10 +329,10 @@ void FlamePage::createFilterWidget() connect(m_ui->filterButton, &QPushButton::clicked, this, [this] { m_filterWidget->setHidden(!m_filterWidget->isHidden()); }); connect(m_filterWidget.get(), &ModFilterWidget::filterChanged, this, &FlamePage::triggerSearch); - auto [task, response] = FlameAPI::getCategories(ModPlatform::ResourceType::Modpack); - m_categoriesTask = task; + auto response = std::make_shared(); + m_categoriesTask = FlameAPI::getCategories(response, ModPlatform::ResourceType::Modpack); connect(m_categoriesTask.get(), &Task::succeeded, [this, response]() { - auto categories = FlameAPI::loadModCategories(*response); + auto categories = FlameAPI::loadModCategories(response); m_filterWidget->setCategories(categories); }); m_categoriesTask->start(); diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.ui b/launcher/ui/pages/modplatform/flame/FlamePage.ui index 5d72f7513..cf882ef1c 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.ui +++ b/launcher/ui/pages/modplatform/flame/FlamePage.ui @@ -72,9 +72,6 @@ 48 - - QAbstractItemView::ScrollPerPixel - diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index acdce29b6..6ff435854 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -41,7 +41,7 @@ #include #include #include "modplatform/flame/FlameAPI.h" -#include "../ui_ResourcePage.h" +#include "ui_ResourcePage.h" #include "FlameResourceModels.h" #include "ui/dialogs/ResourceDownloadDialog.h" @@ -247,10 +247,10 @@ std::unique_ptr FlameModPage::createFilterWidget() void FlameModPage::prepareProviderCategories() { - auto [task, response] = FlameAPI::getModCategories(); - m_categoriesTask = task; + auto response = std::make_shared(); + m_categoriesTask = FlameAPI::getModCategories(response); connect(m_categoriesTask.get(), &Task::succeeded, [this, response]() { - auto categories = FlameAPI::loadModCategories(*response); + auto categories = FlameAPI::loadModCategories(response); m_filter_widget->setCategories(categories); }); m_categoriesTask->start(); diff --git a/launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp b/launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp deleted file mode 100644 index e33dda980..000000000 --- a/launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2020-2021 Jamie Mansfield - * - * 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 "FtbFilterModel.h" - -#include - -#include "modplatform/ftb/FTBPackManifest.h" - -#include "StringUtils.h" - -namespace Ftb { - -FilterModel::FilterModel(QObject* parent) : QSortFilterProxyModel(parent) -{ - m_currentSorting = Sorting::ByPlays; - m_sortings.insert(tr("Sort by Plays"), Sorting::ByPlays); - m_sortings.insert(tr("Sort by Installs"), Sorting::ByInstalls); - m_sortings.insert(tr("Sort by Name"), Sorting::ByName); -} - -const QMap FilterModel::getAvailableSortings() -{ - return m_sortings; -} - -QString FilterModel::translateCurrentSorting() -{ - return m_sortings.key(m_currentSorting); -} - -void FilterModel::setSorting(Sorting sorting) -{ - m_currentSorting = sorting; - invalidate(); -} - -FilterModel::Sorting FilterModel::getCurrentSorting() -{ - return m_currentSorting; -} - -void FilterModel::setSearchTerm(const QString& term) -{ - m_searchTerm = term.trimmed(); - invalidate(); -} - -bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const -{ - if (m_searchTerm.isEmpty()) { - return true; - } - - auto index = sourceModel()->index(sourceRow, 0, sourceParent); - auto pack = sourceModel()->data(index, Qt::UserRole).value(); - return pack.name.contains(m_searchTerm, Qt::CaseInsensitive); -} - -bool FilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) const -{ - FTB::Modpack leftPack = sourceModel()->data(left, Qt::UserRole).value(); - FTB::Modpack rightPack = sourceModel()->data(right, Qt::UserRole).value(); - - if (m_currentSorting == ByPlays) { - return leftPack.plays < rightPack.plays; - } else if (m_currentSorting == ByInstalls) { - return leftPack.installs < rightPack.installs; - } else if (m_currentSorting == ByName) { - return StringUtils::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0; - } - - // Invalid sorting set, somehow... - qWarning() << "Invalid sorting set!"; - return true; -} - -} // namespace Ftb diff --git a/launcher/ui/pages/modplatform/ftb/FtbFilterModel.h b/launcher/ui/pages/modplatform/ftb/FtbFilterModel.h deleted file mode 100644 index b9b958f05..000000000 --- a/launcher/ui/pages/modplatform/ftb/FtbFilterModel.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2020-2021 Jamie Mansfield - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -namespace Ftb { - -class FilterModel : public QSortFilterProxyModel { - Q_OBJECT - - public: - FilterModel(QObject* parent = Q_NULLPTR); - enum Sorting { - ByPlays, - ByInstalls, - ByName, - }; - const QMap getAvailableSortings(); - QString translateCurrentSorting(); - void setSorting(Sorting sorting); - Sorting getCurrentSorting(); - void setSearchTerm(const QString& term); - - protected: - bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; - bool lessThan(const QModelIndex& left, const QModelIndex& right) const override; - - private: - QMap m_sortings; - Sorting m_currentSorting; - QString m_searchTerm{ "" }; -}; - -} // namespace Ftb diff --git a/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp b/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp deleted file mode 100644 index 29d73a4a9..000000000 --- a/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Copyright 2020-2021 Jamie Mansfield - * - * 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 "FtbListModel.h" - -#include "Application.h" -#include "BuildConfig.h" -#include "Json.h" - -#include - -namespace Ftb { - -ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {} - -ListModel::~ListModel() {} - -int ListModel::rowCount(const QModelIndex& parent) const -{ - return parent.isValid() ? 0 : m_modpacks.size(); -} - -int ListModel::columnCount(const QModelIndex& parent) const -{ - return parent.isValid() ? 0 : 1; -} - -QVariant ListModel::data(const QModelIndex& index, int role) const -{ - int pos = index.row(); - if (pos >= m_modpacks.size() || pos < 0 || !index.isValid()) { - return QString("INVALID INDEX %1").arg(pos); - } - - FTB::Modpack pack = m_modpacks.at(pos); - if (role == Qt::DisplayRole) { - return pack.name; - } else if (role == Qt::ToolTipRole) { - return pack.synopsis; - } else if (role == Qt::DecorationRole) { - QIcon placeholder = QIcon::fromTheme("screenshot-placeholder"); - - auto iter = m_logoMap.find(pack.safeName); - if (iter != m_logoMap.end()) { - auto& logo = *iter; - if (!logo.result.isNull()) { - return logo.result; - } - return placeholder; - } - - for (auto art : pack.art) { - if (art.type == "square") { - ((ListModel*)this)->requestLogo(pack.safeName, art.url); - } - } - return placeholder; - } else if (role == Qt::UserRole) { - QVariant v; - v.setValue(pack); - return v; - } - - return QVariant(); -} - -void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback) -{ - if (m_logoMap.contains(logo)) { - callback(APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(logo))->getFullPath()); - } else { - requestLogo(logo, logoUrl); - } -} - -void ListModel::request() -{ - m_aborted = false; - - beginResetModel(); - m_modpacks.clear(); - endResetModel(); - - auto netJob = makeShared("Ftb::Request", APPLICATION->network()); - auto url = QString(BuildConfig.FTB_API_BASE_URL + "/modpack/all"); - auto [action, response] = Net::Download::makeByteArray(QUrl(url)); - netJob->addNetAction(action); - m_jobPtr = netJob; - m_jobPtr->start(); - - QObject::connect(netJob.get(), &NetJob::succeeded, this, [this, response] { requestFinished(response); }); - QObject::connect(netJob.get(), &NetJob::failed, this, &ListModel::requestFailed); -} - -void ListModel::abortRequest() -{ - m_aborted = m_jobPtr->abort(); - m_jobPtr.reset(); -} - -void ListModel::requestFinished(QByteArray* responsePtr) -{ - // NOTE(TheKodeToad): moving the response out to avoid it from being destroyed by m_jobPtr.reset() - QByteArray response = std::move(*responsePtr); - m_jobPtr.reset(); - m_remainingPacks.clear(); - - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString(); - qWarning() << response; - return; - } - - auto packs = doc.object().value("packs").toArray(); - for (auto pack : packs) { - auto packId = pack.toInt(); - m_remainingPacks.append(packId); - } - - if (!m_remainingPacks.isEmpty()) { - m_currentPack = m_remainingPacks.at(0); - requestPack(); - } -} - -void ListModel::requestFailed(QString) -{ - m_jobPtr.reset(); - m_remainingPacks.clear(); -} - -void ListModel::requestPack() -{ - auto netJob = makeShared("Ftb::Search", APPLICATION->network()); - auto searchUrl = QString(BuildConfig.FTB_API_BASE_URL + "/modpack/%1").arg(m_currentPack); - auto [action, response] = Net::Download::makeByteArray(QUrl(searchUrl)); - netJob->addNetAction(action); - m_jobPtr = netJob; - m_jobPtr->start(); - - QObject::connect(netJob.get(), &NetJob::succeeded, this, [this, response] { packRequestFinished(response); }); - QObject::connect(netJob.get(), &NetJob::failed, this, &ListModel::packRequestFailed); -} - -void ListModel::packRequestFinished(QByteArray* responsePtr) -{ - if (!m_jobPtr || m_aborted) - return; - - // NOTE(TheKodeToad): moving the response out to avoid it from being destroyed by jobPtr.reset() - QByteArray response = std::move(*responsePtr); - - m_jobPtr.reset(); - m_remainingPacks.removeOne(m_currentPack); - - QJsonParseError parse_error; - QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); - - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString(); - qWarning() << response; - return; - } - - auto obj = doc.object(); - - FTB::Modpack pack; - try { - FTB::loadModpack(pack, obj); - } catch (const JSONValidationError& e) { - qDebug() << QString::fromUtf8(response); - qWarning() << "Error while reading pack manifest from FTB: " << e.cause(); - return; - } - - // Since there is no guarantee that packs have a version, this will just - // ignore those "dud" packs. - if (pack.versions.empty()) { - qWarning() << "FTB Pack " << pack.id << " ignored. reason: lacking any versions"; - } else { - beginInsertRows(QModelIndex(), m_modpacks.size(), m_modpacks.size()); - m_modpacks.append(pack); - endInsertRows(); - } - - if (!m_remainingPacks.isEmpty()) { - m_currentPack = m_remainingPacks.at(0); - requestPack(); - } -} - -void ListModel::packRequestFailed(QString) -{ - m_jobPtr.reset(); - m_remainingPacks.removeOne(m_currentPack); -} - -void ListModel::logoLoaded(QString logo) -{ - auto& logoObj = m_logoMap[logo]; - logoObj.downloadJob.reset(); - logoObj.result = QIcon(logoObj.fullpath); - for (int i = 0; i < m_modpacks.size(); i++) { - if (m_modpacks[i].safeName == logo) { - emit dataChanged(createIndex(i, 0), createIndex(i, 0), { Qt::DecorationRole }); - } - } -} - -void ListModel::logoFailed(QString logo) -{ - m_logoMap[logo].failed = true; - m_logoMap[logo].downloadJob.reset(); -} - -void ListModel::requestLogo(QString logo, QString url) -{ - if (m_logoMap.contains(logo)) { - return; - } - - MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(logo)); - - auto job = makeShared(QString("FTB Icon Download %1").arg(logo), APPLICATION->network()); - job->setAskRetry(false); - job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); - - auto fullPath = entry->getFullPath(); - QObject::connect(job.get(), &NetJob::finished, this, [this, logo, fullPath] { logoLoaded(logo); }); - - QObject::connect(job.get(), &NetJob::failed, this, [this, logo] { logoFailed(logo); }); - - auto& newLogoEntry = m_logoMap[logo]; - newLogoEntry.downloadJob = job; - newLogoEntry.fullpath = fullPath; - job->start(); -} - -} // namespace Ftb diff --git a/launcher/ui/pages/modplatform/ftb/FtbListModel.h b/launcher/ui/pages/modplatform/ftb/FtbListModel.h deleted file mode 100644 index 339693c7c..000000000 --- a/launcher/ui/pages/modplatform/ftb/FtbListModel.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2020-2021 Jamie Mansfield - * - * 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 "modplatform/ftb/FTBPackManifest.h" -#include "net/NetJob.h" - -namespace Ftb { - -struct Logo { - QString fullpath; - NetJob::Ptr downloadJob; - QIcon result; - bool failed = false; -}; - -using LogoMap = QMap; -using LogoCallback = std::function; - -class ListModel : public QAbstractListModel { - Q_OBJECT - - public: - ListModel(QObject* parent); - virtual ~ListModel(); - - int rowCount(const QModelIndex& parent) const override; - int columnCount(const QModelIndex& parent) const override; - QVariant data(const QModelIndex& index, int role) const override; - - void request(); - void abortRequest(); - - void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback); - - [[nodiscard]] bool isMakingRequest() const { return m_jobPtr.get(); } - [[nodiscard]] bool wasAborted() const { return m_aborted; } - - private slots: - void requestFinished(QByteArray* responsePtr); - void requestFailed(QString reason); - - void requestPack(); - void packRequestFinished(QByteArray* responsePtr); - void packRequestFailed(QString reason); - - void logoFailed(QString logo); - void logoLoaded(QString logo); - - private: - void requestLogo(QString file, QString url); - - private: - bool m_aborted = false; - - QList m_modpacks; - LogoMap m_logoMap; - - NetJob::Ptr m_jobPtr; - int m_currentPack; - QList m_remainingPacks; -}; - -} // namespace Ftb diff --git a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp b/launcher/ui/pages/modplatform/ftb/FtbPage.cpp deleted file mode 100644 index b208f5c74..000000000 --- a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp +++ /dev/null @@ -1,198 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (c) 2022 Jamie Mansfield - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2020-2021 Jamie Mansfield - * Copyright 2021 Philip T - * - * 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 "FtbPage.h" -#include "ui_FtbPage.h" - -#include - -#include "modplatform/ftb/FTBPackInstallTask.h" -#include "ui/dialogs/NewInstanceDialog.h" - -#include "Markdown.h" - -FtbPage::FtbPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), m_ui(new Ui::FtbPage), m_dialog(dialog) -{ - m_ui->setupUi(this); - - m_filterModel = new Ftb::FilterModel(this); - m_listModel = new Ftb::ListModel(this); - m_filterModel->setSourceModel(m_listModel); - m_ui->packView->setModel(m_filterModel); - m_ui->packView->setSortingEnabled(true); - m_ui->packView->header()->hide(); - m_ui->packView->setIndentation(0); - - m_ui->searchEdit->installEventFilter(this); - - m_ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - m_ui->versionSelectionBox->view()->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); - m_ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); - - for (int i = 0; i < m_filterModel->getAvailableSortings().size(); i++) { - m_ui->sortByBox->addItem(m_filterModel->getAvailableSortings().keys().at(i)); - } - m_ui->sortByBox->setCurrentText(m_filterModel->translateCurrentSorting()); - - connect(m_ui->searchEdit, &QLineEdit::textChanged, this, &FtbPage::triggerSearch); - connect(m_ui->sortByBox, &QComboBox::currentTextChanged, this, &FtbPage::onSortingSelectionChanged); - connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FtbPage::onSelectionChanged); - connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FtbPage::onVersionSelectionChanged); - - m_ui->packDescription->setMetaEntry("FTBPacks"); -} - -FtbPage::~FtbPage() -{ - delete m_ui; -} - -bool FtbPage::eventFilter(QObject* watched, QEvent* event) -{ - if (watched == m_ui->searchEdit && event->type() == QEvent::KeyPress) { - QKeyEvent* keyEvent = static_cast(event); - if (keyEvent->key() == Qt::Key_Return) { - triggerSearch(); - keyEvent->accept(); - return true; - } - } - return QWidget::eventFilter(watched, event); -} - -bool FtbPage::shouldDisplay() const -{ - return true; -} - -void FtbPage::retranslate() -{ - m_ui->retranslateUi(this); -} - -void FtbPage::openedImpl() -{ - if (!m_initialised || m_listModel->wasAborted()) { - m_listModel->request(); - m_initialised = true; - } - - suggestCurrent(); -} - -void FtbPage::closedImpl() -{ - if (m_listModel->isMakingRequest()) - m_listModel->abortRequest(); -} - -void FtbPage::suggestCurrent() -{ - if (!isOpened) { - return; - } - - if (m_selectedVersion.isEmpty()) { - m_dialog->setSuggestedPack(); - return; - } - - m_dialog->setSuggestedPack(m_selected.name, m_selectedVersion, new FTB::PackInstallTask(m_selected, m_selectedVersion, this)); - for (auto art : m_selected.art) { - if (art.type == "square") { - auto editedLogoName = "ftb_" + m_selected.safeName; - m_listModel->getLogo(m_selected.safeName, art.url, - [this, editedLogoName](QString logo) { m_dialog->setSuggestedIconFromFile(logo, editedLogoName); }); - } - } -} - -void FtbPage::triggerSearch() -{ - m_filterModel->setSearchTerm(m_ui->searchEdit->text()); -} - -void FtbPage::onSortingSelectionChanged(QString selected) -{ - auto toSet = m_filterModel->getAvailableSortings().value(selected); - m_filterModel->setSorting(toSet); -} - -void FtbPage::onSelectionChanged(QModelIndex first, QModelIndex /*second*/) -{ - m_ui->versionSelectionBox->clear(); - - if (!first.isValid()) { - if (isOpened) { - m_dialog->setSuggestedPack(); - } - return; - } - - m_selected = m_filterModel->data(first, Qt::UserRole).value(); - - QString output = markdownToHTML(m_selected.description.toUtf8()); - m_ui->packDescription->setHtml(output); - - // reverse foreach, so that the newest versions are first - for (auto i = m_selected.versions.size(); i--;) { - m_ui->versionSelectionBox->addItem(m_selected.versions.at(i).name); - } - - suggestCurrent(); -} - -void FtbPage::onVersionSelectionChanged(QString selected) -{ - if (selected.isNull() || selected.isEmpty()) { - m_selectedVersion = ""; - return; - } - - m_selectedVersion = selected; - suggestCurrent(); -} - -QString FtbPage::getSerachTerm() const -{ - return m_ui->searchEdit->text(); -} - -void FtbPage::setSearchTerm(QString term) -{ - m_ui->searchEdit->setText(term); -} \ No newline at end of file diff --git a/launcher/ui/pages/modplatform/ftb/FtbPage.h b/launcher/ui/pages/modplatform/ftb/FtbPage.h deleted file mode 100644 index 84e7740d4..000000000 --- a/launcher/ui/pages/modplatform/ftb/FtbPage.h +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - 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 "FtbFilterModel.h" -#include "FtbListModel.h" - -#include - -#include "Application.h" -#include "tasks/Task.h" -#include "ui/pages/BasePage.h" -#include "ui/pages/modplatform/ModpackProviderBasePage.h" - -namespace Ui { -class FtbPage; -} - -class NewInstanceDialog; - -class FtbPage : public QWidget, public ModpackProviderBasePage { - Q_OBJECT - - public: - explicit FtbPage(NewInstanceDialog* dialog, QWidget* parent = 0); - virtual ~FtbPage(); - virtual QString displayName() const override { return "FTB"; } - virtual QIcon icon() const override { return QIcon::fromTheme("ftb_logo"); } - virtual QString id() const override { return "ftb"; } - virtual QString helpPage() const override { return "FTB-platform"; } - virtual bool shouldDisplay() const override; - void retranslate() override; - - void openedImpl() override; - void closedImpl() override; - - bool eventFilter(QObject* watched, QEvent* event) override; - - /** 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; - - private: - void suggestCurrent(); - - private slots: - void triggerSearch(); - - void onSortingSelectionChanged(QString selected); - void onSelectionChanged(QModelIndex first, QModelIndex second); - void onVersionSelectionChanged(QString selected); - - private: - Ui::FtbPage* m_ui = nullptr; - NewInstanceDialog* m_dialog = nullptr; - Ftb::ListModel* m_listModel = nullptr; - Ftb::FilterModel* m_filterModel = nullptr; - - FTB::Modpack m_selected; - QString m_selectedVersion; - - bool m_initialised{ false }; -}; diff --git a/launcher/ui/pages/modplatform/ftb/FtbPage.ui b/launcher/ui/pages/modplatform/ftb/FtbPage.ui deleted file mode 100644 index e7fe6f482..000000000 --- a/launcher/ui/pages/modplatform/ftb/FtbPage.ui +++ /dev/null @@ -1,96 +0,0 @@ - - - FtbPage - - - - 0 - 0 - 875 - 745 - - - - - - - - - - - - Version selected: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - Search and filter... - - - true - - - - - - - - - true - - - - 48 - 48 - - - - QAbstractItemView::ScrollPerPixel - - - - - - - true - - - true - - - - - - - - - Note: Many recent FTB modpacks are also available from CurseForge! - - - - - - - - ProjectDescriptionPage - QTextBrowser -
ui/widgets/ProjectDescriptionPage.h
-
-
- - searchEdit - versionSelectionBox - - - -
diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui index aa9b5aee2..bb9990650 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui +++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui @@ -61,9 +61,6 @@
- - QAbstractItemView::ScrollPerPixel - 16777215 diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp index 5c9c2fd72..0e352f419 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp @@ -23,7 +23,6 @@ #include #include #include "Application.h" -#include "settings/SettingsObject.h" #include "Exception.h" #include "FileSystem.h" #include "Json.h" diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp index ab2bc6a67..eb95b291c 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp @@ -35,7 +35,6 @@ #include "ListModel.h" #include "Application.h" -#include "settings/SettingsObject.h" #include "net/ApiDownload.h" #include "net/HttpMetaCache.h" #include "net/NetJob.h" @@ -192,7 +191,6 @@ QVariant ListModel::data(const QModelIndex& index, int role) const // bugged pack, currently only indicates bugged xml return QColor(244, 229, 66); } - return {}; } case Qt::DisplayRole: return pack.name; diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp index be4d3161d..1576f52fe 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp @@ -107,7 +107,6 @@ Page::Page(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), dialog } ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - ui->versionSelectionBox->view()->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &Page::onSortingSelectionChanged); diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.ui b/launcher/ui/pages/modplatform/legacy_ftb/Page.ui index d3d696b80..544ad77d3 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.ui +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.ui @@ -46,9 +46,6 @@ true - - QAbstractItemView::ScrollPerPixel - @@ -83,9 +80,6 @@ true - - QAbstractItemView::ScrollPerPixel - @@ -106,9 +100,6 @@ true - - QAbstractItemView::ScrollPerPixel - diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 03518c3af..05cd2970c 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -135,26 +135,20 @@ void ModpackListModel::performPaginatedSearch() return; static const ModrinthAPI api; - // Modrinth ids are not limited to numbers and can be any length - if (m_searchState != ResetRequested && m_currentSearchTerm.startsWith("#")) { + if (m_currentSearchTerm.startsWith("#")) { auto projectId = m_currentSearchTerm.mid(1); if (!projectId.isEmpty()) { ResourceAPI::Callback callbacks; - callbacks.on_fail = [this](QString reason, int network_error_code) { - if (network_error_code == 404) { - m_searchState = ResetRequested; - } - searchRequestFailed(reason, network_error_code); - }; + 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", 0); + searchRequestFailed("Aborted"); }; auto project = std::make_shared(); project->addonId = projectId; - if (auto job = api.getProjectInfo({ project }, std::move(callbacks), false); job) { + if (auto job = api.getProjectInfo({ project }, std::move(callbacks)); job) { m_jobPtr = job; m_jobPtr->start(); } @@ -167,10 +161,10 @@ void ModpackListModel::performPaginatedSearch() ResourceAPI::Callback> callbacks{}; callbacks.on_succeed = [this](auto& doc) { searchRequestFinished(doc); }; - callbacks.on_fail = [this](QString reason, int network_error_code) { searchRequestFailed(reason, network_error_code); }; + callbacks.on_fail = [this](QString reason, int) { searchRequestFailed(reason); }; callbacks.on_abort = [this] { qCritical() << "Search task aborted by an unknown reason!"; - searchRequestFailed("Aborted", 0); + searchRequestFailed("Aborted"); }; auto netJob = api.searchProjects({ ModPlatform::ResourceType::Modpack, m_nextSearchOffset, m_currentSearchTerm, sort, m_filter->loaders, @@ -322,12 +316,13 @@ void ModpackListModel::searchRequestForOneSucceeded(ModPlatform::IndexedPack::Pt endInsertRows(); } -void ModpackListModel::searchRequestFailed(QString reason, int network_error_code) +void ModpackListModel::searchRequestFailed(QString) { - if (network_error_code == -1) { - // Unknown error in network stack + 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.")); - } else if (network_error_code == 409) { + } else if (failed_action->replyStatusCode() == 409) { // 409 Gone, notify user to update QMessageBox::critical(nullptr, tr("Error"), //: %1 refers to the launcher itself diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h index 3e0fc1686..114c07ca6 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h @@ -85,7 +85,7 @@ class ModpackListModel : public QAbstractListModel { public slots: void searchRequestFinished(QList& doc_all); - void searchRequestFailed(QString reason, int network_error_code); + void searchRequestFailed(QString reason); void searchRequestForOneSucceeded(ModPlatform::IndexedPack::Ptr); protected slots: @@ -98,7 +98,7 @@ class ModpackListModel : public QAbstractListModel { protected: void requestLogo(QString file, QString url); - inline auto getMineVersions() const -> std::vector; + inline auto getMineVersions() const -> std::list; protected: ModrinthPage* m_parent; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index 4798583bd..73685d3ba 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -68,7 +68,6 @@ ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) m_ui->packView->setModel(m_model); m_ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - m_ui->versionSelectionBox->view()->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); m_ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); m_search_timer.setTimerType(Qt::TimerType::CoarseTimer); @@ -372,11 +371,11 @@ void ModrinthPage::createFilterWidget() connect(m_ui->filterButton, &QPushButton::clicked, this, [this] { m_filterWidget->setHidden(!m_filterWidget->isHidden()); }); connect(m_filterWidget.get(), &ModFilterWidget::filterChanged, this, &ModrinthPage::triggerSearch); - auto [categoriesTask, response] = ModrinthAPI::getModCategories(); - m_categoriesTask = categoriesTask; + auto response = std::make_shared(); + m_categoriesTask = ModrinthAPI::getModCategories(response); connect(m_categoriesTask.get(), &Task::succeeded, [this, response]() { - auto categories = ModrinthAPI::loadCategories(*response, "modpack"); + auto categories = ModrinthAPI::loadCategories(response, "modpack"); m_filterWidget->setCategories(categories); }); m_categoriesTask->start(); -} +} \ No newline at end of file diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui index c68d01d97..d6e983929 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui @@ -54,9 +54,6 @@ 48 - - QAbstractItemView::ScrollPerPixel - diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index a1a7390bb..32296316f 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -38,7 +38,7 @@ #include "ModrinthResourcePages.h" #include "ui/pages/modplatform/DataPackModel.h" -#include "../ui_ResourcePage.h" +#include "ui_ResourcePage.h" #include "modplatform/modrinth/ModrinthAPI.h" @@ -165,10 +165,10 @@ std::unique_ptr ModrinthModPage::createFilterWidget() void ModrinthModPage::prepareProviderCategories() { - auto [categoriesTask, response] = ModrinthAPI::getModCategories(); - m_categoriesTask = categoriesTask; + auto response = std::make_shared(); + m_categoriesTask = ModrinthAPI::getModCategories(response); connect(m_categoriesTask.get(), &Task::succeeded, [this, response]() { - auto categories = ModrinthAPI::loadModCategories(*response); + auto categories = ModrinthAPI::loadModCategories(response); m_filter_widget->setCategories(categories); }); m_categoriesTask->start(); diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp index af2aed6d2..8e82dd848 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp @@ -37,7 +37,6 @@ #include "Application.h" #include "BuildConfig.h" #include "Json.h" -#include "settings/SettingsObject.h" #include "net/ApiDownload.h" #include "ui/widgets/ProjectItem.h" @@ -157,25 +156,23 @@ void Technic::ListModel::performSearch() if (!clientId.isEmpty()) { searchUrl += "?cid=" + clientId; } - auto [action, response] = Net::ApiDownload::makeByteArray(QUrl(searchUrl)); - netJob->addNetAction(action); + netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response)); jobPtr = netJob; jobPtr->start(); - connect(netJob.get(), &NetJob::succeeded, this, [this, response] { searchRequestFinished(response); }); + connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished); connect(netJob.get(), &NetJob::failed, this, &ListModel::searchRequestFailed); } -void Technic::ListModel::searchRequestFinished(QByteArray* responsePtr) +void Technic::ListModel::searchRequestFinished() { - // NOTE(TheKodeToad): moving the response out to avoid it from being destroyed by jobPtr.reset() - QByteArray response = std::move(*responsePtr); jobPtr.reset(); QJsonParseError parse_error; - QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from Technic at" << parse_error.offset << "reason:" << parse_error.errorString(); - qWarning() << response; + qWarning() << "Error while parsing JSON response from Technic at" << parse_error.offset + << "reason:" << parse_error.errorString(); + qWarning() << *response; return; } diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.h b/launcher/ui/pages/modplatform/technic/TechnicModel.h index 872f8b5d6..4979000e9 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicModel.h +++ b/launcher/ui/pages/modplatform/technic/TechnicModel.h @@ -62,7 +62,7 @@ class ListModel : public QAbstractListModel { Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; } private slots: - void searchRequestFinished(QByteArray* responsePtr); + void searchRequestFinished(); void searchRequestFailed(); void logoFailed(QString logo); @@ -86,6 +86,7 @@ class ListModel : public QAbstractListModel { Single, } searchMode = List; NetJob::Ptr jobPtr; + std::shared_ptr response = std::make_shared(); }; } // namespace Technic diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp index 0858d6397..d5ed18696 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp @@ -61,9 +61,6 @@ TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget* parent) ui->searchEdit->installEventFilter(this); model = new Technic::ListModel(this); ui->packView->setModel(model); - ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - ui->versionSelectionBox->view()->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); - ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); m_search_timer.setTimerType(Qt::TimerType::CoarseTimer); m_search_timer.setSingleShot(true); @@ -165,12 +162,9 @@ void TechnicPage::suggestCurrent() auto netJob = makeShared(QString("Technic::PackMeta(%1)").arg(current.name), APPLICATION->network()); QString slug = current.slug; - auto [action, responsePtr] = Net::ApiDownload::makeByteArray( - QString("%1modpack/%2?build=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, slug, BuildConfig.TECHNIC_API_BUILD)); - netJob->addNetAction(action); - connect(netJob.get(), &NetJob::succeeded, this, [this, responsePtr, slug] { - // NOTE(TheKodeToad): moving the response out to avoid it from being destroyed by jobPtr.reset() - QByteArray response = std::move(*responsePtr); + netJob->addNetAction(Net::ApiDownload::makeByteArray( + QString("%1modpack/%2?build=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, slug, BuildConfig.TECHNIC_API_BUILD), response)); + connect(netJob.get(), &NetJob::succeeded, this, [this, slug] { jobPtr.reset(); if (current.slug != slug) { @@ -178,12 +172,12 @@ void TechnicPage::suggestCurrent() } QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); QJsonObject obj = doc.object(); if (parse_error.error != QJsonParseError::NoError) { qWarning() << "Error while parsing JSON response from Technic at" << parse_error.offset << "reason:" << parse_error.errorString(); - qWarning() << response; + qWarning() << *response; return; } if (!obj.contains("url")) { @@ -266,10 +260,9 @@ void TechnicPage::metadataLoaded() auto netJob = makeShared(QString("Technic::SolderMeta(%1)").arg(current.name), APPLICATION->network()); auto url = QString("%1/modpack/%2").arg(current.url, current.slug); - auto [action, response] = Net::ApiDownload::makeByteArray(QUrl(url)); - netJob->addNetAction(action); + netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(url), response)); - connect(netJob.get(), &NetJob::succeeded, this, [this, response] { onSolderLoaded(response); }); + connect(netJob.get(), &NetJob::succeeded, this, &TechnicPage::onSolderLoaded); connect(jobPtr.get(), &NetJob::failed, [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); @@ -300,10 +293,8 @@ void TechnicPage::selectVersion() } } -void TechnicPage::onSolderLoaded(QByteArray* responsePtr) +void TechnicPage::onSolderLoaded() { - // NOTE(TheKodeToad): moving the response out to avoid it from being destroyed by jobPtr.reset() - QByteArray response = std::move(*responsePtr); jobPtr.reset(); auto fallback = [this]() { @@ -316,10 +307,10 @@ void TechnicPage::onSolderLoaded(QByteArray* responsePtr) current.versions.clear(); QJsonParseError parse_error{}; - auto doc = QJsonDocument::fromJson(response, &parse_error); + auto doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { qWarning() << "Error while parsing JSON response from Solder at" << parse_error.offset << "reason:" << parse_error.errorString(); - qWarning() << response; + qWarning() << *response; fallback(); return; } diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.h b/launcher/ui/pages/modplatform/technic/TechnicPage.h index 466be81d4..a131a6db1 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.h +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.h @@ -83,7 +83,7 @@ class TechnicPage : public QWidget, public ModpackProviderBasePage { private slots: void triggerSearch(); void onSelectionChanged(QModelIndex first, QModelIndex second); - void onSolderLoaded(QByteArray* responsePtr); + void onSolderLoaded(); void onVersionSelectionChanged(QString data); private: @@ -95,6 +95,7 @@ class TechnicPage : public QWidget, public ModpackProviderBasePage { QString selectedVersion; NetJob::Ptr jobPtr; + std::shared_ptr response = std::make_shared(); ProgressWidget m_fetch_progress; diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.ui b/launcher/ui/pages/modplatform/technic/TechnicPage.ui index 31936776a..f4e75ae12 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.ui +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.ui @@ -31,9 +31,6 @@ 48 - - QAbstractItemView::ScrollPerPixel - diff --git a/launcher/ui/setupwizard/AutoJavaWizardPage.cpp b/launcher/ui/setupwizard/AutoJavaWizardPage.cpp index 06fc9075b..fd173e71d 100644 --- a/launcher/ui/setupwizard/AutoJavaWizardPage.cpp +++ b/launcher/ui/setupwizard/AutoJavaWizardPage.cpp @@ -2,7 +2,6 @@ #include "ui_AutoJavaWizardPage.h" #include "Application.h" -#include "settings/SettingsObject.h" AutoJavaWizardPage::AutoJavaWizardPage(QWidget* parent) : BaseWizardPage(parent), ui(new Ui::AutoJavaWizardPage) { diff --git a/launcher/ui/setupwizard/JavaWizardPage.cpp b/launcher/ui/setupwizard/JavaWizardPage.cpp index baeab2da8..6b8ece9f7 100644 --- a/launcher/ui/setupwizard/JavaWizardPage.cpp +++ b/launcher/ui/setupwizard/JavaWizardPage.cpp @@ -1,6 +1,5 @@ #include "JavaWizardPage.h" #include "Application.h" -#include "settings/SettingsObject.h" #include #include @@ -11,6 +10,8 @@ #include #include +#include + #include "JavaCommon.h" #include "ui/widgets/JavaWizardWidget.h" diff --git a/launcher/ui/setupwizard/LanguageWizardPage.cpp b/launcher/ui/setupwizard/LanguageWizardPage.cpp index e9ba36299..09cdb807e 100644 --- a/launcher/ui/setupwizard/LanguageWizardPage.cpp +++ b/launcher/ui/setupwizard/LanguageWizardPage.cpp @@ -1,6 +1,5 @@ #include "LanguageWizardPage.h" #include -#include "settings/SettingsObject.h" #include #include diff --git a/launcher/ui/setupwizard/PasteWizardPage.cpp b/launcher/ui/setupwizard/PasteWizardPage.cpp index 979ec50fd..777fd3a44 100644 --- a/launcher/ui/setupwizard/PasteWizardPage.cpp +++ b/launcher/ui/setupwizard/PasteWizardPage.cpp @@ -2,7 +2,6 @@ #include "ui_PasteWizardPage.h" #include "Application.h" -#include "settings/SettingsObject.h" #include "net/PasteUpload.h" PasteWizardPage::PasteWizardPage(QWidget* parent) : BaseWizardPage(parent), ui(new Ui::PasteWizardPage) diff --git a/launcher/ui/themes/CatPainter.cpp b/launcher/ui/themes/CatPainter.cpp index a4bda0297..7c152fdc9 100644 --- a/launcher/ui/themes/CatPainter.cpp +++ b/launcher/ui/themes/CatPainter.cpp @@ -19,7 +19,6 @@ #include "ui/themes/CatPainter.h" #include #include "Application.h" -#include "settings/SettingsObject.h" CatPainter::CatPainter(const QString& path, QObject* parent) : QObject(parent) { diff --git a/launcher/ui/themes/ThemeManager.cpp b/launcher/ui/themes/ThemeManager.cpp index 89478960d..b69a416d9 100644 --- a/launcher/ui/themes/ThemeManager.cpp +++ b/launcher/ui/themes/ThemeManager.cpp @@ -33,7 +33,6 @@ #include "ui/themes/SystemTheme.h" #include "Application.h" -#include "settings/SettingsObject.h" ThemeManager::ThemeManager() { diff --git a/launcher/ui/widgets/AppearanceWidget.cpp b/launcher/ui/widgets/AppearanceWidget.cpp index 41a80dc2a..5ab622ef9 100644 --- a/launcher/ui/widgets/AppearanceWidget.cpp +++ b/launcher/ui/widgets/AppearanceWidget.cpp @@ -43,9 +43,6 @@ #include "ui/themes/ITheme.h" #include "ui/themes/ThemeManager.h" -#include -#include "settings/SettingsObject.h" - AppearanceWidget::AppearanceWidget(bool themesOnly, QWidget* parent) : QWidget(parent), m_ui(new Ui::AppearanceWidget), m_themesOnly(themesOnly) { @@ -95,7 +92,7 @@ AppearanceWidget::~AppearanceWidget() void AppearanceWidget::applySettings() { - SettingsObject* settings = APPLICATION->settings(); + SettingsObjectPtr settings = APPLICATION->settings(); QString consoleFontFamily = m_ui->consoleFont->currentFont().family(); settings->set("ConsoleFont", consoleFontFamily); settings->set("ConsoleFontSize", m_ui->fontSizeBox->value()); @@ -106,7 +103,7 @@ void AppearanceWidget::applySettings() void AppearanceWidget::loadSettings() { - SettingsObject* settings = APPLICATION->settings(); + SettingsObjectPtr settings = APPLICATION->settings(); QString fontFamily = settings->get("ConsoleFont").toString(); QFont consoleFont(fontFamily); m_ui->consoleFont->setCurrentFont(consoleFont); @@ -178,7 +175,7 @@ void AppearanceWidget::loadThemeSettings() m_ui->widgetStyleComboBox->clear(); m_ui->catPackComboBox->clear(); - SettingsObject* settings = APPLICATION->settings(); + const SettingsObjectPtr settings = APPLICATION->settings(); const QString currentIconTheme = settings->get("IconTheme").toString(); const auto iconThemes = APPLICATION->themeManager()->getValidIconThemes(); diff --git a/launcher/ui/widgets/AppearanceWidget.h b/launcher/ui/widgets/AppearanceWidget.h index a63c53112..1fc89af3a 100644 --- a/launcher/ui/widgets/AppearanceWidget.h +++ b/launcher/ui/widgets/AppearanceWidget.h @@ -20,9 +20,13 @@ #pragma once #include +#include +#include #include #include +#include "java/JavaChecker.h" +#include "ui/pages/BasePage.h" class QTextCharFormat; class SettingsObject; diff --git a/launcher/ui/widgets/EnvironmentVariables.cpp b/launcher/ui/widgets/EnvironmentVariables.cpp index 33bb00c63..9387ef2e2 100644 --- a/launcher/ui/widgets/EnvironmentVariables.cpp +++ b/launcher/ui/widgets/EnvironmentVariables.cpp @@ -104,7 +104,7 @@ QMap EnvironmentVariables::value() const QMap result; QTreeWidgetItem* item = ui->list->topLevelItem(0); for (int i = 1; item != nullptr; item = ui->list->topLevelItem(i++)) - result[item->text(0).trimmed()] = item->text(1).trimmed(); + result[item->text(0)] = item->text(1); return result; } diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp index 3bf9fef82..1e641c4f9 100644 --- a/launcher/ui/widgets/InfoFrame.cpp +++ b/launcher/ui/widgets/InfoFrame.cpp @@ -36,7 +36,6 @@ #include #include -#include #include #include #include @@ -76,7 +75,7 @@ InfoFrame::~InfoFrame() delete ui; } -void InfoFrame::updateWithMod(const Mod& m) +void InfoFrame::updateWithMod(Mod const& m) { if (m.type() == ResourceType::FOLDER) { clear(); @@ -87,9 +86,9 @@ void InfoFrame::updateWithMod(const Mod& m) QString name = ""; QString link = m.homepage(); if (m.name().isEmpty()) - name = m.internalId(); + name = m.internal_id(); else - name = renderColorCodes(m.name()); + name = m.name(); if (link.isEmpty()) text = name; @@ -104,7 +103,7 @@ void InfoFrame::updateWithMod(const Mod& m) if (m.description().isEmpty()) { setDescription(QString()); } else { - setDescription(renderColorCodes(m.description())); + setDescription(m.description()); } setImage(m.icon({ 64, 64 })); @@ -147,12 +146,11 @@ void InfoFrame::updateWithMod(const Mod& m) void InfoFrame::updateWithResource(const Resource& resource) { const QString homepage = resource.homepage(); - auto name = renderColorCodes(resource.name()); if (!homepage.isEmpty()) - setName("" + name + ""); + setName("" + resource.name() + ""); else - setName(name); + setName(resource.name()); setImage(); } @@ -183,10 +181,10 @@ QString InfoFrame::renderColorCodes(QString input) while (it != input.constEnd()) { // is current char § and is there a following char if (*it == u'§' && (it + 1) != input.constEnd()) { - const auto& code = *(++it); // incrementing here! + auto const& code = *(++it); // incrementing here! - const auto color_entry = color_codes_map.constFind(code); - const auto tag_entry = formatting_codes_map.constFind(code); + auto const color_entry = color_codes_map.constFind(code); + auto const tag_entry = formatting_codes_map.constFind(code); if (color_entry != color_codes_map.constEnd()) { // color code html += QString("").arg(color_entry.value()); @@ -271,7 +269,6 @@ void InfoFrame::updateHiddenState() void InfoFrame::setName(QString text) { - resetScroll(); if (text.isEmpty()) { ui->nameLabel->setHidden(true); } else { @@ -421,9 +418,3 @@ void InfoFrame::boxClosed([[maybe_unused]] int result) { m_current_box = nullptr; } - -void InfoFrame::resetScroll() -{ - ui->scrollArea->horizontalScrollBar()->setValue(0); - ui->scrollArea->verticalScrollBar()->setValue(0); -} diff --git a/launcher/ui/widgets/InfoFrame.h b/launcher/ui/widgets/InfoFrame.h index b2c867cce..20c54e2e5 100644 --- a/launcher/ui/widgets/InfoFrame.h +++ b/launcher/ui/widgets/InfoFrame.h @@ -76,7 +76,6 @@ class InfoFrame : public QFrame { private: void updateHiddenState(); - void resetScroll(); private: Ui::InfoFrame* ui; diff --git a/launcher/ui/widgets/InfoFrame.ui b/launcher/ui/widgets/InfoFrame.ui index 58abcffde..c4d8c83d3 100644 --- a/launcher/ui/widgets/InfoFrame.ui +++ b/launcher/ui/widgets/InfoFrame.ui @@ -7,7 +7,7 @@ 0 0 527 - 120 + 113 @@ -22,7 +22,7 @@ 120 - + 0 @@ -35,7 +35,7 @@ 0 - + @@ -60,120 +60,95 @@ - - - - - 0 - 0 - + + + + - + + + + + Qt::RichText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + true - - - - 0 - 0 - 455 - 118 - - - - - - - - - - Qt::RichText - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - true - - - true - - - Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - - - - - - - Qt::RichText - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - true - - - true - - - Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - - - - Qt::RichText - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - true - - - true - - - Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - - - - Qt::RichText - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - true - - - true - - - Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - + + true + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + Qt::RichText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + true + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + Qt::RichText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + true + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + Qt::RichText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + true + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index e13c847d0..01da7cc48 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -42,20 +42,19 @@ #include "Application.h" #include "BuildConfig.h" #include "FileSystem.h" -#include "HardwareInfo.h" #include "JavaCommon.h" -#include "SysInfo.h" #include "java/JavaInstallList.h" #include "java/JavaUtils.h" #include "settings/Setting.h" +#include "sys.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/VersionSelectDialog.h" #include "ui/java/InstallJavaDialog.h" #include "ui_JavaSettingsWidget.h" -JavaSettingsWidget::JavaSettingsWidget(BaseInstance* instance, QWidget* parent) - : QWidget(parent), m_instance(instance), m_ui(new Ui::JavaSettingsWidget) +JavaSettingsWidget::JavaSettingsWidget(InstancePtr instance, QWidget* parent) + : QWidget(parent), m_instance(std::move(instance)), m_ui(new Ui::JavaSettingsWidget) { m_ui->setupUi(this); @@ -80,7 +79,7 @@ JavaSettingsWidget::JavaSettingsWidget(BaseInstance* instance, QWidget* parent) m_ui->memoryGroupBox->setCheckable(true); m_ui->javaArgumentsGroupBox->setCheckable(true); - SettingsObject* settings = m_instance->settings(); + SettingsObjectPtr settings = m_instance->settings(); connect(settings->getSetting("OverrideJavaLocation").get(), &Setting::SettingChanged, m_ui->javaInstallationGroupBox, [this, settings] { m_ui->javaInstallationGroupBox->setChecked(settings->get("OverrideJavaLocation").toBool()); }); @@ -88,7 +87,7 @@ JavaSettingsWidget::JavaSettingsWidget(BaseInstance* instance, QWidget* parent) [this, settings] { m_ui->javaPathTextBox->setText(settings->get("JavaPath").toString()); }); connect(m_ui->javaDownloadBtn, &QPushButton::clicked, this, [this] { - auto javaDialog = new Java::InstallDialog({}, m_instance, this); + auto javaDialog = new Java::InstallDialog({}, m_instance.get(), this); javaDialog->exec(); }); connect(m_ui->javaPathTextBox, &QLineEdit::textChanged, [this](QString newValue) { @@ -116,7 +115,7 @@ JavaSettingsWidget::~JavaSettingsWidget() void JavaSettingsWidget::loadSettings() { - SettingsObject* settings; + SettingsObjectPtr settings; if (m_instance != nullptr) settings = m_instance->settings(); @@ -151,7 +150,6 @@ void JavaSettingsWidget::loadSettings() m_ui->maxMemSpinBox->setValue(min); } m_ui->permGenSpinBox->setValue(settings->get("PermGen").toInt()); - m_ui->lowMemWarningCheckBox->setChecked(settings->get("LowMemWarning").toBool()); // Java arguments m_ui->javaArgumentsGroupBox->setChecked(m_instance == nullptr || settings->get("OverrideJavaArgs").toBool()); @@ -160,7 +158,7 @@ void JavaSettingsWidget::loadSettings() void JavaSettingsWidget::saveSettings() { - SettingsObject* settings; + SettingsObjectPtr settings; if (m_instance != nullptr) settings = m_instance->settings(); @@ -206,12 +204,10 @@ void JavaSettingsWidget::saveSettings() settings->set("MaxMemAlloc", min); } settings->set("PermGen", m_ui->permGenSpinBox->value()); - settings->set("LowMemWarning", m_ui->lowMemWarningCheckBox->isChecked()); } else { settings->reset("MinMemAlloc"); settings->reset("MaxMemAlloc"); settings->reset("PermGen"); - settings->reset("LowMemWarning"); } // Java arguments @@ -269,7 +265,7 @@ void JavaSettingsWidget::onJavaAutodetect() return; } - VersionSelectDialog versionDialog(APPLICATION->javalist(), tr("Select a Java version"), this, true); + VersionSelectDialog versionDialog(APPLICATION->javalist().get(), tr("Select a Java version"), this, true); versionDialog.setResizeOn(2); versionDialog.exec(); @@ -289,7 +285,7 @@ void JavaSettingsWidget::onJavaAutodetect() } void JavaSettingsWidget::updateThresholds() { - auto sysMiB = HardwareInfo::totalRamMiB(); + auto sysMiB = Sys::getSystemRam() / Sys::mebibyte; unsigned int maxMem = m_ui->maxMemSpinBox->value(); unsigned int minMem = m_ui->minMemSpinBox->value(); diff --git a/launcher/ui/widgets/JavaSettingsWidget.h b/launcher/ui/widgets/JavaSettingsWidget.h index 65154597a..21a71fb8b 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.h +++ b/launcher/ui/widgets/JavaSettingsWidget.h @@ -48,8 +48,8 @@ class JavaSettingsWidget : public QWidget { Q_OBJECT public: - explicit JavaSettingsWidget(QWidget* parent = nullptr) : JavaSettingsWidget(nullptr, parent) {} - explicit JavaSettingsWidget(BaseInstance* instance, QWidget* parent = nullptr); + explicit JavaSettingsWidget(QWidget* parent = nullptr) : JavaSettingsWidget(nullptr, nullptr) {} + explicit JavaSettingsWidget(InstancePtr instance, QWidget* parent = nullptr); ~JavaSettingsWidget() override; void loadSettings(); @@ -62,7 +62,7 @@ class JavaSettingsWidget : public QWidget { void updateThresholds(); private: - BaseInstance* m_instance; + InstancePtr m_instance; Ui::JavaSettingsWidget* m_ui; unique_qobject_ptr m_checker; }; diff --git a/launcher/ui/widgets/JavaSettingsWidget.ui b/launcher/ui/widgets/JavaSettingsWidget.ui index 03d632ad9..46f714b76 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.ui +++ b/launcher/ui/widgets/JavaSettingsWidget.ui @@ -55,7 +55,7 @@ - Qt::Orientation::Horizontal + Qt::Horizontal @@ -86,7 +86,7 @@ - Qt::Orientation::Horizontal + Qt::Horizontal @@ -101,10 +101,10 @@ - Qt::Orientation::Vertical + Qt::Vertical - QSizePolicy::Policy::Fixed + QSizePolicy::Fixed @@ -160,10 +160,10 @@ - Qt::Orientation::Vertical + Qt::Vertical - QSizePolicy::Policy::Fixed + QSizePolicy::Fixed @@ -190,166 +190,156 @@ false - - - - - - - M&inimum Memory Usage: - - - minMemSpinBox - - - - - - - - - - 0 - 0 - - - - The amount of memory Minecraft is started with. - - - MiB - - - 8 - - - 1048576 - - - 128 - - - 256 - - - - - - - (-Xms) - - - - - - - - - Ma&ximum Memory Usage: - - - maxMemSpinBox - - - - - - - - - - 0 - 0 - - - - The maximum amount of memory Minecraft is allowed to use. - - - MiB - - - 8 - - - 1048576 - - - 128 - - - 1024 - - - - - - - (-Xmx) - - - - - - - - - &PermGen Size: - - - permGenSpinBox - - - - - - - - - - 0 - 0 - - - - The amount of memory available to store loaded Java classes. - - - MiB - - - 4 - - - 1048576 - - - 8 - - - 64 - - - - - - - (-XX:PermSize) - - - - - - - - - + + + - Warn when there is not enough memory available + (-XX:PermSize) - + + + + + 0 + 0 + + + + The amount of memory available to store loaded Java classes. + + + MiB + + + 4 + + + 1048576 + + + 8 + + + 64 + + + + + + + + 0 + 0 + + + + The maximum amount of memory Minecraft is allowed to use. + + + MiB + + + 8 + + + 1048576 + + + 128 + + + 1024 + + + + + + + (-Xmx) + + + + + + + + 0 + 0 + + + + The amount of memory Minecraft is started with. + + + MiB + + + 8 + + + 1048576 + + + 128 + + + 256 + + + + + + + &PermGen Size: + + + permGenSpinBox + + + + + + + (-Xms) + + + + + + + Ma&ximum Memory Usage: + + + maxMemSpinBox + + + + + + + M&inimum Memory Usage: + + + minMemSpinBox + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + Memory Notice @@ -395,7 +385,6 @@ minMemSpinBox maxMemSpinBox permGenSpinBox - lowMemWarningCheckBox jvmArgsTextBox diff --git a/launcher/ui/widgets/JavaWizardWidget.cpp b/launcher/ui/widgets/JavaWizardWidget.cpp index bcf498b6d..fb2388cc4 100644 --- a/launcher/ui/widgets/JavaWizardWidget.cpp +++ b/launcher/ui/widgets/JavaWizardWidget.cpp @@ -13,6 +13,8 @@ #include #include +#include + #include "DesktopServices.h" #include "FileSystem.h" #include "JavaCommon.h" @@ -27,11 +29,10 @@ #include "Application.h" #include "BuildConfig.h" -#include "HardwareInfo.h" JavaWizardWidget::JavaWizardWidget(QWidget* parent) : QWidget(parent) { - m_availableMemory = HardwareInfo::totalRamMiB(); + m_availableMemory = Sys::getSystemRam() / Sys::mebibyte; goodIcon = QIcon::fromTheme("status-good"); yellowIcon = QIcon::fromTheme("status-yellow"); @@ -185,7 +186,7 @@ void JavaWizardWidget::setupUi() void JavaWizardWidget::initialize() { - m_versionWidget->initialize(APPLICATION->javalist()); + m_versionWidget->initialize(APPLICATION->javalist().get()); m_versionWidget->selectSearch(); m_versionWidget->setResizeOn(2); auto s = APPLICATION->settings(); @@ -256,12 +257,15 @@ JavaWizardWidget::ValidationStatus JavaWizardWidget::validate() return ValidationStatus::JavaBad; case QMessageBox::Help: DesktopServices::openUrl(QUrl(BuildConfig.HELP_URL.arg("java-wizard"))); - [[fallthrough]]; + /* fallthrough */ case QMessageBox::No: /* fallthrough */ default: return ValidationStatus::Bad; } + if (button == QMessageBox::No) { + return ValidationStatus::Bad; + } } return ValidationStatus::JavaBad; } break; diff --git a/launcher/ui/widgets/LanguageSelectionWidget.cpp b/launcher/ui/widgets/LanguageSelectionWidget.cpp index 3f35df7b0..481547b5b 100644 --- a/launcher/ui/widgets/LanguageSelectionWidget.cpp +++ b/launcher/ui/widgets/LanguageSelectionWidget.cpp @@ -6,7 +6,6 @@ #include #include #include "Application.h" -#include "settings/SettingsObject.h" #include "BuildConfig.h" #include "settings/Setting.h" #include "translations/TranslationsModel.h" @@ -41,7 +40,7 @@ LanguageSelectionWidget::LanguageSelectionWidget(QWidget* parent) : QWidget(pare auto translations = APPLICATION->translations(); auto index = translations->selectedIndex(); - languageView->setModel(translations); + languageView->setModel(translations.get()); languageView->setCurrentIndex(index); languageView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); languageView->header()->setSectionResizeMode(0, QHeaderView::Stretch); @@ -63,7 +62,7 @@ void LanguageSelectionWidget::retranslate() QString text = tr("Don't see your language or the quality is poor?
Help us with translations!") .arg(BuildConfig.TRANSLATIONS_URL); helpUsLabel->setText(text); - formatCheckbox->setText(tr("Use system regional standards")); + formatCheckbox->setText(tr("Use system locales")); } void LanguageSelectionWidget::languageRowChanged(const QModelIndex& current, const QModelIndex& previous) diff --git a/launcher/ui/widgets/MinecraftSettingsWidget.cpp b/launcher/ui/widgets/MinecraftSettingsWidget.cpp index 460068bd3..deae79cd8 100644 --- a/launcher/ui/widgets/MinecraftSettingsWidget.cpp +++ b/launcher/ui/widgets/MinecraftSettingsWidget.cpp @@ -48,7 +48,7 @@ #include "minecraft/auth/AccountList.h" #include "settings/Setting.h" -MinecraftSettingsWidget::MinecraftSettingsWidget(MinecraftInstance* instance, QWidget* parent) +MinecraftSettingsWidget::MinecraftSettingsWidget(MinecraftInstancePtr instance, QWidget* parent) : QWidget(parent), m_instance(std::move(instance)), m_ui(new Ui::MinecraftSettingsWidget) { m_ui->setupUi(this); @@ -154,7 +154,7 @@ MinecraftSettingsWidget::~MinecraftSettingsWidget() void MinecraftSettingsWidget::loadSettings() { - SettingsObject* settings; + SettingsObjectPtr settings; if (m_instance != nullptr) settings = m_instance->settings(); @@ -308,7 +308,7 @@ void MinecraftSettingsWidget::loadSettings() void MinecraftSettingsWidget::saveSettings() { - SettingsObject* settings; + SettingsObjectPtr settings; if (m_instance != nullptr) settings = m_instance->settings(); diff --git a/launcher/ui/widgets/MinecraftSettingsWidget.h b/launcher/ui/widgets/MinecraftSettingsWidget.h index 847e05806..0dd8e6ba7 100644 --- a/launcher/ui/widgets/MinecraftSettingsWidget.h +++ b/launcher/ui/widgets/MinecraftSettingsWidget.h @@ -46,7 +46,7 @@ class MinecraftSettingsWidget; class MinecraftSettingsWidget : public QWidget { public: - MinecraftSettingsWidget(MinecraftInstance* instance, QWidget* parent = nullptr); + MinecraftSettingsWidget(MinecraftInstancePtr instance, QWidget* parent = nullptr); ~MinecraftSettingsWidget() override; void loadSettings(); @@ -61,7 +61,7 @@ class MinecraftSettingsWidget : public QWidget { void saveDataPacksPath(); void selectDataPacksFolder(); - MinecraftInstance* m_instance; + MinecraftInstancePtr m_instance; Ui::MinecraftSettingsWidget* m_ui; JavaSettingsWidget* m_javaSettings = nullptr; bool m_quickPlaySingleplayer = false; diff --git a/launcher/ui/widgets/MinecraftSettingsWidget.ui b/launcher/ui/widgets/MinecraftSettingsWidget.ui index a063f9660..80fb8530d 100644 --- a/launcher/ui/widgets/MinecraftSettingsWidget.ui +++ b/launcher/ui/widgets/MinecraftSettingsWidget.ui @@ -858,71 +858,32 @@ It is most likely you will need to change the path - please refer to the mod's w openGlobalSettingsButton settingsTabs scrollArea - - - windowSizeGroupBox maximizedCheckBox - windowWidthSpinBox windowHeightSpinBox + windowWidthSpinBox closeAfterLaunchCheck quitAfterGameStopCheck - - - consoleSettingsBox showConsoleCheck showConsoleErrorCheck autoCloseConsoleCheck - - - globalDataPacksGroupBox - dataPacksPathEdit - dataPacksPathBrowse - - --> - gameTimeGroupBox showGameTime recordGameTime showGlobalGameTime showGameTimeWithoutDays - - instanceAccountGroupBox instanceAccountSelector - - serverJoinGroupBox serverJoinAddressButton serverJoinAddress worldJoinButton worldsCb - - - loaderGroup - neoForge - forge - fabric - quilt - liteLoader - babric - btaBabric - legacyFabric - ornithe - rift - javaScrollArea scrollArea_2 - - - legacySettingsGroupBox onlineFixes - - - nativeWorkaroundsGroupBox useNativeGLFWCheck lineEditGLFWPath useNativeOpenALCheck lineEditOpenALPath - enableFeralGamemodeCheck enableMangoHud useDiscreteGpuCheck diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index 6fab2b2a5..4675b2698 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -248,7 +248,7 @@ void ModFilterWidget::prepareBasicFilter() ui->rift->setChecked(loaders & ModPlatform::Rift); m_filter->loaders = loaders; auto def = m_instance->getPackProfile()->getComponentVersion("net.minecraft"); - m_filter->versions.emplace_back(def); + m_filter->versions.emplace_front(def); ui->versions->setCheckedItems({ def }); ui->version->setCurrentIndex(ui->version->findText(def)); } else { @@ -268,7 +268,7 @@ void ModFilterWidget::onVersionFilterChanged(int) { auto versions = ui->versions->checkedItems(); versions.sort(); - std::vector current_list; + std::list current_list; for (const QString& version : versions) current_list.emplace_back(version); @@ -390,7 +390,7 @@ void ModFilterWidget::onOpenSourceFilterChanged() void ModFilterWidget::onReleaseFilterChanged() { - std::vector releases; + std::list releases; if (ui->releaseCb->isChecked()) releases.push_back(ModPlatform::IndexedVersionType::Release); if (ui->betaCb->isChecked()) diff --git a/launcher/ui/widgets/ModFilterWidget.h b/launcher/ui/widgets/ModFilterWidget.h index 85deb51dc..f00b98eb0 100644 --- a/launcher/ui/widgets/ModFilterWidget.h +++ b/launcher/ui/widgets/ModFilterWidget.h @@ -58,8 +58,8 @@ class ModFilterWidget : public QTabWidget { Q_OBJECT public: struct Filter { - std::vector versions; - std::vector releases; + std::list versions; + std::list releases; ModPlatform::ModLoaderTypes loaders; ModPlatform::Side side; bool hideInstalled; diff --git a/launcher/ui/widgets/ModListView.cpp b/launcher/ui/widgets/ModListView.cpp index c2191ca5a..a38c7c86a 100644 --- a/launcher/ui/widgets/ModListView.cpp +++ b/launcher/ui/widgets/ModListView.cpp @@ -35,7 +35,6 @@ ModListView::ModListView(QWidget* parent) : QTreeView(parent) setDragEnabled(true); setDragDropMode(QAbstractItemView::DropOnly); viewport()->setAcceptDrops(true); - setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); } void ModListView::setModel(QAbstractItemModel* model) diff --git a/launcher/ui/widgets/PageContainer.cpp b/launcher/ui/widgets/PageContainer.cpp index ad43e95e4..58b092275 100644 --- a/launcher/ui/widgets/PageContainer.cpp +++ b/launcher/ui/widgets/PageContainer.cpp @@ -49,7 +49,6 @@ #include #include #include -#include #include "settings/SettingsObject.h" @@ -60,43 +59,40 @@ class PageEntryFilterModel : public QSortFilterProxyModel { public: - explicit PageEntryFilterModel(QObject* parent = nullptr) : QSortFilterProxyModel(parent) {} + explicit PageEntryFilterModel(QObject* parent = 0) : QSortFilterProxyModel(parent) {} protected: - bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override + bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const { const QString pattern = filterRegularExpression().pattern(); - auto* const model = static_cast(sourceModel()); - auto* const page = model->pages().at(sourceRow); - if (!page->shouldDisplay()) { + const auto model = static_cast(sourceModel()); + const auto page = model->pages().at(sourceRow); + if (!page->shouldDisplay()) return false; - } // Regular contents check, then check page-filter. return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); } }; -PageContainer::PageContainer(BasePageProvider* pageProvider, QString defaultId, QWidget* parent) - : QWidget(parent) - , m_proxyModel(new PageEntryFilterModel(this)) - , m_model(new PageModel(this)) +PageContainer::PageContainer(BasePageProvider* pageProvider, QString defaultId, QWidget* parent) : QWidget(parent) { createUI(); useSidebarStyle(true); + m_model = new PageModel(this); + m_proxyModel = new PageEntryFilterModel(this); int counter = 0; auto pages = pageProvider->getPages(); - for (auto* page : pages) { - auto* widget = dynamic_cast(page); + for (auto page : pages) { + auto widget = dynamic_cast(page); widget->setParent(this); page->stackIndex = m_pageStack->addWidget(widget); page->listIndex = counter; page->setParentContainer(this); counter++; - page->updateExtraInfo = [this](const QString& id, const QString& info) { - if (m_currentPage && id == m_currentPage->id()) { + page->updateExtraInfo = [this](QString id, QString info) { + if (m_currentPage && id == m_currentPage->id()) m_header->setText(m_currentPage->displayName() + info); - } }; } m_model->setPages(pages); @@ -112,13 +108,13 @@ PageContainer::PageContainer(BasePageProvider* pageProvider, QString defaultId, connect(m_pageList->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &PageContainer::currentChanged); m_pageStack->setStackingMode(QStackedLayout::StackOne); m_pageList->setFocus(); - selectPage(std::move(defaultId)); + selectPage(defaultId); } bool PageContainer::selectPage(QString pageId) { // now find what we want to have selected... - auto* page = m_model->findPageEntryById(pageId); + auto page = m_model->findPageEntryById(pageId); QModelIndex index; if (page) { index = m_proxyModel->mapFromSource(m_model->index(page->listIndex)); @@ -170,12 +166,11 @@ void PageContainer::createUI() QFont headerLabelFont = m_header->font(); headerLabelFont.setBold(true); const int pointSize = headerLabelFont.pointSize(); - if (pointSize > 0) { + if (pointSize > 0) headerLabelFont.setPointSize(pointSize + 2); - } m_header->setFont(headerLabelFont); - auto* headerHLayout = new QHBoxLayout; + QHBoxLayout* headerHLayout = new QHBoxLayout; const int leftMargin = APPLICATION->style()->pixelMetric(QStyle::PM_LayoutLeftMargin); headerHLayout->addSpacerItem(new QSpacerItem(leftMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored)); headerHLayout->addWidget(m_header); @@ -195,13 +190,11 @@ void PageContainer::createUI() void PageContainer::retranslate() { - if (m_currentPage) { + if (m_currentPage) m_header->setText(m_currentPage->displayName()); - } - for (auto* page : m_model->pages()) { + for (auto page : m_model->pages()) page->retranslate(); - } } void PageContainer::addButtons(QWidget* buttons) @@ -243,23 +236,22 @@ void PageContainer::help() { if (m_currentPage) { QString pageId = m_currentPage->helpPage(); - if (pageId.isEmpty()) { + if (pageId.isEmpty()) return; - } DesktopServices::openUrl(QUrl(BuildConfig.HELP_URL.arg(pageId))); } } void PageContainer::currentChanged(const QModelIndex& current) { - int selectedIndex = current.isValid() ? m_proxyModel->mapToSource(current).row() : -1; + int selected_index = current.isValid() ? m_proxyModel->mapToSource(current).row() : -1; - auto* selected = m_model->pages().at(selectedIndex); + auto* selected = m_model->pages().at(selected_index); auto* previous = m_currentPage; emit selectedPageChanged(previous, selected); - showPage(selectedIndex); + showPage(selected_index); } bool PageContainer::prepareToClose() @@ -275,10 +267,9 @@ bool PageContainer::prepareToClose() bool PageContainer::saveAll() { - for (auto* page : m_model->pages()) { - if (!page->apply()) { + for (auto page : m_model->pages()) { + if (!page->apply()) return false; - } } return true; } diff --git a/launcher/ui/widgets/PageContainer.h b/launcher/ui/widgets/PageContainer.h index 2ca5e6f08..2c7ca9e39 100644 --- a/launcher/ui/widgets/PageContainer.h +++ b/launcher/ui/widgets/PageContainer.h @@ -56,8 +56,8 @@ class QGridLayout; class PageContainer : public QWidget, public BasePageContainer { Q_OBJECT public: - explicit PageContainer(BasePageProvider* pageProvider, QString defaultId = QString(), QWidget* parent = nullptr); - ~PageContainer() override = default; + explicit PageContainer(BasePageProvider* pageProvider, QString defaultId = QString(), QWidget* parent = 0); + virtual ~PageContainer() {} void addButtons(QWidget* buttons); void addButtons(QLayout* buttons); diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp index 1c4fa3596..2fd5c97c2 100644 --- a/launcher/ui/widgets/ProjectItem.cpp +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -47,31 +47,30 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o 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(); + auto icon_width = rect.height(), icon_height = rect.height(); int icon_x_margin = (rect.height() - icon_width) / 2; + int icon_y_margin = (rect.height() - icon_height) / 2; if (!opt.icon.isNull()) { // Icon painting - auto icon_height = 0; { auto icon_size = opt.decorationSize; icon_width = icon_size.width(); icon_height = icon_size.height(); - icon_x_margin = (rect.height() - icon_height) / 2; // use same margins for consistency + icon_y_margin = (rect.height() - icon_height) / 2; + icon_x_margin = icon_y_margin; // use same margins for consistency } // Centralize icon with a margin to separate from the other elements int x = rect.x() + icon_x_margin; - int y = rect.y() + icon_x_margin; + int y = rect.y() + icon_y_margin; - if (opt.features & QStyleOptionViewItem::HasCheckIndicator) { + if (opt.features & QStyleOptionViewItem::HasCheckIndicator) rect.translate(icon_x_margin / 2, 0); - } // Prevent 'scaling null pixmap' warnings - if (icon_width > 0 && icon_height > 0) { + if (icon_width > 0 && icon_height > 0) opt.icon.paint(painter, x, y, icon_width, icon_height); - } } // Change the rect so that funther painting is easier @@ -143,7 +142,7 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o description_y -= opt.fontMetrics.height(); // On the bottom, aligned to the left after the icon, and featuring at most two lines of text (with some margin space to spare) - painter->drawText(description_x, description_y, remaining_width, num_lines * opt.fontMetrics.height(), Qt::TextWordWrap, + painter->drawText(description_x, description_y, remaining_width, cut_text.size() * opt.fontMetrics.height(), Qt::TextWordWrap, description); } diff --git a/launcher/ui/widgets/VersionListView.cpp b/launcher/ui/widgets/VersionListView.cpp index 475c3da86..7701d1271 100644 --- a/launcher/ui/widgets/VersionListView.cpp +++ b/launcher/ui/widgets/VersionListView.cpp @@ -44,7 +44,6 @@ VersionListView::VersionListView(QWidget* parent) : QTreeView(parent) { m_emptyString = tr("No versions are currently available."); - setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); } void VersionListView::rowsInserted(const QModelIndex& parent, int start, int end) diff --git a/launcher/ui/widgets/VersionSelectWidget.cpp b/launcher/ui/widgets/VersionSelectWidget.cpp index 01b876146..040355f4b 100644 --- a/launcher/ui/widgets/VersionSelectWidget.cpp +++ b/launcher/ui/widgets/VersionSelectWidget.cpp @@ -127,9 +127,9 @@ void VersionSelectWidget::closeEvent(QCloseEvent* event) QWidget::closeEvent(event); } -void VersionSelectWidget::loadList(bool forceReload) +void VersionSelectWidget::loadList() { - m_load_task = m_vlist->getLoadTask(forceReload); + m_load_task = m_vlist->getLoadTask(); connect(m_load_task.get(), &Task::succeeded, this, &VersionSelectWidget::onTaskSucceeded); connect(m_load_task.get(), &Task::failed, this, &VersionSelectWidget::onTaskFailed); connect(m_load_task.get(), &Task::progress, this, &VersionSelectWidget::changeProgress); diff --git a/launcher/ui/widgets/VersionSelectWidget.h b/launcher/ui/widgets/VersionSelectWidget.h index ee9dbead7..c66d7e98e 100644 --- a/launcher/ui/widgets/VersionSelectWidget.h +++ b/launcher/ui/widgets/VersionSelectWidget.h @@ -57,7 +57,7 @@ class VersionSelectWidget : public QWidget { void initialize(BaseVersionList* vlist, bool forceLoad = false); //! Starts a task that loads the list. - void loadList(bool forceReload = false); + void loadList(); bool hasVersions() const; BaseVersion::Ptr selectedVersion() const; diff --git a/launcher/updater/PrismExternalUpdater.cpp b/launcher/updater/PrismExternalUpdater.cpp index 00f8404cf..69774dc04 100644 --- a/launcher/updater/PrismExternalUpdater.cpp +++ b/launcher/updater/PrismExternalUpdater.cpp @@ -21,15 +21,15 @@ */ #include "PrismExternalUpdater.h" - #include +#include +#include #include #include #include #include #include #include -#include #include #include "StringUtils.h" @@ -43,46 +43,42 @@ class PrismExternalUpdater::Private { QDir appDir; QDir dataDir; QTimer updateTimer; - bool allowBeta{}; - bool autoCheck{}; - double updateInterval{}; + bool allowBeta; + bool autoCheck; + double updateInterval; QDateTime lastCheck; std::unique_ptr settings; - QWidget* parent{}; + QWidget* parent; }; PrismExternalUpdater::PrismExternalUpdater(QWidget* parent, const QString& appDir, const QString& dataDir) - : priv(new PrismExternalUpdater::Private()) { + priv = new PrismExternalUpdater::Private(); priv->appDir = QDir(appDir); priv->dataDir = QDir(dataDir); - auto settingsFile = priv->dataDir.absoluteFilePath("prismlauncher_update.cfg"); - priv->settings = std::make_unique(settingsFile, QSettings::Format::IniFormat); + auto settings_file = priv->dataDir.absoluteFilePath("prismlauncher_update.cfg"); + priv->settings = std::make_unique(settings_file, QSettings::Format::IniFormat); priv->allowBeta = priv->settings->value("allow_beta", false).toBool(); - priv->autoCheck = priv->settings->value("auto_check", true).toBool(); - bool intervalOk = false; + priv->autoCheck = priv->settings->value("auto_check", false).toBool(); + bool interval_ok; // default once per day - priv->updateInterval = priv->settings->value("update_interval", 86400).toInt(&intervalOk); - if (!intervalOk) { + priv->updateInterval = priv->settings->value("update_interval", 86400).toInt(&interval_ok); + if (!interval_ok) priv->updateInterval = 86400; - } - if (const auto lastCheck = priv->settings->value("last_check"); !lastCheck.isNull() && lastCheck.isValid()) { - priv->lastCheck = QDateTime::fromString(lastCheck.toString(), Qt::ISODate); + auto last_check = priv->settings->value("last_check"); + if (!last_check.isNull() && last_check.isValid()) { + priv->lastCheck = QDateTime::fromString(last_check.toString(), Qt::ISODate); } priv->parent = parent; connectTimer(); resetAutoCheckTimer(); - if (priv->updateInterval == 0) { // "On Launch" - checkForUpdates(false); - } } PrismExternalUpdater::~PrismExternalUpdater() { - if (priv->updateTimer.isActive()) { + if (priv->updateTimer.isActive()) priv->updateTimer.stop(); - } disconnectTimer(); priv->settings->sync(); delete priv; @@ -93,36 +89,33 @@ void PrismExternalUpdater::checkForUpdates() checkForUpdates(true); } -void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) const +void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) { QProgressDialog progress(tr("Checking for updates..."), "", 0, 0, priv->parent); - progress.setMinimumDuration(0); // Appear immediately without waiting progress.setCancelButton(nullptr); progress.adjustSize(); - if (triggeredByUser) { - progress.show(); - } + progress.show(); QCoreApplication::processEvents(); QProcess proc; - auto exeName = QStringLiteral("%1_updater").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME); -#ifdef Q_OS_WIN32 - exeName.append(".exe"); + auto exe_name = QStringLiteral("%1_updater").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME); +#if defined Q_OS_WIN32 + exe_name.append(".exe"); auto env = QProcessEnvironment::systemEnvironment(); env.insert("__COMPAT_LAYER", "RUNASINVOKER"); proc.setProcessEnvironment(env); #else - exeName = QString("bin/%1").arg(exeName); + exe_name = QString("bin/%1").arg(exe_name); #endif QStringList args = { "--check-only", "--dir", priv->dataDir.absolutePath(), "--debug" }; - if (priv->allowBeta) { + if (priv->allowBeta) args.append("--pre-release"); - } - proc.start(priv->appDir.absoluteFilePath(exeName), args); - if (auto resultStart = proc.waitForStarted(5000); !resultStart) { + proc.start(priv->appDir.absoluteFilePath(exe_name), args); + auto result_start = proc.waitForStarted(5000); + if (!result_start) { auto err = proc.error(); qDebug() << "Failed to start updater after 5 seconds." << "reason:" << err << proc.errorString(); @@ -140,7 +133,8 @@ void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) const } QCoreApplication::processEvents(); - if (auto resultFinished = proc.waitForFinished(60000); !resultFinished) { + auto result_finished = proc.waitForFinished(60000); + if (!result_finished) { proc.kill(); auto err = proc.error(); auto output = proc.readAll(); @@ -160,15 +154,15 @@ void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) const return; } - auto exitCode = proc.exitCode(); + auto exit_code = proc.exitCode(); - auto stdOutput = proc.readAllStandardOutput(); - auto stdError = proc.readAllStandardError(); + auto std_output = proc.readAllStandardOutput(); + auto std_error = proc.readAllStandardError(); - progress.cancel(); + progress.hide(); QCoreApplication::processEvents(); - switch (exitCode) { + switch (exit_code) { case 0: // no update available if (triggeredByUser) { @@ -183,10 +177,10 @@ void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) const case 1: // there was an error { - qDebug() << "Updater subprocess error" << qPrintable(stdError); + qDebug() << "Updater subprocess error" << qPrintable(std_error); auto msgBox = QMessageBox(QMessageBox::Warning, tr("Update Check Error"), tr("There was an error running the update check."), QMessageBox::Ok, priv->parent); - msgBox.setDetailedText(QString(stdError)); + msgBox.setDetailedText(QString(std_error)); msgBox.setMinimumWidth(460); msgBox.adjustSize(); msgBox.exec(); @@ -195,27 +189,28 @@ void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) const case 100: // update available { - auto [firstLine, remainder1] = StringUtils::splitFirst(stdOutput, '\n'); - auto [secondLine, remainder2] = StringUtils::splitFirst(remainder1, '\n'); - auto [thirdLine, releaseNotes] = StringUtils::splitFirst(remainder2, '\n'); - auto versionName = StringUtils::splitFirst(firstLine, ": ").second.trimmed(); - auto versionTag = StringUtils::splitFirst(secondLine, ": ").second.trimmed(); - auto releaseTimestamp = QDateTime::fromString(StringUtils::splitFirst(thirdLine, ": ").second.trimmed(), Qt::ISODate); - qDebug() << "Update available:" << versionName << versionTag << releaseTimestamp; - qDebug() << "Update release notes:" << releaseNotes; + auto [first_line, remainder1] = StringUtils::splitFirst(std_output, '\n'); + auto [second_line, remainder2] = StringUtils::splitFirst(remainder1, '\n'); + auto [third_line, release_notes] = StringUtils::splitFirst(remainder2, '\n'); + auto version_name = StringUtils::splitFirst(first_line, ": ").second.trimmed(); + auto version_tag = StringUtils::splitFirst(second_line, ": ").second.trimmed(); + auto release_timestamp = QDateTime::fromString(StringUtils::splitFirst(third_line, ": ").second.trimmed(), Qt::ISODate); + qDebug() << "Update available:" << version_name << version_tag << release_timestamp; + qDebug() << "Update release notes:" << release_notes; - offerUpdate(versionName, versionTag, releaseNotes, triggeredByUser); + offerUpdate(version_name, version_tag, release_notes); } break; default: // unknown error code { - qDebug() << "Updater exited with unknown code" << exitCode; - auto msgBox = QMessageBox(QMessageBox::Information, tr("Unknown Update Error"), - tr("The updater exited with an unknown condition.\nExit Code: %1").arg(QString::number(exitCode)), - QMessageBox::Ok, priv->parent); - auto detailTxt = tr("StdOut: %1\nStdErr: %2").arg(QString(stdOutput)).arg(QString(stdError)); - msgBox.setDetailedText(detailTxt); + qDebug() << "Updater exited with unknown code" << exit_code; + auto msgBox = + QMessageBox(QMessageBox::Information, tr("Unknown Update Error"), + tr("The updater exited with an unknown condition.\nExit Code: %1").arg(QString::number(exit_code)), + QMessageBox::Ok, priv->parent); + auto detail_txt = tr("StdOut: %1\nStdErr: %2").arg(QString(std_output)).arg(QString(std_error)); + msgBox.setDetailedText(detail_txt); msgBox.setMinimumWidth(460); msgBox.adjustSize(); msgBox.exec(); @@ -265,27 +260,23 @@ void PrismExternalUpdater::setBetaAllowed(bool allowed) priv->settings->sync(); } -void PrismExternalUpdater::resetAutoCheckTimer() const +void PrismExternalUpdater::resetAutoCheckTimer() { if (priv->autoCheck && priv->updateInterval > 0) { + int timeoutDuration = 0; auto now = QDateTime::currentDateTime(); - - qint64 timeoutMs = 0; - if (priv->lastCheck.isValid()) { - const qint64 diff = priv->lastCheck.secsTo(now); - const qint64 secsLeft = std::max(priv->updateInterval - diff, 0); - timeoutMs = secsLeft * 1000; + auto diff = priv->lastCheck.secsTo(now); + auto secs_left = priv->updateInterval - diff; + if (secs_left < 0) + secs_left = 0; + timeoutDuration = secs_left * 1000; // to msec } - - timeoutMs = std::min(timeoutMs, static_cast(INT_MAX)); - - qDebug() << "Auto update timer starting," << timeoutMs / 1000 << "seconds left"; - priv->updateTimer.start(static_cast(timeoutMs)); + qDebug() << "Auto update timer starting," << timeoutDuration / 1000 << "seconds left"; + priv->updateTimer.start(timeoutDuration); } else { - if (priv->updateTimer.isActive()) { + if (priv->updateTimer.isActive()) priv->updateTimer.stop(); - } } } @@ -299,72 +290,68 @@ void PrismExternalUpdater::disconnectTimer() disconnect(&priv->updateTimer, &QTimer::timeout, this, &PrismExternalUpdater::autoCheckTimerFired); } -void PrismExternalUpdater::autoCheckTimerFired() const +void PrismExternalUpdater::autoCheckTimerFired() { qDebug() << "Auto update Timer fired"; checkForUpdates(false); } -void PrismExternalUpdater::offerUpdate(const QString& versionName, - const QString& versionTag, - const QString& releaseNotes, - const bool triggeredByUser) const +void PrismExternalUpdater::offerUpdate(const QString& version_name, const QString& version_tag, const QString& release_notes) { priv->settings->beginGroup("skip"); - auto shouldSkip = !triggeredByUser && priv->settings->value(versionTag, false).toBool(); + auto should_skip = priv->settings->value(version_tag, false).toBool(); priv->settings->endGroup(); - if (shouldSkip) { - if (triggeredByUser) { - auto msgBox = QMessageBox(QMessageBox::Information, tr("No Update Available"), tr("There are no new updates available."), - QMessageBox::Ok, priv->parent); - msgBox.setMinimumWidth(460); - msgBox.adjustSize(); - msgBox.exec(); - } + if (should_skip) { + auto msgBox = QMessageBox(QMessageBox::Information, tr("No Update Available"), tr("There are no new updates available."), + QMessageBox::Ok, priv->parent); + msgBox.setMinimumWidth(460); + msgBox.adjustSize(); + msgBox.exec(); return; } - UpdateAvailableDialog dlg(BuildConfig.printableVersionString(), versionName, releaseNotes); + UpdateAvailableDialog dlg(BuildConfig.printableVersionString(), version_name, release_notes); auto result = dlg.exec(); qDebug() << "offer dlg result" << result; - - priv->settings->beginGroup("skip"); - if (result == UpdateAvailableDialog::Skip) { - priv->settings->setValue(versionTag, true); - } else { - if (result == UpdateAvailableDialog::Install) { - performUpdate(versionTag); + switch (result) { + case UpdateAvailableDialog::Install: { + performUpdate(version_tag); + return; + } + case UpdateAvailableDialog::Skip: { + priv->settings->beginGroup("skip"); + priv->settings->setValue(version_tag, true); + priv->settings->endGroup(); + priv->settings->sync(); + return; + } + case UpdateAvailableDialog::DontInstall: { + return; } - priv->settings->remove(versionTag); } - priv->settings->endGroup(); - priv->settings->sync(); } -void PrismExternalUpdater::performUpdate(const QString& versionTag) const +void PrismExternalUpdater::performUpdate(const QString& version_tag) { QProcess proc; - auto exeName = QStringLiteral("%1_updater").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME); -#ifdef Q_OS_WIN32 - exeName.append(".exe"); + auto exe_name = QStringLiteral("%1_updater").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME); +#if defined Q_OS_WIN32 + exe_name.append(".exe"); auto env = QProcessEnvironment::systemEnvironment(); env.insert("__COMPAT_LAYER", "RUNASINVOKER"); proc.setProcessEnvironment(env); #else - exeName = QString("bin/%1").arg(exeName); + exe_name = QString("bin/%1").arg(exe_name); #endif - QStringList args = { "--dir", priv->dataDir.absolutePath(), "--install-version", versionTag }; - if (priv->allowBeta) { + QStringList args = { "--dir", priv->dataDir.absolutePath(), "--install-version", version_tag }; + if (priv->allowBeta) args.append("--pre-release"); - } - proc.setProgram(priv->appDir.absoluteFilePath(exeName)); - proc.setArguments(args); - auto result = proc.startDetached(); + auto result = proc.startDetached(priv->appDir.absoluteFilePath(exe_name), args); if (!result) { qDebug() << "Failed to start updater:" << proc.error() << proc.errorString(); } diff --git a/launcher/updater/PrismExternalUpdater.h b/launcher/updater/PrismExternalUpdater.h index b3f284b33..b88676028 100644 --- a/launcher/updater/PrismExternalUpdater.h +++ b/launcher/updater/PrismExternalUpdater.h @@ -22,10 +22,12 @@ #pragma once +#include + #include "ExternalUpdater.h" /*! - * An implementation for the updater on Windows and linux that uses out external updater. + * An implementation for the updater on windows and linux that uses out external updater. */ class PrismExternalUpdater : public ExternalUpdater { @@ -39,7 +41,7 @@ class PrismExternalUpdater : public ExternalUpdater { * Check for updates manually, showing the user a progress bar and an alert if no updates are found. */ void checkForUpdates() override; - void checkForUpdates(bool triggeredByUser) const; + void checkForUpdates(bool triggeredByUser); /*! * Indicates whether or not to check for updates automatically. @@ -60,7 +62,7 @@ class PrismExternalUpdater : public ExternalUpdater { * Set whether or not to check for updates automatically. * * The update schedule cycle will be reset in a short delay after the property’s new value is set. This is to allow - * reverting this property without kicking off a schedule change immediately. + * reverting this property without kicking off a schedule change immediately." */ void setAutomaticallyChecksForUpdates(bool check) override; @@ -68,7 +70,7 @@ class PrismExternalUpdater : public ExternalUpdater { * Set the current automatic update check interval in seconds. * * The update schedule cycle will be reset in a short delay after the property’s new value is set. This is to allow - * reverting this property without kicking off a schedule change immediately. + * reverting this property without kicking off a schedule change immediately." */ void setUpdateCheckInterval(double seconds) override; @@ -77,15 +79,15 @@ class PrismExternalUpdater : public ExternalUpdater { */ void setBetaAllowed(bool allowed) override; - void resetAutoCheckTimer() const; + void resetAutoCheckTimer(); void disconnectTimer(); void connectTimer(); - void offerUpdate(const QString& versionName, const QString& versionTag, const QString& releaseNotes, bool triggeredByUser) const; - void performUpdate(const QString& versionTag) const; + void offerUpdate(const QString& version_name, const QString& version_tag, const QString& release_notes); + void performUpdate(const QString& version_tag); public slots: - void autoCheckTimerFired() const; + void autoCheckTimerFired(); private: class Private; diff --git a/launcher/updater/prismupdater/PrismUpdater.cpp b/launcher/updater/prismupdater/PrismUpdater.cpp index 6ad60a7c7..d8e6a2469 100644 --- a/launcher/updater/prismupdater/PrismUpdater.cpp +++ b/launcher/updater/prismupdater/PrismUpdater.cpp @@ -40,6 +40,16 @@ #include #include +#include + +#if defined Q_OS_WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include "console/WindowsConsole.h" +#endif + #include namespace fs = std::filesystem; @@ -76,6 +86,12 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext& context, const QSt PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, argv) { +#if defined Q_OS_WIN32 + // attach the parent console if stdout not already captured + if (AttachWindowsConsole()) { + consoleAttached = true; + } +#endif setOrganizationName(BuildConfig.LAUNCHER_NAME); setOrganizationDomain(BuildConfig.LAUNCHER_DOMAIN); setApplicationName(BuildConfig.LAUNCHER_NAME + "Updater"); @@ -184,13 +200,12 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar logFile = std::unique_ptr(new QFile(logBase.arg(0))); if (!logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { showFatalErrorMessage(tr("The launcher data folder is not writable!"), - tr("The updater couldn't create a log file - %1.\n" + tr("The updater couldn't create a log file - the data folder is not writable.\n" "\n" "Make sure you have write permissions to the data folder.\n" - "(%2)\n" + "(%1)\n" "\n" "The updater cannot continue until you fix this problem.") - .arg(logFile->errorString()) .arg(m_dataPath)); return; } @@ -278,7 +293,7 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar } { // network - m_network = std::make_unique(); + m_network = makeShared(new QNetworkAccessManager()); qDebug() << "Detecting proxy settings..."; QNetworkProxy proxy = QNetworkProxy::applicationProxy(); m_network->setProxy(proxy); @@ -367,6 +382,16 @@ PrismUpdaterApp::~PrismUpdaterApp() qDebug() << "updater shutting down"; // Shut down logger by setting the logger function to nothing qInstallMessageHandler(nullptr); + +#if defined Q_OS_WIN32 + // Detach from Windows console + if (consoleAttached) { + fclose(stdout); + fclose(stdin); + fclose(stderr); + FreeConsole(); + } +#endif } void PrismUpdaterApp::fail(const QString& reason) @@ -767,7 +792,7 @@ QFileInfo PrismUpdaterApp::downloadAsset(const GitHubReleaseAsset& asset) qDebug() << "downloading" << file_url << "to" << out_file_path; auto download = Net::Download::makeFile(file_url, out_file_path); - download->setNetwork(m_network.get()); + download->setNetwork(m_network); auto progress_dialog = ProgressDialog(); progress_dialog.adjustSize(); @@ -1136,19 +1161,20 @@ void PrismUpdaterApp::downloadReleasePage(const QString& api_url, int page) { int per_page = 30; auto page_url = QString("%1?per_page=%2&page=%3").arg(api_url).arg(QString::number(per_page)).arg(QString::number(page)); - auto [download, response] = Net::Download::makeByteArray(page_url); - download->setNetwork(m_network.get()); + auto response = std::make_shared(); + auto download = Net::Download::makeByteArray(page_url, response); + download->setNetwork(m_network); m_current_url = page_url; - auto github_api_headers = std::make_unique(); + auto github_api_headers = new Net::RawHeaderProxy(); github_api_headers->addHeaders({ { "Accept", "application/vnd.github+json" }, { "X-GitHub-Api-Version", "2022-11-28" }, }); - download->addHeaderProxy(std::move(github_api_headers)); + download->addHeaderProxy(github_api_headers); connect(download.get(), &Net::Download::succeeded, this, [this, response, per_page, api_url, page]() { - int num_found = parseReleasePage(response); + int num_found = parseReleasePage(response.get()); if (!(num_found < per_page)) { // there may be more, fetch next page downloadReleasePage(api_url, page + 1); } else { @@ -1160,6 +1186,8 @@ void PrismUpdaterApp::downloadReleasePage(const QString& api_url, int page) m_current_task.reset(download); connect(download.get(), &Net::Download::finished, this, [this]() { qDebug() << "Download" << m_current_task->getUid().toString() << "finished"; + m_current_task.reset(); + m_current_url = ""; }); QCoreApplication::processEvents(); diff --git a/launcher/updater/prismupdater/PrismUpdater.h b/launcher/updater/prismupdater/PrismUpdater.h index 5f4baec64..a904cbb6f 100644 --- a/launcher/updater/prismupdater/PrismUpdater.h +++ b/launcher/updater/prismupdater/PrismUpdater.h @@ -46,6 +46,7 @@ #include "GitHubRelease.h" class PrismUpdaterApp : public QApplication { + // friends for the purpose of limiting access to deprecated stuff Q_OBJECT public: enum Status { Starting, Failed, Succeeded, Initialized, Aborted }; @@ -127,7 +128,7 @@ class PrismUpdaterApp : public QApplication { GitHubRelease m_install_release; Status m_status = Status::Starting; - std::unique_ptr m_network; + shared_qobject_ptr m_network; QString m_current_url; Task::Ptr m_current_task; QList m_releases; diff --git a/launcher/updater/prismupdater/UpdaterDialogs.cpp b/launcher/updater/prismupdater/UpdaterDialogs.cpp index 31e1b10aa..eab3e6bbb 100644 --- a/launcher/updater/prismupdater/UpdaterDialogs.cpp +++ b/launcher/updater/prismupdater/UpdaterDialogs.cpp @@ -95,7 +95,7 @@ GitHubRelease SelectReleaseDialog::getRelease(QTreeWidgetItem* item) return release; } -void SelectReleaseDialog::selectionChanged(QTreeWidgetItem* current, QTreeWidgetItem* /*previous*/) +void SelectReleaseDialog::selectionChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous) { GitHubRelease release = getRelease(current); QString body = markdownToHTML(release.body.toUtf8()); @@ -166,7 +166,7 @@ GitHubReleaseAsset SelectReleaseAssetDialog::getAsset(QTreeWidgetItem* item) return selected_asset; } -void SelectReleaseAssetDialog::selectionChanged(QTreeWidgetItem* current, QTreeWidgetItem* /*previous*/) +void SelectReleaseAssetDialog::selectionChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous) { GitHubReleaseAsset asset = getAsset(current); m_selectedAsset = asset; diff --git a/launcher/updater/prismupdater/updater_main.cpp b/launcher/updater/prismupdater/updater_main.cpp index ddc38d5cd..89c1d1198 100644 --- a/launcher/updater/prismupdater/updater_main.cpp +++ b/launcher/updater/prismupdater/updater_main.cpp @@ -21,18 +21,8 @@ */ #include "PrismUpdater.h" - -#if defined Q_OS_WIN32 -#include "console/WindowsConsole.h" -#endif - int main(int argc, char* argv[]) { -#if defined Q_OS_WIN32 - // attach the parent console if stdout not already captured - console::WindowsConsoleGuard _consoleGuard; -#endif - PrismUpdaterApp wUpApp(argc, argv); switch (wUpApp.status()) { diff --git a/libraries/README.md b/libraries/README.md index e15d80eba..df4d251f7 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -89,13 +89,11 @@ Color functions extracted from [KGuiAddons](https://inqlude.org/libraries/kguiad Available either under LGPL version 2.1 or later. -## tomlplusplus +## systeminfo -A TOML language parser. Used by Forge 1.14+ to store mod metadata. +A Prism Launcher-specific library for probing system information. -See [github repo](https://github.com/marzer/tomlplusplus). - -Licenced under the MIT licence. +Apache 2.0 ## qdcss diff --git a/libraries/libnbtplusplus b/libraries/libnbtplusplus index 353893361..531449ba1 160000 --- a/libraries/libnbtplusplus +++ b/libraries/libnbtplusplus @@ -1 +1 @@ -Subproject commit 3538933614059f0f44388a2b16f3db25ce42285b +Subproject commit 531449ba1c930c98e0bcf5d332b237a8566f9d78 diff --git a/libraries/systeminfo/CMakeLists.txt b/libraries/systeminfo/CMakeLists.txt new file mode 100644 index 000000000..e091637cf --- /dev/null +++ b/libraries/systeminfo/CMakeLists.txt @@ -0,0 +1,27 @@ +project(systeminfo) + +if(Launcher_QT_VERSION_MAJOR EQUAL 6) + find_package(Qt6 COMPONENTS Core REQUIRED) +endif() + +set(systeminfo_SOURCES +include/sys.h +include/distroutils.h +src/distroutils.cpp +) + +if (WIN32) + list(APPEND systeminfo_SOURCES src/sys_win32.cpp) +elseif (UNIX) + if(APPLE) + list(APPEND systeminfo_SOURCES src/sys_apple.cpp) + else() + list(APPEND systeminfo_SOURCES src/sys_unix.cpp) + endif() +endif() + +add_library(systeminfo STATIC ${systeminfo_SOURCES}) +target_link_libraries(systeminfo Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Network ${systeminfo_LIBS}) +target_include_directories(systeminfo PUBLIC include) + +ecm_add_test(src/sys_test.cpp LINK_LIBRARIES systeminfo Qt${QT_VERSION_MAJOR}::Test TEST_NAME sys) diff --git a/libraries/systeminfo/include/distroutils.h b/libraries/systeminfo/include/distroutils.h new file mode 100644 index 000000000..caa2688cf --- /dev/null +++ b/libraries/systeminfo/include/distroutils.h @@ -0,0 +1,22 @@ +#include +#include "sys.h" + +namespace Sys { +struct LsbInfo { + QString distributor; + QString version; + QString description; + QString codename; +}; + +bool main_lsb_info(LsbInfo& out); +bool fallback_lsb_info(Sys::LsbInfo& out); +void lsb_postprocess(Sys::LsbInfo& lsb, Sys::DistributionInfo& out); +Sys::DistributionInfo read_lsb_release(); + +QString _extract_distribution(const QString& x); +QString _extract_version(const QString& x); +Sys::DistributionInfo read_legacy_release(); + +Sys::DistributionInfo read_os_release(); +} // namespace Sys diff --git a/libraries/systeminfo/include/sys.h b/libraries/systeminfo/include/sys.h new file mode 100644 index 000000000..dfebbe90b --- /dev/null +++ b/libraries/systeminfo/include/sys.h @@ -0,0 +1,45 @@ +#pragma once +#include + +namespace Sys { +const uint64_t mebibyte = 1024ull * 1024ull; + +enum class KernelType { Undetermined, Windows, Darwin, Linux }; + +struct KernelInfo { + QString kernelName; + QString kernelVersion; + + KernelType kernelType = KernelType::Undetermined; + int kernelMajor = 0; + int kernelMinor = 0; + int kernelPatch = 0; + bool isCursed = false; +}; + +KernelInfo getKernelInfo(); + +struct DistributionInfo { + DistributionInfo operator+(const DistributionInfo& rhs) const + { + DistributionInfo out; + if (!distributionName.isEmpty()) { + out.distributionName = distributionName; + } else { + out.distributionName = rhs.distributionName; + } + if (!distributionVersion.isEmpty()) { + out.distributionVersion = distributionVersion; + } else { + out.distributionVersion = rhs.distributionVersion; + } + return out; + } + QString distributionName; + QString distributionVersion; +}; + +DistributionInfo getDistributionInfo(); + +uint64_t getSystemRam(); +} // namespace Sys diff --git a/libraries/systeminfo/src/distroutils.cpp b/libraries/systeminfo/src/distroutils.cpp new file mode 100644 index 000000000..294dfadbb --- /dev/null +++ b/libraries/systeminfo/src/distroutils.cpp @@ -0,0 +1,242 @@ +/* + +Code has been taken from https://github.com/natefoo/lionshead and loosely +translated to C++ laced with Qt. + +MIT License + +Copyright (c) 2017 Nate Coraor + +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. + +*/ + +#include "distroutils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static const QRegularExpression s_distoSplitRegex("\\s+"); + +Sys::DistributionInfo Sys::read_os_release() +{ + Sys::DistributionInfo out; + QStringList files = { "/etc/os-release", "/usr/lib/os-release" }; + QString name; + QString version; + for (auto& file : files) { + if (!QFile::exists(file)) { + continue; + } + QSettings settings(file, QSettings::IniFormat); + if (settings.contains("ID")) { + name = settings.value("ID").toString().toLower(); + } else if (settings.contains("NAME")) { + name = settings.value("NAME").toString().toLower(); + } else { + continue; + } + + if (settings.contains("VERSION_ID")) { + version = settings.value("VERSION_ID").toString().toLower(); + } else if (settings.contains("VERSION")) { + version = settings.value("VERSION").toString().toLower(); + } + break; + } + if (name.isEmpty()) { + return out; + } + out.distributionName = name; + out.distributionVersion = version; + return out; +} + +bool Sys::main_lsb_info(Sys::LsbInfo& out) +{ + int status = 0; + QProcess lsbProcess; + QStringList arguments; + arguments << "-a"; + lsbProcess.start("lsb_release", arguments); + lsbProcess.waitForFinished(); + status = lsbProcess.exitStatus(); + QString output = lsbProcess.readAllStandardOutput(); + qDebug() << output; + lsbProcess.close(); + if (status == 0) { + auto lines = output.split('\n'); + for (auto line : lines) { + int index = line.indexOf(':'); + auto key = line.left(index).trimmed(); + auto value = line.mid(index + 1).toLower().trimmed(); + if (key == "Distributor ID") + out.distributor = value; + else if (key == "Release") + out.version = value; + else if (key == "Description") + out.description = value; + else if (key == "Codename") + out.codename = value; + } + return !out.distributor.isEmpty(); + } + return false; +} + +bool Sys::fallback_lsb_info(Sys::LsbInfo& out) +{ + // running lsb_release failed, try to read the file instead + // /etc/lsb-release format, if the file even exists, is non-standard. + // Only the `lsb_release` command is specified by LSB. Nonetheless, some + // distributions install an /etc/lsb-release as part of the base + // distribution, but `lsb_release` remains optional. + QString file = "/etc/lsb-release"; + if (QFile::exists(file)) { + QSettings settings(file, QSettings::IniFormat); + if (settings.contains("DISTRIB_ID")) { + out.distributor = settings.value("DISTRIB_ID").toString().toLower(); + } + if (settings.contains("DISTRIB_RELEASE")) { + out.version = settings.value("DISTRIB_RELEASE").toString().toLower(); + } + return !out.distributor.isEmpty(); + } + return false; +} + +void Sys::lsb_postprocess(Sys::LsbInfo& lsb, Sys::DistributionInfo& out) +{ + QString dist = lsb.distributor; + QString vers = lsb.version; + if (dist.startsWith("redhatenterprise")) { + dist = "rhel"; + } else if (dist == "archlinux") { + dist = "arch"; + } else if (dist.startsWith("suse")) { + if (lsb.description.startsWith("opensuse")) { + dist = "opensuse"; + } else if (lsb.description.startsWith("suse linux enterprise")) { + dist = "sles"; + } + } else if (dist == "debian" and vers == "testing") { + vers = lsb.codename; + } else { + // ubuntu, debian, gentoo, scientific, slackware, ... ? + auto parts = dist.split(s_distoSplitRegex, Qt::SkipEmptyParts); + if (parts.size()) { + dist = parts[0]; + } + } + if (!dist.isEmpty()) { + out.distributionName = dist; + out.distributionVersion = vers; + } +} + +Sys::DistributionInfo Sys::read_lsb_release() +{ + LsbInfo lsb; + if (!main_lsb_info(lsb)) { + if (!fallback_lsb_info(lsb)) { + return Sys::DistributionInfo(); + } + } + Sys::DistributionInfo out; + lsb_postprocess(lsb, out); + return out; +} + +QString Sys::_extract_distribution(const QString& x) +{ + QString release = x.toLower(); + if (release.startsWith("red hat enterprise")) { + return "rhel"; + } + if (release.startsWith("suse linux enterprise")) { + return "sles"; + } + QStringList list = release.split(s_distoSplitRegex, Qt::SkipEmptyParts); + if (list.size()) { + return list[0]; + } + return QString(); +} + +QString Sys::_extract_version(const QString& x) +{ + static const QRegularExpression s_versionishString(QRegularExpression::anchoredPattern("\\d+(?:\\.\\d+)*$")); + QStringList list = x.split(s_distoSplitRegex, Qt::SkipEmptyParts); + for (int i = list.size() - 1; i >= 0; --i) { + QString chunk = list[i]; + if (s_versionishString.match(chunk).hasMatch()) { + return chunk; + } + } + return QString(); +} + +Sys::DistributionInfo Sys::read_legacy_release() +{ + struct checkEntry { + QString file; + std::function extract_distro; + std::function extract_version; + }; + QList checks = { + { "/etc/arch-release", [](const QString&) { return "arch"; }, [](const QString&) { return "rolling"; } }, + { "/etc/slackware-version", &Sys::_extract_distribution, &Sys::_extract_version }, + { QString(), &Sys::_extract_distribution, &Sys::_extract_version }, + { "/etc/debian_version", [](const QString&) { return "debian"; }, [](const QString& x) { return x; } }, + }; + for (auto& check : checks) { + QStringList files; + if (check.file.isNull()) { + QDir etcDir("/etc"); + etcDir.setNameFilters({ "*-release" }); + etcDir.setFilter(QDir::Files | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden); + files = etcDir.entryList(); + } else { + files.append(check.file); + } + for (auto file : files) { + QFile relfile(file); + if (!relfile.open(QIODevice::ReadOnly | QIODevice::Text)) + continue; + QString contents = QString::fromUtf8(relfile.readLine()).trimmed(); + QString dist = check.extract_distro(contents); + QString vers = check.extract_version(contents); + if (!dist.isEmpty()) { + Sys::DistributionInfo out; + out.distributionName = dist; + out.distributionVersion = vers; + return out; + } + } + } + return Sys::DistributionInfo(); +} diff --git a/libraries/systeminfo/src/sys_apple.cpp b/libraries/systeminfo/src/sys_apple.cpp new file mode 100644 index 000000000..5cf70f1aa --- /dev/null +++ b/libraries/systeminfo/src/sys_apple.cpp @@ -0,0 +1,57 @@ +#include "sys.h" + +#include + +#include +#include +#include + +Sys::KernelInfo Sys::getKernelInfo() +{ + Sys::KernelInfo out; + struct utsname buf; + uname(&buf); + out.kernelType = KernelType::Darwin; + out.kernelName = buf.sysname; + QString release = out.kernelVersion = buf.release; + + // TODO: figure out how to detect cursed-ness (macOS emulated on linux via mad hacks and so on) + out.isCursed = false; + + out.kernelMajor = 0; + out.kernelMinor = 0; + out.kernelPatch = 0; + auto sections = release.split('-'); + if (sections.size() >= 1) { + auto versionParts = sections[0].split('.'); + if (versionParts.size() >= 3) { + out.kernelMajor = versionParts[0].toInt(); + out.kernelMinor = versionParts[1].toInt(); + out.kernelPatch = versionParts[2].toInt(); + } else { + qWarning() << "Not enough version numbers in " << sections[0] << " found " << versionParts.size(); + } + } else { + qWarning() << "Not enough '-' sections in " << release << " found " << sections.size(); + } + return out; +} + +#include + +uint64_t Sys::getSystemRam() +{ + uint64_t memsize; + size_t memsizesize = sizeof(memsize); + if (!sysctlbyname("hw.memsize", &memsize, &memsizesize, NULL, 0)) { + return memsize; + } else { + return 0; + } +} + +Sys::DistributionInfo Sys::getDistributionInfo() +{ + DistributionInfo result; + return result; +} diff --git a/libraries/systeminfo/src/sys_test.cpp b/libraries/systeminfo/src/sys_test.cpp new file mode 100644 index 000000000..50c75eb77 --- /dev/null +++ b/libraries/systeminfo/src/sys_test.cpp @@ -0,0 +1,28 @@ +#include + +#include + +class SysTest : public QObject { + Q_OBJECT + private slots: + + void test_kernelNotNull() + { + auto kinfo = Sys::getKernelInfo(); + QVERIFY(!kinfo.kernelName.isEmpty()); + QVERIFY(kinfo.kernelVersion != "0.0"); + } + /* + void test_systemDistroNotNull() + { + auto kinfo = Sys::getDistributionInfo(); + QVERIFY(!kinfo.distributionName.isEmpty()); + QVERIFY(!kinfo.distributionVersion.isEmpty()); + qDebug() << "Distro: " << kinfo.distributionName << "version" << kinfo.distributionVersion; + } + */ +}; + +QTEST_GUILESS_MAIN(SysTest) + +#include "sys_test.moc" diff --git a/libraries/systeminfo/src/sys_unix.cpp b/libraries/systeminfo/src/sys_unix.cpp new file mode 100644 index 000000000..4e075959a --- /dev/null +++ b/libraries/systeminfo/src/sys_unix.cpp @@ -0,0 +1,93 @@ +#include "sys.h" + +#include "distroutils.h" + +#include +#include +#include + +#include +#include +#include + +Sys::KernelInfo Sys::getKernelInfo() +{ + Sys::KernelInfo out; + struct utsname buf; + uname(&buf); + // NOTE: we assume linux here. this needs further elaboration + out.kernelType = KernelType::Linux; + out.kernelName = buf.sysname; + QString release = out.kernelVersion = buf.release; + + // linux binary running on WSL is cursed. + out.isCursed = release.contains("WSL", Qt::CaseInsensitive) || release.contains("Microsoft", Qt::CaseInsensitive); + + out.kernelMajor = 0; + out.kernelMinor = 0; + out.kernelPatch = 0; + auto sections = release.split('-'); + if (sections.size() >= 1) { + auto versionParts = sections[0].split('.'); + if (versionParts.size() >= 3) { + out.kernelMajor = versionParts[0].toInt(); + out.kernelMinor = versionParts[1].toInt(); + out.kernelPatch = versionParts[2].toInt(); + } else { + qWarning() << "Not enough version numbers in " << sections[0] << " found " << versionParts.size(); + } + } else { + qWarning() << "Not enough '-' sections in " << release << " found " << sections.size(); + } + return out; +} + +uint64_t Sys::getSystemRam() +{ + std::string token; +#ifdef Q_OS_LINUX + std::ifstream file("/proc/meminfo"); + while (file >> token) { + if (token == "MemTotal:") { + uint64_t mem; + if (file >> mem) { + return mem * 1024ull; + } else { + return 0; + } + } + // ignore rest of the line + file.ignore(std::numeric_limits::max(), '\n'); + } +#elif defined(Q_OS_FREEBSD) + char buff[512]; + FILE* fp = popen("sysctl hw.physmem", "r"); + if (fp != NULL) { + while (fgets(buff, 512, fp) != NULL) { + std::string str(buff); + uint64_t mem = std::stoull(str.substr(12, std::string::npos)); + return mem * 1024ull; + } + } +#endif + return 0; // nothing found +} + +Sys::DistributionInfo Sys::getDistributionInfo() +{ + DistributionInfo systemd_info = read_os_release(); + DistributionInfo lsb_info = read_lsb_release(); + DistributionInfo legacy_info = read_legacy_release(); + DistributionInfo result = systemd_info + lsb_info + legacy_info; + if (result.distributionName.isNull()) { + result.distributionName = "unknown"; + } + if (result.distributionVersion.isNull()) { + if (result.distributionName == "arch") { + result.distributionVersion = "rolling"; + } else { + result.distributionVersion = "unknown"; + } + } + return result; +} diff --git a/libraries/systeminfo/src/sys_win32.cpp b/libraries/systeminfo/src/sys_win32.cpp new file mode 100644 index 000000000..2627761d1 --- /dev/null +++ b/libraries/systeminfo/src/sys_win32.cpp @@ -0,0 +1,34 @@ +#include "sys.h" + +#include + +Sys::KernelInfo Sys::getKernelInfo() +{ + Sys::KernelInfo out; + out.kernelType = KernelType::Windows; + out.kernelName = "Windows"; + OSVERSIONINFOW osvi; + ZeroMemory(&osvi, sizeof(OSVERSIONINFOW)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOW); + GetVersionExW(&osvi); + out.kernelVersion = QString("%1.%2").arg(osvi.dwMajorVersion).arg(osvi.dwMinorVersion); + out.kernelMajor = osvi.dwMajorVersion; + out.kernelMinor = osvi.dwMinorVersion; + out.kernelPatch = osvi.dwBuildNumber; + return out; +} + +uint64_t Sys::getSystemRam() +{ + MEMORYSTATUSEX status; + status.dwLength = sizeof(status); + GlobalMemoryStatusEx(&status); + // bytes + return (uint64_t)status.ullTotalPhys; +} + +Sys::DistributionInfo Sys::getDistributionInfo() +{ + DistributionInfo result; + return result; +} diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix index 829a4843c..478eb7b3e 100644 --- a/nix/unwrapped.nix +++ b/nix/unwrapped.nix @@ -3,12 +3,12 @@ stdenv, cmake, cmark, + extra-cmake-modules, gamemode, jdk17, kdePackages, libnbtplusplus, ninja, - pkg-config, qrencode, self, stripJavaArchivesHook, @@ -35,13 +35,6 @@ let ] else "unknown"; - - # Remove once https://github.com/NixOS/nixpkgs/pull/518987 lands - extra-cmake-modules = kdePackages.extra-cmake-modules.overrideAttrs (prevAttrs: { - meta = prevAttrs.meta // { - platforms = lib.platforms.all; - }; - }); in stdenv.mkDerivation { @@ -72,7 +65,6 @@ stdenv.mkDerivation { cmake ninja extra-cmake-modules - pkg-config jdk17 stripJavaArchivesHook ]; diff --git a/nix/wrapper.nix b/nix/wrapper.nix index 23fc04d9f..00752a8c4 100644 --- a/nix/wrapper.nix +++ b/nix/wrapper.nix @@ -6,7 +6,6 @@ glfw3-minecraft, jdk17, jdk21, - jdk25, jdk8, kdePackages, lib, @@ -35,7 +34,6 @@ controllerSupport ? stdenv.hostPlatform.isLinux, gamemodeSupport ? stdenv.hostPlatform.isLinux, jdks ? [ - jdk25 jdk21 jdk17 jdk8 diff --git a/program_info/CMakeLists.txt b/program_info/CMakeLists.txt index 3afd8a642..a4c56e1a0 100644 --- a/program_info/CMakeLists.txt +++ b/program_info/CMakeLists.txt @@ -10,48 +10,43 @@ endif() set(Launcher_CommonName "PrismLauncher") set(Launcher_DisplayName "Prism Launcher") -set(Launcher_AppID "org.prismlauncher.PrismLauncher") -set(Launcher_Domain "prismlauncher.org") -set(Launcher_Git "https://github.com/PrismLauncher/PrismLauncher") set(Launcher_Name "${Launcher_CommonName}" PARENT_SCOPE) set(Launcher_DisplayName "${Launcher_DisplayName}" PARENT_SCOPE) -set(Launcher_ENVName "PRISMLAUNCHER" PARENT_SCOPE) -set(Launcher_Domain "${Launcher_Domain}" PARENT_SCOPE) -set(Launcher_Git "${Launcher_Git}" PARENT_SCOPE) +set(Launcher_AppID "org.prismlauncher.PrismLauncher") set(Launcher_SVGFileName "${Launcher_AppID}.svg") set(Launcher_Copyright "© 2022-2026 Prism Launcher Contributors\\n© 2021-2022 PolyMC Contributors\\n© 2012-2021 MultiMC Contributors") set(Launcher_Copyright_Mac "© 2022-2026 Prism Launcher Contributors, © 2021-2022 PolyMC Contributors and © 2012-2021 MultiMC Contributors" PARENT_SCOPE) set(Launcher_Copyright "${Launcher_Copyright}" PARENT_SCOPE) +set(Launcher_Domain "prismlauncher.org" PARENT_SCOPE) set(Launcher_UserAgent "${Launcher_CommonName}/${Launcher_VERSION_NAME}" PARENT_SCOPE) -set(Launcher_ConfigFile "${Launcher_APP_BINARY_NAME}.cfg" PARENT_SCOPE) +set(Launcher_ConfigFile "prismlauncher.cfg" PARENT_SCOPE) +set(Launcher_Git "https://github.com/PrismLauncher/PrismLauncher" PARENT_SCOPE) set(Launcher_AppID "${Launcher_AppID}" PARENT_SCOPE) set(Launcher_SVGFileName "${Launcher_SVGFileName}" PARENT_SCOPE) set(Launcher_Desktop "program_info/${Launcher_AppID}.desktop" PARENT_SCOPE) -set(Launcher_MIMEInfo "program_info/${Launcher_AppID}.xml" PARENT_SCOPE) +set(Launcher_mrpack_MIMEInfo "program_info/modrinth-mrpack-mime.xml" PARENT_SCOPE) set(Launcher_MetaInfo "program_info/${Launcher_AppID}.metainfo.xml" PARENT_SCOPE) set(Launcher_PNG_256 "program_info/${Launcher_AppID}_256.png" PARENT_SCOPE) set(Launcher_SVG "program_info/${Launcher_SVGFileName}" PARENT_SCOPE) -set(Launcher_Branding_ICNS "program_info/${Launcher_APP_BINARY_NAME}.icns" PARENT_SCOPE) -set(Launcher_Branding_MAC_ICON "program_info/${Launcher_CommonName}.icon" PARENT_SCOPE) -set(Launcher_Branding_ICO "program_info/${Launcher_APP_BINARY_NAME}.ico") +set(Launcher_Branding_ICNS "program_info/prismlauncher.icns" PARENT_SCOPE) +set(Launcher_Branding_MAC_ICON "program_info/PrismLauncher.icon" PARENT_SCOPE) +set(Launcher_Branding_ICO "program_info/prismlauncher.ico") set(Launcher_Branding_ICO "${Launcher_Branding_ICO}" PARENT_SCOPE) -set(Launcher_Branding_WindowsRC "program_info/${Launcher_APP_BINARY_NAME}.rc" PARENT_SCOPE) -set(Launcher_Branding_LogoQRC "program_info/${Launcher_APP_BINARY_NAME}.qrc" PARENT_SCOPE) -set(Launcher_Authors "MultiMC & Prism Launcher Contributors") +set(Launcher_Branding_WindowsRC "program_info/prismlauncher.rc" PARENT_SCOPE) +set(Launcher_Branding_LogoQRC "program_info/prismlauncher.qrc" PARENT_SCOPE) set(Launcher_Portable_File "program_info/portable.txt" PARENT_SCOPE) configure_file(${Launcher_AppID}.desktop.in ${Launcher_AppID}.desktop) configure_file(${Launcher_AppID}.metainfo.xml.in ${Launcher_AppID}.metainfo.xml) -configure_file(${Launcher_APP_BINARY_NAME}.rc.in ${Launcher_APP_BINARY_NAME}.rc @ONLY) -configure_file(${Launcher_APP_BINARY_NAME}.qrc.in ${Launcher_APP_BINARY_NAME}.qrc @ONLY) -configure_file(${Launcher_APP_BINARY_NAME}.manifest.in ${Launcher_APP_BINARY_NAME}.manifest @ONLY) -configure_file(${Launcher_APP_BINARY_NAME}.ico ${Launcher_APP_BINARY_NAME}.ico COPYONLY) +configure_file(prismlauncher.rc.in prismlauncher.rc @ONLY) +configure_file(prismlauncher.qrc.in prismlauncher.qrc @ONLY) +configure_file(prismlauncher.manifest.in prismlauncher.manifest @ONLY) +configure_file(prismlauncher.ico prismlauncher.ico COPYONLY) configure_file(${Launcher_SVGFileName} ${Launcher_SVGFileName} COPYONLY) -configure_file(${Launcher_AppID}.mime.xml ${Launcher_AppID}.xml COPYONLY) if(MSVC) set(Launcher_MSVC_Redist_NSIS_Section [=[ @@ -80,15 +75,13 @@ endif() configure_file(win_install.nsi.in win_install.nsi @ONLY) if(SCDOC_FOUND) - configure_file(${Launcher_APP_BINARY_NAME}.6.scd.in ${Launcher_APP_BINARY_NAME}.6.scd @ONLY) - - set(in_scd "${CMAKE_CURRENT_BINARY_DIR}/${Launcher_APP_BINARY_NAME}.6.scd") - set(out_man "${CMAKE_CURRENT_BINARY_DIR}/${Launcher_APP_BINARY_NAME}.6") + set(in_scd "${CMAKE_CURRENT_SOURCE_DIR}/prismlauncher.6.scd") + set(out_man "${CMAKE_CURRENT_BINARY_DIR}/prismlauncher.6") add_custom_command( DEPENDS "${in_scd}" OUTPUT "${out_man}" COMMAND ${SCDOC_SCDOC} < "${in_scd}" > "${out_man}" ) add_custom_target(man ALL DEPENDS ${out_man}) - set(Launcher_ManPage "program_info/${Launcher_APP_BINARY_NAME}.6" PARENT_SCOPE) + set(Launcher_ManPage "program_info/prismlauncher.6" PARENT_SCOPE) endif() diff --git a/program_info/org.prismlauncher.PrismLauncher.mime.xml b/program_info/modrinth-mrpack-mime.xml similarity index 100% rename from program_info/org.prismlauncher.PrismLauncher.mime.xml rename to program_info/modrinth-mrpack-mime.xml diff --git a/program_info/org.prismlauncher.PrismLauncher.desktop.in b/program_info/org.prismlauncher.PrismLauncher.desktop.in index 416ca1b6e..74c70c5d1 100644 --- a/program_info/org.prismlauncher.PrismLauncher.desktop.in +++ b/program_info/org.prismlauncher.PrismLauncher.desktop.in @@ -10,4 +10,4 @@ Icon=@Launcher_AppID@ Categories=Game;ActionGame;AdventureGame;Simulation;PackageManager; Keywords=game;minecraft;mc; StartupWMClass=@Launcher_CommonName@ -MimeType=application/zip;application/x-modrinth-modpack+zip;x-scheme-handler/curseforge;x-scheme-handler/prismlauncher;x-scheme-handler/@Launcher_APP_BINARY_NAME@; +MimeType=application/zip;application/x-modrinth-modpack+zip;x-scheme-handler/curseforge;x-scheme-handler/@Launcher_APP_BINARY_NAME@; diff --git a/program_info/prismlauncher.6.scd.in b/program_info/prismlauncher.6.scd similarity index 84% rename from program_info/prismlauncher.6.scd.in rename to program_info/prismlauncher.6.scd index 2b5f3e483..e1ebfff32 100644 --- a/program_info/prismlauncher.6.scd.in +++ b/program_info/prismlauncher.6.scd @@ -1,14 +1,14 @@ -@Launcher_APP_BINARY_NAME@(6) +prismlauncher(6) # NAME -@Launcher_APP_BINARY_NAME@ - a launcher and instance manager for Minecraft. +prismlauncher - a launcher and instance manager for Minecraft. # SYNOPSIS -*@Launcher_APP_BINARY_NAME@* [OPTIONS...] +*prismlauncher* [OPTIONS...] # DESCRIPTION @@ -69,14 +69,14 @@ variables, besides other common Qt variables: # BUGS -@Launcher_BUG_TRACKER_URL@ +https://github.com/PrismLauncher/PrismLauncher/issues # RESOURCES -GitHub: @Launcher_Git@ +GitHub: https://github.com/PrismLauncher/PrismLauncher -Main website: https://@Launcher_Domain@ +Main website: https://prismlauncher.org # AUTHORS -@Launcher_Authors@ +Prism Launcher Contributors diff --git a/program_info/prismlauncher.rc.in b/program_info/prismlauncher.rc.in index 700143182..8f02341e5 100644 --- a/program_info/prismlauncher.rc.in +++ b/program_info/prismlauncher.rc.in @@ -3,8 +3,8 @@ #endif #include -IDI_ICON1 ICON DISCARDABLE "@Launcher_APP_BINARY_NAME@.ico" -1 RT_MANIFEST "@Launcher_APP_BINARY_NAME@.manifest" +IDI_ICON1 ICON DISCARDABLE "prismlauncher.ico" +1 RT_MANIFEST "prismlauncher.manifest" VS_VERSION_INFO VERSIONINFO FILEVERSION @Launcher_VERSION_NAME4_COMMA@ @@ -15,7 +15,7 @@ BEGIN BEGIN BLOCK "000004b0" BEGIN - VALUE "CompanyName", "@Launcher_Authors@" + VALUE "CompanyName", "MultiMC & Prism Launcher Contributors" VALUE "FileDescription", "@Launcher_DisplayName@" VALUE "FileVersion", "@Launcher_VERSION_NAME4@" VALUE "ProductName", "@Launcher_DisplayName@" diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in index 83335ce9d..e957f4d36 100644 --- a/program_info/win_install.nsi.in +++ b/program_info/win_install.nsi.in @@ -4,7 +4,6 @@ !include "x64.nsh" -AllowSkipFiles off Unicode true Name "@Launcher_DisplayName@" @@ -398,10 +397,6 @@ Section "@Launcher_DisplayName@" WriteRegStr HKCU Software\Classes\@Launcher_APP_BINARY_NAME@ "URL Protocol" "" WriteRegStr HKCU Software\Classes\@Launcher_APP_BINARY_NAME@\shell\open\command "" '"$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "%1"' - ; Write the URL Handler into registry for prismlauncher import - WriteRegStr HKCU Software\Classes\prismlauncher "URL Protocol" "" - WriteRegStr HKCU Software\Classes\prismlauncher\shell\open\command "" '"$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "%1"' - ; Write the uninstall keys for Windows ; https://learn.microsoft.com/en-us/windows/win32/msi/uninstall-registry-key ${GetParameters} $R0 diff --git a/renovate.json b/renovate.json index 856b2e91c..f9c2c3270 100644 --- a/renovate.json +++ b/renovate.json @@ -1,13 +1,6 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ - "config:recommended" - ], - "labels": [ - "area: actions", - "complexity: low", - "priority: low", - "type: robot", - "changelog:omit" + "config:base" ] } diff --git a/tests/Library_test.cpp b/tests/Library_test.cpp index 73b6bf4a2..ba9283c37 100644 --- a/tests/Library_test.cpp +++ b/tests/Library_test.cpp @@ -49,7 +49,7 @@ class LibraryTest : public QObject { { QFile jsonFile(path); if (!jsonFile.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open file" << jsonFile.fileName() << "for reading:" << jsonFile.errorString(); + qCritical() << "Failed to open file" << jsonFile.fileName() << "for reading!"; return LibraryPtr(); } auto data = jsonFile.readAll(); @@ -73,7 +73,7 @@ class LibraryTest : public QObject { { cache.reset(new HttpMetaCache()); cache->addBase("libraries", QDir("libraries").absolutePath()); - dataDir = QDir(QFINDTESTDATA("testdata/Libraries")).absolutePath(); + dataDir = QDir(QFINDTESTDATA("testdata/Library")).absolutePath(); } void test_legacy() { @@ -119,14 +119,14 @@ class LibraryTest : public QObject { QCOMPARE(test.isNative(), false); QStringList failedFiles; test.setHint("local"); - auto downloads = test.getDownloads(r, cache.get(), failedFiles, QFINDTESTDATA("testdata/Libraries")); + auto downloads = test.getDownloads(r, cache.get(), failedFiles, QFINDTESTDATA("testdata/Library")); QCOMPARE(downloads.size(), 0); qDebug() << failedFiles; QCOMPARE(failedFiles.size(), 0); QStringList jar, native, native32, native64; - test.getApplicableFiles(r, jar, native, native32, native64, QFINDTESTDATA("testdata/Libraries")); - QCOMPARE(jar, { QFileInfo(QFINDTESTDATA("testdata/Libraries/codecwav-20101023.jar")).absoluteFilePath() }); + test.getApplicableFiles(r, jar, native, native32, native64, QFINDTESTDATA("testdata/Library")); + QCOMPARE(jar, { QFileInfo(QFINDTESTDATA("testdata/Library/codecwav-20101023.jar")).absoluteFilePath() }); QCOMPARE(native, {}); QCOMPARE(native32, {}); QCOMPARE(native64, {}); @@ -217,23 +217,22 @@ class LibraryTest : public QObject { test.setRepositoryURL("file://foo/bar"); { QStringList jar, native, native32, native64; - test.getApplicableFiles(r, jar, native, native32, native64, QFINDTESTDATA("testdata/Libraries")); + test.getApplicableFiles(r, jar, native, native32, native64, QFINDTESTDATA("testdata/Library")); QCOMPARE(jar, {}); QCOMPARE(native, {}); - QCOMPARE(native32, { QFileInfo(QFINDTESTDATA("testdata/Libraries/testname-testversion-linux-32.jar")).absoluteFilePath() }); - QCOMPARE(native64, - { QFileInfo(QFINDTESTDATA("testdata/Libraries") + "/testname-testversion-linux-64.jar").absoluteFilePath() }); + QCOMPARE(native32, { QFileInfo(QFINDTESTDATA("testdata/Library/testname-testversion-linux-32.jar")).absoluteFilePath() }); + QCOMPARE(native64, { QFileInfo(QFINDTESTDATA("testdata/Library") + "/testname-testversion-linux-64.jar").absoluteFilePath() }); QStringList failedFiles; - auto dls = test.getDownloads(r, cache.get(), failedFiles, QFINDTESTDATA("testdata/Libraries")); + auto dls = test.getDownloads(r, cache.get(), failedFiles, QFINDTESTDATA("testdata/Library")); QCOMPARE(dls.size(), 0); QCOMPARE(failedFiles, - { QFileInfo(QFINDTESTDATA("testdata/Libraries") + "/testname-testversion-linux-64.jar").absoluteFilePath() }); + { QFileInfo(QFINDTESTDATA("testdata/Library") + "/testname-testversion-linux-64.jar").absoluteFilePath() }); } } void test_onenine() { RuntimeContext r = dummyContext("osx"); - auto test = readMojangJson(QFINDTESTDATA("testdata/Libraries/lib-simple.json")); + auto test = readMojangJson(QFINDTESTDATA("testdata/Library/lib-simple.json")); { QStringList jar, native, native32, native64; test->getApplicableFiles(r, jar, native, native32, native64, QString()); @@ -254,8 +253,8 @@ class LibraryTest : public QObject { test->setHint("local"); { QStringList jar, native, native32, native64; - test->getApplicableFiles(r, jar, native, native32, native64, QFINDTESTDATA("testdata/Libraries")); - QCOMPARE(jar, { QFileInfo(QFINDTESTDATA("testdata/Libraries/codecwav-20101023.jar")).absoluteFilePath() }); + test->getApplicableFiles(r, jar, native, native32, native64, QFINDTESTDATA("testdata/Library")); + QCOMPARE(jar, { QFileInfo(QFINDTESTDATA("testdata/Library/codecwav-20101023.jar")).absoluteFilePath() }); QCOMPARE(native, {}); QCOMPARE(native32, {}); QCOMPARE(native64, {}); @@ -263,7 +262,7 @@ class LibraryTest : public QObject { r.system = "linux"; { QStringList failedFiles; - auto dls = test->getDownloads(r, cache.get(), failedFiles, QFINDTESTDATA("testdata/Libraries")); + auto dls = test->getDownloads(r, cache.get(), failedFiles, QFINDTESTDATA("testdata/Library")); QCOMPARE(dls.size(), 0); QCOMPARE(failedFiles, {}); } @@ -271,12 +270,12 @@ class LibraryTest : public QObject { void test_onenine_local_override() { RuntimeContext r = dummyContext("osx"); - auto test = readMojangJson(QFINDTESTDATA("testdata/Libraries/lib-simple.json")); + auto test = readMojangJson(QFINDTESTDATA("testdata/Library/lib-simple.json")); test->setHint("local"); { QStringList jar, native, native32, native64; - test->getApplicableFiles(r, jar, native, native32, native64, QFINDTESTDATA("testdata/Libraries")); - QCOMPARE(jar, { QFileInfo(QFINDTESTDATA("testdata/Libraries/codecwav-20101023.jar")).absoluteFilePath() }); + test->getApplicableFiles(r, jar, native, native32, native64, QFINDTESTDATA("testdata/Library")); + QCOMPARE(jar, { QFileInfo(QFINDTESTDATA("testdata/Library/codecwav-20101023.jar")).absoluteFilePath() }); QCOMPARE(native, {}); QCOMPARE(native32, {}); QCOMPARE(native64, {}); @@ -284,7 +283,7 @@ class LibraryTest : public QObject { r.system = "linux"; { QStringList failedFiles; - auto dls = test->getDownloads(r, cache.get(), failedFiles, QFINDTESTDATA("testdata/Libraries")); + auto dls = test->getDownloads(r, cache.get(), failedFiles, QFINDTESTDATA("testdata/Library")); QCOMPARE(dls.size(), 0); QCOMPARE(failedFiles, {}); } @@ -292,7 +291,7 @@ class LibraryTest : public QObject { void test_onenine_native() { RuntimeContext r = dummyContext("osx"); - auto test = readMojangJson(QFINDTESTDATA("testdata/Libraries/lib-native.json")); + auto test = readMojangJson(QFINDTESTDATA("testdata/Library/lib-native.json")); QStringList jar, native, native32, native64; test->getApplicableFiles(r, jar, native, native32, native64, QString()); QCOMPARE(jar, QStringList()); @@ -310,7 +309,7 @@ class LibraryTest : public QObject { void test_onenine_native_arch() { RuntimeContext r = dummyContext("windows"); - auto test = readMojangJson(QFINDTESTDATA("testdata/Libraries/lib-native-arch.json")); + auto test = readMojangJson(QFINDTESTDATA("testdata/Library/lib-native-arch.json")); QStringList jar, native, native32, native64; test->getApplicableFiles(r, jar, native, native32, native64, QString()); QCOMPARE(jar, {}); diff --git a/tests/MojangVersionFormat_test.cpp b/tests/MojangVersionFormat_test.cpp index 385b75bb8..3c8d3ffc5 100644 --- a/tests/MojangVersionFormat_test.cpp +++ b/tests/MojangVersionFormat_test.cpp @@ -10,7 +10,7 @@ class MojangVersionFormatTest : public QObject { { QFile jsonFile(path); if (!jsonFile.open(QIODevice::ReadOnly)) { - qWarning() << "Failed to open file" << jsonFile.fileName() << "for reading:" << jsonFile.errorString(); + qWarning() << "Failed to open file '" << jsonFile.fileName() << "' for reading!"; return QJsonDocument(); } auto data = jsonFile.readAll(); @@ -21,7 +21,7 @@ class MojangVersionFormatTest : public QObject { { QFile jsonFile(file); if (!jsonFile.open(QIODevice::WriteOnly | QIODevice::Text)) { - qCritical() << "Failed to open file" << jsonFile.fileName() << "for writing:" << jsonFile.errorString(); + qCritical() << "Failed to open file '" << jsonFile.fileName() << "' for writing!"; return; } auto data = doc.toJson(QJsonDocument::Indented); @@ -33,7 +33,7 @@ class MojangVersionFormatTest : public QObject { private slots: void test_Through_Simple() { - QJsonDocument doc = readJson(QFINDTESTDATA("testdata/Libraries/1.9-simple.json")); + QJsonDocument doc = readJson(QFINDTESTDATA("testdata/MojangVersionFormat/1.9-simple.json")); auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9-simple.json"); auto doc2 = MojangVersionFormat::versionFileToJson(vfile); writeJson("1.9-simple-passthorugh.json", doc2); @@ -43,7 +43,7 @@ class MojangVersionFormatTest : public QObject { void test_Through() { - QJsonDocument doc = readJson(QFINDTESTDATA("testdata/Libraries/1.9.json")); + QJsonDocument doc = readJson(QFINDTESTDATA("testdata/MojangVersionFormat/1.9.json")); auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9.json"); auto doc2 = MojangVersionFormat::versionFileToJson(vfile); writeJson("1.9-passthorugh.json", doc2); diff --git a/tests/ResourceFolderModel_test.cpp b/tests/ResourceFolderModel_test.cpp index 26fe5ab16..f2201a5e9 100644 --- a/tests/ResourceFolderModel_test.cpp +++ b/tests/ResourceFolderModel_test.cpp @@ -69,7 +69,7 @@ class ResourceFolderModelTest : public QObject { void test_1178() { // source - QString source = QFINDTESTDATA("testdata/Resources/test_folder"); + QString source = QFINDTESTDATA("testdata/ResourceFolderModel/test_folder"); // sanity check QVERIFY(!source.endsWith('/')); @@ -133,7 +133,7 @@ class ResourceFolderModelTest : public QObject { void test_addFromWatch() { - QString source = QFINDTESTDATA("testdata/Resources"); + QString source = QFINDTESTDATA("testdata/ResourceFolderModel"); ModFolderModel model(source, nullptr, false, true); QCOMPARE(model.size(), 0); @@ -150,8 +150,8 @@ class ResourceFolderModelTest : public QObject { void test_removeResource() { - QString folder_resource = QFINDTESTDATA("testdata/Resources/test_folder"); - QString file_mod = QFINDTESTDATA("testdata/Resources/supercoolmod.jar"); + QString folder_resource = QFINDTESTDATA("testdata/ResourceFolderModel/test_folder"); + QString file_mod = QFINDTESTDATA("testdata/ResourceFolderModel/supercoolmod.jar"); QTemporaryDir tmp; ResourceFolderModel model(QDir(tmp.path()), nullptr, false, false); @@ -195,8 +195,8 @@ class ResourceFolderModelTest : public QObject { void test_enable_disable() { - QString folder_resource = QFINDTESTDATA("testdata/Resources/test_folder"); - QString file_mod = QFINDTESTDATA("testdata/Resources/supercoolmod.jar"); + QString folder_resource = QFINDTESTDATA("testdata/ResourceFolderModel/test_folder"); + QString file_mod = QFINDTESTDATA("testdata/ResourceFolderModel/supercoolmod.jar"); QTemporaryDir tmp; ResourceFolderModel model(tmp.path(), nullptr, false, false); @@ -217,8 +217,8 @@ class ResourceFolderModelTest : public QObject { auto& res_1 = model.at(0).type() != ResourceType::FOLDER ? model.at(0) : model.at(1); auto& res_2 = model.at(0).type() == ResourceType::FOLDER ? model.at(0) : model.at(1); - auto id_1 = res_1.internalId(); - auto id_2 = res_2.internalId(); + auto id_1 = res_1.internal_id(); + auto id_2 = res_2.internal_id(); bool initial_enabled_res_2 = res_2.enabled(); bool initial_enabled_res_1 = res_1.enabled(); @@ -236,12 +236,12 @@ class ResourceFolderModelTest : public QObject { qDebug() << "res_1 got successfully toggled again."; QVERIFY(res_1.enabled() == initial_enabled_res_1); - QVERIFY(res_1.internalId() == id_1); + QVERIFY(res_1.internal_id() == id_1); qDebug() << "res_1 got back to its initial state."; QVERIFY(!res_2.enable(initial_enabled_res_2 ? EnableAction::ENABLE : EnableAction::DISABLE)); QVERIFY(res_2.enabled() == initial_enabled_res_2); - QVERIFY(res_2.internalId() == id_2); + QVERIFY(res_2.internal_id() == id_2); } }; diff --git a/tests/ResourcePackParse_test.cpp b/tests/ResourcePackParse_test.cpp index c3a82b83d..5400d888a 100644 --- a/tests/ResourcePackParse_test.cpp +++ b/tests/ResourcePackParse_test.cpp @@ -30,7 +30,7 @@ class ResourcePackParseTest : public QObject { private slots: void test_parseZIP() { - QString source = QFINDTESTDATA("testdata/Resources"); + QString source = QFINDTESTDATA("testdata/ResourcePackParse"); QString zip_rp = FS::PathCombine(source, "test_resource_pack_idk.zip"); ResourcePack pack{ QFileInfo(zip_rp) }; @@ -46,7 +46,7 @@ class ResourcePackParseTest : public QObject { void test_parseFolder() { - QString source = QFINDTESTDATA("testdata/Resources"); + QString source = QFINDTESTDATA("testdata/ResourcePackParse"); QString folder_rp = FS::PathCombine(source, "test_folder"); ResourcePack pack{ QFileInfo(folder_rp) }; @@ -60,7 +60,7 @@ class ResourcePackParseTest : public QObject { void test_parseFolder2() { - QString source = QFINDTESTDATA("testdata/Resources"); + QString source = QFINDTESTDATA("testdata/ResourcePackParse"); QString folder_rp = FS::PathCombine(source, "another_test_folder"); ResourcePack pack{ QFileInfo(folder_rp) }; diff --git a/tests/Version_test.cpp b/tests/Version_test.cpp index 3e52f4eb9..541355b70 100644 --- a/tests/Version_test.cpp +++ b/tests/Version_test.cpp @@ -107,7 +107,7 @@ class VersionTest : public QObject { QFile vector_file{ test_vector_dir.absoluteFilePath("test_vectors.txt") }; if (!vector_file.open(QFile::OpenModeFlag::ReadOnly)) { - qCritical() << "Failed to open file" << vector_file.fileName() << "for reading:" << vector_file.errorString(); + qCritical() << "Failed to open file '" << vector_file.fileName() << "' for reading!"; return; } @@ -181,33 +181,8 @@ class VersionTest : public QObject { QCOMPARE(v1 > v2, !lessThan && !equal); QCOMPARE(v1 == v2, equal); } - - static void test_strict_weak_order() - { - // this tests the strict_weak_order - // https://en.cppreference.com/w/cpp/concepts/strict_weak_order.html - const Version a("1.10 Pre-Release 1"); // this is a pre-relese is before b because ' ' is lower than '-' - const Version b("1.10-pre1"); // this is a pre-release is before c that is an actual release - const Version c("1.10"); - - auto r = [](const Version& a, const Version& b) { return a < b; }; - auto e = [&r](const Version& a, const Version& b) { return !r(a, b) && !r(b, a); }; - - qCritical() << a << b << c; - - // irreflexive - QCOMPARE(r(a, a), false); - QCOMPARE(r(b, b), false); - QCOMPARE(r(c, c), false); - // transitive - QCOMPARE(r(a, b), true); - QCOMPARE(r(b, c), true); - QCOMPARE(r(a, c), true); - // transitive equivalence - QCOMPARE(e(a, b) && e(b, c), e(a, c)); - } }; QTEST_GUILESS_MAIN(VersionTest) -#include "Version_test.moc" \ No newline at end of file +#include "Version_test.moc" diff --git a/tests/testdata/Library b/tests/testdata/Library new file mode 120000 index 000000000..0e7a22864 --- /dev/null +++ b/tests/testdata/Library @@ -0,0 +1 @@ +MojangVersionFormat/ \ No newline at end of file diff --git a/tests/testdata/Libraries/1.9-simple.json b/tests/testdata/MojangVersionFormat/1.9-simple.json similarity index 100% rename from tests/testdata/Libraries/1.9-simple.json rename to tests/testdata/MojangVersionFormat/1.9-simple.json diff --git a/tests/testdata/Libraries/1.9.json b/tests/testdata/MojangVersionFormat/1.9.json similarity index 100% rename from tests/testdata/Libraries/1.9.json rename to tests/testdata/MojangVersionFormat/1.9.json diff --git a/tests/testdata/Libraries/codecwav-20101023.jar b/tests/testdata/MojangVersionFormat/codecwav-20101023.jar similarity index 100% rename from tests/testdata/Libraries/codecwav-20101023.jar rename to tests/testdata/MojangVersionFormat/codecwav-20101023.jar diff --git a/tests/testdata/Libraries/lib-native-arch.json b/tests/testdata/MojangVersionFormat/lib-native-arch.json similarity index 100% rename from tests/testdata/Libraries/lib-native-arch.json rename to tests/testdata/MojangVersionFormat/lib-native-arch.json diff --git a/tests/testdata/Libraries/lib-native.json b/tests/testdata/MojangVersionFormat/lib-native.json similarity index 100% rename from tests/testdata/Libraries/lib-native.json rename to tests/testdata/MojangVersionFormat/lib-native.json diff --git a/tests/testdata/Libraries/lib-simple.json b/tests/testdata/MojangVersionFormat/lib-simple.json similarity index 100% rename from tests/testdata/Libraries/lib-simple.json rename to tests/testdata/MojangVersionFormat/lib-simple.json diff --git a/tests/testdata/Libraries/testname-testversion-linux-32.jar b/tests/testdata/MojangVersionFormat/testname-testversion-linux-32.jar similarity index 100% rename from tests/testdata/Libraries/testname-testversion-linux-32.jar rename to tests/testdata/MojangVersionFormat/testname-testversion-linux-32.jar diff --git a/tests/testdata/ResourceFolderModel b/tests/testdata/ResourceFolderModel new file mode 120000 index 000000000..c653d859b --- /dev/null +++ b/tests/testdata/ResourceFolderModel @@ -0,0 +1 @@ +ResourcePackParse \ No newline at end of file diff --git a/tests/testdata/Resources/another_test_folder/pack.mcmeta b/tests/testdata/ResourcePackParse/another_test_folder/pack.mcmeta similarity index 100% rename from tests/testdata/Resources/another_test_folder/pack.mcmeta rename to tests/testdata/ResourcePackParse/another_test_folder/pack.mcmeta diff --git a/tests/testdata/Resources/supercoolmod.jar b/tests/testdata/ResourcePackParse/supercoolmod.jar similarity index 100% rename from tests/testdata/Resources/supercoolmod.jar rename to tests/testdata/ResourcePackParse/supercoolmod.jar diff --git a/tests/testdata/Resources/test_folder/assets/minecraft/textures/blah.txt b/tests/testdata/ResourcePackParse/test_folder/assets/minecraft/textures/blah.txt similarity index 100% rename from tests/testdata/Resources/test_folder/assets/minecraft/textures/blah.txt rename to tests/testdata/ResourcePackParse/test_folder/assets/minecraft/textures/blah.txt diff --git a/tests/testdata/Resources/test_folder/pack.mcmeta b/tests/testdata/ResourcePackParse/test_folder/pack.mcmeta similarity index 100% rename from tests/testdata/Resources/test_folder/pack.mcmeta rename to tests/testdata/ResourcePackParse/test_folder/pack.mcmeta diff --git a/tests/testdata/Resources/test_folder/pack.nfo b/tests/testdata/ResourcePackParse/test_folder/pack.nfo similarity index 100% rename from tests/testdata/Resources/test_folder/pack.nfo rename to tests/testdata/ResourcePackParse/test_folder/pack.nfo diff --git a/tests/testdata/Resources/test_resource_pack_idk.zip b/tests/testdata/ResourcePackParse/test_resource_pack_idk.zip similarity index 100% rename from tests/testdata/Resources/test_resource_pack_idk.zip rename to tests/testdata/ResourcePackParse/test_resource_pack_idk.zip diff --git a/tests/testdata/Version/test_vectors.txt b/tests/testdata/Version/test_vectors.txt index 971f23daf..e6c6507cf 100644 --- a/tests/testdata/Version/test_vectors.txt +++ b/tests/testdata/Version/test_vectors.txt @@ -1,5 +1,5 @@ # Test vector from: -# https://git.sleeping.town/exa/FlexVer/src/branch/trunk/test/test_vectors.txt +# https://github.com/unascribed/FlexVer/blob/704e12759b6e59220ff888f8bf2ec15b8f8fd969/test/test_vectors.txt # # This test file is formatted as " ", seperated by the space character # Implementations should ignore lines starting with "#" and lines that have a length of 0 @@ -61,10 +61,3 @@ a1.1.2 < a1.1.2_01 13w02a < c0.3.0_01 0.6.0-1.18.x < 0.9.beta-1.18.x -# removeLeadingZeroes (#17) -0000.0.0 = 0.0.0 -0000.00.0 = 0.00.0 -0.0.0 = 0.00.0000 -# General leading zeroes -1.0.01 = 1.0.1 -1.0.0001 = 1.0.01