diff --git a/.clang-tidy b/.clang-tidy
new file mode 100644
index 000000000..436dcf244
--- /dev/null
+++ b/.clang-tidy
@@ -0,0 +1,5 @@
+Checks:
+ - modernize-use-using
+ - readability-avoid-const-params-in-decls
+
+SystemHeaders: false
diff --git a/.envrc b/.envrc
index 3550a30f2..190b5b2b3 100644
--- a/.envrc
+++ b/.envrc
@@ -1 +1,2 @@
use flake
+watch_file nix/*.nix
diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml
index 08cfb56dd..e5443439d 100644
--- a/.github/workflows/backport.yml
+++ b/.github/workflows/backport.yml
@@ -20,11 +20,11 @@ jobs:
if: github.repository_owner == 'PrismLauncher' && github.event.pull_request.merged == true && (github.event_name != 'labeled' || startsWith('backport', github.event.label.name))
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Create backport PRs
- uses: korthout/backport-action@v1.4.0
+ uses: korthout/backport-action@v2.1.1
with:
# Config README: https://github.com/korthout/backport-action#backport-action
pull_description: |-
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index d044f4faf..4fb2d6794 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -37,56 +37,43 @@ jobs:
fail-fast: false
matrix:
include:
-
- os: ubuntu-20.04
qt_ver: 5
- os: ubuntu-20.04
qt_ver: 6
qt_host: linux
- qt_arch: ''
- qt_version: '6.2.4'
- qt_modules: 'qt5compat qtimageformats'
- qt_tools: ''
+ qt_arch: ""
+ qt_version: "6.2.4"
+ qt_modules: "qt5compat qtimageformats"
+ qt_tools: ""
- os: windows-2022
name: "Windows-MinGW-w64"
msystem: clang64
- vcvars_arch: 'amd64_x86'
-
- - os: windows-2022
- name: "Windows-MSVC-Legacy"
- msystem: ''
- architecture: 'win32'
- vcvars_arch: 'amd64_x86'
- qt_ver: 5
- qt_host: windows
- qt_arch: 'win32_msvc2019'
- qt_version: '5.15.2'
- qt_modules: ''
- qt_tools: 'tools_openssl_x86'
+ vcvars_arch: "amd64_x86"
- os: windows-2022
name: "Windows-MSVC"
- msystem: ''
- architecture: 'x64'
- vcvars_arch: 'amd64'
+ msystem: ""
+ architecture: "x64"
+ vcvars_arch: "amd64"
qt_ver: 6
qt_host: windows
qt_arch: ''
- qt_version: '6.5.2'
+ qt_version: '6.6.0'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
- os: windows-2022
name: "Windows-MSVC-arm64"
- msystem: ''
- architecture: 'arm64'
- vcvars_arch: 'amd64_arm64'
+ msystem: ""
+ architecture: "arm64"
+ vcvars_arch: "amd64_arm64"
qt_ver: 6
qt_host: windows
qt_arch: 'win64_msvc2019_arm64'
- qt_version: '6.5.2'
+ qt_version: '6.6.0'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
@@ -96,7 +83,7 @@ jobs:
qt_ver: 6
qt_host: mac
qt_arch: ''
- qt_version: '6.5.2'
+ qt_version: '6.6.0'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
@@ -105,9 +92,9 @@ jobs:
macosx_deployment_target: 10.13
qt_ver: 5
qt_host: mac
- qt_version: '5.15.2'
- qt_modules: ''
- qt_tools: ''
+ qt_version: "5.15.2"
+ qt_modules: ""
+ qt_tools: ""
runs-on: ${{ matrix.os }}
@@ -125,11 +112,11 @@ jobs:
# PREPARE
##
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
- submodules: 'true'
+ submodules: "true"
- - name: 'Setup MSYS2'
+ - name: "Setup MSYS2"
if: runner.os == 'Windows' && matrix.msystem != ''
uses: msys2/setup-msys2@v2
with:
@@ -164,12 +151,12 @@ jobs:
- name: Retrieve ccache cache (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
- uses: actions/cache@v3.3.1
+ uses: actions/cache@v3.3.2
with:
path: '${{ github.workspace }}\.ccache'
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
restore-keys: |
- ${{ matrix.os }}-mingw-w64-ccache
+ ${{ matrix.os }}-mingw-w64-ccache
- name: Setup ccache (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
@@ -214,35 +201,35 @@ jobs:
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
uses: jurplel/install-qt-action@v3
with:
- aqtversion: '==3.1.*'
- py7zrversion: '>=0.20.2'
- version: ${{ matrix.qt_version }}
- host: 'windows'
- target: 'desktop'
- arch: ''
- modules: ${{ matrix.qt_modules }}
- tools: ${{ matrix.qt_tools }}
- cache: ${{ inputs.is_qt_cached }}
- cache-key-prefix: host-qt-arm64-windows
- dir: ${{ github.workspace }}\HostQt
- set-env: false
+ aqtversion: "==3.1.*"
+ py7zrversion: ">=0.20.2"
+ version: ${{ matrix.qt_version }}
+ host: "windows"
+ target: "desktop"
+ arch: ""
+ modules: ${{ matrix.qt_modules }}
+ tools: ${{ matrix.qt_tools }}
+ cache: ${{ inputs.is_qt_cached }}
+ cache-key-prefix: host-qt-arm64-windows
+ dir: ${{ github.workspace }}\HostQt
+ set-env: false
- name: Install Qt (macOS, Linux, Qt 6 & Windows MSVC)
if: runner.os == 'Linux' && matrix.qt_ver == 6 || runner.os == 'macOS' || (runner.os == 'Windows' && matrix.msystem == '')
uses: jurplel/install-qt-action@v3
with:
- aqtversion: '==3.1.*'
- py7zrversion: '>=0.20.2'
- version: ${{ matrix.qt_version }}
- host: ${{ matrix.qt_host }}
- target: 'desktop'
- arch: ${{ matrix.qt_arch }}
- modules: ${{ matrix.qt_modules }}
- tools: ${{ matrix.qt_tools }}
- cache: ${{ inputs.is_qt_cached }}
+ aqtversion: "==3.1.*"
+ py7zrversion: ">=0.20.2"
+ version: ${{ matrix.qt_version }}
+ host: ${{ matrix.qt_host }}
+ target: "desktop"
+ arch: ${{ matrix.qt_arch }}
+ modules: ${{ matrix.qt_modules }}
+ tools: ${{ matrix.qt_tools }}
+ cache: ${{ inputs.is_qt_cached }}
- name: Install MSVC (Windows MSVC)
- if: runner.os == 'Windows' # We want this for MinGW builds as well, as we need SignTool
+ if: runner.os == 'Windows' # We want this for MinGW builds as well, as we need SignTool
uses: ilammy/msvc-dev-cmd@v1
with:
vsversion: 2022
@@ -283,12 +270,12 @@ jobs:
if: runner.os == 'Windows' && matrix.msystem != ''
shell: msys2 {0}
run: |
- cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=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 -G Ninja
+ cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=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 == ''
run: |
- cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=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
+ cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=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 }}")
{
@@ -303,7 +290,7 @@ jobs:
- name: Configure CMake (Linux)
if: runner.os == 'Linux'
run: |
- cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=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 }} -G Ninja
+ cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=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 }} -DLauncher_BUILD_ARTIFACT=Linux-Qt${{ matrix.qt_ver }} -G Ninja
##
# BUILD
@@ -343,7 +330,7 @@ jobs:
- name: Test (Windows MSVC)
if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture != 'arm64'
run: |
- ctest -E "^example64|example$" --test-dir build --output-on-failure -C ${{ inputs.build_type }}
+ ctest -E "^example64|example$" --test-dir build --output-on-failure -C ${{ inputs.build_type }}
##
# PACKAGE BUILDS
@@ -385,7 +372,7 @@ jobs:
run: |
cmake --install ${{ env.BUILD_DIR }}
touch ${{ env.INSTALL_DIR }}/manifest.txt
- for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_DIR }}/manifest.txt
+ for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_DIR }}/manifest.txt
- name: Package (Windows MSVC)
if: runner.os == 'Windows' && matrix.msystem == ''
@@ -402,10 +389,9 @@ jobs:
Get-ChildItem ${{ env.INSTALL_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt
-
- name: Fetch codesign certificate (Windows)
if: runner.os == 'Windows'
- shell: bash # yes, we are not using MSYS2 or PowerShell here
+ shell: bash # yes, we are not using MSYS2 or PowerShell here
run: |
echo '${{ secrets.WINDOWS_CODESIGN_CERT }}' | base64 --decode > codesign.pfx
@@ -415,7 +401,7 @@ jobs:
if (Get-Content ./codesign.pfx){
cd ${{ env.INSTALL_DIR }}
# We ship the exact same executable for portable and non-portable editions, so signing just once is fine
- SignTool sign /fd sha256 /td sha256 /f ../codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com prismlauncher.exe prismlauncher_filelink.exe
+ SignTool sign /fd sha256 /td sha256 /f ../codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com prismlauncher.exe prismlauncher_updater.exe prismlauncher_filelink.exe
} else {
":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY
}
@@ -507,15 +493,7 @@ jobs:
export LD_LIBRARY_PATH
chmod +x AppImageUpdate-x86_64.AppImage
- ./AppImageUpdate-x86_64.AppImage --appimage-extract
-
- mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/optional
- mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins
-
- cp -r squashfs-root/usr/bin/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/bin
- cp -r squashfs-root/usr/lib/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib
- cp -r squashfs-root/usr/optional/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/optional
- cp -r squashfs-root/usr/optional/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins
+ cp AppImageUpdate-x86_64.AppImage ${{ env.INSTALL_APPIMAGE_DIR }}/usr/bin
export UPDATE_INFORMATION="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|PrismLauncher-Linux-x86_64.AppImage.zsync"
@@ -523,7 +501,7 @@ jobs:
export SIGN=1
export SIGN_KEY=${{ secrets.GPG_PRIVATE_KEY_ID }}
mkdir -p ~/.gnupg/
- printf "$GPG_PRIVATE_KEY" | base64 --decode > ~/.gnupg/private.key
+ echo "$GPG_PRIVATE_KEY" > ~/.gnupg/private.key
gpg --import ~/.gnupg/private.key
else
echo ":warning: Skipped code signing for Linux AppImage, as gpg key was not present." >> $GITHUB_STEP_SUMMARY
@@ -569,14 +547,14 @@ jobs:
if: runner.os == 'Linux' && matrix.qt_ver != 6
uses: actions/upload-artifact@v3
with:
- name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}
+ name: PrismLauncher-${{ runner.os }}-Qt5-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher.tar.gz
- name: Upload binary tarball (Linux, portable, Qt 5)
if: runner.os == 'Linux' && matrix.qt_ver != 6
uses: actions/upload-artifact@v3
with:
- name: PrismLauncher-${{ runner.os }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
+ name: PrismLauncher-${{ runner.os }}-Qt5-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher-portable.tar.gz
- name: Upload binary tarball (Linux, Qt 6)
@@ -599,7 +577,7 @@ jobs:
with:
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
path: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
-
+
- name: Upload AppImage Zsync (Linux)
if: runner.os == 'Linux' && matrix.qt_ver != 5
uses: actions/upload-artifact@v3
@@ -616,17 +594,17 @@ jobs:
flatpak:
runs-on: ubuntu-latest
container:
- image: bilelmoussaoui/flatpak-github-actions:kde-5.15-22.08
+ image: bilelmoussaoui/flatpak-github-actions:kde-5.15-23.08
options: --privileged
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
if: inputs.build_type == 'Debug'
with:
- submodules: 'true'
+ submodules: "true"
- name: Build Flatpak (Linux)
if: inputs.build_type == 'Debug'
uses: flatpak/flatpak-github-actions/flatpak-builder@v6
with:
bundle: "Prism Launcher.flatpak"
- manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml
+ manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 0cd1f6e40..a77b4ae1e 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -8,7 +8,7 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
submodules: 'true'
diff --git a/.github/workflows/trigger_builds.yml b/.github/workflows/trigger_builds.yml
index 26ee4380b..70fda60ed 100644
--- a/.github/workflows/trigger_builds.yml
+++ b/.github/workflows/trigger_builds.yml
@@ -3,26 +3,25 @@ name: Build Application
on:
push:
branches-ignore:
- - 'renovate/**'
+ - "renovate/**"
paths-ignore:
- - '**.md'
- - '**/LICENSE'
- - 'flake.lock'
- - 'packages/**'
- - '.github/ISSUE_TEMPLATE/**'
- - '.markdownlint**'
+ - "**.md"
+ - "**/LICENSE"
+ - "flake.lock"
+ - "packages/**"
+ - ".github/ISSUE_TEMPLATE/**"
+ - ".markdownlint**"
pull_request:
paths-ignore:
- - '**.md'
- - '**/LICENSE'
- - 'flake.lock'
- - 'packages/**'
- - '.github/ISSUE_TEMPLATE/**'
- - '.markdownlint**'
+ - "**.md"
+ - "**/LICENSE"
+ - "flake.lock"
+ - "packages/**"
+ - ".github/ISSUE_TEMPLATE/**"
+ - ".markdownlint**"
workflow_dispatch:
jobs:
-
build_debug:
name: Build Debug
uses: ./.github/workflows/build.yml
@@ -34,3 +33,5 @@ jobs:
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
+ GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
+ GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml
index 2a46ff5e7..28578165f 100644
--- a/.github/workflows/trigger_release.yml
+++ b/.github/workflows/trigger_release.yml
@@ -3,10 +3,9 @@ name: Build Application and Make Release
on:
push:
tags:
- - '*'
+ - "*"
jobs:
-
build_release:
name: Build Release
uses: ./.github/workflows/build.yml
@@ -18,6 +17,8 @@ jobs:
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
+ GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
+ GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
create_release:
needs: build_release
@@ -26,10 +27,10 @@ jobs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
- submodules: 'true'
- path: 'PrismLauncher-source'
+ submodules: "true"
+ path: "PrismLauncher-source"
- name: Download artifacts
uses: actions/download-artifact@v3
- name: Grab and store version
@@ -40,9 +41,9 @@ jobs:
run: |
mv ${{ github.workspace }}/PrismLauncher-source PrismLauncher-${{ env.VERSION }}
mv PrismLauncher-Linux-Qt6-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
- mv PrismLauncher-Linux-Qt6*/PrismLauncher.tar.gz PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz
- mv PrismLauncher-Linux-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Portable-${{ env.VERSION }}.tar.gz
- mv PrismLauncher-Linux*/PrismLauncher.tar.gz PrismLauncher-Linux-${{ env.VERSION }}.tar.gz
+ mv PrismLauncher-Linux-Qt6*/PrismLauncher.tar.gz PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz
+ mv PrismLauncher-Linux-Qt5-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt5-Portable-${{ env.VERSION }}.tar.gz
+ mv PrismLauncher-Linux-Qt5*/PrismLauncher.tar.gz PrismLauncher-Linux-Qt5-${{ 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.tar.gz PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
@@ -86,8 +87,8 @@ jobs:
draft: true
prerelease: false
files: |
- PrismLauncher-Linux-${{ env.VERSION }}.tar.gz
- PrismLauncher-Linux-Portable-${{ env.VERSION }}.tar.gz
+ PrismLauncher-Linux-Qt5-${{ env.VERSION }}.tar.gz
+ PrismLauncher-Linux-Qt5-Portable-${{ env.VERSION }}.tar.gz
PrismLauncher-Linux-x86_64.AppImage
PrismLauncher-Linux-x86_64.AppImage.zsync
PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz
@@ -95,9 +96,6 @@ jobs:
PrismLauncher-Windows-MinGW-w64-${{ env.VERSION }}.zip
PrismLauncher-Windows-MinGW-w64-Portable-${{ env.VERSION }}.zip
PrismLauncher-Windows-MinGW-w64-Setup-${{ env.VERSION }}.exe
- PrismLauncher-Windows-MSVC-Legacy-${{ env.VERSION }}.zip
- PrismLauncher-Windows-MSVC-Legacy-Portable-${{ env.VERSION }}.zip
- PrismLauncher-Windows-MSVC-Legacy-Setup-${{ env.VERSION }}.exe
PrismLauncher-Windows-MSVC-arm64-${{ env.VERSION }}.zip
PrismLauncher-Windows-MSVC-arm64-Portable-${{ env.VERSION }}.zip
PrismLauncher-Windows-MSVC-arm64-Setup-${{ env.VERSION }}.exe
diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml
index 6bad57030..6a16b0369 100644
--- a/.github/workflows/update-flake.yml
+++ b/.github/workflows/update-flake.yml
@@ -16,10 +16,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
- - uses: cachix/install-nix-action@v22
+ - uses: actions/checkout@v4
+ - uses: cachix/install-nix-action@6a9a9e84a173d90b3ffb42c5ddaf9ea033fad011 # v23
- - uses: DeterminateSystems/update-flake-lock@v19
+ - uses: DeterminateSystems/update-flake-lock@v20
with:
commit-msg: "chore(nix): update lockfile"
pr-title: "chore(nix): update lockfile"
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 638fba051..faeb1c44e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -188,8 +188,11 @@ set(Launcher_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_M
# 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.")
-# Channel list URL
-set(Launcher_UPDATER_BASE "" CACHE STRING "Base URL for the updater.")
+# Github repo URL with releases for updater
+set(Launcher_UPDATER_GITHUB_REPO "https://github.com/PrismLauncher/PrismLauncher" CACHE STRING "Base github URL for the updater.")
+
+# Name to help updater identify valid artifacts
+set(Launcher_BUILD_ARTIFACT "" CACHE STRING "Artifact name to help the updater identify valid artifacts.")
# The metadata server
set(Launcher_META_URL "https://meta.prismlauncher.org/v1/" CACHE STRING "URL to fetch Launcher's meta files from.")
@@ -245,6 +248,11 @@ set(Launcher_MSA_CLIENT_ID "c36a9fb6-4f2a-41ff-90bd-ae7cc92031eb" CACHE STRING "
# This key was issued specifically for Prism Launcher
set(Launcher_CURSEFORGE_API_KEY "$2a$10$wuAJuNZuted3NORVmpgUC.m8sI.pv1tOPKZyBgLFGjxFp/br0lZCC" CACHE STRING "API key for the CurseForge platform")
+set(Launcher_COMPILER_NAME ${CMAKE_CXX_COMPILER_ID})
+set(Launcher_COMPILER_VERSION ${CMAKE_CXX_COMPILER_VERSION})
+set(Launcher_COMPILER_TARGET_SYSTEM ${CMAKE_SYSTEM_NAME})
+set(Launcher_COMPILER_TARGET_SYSTEM_VERSION ${CMAKE_SYSTEM_VERSION})
+set(Launcher_COMPILER_TARGET_PROCESSOR ${CMAKE_SYSTEM_PROCESSOR})
#### Check the current Git commit and branch
include(GetGitRevisionDescription)
@@ -339,6 +347,11 @@ add_subdirectory(program_info)
####################################### Install layout #######################################
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)
+endif()
if(NOT (UNIX AND APPLE))
# Install "portable.txt" if selected component is "portable"
diff --git a/README.md b/README.md
index 641622b5c..b32132d49 100644
--- a/README.md
+++ b/README.md
@@ -18,11 +18,18 @@
- All downloads and instructions for Prism Launcher can be found on our [Website](https://prismlauncher.org/download).
-- Last build status can be found in the [GitHub Actions](https://github.com/PrismLauncher/PrismLauncher/actions).
+- Last build status can be found in the [GitHub Actions](https://github.com/PrismLauncher/PrismLauncher/actions) tab (this also includes the pull requests status).
### Development Builds
-There are development builds available [here](https://github.com/PrismLauncher/PrismLauncher/actions). These have debug information in the binaries, so their file sizes are relatively larger.
+Please understand that these builds are not intended for most users. There may be bugs, and other instabilities. You have been warned.
+
+There are development builds available through:
+
+- [GitHub Actions](https://github.com/PrismLauncher/PrismLauncher/actions) (includes builds from pull requests opened by contribuitors)
+- [nightly.link](https://nightly.link/PrismLauncher/PrismLauncher/workflows/trigger_builds/develop) (this will always point only to the latest version of develop)
+
+These have debug information in the binaries, so their file sizes are relatively larger.
Prebuilt Development builds are provided for **Linux**, **Windows** and **macOS**.
@@ -30,7 +37,7 @@ For **Arch**, **Debian**, **Fedora**, **OpenSUSE (Tumbleweed)** and **Gentoo**,
[](https://aur.archlinux.org/packages/prismlauncher-git) [](https://aur.archlinux.org/packages/prismlauncher-qt5-git) [](https://mpr.makedeb.org/packages/prismlauncher-git)
[](https://copr.fedorainfracloud.org/coprs/g3tchoo/prismlauncher/) [](https://build.opensuse.org/project/show/home:getchoo) [](https://packages.gentoo.org/packages/games-action/prismlauncher)
-These packages are also availiable to all the distributions based on the ones mentioned above.
+These packages are also available to all the distributions based on the ones mentioned above.
## Community & Support
@@ -50,7 +57,7 @@ Feel free to create a GitHub issue if you find a bug or want to suggest a new fe
## Translations
-The translation effort for Prism Launcher is hosted on [Weblate](https://hosted.weblate.org/projects/prismlauncher/launcher/) and information about translating Prism Launcher is available at
+The translation effort for Prism Launcher is hosted on [Weblate](https://hosted.weblate.org/projects/prismlauncher/launcher/) and information about translating Prism Launcher is available at .
## Building
diff --git a/buildconfig/BuildConfig.cpp.in b/buildconfig/BuildConfig.cpp.in
index 1eb0022b8..b40cacb0f 100644
--- a/buildconfig/BuildConfig.cpp.in
+++ b/buildconfig/BuildConfig.cpp.in
@@ -33,6 +33,7 @@
* limitations under the License.
*/
+#include
#include "BuildConfig.h"
#include
@@ -59,8 +60,16 @@ Config::Config()
VERSION_MINOR = @Launcher_VERSION_MINOR@;
BUILD_PLATFORM = "@Launcher_BUILD_PLATFORM@";
+ BUILD_ARTIFACT = "@Launcher_BUILD_ARTIFACT@";
BUILD_DATE = "@Launcher_BUILD_TIMESTAMP@";
- UPDATER_BASE = "@Launcher_UPDATER_BASE@";
+ UPDATER_GITHUB_REPO = "@Launcher_UPDATER_GITHUB_REPO@";
+
+ COMPILER_NAME = "@Launcher_COMPILER_NAME@";
+ COMPILER_VERSION = "@Launcher_COMPILER_VERSION@";
+
+ COMPILER_TARGET_SYSTEM = "@Launcher_COMPILER_TARGET_SYSTEM@";
+ COMPILER_TARGET_SYSTEM_VERSION = "@Launcher_COMPILER_TARGET_SYSTEM_VERSION@";
+ COMPILER_TARGET_SYSTEM_PROCESSOR = "@Launcher_COMPILER_TARGET_PROCESSOR@";
MAC_SPARKLE_PUB_KEY = "@MACOSX_SPARKLE_UPDATE_PUBLIC_KEY@";
MAC_SPARKLE_APPCAST_URL = "@MACOSX_SPARKLE_UPDATE_FEED_URL@";
@@ -68,6 +77,8 @@ Config::Config()
if (!MAC_SPARKLE_PUB_KEY.isEmpty() && !MAC_SPARKLE_APPCAST_URL.isEmpty())
{
UPDATER_ENABLED = true;
+ } else if(!UPDATER_GITHUB_REPO.isEmpty() && !BUILD_ARTIFACT.isEmpty()) {
+ UPDATER_ENABLED = true;
}
GIT_COMMIT = "@Launcher_GIT_COMMIT@";
@@ -88,10 +99,7 @@ Config::Config()
if (GIT_REFSPEC.startsWith("refs/heads/"))
{
VERSION_CHANNEL = GIT_REFSPEC;
- VERSION_CHANNEL.remove("refs/heads/");
- if(!UPDATER_BASE.isEmpty() && !BUILD_PLATFORM.isEmpty()) {
- UPDATER_ENABLED = true;
- }
+ VERSION_CHANNEL.remove("refs/heads/");
}
else if (!GIT_COMMIT.isEmpty())
{
@@ -136,3 +144,16 @@ QString Config::printableVersionString() const
}
return vstr;
}
+
+QString Config::compilerID() const
+{
+ if (COMPILER_VERSION.isEmpty())
+ return COMPILER_NAME;
+ return QStringLiteral("%1 - %2").arg(COMPILER_NAME).arg(COMPILER_VERSION);
+}
+
+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 a5649b98f..77b6eef54 100644
--- a/buildconfig/BuildConfig.h
+++ b/buildconfig/BuildConfig.h
@@ -71,11 +71,29 @@ class Config {
/// A short string identifying this build's platform or distribution.
QString BUILD_PLATFORM;
+ /// A short string identifying this build's valid artifacts int he updater. For example, "lin64" or "win32".
+ QString BUILD_ARTIFACT;
+
/// A string containing the build timestamp
QString BUILD_DATE;
+ /// A string identifying the compiler use to build
+ QString COMPILER_NAME;
+
+ /// A string identifying the compiler version used to build
+ QString COMPILER_VERSION;
+
+ /// A string identifying the compiler target system os
+ QString COMPILER_TARGET_SYSTEM;
+
+ /// A String identifying the compiler target system version
+ QString COMPILER_TARGET_SYSTEM_VERSION;
+
+ /// A String identifying the compiler target processor
+ QString COMPILER_TARGET_SYSTEM_PROCESSOR;
+
/// URL for the updater's channel
- QString UPDATER_BASE;
+ QString UPDATER_GITHUB_REPO;
/// The public key used to sign releases for the Sparkle updater appcast
QString MAC_SPARKLE_PUB_KEY;
@@ -175,6 +193,18 @@ class Config {
* \return The version number in string format (major.minor.revision.build).
*/
QString printableVersionString() const;
+
+ /**
+ * \brief Compiler ID String
+ * \return a string of the form "Name - Version" of just "Name" if the version is empty
+ */
+ QString compilerID() const;
+
+ /**
+ * \brief System ID String
+ * \return a string of the form "OS Verison Processor"
+ */
+ QString systemID() const;
};
extern const Config BuildConfig;
diff --git a/cmake/CompilerWarnings.cmake b/cmake/CompilerWarnings.cmake
index 635e54289..51d2fb13a 100644
--- a/cmake/CompilerWarnings.cmake
+++ b/cmake/CompilerWarnings.cmake
@@ -68,6 +68,8 @@ function(
/w14906 # string literal cast to 'LPWSTR'
/w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied
/permissive- # standards conformance mode for MSVC compiler.
+
+ /we4062 # forbid omitting a possible value of an enum in a switch statement
)
endif()
@@ -75,7 +77,6 @@ function(
set(CLANG_WARNINGS
-Wall
-Wextra # reasonable and standard
- -Wextra-semi # Warn about semicolon after in-class function definition.
-Wshadow # warn the user if a variable declaration shadows one from a parent context
-Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps
# catch hard to track down memory errors
@@ -90,6 +91,12 @@ function(
-Wdouble-promotion # warn if float is implicit promoted to double
-Wformat=2 # warn on security issues around functions that format output (ie printf)
-Wimplicit-fallthrough # warn on statements that fallthrough without an explicit annotation
+ # -Wgnu-zero-variadic-macro-arguments (part of -pedantic) is triggered by every qCDebug() call and therefore results
+ # in a lot of noise. This warning is only notifying us that clang is emulating the GCC behaviour
+ # instead of the exact standard wording so we can safely ignore it
+ -Wno-gnu-zero-variadic-macro-arguments
+
+ -Werror=switch # forbid omitting a possible value of an enum in a switch statement
)
endif()
@@ -101,6 +108,8 @@ function(
-Wduplicated-branches # warn if if / else branches have duplicated code
-Wlogical-op # warn about logical operations being used where bitwise were probably wanted
-Wuseless-cast # warn if you perform a cast to the same type
+
+ -Werror=switch # forbid omitting a possible value of an enum in a switch statement
)
endif()
@@ -125,6 +134,8 @@ function(
-Woverloaded-virtual
-Wuseless-cast
-Wextra-semi
+
+ -Werror=switch # forbid omitting a possible value of an enum in a switch statement
)
target_compile_options(
diff --git a/flake.lock b/flake.lock
index 28ef60f30..3422af0ac 100644
--- a/flake.lock
+++ b/flake.lock
@@ -3,11 +3,11 @@
"flake-compat": {
"flake": false,
"locked": {
- "lastModified": 1673956053,
- "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
+ "lastModified": 1696426674,
+ "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
- "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
+ "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
@@ -21,11 +21,11 @@
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
- "lastModified": 1690933134,
- "narHash": "sha256-ab989mN63fQZBFrkk4Q8bYxQCktuHmBIBqUG1jl6/FQ=",
+ "lastModified": 1698882062,
+ "narHash": "sha256-HkhafUayIqxXyHH1X8d9RDl1M2CkFgZLjKD3MzabiEo=",
"owner": "hercules-ci",
"repo": "flake-parts",
- "rev": "59cf3f1447cfc75087e7273b04b31e689a8599fb",
+ "rev": "8c9fa2545007b49a5db5f650ae91f227672c3877",
"type": "github"
},
"original": {
@@ -76,11 +76,11 @@
"libnbtplusplus": {
"flake": false,
"locked": {
- "lastModified": 1690036783,
- "narHash": "sha256-A5kTgICnx+Qdq3Fir/bKTfdTt/T1NQP2SC+nhN1ENug=",
+ "lastModified": 1699286814,
+ "narHash": "sha256-yy0q+bky80LtK1GWzz7qpM+aAGrOqLuewbid8WT1ilk=",
"owner": "PrismLauncher",
"repo": "libnbtplusplus",
- "rev": "a5e8fd52b8bf4ab5d5bcc042b2a247867589985f",
+ "rev": "23b955121b8217c1c348a9ed2483167a6f3ff4ad",
"type": "github"
},
"original": {
@@ -89,13 +89,28 @@
"type": "github"
}
},
+ "nix-filter": {
+ "locked": {
+ "lastModified": 1694857738,
+ "narHash": "sha256-bxxNyLHjhu0N8T3REINXQ2ZkJco0ABFPn6PIe2QUfqo=",
+ "owner": "numtide",
+ "repo": "nix-filter",
+ "rev": "41fd48e00c22b4ced525af521ead8792402de0ea",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "nix-filter",
+ "type": "github"
+ }
+ },
"nixpkgs": {
"locked": {
- "lastModified": 1692463654,
- "narHash": "sha256-F8hZmsQINI+S6UROM4jyxAMbQLtzE44pI8Nk6NtMdao=",
+ "lastModified": 1700108881,
+ "narHash": "sha256-+Lqybl8kj0+nD/IlAWPPG/RDTa47gff9nbei0u7BntE=",
"owner": "nixos",
"repo": "nixpkgs",
- "rev": "ca3c9ac9f4cdd4bea19f592b32bb59b74ab7d783",
+ "rev": "7414e9ee0b3e9903c24d3379f577a417f0aae5f1",
"type": "github"
},
"original": {
@@ -108,11 +123,11 @@
"nixpkgs-lib": {
"locked": {
"dir": "lib",
- "lastModified": 1690881714,
- "narHash": "sha256-h/nXluEqdiQHs1oSgkOOWF+j8gcJMWhwnZ9PFabN6q0=",
+ "lastModified": 1698611440,
+ "narHash": "sha256-jPjHjrerhYDy3q9+s5EAsuhyhuknNfowY6yt6pjn9pc=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "9e1960bc196baf6881340d53dccb203a951745a2",
+ "rev": "0cbe9f69c234a7700596e943bfae7ef27a31b735",
"type": "github"
},
"original": {
@@ -138,11 +153,11 @@
]
},
"locked": {
- "lastModified": 1692274144,
- "narHash": "sha256-BxTQuRUANQ81u8DJznQyPmRsg63t4Yc+0kcyq6OLz8s=",
+ "lastModified": 1700064067,
+ "narHash": "sha256-1ZWNDzhu8UlVCK7+DUN9dVQfiHX1bv6OQP9VxstY/gs=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
- "rev": "7e3517c03d46159fdbf8c0e5c97f82d5d4b0c8fa",
+ "rev": "e558068cba67b23b4fbc5537173dbb43748a17e8",
"type": "github"
},
"original": {
@@ -156,6 +171,7 @@
"flake-compat": "flake-compat",
"flake-parts": "flake-parts",
"libnbtplusplus": "libnbtplusplus",
+ "nix-filter": "nix-filter",
"nixpkgs": "nixpkgs",
"pre-commit-hooks": "pre-commit-hooks"
}
diff --git a/flake.nix b/flake.nix
index c3148fe03..afb0ec63a 100644
--- a/flake.nix
+++ b/flake.nix
@@ -4,6 +4,7 @@
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
+ nix-filter.url = "github:numtide/nix-filter";
pre-commit-hooks = {
url = "github:cachix/pre-commit-hooks.nix";
inputs.nixpkgs.follows = "nixpkgs";
@@ -20,8 +21,24 @@
};
};
- outputs = inputs:
- inputs.flake-parts.lib.mkFlake
- {inherit inputs;}
- {imports = [./nix];};
+ outputs = {
+ flake-parts,
+ pre-commit-hooks,
+ ...
+ } @ inputs:
+ flake-parts.lib.mkFlake {inherit inputs;} {
+ imports = [
+ pre-commit-hooks.flakeModule
+
+ ./nix/dev.nix
+ ./nix/distribution.nix
+ ];
+
+ systems = [
+ "x86_64-linux"
+ "aarch64-linux"
+ "x86_64-darwin"
+ "aarch64-darwin"
+ ];
+ };
}
diff --git a/flatpak/org.prismlauncher.PrismLauncher.yml b/flatpak/org.prismlauncher.PrismLauncher.yml
index 46b6da36a..89727751e 100644
--- a/flatpak/org.prismlauncher.PrismLauncher.yml
+++ b/flatpak/org.prismlauncher.PrismLauncher.yml
@@ -1,6 +1,6 @@
id: org.prismlauncher.PrismLauncher
runtime: org.kde.Platform
-runtime-version: "5.15-22.08"
+runtime-version: "5.15-23.08"
sdk: org.kde.Sdk
sdk-extensions:
- org.freedesktop.Sdk.Extension.openjdk17
@@ -113,6 +113,9 @@ modules:
version-query: .tag_name
url-query: .tarball_url
timestamp-query: .published_at
+ # from https://github.com/flathub/net.gaijin.WarThunder/blob/7ea6f7a9f84b9c77150c003a7059dc03f8dcbc7f/gamemode.patch
+ - type: patch
+ path: patches/gamemode.patch
cleanup:
- /include
- /lib/pkgconfig
diff --git a/flatpak/patches/gamemode.patch b/flatpak/patches/gamemode.patch
new file mode 100644
index 000000000..3cc0d7412
--- /dev/null
+++ b/flatpak/patches/gamemode.patch
@@ -0,0 +1,12 @@
+diff -ruN a/common/common-pidfds.c b/common/common-pidfds.c
+--- a/common/common-pidfds.c 2021-02-18 20:00:12.000000000 +0100
++++ b/common/common-pidfds.c 2023-09-07 08:57:42.954362763 +0200
+@@ -58,6 +58,8 @@
+ {
+ return (int)syscall(__NR_pidfd_open, pid, flags);
+ }
++#else
++#include
+ #endif
+
+ /* pidfd functions */
diff --git a/garnix.yaml b/garnix.yaml
index 3bf145248..6cf8f7214 100644
--- a/garnix.yaml
+++ b/garnix.yaml
@@ -1,5 +1,6 @@
builds:
- exclude: []
+ exclude:
+ - "*.x86_64-darwin.*"
include:
- "checks.x86_64-linux.*"
- "devShells.*.*"
diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index 000104656..be252f1c5 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -58,6 +58,7 @@
#include "ui/pages/global/APIPage.h"
#include "ui/pages/global/AccountListPage.h"
#include "ui/pages/global/CustomCommandsPage.h"
+#include "ui/pages/global/EnvironmentVariablesPage.h"
#include "ui/pages/global/ExternalToolsPage.h"
#include "ui/pages/global/JavaPage.h"
#include "ui/pages/global/LanguagePage.h"
@@ -122,6 +123,7 @@
#include
#include
+#include
#include
#ifdef Q_OS_LINUX
@@ -130,9 +132,13 @@
#include "gamemode_client.h"
#endif
-#if defined(Q_OS_MAC) && defined(SPARKLE_ENABLED)
+#if defined(Q_OS_MAC)
+#if defined(SPARKLE_ENABLED)
#include "updater/MacSparkleUpdater.h"
#endif
+#else
+#include "updater/PrismExternalUpdater.h"
+#endif
#if defined Q_OS_WIN32
#include "WindowsConsole.h"
@@ -164,6 +170,34 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext& context, const QSt
} // namespace
+std::tuple read_lock_File(const QString& path)
+{
+ auto contents = QString(FS::read(path));
+ auto lines = contents.split('\n');
+
+ QDateTime timestamp;
+ QString from, to, target, data_path;
+ for (auto line : lines) {
+ auto index = line.indexOf("=");
+ if (index < 0)
+ continue;
+ auto left = line.left(index);
+ auto right = line.mid(index + 1);
+ if (left.toLower() == "timestamp") {
+ timestamp = QDateTime::fromString(right, Qt::ISODate);
+ } else if (left.toLower() == "from") {
+ from = right;
+ } else if (left.toLower() == "to") {
+ to = right;
+ } else if (left.toLower() == "target") {
+ target = right;
+ } else if (left.toLower() == "data_path") {
+ data_path = right;
+ }
+ }
+ return std::make_tuple(timestamp, from, to, target, data_path);
+}
+
Application::Application(int& argc, char** argv) : QApplication(argc, argv)
{
#if defined Q_OS_WIN32
@@ -296,6 +330,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
.arg(dataPath));
return;
}
+ m_dataPath = dataPath;
/*
* Establish the mechanism for communication with an already running PrismLauncher that uses the same data path.
@@ -450,11 +485,16 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
}
{
- qDebug() << BuildConfig.LAUNCHER_DISPLAYNAME << ", (c) 2013-2021 " << BuildConfig.LAUNCHER_COPYRIGHT;
+ qDebug() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME) << ", (c) 2022-2023 "
+ << qPrintable(QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", "));
qDebug() << "Version : " << BuildConfig.printableVersionString();
qDebug() << "Platform : " << BuildConfig.BUILD_PLATFORM;
qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT;
qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC;
+ qDebug() << "Compiled for : " << BuildConfig.systemID();
+ qDebug() << "Compiled by : " << BuildConfig.compilerID();
+ qDebug() << "Build Artifact : " << BuildConfig.BUILD_ARTIFACT;
+ qDebug() << "Updates Enabled : " << (updaterEnabled() ? "Yes" : "No");
if (adjustedBy.size()) {
qDebug() << "Work dir before adjustment : " << origcwdPath;
qDebug() << "Work dir after adjustment : " << QDir::currentPath();
@@ -503,6 +543,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("MenuBarInsteadOfToolBar", false);
+ m_settings->registerSetting("NumberOfConcurrentTasks", 10);
+ m_settings->registerSetting("NumberOfConcurrentDownloads", 6);
+
QString defaultMonospace;
int defaultSize = 11;
#ifdef Q_OS_WIN32
@@ -579,6 +622,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("IgnoreJavaCompatibility", false);
m_settings->registerSetting("IgnoreJavaWizard", false);
+ // Legacy settings
+ m_settings->registerSetting("OnlineFixes", false);
+
// Native library workarounds
m_settings->registerSetting("UseNativeOpenAL", false);
m_settings->registerSetting("CustomOpenALPath", "");
@@ -594,9 +640,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("ShowGameTime", true);
m_settings->registerSetting("ShowGlobalGameTime", true);
m_settings->registerSetting("RecordGameTime", true);
+ m_settings->registerSetting("ShowGameTimeWithoutDays", false);
// Minecraft mods
m_settings->registerSetting("ModMetadataDisabled", false);
+ m_settings->registerSetting("ModDependenciesDisabled", false);
// Minecraft offline player name
m_settings->registerSetting("LastOfflinePlayerName", "");
@@ -673,6 +721,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("CloseAfterLaunch", false);
m_settings->registerSetting("QuitAfterGameStop", false);
+ m_settings->registerSetting("Env", QVariant(QMap()));
+
// Custom Microsoft Authentication Client ID
m_settings->registerSetting("MSAClientIDOverride", "");
@@ -698,6 +748,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_globalSettingsProvider->addPage();
m_globalSettingsProvider->addPage();
m_globalSettingsProvider->addPage();
+ m_globalSettingsProvider->addPage();
m_globalSettingsProvider->addPage();
m_globalSettingsProvider->addPage();
m_globalSettingsProvider->addPage();
@@ -734,15 +785,6 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
qDebug() << "<> Translations loaded.";
}
- // initialize the updater
- if (BuildConfig.UPDATER_ENABLED) {
- qDebug() << "Initializing updater";
-#if defined(Q_OS_MAC) && defined(SPARKLE_ENABLED)
- m_updater.reset(new MacSparkleUpdater());
-#endif
- qDebug() << "<> Updater started.";
- }
-
// Instance icons
{
auto setting = APPLICATION->settings()->getSetting("IconsDir");
@@ -845,6 +887,107 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
detectLibraries();
+ // check update locks
+ {
+ auto update_log_path = FS::PathCombine(m_dataPath, "logs", "prism_launcher_update.log");
+
+ auto update_lock = QFileInfo(FS::PathCombine(m_dataPath, ".prism_launcher_update.lock"));
+ if (update_lock.exists()) {
+ auto [timestamp, from, to, target, data_path] = read_lock_File(update_lock.absoluteFilePath());
+ auto infoMsg = tr("This installation has a update lock file present at: %1\n"
+ "\n"
+ "Timestamp: %2\n"
+ "Updating from version %3 to %4\n"
+ "Target install path: %5\n"
+ "Data Path: %6"
+ "\n"
+ "This likely means that a update attempt failed. Please ensure your installation is in working order before "
+ "proceeding.\n"
+ "Check the Prism Launcher updater log at: \n"
+ "%7\n"
+ "for details on the last update attempt.\n"
+ "\n"
+ "To delete this lock and proceed select \"Ignore\" below.")
+ .arg(update_lock.absoluteFilePath())
+ .arg(timestamp.toString(Qt::ISODate), from, to, target, data_path)
+ .arg(update_log_path);
+ auto msgBox = QMessageBox(QMessageBox::Warning, tr("Update In Progress"), infoMsg, QMessageBox::Ignore | QMessageBox::Abort);
+ msgBox.setDefaultButton(QMessageBox::Abort);
+ msgBox.setModal(true);
+ msgBox.setDetailedText(FS::read(update_log_path));
+ msgBox.setMinimumWidth(460);
+ msgBox.adjustSize();
+ auto res = msgBox.exec();
+ switch (res) {
+ case QMessageBox::Ignore: {
+ FS::deletePath(update_lock.absoluteFilePath());
+ break;
+ }
+ case QMessageBox::Abort:
+ [[fallthrough]];
+ default: {
+ qDebug() << "Exiting because update lockfile is present";
+ QMetaObject::invokeMethod(
+ this, []() { exit(1); }, Qt::QueuedConnection);
+ return;
+ }
+ }
+ }
+
+ auto update_fail_marker = QFileInfo(FS::PathCombine(m_dataPath, ".prism_launcher_update.fail"));
+ if (update_fail_marker.exists()) {
+ auto infoMsg = tr("An update attempt failed\n"
+ "\n"
+ "Please ensure your installation is in working order before "
+ "proceeding.\n"
+ "Check the Prism Launcher updater log at: \n"
+ "%1\n"
+ "for details on the last update attempt.")
+ .arg(update_log_path);
+ auto msgBox = QMessageBox(QMessageBox::Warning, tr("Update Failed"), infoMsg, QMessageBox::Ignore | QMessageBox::Abort);
+ msgBox.setDefaultButton(QMessageBox::Abort);
+ msgBox.setModal(true);
+ msgBox.setDetailedText(FS::read(update_log_path));
+ msgBox.setMinimumWidth(460);
+ msgBox.adjustSize();
+ auto res = msgBox.exec();
+ switch (res) {
+ case QMessageBox::Ignore: {
+ FS::deletePath(update_fail_marker.absoluteFilePath());
+ break;
+ }
+ case QMessageBox::Abort:
+ [[fallthrough]];
+ default: {
+ qDebug() << "Exiting because update lockfile is present";
+ QMetaObject::invokeMethod(
+ this, []() { exit(1); }, Qt::QueuedConnection);
+ return;
+ }
+ }
+ }
+
+ auto update_success_marker = QFileInfo(FS::PathCombine(m_dataPath, ".prism_launcher_update.success"));
+ if (update_success_marker.exists()) {
+ auto infoMsg = tr("Update succeeded\n"
+ "\n"
+ "You are now running %1 .\n"
+ "Check the Prism Launcher updater log at: \n"
+ "%1\n"
+ "for details.")
+ .arg(BuildConfig.printableVersionString())
+ .arg(update_log_path);
+ auto msgBox = new QMessageBox(QMessageBox::Information, tr("Update Succeeded"), infoMsg, QMessageBox::Ok);
+ msgBox->setDefaultButton(QMessageBox::Ok);
+ msgBox->setDetailedText(FS::read(update_log_path));
+ msgBox->setAttribute(Qt::WA_DeleteOnClose);
+ msgBox->setMinimumWidth(460);
+ msgBox->adjustSize();
+ msgBox->open();
+ FS::deletePath(update_success_marker.absoluteFilePath());
+ }
+ }
+
if (createSetupWizard()) {
return;
}
@@ -913,6 +1056,26 @@ bool Application::createSetupWizard()
return false;
}
+bool Application::updaterEnabled()
+{
+#if defined(Q_OS_MAC)
+ return BuildConfig.UPDATER_ENABLED;
+#else
+ return BuildConfig.UPDATER_ENABLED && QFileInfo(FS::PathCombine(m_rootPath, updaterBinaryName())).isFile();
+#endif
+}
+
+QString Application::updaterBinaryName()
+{
+ auto exe_name = QStringLiteral("%1_updater").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME);
+#if defined Q_OS_WIN32
+ exe_name.append(".exe");
+#else
+ exe_name.prepend("bin/");
+#endif
+ return exe_name;
+}
+
bool Application::event(QEvent* event)
{
#ifdef Q_OS_MACOS
@@ -981,6 +1144,20 @@ void Application::performMainStartupAction()
showMainWindow(false);
qDebug() << "<> Main window shown.";
}
+
+ // initialize the updater
+ if (updaterEnabled()) {
+ qDebug() << "Initializing updater";
+#ifdef Q_OS_MAC
+#if defined(SPARKLE_ENABLED)
+ m_updater.reset(new MacSparkleUpdater());
+#endif
+#else
+ m_updater.reset(new PrismExternalUpdater(m_mainWindow, m_rootPath, m_dataPath));
+#endif
+ qDebug() << "<> Updater started.";
+ }
+
if (!m_urlsToImport.isEmpty()) {
qDebug() << "<> Importing from url:" << m_urlsToImport;
m_mainWindow->processURLs(m_urlsToImport);
diff --git a/launcher/Application.h b/launcher/Application.h
index b227bb813..7669e08ec 100644
--- a/launcher/Application.h
+++ b/launcher/Application.h
@@ -159,6 +159,9 @@ class Application : public QApplication {
/// this is the root of the 'installation'. Used for automatic updates
const QString& root() { return m_rootPath; }
+ /// the data path the application is using
+ const QString& dataRoot() { return m_dataPath; }
+
bool isPortable() { return m_portable; }
const Capabilities capabilities() { return m_capabilities; }
@@ -179,6 +182,9 @@ class Application : public QApplication {
int suitableMaxMem();
+ bool updaterEnabled();
+ QString updaterBinaryName();
+
QUrl normalizeImportUrl(QString const& url);
signals:
@@ -244,6 +250,7 @@ class Application : public QApplication {
QMap> m_profilers;
QString m_rootPath;
+ QString m_dataPath;
Status m_status = Application::StartingUp;
Capabilities m_capabilities;
bool m_portable = false;
diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp
index 725036395..33dc3f741 100644
--- a/launcher/BaseInstance.cpp
+++ b/launcher/BaseInstance.cpp
@@ -388,7 +388,7 @@ QString BaseInstance::name() const
QString BaseInstance::windowTitle() const
{
- return BuildConfig.LAUNCHER_DISPLAYNAME + ": " + name().replace(QRegularExpression("\\s+"), " ");
+ return BuildConfig.LAUNCHER_DISPLAYNAME + ": " + name();
}
// FIXME: why is this here? move it to MinecraftInstance!!!
diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h
index 47fff5957..f4ed9113c 100644
--- a/launcher/BaseInstance.h
+++ b/launcher/BaseInstance.h
@@ -64,7 +64,7 @@ class LaunchTask;
class BaseInstance;
// pointer for lazy people
-typedef std::shared_ptr InstancePtr;
+using InstancePtr = std::shared_ptr;
/*!
* \brief Base class for instances.
diff --git a/launcher/BaseVersionList.h b/launcher/BaseVersionList.h
index fe1550c21..231887c4e 100644
--- a/launcher/BaseVersionList.h
+++ b/launcher/BaseVersionList.h
@@ -51,7 +51,7 @@ class BaseVersionList : public QAbstractListModel {
ArchitectureRole,
SortRole
};
- typedef QList RoleList;
+ using RoleList = QList;
explicit BaseVersionList(QObject* parent = 0);
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 18e0acab1..99acf8fc5 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -139,6 +139,7 @@ set(NET_SOURCES
net/HeaderProxy.h
net/RawHeaderProxy.h
net/ApiHeaderProxy.h
+ net/StaticHeaderProxy.h
net/ApiDownload.h
net/ApiDownload.cpp
net/ApiUpload.cpp
@@ -181,6 +182,11 @@ set(MAC_UPDATE_SOURCES
updater/MacSparkleUpdater.mm
)
+set(PRISM_UPDATE_SOURCES
+ updater/PrismExternalUpdater.h
+ updater/PrismExternalUpdater.cpp
+)
+
# Backend for the news bar... there's usually no news.
set(NEWS_SOURCES
# News System
@@ -216,13 +222,9 @@ set(MINECRAFT_SOURCES
minecraft/auth/MinecraftAccount.h
minecraft/auth/Parsers.cpp
minecraft/auth/Parsers.h
- minecraft/auth/Yggdrasil.cpp
- minecraft/auth/Yggdrasil.h
minecraft/auth/flows/AuthFlow.cpp
minecraft/auth/flows/AuthFlow.h
- minecraft/auth/flows/Mojang.cpp
- minecraft/auth/flows/Mojang.h
minecraft/auth/flows/MSA.cpp
minecraft/auth/flows/MSA.h
minecraft/auth/flows/Offline.cpp
@@ -236,12 +238,8 @@ set(MINECRAFT_SOURCES
minecraft/auth/steps/GetSkinStep.h
minecraft/auth/steps/LauncherLoginStep.cpp
minecraft/auth/steps/LauncherLoginStep.h
- minecraft/auth/steps/MigrationEligibilityStep.cpp
- minecraft/auth/steps/MigrationEligibilityStep.h
minecraft/auth/steps/MinecraftProfileStep.cpp
minecraft/auth/steps/MinecraftProfileStep.h
- minecraft/auth/steps/MinecraftProfileStepMojang.cpp
- minecraft/auth/steps/MinecraftProfileStepMojang.h
minecraft/auth/steps/MSAStep.cpp
minecraft/auth/steps/MSAStep.h
minecraft/auth/steps/XboxAuthorizationStep.cpp
@@ -250,8 +248,6 @@ set(MINECRAFT_SOURCES
minecraft/auth/steps/XboxProfileStep.h
minecraft/auth/steps/XboxUserStep.cpp
minecraft/auth/steps/XboxUserStep.h
- minecraft/auth/steps/YggdrasilStep.cpp
- minecraft/auth/steps/YggdrasilStep.h
minecraft/gameoptions/GameOptions.h
minecraft/gameoptions/GameOptions.cpp
@@ -589,6 +585,63 @@ set(LINKEXE_SOURCES
DesktopServices.cpp
)
+set(PRISMUPDATER_SOURCES
+ updater/prismupdater/PrismUpdater.h
+ updater/prismupdater/PrismUpdater.cpp
+ updater/prismupdater/UpdaterDialogs.h
+ updater/prismupdater/UpdaterDialogs.cpp
+ updater/prismupdater/GitHubRelease.h
+ updater/prismupdater/GitHubRelease.cpp
+
+ Json.h
+ Json.cpp
+ FileSystem.h
+ FileSystem.cpp
+ StringUtils.h
+ StringUtils.cpp
+ DesktopServices.h
+ DesktopServices.cpp
+ Version.h
+ Version.cpp
+ Markdown.h
+ Markdown.cpp
+
+ # Zip
+ MMCZip.h
+ MMCZip.cpp
+
+ # Time
+ MMCTime.h
+ MMCTime.cpp
+
+ net/ByteArraySink.h
+ net/ChecksumValidator.h
+ net/Download.cpp
+ net/Download.h
+ net/FileSink.cpp
+ net/FileSink.h
+ net/HttpMetaCache.cpp
+ net/HttpMetaCache.h
+ net/Logging.h
+ net/Logging.cpp
+ net/NetAction.h
+ net/NetRequest.cpp
+ net/NetRequest.h
+ net/NetJob.cpp
+ net/NetJob.h
+ net/NetUtils.h
+ net/Sink.h
+ net/Validator.h
+ net/HeaderProxy.h
+ net/RawHeaderProxy.h
+
+ ui/dialogs/ProgressDialog.cpp
+ ui/dialogs/ProgressDialog.h
+ ui/widgets/SubTaskProgressBar.h
+ ui/widgets/SubTaskProgressBar.cpp
+
+)
+
######## Logging categories ########
ecm_qt_declare_logging_category(CORE_SOURCES
@@ -685,6 +738,8 @@ set(LOGIC_SOURCES
if(APPLE AND Launcher_ENABLE_UPDATER)
set (LOGIC_SOURCES ${LOGIC_SOURCES} ${MAC_UPDATE_SOURCES})
+else()
+ set (LOGIC_SOURCES ${LOGIC_SOURCES} ${PRISM_UPDATE_SOURCES})
endif()
SET(LAUNCHER_SOURCES
@@ -834,6 +889,8 @@ SET(LAUNCHER_SOURCES
ui/pages/global/AccountListPage.h
ui/pages/global/CustomCommandsPage.cpp
ui/pages/global/CustomCommandsPage.h
+ ui/pages/global/EnvironmentVariablesPage.cpp
+ ui/pages/global/EnvironmentVariablesPage.h
ui/pages/global/ExternalToolsPage.cpp
ui/pages/global/ExternalToolsPage.h
ui/pages/global/JavaPage.cpp
@@ -916,6 +973,9 @@ SET(LAUNCHER_SOURCES
ui/pages/modplatform/ImportPage.cpp
ui/pages/modplatform/ImportPage.h
+ ui/pages/modplatform/OptionalModDialog.cpp
+ ui/pages/modplatform/OptionalModDialog.h
+
ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
ui/pages/modplatform/modrinth/ModrinthResourceModels.h
ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp
@@ -944,8 +1004,6 @@ SET(LAUNCHER_SOURCES
ui/dialogs/IconPickerDialog.h
ui/dialogs/ImportResourceDialog.cpp
ui/dialogs/ImportResourceDialog.h
- ui/dialogs/LoginDialog.cpp
- ui/dialogs/LoginDialog.h
ui/dialogs/MSALoginDialog.cpp
ui/dialogs/MSALoginDialog.h
ui/dialogs/OfflineLoginDialog.cpp
@@ -984,6 +1042,8 @@ SET(LAUNCHER_SOURCES
ui/widgets/Common.h
ui/widgets/CustomCommands.cpp
ui/widgets/CustomCommands.h
+ ui/widgets/EnvironmentVariables.cpp
+ ui/widgets/EnvironmentVariables.h
ui/widgets/DropLabel.cpp
ui/widgets/DropLabel.h
ui/widgets/FocusLineEdit.cpp
@@ -1042,6 +1102,15 @@ SET(LAUNCHER_SOURCES
ui/instanceview/VisualGroup.h
)
+if (NOT Apple)
+set(LAUNCHER_SOURCES
+ ${LAUNCHER_SOURCES}
+
+ ui/dialogs/UpdateAvailableDialog.h
+ ui/dialogs/UpdateAvailableDialog.cpp
+)
+endif()
+
if(WIN32)
set(LAUNCHER_SOURCES
WindowsConsole.cpp
@@ -1080,10 +1149,12 @@ qt_wrap_ui(LAUNCHER_UI
ui/pages/modplatform/legacy_ftb/Page.ui
ui/pages/modplatform/import_ftb/ImportFTBPage.ui
ui/pages/modplatform/ImportPage.ui
+ ui/pages/modplatform/OptionalModDialog.ui
ui/pages/modplatform/modrinth/ModrinthPage.ui
ui/pages/modplatform/technic/TechnicPage.ui
ui/widgets/InstanceCardWidget.ui
ui/widgets/CustomCommands.ui
+ ui/widgets/EnvironmentVariables.ui
ui/widgets/InfoFrame.ui
ui/widgets/ModFilterWidget.ui
ui/widgets/SubTaskProgressBar.ui
@@ -1104,7 +1175,6 @@ qt_wrap_ui(LAUNCHER_UI
ui/dialogs/MSALoginDialog.ui
ui/dialogs/OfflineLoginDialog.ui
ui/dialogs/AboutDialog.ui
- ui/dialogs/LoginDialog.ui
ui/dialogs/EditAccountDialog.ui
ui/dialogs/ReviewMessageBox.ui
ui/dialogs/ScrollMessageBox.ui
@@ -1112,6 +1182,14 @@ qt_wrap_ui(LAUNCHER_UI
ui/dialogs/ChooseProviderDialog.ui
)
+qt_wrap_ui(PRISM_UPDATE_UI
+ ui/dialogs/UpdateAvailableDialog.ui
+)
+
+if (NOT Apple)
+ set (LAUNCHER_UI ${LAUNCHER_UI} ${PRISM_UPDATE_UI})
+endif()
+
qt_add_resources(LAUNCHER_RESOURCES
resources/backgrounds/backgrounds.qrc
resources/multimc/multimc.qrc
@@ -1128,6 +1206,12 @@ qt_add_resources(LAUNCHER_RESOURCES
../${Launcher_Branding_LogoQRC}
)
+qt_wrap_ui(PRISMUPDATER_UI
+ updater/prismupdater/SelectReleaseDialog.ui
+ ui/widgets/SubTaskProgressBar.ui
+ ui/dialogs/ProgressDialog.ui
+)
+
######## Windows resource files ########
if(WIN32)
set(LAUNCHER_RCS ${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_WindowsRC})
@@ -1137,12 +1221,16 @@ include(CompilerWarnings)
# Add executable
add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES})
+if(BUILD_TESTING)
+target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_TEST)
+endif()
set_project_warnings(Launcher_logic
"${Launcher_MSVC_WARNINGS}"
"${Launcher_CLANG_WARNINGS}"
"${Launcher_GCC_WARNINGS}")
target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION)
target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
+target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION)
target_link_libraries(Launcher_logic
systeminfo
Launcher_murmur2
@@ -1224,7 +1312,45 @@ install(TARGETS ${Launcher_Name}
FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime
)
-if(WIN32)
+if(Launcher_BUILD_UPDATER)
+ # Updater
+ add_library(prism_updater_logic STATIC ${PRISMUPDATER_SOURCES} ${TASKS_SOURCES} ${PRISMUPDATER_UI})
+ target_include_directories(prism_updater_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
+ target_link_libraries(prism_updater_logic
+ QuaZip::QuaZip
+ ${ZLIB_LIBRARIES}
+ systeminfo
+ BuildConfig
+ ghcFilesystem::ghc_filesystem
+ Qt${QT_VERSION_MAJOR}::Widgets
+ Qt${QT_VERSION_MAJOR}::Core
+ Qt${QT_VERSION_MAJOR}::Network
+ ${Launcher_QT_LIBS}
+ cmark::cmark
+ Katabasis
+ )
+
+ add_executable("${Launcher_Name}_updater" WIN32 updater/prismupdater/updater_main.cpp)
+ target_sources("${Launcher_Name}_updater" PRIVATE updater/prismupdater/updater.exe.manifest)
+ target_link_libraries("${Launcher_Name}_updater" prism_updater_logic)
+
+ if(DEFINED Launcher_APP_BINARY_NAME)
+ set_target_properties("${Launcher_Name}_updater" PROPERTIES OUTPUT_NAME "${Launcher_APP_BINARY_NAME}_updater")
+ endif()
+ if(DEFINED Launcher_BINARY_RPATH)
+ SET_TARGET_PROPERTIES("${Launcher_Name}_updater" PROPERTIES INSTALL_RPATH "${Launcher_BINARY_RPATH}")
+ endif()
+
+ install(TARGETS "${Launcher_Name}_updater"
+ BUNDLE DESTINATION "." COMPONENT Runtime
+ LIBRARY DESTINATION ${LIBRARY_DEST_DIR} COMPONENT Runtime
+ RUNTIME DESTINATION ${BINARY_DEST_DIR} COMPONENT Runtime
+ FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime
+ )
+endif()
+
+if(WIN32 OR (DEFINED Launcher_BUILD_FILELINKER AND Launcher_BUILD_FILELINKER))
+ # File link
add_library(filelink_logic STATIC ${LINKEXE_SOURCES})
set_project_warnings(filelink_logic
"${Launcher_MSVC_WARNINGS}"
@@ -1243,7 +1369,7 @@ if(WIN32)
${Launcher_QT_LIBS}
)
- add_executable("${Launcher_Name}_filelink" WIN32 filelink/main.cpp)
+ add_executable("${Launcher_Name}_filelink" WIN32 filelink/filelink_main.cpp)
target_sources("${Launcher_Name}_filelink" PRIVATE filelink/filelink.exe.manifest)
diff --git a/launcher/DataMigrationTask.h b/launcher/DataMigrationTask.h
index 6cc23b1a8..aba9f2399 100644
--- a/launcher/DataMigrationTask.h
+++ b/launcher/DataMigrationTask.h
@@ -18,7 +18,7 @@
class DataMigrationTask : public Task {
Q_OBJECT
public:
- explicit DataMigrationTask(QObject* parent, const QString& sourcePath, const QString& targetPath, const IPathMatcher::Ptr pathmatcher);
+ explicit DataMigrationTask(QObject* parent, const QString& sourcePath, const QString& targetPath, IPathMatcher::Ptr pathmatcher);
~DataMigrationTask() override = default;
protected:
diff --git a/launcher/DesktopServices.cpp b/launcher/DesktopServices.cpp
index 004e5e085..17eb7c2df 100644
--- a/launcher/DesktopServices.cpp
+++ b/launcher/DesktopServices.cpp
@@ -108,12 +108,9 @@ bool openDirectory(const QString& path, [[maybe_unused]] bool ensureExists)
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if (!isSandbox()) {
return IndirectOpen(f);
- } else {
- return f();
}
-#else
- return f();
#endif
+ return f();
}
bool openFile(const QString& path)
diff --git a/launcher/FileIgnoreProxy.cpp b/launcher/FileIgnoreProxy.cpp
index 4c8c64c72..df06c3c75 100644
--- a/launcher/FileIgnoreProxy.cpp
+++ b/launcher/FileIgnoreProxy.cpp
@@ -267,10 +267,7 @@ bool FileIgnoreProxy::filterAcceptsRow(int sourceRow, const QModelIndex& sourceP
bool FileIgnoreProxy::ignoreFile(QFileInfo fileInfo) const
{
- auto fileName = fileInfo.fileName();
- auto path = relPath(fileInfo.absoluteFilePath());
- return std::any_of(m_ignoreFiles.cbegin(), m_ignoreFiles.cend(), [fileName](auto iFileName) { return fileName == iFileName; }) ||
- m_ignoreFilePaths.covers(path);
+ return m_ignoreFiles.contains(fileInfo.fileName()) || m_ignoreFilePaths.covers(relPath(fileInfo.absoluteFilePath()));
}
bool FileIgnoreProxy::filterFile(const QString& fileName) const
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index defb2cb9e..a30d0ae0b 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -123,26 +123,35 @@ namespace fs = ghc::filesystem;
#if defined(__MINGW32__)
-typedef struct _DUPLICATE_EXTENTS_DATA {
+struct _DUPLICATE_EXTENTS_DATA {
HANDLE FileHandle;
LARGE_INTEGER SourceFileOffset;
LARGE_INTEGER TargetFileOffset;
LARGE_INTEGER ByteCount;
-} DUPLICATE_EXTENTS_DATA, *PDUPLICATE_EXTENTS_DATA;
+};
-typedef struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER {
+using DUPLICATE_EXTENTS_DATA = _DUPLICATE_EXTENTS_DATA;
+using PDUPLICATE_EXTENTS_DATA = _DUPLICATE_EXTENTS_DATA*;
+
+struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER {
WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32
WORD Reserved; // Must be 0
DWORD Flags; // FSCTL_INTEGRITY_FLAG_xxx
DWORD ChecksumChunkSizeInBytes;
DWORD ClusterSizeInBytes;
-} FSCTL_GET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_GET_INTEGRITY_INFORMATION_BUFFER;
+};
-typedef struct _FSCTL_SET_INTEGRITY_INFORMATION_BUFFER {
+using FSCTL_GET_INTEGRITY_INFORMATION_BUFFER = _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER;
+using PFSCTL_GET_INTEGRITY_INFORMATION_BUFFER = _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER*;
+
+struct _FSCTL_SET_INTEGRITY_INFORMATION_BUFFER {
WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32
WORD Reserved; // Must be 0
DWORD Flags; // FSCTL_INTEGRITY_FLAG_xxx
-} FSCTL_SET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_SET_INTEGRITY_INFORMATION_BUFFER;
+};
+
+using FSCTL_SET_INTEGRITY_INFORMATION_BUFFER = _FSCTL_SET_INTEGRITY_INFORMATION_BUFFER;
+using PFSCTL_SET_INTEGRITY_INFORMATION_BUFFER = _FSCTL_SET_INTEGRITY_INFORMATION_BUFFER*;
#endif
@@ -194,6 +203,40 @@ void write(const QString& filename, const QByteArray& data)
}
}
+void appendSafe(const QString& filename, const QByteArray& data)
+{
+ ensureExists(QFileInfo(filename).dir());
+ QByteArray buffer;
+ try {
+ buffer = read(filename);
+ } catch (FileSystemException&) {
+ buffer = QByteArray();
+ }
+ buffer.append(data);
+ QSaveFile file(filename);
+ if (!file.open(QSaveFile::WriteOnly)) {
+ throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
+ }
+ if (buffer.size() != file.write(buffer)) {
+ throw FileSystemException("Error writing data to " + filename + ": " + file.errorString());
+ }
+ if (!file.commit()) {
+ throw FileSystemException("Error while committing data to " + filename + ": " + file.errorString());
+ }
+}
+
+void append(const QString& filename, const QByteArray& data)
+{
+ ensureExists(QFileInfo(filename).dir());
+ QFile file(filename);
+ if (!file.open(QFile::Append)) {
+ throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
+ }
+ if (data.size() != file.write(data)) {
+ throw FileSystemException("Error writing data to " + filename + ": " + file.errorString());
+ }
+}
+
QByteArray read(const QString& filename)
{
QFile file(filename);
@@ -238,6 +281,28 @@ bool ensureFolderPathExists(QString foldernamepath)
return success;
}
+bool copyFileAttributes(QString src, QString dst)
+{
+#ifdef Q_OS_WIN32
+ auto attrs = GetFileAttributesW(src.toStdWString().c_str());
+ if (attrs == INVALID_FILE_ATTRIBUTES)
+ return false;
+ return SetFileAttributesW(dst.toStdWString().c_str(), attrs);
+#endif
+ return true;
+}
+
+// needs folders to exists
+void copyFolderAttributes(QString src, QString dst, QString relative)
+{
+ auto path = PathCombine(src, relative);
+ QDir dsrc(src);
+ while ((path = QFileInfo(path).path()).length() >= src.length()) {
+ auto dst_path = PathCombine(dst, dsrc.relativeFilePath(path));
+ copyFileAttributes(path, dst_path);
+ }
+}
+
/**
* @brief Copies a directory and it's contents from src to dest
* @param offset subdirectory form src to copy to dest
@@ -265,6 +330,9 @@ bool copy::operator()(const QString& offset, bool dryRun)
if (!m_followSymlinks)
opt |= copy_opts::copy_symlinks;
+ if (m_overwrite)
+ opt |= copy_opts::overwrite_existing;
+
// Function that'll do the actual copying
auto copy_file = [&](QString src_path, QString relative_dst_path) {
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
@@ -273,6 +341,9 @@ bool copy::operator()(const QString& offset, bool dryRun)
auto dst_path = PathCombine(dst, relative_dst_path);
if (!dryRun) {
ensureFilePathExists(dst_path);
+#ifdef Q_OS_WIN32
+ copyFolderAttributes(src, dst, relative_dst_path);
+#endif
fs::copy(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), opt, err);
}
if (err) {
@@ -872,6 +943,8 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
<< "\n";
stream << "Type=Application"
<< "\n";
+ stream << "Categories=Game;ActionGame;AdventureGame;Simulation"
+ << "\n";
stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n";
stream << "Name=" << name.toLocal8Bit() << "\n";
if (!icon.isEmpty()) {
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index 946e55a2c..c2fa873f0 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -61,6 +61,16 @@ class FileSystemException : public ::Exception {
*/
void write(const QString& filename, const QByteArray& data);
+/**
+ * append data to a file safely
+ */
+void appendSafe(const QString& filename, const QByteArray& data);
+
+/**
+ * append data to a file
+ */
+void append(const QString& filename, const QByteArray& data);
+
/**
* read data from a file safely\
*/
@@ -109,6 +119,11 @@ class copy : public QObject {
m_whitelist = whitelist;
return *this;
}
+ copy& overwrite(const bool overwrite)
+ {
+ m_overwrite = overwrite;
+ return *this;
+ }
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
@@ -128,6 +143,7 @@ class copy : public QObject {
bool m_followSymlinks = true;
const IPathMatcher* m_matcher = nullptr;
bool m_whitelist = false;
+ bool m_overwrite = false;
QDir m_src;
QDir m_dst;
qsizetype m_copied;
diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp
index bc139e4f1..9f0cf7460 100644
--- a/launcher/InstanceImportTask.cpp
+++ b/launcher/InstanceImportTask.cpp
@@ -59,7 +59,7 @@
#include
-InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent, QMap&& extra_info)
+InstanceImportTask::InstanceImportTask(const QUrl& sourceUrl, QWidget* parent, QMap&& extra_info)
: m_sourceUrl(sourceUrl), m_extra_info(extra_info), m_parent(parent)
{}
diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h
index ca3d30ad6..a1cf2560b 100644
--- a/launcher/InstanceImportTask.h
+++ b/launcher/InstanceImportTask.h
@@ -54,7 +54,7 @@ class FileResolvingTask;
class InstanceImportTask : public InstanceTask {
Q_OBJECT
public:
- explicit InstanceImportTask(const QUrl sourceUrl, QWidget* parent = nullptr, QMap&& extra_info = {});
+ explicit InstanceImportTask(const QUrl& sourceUrl, QWidget* parent = nullptr, QMap&& extra_info = {});
bool abort() override;
const QVector& getBlockedFiles() const { return m_blockedMods; }
diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp
index 856eee816..c884a4f12 100644
--- a/launcher/InstanceList.cpp
+++ b/launcher/InstanceList.cpp
@@ -2,6 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu
+ * Copyright (C) 2023 TheKodeToad
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -237,8 +238,11 @@ GroupId InstanceList::getInstanceGroup(const InstanceId& id) const
return GroupId();
}
-void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
+void InstanceList::setInstanceGroup(const InstanceId& id, GroupId name)
{
+ if (name.isEmpty() && !name.isNull())
+ name = QString();
+
auto inst = getInstanceById(id);
if (!inst) {
qDebug() << "Attempt to set a null instance's group";
@@ -249,6 +253,7 @@ void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
auto iter = m_instanceGroupIndex.find(inst->id());
if (iter != m_instanceGroupIndex.end()) {
if (*iter != name) {
+ decreaseGroupCount(*iter);
*iter = name;
changed = true;
}
@@ -258,7 +263,7 @@ void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
}
if (changed) {
- m_groupNameCache.insert(name);
+ increaseGroupCount(name);
auto idx = getInstIndex(inst.get());
emit dataChanged(index(idx), index(idx), { GroupRole });
saveGroupList();
@@ -267,29 +272,55 @@ void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
QStringList InstanceList::getGroups()
{
- return m_groupNameCache.values();
+ return m_groupNameCache.keys();
}
-void InstanceList::deleteGroup(const QString& name)
+void InstanceList::deleteGroup(const GroupId& name)
{
+ m_groupNameCache.remove(name);
+ m_collapsedGroups.remove(name);
+
bool removed = false;
qDebug() << "Delete group" << name;
for (auto& instance : m_instances) {
- const auto& instID = instance->id();
- auto instGroupName = getInstanceGroup(instID);
+ const QString& instID = instance->id();
+ const QString instGroupName = getInstanceGroup(instID);
if (instGroupName == name) {
m_instanceGroupIndex.remove(instID);
qDebug() << "Remove" << instID << "from group" << name;
removed = true;
auto idx = getInstIndex(instance.get());
- if (idx > 0) {
+ if (idx >= 0)
emit dataChanged(index(idx), index(idx), { GroupRole });
- }
}
}
- if (removed) {
+ if (removed)
saveGroupList();
+}
+
+void InstanceList::renameGroup(const QString& src, const QString& dst)
+{
+ m_groupNameCache.remove(src);
+ if (m_collapsedGroups.remove(src))
+ m_collapsedGroups.insert(dst);
+
+ bool modified = false;
+ qDebug() << "Rename group" << src << "to" << dst;
+ for (auto& instance : m_instances) {
+ const QString& instID = instance->id();
+ const QString instGroupName = getInstanceGroup(instID);
+ if (instGroupName == src) {
+ m_instanceGroupIndex[instID] = dst;
+ increaseGroupCount(dst);
+ qDebug() << "Set" << instID << "group to" << dst;
+ modified = true;
+ auto idx = getInstIndex(instance.get());
+ if (idx >= 0)
+ emit dataChanged(index(idx), index(idx), { GroupRole });
+ }
}
+ if (modified)
+ saveGroupList();
}
bool InstanceList::isGroupCollapsed(const QString& group)
@@ -305,12 +336,13 @@ bool InstanceList::trashInstance(const InstanceId& id)
return false;
}
- auto cachedGroupId = m_instanceGroupIndex[id];
+ QString cachedGroupId = m_instanceGroupIndex[id];
qDebug() << "Will trash instance" << id;
QString trashedLoc;
if (m_instanceGroupIndex.remove(id)) {
+ decreaseGroupCount(cachedGroupId);
saveGroupList();
}
@@ -348,7 +380,7 @@ void InstanceList::undoTrashInstance()
QFile(top.trashPath).rename(top.polyPath);
m_instanceGroupIndex[top.id] = top.groupName;
- m_groupNameCache.insert(top.groupName);
+ increaseGroupCount(top.groupName);
saveGroupList();
emit instancesChanged();
@@ -362,7 +394,10 @@ void InstanceList::deleteInstance(const InstanceId& id)
return;
}
+ QString cachedGroupId = m_instanceGroupIndex[id];
+
if (m_instanceGroupIndex.remove(id)) {
+ decreaseGroupCount(cachedGroupId);
saveGroupList();
}
@@ -610,6 +645,25 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id)
return inst;
}
+void InstanceList::increaseGroupCount(const QString& group)
+{
+ if (group.isEmpty())
+ return;
+
+ ++m_groupNameCache[group];
+}
+
+void InstanceList::decreaseGroupCount(const QString& group)
+{
+ if (group.isEmpty())
+ return;
+
+ if (--m_groupNameCache[group] < 1) {
+ m_groupNameCache.remove(group);
+ m_collapsedGroups.remove(group);
+ }
+}
+
void InstanceList::saveGroupList()
{
qDebug() << "Will save group list now.";
@@ -621,7 +675,7 @@ void InstanceList::saveGroupList()
QString groupFileName = m_instDir + "/instgroups.json";
QMap> reverseGroupMap;
for (auto iter = m_instanceGroupIndex.begin(); iter != m_instanceGroupIndex.end(); iter++) {
- QString id = iter.key();
+ const QString& id = iter.key();
QString group = iter.value();
if (group.isEmpty())
continue;
@@ -711,17 +765,22 @@ void InstanceList::loadGroupList()
return;
}
- QSet groupSet;
m_instanceGroupIndex.clear();
+ m_groupNameCache.clear();
// Iterate through all the groups.
QJsonObject groupMapping = rootObj.value("groups").toObject();
for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++) {
QString groupName = iter.key();
+ if (iter.key().isEmpty()) {
+ qWarning() << "Redundant empty group found";
+ continue;
+ }
+
// If not an object, complain and skip to the next one.
if (!iter.value().isObject()) {
- qWarning() << QString("Group '%1' in the group list should be an object.").arg(groupName).toUtf8();
+ qWarning() << QString("Group '%1' in the group list should be an object").arg(groupName).toUtf8();
continue;
}
@@ -733,23 +792,19 @@ void InstanceList::loadGroupList()
continue;
}
- // keep a list/set of groups for choosing
- groupSet.insert(groupName);
-
auto hidden = groupObj.value("hidden").toBool(false);
- if (hidden) {
+ if (hidden)
m_collapsedGroups.insert(groupName);
- }
// Iterate through the list of instances in the group.
QJsonArray instancesArray = groupObj.value("instances").toArray();
- for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); iter2++) {
- m_instanceGroupIndex[(*iter2).toString()] = groupName;
+ for (auto value : instancesArray) {
+ m_instanceGroupIndex[value.toString()] = groupName;
+ increaseGroupCount(groupName);
}
}
m_groupsLoaded = true;
- m_groupNameCache.unite(groupSet);
qDebug() << "Group list loaded.";
}
@@ -768,6 +823,9 @@ void InstanceList::on_InstFolderChanged([[maybe_unused]] const Setting& setting,
}
m_instDir = newInstDir;
m_groupsLoaded = false;
+ beginRemoveRows(QModelIndex(), 0, count());
+ m_instances.erase(m_instances.begin(), m_instances.end());
+ endRemoveRows();
emit instancesChanged();
}
}
@@ -892,9 +950,12 @@ QString InstanceList::getStagedInstancePath()
bool InstanceList::commitStagedInstance(const QString& path,
InstanceName const& instanceName,
- const QString& groupName,
+ QString groupName,
InstanceTask const& commiting)
{
+ if (groupName.isEmpty() && !groupName.isNull())
+ groupName = QString();
+
QDir dir;
QString instID;
InstancePtr inst;
@@ -925,7 +986,7 @@ bool InstanceList::commitStagedInstance(const QString& path,
}
m_instanceGroupIndex[instID] = groupName;
- m_groupNameCache.insert(groupName);
+ increaseGroupCount(groupName);
}
instanceSet.insert(instID);
diff --git a/launcher/InstanceList.h b/launcher/InstanceList.h
index ee4578ffd..5ddddee95 100644
--- a/launcher/InstanceList.h
+++ b/launcher/InstanceList.h
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2023 TheKodeToad
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * 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
@@ -86,9 +106,10 @@ class InstanceList : public QAbstractListModel {
bool isGroupCollapsed(const QString& groupName);
GroupId getInstanceGroup(const InstanceId& id) const;
- void setInstanceGroup(const InstanceId& id, const GroupId& name);
+ void setInstanceGroup(const InstanceId& id, GroupId name);
void deleteGroup(const GroupId& name);
+ void renameGroup(const GroupId& src, const GroupId& dst);
bool trashInstance(const InstanceId& id);
bool trashedSomething();
void undoTrashInstance();
@@ -109,7 +130,7 @@ class InstanceList : public QAbstractListModel {
* should_override is used when another similar instance already exists, and we want to override it
* - for instance, when updating it.
*/
- bool commitStagedInstance(const QString& keyPath, const InstanceName& instanceName, const QString& groupName, const InstanceTask&);
+ bool commitStagedInstance(const QString& keyPath, const InstanceName& instanceName, QString groupName, const InstanceTask&);
/**
* Destroy a previously created staging area given by @keyPath - used when creation fails.
@@ -158,12 +179,16 @@ class InstanceList : public QAbstractListModel {
QList discoverInstances();
InstancePtr loadInstance(const InstanceId& id);
+ void increaseGroupCount(const QString& group);
+ void decreaseGroupCount(const QString& group);
+
private:
int m_watchLevel = 0;
int totalPlayTime = 0;
bool m_dirty = false;
QList m_instances;
- QSet m_groupNameCache;
+ // id -> refs
+ QMap m_groupNameCache;
SettingsObjectPtr m_globalSettings;
QString m_instDir;
diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp
index b21eb281f..8a8fa00a8 100644
--- a/launcher/LaunchController.cpp
+++ b/launcher/LaunchController.cpp
@@ -36,6 +36,7 @@
#include "LaunchController.h"
#include "Application.h"
+#include "minecraft/auth/AccountData.h"
#include "minecraft/auth/AccountList.h"
#include "ui/InstanceWindow.h"
@@ -88,8 +89,8 @@ void LaunchController::decideAccount()
if (accounts->count() <= 0) {
// Tell the user they need to log in at least one account in order to play.
auto reply = CustomMessageBox::selectable(m_parentWidget, tr("No Accounts"),
- tr("In order to play Minecraft, you must have at least one Microsoft or Mojang "
- "account logged in. Mojang accounts can only be used offline. "
+ tr("In order to play Minecraft, you must have at least one Microsoft "
+ "account which owns Minecraft logged in. "
"Would you like to open the account manager to add an account now?"),
QMessageBox::Information, QMessageBox::Yes | QMessageBox::No)
->exec();
@@ -106,7 +107,7 @@ void LaunchController::decideAccount()
// Select the account to use. If the instance has a specific account set, that will be used. Otherwise, the default account will be used
auto instanceAccountId = m_instance->settings()->get("InstanceAccountId").toString();
auto instanceAccountIndex = accounts->findAccountByProfileId(instanceAccountId);
- if (instanceAccountIndex == -1) {
+ if (instanceAccountIndex == -1 || instanceAccountId.isEmpty()) {
m_accountToUse = accounts->defaultAccount();
} else {
m_accountToUse = accounts->at(instanceAccountIndex);
@@ -161,7 +162,7 @@ void LaunchController::login()
m_accountToUse->fillSession(m_session);
// Launch immediately in true offline mode
- if (m_accountToUse->isOffline()) {
+ if (m_accountToUse->accountType() == AccountType::Offline) {
launchInstance();
return;
}
diff --git a/launcher/MMCTime.cpp b/launcher/MMCTime.cpp
index 3972dbd53..1765fd844 100644
--- a/launcher/MMCTime.cpp
+++ b/launcher/MMCTime.cpp
@@ -16,19 +16,20 @@
*/
#include
+#include
#include
#include
#include
-QString Time::prettifyDuration(int64_t duration)
+QString Time::prettifyDuration(int64_t duration, bool noDays)
{
int seconds = (int)(duration % 60);
duration /= 60;
int minutes = (int)(duration % 60);
duration /= 60;
- int hours = (int)(duration % 24);
- int days = (int)(duration / 24);
+ int hours = (int)(noDays ? duration : (duration % 24));
+ int days = (int)(noDays ? 0 : (duration / 24));
if ((hours == 0) && (days == 0)) {
return QObject::tr("%1min %2s").arg(minutes).arg(seconds);
}
diff --git a/launcher/MMCTime.h b/launcher/MMCTime.h
index b7d34b5d8..ea6d37e7e 100644
--- a/launcher/MMCTime.h
+++ b/launcher/MMCTime.h
@@ -20,7 +20,7 @@
namespace Time {
-QString prettifyDuration(int64_t duration);
+QString prettifyDuration(int64_t duration, bool noDays = false);
/**
* @brief Returns a string with short form time duration ie. `2days 1h3m4s56.0ms`.
diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp
index acd6bf7e4..ce2573329 100644
--- a/launcher/MMCZip.cpp
+++ b/launcher/MMCZip.cpp
@@ -42,11 +42,15 @@
#include
#include
+#include
+
+#if defined(LAUNCHER_APPLICATION)
#include
+#endif
namespace MMCZip {
// ours
-bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet& contained, const FilterFunction filter)
+bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet& contained, const FilterFunction& filter)
{
QuaZip modZip(from.filePath());
modZip.open(QuaZip::mdUnzip);
@@ -132,6 +136,7 @@ bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files,
return result;
}
+#if defined(LAUNCHER_APPLICATION)
// ours
bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList& mods)
{
@@ -217,6 +222,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList
#include
#include
+
+#if defined(LAUNCHER_APPLICATION)
#include "minecraft/mod/Mod.h"
+#endif
#include "tasks/Task.h"
namespace MMCZip {
@@ -57,7 +60,7 @@ using FilterFunction = std::function;
/**
* Merge two zip files, using a filter function
*/
-bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet& contained, const FilterFunction filter = nullptr);
+bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet& contained, const FilterFunction& filter = nullptr);
/**
* Compress directory, by providing a list of files to compress
@@ -79,11 +82,12 @@ bool compressDirFiles(QuaZip* zip, QString dir, QFileInfoList files, bool follow
*/
bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks = false);
+#if defined(LAUNCHER_APPLICATION)
/**
* take a source jar, add mods to it, resulting in target jar
*/
bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList& mods);
-
+#endif
/**
* Find a single file in archive by file name (not path)
*
@@ -147,6 +151,7 @@ bool extractFile(QString fileCompressed, QString file, QString dir);
*/
bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFunction excludeFilter);
+#if defined(LAUNCHER_APPLICATION)
class ExportToZipTask : public Task {
public:
ExportToZipTask(QString outputPath, QDir dir, QFileInfoList files, QString destinationPrefix = "", bool followSymlinks = false)
@@ -167,7 +172,7 @@ class ExportToZipTask : public Task {
void setExcludeFiles(QStringList excludeFiles) { m_exclude_files = excludeFiles; }
void addExtraFile(QString fileName, QByteArray data) { m_extra_files.insert(fileName, data); }
- typedef std::optional ZipResult;
+ using ZipResult = std::optional;
protected:
virtual void executeTask() override;
@@ -189,4 +194,5 @@ class ExportToZipTask : public Task {
QFuture m_build_zip_future;
QFutureWatcher m_build_zip_watcher;
};
+#endif
} // namespace MMCZip
diff --git a/launcher/MTPixmapCache.h b/launcher/MTPixmapCache.h
index 1a3e52160..b6bd13045 100644
--- a/launcher/MTPixmapCache.h
+++ b/launcher/MTPixmapCache.h
@@ -5,6 +5,7 @@
#include
#include
#include
+#include
#define GET_TYPE() \
Qt::ConnectionType type; \
@@ -100,10 +101,14 @@ class PixmapCache final : public QObject {
*/
bool _markCacheMissByEviciton()
{
+ static constexpr uint maxInt = static_cast(std::numeric_limits::max());
+ static constexpr uint step = 10240;
+ static constexpr int oneSecond = 1000;
+
auto now = QTime::currentTime();
if (!m_last_cache_miss_by_eviciton.isNull()) {
auto diff = m_last_cache_miss_by_eviciton.msecsTo(now);
- if (diff < 1000) { // less than a second ago
+ if (diff < oneSecond) { // less than a second ago
++m_consecutive_fast_evicitons;
} else {
m_consecutive_fast_evicitons = 0;
@@ -111,11 +116,17 @@ class PixmapCache final : public QObject {
}
m_last_cache_miss_by_eviciton = now;
if (m_consecutive_fast_evicitons >= m_consecutive_fast_evicitons_threshold) {
- // double the cache size
- auto newSize = _cacheLimit() * 2;
- qDebug() << m_consecutive_fast_evicitons << "pixmap cache misses by eviction happened too fast, doubling cache size to"
- << newSize;
- _setCacheLimit(newSize);
+ // increase the cache size
+ uint newSize = _cacheLimit() + step;
+ if (newSize >= maxInt) { // increase it until you overflow :D
+ newSize = maxInt;
+ qDebug() << m_consecutive_fast_evicitons
+ << tr("pixmap cache misses by eviction happened too fast, doing nothing as the cache size reached it's limit");
+ } else {
+ qDebug() << m_consecutive_fast_evicitons
+ << tr("pixmap cache misses by eviction happened too fast, increasing cache size to") << static_cast(newSize);
+ }
+ _setCacheLimit(static_cast(newSize));
m_consecutive_fast_evicitons = 0;
return true;
}
diff --git a/launcher/RecursiveFileSystemWatcher.h b/launcher/RecursiveFileSystemWatcher.h
index ec3ed804e..7f96f5cd0 100644
--- a/launcher/RecursiveFileSystemWatcher.h
+++ b/launcher/RecursiveFileSystemWatcher.h
@@ -13,7 +13,7 @@ class RecursiveFileSystemWatcher : public QObject {
QDir rootDir() const { return m_root; }
// WARNING: setting this to true may be bad for performance
- void setWatchFiles(const bool watchFiles);
+ void setWatchFiles(bool watchFiles);
bool watchFiles() const { return m_watchFiles; }
void setMatcher(IPathMatcher::Ptr matcher) { m_matcher = matcher; }
diff --git a/launcher/ResourceDownloadTask.h b/launcher/ResourceDownloadTask.h
index 2baddf8a8..f686e819a 100644
--- a/launcher/ResourceDownloadTask.h
+++ b/launcher/ResourceDownloadTask.h
@@ -32,7 +32,7 @@ class ResourceDownloadTask : public SequentialTask {
public:
explicit ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
ModPlatform::IndexedVersion version,
- const std::shared_ptr packs,
+ std::shared_ptr packs,
bool is_indexed = true,
QString custom_target_folder = {});
const QString& getFilename() const { return m_pack_version.fileName; }
diff --git a/launcher/StringUtils.cpp b/launcher/StringUtils.cpp
index e08e6fdce..72ccdfbff 100644
--- a/launcher/StringUtils.cpp
+++ b/launcher/StringUtils.cpp
@@ -35,6 +35,7 @@
*/
#include "StringUtils.h"
+#include
#include
#include
@@ -149,7 +150,7 @@ QString StringUtils::truncateUrlHumanFriendly(QUrl& url, int max_len, bool hard_
}
if ((url_compact.length() >= max_len) && hard_limit) {
- // still too long, truncate normaly
+ // still too long, truncate normally
url_compact = QString(str_url);
auto to_remove = url_compact.length() - max_len + 3;
url_compact.remove(url_compact.length() - to_remove - 1, to_remove);
@@ -182,3 +183,32 @@ QString StringUtils::getRandomAlphaNumeric()
{
return QUuid::createUuid().toString(QUuid::Id128);
}
+
+QPair StringUtils::splitFirst(const QString& s, const QString& sep, Qt::CaseSensitivity cs)
+{
+ QString left, right;
+ auto index = s.indexOf(sep, 0, cs);
+ left = s.mid(0, index);
+ right = s.mid(index + sep.length());
+ return qMakePair(left, right);
+}
+
+QPair StringUtils::splitFirst(const QString& s, QChar sep, Qt::CaseSensitivity cs)
+{
+ QString left, right;
+ auto index = s.indexOf(sep, 0, cs);
+ left = s.mid(0, index);
+ right = s.mid(left.length() + 1);
+ return qMakePair(left, right);
+}
+
+QPair StringUtils::splitFirst(const QString& s, const QRegularExpression& re)
+{
+ QString left, right;
+ QRegularExpressionMatch match;
+ auto index = s.indexOf(re, 0, &match);
+ left = s.mid(0, index);
+ auto end = match.hasMatch() ? left.length() + match.capturedLength() : left.length() + 1;
+ right = s.mid(end);
+ return qMakePair(left, right);
+}
diff --git a/launcher/StringUtils.h b/launcher/StringUtils.h
index eac0d5a7d..9d2bdd85e 100644
--- a/launcher/StringUtils.h
+++ b/launcher/StringUtils.h
@@ -36,8 +36,10 @@
#pragma once
+#include
#include
#include
+#include
namespace StringUtils {
@@ -70,12 +72,17 @@ int naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs)
/**
* @brief Truncate a url while keeping its readability py placing the `...` in the middle of the path
* @param url Url to truncate
- * @param max_len max lenght of url in charaters
- * @param hard_limit if truncating the path can't get the url short enough, truncate it normaly.
+ * @param max_len max length of url in characters
+ * @param hard_limit if truncating the path can't get the url short enough, truncate it normally.
*/
QString truncateUrlHumanFriendly(QUrl& url, int max_len, bool hard_limit = false);
QString humanReadableFileSize(double bytes, bool use_si = false, int decimal_points = 1);
QString getRandomAlphaNumeric();
+
+QPair splitFirst(const QString& s, const QString& sep, Qt::CaseSensitivity cs = Qt::CaseSensitive);
+QPair splitFirst(const QString& s, QChar sep, Qt::CaseSensitivity cs = Qt::CaseSensitive);
+QPair splitFirst(const QString& s, const QRegularExpression& re);
+
} // namespace StringUtils
diff --git a/launcher/Version.h b/launcher/Version.h
index 92bff9ba9..b06e256aa 100644
--- a/launcher/Version.h
+++ b/launcher/Version.h
@@ -56,6 +56,7 @@ class Version {
bool operator!=(const Version& other) const;
QString toString() const { return m_string; }
+ bool isEmpty() const { return m_string.isEmpty(); }
friend QDebug operator<<(QDebug debug, const Version& v);
@@ -103,14 +104,8 @@ class Version {
QString m_fullString;
- [[nodiscard]] inline bool isAppendix() const
- {
- return m_stringPart.startsWith('+');
- }
- [[nodiscard]] inline bool isPreRelease() const
- {
- return m_stringPart.startsWith('-') && m_stringPart.length() > 1;
- }
+ [[nodiscard]] inline bool isAppendix() const { return m_stringPart.startsWith('+'); }
+ [[nodiscard]] inline bool isPreRelease() const { return m_stringPart.startsWith('-') && m_stringPart.length() > 1; }
inline bool operator==(const Section& other) const
{
@@ -156,14 +151,8 @@ class Version {
return m_fullString < other.m_fullString;
}
- inline bool operator!=(const Section& other) const
- {
- return !(*this == other);
- }
- inline bool operator>(const Section& other) const
- {
- return !(*this < other || *this == other);
- }
+ inline bool operator!=(const Section& other) const { return !(*this == other); }
+ inline bool operator>(const Section& other) const { return !(*this < other || *this == other); }
};
private:
diff --git a/launcher/VersionProxyModel.h b/launcher/VersionProxyModel.h
index c7d5fd94e..0863a7c80 100644
--- a/launcher/VersionProxyModel.h
+++ b/launcher/VersionProxyModel.h
@@ -10,7 +10,7 @@ class VersionProxyModel : public QAbstractProxyModel {
Q_OBJECT
public:
enum Column { Name, ParentVersion, Branch, Type, Architecture, Path, Time };
- typedef QHash> FilterMap;
+ using FilterMap = QHash>;
public:
VersionProxyModel(QObject* parent = 0);
@@ -28,7 +28,7 @@ class VersionProxyModel : public QAbstractProxyModel {
const FilterMap& filters() const;
const QString& search() const;
- void setFilter(const BaseVersionList::ModelRoles column, Filter* filter);
+ void setFilter(BaseVersionList::ModelRoles column, Filter* filter);
void setSearch(const QString& search);
void clearFilters();
QModelIndex getRecommended() const;
diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp
index 1ea0e382f..bdf173ebc 100644
--- a/launcher/filelink/FileLink.cpp
+++ b/launcher/filelink/FileLink.cpp
@@ -93,6 +93,7 @@ FileLinkApp::FileLinkApp(int& argc, char** argv) : QCoreApplication(argc, argv),
joinServer(serverToJoin);
} else {
qDebug() << "no server to join";
+ m_status = Failed;
exit();
}
}
@@ -108,6 +109,7 @@ void FileLinkApp::joinServer(QString server)
connect(&socket, &QLocalSocket::readyRead, this, &FileLinkApp::readPathPairs);
connect(&socket, &QLocalSocket::errorOccurred, this, [&](QLocalSocket::LocalSocketError socketError) {
+ m_status = Failed;
switch (socketError) {
case QLocalSocket::ServerNotFoundError:
qDebug()
@@ -132,6 +134,7 @@ void FileLinkApp::joinServer(QString server)
connect(&socket, &QLocalSocket::disconnected, this, [&]() {
qDebug() << "disconnected from server, should exit";
+ m_status = Succeeded;
exit();
});
diff --git a/launcher/filelink/FileLink.h b/launcher/filelink/FileLink.h
index 4c47d9bbb..583d0d43a 100644
--- a/launcher/filelink/FileLink.h
+++ b/launcher/filelink/FileLink.h
@@ -41,8 +41,10 @@ class FileLinkApp : public QCoreApplication {
// friends for the purpose of limiting access to deprecated stuff
Q_OBJECT
public:
+ enum Status { Starting, Failed, Succeeded, Initialized };
FileLinkApp(int& argc, char** argv);
virtual ~FileLinkApp();
+ Status status() const { return m_status; }
private:
void joinServer(QString server);
@@ -50,6 +52,8 @@ class FileLinkApp : public QCoreApplication {
void runLink();
void sendResults();
+ Status m_status = Status::Starting;
+
bool m_useHardLinks = false;
QDateTime m_startTime;
diff --git a/launcher/filelink/main.cpp b/launcher/filelink/filelink_main.cpp
similarity index 75%
rename from launcher/filelink/main.cpp
rename to launcher/filelink/filelink_main.cpp
index 83566a3c6..2a8bcb703 100644
--- a/launcher/filelink/main.cpp
+++ b/launcher/filelink/filelink_main.cpp
@@ -26,5 +26,16 @@ int main(int argc, char* argv[])
{
FileLinkApp ldh(argc, argv);
- return ldh.exec();
+ switch (ldh.status()) {
+ case FileLinkApp::Starting:
+ case FileLinkApp::Initialized: {
+ return ldh.exec();
+ }
+ case FileLinkApp::Failed:
+ return 1;
+ case FileLinkApp::Succeeded:
+ return 0;
+ default:
+ return -1;
+ }
}
diff --git a/launcher/icons/IconList.h b/launcher/icons/IconList.h
index 8afd05574..c51826057 100644
--- a/launcher/icons/IconList.h
+++ b/launcher/icons/IconList.h
@@ -67,7 +67,7 @@ class IconList : public QAbstractListModel {
virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
bool addThemeIcon(const QString& key);
- bool addIcon(const QString& key, const QString& name, const QString& path, const IconType type);
+ bool addIcon(const QString& key, const QString& name, const QString& path, IconType type);
void saveIcon(const QString& key, const QString& path, const char* format) const;
bool deleteIcon(const QString& key);
bool trashIcon(const QString& key);
diff --git a/launcher/java/JavaChecker.h b/launcher/java/JavaChecker.h
index 743f49417..7111f8522 100644
--- a/launcher/java/JavaChecker.h
+++ b/launcher/java/JavaChecker.h
@@ -22,8 +22,8 @@ struct JavaCheckResult {
enum class Validity { Errored, ReturnedInvalidData, Valid } validity = Validity::Errored;
};
-typedef shared_qobject_ptr QProcessPtr;
-typedef shared_qobject_ptr JavaCheckerPtr;
+using QProcessPtr = shared_qobject_ptr;
+using JavaCheckerPtr = shared_qobject_ptr;
class JavaChecker : public QObject {
Q_OBJECT
public:
diff --git a/launcher/java/JavaCheckerJob.h b/launcher/java/JavaCheckerJob.h
index 009687917..ddf827968 100644
--- a/launcher/java/JavaCheckerJob.h
+++ b/launcher/java/JavaCheckerJob.h
@@ -20,7 +20,7 @@
#include "tasks/Task.h"
class JavaCheckerJob;
-typedef shared_qobject_ptr JavaCheckerJobPtr;
+using JavaCheckerJobPtr = shared_qobject_ptr;
// FIXME: this just seems horribly redundant
class JavaCheckerJob : public Task {
diff --git a/launcher/java/JavaInstall.h b/launcher/java/JavaInstall.h
index 30815b5a8..8c2743a00 100644
--- a/launcher/java/JavaInstall.h
+++ b/launcher/java/JavaInstall.h
@@ -24,11 +24,11 @@
struct JavaInstall : public BaseVersion {
JavaInstall() {}
JavaInstall(QString id, QString arch, QString path) : id(id), arch(arch), path(path) {}
- virtual QString descriptor() { return id.toString(); }
+ virtual QString descriptor() override { return id.toString(); }
- virtual QString name() { return id.toString(); }
+ virtual QString name() override { return id.toString(); }
- virtual QString typeString() const { return arch; }
+ virtual QString typeString() const override { return arch; }
virtual bool operator<(BaseVersion& a) override;
virtual bool operator>(BaseVersion& a) override;
@@ -42,4 +42,4 @@ struct JavaInstall : public BaseVersion {
bool recommended = false;
};
-typedef std::shared_ptr JavaInstallPtr;
+using JavaInstallPtr = std::shared_ptr;
diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp
index 3512c3079..074bf54df 100644
--- a/launcher/java/JavaUtils.cpp
+++ b/launcher/java/JavaUtils.cpp
@@ -34,6 +34,7 @@
*/
#include
+#include
#include
#include
@@ -335,6 +336,7 @@ QList JavaUtils::FindJavaPaths()
}
}
+ candidates.append(getMinecraftJavaBundle());
candidates = addJavasFromEnv(candidates);
candidates.removeDuplicates();
return candidates;
@@ -360,6 +362,7 @@ QList JavaUtils::FindJavaPaths()
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java");
}
+ javas.append(getMinecraftJavaBundle());
javas = addJavasFromEnv(javas);
javas.removeDuplicates();
return javas;
@@ -403,6 +406,15 @@ QList JavaUtils::FindJavaPaths()
scanJavaDirs("/opt/jdks");
// flatpak
scanJavaDirs("/app/jdk");
+
+ auto home = qEnvironmentVariable("HOME");
+
+ // javas downloaded by IntelliJ
+ scanJavaDirs(FS::PathCombine(home, ".jdks"));
+ // javas downloaded by sdkman
+ scanJavaDirs(FS::PathCombine(home, ".sdkman/candidates/java"));
+
+ javas.append(getMinecraftJavaBundle());
javas = addJavasFromEnv(javas);
javas.removeDuplicates();
return javas;
@@ -415,6 +427,7 @@ QList JavaUtils::FindJavaPaths()
QList javas;
javas.append(this->GetDefaultJava()->path);
+ javas.append(getMinecraftJavaBundle());
return addJavasFromEnv(javas);
}
#endif
@@ -423,3 +436,50 @@ QString JavaUtils::getJavaCheckPath()
{
return APPLICATION->getJarPath("JavaCheck.jar");
}
+
+QStringList getMinecraftJavaBundle()
+{
+ QString partialPath;
+ QString executable = "java";
+ QStringList processpaths;
+#if defined(Q_OS_OSX)
+ partialPath = FS::PathCombine(QDir::homePath(), "Library/Application Support");
+#elif defined(Q_OS_WIN32)
+ partialPath = QProcessEnvironment::systemEnvironment().value("LOCALAPPDATA", "");
+ executable += "w.exe";
+
+ // add the microsoft store version of the launcher to the search. the current path is:
+ // C:\Users\USERNAME\AppData\Local\Packages\Microsoft.4297127D64EC6_8wekyb3d8bbwe\LocalCache\Local\runtime
+ auto minecraftMSStorePath =
+ FS::PathCombine(QFileInfo(partialPath).absolutePath(), "Local", "Packages", "Microsoft.4297127D64EC6_8wekyb3d8bbwe");
+ minecraftMSStorePath = FS::PathCombine(minecraftMSStorePath, "LocalCache", "Local", "runtime");
+ processpaths << minecraftMSStorePath;
+#else
+ partialPath = QDir::homePath();
+#endif
+ auto minecraftDataPath = FS::PathCombine(partialPath, ".minecraft", "runtime");
+ processpaths << minecraftDataPath;
+
+ QStringList javas;
+ while (!processpaths.isEmpty()) {
+ auto dirPath = processpaths.takeFirst();
+ QDir dir(dirPath);
+ if (!dir.exists())
+ continue;
+ auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
+ auto binFound = false;
+ for (auto& entry : entries) {
+ if (entry.baseName() == "bin") {
+ javas.append(FS::PathCombine(entry.canonicalFilePath(), executable));
+ binFound = true;
+ break;
+ }
+ }
+ if (!binFound) {
+ for (auto& entry : entries) {
+ processpaths << entry.canonicalFilePath();
+ }
+ }
+ }
+ return javas;
+}
diff --git a/launcher/java/JavaUtils.h b/launcher/java/JavaUtils.h
index 616179706..2fb03af7a 100644
--- a/launcher/java/JavaUtils.h
+++ b/launcher/java/JavaUtils.h
@@ -26,6 +26,7 @@
QString stripVariableEntries(QString name, QString target, QString remove);
QProcessEnvironment CleanEnviroment();
+QStringList getMinecraftJavaBundle();
class JavaUtils : public QObject {
Q_OBJECT
diff --git a/launcher/java/JavaVersion.cpp b/launcher/java/JavaVersion.cpp
index f9ac47824..b77bf2adf 100644
--- a/launcher/java/JavaVersion.cpp
+++ b/launcher/java/JavaVersion.cpp
@@ -45,10 +45,12 @@ QString JavaVersion::toString() const
bool JavaVersion::requiresPermGen()
{
- if (m_parseable) {
- return m_major < 8;
- }
- return true;
+ return !m_parseable || m_major < 8;
+}
+
+bool JavaVersion::isModular()
+{
+ return m_parseable && m_major >= 9;
}
bool JavaVersion::operator<(const JavaVersion& rhs)
diff --git a/launcher/java/JavaVersion.h b/launcher/java/JavaVersion.h
index 7e66269cb..421578ea1 100644
--- a/launcher/java/JavaVersion.h
+++ b/launcher/java/JavaVersion.h
@@ -25,6 +25,8 @@ class JavaVersion {
bool requiresPermGen();
+ bool isModular();
+
QString toString() const;
int major() { return m_major; }
diff --git a/launcher/launch/LogModel.h b/launcher/launch/LogModel.h
index ba2e4054a..18e51d7e3 100644
--- a/launcher/launch/LogModel.h
+++ b/launcher/launch/LogModel.h
@@ -30,7 +30,7 @@ class LogModel : public QAbstractListModel {
enum Roles { LevelRole = Qt::UserRole };
- private /* types */:
+ private /* types */:
struct entry {
MessageLevel::Enum level;
QString line;
diff --git a/launcher/meta/Index.h b/launcher/meta/Index.h
index 41fdfcea9..2c650ce2f 100644
--- a/launcher/meta/Index.h
+++ b/launcher/meta/Index.h
@@ -55,6 +55,6 @@ class Index : public QAbstractListModel, public BaseEntity {
QVector m_lists;
QHash m_uids;
- void connectVersionList(const int row, const VersionList::Ptr& list);
+ void connectVersionList(int row, const VersionList::Ptr& list);
};
} // namespace Meta
diff --git a/launcher/meta/Version.h b/launcher/meta/Version.h
index 07dcafb01..24da12d6d 100644
--- a/launcher/meta/Version.h
+++ b/launcher/meta/Version.h
@@ -64,7 +64,7 @@ class Version : public QObject, public BaseVersion, public BaseEntity {
public: // for usage by format parsers only
void setType(const QString& type);
- void setTime(const qint64 time);
+ void setTime(qint64 time);
void setRequires(const Meta::RequireSet& reqs, const Meta::RequireSet& conflicts);
void setVolatile(bool volatile_);
void setRecommended(bool recommended);
diff --git a/launcher/meta/VersionList.h b/launcher/meta/VersionList.h
index 5e587f204..2c5624701 100644
--- a/launcher/meta/VersionList.h
+++ b/launcher/meta/VersionList.h
@@ -79,7 +79,7 @@ class VersionList : public BaseVersionList, public BaseEntity {
Version::Ptr m_recommended;
- void setupAddedVersion(const int row, const Version::Ptr& version);
+ void setupAddedVersion(int row, const Version::Ptr& version);
};
} // namespace Meta
Q_DECLARE_METATYPE(Meta::VersionList::Ptr)
diff --git a/launcher/minecraft/Agent.h b/launcher/minecraft/Agent.h
index 8958521e5..bc385a74e 100644
--- a/launcher/minecraft/Agent.h
+++ b/launcher/minecraft/Agent.h
@@ -6,7 +6,7 @@
class Agent;
-typedef std::shared_ptr AgentPtr;
+using AgentPtr = std::shared_ptr;
class Agent {
public:
diff --git a/launcher/minecraft/AssetsUtils.cpp b/launcher/minecraft/AssetsUtils.cpp
index 4ef007075..48e150d16 100644
--- a/launcher/minecraft/AssetsUtils.cpp
+++ b/launcher/minecraft/AssetsUtils.cpp
@@ -48,6 +48,7 @@
#include "FileSystem.h"
#include "net/ApiDownload.h"
#include "net/ChecksumValidator.h"
+#include "net/Download.h"
#include "Application.h"
diff --git a/launcher/minecraft/Component.h b/launcher/minecraft/Component.h
index 3474a22e0..fdb61c45e 100644
--- a/launcher/minecraft/Component.h
+++ b/launcher/minecraft/Component.h
@@ -105,4 +105,4 @@ class Component : public QObject, public ProblemProvider {
bool m_loaded = false;
};
-typedef shared_qobject_ptr ComponentPtr;
+using ComponentPtr = shared_qobject_ptr;
diff --git a/launcher/minecraft/ComponentUpdateTask.cpp b/launcher/minecraft/ComponentUpdateTask.cpp
index 0b85b81aa..bb838043a 100644
--- a/launcher/minecraft/ComponentUpdateTask.cpp
+++ b/launcher/minecraft/ComponentUpdateTask.cpp
@@ -2,14 +2,14 @@
#include "Component.h"
#include "ComponentUpdateTask_p.h"
-#include "OneSixVersionFormat.h"
#include "PackProfile.h"
#include "PackProfile_p.h"
#include "Version.h"
#include "cassert"
#include "meta/Index.h"
#include "meta/Version.h"
-#include "meta/VersionList.h"
+#include "minecraft/OneSixVersionFormat.h"
+#include "minecraft/ProfileUtils.h"
#include "net/Mode.h"
#include "Application.h"
diff --git a/launcher/minecraft/Library.h b/launcher/minecraft/Library.h
index f8816a97b..adb89c4c6 100644
--- a/launcher/minecraft/Library.h
+++ b/launcher/minecraft/Library.h
@@ -52,7 +52,7 @@
class Library;
class MinecraftInstance;
-typedef std::shared_ptr LibraryPtr;
+using LibraryPtr = std::shared_ptr;
class Library {
friend class OneSixVersionFormat;
diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp
index 0da97c146..4229f73eb 100644
--- a/launcher/minecraft/MinecraftInstance.cpp
+++ b/launcher/minecraft/MinecraftInstance.cpp
@@ -184,6 +184,13 @@ void MinecraftInstance::loadSpecificSettings()
m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), miscellaneousOverride);
+ // Legacy-related options
+ auto legacySettings = m_settings->registerSetting("OverrideLegacySettings", false);
+ m_settings->registerOverride(global_settings->getSetting("OnlineFixes"), legacySettings);
+
+ auto envSetting = m_settings->registerSetting("OverrideEnv", false);
+ m_settings->registerOverride(global_settings->getSetting("Env"), envSetting);
+
m_settings->set("InstanceType", "OneSix");
}
@@ -195,6 +202,12 @@ void MinecraftInstance::loadSpecificSettings()
m_settings->registerSetting("UseAccountForInstance", false);
m_settings->registerSetting("InstanceAccountId", "");
+ m_settings->registerSetting("ExportName", "");
+ m_settings->registerSetting("ExportVersion", "1.0.0");
+ m_settings->registerSetting("ExportSummary", "");
+ m_settings->registerSetting("ExportAuthor", "");
+ m_settings->registerSetting("ExportOptionalFiles", true);
+
qDebug() << "Instance-type specific settings were loaded!";
setSpecificSettingsLoaded(true);
@@ -305,7 +318,7 @@ QString MinecraftInstance::getLocalLibraryPath() const
bool MinecraftInstance::supportsDemo() const
{
Version instance_ver{ getPackProfile()->getComponentVersion("net.minecraft") };
- // Demo mode was introduced in 1.3.1: https://minecraft.fandom.com/wiki/Demo_mode#History
+ // Demo mode was introduced in 1.3.1: https://minecraft.wiki/w/Demo_mode#History
// FIXME: Due to Version constraints atm, this can't handle well non-release versions
return instance_ver >= Version("1.3.1");
}
@@ -507,20 +520,28 @@ QStringList MinecraftInstance::javaArguments()
args << "-Duser.language=en";
+ if (javaVersion.isModular() && shouldApplyOnlineFixes())
+ // allow reflective access to java.net - required by the skin fix
+ args << "--add-opens"
+ << "java.base/java.net=ALL-UNNAMED";
+
return args;
}
QString MinecraftInstance::getLauncher()
{
- auto profile = m_components->getProfile();
-
// use legacy launcher if the traits are set
- if (profile->getTraits().contains("legacyLaunch") || profile->getTraits().contains("alphaLaunch"))
+ if (traits().contains("legacyLaunch") || traits().contains("alphaLaunch"))
return "legacy";
return "standard";
}
+bool MinecraftInstance::shouldApplyOnlineFixes()
+{
+ return traits().contains("legacyServices") && settings()->get("OnlineFixes").toBool();
+}
+
QMap MinecraftInstance::getVariables()
{
QMap out;
@@ -530,6 +551,7 @@ QMap MinecraftInstance::getVariables()
out.insert("INST_MC_DIR", QDir::toNativeSeparators(QDir(gameRoot()).absolutePath()));
out.insert("INST_JAVA", settings()->get("JavaPath").toString());
out.insert("INST_JAVA_ARGS", javaArguments().join(' '));
+ out.insert("NO_COLOR", "1");
return out;
}
@@ -543,6 +565,22 @@ QProcessEnvironment MinecraftInstance::createEnvironment()
for (auto it = variables.begin(); it != variables.end(); ++it) {
env.insert(it.key(), it.value());
}
+ // custom env
+
+ auto insertEnv = [&env](QMap envMap) {
+ if (envMap.isEmpty())
+ return;
+
+ for (auto iter = envMap.begin(); iter != envMap.end(); iter++)
+ env.insert(iter.key(), iter.value().toString());
+ };
+
+ bool overrideEnv = settings()->get("OverrideEnv").toBool();
+
+ if (!overrideEnv)
+ insertEnv(APPLICATION->settings()->get("Env").toMap());
+ else
+ insertEnv(settings()->get("Env").toMap());
return env;
}
@@ -553,16 +591,27 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
#ifdef Q_OS_LINUX
if (settings()->get("EnableMangoHud").toBool() && APPLICATION->capabilities() & Application::SupportsMangoHud) {
- auto preloadList = env.value("LD_PRELOAD").split(QLatin1String(":"));
- auto libPaths = env.value("LD_LIBRARY_PATH").split(QLatin1String(":"));
+ QStringList preloadList;
+ if (auto value = env.value("LD_PRELOAD"); !value.isEmpty())
+ preloadList = value.split(QLatin1String(":"));
+ QStringList libPaths;
+ if (auto value = env.value("LD_LIBRARY_PATH"); !value.isEmpty())
+ libPaths = value.split(QLatin1String(":"));
auto mangoHudLibString = MangoHud::getLibraryString();
if (!mangoHudLibString.isEmpty()) {
QFileInfo mangoHudLib(mangoHudLibString);
+ QString libPath = mangoHudLib.absolutePath();
+ auto appendLib = [libPath, &preloadList](QString fileName) {
+ if (QFileInfo(FS::PathCombine(libPath, fileName)).exists())
+ preloadList << fileName;
+ };
// dlsym variant is only needed for OpenGL and not included in the vulkan layer
- preloadList << "libMangoHud_dlsym.so" << mangoHudLib.fileName();
- libPaths << mangoHudLib.absolutePath();
+ appendLib("libMangoHud_dlsym.so");
+ appendLib("libMangoHud_opengl.so");
+ appendLib(mangoHudLib.fileName());
+ libPaths << libPath;
}
env.insert("LD_PRELOAD", preloadList.join(QLatin1String(":")));
@@ -579,7 +628,6 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
env.insert("__GLX_VENDOR_LIBRARY_NAME", "nvidia");
}
#endif
-
return env;
}
@@ -692,7 +740,7 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
{
QString windowParams;
if (settings()->get("LaunchMaximized").toBool())
- windowParams = "max";
+ windowParams = "maximized";
else
windowParams =
QString("%1x%2").arg(settings()->get("MinecraftWinWidth").toInt()).arg(settings()->get("MinecraftWinHeight").toInt());
@@ -700,6 +748,19 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
launchScript += "windowParams " + windowParams + "\n";
}
+ // launcher info
+ {
+ launchScript += "launcherBrand " + BuildConfig.LAUNCHER_NAME + "\n";
+ launchScript += "launcherVersion " + BuildConfig.printableVersionString() + "\n";
+ }
+
+ // instance info
+ {
+ launchScript += "instanceName " + name() + "\n";
+ launchScript += "instanceIconKey " + name() + "\n";
+ launchScript += "instanceIconPath icon.png\n"; // we already save a copy here
+ }
+
// legacy auth
if (session) {
launchScript += "userName " + session->player_name + "\n";
@@ -710,6 +771,9 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
launchScript += "traits " + trait + "\n";
}
+ if (shouldApplyOnlineFixes())
+ launchScript += "onlineFixes true\n";
+
launchScript += "launcher " + getLauncher() + "\n";
// qDebug() << "Generated launch script:" << launchScript;
@@ -850,9 +914,6 @@ QMap MinecraftInstance::createCensorFilterFromSession(AuthSess
if (sessionRef.access_token != "0") {
addToFilter(sessionRef.access_token, tr(""));
}
- if (sessionRef.client_token.size()) {
- addToFilter(sessionRef.client_token, tr(""));
- }
addToFilter(sessionRef.uuid, tr(""));
return filter;
@@ -934,13 +995,16 @@ QString MinecraftInstance::getStatusbarDescription()
if (m_settings->get("ShowGameTime").toBool()) {
if (lastTimePlayed() > 0) {
QDateTime lastLaunchTime = QDateTime::fromMSecsSinceEpoch(lastLaunch());
- description.append(tr(", last played on %1 for %2")
- .arg(QLocale().toString(lastLaunchTime, QLocale::ShortFormat))
- .arg(Time::prettifyDuration(lastTimePlayed())));
+ description.append(
+ tr(", last played on %1 for %2")
+ .arg(QLocale().toString(lastLaunchTime, QLocale::ShortFormat))
+ .arg(Time::prettifyDuration(lastTimePlayed(), APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool())));
}
if (totalTimePlayed() > 0) {
- description.append(tr(", total played for %1").arg(Time::prettifyDuration(totalTimePlayed())));
+ description.append(
+ tr(", total played for %1")
+ .arg(Time::prettifyDuration(totalTimePlayed(), APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool())));
}
}
if (hasCrashed()) {
diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h
index dabd44ba7..b1f305201 100644
--- a/launcher/minecraft/MinecraftInstance.h
+++ b/launcher/minecraft/MinecraftInstance.h
@@ -129,6 +129,7 @@ class MinecraftInstance : public BaseInstance {
/// get arguments passed to java
QStringList javaArguments();
QString getLauncher();
+ bool shouldApplyOnlineFixes();
/// get variables for launch command variable substitution/environment
QMap getVariables() override;
@@ -173,4 +174,4 @@ class MinecraftInstance : public BaseInstance {
mutable std::shared_ptr m_game_options;
};
-typedef std::shared_ptr MinecraftInstancePtr;
+using MinecraftInstancePtr = std::shared_ptr;
diff --git a/launcher/minecraft/MojangDownloadInfo.h b/launcher/minecraft/MojangDownloadInfo.h
index 855dbe005..eb64f95b7 100644
--- a/launcher/minecraft/MojangDownloadInfo.h
+++ b/launcher/minecraft/MojangDownloadInfo.h
@@ -5,7 +5,7 @@
struct MojangDownloadInfo {
// types
- typedef std::shared_ptr Ptr;
+ using Ptr = std::shared_ptr;
// data
/// Local filesystem path. WARNING: not used, only here so we can pass through mojang files unmolested!
@@ -23,7 +23,7 @@ struct MojangLibraryDownloadInfo {
MojangLibraryDownloadInfo() {}
// types
- typedef std::shared_ptr Ptr;
+ using Ptr = std::shared_ptr;
// methods
MojangDownloadInfo* getDownloadInfo(QString classifier)
@@ -42,7 +42,7 @@ struct MojangLibraryDownloadInfo {
struct MojangAssetIndexInfo : public MojangDownloadInfo {
// types
- typedef std::shared_ptr Ptr;
+ using Ptr = std::shared_ptr;
// methods
MojangAssetIndexInfo() {}
diff --git a/launcher/minecraft/OneSixVersionFormat.h b/launcher/minecraft/OneSixVersionFormat.h
index 9bdc4a4a3..9024d41e4 100644
--- a/launcher/minecraft/OneSixVersionFormat.h
+++ b/launcher/minecraft/OneSixVersionFormat.h
@@ -9,7 +9,7 @@
class OneSixVersionFormat {
public:
// version files / profile patches
- static VersionFilePtr versionFileFromJson(const QJsonDocument& doc, const QString& filename, const bool requireOrder);
+ static VersionFilePtr versionFileFromJson(const QJsonDocument& doc, const QString& filename, bool requireOrder);
static QJsonDocument versionFileToJson(const VersionFilePtr& patch);
// libraries
diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp
index 92988808a..180f8aa30 100644
--- a/launcher/minecraft/PackProfile.cpp
+++ b/launcher/minecraft/PackProfile.cpp
@@ -58,15 +58,14 @@
#include "ComponentUpdateTask.h"
#include "PackProfile.h"
#include "PackProfile_p.h"
+#include "minecraft/mod/Mod.h"
+#include "modplatform/ModIndex.h"
-#include "Application.h"
-#include "modplatform/ResourceAPI.h"
-
-static const QMap modloaderMapping{ { "net.neoforged", ResourceAPI::NeoForge },
- { "net.minecraftforge", ResourceAPI::Forge },
- { "net.fabricmc.fabric-loader", ResourceAPI::Fabric },
- { "org.quiltmc.quilt-loader", ResourceAPI::Quilt },
- { "com.mumfrey.liteloader", ResourceAPI::LiteLoader } };
+static const QMap modloaderMapping{ { "net.neoforged", ModPlatform::NeoForge },
+ { "net.minecraftforge", ModPlatform::Forge },
+ { "net.fabricmc.fabric-loader", ModPlatform::Fabric },
+ { "org.quiltmc.quilt-loader", ModPlatform::Quilt },
+ { "com.mumfrey.liteloader", ModPlatform::LiteLoader } };
PackProfile::PackProfile(MinecraftInstance* instance) : QAbstractListModel()
{
@@ -431,7 +430,7 @@ bool PackProfile::remove(const int index)
return true;
}
-bool PackProfile::remove(const QString id)
+bool PackProfile::remove(const QString& id)
{
int i = 0;
for (auto patch : d->components) {
@@ -990,12 +989,12 @@ void PackProfile::disableInteraction(bool disable)
}
}
-std::optional PackProfile::getModLoaders()
+std::optional PackProfile::getModLoaders()
{
- ResourceAPI::ModLoaderTypes result;
+ ModPlatform::ModLoaderTypes result;
bool has_any_loader = false;
- QMapIterator i(modloaderMapping);
+ QMapIterator i(modloaderMapping);
while (i.hasNext()) {
i.next();
@@ -1009,3 +1008,17 @@ std::optional PackProfile::getModLoaders()
return {};
return result;
}
+
+std::optional PackProfile::getSupportedModLoaders()
+{
+ auto loadersOpt = getModLoaders();
+ if (!loadersOpt.has_value())
+ return loadersOpt;
+ auto loaders = loadersOpt.value();
+ // TODO: remove this or add version condition once Quilt drops official Fabric support
+ if (loaders & ModPlatform::Quilt)
+ loaders |= ModPlatform::Fabric;
+ if (getComponentVersion("net.minecraft") == "1.20.1" && (loaders & ModPlatform::NeoForge))
+ loaders |= ModPlatform::Forge;
+ return loaders;
+}
diff --git a/launcher/minecraft/PackProfile.h b/launcher/minecraft/PackProfile.h
index ce44fa588..e58e9ae9a 100644
--- a/launcher/minecraft/PackProfile.h
+++ b/launcher/minecraft/PackProfile.h
@@ -44,14 +44,11 @@
#include
#include
#include
+#include
-#include "BaseVersion.h"
#include "Component.h"
#include "LaunchProfile.h"
-#include "Library.h"
-#include "MojangDownloadInfo.h"
-#include "ProfileUtils.h"
-#include "modplatform/ResourceAPI.h"
+#include "modplatform/ModIndex.h"
#include "net/Mode.h"
class MinecraftInstance;
@@ -92,13 +89,13 @@ class PackProfile : public QAbstractListModel {
enum MoveDirection { MoveUp, MoveDown };
/// move component file # up or down the list
- void move(const int index, const MoveDirection direction);
+ void move(int index, MoveDirection direction);
/// remove component file # - including files/records
- bool remove(const int index);
+ bool remove(int index);
/// remove component file by id - including files/records
- bool remove(const QString id);
+ bool remove(const QString& id);
bool customize(int index);
@@ -146,7 +143,9 @@ class PackProfile : public QAbstractListModel {
// todo(merged): is this the best approach
void appendComponent(ComponentPtr component);
- std::optional getModLoaders();
+ std::optional getModLoaders();
+ // this returns aditional loaders(Quilt supports fabric and NeoForge supports Forge)
+ std::optional getSupportedModLoaders();
private:
void scheduleSave();
diff --git a/launcher/minecraft/ProfileUtils.cpp b/launcher/minecraft/ProfileUtils.cpp
index d56ed14bd..f81d6cb7f 100644
--- a/launcher/minecraft/ProfileUtils.cpp
+++ b/launcher/minecraft/ProfileUtils.cpp
@@ -140,7 +140,7 @@ VersionFilePtr parseJsonFile(const QFileInfo& fileInfo, const bool requireOrder)
return guardedParseJson(doc, fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), requireOrder);
}
-bool saveJsonFile(const QJsonDocument doc, const QString& filename)
+bool saveJsonFile(const QJsonDocument& doc, const QString& filename)
{
auto data = doc.toJson();
QSaveFile jsonFile(filename);
diff --git a/launcher/minecraft/ProfileUtils.h b/launcher/minecraft/ProfileUtils.h
index 98a7ff739..11e44485f 100644
--- a/launcher/minecraft/ProfileUtils.h
+++ b/launcher/minecraft/ProfileUtils.h
@@ -38,7 +38,7 @@
#include "VersionFile.h"
namespace ProfileUtils {
-typedef QStringList PatchOrder;
+using PatchOrder = QStringList;
/// Read and parse a OneSix format order file
bool readOverrideOrders(QString path, PatchOrder& order);
@@ -47,10 +47,10 @@ bool readOverrideOrders(QString path, PatchOrder& order);
bool writeOverrideOrders(QString path, const PatchOrder& order);
/// Parse a version file in JSON format
-VersionFilePtr parseJsonFile(const QFileInfo& fileInfo, const bool requireOrder);
+VersionFilePtr parseJsonFile(const QFileInfo& fileInfo, bool requireOrder);
/// Save a JSON file (in any format)
-bool saveJsonFile(const QJsonDocument doc, const QString& filename);
+bool saveJsonFile(const QJsonDocument& doc, const QString& filename);
/// Remove LWJGL from a patch file. This is applied to all Mojang-like profile files.
void removeLwjglFromPatch(VersionFilePtr patch);
diff --git a/launcher/minecraft/auth/AccountData.cpp b/launcher/minecraft/auth/AccountData.cpp
index 474bf7c6e..e1f1e9b1e 100644
--- a/launcher/minecraft/auth/AccountData.cpp
+++ b/launcher/minecraft/auth/AccountData.cpp
@@ -278,67 +278,6 @@ bool entitlementFromJSONV3(const QJsonObject& parent, MinecraftEntitlement& out)
} // namespace
-bool AccountData::resumeStateFromV2(QJsonObject data)
-{
- // The JSON object must at least have a username for it to be valid.
- if (!data.value("username").isString()) {
- qCritical() << "Can't load Mojang account info from JSON object. Username field is missing or of the wrong type.";
- return false;
- }
-
- QString userName = data.value("username").toString("");
- QString clientToken = data.value("clientToken").toString("");
- QString accessToken = data.value("accessToken").toString("");
-
- QJsonArray profileArray = data.value("profiles").toArray();
- if (profileArray.size() < 1) {
- qCritical() << "Can't load Mojang account with username \"" << userName << "\". No profiles found.";
- return false;
- }
-
- struct AccountProfile {
- QString id;
- QString name;
- bool legacy;
- };
-
- QList profiles;
- int currentProfileIndex = 0;
- int index = -1;
- QString currentProfile = data.value("activeProfile").toString("");
- for (QJsonValue profileVal : profileArray) {
- index++;
- QJsonObject profileObject = profileVal.toObject();
- QString id = profileObject.value("id").toString("");
- QString name = profileObject.value("name").toString("");
- bool legacy_ = profileObject.value("legacy").toBool(false);
- if (id.isEmpty() || name.isEmpty()) {
- qWarning() << "Unable to load a profile" << name << "because it was missing an ID or a name.";
- continue;
- }
- if (id == currentProfile) {
- currentProfileIndex = index;
- }
- profiles.append({ id, name, legacy_ });
- }
- auto& profile = profiles[currentProfileIndex];
-
- type = AccountType::Mojang;
- legacy = profile.legacy;
-
- minecraftProfile.id = profile.id;
- minecraftProfile.name = profile.name;
- minecraftProfile.validity = Katabasis::Validity::Assumed;
-
- yggdrasilToken.token = accessToken;
- yggdrasilToken.extra["clientToken"] = clientToken;
- yggdrasilToken.extra["userName"] = userName;
- yggdrasilToken.validity = Katabasis::Validity::Assumed;
-
- validity_ = minecraftProfile.validity;
- return true;
-}
-
bool AccountData::resumeStateFromV3(QJsonObject data)
{
auto typeV = data.value("type");
@@ -349,8 +288,6 @@ bool AccountData::resumeStateFromV3(QJsonObject data)
auto typeS = typeV.toString();
if (typeS == "MSA") {
type = AccountType::MSA;
- } else if (typeS == "Mojang") {
- type = AccountType::Mojang;
} else if (typeS == "Offline") {
type = AccountType::Offline;
} else {
@@ -358,11 +295,6 @@ bool AccountData::resumeStateFromV3(QJsonObject data)
return false;
}
- if (type == AccountType::Mojang) {
- legacy = data.value("legacy").toBool(false);
- canMigrateToMSA = data.value("canMigrateToMSA").toBool(false);
- }
-
if (type == AccountType::MSA) {
auto clientIDV = data.value("msa-client-id");
if (clientIDV.isString()) {
@@ -395,15 +327,7 @@ bool AccountData::resumeStateFromV3(QJsonObject data)
QJsonObject AccountData::saveState() const
{
QJsonObject output;
- if (type == AccountType::Mojang) {
- output["type"] = "Mojang";
- if (legacy) {
- output["legacy"] = true;
- }
- if (canMigrateToMSA) {
- output["canMigrateToMSA"] = true;
- }
- } else if (type == AccountType::MSA) {
+ if (type == AccountType::MSA) {
output["type"] = "MSA";
output["msa-client-id"] = msaClientID;
tokenToJSONV3(output, msaToken, "msa");
@@ -420,51 +344,11 @@ QJsonObject AccountData::saveState() const
return output;
}
-QString AccountData::userName() const
-{
- if (type == AccountType::MSA) {
- return QString();
- }
- return yggdrasilToken.extra["userName"].toString();
-}
-
QString AccountData::accessToken() const
{
return yggdrasilToken.token;
}
-QString AccountData::clientToken() const
-{
- if (type != AccountType::Mojang) {
- return QString();
- }
- return yggdrasilToken.extra["clientToken"].toString();
-}
-
-void AccountData::setClientToken(QString clientToken)
-{
- if (type != AccountType::Mojang) {
- return;
- }
- yggdrasilToken.extra["clientToken"] = clientToken;
-}
-
-void AccountData::generateClientTokenIfMissing()
-{
- if (yggdrasilToken.extra.contains("clientToken")) {
- return;
- }
- invalidateClientToken();
-}
-
-void AccountData::invalidateClientToken()
-{
- if (type != AccountType::Mojang) {
- return;
- }
- yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{-}]"));
-}
-
QString AccountData::profileId() const
{
return minecraftProfile.id;
@@ -482,9 +366,6 @@ QString AccountData::profileName() const
QString AccountData::accountDisplayString() const
{
switch (type) {
- case AccountType::Mojang: {
- return userName();
- }
case AccountType::Offline: {
return QObject::tr("");
}
diff --git a/launcher/minecraft/auth/AccountData.h b/launcher/minecraft/auth/AccountData.h
index 9b626c34e..bac77e17f 100644
--- a/launcher/minecraft/auth/AccountData.h
+++ b/launcher/minecraft/auth/AccountData.h
@@ -71,27 +71,17 @@ struct MinecraftProfile {
Katabasis::Validity validity = Katabasis::Validity::None;
};
-enum class AccountType { MSA, Mojang, Offline };
+enum class AccountType { MSA, Offline };
enum class AccountState { Unchecked, Offline, Working, Online, Disabled, Errored, Expired, Gone };
struct AccountData {
QJsonObject saveState() const;
- bool resumeStateFromV2(QJsonObject data);
bool resumeStateFromV3(QJsonObject data);
//! userName for Mojang accounts, gamertag for MSA
QString accountDisplayString() const;
- //! Only valid for Mojang accounts. MSA does not preserve this information
- QString userName() const;
-
- //! Only valid for Mojang accounts.
- QString clientToken() const;
- void setClientToken(QString clientToken);
- void invalidateClientToken();
- void generateClientTokenIfMissing();
-
//! Yggdrasil access token, as passed to the game.
QString accessToken() const;
@@ -101,8 +91,6 @@ struct AccountData {
QString lastError() const;
AccountType type = AccountType::MSA;
- bool legacy = false;
- bool canMigrateToMSA = false;
QString msaClientID;
Katabasis::Token msaToken;
diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp
index c534ea3bd..c3c09003c 100644
--- a/launcher/minecraft/auth/AccountList.cpp
+++ b/launcher/minecraft/auth/AccountList.cpp
@@ -54,7 +54,7 @@
#include
-enum AccountListVersion { MojangOnly = 2, MojangMSA = 3 };
+enum AccountListVersion { MojangMSA = 3 };
AccountList::AccountList(QObject* parent) : QAbstractListModel(parent)
{
@@ -283,9 +283,15 @@ QVariant AccountList::data(const QModelIndex& index, int role) const
return account->accountDisplayString();
case TypeColumn: {
- auto typeStr = account->typeString();
- typeStr[0] = typeStr[0].toUpper();
- return typeStr;
+ switch (account->accountType()) {
+ case AccountType::MSA: {
+ return tr("MSA", "Account type");
+ }
+ case AccountType::Offline: {
+ return tr("Offline", "Account type");
+ }
+ }
+ return tr("Unknown", "Account type");
}
case StatusColumn: {
@@ -320,17 +326,6 @@ QVariant AccountList::data(const QModelIndex& index, int role) const
}
}
- case MigrationColumn: {
- if (account->isMSA() || account->isOffline()) {
- return tr("N/A", "Can Migrate");
- }
- if (account->canMigrate()) {
- return tr("Yes", "Can Migrate");
- } else {
- return tr("No", "Can Migrate");
- }
- }
-
default:
return QVariant();
}
@@ -366,8 +361,6 @@ QVariant AccountList::headerData(int section, [[maybe_unused]] Qt::Orientation o
return tr("Type");
case StatusColumn:
return tr("Status");
- case MigrationColumn:
- return tr("Can Migrate?");
default:
return QVariant();
}
@@ -379,11 +372,9 @@ QVariant AccountList::headerData(int section, [[maybe_unused]] Qt::Orientation o
case NameColumn:
return tr("User name of the account.");
case TypeColumn:
- return tr("Type of the account - Mojang or MSA.");
+ return tr("Type of the account (MSA or Offline)");
case StatusColumn:
return tr("Current status of the account.");
- case MigrationColumn:
- return tr("Can this account migrate to a Microsoft account?");
default:
return QVariant();
}
@@ -415,7 +406,7 @@ Qt::ItemFlags AccountList::flags(const QModelIndex& index) const
bool AccountList::setData(const QModelIndex& idx, const QVariant& value, int role)
{
- if (idx.row() < 0 || idx.row() >= rowCount(idx) || !idx.isValid()) {
+ if (idx.row() < 0 || idx.row() >= rowCount(idx.parent()) || !idx.isValid()) {
return false;
}
@@ -423,7 +414,8 @@ bool AccountList::setData(const QModelIndex& idx, const QVariant& value, int rol
if (value == Qt::Checked) {
MinecraftAccountPtr account = at(idx.row());
setDefaultAccount(account);
- }
+ } else if (m_defaultAccount == at(idx.row()))
+ setDefaultAccount(nullptr);
}
emit dataChanged(idx, index(idx.row(), columnCount(QModelIndex()) - 1));
@@ -472,9 +464,6 @@ bool AccountList::loadList()
// Make sure the format version matches.
auto listVersion = root.value("formatVersion").toVariant().toInt();
switch (listVersion) {
- case AccountListVersion::MojangOnly: {
- return loadV2(root);
- } break;
case AccountListVersion::MojangMSA: {
return loadV3(root);
} break;
@@ -488,36 +477,6 @@ bool AccountList::loadList()
}
}
-bool AccountList::loadV2(QJsonObject& root)
-{
- beginResetModel();
- auto defaultUserName = root.value("activeAccount").toString("");
- QJsonArray accounts = root.value("accounts").toArray();
- for (QJsonValue accountVal : accounts) {
- QJsonObject accountObj = accountVal.toObject();
- MinecraftAccountPtr account = MinecraftAccount::loadFromJsonV2(accountObj);
- if (account.get() != nullptr) {
- auto profileId = account->profileId();
- if (!profileId.size()) {
- continue;
- }
- if (findAccountByProfileId(profileId) != -1) {
- continue;
- }
- connect(account.get(), &MinecraftAccount::changed, this, &AccountList::accountChanged);
- connect(account.get(), &MinecraftAccount::activityChanged, this, &AccountList::accountActivityChanged);
- m_accounts.append(account);
- if (defaultUserName.size() && account->mojangUserName() == defaultUserName) {
- m_defaultAccount = account;
- }
- } else {
- qWarning() << "Failed to load an account.";
- }
- }
- endResetModel();
- return true;
-}
-
bool AccountList::loadV3(QJsonObject& root)
{
beginResetModel();
diff --git a/launcher/minecraft/auth/AccountList.h b/launcher/minecraft/auth/AccountList.h
index 6a0b01916..039730739 100644
--- a/launcher/minecraft/auth/AccountList.h
+++ b/launcher/minecraft/auth/AccountList.h
@@ -55,7 +55,6 @@ class AccountList : public QAbstractListModel {
// TODO: Add icon column.
ProfileNameColumn = 0,
NameColumn,
- MigrationColumn,
TypeColumn,
StatusColumn,
@@ -76,7 +75,7 @@ class AccountList : public QAbstractListModel {
virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
virtual bool setData(const QModelIndex& index, const QVariant& value, int role) override;
- void addAccount(const MinecraftAccountPtr account);
+ void addAccount(MinecraftAccountPtr account);
void removeAccount(QModelIndex index);
int findAccountByProfileId(const QString& profileId) const;
MinecraftAccountPtr getAccountByProfileName(const QString& profileName) const;
@@ -97,7 +96,6 @@ class AccountList : public QAbstractListModel {
void setListFilePath(QString path, bool autosave = false);
bool loadList();
- bool loadV2(QJsonObject& root);
bool loadV3(QJsonObject& root);
bool saveList();
diff --git a/launcher/minecraft/auth/AuthSession.h b/launcher/minecraft/auth/AuthSession.h
index 40519476d..cec238033 100644
--- a/launcher/minecraft/auth/AuthSession.h
+++ b/launcher/minecraft/auth/AuthSession.h
@@ -24,10 +24,6 @@ struct AuthSession {
GoneOrMigrated
} status = Undetermined;
- // client token
- QString client_token;
- // account user name
- QString username;
// combined session ID
QString session;
// volatile auth token
@@ -47,4 +43,4 @@ struct AuthSession {
bool demo = false;
};
-typedef std::shared_ptr AuthSessionPtr;
+using AuthSessionPtr = std::shared_ptr;
diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp
index 6c2f0805f..ecee93d98 100644
--- a/launcher/minecraft/auth/MinecraftAccount.cpp
+++ b/launcher/minecraft/auth/MinecraftAccount.cpp
@@ -51,23 +51,14 @@
#include
#include "flows/MSA.h"
-#include "flows/Mojang.h"
#include "flows/Offline.h"
+#include "minecraft/auth/AccountData.h"
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent)
{
data.internalId = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
}
-MinecraftAccountPtr MinecraftAccount::loadFromJsonV2(const QJsonObject& json)
-{
- MinecraftAccountPtr account(new MinecraftAccount());
- if (account->data.resumeStateFromV2(json)) {
- return account;
- }
- return nullptr;
-}
-
MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json)
{
MinecraftAccountPtr account(new MinecraftAccount());
@@ -77,15 +68,6 @@ MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json)
return nullptr;
}
-MinecraftAccountPtr MinecraftAccount::createFromUsername(const QString& username)
-{
- auto account = makeShared();
- account->data.type = AccountType::Mojang;
- account->data.yggdrasilToken.extra["userName"] = username;
- account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
- return account;
-}
-
MinecraftAccountPtr MinecraftAccount::createBlankMSA()
{
MinecraftAccountPtr account(new MinecraftAccount());
@@ -138,18 +120,6 @@ QPixmap MinecraftAccount::getFace() const
return skin.scaled(64, 64, Qt::KeepAspectRatio);
}
-shared_qobject_ptr MinecraftAccount::login(QString password)
-{
- Q_ASSERT(m_currentTask.get() == nullptr);
-
- m_currentTask.reset(new MojangLogin(&data, password));
- connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
- connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
- connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); });
- emit activityChanged(true);
- return m_currentTask;
-}
-
shared_qobject_ptr MinecraftAccount::loginMSA()
{
Q_ASSERT(m_currentTask.get() == nullptr);
@@ -182,10 +152,8 @@ shared_qobject_ptr MinecraftAccount::refresh()
if (data.type == AccountType::MSA) {
m_currentTask.reset(new MSASilent(&data));
- } else if (data.type == AccountType::Offline) {
- m_currentTask.reset(new OfflineRefresh(&data));
} else {
- m_currentTask.reset(new MojangRefresh(&data));
+ m_currentTask.reset(new OfflineRefresh(&data));
}
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
@@ -218,7 +186,7 @@ void MinecraftAccount::authFailed(QString reason)
// NOTE: this doesn't do much. There was an error of some sort.
} break;
case AccountTaskState::STATE_FAILED_HARD: {
- if (isMSA()) {
+ if (accountType() == AccountType::MSA) {
data.msaToken.token = QString();
data.msaToken.refresh_token = QString();
data.msaToken.validity = Katabasis::Validity::None;
@@ -296,13 +264,8 @@ void MinecraftAccount::fillSession(AuthSessionPtr session)
}
}
- // the user name. you have to have an user name
- // FIXME: not with MSA
- session->username = data.userName();
// volatile auth token
session->access_token = data.accessToken();
- // the semi-permanent client token
- session->client_token = data.clientToken();
// profile name
session->player_name = data.profileName();
// profile ID
diff --git a/launcher/minecraft/auth/MinecraftAccount.h b/launcher/minecraft/auth/MinecraftAccount.h
index f04f947fa..f773b3bc9 100644
--- a/launcher/minecraft/auth/MinecraftAccount.h
+++ b/launcher/minecraft/auth/MinecraftAccount.h
@@ -54,7 +54,7 @@ class Task;
class AccountTask;
class MinecraftAccount;
-typedef shared_qobject_ptr MinecraftAccountPtr;
+using MinecraftAccountPtr = shared_qobject_ptr;
Q_DECLARE_METATYPE(MinecraftAccountPtr)
/**
@@ -85,13 +85,10 @@ class MinecraftAccount : public QObject, public Usable {
//! Default constructor
explicit MinecraftAccount(QObject* parent = 0);
- static MinecraftAccountPtr createFromUsername(const QString& username);
-
static MinecraftAccountPtr createBlankMSA();
static MinecraftAccountPtr createOffline(const QString& username);
- static MinecraftAccountPtr loadFromJsonV2(const QJsonObject& json);
static MinecraftAccountPtr loadFromJsonV3(const QJsonObject& json);
static QUuid uuidFromUsername(QString username);
@@ -100,12 +97,6 @@ class MinecraftAccount : public QObject, public Usable {
QJsonObject saveToJson() const;
public: /* manipulation */
- /**
- * Attempt to login. Empty password means we use the token.
- * If the attempt fails because we already are performing some task, it returns false.
- */
- shared_qobject_ptr login(QString password);
-
shared_qobject_ptr loginMSA();
shared_qobject_ptr loginOffline();
@@ -119,8 +110,6 @@ class MinecraftAccount : public QObject, public Usable {
QString accountDisplayString() const { return data.accountDisplayString(); }
- QString mojangUserName() const { return data.userName(); }
-
QString accessToken() const { return data.accessToken(); }
QString profileId() const { return data.profileId(); }
@@ -129,11 +118,7 @@ class MinecraftAccount : public QObject, public Usable {
bool isActive() const;
- bool canMigrate() const { return data.canMigrateToMSA; }
-
- bool isMSA() const { return data.type == AccountType::MSA; }
-
- bool isOffline() const { return data.type == AccountType::Offline; }
+ [[nodiscard]] AccountType accountType() const noexcept { return data.type; }
bool ownsMinecraft() const { return data.minecraftEntitlement.ownsMinecraft; }
@@ -142,12 +127,6 @@ class MinecraftAccount : public QObject, public Usable {
QString typeString() const
{
switch (data.type) {
- case AccountType::Mojang: {
- if (data.legacy) {
- return "legacy";
- }
- return "mojang";
- } break;
case AccountType::MSA: {
return "msa";
} break;
diff --git a/launcher/minecraft/auth/Yggdrasil.cpp b/launcher/minecraft/auth/Yggdrasil.cpp
deleted file mode 100644
index 97f2a78d4..000000000
--- a/launcher/minecraft/auth/Yggdrasil.cpp
+++ /dev/null
@@ -1,342 +0,0 @@
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "Yggdrasil.h"
-#include "AccountData.h"
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include
-
-#include "Application.h"
-
-Yggdrasil::Yggdrasil(AccountData* data, QObject* parent) : AccountTask(data, parent)
-{
- changeState(AccountTaskState::STATE_CREATED);
-}
-
-void Yggdrasil::sendRequest(QUrl endpoint, QByteArray content)
-{
- changeState(AccountTaskState::STATE_WORKING);
-
- QNetworkRequest netRequest(endpoint);
- netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
- m_netReply = APPLICATION->network()->post(netRequest, content);
- connect(m_netReply, &QNetworkReply::finished, this, &Yggdrasil::processReply);
- connect(m_netReply, &QNetworkReply::uploadProgress, this, &Yggdrasil::refreshTimers);
- connect(m_netReply, &QNetworkReply::downloadProgress, this, &Yggdrasil::refreshTimers);
- connect(m_netReply, &QNetworkReply::sslErrors, this, &Yggdrasil::sslErrors);
- timeout_keeper.setSingleShot(true);
- timeout_keeper.start(timeout_max);
- counter.setSingleShot(false);
- counter.start(time_step);
- progress(0, timeout_max);
- connect(&timeout_keeper, &QTimer::timeout, this, &Yggdrasil::abortByTimeout);
- connect(&counter, &QTimer::timeout, this, &Yggdrasil::heartbeat);
-}
-
-void Yggdrasil::executeTask() {}
-
-void Yggdrasil::refresh()
-{
- start();
- /*
- * {
- * "clientToken": "client identifier"
- * "accessToken": "current access token to be refreshed"
- * "selectedProfile": // specifying this causes errors
- * {
- * "id": "profile ID"
- * "name": "profile name"
- * }
- * "requestUser": true/false // request the user structure
- * }
- */
- QJsonObject req;
- req.insert("clientToken", m_data->clientToken());
- req.insert("accessToken", m_data->accessToken());
- /*
- {
- auto currentProfile = m_account->currentProfile();
- QJsonObject profile;
- profile.insert("id", currentProfile->id());
- profile.insert("name", currentProfile->name());
- req.insert("selectedProfile", profile);
- }
- */
- req.insert("requestUser", false);
- QJsonDocument doc(req);
-
- QUrl reqUrl("https://authserver.mojang.com/refresh");
- QByteArray requestData = doc.toJson();
-
- sendRequest(reqUrl, requestData);
-}
-
-void Yggdrasil::login(QString password)
-{
- start();
- /*
- * {
- * "agent": { // optional
- * "name": "Minecraft", // So far this is the only encountered value
- * "version": 1 // This number might be increased
- * // by the vanilla client in the future
- * },
- * "username": "mojang account name", // Can be an email address or player name for
- * // unmigrated accounts
- * "password": "mojang account password",
- * "clientToken": "client identifier", // optional
- * "requestUser": true/false // request the user structure
- * }
- */
- QJsonObject req;
-
- {
- QJsonObject agent;
- // C++ makes string literals void* for some stupid reason, so we have to tell it
- // QString... Thanks Obama.
- agent.insert("name", QString("Minecraft"));
- agent.insert("version", 1);
- req.insert("agent", agent);
- }
-
- req.insert("username", m_data->userName());
- req.insert("password", password);
- req.insert("requestUser", false);
-
- // If we already have a client token, give it to the server.
- // Otherwise, let the server give us one.
-
- m_data->generateClientTokenIfMissing();
- req.insert("clientToken", m_data->clientToken());
-
- QJsonDocument doc(req);
-
- QUrl reqUrl("https://authserver.mojang.com/authenticate");
- QNetworkRequest netRequest(reqUrl);
- QByteArray requestData = doc.toJson();
-
- sendRequest(reqUrl, requestData);
-}
-
-void Yggdrasil::refreshTimers(qint64, qint64)
-{
- timeout_keeper.stop();
- timeout_keeper.start(timeout_max);
- progress(count = 0, timeout_max);
-}
-
-void Yggdrasil::heartbeat()
-{
- count += time_step;
- progress(count, timeout_max);
-}
-
-bool Yggdrasil::abort()
-{
- progress(timeout_max, timeout_max);
- // TODO: actually use this in a meaningful way
- m_aborted = Yggdrasil::BY_USER;
- m_netReply->abort();
- return true;
-}
-
-void Yggdrasil::abortByTimeout()
-{
- progress(timeout_max, timeout_max);
- // TODO: actually use this in a meaningful way
- m_aborted = Yggdrasil::BY_TIMEOUT;
- m_netReply->abort();
-}
-
-void Yggdrasil::sslErrors(QList errors)
-{
- int i = 1;
- for (auto error : errors) {
- qCritical() << "LOGIN SSL Error #" << i << " : " << error.errorString();
- auto cert = error.certificate();
- qCritical() << "Certificate in question:\n" << cert.toText();
- i++;
- }
-}
-
-void Yggdrasil::processResponse(QJsonObject responseData)
-{
- // Read the response data. We need to get the client token, access token, and the selected
- // profile.
- qDebug() << "Processing authentication response.";
-
- // qDebug() << responseData;
- // If we already have a client token, make sure the one the server gave us matches our
- // existing one.
- QString clientToken = responseData.value("clientToken").toString("");
- if (clientToken.isEmpty()) {
- // Fail if the server gave us an empty client token
- changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send a client token."));
- return;
- }
- if (m_data->clientToken().isEmpty()) {
- m_data->setClientToken(clientToken);
- } else if (clientToken != m_data->clientToken()) {
- changeState(AccountTaskState::STATE_FAILED_HARD,
- tr("Authentication server attempted to change the client token. This isn't supported."));
- return;
- }
-
- // Now, we set the access token.
- qDebug() << "Getting access token.";
- QString accessToken = responseData.value("accessToken").toString("");
- if (accessToken.isEmpty()) {
- // Fail if the server didn't give us an access token.
- changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send an access token."));
- return;
- }
- // Set the access token.
- m_data->yggdrasilToken.token = accessToken;
- m_data->yggdrasilToken.validity = Katabasis::Validity::Certain;
- m_data->yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc();
-
- // Get UUID here since we need it for later
- auto profile = responseData.value("selectedProfile");
- if (!profile.isObject()) {
- changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send a selected profile."));
- return;
- }
-
- auto profileObj = profile.toObject();
- for (auto i = profileObj.constBegin(); i != profileObj.constEnd(); ++i) {
- if (i.key() == "name" && i.value().isString()) {
- m_data->minecraftProfile.name = i->toString();
- } else if (i.key() == "id" && i.value().isString()) {
- m_data->minecraftProfile.id = i->toString();
- }
- }
-
- if (m_data->minecraftProfile.id.isEmpty()) {
- changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send a UUID in selected profile."));
- return;
- }
-
- // We've made it through the minefield of possible errors. Return true to indicate that
- // we've succeeded.
- qDebug() << "Finished reading authentication response.";
- changeState(AccountTaskState::STATE_SUCCEEDED);
-}
-
-void Yggdrasil::processReply()
-{
- changeState(AccountTaskState::STATE_WORKING);
-
- switch (m_netReply->error()) {
- case QNetworkReply::NoError:
- break;
- case QNetworkReply::TimeoutError:
- changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation timed out."));
- return;
- case QNetworkReply::OperationCanceledError:
- changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation cancelled."));
- return;
- case QNetworkReply::SslHandshakeFailedError:
- changeState(AccountTaskState::STATE_FAILED_SOFT,
- tr("SSL Handshake failed.
There might be a few causes for it:
"
- ""
- "- You use Windows and need to update your root certificates, please install any outstanding updates.
"
- "- Some device on your network is interfering with SSL traffic. In that case, "
- "you have bigger worries than Minecraft not starting.
"
- "- Possibly something else. Check the log file for details
"
- "
"));
- return;
- // used for invalid credentials and similar errors. Fall through.
- case QNetworkReply::ContentAccessDenied:
- case QNetworkReply::ContentOperationNotPermittedError:
- break;
- case QNetworkReply::ContentGoneError: {
- changeState(AccountTaskState::STATE_FAILED_GONE,
- tr("The Mojang account no longer exists. It may have been migrated to a Microsoft account."));
- return;
- }
- default:
- changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation failed due to a network error: %1 (%2)")
- .arg(m_netReply->errorString())
- .arg(m_netReply->error()));
- return;
- }
-
- // Try to parse the response regardless of the response code.
- // Sometimes the auth server will give more information and an error code.
- QJsonParseError jsonError;
- QByteArray replyData = m_netReply->readAll();
- QJsonDocument doc = QJsonDocument::fromJson(replyData, &jsonError);
- // Check the response code.
- int responseCode = m_netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- if (responseCode == 200) {
- // If the response code was 200, then there shouldn't be an error. Make sure
- // anyways.
- // Also, sometimes an empty reply indicates success. If there was no data received,
- // pass an empty json object to the processResponse function.
- if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0) {
- processResponse(replyData.size() > 0 ? doc.object() : QJsonObject());
- return;
- } else {
- changeState(AccountTaskState::STATE_FAILED_SOFT,
- tr("Failed to parse authentication server response JSON response: %1 at offset %2.")
- .arg(jsonError.errorString())
- .arg(jsonError.offset));
- qCritical() << replyData;
- }
- return;
- }
-
- // If the response code was not 200, then Yggdrasil may have given us information
- // about the error.
- // If we can parse the response, then get information from it. Otherwise just say
- // there was an unknown error.
- if (jsonError.error == QJsonParseError::NoError) {
- // We were able to parse the server's response. Woo!
- // Call processError. If a subclass has overridden it then they'll handle their
- // stuff there.
- qDebug() << "The request failed, but the server gave us an error message. Processing error.";
- processError(doc.object());
- } else {
- // The server didn't say anything regarding the error. Give the user an unknown
- // error.
- qDebug() << "The request failed and the server gave no error message. Unknown error.";
- changeState(
- AccountTaskState::STATE_FAILED_SOFT,
- tr("An unknown error occurred when trying to communicate with the authentication server: %1").arg(m_netReply->errorString()));
- }
-}
-
-void Yggdrasil::processError(QJsonObject responseData)
-{
- QJsonValue errorVal = responseData.value("error");
- QJsonValue errorMessageValue = responseData.value("errorMessage");
- QJsonValue causeVal = responseData.value("cause");
-
- if (errorVal.isString() && errorMessageValue.isString()) {
- m_error = std::shared_ptr(new Error{ errorVal.toString(""), errorMessageValue.toString(""), causeVal.toString("") });
- changeState(AccountTaskState::STATE_FAILED_HARD, m_error->m_errorMessageVerbose);
- } else {
- // Error is not in standard format. Don't set m_error and return unknown error.
- changeState(AccountTaskState::STATE_FAILED_HARD, tr("An unknown Yggdrasil error occurred."));
- }
-}
diff --git a/launcher/minecraft/auth/Yggdrasil.h b/launcher/minecraft/auth/Yggdrasil.h
deleted file mode 100644
index 560d7fb81..000000000
--- a/launcher/minecraft/auth/Yggdrasil.h
+++ /dev/null
@@ -1,92 +0,0 @@
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "AccountTask.h"
-
-#include
-#include
-#include
-#include
-
-#include "MinecraftAccount.h"
-
-class QNetworkAccessManager;
-class QNetworkReply;
-
-/**
- * A Yggdrasil task is a task that performs an operation on a given mojang account.
- */
-class Yggdrasil : public AccountTask {
- Q_OBJECT
- public:
- explicit Yggdrasil(AccountData* data, QObject* parent = 0);
- virtual ~Yggdrasil() = default;
-
- void refresh();
- void login(QString password);
-
- struct Error {
- QString m_errorMessageShort;
- QString m_errorMessageVerbose;
- QString m_cause;
- };
- std::shared_ptr m_error;
-
- enum AbortedBy { BY_NOTHING, BY_USER, BY_TIMEOUT } m_aborted = BY_NOTHING;
-
- protected:
- void executeTask() override;
-
- /**
- * Processes the response received from the server.
- * If an error occurred, this should emit a failed signal.
- * If Yggdrasil gave an error response, it should call setError() first, and then return false.
- * Otherwise, it should return true.
- * Note: If the response from the server was blank, and the HTTP code was 200, this function is called with
- * an empty QJsonObject.
- */
- void processResponse(QJsonObject responseData);
-
- /**
- * Processes an error response received from the server.
- * The default implementation will read data from Yggdrasil's standard error response format and set it as this task's Error.
- * \returns a QString error message that will be passed to emitFailed.
- */
- virtual void processError(QJsonObject responseData);
-
- protected slots:
- void processReply();
- void refreshTimers(qint64, qint64);
- void heartbeat();
- void sslErrors(QList);
- void abortByTimeout();
-
- public slots:
- virtual bool abort() override;
-
- private:
- void sendRequest(QUrl endpoint, QByteArray content);
-
- protected:
- QNetworkReply* m_netReply = nullptr;
- QTimer timeout_keeper;
- QTimer counter;
- int count = 0; // num msec since time reset
-
- const int timeout_max = 30000;
- const int time_step = 50;
-};
diff --git a/launcher/minecraft/auth/flows/AuthFlow.h b/launcher/minecraft/auth/flows/AuthFlow.h
index c2c412abc..e39e926dd 100644
--- a/launcher/minecraft/auth/flows/AuthFlow.h
+++ b/launcher/minecraft/auth/flows/AuthFlow.h
@@ -12,7 +12,6 @@
#include "minecraft/auth/AccountData.h"
#include "minecraft/auth/AccountTask.h"
#include "minecraft/auth/AuthStep.h"
-#include "minecraft/auth/Yggdrasil.h"
class AuthFlow : public AccountTask {
Q_OBJECT
diff --git a/launcher/minecraft/auth/flows/Mojang.cpp b/launcher/minecraft/auth/flows/Mojang.cpp
deleted file mode 100644
index 7e2db16fa..000000000
--- a/launcher/minecraft/auth/flows/Mojang.cpp
+++ /dev/null
@@ -1,22 +0,0 @@
-#include "Mojang.h"
-
-#include "minecraft/auth/steps/GetSkinStep.h"
-#include "minecraft/auth/steps/MigrationEligibilityStep.h"
-#include "minecraft/auth/steps/MinecraftProfileStepMojang.h"
-#include "minecraft/auth/steps/YggdrasilStep.h"
-
-MojangRefresh::MojangRefresh(AccountData* data, QObject* parent) : AuthFlow(data, parent)
-{
- m_steps.append(makeShared(m_data, QString()));
- m_steps.append(makeShared(m_data));
- m_steps.append(makeShared(m_data));
- m_steps.append(makeShared(m_data));
-}
-
-MojangLogin::MojangLogin(AccountData* data, QString password, QObject* parent) : AuthFlow(data, parent), m_password(password)
-{
- m_steps.append(makeShared(m_data, m_password));
- m_steps.append(makeShared(m_data));
- m_steps.append(makeShared(m_data));
- m_steps.append(makeShared(m_data));
-}
diff --git a/launcher/minecraft/auth/flows/Mojang.h b/launcher/minecraft/auth/flows/Mojang.h
deleted file mode 100644
index 779ca7e34..000000000
--- a/launcher/minecraft/auth/flows/Mojang.h
+++ /dev/null
@@ -1,17 +0,0 @@
-#pragma once
-#include "AuthFlow.h"
-
-class MojangRefresh : public AuthFlow {
- Q_OBJECT
- public:
- explicit MojangRefresh(AccountData* data, QObject* parent = 0);
-};
-
-class MojangLogin : public AuthFlow {
- Q_OBJECT
- public:
- explicit MojangLogin(AccountData* data, QString password, QObject* parent = 0);
-
- private:
- QString m_password;
-};
diff --git a/launcher/minecraft/auth/steps/MigrationEligibilityStep.cpp b/launcher/minecraft/auth/steps/MigrationEligibilityStep.cpp
deleted file mode 100644
index 5ce953dfb..000000000
--- a/launcher/minecraft/auth/steps/MigrationEligibilityStep.cpp
+++ /dev/null
@@ -1,45 +0,0 @@
-#include "MigrationEligibilityStep.h"
-
-#include
-
-#include "minecraft/auth/AuthRequest.h"
-#include "minecraft/auth/Parsers.h"
-
-MigrationEligibilityStep::MigrationEligibilityStep(AccountData* data) : AuthStep(data) {}
-
-MigrationEligibilityStep::~MigrationEligibilityStep() noexcept = default;
-
-QString MigrationEligibilityStep::describe()
-{
- return tr("Checking for migration eligibility.");
-}
-
-void MigrationEligibilityStep::perform()
-{
- auto url = QUrl("https://api.minecraftservices.com/rollout/v1/msamigration");
- QNetworkRequest request = QNetworkRequest(url);
- request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
- request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8());
-
- AuthRequest* requestor = new AuthRequest(this);
- connect(requestor, &AuthRequest::finished, this, &MigrationEligibilityStep::onRequestDone);
- requestor->get(request);
-}
-
-void MigrationEligibilityStep::rehydrate()
-{
- // NOOP, for now. We only save bools and there's nothing to check.
-}
-
-void MigrationEligibilityStep::onRequestDone(QNetworkReply::NetworkError error,
- QByteArray data,
- QList headers)
-{
- auto requestor = qobject_cast(QObject::sender());
- requestor->deleteLater();
-
- if (error == QNetworkReply::NoError) {
- Parsers::parseRolloutResponse(data, m_data->canMigrateToMSA);
- }
- emit finished(AccountTaskState::STATE_WORKING, tr("Got migration flags"));
-}
diff --git a/launcher/minecraft/auth/steps/MigrationEligibilityStep.h b/launcher/minecraft/auth/steps/MigrationEligibilityStep.h
deleted file mode 100644
index 8638975d8..000000000
--- a/launcher/minecraft/auth/steps/MigrationEligibilityStep.h
+++ /dev/null
@@ -1,21 +0,0 @@
-#pragma once
-#include
-
-#include "QObjectPtr.h"
-#include "minecraft/auth/AuthStep.h"
-
-class MigrationEligibilityStep : public AuthStep {
- Q_OBJECT
-
- public:
- explicit MigrationEligibilityStep(AccountData* data);
- virtual ~MigrationEligibilityStep() noexcept;
-
- void perform() override;
- void rehydrate() override;
-
- QString describe() override;
-
- private slots:
- void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList);
-};
diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp
index 7cdce23f0..a854342bc 100644
--- a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp
+++ b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp
@@ -41,10 +41,6 @@ void MinecraftProfileStep::onRequestDone(QNetworkReply::NetworkError error, QByt
qCDebug(authCredentials()) << data;
if (error == QNetworkReply::ContentNotFoundError) {
// NOTE: Succeed even if we do not have a profile. This is a valid account state.
- if (m_data->type == AccountType::Mojang) {
- m_data->minecraftEntitlement.canPlayMinecraft = false;
- m_data->minecraftEntitlement.ownsMinecraft = false;
- }
m_data->minecraftProfile = MinecraftProfile();
emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Account has no Minecraft profile."));
return;
@@ -73,10 +69,5 @@ void MinecraftProfileStep::onRequestDone(QNetworkReply::NetworkError error, QByt
return;
}
- if (m_data->type == AccountType::Mojang) {
- auto validProfile = m_data->minecraftProfile.validity == Katabasis::Validity::Certain;
- m_data->minecraftEntitlement.canPlayMinecraft = validProfile;
- m_data->minecraftEntitlement.ownsMinecraft = validProfile;
- }
emit finished(AccountTaskState::STATE_WORKING, tr("Minecraft Java profile acquisition succeeded."));
}
diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp b/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp
deleted file mode 100644
index d035e39a0..000000000
--- a/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp
+++ /dev/null
@@ -1,87 +0,0 @@
-#include "MinecraftProfileStepMojang.h"
-
-#include
-
-#include "Logging.h"
-#include "minecraft/auth/AuthRequest.h"
-#include "minecraft/auth/Parsers.h"
-#include "net/NetUtils.h"
-
-MinecraftProfileStepMojang::MinecraftProfileStepMojang(AccountData* data) : AuthStep(data) {}
-
-MinecraftProfileStepMojang::~MinecraftProfileStepMojang() noexcept = default;
-
-QString MinecraftProfileStepMojang::describe()
-{
- return tr("Fetching the Minecraft profile.");
-}
-
-void MinecraftProfileStepMojang::perform()
-{
- if (m_data->minecraftProfile.id.isEmpty()) {
- emit finished(AccountTaskState::STATE_FAILED_HARD, tr("A UUID is required to get the profile."));
- return;
- }
-
- // use session server instead of profile due to profile endpoint being locked for locked Mojang accounts
- QUrl url = QUrl("https://sessionserver.mojang.com/session/minecraft/profile/" + m_data->minecraftProfile.id);
- QNetworkRequest req = QNetworkRequest(url);
- AuthRequest* request = new AuthRequest(this);
- connect(request, &AuthRequest::finished, this, &MinecraftProfileStepMojang::onRequestDone);
- request->get(req);
-}
-
-void MinecraftProfileStepMojang::rehydrate()
-{
- // NOOP, for now. We only save bools and there's nothing to check.
-}
-
-void MinecraftProfileStepMojang::onRequestDone(QNetworkReply::NetworkError error,
- QByteArray data,
- QList headers)
-{
- auto requestor = qobject_cast(QObject::sender());
- requestor->deleteLater();
-
- qCDebug(authCredentials()) << data;
- if (error == QNetworkReply::ContentNotFoundError) {
- // NOTE: Succeed even if we do not have a profile. This is a valid account state.
- if (m_data->type == AccountType::Mojang) {
- m_data->minecraftEntitlement.canPlayMinecraft = false;
- m_data->minecraftEntitlement.ownsMinecraft = false;
- }
- m_data->minecraftProfile = MinecraftProfile();
- emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Account has no Minecraft profile."));
- return;
- }
- if (error != QNetworkReply::NoError) {
- qWarning() << "Error getting profile:";
- qWarning() << " HTTP Status: " << requestor->httpStatus_;
- qWarning() << " Internal error no.: " << error;
- qWarning() << " Error string: " << requestor->errorString_;
-
- qWarning() << " Response:";
- qWarning() << QString::fromUtf8(data);
-
- if (Net::isApplicationError(error)) {
- emit finished(AccountTaskState::STATE_FAILED_SOFT,
- tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_));
- } else {
- emit finished(AccountTaskState::STATE_OFFLINE,
- tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_));
- }
- return;
- }
- if (!Parsers::parseMinecraftProfileMojang(data, m_data->minecraftProfile)) {
- m_data->minecraftProfile = MinecraftProfile();
- emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile response could not be parsed"));
- return;
- }
-
- if (m_data->type == AccountType::Mojang) {
- auto validProfile = m_data->minecraftProfile.validity == Katabasis::Validity::Certain;
- m_data->minecraftEntitlement.canPlayMinecraft = validProfile;
- m_data->minecraftEntitlement.ownsMinecraft = validProfile;
- }
- emit finished(AccountTaskState::STATE_WORKING, tr("Minecraft Java profile acquisition succeeded."));
-}
diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.h b/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.h
deleted file mode 100644
index 730ec3f68..000000000
--- a/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.h
+++ /dev/null
@@ -1,21 +0,0 @@
-#pragma once
-#include
-
-#include "QObjectPtr.h"
-#include "minecraft/auth/AuthStep.h"
-
-class MinecraftProfileStepMojang : public AuthStep {
- Q_OBJECT
-
- public:
- explicit MinecraftProfileStepMojang(AccountData* data);
- virtual ~MinecraftProfileStepMojang() noexcept;
-
- void perform() override;
- void rehydrate() override;
-
- QString describe() override;
-
- private slots:
- void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList);
-};
diff --git a/launcher/minecraft/auth/steps/XboxUserStep.cpp b/launcher/minecraft/auth/steps/XboxUserStep.cpp
index 61c33a18d..856036d23 100644
--- a/launcher/minecraft/auth/steps/XboxUserStep.cpp
+++ b/launcher/minecraft/auth/steps/XboxUserStep.cpp
@@ -38,7 +38,7 @@ void XboxUserStep::perform()
QNetworkRequest request = QNetworkRequest(QUrl("https://user.auth.xboxlive.com/user/authenticate"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Accept", "application/json");
- // set contract-verison header (prevent err 400 bad-request?)
+ // set contract-version header (prevent err 400 bad-request?)
// https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/live/rest/additional/httpstandardheaders
request.setRawHeader("x-xbl-contract-version", "1");
diff --git a/launcher/minecraft/auth/steps/YggdrasilStep.cpp b/launcher/minecraft/auth/steps/YggdrasilStep.cpp
deleted file mode 100644
index fdcaa0d67..000000000
--- a/launcher/minecraft/auth/steps/YggdrasilStep.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
-#include "YggdrasilStep.h"
-
-#include "minecraft/auth/AuthRequest.h"
-#include "minecraft/auth/Parsers.h"
-#include "minecraft/auth/Yggdrasil.h"
-
-YggdrasilStep::YggdrasilStep(AccountData* data, QString password) : AuthStep(data), m_password(password)
-{
- m_yggdrasil = new Yggdrasil(m_data, this);
-
- connect(m_yggdrasil, &Task::failed, this, &YggdrasilStep::onAuthFailed);
- connect(m_yggdrasil, &Task::succeeded, this, &YggdrasilStep::onAuthSucceeded);
- connect(m_yggdrasil, &Task::aborted, this, &YggdrasilStep::onAuthFailed);
-}
-
-YggdrasilStep::~YggdrasilStep() noexcept = default;
-
-QString YggdrasilStep::describe()
-{
- return tr("Logging in with Mojang account.");
-}
-
-void YggdrasilStep::rehydrate()
-{
- // NOOP, for now.
-}
-
-void YggdrasilStep::perform()
-{
- if (m_password.size()) {
- m_yggdrasil->login(m_password);
- } else {
- m_yggdrasil->refresh();
- }
-}
-
-void YggdrasilStep::onAuthSucceeded()
-{
- emit finished(AccountTaskState::STATE_WORKING, tr("Logged in with Mojang"));
-}
-
-void YggdrasilStep::onAuthFailed()
-{
- // TODO: hook these in again, expand to MSA
- // m_error = m_yggdrasil->m_error;
- // m_aborted = m_yggdrasil->m_aborted;
-
- auto state = m_yggdrasil->taskState();
- QString errorMessage = tr("Mojang user authentication failed.");
-
- // NOTE: soft error in the first step means 'offline'
- if (state == AccountTaskState::STATE_FAILED_SOFT) {
- state = AccountTaskState::STATE_OFFLINE;
- errorMessage = tr("Mojang user authentication ended with a network error.");
- }
- emit finished(state, errorMessage);
-}
diff --git a/launcher/minecraft/auth/steps/YggdrasilStep.h b/launcher/minecraft/auth/steps/YggdrasilStep.h
deleted file mode 100644
index ef31f34d5..000000000
--- a/launcher/minecraft/auth/steps/YggdrasilStep.h
+++ /dev/null
@@ -1,28 +0,0 @@
-#pragma once
-#include
-
-#include "QObjectPtr.h"
-#include "minecraft/auth/AuthStep.h"
-
-class Yggdrasil;
-
-class YggdrasilStep : public AuthStep {
- Q_OBJECT
-
- public:
- explicit YggdrasilStep(AccountData* data, QString password);
- virtual ~YggdrasilStep() noexcept;
-
- void perform() override;
- void rehydrate() override;
-
- QString describe() override;
-
- private slots:
- void onAuthSucceeded();
- void onAuthFailed();
-
- private:
- Yggdrasil* m_yggdrasil = nullptr;
- QString m_password;
-};
diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp
index 44e5d0a63..aa94edb5d 100644
--- a/launcher/minecraft/launch/LauncherPartLaunch.cpp
+++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp
@@ -105,6 +105,17 @@ void LauncherPartLaunch::executeTask()
auto instance = m_parent->instance();
std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance);
+ QString legacyJarPath;
+ if (minecraftInstance->getLauncher() == "legacy" || minecraftInstance->shouldApplyOnlineFixes()) {
+ legacyJarPath = APPLICATION->getJarPath("NewLaunchLegacy.jar");
+ if (legacyJarPath.isEmpty()) {
+ const char* reason = QT_TR_NOOP("Legacy launcher library could not be found. Please check your installation.");
+ emit logLine(tr(reason), MessageLevel::Fatal);
+ emitFailed(tr(reason));
+ return;
+ }
+ }
+
m_launchScript = minecraftInstance->createLaunchScript(m_session, m_serverToJoin);
QStringList args = minecraftInstance->javaArguments();
QString allArgs = args.join(", ");
@@ -120,6 +131,9 @@ void LauncherPartLaunch::executeTask()
auto classPath = minecraftInstance->getClassPath();
classPath.prepend(jarPath);
+ if (!legacyJarPath.isEmpty())
+ classPath.prepend(legacyJarPath);
+
auto natPath = minecraftInstance->getNativePath();
#ifdef Q_OS_WIN
if (!fitsInLocal8bit(natPath)) {
diff --git a/launcher/minecraft/launch/MinecraftServerTarget.h b/launcher/minecraft/launch/MinecraftServerTarget.h
index af8d6550b..2edd8a30d 100644
--- a/launcher/minecraft/launch/MinecraftServerTarget.h
+++ b/launcher/minecraft/launch/MinecraftServerTarget.h
@@ -26,4 +26,4 @@ struct MinecraftServerTarget {
static MinecraftServerTarget parse(const QString& fullAddress);
};
-typedef std::shared_ptr MinecraftServerTargetPtr;
+using MinecraftServerTargetPtr = std::shared_ptr;
diff --git a/launcher/minecraft/mod/DataPack.cpp b/launcher/minecraft/mod/DataPack.cpp
index 7bf5a3112..fc2d3f68b 100644
--- a/launcher/minecraft/mod/DataPack.cpp
+++ b/launcher/minecraft/mod/DataPack.cpp
@@ -28,7 +28,7 @@
#include "Version.h"
// Values taken from:
-// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_data_pack#%22pack_format%22
+// https://minecraft.wiki/w/Tutorials/Creating_a_data_pack#%22pack_format%22
static const QMap> s_pack_format_versions = {
{ 4, { Version("1.13"), Version("1.14.4") } }, { 5, { Version("1.15"), Version("1.16.1") } },
{ 6, { Version("1.16.2"), Version("1.16.5") } }, { 7, { Version("1.17"), Version("1.17.1") } },
diff --git a/launcher/minecraft/mod/DataPack.h b/launcher/minecraft/mod/DataPack.h
index fc2703c7a..b3787b238 100644
--- a/launcher/minecraft/mod/DataPack.h
+++ b/launcher/minecraft/mod/DataPack.h
@@ -63,7 +63,7 @@ class DataPack : public Resource {
mutable QMutex m_data_lock;
/* The 'version' of a data pack, as defined in the pack.mcmeta file.
- * See https://minecraft.fandom.com/wiki/Data_pack#pack.mcmeta
+ * See https://minecraft.wiki/w/Data_pack#pack.mcmeta
*/
int m_pack_format = 0;
diff --git a/launcher/minecraft/mod/MetadataHandler.h b/launcher/minecraft/mod/MetadataHandler.h
index 88e9ff2b6..3496da2a0 100644
--- a/launcher/minecraft/mod/MetadataHandler.h
+++ b/launcher/minecraft/mod/MetadataHandler.h
@@ -31,6 +31,7 @@ class Mod;
class Metadata {
public:
using ModStruct = Packwiz::V1::Mod;
+ using ModSide = Packwiz::V1::Side;
static auto create(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> ModStruct
{
diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp
index ae3dea8d8..310946379 100644
--- a/launcher/minecraft/mod/Mod.cpp
+++ b/launcher/minecraft/mod/Mod.cpp
@@ -132,17 +132,23 @@ auto Mod::destroy(QDir& index_dir, bool preserve_metadata, bool attempt_trash) -
if (!preserve_metadata) {
qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name());
- if (metadata()) {
- Metadata::remove(index_dir, metadata()->slug);
- } else {
- auto n = name();
- Metadata::remove(index_dir, n);
- }
+ destroyMetadata(index_dir);
}
return Resource::destroy(attempt_trash);
}
+void Mod::destroyMetadata(QDir& index_dir)
+{
+ if (metadata()) {
+ Metadata::remove(index_dir, metadata()->slug);
+ } else {
+ auto n = name();
+ Metadata::remove(index_dir, n);
+ }
+ m_local_details.metadata = nullptr;
+}
+
auto Mod::details() const -> const ModDetails&
{
return m_local_details;
@@ -246,7 +252,8 @@ void Mod::setIcon(QImage new_image) const
PixmapCache::remove(m_pack_image_cache_key.key);
// scale the image to avoid flooding the pixmapcache
- auto pixmap = QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding));
+ auto pixmap =
+ QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
m_pack_image_cache_key.key = PixmapCache::insert(pixmap);
m_pack_image_cache_key.was_ever_used = true;
@@ -259,7 +266,7 @@ QPixmap Mod::icon(QSize size, Qt::AspectRatioMode mode) const
if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
if (size.isNull())
return cached_image;
- return cached_image.scaled(size, mode);
+ return cached_image.scaled(size, mode, Qt::SmoothTransformation);
}
// No valid image we can get
diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h
index 6dafecfc5..e97ee9d3b 100644
--- a/launcher/minecraft/mod/Mod.h
+++ b/launcher/minecraft/mod/Mod.h
@@ -93,6 +93,8 @@ class Mod : public Resource {
// Delete all the files of this mod
auto destroy(QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool;
+ // Delete the metadata only
+ void destroyMetadata(QDir& index_dir);
void finishResolvingWithDetails(ModDetails&& details);
diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp
index 280e70d7b..fc543202f 100644
--- a/launcher/minecraft/mod/ModFolderModel.cpp
+++ b/launcher/minecraft/mod/ModFolderModel.cpp
@@ -37,9 +37,9 @@
#include "ModFolderModel.h"
#include
-#include
#include
#include
+#include
#include
#include
#include
@@ -51,8 +51,13 @@
#include "Application.h"
+#include "Json.h"
#include "minecraft/mod/tasks/LocalModParseTask.h"
+#include "minecraft/mod/tasks/LocalModUpdateTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
+#include "modplatform/ModIndex.h"
+#include "modplatform/flame/FlameAPI.h"
+#include "modplatform/flame/FlameModIndex.h"
ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir)
: ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed)
@@ -60,8 +65,8 @@ ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool
m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider" });
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider") });
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION, SortType::DATE, SortType::PROVIDER };
- m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch,
- QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents };
+ m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch,
+ QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive };
m_columnsHideable = { false, true, false, true, true, true };
}
@@ -126,6 +131,11 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
}
return {};
}
+ case Qt::SizeHintRole:
+ if (column == ImageColumn) {
+ return QSize(32, 32);
+ }
+ return {};
case Qt::CheckStateRole:
switch (column) {
case ActiveColumn:
@@ -228,6 +238,25 @@ bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
return true;
}
+bool ModFolderModel::deleteModsMetadata(const QModelIndexList& indexes)
+{
+ if (indexes.isEmpty())
+ return true;
+
+ for (auto i : indexes) {
+ if (i.column() != 0) {
+ continue;
+ }
+ auto m = at(i.row());
+ auto index_dir = indexDir();
+ m->destroyMetadata(index_dir);
+ }
+
+ update();
+
+ return true;
+}
+
bool ModFolderModel::isValid()
{
return m_dir.exists() && m_dir.isReadable();
@@ -309,3 +338,47 @@ void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
}
+
+static const FlameAPI flameAPI;
+bool ModFolderModel::installMod(QString file_path, ModPlatform::IndexedVersion& vers)
+{
+ if (vers.addonId.isValid()) {
+ ModPlatform::IndexedPack pack{
+ vers.addonId,
+ ModPlatform::ResourceProvider::FLAME,
+ };
+
+ QEventLoop loop;
+
+ auto response = std::make_shared();
+ auto job = flameAPI.getProject(vers.addonId.toString(), response);
+
+ QObject::connect(job.get(), &Task::failed, [&loop] { loop.quit(); });
+ QObject::connect(job.get(), &Task::aborted, &loop, &QEventLoop::quit);
+ QObject::connect(job.get(), &Task::succeeded, [response, this, &vers, &loop, &pack] {
+ QJsonParseError parse_error{};
+ QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
+ if (parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset
+ << " reason: " << parse_error.errorString();
+ qDebug() << *response;
+ return;
+ }
+ try {
+ auto obj = Json::requireObject(Json::requireObject(doc), "data");
+ FlameMod::loadIndexedPack(pack, obj);
+ } catch (const JSONValidationError& e) {
+ qDebug() << doc;
+ qWarning() << "Error while reading mod info: " << e.cause();
+ }
+ LocalModUpdateTask update_metadata(indexDir(), pack, vers);
+ QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit);
+ update_metadata.start();
+ });
+
+ job->start();
+
+ loop.exec();
+ }
+ return ResourceFolderModel::installResource(file_path);
+}
diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h
index 06fd78149..61d840f9b 100644
--- a/launcher/minecraft/mod/ModFolderModel.h
+++ b/launcher/minecraft/mod/ModFolderModel.h
@@ -48,6 +48,7 @@
#include "minecraft/mod/tasks/LocalModParseTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
+#include "modplatform/ModIndex.h"
class LegacyInstance;
class BaseInstance;
@@ -75,10 +76,12 @@ class ModFolderModel : public ResourceFolderModel {
[[nodiscard]] Task* createParseTask(Resource&) override;
bool installMod(QString file_path) { return ResourceFolderModel::installResource(file_path); }
+ bool installMod(QString file_path, ModPlatform::IndexedVersion& vers);
bool uninstallMod(const QString& filename, bool preserve_metadata = false);
/// Deletes all the selected mods
bool deleteMods(const QModelIndexList& indexes);
+ bool deleteModsMetadata(const QModelIndexList& indexes);
bool isValid();
diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp
index d3237b34b..9157f35f0 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourceFolderModel.cpp
@@ -4,6 +4,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -33,6 +34,10 @@ ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObje
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &ResourceFolderModel::directoryChanged);
connect(&m_helper_thread_task, &ConcurrentTask::finished, this, [this] { m_helper_thread_task.clear(); });
+#ifndef LAUNCHER_TEST
+ // in tests the application macro doesn't work
+ m_helper_thread_task.setMaxConcurrent(APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
+#endif
}
ResourceFolderModel::~ResourceFolderModel()
@@ -41,7 +46,7 @@ ResourceFolderModel::~ResourceFolderModel()
QCoreApplication::processEvents();
}
-bool ResourceFolderModel::startWatching(const QStringList paths)
+bool ResourceFolderModel::startWatching(const QStringList& paths)
{
if (m_is_watching)
return false;
@@ -60,7 +65,7 @@ bool ResourceFolderModel::startWatching(const QStringList paths)
return m_is_watching;
}
-bool ResourceFolderModel::stopWatching(const QStringList paths)
+bool ResourceFolderModel::stopWatching(const QStringList& paths)
{
if (!m_is_watching)
return false;
@@ -456,9 +461,9 @@ bool ResourceFolderModel::setData(const QModelIndex& index, [[maybe_unused]] con
if (role == Qt::CheckStateRole) {
if (m_instance != nullptr && m_instance->isRunning()) {
auto response =
- CustomMessageBox::selectable(nullptr, "Confirm toggle",
- "If you enable/disable this resource while the game is running it may crash your game.\n"
- "Are you sure you want to do this?",
+ CustomMessageBox::selectable(nullptr, tr("Confirm toggle"),
+ tr("If you enable/disable this resource while the game is running it may crash your game.\n"
+ "Are you sure you want to do this?"),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
@@ -512,36 +517,22 @@ void ResourceFolderModel::setupHeaderAction(QAction* act, int column)
act->setText(columnNames().at(column));
}
-void ResourceFolderModel::saveHiddenColumn(int column, bool hidden)
+void ResourceFolderModel::saveColumns(QTreeView* tree)
{
- auto const setting_name = QString("UI/%1_Page/HiddenColumns").arg(id());
+ auto const setting_name = QString("UI/%1_Page/Columns").arg(id());
auto setting = (m_instance->settings()->contains(setting_name)) ? m_instance->settings()->getSetting(setting_name)
: m_instance->settings()->registerSetting(setting_name);
- auto hiddenColumns = setting->get().toStringList();
- auto name = columnNames(false).at(column);
- auto index = hiddenColumns.indexOf(name);
- if (index >= 0 && !hidden) {
- hiddenColumns.removeAt(index);
- } else if (index < 0 && hidden) {
- hiddenColumns.append(name);
- }
- setting->set(hiddenColumns);
+ setting->set(tree->header()->saveState());
}
-void ResourceFolderModel::loadHiddenColumns(QTreeView* tree)
+void ResourceFolderModel::loadColumns(QTreeView* tree)
{
- auto const setting_name = QString("UI/%1_Page/HiddenColumns").arg(id());
+ auto const setting_name = QString("UI/%1_Page/Columns").arg(id());
auto setting = (m_instance->settings()->contains(setting_name)) ? m_instance->settings()->getSetting(setting_name)
: m_instance->settings()->registerSetting(setting_name);
- auto hiddenColumns = setting->get().toStringList();
- auto col_names = columnNames(false);
- for (auto col_name : hiddenColumns) {
- auto index = col_names.indexOf(col_name);
- if (index >= 0)
- tree->setColumnHidden(index, true);
- }
+ tree->header()->restoreState(setting->get().toByteArray());
}
QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree)
@@ -566,7 +557,7 @@ QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree)
if (m_column_resize_modes.at(c) == QHeaderView::ResizeToContents)
tree->resizeColumnToContents(c);
}
- saveHiddenColumn(col, !toggled);
+ saveColumns(tree);
});
menu->addAction(act);
diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h
index 80c31e456..d764280b6 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.h
+++ b/launcher/minecraft/mod/ResourceFolderModel.h
@@ -39,14 +39,14 @@ class ResourceFolderModel : public QAbstractListModel {
* Returns whether starting to watch all the paths was successful.
* If one or more fails, it returns false.
*/
- bool startWatching(const QStringList paths);
+ bool startWatching(const QStringList& paths);
/** Stops watching the paths for changes.
*
* Returns whether stopping to watch all the paths was successful.
* If one or more fails, it returns false.
*/
- bool stopWatching(const QStringList paths);
+ bool stopWatching(const QStringList& paths);
/* Helper methods for subclasses, using a predetermined list of paths. */
virtual bool startWatching() { return startWatching({ m_dir.absolutePath() }); }
@@ -117,8 +117,8 @@ class ResourceFolderModel : public QAbstractListModel {
[[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
void setupHeaderAction(QAction* act, int column);
- void saveHiddenColumn(int column, bool hidden);
- void loadHiddenColumns(QTreeView* tree);
+ void saveColumns(QTreeView* tree);
+ void loadColumns(QTreeView* tree);
QMenu* createHeaderContextMenu(QTreeView* tree);
/** This creates a proxy model to filter / sort the model for a UI.
@@ -201,8 +201,7 @@ class ResourceFolderModel : public QAbstractListModel {
QList m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE };
QStringList m_column_names = { "Enable", "Name", "Last Modified" };
QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified") };
- QList m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Stretch,
- QHeaderView::ResizeToContents };
+ QList m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive };
QList m_columnsHideable = { false, false, true };
QDir m_dir;
@@ -330,7 +329,8 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet
// When you have a Qt build with assertions turned on, proceeding here will abort the application
if (added_set.size() > 0) {
- beginInsertRows(QModelIndex(), m_resources.size(), m_resources.size() + added_set.size() - 1);
+ beginInsertRows(QModelIndex(), static_cast(m_resources.size()),
+ static_cast(m_resources.size() + added_set.size() - 1));
for (auto& added : added_set) {
auto res = new_resources[added];
diff --git a/launcher/minecraft/mod/ResourcePack.cpp b/launcher/minecraft/mod/ResourcePack.cpp
index dab0f6d67..074534405 100644
--- a/launcher/minecraft/mod/ResourcePack.cpp
+++ b/launcher/minecraft/mod/ResourcePack.cpp
@@ -11,7 +11,7 @@
#include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
// Values taken from:
-// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
+// https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
static const QMap> s_pack_format_versions = {
{ 1, { Version("1.6.1"), Version("1.8.9") } }, { 2, { Version("1.9"), Version("1.10.2") } },
{ 3, { Version("1.11"), Version("1.12.2") } }, { 4, { Version("1.13"), Version("1.14.4") } },
@@ -50,7 +50,8 @@ void ResourcePack::setImage(QImage new_image) const
PixmapCache::instance().remove(m_pack_image_cache_key.key);
// scale the image to avoid flooding the pixmapcache
- auto pixmap = QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding));
+ auto pixmap =
+ QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
m_pack_image_cache_key.key = PixmapCache::instance().insert(pixmap);
m_pack_image_cache_key.was_ever_used = true;
@@ -68,7 +69,7 @@ QPixmap ResourcePack::image(QSize size, Qt::AspectRatioMode mode) const
if (PixmapCache::instance().find(m_pack_image_cache_key.key, &cached_image)) {
if (size.isNull())
return cached_image;
- return cached_image.scaled(size, mode);
+ return cached_image.scaled(size, mode, Qt::SmoothTransformation);
}
// No valid image we can get
diff --git a/launcher/minecraft/mod/ResourcePack.h b/launcher/minecraft/mod/ResourcePack.h
index da354bc1c..c06f3793d 100644
--- a/launcher/minecraft/mod/ResourcePack.h
+++ b/launcher/minecraft/mod/ResourcePack.h
@@ -51,7 +51,7 @@ class ResourcePack : public Resource {
mutable QMutex m_data_lock;
/* The 'version' of a resource pack, as defined in the pack.mcmeta file.
- * See https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
+ * See https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
*/
int m_pack_format = 0;
diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
index f27431576..693b8af05 100644
--- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
@@ -52,8 +52,8 @@ ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, BaseInstanc
m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified" });
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified") });
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE };
- m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents,
- QHeaderView::ResizeToContents };
+ m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive,
+ QHeaderView::Interactive };
m_columnsHideable = { false, true, false, true, true };
}
@@ -117,6 +117,11 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
}
return m_resources[row]->internal_id();
}
+ case Qt::SizeHintRole:
+ if (column == ImageColumn) {
+ return QSize(32, 32);
+ }
+ return {};
case Qt::CheckStateRole:
switch (column) {
case ActiveColumn:
diff --git a/launcher/minecraft/mod/ShaderPack.cpp b/launcher/minecraft/mod/ShaderPack.cpp
index 6a9641de2..2c094f26a 100644
--- a/launcher/minecraft/mod/ShaderPack.cpp
+++ b/launcher/minecraft/mod/ShaderPack.cpp
@@ -22,7 +22,7 @@
#include "ShaderPack.h"
-#include "minecraft/mod/tasks/LocalShaderPackParseTask.h"
+#include
void ShaderPack::setPackFormat(ShaderPackFormat new_format)
{
@@ -35,3 +35,8 @@ bool ShaderPack::valid() const
{
return m_pack_format != ShaderPackFormat::INVALID;
}
+
+bool ShaderPack::applyFilter(QRegularExpression filter) const
+{
+ return valid() && Resource::applyFilter(filter);
+}
diff --git a/launcher/minecraft/mod/ShaderPack.h b/launcher/minecraft/mod/ShaderPack.h
index ec0f9404e..d07c124be 100644
--- a/launcher/minecraft/mod/ShaderPack.h
+++ b/launcher/minecraft/mod/ShaderPack.h
@@ -54,6 +54,7 @@ class ShaderPack : public Resource {
void setPackFormat(ShaderPackFormat new_format);
bool valid() const override;
+ [[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
protected:
mutable QMutex m_data_lock;
diff --git a/launcher/minecraft/mod/ShaderPackFolderModel.h b/launcher/minecraft/mod/ShaderPackFolderModel.h
index 44ed37a47..186d02139 100644
--- a/launcher/minecraft/mod/ShaderPackFolderModel.h
+++ b/launcher/minecraft/mod/ShaderPackFolderModel.h
@@ -1,6 +1,9 @@
#pragma once
#include "ResourceFolderModel.h"
+#include "minecraft/mod/ShaderPack.h"
+#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
+#include "minecraft/mod/tasks/LocalShaderPackParseTask.h"
class ShaderPackFolderModel : public ResourceFolderModel {
Q_OBJECT
@@ -9,4 +12,14 @@ class ShaderPackFolderModel : public ResourceFolderModel {
explicit ShaderPackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) {}
virtual QString id() const override { return "shaderpacks"; }
+
+ [[nodiscard]] Task* createUpdateTask() override
+ {
+ return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared(entry); });
+ }
+
+ [[nodiscard]] Task* createParseTask(Resource& resource) override
+ {
+ return new LocalShaderPackParseTask(m_next_resolution_ticket, static_cast(resource));
+ }
};
diff --git a/launcher/minecraft/mod/TexturePack.cpp b/launcher/minecraft/mod/TexturePack.cpp
index 7d8c67137..04cc36310 100644
--- a/launcher/minecraft/mod/TexturePack.cpp
+++ b/launcher/minecraft/mod/TexturePack.cpp
@@ -44,7 +44,8 @@ void TexturePack::setImage(QImage new_image) const
PixmapCache::remove(m_pack_image_cache_key.key);
// scale the image to avoid flooding the pixmapcache
- auto pixmap = QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding));
+ auto pixmap =
+ QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
m_pack_image_cache_key.key = PixmapCache::insert(pixmap);
m_pack_image_cache_key.was_ever_used = true;
@@ -56,7 +57,7 @@ QPixmap TexturePack::image(QSize size, Qt::AspectRatioMode mode) const
if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
if (size.isNull())
return cached_image;
- return cached_image.scaled(size, mode);
+ return cached_image.scaled(size, mode, Qt::SmoothTransformation);
}
// No valid image we can get
diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp
index 5c5f2b7c1..f210501c7 100644
--- a/launcher/minecraft/mod/TexturePackFolderModel.cpp
+++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp
@@ -47,8 +47,7 @@ TexturePackFolderModel::TexturePackFolderModel(const QString& dir, BaseInstance*
m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified" });
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified") });
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE };
- m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch,
- QHeaderView::ResizeToContents };
+ m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive };
m_columnsHideable = { false, true, false, true };
}
@@ -104,6 +103,11 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
}
return {};
}
+ case Qt::SizeHintRole:
+ if (column == ImageColumn) {
+ return QSize(32, 32);
+ }
+ return {};
case Qt::CheckStateRole:
if (column == ActiveColumn) {
return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked;
diff --git a/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp
index 0a0f57bf3..efc17298e 100644
--- a/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp
+++ b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp
@@ -39,9 +39,9 @@ static Version mcVersion(BaseInstance* inst)
return static_cast(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion();
}
-static ResourceAPI::ModLoaderTypes mcLoaders(BaseInstance* inst)
+static ModPlatform::ModLoaderTypes mcLoaders(BaseInstance* inst)
{
- return static_cast(inst)->getPackProfile()->getModLoaders().value();
+ return static_cast(inst)->getPackProfile()->getSupportedModLoaders().value();
}
GetModDependenciesTask::GetModDependenciesTask(QObject* parent,
@@ -75,7 +75,7 @@ void GetModDependenciesTask::prepare()
ModPlatform::Dependency GetModDependenciesTask::getOverride(const ModPlatform::Dependency& dep,
const ModPlatform::ResourceProvider providerName)
{
- if (auto isQuilt = m_loaderType & ResourceAPI::Quilt; isQuilt || m_loaderType & ResourceAPI::Fabric) {
+ if (auto isQuilt = m_loaderType & ModPlatform::Quilt; isQuilt || m_loaderType & ModPlatform::Fabric) {
auto overide = ModPlatform::getOverrideDeps();
auto over = std::find_if(overide.cbegin(), overide.cend(), [dep, providerName, isQuilt](auto o) {
return o.provider == providerName && dep.addonId == (isQuilt ? o.fabric : o.quilt);
@@ -94,7 +94,7 @@ QList GetModDependenciesTask::getDependenciesForVersion
for (auto ver_dep : version.dependencies) {
if (ver_dep.type != ModPlatform::DependencyType::REQUIRED)
continue;
-
+ ver_dep = getOverride(ver_dep, providerName);
auto isOnlyVersion = providerName == ModPlatform::ResourceProvider::MODRINTH && ver_dep.addonId.toString().isEmpty();
if (auto dep = std::find_if(c_dependencies.begin(), c_dependencies.end(),
[&ver_dep, isOnlyVersion](const ModPlatform::Dependency& i) {
@@ -127,7 +127,7 @@ QList GetModDependenciesTask::getDependenciesForVersion
dep != m_pack_dependencies.end()) // check loaded dependencies
continue;
- c_dependencies.append(getOverride(ver_dep, providerName));
+ c_dependencies.append(ver_dep);
}
return c_dependencies;
}
@@ -137,10 +137,11 @@ Task::Ptr GetModDependenciesTask::getProjectInfoTask(std::shared_ptrpack->provider == m_flame_provider.name ? m_flame_provider : m_modrinth_provider;
auto responseInfo = std::make_shared();
auto info = provider.api->getProject(pDep->pack->addonId.toString(), responseInfo);
- QObject::connect(info.get(), &NetJob::succeeded, [responseInfo, provider, pDep] {
+ QObject::connect(info.get(), &NetJob::succeeded, [this, responseInfo, provider, pDep] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*responseInfo, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
+ removePack(pDep->pack->addonId);
qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset
<< " reason: " << parse_error.errorString();
qDebug() << *responseInfo;
@@ -151,6 +152,7 @@ Task::Ptr GetModDependenciesTask::getProjectInfoTask(std::shared_ptrloadIndexedPack(*pDep->pack, obj);
} catch (const JSONValidationError& e) {
+ removePack(pDep->pack->addonId);
qDebug() << doc;
qWarning() << "Error while reading mod info: " << e.cause();
}
@@ -191,7 +193,7 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
}
pDep->version = provider.mod->loadDependencyVersions(dep, arr);
if (!pDep->version.addonId.isValid()) {
- if (m_loaderType & ResourceAPI::Quilt) { // falback for quilt
+ if (m_loaderType & ModPlatform::Quilt) { // falback for quilt
auto overide = ModPlatform::getOverrideDeps();
auto over = std::find_if(overide.cbegin(), overide.cend(),
[dep, provider](auto o) { return o.provider == provider.name && dep.addonId == o.quilt; });
@@ -201,6 +203,7 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
return;
}
}
+ removePack(dep.addonId);
qWarning() << "Error while reading mod version empty ";
qDebug() << doc;
return;
@@ -210,11 +213,13 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
pDep->pack->versionsLoaded = true;
} catch (const JSONValidationError& e) {
+ removePack(dep.addonId);
qDebug() << doc;
qWarning() << "Error while reading mod version: " << e.cause();
return;
}
if (level == 0) {
+ removePack(dep.addonId);
qWarning() << "Dependency cycle exceeded";
return;
}
@@ -237,7 +242,7 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
return tasks;
}
-void GetModDependenciesTask::removePack(const QVariant addonId)
+void GetModDependenciesTask::removePack(const QVariant& addonId)
{
auto pred = [addonId](const std::shared_ptr& v) { return v->pack->addonId == addonId; };
#if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0)
@@ -250,3 +255,32 @@ void GetModDependenciesTask::removePack(const QVariant addonId)
++it;
#endif
}
+
+QHash GetModDependenciesTask::getRequiredBy()
+{
+ QHash rby;
+ auto fullList = m_selected + m_pack_dependencies;
+ for (auto& mod : fullList) {
+ auto addonId = mod->pack->addonId;
+ auto provider = mod->pack->provider;
+ auto version = mod->version.fileId;
+ auto req = QStringList();
+ for (auto& smod : fullList) {
+ if (provider != smod->pack->provider)
+ continue;
+ auto deps = smod->version.dependencies;
+ if (auto dep = std::find_if(deps.begin(), deps.end(),
+ [addonId, provider, version](const ModPlatform::Dependency& d) {
+ return d.type == ModPlatform::DependencyType::REQUIRED &&
+ (provider == ModPlatform::ResourceProvider::MODRINTH && d.addonId.toString().isEmpty()
+ ? version == d.version
+ : d.addonId == addonId);
+ });
+ dep != deps.end()) {
+ req.append(smod->pack->name);
+ }
+ }
+ rby[addonId.toString()] = req;
+ }
+ return rby;
+}
\ No newline at end of file
diff --git a/launcher/minecraft/mod/tasks/GetModDependenciesTask.h b/launcher/minecraft/mod/tasks/GetModDependenciesTask.h
index 50eba6afc..e88204bdc 100644
--- a/launcher/minecraft/mod/tasks/GetModDependenciesTask.h
+++ b/launcher/minecraft/mod/tasks/GetModDependenciesTask.h
@@ -62,15 +62,16 @@ class GetModDependenciesTask : public SequentialTask {
QList> selected);
auto getDependecies() const -> QList> { return m_pack_dependencies; }
+ QHash getRequiredBy();
protected slots:
- Task::Ptr prepareDependencyTask(const ModPlatform::Dependency&, const ModPlatform::ResourceProvider, int);
+ Task::Ptr prepareDependencyTask(const ModPlatform::Dependency&, ModPlatform::ResourceProvider, int);
QList getDependenciesForVersion(const ModPlatform::IndexedVersion&,
- const ModPlatform::ResourceProvider providerName);
+ ModPlatform::ResourceProvider providerName);
void prepare();
Task::Ptr getProjectInfoTask(std::shared_ptr pDep);
- ModPlatform::Dependency getOverride(const ModPlatform::Dependency&, const ModPlatform::ResourceProvider providerName);
- void removePack(const QVariant addonId);
+ ModPlatform::Dependency getOverride(const ModPlatform::Dependency&, ModPlatform::ResourceProvider providerName);
+ void removePack(const QVariant& addonId);
private:
QList> m_pack_dependencies;
@@ -80,5 +81,5 @@ class GetModDependenciesTask : public SequentialTask {
Provider m_modrinth_provider;
Version m_version;
- ResourceAPI::ModLoaderTypes m_loaderType;
+ ModPlatform::ModLoaderTypes m_loaderType;
};
diff --git a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp
index 5bb448778..82f6b9df9 100644
--- a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp
@@ -133,7 +133,7 @@ bool processZIP(DataPack& pack, ProcessingLevel level)
return true;
}
-// https://minecraft.fandom.com/wiki/Data_pack#pack.mcmeta
+// https://minecraft.wiki/w/Data_pack#pack.mcmeta
bool processMCMeta(DataPack& pack, QByteArray&& raw_data)
{
try {
diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
index 7ec00c0c6..e9e12d86a 100644
--- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
@@ -688,6 +688,7 @@ bool loadIconFile(const Mod& mod)
return png_invalid(); // icon invalid
}
}
+ return false;
}
case ResourceType::ZIPFILE: {
QuaZip zip(mod.fileinfo().filePath());
@@ -714,6 +715,7 @@ bool loadIconFile(const Mod& mod)
} else {
return png_invalid(); // could not set icon as current file.
}
+ return false;
}
case ResourceType::LITEMOD: {
return false; // can lightmods even have icons?
diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp
index 73cbf891c..26bc07637 100644
--- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp
@@ -178,7 +178,7 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level)
return true;
}
-// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
+// https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
{
try {
@@ -232,10 +232,9 @@ bool processPackPNG(const ResourcePack& pack)
} else {
return png_invalid(); // pack.png does not exists or is not a valid file.
}
+ return false; // not processed correctly; https://github.com/PrismLauncher/PrismLauncher/issues/1740
}
case ResourceType::ZIPFILE: {
- Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
-
QuaZip zip(pack.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip))
return false; // can't open zip file
@@ -259,6 +258,7 @@ bool processPackPNG(const ResourcePack& pack)
} else {
return png_invalid(); // could not set pack.mcmeta as current file.
}
+ return false; // not processed correctly; https://github.com/PrismLauncher/PrismLauncher/issues/1740
}
default:
qWarning() << "Invalid type for resource pack parse task!";
diff --git a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp
index 887a1062e..d7e61ca90 100644
--- a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp
@@ -186,10 +186,9 @@ bool processPackPNG(const TexturePack& pack)
} else {
return png_invalid(); // pack.png does not exists or is not a valid file.
}
+ return false;
}
case ResourceType::ZIPFILE: {
- Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
-
QuaZip zip(pack.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip))
return false; // can't open zip file
@@ -215,6 +214,7 @@ bool processPackPNG(const TexturePack& pack)
zip.close();
return png_invalid(); // could not set pack.mcmeta as current file.
}
+ return false;
}
default:
qWarning() << "Invalid type for resource pack parse task!";
diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
index 9f79ba098..2094df4fc 100644
--- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
+++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
@@ -122,7 +122,7 @@ void ModFolderLoadTask::getFromMetadata()
auto metadata = Metadata::get(m_index_dir, entry);
if (!metadata.isValid()) {
- return;
+ continue;
}
auto* mod = new Mod(m_mods_dir, metadata);
diff --git a/launcher/minecraft/services/SkinDelete.h b/launcher/minecraft/services/SkinDelete.h
index d5b2e63db..44e30453f 100644
--- a/launcher/minecraft/services/SkinDelete.h
+++ b/launcher/minecraft/services/SkinDelete.h
@@ -4,7 +4,7 @@
#include
#include "tasks/Task.h"
-typedef shared_qobject_ptr SkinDeletePtr;
+using SkinDeletePtr = shared_qobject_ptr;
class SkinDelete : public Task {
Q_OBJECT
diff --git a/launcher/minecraft/services/SkinUpload.h b/launcher/minecraft/services/SkinUpload.h
index 5716aa996..016367ff8 100644
--- a/launcher/minecraft/services/SkinUpload.h
+++ b/launcher/minecraft/services/SkinUpload.h
@@ -5,7 +5,7 @@
#include
#include "tasks/Task.h"
-typedef shared_qobject_ptr SkinUploadPtr;
+using SkinUploadPtr = shared_qobject_ptr;
class SkinUpload : public Task {
Q_OBJECT
diff --git a/launcher/modplatform/CheckUpdateTask.h b/launcher/modplatform/CheckUpdateTask.h
index 6d968ea48..8bd83d988 100644
--- a/launcher/modplatform/CheckUpdateTask.h
+++ b/launcher/modplatform/CheckUpdateTask.h
@@ -1,6 +1,7 @@
#pragma once
#include "minecraft/mod/Mod.h"
+#include "minecraft/mod/tasks/GetModDependenciesTask.h"
#include "modplatform/ModIndex.h"
#include "modplatform/ResourceAPI.h"
#include "tasks/Task.h"
@@ -14,7 +15,7 @@ class CheckUpdateTask : public Task {
public:
CheckUpdateTask(QList& mods,
std::list& mcVersions,
- std::optional loaders,
+ std::optional loaders,
std::shared_ptr mods_folder)
: Task(nullptr), m_mods(mods), m_game_versions(mcVersions), m_loaders(loaders), m_mods_folder(mods_folder){};
@@ -23,6 +24,7 @@ class CheckUpdateTask : public Task {
QString old_hash;
QString old_version;
QString new_version;
+ std::optional