diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml
index 60bd86eec..4146cddf4 100644
--- a/.github/workflows/backport.yml
+++ b/.github/workflows/backport.yml
@@ -25,7 +25,7 @@ jobs:
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Create backport PRs
- uses: korthout/backport-action@v2.5.0
+ uses: korthout/backport-action@v3.1.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 f3896550f..ccba62541 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -54,14 +54,17 @@ jobs:
include:
- os: ubuntu-20.04
qt_ver: 5
+ qt_host: linux
+ qt_arch: ""
+ qt_version: "5.12.8"
+ qt_modules: "qtnetworkauth"
- os: ubuntu-20.04
qt_ver: 6
qt_host: linux
qt_arch: ""
qt_version: "6.2.4"
- qt_modules: "qt5compat qtimageformats"
- qt_tools: ""
+ qt_modules: "qt5compat qtimageformats qtnetworkauth"
- os: windows-2022
name: "Windows-MinGW-w64"
@@ -75,10 +78,9 @@ jobs:
vcvars_arch: "amd64"
qt_ver: 6
qt_host: windows
- qt_arch: ''
- qt_version: '6.7.0'
- qt_modules: 'qt5compat qtimageformats'
- qt_tools: ''
+ qt_arch: ""
+ qt_version: "6.7.2"
+ qt_modules: "qt5compat qtimageformats qtnetworkauth"
- os: windows-2022
name: "Windows-MSVC-arm64"
@@ -87,29 +89,26 @@ jobs:
vcvars_arch: "amd64_arm64"
qt_ver: 6
qt_host: windows
- qt_arch: 'win64_msvc2019_arm64'
- qt_version: '6.7.0'
- qt_modules: 'qt5compat qtimageformats'
- qt_tools: ''
+ qt_arch: "win64_msvc2019_arm64"
+ qt_version: "6.7.2"
+ qt_modules: "qt5compat qtimageformats qtnetworkauth"
- - os: macos-12
+ - os: macos-14
name: macOS
macosx_deployment_target: 11.0
qt_ver: 6
qt_host: mac
- qt_arch: ''
- qt_version: '6.7.0'
- qt_modules: 'qt5compat qtimageformats'
- qt_tools: ''
+ qt_arch: ""
+ qt_version: "6.7.2"
+ qt_modules: "qt5compat qtimageformats qtnetworkauth"
- - os: macos-12
+ - os: macos-14
name: macOS-Legacy
macosx_deployment_target: 10.13
qt_ver: 5
qt_host: mac
qt_version: "5.15.2"
- qt_modules: ""
- qt_tools: ""
+ qt_modules: "qtnetworkauth"
runs-on: ${{ matrix.os }}
@@ -151,6 +150,7 @@ jobs:
quazip-qt6:p
ccache:p
qt6-5compat:p
+ qt6-networkauth:p
cmark:p
- name: Force newer ccache
@@ -160,7 +160,7 @@ jobs:
- name: Setup ccache
if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug'
- uses: hendrikmuhs/ccache-action@v1.2.12
+ uses: hendrikmuhs/ccache-action@v1.2.14
with:
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
@@ -207,11 +207,6 @@ jobs:
brew update
brew install ninja extra-cmake-modules
- - name: Install Qt (Linux)
- if: runner.os == 'Linux' && matrix.qt_ver != 6
- run: |
- sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5
-
- name: Install host Qt (Windows MSVC arm64)
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
uses: jurplel/install-qt-action@v3
@@ -223,20 +218,18 @@ jobs:
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 == '')
+ - name: Install Qt (macOS, Linux & Windows MSVC)
+ if: 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 }}
@@ -266,6 +259,12 @@ jobs:
run: |
echo "QT_HOST_PATH=${{ github.workspace }}\HostQt\Qt\${{ matrix.qt_version }}\msvc2019_64" >> $env:GITHUB_ENV
+ - name: Setup java (macOS)
+ if: runner.os == 'macOS'
+ uses: actions/setup-java@v4
+ with:
+ distribution: "temurin"
+ java-version: "17"
##
# CONFIGURE
##
@@ -273,23 +272,23 @@ jobs:
- name: Configure CMake (macOS)
if: runner.os == 'macOS' && matrix.qt_ver == 6
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=${{ matrix.qt_ver }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -G Ninja
+ cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -G Ninja
- name: Configure CMake (macOS-Legacy)
if: runner.os == 'macOS' && matrix.qt_ver == 5
run: |
- cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DMACOSX_SPARKLE_UPDATE_PUBLIC_KEY="" -DMACOSX_SPARKLE_UPDATE_FEED_URL="" -G Ninja
+ cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DMACOSX_SPARKLE_UPDATE_PUBLIC_KEY="" -DMACOSX_SPARKLE_UPDATE_FEED_URL="" -DCMAKE_OSX_ARCHITECTURES="x86_64" -G Ninja
- name: Configure CMake (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != ''
shell: msys2 {0}
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 -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} -G Ninja
+ cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=6 -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} -G Ninja
- name: Configure CMake (Windows MSVC)
if: runner.os == 'Windows' && matrix.msystem == ''
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 -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }}
+ cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }}
# https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix)
if ("${{ env.CCACHE_VAR }}")
{
@@ -304,7 +303,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 }} -DLauncher_BUILD_ARTIFACT=Linux-Qt${{ 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_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DLauncher_BUILD_ARTIFACT=Linux-Qt${{ matrix.qt_ver }} -G Ninja
##
# BUILD
@@ -432,12 +431,6 @@ jobs:
run: |
cmake --install ${{ env.BUILD_DIR }} --config ${{ inputs.build_type }}
- cd ${{ env.INSTALL_DIR }}
- if ("${{ matrix.qt_ver }}" -eq "5")
- {
- Copy-Item ${{ runner.workspace }}/Qt/Tools/OpenSSL/Win_x86/bin/libcrypto-1_1.dll -Destination libcrypto-1_1.dll
- Copy-Item ${{ runner.workspace }}/Qt/Tools/OpenSSL/Win_x86/bin/libssl-1_1.dll -Destination libssl-1_1.dll
- }
cd ${{ github.workspace }}
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
@@ -490,28 +483,6 @@ jobs:
":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY
}
- - name: Package (Linux)
- if: runner.os == 'Linux'
- run: |
- cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_DIR }}
- for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_DIR }}/manifest.txt
- cd ${{ env.INSTALL_DIR }}
- tar --owner root --group root -czf ../PrismLauncher.tar.gz *
-
- - name: Package (Linux, portable)
- if: runner.os == 'Linux'
- run: |
- cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }}
- cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
-
- # workaround to make portable installs to work on fedora
- mkdir ${{ env.INSTALL_PORTABLE_DIR }}/lib
- cp /lib/x86_64-linux-gnu/libbz2.so.1.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib
-
- for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt
- cd ${{ env.INSTALL_PORTABLE_DIR }}
- tar -czf ../PrismLauncher-portable.tar.gz *
-
- name: Package AppImage (Linux)
if: runner.os == 'Linux' && matrix.qt_ver != 5
shell: bash
@@ -558,6 +529,25 @@ jobs:
mv "PrismLauncher-Linux-x86_64.AppImage" "PrismLauncher-Linux-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage"
+ - name: Package (Linux, portable)
+ if: runner.os == 'Linux'
+ run: |
+ cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_PORTABLE_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=${{ matrix.qt_ver }} -DLauncher_BUILD_ARTIFACT=Linux-Qt${{ matrix.qt_ver }} -DINSTALL_BUNDLE=full -G Ninja
+ cmake --install ${{ env.BUILD_DIR }}
+ cmake --install ${{ env.BUILD_DIR }} --component portable
+
+ mkdir ${{ env.INSTALL_PORTABLE_DIR }}/lib
+ cp /lib/x86_64-linux-gnu/libbz2.so.1.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib
+ cp /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib
+ cp /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 ${{ env.INSTALL_PORTABLE_DIR }}/lib
+ cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 ${{ env.INSTALL_PORTABLE_DIR }}/lib
+ cp /usr/lib/x86_64-linux-gnu/libffi.so.7 ${{ env.INSTALL_PORTABLE_DIR }}/lib
+ mv ${{ env.INSTALL_PORTABLE_DIR }}/bin/*.so* ${{ env.INSTALL_PORTABLE_DIR }}/lib
+
+ for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt
+ cd ${{ env.INSTALL_PORTABLE_DIR }}
+ tar -czf ../PrismLauncher-portable.tar.gz *
+
##
# UPLOAD BUILDS
##
@@ -590,13 +580,6 @@ jobs:
name: PrismLauncher-${{ matrix.name }}-Setup-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher-Setup.exe
- - name: Upload binary tarball (Linux, Qt 5)
- if: runner.os == 'Linux' && matrix.qt_ver != 6
- uses: actions/upload-artifact@v4
- with:
- 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@v4
@@ -604,13 +587,6 @@ jobs:
name: PrismLauncher-${{ runner.os }}-Qt5-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
path: PrismLauncher-portable.tar.gz
- - name: Upload binary tarball (Linux, Qt 6)
- if: runner.os == 'Linux' && matrix.qt_ver !=5
- uses: actions/upload-artifact@v4
- with:
- name: PrismLauncher-${{ runner.os }}-Qt6-${{ env.VERSION }}-${{ inputs.build_type }}
- path: PrismLauncher.tar.gz
-
- name: Upload binary tarball (Linux, portable, Qt 6)
if: runner.os == 'Linux' && matrix.qt_ver != 5
uses: actions/upload-artifact@v4
@@ -641,7 +617,7 @@ jobs:
flatpak:
runs-on: ubuntu-latest
container:
- image: bilelmoussaoui/flatpak-github-actions:kde-5.15-23.08
+ image: bilelmoussaoui/flatpak-github-actions:kde-6.7
options: --privileged
steps:
- name: Checkout
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index d40d7eb68..5255f865b 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -23,7 +23,7 @@ jobs:
run:
sudo apt-get -y update
- sudo apt-get -y install ninja-build extra-cmake-modules scdoc qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5
+ sudo apt-get -y install ninja-build extra-cmake-modules scdoc qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 libqt5networkauth5 libqt5networkauth5-dev
- name: Configure and Build
run: |
diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml
index fa22c96d5..134281b2c 100644
--- a/.github/workflows/trigger_release.yml
+++ b/.github/workflows/trigger_release.yml
@@ -46,9 +46,7 @@ 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-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.zip PrismLauncher-macOS-Legacy-${{ env.VERSION }}.zip
@@ -92,11 +90,9 @@ jobs:
draft: true
prerelease: false
files: |
- 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
PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
PrismLauncher-Windows-MinGW-w64-${{ env.VERSION }}.zip
PrismLauncher-Windows-MinGW-w64-Portable-${{ env.VERSION }}.zip
diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml
index 855b105ea..ecc38ff28 100644
--- a/.github/workflows/update-flake.yml
+++ b/.github/workflows/update-flake.yml
@@ -17,9 +17,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- - uses: cachix/install-nix-action@8887e596b4ee1134dae06b98d573bd674693f47c # v26
+ - uses: cachix/install-nix-action@ba0dd844c9180cbf77aa72a116d6fbc515d0e87b # v27
- - uses: DeterminateSystems/update-flake-lock@v21
+ - uses: DeterminateSystems/update-flake-lock@v24
with:
commit-msg: "chore(nix): update lockfile"
pr-title: "chore(nix): update lockfile"
diff --git a/BUILD.md b/BUILD.md
deleted file mode 100644
index a139039df..000000000
--- a/BUILD.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Build Instructions
-
-Full build instructions are available on [the website](https://prismlauncher.org/wiki/development/build-instructions/).
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2e46bb605..3d70fe79b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -176,6 +176,8 @@ endif()
set(Launcher_NEWS_RSS_URL "https://prismlauncher.org/feed/feed.xml" CACHE STRING "URL to fetch Prism Launcher's news RSS feed from.")
set(Launcher_NEWS_OPEN_URL "https://prismlauncher.org/news" CACHE STRING "URL that gets opened when the user clicks 'More News'")
set(Launcher_HELP_URL "https://prismlauncher.org/wiki/help-pages/%1" CACHE STRING "URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help")
+set(Launcher_LOGIN_CALLBACK_URL "https://prismlauncher.org/successful-login" CACHE STRING "URL that gets opened when the user successfully logins.")
+set(Launcher_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" CACHE STRING "URL for FML Libraries.")
######## Set version numbers ########
set(Launcher_VERSION_MAJOR 9)
@@ -205,6 +207,7 @@ set(Launcher_BUG_TRACKER_URL "https://github.com/PrismLauncher/PrismLauncher/iss
# Translations Platform URL
set(Launcher_TRANSLATIONS_URL "https://hosted.weblate.org/projects/prismlauncher/launcher/" CACHE STRING "URL for the translations platform.")
+set(Launcher_TRANSLATION_FILES_URL "https://i18n.prismlauncher.org/" CACHE STRING "URL for the translations files.")
# Matrix Space
set(Launcher_MATRIX_URL "https://prismlauncher.org/matrix" CACHE STRING "URL to the Matrix Space")
@@ -219,6 +222,19 @@ 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")
+# Java downloader
+set(Launcher_ENABLE_JAVA_DOWNLOADER_DEFAULT ON)
+
+# Although we recommend enabling this, we cannot guarantee binary compatibility on
+# differing Linux/BSD/etc distributions. Downstream packagers should be explicitly opt-ing into this
+# feature if they know it will work with their distribution.
+if(UNIX AND NOT APPLE)
+ set(Launcher_ENABLE_JAVA_DOWNLOADER_DEFAULT OFF)
+endif()
+
+# Java downloader
+option(Launcher_ENABLE_JAVA_DOWNLOADER "Build the java downloader feature" ${Launcher_ENABLE_JAVA_DOWNLOADER_DEFAULT})
+
# Native libraries
if(UNIX AND APPLE)
set(Launcher_GLFW_LIBRARY_NAME "libglfw.dylib" CACHE STRING "Name of native glfw library")
@@ -282,7 +298,7 @@ endif()
include(QtVersionlessBackport)
if(Launcher_QT_VERSION_MAJOR EQUAL 5)
set(QT_VERSION_MAJOR 5)
- find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml)
+ find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml NetworkAuth)
if(NOT Launcher_FORCE_BUNDLED_LIBS)
find_package(QuaZip-Qt5 1.3 QUIET)
@@ -296,7 +312,7 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 5)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUNICODE -D_UNICODE")
elseif(Launcher_QT_VERSION_MAJOR EQUAL 6)
set(QT_VERSION_MAJOR 6)
- find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat)
+ find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat NetworkAuth)
list(APPEND Launcher_QT_LIBS Qt6::Core5Compat)
if(NOT Launcher_FORCE_BUNDLED_LIBS)
@@ -417,7 +433,19 @@ elseif(UNIX)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_mrpack_MIMEInfo} DESTINATION ${KDE_INSTALL_MIMEDIR})
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/launcher/qtlogging.ini" DESTINATION "share/${Launcher_Name}")
+
+ if (INSTALL_BUNDLE STREQUAL full)
+ set(PLUGIN_DEST_DIR "plugins")
+ set(BUNDLE_DEST_DIR ".")
+ set(RESOURCES_DEST_DIR ".")
+ # Apps to bundle
+ set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/${Launcher_APP_BINARY_NAME}")
+
+ # directories to look for dependencies
+ set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+ endif()
+
if(Launcher_ManPage)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_ManPage} DESTINATION "${KDE_INSTALL_MANDIR}/man6")
endif()
@@ -511,7 +539,6 @@ if(NOT cmark_FOUND)
else()
message(STATUS "Using system cmark")
endif()
-add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much
add_subdirectory(libraries/gamemode)
add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API
if (NOT ghc_filesystem_FOUND)
diff --git a/COPYING.md b/COPYING.md
index f14e2958e..111587060 100644
--- a/COPYING.md
+++ b/COPYING.md
@@ -333,32 +333,6 @@
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-## O2 (Katabasis fork)
-
- Copyright (c) 2012, Akos Polster
- All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
-
- * Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
-
- * Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
## Gamemode
Copyright (c) 2017-2022, Feral Interactive
diff --git a/README.md b/README.md
index b32132d49..9c4909509 100644
--- a/README.md
+++ b/README.md
@@ -61,7 +61,12 @@ The translation effort for Prism Launcher is hosted on [Weblate](https://hosted.
## Building
-If you want to build Prism Launcher yourself, check the [Build Instructions](https://prismlauncher.org/wiki/development/build-instructions/).
+If you want to build Prism Launcher yourself, check the build instructions:
+
+- [Windows](https://prismlauncher.org/wiki/development/build-instructions/windows/)
+- [Linux](https://prismlauncher.org/wiki/development/build-instructions/linux/)
+- [MacOS](https://prismlauncher.org/wiki/development/build-instructions/macos/)
+- [OpenBSD](https://prismlauncher.org/wiki/development/build-instructions/openbsd/)
## Sponsors & Partners
diff --git a/buildconfig/BuildConfig.cpp.in b/buildconfig/BuildConfig.cpp.in
index b40cacb0f..b48232b43 100644
--- a/buildconfig/BuildConfig.cpp.in
+++ b/buildconfig/BuildConfig.cpp.in
@@ -81,6 +81,9 @@ Config::Config()
UPDATER_ENABLED = true;
}
+ #cmakedefine01 Launcher_ENABLE_JAVA_DOWNLOADER
+ JAVA_DOWNLOADER_ENABLED = Launcher_ENABLE_JAVA_DOWNLOADER;
+
GIT_COMMIT = "@Launcher_GIT_COMMIT@";
GIT_TAG = "@Launcher_GIT_TAG@";
GIT_REFSPEC = "@Launcher_GIT_REFSPEC@";
@@ -113,16 +116,19 @@ Config::Config()
NEWS_RSS_URL = "@Launcher_NEWS_RSS_URL@";
NEWS_OPEN_URL = "@Launcher_NEWS_OPEN_URL@";
HELP_URL = "@Launcher_HELP_URL@";
+ LOGIN_CALLBACK_URL = "@Launcher_LOGIN_CALLBACK_URL@";
IMGUR_CLIENT_ID = "@Launcher_IMGUR_CLIENT_ID@";
MSA_CLIENT_ID = "@Launcher_MSA_CLIENT_ID@";
FLAME_API_KEY = "@Launcher_CURSEFORGE_API_KEY@";
META_URL = "@Launcher_META_URL@";
+ FMLLIBS_BASE_URL = "@Launcher_FMLLIBS_BASE_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@";
+ TRANSLATION_FILES_URL = "@Launcher_TRANSLATION_FILES_URL@";
MATRIX_URL = "@Launcher_MATRIX_URL@";
DISCORD_URL = "@Launcher_DISCORD_URL@";
SUBREDDIT_URL = "@Launcher_SUBREDDIT_URL@";
diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h
index 77b6eef54..ae705d098 100644
--- a/buildconfig/BuildConfig.h
+++ b/buildconfig/BuildConfig.h
@@ -67,6 +67,7 @@ class Config {
QString VERSION_CHANNEL;
bool UPDATER_ENABLED = false;
+ bool JAVA_DOWNLOADER_ENABLED = false;
/// A short string identifying this build's platform or distribution.
QString BUILD_PLATFORM;
@@ -132,6 +133,11 @@ class Config {
*/
QString HELP_URL;
+ /**
+ * URL that gets opened when the user succesfully logins.
+ */
+ QString LOGIN_CALLBACK_URL;
+
/**
* Client ID you can get from Imgur when you register an application
*/
@@ -163,10 +169,9 @@ class Config {
QString RESOURCE_BASE = "https://resources.download.minecraft.net/";
QString LIBRARY_BASE = "https://libraries.minecraft.net/";
- QString AUTH_BASE = "https://authserver.mojang.com/";
QString IMGUR_BASE_URL = "https://api.imgur.com/3/";
- QString FMLLIBS_BASE_URL = "https://files.prismlauncher.org/fmllibs/"; // FIXME: move into CMakeLists
- QString TRANSLATIONS_BASE_URL = "https://i18n.prismlauncher.org/"; // FIXME: move into CMakeLists
+ QString FMLLIBS_BASE_URL;
+ QString TRANSLATION_FILES_URL;
QString MODPACKSCH_API_BASE_URL = "https://api.modpacks.ch/";
diff --git a/cmake/MacOSXBundleInfo.plist.in b/cmake/MacOSXBundleInfo.plist.in
index d36ac3e8f..6d3845dfc 100644
--- a/cmake/MacOSXBundleInfo.plist.in
+++ b/cmake/MacOSXBundleInfo.plist.in
@@ -6,6 +6,8 @@
A Minecraft mod wants to access your camera.
NSMicrophoneUsageDescription
A Minecraft mod wants to access your microphone.
+ NSDownloadsFolderUsageDescription
+ Prism uses access to your Downloads folder to help you more quickly add mods that can't be automatically downloaded to your instance. You can change where Prism scans for downloaded mods in Settings or the prompt that appears.
NSPrincipalClass
NSApplication
NSHighResolutionCapable
@@ -77,6 +79,14 @@
curseforge
+
+ CFBundleURLName
+ Prismlauncher
+ CFBundleURLSchemes
+
+ prismlauncher
+
+
diff --git a/default.nix b/default.nix
index c7d0c267d..6466507b7 100644
--- a/default.nix
+++ b/default.nix
@@ -1,14 +1,9 @@
-(
- import
- (
- let
- lock = builtins.fromJSON (builtins.readFile ./flake.lock);
- in
- fetchTarball {
- url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
- sha256 = lock.nodes.flake-compat.locked.narHash;
- }
- )
- {src = ./.;}
-)
-.defaultNix
+(import (
+ let
+ lock = builtins.fromJSON (builtins.readFile ./flake.lock);
+ in
+ fetchTarball {
+ url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
+ sha256 = lock.nodes.flake-compat.locked.narHash;
+ }
+) { src = ./.; }).defaultNix
diff --git a/flake.lock b/flake.lock
index 0fbfca9cc..58bc4e30f 100644
--- a/flake.lock
+++ b/flake.lock
@@ -16,65 +16,6 @@
"type": "github"
}
},
- "flake-parts": {
- "inputs": {
- "nixpkgs-lib": [
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1712014858,
- "narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
- "owner": "hercules-ci",
- "repo": "flake-parts",
- "rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
- "type": "github"
- },
- "original": {
- "owner": "hercules-ci",
- "repo": "flake-parts",
- "type": "github"
- }
- },
- "flake-utils": {
- "inputs": {
- "systems": "systems"
- },
- "locked": {
- "lastModified": 1710146030,
- "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
- "owner": "numtide",
- "repo": "flake-utils",
- "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
- "type": "github"
- },
- "original": {
- "owner": "numtide",
- "repo": "flake-utils",
- "type": "github"
- }
- },
- "gitignore": {
- "inputs": {
- "nixpkgs": [
- "pre-commit-hooks",
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1709087332,
- "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
- "owner": "hercules-ci",
- "repo": "gitignore.nix",
- "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
- "type": "github"
- },
- "original": {
- "owner": "hercules-ci",
- "repo": "gitignore.nix",
- "type": "github"
- }
- },
"libnbtplusplus": {
"flake": false,
"locked": {
@@ -91,72 +32,43 @@
"type": "github"
}
},
- "nixpkgs": {
+ "nix-filter": {
"locked": {
- "lastModified": 1713596654,
- "narHash": "sha256-LJbHQQ5aX1LVth2ST+Kkse/DRzgxlVhTL1rxthvyhZc=",
- "owner": "nixos",
- "repo": "nixpkgs",
- "rev": "fd16bb6d3bcca96039b11aa52038fafeb6e4f4be",
+ "lastModified": 1710156097,
+ "narHash": "sha256-1Wvk8UP7PXdf8bCCaEoMnOT1qe5/Duqgj+rL8sRQsSM=",
+ "owner": "numtide",
+ "repo": "nix-filter",
+ "rev": "3342559a24e85fc164b295c3444e8a139924675b",
"type": "github"
},
"original": {
- "owner": "nixos",
- "ref": "nixpkgs-unstable",
- "repo": "nixpkgs",
+ "owner": "numtide",
+ "repo": "nix-filter",
"type": "github"
}
},
- "pre-commit-hooks": {
- "inputs": {
- "flake-compat": [
- "flake-compat"
- ],
- "flake-utils": "flake-utils",
- "gitignore": "gitignore",
- "nixpkgs": [
- "nixpkgs"
- ],
- "nixpkgs-stable": [
- "nixpkgs"
- ]
- },
+ "nixpkgs": {
"locked": {
- "lastModified": 1712897695,
- "narHash": "sha256-nMirxrGteNAl9sWiOhoN5tIHyjBbVi5e2tgZUgZlK3Y=",
- "owner": "cachix",
- "repo": "pre-commit-hooks.nix",
- "rev": "40e6053ecb65fcbf12863338a6dcefb3f55f1bf8",
+ "lastModified": 1726062873,
+ "narHash": "sha256-IiA3jfbR7K/B5+9byVi9BZGWTD4VSbWe8VLpp9B/iYk=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "4f807e8940284ad7925ebd0a0993d2a1791acb2f",
"type": "github"
},
"original": {
- "owner": "cachix",
- "repo": "pre-commit-hooks.nix",
+ "owner": "NixOS",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-compat": "flake-compat",
- "flake-parts": "flake-parts",
"libnbtplusplus": "libnbtplusplus",
- "nixpkgs": "nixpkgs",
- "pre-commit-hooks": "pre-commit-hooks"
- }
- },
- "systems": {
- "locked": {
- "lastModified": 1681028828,
- "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
- "owner": "nix-systems",
- "repo": "default",
- "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
- "type": "github"
- },
- "original": {
- "owner": "nix-systems",
- "repo": "default",
- "type": "github"
+ "nix-filter": "nix-filter",
+ "nixpkgs": "nixpkgs"
}
}
},
diff --git a/flake.nix b/flake.nix
index e16c76699..987fc0eda 100644
--- a/flake.nix
+++ b/flake.nix
@@ -2,52 +2,121 @@
description = "A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once (Fork of MultiMC)";
nixConfig = {
- extra-substituters = ["https://cache.garnix.io"];
- extra-trusted-public-keys = ["cache.garnix.io:CTFPyKSLcx5RMJKfLo5EEPUObbA78b0YQ2DTCJXqr9g="];
+ extra-substituters = [ "https://cache.garnix.io" ];
+ extra-trusted-public-keys = [ "cache.garnix.io:CTFPyKSLcx5RMJKfLo5EEPUObbA78b0YQ2DTCJXqr9g=" ];
};
inputs = {
- nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
- flake-parts = {
- url = "github:hercules-ci/flake-parts";
- inputs.nixpkgs-lib.follows = "nixpkgs";
- };
- pre-commit-hooks = {
- url = "github:cachix/pre-commit-hooks.nix";
- inputs = {
- nixpkgs.follows = "nixpkgs";
- nixpkgs-stable.follows = "nixpkgs";
- flake-compat.follows = "flake-compat";
- };
- };
- flake-compat = {
- url = "github:edolstra/flake-compat";
- flake = false;
- };
+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+
libnbtplusplus = {
url = "github:PrismLauncher/libnbtplusplus";
flake = false;
};
+
+ nix-filter.url = "github:numtide/nix-filter";
+
+ /*
+ Inputs below this are optional and can be removed
+
+ ```
+ {
+ inputs.prismlauncher = {
+ url = "github:PrismLauncher/PrismLauncher";
+ inputs = {
+ flake-compat.follows = "";
+ };
+ };
+ }
+ ```
+ */
+
+ flake-compat = {
+ url = "github:edolstra/flake-compat";
+ flake = false;
+ };
};
- outputs = {
- flake-parts,
- pre-commit-hooks,
- ...
- } @ inputs:
- flake-parts.lib.mkFlake {inherit inputs;} {
- imports = [
- pre-commit-hooks.flakeModule
+ outputs =
+ {
+ self,
+ nixpkgs,
+ libnbtplusplus,
+ nix-filter,
+ ...
+ }:
+ let
+ inherit (nixpkgs) lib;
- ./nix/dev.nix
- ./nix/distribution.nix
- ];
+ # While we only officially support aarch and x86_64 on Linux and MacOS,
+ # we expose a reasonable amount of other systems for users who want to
+ # build for most exotic platforms
+ systems = lib.systems.flakeExposed;
- systems = [
- "x86_64-linux"
- "aarch64-linux"
- "x86_64-darwin"
- "aarch64-darwin"
- ];
+ forAllSystems = lib.genAttrs systems;
+ nixpkgsFor = forAllSystems (system: nixpkgs.legacyPackages.${system});
+ in
+ {
+ checks = forAllSystems (
+ system:
+ let
+ checks' = nixpkgsFor.${system}.callPackage ./nix/checks.nix { inherit self; };
+ in
+ lib.filterAttrs (_: lib.isDerivation) checks'
+ );
+
+ devShells = forAllSystems (
+ system:
+ let
+ pkgs = nixpkgsFor.${system};
+ in
+ {
+ default = pkgs.mkShell {
+ inputsFrom = [ self.packages.${system}.prismlauncher-unwrapped ];
+ buildInputs = with pkgs; [
+ ccache
+ ninja
+ ];
+ };
+ }
+ );
+
+ formatter = forAllSystems (system: nixpkgsFor.${system}.nixfmt-rfc-style);
+
+ overlays.default =
+ final: prev:
+ let
+ version = builtins.substring 0 8 self.lastModifiedDate or "dirty";
+ in
+ {
+ prismlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix {
+ inherit
+ libnbtplusplus
+ nix-filter
+ self
+ version
+ ;
+ };
+
+ prismlauncher = final.callPackage ./nix/wrapper.nix { };
+ };
+
+ packages = forAllSystems (
+ system:
+ let
+ pkgs = nixpkgsFor.${system};
+
+ # Build a scope from our overlay
+ prismPackages = lib.makeScope pkgs.newScope (final: self.overlays.default final pkgs);
+
+ # Grab our packages from it and set the default
+ packages = {
+ inherit (prismPackages) prismlauncher-unwrapped prismlauncher;
+ default = prismPackages.prismlauncher;
+ };
+ in
+ # Only output them if they're available on the current system
+ lib.filterAttrs (_: lib.meta.availableOn pkgs.stdenv.hostPlatform) packages
+ );
};
}
diff --git a/flatpak/org.prismlauncher.PrismLauncher.yml b/flatpak/org.prismlauncher.PrismLauncher.yml
index b4c6e8143..bd09f7fd8 100644
--- a/flatpak/org.prismlauncher.PrismLauncher.yml
+++ b/flatpak/org.prismlauncher.PrismLauncher.yml
@@ -1,6 +1,6 @@
id: org.prismlauncher.PrismLauncher
runtime: org.kde.Platform
-runtime-version: 5.15-23.08
+runtime-version: 6.7
sdk: org.kde.Sdk
sdk-extensions:
- org.freedesktop.Sdk.Extension.openjdk21
@@ -38,7 +38,6 @@ modules:
config-opts:
- -DLauncher_BUILD_PLATFORM=flatpak
- -DCMAKE_BUILD_TYPE=RelWithDebInfo
- - -DLauncher_QT_VERSION_MAJOR=5
build-options:
env:
JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17
@@ -65,7 +64,8 @@ modules:
config-opts:
- -DCMAKE_BUILD_TYPE=RelWithDebInfo
- -DBUILD_SHARED_LIBS:BOOL=ON
- - -DGLFW_USE_WAYLAND=ON
+ - -DGLFW_USE_WAYLAND:BOOL=ON
+ - -DGLFW_BUILD_DOCS:BOOL=OFF
sources:
- type: git
url: https://github.com/glfw/glfw.git
diff --git a/garnix.yaml b/garnix.yaml
index 6cf8f7214..a7c1b48a9 100644
--- a/garnix.yaml
+++ b/garnix.yaml
@@ -1,7 +1,10 @@
builds:
exclude:
+ # Currently broken on Garnix's end
- "*.x86_64-darwin.*"
include:
- "checks.x86_64-linux.*"
- - "devShells.*.*"
- - "packages.*.*"
+ - "packages.x86_64-linux.*"
+ - "packages.aarch64-linux.*"
+ - "packages.x86_64-darwin.*"
+ - "packages.aarch64-darwin.*"
diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index bb8751ccc..3bed11db2 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -44,10 +44,11 @@
#include "BuildConfig.h"
#include "DataMigrationTask.h"
+#include "java/JavaInstallList.h"
#include "net/PasteUpload.h"
#include "pathmatcher/MultiMatcher.h"
#include "pathmatcher/SimplePrefixMatcher.h"
-#include "settings/INIFile.h"
+#include "tools/GenericProfiler.h"
#include "ui/InstanceWindow.h"
#include "ui/MainWindow.h"
@@ -66,8 +67,10 @@
#include "ui/pages/global/MinecraftPage.h"
#include "ui/pages/global/ProxyPage.h"
+#include "ui/setupwizard/AutoJavaWizardPage.h"
#include "ui/setupwizard/JavaWizardPage.h"
#include "ui/setupwizard/LanguageWizardPage.h"
+#include "ui/setupwizard/LoginWizardPage.h"
#include "ui/setupwizard/PasteWizardPage.h"
#include "ui/setupwizard/SetupWizard.h"
#include "ui/setupwizard/ThemeWizardPage.h"
@@ -105,7 +108,7 @@
#include "icons/IconList.h"
#include "net/HttpMetaCache.h"
-#include "java/JavaUtils.h"
+#include "java/JavaInstallList.h"
#include "updater/ExternalUpdater.h"
@@ -125,6 +128,7 @@
#include
#include
+#include "SysInfo.h"
#ifdef Q_OS_LINUX
#include
@@ -150,6 +154,7 @@
#endif
#if defined Q_OS_WIN32
+#include
#include "WindowsConsole.h"
#endif
@@ -235,6 +240,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
{ { { "d", "dir" }, "Use a custom path as application root (use '.' for current directory)", "directory" },
{ { "l", "launch" }, "Launch the specified instance (by instance ID)", "instance" },
{ { "s", "server" }, "Join the specified server on launch (only valid in combination with --launch)", "address" },
+ { { "w", "world" }, "Join the specified world on launch (only valid in combination with --launch)", "world" },
{ { "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 or resource from specified local path or URL", "url" },
@@ -249,6 +255,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_instanceIdToLaunch = parser.value("launch");
m_serverToJoin = parser.value("server");
+ m_worldToJoin = parser.value("world");
m_profileToUse = parser.value("profile");
m_liveCheck = parser.isSet("alive");
@@ -264,7 +271,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
}
// error if --launch is missing with --server or --profile
- if ((!m_serverToJoin.isEmpty() || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty()) {
+ if (((!m_serverToJoin.isEmpty() || !m_worldToJoin.isEmpty()) || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty()) {
std::cerr << "--server and --profile can only be used in combination with --launch!" << std::endl;
m_status = Application::Failed;
return;
@@ -291,12 +298,17 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
QString adjustedBy;
QString dataPath;
// change folder
+ QString dataDirEnv;
QString dirParam = parser.value("dir");
if (!dirParam.isEmpty()) {
// the dir param. it makes multimc data path point to whatever the user specified
// on command line
adjustedBy = "Command line";
dataPath = dirParam;
+ } else if (dataDirEnv = QProcessEnvironment::systemEnvironment().value(QString("%1_DATA_DIR").arg(BuildConfig.LAUNCHER_NAME.toUpper()));
+ !dataDirEnv.isEmpty()) {
+ adjustedBy = "System environment";
+ dataPath = dataDirEnv;
} else {
QDir foo;
if (DesktopServices::isSnap()) {
@@ -379,6 +391,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
if (!m_serverToJoin.isEmpty()) {
launch.args["server"] = m_serverToJoin;
+ } else if (!m_worldToJoin.isEmpty()) {
+ launch.args["world"] = m_worldToJoin;
}
if (!m_profileToUse.isEmpty()) {
launch.args["profile"] = m_profileToUse;
@@ -394,20 +408,15 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
{
static const QString baseLogFile = BuildConfig.LAUNCHER_NAME + "-%0.log";
static const QString logBase = FS::PathCombine("logs", baseLogFile);
- auto moveFile = [](const QString& oldName, const QString& newName) {
- QFile::remove(newName);
- QFile::copy(oldName, newName);
- QFile::remove(oldName);
- };
if (FS::ensureFolderPathExists("logs")) { // if this did not fail
for (auto i = 0; i <= 4; i++)
if (auto oldName = baseLogFile.arg(i);
QFile::exists(oldName)) // do not pointlessly delete new files if the old ones are not there
- moveFile(oldName, logBase.arg(i));
+ FS::move(oldName, logBase.arg(i));
}
for (auto i = 4; i > 0; i--)
- moveFile(logBase.arg(i - 1), logBase.arg(i));
+ FS::move(logBase.arg(i - 1), logBase.arg(i));
logFile = std::unique_ptr(new QFile(logBase.arg(0)));
if (!logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
@@ -447,7 +456,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
// search the dataPath()
// seach app data standard path
- if (!foundLoggingRules && !isPortable() && dirParam.isEmpty()) {
+ if (!foundLoggingRules && !isPortable() && dirParam.isEmpty() && dataDirEnv.isEmpty()) {
logRulesPath = QStandardPaths::locate(QStandardPaths::AppDataLocation, FS::PathCombine("..", logRulesFile));
if (!logRulesPath.isEmpty()) {
qDebug() << "Found" << logRulesPath << "...";
@@ -522,6 +531,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
}
if (!m_serverToJoin.isEmpty()) {
qDebug() << "Address of server to join :" << m_serverToJoin;
+ } else if (!m_worldToJoin.isEmpty()) {
+ qDebug() << "Name of the world to join :" << m_worldToJoin;
}
qDebug() << "<> Paths set.";
}
@@ -558,6 +569,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("NumberOfConcurrentTasks", 10);
m_settings->registerSetting("NumberOfConcurrentDownloads", 6);
+ m_settings->registerSetting("NumberOfManualRetries", 1);
+ m_settings->registerSetting("RequestTimeout", 60);
QString defaultMonospace;
int defaultSize = 11;
@@ -592,6 +605,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("IconsDir", "icons");
m_settings->registerSetting("DownloadsDir", QStandardPaths::writableLocation(QStandardPaths::DownloadLocation));
m_settings->registerSetting("DownloadsDirWatchRecursive", false);
+ m_settings->registerSetting("SkinsDir", "skins");
+ m_settings->registerSetting("JavaDir", "java");
// Editors
m_settings->registerSetting("JsonEditor", QString());
@@ -620,7 +635,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
// Memory
m_settings->registerSetting({ "MinMemAlloc", "MinMemoryAlloc" }, 512);
- m_settings->registerSetting({ "MaxMemAlloc", "MaxMemoryAlloc" }, suitableMaxMem());
+ m_settings->registerSetting({ "MaxMemAlloc", "MaxMemoryAlloc" }, SysInfo::suitableMaxMem());
m_settings->registerSetting("PermGen", 128);
// Java Settings
@@ -634,6 +649,10 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("JvmArgs", "");
m_settings->registerSetting("IgnoreJavaCompatibility", false);
m_settings->registerSetting("IgnoreJavaWizard", false);
+ auto defaultEnableAutoJava = m_settings->get("JavaPath").toString().isEmpty();
+ m_settings->registerSetting("AutomaticJavaSwitch", defaultEnableAutoJava);
+ m_settings->registerSetting("AutomaticJavaDownload", defaultEnableAutoJava);
+ m_settings->registerSetting("UserAskedAboutAutomaticJavaDownload", false);
// Legacy settings
m_settings->registerSetting("OnlineFixes", false);
@@ -659,6 +678,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
// Minecraft mods
m_settings->registerSetting("ModMetadataDisabled", false);
m_settings->registerSetting("ModDependenciesDisabled", false);
+ m_settings->registerSetting("SkipModpackUpdatePrompt", false);
// Minecraft offline player name
m_settings->registerSetting("LastOfflinePlayerName", "");
@@ -813,7 +833,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_icons.reset(new IconList(instFolders, setting->get().toString()));
connect(setting.get(), &Setting::SettingChanged,
[&](const Setting&, QVariant value) { m_icons->directoryChanged(value.toString()); });
- qDebug() << "<> Instance icons intialized.";
+ qDebug() << "<> Instance icons initialized.";
}
// Themes
@@ -850,25 +870,19 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
{
m_metacache.reset(new HttpMetaCache("metacache"));
m_metacache->addBase("asset_indexes", QDir("assets/indexes").absolutePath());
- m_metacache->addBase("asset_objects", QDir("assets/objects").absolutePath());
- m_metacache->addBase("versions", QDir("versions").absolutePath());
m_metacache->addBase("libraries", QDir("libraries").absolutePath());
- m_metacache->addBase("minecraftforge", QDir("mods/minecraftforge").absolutePath());
m_metacache->addBase("fmllibs", QDir("mods/minecraftforge/libs").absolutePath());
- m_metacache->addBase("liteloader", QDir("mods/liteloader").absolutePath());
m_metacache->addBase("general", QDir("cache").absolutePath());
m_metacache->addBase("ATLauncherPacks", QDir("cache/ATLauncherPacks").absolutePath());
m_metacache->addBase("FTBPacks", QDir("cache/FTBPacks").absolutePath());
- m_metacache->addBase("ModpacksCHPacks", QDir("cache/ModpacksCHPacks").absolutePath());
m_metacache->addBase("TechnicPacks", QDir("cache/TechnicPacks").absolutePath());
m_metacache->addBase("FlamePacks", QDir("cache/FlamePacks").absolutePath());
m_metacache->addBase("FlameMods", QDir("cache/FlameMods").absolutePath());
m_metacache->addBase("ModrinthPacks", QDir("cache/ModrinthPacks").absolutePath());
m_metacache->addBase("ModrinthModpacks", QDir("cache/ModrinthModpacks").absolutePath());
- m_metacache->addBase("root", QDir::currentPath());
m_metacache->addBase("translations", QDir("translations").absolutePath());
- m_metacache->addBase("icons", QDir("cache/icons").absolutePath());
m_metacache->addBase("meta", QDir("meta").absolutePath());
+ m_metacache->addBase("java", QDir("cache/java").absolutePath());
m_metacache->Load();
qDebug() << "<> Cache initialized.";
}
@@ -879,6 +893,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
// FIXME: what to do with these?
m_profilers.insert("jprofiler", std::shared_ptr(new JProfilerFactory()));
m_profilers.insert("jvisualvm", std::shared_ptr(new JVisualVMFactory()));
+ m_profilers.insert("generic", std::shared_ptr(new GenericProfilerFactory()));
for (auto profiler : m_profilers.values()) {
profiler->registerSettings(m_settings);
}
@@ -947,8 +962,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
[[fallthrough]];
default: {
qDebug() << "Exiting because update lockfile is present";
- QMetaObject::invokeMethod(
- this, []() { exit(1); }, Qt::QueuedConnection);
+ QMetaObject::invokeMethod(this, []() { exit(1); }, Qt::QueuedConnection);
return;
}
}
@@ -980,8 +994,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
[[fallthrough]];
default: {
qDebug() << "Exiting because update lockfile is present";
- QMetaObject::invokeMethod(
- this, []() { exit(1); }, Qt::QueuedConnection);
+ QMetaObject::invokeMethod(this, []() { exit(1); }, Qt::QueuedConnection);
return;
}
}
@@ -993,7 +1006,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
"\n"
"You are now running %1 .\n"
"Check the Prism Launcher updater log at: \n"
- "%1\n"
+ "%2\n"
"for details.")
.arg(BuildConfig.printableVersionString())
.arg(update_log_path);
@@ -1067,13 +1080,15 @@ bool Application::createSetupWizard()
}
return false;
}();
+ bool askjava = BuildConfig.JAVA_DOWNLOADER_ENABLED && !javaRequired && !m_settings->get("AutomaticJavaDownload").toBool() &&
+ !m_settings->get("AutomaticJavaSwitch").toBool() && !m_settings->get("UserAskedAboutAutomaticJavaDownload").toBool();
bool languageRequired = settings()->get("Language").toString().isEmpty();
bool pasteInterventionRequired = settings()->get("PastebinURL") != "";
bool validWidgets = m_themeManager->isValidApplicationTheme(settings()->get("ApplicationTheme").toString());
bool validIcons = m_themeManager->isValidIconTheme(settings()->get("IconTheme").toString());
+ bool login = !m_accounts->anyAccountIsValid() && capabilities() & Application::SupportsMSA;
bool themeInterventionRequired = !validWidgets || !validIcons;
- bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired || themeInterventionRequired;
-
+ bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired || themeInterventionRequired || askjava || login;
if (wizardRequired) {
// set default theme after going into theme wizard
if (!validIcons)
@@ -1090,6 +1105,8 @@ bool Application::createSetupWizard()
if (javaRequired) {
m_setupWizard->addPage(new JavaWizardPage(m_setupWizard));
+ } else if (askjava) {
+ m_setupWizard->addPage(new AutoJavaWizardPage(m_setupWizard));
}
if (pasteInterventionRequired) {
@@ -1100,11 +1117,14 @@ bool Application::createSetupWizard()
m_setupWizard->addPage(new ThemeWizardPage(m_setupWizard));
}
+ if (login) {
+ m_setupWizard->addPage(new LoginWizardPage(m_setupWizard));
+ }
connect(m_setupWizard, &QDialog::finished, this, &Application::setupWizardFinished);
m_setupWizard->show();
- return true;
}
- return false;
+
+ return wizardRequired || login;
}
bool Application::updaterEnabled()
@@ -1160,14 +1180,17 @@ void Application::performMainStartupAction()
if (!m_instanceIdToLaunch.isEmpty()) {
auto inst = instances()->getInstanceById(m_instanceIdToLaunch);
if (inst) {
- MinecraftServerTargetPtr serverToJoin = nullptr;
+ MinecraftTarget::Ptr targetToJoin = nullptr;
MinecraftAccountPtr accountToUse = nullptr;
qDebug() << "<> Instance" << m_instanceIdToLaunch << "launching";
if (!m_serverToJoin.isEmpty()) {
// FIXME: validate the server string
- serverToJoin.reset(new MinecraftServerTarget(MinecraftServerTarget::parse(m_serverToJoin)));
+ targetToJoin.reset(new MinecraftTarget(MinecraftTarget::parse(m_serverToJoin, false)));
qDebug() << " Launching with server" << m_serverToJoin;
+ } else if (!m_worldToJoin.isEmpty()) {
+ targetToJoin.reset(new MinecraftTarget(MinecraftTarget::parse(m_worldToJoin, true)));
+ qDebug() << " Launching with world" << m_worldToJoin;
}
if (!m_profileToUse.isEmpty()) {
@@ -1178,7 +1201,7 @@ void Application::performMainStartupAction()
qDebug() << " Launching with account" << m_profileToUse;
}
- launch(inst, true, false, serverToJoin, accountToUse);
+ launch(inst, true, false, targetToJoin, accountToUse);
return;
}
}
@@ -1209,6 +1232,12 @@ void Application::performMainStartupAction()
qDebug() << "<> Updater started.";
}
+ { // delete instances tmp dirctory
+ auto instDir = m_settings->get("InstanceDir").toString();
+ const QString tempRoot = FS::PathCombine(instDir, ".tmp");
+ FS::deletePath(tempRoot);
+ }
+
if (!m_urlsToImport.isEmpty()) {
qDebug() << "<> Importing from url:" << m_urlsToImport;
m_mainWindow->processURLs(m_urlsToImport);
@@ -1240,16 +1269,23 @@ Application::~Application()
void Application::messageReceived(const QByteArray& message)
{
- if (status() != Initialized) {
- qDebug() << "Received message" << message << "while still initializing. It will be ignored.";
- return;
- }
-
ApplicationMessage received;
received.parse(message);
auto& command = received.command;
+ if (status() != Initialized) {
+ bool isLoginAtempt = false;
+ if (command == "import") {
+ QString url = received.args["url"];
+ isLoginAtempt = !url.isEmpty() && normalizeImportUrl(url).scheme() == BuildConfig.LAUNCHER_APP_BINARY_NAME;
+ }
+ if (!isLoginAtempt) {
+ qDebug() << "Received message" << message << "while still initializing. It will be ignored.";
+ return;
+ }
+ }
+
if (command == "activate") {
showMainWindow();
} else if (command == "import") {
@@ -1262,6 +1298,7 @@ void Application::messageReceived(const QByteArray& message)
} else if (command == "launch") {
QString id = received.args["id"];
QString server = received.args["server"];
+ QString world = received.args["world"];
QString profile = received.args["profile"];
InstancePtr instance;
@@ -1276,11 +1313,12 @@ void Application::messageReceived(const QByteArray& message)
return;
}
- MinecraftServerTargetPtr serverObject = nullptr;
+ MinecraftTarget::Ptr serverObject = nullptr;
if (!server.isEmpty()) {
- serverObject = std::make_shared(MinecraftServerTarget::parse(server));
+ serverObject = std::make_shared(MinecraftTarget::parse(server, false));
+ } else if (!world.isEmpty()) {
+ serverObject = std::make_shared(MinecraftTarget::parse(world, true));
}
-
MinecraftAccountPtr accountObject;
if (!profile.isEmpty()) {
accountObject = accounts()->getAccountByProfileName(profile);
@@ -1329,11 +1367,7 @@ bool Application::openJsonEditor(const QString& filename)
}
}
-bool Application::launch(InstancePtr instance,
- bool online,
- bool demo,
- MinecraftServerTargetPtr serverToJoin,
- MinecraftAccountPtr accountToUse)
+bool Application::launch(InstancePtr instance, bool online, bool demo, MinecraftTarget::Ptr targetToJoin, MinecraftAccountPtr accountToUse)
{
if (m_updateRunning) {
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
@@ -1351,7 +1385,7 @@ bool Application::launch(InstancePtr instance,
controller->setOnline(online);
controller->setDemo(demo);
controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get());
- controller->setServerToJoin(serverToJoin);
+ controller->setTargetToJoin(targetToJoin);
controller->setAccountToUse(accountToUse);
if (window) {
controller->setParentWidget(window);
@@ -1669,8 +1703,7 @@ QString Application::getJarPath(QString jarFile)
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
FS::PathCombine(m_rootPath, "share", BuildConfig.LAUNCHER_NAME),
#endif
- FS::PathCombine(m_rootPath, "jars"),
- FS::PathCombine(applicationDirPath(), "jars"),
+ FS::PathCombine(m_rootPath, "jars"), FS::PathCombine(applicationDirPath(), "jars"),
FS::PathCombine(applicationDirPath(), "..", "jars") // from inside build dir, for debuging
};
for (QString p : potentialPaths) {
@@ -1731,20 +1764,6 @@ QString Application::getUserAgentUncached()
return BuildConfig.USER_AGENT_UNCACHED;
}
-int Application::suitableMaxMem()
-{
- float totalRAM = (float)Sys::getSystemRam() / (float)Sys::mebibyte;
- int maxMemoryAlloc;
-
- // If totalRAM < 6GB, use (totalRAM / 1.5), else 4GB
- if (totalRAM < (4096 * 1.5))
- maxMemoryAlloc = (int)(totalRAM / 1.5);
- else
- maxMemoryAlloc = 4096;
-
- return maxMemoryAlloc;
-}
-
bool Application::handleDataMigration(const QString& currentData,
const QString& oldData,
const QString& name,
@@ -1851,3 +1870,8 @@ QUrl Application::normalizeImportUrl(QString const& url)
return QUrl::fromUserInput(url);
}
}
+
+const QString Application::javaPath()
+{
+ return m_settings->get("JavaDir").toString();
+}
diff --git a/launcher/Application.h b/launcher/Application.h
index 7669e08ec..7432c9683 100644
--- a/launcher/Application.h
+++ b/launcher/Application.h
@@ -47,8 +47,7 @@
#include
-#include "minecraft/launch/MinecraftServerTarget.h"
-#include "ui/themes/CatPack.h"
+#include "minecraft/launch/MinecraftTarget.h"
class LaunchController;
class LocalPeer;
@@ -162,6 +161,9 @@ class Application : public QApplication {
/// the data path the application is using
const QString& dataRoot() { return m_dataPath; }
+ /// the java installed path the application is using
+ const QString javaPath();
+
bool isPortable() { return m_portable; }
const Capabilities capabilities() { return m_capabilities; }
@@ -180,8 +182,6 @@ class Application : public QApplication {
void ShowGlobalSettings(class QWidget* parent, QString open_page = QString());
- int suitableMaxMem();
-
bool updaterEnabled();
QString updaterBinaryName();
@@ -193,6 +193,8 @@ class Application : public QApplication {
void globalSettingsClosed();
int currentCatChanged(int index);
+ void oauthReplyRecieved(QVariantMap);
+
#ifdef Q_OS_MACOS
void clickedOnDock();
#endif
@@ -201,7 +203,7 @@ class Application : public QApplication {
bool launch(InstancePtr instance,
bool online = true,
bool demo = false,
- MinecraftServerTargetPtr serverToJoin = nullptr,
+ MinecraftTarget::Ptr targetToJoin = nullptr,
MinecraftAccountPtr accountToUse = nullptr);
bool kill(InstancePtr instance);
void closeCurrentWindow();
@@ -289,6 +291,7 @@ class Application : public QApplication {
QString m_detectedOpenALPath;
QString m_instanceIdToLaunch;
QString m_serverToJoin;
+ QString m_worldToJoin;
QString m_profileToUse;
bool m_liveCheck = false;
QList m_urlsToImport;
diff --git a/launcher/BaseInstaller.cpp b/launcher/BaseInstaller.cpp
index 1ff86ed40..96a3b5ebe 100644
--- a/launcher/BaseInstaller.cpp
+++ b/launcher/BaseInstaller.cpp
@@ -16,6 +16,7 @@
#include
#include "BaseInstaller.h"
+#include "FileSystem.h"
#include "minecraft/MinecraftInstance.h"
BaseInstaller::BaseInstaller() {}
@@ -42,7 +43,7 @@ bool BaseInstaller::add(MinecraftInstance* to)
bool BaseInstaller::remove(MinecraftInstance* from)
{
- return QFile::remove(filename(from->instanceRoot()));
+ return FS::deletePath(filename(from->instanceRoot()));
}
QString BaseInstaller::filename(const QString& root) const
diff --git a/launcher/BaseInstaller.h b/launcher/BaseInstaller.h
index 6244ced7d..1cf7d65f5 100644
--- a/launcher/BaseInstaller.h
+++ b/launcher/BaseInstaller.h
@@ -29,7 +29,7 @@ class BaseVersion;
class BaseInstaller {
public:
BaseInstaller();
- virtual ~BaseInstaller(){};
+ virtual ~BaseInstaller() {};
bool isApplied(MinecraftInstance* on);
virtual bool add(MinecraftInstance* to);
diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp
index cda44b454..69cf95e3c 100644
--- a/launcher/BaseInstance.cpp
+++ b/launcher/BaseInstance.cpp
@@ -269,13 +269,18 @@ void BaseInstance::setRunning(bool running)
m_isRunning = running;
- if (!m_settings->get("RecordGameTime").toBool()) {
- emit runningStatusChanged(running);
+ emit runningStatusChanged(running);
+}
+
+void BaseInstance::setMinecraftRunning(bool running)
+{
+ if (!settings()->get("RecordGameTime").toBool()) {
return;
}
if (running) {
m_timeStarted = QDateTime::currentDateTime();
+ setLastLaunch(m_timeStarted.toMSecsSinceEpoch());
} else {
QDateTime timeEnded = QDateTime::currentDateTime();
@@ -285,8 +290,6 @@ void BaseInstance::setRunning(bool running)
emit propertiesChanged(this);
}
-
- emit runningStatusChanged(running);
}
int64_t BaseInstance::totalTimePlayed() const
diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h
index f4ed9113c..2be28d1ec 100644
--- a/launcher/BaseInstance.h
+++ b/launcher/BaseInstance.h
@@ -56,7 +56,7 @@
#include "net/Mode.h"
#include "RuntimeContext.h"
-#include "minecraft/launch/MinecraftServerTarget.h"
+#include "minecraft/launch/MinecraftTarget.h"
class QDir;
class Task;
@@ -104,6 +104,7 @@ class BaseInstance : public QObject, public std::enable_shared_from_this createUpdateTask() = 0;
/// returns a valid launcher (task container)
- virtual shared_qobject_ptr createLaunchTask(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) = 0;
+ virtual shared_qobject_ptr createLaunchTask(AuthSessionPtr account, MinecraftTarget::Ptr targetToJoin) = 0;
/// returns the current launch task (if any)
shared_qobject_ptr getLaunchTask();
@@ -214,7 +215,7 @@ class BaseInstance : public QObject, public std::enable_shared_from_thistypeString();
+ case JavaMajorRole: {
+ auto major = version->name();
+ if (major.startsWith("java")) {
+ major = "Java " + major.mid(4);
+ }
+ return major;
+ }
+
default:
return QVariant();
}
@@ -110,6 +118,8 @@ QHash BaseVersionList::roleNames() const
roles.insert(TypeRole, "type");
roles.insert(BranchRole, "branch");
roles.insert(PathRole, "path");
- roles.insert(ArchitectureRole, "architecture");
+ roles.insert(JavaNameRole, "javaName");
+ roles.insert(CPUArchitectureRole, "architecture");
+ roles.insert(JavaMajorRole, "javaMajor");
return roles;
}
diff --git a/launcher/BaseVersionList.h b/launcher/BaseVersionList.h
index 231887c4e..673d13562 100644
--- a/launcher/BaseVersionList.h
+++ b/launcher/BaseVersionList.h
@@ -48,7 +48,9 @@ class BaseVersionList : public QAbstractListModel {
TypeRole,
BranchRole,
PathRole,
- ArchitectureRole,
+ JavaNameRole,
+ JavaMajorRole,
+ CPUArchitectureRole,
SortRole
};
using RoleList = QList;
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index bc48abdef..3d1f38c03 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -24,6 +24,8 @@ set(CORE_SOURCES
NullInstance.h
MMCZip.h
MMCZip.cpp
+ Untar.h
+ Untar.cpp
StringUtils.h
StringUtils.cpp
QVariantUtils.h
@@ -126,7 +128,6 @@ set(NET_SOURCES
net/MetaCacheSink.h
net/Logging.h
net/Logging.cpp
- net/NetAction.h
net/NetJob.cpp
net/NetJob.h
net/NetUtils.h
@@ -139,7 +140,6 @@ set(NET_SOURCES
net/HeaderProxy.h
net/RawHeaderProxy.h
net/ApiHeaderProxy.h
- net/StaticHeaderProxy.h
net/ApiDownload.h
net/ApiDownload.cpp
net/ApiUpload.cpp
@@ -160,16 +160,18 @@ set(LAUNCH_SOURCES
launch/steps/PreLaunchCommand.h
launch/steps/TextPrint.cpp
launch/steps/TextPrint.h
- launch/steps/Update.cpp
- launch/steps/Update.h
launch/steps/QuitAfterGameStop.cpp
launch/steps/QuitAfterGameStop.h
+ launch/steps/PrintServers.cpp
+ launch/steps/PrintServers.h
launch/LaunchStep.cpp
launch/LaunchStep.h
launch/LaunchTask.cpp
launch/LaunchTask.h
launch/LogModel.cpp
launch/LogModel.h
+ launch/TaskStepWrapper.cpp
+ launch/TaskStepWrapper.h
)
# Old update system
@@ -205,33 +207,27 @@ set(ICONS_SOURCES
# Support for Minecraft instances and launch
set(MINECRAFT_SOURCES
+
+ # Logging
+ minecraft/Logging.h
+ minecraft/Logging.cpp
+
# Minecraft support
minecraft/auth/AccountData.cpp
minecraft/auth/AccountData.h
minecraft/auth/AccountList.cpp
minecraft/auth/AccountList.h
- minecraft/auth/AccountTask.cpp
- minecraft/auth/AccountTask.h
- minecraft/auth/AuthRequest.cpp
- minecraft/auth/AuthRequest.h
minecraft/auth/AuthSession.cpp
minecraft/auth/AuthSession.h
- minecraft/auth/AuthStep.cpp
minecraft/auth/AuthStep.h
minecraft/auth/MinecraftAccount.cpp
minecraft/auth/MinecraftAccount.h
minecraft/auth/Parsers.cpp
minecraft/auth/Parsers.h
- minecraft/auth/flows/AuthFlow.cpp
- minecraft/auth/flows/AuthFlow.h
- minecraft/auth/flows/MSA.cpp
- minecraft/auth/flows/MSA.h
- minecraft/auth/flows/Offline.cpp
- minecraft/auth/flows/Offline.h
+ minecraft/auth/AuthFlow.cpp
+ minecraft/auth/AuthFlow.h
- minecraft/auth/steps/OfflineStep.cpp
- minecraft/auth/steps/OfflineStep.h
minecraft/auth/steps/EntitlementsStep.cpp
minecraft/auth/steps/EntitlementsStep.h
minecraft/auth/steps/GetSkinStep.cpp
@@ -240,6 +236,8 @@ set(MINECRAFT_SOURCES
minecraft/auth/steps/LauncherLoginStep.h
minecraft/auth/steps/MinecraftProfileStep.cpp
minecraft/auth/steps/MinecraftProfileStep.h
+ minecraft/auth/steps/MSADeviceCodeStep.cpp
+ minecraft/auth/steps/MSADeviceCodeStep.h
minecraft/auth/steps/MSAStep.cpp
minecraft/auth/steps/MSAStep.h
minecraft/auth/steps/XboxAuthorizationStep.cpp
@@ -271,8 +269,8 @@ set(MINECRAFT_SOURCES
minecraft/launch/ExtractNatives.h
minecraft/launch/LauncherPartLaunch.cpp
minecraft/launch/LauncherPartLaunch.h
- minecraft/launch/MinecraftServerTarget.cpp
- minecraft/launch/MinecraftServerTarget.h
+ minecraft/launch/MinecraftTarget.cpp
+ minecraft/launch/MinecraftTarget.h
minecraft/launch/PrintInstanceInfo.cpp
minecraft/launch/PrintInstanceInfo.h
minecraft/launch/ReconstructAssets.cpp
@@ -281,6 +279,8 @@ set(MINECRAFT_SOURCES
minecraft/launch/ScanModFolders.h
minecraft/launch/VerifyJavaInstall.cpp
minecraft/launch/VerifyJavaInstall.h
+ minecraft/launch/AutoInstallJava.cpp
+ minecraft/launch/AutoInstallJava.h
minecraft/GradleSpecifier.h
minecraft/MinecraftInstance.cpp
@@ -295,8 +295,6 @@ set(MINECRAFT_SOURCES
minecraft/ComponentUpdateTask.h
minecraft/MinecraftLoadAndCheck.h
minecraft/MinecraftLoadAndCheck.cpp
- minecraft/MinecraftUpdate.h
- minecraft/MinecraftUpdate.cpp
minecraft/MojangVersionFormat.cpp
minecraft/MojangVersionFormat.h
minecraft/Rule.cpp
@@ -371,13 +369,17 @@ set(MINECRAFT_SOURCES
minecraft/AssetsUtils.h
minecraft/AssetsUtils.cpp
- # Minecraft services
- minecraft/services/CapeChange.cpp
- minecraft/services/CapeChange.h
- minecraft/services/SkinUpload.cpp
- minecraft/services/SkinUpload.h
- minecraft/services/SkinDelete.cpp
- minecraft/services/SkinDelete.h
+ # Minecraft skins
+ minecraft/skins/CapeChange.cpp
+ minecraft/skins/CapeChange.h
+ minecraft/skins/SkinUpload.cpp
+ minecraft/skins/SkinUpload.h
+ minecraft/skins/SkinDelete.cpp
+ minecraft/skins/SkinDelete.h
+ minecraft/skins/SkinModel.cpp
+ minecraft/skins/SkinModel.h
+ minecraft/skins/SkinList.cpp
+ minecraft/skins/SkinList.h
minecraft/Agent.h)
@@ -421,8 +423,6 @@ set(SETTINGS_SOURCES
set(JAVA_SOURCES
java/JavaChecker.h
java/JavaChecker.cpp
- java/JavaCheckerJob.h
- java/JavaCheckerJob.cpp
java/JavaInstall.h
java/JavaInstall.cpp
java/JavaInstallList.h
@@ -431,6 +431,20 @@ set(JAVA_SOURCES
java/JavaUtils.cpp
java/JavaVersion.h
java/JavaVersion.cpp
+
+ java/JavaMetadata.h
+ java/JavaMetadata.cpp
+ java/download/ArchiveDownloadTask.cpp
+ java/download/ArchiveDownloadTask.h
+ java/download/ManifestDownloadTask.cpp
+ java/download/ManifestDownloadTask.h
+ java/download/SymlinkTask.cpp
+ java/download/SymlinkTask.h
+
+ ui/java/InstallJavaDialog.h
+ ui/java/InstallJavaDialog.cpp
+ ui/java/VersionList.h
+ ui/java/VersionList.cpp
)
set(TRANSLATIONS_SOURCES
@@ -452,6 +466,8 @@ set(TOOLS_SOURCES
tools/JVisualVM.h
tools/MCEditTool.cpp
tools/MCEditTool.h
+ tools/GenericProfiler.cpp
+ tools/GenericProfiler.h
)
set(META_SOURCES
@@ -623,7 +639,6 @@ set(PRISMUPDATER_SOURCES
net/HttpMetaCache.h
net/Logging.h
net/Logging.cpp
- net/NetAction.h
net/NetRequest.cpp
net/NetRequest.h
net/NetJob.cpp
@@ -652,6 +667,22 @@ ecm_qt_declare_logging_category(CORE_SOURCES
EXPORT "${Launcher_Name}"
)
+ecm_qt_export_logging_category(
+ IDENTIFIER instanceProfileC
+ CATEGORY_NAME "launcher.instance.profile"
+ DEFAULT_SEVERITY Debug
+ DESCRIPTION "Profile actions"
+ EXPORT "${Launcher_Name}"
+)
+
+ecm_qt_export_logging_category(
+ IDENTIFIER instanceProfileResolveC
+ CATEGORY_NAME "launcher.instance.profile.resolve"
+ DEFAULT_SEVERITY Debug
+ DESCRIPTION "Profile component resolusion actions"
+ EXPORT "${Launcher_Name}"
+)
+
ecm_qt_export_logging_category(
IDENTIFIER taskLogC
CATEGORY_NAME "launcher.task"
@@ -664,7 +695,7 @@ ecm_qt_export_logging_category(
IDENTIFIER taskNetLogC
CATEGORY_NAME "launcher.task.net"
DEFAULT_SEVERITY Debug
- DESCRIPTION "task network action"
+ DESCRIPTION "Task network action"
EXPORT "${Launcher_Name}"
)
@@ -672,14 +703,14 @@ ecm_qt_export_logging_category(
IDENTIFIER taskDownloadLogC
CATEGORY_NAME "launcher.task.net.download"
DEFAULT_SEVERITY Debug
- DESCRIPTION "task network download actions"
+ DESCRIPTION "Task network download actions"
EXPORT "${Launcher_Name}"
)
ecm_qt_export_logging_category(
IDENTIFIER taskUploadLogC
CATEGORY_NAME "launcher.task.net.upload"
DEFAULT_SEVERITY Debug
- DESCRIPTION "task network upload actions"
+ DESCRIPTION "Task network upload actions"
EXPORT "${Launcher_Name}"
)
@@ -749,6 +780,8 @@ SET(LAUNCHER_SOURCES
DataMigrationTask.cpp
ApplicationMessage.h
ApplicationMessage.cpp
+ SysInfo.h
+ SysInfo.cpp
# GUI - general utilities
DesktopServices.h
@@ -787,16 +820,12 @@ SET(LAUNCHER_SOURCES
# GUI - windows
ui/GuiUtil.h
ui/GuiUtil.cpp
- ui/ColorCache.h
- ui/ColorCache.cpp
ui/MainWindow.h
ui/MainWindow.cpp
ui/InstanceWindow.h
ui/InstanceWindow.cpp
# FIXME: maybe find a better home for this.
- SkinUtils.cpp
- SkinUtils.h
FileIgnoreProxy.cpp
FileIgnoreProxy.h
FastFileIconProvider.cpp
@@ -814,6 +843,10 @@ SET(LAUNCHER_SOURCES
ui/setupwizard/PasteWizardPage.h
ui/setupwizard/ThemeWizardPage.cpp
ui/setupwizard/ThemeWizardPage.h
+ ui/setupwizard/AutoJavaWizardPage.cpp
+ ui/setupwizard/AutoJavaWizardPage.h
+ ui/setupwizard/LoginWizardPage.cpp
+ ui/setupwizard/LoginWizardPage.h
# GUI - themes
ui/themes/FusionTheme.cpp
@@ -826,6 +859,8 @@ SET(LAUNCHER_SOURCES
ui/themes/DarkTheme.h
ui/themes/ITheme.cpp
ui/themes/ITheme.h
+ ui/themes/HintOverrideProxyStyle.cpp
+ ui/themes/HintOverrideProxyStyle.h
ui/themes/SystemTheme.cpp
ui/themes/SystemTheme.h
ui/themes/IconTheme.cpp
@@ -1021,8 +1056,6 @@ SET(LAUNCHER_SOURCES
ui/dialogs/ReviewMessageBox.h
ui/dialogs/VersionSelectDialog.cpp
ui/dialogs/VersionSelectDialog.h
- ui/dialogs/SkinUploadDialog.cpp
- ui/dialogs/SkinUploadDialog.h
ui/dialogs/ResourceDownloadDialog.cpp
ui/dialogs/ResourceDownloadDialog.h
ui/dialogs/ScrollMessageBox.cpp
@@ -1036,7 +1069,12 @@ SET(LAUNCHER_SOURCES
ui/dialogs/InstallLoaderDialog.cpp
ui/dialogs/InstallLoaderDialog.h
+ ui/dialogs/skins/SkinManageDialog.cpp
+ ui/dialogs/skins/SkinManageDialog.h
+
# GUI - widgets
+ ui/widgets/CheckComboBox.cpp
+ ui/widgets/CheckComboBox.h
ui/widgets/Common.cpp
ui/widgets/Common.h
ui/widgets/CustomCommands.cpp
@@ -1121,6 +1159,8 @@ endif()
qt_wrap_ui(LAUNCHER_UI
ui/MainWindow.ui
ui/setupwizard/PasteWizardPage.ui
+ ui/setupwizard/AutoJavaWizardPage.ui
+ ui/setupwizard/LoginWizardPage.ui
ui/setupwizard/ThemeWizardPage.ui
ui/pages/global/AccountListPage.ui
ui/pages/global/JavaPage.ui
@@ -1165,7 +1205,6 @@ qt_wrap_ui(LAUNCHER_UI
ui/dialogs/NewComponentDialog.ui
ui/dialogs/NewsDialog.ui
ui/dialogs/ProfileSelectDialog.ui
- ui/dialogs/SkinUploadDialog.ui
ui/dialogs/ExportInstanceDialog.ui
ui/dialogs/ExportPackDialog.ui
ui/dialogs/ExportToModListDialog.ui
@@ -1179,6 +1218,8 @@ qt_wrap_ui(LAUNCHER_UI
ui/dialogs/ScrollMessageBox.ui
ui/dialogs/BlockedModsDialog.ui
ui/dialogs/ChooseProviderDialog.ui
+
+ ui/dialogs/skins/SkinManageDialog.ui
)
qt_wrap_ui(PRISM_UPDATE_UI
@@ -1238,7 +1279,6 @@ target_link_libraries(Launcher_logic
tomlplusplus::tomlplusplus
qdcss
BuildConfig
- Katabasis
Qt${QT_VERSION_MAJOR}::Widgets
ghcFilesystem::ghc_filesystem
)
@@ -1256,6 +1296,7 @@ target_link_libraries(Launcher_logic
Qt${QT_VERSION_MAJOR}::Concurrent
Qt${QT_VERSION_MAJOR}::Gui
Qt${QT_VERSION_MAJOR}::Widgets
+ Qt${QT_VERSION_MAJOR}::NetworkAuth
${Launcher_QT_LIBS}
)
target_link_libraries(Launcher_logic
@@ -1326,7 +1367,6 @@ if(Launcher_BUILD_UPDATER)
Qt${QT_VERSION_MAJOR}::Network
${Launcher_QT_LIBS}
cmark::cmark
- Katabasis
)
add_executable("${Launcher_Name}_updater" WIN32 updater/prismupdater/updater_main.cpp)
@@ -1491,7 +1531,6 @@ if(INSTALL_BUNDLE STREQUAL "full")
CONFIGURATIONS Debug RelWithDebInfo ""
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
- PATTERN "*qopensslbackend*" EXCLUDE
PATTERN "*qcertonlybackend*" EXCLUDE
)
install(
@@ -1502,10 +1541,78 @@ if(INSTALL_BUNDLE STREQUAL "full")
REGEX "dd\\." EXCLUDE
REGEX "_debug\\." EXCLUDE
REGEX "\\.dSYM" EXCLUDE
- PATTERN "*qopensslbackend*" EXCLUDE
PATTERN "*qcertonlybackend*" EXCLUDE
)
endif()
+ # Wayland support
+ if(EXISTS "${QT_PLUGINS_DIR}/wayland-graphics-integration-client")
+ install(
+ DIRECTORY "${QT_PLUGINS_DIR}/wayland-graphics-integration-client"
+ CONFIGURATIONS Debug RelWithDebInfo ""
+ DESTINATION ${PLUGIN_DEST_DIR}
+ COMPONENT Runtime
+ )
+ install(
+ DIRECTORY "${QT_PLUGINS_DIR}/wayland-graphics-integration-client"
+ CONFIGURATIONS Release MinSizeRel
+ DESTINATION ${PLUGIN_DEST_DIR}
+ COMPONENT Runtime
+ REGEX "dd\\." EXCLUDE
+ REGEX "_debug\\." EXCLUDE
+ REGEX "\\.dSYM" EXCLUDE
+ )
+ endif()
+ if(EXISTS "${QT_PLUGINS_DIR}/wayland-graphics-integration-server")
+ install(
+ DIRECTORY "${QT_PLUGINS_DIR}/wayland-graphics-integration-server"
+ CONFIGURATIONS Debug RelWithDebInfo ""
+ DESTINATION ${PLUGIN_DEST_DIR}
+ COMPONENT Runtime
+ )
+ install(
+ DIRECTORY "${QT_PLUGINS_DIR}/wayland-graphics-integration-server"
+ CONFIGURATIONS Release MinSizeRel
+ DESTINATION ${PLUGIN_DEST_DIR}
+ COMPONENT Runtime
+ REGEX "dd\\." EXCLUDE
+ REGEX "_debug\\." EXCLUDE
+ REGEX "\\.dSYM" EXCLUDE
+ )
+ endif()
+ if(EXISTS "${QT_PLUGINS_DIR}/wayland-decoration-client")
+ install(
+ DIRECTORY "${QT_PLUGINS_DIR}/wayland-decoration-client"
+ CONFIGURATIONS Debug RelWithDebInfo ""
+ DESTINATION ${PLUGIN_DEST_DIR}
+ COMPONENT Runtime
+ )
+ install(
+ DIRECTORY "${QT_PLUGINS_DIR}/wayland-decoration-client"
+ CONFIGURATIONS Release MinSizeRel
+ DESTINATION ${PLUGIN_DEST_DIR}
+ COMPONENT Runtime
+ REGEX "dd\\." EXCLUDE
+ REGEX "_debug\\." EXCLUDE
+ REGEX "\\.dSYM" EXCLUDE
+ )
+ endif()
+ if(EXISTS "${QT_PLUGINS_DIR}/wayland-shell-integration")
+ install(
+ DIRECTORY "${QT_PLUGINS_DIR}/wayland-shell-integration"
+ CONFIGURATIONS Debug RelWithDebInfo ""
+ DESTINATION ${PLUGIN_DEST_DIR}
+ COMPONENT Runtime
+ )
+ install(
+ DIRECTORY "${QT_PLUGINS_DIR}/wayland-shell-integration"
+ CONFIGURATIONS Release MinSizeRel
+ DESTINATION ${PLUGIN_DEST_DIR}
+ COMPONENT Runtime
+ REGEX "dd\\." EXCLUDE
+ REGEX "_debug\\." EXCLUDE
+ REGEX "\\.dSYM" EXCLUDE
+ )
+ endif()
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/install_prereqs.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/install_prereqs.cmake"
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index 70704e1d3..7f38cff04 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -276,6 +276,9 @@ bool ensureFolderPathExists(const QFileInfo folderPath)
{
QDir dir;
QString ensuredPath = folderPath.filePath();
+ if (folderPath.exists())
+ return true;
+
bool success = dir.mkpath(ensuredPath);
return success;
}
@@ -647,6 +650,19 @@ void ExternalLinkFileProcess::runLinkFile()
qDebug() << "Process exited";
}
+bool moveByCopy(const QString& source, const QString& dest)
+{
+ if (!copy(source, dest)()) { // copy
+ qDebug() << "Copy of" << source << "to" << dest << "failed!";
+ return false;
+ }
+ if (!deletePath(source)) { // remove original
+ qDebug() << "Deletion of" << source << "failed!";
+ return false;
+ };
+ return true;
+}
+
bool move(const QString& source, const QString& dest)
{
std::error_code err;
@@ -654,13 +670,14 @@ bool move(const QString& source, const QString& dest)
ensureFilePathExists(dest);
fs::rename(StringUtils::toStdString(source), StringUtils::toStdString(dest), err);
- if (err) {
- qWarning() << "Failed to move file:" << QString::fromStdString(err.message());
- qDebug() << "Source file:" << source;
- qDebug() << "Destination file:" << dest;
+ if (err.value() != 0) {
+ if (moveByCopy(source, dest))
+ return true;
+ qDebug() << "Move of" << source << "to" << dest << "failed!";
+ qWarning() << "Failed to move file:" << QString::fromStdString(err.message()) << QString::number(err.value());
+ return false;
}
-
- return err.value() == 0;
+ return true;
}
bool deletePath(QString path)
@@ -801,25 +818,68 @@ QString NormalizePath(QString path)
}
}
-static const QString BAD_PATH_CHARS = "\"?<>:;*|!+\r\n";
-static const QString BAD_FILENAME_CHARS = BAD_PATH_CHARS + "\\/";
+static const QString BAD_WIN_CHARS = "<>:\"|?*\r\n";
+static const QString BAD_NTFS_CHARS = "<>:\"|?*";
+static const QString BAD_HFS_CHARS = ":";
+
+static const QString BAD_FILENAME_CHARS = BAD_WIN_CHARS + "\\/";
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith)
{
for (int i = 0; i < string.length(); i++)
if (string.at(i) < ' ' || BAD_FILENAME_CHARS.contains(string.at(i)))
string[i] = replaceWith;
-
return string;
}
-QString RemoveInvalidPathChars(QString string, QChar replaceWith)
+QString RemoveInvalidPathChars(QString path, QChar replaceWith)
{
- for (int i = 0; i < string.length(); i++)
- if (string.at(i) < ' ' || BAD_PATH_CHARS.contains(string.at(i)))
- string[i] = replaceWith;
+ QString invalidChars;
+#ifdef Q_OS_WIN
+ invalidChars = BAD_WIN_CHARS;
+#endif
- return string;
+ // the null character is ignored in this check as it was not a problem until now
+ switch (statFS(path).fsType) {
+ case FilesystemType::FAT: // similar to NTFS
+ /* fallthrough */
+ case FilesystemType::NTFS:
+ /* fallthrough */
+ case FilesystemType::REFS: // similar to NTFS(should be available only on windows)
+ invalidChars += BAD_NTFS_CHARS;
+ break;
+ // case FilesystemType::EXT:
+ // case FilesystemType::EXT_2_OLD:
+ // case FilesystemType::EXT_2_3_4:
+ // case FilesystemType::XFS:
+ // case FilesystemType::BTRFS:
+ // case FilesystemType::NFS:
+ // case FilesystemType::ZFS:
+ case FilesystemType::APFS:
+ /* fallthrough */
+ case FilesystemType::HFS:
+ /* fallthrough */
+ case FilesystemType::HFSPLUS:
+ /* fallthrough */
+ case FilesystemType::HFSX:
+ invalidChars += BAD_HFS_CHARS;
+ break;
+ // case FilesystemType::FUSEBLK:
+ // case FilesystemType::F2FS:
+ // case FilesystemType::UNKNOWN:
+ default:
+ break;
+ }
+
+ if (invalidChars.size() != 0) {
+ for (int i = 0; i < path.length(); i++) {
+ if (path.at(i) < ' ' || invalidChars.contains(path.at(i))) {
+ path[i] = replaceWith;
+ }
+ }
+ }
+
+ return path;
}
QString DirNameFromString(QString string, QString inDir)
@@ -861,6 +921,10 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
if (destination.isEmpty()) {
destination = PathCombine(getDesktopDir(), RemoveInvalidFilenameChars(name));
}
+ if (!ensureFilePathExists(destination)) {
+ qWarning() << "Destination path can't be created!";
+ return false;
+ }
#if defined(Q_OS_MACOS)
// Create the Application
QDir applicationDirectory =
@@ -1634,4 +1698,30 @@ QString getPathNameInLocal8bit(const QString& file)
}
#endif
+QString getUniqueResourceName(const QString& filePath)
+{
+ auto newFileName = filePath;
+ if (!newFileName.endsWith(".disabled")) {
+ return newFileName; // prioritize enabled mods
+ }
+ newFileName.chop(9);
+ if (!QFile::exists(newFileName)) {
+ return filePath;
+ }
+ QFileInfo fileInfo(filePath);
+ auto baseName = fileInfo.completeBaseName();
+ auto path = fileInfo.absolutePath();
+
+ int counter = 1;
+ do {
+ if (counter == 1) {
+ newFileName = FS::PathCombine(path, baseName + ".duplicate");
+ } else {
+ newFileName = FS::PathCombine(path, baseName + ".duplicate" + QString::number(counter));
+ }
+ counter++;
+ } while (QFile::exists(newFileName));
+
+ return newFileName;
+}
} // namespace FS
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index 5496c3795..c5beef7bd 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -72,7 +72,7 @@ void appendSafe(const QString& filename, const QByteArray& data);
void append(const QString& filename, const QByteArray& data);
/**
- * read data from a file safely\
+ * read data from a file safely
*/
QByteArray read(const QString& filename);
@@ -240,6 +240,7 @@ class create_link : public QObject {
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
int totalLinked() { return m_linked; }
+ int totalToLink() { return static_cast(m_links_to_make.size()); }
void runPrivileged() { runPrivileged(QString()); }
void runPrivileged(const QString& offset);
@@ -378,6 +379,7 @@ enum class FilesystemType {
HFSX,
FUSEBLK,
F2FS,
+ BCACHEFS,
UNKNOWN
};
@@ -406,6 +408,7 @@ static const QMap s_filesystem_type_names = { { Fil
{ FilesystemType::HFSX, { "HFSX" } },
{ FilesystemType::FUSEBLK, { "FUSEBLK" } },
{ FilesystemType::F2FS, { "F2FS" } },
+ { FilesystemType::BCACHEFS, { "BCACHEFS" } },
{ FilesystemType::UNKNOWN, { "UNKNOWN" } } };
/**
@@ -458,7 +461,7 @@ QString nearestExistentAncestor(const QString& path);
FilesystemInfo statFS(const QString& path);
static const QList s_clone_filesystems = { FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS,
- FilesystemType::XFS, FilesystemType::REFS };
+ FilesystemType::XFS, FilesystemType::REFS, FilesystemType::BCACHEFS };
/**
* @brief if the Filesystem is reflink/clone capable
@@ -557,4 +560,6 @@ uintmax_t hardLinkCount(const QString& path);
QString getPathNameInLocal8bit(const QString& file);
#endif
+QString getUniqueResourceName(const QString& filePath);
+
} // namespace FS
diff --git a/launcher/Filter.cpp b/launcher/Filter.cpp
index fc1c42344..adeb2209e 100644
--- a/launcher/Filter.cpp
+++ b/launcher/Filter.cpp
@@ -1,16 +1,12 @@
#include "Filter.h"
-Filter::~Filter() {}
-
ContainsFilter::ContainsFilter(const QString& pattern) : pattern(pattern) {}
-ContainsFilter::~ContainsFilter() {}
bool ContainsFilter::accepts(const QString& value)
{
return value.contains(pattern);
}
ExactFilter::ExactFilter(const QString& pattern) : pattern(pattern) {}
-ExactFilter::~ExactFilter() {}
bool ExactFilter::accepts(const QString& value)
{
return value == pattern;
@@ -27,10 +23,15 @@ RegexpFilter::RegexpFilter(const QString& regexp, bool invert) : invert(invert)
pattern.setPattern(regexp);
pattern.optimize();
}
-RegexpFilter::~RegexpFilter() {}
bool RegexpFilter::accepts(const QString& value)
{
auto match = pattern.match(value);
bool matched = match.hasMatch();
return invert ? (!matched) : (matched);
}
+
+ExactListFilter::ExactListFilter(const QStringList& pattern) : m_pattern(pattern) {}
+bool ExactListFilter::accepts(const QString& value)
+{
+ return m_pattern.isEmpty() || m_pattern.contains(value);
+}
\ No newline at end of file
diff --git a/launcher/Filter.h b/launcher/Filter.h
index 089c844d4..ae835e724 100644
--- a/launcher/Filter.h
+++ b/launcher/Filter.h
@@ -5,14 +5,14 @@
class Filter {
public:
- virtual ~Filter();
+ virtual ~Filter() = default;
virtual bool accepts(const QString& value) = 0;
};
class ContainsFilter : public Filter {
public:
ContainsFilter(const QString& pattern);
- virtual ~ContainsFilter();
+ virtual ~ContainsFilter() = default;
bool accepts(const QString& value) override;
private:
@@ -22,7 +22,7 @@ class ContainsFilter : public Filter {
class ExactFilter : public Filter {
public:
ExactFilter(const QString& pattern);
- virtual ~ExactFilter();
+ virtual ~ExactFilter() = default;
bool accepts(const QString& value) override;
private:
@@ -32,7 +32,7 @@ class ExactFilter : public Filter {
class ExactIfPresentFilter : public Filter {
public:
ExactIfPresentFilter(const QString& pattern);
- ~ExactIfPresentFilter() override = default;
+ virtual ~ExactIfPresentFilter() override = default;
bool accepts(const QString& value) override;
private:
@@ -42,10 +42,20 @@ class ExactIfPresentFilter : public Filter {
class RegexpFilter : public Filter {
public:
RegexpFilter(const QString& regexp, bool invert);
- virtual ~RegexpFilter();
+ virtual ~RegexpFilter() = default;
bool accepts(const QString& value) override;
private:
QRegularExpression pattern;
bool invert = false;
};
+
+class ExactListFilter : public Filter {
+ public:
+ ExactListFilter(const QStringList& pattern = {});
+ virtual ~ExactListFilter() = default;
+ bool accepts(const QString& value) override;
+
+ private:
+ QStringList m_pattern;
+};
diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp
index 52eb7d879..0220a4144 100644
--- a/launcher/InstanceCopyTask.cpp
+++ b/launcher/InstanceCopyTask.cpp
@@ -1,10 +1,12 @@
#include "InstanceCopyTask.h"
#include
#include
+#include
#include "FileSystem.h"
#include "NullInstance.h"
#include "pathmatcher/RegexpMatcher.h"
#include "settings/INISettingsObject.h"
+#include "tasks/Task.h"
InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs)
{
@@ -38,38 +40,50 @@ void InstanceCopyTask::executeTask()
{
setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
- auto copySaves = [&]() {
- QFileInfo mcDir(FS::PathCombine(m_stagingPath, "minecraft"));
- QFileInfo dotMCDir(FS::PathCombine(m_stagingPath, ".minecraft"));
-
- QString staging_mc_dir;
- if (dotMCDir.exists() && !mcDir.exists())
- staging_mc_dir = dotMCDir.filePath();
- else
- staging_mc_dir = mcDir.filePath();
-
- FS::copy savesCopy(FS::PathCombine(m_origInstance->gameRoot(), "saves"), FS::PathCombine(staging_mc_dir, "saves"));
- savesCopy.followSymlinks(true);
-
- return savesCopy();
- };
-
- m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this, copySaves] {
+ m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] {
if (m_useClone) {
FS::clone folderClone(m_origInstance->instanceRoot(), m_stagingPath);
folderClone.matcher(m_matcher.get());
+ folderClone(true);
+ setProgress(0, folderClone.totalCloned());
+ connect(&folderClone, &FS::clone::fileCloned,
+ [this](QString src, QString dst) { setProgress(m_progress + 1, m_progressTotal); });
return folderClone();
- } else if (m_useLinks || m_useHardLinks) {
+ }
+ if (m_useLinks || m_useHardLinks) {
+ std::unique_ptr savesCopy;
+ if (m_copySaves) {
+ QFileInfo mcDir(FS::PathCombine(m_stagingPath, "minecraft"));
+ QFileInfo dotMCDir(FS::PathCombine(m_stagingPath, ".minecraft"));
+
+ QString staging_mc_dir;
+ if (dotMCDir.exists() && !mcDir.exists())
+ staging_mc_dir = dotMCDir.filePath();
+ else
+ staging_mc_dir = mcDir.filePath();
+
+ savesCopy = std::make_unique(FS::PathCombine(m_origInstance->gameRoot(), "saves"),
+ FS::PathCombine(staging_mc_dir, "saves"));
+ savesCopy->followSymlinks(true);
+ (*savesCopy)(true);
+ setProgress(0, savesCopy->totalCopied());
+ connect(savesCopy.get(), &FS::copy::fileCopied, [this](QString src) { setProgress(m_progress + 1, m_progressTotal); });
+ }
FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath);
int depth = m_linkRecursively ? -1 : 0; // we need to at least link the top level instead of the instance folder
folderLink.linkRecursively(true).setMaxDepth(depth).useHardLinks(m_useHardLinks).matcher(m_matcher.get());
+ folderLink(true);
+ setProgress(0, m_progressTotal + folderLink.totalToLink());
+ connect(&folderLink, &FS::create_link::fileLinked,
+ [this](QString src, QString dst) { setProgress(m_progress + 1, m_progressTotal); });
bool there_were_errors = false;
if (!folderLink()) {
#if defined Q_OS_WIN32
if (!m_useHardLinks) {
+ setProgress(0, m_progressTotal);
qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks";
qDebug() << "attempting to run with privelage";
@@ -94,13 +108,11 @@ void InstanceCopyTask::executeTask()
}
}
- if (m_copySaves) {
- there_were_errors |= !copySaves();
+ if (savesCopy) {
+ there_were_errors |= !(*savesCopy)();
}
return got_priv_results && !there_were_errors;
- } else {
- qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str();
}
#else
qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str();
@@ -108,17 +120,19 @@ void InstanceCopyTask::executeTask()
return false;
}
- if (m_copySaves) {
- there_were_errors |= !copySaves();
+ if (savesCopy) {
+ there_were_errors |= !(*savesCopy)();
}
return !there_were_errors;
- } else {
- FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
- folderCopy.followSymlinks(false).matcher(m_matcher.get());
-
- return folderCopy();
}
+ FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
+ folderCopy.followSymlinks(false).matcher(m_matcher.get());
+
+ folderCopy(true);
+ setProgress(0, folderCopy.totalCopied());
+ connect(&folderCopy, &FS::copy::fileCopied, [this](QString src) { setProgress(m_progress + 1, m_progressTotal); });
+ return folderCopy();
});
connect(&m_copyFutureWatcher, &QFutureWatcher::finished, this, &InstanceCopyTask::copyFinished);
connect(&m_copyFutureWatcher, &QFutureWatcher::canceled, this, &InstanceCopyTask::copyAborted);
@@ -159,7 +173,11 @@ void InstanceCopyTask::copyFinished()
allowed_symlinks_file
.filePath()); // we dont want to modify the original. also make sure the resulting file is not itself a link.
- FS::write(allowed_symlinks_file.filePath(), allowed_symlinks);
+ try {
+ FS::write(allowed_symlinks_file.filePath(), allowed_symlinks);
+ } catch (const FS::FileSystemException& e) {
+ qCritical() << "Failed to write symlink :" << e.cause();
+ }
}
emitSucceeded();
@@ -170,3 +188,14 @@ void InstanceCopyTask::copyAborted()
emitFailed(tr("Instance folder copy has been aborted."));
return;
}
+
+bool InstanceCopyTask::abort()
+{
+ if (m_copyFutureWatcher.isRunning()) {
+ m_copyFutureWatcher.cancel();
+ // NOTE: Here we don't do `emitAborted()` because it will be done when `m_copyFutureWatcher` actually cancels, which may not occur
+ // immediately.
+ return true;
+ }
+ return false;
+}
\ No newline at end of file
diff --git a/launcher/InstanceCopyTask.h b/launcher/InstanceCopyTask.h
index 357c6df0b..0f7f1020d 100644
--- a/launcher/InstanceCopyTask.h
+++ b/launcher/InstanceCopyTask.h
@@ -19,6 +19,7 @@ class InstanceCopyTask : public InstanceTask {
protected:
//! Entry point for tasks.
virtual void executeTask() override;
+ bool abort() override;
void copyFinished();
void copyAborted();
diff --git a/launcher/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp
index 73dc17891..9c17dfc9f 100644
--- a/launcher/InstanceCreationTask.cpp
+++ b/launcher/InstanceCreationTask.cpp
@@ -2,8 +2,7 @@
#include
#include
-
-InstanceCreationTask::InstanceCreationTask() = default;
+#include "FileSystem.h"
void InstanceCreationTask::executeTask()
{
@@ -47,7 +46,7 @@ void InstanceCreationTask::executeTask()
if (!QFile::exists(path))
continue;
qDebug() << "Removing" << path;
- if (!QFile::remove(path)) {
+ if (!FS::deletePath(path)) {
qCritical() << "Couldn't remove the old conflicting files.";
emitFailed(tr("Failed to remove old conflicting files."));
return;
diff --git a/launcher/InstanceCreationTask.h b/launcher/InstanceCreationTask.h
index 380fdf8a4..84fb2a145 100644
--- a/launcher/InstanceCreationTask.h
+++ b/launcher/InstanceCreationTask.h
@@ -6,7 +6,7 @@
class InstanceCreationTask : public InstanceTask {
Q_OBJECT
public:
- InstanceCreationTask();
+ InstanceCreationTask() = default;
virtual ~InstanceCreationTask() = default;
protected:
diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp
index d4676f358..57cc77527 100644
--- a/launcher/InstanceImportTask.cpp
+++ b/launcher/InstanceImportTask.cpp
@@ -56,6 +56,7 @@
#include
#include
+#include
#include
@@ -68,15 +69,8 @@ bool InstanceImportTask::abort()
if (!canAbort())
return false;
- if (m_filesNetJob)
- m_filesNetJob->abort();
- if (m_extractFuture.isRunning()) {
- // NOTE: The tasks created by QtConcurrent::run() can't actually get cancelled,
- // but we can use this call to check the state when the extraction finishes.
- m_extractFuture.cancel();
- m_extractFuture.waitForFinished();
- }
-
+ if (task)
+ task->abort();
return Task::abort();
}
@@ -89,7 +83,6 @@ void InstanceImportTask::executeTask()
processZipPack();
} else {
setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString()));
- m_downloadRequired = true;
downloadFromUrl();
}
@@ -97,115 +90,132 @@ void InstanceImportTask::executeTask()
void InstanceImportTask::downloadFromUrl()
{
- const QString path = m_sourceUrl.host() + '/' + m_sourceUrl.path();
+ 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();
+ auto filesNetJob = makeShared(tr("Modpack download"), APPLICATION->network());
+ filesNetJob->addNetAction(Net::ApiDownload::makeCached(m_sourceUrl, entry));
+
+ connect(filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::processZipPack);
+ connect(filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::setProgress);
+ connect(filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propagateStepProgress);
+ connect(filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::emitFailed);
+ connect(filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::emitAborted);
+ task.reset(filesNetJob);
+ filesNetJob->start();
}
-void InstanceImportTask::downloadSucceeded()
+QString InstanceImportTask::getRootFromZip(QuaZip* zip, const QString& root)
{
- processZipPack();
- m_filesNetJob.reset();
-}
+ if (!isRunning()) {
+ return {};
+ }
+ QuaZipDir rootDir(zip, root);
+ for (auto&& fileName : rootDir.entryList(QDir::Files)) {
+ setDetails(fileName);
+ if (fileName == "instance.cfg") {
+ qDebug() << "MultiMC:" << true;
+ m_modpackType = ModpackType::MultiMC;
+ return root;
+ }
+ if (fileName == "manifest.json") {
+ qDebug() << "Flame:" << true;
+ m_modpackType = ModpackType::Flame;
+ return root;
+ }
-void InstanceImportTask::downloadFailed(QString reason)
-{
- emitFailed(reason);
- m_filesNetJob.reset();
-}
+ QCoreApplication::processEvents();
+ }
-void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total)
-{
- setProgress(current, total);
-}
+ // Recurse the search to non-ignored subfolders
+ for (auto&& fileName : rootDir.entryList(QDir::Dirs)) {
+ if ("overrides/" == fileName)
+ continue;
-void InstanceImportTask::downloadAborted()
-{
- emitAborted();
- m_filesNetJob.reset();
+ QString result = getRootFromZip(zip, root + fileName);
+ if (!result.isEmpty())
+ return result;
+ }
+
+ return {};
}
void InstanceImportTask::processZipPack()
{
- setStatus(tr("Extracting modpack"));
+ setStatus(tr("Attempting to determine instance type"));
QDir extractDir(m_stagingPath);
qDebug() << "Attempting to create instance from" << m_archivePath;
// open the zip and find relevant files in it
- m_packZip.reset(new QuaZip(m_archivePath));
- if (!m_packZip->open(QuaZip::mdUnzip)) {
+ auto packZip = std::make_shared(m_archivePath);
+ if (!packZip->open(QuaZip::mdUnzip)) {
emitFailed(tr("Unable to open supplied modpack zip file."));
return;
}
- QuaZipDir packZipDir(m_packZip.get());
+ QuaZipDir packZipDir(packZip.get());
+ qDebug() << "Attempting to determine instance type";
- // https://docs.modrinth.com/docs/modpacks/format_definition/#storage
- bool modrinthFound = packZipDir.exists("/modrinth.index.json");
- bool technicFound = packZipDir.exists("/bin/modpack.jar") || packZipDir.exists("/bin/version.json");
QString root;
// NOTE: Prioritize modpack platforms that aren't searched for recursively.
// Especially Flame has a very common filename for its manifest, which may appear inside overrides for example
- if (modrinthFound) {
+ // https://docs.modrinth.com/docs/modpacks/format_definition/#storage
+ if (packZipDir.exists("/modrinth.index.json")) {
// process as Modrinth pack
- qDebug() << "Modrinth:" << modrinthFound;
+ qDebug() << "Modrinth:" << true;
m_modpackType = ModpackType::Modrinth;
- } else if (technicFound) {
+ } else if (packZipDir.exists("/bin/modpack.jar") || packZipDir.exists("/bin/version.json")) {
// process as Technic pack
- qDebug() << "Technic:" << technicFound;
+ qDebug() << "Technic:" << true;
extractDir.mkpath("minecraft");
extractDir.cd("minecraft");
m_modpackType = ModpackType::Technic;
} else {
- QStringList paths_to_ignore{ "overrides/" };
-
- if (QString mmcRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg", paths_to_ignore); !mmcRoot.isNull()) {
- // process as MultiMC instance/pack
- qDebug() << "MultiMC:" << mmcRoot;
- root = mmcRoot;
- m_modpackType = ModpackType::MultiMC;
- } else if (QString flameRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json", paths_to_ignore);
- !flameRoot.isNull()) {
- // process as Flame pack
- qDebug() << "Flame:" << flameRoot;
- root = flameRoot;
- m_modpackType = ModpackType::Flame;
- }
+ root = getRootFromZip(packZip.get());
+ setDetails("");
}
if (m_modpackType == ModpackType::Unknown) {
emitFailed(tr("Archive does not contain a recognized modpack type."));
return;
}
+ setStatus(tr("Extracting modpack"));
// make sure we extract just the pack
- m_extractFuture =
- QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), root, extractDir.absolutePath());
- connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, &InstanceImportTask::extractFinished);
- m_extractFutureWatcher.setFuture(m_extractFuture);
+ auto zipTask = makeShared(packZip, extractDir, root);
+
+ auto progressStep = std::make_shared();
+ connect(zipTask.get(), &Task::finished, this, [this, progressStep] {
+ progressStep->state = TaskStepState::Succeeded;
+ stepProgress(*progressStep);
+ });
+
+ connect(zipTask.get(), &Task::succeeded, this, &InstanceImportTask::extractFinished);
+ connect(zipTask.get(), &Task::aborted, this, &InstanceImportTask::emitAborted);
+ connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) {
+ progressStep->state = TaskStepState::Failed;
+ stepProgress(*progressStep);
+ emitFailed(reason);
+ });
+ connect(zipTask.get(), &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress);
+
+ connect(zipTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) {
+ progressStep->update(current, total);
+ stepProgress(*progressStep);
+ });
+ connect(zipTask.get(), &Task::status, this, [this, progressStep](QString status) {
+ progressStep->status = status;
+ stepProgress(*progressStep);
+ });
+ task.reset(zipTask);
+ zipTask->start();
}
void InstanceImportTask::extractFinished()
{
- m_packZip.reset();
-
- if (m_extractFuture.isCanceled())
- return;
- if (!m_extractFuture.result().has_value()) {
- emitFailed(tr("Failed to extract modpack"));
- return;
- }
-
QDir extractDir(m_stagingPath);
qDebug() << "Fixing permissions for extracted pack files...";
@@ -324,13 +334,15 @@ void InstanceImportTask::processMultiMC()
m_instIcon = instance.iconKey();
auto importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), m_instIcon);
+ if (importIconPath.isNull() || !QFile::exists(importIconPath))
+ importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), "icon.png");
if (!importIconPath.isNull() && QFile::exists(importIconPath)) {
// import icon
auto iconList = APPLICATION->icons();
if (iconList->iconFileExists(m_instIcon)) {
iconList->deleteIcon(m_instIcon);
}
- iconList->installIcons({ importIconPath });
+ iconList->installIcon(importIconPath, m_instIcon);
}
}
emitSucceeded();
diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h
index 28efd7ec5..cf86af4ea 100644
--- a/launcher/InstanceImportTask.h
+++ b/launcher/InstanceImportTask.h
@@ -39,11 +39,8 @@
#include
#include
#include "InstanceTask.h"
-#include "QObjectPtr.h"
-#include "modplatform/flame/PackManifest.h"
-#include "net/NetJob.h"
-#include "settings/SettingsObject.h"
+#include
#include
class QuaZip;
@@ -54,35 +51,26 @@ class InstanceImportTask : public InstanceTask {
explicit InstanceImportTask(const QUrl& sourceUrl, QWidget* parent = nullptr, QMap&& extra_info = {});
bool abort() override;
- const QVector& getBlockedFiles() const { return m_blockedMods; }
protected:
//! Entry point for tasks.
virtual void executeTask() override;
private:
- void processZipPack();
void processMultiMC();
void processTechnic();
void processFlame();
void processModrinth();
+ QString getRootFromZip(QuaZip* zip, const QString& root = "");
private slots:
- void downloadSucceeded();
- void downloadFailed(QString reason);
- void downloadProgressChanged(qint64 current, qint64 total);
- void downloadAborted();
+ void processZipPack();
void extractFinished();
private: /* data */
- NetJob::Ptr m_filesNetJob;
QUrl m_sourceUrl;
QString m_archivePath;
- bool m_downloadRequired = false;
- std::unique_ptr m_packZip;
- QFuture> m_extractFuture;
- QFutureWatcher> m_extractFutureWatcher;
- QVector m_blockedMods;
+ Task::Ptr task;
enum class ModpackType {
Unknown,
MultiMC,
diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp
index 5e4abf020..e1fa755dd 100644
--- a/launcher/InstanceList.cpp
+++ b/launcher/InstanceList.cpp
@@ -372,13 +372,13 @@ void InstanceList::undoTrashInstance()
auto top = m_trashHistory.pop();
- while (QDir(top.polyPath).exists()) {
+ while (QDir(top.path).exists()) {
top.id += "1";
- top.polyPath += "1";
+ top.path += "1";
}
- qDebug() << "Moving" << top.trashPath << "back to" << top.polyPath;
- QFile(top.trashPath).rename(top.polyPath);
+ qDebug() << "Moving" << top.trashPath << "back to" << top.path;
+ QFile(top.trashPath).rename(top.path);
m_instanceGroupIndex[top.id] = top.groupName;
increaseGroupCount(top.groupName);
@@ -635,8 +635,8 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id)
QString inst_type = instanceSettings->get("InstanceType").toString();
- // NOTE: Some PolyMC versions didn't save the InstanceType properly. We will just bank on the probability that this is probably a OneSix
- // instance
+ // NOTE: Some launcher versions didn't save the InstanceType properly. We will just bank on the probability that this is probably a
+ // OneSix instance
if (inst_type == "OneSix" || inst_type.isEmpty()) {
inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot));
} else {
@@ -710,6 +710,12 @@ void InstanceList::saveGroupList()
groupsArr.insert(name, groupObj);
}
toplevel.insert("groups", groupsArr);
+ // empty string represents ungrouped "group"
+ if (m_collapsedGroups.contains("")) {
+ QJsonObject ungrouped;
+ ungrouped.insert("hidden", QJsonValue(true));
+ toplevel.insert("ungrouped", ungrouped);
+ }
QJsonDocument doc(toplevel);
try {
FS::write(groupFileName, doc.toJson());
@@ -805,6 +811,16 @@ void InstanceList::loadGroupList()
increaseGroupCount(groupName);
}
}
+
+ bool ungroupedHidden = false;
+ if (rootObj.value("ungrouped").isObject()) {
+ QJsonObject ungrouped = rootObj.value("ungrouped").toObject();
+ ungroupedHidden = ungrouped.value("hidden").toBool(false);
+ }
+ if (ungroupedHidden) {
+ // empty string represents ungrouped "group"
+ m_collapsedGroups.insert("");
+ }
m_groupsLoaded = true;
qDebug() << "Group list loaded.";
}
@@ -972,7 +988,6 @@ bool InstanceList::commitStagedInstance(const QString& path,
if (groupName.isEmpty() && !groupName.isNull())
groupName = QString();
- QDir dir;
QString instID;
InstancePtr inst;
@@ -996,7 +1011,7 @@ bool InstanceList::commitStagedInstance(const QString& path,
return false;
}
} else {
- if (!dir.rename(path, destination)) {
+ if (!FS::move(path, destination)) {
qWarning() << "Failed to move" << path << "to" << destination;
return false;
}
diff --git a/launcher/InstanceList.h b/launcher/InstanceList.h
index 5ddddee95..c85fe55c7 100644
--- a/launcher/InstanceList.h
+++ b/launcher/InstanceList.h
@@ -58,7 +58,7 @@ enum class GroupsState { NotLoaded, Steady, Dirty };
struct TrashHistoryItem {
QString id;
- QString polyPath;
+ QString path;
QString trashPath;
QString groupName;
};
diff --git a/launcher/InstancePageProvider.h b/launcher/InstancePageProvider.h
index 66d2b6750..174041f89 100644
--- a/launcher/InstancePageProvider.h
+++ b/launcher/InstancePageProvider.h
@@ -22,7 +22,7 @@ class InstancePageProvider : protected QObject, public BasePageProvider {
public:
explicit InstancePageProvider(InstancePtr parent) { inst = parent; }
- virtual ~InstancePageProvider(){};
+ virtual ~InstancePageProvider() = default;
virtual QList getPages() override
{
QList values;
@@ -39,7 +39,7 @@ class InstancePageProvider : protected QObject, public BasePageProvider {
values.append(new TexturePackPage(onesix.get(), onesix->texturePackList()));
values.append(new ShaderPackPage(onesix.get(), onesix->shaderPackList()));
values.append(new NotesPage(onesix.get()));
- values.append(new WorldListPage(onesix.get(), onesix->worldList()));
+ values.append(new WorldListPage(onesix, onesix->worldList()));
values.append(new ServersPage(onesix));
// values.append(new GameOptionsPage(onesix.get()));
values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots")));
diff --git a/launcher/InstanceTask.cpp b/launcher/InstanceTask.cpp
index 53476897c..be10bbe07 100644
--- a/launcher/InstanceTask.cpp
+++ b/launcher/InstanceTask.cpp
@@ -1,5 +1,7 @@
#include "InstanceTask.h"
+#include "Application.h"
+#include "settings/SettingsObject.h"
#include "ui/dialogs/CustomMessageBox.h"
#include
@@ -22,6 +24,9 @@ InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& ol
ShouldUpdate askIfShouldUpdate(QWidget* parent, QString original_version_name)
{
+ if (APPLICATION->settings()->get("SkipModpackUpdatePrompt").toBool())
+ return ShouldUpdate::SkipUpdating;
+
auto info = CustomMessageBox::selectable(
parent, QObject::tr("Similar modpack was found!"),
QObject::tr(
diff --git a/launcher/JavaCommon.cpp b/launcher/JavaCommon.cpp
index e16ac9255..3cbf9f9d5 100644
--- a/launcher/JavaCommon.cpp
+++ b/launcher/JavaCommon.cpp
@@ -63,7 +63,7 @@ bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget* parent)
return true;
}
-void JavaCommon::javaWasOk(QWidget* parent, const JavaCheckResult& result)
+void JavaCommon::javaWasOk(QWidget* parent, const JavaChecker::Result& result)
{
QString text;
text += QObject::tr(
@@ -79,7 +79,7 @@ void JavaCommon::javaWasOk(QWidget* parent, const JavaCheckResult& result)
CustomMessageBox::selectable(parent, QObject::tr("Java test success"), text, QMessageBox::Information)->show();
}
-void JavaCommon::javaArgsWereBad(QWidget* parent, const JavaCheckResult& result)
+void JavaCommon::javaArgsWereBad(QWidget* parent, const JavaChecker::Result& result)
{
auto htmlError = result.errorLog;
QString text;
@@ -89,7 +89,7 @@ void JavaCommon::javaArgsWereBad(QWidget* parent, const JavaCheckResult& result)
CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show();
}
-void JavaCommon::javaBinaryWasBad(QWidget* parent, const JavaCheckResult& result)
+void JavaCommon::javaBinaryWasBad(QWidget* parent, const JavaChecker::Result& result)
{
QString text;
text += QObject::tr(
@@ -116,34 +116,26 @@ void JavaCommon::TestCheck::run()
emit finished();
return;
}
- checker.reset(new JavaChecker());
+ checker.reset(new JavaChecker(m_path, "", 0, 0, 0, 0, this));
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinished);
- checker->m_path = m_path;
- checker->performCheck();
+ checker->start();
}
-void JavaCommon::TestCheck::checkFinished(JavaCheckResult result)
+void JavaCommon::TestCheck::checkFinished(const JavaChecker::Result& result)
{
- if (result.validity != JavaCheckResult::Validity::Valid) {
+ if (result.validity != JavaChecker::Result::Validity::Valid) {
javaBinaryWasBad(m_parent, result);
emit finished();
return;
}
- checker.reset(new JavaChecker());
+ checker.reset(new JavaChecker(m_path, m_args, m_maxMem, m_maxMem, result.javaVersion.requiresPermGen() ? m_permGen : 0, 0, this));
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinishedWithArgs);
- checker->m_path = m_path;
- checker->m_args = m_args;
- checker->m_minMem = m_minMem;
- checker->m_maxMem = m_maxMem;
- if (result.javaVersion.requiresPermGen()) {
- checker->m_permGen = m_permGen;
- }
- checker->performCheck();
+ checker->start();
}
-void JavaCommon::TestCheck::checkFinishedWithArgs(JavaCheckResult result)
+void JavaCommon::TestCheck::checkFinishedWithArgs(const JavaChecker::Result& result)
{
- if (result.validity == JavaCheckResult::Validity::Valid) {
+ if (result.validity == JavaChecker::Result::Validity::Valid) {
javaWasOk(m_parent, result);
emit finished();
return;
diff --git a/launcher/JavaCommon.h b/launcher/JavaCommon.h
index c96f7a985..a21b5a494 100644
--- a/launcher/JavaCommon.h
+++ b/launcher/JavaCommon.h
@@ -10,11 +10,11 @@ namespace JavaCommon {
bool checkJVMArgs(QString args, QWidget* parent);
// Show a dialog saying that the Java binary was usable
-void javaWasOk(QWidget* parent, const JavaCheckResult& result);
+void javaWasOk(QWidget* parent, const JavaChecker::Result& result);
// Show a dialog saying that the Java binary was not usable because of bad options
-void javaArgsWereBad(QWidget* parent, const JavaCheckResult& result);
+void javaArgsWereBad(QWidget* parent, const JavaChecker::Result& result);
// Show a dialog saying that the Java binary was not usable
-void javaBinaryWasBad(QWidget* parent, const JavaCheckResult& result);
+void javaBinaryWasBad(QWidget* parent, const JavaChecker::Result& result);
// Show a dialog if we couldn't find Java Checker
void javaCheckNotFound(QWidget* parent);
@@ -24,7 +24,7 @@ class TestCheck : public QObject {
TestCheck(QWidget* parent, QString path, QString args, int minMem, int maxMem, int permGen)
: m_parent(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen)
{}
- virtual ~TestCheck(){};
+ virtual ~TestCheck() {};
void run();
@@ -32,11 +32,11 @@ class TestCheck : public QObject {
void finished();
private slots:
- void checkFinished(JavaCheckResult result);
- void checkFinishedWithArgs(JavaCheckResult result);
+ void checkFinished(const JavaChecker::Result& result);
+ void checkFinishedWithArgs(const JavaChecker::Result& result);
private:
- std::shared_ptr checker;
+ JavaChecker::Ptr checker;
QWidget* m_parent = nullptr;
QString m_path;
QString m_args;
diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp
index ff8558ce7..687da1322 100644
--- a/launcher/LaunchController.cpp
+++ b/launcher/LaunchController.cpp
@@ -36,6 +36,7 @@
#include "LaunchController.h"
#include "Application.h"
+#include "launch/steps/PrintServers.h"
#include "minecraft/auth/AccountData.h"
#include "minecraft/auth/AccountList.h"
@@ -52,12 +53,12 @@
#include
#include
#include
+#include
#include
#include "BuildConfig.h"
#include "JavaCommon.h"
#include "launch/steps/TextPrint.h"
-#include "minecraft/auth/AccountTask.h"
#include "tasks/Task.h"
LaunchController::LaunchController(QObject* parent) : Task(parent) {}
@@ -85,7 +86,7 @@ void LaunchController::decideAccount()
// Find an account to use.
auto accounts = APPLICATION->accounts();
- if (accounts->count() <= 0) {
+ if (accounts->count() <= 0 || !accounts->anyAccountIsValid()) {
// Tell the user they need to log in at least one account in order to play.
auto reply = CustomMessageBox::selectable(m_parentWidget, tr("No Accounts"),
tr("In order to play Minecraft, you must have at least one Microsoft "
@@ -129,12 +130,63 @@ void LaunchController::decideAccount()
}
}
+bool LaunchController::askPlayDemo()
+{
+ QMessageBox box(m_parentWidget);
+ box.setWindowTitle(tr("Play demo?"));
+ box.setText(
+ tr("This account does not own Minecraft.\nYou need to purchase the game first to play it.\n\nDo you want to play "
+ "the demo?"));
+ box.setIcon(QMessageBox::Warning);
+ auto demoButton = box.addButton(tr("Play Demo"), QMessageBox::ButtonRole::YesRole);
+ auto cancelButton = box.addButton(tr("Cancel"), QMessageBox::ButtonRole::NoRole);
+ box.setDefaultButton(cancelButton);
+
+ box.exec();
+ return box.clickedButton() == demoButton;
+}
+
+QString LaunchController::askOfflineName(QString playerName, bool demo, bool& ok)
+{
+ // we ask the user for a player name
+ QString message = tr("Choose your offline mode player name.");
+ if (demo) {
+ message = tr("Choose your demo mode player name.");
+ }
+
+ QString lastOfflinePlayerName = APPLICATION->settings()->get("LastOfflinePlayerName").toString();
+ QString usedname = lastOfflinePlayerName.isEmpty() ? playerName : lastOfflinePlayerName;
+ QString name = QInputDialog::getText(m_parentWidget, tr("Player name"), message, QLineEdit::Normal, usedname, &ok);
+ if (!ok)
+ return {};
+ if (name.length()) {
+ usedname = name;
+ APPLICATION->settings()->set("LastOfflinePlayerName", usedname);
+ }
+ return usedname;
+}
+
void LaunchController::login()
{
decideAccount();
- // if no account is selected, we bail
if (!m_accountToUse) {
+ // if no account is selected, ask about demo
+ if (!m_demo) {
+ m_demo = askPlayDemo();
+ }
+ if (m_demo) {
+ // we ask the user for a player name
+ bool ok = false;
+ auto name = askOfflineName("Player", m_demo, ok);
+ if (ok) {
+ m_session = std::make_shared();
+ m_session->MakeDemo(name, MinecraftAccount::uuidFromUsername(name).toString().remove(QRegularExpression("[{}-]")));
+ launchInstance();
+ return;
+ }
+ }
+ // if no account is selected, we bail
emitFailed(tr("No account selected for launch."));
return;
}
@@ -143,7 +195,8 @@ void LaunchController::login()
bool tryagain = true;
unsigned int tries = 0;
- if (m_accountToUse->accountType() != AccountType::Offline && m_accountToUse->accountState() == AccountState::Offline) {
+ if ((m_accountToUse->accountType() != AccountType::Offline && m_accountToUse->accountState() == AccountState::Offline) ||
+ m_accountToUse->shouldRefresh()) {
// Force account refresh on the account used to launch the instance updating the AccountState
// only on first try and if it is not meant to be offline
auto accounts = APPLICATION->accounts();
@@ -181,24 +234,12 @@ void LaunchController::login()
if (!m_session->wants_online) {
// we ask the user for a player name
bool ok = false;
-
- QString message = tr("Choose your offline mode player name.");
- if (m_session->demo) {
- message = tr("Choose your demo mode player name.");
- }
-
- QString lastOfflinePlayerName = APPLICATION->settings()->get("LastOfflinePlayerName").toString();
- QString usedname = lastOfflinePlayerName.isEmpty() ? m_session->player_name : lastOfflinePlayerName;
- QString name = QInputDialog::getText(m_parentWidget, tr("Player name"), message, QLineEdit::Normal, usedname, &ok);
+ auto name = askOfflineName(m_session->player_name, m_session->demo, ok);
if (!ok) {
tryagain = false;
break;
}
- if (name.length()) {
- usedname = name;
- APPLICATION->settings()->set("LastOfflinePlayerName", usedname);
- }
- m_session->MakeOffline(usedname);
+ m_session->MakeOffline(name);
// offline flavored game from here :3
}
if (m_accountToUse->ownsMinecraft()) {
@@ -218,20 +259,10 @@ void LaunchController::login()
return;
} else {
// play demo ?
- QMessageBox box(m_parentWidget);
- box.setWindowTitle(tr("Play demo?"));
- box.setText(
- tr("This account does not own Minecraft.\nYou need to purchase the game first to play it.\n\nDo you want to play "
- "the demo?"));
- box.setIcon(QMessageBox::Warning);
- auto demoButton = box.addButton(tr("Play Demo"), QMessageBox::ButtonRole::YesRole);
- auto cancelButton = box.addButton(tr("Cancel"), QMessageBox::ButtonRole::NoRole);
- box.setDefaultButton(cancelButton);
-
- box.exec();
- if (box.clickedButton() == demoButton) {
- // play demo here
- m_session->MakeDemo();
+ if (!m_session->demo) {
+ m_session->demo = askPlayDemo();
+ }
+ if (m_session->demo) { // play demo here
launchInstance();
} else {
emitFailed(tr("Launch cancelled - account does not own Minecraft."));
@@ -294,7 +325,7 @@ void LaunchController::launchInstance()
return;
}
- m_launcher = m_instance->createLaunchTask(m_session, m_serverToJoin);
+ m_launcher = m_instance->createLaunchTask(m_session, m_targetToJoin);
if (!m_launcher) {
emitFailed(tr("Couldn't instantiate a launcher."));
return;
@@ -316,26 +347,9 @@ void LaunchController::launchInstance()
online_mode = "online";
// Prepend Server Status
- QStringList servers = { "session.minecraft.net", "textures.minecraft.net", "api.mojang.com" };
- QString resolved_servers = "";
- QHostInfo host_info;
+ QStringList servers = { "login.microsoftonline.com", "session.minecraft.net", "textures.minecraft.net", "api.mojang.com" };
- for (QString server : servers) {
- host_info = QHostInfo::fromName(server);
- resolved_servers = resolved_servers + server + " resolves to:\n [";
- if (!host_info.addresses().isEmpty()) {
- for (QHostAddress address : host_info.addresses()) {
- resolved_servers = resolved_servers + address.toString();
- if (!host_info.addresses().endsWith(address)) {
- resolved_servers = resolved_servers + ", ";
- }
- }
- } else {
- resolved_servers = resolved_servers + "N/A";
- }
- resolved_servers = resolved_servers + "]\n\n";
- }
- m_launcher->prependStep(makeShared(m_launcher.get(), resolved_servers, MessageLevel::Launcher));
+ m_launcher->prependStep(makeShared(m_launcher.get(), servers));
} else {
online_mode = m_demo ? "demo" : "offline";
}
diff --git a/launcher/LaunchController.h b/launcher/LaunchController.h
index f1c88afb7..6e2a94258 100644
--- a/launcher/LaunchController.h
+++ b/launcher/LaunchController.h
@@ -39,7 +39,7 @@
#include
#include "minecraft/auth/MinecraftAccount.h"
-#include "minecraft/launch/MinecraftServerTarget.h"
+#include "minecraft/launch/MinecraftTarget.h"
class InstanceWindow;
class LaunchController : public Task {
@@ -48,7 +48,7 @@ class LaunchController : public Task {
void executeTask() override;
LaunchController(QObject* parent = nullptr);
- virtual ~LaunchController(){};
+ virtual ~LaunchController() = default;
void setInstance(InstancePtr instance) { m_instance = instance; }
@@ -62,7 +62,7 @@ class LaunchController : public Task {
void setParentWidget(QWidget* widget) { m_parentWidget = widget; }
- void setServerToJoin(MinecraftServerTargetPtr serverToJoin) { m_serverToJoin = std::move(serverToJoin); }
+ void setTargetToJoin(MinecraftTarget::Ptr targetToJoin) { m_targetToJoin = std::move(targetToJoin); }
void setAccountToUse(MinecraftAccountPtr accountToUse) { m_accountToUse = std::move(accountToUse); }
@@ -74,6 +74,8 @@ class LaunchController : public Task {
void login();
void launchInstance();
void decideAccount();
+ bool askPlayDemo();
+ QString askOfflineName(QString playerName, bool demo, bool& ok);
private slots:
void readyForLaunch();
@@ -92,5 +94,5 @@ class LaunchController : public Task {
MinecraftAccountPtr m_accountToUse = nullptr;
AuthSessionPtr m_session;
shared_qobject_ptr m_launcher;
- MinecraftServerTargetPtr m_serverToJoin;
+ MinecraftTarget::Ptr m_targetToJoin;
};
diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp
index 9a5ae7a9d..dcf3d566f 100644
--- a/launcher/MMCZip.cpp
+++ b/launcher/MMCZip.cpp
@@ -2,7 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu
- * Copyright (c) 2023 Trial97
+ * Copyright (c) 2023-2024 Trial97
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -42,6 +42,7 @@
#include
#include
+#include
#include
#if defined(LAUNCHER_APPLICATION)
@@ -122,7 +123,7 @@ bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files,
zip.setUtf8Enabled(true);
QDir().mkpath(QFileInfo(fileCompressed).absolutePath());
if (!zip.open(QuaZip::mdCreate)) {
- QFile::remove(fileCompressed);
+ FS::deletePath(fileCompressed);
return false;
}
@@ -130,7 +131,7 @@ bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files,
zip.close();
if (zip.getZipError() != 0) {
- QFile::remove(fileCompressed);
+ FS::deletePath(fileCompressed);
return false;
}
@@ -144,7 +145,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QListtype() == ResourceType::ZIPFILE) {
if (!mergeZipFiles(&zipOut, mod->fileinfo(), addedFiles)) {
zipOut.close();
- QFile::remove(targetJarPath);
+ FS::deletePath(targetJarPath);
qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar.";
return false;
}
@@ -171,7 +172,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QListfileinfo();
if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName())) {
zipOut.close();
- QFile::remove(targetJarPath);
+ FS::deletePath(targetJarPath);
qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar.";
return false;
}
@@ -194,7 +195,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QListfileinfo().fileName() << "to the jar.";
return false;
}
@@ -202,7 +203,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QListfileinfo().fileName() << "to the jar.";
return false;
}
@@ -210,7 +211,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList extractSubDir(QuaZip* zip, const QString& subdir, con
do {
QString file_name = zip->getCurrentFileName();
-#ifdef Q_OS_WIN
file_name = FS::RemoveInvalidPathChars(file_name);
-#endif
if (!file_name.startsWith(subdir))
continue;
@@ -332,9 +331,31 @@ std::optional extractSubDir(QuaZip* zip, const QString& subdir, con
}
extracted.append(target_file_path);
- QFile::setPermissions(target_file_path,
- QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser);
+ auto fileInfo = QFileInfo(target_file_path);
+ if (fileInfo.isFile()) {
+ auto permissions = fileInfo.permissions();
+ auto maxPermisions = QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser |
+ QFileDevice::Permission::ReadGroup | QFileDevice::Permission::ReadOther;
+ auto minPermisions = QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser;
+ auto newPermisions = (permissions & maxPermisions) | minPermisions;
+ if (newPermisions != permissions) {
+ if (!QFile::setPermissions(target_file_path, newPermisions)) {
+ qWarning() << (QObject::tr("Could not fix permissions for %1").arg(target_file_path));
+ }
+ }
+ } else if (fileInfo.isDir()) {
+ // Ensure the folder has the minimal required permissions
+ QFile::Permissions minimalPermissions = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadGroup |
+ QFile::ExeGroup | QFile::ReadOther | QFile::ExeOther;
+
+ QFile::Permissions currentPermissions = fileInfo.permissions();
+ if ((currentPermissions & minimalPermissions) != minimalPermissions) {
+ if (!QFile::setPermissions(target_file_path, minimalPermissions)) {
+ qWarning() << (QObject::tr("Could not fix permissions for %1").arg(target_file_path));
+ }
+ }
+ }
qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path;
} while (zip->goToNextFile());
@@ -492,10 +513,10 @@ auto ExportToZipTask::exportZip() -> ZipResult
void ExportToZipTask::finish()
{
if (m_build_zip_future.isCanceled()) {
- QFile::remove(m_output_path);
+ FS::deletePath(m_output_path);
emitAborted();
} else if (auto result = m_build_zip_future.result(); result.has_value()) {
- QFile::remove(m_output_path);
+ FS::deletePath(m_output_path);
emitFailed(result.value());
} else {
emitSucceeded();
@@ -512,6 +533,138 @@ bool ExportToZipTask::abort()
}
return false;
}
-#endif
+void ExtractZipTask::executeTask()
+{
+ if (!m_input->isOpen() && !m_input->open(QuaZip::mdUnzip)) {
+ emitFailed(tr("Unable to open supplied zip file."));
+ return;
+ }
+ m_zip_future = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return extractZip(); });
+ connect(&m_zip_watcher, &QFutureWatcher::finished, this, &ExtractZipTask::finish);
+ m_zip_watcher.setFuture(m_zip_future);
+}
+
+auto ExtractZipTask::extractZip() -> ZipResult
+{
+ auto target = m_output_dir.absolutePath();
+ auto target_top_dir = QUrl::fromLocalFile(target);
+
+ QStringList extracted;
+
+ qDebug() << "Extracting subdir" << m_subdirectory << "from" << m_input->getZipName() << "to" << target;
+ auto numEntries = m_input->getEntriesCount();
+ if (numEntries < 0) {
+ return ZipResult(tr("Failed to enumerate files in archive"));
+ }
+ if (numEntries == 0) {
+ logWarning(tr("Extracting empty archives seems odd..."));
+ return ZipResult();
+ }
+ if (!m_input->goToFirstFile()) {
+ return ZipResult(tr("Failed to seek to first file in zip"));
+ }
+
+ setStatus("Extracting files...");
+ setProgress(0, numEntries);
+ do {
+ if (m_zip_future.isCanceled())
+ return ZipResult();
+ setProgress(m_progress + 1, m_progressTotal);
+ QString file_name = m_input->getCurrentFileName();
+ if (!file_name.startsWith(m_subdirectory))
+ continue;
+
+ auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(m_subdirectory.size()));
+ auto original_name = relative_file_name;
+ setStatus("Unziping: " + relative_file_name);
+
+ // Fix subdirs/files ending with a / getting transformed into absolute paths
+ if (relative_file_name.startsWith('/'))
+ relative_file_name = relative_file_name.mid(1);
+
+ // Fix weird "folders with a single file get squashed" thing
+ QString sub_path;
+ if (relative_file_name.contains('/') && !relative_file_name.endsWith('/')) {
+ sub_path = relative_file_name.section('/', 0, -2) + '/';
+ FS::ensureFolderPathExists(FS::PathCombine(target, sub_path));
+
+ relative_file_name = relative_file_name.split('/').last();
+ }
+
+ QString target_file_path;
+ if (relative_file_name.isEmpty()) {
+ target_file_path = target + '/';
+ } else {
+ target_file_path = FS::PathCombine(target_top_dir.toLocalFile(), sub_path, relative_file_name);
+ if (relative_file_name.endsWith('/') && !target_file_path.endsWith('/'))
+ target_file_path += '/';
+ }
+
+ if (!target_top_dir.isParentOf(QUrl::fromLocalFile(target_file_path))) {
+ return ZipResult(tr("Extracting %1 was cancelled, because it was effectively outside of the target path %2")
+ .arg(relative_file_name, target));
+ }
+
+ if (!JlCompress::extractFile(m_input.get(), "", target_file_path)) {
+ JlCompress::removeFile(extracted);
+ return ZipResult(tr("Failed to extract file %1 to %2").arg(original_name, target_file_path));
+ }
+
+ extracted.append(target_file_path);
+ auto fileInfo = QFileInfo(target_file_path);
+ if (fileInfo.isFile()) {
+ auto permissions = fileInfo.permissions();
+ auto maxPermisions = QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser |
+ QFileDevice::Permission::ReadGroup | QFileDevice::Permission::ReadOther;
+ auto minPermisions = QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser;
+
+ auto newPermisions = (permissions & maxPermisions) | minPermisions;
+ if (newPermisions != permissions) {
+ if (!QFile::setPermissions(target_file_path, newPermisions)) {
+ logWarning(tr("Could not fix permissions for %1").arg(target_file_path));
+ }
+ }
+ } else if (fileInfo.isDir()) {
+ // Ensure the folder has the minimal required permissions
+ QFile::Permissions minimalPermissions = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadGroup |
+ QFile::ExeGroup | QFile::ReadOther | QFile::ExeOther;
+
+ QFile::Permissions currentPermissions = fileInfo.permissions();
+ if ((currentPermissions & minimalPermissions) != minimalPermissions) {
+ if (!QFile::setPermissions(target_file_path, minimalPermissions)) {
+ logWarning(tr("Could not fix permissions for %1").arg(target_file_path));
+ }
+ }
+ }
+
+ qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path;
+ } while (m_input->goToNextFile());
+
+ return ZipResult();
+}
+
+void ExtractZipTask::finish()
+{
+ if (m_zip_future.isCanceled()) {
+ emitAborted();
+ } else if (auto result = m_zip_future.result(); result.has_value()) {
+ emitFailed(result.value());
+ } else {
+ emitSucceeded();
+ }
+}
+
+bool ExtractZipTask::abort()
+{
+ if (m_zip_future.isRunning()) {
+ m_zip_future.cancel();
+ // NOTE: Here we don't do `emitAborted()` because it will be done when `m_build_zip_future` actually cancels, which may not occur
+ // immediately.
+ return true;
+ }
+ return false;
+}
+
+#endif
} // namespace MMCZip
diff --git a/launcher/MMCZip.h b/launcher/MMCZip.h
index 43b4ab933..1635f8b32 100644
--- a/launcher/MMCZip.h
+++ b/launcher/MMCZip.h
@@ -2,7 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu
- * Copyright (c) 2023 Trial97
+ * Copyright (c) 2023-2024 Trial97
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -154,7 +154,12 @@ bool collectFileListRecursively(const QString& rootDir, const QString& subDir, Q
#if defined(LAUNCHER_APPLICATION)
class ExportToZipTask : public Task {
public:
- ExportToZipTask(QString outputPath, QDir dir, QFileInfoList files, QString destinationPrefix = "", bool followSymlinks = false)
+ ExportToZipTask(QString outputPath,
+ QDir dir,
+ QFileInfoList files,
+ QString destinationPrefix = "",
+ bool followSymlinks = false,
+ bool utf8Enabled = false)
: m_output_path(outputPath)
, m_output(outputPath)
, m_dir(dir)
@@ -163,10 +168,15 @@ class ExportToZipTask : public Task {
, m_follow_symlinks(followSymlinks)
{
setAbortable(true);
- m_output.setUtf8Enabled(true);
+ m_output.setUtf8Enabled(utf8Enabled);
};
- ExportToZipTask(QString outputPath, QString dir, QFileInfoList files, QString destinationPrefix = "", bool followSymlinks = false)
- : ExportToZipTask(outputPath, QDir(dir), files, destinationPrefix, followSymlinks){};
+ ExportToZipTask(QString outputPath,
+ QString dir,
+ QFileInfoList files,
+ QString destinationPrefix = "",
+ bool followSymlinks = false,
+ bool utf8Enabled = false)
+ : ExportToZipTask(outputPath, QDir(dir), files, destinationPrefix, followSymlinks, utf8Enabled) {};
virtual ~ExportToZipTask() = default;
@@ -195,5 +205,33 @@ class ExportToZipTask : public Task {
QFuture m_build_zip_future;
QFutureWatcher m_build_zip_watcher;
};
+
+class ExtractZipTask : public Task {
+ public:
+ ExtractZipTask(QString input, QDir outputDir, QString subdirectory = "")
+ : ExtractZipTask(std::make_shared(input), outputDir, subdirectory)
+ {}
+ ExtractZipTask(std::shared_ptr input, QDir outputDir, QString subdirectory = "")
+ : m_input(input), m_output_dir(outputDir), m_subdirectory(subdirectory)
+ {}
+ virtual ~ExtractZipTask() = default;
+
+ using ZipResult = std::optional;
+
+ protected:
+ virtual void executeTask() override;
+ bool abort() override;
+
+ ZipResult extractZip();
+ void finish();
+
+ private:
+ std::shared_ptr m_input;
+ QDir m_output_dir;
+ QString m_subdirectory;
+
+ QFuture m_zip_future;
+ QFutureWatcher m_zip_watcher;
+};
#endif
} // namespace MMCZip
diff --git a/launcher/MangoHud.cpp b/launcher/MangoHud.cpp
index ab79f418b..29a7c63d9 100644
--- a/launcher/MangoHud.cpp
+++ b/launcher/MangoHud.cpp
@@ -40,8 +40,8 @@ namespace MangoHud {
QString getLibraryString()
{
- /*
- * Check for vulkan layers in this order:
+ /**
+ * Guess MangoHud install location by searching for vulkan layers in this order:
*
* $VK_LAYER_PATH
* $XDG_DATA_DIRS (/usr/local/share/:/usr/share/)
@@ -49,8 +49,9 @@ QString getLibraryString()
* /etc
* $XDG_CONFIG_DIRS (/etc/xdg)
* $XDG_CONFIG_HOME (~/.config)
+ *
+ * @returns Absolute path of libMangoHud.so if found and empty QString otherwise.
*/
-
QStringList vkLayerList;
{
QString home = QDir::homePath();
@@ -85,7 +86,7 @@ QString getLibraryString()
vkLayerList << FS::PathCombine(xdgConfigHome, "vulkan", "implicit_layer.d");
}
- for (QString vkLayer : vkLayerList) {
+ for (const QString& vkLayer : vkLayerList) {
// prefer to use architecture specific vulkan layers
QString currentArch = QSysInfo::currentCpuArchitecture();
@@ -95,8 +96,8 @@ QString getLibraryString()
QStringList manifestNames = { QString("MangoHud.%1.json").arg(currentArch), "MangoHud.json" };
- QString filePath = "";
- for (QString manifestName : manifestNames) {
+ QString filePath{};
+ for (const QString& manifestName : manifestNames) {
QString tryPath = FS::PathCombine(vkLayer, manifestName);
if (QFile::exists(tryPath)) {
filePath = tryPath;
@@ -107,14 +108,34 @@ QString getLibraryString()
if (filePath.isEmpty()) {
continue;
}
+ try {
+ auto conf = Json::requireDocument(filePath, vkLayer);
+ auto confObject = Json::requireObject(conf, vkLayer);
+ auto layer = Json::ensureObject(confObject, "layer");
+ QString libraryName = Json::ensureString(layer, "library_path");
- auto conf = Json::requireDocument(filePath, vkLayer);
- auto confObject = Json::requireObject(conf, vkLayer);
- auto layer = Json::ensureObject(confObject, "layer");
- return Json::ensureString(layer, "library_path");
+ if (libraryName.isEmpty()) {
+ continue;
+ }
+ if (QFileInfo(libraryName).isAbsolute()) {
+ return libraryName;
+ }
+
+#ifdef __GLIBC__
+ // Check whether mangohud is usable on a glibc based system
+ QString libraryPath = findLibrary(libraryName);
+ if (!libraryPath.isEmpty()) {
+ return libraryPath;
+ }
+#else
+ // Without glibc return recorded shared library as-is.
+ return libraryName;
+#endif
+ } catch (const Exception& e) {
+ }
}
- return QString();
+ return {};
}
QString findLibrary(QString libName)
diff --git a/launcher/NullInstance.h b/launcher/NullInstance.h
index c79600e7d..3d01c9d33 100644
--- a/launcher/NullInstance.h
+++ b/launcher/NullInstance.h
@@ -46,14 +46,14 @@ class NullInstance : public BaseInstance {
{
setVersionBroken(true);
}
- virtual ~NullInstance(){};
+ virtual ~NullInstance() = default;
void saveNow() override {}
void loadSpecificSettings() override { setSpecificSettingsLoaded(true); }
QString getStatusbarDescription() override { return tr("Unknown instance type"); };
QSet traits() const override { return {}; };
QString instanceConfigFolder() const override { return instanceRoot(); };
- shared_qobject_ptr createLaunchTask(AuthSessionPtr, MinecraftServerTargetPtr) override { return nullptr; }
- shared_qobject_ptr createUpdateTask([[maybe_unused]] Net::Mode mode) override { return nullptr; }
+ shared_qobject_ptr createLaunchTask(AuthSessionPtr, MinecraftTarget::Ptr) override { return nullptr; }
+ QList createUpdateTask() override { return {}; }
QProcessEnvironment createEnvironment() override { return QProcessEnvironment(); }
QProcessEnvironment createLaunchEnvironment() override { return QProcessEnvironment(); }
QMap getVariables() override { return QMap(); }
@@ -64,7 +64,7 @@ class NullInstance : public BaseInstance {
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 verboseDescription(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) override
{
QStringList out;
out << "Null instance - placeholder.";
diff --git a/launcher/ResourceDownloadTask.cpp b/launcher/ResourceDownloadTask.cpp
index e5828b569..0fe082ac4 100644
--- a/launcher/ResourceDownloadTask.cpp
+++ b/launcher/ResourceDownloadTask.cpp
@@ -24,7 +24,9 @@
#include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/ResourceFolderModel.h"
+#include "modplatform/helpers/HashUtils.h"
#include "net/ApiDownload.h"
+#include "net/ChecksumValidator.h"
ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
ModPlatform::IndexedVersion version,
@@ -53,7 +55,29 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
}
}
- m_filesNetJob->addNetAction(Net::ApiDownload::makeFile(m_pack_version.downloadUrl, dir.absoluteFilePath(getFilename())));
+ auto action = Net::ApiDownload::makeFile(m_pack_version.downloadUrl, dir.absoluteFilePath(getFilename()));
+ if (!m_pack_version.hash_type.isEmpty() && !m_pack_version.hash.isEmpty()) {
+ switch (Hashing::algorithmFromString(m_pack_version.hash_type)) {
+ case Hashing::Algorithm::Md4:
+ action->addValidator(new Net::ChecksumValidator(QCryptographicHash::Algorithm::Md4, m_pack_version.hash));
+ break;
+ case Hashing::Algorithm::Md5:
+ action->addValidator(new Net::ChecksumValidator(QCryptographicHash::Algorithm::Md5, m_pack_version.hash));
+ break;
+ case Hashing::Algorithm::Sha1:
+ action->addValidator(new Net::ChecksumValidator(QCryptographicHash::Algorithm::Sha1, m_pack_version.hash));
+ break;
+ case Hashing::Algorithm::Sha256:
+ action->addValidator(new Net::ChecksumValidator(QCryptographicHash::Algorithm::Sha256, m_pack_version.hash));
+ break;
+ case Hashing::Algorithm::Sha512:
+ action->addValidator(new Net::ChecksumValidator(QCryptographicHash::Algorithm::Sha512, m_pack_version.hash));
+ break;
+ default:
+ break;
+ }
+ }
+ m_filesNetJob->addNetAction(action);
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ResourceDownloadTask::downloadSucceeded);
connect(m_filesNetJob.get(), &NetJob::progress, this, &ResourceDownloadTask::downloadProgressChanged);
connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &ResourceDownloadTask::propagateStepProgress);
diff --git a/launcher/RuntimeContext.h b/launcher/RuntimeContext.h
index c57140d28..85304a5bc 100644
--- a/launcher/RuntimeContext.h
+++ b/launcher/RuntimeContext.h
@@ -20,13 +20,13 @@
#include
#include
+#include "SysInfo.h"
#include "settings/SettingsObject.h"
struct RuntimeContext {
QString javaArchitecture;
QString javaRealArchitecture;
- QString javaPath;
- QString system;
+ QString system = SysInfo::currentSystem();
QString mappedJavaRealArchitecture() const
{
@@ -45,8 +45,6 @@ struct RuntimeContext {
{
javaArchitecture = instanceSettings->get("JavaArchitecture").toString();
javaRealArchitecture = instanceSettings->get("JavaRealArchitecture").toString();
- javaPath = instanceSettings->get("JavaPath").toString();
- system = currentSystem();
}
QString getClassifier() const { return system + "-" + mappedJavaRealArchitecture(); }
@@ -68,21 +66,4 @@ struct RuntimeContext {
return x;
}
-
- static QString currentSystem()
- {
-#if defined(Q_OS_LINUX)
- return "linux";
-#elif defined(Q_OS_MACOS)
- return "osx";
-#elif defined(Q_OS_WINDOWS)
- return "windows";
-#elif defined(Q_OS_FREEBSD)
- return "freebsd";
-#elif defined(Q_OS_OPENBSD)
- return "openbsd";
-#else
- return "unknown";
-#endif
- }
};
diff --git a/launcher/SkinUtils.cpp b/launcher/SkinUtils.cpp
deleted file mode 100644
index 989114ad5..000000000
--- a/launcher/SkinUtils.cpp
+++ /dev/null
@@ -1,52 +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 "SkinUtils.h"
-#include "Application.h"
-#include "net/HttpMetaCache.h"
-
-#include
-#include
-#include
-#include
-#include
-
-namespace SkinUtils {
-/*
- * Given a username, return a pixmap of the cached skin (if it exists), QPixmap() otherwise
- */
-QPixmap getFaceFromCache(QString username, int height, int width)
-{
- QFile fskin(APPLICATION->metacache()->resolveEntry("skins", username + ".png")->getFullPath());
-
- if (fskin.exists()) {
- QPixmap skinTexture(fskin.fileName());
- if (!skinTexture.isNull()) {
- QPixmap skin = QPixmap(8, 8);
-#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
- skin.fill(QColorConstants::Transparent);
-#else
- skin.fill(QColor(0, 0, 0, 0));
-#endif
- QPainter painter(&skin);
- painter.drawPixmap(0, 0, skinTexture.copy(8, 8, 8, 8));
- painter.drawPixmap(0, 0, skinTexture.copy(40, 8, 8, 8));
- return skin.scaled(height, width, Qt::KeepAspectRatio);
- }
- }
-
- return QPixmap();
-}
-} // namespace SkinUtils
diff --git a/launcher/SkinUtils.h b/launcher/SkinUtils.h
deleted file mode 100644
index 11bc8bc6f..000000000
--- a/launcher/SkinUtils.h
+++ /dev/null
@@ -1,22 +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
-
-namespace SkinUtils {
-QPixmap getFaceFromCache(QString id, int height = 64, int width = 64);
-}
diff --git a/launcher/StringUtils.cpp b/launcher/StringUtils.cpp
index 72ccdfbff..edda9f247 100644
--- a/launcher/StringUtils.cpp
+++ b/launcher/StringUtils.cpp
@@ -212,3 +212,25 @@ QPair StringUtils::splitFirst(const QString& s, const QRegular
right = s.mid(end);
return qMakePair(left, right);
}
+
+static const QRegularExpression ulMatcher("<\\s*/\\s*ul\\s*>");
+
+QString StringUtils::htmlListPatch(QString htmlStr)
+{
+ int pos = htmlStr.indexOf(ulMatcher);
+ int imgPos;
+ while (pos != -1) {
+ pos = htmlStr.indexOf(">", pos) + 1; // Get the size of the tag. Add one for zeroeth index
+ imgPos = htmlStr.indexOf("
");
+
+ pos = htmlStr.indexOf(ulMatcher, pos);
+ }
+ return htmlStr;
+}
\ No newline at end of file
diff --git a/launcher/StringUtils.h b/launcher/StringUtils.h
index 9d2bdd85e..624ee41a3 100644
--- a/launcher/StringUtils.h
+++ b/launcher/StringUtils.h
@@ -85,4 +85,6 @@ QPair splitFirst(const QString& s, const QString& sep, Qt::Cas
QPair splitFirst(const QString& s, QChar sep, Qt::CaseSensitivity cs = Qt::CaseSensitive);
QPair splitFirst(const QString& s, const QRegularExpression& re);
+QString htmlListPatch(QString htmlStr);
+
} // namespace StringUtils
diff --git a/launcher/SysInfo.cpp b/launcher/SysInfo.cpp
new file mode 100644
index 000000000..0dfa74de7
--- /dev/null
+++ b/launcher/SysInfo.cpp
@@ -0,0 +1,99 @@
+#include
+#include
+#include "sys.h"
+#ifdef Q_OS_MACOS
+#include
+#endif
+#include
+#include
+#include
+#include
+
+#ifdef Q_OS_MACOS
+bool rosettaDetect()
+{
+ int ret = 0;
+ size_t size = sizeof(ret);
+ if (sysctlbyname("sysctl.proc_translated", &ret, &size, NULL, 0) == -1) {
+ return false;
+ }
+ return ret == 1;
+}
+#endif
+
+namespace SysInfo {
+QString currentSystem()
+{
+#if defined(Q_OS_LINUX)
+ return "linux";
+#elif defined(Q_OS_MACOS)
+ return "osx";
+#elif defined(Q_OS_WINDOWS)
+ return "windows";
+#elif defined(Q_OS_FREEBSD)
+ return "freebsd";
+#elif defined(Q_OS_OPENBSD)
+ return "openbsd";
+#else
+ return "unknown";
+#endif
+}
+
+QString useQTForArch()
+{
+#if defined(Q_OS_MACOS) && !defined(Q_PROCESSOR_ARM)
+ if (rosettaDetect()) {
+ return "arm64";
+ } else {
+ return "x86_64";
+ }
+#endif
+ return QSysInfo::currentCpuArchitecture();
+}
+
+int suitableMaxMem()
+{
+ float totalRAM = (float)Sys::getSystemRam() / (float)Sys::mebibyte;
+ int maxMemoryAlloc;
+
+ // If totalRAM < 6GB, use (totalRAM / 1.5), else 4GB
+ if (totalRAM < (4096 * 1.5))
+ maxMemoryAlloc = (int)(totalRAM / 1.5);
+ else
+ maxMemoryAlloc = 4096;
+
+ return maxMemoryAlloc;
+}
+
+QString getSupportedJavaArchitecture()
+{
+ auto sys = currentSystem();
+ auto arch = useQTForArch();
+ if (sys == "windows") {
+ if (arch == "x86_64")
+ return "windows-x64";
+ if (arch == "i386")
+ return "windows-x86";
+ // Unknown, maybe arm, appending arch
+ return "windows-" + arch;
+ }
+ if (sys == "osx") {
+ if (arch == "arm64")
+ return "mac-os-arm64";
+ if (arch.contains("64"))
+ return "mac-os-64";
+ if (arch.contains("86"))
+ return "mac-os-86";
+ // Unknown, maybe something new, appending arch
+ return "mac-os-" + arch;
+ } else if (sys == "linux") {
+ if (arch == "x86_64")
+ return "linux-x64";
+ if (arch == "i386")
+ return "linux-x86";
+ // will work for arm32 arm(64)
+ return "linux-" + arch;
+ }
+ return {};
+}
+} // namespace SysInfo
diff --git a/launcher/SysInfo.h b/launcher/SysInfo.h
new file mode 100644
index 000000000..f3688d60d
--- /dev/null
+++ b/launcher/SysInfo.h
@@ -0,0 +1,8 @@
+#include
+
+namespace SysInfo {
+QString currentSystem();
+QString useQTForArch();
+QString getSupportedJavaArchitecture();
+int suitableMaxMem();
+} // namespace SysInfo
diff --git a/launcher/Untar.cpp b/launcher/Untar.cpp
new file mode 100644
index 000000000..f1963e7aa
--- /dev/null
+++ b/launcher/Untar.cpp
@@ -0,0 +1,260 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2023-2024 Trial97
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "Untar.h"
+#include
+#include
+#include
+#include
+#include
+#include "FileSystem.h"
+
+// adaptation of the:
+// - https://github.com/madler/zlib/blob/develop/contrib/untgz/untgz.c
+// - https://en.wikipedia.org/wiki/Tar_(computing)
+// - https://github.com/euroelessar/cutereader/blob/master/karchive/src/ktar.cpp
+
+#define BLOCKSIZE 512
+#define SHORTNAMESIZE 100
+
+enum class TypeFlag : char {
+ Regular = '0', // regular file
+ ARegular = 0, // regular file
+ Link = '1', // link
+ Symlink = '2', // reserved
+ Character = '3', // character special
+ Block = '4', // block special
+ Directory = '5', // directory
+ FIFO = '6', // FIFO special
+ Contiguous = '7', // reserved
+ // Posix stuff
+ GlobalPosixHeader = 'g',
+ ExtendedPosixHeader = 'x',
+ // 'A'– 'Z' Vendor specific extensions(POSIX .1 - 1988)
+ // GNU
+ GNULongLink = 'K', /* long link name */
+ GNULongName = 'L', /* long file name */
+};
+
+// struct Header { /* byte offset */
+// char name[100]; /* 0 */
+// char mode[8]; /* 100 */
+// char uid[8]; /* 108 */
+// char gid[8]; /* 116 */
+// char size[12]; /* 124 */
+// char mtime[12]; /* 136 */
+// char chksum[8]; /* 148 */
+// TypeFlag typeflag; /* 156 */
+// char linkname[100]; /* 157 */
+// char magic[6]; /* 257 */
+// char version[2]; /* 263 */
+// char uname[32]; /* 265 */
+// char gname[32]; /* 297 */
+// char devmajor[8]; /* 329 */
+// char devminor[8]; /* 337 */
+// char prefix[155]; /* 345 */
+// /* 500 */
+// };
+
+bool readLonglink(QIODevice* in, qint64 size, QByteArray& longlink)
+{
+ qint64 n = 0;
+ size--; // ignore trailing null
+ if (size < 0) {
+ qCritical() << "The filename size is negative";
+ return false;
+ }
+ longlink.resize(size + (BLOCKSIZE - size % BLOCKSIZE)); // make the size divisible by BLOCKSIZE
+ for (qint64 offset = 0; offset < longlink.size(); offset += BLOCKSIZE) {
+ n = in->read(longlink.data() + offset, BLOCKSIZE);
+ if (n != BLOCKSIZE) {
+ qCritical() << "The expected blocksize was not respected for the name";
+ return false;
+ }
+ }
+ longlink.truncate(qstrlen(longlink.constData()));
+ return true;
+}
+
+int getOctal(char* buffer, int maxlenght, bool* ok)
+{
+ return QByteArray(buffer, qstrnlen(buffer, maxlenght)).toInt(ok, 8);
+}
+
+QString decodeName(char* name)
+{
+ return QFile::decodeName(QByteArray(name, qstrnlen(name, 100)));
+}
+bool Tar::extract(QIODevice* in, QString dst)
+{
+ char buffer[BLOCKSIZE];
+ QString name, symlink, firstFolderName;
+ bool doNotReset = false, ok;
+ while (true) {
+ auto n = in->read(buffer, BLOCKSIZE);
+ if (n != BLOCKSIZE) { // allways expect complete blocks
+ qCritical() << "The expected blocksize was not respected";
+ return false;
+ }
+ if (buffer[0] == 0) { // end of archive
+ return true;
+ }
+ int mode = getOctal(buffer + 100, 8, &ok) | QFile::ReadUser | QFile::WriteUser; // hack to ensure write and read permisions
+ if (!ok) {
+ qCritical() << "The file mode can't be read";
+ return false;
+ }
+ // there are names that are exactly 100 bytes long
+ // and neither longlink nor \0 terminated (bug:101472)
+
+ if (name.isEmpty()) {
+ name = decodeName(buffer);
+ if (!firstFolderName.isEmpty() && name.startsWith(firstFolderName)) {
+ name = name.mid(firstFolderName.size());
+ }
+ }
+ if (symlink.isEmpty())
+ symlink = decodeName(buffer);
+ qint64 size = getOctal(buffer + 124, 12, &ok);
+ if (!ok) {
+ qCritical() << "The file size can't be read";
+ return false;
+ }
+ switch (TypeFlag(buffer[156])) {
+ case TypeFlag::Regular:
+ /* fallthrough */
+ case TypeFlag::ARegular: {
+ auto fileName = FS::PathCombine(dst, name);
+ if (!FS::ensureFilePathExists(fileName)) {
+ qCritical() << "Can't ensure the file path to exist: " << fileName;
+ return false;
+ }
+ QFile out(fileName);
+ if (!out.open(QFile::WriteOnly)) {
+ qCritical() << "Can't open file:" << fileName;
+ return false;
+ }
+ out.setPermissions(QFile::Permissions(mode));
+ while (size > 0) {
+ QByteArray tmp(BLOCKSIZE, 0);
+ n = in->read(tmp.data(), BLOCKSIZE);
+ if (n != BLOCKSIZE) {
+ qCritical() << "The expected blocksize was not respected when reading file";
+ return false;
+ }
+ tmp.truncate(qMin(qint64(BLOCKSIZE), size));
+ out.write(tmp);
+ size -= BLOCKSIZE;
+ }
+ break;
+ }
+ case TypeFlag::Directory: {
+ if (firstFolderName.isEmpty()) {
+ firstFolderName = name;
+ break;
+ }
+ auto folderPath = FS::PathCombine(dst, name);
+ if (!FS::ensureFolderPathExists(folderPath)) {
+ qCritical() << "Can't ensure that folder exists: " << folderPath;
+ return false;
+ }
+ break;
+ }
+ case TypeFlag::GNULongLink: {
+ doNotReset = true;
+ QByteArray longlink;
+ if (readLonglink(in, size, longlink)) {
+ symlink = QFile::decodeName(longlink.constData());
+ } else {
+ qCritical() << "Failed to read long link";
+ return false;
+ }
+ break;
+ }
+ case TypeFlag::GNULongName: {
+ doNotReset = true;
+ QByteArray longlink;
+ if (readLonglink(in, size, longlink)) {
+ name = QFile::decodeName(longlink.constData());
+ } else {
+ qCritical() << "Failed to read long name";
+ return false;
+ }
+ break;
+ }
+ case TypeFlag::Link:
+ /* fallthrough */
+ case TypeFlag::Symlink: {
+ auto fileName = FS::PathCombine(dst, name);
+ if (!FS::create_link(FS::PathCombine(QFileInfo(fileName).path(), symlink), fileName)()) { // do not use symlinks
+ qCritical() << "Can't create link for:" << fileName << " to:" << FS::PathCombine(QFileInfo(fileName).path(), symlink);
+ return false;
+ }
+ FS::ensureFilePathExists(fileName);
+ QFile::setPermissions(fileName, QFile::Permissions(mode));
+ break;
+ }
+ case TypeFlag::Character:
+ /* fallthrough */
+ case TypeFlag::Block:
+ /* fallthrough */
+ case TypeFlag::FIFO:
+ /* fallthrough */
+ case TypeFlag::Contiguous:
+ /* fallthrough */
+ case TypeFlag::GlobalPosixHeader:
+ /* fallthrough */
+ case TypeFlag::ExtendedPosixHeader:
+ /* fallthrough */
+ default:
+ break;
+ }
+ if (!doNotReset) {
+ name.truncate(0);
+ symlink.truncate(0);
+ }
+ doNotReset = false;
+ }
+ return true;
+}
+
+bool GZTar::extract(QString src, QString dst)
+{
+ QuaGzipFile a(src);
+ if (!a.open(QIODevice::ReadOnly)) {
+ qCritical() << "Can't open tar file:" << src;
+ return false;
+ }
+ return Tar::extract(&a, dst);
+}
\ No newline at end of file
diff --git a/launcher/Untar.h b/launcher/Untar.h
new file mode 100644
index 000000000..50e3a16e3
--- /dev/null
+++ b/launcher/Untar.h
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2023-2024 Trial97
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+#include
+
+// this is a hack used for the java downloader (feel free to remove it in favor of a library)
+// both extract functions will extract the first folder inside dest(disregarding the prefix)
+namespace Tar {
+bool extract(QIODevice* in, QString dst);
+}
+
+namespace GZTar {
+bool extract(QString src, QString dst);
+}
\ No newline at end of file
diff --git a/launcher/VersionProxyModel.cpp b/launcher/VersionProxyModel.cpp
index 0ab9ae2c3..552900d35 100644
--- a/launcher/VersionProxyModel.cpp
+++ b/launcher/VersionProxyModel.cpp
@@ -114,10 +114,14 @@ QVariant VersionProxyModel::headerData(int section, Qt::Orientation orientation,
return tr("Branch");
case Type:
return tr("Type");
- case Architecture:
+ case CPUArchitecture:
return tr("Architecture");
case Path:
return tr("Path");
+ case JavaName:
+ return tr("Java Name");
+ case JavaMajor:
+ return tr("Major Version");
case Time:
return tr("Released");
}
@@ -131,10 +135,14 @@ QVariant VersionProxyModel::headerData(int section, Qt::Orientation orientation,
return tr("The version's branch");
case Type:
return tr("The version's type");
- case Architecture:
+ case CPUArchitecture:
return tr("CPU Architecture");
case Path:
return tr("Filesystem path to this version");
+ case JavaName:
+ return tr("The alternative name of the java version");
+ case JavaMajor:
+ return tr("The java major version");
case Time:
return tr("Release date of this version");
}
@@ -165,10 +173,14 @@ QVariant VersionProxyModel::data(const QModelIndex& index, int role) const
return sourceModel()->data(parentIndex, BaseVersionList::BranchRole);
case Type:
return sourceModel()->data(parentIndex, BaseVersionList::TypeRole);
- case Architecture:
- return sourceModel()->data(parentIndex, BaseVersionList::ArchitectureRole);
+ case CPUArchitecture:
+ return sourceModel()->data(parentIndex, BaseVersionList::CPUArchitectureRole);
case Path:
return sourceModel()->data(parentIndex, BaseVersionList::PathRole);
+ case JavaName:
+ return sourceModel()->data(parentIndex, BaseVersionList::JavaNameRole);
+ case JavaMajor:
+ return sourceModel()->data(parentIndex, BaseVersionList::JavaMajorRole);
case Time:
return sourceModel()->data(parentIndex, Meta::VersionList::TimeRole).toDate();
default:
@@ -308,12 +320,18 @@ void VersionProxyModel::setSourceModel(QAbstractItemModel* replacingRaw)
m_columns.push_back(ParentVersion);
}
*/
- if (roles.contains(BaseVersionList::ArchitectureRole)) {
- m_columns.push_back(Architecture);
+ if (roles.contains(BaseVersionList::CPUArchitectureRole)) {
+ m_columns.push_back(CPUArchitecture);
}
if (roles.contains(BaseVersionList::PathRole)) {
m_columns.push_back(Path);
}
+ if (roles.contains(BaseVersionList::JavaNameRole)) {
+ m_columns.push_back(JavaName);
+ }
+ if (roles.contains(BaseVersionList::JavaMajorRole)) {
+ m_columns.push_back(JavaMajor);
+ }
if (roles.contains(Meta::VersionList::TimeRole)) {
m_columns.push_back(Time);
}
diff --git a/launcher/VersionProxyModel.h b/launcher/VersionProxyModel.h
index 0863a7c80..7965af0ad 100644
--- a/launcher/VersionProxyModel.h
+++ b/launcher/VersionProxyModel.h
@@ -9,12 +9,12 @@ class VersionFilterModel;
class VersionProxyModel : public QAbstractProxyModel {
Q_OBJECT
public:
- enum Column { Name, ParentVersion, Branch, Type, Architecture, Path, Time };
+ enum Column { Name, ParentVersion, Branch, Type, CPUArchitecture, Path, Time, JavaName, JavaMajor };
using FilterMap = QHash>;
public:
VersionProxyModel(QObject* parent = 0);
- virtual ~VersionProxyModel(){};
+ virtual ~VersionProxyModel() {};
virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override;
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp
index 5576b9745..e4157ea2d 100644
--- a/launcher/icons/IconList.cpp
+++ b/launcher/icons/IconList.cpp
@@ -322,7 +322,7 @@ const MMCIcon* IconList::icon(const QString& key) const
bool IconList::deleteIcon(const QString& key)
{
- return iconFileExists(key) && QFile::remove(icon(key)->getFilePath());
+ return iconFileExists(key) && FS::deletePath(icon(key)->getFilePath());
}
bool IconList::trashIcon(const QString& key)
diff --git a/launcher/icons/IconList.h b/launcher/icons/IconList.h
index c51826057..553946c42 100644
--- a/launcher/icons/IconList.h
+++ b/launcher/icons/IconList.h
@@ -52,7 +52,7 @@ class IconList : public QAbstractListModel {
Q_OBJECT
public:
explicit IconList(const QStringList& builtinPaths, QString path, QObject* parent = 0);
- virtual ~IconList(){};
+ virtual ~IconList() {};
QIcon getIcon(const QString& key) const;
int getIconIndex(const QString& key) const;
diff --git a/launcher/icons/IconUtils.cpp b/launcher/icons/IconUtils.cpp
index 99c38f47a..87e948729 100644
--- a/launcher/icons/IconUtils.cpp
+++ b/launcher/icons/IconUtils.cpp
@@ -39,7 +39,7 @@
#include "FileSystem.h"
namespace {
-static const QStringList validIconExtensions = { { "svg", "png", "ico", "gif", "jpg", "jpeg" } };
+static const QStringList validIconExtensions = { { "svg", "png", "ico", "gif", "jpg", "jpeg", "webp" } };
}
namespace IconUtils {
@@ -52,8 +52,7 @@ QString findBestIconIn(const QString& folder, const QString& iconKey)
while (it.hasNext()) {
it.next();
auto fileInfo = it.fileInfo();
-
- if (fileInfo.completeBaseName() == iconKey && isIconSuffix(fileInfo.suffix()))
+ if ((fileInfo.completeBaseName() == iconKey || fileInfo.fileName() == iconKey) && isIconSuffix(fileInfo.suffix()))
return fileInfo.absoluteFilePath();
}
return {};
diff --git a/launcher/install_prereqs.cmake.in b/launcher/install_prereqs.cmake.in
index e4408d161..acbce9650 100644
--- a/launcher/install_prereqs.cmake.in
+++ b/launcher/install_prereqs.cmake.in
@@ -1,5 +1,4 @@
set(CMAKE_MODULE_PATH "@CMAKE_MODULE_PATH@")
-
file(GLOB_RECURSE QTPLUGINS "${CMAKE_INSTALL_PREFIX}/@PLUGIN_DEST_DIR@/*@CMAKE_SHARED_LIBRARY_SUFFIX@")
function(gp_resolved_file_type_override resolved_file type_var)
if(resolved_file MATCHES "^/(usr/)?lib/libQt")
diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp
index fc8da55c2..c54a5b04b 100644
--- a/launcher/java/JavaChecker.cpp
+++ b/launcher/java/JavaChecker.cpp
@@ -40,14 +40,15 @@
#include
#include
-#include "Application.h"
#include "Commandline.h"
#include "FileSystem.h"
-#include "JavaUtils.h"
+#include "java/JavaUtils.h"
-JavaChecker::JavaChecker(QObject* parent) : QObject(parent) {}
+JavaChecker::JavaChecker(QString path, QString args, int minMem, int maxMem, int permGen, int id, QObject* parent)
+ : Task(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen), m_id(id)
+{}
-void JavaChecker::performCheck()
+void JavaChecker::executeTask()
{
QString checkerJar = JavaUtils::getJavaCheckPath();
@@ -72,7 +73,7 @@ void JavaChecker::performCheck()
if (m_maxMem != 0) {
args << QString("-Xmx%1m").arg(m_maxMem);
}
- if (m_permGen != 64) {
+ if (m_permGen != 64 && m_permGen != 0) {
args << QString("-XX:PermSize=%1m").arg(m_permGen);
}
@@ -115,11 +116,10 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
QProcessPtr _process = process;
process.reset();
- JavaCheckResult result;
- {
- result.path = m_path;
- result.id = m_id;
- }
+ Result result = {
+ m_path,
+ m_id,
+ };
result.errorLog = m_stderr;
result.outLog = m_stdout;
qDebug() << "STDOUT" << m_stdout;
@@ -127,8 +127,9 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
qDebug() << "Java checker finished with status" << status << "exit code" << exitcode;
if (status == QProcess::CrashExit || exitcode == 1) {
- result.validity = JavaCheckResult::Validity::Errored;
+ result.validity = Result::Validity::Errored;
emit checkFinished(result);
+ emitSucceeded();
return;
}
@@ -161,8 +162,9 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
}
if (!results.contains("os.arch") || !results.contains("java.version") || !results.contains("java.vendor") || !success) {
- result.validity = JavaCheckResult::Validity::ReturnedInvalidData;
+ result.validity = Result::Validity::ReturnedInvalidData;
emit checkFinished(result);
+ emitSucceeded();
return;
}
@@ -171,7 +173,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
auto java_vendor = results["java.vendor"];
bool is_64 = os_arch == "x86_64" || os_arch == "amd64" || os_arch == "aarch64" || os_arch == "arm64";
- result.validity = JavaCheckResult::Validity::Valid;
+ result.validity = Result::Validity::Valid;
result.is_64bit = is_64;
result.mojangPlatform = is_64 ? "64" : "32";
result.realPlatform = os_arch;
@@ -179,6 +181,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
result.javaVendor = java_vendor;
qDebug() << "Java checker succeeded.";
emit checkFinished(result);
+ emitSucceeded();
}
void JavaChecker::error(QProcess::ProcessError err)
@@ -190,15 +193,9 @@ void JavaChecker::error(QProcess::ProcessError err)
qDebug() << "Native environment:";
qDebug() << QProcessEnvironment::systemEnvironment().toStringList();
killTimer.stop();
- JavaCheckResult result;
- {
- result.path = m_path;
- result.id = m_id;
- }
-
- emit checkFinished(result);
- return;
+ emit checkFinished({ m_path, m_id });
}
+ emitSucceeded();
}
void JavaChecker::timeout()
diff --git a/launcher/java/JavaChecker.h b/launcher/java/JavaChecker.h
index 7111f8522..171a18b76 100644
--- a/launcher/java/JavaChecker.h
+++ b/launcher/java/JavaChecker.h
@@ -3,49 +3,51 @@
#include
#include
-#include "QObjectPtr.h"
-
#include "JavaVersion.h"
+#include "QObjectPtr.h"
+#include "tasks/Task.h"
-class JavaChecker;
-
-struct JavaCheckResult {
- QString path;
- QString mojangPlatform;
- QString realPlatform;
- JavaVersion javaVersion;
- QString javaVendor;
- QString outLog;
- QString errorLog;
- bool is_64bit = false;
- int id;
- enum class Validity { Errored, ReturnedInvalidData, Valid } validity = Validity::Errored;
-};
-
-using QProcessPtr = shared_qobject_ptr;
-using JavaCheckerPtr = shared_qobject_ptr;
-class JavaChecker : public QObject {
+class JavaChecker : public Task {
Q_OBJECT
public:
- explicit JavaChecker(QObject* parent = 0);
- void performCheck();
+ using QProcessPtr = shared_qobject_ptr;
+ using Ptr = shared_qobject_ptr;
- QString m_path;
- QString m_args;
- int m_id = 0;
- int m_minMem = 0;
- int m_maxMem = 0;
- int m_permGen = 64;
+ struct Result {
+ QString path;
+ int id;
+ QString mojangPlatform;
+ QString realPlatform;
+ JavaVersion javaVersion;
+ QString javaVendor;
+ QString outLog;
+ QString errorLog;
+ bool is_64bit = false;
+ enum class Validity { Errored, ReturnedInvalidData, Valid } validity = Validity::Errored;
+ };
+
+ explicit JavaChecker(QString path, QString args, int minMem = 0, int maxMem = 0, int permGen = 0, int id = 0, QObject* parent = 0);
signals:
- void checkFinished(JavaCheckResult result);
+ void checkFinished(const Result& result);
+
+ protected:
+ virtual void executeTask() override;
private:
QProcessPtr process;
QTimer killTimer;
QString m_stdout;
QString m_stderr;
- public slots:
+
+ QString m_path;
+ QString m_args;
+ int m_minMem = 0;
+ int m_maxMem = 0;
+ int m_permGen = 64;
+ int m_id = 0;
+
+ private slots:
void timeout();
void finished(int exitcode, QProcess::ExitStatus);
void error(QProcess::ProcessError);
diff --git a/launcher/java/JavaCheckerJob.cpp b/launcher/java/JavaCheckerJob.cpp
deleted file mode 100644
index 870e2a09a..000000000
--- a/launcher/java/JavaCheckerJob.cpp
+++ /dev/null
@@ -1,41 +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 "JavaCheckerJob.h"
-
-#include
-
-void JavaCheckerJob::partFinished(JavaCheckResult result)
-{
- num_finished++;
- qDebug() << m_job_name.toLocal8Bit() << "progress:" << num_finished << "/" << javacheckers.size();
- setProgress(num_finished, javacheckers.size());
-
- javaresults.replace(result.id, result);
-
- if (num_finished == javacheckers.size()) {
- emitSucceeded();
- }
-}
-
-void JavaCheckerJob::executeTask()
-{
- qDebug() << m_job_name.toLocal8Bit() << " started.";
- for (auto iter : javacheckers) {
- javaresults.append(JavaCheckResult());
- connect(iter.get(), &JavaChecker::checkFinished, this, &JavaCheckerJob::partFinished);
- iter->performCheck();
- }
-}
diff --git a/launcher/java/JavaCheckerJob.h b/launcher/java/JavaCheckerJob.h
deleted file mode 100644
index ddf827968..000000000
--- a/launcher/java/JavaCheckerJob.h
+++ /dev/null
@@ -1,56 +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
-#include "JavaChecker.h"
-#include "tasks/Task.h"
-
-class JavaCheckerJob;
-using JavaCheckerJobPtr = shared_qobject_ptr;
-
-// FIXME: this just seems horribly redundant
-class JavaCheckerJob : public Task {
- Q_OBJECT
- public:
- explicit JavaCheckerJob(QString job_name) : Task(), m_job_name(job_name){};
- virtual ~JavaCheckerJob(){};
-
- bool addJavaCheckerAction(JavaCheckerPtr base)
- {
- javacheckers.append(base);
- // if this is already running, the action needs to be started right away!
- if (isRunning()) {
- setProgress(num_finished, javacheckers.size());
- connect(base.get(), &JavaChecker::checkFinished, this, &JavaCheckerJob::partFinished);
- base->performCheck();
- }
- return true;
- }
- QList getResults() { return javaresults; }
-
- private slots:
- void partFinished(JavaCheckResult result);
-
- protected:
- virtual void executeTask() override;
-
- private:
- QString m_job_name;
- QList javacheckers;
- QList javaresults;
- int num_finished = 0;
-};
diff --git a/launcher/java/JavaInstall.cpp b/launcher/java/JavaInstall.cpp
index cfa471402..8e97e0e14 100644
--- a/launcher/java/JavaInstall.cpp
+++ b/launcher/java/JavaInstall.cpp
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
- * Copyright (c) 2023 Trial97
+ * Copyright (c) 2023-2024 Trial97
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/launcher/java/JavaInstall.h b/launcher/java/JavaInstall.h
index 8c2743a00..7d8d392fa 100644
--- a/launcher/java/JavaInstall.h
+++ b/launcher/java/JavaInstall.h
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
- * Copyright (c) 2023 Trial97
+ * Copyright (c) 2023-2024 Trial97
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -40,6 +40,7 @@ struct JavaInstall : public BaseVersion {
QString arch;
QString path;
bool recommended = false;
+ bool is_64bit = false;
};
using JavaInstallPtr = std::shared_ptr;
diff --git a/launcher/java/JavaInstallList.cpp b/launcher/java/JavaInstallList.cpp
index d8be4963f..569fda306 100644
--- a/launcher/java/JavaInstallList.cpp
+++ b/launcher/java/JavaInstallList.cpp
@@ -38,13 +38,17 @@
#include
#include
+#include
-#include "java/JavaCheckerJob.h"
+#include "Application.h"
+#include "java/JavaChecker.h"
#include "java/JavaInstallList.h"
#include "java/JavaUtils.h"
-#include "minecraft/VersionFilterData.h"
+#include "tasks/ConcurrentTask.h"
-JavaInstallList::JavaInstallList(QObject* parent) : BaseVersionList(parent) {}
+JavaInstallList::JavaInstallList(QObject* parent, bool onlyManagedVersions)
+ : BaseVersionList(parent), m_only_managed_versions(onlyManagedVersions)
+{}
Task::Ptr JavaInstallList::getLoadTask()
{
@@ -55,7 +59,7 @@ Task::Ptr JavaInstallList::getLoadTask()
Task::Ptr JavaInstallList::getCurrentTask()
{
if (m_status == Status::InProgress) {
- return m_loadTask;
+ return m_load_task;
}
return nullptr;
}
@@ -64,8 +68,8 @@ void JavaInstallList::load()
{
if (m_status != Status::InProgress) {
m_status = Status::InProgress;
- m_loadTask.reset(new JavaListLoadTask(this));
- m_loadTask->start();
+ m_load_task.reset(new JavaListLoadTask(this, m_only_managed_versions));
+ m_load_task->start();
}
}
@@ -106,7 +110,7 @@ QVariant JavaInstallList::data(const QModelIndex& index, int role) const
return version->recommended;
case PathRole:
return version->path;
- case ArchitectureRole:
+ case CPUArchitectureRole:
return version->arch;
default:
return QVariant();
@@ -115,7 +119,7 @@ QVariant JavaInstallList::data(const QModelIndex& index, int role) const
BaseVersionList::RoleList JavaInstallList::providesRoles() const
{
- return { VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, PathRole, ArchitectureRole };
+ return { VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, PathRole, CPUArchitectureRole };
}
void JavaInstallList::updateListData(QList versions)
@@ -129,7 +133,7 @@ void JavaInstallList::updateListData(QList versions)
}
endResetModel();
m_status = Status::Done;
- m_loadTask.reset();
+ m_load_task.reset();
}
bool sortJavas(BaseVersion::Ptr left, BaseVersion::Ptr right)
@@ -146,35 +150,30 @@ void JavaInstallList::sortVersions()
endResetModel();
}
-JavaListLoadTask::JavaListLoadTask(JavaInstallList* vlist) : Task()
+JavaListLoadTask::JavaListLoadTask(JavaInstallList* vlist, bool onlyManagedVersions) : Task(), m_only_managed_versions(onlyManagedVersions)
{
m_list = vlist;
- m_currentRecommended = NULL;
+ m_current_recommended = NULL;
}
-JavaListLoadTask::~JavaListLoadTask() {}
-
void JavaListLoadTask::executeTask()
{
setStatus(tr("Detecting Java installations..."));
JavaUtils ju;
- QList candidate_paths = ju.FindJavaPaths();
+ QList candidate_paths = m_only_managed_versions ? getPrismJavaBundle() : ju.FindJavaPaths();
- m_job.reset(new JavaCheckerJob("Java detection"));
+ ConcurrentTask::Ptr job(new ConcurrentTask(this, "Java detection", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
+ m_job.reset(job);
connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished);
connect(m_job.get(), &Task::progress, this, &Task::setProgress);
qDebug() << "Probing the following Java paths: ";
int id = 0;
for (QString candidate : candidate_paths) {
- qDebug() << " " << candidate;
-
- auto candidate_checker = new JavaChecker();
- candidate_checker->m_path = candidate;
- candidate_checker->m_id = id;
- m_job->addJavaCheckerAction(JavaCheckerPtr(candidate_checker));
-
+ auto checker = new JavaChecker(candidate, "", 0, 0, 0, id, this);
+ connect(checker, &JavaChecker::checkFinished, [this](const JavaChecker::Result& result) { m_results << result; });
+ job->addTask(Task::Ptr(checker));
id++;
}
@@ -184,16 +183,17 @@ void JavaListLoadTask::executeTask()
void JavaListLoadTask::javaCheckerFinished()
{
QList candidates;
- auto results = m_job->getResults();
+ std::sort(m_results.begin(), m_results.end(), [](const JavaChecker::Result& a, const JavaChecker::Result& b) { return a.id < b.id; });
qDebug() << "Found the following valid Java installations:";
- for (JavaCheckResult result : results) {
- if (result.validity == JavaCheckResult::Validity::Valid) {
+ for (auto result : m_results) {
+ if (result.validity == JavaChecker::Result::Validity::Valid) {
JavaInstallPtr javaVersion(new JavaInstall());
javaVersion->id = result.javaVersion;
javaVersion->arch = result.realPlatform;
javaVersion->path = result.path;
+ javaVersion->is_64bit = result.is_64bit;
candidates.append(javaVersion);
qDebug() << " " << javaVersion->id.toString() << javaVersion->arch << javaVersion->path;
diff --git a/launcher/java/JavaInstallList.h b/launcher/java/JavaInstallList.h
index 1eebadf23..b77f17b28 100644
--- a/launcher/java/JavaInstallList.h
+++ b/launcher/java/JavaInstallList.h
@@ -19,9 +19,9 @@
#include
#include "BaseVersionList.h"
+#include "java/JavaChecker.h"
#include "tasks/Task.h"
-#include "JavaCheckerJob.h"
#include "JavaInstall.h"
#include "QObjectPtr.h"
@@ -33,9 +33,9 @@ class JavaInstallList : public BaseVersionList {
enum class Status { NotDone, InProgress, Done };
public:
- explicit JavaInstallList(QObject* parent = 0);
+ explicit JavaInstallList(QObject* parent = 0, bool onlyManagedVersions = false);
- Task::Ptr getLoadTask() override;
+ [[nodiscard]] Task::Ptr getLoadTask() override;
bool isLoaded() override;
const BaseVersion::Ptr at(int i) const override;
int count() const override;
@@ -53,23 +53,27 @@ class JavaInstallList : public BaseVersionList {
protected:
Status m_status = Status::NotDone;
- shared_qobject_ptr m_loadTask;
+ shared_qobject_ptr m_load_task;
QList m_vlist;
+ bool m_only_managed_versions;
};
class JavaListLoadTask : public Task {
Q_OBJECT
public:
- explicit JavaListLoadTask(JavaInstallList* vlist);
- virtual ~JavaListLoadTask();
+ explicit JavaListLoadTask(JavaInstallList* vlist, bool onlyManagedVersions = false);
+ virtual ~JavaListLoadTask() = default;
+ protected:
void executeTask() override;
public slots:
void javaCheckerFinished();
protected:
- shared_qobject_ptr m_job;
+ Task::Ptr m_job;
JavaInstallList* m_list;
- JavaInstall* m_currentRecommended;
+ JavaInstall* m_current_recommended;
+ QList m_results;
+ bool m_only_managed_versions;
};
diff --git a/launcher/java/JavaMetadata.cpp b/launcher/java/JavaMetadata.cpp
new file mode 100644
index 000000000..2d68f55c8
--- /dev/null
+++ b/launcher/java/JavaMetadata.cpp
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2023-2024 Trial97
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "java/JavaMetadata.h"
+
+#include
+
+#include "Json.h"
+#include "StringUtils.h"
+#include "java/JavaVersion.h"
+#include "minecraft/ParseUtils.h"
+
+namespace Java {
+
+DownloadType parseDownloadType(QString javaDownload)
+{
+ if (javaDownload == "manifest")
+ return DownloadType::Manifest;
+ else if (javaDownload == "archive")
+ return DownloadType::Archive;
+ else
+ return DownloadType::Unknown;
+}
+QString downloadTypeToString(DownloadType javaDownload)
+{
+ switch (javaDownload) {
+ case DownloadType::Manifest:
+ return "manifest";
+ case DownloadType::Archive:
+ return "archive";
+ case DownloadType::Unknown:
+ break;
+ }
+ return "unknown";
+}
+MetadataPtr parseJavaMeta(const QJsonObject& in)
+{
+ auto meta = std::make_shared();
+
+ meta->m_name = Json::ensureString(in, "name", "");
+ meta->vendor = Json::ensureString(in, "vendor", "");
+ meta->url = Json::ensureString(in, "url", "");
+ meta->releaseTime = timeFromS3Time(Json::ensureString(in, "releaseTime", ""));
+ meta->downloadType = parseDownloadType(Json::ensureString(in, "downloadType", ""));
+ meta->packageType = Json::ensureString(in, "packageType", "");
+ meta->runtimeOS = Json::ensureString(in, "runtimeOS", "unknown");
+
+ if (in.contains("checksum")) {
+ auto obj = Json::requireObject(in, "checksum");
+ meta->checksumHash = Json::ensureString(obj, "hash", "");
+ meta->checksumType = Json::ensureString(obj, "type", "");
+ }
+
+ if (in.contains("version")) {
+ auto obj = Json::requireObject(in, "version");
+ auto name = Json::ensureString(obj, "name", "");
+ auto major = Json::ensureInteger(obj, "major", 0);
+ auto minor = Json::ensureInteger(obj, "minor", 0);
+ auto security = Json::ensureInteger(obj, "security", 0);
+ auto build = Json::ensureInteger(obj, "build", 0);
+ meta->version = JavaVersion(major, minor, security, build, name);
+ }
+ return meta;
+}
+
+bool Metadata::operator<(const Metadata& rhs)
+{
+ auto id = version;
+ if (id < rhs.version) {
+ return true;
+ }
+ if (id > rhs.version) {
+ return false;
+ }
+ auto date = releaseTime;
+ if (date < rhs.releaseTime) {
+ return true;
+ }
+ if (date > rhs.releaseTime) {
+ return false;
+ }
+ return StringUtils::naturalCompare(m_name, rhs.m_name, Qt::CaseInsensitive) < 0;
+}
+
+bool Metadata::operator==(const Metadata& rhs)
+{
+ return version == rhs.version && m_name == rhs.m_name;
+}
+
+bool Metadata::operator>(const Metadata& rhs)
+{
+ return (!operator<(rhs)) && (!operator==(rhs));
+}
+
+bool Metadata::operator<(BaseVersion& a)
+{
+ try {
+ return operator<(dynamic_cast(a));
+ } catch (const std::bad_cast& e) {
+ return BaseVersion::operator<(a);
+ }
+}
+
+bool Metadata::operator>(BaseVersion& a)
+{
+ try {
+ return operator>(dynamic_cast(a));
+ } catch (const std::bad_cast& e) {
+ return BaseVersion::operator>(a);
+ }
+}
+
+} // namespace Java
diff --git a/launcher/java/JavaMetadata.h b/launcher/java/JavaMetadata.h
new file mode 100644
index 000000000..77a42fd78
--- /dev/null
+++ b/launcher/java/JavaMetadata.h
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2023-2024 Trial97
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+#pragma once
+
+#include
+#include
+#include
+
+#include
+
+#include "BaseVersion.h"
+#include "java/JavaVersion.h"
+
+namespace Java {
+
+enum class DownloadType { Manifest, Archive, Unknown };
+
+class Metadata : public BaseVersion {
+ public:
+ virtual QString descriptor() override { return version.toString(); }
+
+ virtual QString name() override { return m_name; }
+
+ virtual QString typeString() const override { return vendor; }
+
+ virtual bool operator<(BaseVersion& a) override;
+ virtual bool operator>(BaseVersion& a) override;
+ bool operator<(const Metadata& rhs);
+ bool operator==(const Metadata& rhs);
+ bool operator>(const Metadata& rhs);
+
+ QString m_name;
+ QString vendor;
+ QString url;
+ QDateTime releaseTime;
+ QString checksumType;
+ QString checksumHash;
+ DownloadType downloadType;
+ QString packageType;
+ JavaVersion version;
+ QString runtimeOS;
+};
+using MetadataPtr = std::shared_ptr;
+
+DownloadType parseDownloadType(QString javaDownload);
+QString downloadTypeToString(DownloadType javaDownload);
+MetadataPtr parseJavaMeta(const QJsonObject& libObj);
+
+} // namespace Java
\ No newline at end of file
diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp
index 3627cec39..bc8026348 100644
--- a/launcher/java/JavaUtils.cpp
+++ b/launcher/java/JavaUtils.cpp
@@ -79,11 +79,9 @@ QProcessEnvironment CleanEnviroment()
QStringList stripped = {
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
- "LD_LIBRARY_PATH",
- "LD_PRELOAD",
+ "LD_LIBRARY_PATH", "LD_PRELOAD",
#endif
- "QT_PLUGIN_PATH",
- "QT_FONTPATH"
+ "QT_PLUGIN_PATH", "QT_FONTPATH"
};
for (auto key : rawenv.keys()) {
auto value = rawenv.value(key);
@@ -184,56 +182,58 @@ QList JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString
else if (keyType == KEY_WOW64_32KEY)
archType = "32";
- HKEY jreKey;
- if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, keyName.toStdWString().c_str(), 0, KEY_READ | keyType | KEY_ENUMERATE_SUB_KEYS, &jreKey) ==
- ERROR_SUCCESS) {
- // Read the current type version from the registry.
- // This will be used to find any key that contains the JavaHome value.
+ for (HKEY baseRegistry : { HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE }) {
+ HKEY jreKey;
+ if (RegOpenKeyExW(baseRegistry, keyName.toStdWString().c_str(), 0, KEY_READ | keyType | KEY_ENUMERATE_SUB_KEYS, &jreKey) ==
+ ERROR_SUCCESS) {
+ // Read the current type version from the registry.
+ // This will be used to find any key that contains the JavaHome value.
- WCHAR subKeyName[255];
- DWORD subKeyNameSize, numSubKeys, retCode;
+ WCHAR subKeyName[255];
+ DWORD subKeyNameSize, numSubKeys, retCode;
- // Get the number of subkeys
- RegQueryInfoKeyW(jreKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+ // Get the number of subkeys
+ RegQueryInfoKeyW(jreKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
- // Iterate until RegEnumKeyEx fails
- if (numSubKeys > 0) {
- for (DWORD i = 0; i < numSubKeys; i++) {
- subKeyNameSize = 255;
- retCode = RegEnumKeyExW(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL, NULL);
- QString newSubkeyName = QString::fromWCharArray(subKeyName);
- if (retCode == ERROR_SUCCESS) {
- // Now open the registry key for the version that we just got.
- QString newKeyName = keyName + "\\" + newSubkeyName + subkeySuffix;
+ // Iterate until RegEnumKeyEx fails
+ if (numSubKeys > 0) {
+ for (DWORD i = 0; i < numSubKeys; i++) {
+ subKeyNameSize = 255;
+ retCode = RegEnumKeyExW(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL, NULL);
+ QString newSubkeyName = QString::fromWCharArray(subKeyName);
+ if (retCode == ERROR_SUCCESS) {
+ // Now open the registry key for the version that we just got.
+ QString newKeyName = keyName + "\\" + newSubkeyName + subkeySuffix;
- HKEY newKey;
- if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, newKeyName.toStdWString().c_str(), 0, KEY_READ | KEY_WOW64_64KEY, &newKey) ==
- ERROR_SUCCESS) {
- // Read the JavaHome value to find where Java is installed.
- DWORD valueSz = 0;
- if (RegQueryValueExW(newKey, keyJavaDir.toStdWString().c_str(), NULL, NULL, NULL, &valueSz) == ERROR_SUCCESS) {
- WCHAR* value = new WCHAR[valueSz];
- RegQueryValueExW(newKey, keyJavaDir.toStdWString().c_str(), NULL, NULL, (BYTE*)value, &valueSz);
+ HKEY newKey;
+ if (RegOpenKeyExW(baseRegistry, newKeyName.toStdWString().c_str(), 0, KEY_READ | keyType, &newKey) ==
+ ERROR_SUCCESS) {
+ // Read the JavaHome value to find where Java is installed.
+ DWORD valueSz = 0;
+ if (RegQueryValueExW(newKey, keyJavaDir.toStdWString().c_str(), NULL, NULL, NULL, &valueSz) == ERROR_SUCCESS) {
+ WCHAR* value = new WCHAR[valueSz];
+ RegQueryValueExW(newKey, keyJavaDir.toStdWString().c_str(), NULL, NULL, (BYTE*)value, &valueSz);
- QString newValue = QString::fromWCharArray(value);
- delete[] value;
+ QString newValue = QString::fromWCharArray(value);
+ delete[] value;
- // Now, we construct the version object and add it to the list.
- JavaInstallPtr javaVersion(new JavaInstall());
+ // Now, we construct the version object and add it to the list.
+ JavaInstallPtr javaVersion(new JavaInstall());
- javaVersion->id = newSubkeyName;
- javaVersion->arch = archType;
- javaVersion->path = QDir(FS::PathCombine(newValue, "bin")).absoluteFilePath("javaw.exe");
- javas.append(javaVersion);
+ javaVersion->id = newSubkeyName;
+ javaVersion->arch = archType;
+ javaVersion->path = QDir(FS::PathCombine(newValue, "bin")).absoluteFilePath("javaw.exe");
+ javas.append(javaVersion);
+ }
+
+ RegCloseKey(newKey);
}
-
- RegCloseKey(newKey);
}
}
}
- }
- RegCloseKey(jreKey);
+ RegCloseKey(jreKey);
+ }
}
return javas;
@@ -283,6 +283,12 @@ QList JavaUtils::FindJavaPaths()
QList ADOPTIUMJDK64s =
this->FindJavaFromRegistryKey(KEY_WOW64_64KEY, "SOFTWARE\\Eclipse Adoptium\\JDK", "Path", "\\hotspot\\MSI");
+ // IBM Semeru
+ QList SEMERUJRE32s = this->FindJavaFromRegistryKey(KEY_WOW64_32KEY, "SOFTWARE\\Semeru\\JRE", "Path", "\\openj9\\MSI");
+ QList SEMERUJRE64s = this->FindJavaFromRegistryKey(KEY_WOW64_64KEY, "SOFTWARE\\Semeru\\JRE", "Path", "\\openj9\\MSI");
+ QList SEMERUJDK32s = this->FindJavaFromRegistryKey(KEY_WOW64_32KEY, "SOFTWARE\\Semeru\\JDK", "Path", "\\openj9\\MSI");
+ QList SEMERUJDK64s = this->FindJavaFromRegistryKey(KEY_WOW64_64KEY, "SOFTWARE\\Semeru\\JDK", "Path", "\\openj9\\MSI");
+
// Microsoft
QList MICROSOFTJDK64s =
this->FindJavaFromRegistryKey(KEY_WOW64_64KEY, "SOFTWARE\\Microsoft\\JDK", "Path", "\\hotspot\\MSI");
@@ -300,6 +306,7 @@ QList JavaUtils::FindJavaPaths()
java_candidates.append(NEWJRE64s);
java_candidates.append(ADOPTOPENJRE64s);
java_candidates.append(ADOPTIUMJRE64s);
+ java_candidates.append(SEMERUJRE64s);
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre8/bin/javaw.exe"));
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre7/bin/javaw.exe"));
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre6/bin/javaw.exe"));
@@ -308,6 +315,7 @@ QList JavaUtils::FindJavaPaths()
java_candidates.append(ADOPTOPENJDK64s);
java_candidates.append(FOUNDATIONJDK64s);
java_candidates.append(ADOPTIUMJDK64s);
+ java_candidates.append(SEMERUJDK64s);
java_candidates.append(MICROSOFTJDK64s);
java_candidates.append(ZULU64s);
java_candidates.append(LIBERICA64s);
@@ -316,6 +324,7 @@ QList JavaUtils::FindJavaPaths()
java_candidates.append(NEWJRE32s);
java_candidates.append(ADOPTOPENJRE32s);
java_candidates.append(ADOPTIUMJRE32s);
+ java_candidates.append(SEMERUJRE32s);
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre8/bin/javaw.exe"));
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre7/bin/javaw.exe"));
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre6/bin/javaw.exe"));
@@ -324,6 +333,7 @@ QList JavaUtils::FindJavaPaths()
java_candidates.append(ADOPTOPENJDK32s);
java_candidates.append(FOUNDATIONJDK32s);
java_candidates.append(ADOPTIUMJDK32s);
+ java_candidates.append(SEMERUJDK32s);
java_candidates.append(ZULU32s);
java_candidates.append(LIBERICA32s);
@@ -337,6 +347,7 @@ QList JavaUtils::FindJavaPaths()
}
candidates.append(getMinecraftJavaBundle());
+ candidates.append(getPrismJavaBundle());
candidates = addJavasFromEnv(candidates);
candidates.removeDuplicates();
return candidates;
@@ -362,23 +373,47 @@ QList JavaUtils::FindJavaPaths()
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java");
}
+
+ auto home = qEnvironmentVariable("HOME");
+
+ // javas downloaded by sdkman
+ QDir sdkmanDir(FS::PathCombine(home, ".sdkman/candidates/java"));
+ QStringList sdkmanJavas = sdkmanDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
+ foreach (const QString& java, sdkmanJavas) {
+ javas.append(sdkmanDir.absolutePath() + "/" + java + "/bin/java");
+ }
+
+ // java in user library folder (like from intellij downloads)
+ QDir userLibraryJVMDir(FS::PathCombine(home, "Library/Java/JavaVirtualMachines/"));
+ QStringList userLibraryJVMJavas = userLibraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
+ foreach (const QString& java, userLibraryJVMJavas) {
+ javas.append(userLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
+ javas.append(userLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java");
+ }
+
javas.append(getMinecraftJavaBundle());
+ javas.append(getPrismJavaBundle());
javas = addJavasFromEnv(javas);
javas.removeDuplicates();
return javas;
}
-#elif defined(Q_OS_LINUX)
+#elif defined(Q_OS_LINUX) || defined(Q_OS_OPENBSD) || defined(Q_OS_FREEBSD)
QList JavaUtils::FindJavaPaths()
{
QList javas;
javas.append(this->GetDefaultJava()->path);
- auto scanJavaDir = [&](const QString& dirPath) {
+ auto scanJavaDir = [&](
+ const QString& dirPath,
+ const std::function& filter = [](const QFileInfo&) { return true; }) {
QDir dir(dirPath);
if (!dir.exists())
return;
auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
for (auto& entry : entries) {
+ if (!filter(entry))
+ continue;
+
QString prefix;
prefix = entry.canonicalFilePath();
javas.append(FS::PathCombine(prefix, "jre/bin/java"));
@@ -393,20 +428,33 @@ QList JavaUtils::FindJavaPaths()
scanJavaDir(snap + dirPath);
}
};
+#if defined(Q_OS_LINUX)
// oracle RPMs
scanJavaDirs("/usr/java");
// general locations used by distro packaging
scanJavaDirs("/usr/lib/jvm");
scanJavaDirs("/usr/lib64/jvm");
scanJavaDirs("/usr/lib32/jvm");
+ // Gentoo's locations for openjdk and openjdk-bin respectively
+ auto gentooFilter = [](const QFileInfo& info) {
+ QString fileName = info.fileName();
+ return fileName.startsWith("openjdk-") || fileName.startsWith("openj9-");
+ };
+ scanJavaDir("/usr/lib64", gentooFilter);
+ scanJavaDir("/usr/lib", gentooFilter);
+ scanJavaDir("/opt", gentooFilter);
// javas stored in Prism Launcher's folder
scanJavaDirs("java");
// manually installed JDKs in /opt
scanJavaDirs("/opt/jdk");
scanJavaDirs("/opt/jdks");
+ scanJavaDirs("/opt/ibm"); // IBM Semeru Certified Edition
// flatpak
scanJavaDirs("/app/jdk");
-
+#elif defined(Q_OS_OPENBSD) || defined(Q_OS_FREEBSD)
+ // ports install to /usr/local on OpenBSD & FreeBSD
+ scanJavaDirs("/usr/local");
+#endif
auto home = qEnvironmentVariable("HOME");
// javas downloaded by IntelliJ
@@ -417,6 +465,7 @@ QList JavaUtils::FindJavaPaths()
scanJavaDirs(FS::PathCombine(home, ".gradle/jdks"));
javas.append(getMinecraftJavaBundle());
+ javas.append(getPrismJavaBundle());
javas = addJavasFromEnv(javas);
javas.removeDuplicates();
return javas;
@@ -430,6 +479,8 @@ QList JavaUtils::FindJavaPaths()
javas.append(this->GetDefaultJava()->path);
javas.append(getMinecraftJavaBundle());
+ javas.append(getPrismJavaBundle());
+ javas.removeDuplicates();
return addJavasFromEnv(javas);
}
#endif
@@ -441,12 +492,10 @@ QString JavaUtils::getJavaCheckPath()
QStringList getMinecraftJavaBundle()
{
- QString executable = "java";
QStringList processpaths;
#if defined(Q_OS_OSX)
processpaths << FS::PathCombine(QDir::homePath(), FS::PathCombine("Library", "Application Support", "minecraft", "runtime"));
#elif defined(Q_OS_WIN32)
- executable += "w.exe";
auto appDataPath = QProcessEnvironment::systemEnvironment().value("APPDATA", "");
processpaths << FS::PathCombine(QFileInfo(appDataPath).absoluteFilePath(), ".minecraft", "runtime");
@@ -471,7 +520,7 @@ QStringList getMinecraftJavaBundle()
auto binFound = false;
for (auto& entry : entries) {
if (entry.baseName() == "bin") {
- javas.append(FS::PathCombine(entry.canonicalFilePath(), executable));
+ javas.append(FS::PathCombine(entry.canonicalFilePath(), JavaUtils::javaExecutable));
binFound = true;
break;
}
@@ -484,3 +533,33 @@ QStringList getMinecraftJavaBundle()
}
return javas;
}
+
+#if defined(Q_OS_WIN32)
+const QString JavaUtils::javaExecutable = "javaw.exe";
+#else
+const QString JavaUtils::javaExecutable = "java";
+#endif
+
+QStringList getPrismJavaBundle()
+{
+ QList javas;
+
+ auto scanDir = [&](QString prefix) {
+ javas.append(FS::PathCombine(prefix, "jre", "bin", JavaUtils::javaExecutable));
+ javas.append(FS::PathCombine(prefix, "bin", JavaUtils::javaExecutable));
+ javas.append(FS::PathCombine(prefix, JavaUtils::javaExecutable));
+ };
+ auto scanJavaDir = [&](const QString& dirPath) {
+ QDir dir(dirPath);
+ if (!dir.exists())
+ return;
+ auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
+ for (auto& entry : entries) {
+ scanDir(entry.canonicalFilePath());
+ }
+ };
+
+ scanJavaDir(APPLICATION->javaPath());
+
+ return javas;
+}
diff --git a/launcher/java/JavaUtils.h b/launcher/java/JavaUtils.h
index 2fb03af7a..eb3a17316 100644
--- a/launcher/java/JavaUtils.h
+++ b/launcher/java/JavaUtils.h
@@ -15,10 +15,9 @@
#pragma once
+#include
#include
-
-#include "JavaChecker.h"
-#include "JavaInstallList.h"
+#include "java/JavaInstall.h"
#ifdef Q_OS_WIN
#include
@@ -27,6 +26,7 @@
QString stripVariableEntries(QString name, QString target, QString remove);
QProcessEnvironment CleanEnviroment();
QStringList getMinecraftJavaBundle();
+QStringList getPrismJavaBundle();
class JavaUtils : public QObject {
Q_OBJECT
@@ -42,4 +42,5 @@ class JavaUtils : public QObject {
#endif
static QString getJavaCheckPath();
+ static const QString javaExecutable;
};
diff --git a/launcher/java/JavaVersion.cpp b/launcher/java/JavaVersion.cpp
index b77bf2adf..5e9700012 100644
--- a/launcher/java/JavaVersion.cpp
+++ b/launcher/java/JavaVersion.cpp
@@ -43,12 +43,12 @@ QString JavaVersion::toString() const
return m_string;
}
-bool JavaVersion::requiresPermGen()
+bool JavaVersion::requiresPermGen() const
{
return !m_parseable || m_major < 8;
}
-bool JavaVersion::isModular()
+bool JavaVersion::isModular() const
{
return m_parseable && m_major >= 9;
}
@@ -59,12 +59,6 @@ bool JavaVersion::operator<(const JavaVersion& rhs)
auto major = m_major;
auto rmajor = rhs.m_major;
- // HACK: discourage using java 9
- if (major > 8)
- major = -major;
- if (rmajor > 8)
- rmajor = -rmajor;
-
if (major < rmajor)
return true;
if (major > rmajor)
@@ -109,3 +103,24 @@ bool JavaVersion::operator>(const JavaVersion& rhs)
{
return (!operator<(rhs)) && (!operator==(rhs));
}
+
+JavaVersion::JavaVersion(int major, int minor, int security, int build, QString name)
+ : m_major(major), m_minor(minor), m_security(security), m_name(name), m_parseable(true)
+{
+ QStringList versions;
+ if (build != 0) {
+ m_prerelease = QString::number(build);
+ versions.push_front(m_prerelease);
+ }
+ if (m_security != 0)
+ versions.push_front(QString::number(m_security));
+ else if (!versions.isEmpty())
+ versions.push_front("0");
+
+ if (m_minor != 0)
+ versions.push_front(QString::number(m_minor));
+ else if (!versions.isEmpty())
+ versions.push_front("0");
+ versions.push_front(QString::number(m_major));
+ m_string = versions.join(".");
+}
diff --git a/launcher/java/JavaVersion.h b/launcher/java/JavaVersion.h
index 421578ea1..dfb4770da 100644
--- a/launcher/java/JavaVersion.h
+++ b/launcher/java/JavaVersion.h
@@ -16,6 +16,7 @@ class JavaVersion {
public:
JavaVersion() {}
JavaVersion(const QString& rhs);
+ JavaVersion(int major, int minor, int security, int build = 0, QString name = "");
JavaVersion& operator=(const QString& rhs);
@@ -23,21 +24,24 @@ class JavaVersion {
bool operator==(const JavaVersion& rhs);
bool operator>(const JavaVersion& rhs);
- bool requiresPermGen();
+ bool requiresPermGen() const;
- bool isModular();
+ bool isModular() const;
QString toString() const;
- int major() { return m_major; }
- int minor() { return m_minor; }
- int security() { return m_security; }
+ int major() const { return m_major; }
+ int minor() const { return m_minor; }
+ int security() const { return m_security; }
+ QString build() const { return m_prerelease; }
+ QString name() const { return m_name; }
private:
QString m_string;
int m_major = 0;
int m_minor = 0;
int m_security = 0;
+ QString m_name = "";
bool m_parseable = false;
QString m_prerelease;
};
diff --git a/launcher/java/download/ArchiveDownloadTask.cpp b/launcher/java/download/ArchiveDownloadTask.cpp
new file mode 100644
index 000000000..6d6ab0cef
--- /dev/null
+++ b/launcher/java/download/ArchiveDownloadTask.cpp
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2023-2024 Trial97
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+#include "java/download/ArchiveDownloadTask.h"
+#include
+#include
+#include "MMCZip.h"
+
+#include "Application.h"
+#include "Untar.h"
+#include "net/ChecksumValidator.h"
+#include "net/NetJob.h"
+#include "tasks/Task.h"
+
+namespace Java {
+ArchiveDownloadTask::ArchiveDownloadTask(QUrl url, QString final_path, QString checksumType, QString checksumHash)
+ : m_url(url), m_final_path(final_path), m_checksum_type(checksumType), m_checksum_hash(checksumHash)
+{}
+
+void ArchiveDownloadTask::executeTask()
+{
+ // JRE found ! download the zip
+ setStatus(tr("Downloading Java"));
+
+ MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("java", m_url.fileName());
+
+ auto download = makeShared(QString("JRE::DownloadJava"), APPLICATION->network());
+ auto action = Net::Download::makeCached(m_url, entry);
+ if (!m_checksum_hash.isEmpty() && !m_checksum_type.isEmpty()) {
+ auto hashType = QCryptographicHash::Algorithm::Sha1;
+ if (m_checksum_type == "sha256") {
+ hashType = QCryptographicHash::Algorithm::Sha256;
+ }
+ action->addValidator(new Net::ChecksumValidator(hashType, QByteArray::fromHex(m_checksum_hash.toUtf8())));
+ }
+ download->addNetAction(action);
+ auto fullPath = entry->getFullPath();
+
+ connect(download.get(), &Task::failed, this, &ArchiveDownloadTask::emitFailed);
+ connect(download.get(), &Task::progress, this, &ArchiveDownloadTask::setProgress);
+ connect(download.get(), &Task::stepProgress, this, &ArchiveDownloadTask::propagateStepProgress);
+ connect(download.get(), &Task::status, this, &ArchiveDownloadTask::setStatus);
+ connect(download.get(), &Task::details, this, &ArchiveDownloadTask::setDetails);
+ connect(download.get(), &Task::succeeded, [this, fullPath] {
+ // This should do all of the extracting and creating folders
+ extractJava(fullPath);
+ });
+ m_task = download;
+ m_task->start();
+}
+
+void ArchiveDownloadTask::extractJava(QString input)
+{
+ setStatus(tr("Extracting java"));
+ if (input.endsWith("tar")) {
+ setStatus(tr("Extracting Java (Progress is not reported for tar archives)"));
+ QFile in(input);
+ if (!in.open(QFile::ReadOnly)) {
+ emitFailed(tr("Unable to open supplied tar file."));
+ return;
+ }
+ if (!Tar::extract(&in, QDir(m_final_path).absolutePath())) {
+ emitFailed(tr("Unable to extract supplied tar file."));
+ return;
+ }
+ emitSucceeded();
+ return;
+ } else if (input.endsWith("tar.gz") || input.endsWith("taz") || input.endsWith("tgz")) {
+ setStatus(tr("Extracting Java (Progress is not reported for tar archives)"));
+ if (!GZTar::extract(input, QDir(m_final_path).absolutePath())) {
+ emitFailed(tr("Unable to extract supplied tar file."));
+ return;
+ }
+ emitSucceeded();
+ return;
+ } else if (input.endsWith("zip")) {
+ auto zip = std::make_shared(input);
+ if (!zip->open(QuaZip::mdUnzip)) {
+ emitFailed(tr("Unable to open supplied zip file."));
+ return;
+ }
+ auto files = zip->getFileNameList();
+ if (files.isEmpty()) {
+ emitFailed(tr("No files were found in the supplied zip file."));
+ return;
+ }
+ m_task = makeShared(zip, m_final_path, files[0]);
+
+ auto progressStep = std::make_shared();
+ connect(m_task.get(), &Task::finished, this, [this, progressStep] {
+ progressStep->state = TaskStepState::Succeeded;
+ stepProgress(*progressStep);
+ });
+
+ connect(m_task.get(), &Task::succeeded, this, &ArchiveDownloadTask::emitSucceeded);
+ connect(m_task.get(), &Task::aborted, this, &ArchiveDownloadTask::emitAborted);
+ connect(m_task.get(), &Task::failed, this, [this, progressStep](QString reason) {
+ progressStep->state = TaskStepState::Failed;
+ stepProgress(*progressStep);
+ emitFailed(reason);
+ });
+ connect(m_task.get(), &Task::stepProgress, this, &ArchiveDownloadTask::propagateStepProgress);
+
+ connect(m_task.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) {
+ progressStep->update(current, total);
+ stepProgress(*progressStep);
+ });
+ connect(m_task.get(), &Task::status, this, [this, progressStep](QString status) {
+ progressStep->status = status;
+ stepProgress(*progressStep);
+ });
+ m_task->start();
+ return;
+ }
+
+ emitFailed(tr("Could not determine archive type!"));
+}
+
+bool ArchiveDownloadTask::abort()
+{
+ auto aborted = canAbort();
+ if (m_task)
+ aborted = m_task->abort();
+ emitAborted();
+ return aborted;
+};
+} // namespace Java
\ No newline at end of file
diff --git a/launcher/java/download/ArchiveDownloadTask.h b/launcher/java/download/ArchiveDownloadTask.h
new file mode 100644
index 000000000..1db33763a
--- /dev/null
+++ b/launcher/java/download/ArchiveDownloadTask.h
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2023-2024 Trial97
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+
+#include
+#include "tasks/Task.h"
+
+namespace Java {
+class ArchiveDownloadTask : public Task {
+ Q_OBJECT
+ public:
+ ArchiveDownloadTask(QUrl url, QString final_path, QString checksumType = "", QString checksumHash = "");
+ virtual ~ArchiveDownloadTask() = default;
+
+ [[nodiscard]] bool canAbort() const override { return true; }
+ void executeTask() override;
+ virtual bool abort() override;
+
+ private slots:
+ void extractJava(QString input);
+
+ protected:
+ QUrl m_url;
+ QString m_final_path;
+ QString m_checksum_type;
+ QString m_checksum_hash;
+ Task::Ptr m_task;
+};
+} // namespace Java
\ No newline at end of file
diff --git a/launcher/java/download/ManifestDownloadTask.cpp b/launcher/java/download/ManifestDownloadTask.cpp
new file mode 100644
index 000000000..836afeaac
--- /dev/null
+++ b/launcher/java/download/ManifestDownloadTask.cpp
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2023-2024 Trial97
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+#include "java/download/ManifestDownloadTask.h"
+
+#include "Application.h"
+#include "FileSystem.h"
+#include "Json.h"
+#include "net/ChecksumValidator.h"
+#include "net/NetJob.h"
+
+struct File {
+ QString path;
+ QString url;
+ QByteArray hash;
+ bool isExec;
+};
+
+namespace Java {
+ManifestDownloadTask::ManifestDownloadTask(QUrl url, QString final_path, QString checksumType, QString checksumHash)
+ : m_url(url), m_final_path(final_path), m_checksum_type(checksumType), m_checksum_hash(checksumHash)
+{}
+
+void ManifestDownloadTask::executeTask()
+{
+ setStatus(tr("Downloading Java"));
+ auto download = makeShared(QString("JRE::DownloadJava"), APPLICATION->network());
+ auto files = std::make_shared();
+
+ auto action = Net::Download::makeByteArray(m_url, files);
+ if (!m_checksum_hash.isEmpty() && !m_checksum_type.isEmpty()) {
+ auto hashType = QCryptographicHash::Algorithm::Sha1;
+ if (m_checksum_type == "sha256") {
+ hashType = QCryptographicHash::Algorithm::Sha256;
+ }
+ action->addValidator(new Net::ChecksumValidator(hashType, QByteArray::fromHex(m_checksum_hash.toUtf8())));
+ }
+ download->addNetAction(action);
+
+ connect(download.get(), &Task::failed, this, &ManifestDownloadTask::emitFailed);
+ connect(download.get(), &Task::progress, this, &ManifestDownloadTask::setProgress);
+ connect(download.get(), &Task::stepProgress, this, &ManifestDownloadTask::propagateStepProgress);
+ connect(download.get(), &Task::status, this, &ManifestDownloadTask::setStatus);
+ connect(download.get(), &Task::details, this, &ManifestDownloadTask::setDetails);
+
+ connect(download.get(), &Task::succeeded, [files, this] {
+ QJsonParseError parse_error{};
+ QJsonDocument doc = QJsonDocument::fromJson(*files, &parse_error);
+ if (parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response at " << parse_error.offset << ". Reason: " << parse_error.errorString();
+ qWarning() << *files;
+ emitFailed(parse_error.errorString());
+ return;
+ }
+ downloadJava(doc);
+ });
+ m_task = download;
+ m_task->start();
+}
+
+void ManifestDownloadTask::downloadJava(const QJsonDocument& doc)
+{
+ // valid json doc, begin making jre spot
+ FS::ensureFolderPathExists(m_final_path);
+ std::vector toDownload;
+ auto list = Json::ensureObject(Json::ensureObject(doc.object()), "files");
+ for (const auto& paths : list.keys()) {
+ auto file = FS::PathCombine(m_final_path, paths);
+
+ const QJsonObject& meta = Json::ensureObject(list, paths);
+ auto type = Json::ensureString(meta, "type");
+ if (type == "directory") {
+ FS::ensureFolderPathExists(file);
+ } else if (type == "link") {
+ // this is linux only !
+ auto path = Json::ensureString(meta, "target");
+ if (!path.isEmpty()) {
+ auto target = FS::PathCombine(file, "../" + path);
+ QFile(target).link(file);
+ }
+ } else if (type == "file") {
+ // TODO download compressed version if it exists ?
+ auto raw = Json::ensureObject(Json::ensureObject(meta, "downloads"), "raw");
+ auto isExec = Json::ensureBoolean(meta, "executable", false);
+ auto url = Json::ensureString(raw, "url");
+ if (!url.isEmpty() && QUrl(url).isValid()) {
+ auto f = File{ file, url, QByteArray::fromHex(Json::ensureString(raw, "sha1").toLatin1()), isExec };
+ toDownload.push_back(f);
+ }
+ }
+ }
+ auto elementDownload = makeShared("JRE::FileDownload", APPLICATION->network());
+ for (const auto& file : toDownload) {
+ auto dl = Net::Download::makeFile(file.url, file.path);
+ if (!file.hash.isEmpty()) {
+ dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, file.hash));
+ }
+ if (file.isExec) {
+ connect(dl.get(), &Net::Download::succeeded,
+ [file] { QFile(file.path).setPermissions(QFile(file.path).permissions() | QFileDevice::Permissions(0x1111)); });
+ }
+ elementDownload->addNetAction(dl);
+ }
+
+ connect(elementDownload.get(), &Task::failed, this, &ManifestDownloadTask::emitFailed);
+ connect(elementDownload.get(), &Task::progress, this, &ManifestDownloadTask::setProgress);
+ connect(elementDownload.get(), &Task::stepProgress, this, &ManifestDownloadTask::propagateStepProgress);
+ connect(elementDownload.get(), &Task::status, this, &ManifestDownloadTask::setStatus);
+ connect(elementDownload.get(), &Task::details, this, &ManifestDownloadTask::setDetails);
+
+ connect(elementDownload.get(), &Task::succeeded, this, &ManifestDownloadTask::emitSucceeded);
+ m_task = elementDownload;
+ m_task->start();
+}
+
+bool ManifestDownloadTask::abort()
+{
+ auto aborted = canAbort();
+ if (m_task)
+ aborted = m_task->abort();
+ emitAborted();
+ return aborted;
+};
+} // namespace Java
\ No newline at end of file
diff --git a/launcher/java/download/ManifestDownloadTask.h b/launcher/java/download/ManifestDownloadTask.h
new file mode 100644
index 000000000..ae9e0d0ed
--- /dev/null
+++ b/launcher/java/download/ManifestDownloadTask.h
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2023-2024 Trial97
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+
+#include
+#include "tasks/Task.h"
+
+namespace Java {
+
+class ManifestDownloadTask : public Task {
+ Q_OBJECT
+ public:
+ ManifestDownloadTask(QUrl url, QString final_path, QString checksumType = "", QString checksumHash = "");
+ virtual ~ManifestDownloadTask() = default;
+
+ [[nodiscard]] bool canAbort() const override { return true; }
+ void executeTask() override;
+ virtual bool abort() override;
+
+ private slots:
+ void downloadJava(const QJsonDocument& doc);
+
+ protected:
+ QUrl m_url;
+ QString m_final_path;
+ QString m_checksum_type;
+ QString m_checksum_hash;
+ Task::Ptr m_task;
+};
+} // namespace Java
\ No newline at end of file
diff --git a/launcher/java/download/SymlinkTask.cpp b/launcher/java/download/SymlinkTask.cpp
new file mode 100644
index 000000000..843c7caa9
--- /dev/null
+++ b/launcher/java/download/SymlinkTask.cpp
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2023-2024 Trial97
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+#include "java/download/SymlinkTask.h"
+#include
+
+#include "FileSystem.h"
+
+namespace Java {
+SymlinkTask::SymlinkTask(QString final_path) : m_path(final_path) {}
+
+QString findBinPath(QString root, QString pattern)
+{
+ auto path = FS::PathCombine(root, pattern);
+ if (QFileInfo::exists(path)) {
+ return path;
+ }
+
+ auto entries = QDir(root).entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
+ for (auto& entry : entries) {
+ path = FS::PathCombine(entry.absoluteFilePath(), pattern);
+ if (QFileInfo::exists(path)) {
+ return path;
+ }
+ }
+
+ return {};
+}
+
+void SymlinkTask::executeTask()
+{
+ setStatus(tr("Checking for Java binary path"));
+ const auto binPath = FS::PathCombine("bin", "java");
+ const auto wantedPath = FS::PathCombine(m_path, binPath);
+ if (QFileInfo::exists(wantedPath)) {
+ emitSucceeded();
+ return;
+ }
+
+ setStatus(tr("Searching for Java binary path"));
+ const auto contentsPartialPath = FS::PathCombine("Contents", "Home", binPath);
+ const auto relativePathToBin = findBinPath(m_path, contentsPartialPath);
+ if (relativePathToBin.isEmpty()) {
+ emitFailed(tr("Failed to find Java binary path"));
+ return;
+ }
+ const auto folderToLink = relativePathToBin.chopped(binPath.length());
+
+ setStatus(tr("Collecting folders to symlink"));
+ auto entries = QDir(folderToLink).entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries);
+ QList files;
+ setProgress(0, entries.length());
+ for (auto& entry : entries) {
+ files.append({ entry.absoluteFilePath(), FS::PathCombine(m_path, entry.fileName()) });
+ }
+
+ setStatus(tr("Symlinking Java binary path"));
+ FS::create_link folderLink(files);
+ connect(&folderLink, &FS::create_link::fileLinked, [this](QString src, QString dst) { setProgress(m_progress + 1, m_progressTotal); });
+ if (!folderLink()) {
+ emitFailed(folderLink.getOSError().message().c_str());
+ } else {
+ emitSucceeded();
+ }
+}
+
+} // namespace Java
\ No newline at end of file
diff --git a/launcher/java/download/SymlinkTask.h b/launcher/java/download/SymlinkTask.h
new file mode 100644
index 000000000..88cb20dd7
--- /dev/null
+++ b/launcher/java/download/SymlinkTask.h
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2023-2024 Trial97
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+
+#include "tasks/Task.h"
+namespace Java {
+
+class SymlinkTask : public Task {
+ Q_OBJECT
+ public:
+ SymlinkTask(QString final_path);
+ virtual ~SymlinkTask() = default;
+
+ void executeTask() override;
+
+ protected:
+ QString m_path;
+ Task::Ptr m_task;
+};
+} // namespace Java
\ No newline at end of file
diff --git a/launcher/launch/LaunchStep.cpp b/launcher/launch/LaunchStep.cpp
index ebc534617..f3e9dfce0 100644
--- a/launcher/launch/LaunchStep.cpp
+++ b/launcher/launch/LaunchStep.cpp
@@ -16,9 +16,8 @@
#include "LaunchStep.h"
#include "LaunchTask.h"
-void LaunchStep::bind(LaunchTask* parent)
+LaunchStep::LaunchStep(LaunchTask* parent) : Task(parent), m_parent(parent)
{
- m_parent = parent;
connect(this, &LaunchStep::readyForLaunch, parent, &LaunchTask::onReadyForLaunch);
connect(this, &LaunchStep::logLine, parent, &LaunchTask::onLogLine);
connect(this, &LaunchStep::logLines, parent, &LaunchTask::onLogLines);
diff --git a/launcher/launch/LaunchStep.h b/launcher/launch/LaunchStep.h
index b1bec2b4a..d49d7545b 100644
--- a/launcher/launch/LaunchStep.h
+++ b/launcher/launch/LaunchStep.h
@@ -24,11 +24,8 @@ class LaunchTask;
class LaunchStep : public Task {
Q_OBJECT
public: /* methods */
- explicit LaunchStep(LaunchTask* parent) : Task(nullptr), m_parent(parent) { bind(parent); };
- virtual ~LaunchStep(){};
-
- private: /* methods */
- void bind(LaunchTask* parent);
+ explicit LaunchStep(LaunchTask* parent);
+ virtual ~LaunchStep() = default;
signals:
void logLines(QStringList lines, MessageLevel::Enum level);
@@ -37,9 +34,9 @@ class LaunchStep : public Task {
void progressReportingRequest();
public slots:
- virtual void proceed(){};
+ virtual void proceed() {};
// called in the opposite order than the Task launch(), used to clean up or otherwise undo things after the launch ends
- virtual void finalize(){};
+ virtual void finalize() {};
protected: /* data */
LaunchTask* m_parent;
diff --git a/launcher/launch/LaunchTask.cpp b/launcher/launch/LaunchTask.cpp
index 06a32bd28..4e4f5ead4 100644
--- a/launcher/launch/LaunchTask.cpp
+++ b/launcher/launch/LaunchTask.cpp
@@ -44,7 +44,6 @@
#include
#include
#include "MessageLevel.h"
-#include "java/JavaChecker.h"
#include "tasks/Task.h"
void LaunchTask::init()
diff --git a/launcher/launch/LaunchTask.h b/launcher/launch/LaunchTask.h
index e79c43557..2fd8c78c7 100644
--- a/launcher/launch/LaunchTask.h
+++ b/launcher/launch/LaunchTask.h
@@ -41,7 +41,6 @@
#include "BaseInstance.h"
#include "LaunchStep.h"
#include "LogModel.h"
-#include "LoggedProcess.h"
#include "MessageLevel.h"
class LaunchTask : public Task {
@@ -55,7 +54,7 @@ class LaunchTask : public Task {
public: /* methods */
static shared_qobject_ptr create(InstancePtr inst);
- virtual ~LaunchTask(){};
+ virtual ~LaunchTask() = default;
void appendStep(shared_qobject_ptr step);
void prependStep(shared_qobject_ptr step);
diff --git a/launcher/launch/TaskStepWrapper.cpp b/launcher/launch/TaskStepWrapper.cpp
new file mode 100644
index 000000000..db9e8fad2
--- /dev/null
+++ b/launcher/launch/TaskStepWrapper.cpp
@@ -0,0 +1,67 @@
+/* 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 "TaskStepWrapper.h"
+#include "tasks/Task.h"
+
+void TaskStepWrapper::executeTask()
+{
+ if (m_state == Task::State::AbortedByUser) {
+ emitFailed(tr("Task aborted."));
+ return;
+ }
+ connect(m_task.get(), &Task::finished, this, &TaskStepWrapper::updateFinished);
+ connect(m_task.get(), &Task::progress, this, &TaskStepWrapper::setProgress);
+ connect(m_task.get(), &Task::stepProgress, this, &TaskStepWrapper::propagateStepProgress);
+ connect(m_task.get(), &Task::status, this, &TaskStepWrapper::setStatus);
+ connect(m_task.get(), &Task::details, this, &TaskStepWrapper::setDetails);
+ emit progressReportingRequest();
+}
+
+void TaskStepWrapper::proceed()
+{
+ m_task->start();
+}
+
+void TaskStepWrapper::updateFinished()
+{
+ if (m_task->wasSuccessful()) {
+ m_task.reset();
+ emitSucceeded();
+ } else {
+ QString reason = tr("Instance update failed because: %1\n\n").arg(m_task->failReason());
+ m_task.reset();
+ emit logLine(reason, MessageLevel::Fatal);
+ emitFailed(reason);
+ }
+}
+
+bool TaskStepWrapper::canAbort() const
+{
+ if (m_task) {
+ return m_task->canAbort();
+ }
+ return true;
+}
+
+bool TaskStepWrapper::abort()
+{
+ if (m_task && m_task->canAbort()) {
+ auto status = m_task->abort();
+ emitFailed("Aborted.");
+ return status;
+ }
+ return Task::abort();
+}
diff --git a/launcher/launch/steps/Update.h b/launcher/launch/TaskStepWrapper.h
similarity index 73%
rename from launcher/launch/steps/Update.h
rename to launcher/launch/TaskStepWrapper.h
index 9262cdbe4..aec1b7037 100644
--- a/launcher/launch/steps/Update.h
+++ b/launcher/launch/TaskStepWrapper.h
@@ -21,12 +21,11 @@
#include
#include
-// FIXME: stupid. should be defined by the instance type? or even completely abstracted away...
-class Update : public LaunchStep {
+class TaskStepWrapper : public LaunchStep {
Q_OBJECT
public:
- explicit Update(LaunchTask* parent, Net::Mode mode) : LaunchStep(parent), m_mode(mode){};
- virtual ~Update(){};
+ explicit TaskStepWrapper(LaunchTask* parent, Task::Ptr task) : LaunchStep(parent), m_task(task) {};
+ virtual ~TaskStepWrapper() = default;
void executeTask() override;
bool canAbort() const override;
@@ -38,7 +37,5 @@ class Update : public LaunchStep {
void updateFinished();
private:
- Task::Ptr m_updateTask;
- bool m_aborted = false;
- Net::Mode m_mode = Net::Mode::Offline;
+ Task::Ptr m_task;
};
diff --git a/launcher/launch/steps/CheckJava.cpp b/launcher/launch/steps/CheckJava.cpp
index 81337a88e..55d13b58c 100644
--- a/launcher/launch/steps/CheckJava.cpp
+++ b/launcher/launch/steps/CheckJava.cpp
@@ -37,6 +37,7 @@
#include
#include
#include
+#include
#include
#include
#include "java/JavaUtils.h"
@@ -45,20 +46,23 @@ void CheckJava::executeTask()
{
auto instance = m_parent->instance();
auto settings = instance->settings();
- m_javaPath = FS::ResolveExecutable(settings->get("JavaPath").toString());
+
+ QString javaPathSetting = settings->get("JavaPath").toString();
+ m_javaPath = FS::ResolveExecutable(javaPathSetting);
+
bool perInstance = settings->get("OverrideJava").toBool() || settings->get("OverrideJavaLocation").toBool();
auto realJavaPath = QStandardPaths::findExecutable(m_javaPath);
if (realJavaPath.isEmpty()) {
if (perInstance) {
- emit logLine(QString("The java binary \"%1\" couldn't be found. Please fix the java path "
+ emit logLine(QString("The Java binary \"%1\" couldn't be found. Please fix the Java path "
"override in the instance's settings or disable it.")
- .arg(m_javaPath),
+ .arg(javaPathSetting),
MessageLevel::Warning);
} else {
- emit logLine(QString("The java binary \"%1\" couldn't be found. Please set up java in "
+ emit logLine(QString("The Java binary \"%1\" couldn't be found. Please set up Java in "
"the settings.")
- .arg(m_javaPath),
+ .arg(javaPathSetting),
MessageLevel::Warning);
}
emitFailed(QString("Java path is not valid."));
@@ -90,11 +94,10 @@ void CheckJava::executeTask()
// if timestamps are not the same, or something is missing, check!
if (m_javaSignature != storedSignature || storedVersion.size() == 0 || storedArchitecture.size() == 0 ||
storedRealArchitecture.size() == 0 || storedVendor.size() == 0) {
- m_JavaChecker.reset(new JavaChecker);
+ m_JavaChecker.reset(new JavaChecker(realJavaPath, "", 0, 0, 0, 0, this));
emit logLine(QString("Checking Java version..."), MessageLevel::Launcher);
connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished);
- m_JavaChecker->m_path = realJavaPath;
- m_JavaChecker->performCheck();
+ m_JavaChecker->start();
return;
} else {
auto verString = instance->settings()->get("JavaVersion").toString();
@@ -103,13 +106,14 @@ void CheckJava::executeTask()
auto vendorString = instance->settings()->get("JavaVendor").toString();
printJavaInfo(verString, archString, realArchString, vendorString);
}
+ m_parent->instance()->updateRuntimeContext();
emitSucceeded();
}
-void CheckJava::checkJavaFinished(JavaCheckResult result)
+void CheckJava::checkJavaFinished(const JavaChecker::Result& result)
{
switch (result.validity) {
- case JavaCheckResult::Validity::Errored: {
+ case JavaChecker::Result::Validity::Errored: {
// Error message displayed if java can't start
emit logLine(QString("Could not start java:"), MessageLevel::Error);
emit logLines(result.errorLog.split('\n'), MessageLevel::Error);
@@ -117,14 +121,15 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
emitFailed(QString("Could not start java!"));
return;
}
- case JavaCheckResult::Validity::ReturnedInvalidData: {
+ case JavaChecker::Result::Validity::ReturnedInvalidData: {
emit logLine(QString("Java checker returned some invalid data we don't understand:"), MessageLevel::Error);
emit logLines(result.outLog.split('\n'), MessageLevel::Warning);
emit logLine("\nMinecraft might not start properly.", MessageLevel::Launcher);
+ m_parent->instance()->updateRuntimeContext();
emitSucceeded();
return;
}
- case JavaCheckResult::Validity::Valid: {
+ case JavaChecker::Result::Validity::Valid: {
auto instance = m_parent->instance();
printJavaInfo(result.javaVersion.toString(), result.mojangPlatform, result.realPlatform, result.javaVendor);
instance->settings()->set("JavaVersion", result.javaVersion.toString());
@@ -132,6 +137,7 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
instance->settings()->set("JavaRealArchitecture", result.realPlatform);
instance->settings()->set("JavaVendor", result.javaVendor);
instance->settings()->set("JavaSignature", m_javaSignature);
+ m_parent->instance()->updateRuntimeContext();
emitSucceeded();
return;
}
diff --git a/launcher/launch/steps/CheckJava.h b/launcher/launch/steps/CheckJava.h
index 4436e2a55..1c59b0053 100644
--- a/launcher/launch/steps/CheckJava.h
+++ b/launcher/launch/steps/CheckJava.h
@@ -22,13 +22,13 @@
class CheckJava : public LaunchStep {
Q_OBJECT
public:
- explicit CheckJava(LaunchTask* parent) : LaunchStep(parent){};
- virtual ~CheckJava(){};
+ explicit CheckJava(LaunchTask* parent) : LaunchStep(parent) {};
+ virtual ~CheckJava() = default;
virtual void executeTask();
virtual bool canAbort() const { return false; }
private slots:
- void checkJavaFinished(JavaCheckResult result);
+ void checkJavaFinished(const JavaChecker::Result& result);
private:
void printJavaInfo(const QString& version, const QString& architecture, const QString& realArchitecture, const QString& vendor);
@@ -37,5 +37,5 @@ class CheckJava : public LaunchStep {
private:
QString m_javaPath;
QString m_javaSignature;
- JavaCheckerPtr m_JavaChecker;
+ JavaChecker::Ptr m_JavaChecker;
};
diff --git a/launcher/launch/steps/LookupServerAddress.cpp b/launcher/launch/steps/LookupServerAddress.cpp
index 9bdac203b..4b67b3092 100644
--- a/launcher/launch/steps/LookupServerAddress.cpp
+++ b/launcher/launch/steps/LookupServerAddress.cpp
@@ -30,7 +30,7 @@ void LookupServerAddress::setLookupAddress(const QString& lookupAddress)
m_dnsLookup->setName(QString("_minecraft._tcp.%1").arg(lookupAddress));
}
-void LookupServerAddress::setOutputAddressPtr(MinecraftServerTargetPtr output)
+void LookupServerAddress::setOutputAddressPtr(MinecraftTarget::Ptr output)
{
m_output = std::move(output);
}
diff --git a/launcher/launch/steps/LookupServerAddress.h b/launcher/launch/steps/LookupServerAddress.h
index abd92a5e8..506314ee8 100644
--- a/launcher/launch/steps/LookupServerAddress.h
+++ b/launcher/launch/steps/LookupServerAddress.h
@@ -19,20 +19,20 @@
#include
#include
-#include "minecraft/launch/MinecraftServerTarget.h"
+#include "minecraft/launch/MinecraftTarget.h"
class LookupServerAddress : public LaunchStep {
Q_OBJECT
public:
explicit LookupServerAddress(LaunchTask* parent);
- virtual ~LookupServerAddress(){};
+ virtual ~LookupServerAddress() = default;
virtual void executeTask();
virtual bool abort();
virtual bool canAbort() const { return true; }
void setLookupAddress(const QString& lookupAddress);
- void setOutputAddressPtr(MinecraftServerTargetPtr output);
+ void setOutputAddressPtr(MinecraftTarget::Ptr output);
private slots:
void on_dnsLookupFinished();
@@ -42,5 +42,5 @@ class LookupServerAddress : public LaunchStep {
QDnsLookup* m_dnsLookup;
QString m_lookupAddress;
- MinecraftServerTargetPtr m_output;
+ MinecraftTarget::Ptr m_output;
};
diff --git a/launcher/launch/steps/PostLaunchCommand.h b/launcher/launch/steps/PostLaunchCommand.h
index 578433b86..fd1443b29 100644
--- a/launcher/launch/steps/PostLaunchCommand.h
+++ b/launcher/launch/steps/PostLaunchCommand.h
@@ -22,7 +22,7 @@ class PostLaunchCommand : public LaunchStep {
Q_OBJECT
public:
explicit PostLaunchCommand(LaunchTask* parent);
- virtual ~PostLaunchCommand(){};
+ virtual ~PostLaunchCommand() {};
virtual void executeTask();
virtual bool abort();
diff --git a/launcher/launch/steps/PreLaunchCommand.h b/launcher/launch/steps/PreLaunchCommand.h
index 10568ea34..b6dc6cd8b 100644
--- a/launcher/launch/steps/PreLaunchCommand.h
+++ b/launcher/launch/steps/PreLaunchCommand.h
@@ -22,7 +22,7 @@ class PreLaunchCommand : public LaunchStep {
Q_OBJECT
public:
explicit PreLaunchCommand(LaunchTask* parent);
- virtual ~PreLaunchCommand(){};
+ virtual ~PreLaunchCommand() {};
virtual void executeTask();
virtual bool abort();
diff --git a/launcher/launch/steps/PrintServers.cpp b/launcher/launch/steps/PrintServers.cpp
new file mode 100644
index 000000000..ba96d37b9
--- /dev/null
+++ b/launcher/launch/steps/PrintServers.cpp
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2024 Leia uwu
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "PrintServers.h"
+#include "QHostInfo"
+
+PrintServers::PrintServers(LaunchTask* parent, const QStringList& servers) : LaunchStep(parent)
+{
+ m_servers = servers;
+}
+
+void PrintServers::executeTask()
+{
+ for (QString server : m_servers) {
+ QHostInfo::lookupHost(server, this, &PrintServers::resolveServer);
+ }
+}
+
+void PrintServers::resolveServer(const QHostInfo& host_info)
+{
+ QString server = host_info.hostName();
+ QString addresses = server + " resolves to:\n [";
+
+ if (!host_info.addresses().isEmpty()) {
+ for (QHostAddress address : host_info.addresses()) {
+ addresses += address.toString();
+ if (!host_info.addresses().endsWith(address)) {
+ addresses += ", ";
+ }
+ }
+ } else {
+ addresses += "N/A";
+ }
+ addresses += "]\n\n";
+
+ m_server_to_address.insert(server, addresses);
+
+ // print server info in order once all servers are resolved
+ if (m_server_to_address.size() >= m_servers.size()) {
+ for (QString serv : m_servers) {
+ emit logLine(m_server_to_address.value(serv), MessageLevel::Launcher);
+ }
+ emitSucceeded();
+ }
+}
+
+bool PrintServers::canAbort() const
+{
+ return true;
+}
diff --git a/launcher/launch/steps/PrintServers.h b/launcher/launch/steps/PrintServers.h
new file mode 100644
index 000000000..7d2f1b194
--- /dev/null
+++ b/launcher/launch/steps/PrintServers.h
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2024 Leia uwu
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+#pragma once
+
+#include
+#include
+#include
+#include
+
+class PrintServers : public LaunchStep {
+ Q_OBJECT
+ public:
+ PrintServers(LaunchTask* parent, const QStringList& servers);
+
+ virtual void executeTask();
+ virtual bool canAbort() const;
+
+ private:
+ void resolveServer(const QHostInfo& host_info);
+ QMap m_server_to_address;
+ QStringList m_servers;
+};
diff --git a/launcher/launch/steps/QuitAfterGameStop.h b/launcher/launch/steps/QuitAfterGameStop.h
index 9326b2a8c..19ca59632 100644
--- a/launcher/launch/steps/QuitAfterGameStop.h
+++ b/launcher/launch/steps/QuitAfterGameStop.h
@@ -23,8 +23,8 @@
class QuitAfterGameStop : public LaunchStep {
Q_OBJECT
public:
- explicit QuitAfterGameStop(LaunchTask* parent) : LaunchStep(parent){};
- virtual ~QuitAfterGameStop(){};
+ explicit QuitAfterGameStop(LaunchTask* parent) : LaunchStep(parent) {};
+ virtual ~QuitAfterGameStop() = default;
virtual void executeTask();
virtual bool canAbort() const { return false; }
diff --git a/launcher/launch/steps/TextPrint.h b/launcher/launch/steps/TextPrint.h
index bd6c28567..a96c2f887 100644
--- a/launcher/launch/steps/TextPrint.h
+++ b/launcher/launch/steps/TextPrint.h
@@ -28,7 +28,7 @@ class TextPrint : public LaunchStep {
public:
explicit TextPrint(LaunchTask* parent, const QStringList& lines, MessageLevel::Enum level);
explicit TextPrint(LaunchTask* parent, const QString& line, MessageLevel::Enum level);
- virtual ~TextPrint(){};
+ virtual ~TextPrint() {};
virtual void executeTask();
virtual bool canAbort() const;
diff --git a/launcher/launch/steps/Update.cpp b/launcher/launch/steps/Update.cpp
deleted file mode 100644
index f23c0bb4b..000000000
--- a/launcher/launch/steps/Update.cpp
+++ /dev/null
@@ -1,73 +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 "Update.h"
-#include
-
-void Update::executeTask()
-{
- if (m_aborted) {
- emitFailed(tr("Task aborted."));
- return;
- }
- m_updateTask.reset(m_parent->instance()->createUpdateTask(m_mode));
- if (m_updateTask) {
- connect(m_updateTask.get(), &Task::finished, this, &Update::updateFinished);
- connect(m_updateTask.get(), &Task::progress, this, &Update::setProgress);
- connect(m_updateTask.get(), &Task::stepProgress, this, &Update::propagateStepProgress);
- connect(m_updateTask.get(), &Task::status, this, &Update::setStatus);
- connect(m_updateTask.get(), &Task::details, this, &Update::setDetails);
- emit progressReportingRequest();
- return;
- }
- emitSucceeded();
-}
-
-void Update::proceed()
-{
- m_updateTask->start();
-}
-
-void Update::updateFinished()
-{
- if (m_updateTask->wasSuccessful()) {
- m_updateTask.reset();
- emitSucceeded();
- } else {
- QString reason = tr("Instance update failed because: %1\n\n").arg(m_updateTask->failReason());
- m_updateTask.reset();
- emit logLine(reason, MessageLevel::Fatal);
- emitFailed(reason);
- }
-}
-
-bool Update::canAbort() const
-{
- if (m_updateTask) {
- return m_updateTask->canAbort();
- }
- return true;
-}
-
-bool Update::abort()
-{
- m_aborted = true;
- if (m_updateTask) {
- if (m_updateTask->canAbort()) {
- return m_updateTask->abort();
- }
- }
- return true;
-}
diff --git a/launcher/meta/BaseEntity.cpp b/launcher/meta/BaseEntity.cpp
index 5f9804e48..b0e754ada 100644
--- a/launcher/meta/BaseEntity.cpp
+++ b/launcher/meta/BaseEntity.cpp
@@ -15,27 +15,43 @@
#include "BaseEntity.h"
+#include "Exception.h"
+#include "FileSystem.h"
#include "Json.h"
+#include "modplatform/helpers/HashUtils.h"
#include "net/ApiDownload.h"
+#include "net/ChecksumValidator.h"
#include "net/HttpMetaCache.h"
+#include "net/Mode.h"
#include "net/NetJob.h"
#include "Application.h"
#include "BuildConfig.h"
+#include "tasks/Task.h"
+
+namespace Meta {
class ParsingValidator : public Net::Validator {
public: /* con/des */
- ParsingValidator(Meta::BaseEntity* entity) : m_entity(entity){};
- virtual ~ParsingValidator(){};
+ ParsingValidator(BaseEntity* entity) : m_entity(entity) {};
+ virtual ~ParsingValidator() = default;
public: /* methods */
- bool init(QNetworkRequest&) override { return true; }
+ bool init(QNetworkRequest&) override
+ {
+ m_data.clear();
+ return true;
+ }
bool write(QByteArray& data) override
{
this->m_data.append(data);
return true;
}
- bool abort() override { return true; }
+ bool abort() override
+ {
+ m_data.clear();
+ return true;
+ }
bool validate(QNetworkReply&) override
{
auto fname = m_entity->localFilename();
@@ -52,93 +68,131 @@ class ParsingValidator : public Net::Validator {
private: /* data */
QByteArray m_data;
- Meta::BaseEntity* m_entity;
+ BaseEntity* m_entity;
};
-Meta::BaseEntity::~BaseEntity() {}
-
-QUrl Meta::BaseEntity::url() const
+QUrl BaseEntity::url() const
{
auto s = APPLICATION->settings();
QString metaOverride = s->get("MetaURLOverride").toString();
if (metaOverride.isEmpty()) {
return QUrl(BuildConfig.META_URL).resolved(localFilename());
- } else {
- return QUrl(metaOverride).resolved(localFilename());
}
+ return QUrl(metaOverride).resolved(localFilename());
}
-bool Meta::BaseEntity::loadLocalFile()
+Task::Ptr BaseEntity::loadTask(Net::Mode mode)
{
- const QString fname = QDir("meta").absoluteFilePath(localFilename());
- if (!QFile::exists(fname)) {
- return false;
- }
- // TODO: check if the file has the expected checksum
- try {
- auto doc = Json::requireDocument(fname, fname);
- auto obj = Json::requireObject(doc, fname);
- parse(obj);
- return true;
- } catch (const Exception& e) {
- qDebug() << QString("Unable to parse file %1: %2").arg(fname, e.cause());
- // just make sure it's gone and we never consider it again.
- QFile::remove(fname);
- return false;
+ if (m_task && m_task->isRunning()) {
+ return m_task;
}
+ m_task.reset(new BaseEntityLoadTask(this, mode));
+ return m_task;
}
-void Meta::BaseEntity::load(Net::Mode loadType)
+bool BaseEntity::isLoaded() const
{
- // load local file if nothing is loaded yet
- if (!isLoaded()) {
- if (loadLocalFile()) {
- m_loadStatus = LoadStatus::Local;
+ // consider it loaded only if the main hash is either empty and was remote loadded or the hashes match and was loaded
+ return m_sha256.isEmpty() ? m_load_status == LoadStatus::Remote : m_load_status != LoadStatus::NotLoaded && m_sha256 == m_file_sha256;
+}
+
+void BaseEntity::setSha256(QString sha256)
+{
+ m_sha256 = sha256;
+}
+
+BaseEntity::LoadStatus BaseEntity::status() const
+{
+ return m_load_status;
+}
+
+BaseEntityLoadTask::BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode) : m_entity(parent), m_mode(mode) {}
+
+void BaseEntityLoadTask::executeTask()
+{
+ const QString fname = QDir("meta").absoluteFilePath(m_entity->localFilename());
+ auto hashMatches = false;
+ // the file exists on disk try to load it
+ if (QFile::exists(fname)) {
+ try {
+ QByteArray fileData;
+ // read local file if nothing is loaded yet
+ if (m_entity->m_load_status == BaseEntity::LoadStatus::NotLoaded || m_entity->m_file_sha256.isEmpty()) {
+ setStatus(tr("Loading local file"));
+ fileData = FS::read(fname);
+ m_entity->m_file_sha256 = Hashing::hash(fileData, Hashing::Algorithm::Sha256);
+ }
+
+ // on online the hash needs to match
+ hashMatches = m_entity->m_sha256 == m_entity->m_file_sha256;
+ if (m_mode == Net::Mode::Online && !m_entity->m_sha256.isEmpty() && !hashMatches) {
+ throw Exception("mismatched checksum");
+ }
+
+ // load local file
+ if (m_entity->m_load_status == BaseEntity::LoadStatus::NotLoaded) {
+ auto doc = Json::requireDocument(fileData, fname);
+ auto obj = Json::requireObject(doc, fname);
+ m_entity->parse(obj);
+ m_entity->m_load_status = BaseEntity::LoadStatus::Local;
+ }
+
+ } catch (const Exception& e) {
+ qDebug() << QString("Unable to parse file %1: %2").arg(fname, e.cause());
+ // just make sure it's gone and we never consider it again.
+ FS::deletePath(fname);
+ m_entity->m_load_status = BaseEntity::LoadStatus::NotLoaded;
}
}
// if we need remote update, run the update task
- if (loadType == Net::Mode::Offline || !shouldStartRemoteUpdate()) {
+ auto wasLoadedOffline = m_entity->m_load_status != BaseEntity::LoadStatus::NotLoaded && m_mode == Net::Mode::Offline;
+ // if has is not present allways fetch from remote(e.g. the main index file), else only fetch if hash doesn't match
+ auto wasLoadedRemote = m_entity->m_sha256.isEmpty() ? m_entity->m_load_status == BaseEntity::LoadStatus::Remote : hashMatches;
+ if (wasLoadedOffline || wasLoadedRemote) {
+ emitSucceeded();
return;
}
- m_updateTask.reset(new NetJob(QObject::tr("Download of meta file %1").arg(localFilename()), APPLICATION->network()));
- auto url = this->url();
- auto entry = APPLICATION->metacache()->resolveEntry("meta", localFilename());
+ m_task.reset(new NetJob(QObject::tr("Download of meta file %1").arg(m_entity->localFilename()), APPLICATION->network()));
+ auto url = m_entity->url();
+ auto entry = APPLICATION->metacache()->resolveEntry("meta", m_entity->localFilename());
entry->setStale(true);
auto dl = Net::ApiDownload::makeCached(url, entry);
/*
* The validator parses the file and loads it into the object.
* If that fails, the file is not written to storage.
*/
- dl->addValidator(new ParsingValidator(this));
- m_updateTask->addNetAction(dl);
- m_updateStatus = UpdateStatus::InProgress;
- QObject::connect(m_updateTask.get(), &NetJob::succeeded, [&]() {
- m_loadStatus = LoadStatus::Remote;
- m_updateStatus = UpdateStatus::Succeeded;
- m_updateTask.reset();
+ if (!m_entity->m_sha256.isEmpty())
+ dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Algorithm::Sha256, m_entity->m_sha256));
+ dl->addValidator(new ParsingValidator(m_entity));
+ m_task->addNetAction(dl);
+ m_task->setAskRetry(false);
+ connect(m_task.get(), &Task::failed, this, &BaseEntityLoadTask::emitFailed);
+ connect(m_task.get(), &Task::succeeded, this, &BaseEntityLoadTask::emitSucceeded);
+ connect(m_task.get(), &Task::succeeded, this, [this]() {
+ m_entity->m_load_status = BaseEntity::LoadStatus::Remote;
+ m_entity->m_file_sha256 = m_entity->m_sha256;
});
- QObject::connect(m_updateTask.get(), &NetJob::failed, [&]() {
- m_updateStatus = UpdateStatus::Failed;
- m_updateTask.reset();
- });
- m_updateTask->start();
+
+ connect(m_task.get(), &Task::progress, this, &Task::setProgress);
+ connect(m_task.get(), &Task::stepProgress, this, &BaseEntityLoadTask::propagateStepProgress);
+ connect(m_task.get(), &Task::status, this, &Task::setStatus);
+ connect(m_task.get(), &Task::details, this, &Task::setDetails);
+
+ m_task->start();
}
-bool Meta::BaseEntity::isLoaded() const
+bool BaseEntityLoadTask::canAbort() const
{
- return m_loadStatus > LoadStatus::NotLoaded;
+ return m_task ? m_task->canAbort() : false;
}
-bool Meta::BaseEntity::shouldStartRemoteUpdate() const
+bool BaseEntityLoadTask::abort()
{
- // TODO: version-locks and offline mode?
- return m_updateStatus != UpdateStatus::InProgress;
-}
-
-Task::Ptr Meta::BaseEntity::getCurrentTask()
-{
- if (m_updateStatus == UpdateStatus::InProgress) {
- return m_updateTask;
+ if (m_task) {
+ Task::abort();
+ return m_task->abort();
}
- return nullptr;
+ return Task::abort();
}
+
+} // namespace Meta
diff --git a/launcher/meta/BaseEntity.h b/launcher/meta/BaseEntity.h
index 1336a5217..17aa0cb87 100644
--- a/launcher/meta/BaseEntity.h
+++ b/launcher/meta/BaseEntity.h
@@ -17,38 +17,57 @@
#include
#include
-#include "QObjectPtr.h"
#include "net/Mode.h"
#include "net/NetJob.h"
+#include "tasks/Task.h"
namespace Meta {
+class BaseEntityLoadTask;
class BaseEntity {
+ friend BaseEntityLoadTask;
+
public: /* types */
using Ptr = std::shared_ptr;
enum class LoadStatus { NotLoaded, Local, Remote };
- enum class UpdateStatus { NotDone, InProgress, Failed, Succeeded };
public:
- virtual ~BaseEntity();
-
- virtual void parse(const QJsonObject& obj) = 0;
+ virtual ~BaseEntity() = default;
virtual QString localFilename() const = 0;
virtual QUrl url() const;
-
bool isLoaded() const;
- bool shouldStartRemoteUpdate() const;
+ LoadStatus status() const;
- void load(Net::Mode loadType);
- Task::Ptr getCurrentTask();
+ /* for parsers */
+ void setSha256(QString sha256);
- protected: /* methods */
- bool loadLocalFile();
+ virtual void parse(const QJsonObject& obj) = 0;
+ [[nodiscard]] Task::Ptr loadTask(Net::Mode loadType = Net::Mode::Online);
+
+ protected:
+ QString m_sha256; // the expected sha256
+ QString m_file_sha256; // the file sha256
private:
- LoadStatus m_loadStatus = LoadStatus::NotLoaded;
- UpdateStatus m_updateStatus = UpdateStatus::NotDone;
- NetJob::Ptr m_updateTask;
+ LoadStatus m_load_status = LoadStatus::NotLoaded;
+ Task::Ptr m_task;
+};
+
+class BaseEntityLoadTask : public Task {
+ Q_OBJECT
+
+ public:
+ explicit BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode);
+ ~BaseEntityLoadTask() override = default;
+
+ virtual void executeTask() override;
+ virtual bool canAbort() const override;
+ virtual bool abort() override;
+
+ private:
+ BaseEntity* m_entity;
+ Net::Mode m_mode;
+ NetJob::Ptr m_task;
};
} // namespace Meta
diff --git a/launcher/meta/Index.cpp b/launcher/meta/Index.cpp
index 657019f8a..bd0745b6b 100644
--- a/launcher/meta/Index.cpp
+++ b/launcher/meta/Index.cpp
@@ -16,7 +16,10 @@
#include "Index.h"
#include "JsonFormat.h"
+#include "QObjectPtr.h"
#include "VersionList.h"
+#include "meta/BaseEntity.h"
+#include "tasks/SequentialTask.h"
namespace Meta {
Index::Index(QObject* parent) : QAbstractListModel(parent) {}
@@ -51,14 +54,17 @@ QVariant Index::data(const QModelIndex& index, int role) const
}
return QVariant();
}
+
int Index::rowCount(const QModelIndex& parent) const
{
return parent.isValid() ? 0 : m_lists.size();
}
+
int Index::columnCount(const QModelIndex& parent) const
{
return parent.isValid() ? 0 : 1;
}
+
QVariant Index::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0) {
@@ -79,6 +85,7 @@ VersionList::Ptr Index::get(const QString& uid)
if (!out) {
out = std::make_shared(uid);
m_uids[uid] = out;
+ m_lists.append(out);
}
return out;
}
@@ -96,7 +103,7 @@ void Index::parse(const QJsonObject& obj)
void Index::merge(const std::shared_ptr& other)
{
- const QVector lists = std::dynamic_pointer_cast(other)->m_lists;
+ const QVector lists = other->m_lists;
// initial load, no need to merge
if (m_lists.isEmpty()) {
beginResetModel();
@@ -123,7 +130,33 @@ void Index::merge(const std::shared_ptr& other)
void Index::connectVersionList(const int row, const VersionList::Ptr& list)
{
- connect(list.get(), &VersionList::nameChanged, this,
- [this, row]() { emit dataChanged(index(row), index(row), QVector() << Qt::DisplayRole); });
+ connect(list.get(), &VersionList::nameChanged, this, [this, row] { emit dataChanged(index(row), index(row), { Qt::DisplayRole }); });
+}
+
+Task::Ptr Index::loadVersion(const QString& uid, const QString& version, Net::Mode mode, bool force)
+{
+ if (mode == Net::Mode::Offline) {
+ return get(uid, version)->loadTask(mode);
+ }
+
+ auto versionList = get(uid);
+ auto loadTask = makeShared(
+ this, tr("Load meta for %1:%2", "This is for the task name that loads the meta index.").arg(uid, version));
+ if (status() != BaseEntity::LoadStatus::Remote || force) {
+ loadTask->addTask(this->loadTask(mode));
+ }
+ loadTask->addTask(versionList->loadTask(mode));
+ loadTask->addTask(versionList->getVersion(version)->loadTask(mode));
+ return loadTask;
+}
+
+Version::Ptr Index::getLoadedVersion(const QString& uid, const QString& version)
+{
+ QEventLoop ev;
+ auto task = loadVersion(uid, version);
+ QObject::connect(task.get(), &Task::finished, &ev, &QEventLoop::quit);
+ task->start();
+ ev.exec();
+ return get(uid, version);
}
} // namespace Meta
diff --git a/launcher/meta/Index.h b/launcher/meta/Index.h
index 2c650ce2f..026a00c07 100644
--- a/launcher/meta/Index.h
+++ b/launcher/meta/Index.h
@@ -16,10 +16,10 @@
#pragma once
#include
-#include
#include "BaseEntity.h"
#include "meta/VersionList.h"
+#include "net/Mode.h"
class Task;
@@ -30,6 +30,7 @@ class Index : public QAbstractListModel, public BaseEntity {
public:
explicit Index(QObject* parent = nullptr);
explicit Index(const QVector& lists, QObject* parent = nullptr);
+ virtual ~Index() = default;
enum { UidRole = Qt::UserRole, NameRole, ListPtrRole };
@@ -47,8 +48,15 @@ class Index : public QAbstractListModel, public BaseEntity {
QVector lists() const { return m_lists; }
+ Task::Ptr loadVersion(const QString& uid, const QString& version = {}, Net::Mode mode = Net::Mode::Online, bool force = false);
+
+ // this blocks until the version is loaded
+ Version::Ptr getLoadedVersion(const QString& uid, const QString& version);
+
public: // for usage by parsers only
void merge(const std::shared_ptr& other);
+
+ protected:
void parse(const QJsonObject& obj) override;
private:
diff --git a/launcher/meta/JsonFormat.cpp b/launcher/meta/JsonFormat.cpp
index 6c993f720..86af7277e 100644
--- a/launcher/meta/JsonFormat.cpp
+++ b/launcher/meta/JsonFormat.cpp
@@ -41,6 +41,7 @@ static std::shared_ptr