diff --git a/.clang-tidy b/.clang-tidy
new file mode 100644
index 000000000..11ddc9cfc
--- /dev/null
+++ b/.clang-tidy
@@ -0,0 +1,4 @@
+Checks:
+ - modernize-use-using
+
+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 77c1a8802..c705ff7b0 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.3.1
+ uses: korthout/backport-action@v1.4.0
with:
# Config README: https://github.com/korthout/backport-action#backport-action
pull_description: |-
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index d044f4faf..8077ea59a 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
@@ -620,13 +598,13 @@ jobs:
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 a2853cb5a..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.")
@@ -216,6 +219,18 @@ set(Launcher_SUBREDDIT_URL "https://prismlauncher.org/reddit" CACHE STRING "URL
set(Launcher_FORCE_BUNDLED_LIBS OFF CACHE BOOL "Prevent using system libraries, if they are available as submodules")
set(Launcher_QT_VERSION_MAJOR "6" CACHE STRING "Major Qt version to build against")
+# Native libraries
+if(UNIX AND APPLE)
+ set(Launcher_GLFW_LIBRARY_NAME "libglfw.dylib" CACHE STRING "Name of native glfw library")
+ set(Launcher_OPENAL_LIBRARY_NAME "libopenal.dylib" CACHE STRING "Name of native openal library")
+elseif(UNIX)
+ set(Launcher_GLFW_LIBRARY_NAME "libglfw.so" CACHE STRING "Name of native glfw library")
+ set(Launcher_OPENAL_LIBRARY_NAME "libopenal.so" CACHE STRING "Name of native openal library")
+elseif(WIN32)
+ set(Launcher_GLFW_LIBRARY_NAME "glfw.dll" CACHE STRING "Name of native glfw library")
+ set(Launcher_OPENAL_LIBRARY_NAME "OpenAL.dll" CACHE STRING "Name of native openal library")
+endif()
+
# API Keys
# NOTE: These API keys are here for convenience. If you rebrand this software or intend to break the terms of service
# of these platforms, please change these API keys beforehand.
@@ -233,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)
@@ -327,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..61bf4c20b 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,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 +50,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 d7662a7a4..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())
{
@@ -110,6 +118,9 @@ Config::Config()
FLAME_API_KEY = "@Launcher_CURSEFORGE_API_KEY@";
META_URL = "@Launcher_META_URL@";
+ GLFW_LIBRARY_NAME = "@Launcher_GLFW_LIBRARY_NAME@";
+ OPENAL_LIBRARY_NAME = "@Launcher_OPENAL_LIBRARY_NAME@";
+
BUG_TRACKER_URL = "@Launcher_BUG_TRACKER_URL@";
TRANSLATIONS_URL = "@Launcher_TRANSLATIONS_URL@";
MATRIX_URL = "@Launcher_MATRIX_URL@";
@@ -133,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 387f494f3..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;
@@ -134,6 +152,9 @@ class Config {
*/
QString META_URL;
+ QString GLFW_LIBRARY_NAME;
+ QString OPENAL_LIBRARY_NAME;
+
QString BUG_TRACKER_URL;
QString TRANSLATIONS_URL;
QString MATRIX_URL;
@@ -172,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..c9962c443 100644
--- a/cmake/CompilerWarnings.cmake
+++ b/cmake/CompilerWarnings.cmake
@@ -75,7 +75,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 +89,10 @@ 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
)
endif()
diff --git a/cmake/MacOSXBundleInfo.plist.in b/cmake/MacOSXBundleInfo.plist.in
index 400e482fe..d36ac3e8f 100644
--- a/cmake/MacOSXBundleInfo.plist.in
+++ b/cmake/MacOSXBundleInfo.plist.in
@@ -67,5 +67,16 @@
Alternate
+ CFBundleURLTypes
+
+
+ CFBundleURLName
+ Curseforge
+ CFBundleURLSchemes
+
+ curseforge
+
+
+
diff --git a/flake.lock b/flake.lock
index bfe151758..d71403358 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": 1696343447,
+ "narHash": "sha256-B2xAZKLkkeRFG5XcHHSXXcP7To9Xzr59KXeZiRf4vdQ=",
"owner": "hercules-ci",
"repo": "flake-parts",
- "rev": "59cf3f1447cfc75087e7273b04b31e689a8599fb",
+ "rev": "c9afaba3dfa4085dbd2ccb38dfade5141e33d9d4",
"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": 1691853136,
- "narHash": "sha256-wTzDsRV4HN8A2Sl0SVQY0q8ILs90CD43Ha//7gNZE+E=",
+ "lastModified": 1697886341,
+ "narHash": "sha256-AdE67xPty9M9wn36nPVp6aDntIdigrs7UbyaGv1VAaM=",
"owner": "nixos",
"repo": "nixpkgs",
- "rev": "f0451844bbdf545f696f029d1448de4906c7f753",
+ "rev": "44881e03af1c730cbb1d72a4d41274a2c957813a",
"type": "github"
},
"original": {
@@ -108,11 +123,11 @@
"nixpkgs-lib": {
"locked": {
"dir": "lib",
- "lastModified": 1690881714,
- "narHash": "sha256-h/nXluEqdiQHs1oSgkOOWF+j8gcJMWhwnZ9PFabN6q0=",
+ "lastModified": 1696019113,
+ "narHash": "sha256-X3+DKYWJm93DRSdC5M6K5hLqzSya9BjibtBsuARoPco=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "9e1960bc196baf6881340d53dccb203a951745a2",
+ "rev": "f5892ddac112a1e9b3612c39af1b72987ee5783a",
"type": "github"
},
"original": {
@@ -138,11 +153,11 @@
]
},
"locked": {
- "lastModified": 1691747570,
- "narHash": "sha256-J3fnIwJtHVQ0tK2JMBv4oAmII+1mCdXdpeCxtIsrL2A=",
+ "lastModified": 1697746376,
+ "narHash": "sha256-gu77VkgdfaHgNCVufeb6WP9oqFLjwK4jHcoPZmBVF3E=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
- "rev": "c5ac3aa3324bd8aebe8622a3fc92eeb3975d317a",
+ "rev": "8cc349bfd082da8782b989cad2158c9ad5bd70fd",
"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/launcher/Application.cpp b/launcher/Application.cpp
index a13935101..661c6c5be 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -9,7 +9,6 @@
* Copyright (C) 2022 Tayou
* Copyright (C) 2023 TheKodeToad
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
- * Copyright (C) 2023 seth
*
* 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
@@ -59,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"
@@ -123,6 +123,7 @@
#include
#include
+#include
#include
#ifdef Q_OS_LINUX
@@ -131,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"
@@ -165,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
@@ -194,8 +227,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
{ { "s", "server" }, "Join the specified server on launch (only valid in combination with --launch)", "address" },
{ { "a", "profile" }, "Use the account specified by its profile name (only valid in combination with --launch)", "profile" },
{ "alive", "Write a small '" + liveCheckFile + "' file after the launcher starts" },
- { { "I", "import" }, "Import instance from specified zip (local path or URL)", "file" },
+ { { "I", "import" }, "Import instance or resource from specified local path or URL", "url" },
{ "show", "Opens the window for the specified instance (by instance ID)", "show" } });
+ // Has to be positional for some OS to handle that properly
+ parser.addPositionalArgument("URL", "Import the resource(s) at the given URL(s) (same as -I / --import)", "[URL...]");
+
parser.addHelpOption();
parser.addVersionOption();
@@ -208,13 +244,13 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_instanceIdToShowWindowOf = parser.value("show");
- for (auto zip_path : parser.values("import")) {
- m_zipsToImport.append(QUrl::fromLocalFile(QFileInfo(zip_path).absoluteFilePath()));
+ for (auto url : parser.values("import")) {
+ m_urlsToImport.append(normalizeImportUrl(url));
}
// treat unspecified positional arguments as import urls
- for (auto zip_path : parser.positionalArguments()) {
- m_zipsToImport.append(QUrl::fromLocalFile(QFileInfo(zip_path).absoluteFilePath()));
+ for (auto url : parser.positionalArguments()) {
+ m_urlsToImport.append(normalizeImportUrl(url));
}
// error if --launch is missing with --server or --profile
@@ -294,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.
@@ -313,11 +350,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
activate.command = "activate";
m_peerInstance->sendMessage(activate.serialize(), timeout);
- if (!m_zipsToImport.isEmpty()) {
- for (auto zip_url : m_zipsToImport) {
+ if (!m_urlsToImport.isEmpty()) {
+ for (auto url : m_urlsToImport) {
ApplicationMessage import;
import.command = "import";
- import.args.insert("path", zip_url.toString());
+ import.args.insert("url", url.toString());
m_peerInstance->sendMessage(import.serialize(), timeout);
}
}
@@ -448,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();
@@ -501,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
@@ -577,12 +622,14 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("IgnoreJavaCompatibility", false);
m_settings->registerSetting("IgnoreJavaWizard", false);
- // Mod loader settings
- m_settings->registerSetting("DisableQuiltBeacon", false);
+ // Legacy settings
+ m_settings->registerSetting("OnlineFixes", false);
// Native library workarounds
m_settings->registerSetting("UseNativeOpenAL", false);
+ m_settings->registerSetting("CustomOpenALPath", "");
m_settings->registerSetting("UseNativeGLFW", false);
+ m_settings->registerSetting("CustomGLFWPath", "");
// Peformance related options
m_settings->registerSetting("EnableFeralGamemode", false);
@@ -593,6 +640,7 @@ 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);
@@ -672,6 +720,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", "");
@@ -697,6 +747,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();
@@ -733,15 +784,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");
@@ -842,6 +884,109 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
updateCapabilities();
+ 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;
}
@@ -910,6 +1055,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
@@ -961,7 +1126,7 @@ void Application::performMainStartupAction()
qDebug() << " Launching with account" << m_profileToUse;
}
- launch(inst, true, false, nullptr, serverToJoin, accountToUse);
+ launch(inst, true, false, serverToJoin, accountToUse);
return;
}
}
@@ -978,9 +1143,23 @@ void Application::performMainStartupAction()
showMainWindow(false);
qDebug() << "<> Main window shown.";
}
- if (!m_zipsToImport.isEmpty()) {
- qDebug() << "<> Importing from zip:" << m_zipsToImport;
- m_mainWindow->processURLs(m_zipsToImport);
+
+ // 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);
}
}
@@ -1022,12 +1201,12 @@ void Application::messageReceived(const QByteArray& message)
if (command == "activate") {
showMainWindow();
} else if (command == "import") {
- QString path = received.args["path"];
- if (path.isEmpty()) {
+ QString url = received.args["url"];
+ if (url.isEmpty()) {
qWarning() << "Received" << command << "message without a zip path/URL.";
return;
}
- m_mainWindow->processURLs({ QUrl::fromLocalFile(QFileInfo(path).absoluteFilePath()) });
+ m_mainWindow->processURLs({ normalizeImportUrl(url) });
} else if (command == "launch") {
QString id = received.args["id"];
QString server = received.args["server"];
@@ -1060,7 +1239,7 @@ void Application::messageReceived(const QByteArray& message)
}
}
- launch(instance, true, false, nullptr, serverObject, accountObject);
+ launch(instance, true, false, serverObject, accountObject);
} else {
qWarning() << "Received invalid message" << message;
}
@@ -1101,7 +1280,6 @@ bool Application::openJsonEditor(const QString& filename)
bool Application::launch(InstancePtr instance,
bool online,
bool demo,
- BaseProfilerFactory* profiler,
MinecraftServerTargetPtr serverToJoin,
MinecraftAccountPtr accountToUse)
{
@@ -1109,7 +1287,7 @@ bool Application::launch(InstancePtr instance,
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
} else if (instance->canLaunch()) {
auto& extras = m_instanceExtras[instance->id()];
- auto& window = extras.window;
+ auto window = extras.window;
if (window) {
if (!window->saveAll()) {
return false;
@@ -1120,7 +1298,7 @@ bool Application::launch(InstancePtr instance,
controller->setInstance(instance);
controller->setOnline(online);
controller->setDemo(demo);
- controller->setProfiler(profiler);
+ controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get());
controller->setServerToJoin(serverToJoin);
controller->setAccountToUse(accountToUse);
if (window) {
@@ -1412,6 +1590,15 @@ void Application::updateCapabilities()
#endif
}
+void Application::detectLibraries()
+{
+#ifdef Q_OS_LINUX
+ m_detectedGLFWPath = MangoHud::findLibrary(BuildConfig.GLFW_LIBRARY_NAME);
+ m_detectedOpenALPath = MangoHud::findLibrary(BuildConfig.OPENAL_LIBRARY_NAME);
+ qDebug() << "Detected native libraries:" << m_detectedGLFWPath << m_detectedOpenALPath;
+#endif
+}
+
QString Application::getJarPath(QString jarFile)
{
QStringList potentialPaths = {
@@ -1590,3 +1777,13 @@ void Application::triggerUpdateCheck()
qDebug() << "Updater not available.";
}
}
+
+QUrl Application::normalizeImportUrl(QString const& url)
+{
+ auto local_file = QFileInfo(url);
+ if (local_file.exists()) {
+ return QUrl::fromLocalFile(local_file.absoluteFilePath());
+ } else {
+ return QUrl::fromUserInput(url);
+ }
+}
diff --git a/launcher/Application.h b/launcher/Application.h
index 6bc332749..7669e08ec 100644
--- a/launcher/Application.h
+++ b/launcher/Application.h
@@ -142,6 +142,8 @@ class Application : public QApplication {
void updateCapabilities();
+ void detectLibraries();
+
/*!
* Finds and returns the full path to a jar file.
* Returns a null-string if it could not be found.
@@ -157,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; }
@@ -177,6 +182,11 @@ class Application : public QApplication {
int suitableMaxMem();
+ bool updaterEnabled();
+ QString updaterBinaryName();
+
+ QUrl normalizeImportUrl(QString const& url);
+
signals:
void updateAllowedChanged(bool status);
void globalSettingsAboutToOpen();
@@ -191,7 +201,6 @@ class Application : public QApplication {
bool launch(InstancePtr instance,
bool online = true,
bool demo = false,
- BaseProfilerFactory* profiler = nullptr,
MinecraftServerTargetPtr serverToJoin = nullptr,
MinecraftAccountPtr accountToUse = nullptr);
bool kill(InstancePtr instance);
@@ -241,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;
@@ -275,11 +285,13 @@ class Application : public QApplication {
SetupWizard* m_setupWizard = nullptr;
public:
+ QString m_detectedGLFWPath;
+ QString m_detectedOpenALPath;
QString m_instanceIdToLaunch;
QString m_serverToJoin;
QString m_profileToUse;
bool m_liveCheck = false;
- QList m_zipsToImport;
+ QList m_urlsToImport;
QString m_instanceIdToShowWindowOf;
std::unique_ptr logFile;
};
diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp
index 70c6da6b3..33dc3f741 100644
--- a/launcher/BaseInstance.cpp
+++ b/launcher/BaseInstance.cpp
@@ -3,6 +3,7 @@
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu
* Copyright (c) 2022 Jamie Mansfield
+ * 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
@@ -100,6 +101,8 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
m_settings->registerSetting("ManagedPackName", "");
m_settings->registerSetting("ManagedPackVersionID", "");
m_settings->registerSetting("ManagedPackVersionName", "");
+
+ m_settings->registerSetting("Profiler", "");
}
QString BaseInstance::getPreLaunchCommand()
@@ -385,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 38dc7c4aa..f4ed9113c 100644
--- a/launcher/BaseInstance.h
+++ b/launcher/BaseInstance.h
@@ -3,6 +3,7 @@
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu
* Copyright (c) 2022 Jamie Mansfield
+ * 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
@@ -38,6 +39,7 @@
#include
#include
+#include
#include
#include
#include
@@ -62,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.
@@ -246,6 +248,8 @@ class BaseInstance : public QObject, public std::enable_shared_from_this RoleList;
+ using RoleList = QList;
explicit BaseVersionList(QObject* parent = 0);
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index d220cf047..48ca8f085 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
@@ -836,6 +891,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
@@ -918,6 +975,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
@@ -946,8 +1006,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
@@ -986,6 +1044,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
@@ -1046,6 +1106,15 @@ SET(LAUNCHER_SOURCES
JavaDownloader.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
@@ -1084,10 +1153,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
@@ -1108,7 +1179,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
@@ -1116,6 +1186,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
@@ -1132,6 +1210,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})
@@ -1141,12 +1225,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
@@ -1228,7 +1316,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}"
@@ -1247,7 +1373,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/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..c7d5f85fa 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) {
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index bfed576c1..861cfa267 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 3f948a336..bc139e4f1 100644
--- a/launcher/InstanceImportTask.cpp
+++ b/launcher/InstanceImportTask.cpp
@@ -50,6 +50,7 @@
#include "modplatform/technic/TechnicPackProcessor.h"
#include "settings/INISettingsObject.h"
+#include "tasks/Task.h"
#include "net/ApiDownload.h"
@@ -90,25 +91,27 @@ void InstanceImportTask::executeTask()
setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString()));
m_downloadRequired = true;
- const QString path(m_sourceUrl.host() + '/' + m_sourceUrl.path());
-
- auto entry = APPLICATION->metacache()->resolveEntry("general", path);
- entry->setStale(true);
- m_archivePath = entry->getFullPath();
-
- m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network()));
- m_filesNetJob->addNetAction(Net::ApiDownload::makeCached(m_sourceUrl, entry));
-
- connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
- connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
- connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propagateStepProgress);
- connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed);
- connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted);
-
- m_filesNetJob->start();
+ downloadFromUrl();
}
}
+void InstanceImportTask::downloadFromUrl()
+{
+ const QString path = m_sourceUrl.host() + '/' + m_sourceUrl.path();
+ auto entry = APPLICATION->metacache()->resolveEntry("general", path);
+ entry->setStale(true);
+ m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network()));
+ m_filesNetJob->addNetAction(Net::ApiDownload::makeCached(m_sourceUrl, entry));
+ m_archivePath = entry->getFullPath();
+
+ connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
+ connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
+ connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propagateStepProgress);
+ connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed);
+ connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted);
+ m_filesNetJob->start();
+}
+
void InstanceImportTask::downloadSucceeded()
{
processZipPack();
diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h
index 4459e440c..ca3d30ad6 100644
--- a/launcher/InstanceImportTask.h
+++ b/launcher/InstanceImportTask.h
@@ -101,4 +101,5 @@ class InstanceImportTask : public InstanceTask {
// FIXME: nuke
QWidget* m_parent;
+ void downloadFromUrl();
};
diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp
index 856eee816..756ff93dd 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.";
}
@@ -925,7 +980,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..6b0bcd810 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();
@@ -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 380489e06..9fb385777 100644
--- a/launcher/LaunchController.cpp
+++ b/launcher/LaunchController.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
@@ -87,8 +88,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();
@@ -105,7 +106,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);
@@ -361,22 +362,21 @@ void LaunchController::readyForLaunch()
QString error;
if (!m_profiler->check(&error)) {
m_launcher->abort();
- QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Couldn't start profiler: %1").arg(error));
emitFailed("Profiler startup failed!");
+ QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Profiler check for %1 failed: %2").arg(m_profiler->name(), error));
return;
}
BaseProfiler* profilerInstance = m_profiler->createProfiler(m_launcher->instance(), this);
connect(profilerInstance, &BaseProfiler::readyToLaunch, [this](const QString& message) {
- QMessageBox msg;
+ QMessageBox msg(m_parentWidget);
msg.setText(tr("The game launch is delayed until you press the "
"button. This is the right time to setup the profiler, as the "
"profiler server is running now.\n\n%1")
.arg(message));
msg.setWindowTitle(tr("Waiting."));
msg.setIcon(QMessageBox::Information);
- msg.addButton(tr("Launch"), QMessageBox::AcceptRole);
- msg.setModal(true);
+ msg.addButton(tr("&Launch"), QMessageBox::AcceptRole);
msg.exec();
m_launcher->proceed();
});
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..3bfe16ab5 100644
--- a/launcher/MMCZip.cpp
+++ b/launcher/MMCZip.cpp
@@ -42,7 +42,11 @@
#include
#include
+#include
+
+#if defined(LAUNCHER_APPLICATION)
#include
+#endif
namespace MMCZip {
// ours
@@ -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 {
@@ -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/MangoHud.cpp b/launcher/MangoHud.cpp
index 5758da3aa..ab79f418b 100644
--- a/launcher/MangoHud.cpp
+++ b/launcher/MangoHud.cpp
@@ -16,6 +16,7 @@
* along with this program. If not, see .
*/
+#include
#include
#include
#include
@@ -26,6 +27,15 @@
#include "Json.h"
#include "MangoHud.h"
+#ifdef __GLIBC__
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#define UNDEF_GNU_SOURCE
+#endif
+#include
+#include
+#endif
+
namespace MangoHud {
QString getLibraryString()
@@ -106,4 +116,37 @@ QString getLibraryString()
return QString();
}
+
+QString findLibrary(QString libName)
+{
+#ifdef __GLIBC__
+ const char* library = libName.toLocal8Bit().constData();
+
+ void* handle = dlopen(library, RTLD_NOW);
+ if (!handle) {
+ qCritical() << "dlopen() failed:" << dlerror();
+ return {};
+ }
+
+ char path[PATH_MAX];
+ if (dlinfo(handle, RTLD_DI_ORIGIN, path) == -1) {
+ qCritical() << "dlinfo() failed:" << dlerror();
+ dlclose(handle);
+ return {};
+ }
+
+ auto fullPath = FS::PathCombine(QString(path), libName);
+
+ dlclose(handle);
+ return fullPath;
+#else
+ qWarning() << "MangoHud::findLibrary is not implemented on this platform";
+ return {};
+#endif
+}
} // namespace MangoHud
+
+#ifdef UNDEF_GNU_SOURCE
+#undef _GNU_SOURCE
+#undef UNDEF_GNU_SOURCE
+#endif
diff --git a/launcher/MangoHud.h b/launcher/MangoHud.h
index 7b7c2849c..5361999b4 100644
--- a/launcher/MangoHud.h
+++ b/launcher/MangoHud.h
@@ -24,4 +24,6 @@
namespace MangoHud {
QString getLibraryString();
-}
+
+QString findLibrary(QString libName);
+} // namespace MangoHud
diff --git a/launcher/NullInstance.h b/launcher/NullInstance.h
index ff658c431..c79600e7d 100644
--- a/launcher/NullInstance.h
+++ b/launcher/NullInstance.h
@@ -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
@@ -62,6 +63,7 @@ class NullInstance : public BaseInstance {
bool canExport() const override { return false; }
bool canEdit() const override { return false; }
bool canLaunch() const override { return false; }
+ void populateLaunchMenu(QMenu* menu) override {}
QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override
{
QStringList out;
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..57c711d58 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);
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/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..cca1ed6d4 100644
--- a/launcher/java/JavaUtils.cpp
+++ b/launcher/java/JavaUtils.cpp
@@ -403,6 +403,14 @@ 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 = addJavasFromEnv(javas);
javas.removeDuplicates();
return javas;
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/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 305bff67b..9d177f318 100644
--- a/launcher/minecraft/MinecraftInstance.cpp
+++ b/launcher/minecraft/MinecraftInstance.cpp
@@ -3,8 +3,7 @@
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu
* Copyright (C) 2022 Jamie Mansfield
- * Copyright (C) 2022 TheKodeToad
- * Copyright (c) 2023 seth
+ * 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
@@ -88,6 +87,10 @@
#include "minecraft/gameoptions/GameOptions.h"
#include "minecraft/update/FoldersTask.h"
+#include "tools/BaseProfiler.h"
+
+#include
+
#ifdef Q_OS_LINUX
#include "MangoHud.h"
#endif
@@ -166,7 +169,9 @@ void MinecraftInstance::loadSpecificSettings()
// Native library workarounds
auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false);
m_settings->registerOverride(global_settings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride);
+ m_settings->registerOverride(global_settings->getSetting("CustomOpenALPath"), nativeLibraryWorkaroundsOverride);
m_settings->registerOverride(global_settings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride);
+ m_settings->registerOverride(global_settings->getSetting("CustomGLFWPath"), nativeLibraryWorkaroundsOverride);
// Peformance related options
auto performanceOverride = m_settings->registerSetting("OverridePerformance", false);
@@ -179,9 +184,12 @@ void MinecraftInstance::loadSpecificSettings()
m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), miscellaneousOverride);
- // Mod loader specific options
- auto modLoaderSettings = m_settings->registerSetting("OverrideModLoaderSettings", false);
- m_settings->registerOverride(global_settings->getSetting("DisableQuiltBeacon"), modLoaderSettings);
+ // 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");
}
@@ -194,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);
@@ -229,6 +243,50 @@ QSet MinecraftInstance::traits() const
return profile->getTraits();
}
+// FIXME: move UI code out of MinecraftInstance
+void MinecraftInstance::populateLaunchMenu(QMenu* menu)
+{
+ QAction* normalLaunch = menu->addAction(tr("&Launch"));
+ normalLaunch->setShortcut(QKeySequence::Open);
+ QAction* normalLaunchOffline = menu->addAction(tr("Launch &Offline"));
+ normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O")));
+ QAction* normalLaunchDemo = menu->addAction(tr("Launch &Demo"));
+ normalLaunchDemo->setShortcut(QKeySequence(tr("Ctrl+Alt+O")));
+
+ normalLaunchDemo->setEnabled(supportsDemo());
+
+ connect(normalLaunch, &QAction::triggered, [this] { APPLICATION->launch(shared_from_this()); });
+ connect(normalLaunchOffline, &QAction::triggered, [this] { APPLICATION->launch(shared_from_this(), false, false); });
+ connect(normalLaunchDemo, &QAction::triggered, [this] { APPLICATION->launch(shared_from_this(), false, true); });
+
+ QString profilersTitle = tr("Profilers");
+ menu->addSeparator()->setText(profilersTitle);
+
+ auto profilers = new QActionGroup(menu);
+ profilers->setExclusive(true);
+ connect(profilers, &QActionGroup::triggered, [this](QAction* action) {
+ settings()->set("Profiler", action->data());
+ emit profilerChanged();
+ });
+
+ QAction* noProfilerAction = menu->addAction(tr("&No Profiler"));
+ noProfilerAction->setData("");
+ noProfilerAction->setCheckable(true);
+ noProfilerAction->setChecked(true);
+ profilers->addAction(noProfilerAction);
+
+ for (auto profiler = APPLICATION->profilers().begin(); profiler != APPLICATION->profilers().end(); profiler++) {
+ QAction* profilerAction = menu->addAction(profiler.value()->name());
+ profilers->addAction(profilerAction);
+ profilerAction->setData(profiler.key());
+ profilerAction->setCheckable(true);
+ profilerAction->setChecked(settings()->get("Profiler").toString() == profiler.key());
+
+ QString error;
+ profilerAction->setEnabled(profiler.value()->check(&error));
+ }
+}
+
QString MinecraftInstance::gameRoot() const
{
QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft"));
@@ -260,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");
}
@@ -385,10 +443,31 @@ QStringList MinecraftInstance::extraArguments()
}
{
- const auto loaders = version->getModLoaders();
- if (loaders.has_value() && loaders.value() & ResourceAPI::Quilt && settings()->get("DisableQuiltBeacon").toBool())
- list.append("-Dloader.disable_beacon=true");
+ QString openALPath;
+ QString glfwPath;
+
+ if (settings()->get("UseNativeOpenAL").toBool()) {
+ openALPath = APPLICATION->m_detectedOpenALPath;
+ auto customPath = settings()->get("CustomOpenALPath").toString();
+ if (!customPath.isEmpty())
+ openALPath = customPath;
+ }
+ if (settings()->get("UseNativeGLFW").toBool()) {
+ glfwPath = APPLICATION->m_detectedGLFWPath;
+ auto customPath = settings()->get("CustomGLFWPath").toString();
+ if (!customPath.isEmpty())
+ glfwPath = customPath;
+ }
+
+ QFileInfo openALInfo(openALPath);
+ QFileInfo glfwInfo(glfwPath);
+
+ if (!openALPath.isEmpty() && openALInfo.exists())
+ list.append("-Dorg.lwjgl.openal.libname=" + openALInfo.absoluteFilePath());
+ if (!glfwPath.isEmpty() && glfwInfo.exists())
+ list.append("-Dorg.lwjgl.glfw.libname=" + glfwInfo.absoluteFilePath());
}
+
return list;
}
@@ -441,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;
@@ -514,6 +601,23 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
}
#endif
+ // 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;
}
@@ -626,7 +730,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());
@@ -634,6 +738,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";
@@ -644,6 +761,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;
@@ -784,9 +904,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;
@@ -868,13 +985,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 c331cc6f5..b1f305201 100644
--- a/launcher/minecraft/MinecraftInstance.h
+++ b/launcher/minecraft/MinecraftInstance.h
@@ -2,7 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu
- * Copyright (C) 2022 TheKodeToad
+ * 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
@@ -70,6 +70,8 @@ class MinecraftInstance : public BaseInstance {
bool canExport() const override { return true; }
+ void populateLaunchMenu(QMenu* menu) override;
+
////// Directories and files //////
QString jarModsDir() const;
QString resourcePacksDir() const;
@@ -127,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;
@@ -171,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/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp
index cf8270cd2..9e706ae0a 100644
--- a/launcher/minecraft/PackProfile.cpp
+++ b/launcher/minecraft/PackProfile.cpp
@@ -58,14 +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.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()
{
@@ -989,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();
@@ -1008,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..e72b6ebfe 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;
@@ -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.h b/launcher/minecraft/ProfileUtils.h
index 98a7ff739..edabe0bf0 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);
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..b141909a9 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)
{
@@ -320,17 +320,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 +355,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 +366,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 +400,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 +408,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 +458,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 +471,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..051d8f958 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,
@@ -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..545d06aed 100644
--- a/launcher/minecraft/auth/MinecraftAccount.cpp
+++ b/launcher/minecraft/auth/MinecraftAccount.cpp
@@ -51,7 +51,6 @@
#include
#include "flows/MSA.h"
-#include "flows/Mojang.h"
#include "flows/Offline.h"
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent)
@@ -59,15 +58,6 @@ 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 +67,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 +119,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 +151,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);
@@ -296,13 +263,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..c4e9756a9 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,8 +118,6 @@ 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; }
@@ -142,12 +129,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/ExtractNatives.cpp b/launcher/minecraft/launch/ExtractNatives.cpp
index cebeaedd4..8f3cac4d1 100644
--- a/launcher/minecraft/launch/ExtractNatives.cpp
+++ b/launcher/minecraft/launch/ExtractNatives.cpp
@@ -39,7 +39,7 @@ static QString replaceSuffix(QString target, const QString& suffix, const QStrin
return target + replacement;
}
-static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibHack, bool nativeOpenAL, bool nativeGLFW)
+static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibHack)
{
QuaZip zip(source);
if (!zip.open(QuaZip::mdUnzip)) {
@@ -52,12 +52,6 @@ static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibH
do {
QString name = zip.getCurrentFileName();
auto lowercase = name.toLower();
- if (nativeGLFW && name.contains("glfw")) {
- continue;
- }
- if (nativeOpenAL && name.contains("openal")) {
- continue;
- }
if (applyJnilibHack) {
name = replaceSuffix(name, ".jnilib", ".dylib");
}
@@ -83,14 +77,12 @@ void ExtractNatives::executeTask()
return;
}
auto settings = minecraftInstance->settings();
- bool nativeOpenAL = settings->get("UseNativeOpenAL").toBool();
- bool nativeGLFW = settings->get("UseNativeGLFW").toBool();
auto outputPath = minecraftInstance->getNativePath();
auto javaVersion = minecraftInstance->getJavaVersion();
bool jniHackEnabled = javaVersion.major() >= 8;
for (const auto& source : toExtract) {
- if (!unzipNatives(source, outputPath, jniHackEnabled, nativeOpenAL, nativeGLFW)) {
+ if (!unzipNatives(source, outputPath, jniHackEnabled)) {
const char* reason = QT_TR_NOOP("Couldn't extract native jar '%1' to destination '%2'");
emit logLine(QString(reason).arg(source, outputPath), MessageLevel::Fatal);
emitFailed(tr(reason).arg(source, outputPath));
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..a5f1489dd 100644
--- a/launcher/minecraft/mod/ModFolderModel.cpp
+++ b/launcher/minecraft/mod/ModFolderModel.cpp
@@ -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)
@@ -228,6 +233,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 +333,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..0503b660b 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourceFolderModel.cpp
@@ -33,6 +33,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()
diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h
index 80c31e456..60b8879c0 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.h
+++ b/launcher/minecraft/mod/ResourceFolderModel.h
@@ -330,7 +330,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/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/tasks/GetModDependenciesTask.cpp b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp
index 0a0f57bf3..df8c690af 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;
}
@@ -191,7 +191,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 +201,7 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
return;
}
}
+ removePack(dep.addonId);
qWarning() << "Error while reading mod version empty ";
qDebug() << doc;
return;
@@ -250,3 +251,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..2580f8077 100644
--- a/launcher/minecraft/mod/tasks/GetModDependenciesTask.h
+++ b/launcher/minecraft/mod/tasks/GetModDependenciesTask.h
@@ -62,6 +62,7 @@ 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);
@@ -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/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp
index 73cbf891c..7b9f4f594 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 {
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 new_version_type;
QString changelog;
ModPlatform::ResourceProvider provider;
shared_qobject_ptr download;
@@ -32,14 +34,23 @@ class CheckUpdateTask : public Task {
QString old_h,
QString old_v,
QString new_v,
+ std::optional new_v_type,
QString changelog,
ModPlatform::ResourceProvider p,
shared_qobject_ptr t)
- : name(name), old_hash(old_h), old_version(old_v), new_version(new_v), changelog(changelog), provider(p), download(t)
+ : name(name)
+ , old_hash(old_h)
+ , old_version(old_v)
+ , new_version(new_v)
+ , new_version_type(new_v_type)
+ , changelog(changelog)
+ , provider(p)
+ , download(t)
{}
};
auto getUpdatable() -> std::vector&& { return std::move(m_updatable); }
+ auto getDependencies() -> QList>&& { return std::move(m_deps); }
public slots:
bool abort() override = 0;
@@ -53,8 +64,9 @@ class CheckUpdateTask : public Task {
protected:
QList& m_mods;
std::list& m_game_versions;
- std::optional m_loaders;
+ std::optional m_loaders;
std::shared_ptr m_mods_folder;
std::vector m_updatable;
+ QList> m_deps;
};
diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp
index c3eadd06d..a9ad22581 100644
--- a/launcher/modplatform/EnsureMetadataTask.cpp
+++ b/launcher/modplatform/EnsureMetadataTask.cpp
@@ -3,6 +3,7 @@
#include
#include
+#include "Application.h"
#include "Json.h"
#include "minecraft/mod/Mod.h"
@@ -33,7 +34,7 @@ EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::Resource
EnsureMetadataTask::EnsureMetadataTask(QList& mods, QDir dir, ModPlatform::ResourceProvider prov)
: Task(nullptr), m_index_dir(dir), m_provider(prov), m_current_task(nullptr)
{
- m_hashing_task.reset(new ConcurrentTask(this, "MakeHashesTask", 10));
+ m_hashing_task.reset(new ConcurrentTask(this, "MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
for (auto* mod : mods) {
auto hash_task = createNewHash(mod);
if (!hash_task)
diff --git a/launcher/modplatform/ModIndex.cpp b/launcher/modplatform/ModIndex.cpp
index 350a9f10b..fc79dff15 100644
--- a/launcher/modplatform/ModIndex.cpp
+++ b/launcher/modplatform/ModIndex.cpp
@@ -24,6 +24,40 @@
namespace ModPlatform {
+static const QMap s_indexed_version_type_names = {
+ { "release", IndexedVersionType::VersionType::Release },
+ { "beta", IndexedVersionType::VersionType::Beta },
+ { "alpha", IndexedVersionType::VersionType::Alpha }
+};
+
+IndexedVersionType::IndexedVersionType(const QString& type) : IndexedVersionType(enumFromString(type)) {}
+
+IndexedVersionType::IndexedVersionType(const IndexedVersionType::VersionType& type)
+{
+ m_type = type;
+}
+
+IndexedVersionType::IndexedVersionType(const IndexedVersionType& other)
+{
+ m_type = other.m_type;
+}
+
+IndexedVersionType& IndexedVersionType::operator=(const IndexedVersionType& other)
+{
+ m_type = other.m_type;
+ return *this;
+}
+
+const QString IndexedVersionType::toString(const IndexedVersionType::VersionType& type)
+{
+ return s_indexed_version_type_names.key(type, "unknown");
+}
+
+IndexedVersionType::VersionType IndexedVersionType::enumFromString(const QString& type)
+{
+ return s_indexed_version_type_names.value(type, IndexedVersionType::VersionType::Unknown);
+}
+
auto ProviderCapabilities::name(ResourceProvider p) -> const char*
{
switch (p) {
@@ -83,4 +117,25 @@ QString getMetaURL(ResourceProvider provider, QVariant projectID)
projectID.toString();
}
+auto getModLoaderString(ModLoaderType type) -> const QString
+{
+ switch (type) {
+ case NeoForge:
+ return "neoforge";
+ case Forge:
+ return "forge";
+ case Cauldron:
+ return "cauldron";
+ case LiteLoader:
+ return "liteloader";
+ case Fabric:
+ return "fabric";
+ case Quilt:
+ return "quilt";
+ default:
+ break;
+ }
+ return "";
+}
+
} // namespace ModPlatform
diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h
index cad217034..72294c399 100644
--- a/launcher/modplatform/ModIndex.h
+++ b/launcher/modplatform/ModIndex.h
@@ -25,11 +25,15 @@
#include
#include
#include
+#include
class QIODevice;
namespace ModPlatform {
+enum ModLoaderType { NeoForge = 1 << 0, Forge = 1 << 1, Cauldron = 1 << 2, LiteLoader = 1 << 3, Fabric = 1 << 4, Quilt = 1 << 5 };
+Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType)
+
enum class ResourceProvider { MODRINTH, FLAME };
enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK };
@@ -55,6 +59,34 @@ struct DonationData {
QString url;
};
+struct IndexedVersionType {
+ enum class VersionType { Release = 1, Beta, Alpha, Unknown };
+ IndexedVersionType(const QString& type);
+ IndexedVersionType(const IndexedVersionType::VersionType& type);
+ IndexedVersionType(const IndexedVersionType& type);
+ IndexedVersionType() : IndexedVersionType(IndexedVersionType::VersionType::Unknown) {}
+ static const QString toString(const IndexedVersionType::VersionType& type);
+ static IndexedVersionType::VersionType enumFromString(const QString& type);
+ bool isValid() const { return m_type != IndexedVersionType::VersionType::Unknown; }
+ IndexedVersionType& operator=(const IndexedVersionType& other);
+ bool operator==(const IndexedVersionType& other) const { return m_type == other.m_type; }
+ bool operator==(const IndexedVersionType::VersionType& type) const { return m_type == type; }
+ bool operator!=(const IndexedVersionType& other) const { return m_type != other.m_type; }
+ bool operator!=(const IndexedVersionType::VersionType& type) const { return m_type != type; }
+ bool operator<(const IndexedVersionType& other) const { return m_type < other.m_type; }
+ bool operator<(const IndexedVersionType::VersionType& type) const { return m_type < type; }
+ bool operator<=(const IndexedVersionType& other) const { return m_type <= other.m_type; }
+ bool operator<=(const IndexedVersionType::VersionType& type) const { return m_type <= type; }
+ bool operator>(const IndexedVersionType& other) const { return m_type > other.m_type; }
+ bool operator>(const IndexedVersionType::VersionType& type) const { return m_type > type; }
+ bool operator>=(const IndexedVersionType& other) const { return m_type >= other.m_type; }
+ bool operator>=(const IndexedVersionType::VersionType& type) const { return m_type >= type; }
+
+ QString toString() const { return toString(m_type); }
+
+ IndexedVersionType::VersionType m_type;
+};
+
struct Dependency {
QVariant addonId;
DependencyType type;
@@ -66,11 +98,12 @@ struct IndexedVersion {
QVariant fileId;
QString version;
QString version_number = {};
+ IndexedVersionType version_type;
QStringList mcVersion;
QString downloadUrl;
QString date;
QString fileName;
- QStringList loaders = {};
+ ModLoaderTypes loaders = {};
QString hash_type;
QString hash;
bool is_preferred = true;
@@ -104,6 +137,7 @@ struct IndexedPack {
QString logoName;
QString logoUrl;
QString websiteUrl;
+ QString side;
bool versionsLoaded = false;
QVector