diff --git a/.github/workflows/blocked-prs.yml b/.github/workflows/blocked-prs.yml
new file mode 100644
index 000000000..bd49b7230
--- /dev/null
+++ b/.github/workflows/blocked-prs.yml
@@ -0,0 +1,239 @@
+name: Blocked/Stacked Pull Requests Automation
+
+on:
+ pull_request_target:
+ types:
+ - opened
+ - reopened
+ - edited
+ - synchronize
+ workflow_dispatch:
+ inputs:
+ pr_id:
+ description: Local Pull Request number to work on
+ required: true
+ type: number
+
+jobs:
+ blocked_status:
+ name: Check Blocked Status
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Generate token
+ id: generate-token
+ uses: actions/create-github-app-token@v2
+ with:
+ app-id: ${{ vars.PULL_REQUEST_APP_ID }}
+ private-key: ${{ secrets.PULL_REQUEST_APP_PRIVATE_KEY }}
+
+ - name: Setup From Dispatch Event
+ if: github.event_name == 'workflow_dispatch'
+ id: dispatch_event_setup
+ env:
+ GH_TOKEN: ${{ steps.generate-token.outputs.token }}
+ PR_NUMBER: ${{ inputs.pr_id }}
+ run: |
+ # setup env for the rest of the workflow
+ OWNER=$(dirname "${{ github.repository }}")
+ REPO=$(basename "${{ github.repository }}")
+ PR_JSON=$(
+ gh api \
+ -H "Accept: application/vnd.github.raw+json" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ "/repos/$OWNER/$REPO/pulls/$PR_NUMBER"
+ )
+ echo "PR_JSON=$PR_JSON" >> "$GITHUB_ENV"
+
+ - name: Setup Environment
+ id: env_setup
+ env:
+ EVENT_PR_JSON: ${{ toJSON(github.event.pull_request) }}
+ run: |
+ # setup env for the rest of the workflow
+ PR_JSON=${PR_JSON:-"$EVENT_PR_JSON"}
+ {
+ echo "REPO=$(jq -r '.base.repo.name' <<< "$PR_JSON")"
+ echo "OWNER=$(jq -r '.base.repo.owner.login' <<< "$PR_JSON")"
+ echo "PR_NUMBER=$(jq -r '.number' <<< "$PR_JSON")"
+ echo "JOB_DATA=$(jq -c '
+ {
+ "repo": .base.repo.name,
+ "owner": .base.repo.owner.login,
+ "repoUrl": .base.repo.html_url,
+ "prNumber": .number,
+ "prHeadSha": .head.sha,
+ "prHeadLabel": .head.label,
+ "prBody": .body,
+ "prLabels": (reduce .labels[].name as $l ([]; . + [$l]))
+ }
+ ' <<< "$PR_JSON")"
+ } >> "$GITHUB_ENV"
+
+
+ - name: Find Blocked/Stacked PRs in body
+ id: pr_ids
+ run: |
+ prs=$(
+ jq -c '
+ .prBody as $body
+ | (
+ $body |
+ reduce (
+ . | scan("blocked (?:by|on):? #([0-9]+)")
+ | map({
+ "type": "Blocked on",
+ "number": ( . | tonumber )
+ })
+ ) as $i ([]; . + [$i[]])
+ ) as $bprs
+ | (
+ $body |
+ reduce (
+ . | scan("stacked on:? #([0-9]+)")
+ | map({
+ "type": "Stacked on",
+ "number": ( . | tonumber )
+ })
+ ) as $i ([]; . + [$i[]])
+ ) as $sprs
+ | ($bprs + $sprs) as $prs
+ | {
+ "blocking": $prs,
+ "numBlocking": ( $prs | length),
+ }
+ ' <<< "$JOB_DATA"
+ )
+ echo "prs=$prs" >> "$GITHUB_OUTPUT"
+
+ - name: Collect Blocked PR Data
+ id: blocking_data
+ if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0
+ env:
+ GH_TOKEN: ${{ steps.generate-token.outputs.token }}
+ BLOCKING_PRS: ${{ steps.pr_ids.outputs.prs }}
+ run: |
+ blocked_pr_data=$(
+ while read -r pr_data ; do
+ gh api \
+ -H "Accept: application/vnd.github+json" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ "/repos/$OWNER/$REPO/pulls/$(jq -r '.number' <<< "$pr_data")" \
+ | jq -c --arg type "$(jq -r '.type' <<< "$pr_data")" \
+ '
+ . | {
+ "type": $type,
+ "number": .number,
+ "merged": .merged,
+ "labels": (reduce .labels[].name as $l ([]; . + [$l])),
+ "basePrUrl": .html_url,
+ "baseRepoName": .head.repo.name,
+ "baseRepoOwner": .head.repo.owner.login,
+ "baseRepoUrl": .head.repo.html_url,
+ "baseSha": .head.sha,
+ "baseRefName": .head.ref,
+ }
+ '
+ done < <(jq -c '.blocking[]' <<< "$BLOCKING_PRS") | jq -c -s
+ )
+ {
+ echo "data=$blocked_pr_data";
+ echo "all_merged=$(jq -r 'all(.[].merged; .)' <<< "$blocked_pr_data")";
+ echo "current_blocking=$(jq -c 'map( select( .merged | not ) | .number )' <<< "$blocked_pr_data" )";
+ } >> "$GITHUB_OUTPUT"
+
+ - name: Add 'blocked' Label is Missing
+ id: label_blocked
+ 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 'blocked' "$PR_NUMBER"
+
+ - name: Remove 'blocked' Label if All Dependencies Are Merged
+ id: unlabel_blocked
+ if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && 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 --remove-label 'blocked' "$PR_NUMBER"
+
+ - name: Apply 'blocking' Label to Unmerged Dependencies
+ id: label_blocking
+ if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0
+ continue-on-error: true
+ env:
+ GH_TOKEN: ${{ steps.generate-token.outputs.token }}
+ BLOCKING_ISSUES: ${{ steps.blocking_data.outputs.current_blocking }}
+ run: |
+ while read -r pr ; do
+ gh -R ${{ github.repository }} issue edit --add-label 'blocking' "$pr" || true
+ done < <(jq -c '.[]' <<< "$BLOCKING_ISSUES")
+
+ - name: Apply Blocking PR Status Check
+ id: blocked_check
+ if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0
+ continue-on-error: true
+ env:
+ GH_TOKEN: ${{ steps.generate-token.outputs.token }}
+ BLOCKING_DATA: ${{ steps.blocking_data.outputs.data }}
+ run: |
+ pr_head_sha=$(jq -r '.prHeadSha' <<< "$JOB_DATA")
+ # create commit Status, overwrites previous identical context
+ while read -r pr_data ; do
+ DESC=$(
+ jq -r ' "Blocking PR #" + (.number | tostring) + " is " + (if .merged then "" else "not yet " end) + "merged"' <<< "$pr_data"
+ )
+ gh api \
+ --method POST \
+ -H "Accept: application/vnd.github+json" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ "/repos/${OWNER}/${REPO}/statuses/${pr_head_sha}" \
+ -f "state=$(jq -r 'if .merged then "success" else "failure" end' <<< "$pr_data")" \
+ -f "target_url=$(jq -r '.basePrUrl' <<< "$pr_data" )" \
+ -f "description=$DESC" \
+ -f "context=ci/blocking-pr-check:$(jq '.number' <<< "$pr_data")"
+ done < <(jq -c '.[]' <<< "$BLOCKING_DATA")
+
+ - name: Context Comment
+ id: generate-comment
+ if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0
+ continue-on-error: true
+ env:
+ BLOCKING_DATA: ${{ steps.blocking_data.outputs.data }}
+ run: |
+ COMMENT_PATH="$(pwd)/temp_comment_file.txt"
+ echo '
PR Dependencies :pushpin:
' > "$COMMENT_PATH"
+ echo >> "$COMMENT_PATH"
+ pr_head_label=$(jq -r '.prHeadLabel' <<< "$JOB_DATA")
+ while read -r pr_data ; do
+ base_pr=$(jq -r '.number' <<< "$pr_data")
+ base_ref_name=$(jq -r '.baseRefName' <<< "$pr_data")
+ base_repo_owner=$(jq -r '.baseRepoOwner' <<< "$pr_data")
+ base_repo_name=$(jq -r '.baseRepoName' <<< "$pr_data")
+ compare_url="https://github.com/$base_repo_owner/$base_repo_name/compare/$base_ref_name...$pr_head_label"
+ status=$(jq -r 'if .merged then ":heavy_check_mark: Merged" else ":x: Not Merged" end' <<< "$pr_data")
+ type=$(jq -r '.type' <<< "$pr_data")
+ echo " - $type #$base_pr $status [(compare)]($compare_url)" >> "$COMMENT_PATH"
+ done < <(jq -c '.[]' <<< "$BLOCKING_DATA")
+
+ {
+ echo 'body<> "$GITHUB_OUTPUT"
+
+ - name: 💬 PR Comment
+ if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0
+ continue-on-error: true
+ env:
+ GH_TOKEN: ${{ steps.generate-token.outputs.token }}
+ COMMENT_BODY: ${{ steps.generate-comment.outputs.body }}
+ run: |
+ gh -R ${{ github.repository }} issue comment "$PR_NUMBER" \
+ --body "$COMMENT_BODY" \
+ --create-if-none \
+ --edit-last
+
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 3b71d642a..11ef262ea 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -52,7 +52,7 @@ jobs:
fail-fast: false
matrix:
include:
- - os: ubuntu-20.04
+ - os: ubuntu-22.04
qt_ver: 5
qt_host: linux
qt_arch: ""
@@ -63,8 +63,11 @@ jobs:
qt_ver: 6
qt_host: linux
qt_arch: ""
- qt_version: "6.5.3"
+ qt_version: "6.8.1"
qt_modules: "qt5compat qtimageformats qtnetworkauth"
+ linuxdeploy_hash: "4648f278ab3ef31f819e67c30d50f462640e5365a77637d7e6f2ad9fd0b4522a linuxdeploy-x86_64.AppImage"
+ linuxdeploy_qt_hash: "15106be885c1c48a021198e7e1e9a48ce9d02a86dd0a1848f00bdbf3c1c92724 linuxdeploy-plugin-qt-x86_64.AppImage"
+ appimageupdate_hash: "f1747cf60058e99f1bb9099ee9787d16c10241313b7acec81810ea1b1e568c11 AppImageUpdate-x86_64.AppImage"
- os: windows-2022
name: "Windows-MinGW-w64"
@@ -106,14 +109,6 @@ jobs:
qt_version: "6.8.1"
qt_modules: "qt5compat qtimageformats qtnetworkauth"
- - os: macos-14
- name: macOS-Legacy
- macosx_deployment_target: 10.13
- qt_ver: 5
- qt_host: mac
- qt_version: "5.15.2"
- qt_modules: "qtnetworkauth"
-
runs-on: ${{ matrix.os }}
env:
@@ -168,9 +163,15 @@ jobs:
with:
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
+ - name: Use ccache on Debug builds only
+ if: inputs.build_type == 'Debug'
+ shell: bash
+ run: |
+ echo "CCACHE_VAR=ccache" >> $GITHUB_ENV
+
- name: Retrieve ccache cache (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
- uses: actions/cache@v4.2.2
+ uses: actions/cache@v4.2.3
with:
path: '${{ github.workspace }}\.ccache'
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
@@ -187,11 +188,16 @@ jobs:
ccache -p # Show config
ccache -z # Zero stats
- - name: Use ccache on Debug builds only
- if: inputs.build_type == 'Debug'
- shell: bash
+ - name: Configure ccache (Windows MSVC)
+ if: ${{ runner.os == 'Windows' && matrix.msystem == '' && inputs.build_type == 'Debug' }}
run: |
- echo "CCACHE_VAR=ccache" >> $GITHUB_ENV
+ # https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix)
+ Copy-Item C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/ccache.exe -Destination C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/cl.exe
+ echo "CLToolExe=cl.exe" >> $env:GITHUB_ENV
+ echo "CLToolPath=C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/" >> $env:GITHUB_ENV
+ echo "TrackFileAccess=false" >> $env:GITHUB_ENV
+ # Needed for ccache, but also speeds up compile
+ echo "UseMultiToolTask=true" >> $env:GITHUB_ENV
- name: Set short version
shell: bash
@@ -249,14 +255,21 @@ jobs:
- name: Prepare AppImage (Linux)
if: runner.os == 'Linux' && matrix.qt_ver != 5
+ env:
+ APPIMAGEUPDATE_HASH: ${{ matrix.appimageupdate_hash }}
+ LINUXDEPLOY_HASH: ${{ matrix.linuxdeploy_hash }}
+ LINUXDEPLOY_QT_HASH: ${{ matrix.linuxdeploy_qt_hash }}
run: |
- wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage"
- wget "https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage"
- wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage"
+ wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20250213-2/linuxdeploy-x86_64.AppImage"
+ wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/1-alpha-20250213-1/linuxdeploy-plugin-qt-x86_64.AppImage"
- wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/continuous/AppImageUpdate-x86_64.AppImage"
+ wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/2.0.0-alpha-1-20241225/AppImageUpdate-x86_64.AppImage"
- sudo apt install libopengl0 libfuse2
+ sha256sum -c - <<< "$LINUXDEPLOY_HASH"
+ sha256sum -c - <<< "$LINUXDEPLOY_QT_HASH"
+ sha256sum -c - <<< "$APPIMAGEUPDATE_HASH"
+
+ sudo apt install libopengl0
- name: Add QT_HOST_PATH var (Windows MSVC arm64)
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
@@ -278,11 +291,6 @@ jobs:
run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -G Ninja
- - name: Configure CMake (macOS-Legacy)
- if: runner.os == 'macOS' && matrix.qt_ver == 5
- run: |
- cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DMACOSX_SPARKLE_UPDATE_PUBLIC_KEY="" -DMACOSX_SPARKLE_UPDATE_FEED_URL="" -DCMAKE_OSX_ARCHITECTURES="x86_64" -G Ninja
-
- name: Configure CMake (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != ''
shell: msys2 {0}
@@ -290,19 +298,14 @@ jobs:
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=6 -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} -G Ninja
- name: Configure CMake (Windows MSVC)
- if: runner.os == 'Windows' && matrix.msystem == ''
+ if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture != 'arm64'
+ run: |
+ cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -DLauncher_FORCE_BUNDLED_LIBS=ON -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} -G Ninja
+
+ - name: Configure CMake (Windows MSVC arm64)
+ if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture == 'arm64'
run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }}
- # https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix)
- if ("${{ env.CCACHE_VAR }}")
- {
- Copy-Item C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/ccache.exe -Destination C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/cl.exe
- echo "CLToolExe=cl.exe" >> $env:GITHUB_ENV
- echo "CLToolPath=C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/" >> $env:GITHUB_ENV
- echo "TrackFileAccess=false" >> $env:GITHUB_ENV
- }
- # Needed for ccache, but also speeds up compile
- echo "UseMultiToolTask=true" >> $env:GITHUB_ENV
- name: Configure CMake (Linux)
if: runner.os == 'Linux'
diff --git a/.github/workflows/merge-blocking-pr.yml b/.github/workflows/merge-blocking-pr.yml
new file mode 100644
index 000000000..d6410f997
--- /dev/null
+++ b/.github/workflows/merge-blocking-pr.yml
@@ -0,0 +1,56 @@
+name: Merged Blocking Pull Request Automation
+
+on:
+ pull_request:
+ types:
+ - closed
+
+jobs:
+ update-blocked-status:
+ name: Update Blocked Status
+ 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.pull_request.merged == true && contains( join( github.event.pull_request.labels.*.name, ',' ), 'blocking' )
+
+ steps:
+ - name: Generate token
+ id: generate-token
+ uses: actions/create-github-app-token@v2
+ with:
+ app-id: ${{ vars.PULL_REQUEST_APP_ID }}
+ private-key: ${{ secrets.PULL_REQUEST_APP_PRIVATE_KEY }}
+
+ - name: Gather Dependent PRs
+ id: gather_deps
+ env:
+ GH_TOKEN: ${{ steps.generate-token.outputs.token }}
+ PR_NUMBER: ${{ github.event.pull_request.number }}
+ run: |
+ blocked_prs=$(
+ gh -R ${{ github.repository }} pr list --label 'blocked' --json 'number,body' \
+ | jq -c --argjson pr "${{ github.event.pull_request.number }}" '
+ reduce ( .[] | select(
+ .body |
+ scan("(?:blocked (?:by|on)|stacked on):? #([0-9]+)") |
+ map(tonumber) |
+ any(.[]; . == $pr)
+ )) as $i ([]; . + [$i])
+ '
+ )
+ {
+ echo "deps=$blocked_prs"
+ echo "numdeps=$(jq -r '. | length' <<< "$blocked_prs")"
+ } >> "$GITHUB_OUTPUT"
+
+ - name: Trigger Blocked PR Workflows for Dependants
+ if: fromJSON(steps.gather_deps.outputs.numdeps) > 0
+ env:
+ GH_TOKEN: ${{ steps.generate-token.outputs.token }}
+ DEPS: ${{ steps.gather_deps.outputs.deps }}
+ run: |
+ while read -r pr ; do
+ gh -R ${{ github.repository }} workflow run 'blocked-prs.yml' -r "${{ github.ref_name }}" -f pr_id="$pr"
+ done < <(jq -c '.[].number' <<< "$DEPS")
+
diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml
index 6c2b13de7..fea0df6ce 100644
--- a/.github/workflows/nix.yml
+++ b/.github/workflows/nix.yml
@@ -2,19 +2,30 @@ name: Nix
on:
push:
+ tags:
+ - "*"
paths-ignore:
+ - ".github/**"
+ - "!.github/workflows/nix.yml"
+ - "flatpak/"
+ - "scripts/"
+
+ - ".git*"
+ - ".envrc"
- "**.md"
- - "**/LICENSE"
- - ".github/ISSUE_TEMPLATE/**"
- - ".markdownlint**"
- - "flatpak/**"
+ - "!COPYING.md"
+ - "renovate.json"
pull_request_target:
paths-ignore:
+ - ".github/**"
+ - "flatpak/"
+ - "scripts/"
+
+ - ".git*"
+ - ".envrc"
- "**.md"
- - "**/LICENSE"
- - ".github/ISSUE_TEMPLATE/**"
- - ".markdownlint**"
- - "flatpak/**"
+ - "!COPYING.md"
+ - "renovate.json"
workflow_dispatch:
permissions:
@@ -60,7 +71,7 @@ jobs:
# For PRs
- name: Setup Nix Magic Cache
- if: ${{ env.USE_DETERMINATE }}
+ if: ${{ env.USE_DETERMINATE == 'true' }}
uses: DeterminateSystems/flakehub-cache-action@v1
# For in-tree builds
@@ -76,15 +87,18 @@ jobs:
nix flake check --print-build-logs --show-trace
- name: Build debug package
- if: ${{ env.DEBUG }}
+ if: ${{ env.DEBUG == 'true' }}
run: |
nix build \
--no-link --print-build-logs --print-out-paths \
.#prismlauncher-debug >> "$GITHUB_STEP_SUMMARY"
- name: Build release package
- if: ${{ !env.DEBUG }}
+ if: ${{ env.DEBUG == 'false' }}
+ env:
+ TAG: ${{ github.ref_name }}
+ SYSTEM: ${{ matrix.system }}
run: |
- nix build \
- --no-link --print-build-logs --print-out-paths \
- .#prismlauncher >> "$GITHUB_STEP_SUMMARY"
+ nix build --no-link --print-out-paths .#prismlauncher \
+ | tee -a "$GITHUB_STEP_SUMMARY" \
+ | xargs cachix pin prismlauncher "$TAG"-"$SYSTEM"
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 034a8548b..8a7da812e 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -8,28 +8,6 @@ permissions:
contents: read
jobs:
- flakehub:
- name: FlakeHub
-
- runs-on: ubuntu-latest
-
- permissions:
- id-token: write
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
- with:
- ref: ${{ github.ref }}
-
- - name: Install Nix
- uses: cachix/install-nix-action@v31
-
- - name: Publish on FlakeHub
- uses: determinatesystems/flakehub-push@v5
- with:
- visibility: "public"
-
winget:
name: Winget
diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml
index 134281b2c..411a5bbeb 100644
--- a/.github/workflows/trigger_release.yml
+++ b/.github/workflows/trigger_release.yml
@@ -49,7 +49,6 @@ jobs:
mv PrismLauncher-Linux-Qt5-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt5-Portable-${{ env.VERSION }}.tar.gz
mv PrismLauncher-*.AppImage/PrismLauncher-*.AppImage PrismLauncher-Linux-x86_64.AppImage
mv PrismLauncher-*.AppImage.zsync/PrismLauncher-*.AppImage.zsync PrismLauncher-Linux-x86_64.AppImage.zsync
- mv PrismLauncher-macOS-Legacy*/PrismLauncher.zip PrismLauncher-macOS-Legacy-${{ env.VERSION }}.zip
mv PrismLauncher-macOS*/PrismLauncher.zip PrismLauncher-macOS-${{ env.VERSION }}.zip
tar --exclude='.git' -czf PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }}
@@ -104,5 +103,4 @@ jobs:
PrismLauncher-Windows-MSVC-Portable-${{ env.VERSION }}.zip
PrismLauncher-Windows-MSVC-Setup-${{ env.VERSION }}.exe
PrismLauncher-macOS-${{ env.VERSION }}.zip
- PrismLauncher-macOS-Legacy-${{ env.VERSION }}.zip
PrismLauncher-${{ env.VERSION }}.tar.gz
diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml
index 48a8418f5..cdd360589 100644
--- a/.github/workflows/update-flake.yml
+++ b/.github/workflows/update-flake.yml
@@ -17,7 +17,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- - uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f # v31
+ - uses: cachix/install-nix-action@d1ca217b388ee87b2507a9a93bf01368bde7cec2 # v31
- uses: DeterminateSystems/update-flake-lock@v24
with:
diff --git a/.gitignore b/.gitignore
index b5523f685..b563afbc7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,6 +21,7 @@ CMakeCache.txt
/.vs
cmake-build-*/
Debug
+compile_commands.json
# Build dirs
build
@@ -47,8 +48,12 @@ run/
# Nix/NixOS
.direnv/
-.pre-commit-config.yaml
+## Used when manually invoking stdenv phases
+outputs/
+## Regular artifacts
result
+result-*
+repl-result-*
# Flatpak
.flatpak-builder
diff --git a/.gitmodules b/.gitmodules
index 0f437d277..7ad40becb 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,9 +4,6 @@
[submodule "libraries/tomlplusplus"]
path = libraries/tomlplusplus
url = https://github.com/marzer/tomlplusplus.git
-[submodule "libraries/filesystem"]
- path = libraries/filesystem
- url = https://github.com/gulrak/filesystem
[submodule "libraries/libnbtplusplus"]
path = libraries/libnbtplusplus
url = https://github.com/PrismLauncher/libnbtplusplus.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index bc2e77d4a..10153c3ec 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -99,6 +99,12 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTOML_ENABLE_FLOAT16=0")
# 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 STREQUAL "Ninja" AND CMAKE_BUILD_TYPE STREQUAL "Debug")
+ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+endif()
+
option(DEBUG_ADDRESS_SANITIZER "Enable Address Sanitizer in Debug builds" OFF)
# If this is a Debug build turn on address sanitiser
@@ -189,10 +195,11 @@ set(Launcher_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" CACHE S
######## Set version numbers ########
set(Launcher_VERSION_MAJOR 10)
set(Launcher_VERSION_MINOR 0)
+set(Launcher_VERSION_PATCH 0)
-set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}")
-set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.0.0")
-set(Launcher_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_MINOR},0,0")
+set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}")
+set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}.0")
+set(Launcher_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_MINOR},${Launcher_VERSION_PATCH},0")
# Build platform.
set(Launcher_BUILD_PLATFORM "unknown" CACHE STRING "A short string identifying the platform that this build was built for. Only used to display in the about dialog.")
@@ -236,7 +243,7 @@ set(Launcher_ENABLE_JAVA_DOWNLOADER_DEFAULT ON)
# differing Linux/BSD/etc distributions. Downstream packagers should be explicitly opt-ing into this
# feature if they know it will work with their distribution.
if(UNIX AND NOT APPLE)
- set(Launcher_ENABLE_JAVA_DOWNLOADER_DEFAULT OFF)
+ set(Launcher_ENABLE_JAVA_DOWNLOADER_DEFAULT OFF)
endif()
# Java downloader
@@ -357,9 +364,6 @@ if(NOT Launcher_FORCE_BUNDLED_LIBS)
# Find toml++
find_package(tomlplusplus 3.2.0 QUIET)
- # Find ghc_filesystem
- find_package(ghc_filesystem QUIET)
-
# Find cmark
find_package(cmark QUIET)
endif()
@@ -377,7 +381,7 @@ set(Launcher_ENABLE_UPDATER NO)
set(Launcher_BUILD_UPDATER NO)
if (NOT APPLE AND (NOT Launcher_UPDATER_GITHUB_REPO STREQUAL "" AND NOT Launcher_BUILD_ARTIFACT STREQUAL ""))
- set(Launcher_BUILD_UPDATER YES)
+ set(Launcher_BUILD_UPDATER YES)
endif()
if(NOT (UNIX AND APPLE))
@@ -554,12 +558,6 @@ else()
endif()
add_subdirectory(libraries/gamemode)
add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API
-if (NOT ghc_filesystem_FOUND)
- message(STATUS "Using bundled ghc_filesystem")
- add_subdirectory(libraries/filesystem) # Implementation of std::filesystem for old C++, for usage in old macOS
-else()
- message(STATUS "Using system ghc_filesystem")
-endif()
add_subdirectory(libraries/qdcss) # css parser
############################### Built Artifacts ###############################
diff --git a/COPYING.md b/COPYING.md
index 0ea3437d3..f1f0b3a70 100644
--- a/COPYING.md
+++ b/COPYING.md
@@ -362,28 +362,6 @@
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
-## gulrak/filesystem
-
- Copyright (c) 2018, Steffen Schümann
-
- 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.
-
## Breeze icons
Copyright (C) 2014 Uri Herrera and others
diff --git a/buildconfig/BuildConfig.cpp.in b/buildconfig/BuildConfig.cpp.in
index 2124d02ae..6bebcb80e 100644
--- a/buildconfig/BuildConfig.cpp.in
+++ b/buildconfig/BuildConfig.cpp.in
@@ -34,8 +34,8 @@
*/
#include
-#include "BuildConfig.h"
#include
+#include "BuildConfig.h"
const Config BuildConfig;
@@ -58,6 +58,7 @@ Config::Config()
// Version information
VERSION_MAJOR = @Launcher_VERSION_MAJOR@;
VERSION_MINOR = @Launcher_VERSION_MINOR@;
+ VERSION_PATCH = @Launcher_VERSION_PATCH@;
BUILD_PLATFORM = "@Launcher_BUILD_PLATFORM@";
BUILD_ARTIFACT = "@Launcher_BUILD_ARTIFACT@";
@@ -74,14 +75,13 @@ Config::Config()
MAC_SPARKLE_PUB_KEY = "@MACOSX_SPARKLE_UPDATE_PUBLIC_KEY@";
MAC_SPARKLE_APPCAST_URL = "@MACOSX_SPARKLE_UPDATE_FEED_URL@";
- if (!MAC_SPARKLE_PUB_KEY.isEmpty() && !MAC_SPARKLE_APPCAST_URL.isEmpty())
- {
+ if (!MAC_SPARKLE_PUB_KEY.isEmpty() && !MAC_SPARKLE_APPCAST_URL.isEmpty()) {
UPDATER_ENABLED = true;
- } else if(!UPDATER_GITHUB_REPO.isEmpty() && !BUILD_ARTIFACT.isEmpty()) {
+ } else if (!UPDATER_GITHUB_REPO.isEmpty() && !BUILD_ARTIFACT.isEmpty()) {
UPDATER_ENABLED = true;
}
- #cmakedefine01 Launcher_ENABLE_JAVA_DOWNLOADER
+#cmakedefine01 Launcher_ENABLE_JAVA_DOWNLOADER
JAVA_DOWNLOADER_ENABLED = Launcher_ENABLE_JAVA_DOWNLOADER;
GIT_COMMIT = "@Launcher_GIT_COMMIT@";
@@ -89,27 +89,19 @@ Config::Config()
GIT_REFSPEC = "@Launcher_GIT_REFSPEC@";
// Assume that builds outside of Git repos are "stable"
- if (GIT_REFSPEC == QStringLiteral("GITDIR-NOTFOUND")
- || GIT_TAG == QStringLiteral("GITDIR-NOTFOUND")
- || GIT_REFSPEC == QStringLiteral("")
- || GIT_TAG == QStringLiteral("GIT-NOTFOUND"))
- {
+ if (GIT_REFSPEC == QStringLiteral("GITDIR-NOTFOUND") || GIT_TAG == QStringLiteral("GITDIR-NOTFOUND") ||
+ GIT_REFSPEC == QStringLiteral("") || GIT_TAG == QStringLiteral("GIT-NOTFOUND")) {
GIT_REFSPEC = "refs/heads/stable";
GIT_TAG = versionString();
GIT_COMMIT = "";
}
- if (GIT_REFSPEC.startsWith("refs/heads/"))
- {
+ if (GIT_REFSPEC.startsWith("refs/heads/")) {
VERSION_CHANNEL = GIT_REFSPEC;
- VERSION_CHANNEL.remove("refs/heads/");
- }
- else if (!GIT_COMMIT.isEmpty())
- {
+ VERSION_CHANNEL.remove("refs/heads/");
+ } else if (!GIT_COMMIT.isEmpty()) {
VERSION_CHANNEL = GIT_COMMIT.mid(0, 8);
- }
- else
- {
+ } else {
VERSION_CHANNEL = "unknown";
}
@@ -136,7 +128,7 @@ Config::Config()
QString Config::versionString() const
{
- return QString("%1.%2").arg(VERSION_MAJOR).arg(VERSION_MINOR);
+ return QString("%1.%2.%3").arg(VERSION_MAJOR).arg(VERSION_MINOR).arg(VERSION_PATCH);
}
QString Config::printableVersionString() const
@@ -144,8 +136,7 @@ QString Config::printableVersionString() const
QString vstr = versionString();
// If the build is not a main release, append the channel
- if(VERSION_CHANNEL != "stable" && GIT_TAG != vstr)
- {
+ if (VERSION_CHANNEL != "stable" && GIT_TAG != vstr) {
vstr += "-" + VERSION_CHANNEL;
}
return vstr;
@@ -162,4 +153,3 @@ QString Config::systemID() const
{
return QStringLiteral("%1 %2 %3").arg(COMPILER_TARGET_SYSTEM, COMPILER_TARGET_SYSTEM_VERSION, COMPILER_TARGET_SYSTEM_PROCESSOR);
}
-
diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h
index 099d9b5ca..b59adcb57 100644
--- a/buildconfig/BuildConfig.h
+++ b/buildconfig/BuildConfig.h
@@ -59,6 +59,8 @@ class Config {
int VERSION_MAJOR;
/// The minor version number.
int VERSION_MINOR;
+ /// The patch version number.
+ int VERSION_PATCH;
/**
* The version channel
diff --git a/default.nix b/default.nix
index 6466507b7..5ecef5590 100644
--- a/default.nix
+++ b/default.nix
@@ -1,9 +1,4 @@
-(import (
- let
- lock = builtins.fromJSON (builtins.readFile ./flake.lock);
- in
- fetchTarball {
- url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
- sha256 = lock.nodes.flake-compat.locked.narHash;
- }
-) { src = ./.; }).defaultNix
+(import (fetchTarball {
+ url = "https://github.com/edolstra/flake-compat/archive/ff81ac966bb2cae68946d5ed5fc4994f96d0ffec.tar.gz";
+ sha256 = "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=";
+}) { src = ./.; }).defaultNix
diff --git a/flake.lock b/flake.lock
index 0d1bd2df0..2d79b8335 100644
--- a/flake.lock
+++ b/flake.lock
@@ -1,21 +1,5 @@
{
"nodes": {
- "flake-compat": {
- "flake": false,
- "locked": {
- "lastModified": 1733328505,
- "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
- "owner": "edolstra",
- "repo": "flake-compat",
- "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
- "type": "github"
- },
- "original": {
- "owner": "edolstra",
- "repo": "flake-compat",
- "type": "github"
- }
- },
"libnbtplusplus": {
"flake": false,
"locked": {
@@ -32,28 +16,13 @@
"type": "github"
}
},
- "nix-filter": {
- "locked": {
- "lastModified": 1731533336,
- "narHash": "sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms=",
- "owner": "numtide",
- "repo": "nix-filter",
- "rev": "f7653272fd234696ae94229839a99b73c9ab7de0",
- "type": "github"
- },
- "original": {
- "owner": "numtide",
- "repo": "nix-filter",
- "type": "github"
- }
- },
"nixpkgs": {
"locked": {
- "lastModified": 1741851582,
- "narHash": "sha256-cPfs8qMccim2RBgtKGF+x9IBCduRvd/N5F4nYpU0TVE=",
+ "lastModified": 1744463964,
+ "narHash": "sha256-LWqduOgLHCFxiTNYi3Uj5Lgz0SR+Xhw3kr/3Xd0GPTM=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "6607cf789e541e7873d40d3a8f7815ea92204f32",
+ "rev": "2631b0b7abcea6e640ce31cd78ea58910d31e650",
"type": "github"
},
"original": {
@@ -65,9 +34,7 @@
},
"root": {
"inputs": {
- "flake-compat": "flake-compat",
"libnbtplusplus": "libnbtplusplus",
- "nix-filter": "nix-filter",
"nixpkgs": "nixpkgs"
}
}
diff --git a/flake.nix b/flake.nix
index 54add656d..fd3003bc4 100644
--- a/flake.nix
+++ b/flake.nix
@@ -15,28 +15,6 @@
url = "github:PrismLauncher/libnbtplusplus";
flake = false;
};
-
- nix-filter.url = "github:numtide/nix-filter";
-
- /*
- Inputs below this are optional and can be removed
-
- ```
- {
- inputs.prismlauncher = {
- url = "github:PrismLauncher/PrismLauncher";
- inputs = {
- flake-compat.follows = "";
- };
- };
- }
- ```
- */
-
- flake-compat = {
- url = "github:edolstra/flake-compat";
- flake = false;
- };
};
outputs =
@@ -44,9 +22,8 @@
self,
nixpkgs,
libnbtplusplus,
- nix-filter,
- ...
}:
+
let
inherit (nixpkgs) lib;
@@ -58,27 +35,128 @@
forAllSystems = lib.genAttrs systems;
nixpkgsFor = forAllSystems (system: nixpkgs.legacyPackages.${system});
in
+
{
checks = forAllSystems (
system:
+
let
- checks' = nixpkgsFor.${system}.callPackage ./nix/checks.nix { inherit self; };
+ pkgs = nixpkgsFor.${system};
+ llvm = pkgs.llvmPackages_19;
in
- lib.filterAttrs (_: lib.isDerivation) checks'
+
+ {
+ formatting =
+ pkgs.runCommand "check-formatting"
+ {
+ nativeBuildInputs = with pkgs; [
+ deadnix
+ llvm.clang-tools
+ markdownlint-cli
+ nixfmt-rfc-style
+ statix
+ ];
+ }
+ ''
+ cd ${self}
+
+ echo "Running clang-format...."
+ clang-format --dry-run --style='file' --Werror */**.{c,cc,cpp,h,hh,hpp}
+
+ echo "Running deadnix..."
+ deadnix --fail
+
+ echo "Running markdownlint..."
+ markdownlint --dot .
+
+ echo "Running nixfmt..."
+ find -type f -name '*.nix' -exec nixfmt --check {} +
+
+ echo "Running statix"
+ statix check .
+
+ touch $out
+ '';
+ }
);
devShells = forAllSystems (
system:
+
let
pkgs = nixpkgsFor.${system};
+ llvm = pkgs.llvmPackages_19;
+
+ packages' = self.packages.${system};
+
+ welcomeMessage = ''
+ Welcome to the Prism Launcher repository! 🌈
+
+ We just set some things up for you. To get building, you can run:
+
+ ```
+ $ cd "$cmakeBuildDir"
+ $ ninjaBuildPhase
+ $ ninjaInstallPhase
+ ```
+
+ Feel free to ask any questions in our Discord server or Matrix space:
+ - https://prismlauncher.org/discord
+ - https://matrix.to/#/#prismlauncher:matrix.org
+
+ And thanks for helping out :)
+ '';
+
+ # Re-use our package wrapper to wrap our development environment
+ qt-wrapper-env = packages'.prismlauncher.overrideAttrs (old: {
+ name = "qt-wrapper-env";
+
+ # Required to use script-based makeWrapper below
+ strictDeps = true;
+
+ # We don't need/want the unwrapped Prism package
+ paths = [ ];
+
+ nativeBuildInputs = old.nativeBuildInputs or [ ] ++ [
+ # Ensure the wrapper is script based so it can be sourced
+ pkgs.makeWrapper
+ ];
+
+ # Inspired by https://discourse.nixos.org/t/python-qt-woes/11808/10
+ buildCommand = ''
+ makeQtWrapper ${lib.getExe pkgs.runtimeShellPackage} "$out"
+ sed -i '/^exec/d' "$out"
+ '';
+ });
in
+
{
default = pkgs.mkShell {
- inputsFrom = [ self.packages.${system}.prismlauncher-unwrapped ];
- buildInputs = with pkgs; [
+ inputsFrom = [ packages'.prismlauncher-unwrapped ];
+
+ packages = with pkgs; [
ccache
- ninja
+ llvm.clang-tools
];
+
+ cmakeBuildType = "Debug";
+ cmakeFlags = [ "-GNinja" ] ++ packages'.prismlauncher.cmakeFlags;
+ dontFixCmake = true;
+
+ shellHook = ''
+ echo "Sourcing ${qt-wrapper-env}"
+ source ${qt-wrapper-env}
+
+ git submodule update --init --force
+
+ if [ ! -f compile_commands.json ]; then
+ cmakeConfigurePhase
+ cd ..
+ ln -s "$cmakeBuildDir"/compile_commands.json compile_commands.json
+ fi
+
+ echo ${lib.escapeShellArg welcomeMessage}
+ '';
};
}
);
@@ -89,7 +167,6 @@
prismlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix {
inherit
libnbtplusplus
- nix-filter
self
;
};
@@ -99,6 +176,7 @@
packages = forAllSystems (
system:
+
let
pkgs = nixpkgsFor.${system};
@@ -111,6 +189,7 @@
default = prismPackages.prismlauncher;
};
in
+
# Only output them if they're available on the current system
lib.filterAttrs (_: lib.meta.availableOn pkgs.stdenv.hostPlatform) packages
);
@@ -118,16 +197,18 @@
# We put these under legacyPackages as they are meant for CI, not end user consumption
legacyPackages = forAllSystems (
system:
+
let
- prismPackages = self.packages.${system};
- legacyPackages = self.legacyPackages.${system};
+ packages' = self.packages.${system};
+ legacyPackages' = self.legacyPackages.${system};
in
+
{
- prismlauncher-debug = prismPackages.prismlauncher.override {
- prismlauncher-unwrapped = legacyPackages.prismlauncher-unwrapped-debug;
+ prismlauncher-debug = packages'.prismlauncher.override {
+ prismlauncher-unwrapped = legacyPackages'.prismlauncher-unwrapped-debug;
};
- prismlauncher-unwrapped-debug = prismPackages.prismlauncher-unwrapped.overrideAttrs {
+ prismlauncher-unwrapped-debug = packages'.prismlauncher-unwrapped.overrideAttrs {
cmakeBuildType = "Debug";
dontStrip = true;
};
diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index bf9fa32ed..e131c57e8 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -376,19 +376,20 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_peerInstance = new LocalPeer(this, appID);
connect(m_peerInstance, &LocalPeer::messageReceived, this, &Application::messageReceived);
if (m_peerInstance->isClient()) {
+ bool sentMessage = false;
int timeout = 2000;
if (m_instanceIdToLaunch.isEmpty()) {
ApplicationMessage activate;
activate.command = "activate";
- m_peerInstance->sendMessage(activate.serialize(), timeout);
+ sentMessage = m_peerInstance->sendMessage(activate.serialize(), timeout);
if (!m_urlsToImport.isEmpty()) {
for (auto url : m_urlsToImport) {
ApplicationMessage import;
import.command = "import";
import.args.insert("url", url.toString());
- m_peerInstance->sendMessage(import.serialize(), timeout);
+ sentMessage = m_peerInstance->sendMessage(import.serialize(), timeout);
}
}
} else {
@@ -408,10 +409,16 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
launch.args["offline_enabled"] = "true";
launch.args["offline_name"] = m_offlineName;
}
- m_peerInstance->sendMessage(launch.serialize(), timeout);
+ sentMessage = m_peerInstance->sendMessage(launch.serialize(), timeout);
+ }
+ if (sentMessage) {
+ m_status = Application::Succeeded;
+ return;
+ } else {
+ std::cerr << "Unable to redirect command to already running instance\n";
+ // C function not Qt function - event loop not started yet
+ ::exit(1);
}
- m_status = Application::Succeeded;
- return;
}
}
@@ -710,7 +717,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("ToolbarsLocked", false);
+ // Instance
m_settings->registerSetting("InstSortMode", "Name");
+ m_settings->registerSetting("InstRenamingMode", "AskEverytime");
m_settings->registerSetting("SelectedInstance", QString());
// Window state and geometry
diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp
index ccfd0b847..eab91a5eb 100644
--- a/launcher/BaseInstance.cpp
+++ b/launcher/BaseInstance.cpp
@@ -386,6 +386,12 @@ void BaseInstance::setName(QString val)
emit propertiesChanged(this);
}
+bool BaseInstance::syncInstanceDirName(const QString& newRoot) const
+{
+ auto oldRoot = instanceRoot();
+ return oldRoot == newRoot || QFile::rename(oldRoot, newRoot);
+}
+
QString BaseInstance::name() const
{
return m_settings->get("name").toString();
diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h
index 9827a08b4..2a2b4dc4a 100644
--- a/launcher/BaseInstance.h
+++ b/launcher/BaseInstance.h
@@ -126,6 +126,9 @@ class BaseInstance : public QObject, public std::enable_shared_from_this
#endif
-// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header
-
-#ifdef __APPLE__
-#include // for deployment target to support pre-catalina targets without std::fs
-#endif // __APPLE__
-
-#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include)
-#if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500)
-#define GHC_USE_STD_FS
#include
namespace fs = std::filesystem;
-#endif // MacOS min version check
-#endif // Other OSes version check
-
-#ifndef GHC_USE_STD_FS
-#include
-namespace fs = ghc::filesystem;
-#endif
// clone
#if defined(Q_OS_LINUX)
@@ -950,7 +934,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
QDir content = application.path() + "/Contents/";
QDir resources = content.path() + "/Resources/";
QDir binaryDir = content.path() + "/MacOS/";
- QFile info = content.path() + "/Info.plist";
+ QFile info(content.path() + "/Info.plist");
if (!(content.mkpath(".") && resources.mkpath(".") && binaryDir.mkpath("."))) {
qWarning() << "Couldn't create directories within application";
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index c5beef7bd..bf91c603c 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -115,7 +115,7 @@ class copy : public QObject {
m_followSymlinks = follow;
return *this;
}
- copy& matcher(const IPathMatcher* filter)
+ copy& matcher(IPathMatcher::Ptr filter)
{
m_matcher = filter;
return *this;
@@ -147,7 +147,7 @@ class copy : public QObject {
private:
bool m_followSymlinks = true;
- const IPathMatcher* m_matcher = nullptr;
+ IPathMatcher::Ptr m_matcher = nullptr;
bool m_whitelist = false;
bool m_overwrite = false;
QDir m_src;
@@ -209,7 +209,7 @@ class create_link : public QObject {
m_useHardLinks = useHard;
return *this;
}
- create_link& matcher(const IPathMatcher* filter)
+ create_link& matcher(IPathMatcher::Ptr filter)
{
m_matcher = filter;
return *this;
@@ -260,7 +260,7 @@ class create_link : public QObject {
private:
bool m_useHardLinks = false;
- const IPathMatcher* m_matcher = nullptr;
+ IPathMatcher::Ptr m_matcher = nullptr;
bool m_whitelist = false;
bool m_recursive = true;
@@ -488,7 +488,7 @@ class clone : public QObject {
m_src.setPath(src);
m_dst.setPath(dst);
}
- clone& matcher(const IPathMatcher* filter)
+ clone& matcher(IPathMatcher::Ptr filter)
{
m_matcher = filter;
return *this;
@@ -514,7 +514,7 @@ class clone : public QObject {
bool operator()(const QString& offset, bool dryRun = false);
private:
- const IPathMatcher* m_matcher = nullptr;
+ IPathMatcher::Ptr m_matcher = nullptr;
bool m_whitelist = false;
QDir m_src;
QDir m_dst;
diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp
index d335b11c4..fb5963532 100644
--- a/launcher/InstanceCopyTask.cpp
+++ b/launcher/InstanceCopyTask.cpp
@@ -43,7 +43,7 @@ void InstanceCopyTask::executeTask()
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] {
if (m_useClone) {
FS::clone folderClone(m_origInstance->instanceRoot(), m_stagingPath);
- folderClone.matcher(m_matcher.get());
+ folderClone.matcher(m_matcher);
folderClone(true);
setProgress(0, folderClone.totalCloned());
@@ -72,7 +72,7 @@ void InstanceCopyTask::executeTask()
}
FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath);
int depth = m_linkRecursively ? -1 : 0; // we need to at least link the top level instead of the instance folder
- folderLink.linkRecursively(true).setMaxDepth(depth).useHardLinks(m_useHardLinks).matcher(m_matcher.get());
+ folderLink.linkRecursively(true).setMaxDepth(depth).useHardLinks(m_useHardLinks).matcher(m_matcher);
folderLink(true);
setProgress(0, m_progressTotal + folderLink.totalToLink());
@@ -127,7 +127,7 @@ void InstanceCopyTask::executeTask()
return !there_were_errors;
}
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
- folderCopy.followSymlinks(false).matcher(m_matcher.get());
+ folderCopy.followSymlinks(false).matcher(m_matcher);
folderCopy(true);
setProgress(0, folderCopy.totalCopied());
diff --git a/launcher/InstanceCopyTask.h b/launcher/InstanceCopyTask.h
index 0f7f1020d..3aba13e5c 100644
--- a/launcher/InstanceCopyTask.h
+++ b/launcher/InstanceCopyTask.h
@@ -28,7 +28,7 @@ class InstanceCopyTask : public InstanceTask {
InstancePtr m_origInstance;
QFuture m_copyFuture;
QFutureWatcher m_copyFutureWatcher;
- std::unique_ptr m_matcher;
+ IPathMatcher::Ptr m_matcher;
bool m_keepPlaytime;
bool m_useLinks = false;
bool m_useHardLinks = false;
diff --git a/launcher/InstanceDirUpdate.cpp b/launcher/InstanceDirUpdate.cpp
new file mode 100644
index 000000000..8be0dccac
--- /dev/null
+++ b/launcher/InstanceDirUpdate.cpp
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ *
+ * 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 "InstanceDirUpdate.h"
+
+#include
+
+#include "Application.h"
+#include "FileSystem.h"
+
+#include "InstanceList.h"
+#include "ui/dialogs/CustomMessageBox.h"
+
+QString askToUpdateInstanceDirName(InstancePtr instance, const QString& oldName, const QString& newName, QWidget* parent)
+{
+ if (oldName == newName)
+ return QString();
+
+ QString renamingMode = APPLICATION->settings()->get("InstRenamingMode").toString();
+ if (renamingMode == "MetadataOnly")
+ return QString();
+
+ auto oldRoot = instance->instanceRoot();
+ auto newDirName = FS::DirNameFromString(newName, QFileInfo(oldRoot).dir().absolutePath());
+ auto newRoot = FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), newDirName);
+ if (oldRoot == newRoot)
+ return QString();
+ if (oldRoot == FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), newName))
+ return QString();
+
+ // Check for conflict
+ if (QDir(newRoot).exists()) {
+ QMessageBox::warning(parent, QObject::tr("Cannot rename instance"),
+ QObject::tr("New instance root (%1) already exists.
Only the metadata will be renamed.").arg(newRoot));
+ return QString();
+ }
+
+ // Ask if we should rename
+ if (renamingMode == "AskEverytime") {
+ auto checkBox = new QCheckBox(QObject::tr("&Remember my choice"), parent);
+ auto dialog =
+ CustomMessageBox::selectable(parent, QObject::tr("Rename instance folder"),
+ QObject::tr("Would you also like to rename the instance folder?\n\n"
+ "Old name: %1\n"
+ "New name: %2")
+ .arg(oldName, newName),
+ QMessageBox::Question, QMessageBox::No | QMessageBox::Yes, QMessageBox::NoButton, checkBox);
+
+ auto res = dialog->exec();
+ if (checkBox->isChecked()) {
+ if (res == QMessageBox::Yes)
+ APPLICATION->settings()->set("InstRenamingMode", "PhysicalDir");
+ else
+ APPLICATION->settings()->set("InstRenamingMode", "MetadataOnly");
+ }
+ if (res == QMessageBox::No)
+ return QString();
+ }
+
+ // Check for linked instances
+ if (!checkLinkedInstances(instance->id(), parent, QObject::tr("Renaming")))
+ return QString();
+
+ // Now we can confirm that a renaming is happening
+ if (!instance->syncInstanceDirName(newRoot)) {
+ QMessageBox::warning(parent, QObject::tr("Cannot rename instance"),
+ QObject::tr("An error occurred when performing the following renaming operation:
"
+ " - Old instance root: %1
"
+ " - New instance root: %2
"
+ "Only the metadata is renamed.")
+ .arg(oldRoot, newRoot));
+ return QString();
+ }
+ return newRoot;
+}
+
+bool checkLinkedInstances(const QString& id, QWidget* parent, const QString& verb)
+{
+ auto linkedInstances = APPLICATION->instances()->getLinkedInstancesById(id);
+ if (!linkedInstances.empty()) {
+ auto response = CustomMessageBox::selectable(parent, QObject::tr("There are linked instances"),
+ QObject::tr("The following instance(s) might reference files in this instance:\n\n"
+ "%1\n\n"
+ "%2 it could break the other instance(s), \n\n"
+ "Do you wish to proceed?",
+ nullptr, linkedInstances.count())
+ .arg(linkedInstances.join("\n"))
+ .arg(verb),
+ QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
+ ->exec();
+ if (response != QMessageBox::Yes)
+ return false;
+ }
+ return true;
+}
diff --git a/launcher/InstanceDirUpdate.h b/launcher/InstanceDirUpdate.h
new file mode 100644
index 000000000..b92a59c4c
--- /dev/null
+++ b/launcher/InstanceDirUpdate.h
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "BaseInstance.h"
+
+/// Update instanceRoot to make it sync with name/id; return newRoot if a directory rename happened
+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/JavaCommon.h b/launcher/JavaCommon.h
index a21b5a494..0e4aa2b0a 100644
--- a/launcher/JavaCommon.h
+++ b/launcher/JavaCommon.h
@@ -24,7 +24,7 @@ class TestCheck : public QObject {
TestCheck(QWidget* parent, QString path, QString args, int minMem, int maxMem, int permGen)
: m_parent(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen)
{}
- virtual ~TestCheck() {};
+ virtual ~TestCheck() = default;
void run();
diff --git a/launcher/VersionProxyModel.cpp b/launcher/VersionProxyModel.cpp
index 7538ce08c..3d9d95eb6 100644
--- a/launcher/VersionProxyModel.cpp
+++ b/launcher/VersionProxyModel.cpp
@@ -193,8 +193,8 @@ QVariant VersionProxyModel::data(const QModelIndex& index, int role) const
if (value.toBool()) {
return tr("Recommended");
} else if (hasLatest) {
- auto value = sourceModel()->data(parentIndex, BaseVersionList::LatestRole);
- if (value.toBool()) {
+ auto latest = sourceModel()->data(parentIndex, BaseVersionList::LatestRole);
+ if (latest.toBool()) {
return tr("Latest");
}
}
@@ -203,33 +203,27 @@ QVariant VersionProxyModel::data(const QModelIndex& index, int role) const
}
}
case Qt::DecorationRole: {
- switch (column) {
- case Name: {
- if (hasRecommended) {
- auto recommenced = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole);
- if (recommenced.toBool()) {
- return APPLICATION->getThemedIcon("star");
- } else if (hasLatest) {
- auto latest = sourceModel()->data(parentIndex, BaseVersionList::LatestRole);
- if (latest.toBool()) {
- return APPLICATION->getThemedIcon("bug");
- }
- }
- QPixmap pixmap;
- QPixmapCache::find("placeholder", &pixmap);
- if (!pixmap) {
- QPixmap px(16, 16);
- px.fill(Qt::transparent);
- QPixmapCache::insert("placeholder", px);
- return px;
- }
- return pixmap;
+ if (column == Name && hasRecommended) {
+ auto recommenced = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole);
+ if (recommenced.toBool()) {
+ return APPLICATION->getThemedIcon("star");
+ } else if (hasLatest) {
+ auto latest = sourceModel()->data(parentIndex, BaseVersionList::LatestRole);
+ if (latest.toBool()) {
+ return APPLICATION->getThemedIcon("bug");
}
}
- default: {
- return QVariant();
+ QPixmap pixmap;
+ QPixmapCache::find("placeholder", &pixmap);
+ if (!pixmap) {
+ QPixmap px(16, 16);
+ px.fill(Qt::transparent);
+ QPixmapCache::insert("placeholder", px);
+ return px;
}
+ return pixmap;
}
+ return QVariant();
}
default: {
if (roles.contains((BaseVersionList::ModelRoles)role)) {
diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp
index b641b41d5..a082b4b5b 100644
--- a/launcher/filelink/FileLink.cpp
+++ b/launcher/filelink/FileLink.cpp
@@ -40,24 +40,8 @@
#include "WindowsConsole.h"
#endif
-// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header
-
-#ifdef __APPLE__
-#include // for deployment target to support pre-catalina targets without std::fs
-#endif // __APPLE__
-
-#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include)
-#if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500)
-#define GHC_USE_STD_FS
#include
namespace fs = std::filesystem;
-#endif // MacOS min version check
-#endif // Other OSes version check
-
-#ifndef GHC_USE_STD_FS
-#include
-namespace fs = ghc::filesystem;
-#endif
FileLinkApp::FileLinkApp(int& argc, char** argv) : QCoreApplication(argc, argv), socket(new QLocalSocket(this))
{
diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp
index d6534b910..4deee9712 100644
--- a/launcher/minecraft/PackProfile.cpp
+++ b/launcher/minecraft/PackProfile.cpp
@@ -517,13 +517,9 @@ QVariant PackProfile::data(const QModelIndex& index, int role) const
switch (role) {
case Qt::CheckStateRole: {
- switch (column) {
- case NameColumn: {
- return patch->isEnabled() ? Qt::Checked : Qt::Unchecked;
- }
- default:
- return QVariant();
- }
+ if (column == NameColumn)
+ return patch->isEnabled() ? Qt::Checked : Qt::Unchecked;
+ return QVariant();
}
case Qt::DisplayRole: {
switch (column) {
diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp
index 812b13c71..cf27be676 100644
--- a/launcher/minecraft/WorldList.cpp
+++ b/launcher/minecraft/WorldList.cpp
@@ -208,13 +208,9 @@ QVariant WorldList::data(const QModelIndex& index, int role) const
}
case Qt::UserRole:
- switch (column) {
- case SizeColumn:
- return QVariant::fromValue(world.bytes());
-
- default:
- return data(index, Qt::DisplayRole);
- }
+ if (column == SizeColumn)
+ return QVariant::fromValue(world.bytes());
+ return data(index, Qt::DisplayRole);
case Qt::ToolTipRole: {
if (column == InfoColumn) {
diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp
index d276d4c41..3cbbb2a74 100644
--- a/launcher/minecraft/auth/AccountList.cpp
+++ b/launcher/minecraft/auth/AccountList.cpp
@@ -260,6 +260,30 @@ int AccountList::count() const
return m_accounts.count();
}
+QString getAccountStatus(AccountState status)
+{
+ switch (status) {
+ case AccountState::Unchecked:
+ return QObject::tr("Unchecked", "Account status");
+ case AccountState::Offline:
+ return QObject::tr("Offline", "Account status");
+ case AccountState::Online:
+ return QObject::tr("Ready", "Account status");
+ case AccountState::Working:
+ return QObject::tr("Working", "Account status");
+ case AccountState::Errored:
+ return QObject::tr("Errored", "Account status");
+ case AccountState::Expired:
+ return QObject::tr("Expired", "Account status");
+ case AccountState::Disabled:
+ return QObject::tr("Disabled", "Account status");
+ case AccountState::Gone:
+ return QObject::tr("Gone", "Account status");
+ default:
+ return QObject::tr("Unknown", "Account status");
+ }
+}
+
QVariant AccountList::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
@@ -273,13 +297,10 @@ QVariant AccountList::data(const QModelIndex& index, int role) const
switch (role) {
case Qt::DisplayRole:
switch (index.column()) {
- case ProfileNameColumn: {
+ case ProfileNameColumn:
return account->profileName();
- }
-
case NameColumn:
return account->accountDisplayString();
-
case TypeColumn: {
switch (account->accountType()) {
case AccountType::MSA: {
@@ -291,39 +312,8 @@ QVariant AccountList::data(const QModelIndex& index, int role) const
}
return tr("Unknown", "Account type");
}
-
- case StatusColumn: {
- switch (account->accountState()) {
- case AccountState::Unchecked: {
- return tr("Unchecked", "Account status");
- }
- case AccountState::Offline: {
- return tr("Offline", "Account status");
- }
- case AccountState::Online: {
- return tr("Ready", "Account status");
- }
- case AccountState::Working: {
- return tr("Working", "Account status");
- }
- case AccountState::Errored: {
- return tr("Errored", "Account status");
- }
- case AccountState::Expired: {
- return tr("Expired", "Account status");
- }
- case AccountState::Disabled: {
- return tr("Disabled", "Account status");
- }
- case AccountState::Gone: {
- return tr("Gone", "Account status");
- }
- default: {
- return tr("Unknown", "Account status");
- }
- }
- }
-
+ case StatusColumn:
+ return getAccountStatus(account->accountState());
default:
return QVariant();
}
@@ -335,11 +325,9 @@ QVariant AccountList::data(const QModelIndex& index, int role) const
return QVariant::fromValue(account);
case Qt::CheckStateRole:
- if (index.column() == ProfileNameColumn) {
+ if (index.column() == ProfileNameColumn)
return account == m_defaultAccount ? Qt::Checked : Qt::Unchecked;
- } else {
- return QVariant();
- }
+ return QVariant();
default:
return QVariant();
@@ -461,18 +449,14 @@ bool AccountList::loadList()
// Make sure the format version matches.
auto listVersion = root.value("formatVersion").toVariant().toInt();
- switch (listVersion) {
- case AccountListVersion::MojangMSA: {
- return loadV3(root);
- } break;
- default: {
- QString newName = "accounts-old.json";
- qWarning() << "Unknown format version when loading account list. Existing one will be renamed to" << newName;
- // Attempt to rename the old version.
- file.rename(newName);
- return false;
- }
- }
+ if (listVersion == AccountListVersion::MojangMSA)
+ return loadV3(root);
+
+ QString newName = "accounts-old.json";
+ qWarning() << "Unknown format version when loading account list. Existing one will be renamed to" << newName;
+ // Attempt to rename the old version.
+ file.rename(newName);
+ return false;
}
bool AccountList::loadV3(QJsonObject& root)
diff --git a/launcher/minecraft/auth/AuthFlow.cpp b/launcher/minecraft/auth/AuthFlow.cpp
index 19fbe15dd..287831b2f 100644
--- a/launcher/minecraft/auth/AuthFlow.cpp
+++ b/launcher/minecraft/auth/AuthFlow.cpp
@@ -1,5 +1,4 @@
#include
-#include
#include
#include
diff --git a/launcher/minecraft/auth/AuthSession.h b/launcher/minecraft/auth/AuthSession.h
index 54e7d69e0..cbe604805 100644
--- a/launcher/minecraft/auth/AuthSession.h
+++ b/launcher/minecraft/auth/AuthSession.h
@@ -1,12 +1,9 @@
#pragma once
-#include
#include
#include
-#include "QObjectPtr.h"
class MinecraftAccount;
-class QNetworkAccessManager;
struct AuthSession {
bool MakeOffline(QString offline_playername);
diff --git a/launcher/minecraft/gameoptions/GameOptions.cpp b/launcher/minecraft/gameoptions/GameOptions.cpp
index 4f4fb99a7..25f7074ec 100644
--- a/launcher/minecraft/gameoptions/GameOptions.cpp
+++ b/launcher/minecraft/gameoptions/GameOptions.cpp
@@ -87,16 +87,12 @@ QVariant GameOptions::data(const QModelIndex& index, int role) const
if (row < 0 || row >= int(contents.size()))
return QVariant();
- switch (role) {
- case Qt::DisplayRole:
- if (column == 0) {
- return contents[row].key;
- } else {
- return contents[row].value;
- }
- default:
- return QVariant();
+ if (role == Qt::DisplayRole) {
+ if (column == 0)
+ return contents[row].key;
+ return contents[row].value;
}
+ return QVariant();
}
int GameOptions::rowCount(const QModelIndex&) const
diff --git a/launcher/minecraft/mod/DataPack.cpp b/launcher/minecraft/mod/DataPack.cpp
index 4a9e77a70..580d5c714 100644
--- a/launcher/minecraft/mod/DataPack.cpp
+++ b/launcher/minecraft/mod/DataPack.cpp
@@ -105,19 +105,16 @@ std::pair DataPack::compatibleVersions() const
int DataPack::compare(const Resource& other, SortType type) const
{
auto const& cast_other = static_cast(other);
- switch (type) {
- default:
- return Resource::compare(other, type);
- case SortType::PACK_FORMAT: {
- auto this_ver = packFormat();
- auto other_ver = cast_other.packFormat();
+ if (type == SortType::PACK_FORMAT) {
+ auto this_ver = packFormat();
+ auto other_ver = cast_other.packFormat();
- if (this_ver > other_ver)
- return 1;
- if (this_ver < other_ver)
- return -1;
- break;
- }
+ if (this_ver > other_ver)
+ return 1;
+ if (this_ver < other_ver)
+ return -1;
+ } else {
+ return Resource::compare(other, type);
}
return 0;
}
diff --git a/launcher/minecraft/mod/DataPack.h b/launcher/minecraft/mod/DataPack.h
index 4855b0203..266c2061b 100644
--- a/launcher/minecraft/mod/DataPack.h
+++ b/launcher/minecraft/mod/DataPack.h
@@ -35,8 +35,6 @@ class Version;
class DataPack : public Resource {
Q_OBJECT
public:
- using Ptr = shared_qobject_ptr;
-
DataPack(QObject* parent = nullptr) : Resource(parent) {}
DataPack(QFileInfo file_info) : Resource(file_info) {}
@@ -59,6 +57,8 @@ class DataPack : public Resource {
[[nodiscard]] int compare(Resource const& other, SortType type) const override;
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
+ virtual QString directory() { return "/data"; }
+
protected:
mutable QMutex m_data_lock;
diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp
index 50fb45d77..99fc39ce0 100644
--- a/launcher/minecraft/mod/Mod.cpp
+++ b/launcher/minecraft/mod/Mod.cpp
@@ -138,6 +138,15 @@ auto Mod::name() const -> QString
return Resource::name();
}
+auto Mod::mod_id() const -> QString
+{
+ auto d_mod_id = details().mod_id;
+ if (!d_mod_id.isEmpty())
+ return d_mod_id;
+
+ return Resource::name();
+}
+
auto Mod::version() const -> QString
{
return details().version;
diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h
index 8a352c66c..deb1859de 100644
--- a/launcher/minecraft/mod/Mod.h
+++ b/launcher/minecraft/mod/Mod.h
@@ -61,6 +61,7 @@ class Mod : public Resource {
auto details() const -> const ModDetails&;
auto name() const -> QString override;
+ auto mod_id() const -> QString;
auto version() const -> QString;
auto homepage() const -> QString override;
auto description() const -> QString;
diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp
index 027f3d4ca..43888ae27 100644
--- a/launcher/minecraft/mod/ModFolderModel.cpp
+++ b/launcher/minecraft/mod/ModFolderModel.cpp
@@ -145,12 +145,9 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
}
return {};
case Qt::CheckStateRole:
- switch (column) {
- case ActiveColumn:
- return at(row).enabled() ? Qt::Checked : Qt::Unchecked;
- default:
- return QVariant();
- }
+ if (column == ActiveColumn)
+ return at(row).enabled() ? Qt::Checked : Qt::Unchecked;
+ return QVariant();
default:
return QVariant();
}
diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h
index 42463fe8f..16d8c2a89 100644
--- a/launcher/minecraft/mod/Resource.h
+++ b/launcher/minecraft/mod/Resource.h
@@ -152,6 +152,9 @@ class Resource : public QObject {
[[nodiscard]] bool isMoreThanOneHardLink() const;
+ [[nodiscard]] auto mod_id() const -> QString { return m_mod_id; }
+ void setModId(const QString& modId) { m_mod_id = modId; }
+
protected:
/* The file corresponding to this resource. */
QFileInfo m_file_info;
@@ -162,6 +165,7 @@ class Resource : public QObject {
QString m_internal_id;
/* Name as reported via the file name. In the absence of a better name, this is shown to the user. */
QString m_name;
+ QString m_mod_id;
/* The type of file we're dealing with. */
ResourceType m_type = ResourceType::UNKNOWN;
diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp
index 70555fa35..d4900616b 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourceFolderModel.cpp
@@ -513,12 +513,9 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
return {};
}
case Qt::CheckStateRole:
- switch (column) {
- case ActiveColumn:
- return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked;
- default:
- return {};
- }
+ if (column == ActiveColumn)
+ return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked;
+ return {};
default:
return {};
}
diff --git a/launcher/minecraft/mod/ResourcePack.cpp b/launcher/minecraft/mod/ResourcePack.cpp
index 81fb91485..4ed3c67e3 100644
--- a/launcher/minecraft/mod/ResourcePack.cpp
+++ b/launcher/minecraft/mod/ResourcePack.cpp
@@ -42,13 +42,6 @@ void ResourcePack::setPackFormat(int new_format_id)
m_pack_format = new_format_id;
}
-void ResourcePack::setDescription(QString new_description)
-{
- QMutexLocker locker(&m_data_lock);
-
- m_description = new_description;
-}
-
void ResourcePack::setImage(QImage new_image) const
{
QMutexLocker locker(&m_data_lock);
@@ -90,7 +83,7 @@ QPixmap ResourcePack::image(QSize size, Qt::AspectRatioMode mode) const
}
// Imaged got evicted from the cache. Re-process it and retry.
- ResourcePackUtils::processPackPNG(*this);
+ ResourcePackUtils::processPackPNG(this);
return image(size);
}
@@ -102,44 +95,3 @@ std::pair ResourcePack::compatibleVersions() const
return s_pack_format_versions.constFind(m_pack_format).value();
}
-
-int ResourcePack::compare(const Resource& other, SortType type) const
-{
- auto const& cast_other = static_cast(other);
- switch (type) {
- default:
- return Resource::compare(other, type);
- case SortType::PACK_FORMAT: {
- auto this_ver = packFormat();
- auto other_ver = cast_other.packFormat();
-
- if (this_ver > other_ver)
- return 1;
- if (this_ver < other_ver)
- return -1;
- break;
- }
- }
- return 0;
-}
-
-bool ResourcePack::applyFilter(QRegularExpression filter) const
-{
- if (filter.match(description()).hasMatch())
- return true;
-
- if (filter.match(QString::number(packFormat())).hasMatch())
- return true;
-
- if (filter.match(compatibleVersions().first.toString()).hasMatch())
- return true;
- if (filter.match(compatibleVersions().second.toString()).hasMatch())
- return true;
-
- return Resource::applyFilter(filter);
-}
-
-bool ResourcePack::valid() const
-{
- return m_pack_format != 0;
-}
diff --git a/launcher/minecraft/mod/ResourcePack.h b/launcher/minecraft/mod/ResourcePack.h
index 2254fc5c4..bb5aeecb5 100644
--- a/launcher/minecraft/mod/ResourcePack.h
+++ b/launcher/minecraft/mod/ResourcePack.h
@@ -1,6 +1,7 @@
#pragma once
#include "Resource.h"
+#include "minecraft/mod/DataPack.h"
#include
#include
@@ -14,51 +15,27 @@ class Version;
* Store localized descriptions
* */
-class ResourcePack : public Resource {
+class ResourcePack : public DataPack {
Q_OBJECT
public:
- using Ptr = shared_qobject_ptr;
+ ResourcePack(QObject* parent = nullptr) : DataPack(parent) {}
+ ResourcePack(QFileInfo file_info) : DataPack(file_info) {}
- ResourcePack(QObject* parent = nullptr) : Resource(parent) {}
- ResourcePack(QFileInfo file_info) : Resource(file_info) {}
-
- /** Gets the numerical ID of the pack format. */
- [[nodiscard]] int packFormat() const { return m_pack_format; }
/** Gets, respectively, the lower and upper versions supported by the set pack format. */
[[nodiscard]] std::pair compatibleVersions() const;
- /** Gets the description of the resource pack. */
- [[nodiscard]] QString description() const { return m_description; }
-
/** Gets the image of the resource pack, converted to a QPixmap for drawing, and scaled to size. */
[[nodiscard]] QPixmap image(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const;
- /** Thread-safe. */
- void setPackFormat(int new_format_id);
-
- /** Thread-safe. */
- void setDescription(QString new_description);
-
/** Thread-safe. */
void setImage(QImage new_image) const;
- bool valid() const override;
+ /** Thread-safe. */
+ void setPackFormat(int new_format_id);
- [[nodiscard]] int compare(Resource const& other, SortType type) const override;
- [[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
+ virtual QString directory() { return "/assets"; }
protected:
- mutable QMutex m_data_lock;
-
- /* The 'version' of a resource pack, as defined in the pack.mcmeta file.
- * See https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
- */
- int m_pack_format = 0;
-
- /** The resource pack's description, as defined in the pack.mcmeta file.
- */
- QString m_description;
-
/** The resource pack's image file cache key, for access in the QPixmapCache global instance.
*
* The 'was_ever_used' state simply identifies whether the key was never inserted on the cache (true),
diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
index e106a2be9..d9f27a043 100644
--- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
@@ -44,7 +44,7 @@
#include "Application.h"
#include "Version.h"
-#include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
+#include "minecraft/mod/tasks/LocalDataPackParseTask.h"
ResourcePackFolderModel::ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
: ResourceFolderModel(dir, instance, is_indexed, create_dir, parent)
@@ -128,12 +128,9 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
}
return {};
case Qt::CheckStateRole:
- switch (column) {
- case ActiveColumn:
- return at(row).enabled() ? Qt::Checked : Qt::Unchecked;
- default:
- return {};
- }
+ if (column == ActiveColumn)
+ return at(row).enabled() ? Qt::Checked : Qt::Unchecked;
+ return {};
default:
return {};
}
@@ -191,5 +188,5 @@ int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const
Task* ResourcePackFolderModel::createParseTask(Resource& resource)
{
- return new LocalResourcePackParseTask(m_next_resolution_ticket, static_cast(resource));
+ return new LocalDataPackParseTask(m_next_resolution_ticket, dynamic_cast(&resource));
}
diff --git a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp
index 19b15709d..530ce44cc 100644
--- a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp
@@ -23,6 +23,8 @@
#include "FileSystem.h"
#include "Json.h"
+#include "minecraft/mod/ResourcePack.h"
+#include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
#include
#include
@@ -32,9 +34,9 @@
namespace DataPackUtils {
-bool process(DataPack& pack, ProcessingLevel level)
+bool process(DataPack* pack, ProcessingLevel level)
{
- switch (pack.type()) {
+ switch (pack->type()) {
case ResourceType::FOLDER:
return DataPackUtils::processFolder(pack, level);
case ResourceType::ZIPFILE:
@@ -45,16 +47,16 @@ bool process(DataPack& pack, ProcessingLevel level)
}
}
-bool processFolder(DataPack& pack, ProcessingLevel level)
+bool processFolder(DataPack* pack, ProcessingLevel level)
{
- Q_ASSERT(pack.type() == ResourceType::FOLDER);
+ Q_ASSERT(pack->type() == ResourceType::FOLDER);
auto mcmeta_invalid = [&pack]() {
- qWarning() << "Data pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta";
+ qWarning() << "Data pack at" << pack->fileinfo().filePath() << "does not have a valid pack.mcmeta";
return false; // the mcmeta is not optional
};
- QFileInfo mcmeta_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.mcmeta"));
+ QFileInfo mcmeta_file_info(FS::PathCombine(pack->fileinfo().filePath(), "pack.mcmeta"));
if (mcmeta_file_info.exists() && mcmeta_file_info.isFile()) {
QFile mcmeta_file(mcmeta_file_info.filePath());
if (!mcmeta_file.open(QIODevice::ReadOnly))
@@ -72,7 +74,7 @@ bool processFolder(DataPack& pack, ProcessingLevel level)
return mcmeta_invalid(); // mcmeta file isn't a valid file
}
- QFileInfo data_dir_info(FS::PathCombine(pack.fileinfo().filePath(), "data"));
+ QFileInfo data_dir_info(FS::PathCombine(pack->fileinfo().filePath(), pack->directory()));
if (!data_dir_info.exists() || !data_dir_info.isDir()) {
return false; // data dir does not exists or isn't valid
}
@@ -80,22 +82,46 @@ bool processFolder(DataPack& pack, ProcessingLevel level)
if (level == ProcessingLevel::BasicInfoOnly) {
return true; // only need basic info already checked
}
+ if (auto rp = dynamic_cast(pack)) {
+ auto png_invalid = [&pack]() {
+ qWarning() << "Resource pack at" << pack->fileinfo().filePath() << "does not have a valid pack.png";
+ return true; // the png is optional
+ };
+
+ QFileInfo image_file_info(FS::PathCombine(pack->fileinfo().filePath(), "pack.png"));
+ if (image_file_info.exists() && image_file_info.isFile()) {
+ QFile pack_png_file(image_file_info.filePath());
+ if (!pack_png_file.open(QIODevice::ReadOnly))
+ return png_invalid(); // can't open pack.png file
+
+ auto data = pack_png_file.readAll();
+
+ bool pack_png_result = ResourcePackUtils::processPackPNG(rp, std::move(data));
+
+ pack_png_file.close();
+ if (!pack_png_result) {
+ return png_invalid(); // pack.png invalid
+ }
+ } else {
+ return png_invalid(); // pack.png does not exists or is not a valid file.
+ }
+ }
return true; // all tests passed
}
-bool processZIP(DataPack& pack, ProcessingLevel level)
+bool processZIP(DataPack* pack, ProcessingLevel level)
{
- Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
+ Q_ASSERT(pack->type() == ResourceType::ZIPFILE);
- QuaZip zip(pack.fileinfo().filePath());
+ QuaZip zip(pack->fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip))
return false; // can't open zip file
QuaZipFile file(&zip);
auto mcmeta_invalid = [&pack]() {
- qWarning() << "Data pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta";
+ qWarning() << "Data pack at" << pack->fileinfo().filePath() << "does not have a valid pack.mcmeta";
return false; // the mcmeta is not optional
};
@@ -119,7 +145,7 @@ bool processZIP(DataPack& pack, ProcessingLevel level)
}
QuaZipDir zipDir(&zip);
- if (!zipDir.exists("/data")) {
+ if (!zipDir.exists(pack->directory())) {
return false; // data dir does not exists at zip root
}
@@ -127,21 +153,49 @@ bool processZIP(DataPack& pack, ProcessingLevel level)
zip.close();
return true; // only need basic info already checked
}
+ if (auto rp = dynamic_cast(pack)) {
+ auto png_invalid = [&pack]() {
+ qWarning() << "Resource pack at" << pack->fileinfo().filePath() << "does not have a valid pack.png";
+ return true; // the png is optional
+ };
+ if (zip.setCurrentFile("pack.png")) {
+ if (!file.open(QIODevice::ReadOnly)) {
+ qCritical() << "Failed to open file in zip.";
+ zip.close();
+ return png_invalid();
+ }
+
+ auto data = file.readAll();
+
+ bool pack_png_result = ResourcePackUtils::processPackPNG(rp, std::move(data));
+
+ file.close();
+ zip.close();
+ if (!pack_png_result) {
+ return png_invalid(); // pack.png invalid
+ }
+ } else {
+ zip.close();
+ return png_invalid(); // could not set pack.mcmeta as current file.
+ }
+ }
zip.close();
return true;
}
// https://minecraft.wiki/w/Data_pack#pack.mcmeta
-bool processMCMeta(DataPack& pack, QByteArray&& raw_data)
+// https://minecraft.wiki/w/Raw_JSON_text_format
+// https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
+bool processMCMeta(DataPack* pack, QByteArray&& raw_data)
{
try {
auto json_doc = QJsonDocument::fromJson(raw_data);
auto pack_obj = Json::requireObject(json_doc.object(), "pack", {});
- pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0));
- pack.setDescription(Json::ensureString(pack_obj, "description", ""));
+ pack->setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0));
+ pack->setDescription(ResourcePackUtils::processComponent(pack_obj.value("description")));
} catch (Json::JsonException& e) {
qWarning() << "JsonException: " << e.what() << e.cause();
return false;
@@ -152,26 +206,19 @@ bool processMCMeta(DataPack& pack, QByteArray&& raw_data)
bool validate(QFileInfo file)
{
DataPack dp{ file };
- return DataPackUtils::process(dp, ProcessingLevel::BasicInfoOnly) && dp.valid();
+ return DataPackUtils::process(&dp, ProcessingLevel::BasicInfoOnly) && dp.valid();
}
} // namespace DataPackUtils
-LocalDataPackParseTask::LocalDataPackParseTask(int token, DataPack& dp) : Task(false), m_token(token), m_data_pack(dp) {}
-
-bool LocalDataPackParseTask::abort()
-{
- m_aborted = true;
- return true;
-}
+LocalDataPackParseTask::LocalDataPackParseTask(int token, DataPack* dp) : Task(false), m_token(token), m_data_pack(dp) {}
void LocalDataPackParseTask::executeTask()
{
- if (!DataPackUtils::process(m_data_pack))
+ if (!DataPackUtils::process(m_data_pack)) {
+ emitFailed("process failed");
return;
+ }
- if (m_aborted)
- emitAborted();
- else
- emitSucceeded();
+ emitSucceeded();
}
diff --git a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h
index 12fd8c82c..7355783df 100644
--- a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h
+++ b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h
@@ -32,12 +32,12 @@ namespace DataPackUtils {
enum class ProcessingLevel { Full, BasicInfoOnly };
-bool process(DataPack& pack, ProcessingLevel level = ProcessingLevel::Full);
+bool process(DataPack* pack, ProcessingLevel level = ProcessingLevel::Full);
-bool processZIP(DataPack& pack, ProcessingLevel level = ProcessingLevel::Full);
-bool processFolder(DataPack& pack, ProcessingLevel level = ProcessingLevel::Full);
+bool processZIP(DataPack* pack, ProcessingLevel level = ProcessingLevel::Full);
+bool processFolder(DataPack* pack, ProcessingLevel level = ProcessingLevel::Full);
-bool processMCMeta(DataPack& pack, QByteArray&& raw_data);
+bool processMCMeta(DataPack* pack, QByteArray&& raw_data);
/** Checks whether a file is valid as a data pack or not. */
bool validate(QFileInfo file);
@@ -47,10 +47,7 @@ bool validate(QFileInfo file);
class LocalDataPackParseTask : public Task {
Q_OBJECT
public:
- LocalDataPackParseTask(int token, DataPack& dp);
-
- [[nodiscard]] bool canAbort() const override { return true; }
- bool abort() override;
+ LocalDataPackParseTask(int token, DataPack* dp);
void executeTask() override;
@@ -59,7 +56,5 @@ class LocalDataPackParseTask : public Task {
private:
int m_token;
- DataPack& m_data_pack;
-
- bool m_aborted = false;
+ DataPack* m_data_pack;
};
diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp
index 0b80db82d..db4b2e55c 100644
--- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp
@@ -20,6 +20,7 @@
#include "FileSystem.h"
#include "Json.h"
+#include "minecraft/mod/tasks/LocalDataPackParseTask.h"
#include
#include
@@ -29,155 +30,6 @@
namespace ResourcePackUtils {
-bool process(ResourcePack& pack, ProcessingLevel level)
-{
- switch (pack.type()) {
- case ResourceType::FOLDER:
- return ResourcePackUtils::processFolder(pack, level);
- case ResourceType::ZIPFILE:
- return ResourcePackUtils::processZIP(pack, level);
- default:
- qWarning() << "Invalid type for resource pack parse task!";
- return false;
- }
-}
-
-bool processFolder(ResourcePack& pack, ProcessingLevel level)
-{
- Q_ASSERT(pack.type() == ResourceType::FOLDER);
-
- auto mcmeta_invalid = [&pack]() {
- qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta";
- return false; // the mcmeta is not optional
- };
-
- QFileInfo mcmeta_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.mcmeta"));
- if (mcmeta_file_info.exists() && mcmeta_file_info.isFile()) {
- QFile mcmeta_file(mcmeta_file_info.filePath());
- if (!mcmeta_file.open(QIODevice::ReadOnly))
- return mcmeta_invalid(); // can't open mcmeta file
-
- auto data = mcmeta_file.readAll();
-
- bool mcmeta_result = ResourcePackUtils::processMCMeta(pack, std::move(data));
-
- mcmeta_file.close();
- if (!mcmeta_result) {
- return mcmeta_invalid(); // mcmeta invalid
- }
- } else {
- return mcmeta_invalid(); // mcmeta file isn't a valid file
- }
-
- QFileInfo assets_dir_info(FS::PathCombine(pack.fileinfo().filePath(), "assets"));
- if (!assets_dir_info.exists() || !assets_dir_info.isDir()) {
- return false; // assets dir does not exists or isn't valid
- }
-
- if (level == ProcessingLevel::BasicInfoOnly) {
- return true; // only need basic info already checked
- }
-
- auto png_invalid = [&pack]() {
- qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png";
- return true; // the png is optional
- };
-
- QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
- if (image_file_info.exists() && image_file_info.isFile()) {
- QFile pack_png_file(image_file_info.filePath());
- if (!pack_png_file.open(QIODevice::ReadOnly))
- return png_invalid(); // can't open pack.png file
-
- auto data = pack_png_file.readAll();
-
- bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data));
-
- pack_png_file.close();
- if (!pack_png_result) {
- return png_invalid(); // pack.png invalid
- }
- } else {
- return png_invalid(); // pack.png does not exists or is not a valid file.
- }
-
- return true; // all tests passed
-}
-
-bool processZIP(ResourcePack& pack, ProcessingLevel level)
-{
- Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
-
- QuaZip zip(pack.fileinfo().filePath());
- if (!zip.open(QuaZip::mdUnzip))
- return false; // can't open zip file
-
- QuaZipFile file(&zip);
-
- auto mcmeta_invalid = [&pack]() {
- qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta";
- return false; // the mcmeta is not optional
- };
-
- if (zip.setCurrentFile("pack.mcmeta")) {
- if (!file.open(QIODevice::ReadOnly)) {
- qCritical() << "Failed to open file in zip.";
- zip.close();
- return mcmeta_invalid();
- }
-
- auto data = file.readAll();
-
- bool mcmeta_result = ResourcePackUtils::processMCMeta(pack, std::move(data));
-
- file.close();
- if (!mcmeta_result) {
- return mcmeta_invalid(); // mcmeta invalid
- }
- } else {
- return mcmeta_invalid(); // could not set pack.mcmeta as current file.
- }
-
- QuaZipDir zipDir(&zip);
- if (!zipDir.exists("/assets")) {
- return false; // assets dir does not exists at zip root
- }
-
- if (level == ProcessingLevel::BasicInfoOnly) {
- zip.close();
- return true; // only need basic info already checked
- }
-
- auto png_invalid = [&pack]() {
- qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png";
- return true; // the png is optional
- };
-
- if (zip.setCurrentFile("pack.png")) {
- if (!file.open(QIODevice::ReadOnly)) {
- qCritical() << "Failed to open file in zip.";
- zip.close();
- return png_invalid();
- }
-
- auto data = file.readAll();
-
- bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data));
-
- file.close();
- zip.close();
- if (!pack_png_result) {
- return png_invalid(); // pack.png invalid
- }
- } else {
- zip.close();
- return png_invalid(); // could not set pack.mcmeta as current file.
- }
-
- zip.close();
- return true;
-}
-
QString buildStyle(const QJsonObject& obj)
{
QStringList styles;
@@ -259,30 +111,11 @@ QString processComponent(const QJsonValue& value, bool strikethrough, bool under
return {};
}
-// https://minecraft.wiki/w/Raw_JSON_text_format
-// https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
-bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
-{
- try {
- auto json_doc = QJsonDocument::fromJson(raw_data);
- auto pack_obj = Json::requireObject(json_doc.object(), "pack", {});
-
- pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0));
-
- pack.setDescription(processComponent(pack_obj.value("description")));
-
- } catch (Json::JsonException& e) {
- qWarning() << "JsonException: " << e.what() << e.cause();
- return false;
- }
- return true;
-}
-
-bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data)
+bool processPackPNG(const ResourcePack* pack, QByteArray&& raw_data)
{
auto img = QImage::fromData(raw_data);
if (!img.isNull()) {
- pack.setImage(img);
+ pack->setImage(img);
} else {
qWarning() << "Failed to parse pack.png.";
return false;
@@ -290,16 +123,16 @@ bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data)
return true;
}
-bool processPackPNG(const ResourcePack& pack)
+bool processPackPNG(const ResourcePack* pack)
{
auto png_invalid = [&pack]() {
- qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png";
+ qWarning() << "Resource pack at" << pack->fileinfo().filePath() << "does not have a valid pack.png";
return false;
};
- switch (pack.type()) {
+ switch (pack->type()) {
case ResourceType::FOLDER: {
- QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
+ QFileInfo image_file_info(FS::PathCombine(pack->fileinfo().filePath(), "pack.png"));
if (image_file_info.exists() && image_file_info.isFile()) {
QFile pack_png_file(image_file_info.filePath());
if (!pack_png_file.open(QIODevice::ReadOnly))
@@ -319,7 +152,7 @@ bool processPackPNG(const ResourcePack& pack)
return false; // not processed correctly; https://github.com/PrismLauncher/PrismLauncher/issues/1740
}
case ResourceType::ZIPFILE: {
- QuaZip zip(pack.fileinfo().filePath());
+ QuaZip zip(pack->fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip))
return false; // can't open zip file
@@ -353,28 +186,7 @@ bool processPackPNG(const ResourcePack& pack)
bool validate(QFileInfo file)
{
ResourcePack rp{ file };
- return ResourcePackUtils::process(rp, ProcessingLevel::BasicInfoOnly) && rp.valid();
+ return DataPackUtils::process(&rp, DataPackUtils::ProcessingLevel::BasicInfoOnly) && rp.valid();
}
} // namespace ResourcePackUtils
-
-LocalResourcePackParseTask::LocalResourcePackParseTask(int token, ResourcePack& rp) : Task(false), m_token(token), m_resource_pack(rp) {}
-
-bool LocalResourcePackParseTask::abort()
-{
- m_aborted = true;
- return true;
-}
-
-void LocalResourcePackParseTask::executeTask()
-{
- if (!ResourcePackUtils::process(m_resource_pack)) {
- emitFailed("this is not a resource pack");
- return;
- }
-
- if (m_aborted)
- emitAborted();
- else
- emitSucceeded();
-}
diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h
index 97bf7b2ba..6b4378aa6 100644
--- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h
+++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h
@@ -23,44 +23,14 @@
#include "minecraft/mod/ResourcePack.h"
-#include "tasks/Task.h"
-
namespace ResourcePackUtils {
-enum class ProcessingLevel { Full, BasicInfoOnly };
-
-bool process(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
-
-bool processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
-bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
-
QString processComponent(const QJsonValue& value, bool strikethrough = false, bool underline = false);
-bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data);
-bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data);
+bool processPackPNG(const ResourcePack* pack, QByteArray&& raw_data);
/// processes ONLY the pack.png (rest of the pack may be invalid)
-bool processPackPNG(const ResourcePack& pack);
+bool processPackPNG(const ResourcePack* pack);
/** Checks whether a file is valid as a resource pack or not. */
bool validate(QFileInfo file);
} // namespace ResourcePackUtils
-
-class LocalResourcePackParseTask : public Task {
- Q_OBJECT
- public:
- LocalResourcePackParseTask(int token, ResourcePack& rp);
-
- [[nodiscard]] bool canAbort() const override { return true; }
- bool abort() override;
-
- void executeTask() override;
-
- [[nodiscard]] int token() const { return m_token; }
-
- private:
- int m_token;
-
- ResourcePack& m_resource_pack;
-
- bool m_aborted = false;
-};
diff --git a/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp b/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp
index 74d8d8d60..d45f537fa 100644
--- a/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp
@@ -180,8 +180,10 @@ bool LocalWorldSaveParseTask::abort()
void LocalWorldSaveParseTask::executeTask()
{
- if (!WorldSaveUtils::process(m_save))
+ if (!WorldSaveUtils::process(m_save)) {
+ emitFailed("this is not a world");
return;
+ }
if (m_aborted)
emitAborted();
diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.h b/launcher/modplatform/atlauncher/ATLPackInstallTask.h
index ffc358fbb..ee5960e30 100644
--- a/launcher/modplatform/atlauncher/ATLPackInstallTask.h
+++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.h
@@ -62,7 +62,7 @@ class UserInteractionSupport {
/**
* Requests a user interaction to select which optional mods should be installed.
*/
- virtual std::optional> chooseOptionalMods(PackVersion version, QVector mods) = 0;
+ virtual std::optional> chooseOptionalMods(const PackVersion& version, QVector mods) = 0;
/**
* Requests a user interaction to select a component version from a given version list
diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp
index 63b7d2b50..5f812d219 100644
--- a/launcher/modplatform/flame/FileResolvingTask.cpp
+++ b/launcher/modplatform/flame/FileResolvingTask.cpp
@@ -20,7 +20,6 @@
#include
#include "Json.h"
-#include "QObjectPtr.h"
#include "modplatform/ModIndex.h"
#include "modplatform/flame/FlameAPI.h"
#include "modplatform/flame/FlameModIndex.h"
@@ -33,9 +32,7 @@
static const FlameAPI flameAPI;
static ModrinthAPI modrinthAPI;
-Flame::FileResolvingTask::FileResolvingTask(const shared_qobject_ptr& network, Flame::Manifest& toProcess)
- : m_network(network), m_manifest(toProcess)
-{}
+Flame::FileResolvingTask::FileResolvingTask(Flame::Manifest& toProcess) : m_manifest(toProcess) {}
bool Flame::FileResolvingTask::abort()
{
diff --git a/launcher/modplatform/flame/FileResolvingTask.h b/launcher/modplatform/flame/FileResolvingTask.h
index edd9fce9a..3fe8dfb1a 100644
--- a/launcher/modplatform/flame/FileResolvingTask.h
+++ b/launcher/modplatform/flame/FileResolvingTask.h
@@ -17,8 +17,6 @@
*/
#pragma once
-#include
-
#include "PackManifest.h"
#include "tasks/Task.h"
@@ -26,7 +24,7 @@ namespace Flame {
class FileResolvingTask : public Task {
Q_OBJECT
public:
- explicit FileResolvingTask(const shared_qobject_ptr& network, Flame::Manifest& toProcess);
+ explicit FileResolvingTask(Flame::Manifest& toProcess);
virtual ~FileResolvingTask() = default;
bool canAbort() const override { return true; }
@@ -44,7 +42,6 @@ class FileResolvingTask : public Task {
void getFlameProjects();
private: /* data */
- shared_qobject_ptr m_network;
Flame::Manifest m_manifest;
std::shared_ptr m_result;
Task::Ptr m_task;
diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp
index 699eb792a..a06793de0 100644
--- a/launcher/modplatform/flame/FlameAPI.cpp
+++ b/launcher/modplatform/flame/FlameAPI.cpp
@@ -102,57 +102,6 @@ QString FlameAPI::getModDescription(int modId)
return description;
}
-QList FlameAPI::getLatestVersions(VersionSearchArgs&& args)
-{
- auto versions_url_optional = getVersionsURL(args);
- if (!versions_url_optional.has_value())
- return {};
-
- auto versions_url = versions_url_optional.value();
-
- QEventLoop loop;
-
- auto netJob = makeShared(QString("Flame::GetLatestVersion(%1)").arg(args.pack.name), APPLICATION->network());
- auto response = std::make_shared();
- QList ver;
-
- netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response));
-
- QObject::connect(netJob.get(), &NetJob::succeeded, [response, args, &ver] {
- QJsonParseError parse_error{};
- QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
- if (parse_error.error != QJsonParseError::NoError) {
- qWarning() << "Error while parsing JSON response from latest mod version at " << parse_error.offset
- << " reason: " << parse_error.errorString();
- qWarning() << *response;
- return;
- }
-
- try {
- auto obj = Json::requireObject(doc);
- auto arr = Json::requireArray(obj, "data");
-
- for (auto file : arr) {
- auto file_obj = Json::requireObject(file);
- ver.append(FlameMod::loadIndexedPackVersion(file_obj));
- }
-
- } catch (Json::JsonException& e) {
- qCritical() << "Failed to parse response from a version request.";
- qCritical() << e.what();
- qDebug() << doc;
- }
- });
-
- QObject::connect(netJob.get(), &NetJob::finished, &loop, &QEventLoop::quit);
-
- netJob->start();
-
- loop.exec();
-
- return ver;
-}
-
Task::Ptr FlameAPI::getProjects(QStringList addonIds, std::shared_ptr response) const
{
auto netJob = makeShared(QString("Flame::GetProjects"), APPLICATION->network());
@@ -266,7 +215,7 @@ QList FlameAPI::loadModCategories(std::shared_ptr FlameAPI::getLatestVersion(QList versions,
+std::optional FlameAPI::getLatestVersion(QVector versions,
QList instanceLoaders,
ModPlatform::ModLoaderTypes modLoaders)
{
diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h
index 3ca0d5448..509e1abcd 100644
--- a/launcher/modplatform/flame/FlameAPI.h
+++ b/launcher/modplatform/flame/FlameAPI.h
@@ -15,8 +15,7 @@ class FlameAPI : public NetworkResourceAPI {
QString getModFileChangelog(int modId, int fileId);
QString getModDescription(int modId);
- QList getLatestVersions(VersionSearchArgs&& args);
- std::optional getLatestVersion(QList versions,
+ std::optional getLatestVersion(QVector versions,
QList instanceLoaders,
ModPlatform::ModLoaderTypes fallback);
@@ -108,12 +107,6 @@ class FlameAPI : public NetworkResourceAPI {
return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&');
}
- private:
- [[nodiscard]] std::optional getInfoURL(QString const& id) const override
- {
- return QString("https://api.curseforge.com/v1/mods/%1").arg(id);
- }
-
[[nodiscard]] std::optional getVersionsURL(VersionSearchArgs const& args) const override
{
auto addonId = args.pack.addonId.toString();
@@ -129,6 +122,11 @@ class FlameAPI : public NetworkResourceAPI {
return url;
}
+ private:
+ [[nodiscard]] std::optional getInfoURL(QString const& id) const override
+ {
+ return QString("https://api.curseforge.com/v1/mods/%1").arg(id);
+ }
[[nodiscard]] std::optional getDependencyURL(DependencySearchArgs const& args) const override
{
auto addonId = args.dependency.addonId.toString();
diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp
index 2b469276d..047813675 100644
--- a/launcher/modplatform/flame/FlameCheckUpdate.cpp
+++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp
@@ -3,115 +3,31 @@
#include "FlameAPI.h"
#include "FlameModIndex.h"
-#include
+#include
#include
#include "Json.h"
+#include "QObjectPtr.h"
#include "ResourceDownloadTask.h"
-#include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/tasks/GetModDependenciesTask.h"
+#include "modplatform/ModIndex.h"
#include "net/ApiDownload.h"
+#include "net/NetJob.h"
+#include "tasks/Task.h"
static FlameAPI api;
bool FlameCheckUpdate::abort()
{
- m_was_aborted = true;
- if (m_net_job)
- return m_net_job->abort();
- return true;
-}
-
-ModPlatform::IndexedPack FlameCheckUpdate::getProjectInfo(ModPlatform::IndexedVersion& ver_info)
-{
- ModPlatform::IndexedPack pack;
-
- QEventLoop loop;
-
- auto get_project_job = new NetJob("Flame::GetProjectJob", APPLICATION->network());
-
- auto response = std::make_shared();
- auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(ver_info.addonId.toString());
- auto dl = Net::ApiDownload::makeByteArray(url, response);
- get_project_job->addNetAction(dl);
-
- QObject::connect(get_project_job, &NetJob::succeeded, [response, &pack]() {
- QJsonParseError parse_error{};
- QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
- if (parse_error.error != QJsonParseError::NoError) {
- qWarning() << "Error while parsing JSON response from FlameCheckUpdate at " << parse_error.offset
- << " reason: " << parse_error.errorString();
- qWarning() << *response;
- return;
- }
-
- try {
- auto doc_obj = Json::requireObject(doc);
- auto data_obj = Json::requireObject(doc_obj, "data");
- FlameMod::loadIndexedPack(pack, data_obj);
- } catch (Json::JsonException& e) {
- qWarning() << e.cause();
- qDebug() << doc;
- }
- });
-
- connect(get_project_job, &NetJob::failed, this, &FlameCheckUpdate::emitFailed);
- QObject::connect(get_project_job, &NetJob::finished, [&loop, get_project_job] {
- get_project_job->deleteLater();
- loop.quit();
- });
-
- get_project_job->start();
- loop.exec();
-
- return pack;
-}
-
-ModPlatform::IndexedVersion FlameCheckUpdate::getFileInfo(int addonId, int fileId)
-{
- ModPlatform::IndexedVersion ver;
-
- QEventLoop loop;
-
- auto get_file_info_job = new NetJob("Flame::GetFileInfoJob", APPLICATION->network());
-
- auto response = std::make_shared();
- auto url = QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(QString::number(addonId), QString::number(fileId));
- auto dl = Net::ApiDownload::makeByteArray(url, response);
- get_file_info_job->addNetAction(dl);
-
- QObject::connect(get_file_info_job, &NetJob::succeeded, [response, &ver]() {
- QJsonParseError parse_error{};
- QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
- if (parse_error.error != QJsonParseError::NoError) {
- qWarning() << "Error while parsing JSON response from FlameCheckUpdate at " << parse_error.offset
- << " reason: " << parse_error.errorString();
- qWarning() << *response;
- return;
- }
-
- try {
- auto doc_obj = Json::requireObject(doc);
- auto data_obj = Json::requireObject(doc_obj, "data");
- ver = FlameMod::loadIndexedPackVersion(data_obj);
- } catch (Json::JsonException& e) {
- qWarning() << e.cause();
- qDebug() << doc;
- }
- });
- connect(get_file_info_job, &NetJob::failed, this, &FlameCheckUpdate::emitFailed);
- QObject::connect(get_file_info_job, &NetJob::finished, [&loop, get_file_info_job] {
- get_file_info_job->deleteLater();
- loop.quit();
- });
-
- get_file_info_job->start();
- loop.exec();
-
- return ver;
+ bool result = false;
+ if (m_task && m_task->canAbort()) {
+ result = m_task->abort();
+ }
+ Task::abort();
+ return result;
}
/* Check for update:
@@ -123,66 +39,163 @@ void FlameCheckUpdate::executeTask()
{
setStatus(tr("Preparing resources for CurseForge..."));
- int i = 0;
+ auto netJob = new NetJob("Get latest versions", APPLICATION->network());
+ connect(netJob, &Task::finished, this, &FlameCheckUpdate::collectBlockedMods);
+
+ connect(netJob, &Task::progress, this, &FlameCheckUpdate::setProgress);
+ connect(netJob, &Task::stepProgress, this, &FlameCheckUpdate::propagateStepProgress);
+ connect(netJob, &Task::details, this, &FlameCheckUpdate::setDetails);
for (auto* resource : m_resources) {
- setStatus(tr("Getting API response from CurseForge for '%1'...").arg(resource->name()));
- setProgress(i++, m_resources.size());
-
- auto latest_vers = api.getLatestVersions({ { resource->metadata()->project_id.toString() }, m_game_versions });
-
- // Check if we were aborted while getting the latest version
- if (m_was_aborted) {
- aborted();
- return;
- }
- auto latest_ver = api.getLatestVersion(latest_vers, m_loaders_list, resource->metadata()->loaders);
-
- setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(resource->name()));
-
- if (!latest_ver.has_value() || !latest_ver->addonId.isValid()) {
- QString reason;
- if (dynamic_cast(resource) != nullptr)
- reason =
- tr("No valid version found for this resource. It's probably unavailable for the current game "
- "version / mod loader.");
- else
- reason = tr("No valid version found for this resource. It's probably unavailable for the current game version.");
-
- emit checkFailed(resource, reason);
+ auto versions_url_optional = api.getVersionsURL({ { resource->metadata()->project_id.toString() }, m_game_versions });
+ if (!versions_url_optional.has_value())
continue;
- }
- if (latest_ver->downloadUrl.isEmpty() && latest_ver->fileId != resource->metadata()->file_id) {
- auto pack = getProjectInfo(latest_ver.value());
- auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, latest_ver->fileId.toString());
- emit checkFailed(resource, tr("Resource has a new update available, but is not downloadable using CurseForge."), recover_url);
+ auto response = std::make_shared();
+ auto task = Net::ApiDownload::makeByteArray(versions_url_optional.value(), response);
- continue;
- }
+ connect(task.get(), &Task::succeeded, this, [this, resource, response] { getLatestVersionCallback(resource, response); });
+ netJob->addNetAction(task);
+ }
+ m_task.reset(netJob);
+ m_task->start();
+}
- // Fake pack with the necessary info to pass to the download task :)
- auto pack = std::make_shared();
- pack->name = resource->name();
- pack->slug = resource->metadata()->slug;
- pack->addonId = resource->metadata()->project_id;
- pack->provider = ModPlatform::ResourceProvider::FLAME;
- if (!latest_ver->hash.isEmpty() &&
- (resource->metadata()->hash != latest_ver->hash || resource->status() == ResourceStatus::NOT_INSTALLED)) {
- auto old_version = resource->metadata()->version_number;
- if (old_version.isEmpty()) {
- if (resource->status() == ResourceStatus::NOT_INSTALLED)
- old_version = tr("Not installed");
- else
- old_version = tr("Unknown");
- }
-
- auto download_task = makeShared(pack, latest_ver.value(), m_resource_model);
- m_updates.emplace_back(pack->name, resource->metadata()->hash, old_version, latest_ver->version, latest_ver->version_type,
- api.getModFileChangelog(latest_ver->addonId.toInt(), latest_ver->fileId.toInt()),
- ModPlatform::ResourceProvider::FLAME, download_task, resource->enabled());
- }
- m_deps.append(std::make_shared(pack, latest_ver.value()));
+void FlameCheckUpdate::getLatestVersionCallback(Resource* resource, std::shared_ptr response)
+{
+ QJsonParseError parse_error{};
+ QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
+ if (parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from latest mod version at " << parse_error.offset
+ << " reason: " << parse_error.errorString();
+ qWarning() << *response;
+ return;
}
- emitSucceeded();
+ // Fake pack with the necessary info to pass to the download task :)
+ auto pack = std::make_shared();
+ pack->name = resource->name();
+ pack->slug = resource->metadata()->slug;
+ pack->addonId = resource->metadata()->project_id;
+ pack->provider = ModPlatform::ResourceProvider::FLAME;
+ try {
+ auto obj = Json::requireObject(doc);
+ auto arr = Json::requireArray(obj, "data");
+
+ FlameMod::loadIndexedPackVersions(*pack.get(), arr);
+ } catch (Json::JsonException& e) {
+ qCritical() << "Failed to parse response from a version request.";
+ qCritical() << e.what();
+ qDebug() << doc;
+ }
+ auto latest_ver = api.getLatestVersion(pack->versions, m_loaders_list, resource->metadata()->loaders);
+
+ setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(resource->name()));
+
+ if (!latest_ver.has_value() || !latest_ver->addonId.isValid()) {
+ QString reason;
+ if (dynamic_cast(resource) != nullptr)
+ reason =
+ tr("No valid version found for this resource. It's probably unavailable for the current game "
+ "version / mod loader.");
+ else
+ reason = tr("No valid version found for this resource. It's probably unavailable for the current game version.");
+
+ emit checkFailed(resource, reason);
+ return;
+ }
+
+ if (latest_ver->downloadUrl.isEmpty() && latest_ver->fileId != resource->metadata()->file_id) {
+ m_blocked[resource] = latest_ver->fileId.toString();
+ return;
+ }
+
+ if (!latest_ver->hash.isEmpty() &&
+ (resource->metadata()->hash != latest_ver->hash || resource->status() == ResourceStatus::NOT_INSTALLED)) {
+ auto old_version = resource->metadata()->version_number;
+ if (old_version.isEmpty()) {
+ if (resource->status() == ResourceStatus::NOT_INSTALLED)
+ old_version = tr("Not installed");
+ else
+ old_version = tr("Unknown");
+ }
+
+ auto download_task = makeShared(pack, latest_ver.value(), m_resource_model);
+ m_updates.emplace_back(pack->name, resource->metadata()->hash, old_version, latest_ver->version, latest_ver->version_type,
+ api.getModFileChangelog(latest_ver->addonId.toInt(), latest_ver->fileId.toInt()),
+ ModPlatform::ResourceProvider::FLAME, download_task, resource->enabled());
+ }
+ m_deps.append(std::make_shared(pack, latest_ver.value()));
}
+
+void FlameCheckUpdate::collectBlockedMods()
+{
+ QStringList addonIds;
+ QHash quickSearch;
+ for (auto const& resource : m_blocked.keys()) {
+ auto addonId = resource->metadata()->project_id.toString();
+ addonIds.append(addonId);
+ quickSearch[addonId] = resource;
+ }
+
+ auto response = std::make_shared();
+ Task::Ptr projTask;
+
+ if (addonIds.isEmpty()) {
+ emitSucceeded();
+ return;
+ } else if (addonIds.size() == 1) {
+ projTask = api.getProject(*addonIds.begin(), response);
+ } else {
+ projTask = api.getProjects(addonIds, response);
+ }
+
+ connect(projTask.get(), &Task::succeeded, this, [this, response, addonIds, quickSearch] {
+ QJsonParseError parse_error{};
+ auto doc = QJsonDocument::fromJson(*response, &parse_error);
+ if (parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from Flame projects task at " << parse_error.offset
+ << " reason: " << parse_error.errorString();
+ qWarning() << *response;
+ return;
+ }
+
+ try {
+ QJsonArray entries;
+ if (addonIds.size() == 1)
+ entries = { Json::requireObject(Json::requireObject(doc), "data") };
+ else
+ entries = Json::requireArray(Json::requireObject(doc), "data");
+
+ for (auto entry : entries) {
+ auto entry_obj = Json::requireObject(entry);
+
+ auto id = QString::number(Json::requireInteger(entry_obj, "id"));
+
+ auto resource = quickSearch.find(id).value();
+
+ ModPlatform::IndexedPack pack;
+ try {
+ setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(resource->name()));
+
+ FlameMod::loadIndexedPack(pack, entry_obj);
+ auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, m_blocked[resource]);
+ emit checkFailed(resource, tr("Resource has a new update available, but is not downloadable using CurseForge."),
+ recover_url);
+ } catch (Json::JsonException& e) {
+ qDebug() << e.cause();
+ qDebug() << entries;
+ }
+ }
+ } catch (Json::JsonException& e) {
+ qDebug() << e.cause();
+ qDebug() << doc;
+ }
+ });
+
+ connect(projTask.get(), &Task::finished, this, &FlameCheckUpdate::emitSucceeded); // do not care much about error
+ connect(projTask.get(), &Task::progress, this, &FlameCheckUpdate::setProgress);
+ connect(projTask.get(), &Task::stepProgress, this, &FlameCheckUpdate::propagateStepProgress);
+ connect(projTask.get(), &Task::details, this, &FlameCheckUpdate::setDetails);
+ m_task.reset(projTask);
+ m_task->start();
+}
\ No newline at end of file
diff --git a/launcher/modplatform/flame/FlameCheckUpdate.h b/launcher/modplatform/flame/FlameCheckUpdate.h
index 6543a0e04..eb80ce47c 100644
--- a/launcher/modplatform/flame/FlameCheckUpdate.h
+++ b/launcher/modplatform/flame/FlameCheckUpdate.h
@@ -1,7 +1,6 @@
#pragma once
#include "modplatform/CheckUpdateTask.h"
-#include "net/NetJob.h"
class FlameCheckUpdate : public CheckUpdateTask {
Q_OBJECT
@@ -19,12 +18,12 @@ class FlameCheckUpdate : public CheckUpdateTask {
protected slots:
void executeTask() override;
+ private slots:
+ void getLatestVersionCallback(Resource* resource, std::shared_ptr response);
+ void collectBlockedMods();
private:
- ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info);
- ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId);
+ Task::Ptr m_task = nullptr;
- NetJob* m_net_job = nullptr;
-
- bool m_was_aborted = false;
+ QHash m_blocked;
};
diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp
index 16d47f64a..22c9e603b 100644
--- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp
+++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp
@@ -442,7 +442,7 @@ bool FlameCreationTask::createInstance()
instance.setName(name());
- m_modIdResolver.reset(new Flame::FileResolvingTask(APPLICATION->network(), m_pack));
+ m_modIdResolver.reset(new Flame::FileResolvingTask(m_pack));
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, this, [this, &loop] { idResolverSucceeded(loop); });
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [this, &loop](QString reason) {
m_modIdResolver.reset();
diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp
index eb5b3cc67..ff9d2d9ce 100644
--- a/launcher/modplatform/flame/FlameModIndex.cpp
+++ b/launcher/modplatform/flame/FlameModIndex.cpp
@@ -76,10 +76,7 @@ static QString enumToString(int hash_algorithm)
}
}
-void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
- QJsonArray& arr,
- [[maybe_unused]] const shared_qobject_ptr& network,
- const BaseInstance* inst)
+void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr)
{
QVector unsortedVersions;
for (auto versionIter : arr) {
diff --git a/launcher/modplatform/flame/FlameModIndex.h b/launcher/modplatform/flame/FlameModIndex.h
index 1bcaa44ba..f6b4b22be 100644
--- a/launcher/modplatform/flame/FlameModIndex.h
+++ b/launcher/modplatform/flame/FlameModIndex.h
@@ -6,7 +6,6 @@
#include "modplatform/ModIndex.h"
-#include
#include "BaseInstance.h"
namespace FlameMod {
@@ -14,10 +13,7 @@ namespace FlameMod {
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj);
void loadURLs(ModPlatform::IndexedPack& m, QJsonObject& obj);
void loadBody(ModPlatform::IndexedPack& m, QJsonObject& obj);
-void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
- QJsonArray& arr,
- const shared_qobject_ptr& network,
- const BaseInstance* inst);
-auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion;
-auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst) -> ModPlatform::IndexedVersion;
+void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr);
+ModPlatform::IndexedVersion loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false);
+ModPlatform::IndexedVersion loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst);
} // namespace FlameMod
\ No newline at end of file
diff --git a/launcher/modplatform/helpers/ExportToModList.cpp b/launcher/modplatform/helpers/ExportToModList.cpp
index bddc7e320..678f89917 100644
--- a/launcher/modplatform/helpers/ExportToModList.cpp
+++ b/launcher/modplatform/helpers/ExportToModList.cpp
@@ -203,6 +203,7 @@ QString exportToModList(QList mods, QString lineTemplate)
for (auto mod : mods) {
auto meta = mod->metadata();
auto modName = mod->name();
+ auto modID = mod->mod_id();
auto url = mod->homepage();
auto ver = mod->version();
if (ver.isEmpty() && meta != nullptr)
@@ -211,6 +212,7 @@ QString exportToModList(QList mods, QString lineTemplate)
auto filename = mod->fileinfo().fileName();
lines << QString(lineTemplate)
.replace("{name}", modName)
+ .replace("{mod_id}", modID)
.replace("{url}", url)
.replace("{version}", ver)
.replace("{authors}", authors)
diff --git a/launcher/modplatform/legacy_ftb/PackFetchTask.h b/launcher/modplatform/legacy_ftb/PackFetchTask.h
index e37d949d5..4c7a8f6aa 100644
--- a/launcher/modplatform/legacy_ftb/PackFetchTask.h
+++ b/launcher/modplatform/legacy_ftb/PackFetchTask.h
@@ -40,7 +40,7 @@ class PackFetchTask : public QObject {
void failed(QString reason);
void aborted();
- void privateFileDownloadFinished(Modpack modpack);
+ void privateFileDownloadFinished(const Modpack& modpack);
void privateFileDownloadFailed(QString reason, QString packCode);
};
diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp
index d6252663f..c04c0b2f3 100644
--- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp
+++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp
@@ -52,7 +52,7 @@
namespace LegacyFTB {
-PackInstallTask::PackInstallTask(shared_qobject_ptr network, Modpack pack, QString version)
+PackInstallTask::PackInstallTask(shared_qobject_ptr network, const Modpack& pack, QString version)
{
m_pack = pack;
m_version = version;
diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.h b/launcher/modplatform/legacy_ftb/PackInstallTask.h
index 30ff48597..42808a1a2 100644
--- a/launcher/modplatform/legacy_ftb/PackInstallTask.h
+++ b/launcher/modplatform/legacy_ftb/PackInstallTask.h
@@ -18,7 +18,7 @@ class PackInstallTask : public InstanceTask {
Q_OBJECT
public:
- explicit PackInstallTask(shared_qobject_ptr network, Modpack pack, QString version);
+ explicit PackInstallTask(shared_qobject_ptr network, const Modpack& pack, QString version);
virtual ~PackInstallTask() {}
bool canAbort() const override { return true; }
diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp
index 72550937c..16b300b02 100644
--- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp
+++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp
@@ -112,7 +112,7 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob
pack.extraDataLoaded = true;
}
-void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const BaseInstance* inst)
+void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr)
{
QVector unsortedVersions;
for (auto versionIter : arr) {
diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.h b/launcher/modplatform/modrinth/ModrinthPackIndex.h
index 93f91eec2..16f3d262c 100644
--- a/launcher/modplatform/modrinth/ModrinthPackIndex.h
+++ b/launcher/modplatform/modrinth/ModrinthPackIndex.h
@@ -19,14 +19,13 @@
#include "modplatform/ModIndex.h"
-#include
#include "BaseInstance.h"
namespace Modrinth {
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj);
void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj);
-void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const BaseInstance* inst);
+void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr);
auto loadIndexedPackVersion(QJsonObject& obj, QString hash_type = "sha512", QString filename_prefer = "") -> ModPlatform::IndexedVersion;
auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst) -> ModPlatform::IndexedVersion;
diff --git a/launcher/net/FileSink.cpp b/launcher/net/FileSink.cpp
index 95c1a8f44..3a58a4667 100644
--- a/launcher/net/FileSink.cpp
+++ b/launcher/net/FileSink.cpp
@@ -54,7 +54,7 @@ Task::State FileSink::init(QNetworkRequest& request)
return Task::State::Failed;
}
- wroteAnyData = false;
+ m_wroteAnyData = false;
m_output_file.reset(new PSaveFile(m_filename));
if (!m_output_file->open(QIODevice::WriteOnly)) {
qCCritical(taskNetLogC) << "Could not open " + m_filename + " for writing";
@@ -72,17 +72,19 @@ Task::State FileSink::write(QByteArray& data)
qCCritical(taskNetLogC) << "Failed writing into " + m_filename;
m_output_file->cancelWriting();
m_output_file.reset();
- wroteAnyData = false;
+ m_wroteAnyData = false;
return Task::State::Failed;
}
- wroteAnyData = true;
+ m_wroteAnyData = true;
return Task::State::Running;
}
Task::State FileSink::abort()
{
- m_output_file->cancelWriting();
+ if (m_output_file) {
+ m_output_file->cancelWriting();
+ }
failAllValidators();
return Task::State::Failed;
}
@@ -100,7 +102,7 @@ Task::State FileSink::finalize(QNetworkReply& reply)
// if we wrote any data to the save file, we try to commit the data to the real file.
// if it actually got a proper file, we write it even if it was empty
- if (gotFile || wroteAnyData) {
+ if (gotFile || m_wroteAnyData) {
// ask validators for data consistency
// we only do this for actual downloads, not 'your data is still the same' cache hits
if (!finalizeAllValidators(reply))
diff --git a/launcher/net/FileSink.h b/launcher/net/FileSink.h
index 272f8ddc3..67c25361c 100644
--- a/launcher/net/FileSink.h
+++ b/launcher/net/FileSink.h
@@ -58,7 +58,7 @@ class FileSink : public Sink {
protected:
QString m_filename;
- bool wroteAnyData = false;
+ bool m_wroteAnyData = false;
std::unique_ptr m_output_file;
};
} // namespace Net
diff --git a/launcher/net/MetaCacheSink.cpp b/launcher/net/MetaCacheSink.cpp
index 889588a11..432c0c84b 100644
--- a/launcher/net/MetaCacheSink.cpp
+++ b/launcher/net/MetaCacheSink.cpp
@@ -78,7 +78,7 @@ Task::State MetaCacheSink::finalizeCache(QNetworkReply& reply)
{
QFileInfo output_file_info(m_filename);
- if (wroteAnyData) {
+ if (m_wroteAnyData) {
m_entry->setMD5Sum(m_md5Node->hash().toHex().constData());
}
diff --git a/launcher/net/NetRequest.cpp b/launcher/net/NetRequest.cpp
index cf2e72858..310653508 100644
--- a/launcher/net/NetRequest.cpp
+++ b/launcher/net/NetRequest.cpp
@@ -80,7 +80,7 @@ void NetRequest::executeTask()
emit finished();
return;
case State::Running:
- qCDebug(logCat) << getUid().toString() << "Runninng " << m_url.toString();
+ qCDebug(logCat) << getUid().toString() << "Running " << m_url.toString();
break;
case State::Inactive:
case State::Failed:
diff --git a/launcher/pathmatcher/MultiMatcher.h b/launcher/pathmatcher/MultiMatcher.h
index 3e2bdb95d..ccd5a9163 100644
--- a/launcher/pathmatcher/MultiMatcher.h
+++ b/launcher/pathmatcher/MultiMatcher.h
@@ -1,3 +1,5 @@
+#pragma once
+
#include
#include
#include "IPathMatcher.h"
diff --git a/launcher/pathmatcher/RegexpMatcher.h b/launcher/pathmatcher/RegexpMatcher.h
index a6a3e616d..18c42f887 100644
--- a/launcher/pathmatcher/RegexpMatcher.h
+++ b/launcher/pathmatcher/RegexpMatcher.h
@@ -1,3 +1,5 @@
+#pragma once
+
#include
#include "IPathMatcher.h"
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 8b41f838c..f29d5ce89 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -123,6 +123,7 @@
#include "KonamiCode.h"
#include "InstanceCopyTask.h"
+#include "InstanceDirUpdate.h"
#include "Json.h"
@@ -288,10 +289,27 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
view->setSelectionMode(QAbstractItemView::SingleSelection);
// FIXME: leaks ListViewDelegate
- view->setItemDelegate(new ListViewDelegate(this));
+ auto delegate = new ListViewDelegate(this);
+ view->setItemDelegate(delegate);
view->setFrameShape(QFrame::NoFrame);
// do not show ugly blue border on the mac
view->setAttribute(Qt::WA_MacShowFocusRect, false);
+ connect(delegate, &ListViewDelegate::textChanged, this, [this](QString before, QString after) {
+ if (auto newRoot = askToUpdateInstanceDirName(m_selectedInstance, before, after, this); !newRoot.isEmpty()) {
+ auto oldID = m_selectedInstance->id();
+ auto newID = QFileInfo(newRoot).fileName();
+ QString origGroup(APPLICATION->instances()->getInstanceGroup(oldID));
+ bool syncGroup = origGroup != GroupId() && oldID != newID;
+ if (syncGroup)
+ APPLICATION->instances()->setInstanceGroup(oldID, GroupId());
+
+ refreshInstances();
+ setSelectedInstanceById(newID);
+
+ if (syncGroup)
+ APPLICATION->instances()->setInstanceGroup(newID, origGroup);
+ }
+ });
view->installEventFilter(this);
view->setContextMenuPolicy(Qt::CustomContextMenu);
@@ -1377,20 +1395,8 @@ void MainWindow::on_actionDeleteInstance_triggered()
if (response != QMessageBox::Yes)
return;
- auto linkedInstances = APPLICATION->instances()->getLinkedInstancesById(id);
- if (!linkedInstances.empty()) {
- response = CustomMessageBox::selectable(this, tr("There are linked instances"),
- tr("The following instance(s) might reference files in this instance:\n\n"
- "%1\n\n"
- "Deleting it could break the other instance(s), \n\n"
- "Do you wish to proceed?",
- nullptr, linkedInstances.count())
- .arg(linkedInstances.join("\n")),
- QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
- ->exec();
- if (response != QMessageBox::Yes)
- return;
- }
+ if (!checkLinkedInstances(id, this, tr("Deleting")))
+ return;
if (APPLICATION->instances()->trashInstance(id)) {
ui->actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething());
diff --git a/launcher/ui/dialogs/CustomMessageBox.cpp b/launcher/ui/dialogs/CustomMessageBox.cpp
index 1af47a449..ca0fe99e0 100644
--- a/launcher/ui/dialogs/CustomMessageBox.cpp
+++ b/launcher/ui/dialogs/CustomMessageBox.cpp
@@ -21,7 +21,8 @@ QMessageBox* selectable(QWidget* parent,
const QString& text,
QMessageBox::Icon icon,
QMessageBox::StandardButtons buttons,
- QMessageBox::StandardButton defaultButton)
+ QMessageBox::StandardButton defaultButton,
+ QCheckBox* checkBox)
{
QMessageBox* messageBox = new QMessageBox(parent);
messageBox->setWindowTitle(title);
@@ -31,6 +32,8 @@ QMessageBox* selectable(QWidget* parent,
messageBox->setTextInteractionFlags(Qt::TextSelectableByMouse);
messageBox->setIcon(icon);
messageBox->setTextInteractionFlags(Qt::TextBrowserInteraction);
+ if (checkBox)
+ messageBox->setCheckBox(checkBox);
return messageBox;
}
diff --git a/launcher/ui/dialogs/CustomMessageBox.h b/launcher/ui/dialogs/CustomMessageBox.h
index a9bc6a24a..1ee29903e 100644
--- a/launcher/ui/dialogs/CustomMessageBox.h
+++ b/launcher/ui/dialogs/CustomMessageBox.h
@@ -23,5 +23,6 @@ QMessageBox* selectable(QWidget* parent,
const QString& text,
QMessageBox::Icon icon = QMessageBox::NoIcon,
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
- QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
+ QMessageBox::StandardButton defaultButton = QMessageBox::NoButton,
+ QCheckBox* checkBox = nullptr);
}
diff --git a/launcher/ui/dialogs/ExportToModListDialog.ui b/launcher/ui/dialogs/ExportToModListDialog.ui
index 3afda2fa8..ec049d7e7 100644
--- a/launcher/ui/dialogs/ExportToModListDialog.ui
+++ b/launcher/ui/dialogs/ExportToModListDialog.ui
@@ -79,6 +79,9 @@
0
+
+ This text supports the following placeholders:
{name} - Mod name
{mod_id} - Mod ID
{url} - Mod URL
{version} - Mod version
{authors} - Mod authors
+
diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp
index 9897687e3..aa2f67bdb 100644
--- a/launcher/ui/dialogs/ProgressDialog.cpp
+++ b/launcher/ui/dialogs/ProgressDialog.cpp
@@ -92,7 +92,7 @@ ProgressDialog::~ProgressDialog()
{
for (auto conn : this->m_taskConnections) {
disconnect(conn);
- }
+ }
delete ui;
}
diff --git a/launcher/ui/dialogs/ProgressDialog.h b/launcher/ui/dialogs/ProgressDialog.h
index 4a696a49d..50e4418da 100644
--- a/launcher/ui/dialogs/ProgressDialog.h
+++ b/launcher/ui/dialogs/ProgressDialog.h
@@ -93,7 +93,7 @@ class ProgressDialog : public QDialog {
Ui::ProgressDialog* ui;
Task* m_task;
-
+
QList m_taskConnections;
bool m_is_multi_step = false;
diff --git a/launcher/ui/dialogs/ResourceUpdateDialog.cpp b/launcher/ui/dialogs/ResourceUpdateDialog.cpp
index 7e29e1192..aa4bbd294 100644
--- a/launcher/ui/dialogs/ResourceUpdateDialog.cpp
+++ b/launcher/ui/dialogs/ResourceUpdateDialog.cpp
@@ -282,6 +282,7 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool
bool skip_rest = false;
ModPlatform::ResourceProvider provider_rest = ModPlatform::ResourceProvider::MODRINTH;
+ // adds resource to list based on provider
auto addToTmp = [&modrinth_tmp, &flame_tmp](Resource* resource, ModPlatform::ResourceProvider p) {
switch (p) {
case ModPlatform::ResourceProvider::MODRINTH:
@@ -293,6 +294,7 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool
}
};
+ // ask the user on what provider to seach for the mod first
for (auto candidate : m_candidates) {
if (candidate->status() != ResourceStatus::NO_METADATA) {
onMetadataEnsured(candidate);
@@ -335,6 +337,7 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool
addToTmp(candidate, response.chosen);
}
+ // prepare task for the modrinth mods
if (!modrinth_tmp.empty()) {
auto modrinth_task = makeShared(modrinth_tmp, index_dir, ModPlatform::ResourceProvider::MODRINTH);
connect(modrinth_task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); });
@@ -350,6 +353,7 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool
seq.addTask(modrinth_task);
}
+ // prepare task for the flame mods
if (!flame_tmp.empty()) {
auto flame_task = makeShared(flame_tmp, index_dir, ModPlatform::ResourceProvider::FLAME);
connect(flame_task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); });
@@ -367,6 +371,7 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool
seq.addTask(m_second_try_metadata);
+ // execute all the tasks
ProgressDialog checking_dialog(m_parent);
checking_dialog.setSkipButton(true, tr("Abort"));
checking_dialog.setWindowTitle(tr("Generating metadata..."));
@@ -477,13 +482,8 @@ void ResourceUpdateDialog::appendResource(CheckUpdateTask::Update const& info, Q
auto changelog_area = new QTextBrowser();
QString text = info.changelog;
- switch (info.provider) {
- case ModPlatform::ResourceProvider::MODRINTH: {
- text = markdownToHTML(info.changelog.toUtf8());
- break;
- }
- default:
- break;
+ if (info.provider == ModPlatform::ResourceProvider::MODRINTH) {
+ text = markdownToHTML(info.changelog.toUtf8());
}
changelog_area->setHtml(StringUtils::htmlListPatch(text));
diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp
index 2127fc9cc..3bc0bc2d9 100644
--- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp
+++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp
@@ -223,6 +223,8 @@ void SkinManageDialog::on_capeCombo_currentIndexChanged(int index)
auto cape = m_capes.value(id.toString(), {});
if (!cape.isNull()) {
m_ui->capeImage->setPixmap(previewCape(cape).scaled(size() * (1. / 3), Qt::KeepAspectRatio, Qt::FastTransformation));
+ } else {
+ m_ui->capeImage->clear();
}
m_skinPreview->updateCape(cape);
if (auto skin = getSelectedSkin(); skin) {
@@ -522,6 +524,8 @@ void SkinManageDialog::resizeEvent(QResizeEvent* event)
auto cape = m_capes.value(id.toString(), {});
if (!cape.isNull()) {
m_ui->capeImage->setPixmap(previewCape(cape).scaled(s, Qt::KeepAspectRatio, Qt::FastTransformation));
+ } else {
+ m_ui->capeImage->clear();
}
}
diff --git a/launcher/ui/instanceview/InstanceDelegate.cpp b/launcher/ui/instanceview/InstanceDelegate.cpp
index d947163bc..c7115801e 100644
--- a/launcher/ui/instanceview/InstanceDelegate.cpp
+++ b/launcher/ui/instanceview/InstanceDelegate.cpp
@@ -397,6 +397,7 @@ void ListViewDelegate::setModelData(QWidget* editor, QAbstractItemModel* model,
// Prevent instance names longer than 128 chars
text.truncate(128);
if (text.size() != 0) {
+ emit textChanged(model->data(index).toString(), text);
model->setData(index, text);
}
}
diff --git a/launcher/ui/instanceview/InstanceDelegate.h b/launcher/ui/instanceview/InstanceDelegate.h
index 69dd32ba7..98ff9a2fc 100644
--- a/launcher/ui/instanceview/InstanceDelegate.h
+++ b/launcher/ui/instanceview/InstanceDelegate.h
@@ -33,6 +33,9 @@ class ListViewDelegate : public QStyledItemDelegate {
void setEditorData(QWidget* editor, const QModelIndex& index) const override;
void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override;
+ signals:
+ void textChanged(QString before, QString after) const;
+
private slots:
void editingDone();
};
diff --git a/launcher/ui/pages/global/AccountListPage.h b/launcher/ui/pages/global/AccountListPage.h
index 4f02b7df5..7bd5101c0 100644
--- a/launcher/ui/pages/global/AccountListPage.h
+++ b/launcher/ui/pages/global/AccountListPage.h
@@ -66,7 +66,7 @@ class AccountListPage : public QMainWindow, public BasePage {
return icon;
}
QString id() const override { return "accounts"; }
- QString helpPage() const override { return "/getting-started/adding-an-account"; }
+ QString helpPage() const override { return "getting-started/adding-an-account"; }
void retranslate() override;
public slots:
diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp
index 36d404660..132a2c320 100644
--- a/launcher/ui/pages/global/LauncherPage.cpp
+++ b/launcher/ui/pages/global/LauncherPage.cpp
@@ -65,6 +65,15 @@ enum InstSortMode {
Sort_LastLaunch
};
+enum InstRenamingMode {
+ // Rename metadata only.
+ Rename_Always,
+ // Ask everytime.
+ Rename_Ask,
+ // Rename physical directory too.
+ Rename_Never
+};
+
LauncherPage::LauncherPage(QWidget* parent) : QWidget(parent), ui(new Ui::LauncherPage)
{
ui->setupUi(this);
@@ -221,6 +230,7 @@ void LauncherPage::applySettings()
s->set("DownloadsDirWatchRecursive", ui->downloadsDirWatchRecursiveCheckBox->isChecked());
s->set("MoveModsFromDownloadsDir", ui->downloadsDirMoveCheckBox->isChecked());
+ // Instance
auto sortMode = (InstSortMode)ui->sortingModeGroup->checkedId();
switch (sortMode) {
case Sort_LastLaunch:
@@ -232,6 +242,20 @@ void LauncherPage::applySettings()
break;
}
+ auto renamingMode = (InstRenamingMode)ui->renamingBehaviorComboBox->currentIndex();
+ switch (renamingMode) {
+ case Rename_Always:
+ s->set("InstRenamingMode", "MetadataOnly");
+ break;
+ case Rename_Never:
+ s->set("InstRenamingMode", "PhysicalDir");
+ break;
+ case Rename_Ask:
+ default:
+ s->set("InstRenamingMode", "AskEverytime");
+ break;
+ }
+
// Mods
s->set("ModMetadataDisabled", !ui->metadataEnableBtn->isChecked());
s->set("ModDependenciesDisabled", !ui->dependenciesEnableBtn->isChecked());
@@ -267,14 +291,25 @@ void LauncherPage::loadSettings()
ui->downloadsDirWatchRecursiveCheckBox->setChecked(s->get("DownloadsDirWatchRecursive").toBool());
ui->downloadsDirMoveCheckBox->setChecked(s->get("MoveModsFromDownloadsDir").toBool());
+ // Instance
QString sortMode = s->get("InstSortMode").toString();
-
if (sortMode == "LastLaunch") {
ui->sortLastLaunchedBtn->setChecked(true);
} else {
ui->sortByNameBtn->setChecked(true);
}
+ QString renamingMode = s->get("InstRenamingMode").toString();
+ InstRenamingMode renamingModeEnum;
+ if (renamingMode == "MetadataOnly") {
+ renamingModeEnum = Rename_Always;
+ } else if (renamingMode == "PhysicalDir") {
+ renamingModeEnum = Rename_Never;
+ } else {
+ renamingModeEnum = Rename_Ask;
+ }
+ ui->renamingBehaviorComboBox->setCurrentIndex(renamingModeEnum);
+
// Mods
ui->metadataEnableBtn->setChecked(!s->get("ModMetadataDisabled").toBool());
ui->metadataWarningLabel->setHidden(ui->metadataEnableBtn->isChecked());
diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui
index 3821c09c4..2d04093d2 100644
--- a/launcher/ui/pages/global/LauncherPage.ui
+++ b/launcher/ui/pages/global/LauncherPage.ui
@@ -43,7 +43,7 @@
0
0
575
- 1294
+ 1368
@@ -99,6 +99,51 @@
+ -
+
+
+ When renaming instances...
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
-
+
+ Ask what to do with the folder
+
+
+ -
+
+ Always rename the folder
+
+
+ -
+
+ Never rename the folder - only the displayed name
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 6
+
+
+
+
-
@@ -185,29 +230,16 @@
Folders
-
-
-
+
-
+
- &Java
+ &Icons
- javaDirTextBox
+ iconsDirTextBox
- -
-
-
- I&nstances
-
-
- instDirTextBox
-
-
-
- -
-
-
-
@@ -218,56 +250,13 @@
- -
-
+
-
+
- &Icons
+ &Java
- iconsDirTextBox
-
-
-
- -
-
-
- -
-
-
- Browse
-
-
-
- -
-
-
- -
-
-
- -
-
-
- Browse
-
-
-
- -
-
-
- -
-
-
- &Mods
-
-
- modsDirTextBox
-
-
-
- -
-
-
- Browse
+ javaDirTextBox
@@ -281,19 +270,18 @@
- -
-
+
-
+
- Browse
+ I&nstances
+
+
+ instDirTextBox
- -
-
-
- Browse
-
-
+
-
+
-
@@ -302,9 +290,66 @@
+ -
+
+
+ -
+
+
+ Browse
+
+
+
+ -
+
+
-
+ -
+
+
+ -
+
+
+ Browse
+
+
+
+ -
+
+
+ Browse
+
+
+
+ -
+
+
+ Browse
+
+
+
+ -
+
+
+ Browse
+
+
+
+ -
+
+
+ &Mods
+
+
+ modsDirTextBox
+
+
+
+ -
+
+
@@ -620,7 +665,6 @@
downloadsDirBrowseBtn
metadataEnableBtn
dependenciesEnableBtn
- sortLastLaunchedBtn
sortByNameBtn
diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp
index a909d10d1..0fccd1d33 100644
--- a/launcher/ui/pages/instance/ManagedPackPage.cpp
+++ b/launcher/ui/pages/instance/ManagedPackPage.cpp
@@ -245,7 +245,6 @@ ModrinthManagedPackPage::ModrinthManagedPackPage(BaseInstance* inst, InstanceWin
}
// MODRINTH
-
void ModrinthManagedPackPage::parseManagedPack()
{
qDebug() << "Parsing Modrinth pack";
@@ -338,6 +337,25 @@ void ModrinthManagedPackPage::suggestVersion()
ManagedPackPage::suggestVersion();
}
+/// @brief Called when the update task has completed.
+/// Internally handles the closing of the instance window if the update was successful and shows a message box.
+/// @param did_succeed Whether the update task was successful.
+void ManagedPackPage::onUpdateTaskCompleted(bool did_succeed) const
+{
+ // Close the window if the update was successful
+ if (did_succeed) {
+ if (m_instance_window != nullptr)
+ m_instance_window->close();
+
+ CustomMessageBox::selectable(nullptr, tr("Update Successful"), tr("The instance updated to pack version %1 successfully.").arg(m_inst->getManagedPackVersionName()), QMessageBox::Information)
+ ->show();
+ } else {
+ CustomMessageBox::selectable(nullptr, tr("Update Failed"), tr("The instance failed to update to pack version %1. Please check launcher logs for more information.").arg(m_inst->getManagedPackVersionName()), QMessageBox::Critical)
+ ->show();
+ }
+
+}
+
void ModrinthManagedPackPage::update()
{
auto index = ui->versionsComboBox->currentIndex();
@@ -363,10 +381,9 @@ void ModrinthManagedPackPage::update()
extracted->setIcon(m_inst->iconKey());
extracted->setConfirmUpdate(false);
+ // Run our task then handle the result
auto did_succeed = runUpdateTask(extracted);
-
- if (m_instance_window && did_succeed)
- m_instance_window->close();
+ onUpdateTaskCompleted(did_succeed);
}
void ModrinthManagedPackPage::updateFromFile()
@@ -386,14 +403,12 @@ void ModrinthManagedPackPage::updateFromFile()
extracted->setIcon(m_inst->iconKey());
extracted->setConfirmUpdate(false);
+ // Run our task then handle the result
auto did_succeed = runUpdateTask(extracted);
-
- if (m_instance_window && did_succeed)
- m_instance_window->close();
+ onUpdateTaskCompleted(did_succeed);
}
// FLAME
-
FlameManagedPackPage::FlameManagedPackPage(BaseInstance* inst, InstanceWindow* instance_window, QWidget* parent)
: ManagedPackPage(inst, instance_window, parent)
{
@@ -531,9 +546,7 @@ void FlameManagedPackPage::update()
extracted->setConfirmUpdate(false);
auto did_succeed = runUpdateTask(extracted);
-
- if (m_instance_window && did_succeed)
- m_instance_window->close();
+ onUpdateTaskCompleted(did_succeed);
}
void FlameManagedPackPage::updateFromFile()
@@ -555,8 +568,6 @@ void FlameManagedPackPage::updateFromFile()
extracted->setConfirmUpdate(false);
auto did_succeed = runUpdateTask(extracted);
-
- if (m_instance_window && did_succeed)
- m_instance_window->close();
+ onUpdateTaskCompleted(did_succeed);
}
#include "ManagedPackPage.moc"
diff --git a/launcher/ui/pages/instance/ManagedPackPage.h b/launcher/ui/pages/instance/ManagedPackPage.h
index c44f77070..e8d304c6b 100644
--- a/launcher/ui/pages/instance/ManagedPackPage.h
+++ b/launcher/ui/pages/instance/ManagedPackPage.h
@@ -94,6 +94,8 @@ class ManagedPackPage : public QWidget, public BasePage {
BaseInstance* m_inst;
bool m_loaded = false;
+
+ void onUpdateTaskCompleted(bool did_succeed) const;
};
/** Simple page for when we aren't a managed pack. */
diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp
index 4bc2e6998..136fb47c7 100644
--- a/launcher/ui/pages/instance/ServersPage.cpp
+++ b/launcher/ui/pages/instance/ServersPage.cpp
@@ -36,9 +36,9 @@
*/
#include "ServersPage.h"
+#include "ServerPingTask.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui_ServersPage.h"
-#include "ServerPingTask.h"
#include
#include
@@ -49,10 +49,10 @@
#include
#include
+#include
#include
#include
#include
-#include
static const int COLUMN_COUNT = 3; // 3 , TBD: latency and other nice things.
@@ -113,8 +113,8 @@ struct Server {
// Data - temporary
bool m_checked = false;
bool m_up = false;
- QString m_motd; // https://mctools.org/motd-creator
- std::optional m_currentPlayers; // nullopt if not calculated/calculating
+ QString m_motd; // https://mctools.org/motd-creator
+ std::optional m_currentPlayers; // nullopt if not calculated/calculating
int m_maxPlayers = 0;
};
@@ -317,10 +317,10 @@ class ServersModel : public QAbstractListModel {
if (row < 0 || row >= m_servers.size())
return QVariant();
- switch (column) {
- case 0:
- switch (role) {
- case Qt::DecorationRole: {
+ switch (role) {
+ case Qt::DecorationRole: {
+ switch (column) {
+ case 0: {
auto& bytes = m_servers[row].m_icon;
if (bytes.size()) {
QPixmap px;
@@ -329,31 +329,32 @@ class ServersModel : public QAbstractListModel {
}
return APPLICATION->getThemedIcon("unknown_server");
}
- case Qt::DisplayRole:
- return m_servers[row].m_name;
- case ServerPtrRole:
- return QVariant::fromValue((void*)&m_servers[row]);
- default:
- return QVariant();
- }
- case 1:
- switch (role) {
- case Qt::DisplayRole:
+ case 1:
return m_servers[row].m_address;
default:
return QVariant();
}
- case 2:
- switch (role) {
- case Qt::DisplayRole:
+ case 2:
+ if (role == Qt::DisplayRole) {
if (m_servers[row].m_currentPlayers) {
return *m_servers[row].m_currentPlayers;
} else {
return "...";
}
- default:
+ } else {
return QVariant();
- }
+ }
+ }
+ case Qt::DisplayRole:
+ if (column == 0)
+ return m_servers[row].m_name;
+ else
+ return QVariant();
+ case ServerPtrRole:
+ if (column == 0)
+ return QVariant::fromValue((void*)&m_servers[row]);
+ else
+ return QVariant();
default:
return QVariant();
}
@@ -447,22 +448,22 @@ class ServersModel : public QAbstractListModel {
}
m_currentQueryTask = ConcurrentTask::Ptr(
- new ConcurrentTask("Query servers status", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())
- );
+ new ConcurrentTask("Query servers status", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
int row = 0;
- for (Server &server : m_servers) {
+ for (Server& server : m_servers) {
// reset current players
server.m_currentPlayers = {};
emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1));
// Start task to query server status
auto target = MinecraftTarget::parse(server.m_address, false);
- auto *task = new ServerPingTask(target.address, target.port);
+ auto* task = new ServerPingTask(target.address, target.port);
m_currentQueryTask->addTask(Task::Ptr(task));
// Update the model when the task is done
connect(task, &Task::finished, this, [this, task, row]() {
- if (m_servers.size() < row) return;
+ if (m_servers.size() < row)
+ return;
m_servers[row].m_currentPlayers = task->m_outputOnlinePlayers;
emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1));
});
@@ -717,7 +718,7 @@ void ServersPage::openedImpl()
ui->toolBar->setVisibilityState(m_wide_bar_setting->get().toByteArray());
- // ping servers
+ // ping servers
m_model->queryServersStatus();
}
diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp
index 4ed5f1f73..dd7486a6c 100644
--- a/launcher/ui/pages/instance/WorldListPage.cpp
+++ b/launcher/ui/pages/instance/WorldListPage.cpp
@@ -166,12 +166,9 @@ void WorldListPage::retranslate()
bool WorldListPage::worldListFilter(QKeyEvent* keyEvent)
{
- switch (keyEvent->key()) {
- case Qt::Key_Delete:
- on_actionRemove_triggered();
- return true;
- default:
- break;
+ if (keyEvent->key() == Qt::Key_Delete) {
+ on_actionRemove_triggered();
+ return true;
}
return QWidget::eventFilter(ui->worldTreeView, keyEvent);
}
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
index 6fb867733..d84737bf5 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
@@ -45,7 +45,9 @@
#include "net/ApiDownload.h"
-AtlOptionalModListModel::AtlOptionalModListModel(QWidget* parent, ATLauncher::PackVersion version, QVector mods)
+AtlOptionalModListModel::AtlOptionalModListModel(QWidget* parent,
+ const ATLauncher::PackVersion& version,
+ QVector mods)
: QAbstractListModel(parent), m_version(version), m_mods(mods)
{
// fill mod index
@@ -233,7 +235,7 @@ void AtlOptionalModListModel::clearAll()
emit dataChanged(AtlOptionalModListModel::index(0, EnabledColumn), AtlOptionalModListModel::index(m_mods.size() - 1, EnabledColumn));
}
-void AtlOptionalModListModel::toggleMod(ATLauncher::VersionMod mod, int index)
+void AtlOptionalModListModel::toggleMod(const ATLauncher::VersionMod& mod, int index)
{
auto enable = !m_selection[mod.name];
@@ -251,7 +253,7 @@ void AtlOptionalModListModel::toggleMod(ATLauncher::VersionMod mod, int index)
setMod(mod, index, enable);
}
-void AtlOptionalModListModel::setMod(ATLauncher::VersionMod mod, int index, bool enable, bool shouldEmit)
+void AtlOptionalModListModel::setMod(const ATLauncher::VersionMod& mod, int index, bool enable, bool shouldEmit)
{
if (m_selection[mod.name] == enable)
return;
@@ -313,7 +315,7 @@ void AtlOptionalModListModel::setMod(ATLauncher::VersionMod mod, int index, bool
}
}
-AtlOptionalModDialog::AtlOptionalModDialog(QWidget* parent, ATLauncher::PackVersion version, QVector mods)
+AtlOptionalModDialog::AtlOptionalModDialog(QWidget* parent, const ATLauncher::PackVersion& version, QVector mods)
: QDialog(parent), ui(new Ui::AtlOptionalModDialog)
{
ui->setupUi(this);
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h
index 767d277d9..0636715cc 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h
@@ -55,7 +55,7 @@ class AtlOptionalModListModel : public QAbstractListModel {
DescriptionColumn,
};
- AtlOptionalModListModel(QWidget* parent, ATLauncher::PackVersion version, QVector mods);
+ AtlOptionalModListModel(QWidget* parent, const ATLauncher::PackVersion& version, QVector mods);
QVector getResult();
@@ -78,8 +78,8 @@ class AtlOptionalModListModel : public QAbstractListModel {
void clearAll();
private:
- void toggleMod(ATLauncher::VersionMod mod, int index);
- void setMod(ATLauncher::VersionMod mod, int index, bool enable, bool shouldEmit = true);
+ void toggleMod(const ATLauncher::VersionMod& mod, int index);
+ void setMod(const ATLauncher::VersionMod& mod, int index, bool enable, bool shouldEmit = true);
private:
NetJob::Ptr m_jobPtr;
@@ -97,7 +97,7 @@ class AtlOptionalModDialog : public QDialog {
Q_OBJECT
public:
- AtlOptionalModDialog(QWidget* parent, ATLauncher::PackVersion version, QVector mods);
+ AtlOptionalModDialog(QWidget* parent, const ATLauncher::PackVersion& version, QVector mods);
~AtlOptionalModDialog() override;
QVector getResult() { return listModel->getResult(); }
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp
index 0c7257859..7550ff758 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp
@@ -41,7 +41,7 @@
AtlUserInteractionSupportImpl::AtlUserInteractionSupportImpl(QWidget* parent) : m_parent(parent) {}
-std::optional> AtlUserInteractionSupportImpl::chooseOptionalMods(ATLauncher::PackVersion version,
+std::optional> AtlUserInteractionSupportImpl::chooseOptionalMods(const ATLauncher::PackVersion& version,
QVector mods)
{
AtlOptionalModDialog optionalModDialog(m_parent, version, mods);
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h
index 52ced2615..7ff021105 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h
@@ -48,7 +48,8 @@ class AtlUserInteractionSupportImpl : public QObject, public ATLauncher::UserInt
private:
QString chooseVersion(Meta::VersionList::Ptr vlist, QString minecraftVersion) override;
- std::optional> chooseOptionalMods(ATLauncher::PackVersion version, QVector mods) override;
+ std::optional> chooseOptionalMods(const ATLauncher::PackVersion& version,
+ QVector mods) override;
void displayMessage(QString message) override;
private:
diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp
index ae4562be4..fea1fc27a 100644
--- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp
@@ -32,7 +32,7 @@ void FlameModModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject&
void FlameModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{
- FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
+ FlameMod::loadIndexedPackVersions(m, arr);
}
auto FlameModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion
@@ -65,7 +65,7 @@ void FlameResourcePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJso
void FlameResourcePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{
- FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
+ FlameMod::loadIndexedPackVersions(m, arr);
}
bool FlameResourcePackModel::optedOut(const ModPlatform::IndexedVersion& ver) const
@@ -93,7 +93,7 @@ void FlameTexturePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJson
void FlameTexturePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{
- FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
+ FlameMod::loadIndexedPackVersions(m, arr);
QVector filtered_versions(m.versions.size());
@@ -157,7 +157,7 @@ void FlameShaderPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonO
void FlameShaderPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{
- FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
+ FlameMod::loadIndexedPackVersions(m, arr);
}
bool FlameShaderPackModel::optedOut(const ModPlatform::IndexedVersion& ver) const
diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp
index f3c737977..0fb83b6cb 100644
--- a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp
+++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp
@@ -106,9 +106,6 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
}
auto pack = m_modpacks.at(pos);
- if (role == Qt::ToolTipRole) {
- }
-
switch (role) {
case Qt::ToolTipRole:
return tr("Minecraft %1").arg(pack.mcVersion);
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
index 98922123c..bdb7c64d7 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
+++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
@@ -213,7 +213,7 @@ void ListModel::fill(ModpackList modpacks_)
endResetModel();
}
-void ListModel::addPack(Modpack modpack)
+void ListModel::addPack(const Modpack& modpack)
{
beginResetModel();
this->modpacks.append(modpack);
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h
index f35012078..e4477c929 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h
+++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h
@@ -62,7 +62,7 @@ class ListModel : public QAbstractListModel {
Qt::ItemFlags flags(const QModelIndex& index) const override;
void fill(ModpackList modpacks);
- void addPack(Modpack modpack);
+ void addPack(const Modpack& modpack);
void clear();
void remove(int row);
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp
index 226a30ee3..5752b6c61 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp
+++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp
@@ -213,7 +213,7 @@ void Page::ftbPackDataDownloadAborted()
CustomMessageBox::selectable(this, tr("Task aborted"), tr("The task has been aborted by the user."), QMessageBox::Information)->show();
}
-void Page::ftbPrivatePackDataDownloadSuccessfully(Modpack pack)
+void Page::ftbPrivatePackDataDownloadSuccessfully(const Modpack& pack)
{
privateListModel->addPack(pack);
}
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.h b/launcher/ui/pages/modplatform/legacy_ftb/Page.h
index a2dee24e9..818000c05 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/Page.h
+++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.h
@@ -85,7 +85,7 @@ class Page : public QWidget, public ModpackProviderBasePage {
void ftbPackDataDownloadFailed(QString reason);
void ftbPackDataDownloadAborted();
- void ftbPrivatePackDataDownloadSuccessfully(Modpack pack);
+ void ftbPrivatePackDataDownloadSuccessfully(const Modpack& pack);
void ftbPrivatePackDataDownloadFailed(QString reason, QString packCode);
void onSortingSelectionChanged(QString data);
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
index 856018294..213d6e39e 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
@@ -39,7 +39,7 @@ void ModrinthModModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObjec
void ModrinthModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{
- ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);
+ ::Modrinth::loadIndexedPackVersions(m, arr);
}
auto ModrinthModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion
@@ -66,7 +66,7 @@ void ModrinthResourcePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, Q
void ModrinthResourcePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{
- ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);
+ ::Modrinth::loadIndexedPackVersions(m, arr);
}
auto ModrinthResourcePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
@@ -88,7 +88,7 @@ void ModrinthTexturePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJ
void ModrinthTexturePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{
- ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);
+ ::Modrinth::loadIndexedPackVersions(m, arr);
}
auto ModrinthTexturePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
@@ -110,7 +110,7 @@ void ModrinthShaderPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJs
void ModrinthShaderPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{
- ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);
+ ::Modrinth::loadIndexedPackVersions(m, arr);
}
auto ModrinthShaderPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
diff --git a/launcher/ui/themes/SystemTheme.cpp b/launcher/ui/themes/SystemTheme.cpp
index a1674455a..7fba08026 100644
--- a/launcher/ui/themes/SystemTheme.cpp
+++ b/launcher/ui/themes/SystemTheme.cpp
@@ -40,18 +40,29 @@
#include "HintOverrideProxyStyle.h"
#include "ThemeManager.h"
-SystemTheme::SystemTheme(const QString& styleName, const QPalette& palette, bool isDefaultTheme)
+// See https://github.com/MultiMC/Launcher/issues/1790
+// or https://github.com/PrismLauncher/PrismLauncher/issues/490
+static const QStringList S_NATIVE_STYLES{ "windows11", "windowsvista", "macos", "system", "windows" };
+
+SystemTheme::SystemTheme(const QString& styleName, const QPalette& defaultPalette, bool isDefaultTheme)
{
- themeName = isDefaultTheme ? "system" : styleName;
- widgetTheme = styleName;
- colorPalette = palette;
+ m_themeName = isDefaultTheme ? "system" : styleName;
+ m_widgetTheme = styleName;
+ // NOTE: SystemTheme is reconstructed on page refresh. We can't accurately determine the system palette here
+ // See also S_NATIVE_STYLES comment
+ if (S_NATIVE_STYLES.contains(m_themeName)) {
+ m_colorPalette = defaultPalette;
+ } else {
+ auto style = QStyleFactory::create(styleName);
+ m_colorPalette = style->standardPalette();
+ delete style;
+ }
}
void SystemTheme::apply(bool initial)
{
- // See https://github.com/MultiMC/Launcher/issues/1790
- // or https://github.com/PrismLauncher/PrismLauncher/issues/490
- if (initial) {
+ // See S_NATIVE_STYLES comment
+ if (initial && S_NATIVE_STYLES.contains(m_themeName)) {
QApplication::setStyle(new HintOverrideProxyStyle(QStyleFactory::create(qtTheme())));
return;
}
@@ -61,35 +72,35 @@ void SystemTheme::apply(bool initial)
QString SystemTheme::id()
{
- return themeName;
+ return m_themeName;
}
QString SystemTheme::name()
{
- if (themeName.toLower() == "windowsvista") {
+ if (m_themeName.toLower() == "windowsvista") {
return QObject::tr("Windows Vista");
- } else if (themeName.toLower() == "windows") {
+ } else if (m_themeName.toLower() == "windows") {
return QObject::tr("Windows 9x");
- } else if (themeName.toLower() == "windows11") {
+ } else if (m_themeName.toLower() == "windows11") {
return QObject::tr("Windows 11");
- } else if (themeName.toLower() == "system") {
+ } else if (m_themeName.toLower() == "system") {
return QObject::tr("System");
} else {
- return themeName;
+ return m_themeName;
}
}
QString SystemTheme::tooltip()
{
- if (themeName.toLower() == "windowsvista") {
+ if (m_themeName.toLower() == "windowsvista") {
return QObject::tr("Widget style trying to look like your win32 theme");
- } else if (themeName.toLower() == "windows") {
+ } else if (m_themeName.toLower() == "windows") {
return QObject::tr("Windows 9x inspired widget style");
- } else if (themeName.toLower() == "windows11") {
+ } else if (m_themeName.toLower() == "windows11") {
return QObject::tr("WinUI 3 inspired Qt widget style");
- } else if (themeName.toLower() == "fusion") {
+ } else if (m_themeName.toLower() == "fusion") {
return QObject::tr("The default Qt widget style");
- } else if (themeName.toLower() == "system") {
+ } else if (m_themeName.toLower() == "system") {
return QObject::tr("Your current system theme");
} else {
return "";
@@ -98,12 +109,12 @@ QString SystemTheme::tooltip()
QString SystemTheme::qtTheme()
{
- return widgetTheme;
+ return m_widgetTheme;
}
QPalette SystemTheme::colorScheme()
{
- return colorPalette;
+ return m_colorPalette;
}
QString SystemTheme::appStyleSheet()
diff --git a/launcher/ui/themes/SystemTheme.h b/launcher/ui/themes/SystemTheme.h
index 7c260fdc4..7ae24c3db 100644
--- a/launcher/ui/themes/SystemTheme.h
+++ b/launcher/ui/themes/SystemTheme.h
@@ -38,7 +38,7 @@
class SystemTheme : public ITheme {
public:
- SystemTheme(const QString& styleName, const QPalette& palette, bool isDefaultTheme);
+ SystemTheme(const QString& styleName, const QPalette& defaultPalette, bool isDefaultTheme);
virtual ~SystemTheme() {}
void apply(bool initial) override;
@@ -53,7 +53,7 @@ class SystemTheme : public ITheme {
QColor fadeColor() override;
private:
- QPalette colorPalette;
- QString widgetTheme;
- QString themeName;
+ QPalette m_colorPalette;
+ QString m_widgetTheme;
+ QString m_themeName;
};
diff --git a/launcher/ui/themes/ThemeManager.h b/launcher/ui/themes/ThemeManager.h
index a9036107c..8de7562d1 100644
--- a/launcher/ui/themes/ThemeManager.h
+++ b/launcher/ui/themes/ThemeManager.h
@@ -68,8 +68,8 @@ class ThemeManager {
QDir m_applicationThemeFolder{ "themes" };
QDir m_catPacksFolder{ "catpacks" };
std::map> m_catPacks;
- QString m_defaultStyle;
QPalette m_defaultPalette;
+ QString m_defaultStyle;
LogColors m_logColors;
void initializeThemes();
diff --git a/launcher/ui/widgets/FocusLineEdit.h b/launcher/ui/widgets/FocusLineEdit.h
index f5ea6602e..797969406 100644
--- a/launcher/ui/widgets/FocusLineEdit.h
+++ b/launcher/ui/widgets/FocusLineEdit.h
@@ -1,3 +1,5 @@
+#pragma once
+
#include
class FocusLineEdit : public QLineEdit {
diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp
index 37211693f..0fda7933e 100644
--- a/launcher/ui/widgets/ModFilterWidget.cpp
+++ b/launcher/ui/widgets/ModFilterWidget.cpp
@@ -148,11 +148,10 @@ ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, QWi
connect(ui->forge, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged);
connect(ui->fabric, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged);
connect(ui->quilt, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged);
-
- connect(ui->neoForge, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged);
- connect(ui->forge, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged);
- connect(ui->fabric, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged);
- connect(ui->quilt, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged);
+ if (extended)
+ connect(ui->liteLoader, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged);
+ else
+ ui->liteLoader->setVisible(false);
if (extended) {
connect(ui->clientSide, &QCheckBox::stateChanged, this, &ModFilterWidget::onSideFilterChanged);
@@ -224,6 +223,7 @@ void ModFilterWidget::prepareBasicFilter()
ui->forge->setChecked(loaders & ModPlatform::Forge);
ui->fabric->setChecked(loaders & ModPlatform::Fabric);
ui->quilt->setChecked(loaders & ModPlatform::Quilt);
+ ui->liteLoader->setChecked(loaders & ModPlatform::LiteLoader);
m_filter->loaders = loaders;
auto def = m_instance->getPackProfile()->getComponentVersion("net.minecraft");
m_filter->versions.emplace_front(def);
@@ -269,6 +269,8 @@ void ModFilterWidget::onLoadersFilterChanged()
loaders |= ModPlatform::Fabric;
if (ui->quilt->isChecked())
loaders |= ModPlatform::Quilt;
+ if (ui->liteLoader->isChecked())
+ loaders |= ModPlatform::LiteLoader;
m_filter_changed = loaders != m_filter->loaders;
m_filter->loaders = loaders;
if (m_filter_changed)
diff --git a/launcher/ui/widgets/ModFilterWidget.ui b/launcher/ui/widgets/ModFilterWidget.ui
index 807a0019a..788202714 100644
--- a/launcher/ui/widgets/ModFilterWidget.ui
+++ b/launcher/ui/widgets/ModFilterWidget.ui
@@ -121,6 +121,13 @@
+ -
+
+
+ LiteLoader
+
+
+
diff --git a/launcher/updater/prismupdater/PrismUpdater.cpp b/launcher/updater/prismupdater/PrismUpdater.cpp
index 8bf8cb473..f9ffeb658 100644
--- a/launcher/updater/prismupdater/PrismUpdater.cpp
+++ b/launcher/updater/prismupdater/PrismUpdater.cpp
@@ -53,24 +53,8 @@
#include
#endif
-// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header
-
-#ifdef __APPLE__
-#include // for deployment target to support pre-catalina targets without std::fs
-#endif // __APPLE__
-
-#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include)
-#if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500)
-#define GHC_USE_STD_FS
#include
namespace fs = std::filesystem;
-#endif // MacOS min version check
-#endif // Other OSes version check
-
-#ifndef GHC_USE_STD_FS
-#include
-namespace fs = ghc::filesystem;
-#endif
#include "DesktopServices.h"
@@ -298,6 +282,10 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar
auto version_parts = version.split('.');
m_prismVersionMajor = version_parts.takeFirst().toInt();
m_prismVersionMinor = version_parts.takeFirst().toInt();
+ if (!version_parts.isEmpty())
+ m_prismVersionPatch = version_parts.takeFirst().toInt();
+ else
+ m_prismVersionPatch = 0;
}
m_allowPreRelease = parser.isSet("pre-release");
@@ -556,6 +544,7 @@ void PrismUpdaterApp::run()
m_prismVersion = BuildConfig.printableVersionString();
m_prismVersionMajor = BuildConfig.VERSION_MAJOR;
m_prismVersionMinor = BuildConfig.VERSION_MINOR;
+ m_prismVersionPatch = BuildConfig.VERSION_PATCH;
m_prsimVersionChannel = BuildConfig.VERSION_CHANNEL;
m_prismGitCommit = BuildConfig.GIT_COMMIT;
}
@@ -564,6 +553,7 @@ void PrismUpdaterApp::run()
qDebug() << "Executable reports as:" << m_prismBinaryName << "version:" << m_prismVersion;
qDebug() << "Version major:" << m_prismVersionMajor;
qDebug() << "Version minor:" << m_prismVersionMinor;
+ qDebug() << "Version minor:" << m_prismVersionPatch;
qDebug() << "Version channel:" << m_prsimVersionChannel;
qDebug() << "Git Commit:" << m_prismGitCommit;
@@ -1277,6 +1267,10 @@ bool PrismUpdaterApp::loadPrismVersionFromExe(const QString& exe_path)
return false;
m_prismVersionMajor = version_parts.takeFirst().toInt();
m_prismVersionMinor = version_parts.takeFirst().toInt();
+ if (!version_parts.isEmpty())
+ m_prismVersionPatch = version_parts.takeFirst().toInt();
+ else
+ m_prismVersionPatch = 0;
m_prismGitCommit = lines.takeFirst().simplified();
return true;
}
@@ -1400,7 +1394,7 @@ GitHubRelease PrismUpdaterApp::getLatestRelease()
bool PrismUpdaterApp::needUpdate(const GitHubRelease& release)
{
- auto current_ver = Version(QString("%1.%2").arg(QString::number(m_prismVersionMajor)).arg(QString::number(m_prismVersionMinor)));
+ auto current_ver = Version(QString("%1.%2.%3").arg(m_prismVersionMajor).arg(m_prismVersionMinor).arg(m_prismVersionPatch));
return current_ver < release.version;
}
diff --git a/launcher/updater/prismupdater/PrismUpdater.h b/launcher/updater/prismupdater/PrismUpdater.h
index f3dd6e062..a904cbb6f 100644
--- a/launcher/updater/prismupdater/PrismUpdater.h
+++ b/launcher/updater/prismupdater/PrismUpdater.h
@@ -121,6 +121,7 @@ class PrismUpdaterApp : public QApplication {
QString m_prismVersion;
int m_prismVersionMajor = -1;
int m_prismVersionMinor = -1;
+ int m_prismVersionPatch = -1;
QString m_prsimVersionChannel;
QString m_prismGitCommit;
diff --git a/libraries/LocalPeer/src/LocalPeer.cpp b/libraries/LocalPeer/src/LocalPeer.cpp
index bd407042f..c1875bf98 100644
--- a/libraries/LocalPeer/src/LocalPeer.cpp
+++ b/libraries/LocalPeer/src/LocalPeer.cpp
@@ -76,7 +76,7 @@ ApplicationId ApplicationId::fromTraditionalApp()
prefix.truncate(6);
QByteArray idc = protoId.toUtf8();
quint16 idNum = qChecksum(idc.constData(), idc.size());
- auto socketName = QLatin1String("qtsingleapp-") + prefix + QLatin1Char('-') + QString::number(idNum, 16);
+ auto socketName = QLatin1String("pl") + prefix + QLatin1Char('-') + QString::number(idNum, 16).left(12);
#if defined(Q_OS_WIN)
if (!pProcessIdToSessionId) {
QLibrary lib("kernel32");
@@ -98,12 +98,12 @@ ApplicationId ApplicationId::fromPathAndVersion(const QString& dataPath, const Q
QCryptographicHash shasum(QCryptographicHash::Algorithm::Sha1);
QString result = dataPath + QLatin1Char('-') + version;
shasum.addData(result.toUtf8());
- return ApplicationId(QLatin1String("qtsingleapp-") + QString::fromLatin1(shasum.result().toHex()));
+ return ApplicationId(QLatin1String("pl") + QString::fromLatin1(shasum.result().toHex()).left(12));
}
ApplicationId ApplicationId::fromCustomId(const QString& id)
{
- return ApplicationId(QLatin1String("qtsingleapp-") + id);
+ return ApplicationId(QLatin1String("pl") + id);
}
ApplicationId ApplicationId::fromRawString(const QString& id)
@@ -139,7 +139,7 @@ bool LocalPeer::isClient()
#if defined(Q_OS_UNIX)
// ### Workaround
if (!res && server->serverError() == QAbstractSocket::AddressInUseError) {
- QFile::remove(QDir::cleanPath(QDir::tempPath()) + QLatin1Char('/') + socketName);
+ QLocalServer::removeServer(socketName);
res = server->listen(socketName);
}
#endif
diff --git a/libraries/README.md b/libraries/README.md
index 67d78dade..3c5f5a4ab 100644
--- a/libraries/README.md
+++ b/libraries/README.md
@@ -2,14 +2,6 @@
This folder has third-party or otherwise external libraries needed for other parts to work.
-## filesystem
-
-Gulrak's implementation of C++17 std::filesystem for C++11 /C++14/C++17/C++20 on Windows, macOS, Linux and FreeBSD.
-
-See [github repo](https://github.com/gulrak/filesystem).
-
-MIT licensed.
-
## gamemode
A performance optimization daemon.
diff --git a/nix/README.md b/nix/README.md
index 7c43658f9..21714d64a 100644
--- a/nix/README.md
+++ b/nix/README.md
@@ -44,9 +44,6 @@ Example:
# Note that this may break the reproducibility mentioned above, and you might not be able to access the binary cache
#
# inputs.nixpkgs.follows = "nixpkgs";
-
- # This is not required for Flakes
- inputs.flake-compat.follows = "";
};
};
@@ -92,9 +89,6 @@ Example:
# Note that this may break the reproducibility mentioned above, and you might not be able to access the binary cache
#
# inputs.nixpkgs.follows = "nixpkgs";
-
- # This is not required for Flakes
- inputs.flake-compat.follows = "";
};
};
diff --git a/nix/checks.nix b/nix/checks.nix
deleted file mode 100644
index ec219d6f8..000000000
--- a/nix/checks.nix
+++ /dev/null
@@ -1,42 +0,0 @@
-{
- runCommand,
- deadnix,
- llvmPackages_18,
- markdownlint-cli,
- nixfmt-rfc-style,
- statix,
- self,
-}:
-{
- formatting =
- runCommand "check-formatting"
- {
- nativeBuildInputs = [
- deadnix
- llvmPackages_18.clang-tools
- markdownlint-cli
- nixfmt-rfc-style
- statix
- ];
- }
- ''
- cd ${self}
-
- echo "Running clang-format...."
- clang-format --dry-run --style='file' --Werror */**.{c,cc,cpp,h,hh,hpp}
-
- echo "Running deadnix..."
- deadnix --fail
-
- echo "Running markdownlint..."
- markdownlint --dot .
-
- echo "Running nixfmt..."
- nixfmt --check .
-
- echo "Running statix"
- statix check .
-
- touch $out
- '';
-}
diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix
index 7ba20b68b..93cda8e1a 100644
--- a/nix/unwrapped.nix
+++ b/nix/unwrapped.nix
@@ -6,12 +6,10 @@
apple-sdk_11,
extra-cmake-modules,
gamemode,
- ghc_filesystem,
jdk17,
kdePackages,
libnbtplusplus,
ninja,
- nix-filter,
self,
stripJavaArchivesHook,
tomlplusplus,
@@ -25,21 +23,41 @@ assert lib.assertMsg (
gamemodeSupport -> stdenv.hostPlatform.isLinux
) "gamemodeSupport is only available on Linux.";
+let
+ date =
+ let
+ # YYYYMMDD
+ date' = lib.substring 0 8 self.lastModifiedDate;
+ year = lib.substring 0 4 date';
+ month = lib.substring 4 2 date';
+ date = lib.substring 6 2 date';
+ in
+ if (self ? "lastModifiedDate") then
+ lib.concatStringsSep "-" [
+ year
+ month
+ date
+ ]
+ else
+ "unknown";
+in
+
stdenv.mkDerivation {
pname = "prismlauncher-unwrapped";
- version = self.shortRev or self.dirtyShortRev or "unknown";
+ version = "10.0-unstable-${date}";
- src = nix-filter.lib {
- root = self;
- include = [
- "buildconfig"
- "cmake"
- "launcher"
- "libraries"
- "program_info"
- "tests"
- ../COPYING.md
+ src = lib.fileset.toSource {
+ root = ../.;
+ fileset = lib.fileset.unions [
../CMakeLists.txt
+ ../COPYING.md
+
+ ../buildconfig
+ ../cmake
+ ../launcher
+ ../libraries
+ ../program_info
+ ../tests
];
};
@@ -59,7 +77,6 @@ stdenv.mkDerivation {
buildInputs =
[
cmark
- ghc_filesystem
kdePackages.qtbase
kdePackages.qtnetworkauth
kdePackages.quazip
diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in
index 24f6ee4e8..dfdce2e1c 100644
--- a/program_info/win_install.nsi.in
+++ b/program_info/win_install.nsi.in
@@ -398,6 +398,7 @@ Section "@Launcher_DisplayName@"
WriteRegStr HKCU Software\Classes\prismlauncher\shell\open\command "" '"$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "%1"'
; Write the uninstall keys for Windows
+ ; https://learn.microsoft.com/en-us/windows/win32/msi/uninstall-registry-key
${GetParameters} $R0
${GetOptions} $R0 "/NoUninstaller" $R1
${If} ${Errors}
diff --git a/tests/DataPackParse_test.cpp b/tests/DataPackParse_test.cpp
index cd6ae8e8f..14f80858f 100644
--- a/tests/DataPackParse_test.cpp
+++ b/tests/DataPackParse_test.cpp
@@ -38,7 +38,7 @@ class DataPackParseTest : public QObject {
QString zip_dp = FS::PathCombine(source, "test_data_pack_boogaloo.zip");
DataPack pack{ QFileInfo(zip_dp) };
- bool valid = DataPackUtils::processZIP(pack);
+ bool valid = DataPackUtils::processZIP(&pack);
QVERIFY(pack.packFormat() == 4);
QVERIFY(pack.description() == "Some data pack 2 boobgaloo");
@@ -52,7 +52,7 @@ class DataPackParseTest : public QObject {
QString folder_dp = FS::PathCombine(source, "test_folder");
DataPack pack{ QFileInfo(folder_dp) };
- bool valid = DataPackUtils::processFolder(pack);
+ bool valid = DataPackUtils::processFolder(&pack);
QVERIFY(pack.packFormat() == 10);
QVERIFY(pack.description() == "Some data pack, maybe");
@@ -66,7 +66,7 @@ class DataPackParseTest : public QObject {
QString folder_dp = FS::PathCombine(source, "another_test_folder");
DataPack pack{ QFileInfo(folder_dp) };
- bool valid = DataPackUtils::process(pack);
+ bool valid = DataPackUtils::process(&pack);
QVERIFY(pack.packFormat() == 6);
QVERIFY(pack.description() == "Some data pack three, leaves on the tree");
diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp
index ca0313bb4..995867e46 100644
--- a/tests/FileSystem_test.cpp
+++ b/tests/FileSystem_test.cpp
@@ -2,30 +2,15 @@
#include
#include
#include
+#include
#include
#include
#include
-// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header
-
-#ifdef __APPLE__
-#include // for deployment target to support pre-catalina targets without std::fs
-#endif // __APPLE__
-
-#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include)
-#if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500)
-#define GHC_USE_STD_FS
#include
namespace fs = std::filesystem;
-#endif // MacOS min version check
-#endif // Other OSes version check
-
-#ifndef GHC_USE_STD_FS
-#include
-namespace fs = ghc::filesystem;
-#endif
#include
@@ -42,7 +27,7 @@ class LinkTask : public Task {
~LinkTask() { delete m_lnk; }
- void matcher(const IPathMatcher* filter) { m_lnk->matcher(filter); }
+ void matcher(IPathMatcher::Ptr filter) { m_lnk->matcher(filter); }
void linkRecursively(bool recursive)
{
@@ -203,8 +188,8 @@ class FileSystemTest : public QObject {
qDebug() << tempDir.path();
qDebug() << target_dir.path();
FS::copy c(folder, target_dir.path());
- RegexpMatcher re("[.]?mcmeta");
- c.matcher(&re);
+ RegexpMatcher::Ptr re = std::make_shared("[.]?mcmeta");
+ c.matcher(re);
c();
for (auto entry : target_dir.entryList()) {
@@ -236,8 +221,8 @@ class FileSystemTest : public QObject {
qDebug() << tempDir.path();
qDebug() << target_dir.path();
FS::copy c(folder, target_dir.path());
- RegexpMatcher re("[.]?mcmeta");
- c.matcher(&re);
+ RegexpMatcher::Ptr re = std::make_shared("[.]?mcmeta");
+ c.matcher(re);
c.whitelist(true);
c();
@@ -429,8 +414,8 @@ class FileSystemTest : public QObject {
qDebug() << target_dir.path();
LinkTask lnk_tsk(folder, target_dir.path());
- RegexpMatcher re("[.]?mcmeta");
- lnk_tsk.matcher(&re);
+ RegexpMatcher::Ptr re = std::make_shared("[.]?mcmeta");
+ lnk_tsk.matcher(re);
lnk_tsk.linkRecursively(true);
QObject::connect(&lnk_tsk, &Task::finished, [&lnk_tsk] {
QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been.");
@@ -476,8 +461,8 @@ class FileSystemTest : public QObject {
qDebug() << target_dir.path();
LinkTask lnk_tsk(folder, target_dir.path());
- RegexpMatcher re("[.]?mcmeta");
- lnk_tsk.matcher(&re);
+ RegexpMatcher::Ptr re = std::make_shared("[.]?mcmeta");
+ lnk_tsk.matcher(re);
lnk_tsk.linkRecursively(true);
lnk_tsk.whitelist(true);
QObject::connect(&lnk_tsk, &Task::finished, [&lnk_tsk] {
diff --git a/tests/ResourcePackParse_test.cpp b/tests/ResourcePackParse_test.cpp
index e1092167d..17c0482fc 100644
--- a/tests/ResourcePackParse_test.cpp
+++ b/tests/ResourcePackParse_test.cpp
@@ -18,6 +18,7 @@
#include
#include
+#include "minecraft/mod/tasks/LocalDataPackParseTask.h"
#include
@@ -35,7 +36,7 @@ class ResourcePackParseTest : public QObject {
QString zip_rp = FS::PathCombine(source, "test_resource_pack_idk.zip");
ResourcePack pack{ QFileInfo(zip_rp) };
- bool valid = ResourcePackUtils::processZIP(pack, ResourcePackUtils::ProcessingLevel::BasicInfoOnly);
+ bool valid = DataPackUtils::processZIP(&pack, DataPackUtils::ProcessingLevel::BasicInfoOnly);
QVERIFY(pack.packFormat() == 3);
QVERIFY(pack.description() ==
@@ -51,7 +52,7 @@ class ResourcePackParseTest : public QObject {
QString folder_rp = FS::PathCombine(source, "test_folder");
ResourcePack pack{ QFileInfo(folder_rp) };
- bool valid = ResourcePackUtils::processFolder(pack, ResourcePackUtils::ProcessingLevel::BasicInfoOnly);
+ bool valid = DataPackUtils::processFolder(&pack, DataPackUtils::ProcessingLevel::BasicInfoOnly);
QVERIFY(pack.packFormat() == 1);
QVERIFY(pack.description() == "Some resource pack maybe");
@@ -65,7 +66,7 @@ class ResourcePackParseTest : public QObject {
QString folder_rp = FS::PathCombine(source, "another_test_folder");
ResourcePack pack{ QFileInfo(folder_rp) };
- bool valid = ResourcePackUtils::process(pack, ResourcePackUtils::ProcessingLevel::BasicInfoOnly);
+ bool valid = DataPackUtils::process(&pack, DataPackUtils::ProcessingLevel::BasicInfoOnly);
QVERIFY(pack.packFormat() == 6);
QVERIFY(pack.description() == "o quartel pegou fogo, policia deu sinal, acode acode acode a bandeira nacional");