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..ef5166da4 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: SCREAMING_SNAKE_CASE } \ No newline at end of file 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..fa7cdbe61 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: @@ -44,8 +44,8 @@ body: attributes: label: Unresolved Questions description: | - Are there any portions of your proposal which need to be discussed with the community before the RFC can proceed? - Be careful here -- an RFC with a lot of remaining questions is likely to be stalled. + Are there any portions of your proposal which need to be discussed with the community before the RFC can proceed? + Be careful here -- an RFC with a lot of remaining questions is likely to be stalled. If your RFC is mostly unresolved questions and not too much substance, it may not be ready. placeholder: Do a lot of users care about the cat? validations: 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..e87d79665 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: | @@ -64,7 +52,7 @@ runs: - name: Package AppImage shell: bash env: - VERSION: ${{ github.ref_type == 'tag' && github.ref_name || inputs.version }} + VERSION: ${{ inputs.version }} BUILD_DIR: build INSTALL_APPIMAGE_DIR: install-appdir @@ -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,26 +78,16 @@ 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 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" mv "$INSTALL_APPIMAGE_DIR"/{sharun,AppRun} ls -la "$INSTALL_APPIMAGE_DIR" - if [[ "${{ github.ref_type }}" == "tag" ]]; then - APPIMAGE_DEST="PrismLauncher-Linux-$APPIMAGE_ARCH.AppImage" - else - APPIMAGE_DEST="PrismLauncher-Linux-$VERSION-${{ inputs.build-type }}-$APPIMAGE_ARCH.AppImage" - fi - mkappimage \ --updateinformation "gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|PrismLauncher-Linux-$APPIMAGE_ARCH.AppImage.zsync" \ "$INSTALL_APPIMAGE_DIR" \ - "$APPIMAGE_DEST" + "PrismLauncher-Linux-$VERSION-${{ inputs.build-type }}-$APPIMAGE_ARCH.AppImage" - name: Package portable tarball shell: bash @@ -130,24 +108,24 @@ runs: # FIXME(@getchoo): gamemode doesn't seem to be very portable with DBus. Find a way to make it work! find "$INSTALL_PORTABLE_DIR" -name '*gamemode*' -exec rm {} + - for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f -o -type l); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt + for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt cd ${{ env.INSTALL_PORTABLE_DIR }} tar -czf ../PrismLauncher-portable.tar.gz * - 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 + path: PrismLauncher-${{ runner.os }}-${{ inputs.version }}-${{ inputs.build-type }}-${{ 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 + path: PrismLauncher-${{ runner.os }}-${{ inputs.version }}-${{ inputs.build-type }}-${{ 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..49dfbd545 100644 --- a/.github/actions/package/windows/action.yml +++ b/.github/actions/package/windows/action.yml @@ -54,33 +54,32 @@ runs: Get-ChildItem ${{ env.INSTALL_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt - name: Emit warning for unsigned builds - if: ${{ env.CI_HAS_ACCESS_TO_AZURE == '' || inputs.azure-client-id == '' }} + if: ${{ github.ref_name != 'develop' || inputs.azure-client-id == '' }} shell: pwsh run: | ":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY - name: Login to Azure - if: ${{ env.CI_HAS_ACCESS_TO_AZURE != '' && inputs.azure-client-id != '' }} - uses: azure/login@v3 + if: ${{ github.ref_name == 'develop' && inputs.azure-client-id != '' }} + uses: azure/login@v2 with: client-id: ${{ inputs.azure-client-id }} tenant-id: ${{ inputs.azure-tenant-id }} subscription-id: ${{ inputs.azure-subscription-id }} - name: Sign executables - if: ${{ env.CI_HAS_ACCESS_TO_AZURE != '' && inputs.azure-client-id != '' }} - uses: azure/artifact-signing-action@v2 + if: ${{ github.ref_name == 'develop' && inputs.azure-client-id != '' }} + uses: azure/trusted-signing-action@v0 with: endpoint: https://eus.codesigning.azure.net/ trusted-signing-account-name: PrismLauncher certificate-profile-name: PrismLauncher - files-folder: ${{ github.workspace }}\install\ - files-folder-filter: dll,exe - 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' + + files: | + ${{ github.workspace }}\install\prismlauncher.exe + ${{ github.workspace }}\install\prismlauncher_filelink.exe + ${{ github.workspace }}\install\prismlauncher_updater.exe + # 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 +141,7 @@ runs: - name: Sign installer if: ${{ env.CI_HAS_ACCESS_TO_AZURE != '' && inputs.azure-client-id != '' }} - uses: azure/artifact-signing-action@v2 + uses: azure/trusted-signing-action@v0 with: endpoint: https://eus.codesigning.azure.net/ trusted-signing-account-name: PrismLauncher @@ -151,9 +150,6 @@ runs: files: | ${{ 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' # 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 +164,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/macos/action.yml b/.github/actions/setup-dependencies/macos/action.yml index a90544be0..7de08e261 100644 --- a/.github/actions/setup-dependencies/macos/action.yml +++ b/.github/actions/setup-dependencies/macos/action.yml @@ -44,4 +44,4 @@ runs: - name: Setup vcpkg environment shell: bash run: | - echo "VCPKG_ROOT=$VCPKG_INSTALLATION_ROOT" >> "$GITHUB_ENV" + echo "CMAKE_TOOLCHAIN_FILE=$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" >> "$GITHUB_ENV" diff --git a/.github/actions/setup-dependencies/windows/action.yml b/.github/actions/setup-dependencies/windows/action.yml index 24ad51d8f..b250b0088 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: @@ -58,7 +57,7 @@ runs: if: ${{ inputs.msystem == '' }} shell: bash run: | - echo "VCPKG_ROOT=$VCPKG_INSTALLATION_ROOT" >> "$GITHUB_ENV" + echo "CMAKE_TOOLCHAIN_FILE=$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" >> "$GITHUB_ENV" - name: Setup MSYS2 (MinGW) if: ${{ inputs.msystem != '' }} @@ -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..ecbaf755d 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 }} @@ -78,11 +76,11 @@ jobs: run: | prs=$( jq -c ' - .prBody as $body + .prBody as $body | ( - $body | - reduce ( - . | scan("[Bb]locked (?:[Bb]y|[Oo]n):? #([0-9]+)") + $body | + reduce ( + . | scan("blocked (?:by|on):? #([0-9]+)") | map({ "type": "Blocked on", "number": ( . | tonumber ) @@ -90,17 +88,17 @@ jobs: ) as $i ([]; . + [$i[]]) ) as $bprs | ( - $body | - reduce ( - . | scan("[Ss]tacked [Oo]n:? #([0-9]+)") + $body | + reduce ( + . | scan("stacked on:? #([0-9]+)") | map({ "type": "Stacked on", "number": ( . | tonumber ) }) ) as $i ([]; . + [$i[]]) ) as $sprs - | ($bprs + $sprs) as $prs - | { + | ($bprs + $sprs) as $prs + | { "blocking": $prs, "numBlocking": ( $prs | length), } @@ -128,7 +126,7 @@ jobs: "number": .number, "merged": .merged, "state": (if .state == "open" then "Open" elif .merged then "Merged" else "Closed" end), - "labels": (reduce .labels[].name as $l ([]; . + [$l])), + "labels": (reduce .labels[].name as $l ([]; . + [$l])), "basePrUrl": .html_url, "baseRepoName": .head.repo.name, "baseRepoOwner": .head.repo.owner.login, @@ -145,19 +143,19 @@ jobs: echo "current_blocking=$(jq -c 'map( select( (.type == "Stacked on" and (.merged | not)) or - (.type == "Blocked on" and (.state == "Open")) + (.type == "Blocked on" and (.state == "Open")) ) | .number )' <<< "$blocked_pr_data" )"; } >> "$GITHUB_OUTPUT" - 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 +164,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 +175,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 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0596906c8..eaf9e49ef 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 @@ -46,12 +91,12 @@ jobs: - os: ubuntu-24.04 artifact-name: Linux cmake-preset: linux - qt-version: 6.10.2 + qt-version: 6.10.1 - os: ubuntu-24.04-arm artifact-name: Linux-aarch64 cmake-preset: linux - qt-version: 6.10.2 + qt-version: 6.10.1 - os: windows-2022 artifact-name: Windows-MinGW-w64 @@ -70,13 +115,13 @@ jobs: cmake-preset: windows_msvc # TODO(@getchoo): This is the default in setup-dependencies/windows. Why isn't it working?!?! vcvars-arch: amd64 - qt-version: 6.10.2 + qt-version: 6.10.1 - os: windows-11-arm artifact-name: Windows-MSVC-arm64 cmake-preset: windows_msvc vcvars-arch: arm64 - qt-version: 6.10.2 + qt-version: 6.10.1 - os: macos-26 artifact-name: macOS @@ -175,8 +220,6 @@ jobs: - name: Package (Windows) if: ${{ runner.os == 'Windows' }} uses: ./.github/actions/package/windows - env: - CI_HAS_ACCESS_TO_AZURE: ${{ vars.CI_HAS_ACCESS_TO_AZURE || '' }} with: version: ${{ steps.short-version.outputs.version }} build-type: ${{ steps.setup-dependencies.outputs.build-type }} 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..8a5fa26fb 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 @@ -37,16 +79,12 @@ jobs: uses: ./.github/actions/setup-dependencies with: build-type: Debug - qt-version: 6.4.3 + qt-version: 6.10.1 - 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 - - 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/flatpak.yml b/.github/workflows/flatpak.yml new file mode 100644 index 000000000..e3fbb2c32 --- /dev/null +++ b/.github/workflows/flatpak.yml @@ -0,0 +1,102 @@ +name: Flatpak + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + push: + branches: + - "develop" + - "release-*" + # We don't do anything with these artifacts on releases. They go to Flathub + tags-ignore: + - "*" + paths: + # File types + - "**.cpp" + - "**.h" + - "**.java" + - "**.ui" + + # Build files + - "flatpak/**" + + # Directories + - "buildconfig/**" + - "cmake/**" + - "launcher/**" + - "libraries/**" + - "program_info/**" + - "tests/**" + + # Files + - "CMakeLists.txt" + - "COPYING.md" + + # Workflows + - ".github/workflows/flatpak.yml" + pull_request: + paths: + # File types + - "**.cpp" + - "**.h" + - "**.java" + - "**.ui" + + # Build files + - "flatpak/**" + + # Directories + - "buildconfig/**" + - "cmake/**" + - "launcher/**" + - "libraries/**" + - "program_info/**" + - "tests/**" + + # Files + - "CMakeLists.txt" + - "COPYING.md" + + # Workflows + - ".github/workflows/flatpak.yml" + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + name: Build (${{ matrix.arch }}) + + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-22.04 + arch: x86_64 + + runs-on: ${{ matrix.os }} + + container: + image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.10 + options: --privileged + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + submodules: true + + - name: Set short version + shell: bash + run: | + echo "VERSION=${GITHUB_SHA::7}" >> "$GITHUB_ENV" + + - name: Build Flatpak + uses: flatpak/flatpak-github-actions/flatpak-builder@v6 + with: + bundle: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-Flatpak.flatpak + manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml + arch: ${{ matrix.arch }} diff --git a/.github/workflows/merge-blocking-pr.yml b/.github/workflows/merge-blocking-pr.yml index 3542a470e..d37c33761 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,10 +35,10 @@ 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 | + .body | scan("(?:blocked (?:by|on)|stacked on):? #([0-9]+)") | map(tonumber) | any(.[]; . == $pr) @@ -49,7 +47,7 @@ jobs: ) { echo "deps=$blocked_prs" - echo "numdeps=$(jq -r '. | length' <<< "$blocked_prs")" + echo "numdeps=$(jq -r '. | length' <<< "$blocked_prs")" } >> "$GITHUB_OUTPUT" - name: Trigger Blocked PR Workflows for Dependants 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/.gitmodules b/.gitmodules index 42c566fa8..f2d71ef29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "libraries/libnbtplusplus"] path = libraries/libnbtplusplus url = https://github.com/PrismLauncher/libnbtplusplus.git +[submodule "flatpak/shared-modules"] + path = flatpak/shared-modules + url = https://github.com/flathub/shared-modules.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 12afdefcc..8816be82e 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,13 +169,12 @@ 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) @@ -225,8 +216,6 @@ set(Launcher_SUBREDDIT_URL "https://prismlauncher.org/reddit" CACHE STRING "URL # Builds set(Launcher_QT_VERSION_MAJOR "6" CACHE STRING "Major Qt version to build against") -option(Launcher_USE_PCH "Use precompiled headers where possible" ON) - # Java downloader set(Launcher_ENABLE_JAVA_DOWNLOADER_DEFAULT ON) @@ -292,7 +281,7 @@ set(Launcher_BUILD_TIMESTAMP "${TODAY}") # Find the required Qt parts if(Launcher_QT_VERSION_MAJOR EQUAL 6) set(QT_VERSION_MAJOR 6) - find_package(Qt6 6.4 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml NetworkAuth OpenGL) + find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml NetworkAuth OpenGL) find_package(Qt6 COMPONENTS DBus) list(APPEND Launcher_QT_DBUS Qt6::DBus) else() @@ -374,7 +363,7 @@ if(UNIX AND APPLE) # Mac bundle settings set(MACOSX_BUNDLE_BUNDLE_NAME "${Launcher_DisplayName}") set(MACOSX_BUNDLE_INFO_STRING "${Launcher_DisplayName}: A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once.") - set(MACOSX_BUNDLE_GUI_IDENTIFIER "${Launcher_AppID}") + set(MACOSX_BUNDLE_GUI_IDENTIFIER "org.prismlauncher.${Launcher_Name}") set(MACOSX_BUNDLE_BUNDLE_VERSION "${Launcher_VERSION_NAME}") set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_NAME}") set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_NAME}") @@ -419,7 +408,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} @@ -436,7 +425,7 @@ if(UNIX AND APPLE) else() message(WARNING "actool not found. Cannot compile macOS app icons.\n" - "Install Xcode command line tools: 'xcode-select --install'") + "Install Xcode command line tools: 'xcode-select --install'") endif() @@ -454,7 +443,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 +481,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..613fada07 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -1,222 +1,214 @@ { - "$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json", - "version": 8, - "cmakeMinimumRequired": { - "major": 3, - "minor": 28 - }, - "configurePresets": [ - { - "name": "base", - "hidden": true, - "binaryDir": "build", - "installDir": "install", - "generator": "Ninja Multi-Config", - "cacheVariables": { - "Launcher_BUILD_ARTIFACT": "$penv{ARTIFACT_NAME}", - "Launcher_BUILD_PLATFORM": "$penv{BUILD_PLATFORM}", - "Launcher_ENABLE_JAVA_DOWNLOADER": "ON", - "ENABLE_LTO": "ON" - } - }, - { - "name": "linux", - "displayName": "Linux", - "inherits": [ - "base" - ], - "condition": { - "type": "equals", - "lhs": "${hostSystemName}", - "rhs": "Linux" - } - }, - { - "name": "macos", - "displayName": "macOS", - "inherits": [ - "base" - ], - "condition": { - "type": "equals", - "lhs": "${hostSystemName}", - "rhs": "Darwin" - }, - "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "$penv{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" - } - }, - { - "name": "macos_universal", - "displayName": "macOS (Universal Binary)", - "inherits": [ - "macos" - ], - "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "$penv{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", - "CMAKE_OSX_ARCHITECTURES": "x86_64;arm64", - "VCPKG_TARGET_TRIPLET": "universal-osx" - } - }, - { - "name": "windows_mingw", - "displayName": "Windows (MinGW)", - "inherits": [ - "base" - ], - "condition": { - "type": "equals", - "lhs": "${hostSystemName}", - "rhs": "Windows" - } - }, - { - "name": "windows_msvc", - "displayName": "Windows (MSVC)", - "inherits": [ - "base" - ], - "condition": { - "type": "equals", - "lhs": "${hostSystemName}", - "rhs": "Windows" - }, - "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "$penv{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" - } - } - ], - "buildPresets": [ - { - "name": "linux", - "displayName": "Linux", - "condition": { - "type": "equals", - "lhs": "${hostSystemName}", - "rhs": "Linux" - }, - "configurePreset": "linux" - }, - { - "name": "macos", - "displayName": "macOS", - "condition": { - "type": "equals", - "lhs": "${hostSystemName}", - "rhs": "Darwin" - }, - "configurePreset": "macos" - }, - { - "name": "macos_universal", - "displayName": "macOS (Universal Binary)", - "inherits": [ - "macos" - ], - "configurePreset": "macos_universal" - }, - { - "name": "windows_mingw", - "displayName": "Windows (MinGW)", - "condition": { - "type": "equals", - "lhs": "${hostSystemName}", - "rhs": "Windows" - }, - "configurePreset": "windows_mingw" - }, - { - "name": "windows_msvc", - "displayName": "Windows (MSVC)", - "condition": { - "type": "equals", - "lhs": "${hostSystemName}", - "rhs": "Windows" - }, - "configurePreset": "windows_msvc" - } - ], - "testPresets": [ - { - "name": "base", - "hidden": true, - "output": { - "outputOnFailure": true, - "verbosity": "extra" - }, - "execution": { - "noTestsAction": "error" - }, - "filter": { - "exclude": { - "name": "^example64|example$" - } - } - }, - { - "name": "linux", - "displayName": "Linux", - "inherits": [ - "base" - ], - "condition": { - "type": "equals", - "lhs": "${hostSystemName}", - "rhs": "Linux" - }, - "configurePreset": "linux" - }, - { - "name": "macos", - "displayName": "macOS", - "inherits": [ - "base" - ], - "condition": { - "type": "equals", - "lhs": "${hostSystemName}", - "rhs": "Darwin" - }, - "configurePreset": "macos" - }, - { - "name": "macos_universal", - "displayName": "macOS (Universal Binary)", - "inherits": [ - "base" - ], - "condition": { - "type": "equals", - "lhs": "${hostSystemName}", - "rhs": "Darwin" - }, - "configurePreset": "macos_universal" - }, - { - "name": "windows_mingw", - "displayName": "Windows (MinGW)", - "inherits": [ - "base" - ], - "condition": { - "type": "equals", - "lhs": "${hostSystemName}", - "rhs": "Windows" - }, - "configurePreset": "windows_mingw" - }, - { - "name": "windows_msvc", - "displayName": "Windows (MSVC)", - "inherits": [ - "base" - ], - "condition": { - "type": "equals", - "lhs": "${hostSystemName}", - "rhs": "Windows" - }, - "configurePreset": "windows_msvc" - } - ] + "$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json", + "version": 8, + "cmakeMinimumRequired": { + "major": 3, + "minor": 28 + }, + "configurePresets": [ + { + "name": "base", + "hidden": true, + "binaryDir": "build", + "installDir": "install", + "generator": "Ninja Multi-Config", + "cacheVariables": { + "Launcher_BUILD_ARTIFACT": "$penv{ARTIFACT_NAME}", + "Launcher_BUILD_PLATFORM": "$penv{BUILD_PLATFORM}", + "Launcher_ENABLE_JAVA_DOWNLOADER": "ON", + "ENABLE_LTO": "ON" + } + }, + { + "name": "linux", + "displayName": "Linux", + "inherits": [ + "base" + ], + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + } + }, + { + "name": "macos", + "displayName": "macOS", + "inherits": [ + "base" + ], + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + } + }, + { + "name": "macos_universal", + "displayName": "macOS (Universal Binary)", + "inherits": [ + "macos" + ], + "cacheVariables": { + "CMAKE_OSX_ARCHITECTURES": "x86_64;arm64", + "VCPKG_TARGET_TRIPLET": "universal-osx" + } + }, + { + "name": "windows_mingw", + "displayName": "Windows (MinGW)", + "inherits": [ + "base" + ], + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "windows_msvc", + "displayName": "Windows (MSVC)", + "inherits": [ + "base" + ], + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + } + ], + "buildPresets": [ + { + "name": "linux", + "displayName": "Linux", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + }, + "configurePreset": "linux" + }, + { + "name": "macos", + "displayName": "macOS", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + }, + "configurePreset": "macos" + }, + { + "name": "macos_universal", + "displayName": "macOS (Universal Binary)", + "inherits": [ + "macos" + ], + "configurePreset": "macos_universal" + }, + { + "name": "windows_mingw", + "displayName": "Windows (MinGW)", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + }, + "configurePreset": "windows_mingw" + }, + { + "name": "windows_msvc", + "displayName": "Windows (MSVC)", + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + }, + "configurePreset": "windows_msvc" + } + ], + "testPresets": [ + { + "name": "base", + "hidden": true, + "output": { + "outputOnFailure": true + }, + "execution": { + "noTestsAction": "error" + }, + "filter": { + "exclude": { + "name": "^example64|example$" + } + } + }, + { + "name": "linux", + "displayName": "Linux", + "inherits": [ + "base" + ], + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + }, + "configurePreset": "linux" + }, + { + "name": "macos", + "displayName": "macOS", + "inherits": [ + "base" + ], + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + }, + "configurePreset": "macos" + }, + { + "name": "macos_universal", + "displayName": "macOS (Universal Binary)", + "inherits": [ + "base" + ], + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + }, + "configurePreset": "macos_universal" + }, + { + "name": "windows_mingw", + "displayName": "Windows (MinGW)", + "inherits": [ + "base" + ], + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + }, + "configurePreset": "windows_mingw" + }, + { + "name": "windows_msvc", + "displayName": "Windows (MSVC)", + "inherits": [ + "base" + ], + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + }, + "configurePreset": "windows_msvc" + } + ] } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f4b12d08b..fdc79faf2 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! @@ -60,8 +13,7 @@ Please also follow the project's conventions for C++: - Public, private or protected `static const` class data members should be formatted as `SCREAMING_SNAKE_CASE`: `MAX_VALUE`. - Class function members should be formatted as `camelCase` without a prefix: `incrementCounter`. - Global functions and non-`const` global variables should be formatted as `camelCase` without a prefix: `globalData`. -- `const` global variables and macros should be formatted as `SCREAMING_SNAKE_CASE`: `LIGHT_GRAY`. -- enum constants should be formatted as `PascalCase`: `CamelusBactrianus` +- `const` global variables, macros, and enum constants should be formatted as `SCREAMING_SNAKE_CASE`: `LIGHT_GRAY`. - Avoid inventing acronyms or abbreviations especially for a name of multiple words - like `tp` for `texturePack`. - Avoid using `[[nodiscard]]` unless ignoring the return value is likely to cause a bug in cases such as: - A function allocates memory or another resource and the caller needs to clean it up. @@ -78,7 +30,7 @@ Here is what these conventions with the formatting configuration look like: constexpr double PI = 3.14159; -enum class PizzaToppings { HamAndPineapple, OreoAndKetchup }; +enum class PizzaToppings { HAM_AND_PINEAPPLE, OREO_AND_KETCHUP }; struct Person { QString name; diff --git a/COPYING.md b/COPYING.md index 52f29f2e6..fb33844f7 100644 --- a/COPYING.md +++ b/COPYING.md @@ -1,7 +1,7 @@ ## Prism Launcher Prism Launcher - Minecraft Launcher - Copyright (C) 2022-2026 Prism Launcher Contributors + Copyright (C) 2022-2025 Prism Launcher Contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by 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..850370a29 100644 --- a/README.md +++ b/README.md @@ -27,13 +27,17 @@ 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. Prebuilt Development builds are provided for **Linux**, **Windows** and **macOS**. -On Linux, we also offer our own [Flatpak nightly repository](https://github.com/PrismLauncher/flatpak). Most software centers are able to install it by opening [this link](https://flatpak.prismlauncher.org/prismlauncher-nightly.flatpakref). +For **Arch**, **Debian**, **Fedora**, **OpenSUSE (Tumbleweed)** and **Gentoo**, respectively, you can use these packages for the latest development versions: + +[![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--git-1793D1?label=AUR&logo=archlinux&logoColor=white)](https://aur.archlinux.org/packages/prismlauncher-git) [![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--qt5--git-1793D1?label=AUR&logo=archlinux&logoColor=white)](https://aur.archlinux.org/packages/prismlauncher-qt5-git) [![prismlauncher-git](https://img.shields.io/badge/mpr-prismlauncher--git-A80030?label=MPR&logo=debian&logoColor=white)](https://mpr.makedeb.org/packages/prismlauncher-git)
[![prismlauncher-nightly](https://img.shields.io/badge/copr-prismlauncher--nightly-51A2DA?label=COPR&logo=fedora&logoColor=white)](https://copr.fedorainfracloud.org/coprs/g3tchoo/prismlauncher/) [![prismlauncher-nightly](https://img.shields.io/badge/OBS-prismlauncher--nightly-3AB6A9?logo=opensuse&logoColor=white)](https://build.opensuse.org/project/show/home:getchoo) [![prismlauncher-9999](https://img.shields.io/badge/gentoo-prismlauncher--9999-4D4270?label=Gentoo&logo=gentoo&logoColor=white)](https://packages.gentoo.org/packages/games-action/prismlauncher) + +These packages are also available to all the distributions based on the ones mentioned above. ## Community & Support @@ -57,7 +61,12 @@ The translation effort for Prism Launcher is hosted on [Weblate](https://hosted. ## Building -If you want to build Prism Launcher yourself, check the [build instructions](https://prismlauncher.org/wiki/development/build-instructions). +If you want to build Prism Launcher yourself, check the build instructions: + +- [Windows](https://prismlauncher.org/wiki/development/build-instructions/windows/) +- [Linux](https://prismlauncher.org/wiki/development/build-instructions/linux/) +- [MacOS](https://prismlauncher.org/wiki/development/build-instructions/macos/) +- [OpenBSD](https://prismlauncher.org/wiki/development/build-instructions/openbsd/) ## Sponsors & Partners diff --git a/buildconfig/BuildConfig.cpp.in b/buildconfig/BuildConfig.cpp.in index 14d8236d8..3637e7369 100644 --- a/buildconfig/BuildConfig.cpp.in +++ b/buildconfig/BuildConfig.cpp.in @@ -33,6 +33,7 @@ * limitations under the License. */ +#include #include #include "BuildConfig.h" @@ -50,7 +51,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 +106,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..cfd671d68 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 @@ -44,8 +44,6 @@ LSRequiresCarbon - LSApplicationCategoryType - public.app-category.games NSHumanReadableCopyright ${MACOSX_BUNDLE_COPYRIGHT} SUPublicEDKey @@ -61,7 +59,7 @@ mrpack CFBundleTypeName - ${Launcher_DisplayName} instance + Prism Launcher instance CFBundleTypeOSTypes TEXT @@ -87,11 +85,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/flatpak/cmark.yml b/flatpak/cmark.yml new file mode 100644 index 000000000..d5078baab --- /dev/null +++ b/flatpak/cmark.yml @@ -0,0 +1,14 @@ +name: cmark +buildsystem: cmake-ninja +builddir: true +config-opts: + - -DCMAKE_TESTS=OFF +sources: + - type: archive + url: https://github.com/commonmark/cmark/archive/0.31.1.tar.gz + sha256: 3da93db5469c30588cfeb283d9d62edfc6ded9eb0edc10a4f5bbfb7d722ea802 + x-checker-data: + type: anitya + project-id: 9159 + stable-only: true + url-template: https://github.com/commonmark/cmark/archive/$version.tar.gz diff --git a/flatpak/flite.json b/flatpak/flite.json new file mode 100644 index 000000000..1bf280af1 --- /dev/null +++ b/flatpak/flite.json @@ -0,0 +1,20 @@ +{ + "name": "flite", + "config-opts": [ + "--enable-shared", + "--with-audio=pulseaudio" + ], + "no-parallel-make": true, + "sources": [ + { + "type": "git", + "url": "https://github.com/festvox/flite.git", + "tag": "v2.2", + "commit": "e9e2e37c329dbe98bfeb27a1828ef9a71fa84f88", + "x-checker-data": { + "type": "git", + "tag-pattern": "^v([\\d.]+)$" + } + } + ] +} diff --git a/flatpak/libdecor.json b/flatpak/libdecor.json new file mode 100644 index 000000000..1652a2f04 --- /dev/null +++ b/flatpak/libdecor.json @@ -0,0 +1,18 @@ +{ + "name": "libdecor", + "buildsystem": "meson", + "config-opts": [ + "-Ddemo=false" + ], + "sources": [ + { + "type": "git", + "url": "https://gitlab.freedesktop.org/libdecor/libdecor.git", + "commit": "c2bd8ad6fa42c0cb17553ce77ad8a87d1f543b1f" + } + ], + "cleanup": [ + "/include", + "/lib/pkgconfig" + ] +} diff --git a/flatpak/org.prismlauncher.PrismLauncher.yml b/flatpak/org.prismlauncher.PrismLauncher.yml new file mode 100644 index 000000000..3de68e228 --- /dev/null +++ b/flatpak/org.prismlauncher.PrismLauncher.yml @@ -0,0 +1,154 @@ +id: org.prismlauncher.PrismLauncher +runtime: org.kde.Platform +runtime-version: '6.10' +sdk: org.kde.Sdk +sdk-extensions: + - org.freedesktop.Sdk.Extension.openjdk17 + +command: prismlauncher +finish-args: + - --share=ipc + - --socket=x11 + - --socket=wayland + - --device=all + - --share=network + - --socket=pulseaudio + # for Discord RPC mods + - --filesystem=xdg-run/app/com.discordapp.Discord:create + # Mod drag&drop + - --filesystem=xdg-download:ro + # FTBApp import + - --filesystem=~/.ftba:ro + # Userspace visibility for manual hugepages configuration + # Required for -XX:+UseLargePages + - --filesystem=/sys/kernel/mm/hugepages:ro + # Userspace visibility for transparent hugepages configuration + # Required for -XX:+UseTransparentHugePages + - --filesystem=/sys/kernel/mm/transparent_hugepage:ro + +modules: + - cmark.yml + - tomlplusplus.yml + + # Might be needed by some Controller mods (see https://github.com/isXander/Controlify/issues/31) + - shared-modules/libusb/libusb.json + + # Needed for proper Wayland support + - libdecor.json + + # Text to Speech in the game + - flite.json + + - name: prismlauncher + buildsystem: cmake-ninja + builddir: true + config-opts: + - -DLauncher_BUILD_PLATFORM=flatpak + # This allows us to manage and update Java independently of this Flatpak + - -DLauncher_ENABLE_JAVA_DOWNLOADER=ON + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + build-options: + env: + JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17 + JAVA_COMPILER: /usr/lib/sdk/openjdk17/jvm/openjdk-17/bin/javac + run-tests: true + sources: + - type: dir + path: ../ + + - name: glfw + buildsystem: cmake-ninja + config-opts: + - -DCMAKE_BUILD_TYPE=RelWithDebInfo + - -DBUILD_SHARED_LIBS:BOOL=ON + - -DGLFW_BUILD_WAYLAND:BOOL=ON + - -DGLFW_BUILD_DOCS:BOOL=OFF + sources: + - type: git + url: https://github.com/glfw/glfw.git + commit: 7b6aead9fb88b3623e3b3725ebb42670cbe4c579 # 3.4 + - type: patch + path: patches/0009-Defer-setting-cursor-position-until-the-cursor-is-lo.patch + cleanup: + - /include + - /lib/cmake + - /lib/pkgconfig + + - name: xrandr + buildsystem: autotools + sources: + - type: archive + url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.3.tar.xz + sha256: f8dd7566adb74147fab9964680b6bbadee87cf406a7fcff51718a5e6949b841c + x-checker-data: + type: anitya + project-id: 14957 + stable-only: true + url-template: https://xorg.freedesktop.org/archive/individual/app/xrandr-$version.tar.xz + cleanup: + - /share/man + - /bin/xkeystone + + - name: gamemode + buildsystem: meson + config-opts: + - -Dwith-sd-bus-provider=no-daemon + - -Dwith-examples=false + post-install: + # gamemoderun is installed for users who want to use wrapper commands + # post-install is running inside the build dir, we need it from the source though + - install -Dm755 ../data/gamemoderun -t /app/bin + sources: + - type: archive + dest-filename: gamemode.tar.gz + url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.8.2 + sha256: 2886d4ce543c78bd2a364316d5e7fd59ef06b71de63f896b37c6d3dc97658f60 + x-checker-data: + type: json + url: https://api.github.com/repos/FeralInteractive/gamemode/releases/latest + version-query: .tag_name + url-query: .tarball_url + timestamp-query: .published_at + cleanup: + - /include + - /lib/pkgconfig + - /lib/libgamemodeauto.a + + - name: glxinfo + buildsystem: meson + config-opts: + - --bindir=/app/mesa-demos + - -Degl=disabled + - -Dglut=disabled + - -Dosmesa=disabled + - -Dvulkan=disabled + - -Dwayland=disabled + post-install: + - mv -v /app/mesa-demos/glxinfo /app/bin + sources: + - type: archive + url: https://archive.mesa3d.org/demos/mesa-demos-9.0.0.tar.xz + sha256: 3046a3d26a7b051af7ebdd257a5f23bfeb160cad6ed952329cdff1e9f1ed496b + x-checker-data: + type: anitya + project-id: 16781 + stable-only: true + url-template: https://archive.mesa3d.org/demos/mesa-demos-$version.tar.xz + cleanup: + - /include + - /mesa-demos + - /share + modules: + - shared-modules/glu/glu-9.json + + - name: enhance + buildsystem: simple + build-commands: + - install -Dm755 prime-run /app/bin/prime-run + - mv /app/bin/prismlauncher /app/bin/prismrun + - install -Dm755 prismlauncher /app/bin/prismlauncher + sources: + - type: file + path: prime-run + - type: file + path: prismlauncher diff --git a/flatpak/patches/0009-Defer-setting-cursor-position-until-the-cursor-is-lo.patch b/flatpak/patches/0009-Defer-setting-cursor-position-until-the-cursor-is-lo.patch new file mode 100644 index 000000000..70cec9981 --- /dev/null +++ b/flatpak/patches/0009-Defer-setting-cursor-position-until-the-cursor-is-lo.patch @@ -0,0 +1,59 @@ +From 9997ae55a47de469ea26f8437c30b51483abda5f Mon Sep 17 00:00:00 2001 +From: Dan Klishch +Date: Sat, 30 Sep 2023 23:38:05 -0400 +Subject: Defer setting cursor position until the cursor is locked + +--- + src/wl_platform.h | 3 +++ + src/wl_window.c | 14 ++++++++++++-- + 2 files changed, 15 insertions(+), 2 deletions(-) + +diff --git a/src/wl_platform.h b/src/wl_platform.h +index ca34f66e..cd1f227f 100644 +--- a/src/wl_platform.h ++++ b/src/wl_platform.h +@@ -403,6 +403,9 @@ typedef struct _GLFWwindowWayland + int scaleSize; + int compositorPreferredScale; + ++ double askedCursorPosX, askedCursorPosY; ++ GLFWbool didAskForSetCursorPos; ++ + struct zwp_relative_pointer_v1* relativePointer; + struct zwp_locked_pointer_v1* lockedPointer; + struct zwp_confined_pointer_v1* confinedPointer; +diff --git a/src/wl_window.c b/src/wl_window.c +index 1de26558..0df16747 100644 +--- a/src/wl_window.c ++++ b/src/wl_window.c +@@ -2586,8 +2586,9 @@ void _glfwGetCursorPosWayland(_GLFWwindow* window, double* xpos, double* ypos) + + void _glfwSetCursorPosWayland(_GLFWwindow* window, double x, double y) + { +- _glfwInputError(GLFW_FEATURE_UNAVAILABLE, +- "Wayland: The platform does not support setting the cursor position"); ++ window->wl.didAskForSetCursorPos = true; ++ window->wl.askedCursorPosX = x; ++ window->wl.askedCursorPosY = y; + } + + void _glfwSetCursorModeWayland(_GLFWwindow* window, int mode) +@@ -2819,6 +2820,15 @@ static const struct zwp_relative_pointer_v1_listener relativePointerListener = + static void lockedPointerHandleLocked(void* userData, + struct zwp_locked_pointer_v1* lockedPointer) + { ++ _GLFWwindow* window = userData; ++ ++ if (window->wl.didAskForSetCursorPos) ++ { ++ window->wl.didAskForSetCursorPos = false; ++ zwp_locked_pointer_v1_set_cursor_position_hint(window->wl.lockedPointer, ++ wl_fixed_from_double(window->wl.askedCursorPosX), ++ wl_fixed_from_double(window->wl.askedCursorPosY)); ++ } + } + + static void lockedPointerHandleUnlocked(void* userData, +-- +2.42.0 + diff --git a/flatpak/prime-run b/flatpak/prime-run new file mode 100644 index 000000000..946c28dd5 --- /dev/null +++ b/flatpak/prime-run @@ -0,0 +1,4 @@ +#!/bin/sh + +export __NV_PRIME_RENDER_OFFLOAD=1 __VK_LAYER_NV_optimus=NVIDIA_only __GLX_VENDOR_LIBRARY_NAME=nvidia +exec "$@" diff --git a/flatpak/prismlauncher b/flatpak/prismlauncher new file mode 100644 index 000000000..039d890d2 --- /dev/null +++ b/flatpak/prismlauncher @@ -0,0 +1,11 @@ +#!/bin/bash + +# discord RPC +for i in {0..9}; do + test -S "$XDG_RUNTIME_DIR"/discord-ipc-"$i" || ln -sf {app/com.discordapp.Discord,"$XDG_RUNTIME_DIR"}/discord-ipc-"$i"; +done + +export PATH="${PATH}${PATH:+:}/usr/lib/extensions/vulkan/gamescope/bin:/usr/lib/extensions/vulkan/MangoHud/bin" +export VK_LAYER_PATH="/usr/lib/extensions/vulkan/share/vulkan/implicit_layer.d/" + +exec /app/bin/prismrun "$@" diff --git a/flatpak/shared-modules b/flatpak/shared-modules new file mode 160000 index 000000000..73f08ed2c --- /dev/null +++ b/flatpak/shared-modules @@ -0,0 +1 @@ +Subproject commit 73f08ed2c3187f6648ca04ebef030930a6c9f0be diff --git a/flatpak/tomlplusplus.yml b/flatpak/tomlplusplus.yml new file mode 100644 index 000000000..0afaf6678 --- /dev/null +++ b/flatpak/tomlplusplus.yml @@ -0,0 +1,6 @@ +name: tomlplusplus +buildsystem: cmake-ninja +sources: + - type: archive + url: https://github.com/marzer/tomlplusplus/archive/v3.4.0.tar.gz + sha256: 8517f65938a4faae9ccf8ebb36631a38c1cadfb5efa85d9a72e15b9e97d25155 diff --git a/launcher/Application.cpp b/launcher/Application.cpp index ddeb30588..67ddb53e8 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -50,7 +50,6 @@ #include "tools/GenericProfiler.h" #include "ui/InstanceWindow.h" #include "ui/MainWindow.h" -#include "ui/ToolTipFilter.h" #include "ui/ViewLogWindow.h" #include "ui/dialogs/ProgressDialog.h" @@ -126,11 +125,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 +157,7 @@ #endif #include #include +#include "console/WindowsConsole.h" #endif #include "console/Console.h" @@ -290,9 +291,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 +331,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 +346,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 +363,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 +405,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 +491,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 +525,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,37 +588,39 @@ 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"); } { qInfo() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME + ", " + QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", ")); - qInfo() << "Version :" << BuildConfig.printableVersionString(); - qInfo() << "Platform :" << BuildConfig.BUILD_PLATFORM; - qInfo() << "Git commit :" << BuildConfig.GIT_COMMIT; - qInfo() << "Git refspec :" << BuildConfig.GIT_REFSPEC; - qInfo() << "Compiled for :" << BuildConfig.systemID(); - qInfo() << "Compiled by :" << BuildConfig.compilerID(); - qInfo() << "Build Artifact :" << BuildConfig.BUILD_ARTIFACT; - qInfo() << "Updates Enabled :" << (updaterEnabled() ? "Yes" : "No"); + qInfo() << "Version : " << BuildConfig.printableVersionString(); + qInfo() << "Platform : " << BuildConfig.BUILD_PLATFORM; + qInfo() << "Git commit : " << BuildConfig.GIT_COMMIT; + qInfo() << "Git refspec : " << BuildConfig.GIT_REFSPEC; + qInfo() << "Compiled for : " << BuildConfig.systemID(); + qInfo() << "Compiled by : " << BuildConfig.compilerID(); + qInfo() << "Build Artifact : " << BuildConfig.BUILD_ARTIFACT; + qInfo() << "Updates Enabled : " << (updaterEnabled() ? "Yes" : "No"); if (adjustedBy.size()) { - qInfo() << "Work dir before adjustment :" << origcwdPath; - qInfo() << "Work dir after adjustment :" << QDir::currentPath(); - qInfo() << "Adjusted by :" << adjustedBy; + qInfo() << "Work dir before adjustment : " << origcwdPath; + qInfo() << "Work dir after adjustment : " << QDir::currentPath(); + qInfo() << "Adjusted by : " << adjustedBy; } else { - qInfo() << "Work dir :" << QDir::currentPath(); + qInfo() << "Work dir : " << QDir::currentPath(); } - qInfo() << "Binary path :" << binPath; - qInfo() << "Application root path :" << m_rootPath; + qInfo() << "Binary path : " << binPath; + qInfo() << "Application root path : " << m_rootPath; if (!m_instanceIdToLaunch.isEmpty()) { - qInfo() << "ID of instance to launch :" << m_instanceIdToLaunch; + qInfo() << "ID of instance to launch : " << m_instanceIdToLaunch; } if (!m_serverToJoin.isEmpty()) { qInfo() << "Address of server to join :" << m_serverToJoin; @@ -625,11 +637,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!"; } } @@ -672,8 +684,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) QFontInfo consoleFontInfo(consoleFont); QString resolvedDefaultMonospace = consoleFontInfo.family(); QFont resolvedFont(resolvedDefaultMonospace); - qDebug().nospace() << "Detected default console font: " << resolvedDefaultMonospace - << ", substitutions: " << resolvedFont.substitutions().join(','); + qDebug() << "Detected default console font:" << resolvedDefaultMonospace + << ", substitutions:" << resolvedFont.substitutions().join(','); m_settings->registerSetting("ConsoleFont", resolvedDefaultMonospace); m_settings->registerSetting("ConsoleFontSize", defaultSize); @@ -731,9 +743,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 +787,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 +827,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 +861,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 +899,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 +910,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 +943,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"); @@ -970,11 +987,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) // instance path: check for problems with '!' in instance path and warn the user in the log // and remember that we have to show him a dialog when the gui starts (if it does so) QString instDir = m_settings->get("InstanceDir").toString(); - qInfo() << "Instance path :" << instDir; + qInfo() << "Instance path : " << instDir; 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 +1025,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 @@ -1188,10 +1199,6 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) } } - if (qgetenv("XDG_CURRENT_DESKTOP") == "gamescope") { - installEventFilter(new ToolTipFilter); - } - if (createSetupWizard()) { return; } @@ -1352,11 +1359,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 +1413,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 +1464,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 +1492,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 +1527,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 +1548,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 +1559,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 +1575,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 +1584,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 +1633,7 @@ void Application::updateIsRunning(bool running) m_updateRunning = running; } -void Application::controllerFinished() +void Application::controllerSucceeded() { auto controller = qobject_cast(sender()); if (!controller) @@ -1623,11 +1641,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 +1654,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 +1733,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 +1845,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 +1875,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 +1883,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 +1991,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 +2045,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/AssertHelpers.h b/launcher/AssertHelpers.h deleted file mode 100644 index 0b1cdb742..000000000 --- a/launcher/AssertHelpers.h +++ /dev/null @@ -1,25 +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 - -#if defined(ASSERT_NEVER) -#error ASSERT_NEVER already defined -#else -#define ASSERT_NEVER(cond) (Q_ASSERT((cond) == false), (cond)) -#endif 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..fce3bb177 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 @@ -99,17 +102,14 @@ set(CORE_SOURCES MMCTime.cpp MTPixmapCache.h - - # Assertion helper - AssertHelpers.h ) 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 +119,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 +243,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,10 +261,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 minecraft/launch/ModMinecraftJar.h minecraft/launch/ExtractNatives.cpp @@ -349,7 +346,6 @@ set(MINECRAFT_SOURCES minecraft/mod/TexturePackFolderModel.h minecraft/mod/TexturePackFolderModel.cpp minecraft/mod/ShaderPackFolderModel.h - minecraft/mod/ShaderPackFolderModel.cpp minecraft/mod/tasks/ResourceFolderLoadTask.h minecraft/mod/tasks/ResourceFolderLoadTask.cpp minecraft/mod/tasks/LocalModParseTask.h @@ -529,11 +525,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 +790,6 @@ SET(LAUNCHER_SOURCES ApplicationMessage.cpp SysInfo.h SysInfo.cpp - HardwareInfo.cpp - HardwareInfo.h # console utils console/Console.h @@ -817,6 +806,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 @@ -836,8 +842,6 @@ SET(LAUNCHER_SOURCES ui/InstanceWindow.cpp ui/ViewLogWindow.h ui/ViewLogWindow.cpp - ui/ToolTipFilter.h - ui/ToolTipFilter.cpp # FIXME: maybe find a better home for this. FileIgnoreProxy.cpp @@ -886,7 +890,6 @@ SET(LAUNCHER_SOURCES ui/themes/CatPainter.h # Processes - LaunchMode.h LaunchController.h LaunchController.cpp @@ -997,13 +1000,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 +1063,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 +1200,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,40 +1282,45 @@ 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}) - message(STATUS "Using precompiled headers for applicable launcher targets") - set(PRECOMPILED_HEADERS - include/base.pch.hpp - include/qtcore.pch.hpp - include/qtgui.pch.hpp - ) -endif() +set(PRECOMPILED_HEADERS + include/base.pch.hpp + include/qtcore.pch.hpp + include/qtgui.pch.hpp +) ####### 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) - -if(${Launcher_USE_PCH}) - target_precompile_headers(Launcher_logic PRIVATE ${PRECOMPILED_HEADERS}) -endif() - +target_precompile_headers(Launcher_logic PRIVATE ${PRECOMPILED_HEADERS}) target_link_libraries(Launcher_logic + systeminfo Launcher_murmur2 nbt++ ${ZLIB_LIBRARIES} @@ -1328,12 +1397,10 @@ if(APPLE) endif() endif() +target_link_libraries(Launcher_logic) + add_executable(${Launcher_Name} MACOSX_BUNDLE WIN32 main.cpp ${LAUNCHER_RCS}) - -if(${Launcher_USE_PCH}) - target_precompile_headers(${Launcher_Name} REUSE_FROM Launcher_logic) -endif() - +target_precompile_headers(${Launcher_Name} REUSE_FROM Launcher_logic) target_link_libraries(${Launcher_Name} Launcher_logic) if(DEFINED Launcher_APP_BINARY_NAME) @@ -1363,15 +1430,12 @@ 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}) - target_precompile_headers(prism_updater_logic PRIVATE ${PRECOMPILED_HEADERS}) - endif() - + target_precompile_headers(prism_updater_logic PRIVATE ${PRECOMPILED_HEADERS}) target_link_libraries(prism_updater_logic ${ZLIB_LIBRARIES} + systeminfo BuildConfig Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Core @@ -1388,10 +1452,7 @@ if(Launcher_BUILD_UPDATER) add_executable("${Launcher_Name}_updater" WIN32 updater/prismupdater/updater_main.cpp) target_sources("${Launcher_Name}_updater" PRIVATE updater/prismupdater/updater.exe.manifest) target_link_libraries("${Launcher_Name}_updater" prism_updater_logic) - - if(${Launcher_USE_PCH}) - target_precompile_headers("${Launcher_Name}_updater" REUSE_FROM prism_updater_logic) - endif() + target_precompile_headers("${Launcher_Name}_updater" REUSE_FROM prism_updater_logic) if(DEFINED Launcher_APP_BINARY_NAME) set_target_properties("${Launcher_Name}_updater" PROPERTIES OUTPUT_NAME "${Launcher_APP_BINARY_NAME}_updater") @@ -1416,14 +1477,16 @@ 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}) - - if(${Launcher_USE_PCH}) - target_precompile_headers(filelink_logic PRIVATE ${PRECOMPILED_HEADERS}) - endif() + target_precompile_headers(filelink_logic PRIVATE ${PRECOMPILED_HEADERS}) target_link_libraries(filelink_logic + systeminfo BuildConfig Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Core @@ -1433,11 +1496,9 @@ if(WIN32 OR (DEFINED Launcher_BUILD_FILELINKER AND Launcher_BUILD_FILELINKER)) ) add_executable("${Launcher_Name}_filelink" WIN32 filelink/filelink_main.cpp) - target_sources("${Launcher_Name}_filelink" PRIVATE filelink/filelink.exe.manifest) - if(${Launcher_USE_PCH}) - target_precompile_headers("${Launcher_Name}_filelink" REUSE_FROM filelink_logic) - endif() + target_sources("${Launcher_Name}_filelink" PRIVATE filelink/filelink.exe.manifest) + target_precompile_headers("${Launcher_Name}_filelink" REUSE_FROM filelink_logic) # HACK: Fix manifest issues with Ninja in release mode (and only release mode) and MSVC # I have no idea why this works or why it's needed. UPDATE THIS IF YOU EDIT THE MANIFEST!!! -@getchoo @@ -1475,28 +1536,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. @@ -1546,49 +1585,6 @@ if(WIN32 OR (UNIX AND APPLE)) SCRIPT ${QT_DEPLOY_SCRIPT} COMPONENT bundle ) - # FIXME: remove this crap once we stop using msys2 - if(MINGW) - # i've not found a solution better than injecting the config vars like this... - # with install(CODE" for everything everything just breaks - install(CODE " - set(QT_PLUGINS_DIR \"${QT_PLUGINS_DIR}\") - set(QT_LIBS_DIR \"${QT_LIBS_DIR}\") - set(QT_LIBEXECS_DIR \"${QT_LIBEXECS_DIR}\") - set(CMAKE_SYSTEM_LIBRARY_PATH \"${CMAKE_SYSTEM_LIBRARY_PATH}\") - set(CMAKE_INSTALL_PREFIX \"${CMAKE_INSTALL_PREFIX}\") - " - COMPONENT bundle) - - install(CODE [[ - file(GLOB QT_IMAGEFORMAT_DLLS "${QT_PLUGINS_DIR}/imageformats/*.dll") - set(CMAKE_GET_RUNTIME_DEPENDENCIES_TOOL objdump) - file(GET_RUNTIME_DEPENDENCIES - RESOLVED_DEPENDENCIES_VAR imageformatdeps - LIBRARIES ${QT_IMAGEFORMAT_DLLS} - DIRECTORIES - ${CMAKE_SYSTEM_LIBRARY_PATH} - ${QT_PLUGINS_DIR} - ${QT_LIBS_DIR} - ${QT_LIBEXECS_DIR} - PRE_EXCLUDE_REGEXES - "^(api-ms-win|ext-ms)-.*\\.dll$" - # FIXME: Why aren't these caught by the below regex??? - "^azure.*\\.dll$" - "^vcruntime.*\\.dll$" - POST_EXCLUDE_REGEXES - "system32" - ) - foreach(_lib ${imageformatdeps}) - file(INSTALL - DESTINATION ${CMAKE_INSTALL_PREFIX} - TYPE SHARED_LIBRARY - FOLLOW_SYMLINK_CHAIN - FILES ${_lib} - ) - endforeach() - ]] - COMPONENT bundle) - endif() # Add qt.conf - this makes Qt stop looking for things outside the bundle install( @@ -1602,15 +1598,3 @@ if(WIN32 OR (UNIX AND APPLE)) COMPONENT bundle ) endif() - -find_program(CLANG_FORMAT clang-format OPTIONAL) -if(CLANG_FORMAT) - message(STATUS "Creating clang-format target") - add_custom_target( - clang-format - COMMAND ${CLANG_FORMAT} -i --style=file:${CMAKE_SOURCE_DIR}/.clang-format ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${PRISMUPDATER_SOURCES} ${LINKEXE_SOURCES} ${PRECOMPILED_HEADERS} - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - ) -else() - message(WARNING "Unable to find `clang-format`. Not creating custom target") -endif() 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/FastFileIconProvider.cpp b/launcher/FastFileIconProvider.cpp index 1dbab27ba..f2b6f4425 100644 --- a/launcher/FastFileIconProvider.cpp +++ b/launcher/FastFileIconProvider.cpp @@ -44,4 +44,4 @@ QIcon FastFileIconProvider::icon(const QFileInfo& info) const } return QApplication::style()->standardIcon(icon); -} +} \ No newline at end of file diff --git a/launcher/FastFileIconProvider.h b/launcher/FastFileIconProvider.h index 7799b7879..208534044 100644 --- a/launcher/FastFileIconProvider.h +++ b/launcher/FastFileIconProvider.h @@ -23,4 +23,4 @@ class FastFileIconProvider : public QFileIconProvider { public: QIcon icon(const QFileInfo& info) const override; -}; +}; \ No newline at end of file diff --git a/launcher/FileIgnoreProxy.cpp b/launcher/FileIgnoreProxy.cpp index 445c2a881..cebe82eda 100644 --- a/launcher/FileIgnoreProxy.cpp +++ b/launcher/FileIgnoreProxy.cpp @@ -266,21 +266,7 @@ bool FileIgnoreProxy::filterAcceptsRow(int sourceRow, const QModelIndex& sourceP bool FileIgnoreProxy::ignoreFile(QFileInfo fileInfo) const { - if (m_ignoreFiles.contains(fileInfo.fileName())) { - return true; - } - - for (const auto& suffix : m_ignoreFilesSuffixes) { - if (fileInfo.fileName().endsWith(suffix)) { - return true; - } - } - - if (m_ignoreFilePaths.covers(relPath(fileInfo.absoluteFilePath()))) { - return true; - } - - return false; + return m_ignoreFiles.contains(fileInfo.fileName()) || m_ignoreFilePaths.covers(relPath(fileInfo.absoluteFilePath())); } bool FileIgnoreProxy::filterFile(const QFileInfo& file) const diff --git a/launcher/FileIgnoreProxy.h b/launcher/FileIgnoreProxy.h index 0f149ecb6..5184fc354 100644 --- a/launcher/FileIgnoreProxy.h +++ b/launcher/FileIgnoreProxy.h @@ -66,7 +66,6 @@ class FileIgnoreProxy : public QSortFilterProxyModel { // list of file names that need to be removed completely from model inline QStringList& ignoreFilesWithName() { return m_ignoreFiles; } - inline QStringList& ignoreFilesWithSuffix() { return m_ignoreFilesSuffixes; } // list of relative paths that need to be removed completely from model inline SeparatorPrefixTree<'/'>& ignoreFilesWithPath() { return m_ignoreFilePaths; } @@ -86,6 +85,5 @@ class FileIgnoreProxy : public QSortFilterProxyModel { const QString m_root; SeparatorPrefixTree<'/'> m_blocked; QStringList m_ignoreFiles; - QStringList m_ignoreFilesSuffixes; SeparatorPrefixTree<'/'> m_ignoreFilePaths; }; diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index ef56e3e65..30d0a9c4c 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; } @@ -436,7 +432,7 @@ void create_link::make_link_list(const QString& offset) link_file(src, ""); } else { if (m_debug) - qDebug().nospace() << "linking recursively: " << src << " to " << dst << ", max_depth: " << m_max_depth; + qDebug() << "linking recursively:" << src << "to" << dst << ", max_depth:" << m_max_depth; QDir src_dir(src); QDirIterator source_it(src, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::Subdirectories); @@ -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); @@ -1101,17 +1100,17 @@ QString createShortcut(QString destination, QString target, QStringList args, QS hres = ppf->Save(wsz, TRUE); if (FAILED(hres)) { qWarning() << "IPresistFile->Save() failed"; - qWarning() << "hres =" << hres; + qWarning() << "hres = " << hres; } ppf->Release(); } else { qWarning() << "Failed to query IPersistFile interface from IShellLink instance"; - qWarning() << "hres =" << hres; + qWarning() << "hres = " << hres; } psl->Release(); } else { qWarning() << "Failed to create IShellLink instance"; - qWarning() << "hres =" << hres; + qWarning() << "hres = " << hres; } // go away COM, nobody likes you @@ -1400,14 +1399,14 @@ bool win_ioctl_clone(const std::wstring& src_path, const std::wstring& dst_path, ULONG fs_flags; if (!GetVolumeInformationByHandleW(hSourceFile, nullptr, 0, nullptr, nullptr, &fs_flags, nullptr, 0)) { ec = std::error_code(GetLastError(), std::system_category()); - qDebug() << "Failed to get Filesystem information for" << src_path.c_str(); + qDebug() << "Failed to get Filesystem information for " << src_path.c_str(); CloseHandle(hSourceFile); return false; } if (!(fs_flags & FILE_SUPPORTS_BLOCK_REFCOUNTING)) { SetLastError(ERROR_NOT_CAPABLE); ec = std::error_code(GetLastError(), std::system_category()); - qWarning() << "Filesystem at" << src_path.c_str() << "does not support reflink"; + qWarning() << "Filesystem at " << src_path.c_str() << " does not support reflink"; CloseHandle(hSourceFile); return false; } 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..29c71c012 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); @@ -216,4 +215,4 @@ QString GZip::readGzFileByBlocks(QFile* source, std::function - * - * 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/InstanceCopyPrefs.cpp b/launcher/InstanceCopyPrefs.cpp index 087b37340..63c200cc4 100644 --- a/launcher/InstanceCopyPrefs.cpp +++ b/launcher/InstanceCopyPrefs.cpp @@ -189,4 +189,4 @@ void InstanceCopyPrefs::enableDontLinkSaves(bool b) void InstanceCopyPrefs::enableUseClone(bool b) { useClone = b; -} +} \ No newline at end of file diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index e32cdf095..eba1a1339 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(); @@ -64,6 +64,7 @@ void InstanceCopyTask::executeTask() savesCopy = std::make_unique(FS::PathCombine(m_origInstance->gameRoot(), "saves"), FS::PathCombine(staging_mc_dir, "saves")); + savesCopy->followSymlinks(true); (*savesCopy)(true); setProgress(0, savesCopy->totalCopied()); connect(savesCopy.get(), &FS::copy::fileCopied, [this](QString src) { setProgress(m_progress + 1, m_progressTotal); }); @@ -125,11 +126,11 @@ void InstanceCopyTask::executeTask() return !there_were_errors; } FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath); - folderCopy.matcher(m_matcher); + folderCopy.followSymlinks(false).matcher(m_matcher); folderCopy(true); setProgress(0, folderCopy.totalCopied()); - connect(&folderCopy, &FS::copy::fileCopied, [this]() { setProgress(m_progress + 1, m_progressTotal); }); + connect(&folderCopy, &FS::copy::fileCopied, [this](QString src) { setProgress(m_progress + 1, m_progressTotal); }); return folderCopy(); }); connect(&m_copyFutureWatcher, &QFutureWatcher::finished, this, &InstanceCopyTask::copyFinished); @@ -146,9 +147,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) { @@ -196,4 +197,4 @@ bool InstanceCopyTask::abort() return true; } return false; -} +} \ No newline at end of file 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..bd3514798 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,15 +19,13 @@ 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()) { - qWarning() << "Reason:" << m_error_message; + qWarning() << "Reason: " << m_error_message; emitFailed(tr("Error while creating new instance:\n%1").arg(m_error_message)); } else { emitFailed(tr("Error while creating new instance.")); @@ -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..6bc17e058 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -150,15 +150,22 @@ void InstanceImportTask::processZipPack() extractDir.cd("minecraft"); m_modpackType = ModpackType::Technic; stop = true; - } else if (fileName == "manifest.json") { - qDebug() << "Flame:" << true; - m_modpackType = ModpackType::Flame; - stop = true; - } else if (QFileInfo fileInfo(fileName); fileInfo.fileName() == "instance.cfg") { - qDebug() << "MultiMC:" << true; - m_modpackType = ModpackType::MultiMC; - root = cleanPath(fileInfo.path()); - stop = true; + } else { + QFileInfo fileInfo(fileName); + if (fileInfo.fileName() == "instance.cfg") { + qDebug() << "MultiMC:" << true; + m_modpackType = ModpackType::MultiMC; + root = cleanPath(fileInfo.path()); + stop = true; + return true; + } + if (fileInfo.fileName() == "manifest.json") { + qDebug() << "Flame:" << true; + m_modpackType = ModpackType::Flame; + root = cleanPath(fileInfo.path()); + stop = true; + return true; + } } QCoreApplication::processEvents(); return true; @@ -322,7 +329,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 +348,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 +428,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..cbea045fc 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,189 @@ 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. 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) { + // 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 +377,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 +398,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 +485,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..057171a27 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}" @@ -39,4 +33,4 @@ fi ARGS+=("$@") # Run the launcher -exec -a "${ARGS[@]}" +exec -a "${ARGS[@]}" \ No newline at end of file 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/MMCTime.cpp b/launcher/MMCTime.cpp index 252e6ac57..1765fd844 100644 --- a/launcher/MMCTime.cpp +++ b/launcher/MMCTime.cpp @@ -16,6 +16,7 @@ */ #include +#include #include #include @@ -98,4 +99,4 @@ QString Time::humanReadableDuration(double duration, int precision) os.flush(); return outStr; -} +} \ No newline at end of file diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 64be89643..8e4e433ed 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -56,18 +56,18 @@ bool mergeZipFiles(ArchiveWriter& into, QFileInfo from, QSet& contained return r.parse([&into, &contained, &filter, from](ArchiveReader::File* f) { auto filename = f->filename(); if (filter && !filter(filename)) { - qDebug() << "Skipping file" << filename << "from" << from.fileName() << "- filtered"; + qDebug() << "Skipping file " << filename << " from " << from.fileName() << " - filtered"; f->skip(); return true; } if (contained.contains(filename)) { - qDebug() << "Skipping already contained file" << filename << "from" << from.fileName(); + qDebug() << "Skipping already contained file " << filename << " from " << from.fileName(); f->skip(); return true; } contained.insert(filename); if (!into.addFile(f)) { - qCritical() << "Failed to copy data of" << filename << "into the jar"; + qCritical() << "Failed to copy data of " << filename << " into the jar"; return false; } return true; @@ -149,7 +149,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QListfileinfo().fileName() << "to the jar."; return false; } - qDebug() << "Adding folder" << filename.fileName() << "from" << filename.absoluteFilePath(); + qDebug() << "Adding folder " << filename.fileName() << " from " << filename.absoluteFilePath(); } else { // Make sure we do not continue launching when something is missing or undefined... zipOut.close(); @@ -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; } @@ -321,7 +321,7 @@ bool collectFileListRecursively(const QString& rootDir, const QString& subDir, Q for (const auto& e : entries) { if (excludeFilter && excludeFilter(e)) { QString relativeFilePath = rootDirectory.relativeFilePath(e.absoluteFilePath()); - qDebug() << "Skipping file" << relativeFilePath; + qDebug() << "Skipping file " << relativeFilePath; continue; } 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/Markdown.cpp b/launcher/Markdown.cpp index 6f6d34828..426067bf6 100644 --- a/launcher/Markdown.cpp +++ b/launcher/Markdown.cpp @@ -28,4 +28,4 @@ QString markdownToHTML(const QString& markdown) free(buffer); return htmlStr; -} +} \ No newline at end of file diff --git a/launcher/Markdown.h b/launcher/Markdown.h index 57a2e5437..f91a016bd 100644 --- a/launcher/Markdown.h +++ b/launcher/Markdown.h @@ -21,4 +21,4 @@ #include #include -QString markdownToHTML(const QString& markdown); +QString markdownToHTML(const QString& markdown); \ No newline at end of file diff --git a/launcher/MessageLevel.h b/launcher/MessageLevel.h index cff098664..006184780 100644 --- a/launcher/MessageLevel.h +++ b/launcher/MessageLevel.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include 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..ba6154ad8 100644 --- a/launcher/PSaveFile.h +++ b/launcher/PSaveFile.h @@ -67,5 +67,5 @@ class PSaveFile : public QSaveFile { QString m_absoluteFilePath; }; #else -using PSaveFile = QSaveFile; -#endif +#define PSaveFile QSaveFile +#endif \ No newline at end of file diff --git a/launcher/QVariantUtils.h b/launcher/QVariantUtils.h index 23fe82573..91f2ad29c 100644 --- a/launcher/QVariantUtils.h +++ b/launcher/QVariantUtils.h @@ -66,4 +66,4 @@ inline QVariant fromList(QList val) return variantList; } -} // namespace QVariantUtils +} // namespace QVariantUtils \ No newline at end of file diff --git a/launcher/ResourceDownloadTask.cpp b/launcher/ResourceDownloadTask.cpp index bb44d2495..875a7f02d 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,31 +82,29 @@ 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())); - } } } } void ResourceDownloadTask::downloadFailed(QString reason) { + emitFailed(reason); m_filesNetJob.reset(); - emitFailed(std::move(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/StringUtils.cpp b/launcher/StringUtils.cpp index a79cfb5c5..b9e875482 100644 --- a/launcher/StringUtils.cpp +++ b/launcher/StringUtils.cpp @@ -35,6 +35,7 @@ */ #include "StringUtils.h" +#include #include #include @@ -231,4 +232,4 @@ QString StringUtils::htmlListPatch(QString htmlStr) pos = htmlStr.indexOf(s_ulMatcher, pos); } return htmlStr; -} +} \ No newline at end of file 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..d29963c6d 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,41 +34,30 @@ 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)); } if (status != ARCHIVE_EOF && status != ARCHIVE_OK) { - qWarning() << "libarchive read error:" << archive_error_string(m_archive.get()); + qWarning() << "libarchive read error: " << archive_error_string(m_archive.get()); } if (outStatus) { *outStatus = status; @@ -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); + 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(); @@ -237,8 +180,7 @@ bool ArchiveReader::parse(const std::function& doStuff) archive_read_close(a); 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 +204,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..379006278 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 +} // namespace MMCZip \ No newline at end of file diff --git a/launcher/archive/ArchiveWriter.cpp b/launcher/archive/ArchiveWriter.cpp index 43dbe4dbd..a546f2bb7 100644 --- a/launcher/archive/ArchiveWriter.cpp +++ b/launcher/archive/ArchiveWriter.cpp @@ -167,14 +167,14 @@ bool ArchiveWriter::addFile(const QString& fileName, const QString& fileDest) } if (archive_write_header(m_archive, entry) != ARCHIVE_OK) { - qCritical() << "Failed to write header for:" << fileDest << "-" << archive_error_string(m_archive); + qCritical() << "Failed to write header for: " << fileDest << "-" << archive_error_string(m_archive); return false; } 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; } @@ -185,12 +185,12 @@ bool ArchiveWriter::addFile(const QString& fileName, const QString& fileDest) while (!file.atEnd()) { auto bytesRead = file.read(buffer.data(), chunkSize); if (bytesRead < 0) { - qCritical() << "Read error in file:" << fileInfo.filePath(); + qCritical() << "Read error in file: " << fileInfo.filePath(); return false; } if (archive_write_data(m_archive, buffer.constData(), bytesRead) < 0) { - qCritical() << "Write error in archive for:" << fileDest; + qCritical() << "Write error in archive for: " << fileDest; return false; } } @@ -216,12 +216,12 @@ bool ArchiveWriter::addFile(const QString& fileDest, const QByteArray& data) archive_entry_set_size(entry, data.size()); if (archive_write_header(m_archive, entry) != ARCHIVE_OK) { - qCritical() << "Failed to write header for:" << fileDest << "-" << archive_error_string(m_archive); + qCritical() << "Failed to write header for: " << fileDest << "-" << archive_error_string(m_archive); return false; } if (archive_write_data(m_archive, data.constData(), data.size()) < 0) { - qCritical() << "Write error in archive for:" << fileDest << "-" << archive_error_string(m_archive); + qCritical() << "Write error in archive for: " << fileDest << "-" << archive_error_string(m_archive); return false; } return true; diff --git a/launcher/archive/ArchiveWriter.h b/launcher/archive/ArchiveWriter.h index 50858b517..807bb297c 100644 --- a/launcher/archive/ArchiveWriter.h +++ b/launcher/archive/ArchiveWriter.h @@ -43,4 +43,4 @@ class ArchiveWriter { QString m_filename; QString m_format = "zip"; }; -} // namespace MMCZip +} // namespace MMCZip \ No newline at end of file diff --git a/launcher/archive/ExportToZipTask.cpp b/launcher/archive/ExportToZipTask.cpp index bd3bc9032..9df9539be 100644 --- a/launcher/archive/ExportToZipTask.cpp +++ b/launcher/archive/ExportToZipTask.cpp @@ -97,4 +97,4 @@ bool ExportToZipTask::abort() } return false; } -} // namespace MMCZip +} // namespace MMCZip \ No newline at end of file diff --git a/launcher/archive/ExportToZipTask.h b/launcher/archive/ExportToZipTask.h index 0c8329c93..21ad1db48 100644 --- a/launcher/archive/ExportToZipTask.h +++ b/launcher/archive/ExportToZipTask.h @@ -69,4 +69,4 @@ class ExportToZipTask : public Task { QFuture m_buildZipFuture; QFutureWatcher m_buildZipWatcher; }; -} // namespace MMCZip +} // namespace MMCZip \ No newline at end of file diff --git a/launcher/archive/ExtractZipTask.cpp b/launcher/archive/ExtractZipTask.cpp index 35dc39d90..b958640c9 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; } @@ -132,4 +132,4 @@ bool ExtractZipTask::abort() return false; } -} // namespace MMCZip +} // namespace MMCZip \ No newline at end of file diff --git a/launcher/archive/ExtractZipTask.h b/launcher/archive/ExtractZipTask.h index 03c391aee..284d873fe 100644 --- a/launcher/archive/ExtractZipTask.h +++ b/launcher/archive/ExtractZipTask.h @@ -50,4 +50,4 @@ class ExtractZipTask : public Task { QFuture m_zipFuture; QFutureWatcher m_zipWatcher; }; -} // namespace MMCZip +} // namespace MMCZip \ No newline at end of file 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..1494fa8cc 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"); @@ -100,7 +115,7 @@ void FileLinkApp::joinServer(QString server) qDebug() << ("The connection was closed by the peer. "); break; default: - qDebug() << "The following error occurred:" << socket.errorString(); + qDebug() << "The following error occurred: " << socket.errorString(); } }); @@ -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/icons/IconList.cpp b/launcher/icons/IconList.cpp index ad48665d4..fb80f89da 100644 --- a/launcher/icons/IconList.cpp +++ b/launcher/icons/IconList.cpp @@ -40,7 +40,6 @@ #include #include #include -#include #include #include #include "icons/IconUtils.h" @@ -147,7 +146,8 @@ void IconList::directoryChanged(const QString& path) { QDir newDir(path); if (m_dir.absolutePath() != newDir.absolutePath()) { - m_dir.setPath(path); + if (!path.startsWith(m_dir.absolutePath())) + m_dir.setPath(path); m_dir.refresh(); if (m_isWatching) stopWatching(); @@ -169,7 +169,7 @@ void IconList::directoryChanged(const QString& path) QSet toAdd = newSet - currentSet; for (const QString& removedPath : toRemove) { - qDebug() << "Removing icon" << removedPath; + qDebug() << "Removing icon " << removedPath; QFileInfo removedFile(removedPath); QString relativePath = m_dir.relativeFilePath(removedFile.absoluteFilePath()); QString key = QFileInfo(relativePath).completeBaseName(); @@ -191,7 +191,7 @@ void IconList::directoryChanged(const QString& path) } for (const QString& addedPath : toAdd) { - qDebug() << "Adding icon" << addedPath; + qDebug() << "Adding icon " << addedPath; QFileInfo addfile(addedPath); QString relativePath = m_dir.relativeFilePath(addfile.absoluteFilePath()); @@ -209,7 +209,7 @@ void IconList::directoryChanged(const QString& path) void IconList::fileChanged(const QString& path) { - qDebug() << "Checking icon" << path; + qDebug() << "Checking icon " << path; QFileInfo checkfile(path); if (!checkfile.exists()) return; @@ -217,13 +217,7 @@ void IconList::fileChanged(const QString& path) int idx = getIconIndex(key); if (idx == -1) return; - QIcon icon; - // special handling for jpg and jpeg to go through pixmap to keep the size constant - if (path.endsWith(".jpg") || path.endsWith(".jpeg")) { - icon.addPixmap(QPixmap(path)); - } else { - icon.addFile(path); - } + QIcon icon(path); if (icon.availableSizes().empty()) return; @@ -246,9 +240,9 @@ void IconList::startWatching() FS::ensureFolderPathExists(abs_path); m_isWatching = addPathRecursively(abs_path); if (m_isWatching) { - qDebug() << "Started watching" << abs_path; + qDebug() << "Started watching " << abs_path; } else { - qDebug() << "Failed to start watching" << abs_path; + qDebug() << "Failed to start watching " << abs_path; } } @@ -401,14 +395,7 @@ bool IconList::addThemeIcon(const QString& key) bool IconList::addIcon(const QString& key, const QString& name, const QString& path, const IconType type) { // replace the icon even? is the input valid? - QIcon icon; - // special handling for jpg and jpeg to go through pixmap to keep the size constant - if (path.endsWith(".jpg") || path.endsWith(".jpeg")) { - icon.addPixmap(QPixmap(path)); - } else { - icon.addFile(path); - } - + QIcon icon(path); if (icon.isNull()) return false; auto iter = m_nameIndex.find(key); @@ -487,4 +474,4 @@ QString IconList::iconDirectory(const QString& key) const } } return getDirectory(); -} +} \ No newline at end of file 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/JavaMetadata.h b/launcher/java/JavaMetadata.h index 0757a6935..2e569ee39 100644 --- a/launcher/java/JavaMetadata.h +++ b/launcher/java/JavaMetadata.h @@ -61,4 +61,4 @@ DownloadType parseDownloadType(QString javaDownload); QString downloadTypeToString(DownloadType javaDownload); MetadataPtr parseJavaMeta(const QJsonObject& libObj); -} // namespace Java +} // namespace Java \ No newline at end of file diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index c58fe5601..4f7a0ae3f 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(";")); @@ -484,8 +483,7 @@ QList JavaUtils::FindJavaPaths() QString asdfDataDir = qEnvironmentVariable("ASDF_DATA_DIR", FS::PathCombine(home, ".asdf")); scanJavaDirs(FS::PathCombine(asdfDataDir, "installs/java")); // javas downloaded by gradle (toolchains) - QString gradleUserHome = qEnvironmentVariable("GRADLE_USER_HOME", FS::PathCombine(home, ".gradle")); - scanJavaDirs(FS::PathCombine(gradleUserHome, "jdks")); + scanJavaDirs(FS::PathCombine(home, ".gradle/jdks")); javas.append(getMinecraftJavaBundle()); javas.append(getPrismJavaBundle()); diff --git a/launcher/java/download/ArchiveDownloadTask.cpp b/launcher/java/download/ArchiveDownloadTask.cpp index c60908cec..92357930b 100644 --- a/launcher/java/download/ArchiveDownloadTask.cpp +++ b/launcher/java/download/ArchiveDownloadTask.cpp @@ -114,4 +114,4 @@ bool ArchiveDownloadTask::abort() aborted = m_task->abort(); return aborted; }; -} // namespace Java +} // namespace Java \ No newline at end of file diff --git a/launcher/java/download/ArchiveDownloadTask.h b/launcher/java/download/ArchiveDownloadTask.h index cfcdf9dcf..4cd919543 100644 --- a/launcher/java/download/ArchiveDownloadTask.h +++ b/launcher/java/download/ArchiveDownloadTask.h @@ -42,4 +42,4 @@ class ArchiveDownloadTask : public Task { QString m_checksum_hash; Task::Ptr m_task; }; -} // namespace Java +} // namespace Java \ No newline at end of file diff --git a/launcher/java/download/ManifestDownloadTask.cpp b/launcher/java/download/ManifestDownloadTask.cpp index 0a51741a2..04d28a5cc 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") { @@ -60,7 +61,7 @@ void ManifestDownloadTask::executeTask() QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*files, &parse_error); if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response at" << parse_error.offset << "reason:" << parse_error.errorString(); + qWarning() << "Error while parsing JSON response at " << parse_error.offset << ". Reason: " << parse_error.errorString(); qWarning() << *files; emitFailed(parse_error.errorString()); return; diff --git a/launcher/java/download/ManifestDownloadTask.h b/launcher/java/download/ManifestDownloadTask.h index e68c8236f..0f65b343c 100644 --- a/launcher/java/download/ManifestDownloadTask.h +++ b/launcher/java/download/ManifestDownloadTask.h @@ -43,4 +43,4 @@ class ManifestDownloadTask : public Task { QString m_checksum_hash; Task::Ptr m_task; }; -} // namespace Java +} // namespace Java \ No newline at end of file diff --git a/launcher/java/download/SymlinkTask.cpp b/launcher/java/download/SymlinkTask.cpp index 9bbd50c63..843c7caa9 100644 --- a/launcher/java/download/SymlinkTask.cpp +++ b/launcher/java/download/SymlinkTask.cpp @@ -78,4 +78,4 @@ void SymlinkTask::executeTask() } } -} // namespace Java +} // namespace Java \ No newline at end of file diff --git a/launcher/java/download/SymlinkTask.h b/launcher/java/download/SymlinkTask.h index e38323eae..88cb20dd7 100644 --- a/launcher/java/download/SymlinkTask.h +++ b/launcher/java/download/SymlinkTask.h @@ -33,4 +33,4 @@ class SymlinkTask : public Task { QString m_path; Task::Ptr m_task; }; -} // namespace Java +} // namespace Java \ No newline at end of file diff --git a/launcher/launch/LaunchTask.cpp b/launcher/launch/LaunchTask.cpp index 26b2b582d..9985e0f4f 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) { @@ -76,7 +76,6 @@ void LaunchTask::executeTask() if (!m_steps.size()) { state = LaunchTask::Finished; emitSucceeded(); - return; } state = LaunchTask::Running; onStepFinished(); @@ -180,7 +179,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..4b67b3092 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; } @@ -87,6 +87,6 @@ void LookupServerAddress::resolve(const QString& address, quint16 port) m_output->address = address; m_output->port = port; - m_dnsLookup->deleteLater(); emitSucceeded(); + m_dnsLookup->deleteLater(); } 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/logs/AnonymizeLog.cpp b/launcher/logs/AnonymizeLog.cpp index b808b35a3..e5021a616 100644 --- a/launcher/logs/AnonymizeLog.cpp +++ b/launcher/logs/AnonymizeLog.cpp @@ -65,4 +65,4 @@ void anonymizeLog(QString& log) for (auto rule : anonymizeRules) { log.replace(rule.reg, rule.with); } -} +} \ No newline at end of file diff --git a/launcher/logs/AnonymizeLog.h b/launcher/logs/AnonymizeLog.h index 215d1e468..2409ecee7 100644 --- a/launcher/logs/AnonymizeLog.h +++ b/launcher/logs/AnonymizeLog.h @@ -37,4 +37,4 @@ #include -void anonymizeLog(QString& log); +void anonymizeLog(QString& log); \ No newline at end of file diff --git a/launcher/macsandbox/SecurityBookmarkFileAccess.h b/launcher/macsandbox/SecurityBookmarkFileAccess.h index 69b344af9..5bddf0e31 100644 --- a/launcher/macsandbox/SecurityBookmarkFileAccess.h +++ b/launcher/macsandbox/SecurityBookmarkFileAccess.h @@ -42,8 +42,7 @@ class SecurityBookmarkFileAccess { bool m_readOnly; NSURL* securityScopedBookmarkToNSURL(QByteArray& bookmark, bool& isStale); - - public: +public: /// \param readOnly A boolean indicating whether the bookmark should be read-only. SecurityBookmarkFileAccess(bool readOnly = false); ~SecurityBookmarkFileAccess(); @@ -87,4 +86,4 @@ class SecurityBookmarkFileAccess { bool isAccessingPath(const QString& path); }; -#endif // FILEACCESS_H +#endif //FILEACCESS_H 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/Component.cpp b/launcher/minecraft/Component.cpp index 6a8bb27c0..5f114e942 100644 --- a/launcher/minecraft/Component.cpp +++ b/launcher/minecraft/Component.cpp @@ -479,7 +479,6 @@ void Component::clearUpdateAction() QDebug operator<<(QDebug d, const Component& comp) { - QDebugStateSaver saver(d); - d.nospace() << "Component(" << comp.m_uid << " : " << comp.m_cachedVersion << ")"; + d << "Component(" << comp.m_uid << " : " << comp.m_cachedVersion << ")"; return d; } diff --git a/launcher/minecraft/ComponentUpdateTask.cpp b/launcher/minecraft/ComponentUpdateTask.cpp index 73203f74b..56db20205 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 { @@ -556,7 +521,7 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly) // change a version of something that exists for (auto& change : toChange) { // FIXME: this should not work directly with the component list - qCDebug(instanceProfileResolveC) << "Setting version of" << change.uid << "to" << change.equalsVersion; + qCDebug(instanceProfileResolveC) << "Setting version of " << change.uid << "to" << change.equalsVersion; auto component = componentIndex[change.uid]; component->setVersion(change.equalsVersion); } @@ -779,7 +744,7 @@ void ComponentUpdateTask::remoteLoadFailed(size_t taskIndex, const QString& msg) qCWarning(instanceProfileResolveC) << "Got multiple results from remote load task" << taskIndex; return; } - qCDebug(instanceProfileResolveC) << "Remote task" << taskIndex << "failed:" << msg; + qCDebug(instanceProfileResolveC) << "Remote task" << taskIndex << "failed: " << msg; d->remoteLoadSuccessful = false; taskSlot.succeeded = false; taskSlot.finished = true; @@ -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; @@ -809,9 +773,8 @@ void ComponentUpdateTask::checkIfAllFinished() .arg(component->getName(), component->m_version)); } } - d->remoteLoadStatusList.clear(); - auto allErrors = allErrorsList.join("\n"); emitFailed(tr("Component metadata update task failed while downloading from remote server:\n%1").arg(allErrors)); + d->remoteLoadStatusList.clear(); } } 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/Logging.cpp b/launcher/minecraft/Logging.cpp index 8b6304205..92596de3e 100644 --- a/launcher/minecraft/Logging.cpp +++ b/launcher/minecraft/Logging.cpp @@ -19,6 +19,7 @@ */ #include "minecraft/Logging.h" +#include Q_LOGGING_CATEGORY(instanceProfileC, "launcher.instance.profile") Q_LOGGING_CATEGORY(instanceProfileResolveC, "launcher.instance.profile.resolve") diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 8e98a2efe..cdf670b0f 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -40,6 +40,13 @@ #include "BuildConfig.h" #include "Json.h" #include "QObjectPtr.h" +#include "minecraft/launch/AutoInstallJava.h" +#include "minecraft/launch/CreateGameFolders.h" +#include "minecraft/launch/ExtractNatives.h" +#include "minecraft/launch/PrintInstanceInfo.h" +#include "minecraft/update/AssetUpdateTask.h" +#include "minecraft/update/FMLLibrariesTask.h" +#include "minecraft/update/LibrariesTask.h" #include "settings/Setting.h" #include "settings/SettingsObject.h" @@ -56,24 +63,13 @@ #include "launch/steps/QuitAfterGameStop.h" #include "launch/steps/TextPrint.h" -#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" #include "minecraft/launch/ModMinecraftJar.h" -#include "minecraft/launch/PrintInstanceInfo.h" #include "minecraft/launch/ReconstructAssets.h" #include "minecraft/launch/ScanModFolders.h" #include "minecraft/launch/VerifyJavaInstall.h" -#include "minecraft/update/AssetUpdateTask.h" -#include "minecraft/update/FoldersTask.h" -#include "minecraft/update/LegacyFMLLibrariesTask.h" -#include "minecraft/update/LibrariesTask.h" - #include "java/JavaUtils.h" #include "icons/IconList.h" @@ -88,6 +84,7 @@ #include "AssetsUtils.h" #include "MinecraftLoadAndCheck.h" #include "PackProfile.h" +#include "minecraft/update/FoldersTask.h" #include "tools/BaseProfiler.h" @@ -98,7 +95,7 @@ #include #ifdef Q_OS_LINUX -#include "LibraryUtils.h" +#include "MangoHud.h" #endif #ifdef WITH_QTDBUS @@ -131,8 +128,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 +160,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 +204,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 +269,7 @@ void MinecraftInstance::loadSpecificSettings() void MinecraftInstance::updateRuntimeContext() { - m_runtimeContext.updateFromInstanceSettings(m_settings.get()); + m_runtimeContext.updateFromInstanceSettings(m_settings); m_components->invalidateLaunchProfile(); } @@ -285,9 +278,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 +308,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); @@ -491,7 +484,7 @@ QStringList MinecraftInstance::getNativeJars() return nativeJars; } -static QString replaceTokensIn(const QString& text, const QMap& with) +static QString replaceTokensIn(const QString &text, const QMap &with) { // TODO: does this still work?? QString result; @@ -513,6 +506,7 @@ static QString replaceTokensIn(const QString& text, const QMap return result; } + QStringList MinecraftInstance::extraArguments() { auto list = BaseInstance::extraArguments(); @@ -527,15 +521,15 @@ QStringList MinecraftInstance::extraArguments() if (!addn.isEmpty()) { QMap tokenMapping = makeProfileVarMapping(m_components->getProfile()); - for (const QString& item : addn) { + for (const QString &item : addn) { list.append(replaceTokensIn(item, tokenMapping)); } } 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 +565,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 +618,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 +648,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 +682,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 +702,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 +738,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,24 +746,25 @@ 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; } } + QMap tokenMapping = makeProfileVarMapping(profile); // yggdrasil! @@ -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,58 @@ 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()); + auto printLibFile = [&out](const QString& path) { + QFileInfo info(path); + if (info.exists()) { + out << " " + path; + } else { + out << " " + path + " (missing)"; + } + }; + for (auto file : jars) { + printLibFile(file); + } + out << ""; + out << "Native libraries:"; + for (auto file : nativeJars) { + printLibFile(file); + } + out << ""; + } + // mods and core mods auto printModList = [&out](const QString& label, ModFolderModel& model) { if (model.size()) { @@ -928,12 +962,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 +977,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 +1000,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 +1102,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 +1149,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 +1157,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,14 +1166,14 @@ 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)); } - } else { - process->appendStep(makeShared(pptr, this)); } // if there are any jar mods @@ -1202,11 +1186,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 +1201,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 +1227,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 +1237,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..c0a82e61e 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(); -} +} \ No newline at end of file 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..74c8d5ef8 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -38,6 +38,7 @@ */ #include +#include #include #include #include @@ -167,7 +168,6 @@ static bool savePackProfile(const QString& filename, const ComponentContainer& c } if (!outFile.commit()) { qCCritical(instanceProfileC) << "Couldn't save" << outFile.fileName() << "because:" << outFile.errorString(); - return false; } return true; } @@ -230,8 +230,9 @@ static PackProfile::Result loadPackProfile(PackProfile* parent, void PackProfile::saveNow() { - if (saveIsScheduled() && save_internal()) { + if (saveIsScheduled()) { d->m_saveTimer.stop(); + save_internal(); } } @@ -279,15 +280,12 @@ QString PackProfile::patchFilePathForUid(const QString& uid) const return patchesPattern().arg(uid); } -bool PackProfile::save_internal() +void PackProfile::save_internal() { qDebug() << d->m_instance->name() << "|" << "Component list save performed now"; auto filename = componentsFilePath(); - if (savePackProfile(filename, d->components)) { - d->dirty = false; - return true; - } - return false; + savePackProfile(filename, d->components); + d->dirty = false; } PackProfile::Result PackProfile::load() @@ -326,15 +324,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 @@ -374,7 +364,7 @@ void PackProfile::updateSucceeded() void PackProfile::updateFailed(const QString& error) { - qCDebug(instanceProfileC) << d->m_instance->name() << "|" << "Component list update/resolve task failed. Reason:" << error; + qCDebug(instanceProfileC) << d->m_instance->name() << "|" << "Component list update/resolve task failed " << "Reason:" << error; d->m_updateTask.reset(); invalidateLaunchProfile(); } @@ -925,7 +915,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; @@ -962,7 +952,7 @@ std::shared_ptr PackProfile::getProfile() const } d->m_profile = profile; } catch (const Exception& error) { - qCWarning(instanceProfileC) << d->m_instance->name() << "|" << "Couldn't apply profile patches because:" << error.cause(); + qCWarning(instanceProfileC) << d->m_instance->name() << "|" << "Couldn't apply profile patches because: " << error.cause(); } } return d->m_profile; diff --git a/launcher/minecraft/PackProfile.h b/launcher/minecraft/PackProfile.h index 70dd04514..d812dfa48 100644 --- a/launcher/minecraft/PackProfile.h +++ b/launcher/minecraft/PackProfile.h @@ -175,7 +175,7 @@ class PackProfile : public QAbstractListModel { QString patchesPattern() const; private slots: - bool save_internal(); + void save_internal(); void updateSucceeded(); void updateFailed(const QString& error); void componentDataChanged(); 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..a79f89529 100644 --- a/launcher/minecraft/ProfileUtils.cpp +++ b/launcher/minecraft/ProfileUtils.cpp @@ -55,7 +55,7 @@ bool readOverrideOrders(QString path, PatchOrder& order) return false; } if (!orderFile.open(QFile::ReadOnly)) { - qCritical() << "Couldn't open" << orderFile.fileName() << "for reading:" << orderFile.errorString(); + qCritical() << "Couldn't open" << orderFile.fileName() << " for reading:" << orderFile.errorString(); qWarning() << "Ignoring overridden order"; return false; } @@ -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..bdbe721e3 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); @@ -447,7 +405,7 @@ void World::loadFromLevelDat(QByteArray data) try { valPtr = &levelData->at("Data"); } catch (const std::out_of_range& e) { - qWarning().nospace() << "Unable to read NBT tags from " << m_folderName << ": " << e.what(); + qWarning() << "Unable to read NBT tags from " << m_folderName << ":" << e.what(); m_isValid = false; return; } diff --git a/launcher/minecraft/World.h b/launcher/minecraft/World.h index bb4baa33d..cca931826 100644 --- a/launcher/minecraft/World.h +++ b/launcher/minecraft/World.h @@ -87,7 +87,7 @@ class World { QString m_iconFile; QDateTime m_levelDatTime; QDateTime m_lastPlayed; - int64_t m_size = 0; + int64_t m_size; int64_t m_randomSeed = 0; GameType m_gameType; bool m_isValid = false; diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index ca8ac1aa8..059feabde 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -36,6 +36,7 @@ #include "WorldList.h" #include +#include #include #include #include @@ -64,9 +65,9 @@ void WorldList::startWatching() update(); m_isWatching = m_watcher->addPath(m_dir.absolutePath()); if (m_isWatching) { - qDebug() << "Started watching" << m_dir.absolutePath(); + qDebug() << "Started watching " << m_dir.absolutePath(); } else { - qDebug() << "Failed to start watching" << m_dir.absolutePath(); + qDebug() << "Failed to start watching " << m_dir.absolutePath(); } } @@ -77,9 +78,9 @@ void WorldList::stopWatching() } m_isWatching = !m_watcher->removePath(m_dir.absolutePath()); if (!m_isWatching) { - qDebug() << "Stopped watching" << m_dir.absolutePath(); + qDebug() << "Stopped watching " << m_dir.absolutePath(); } else { - qDebug() << "Failed to stop watching" << m_dir.absolutePath(); + qDebug() << "Failed to stop watching " << m_dir.absolutePath(); } } @@ -157,8 +158,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; @@ -352,7 +352,7 @@ Qt::DropActions WorldList::supportedDropActions() const void WorldList::installWorld(QFileInfo filename) { - qDebug() << "installing:" << filename.absoluteFilePath(); + qDebug() << "installing: " << filename.absoluteFilePath(); World w(filename); if (!w.isValid()) { return; @@ -427,7 +427,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..cef79d200 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; } } @@ -604,7 +574,7 @@ void AccountList::fillQueue() if (m_defaultAccount && m_defaultAccount->shouldRefresh()) { auto idToRefresh = m_defaultAccount->internalId(); m_refreshQueue.push_back(idToRefresh); - qDebug() << "AccountList: Queued default account with internal ID" << idToRefresh << "to refresh first"; + qDebug() << "AccountList: Queued default account with internal ID " << idToRefresh << " to refresh first"; } for (int i = 0; i < count(); i++) { @@ -628,7 +598,7 @@ void AccountList::requestRefresh(QString accountId) m_refreshQueue.removeAt(index); } m_refreshQueue.push_front(accountId); - qDebug() << "AccountList: Pushed account with internal ID" << accountId << "to the front of the queue"; + qDebug() << "AccountList: Pushed account with internal ID " << accountId << " to the front of the queue"; if (!isActive()) { tryNext(); } @@ -640,7 +610,7 @@ void AccountList::queueRefresh(QString accountId) return; } m_refreshQueue.push_back(accountId); - qDebug() << "AccountList: Queued account with internal ID" << accountId << "to refresh"; + qDebug() << "AccountList: Queued account with internal ID " << accountId << " to refresh"; } void AccountList::tryNext() @@ -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 with internal ID " << accountId << " not found."; } // if we get here, no account needed refreshing. Schedule refresh in an hour. m_refreshTimer->start(1000 * 3600); @@ -688,7 +647,7 @@ void AccountList::authSucceeded() void AccountList::authFailed(QString reason) { - qDebug() << "RefreshSchedule: Background account refresh failed:" << reason; + qDebug() << "RefreshSchedule: Background account refresh failed: " << reason; m_currentTask.reset(); m_nextTimer->start(1000 * 20); } 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..287831b2f 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)); @@ -66,7 +69,6 @@ void AuthFlow::nextStep() } m_currentStep = m_steps.front(); qDebug() << "AuthFlow:" << m_currentStep->describe(); - setStatus(m_currentStep->describe()); m_steps.pop_front(); connect(m_currentStep.get(), &AuthStep::finished, this, &AuthFlow::stepFinished); @@ -90,9 +92,7 @@ bool AuthFlow::changeState(AccountTaskState newState, QString reason) return true; } case AccountTaskState::STATE_WORKING: { - if (!m_currentStep) { - setStatus(tr("Preparing to log in...")); - } + setStatus(m_currentStep ? m_currentStep->describe() : tr("Working...")); m_data->accountState = AccountState::Working; return true; } @@ -148,8 +148,8 @@ bool AuthFlow::changeState(AccountTaskState newState, QString reason) } bool AuthFlow::abort() { + emitAborted(); if (m_currentStep) m_currentStep->abort(); - emitAborted(); return true; -} +} \ No newline at end of file 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..3657befec 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 +}; \ No newline at end of file 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/Parsers.cpp b/launcher/minecraft/auth/Parsers.cpp index 08c780694..ba77d3e31 100644 --- a/launcher/minecraft/auth/Parsers.cpp +++ b/launcher/minecraft/auth/Parsers.cpp @@ -75,7 +75,7 @@ bool getBool(QJsonValue value, bool& out) "Message":"", "Redirect":"https://start.ui.xboxlive.com/AddChildToFamily" } -// 2148916233 = missing Xbox account +// 2148916233 = missing XBox account // 2148916238 = child account not linked to a family */ @@ -86,7 +86,7 @@ bool parseXTokenResponse(QByteArray& data, Token& output, QString name) QJsonParseError jsonError; QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); if (jsonError.error) { - qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON:" << jsonError.errorString(); + qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON: " << jsonError.errorString(); return false; } @@ -123,7 +123,7 @@ bool parseXTokenResponse(QByteArray& data, Token& output, QString name) for (auto iter = obj_.begin(); iter != obj_.end(); iter++) { QString claim; if (!getString(obj_.value(iter.key()), claim)) { - qWarning() << "display claim" << iter.key() << "is not a string..."; + qWarning() << "display claim " << iter.key() << " is not a string..."; return false; } output.extra[iter.key()] = claim; @@ -148,7 +148,7 @@ bool parseMinecraftProfile(QByteArray& data, MinecraftProfile& output) QJsonParseError jsonError; QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); if (jsonError.error) { - qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON:" << jsonError.errorString(); + qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON: " << jsonError.errorString(); return false; } @@ -290,7 +290,7 @@ bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output) QJsonParseError jsonError; QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); if (jsonError.error) { - qWarning() << "Failed to parse response as JSON:" << jsonError.errorString(); + qWarning() << "Failed to parse response as JSON: " << jsonError.errorString(); return false; } @@ -331,7 +331,7 @@ bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output) doc = QJsonDocument::fromJson(texturePayload, &jsonError); if (jsonError.error) { - qWarning() << "Failed to parse response as JSON:" << jsonError.errorString(); + qWarning() << "Failed to parse response as JSON: " << jsonError.errorString(); return false; } @@ -400,7 +400,7 @@ bool parseMinecraftEntitlements(QByteArray& data, MinecraftEntitlement& output) QJsonParseError jsonError; QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); if (jsonError.error) { - qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON:" << jsonError.errorString(); + qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON: " << jsonError.errorString(); return false; } @@ -463,7 +463,7 @@ bool parseMojangResponse(QByteArray& data, Token& output) qCDebug(authCredentials()) << data; QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); if (jsonError.error) { - qWarning() << "Failed to parse response from api.minecraftservices.com/launcher/login as JSON:" << jsonError.errorString(); + qWarning() << "Failed to parse response from api.minecraftservices.com/launcher/login as JSON: " << jsonError.errorString(); return false; } 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..954f013af 100644 --- a/launcher/minecraft/auth/steps/LauncherLoginStep.cpp +++ b/launcher/minecraft/auth/steps/LauncherLoginStep.cpp @@ -14,7 +14,7 @@ LauncherLoginStep::LauncherLoginStep(AccountData* data) : AuthStep(data) {} QString LauncherLoginStep::describe() { - return tr("Fetching Minecraft access token"); + return tr("Accessing Mojang services."); } void LauncherLoginStep::perform() @@ -36,40 +36,38 @@ 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; } - emit finished(AccountTaskState::STATE_WORKING, tr("Got Minecraft access token")); + emit finished(AccountTaskState::STATE_WORKING, tr("")); } 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..7a4722f21 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,23 +110,22 @@ 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) { + emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Failed to retrieve device authorization")); + qDebug() << *m_response; + 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")); return; } @@ -182,11 +180,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 +225,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 +237,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 @@ -274,5 +272,5 @@ void MSADeviceCodeStep::authenticationFinished(QByteArray* response) m_data->msaToken.extra = rsp.extra; m_data->msaToken.refresh_token = rsp.refresh_token; m_data->msaToken.token = rsp.access_token; - emit finished(AccountTaskState::STATE_WORKING, tr("Got MSA token")); + emit finished(AccountTaskState::STATE_WORKING, tr("Got")); } 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..aa972be71 100644 --- a/launcher/minecraft/auth/steps/MSAStep.cpp +++ b/launcher/minecraft/auth/steps/MSAStep.cpp @@ -37,7 +37,6 @@ #include #include -#include #include #include @@ -57,14 +56,13 @@ bool isSchemeHandlerRegistered() process.waitForFinished(); QString output = process.readAllStandardOutput().trimmed(); - return output.contains(APPLICATION->desktopFileName()); + return output.contains(BuildConfig.LAUNCHER_APP_BINARY_NAME); #elif defined(Q_OS_WIN) QString regPath = QString("HKEY_CURRENT_USER\\Software\\Classes\\%1").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME); QSettings settings(regPath, QSettings::NativeFormat); - const QString registeredRunCommand = settings.value("shell/open/command/.").toString().replace("\\", "/"); - return registeredRunCommand.contains(QCoreApplication::applicationFilePath()); + return settings.contains("shell/open/command/."); #endif return true; } @@ -82,33 +80,6 @@ class CustomOAuthOobReplyHandler : public QOAuthOobReplyHandler { disconnect(APPLICATION, &Application::oauthReplyRecieved, this, &QOAuthOobReplyHandler::callbackReceived); } QString callback() const override { return BuildConfig.LAUNCHER_APP_BINARY_NAME + "://oauth/microsoft"; } - - protected: - void networkReplyFinished(QNetworkReply* reply) override - { - if (reply->error() != QNetworkReply::NoError) { - qWarning() << "OAuth2 request failed:" << reply->readAll(); - } - - QOAuthOobReplyHandler::networkReplyFinished(reply); - } -}; - -class LoggingOAuthHttpServerReplyHandler final : public QOAuthHttpServerReplyHandler { - Q_OBJECT - - public: - explicit LoggingOAuthHttpServerReplyHandler(QObject* parent = nullptr) : QOAuthHttpServerReplyHandler(parent) {} - - protected: - void networkReplyFinished(QNetworkReply* reply) override - { - if (reply->error() != QNetworkReply::NoError) { - qWarning() << "OAuth2 request failed:" << reply->readAll(); - } - - QOAuthHttpServerReplyHandler::networkReplyFinished(reply); - } }; MSAStep::MSAStep(AccountData* data, bool silent) : AuthStep(data), m_silent(silent) @@ -117,7 +88,7 @@ MSAStep::MSAStep(AccountData* data, bool silent) : AuthStep(data), m_silent(sile if (QCoreApplication::applicationFilePath().startsWith("/tmp/.mount_") || APPLICATION->isPortable() || !isSchemeHandlerRegistered()) { - auto replyHandler = new LoggingOAuthHttpServerReplyHandler(this); + auto replyHandler = new QOAuthHttpServerReplyHandler(this); replyHandler->setCallbackText(QString(R"XXX(