Merge remote-tracking branch 'upstream/develop' into data-packs

Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
This commit is contained in:
TheKodeToad 2025-03-24 21:11:46 +00:00
commit 6ab4fef0c5
No known key found for this signature in database
GPG Key ID: 5E39D70B4C93C38E
686 changed files with 15261 additions and 9719 deletions

View File

@ -1,5 +1,23 @@
Checks: Checks:
- modernize-use-using - modernize-use-using
- readability-avoid-const-params-in-decls - readability-avoid-const-params-in-decls
- misc-unused-parameters,
- readability-identifier-naming
SystemHeaders: false # ^ Without unused-parameters the readability-identifier-naming check doesn't cause any warnings.
CheckOptions:
- { key: readability-identifier-naming.ClassCase, value: PascalCase }
- { key: readability-identifier-naming.EnumCase, value: PascalCase }
- { key: readability-identifier-naming.FunctionCase, value: camelCase }
- { key: readability-identifier-naming.GlobalVariableCase, value: camelCase }
- { key: readability-identifier-naming.GlobalFunctionCase, value: camelCase }
- { key: readability-identifier-naming.GlobalConstantCase, value: SCREAMING_SNAKE_CASE }
- { key: readability-identifier-naming.MacroDefinitionCase, value: SCREAMING_SNAKE_CASE }
- { key: readability-identifier-naming.ClassMemberCase, value: camelCase }
- { key: readability-identifier-naming.PrivateMemberPrefix, value: m_ }
- { key: readability-identifier-naming.ProtectedMemberPrefix, value: m_ }
- { key: readability-identifier-naming.PrivateStaticMemberPrefix, value: s_ }
- { key: readability-identifier-naming.ProtectedStaticMemberPrefix, value: s_ }
- { key: readability-identifier-naming.PublicStaticConstantCase, value: SCREAMING_SNAKE_CASE }
- { key: readability-identifier-naming.EnumConstantCase, value: SCREAMING_SNAKE_CASE }

View File

@ -2,3 +2,6 @@
# tabs -> spaces # tabs -> spaces
bbb3b3e6f6e3c0f95873f22e6d0a4aaf350f49d9 bbb3b3e6f6e3c0f95873f22e6d0a4aaf350f49d9
# (nix) alejandra -> nixfmt
4c81d8c53d09196426568c4a31a4e752ed05397a

View File

@ -25,7 +25,7 @@ jobs:
with: with:
ref: ${{ github.event.pull_request.head.sha }} ref: ${{ github.event.pull_request.head.sha }}
- name: Create backport PRs - name: Create backport PRs
uses: korthout/backport-action@v3.0.2 uses: korthout/backport-action@v3.2.0
with: with:
# Config README: https://github.com/korthout/backport-action#backport-action # Config README: https://github.com/korthout/backport-action#backport-action
pull_description: |- pull_description: |-

View File

@ -56,14 +56,14 @@ jobs:
qt_ver: 5 qt_ver: 5
qt_host: linux qt_host: linux
qt_arch: "" qt_arch: ""
qt_version: "5.12.8" qt_version: "5.15.2"
qt_modules: "qtnetworkauth" qt_modules: "qtnetworkauth"
- os: ubuntu-20.04 - os: ubuntu-22.04
qt_ver: 6 qt_ver: 6
qt_host: linux qt_host: linux
qt_arch: "" qt_arch: ""
qt_version: "6.2.4" qt_version: "6.5.3"
qt_modules: "qt5compat qtimageformats qtnetworkauth" qt_modules: "qt5compat qtimageformats qtnetworkauth"
- os: windows-2022 - os: windows-2022
@ -77,10 +77,12 @@ jobs:
architecture: "x64" architecture: "x64"
vcvars_arch: "amd64" vcvars_arch: "amd64"
qt_ver: 6 qt_ver: 6
qt_host: windows qt_host: "windows"
qt_arch: "" qt_arch: "win64_msvc2022_64"
qt_version: "6.7.2" qt_version: "6.8.1"
qt_modules: "qt5compat qtimageformats qtnetworkauth" qt_modules: "qt5compat qtimageformats qtnetworkauth"
nscurl_tag: "v24.9.26.122"
nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0"
- os: windows-2022 - os: windows-2022
name: "Windows-MSVC-arm64" name: "Windows-MSVC-arm64"
@ -88,21 +90,23 @@ jobs:
architecture: "arm64" architecture: "arm64"
vcvars_arch: "amd64_arm64" vcvars_arch: "amd64_arm64"
qt_ver: 6 qt_ver: 6
qt_host: windows qt_host: "windows"
qt_arch: "win64_msvc2019_arm64" qt_arch: "win64_msvc2022_arm64_cross_compiled"
qt_version: "6.7.2" qt_version: "6.8.1"
qt_modules: "qt5compat qtimageformats qtnetworkauth" qt_modules: "qt5compat qtimageformats qtnetworkauth"
nscurl_tag: "v24.9.26.122"
nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0"
- os: macos-12 - os: macos-14
name: macOS name: macOS
macosx_deployment_target: 11.0 macosx_deployment_target: 11.0
qt_ver: 6 qt_ver: 6
qt_host: mac qt_host: mac
qt_arch: "" qt_arch: ""
qt_version: "6.7.2" qt_version: "6.8.1"
qt_modules: "qt5compat qtimageformats qtnetworkauth" qt_modules: "qt5compat qtimageformats qtnetworkauth"
- os: macos-12 - os: macos-14
name: macOS-Legacy name: macOS-Legacy
macosx_deployment_target: 10.13 macosx_deployment_target: 10.13
qt_ver: 5 qt_ver: 5
@ -160,13 +164,13 @@ jobs:
- name: Setup ccache - name: Setup ccache
if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug' if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug'
uses: hendrikmuhs/ccache-action@v1.2.14 uses: hendrikmuhs/ccache-action@v1.2.17
with: with:
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }} key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
- name: Retrieve ccache cache (Windows MinGW-w64) - name: Retrieve ccache cache (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug' if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
uses: actions/cache@v4.0.2 uses: actions/cache@v4.2.3
with: with:
path: '${{ github.workspace }}\.ccache' path: '${{ github.workspace }}\.ccache'
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }} key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
@ -199,7 +203,7 @@ jobs:
if: runner.os == 'Linux' if: runner.os == 'Linux'
run: | run: |
sudo apt-get -y update sudo apt-get -y update
sudo apt-get -y install ninja-build extra-cmake-modules scdoc appstream sudo apt-get -y install ninja-build extra-cmake-modules scdoc appstream libxcb-cursor-dev
- name: Install Dependencies (macOS) - name: Install Dependencies (macOS)
if: runner.os == 'macOS' if: runner.os == 'macOS'
@ -209,14 +213,14 @@ jobs:
- name: Install host Qt (Windows MSVC arm64) - name: Install host Qt (Windows MSVC arm64)
if: runner.os == 'Windows' && matrix.architecture == 'arm64' if: runner.os == 'Windows' && matrix.architecture == 'arm64'
uses: jurplel/install-qt-action@v3 uses: jurplel/install-qt-action@v4
with: with:
aqtversion: "==3.1.*" aqtversion: "==3.1.*"
py7zrversion: ">=0.20.2" py7zrversion: ">=0.20.2"
version: ${{ matrix.qt_version }} version: ${{ matrix.qt_version }}
host: "windows" host: "windows"
target: "desktop" target: "desktop"
arch: "" arch: ${{ matrix.qt_arch }}
modules: ${{ matrix.qt_modules }} modules: ${{ matrix.qt_modules }}
cache: ${{ inputs.is_qt_cached }} cache: ${{ inputs.is_qt_cached }}
cache-key-prefix: host-qt-arm64-windows cache-key-prefix: host-qt-arm64-windows
@ -225,7 +229,7 @@ jobs:
- name: Install Qt (macOS, Linux & Windows MSVC) - name: Install Qt (macOS, Linux & Windows MSVC)
if: matrix.msystem == '' if: matrix.msystem == ''
uses: jurplel/install-qt-action@v3 uses: jurplel/install-qt-action@v4
with: with:
aqtversion: "==3.1.*" aqtversion: "==3.1.*"
py7zrversion: ">=0.20.2" py7zrversion: ">=0.20.2"
@ -252,13 +256,19 @@ jobs:
wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/continuous/AppImageUpdate-x86_64.AppImage" wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/continuous/AppImageUpdate-x86_64.AppImage"
sudo apt install libopengl0 sudo apt install libopengl0 libfuse2
- name: Add QT_HOST_PATH var (Windows MSVC arm64) - name: Add QT_HOST_PATH var (Windows MSVC arm64)
if: runner.os == 'Windows' && matrix.architecture == 'arm64' if: runner.os == 'Windows' && matrix.architecture == 'arm64'
run: | run: |
echo "QT_HOST_PATH=${{ github.workspace }}\HostQt\Qt\${{ matrix.qt_version }}\msvc2019_64" >> $env:GITHUB_ENV echo "QT_HOST_PATH=${{ github.workspace }}\HostQt\Qt\${{ matrix.qt_version }}\msvc2022_64" >> $env:GITHUB_ENV
- name: Setup java (macOS)
if: runner.os == 'macOS'
uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: "17"
## ##
# CONFIGURE # CONFIGURE
## ##
@ -266,23 +276,23 @@ jobs:
- name: Configure CMake (macOS) - name: Configure CMake (macOS)
if: runner.os == 'macOS' && matrix.qt_ver == 6 if: runner.os == 'macOS' && matrix.qt_ver == 6
run: | 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) - name: Configure CMake (macOS-Legacy)
if: runner.os == 'macOS' && matrix.qt_ver == 5 if: runner.os == 'macOS' && matrix.qt_ver == 5
run: | 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) - name: Configure CMake (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' if: runner.os == 'Windows' && matrix.msystem != ''
shell: msys2 {0} shell: msys2 {0}
run: | 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) - name: Configure CMake (Windows MSVC)
if: runner.os == 'Windows' && matrix.msystem == '' if: runner.os == 'Windows' && matrix.msystem == ''
run: | 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) # https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix)
if ("${{ env.CCACHE_VAR }}") if ("${{ env.CCACHE_VAR }}")
{ {
@ -297,7 +307,7 @@ jobs:
- name: Configure CMake (Linux) - name: Configure CMake (Linux)
if: runner.os == 'Linux' if: runner.os == 'Linux'
run: | 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 # BUILD
@ -367,11 +377,13 @@ jobs:
if [ -n '${{ secrets.APPLE_CODESIGN_ID }}' ]; then if [ -n '${{ secrets.APPLE_CODESIGN_ID }}' ]; then
APPLE_CODESIGN_ID='${{ secrets.APPLE_CODESIGN_ID }}' APPLE_CODESIGN_ID='${{ secrets.APPLE_CODESIGN_ID }}'
ENTITLEMENTS_FILE='../program_info/App.entitlements'
else else
APPLE_CODESIGN_ID='-' APPLE_CODESIGN_ID='-'
ENTITLEMENTS_FILE='../program_info/AdhocSignedApp.entitlements'
fi fi
sudo codesign --sign "$APPLE_CODESIGN_ID" --deep --force --entitlements "../program_info/App.entitlements" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher" sudo codesign --sign "$APPLE_CODESIGN_ID" --deep --force --entitlements "$ENTITLEMENTS_FILE" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
mv "PrismLauncher.app" "Prism Launcher.app" mv "PrismLauncher.app" "Prism Launcher.app"
- name: Notarize (macOS) - name: Notarize (macOS)
@ -397,9 +409,8 @@ jobs:
if: matrix.name == 'macOS' if: matrix.name == 'macOS'
run: | run: |
if [ '${{ secrets.SPARKLE_ED25519_KEY }}' != '' ]; then if [ '${{ secrets.SPARKLE_ED25519_KEY }}' != '' ]; then
brew install openssl@3
echo '${{ secrets.SPARKLE_ED25519_KEY }}' > ed25519-priv.pem echo '${{ secrets.SPARKLE_ED25519_KEY }}' > ed25519-priv.pem
signature=$(/usr/local/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.zip -inkey ed25519-priv.pem | openssl base64 | tr -d \\n) signature=$(/opt/homebrew/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.zip -inkey ed25519-priv.pem | openssl base64 | tr -d \\n)
rm ed25519-priv.pem rm ed25519-priv.pem
cat >> $GITHUB_STEP_SUMMARY << EOF cat >> $GITHUB_STEP_SUMMARY << EOF
### Artifact Information :information_source: ### Artifact Information :information_source:
@ -465,6 +476,16 @@ jobs:
- name: Package (Windows, installer) - name: Package (Windows, installer)
if: runner.os == 'Windows' if: runner.os == 'Windows'
run: | run: |
if ('${{ matrix.nscurl_tag }}') {
New-Item -Name NSISPlugins -ItemType Directory
Invoke-Webrequest https://github.com/negrutiu/nsis-nscurl/releases/download/${{ matrix.nscurl_tag }}/NScurl.zip -OutFile NSISPlugins\NScurl.zip
$nscurl_hash = Get-FileHash NSISPlugins\NScurl.zip -Algorithm Sha256 | Select-Object -ExpandProperty Hash
if ( $nscurl_hash -ne "${{ matrix.nscurl_sha256 }}") {
echo "::error:: NSCurl.zip sha256 mismatch"
exit 1
}
Expand-Archive -Path NSISPlugins\NScurl.zip -DestinationPath NSISPlugins\NScurl
}
cd ${{ env.INSTALL_DIR }} cd ${{ env.INSTALL_DIR }}
makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi" makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi"
@ -497,8 +518,8 @@ jobs:
cp -r ${{ runner.workspace }}/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines cp -r ${{ runner.workspace }}/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines
cp /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ cp /usr/lib/x86_64-linux-gnu/libcrypto.so.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ cp /usr/lib/x86_64-linux-gnu/libssl.so.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
cp /usr/lib/x86_64-linux-gnu/libOpenGL.so.0* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ cp /usr/lib/x86_64-linux-gnu/libOpenGL.so.0* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib" LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib"
@ -533,9 +554,9 @@ jobs:
mkdir ${{ env.INSTALL_PORTABLE_DIR }}/lib mkdir ${{ env.INSTALL_PORTABLE_DIR }}/lib
cp /lib/x86_64-linux-gnu/libbz2.so.1.0 ${{ 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/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/libcrypto.so.* ${{ 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/libssl.so.* ${{ env.INSTALL_PORTABLE_DIR }}/lib
cp /usr/lib/x86_64-linux-gnu/libffi.so.7 ${{ env.INSTALL_PORTABLE_DIR }}/lib cp /usr/lib/x86_64-linux-gnu/libffi.so.*.* ${{ env.INSTALL_PORTABLE_DIR }}/lib
mv ${{ env.INSTALL_PORTABLE_DIR }}/bin/*.so* ${{ 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 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
@ -607,21 +628,3 @@ jobs:
shell: msys2 {0} shell: msys2 {0}
run: | run: |
ccache -s ccache -s
flatpak:
runs-on: ubuntu-latest
container:
image: bilelmoussaoui/flatpak-github-actions:kde-6.7
options: --privileged
steps:
- name: Checkout
uses: actions/checkout@v4
if: inputs.build_type == 'Debug'
with:
submodules: "true"
- name: Build Flatpak (Linux)
if: inputs.build_type == 'Debug'
uses: flatpak/flatpak-github-actions/flatpak-builder@v6
with:
bundle: "Prism Launcher.flatpak"
manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml

View File

@ -23,7 +23,7 @@ jobs:
run: run:
sudo apt-get -y update 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 libqt5networkauth5 libqt5networkauth5-dev 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 libqt5opengl5 libqt5opengl5-dev
- name: Configure and Build - name: Configure and Build
run: | run: |

62
.github/workflows/flatpak.yml vendored Normal file
View File

@ -0,0 +1,62 @@
name: Flatpak
on:
push:
paths-ignore:
- "**.md"
- "**/LICENSE"
- ".github/ISSUE_TEMPLATE/**"
- ".markdownlint**"
- "nix/**"
# We don't do anything with these artifacts on releases. They go to Flathub
tags-ignore:
- "*"
pull_request:
paths-ignore:
- "**.md"
- "**/LICENSE"
- ".github/ISSUE_TEMPLATE/**"
- ".markdownlint**"
- "nix/**"
workflow_dispatch:
permissions:
contents: read
jobs:
build:
name: Build (${{ matrix.arch }})
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
arch: x86_64
- os: ubuntu-22.04-arm
arch: aarch64
runs-on: ${{ matrix.os }}
container:
image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.8
options: --privileged
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: true
- name: Set short version
shell: bash
run: |
echo "VERSION=${GITHUB_SHA::7}" >> "$GITHUB_ENV"
- name: Build Flatpak
uses: flatpak/flatpak-github-actions/flatpak-builder@v6
with:
bundle: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-Flatpak.flatpak
manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml
arch: ${{ matrix.arch }}

90
.github/workflows/nix.yml vendored Normal file
View File

@ -0,0 +1,90 @@
name: Nix
on:
push:
paths-ignore:
- "**.md"
- "**/LICENSE"
- ".github/ISSUE_TEMPLATE/**"
- ".markdownlint**"
- "flatpak/**"
pull_request_target:
paths-ignore:
- "**.md"
- "**/LICENSE"
- ".github/ISSUE_TEMPLATE/**"
- ".markdownlint**"
- "flatpak/**"
workflow_dispatch:
permissions:
contents: read
env:
DEBUG: ${{ github.ref_type != 'tag' }}
USE_DETERMINATE: ${{ github.event_name == 'pull_request' }}
jobs:
build:
name: Build (${{ matrix.system }})
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
system: x86_64-linux
- os: ubuntu-22.04-arm
system: aarch64-linux
- os: macos-13
system: x86_64-darwin
- os: macos-14
system: aarch64-darwin
runs-on: ${{ matrix.os }}
permissions:
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@v16
with:
determinate: ${{ env.USE_DETERMINATE }}
# For PRs
- name: Setup Nix Magic Cache
if: ${{ env.USE_DETERMINATE }}
uses: DeterminateSystems/flakehub-cache-action@v1
# For in-tree builds
- name: Setup Cachix
if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }}
uses: cachix/cachix-action@v16
with:
name: prismlauncher
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- name: Run Flake checks
run: |
nix flake check --print-build-logs --show-trace
- name: Build debug package
if: ${{ env.DEBUG }}
run: |
nix build \
--no-link --print-build-logs --print-out-paths \
.#prismlauncher-debug >> "$GITHUB_STEP_SUMMARY"
- name: Build release package
if: ${{ !env.DEBUG }}
run: |
nix build \
--no-link --print-build-logs --print-out-paths \
.#prismlauncher >> "$GITHUB_STEP_SUMMARY"

45
.github/workflows/publish.yml vendored Normal file
View File

@ -0,0 +1,45 @@
name: Publish
on:
release:
types: [ released ]
permissions:
contents: read
jobs:
flakehub:
name: FlakeHub
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
- name: Install Nix
uses: cachix/install-nix-action@v31
- name: Publish on FlakeHub
uses: determinatesystems/flakehub-push@v5
with:
visibility: "public"
winget:
name: Winget
runs-on: windows-latest
steps:
- name: Publish on Winget
uses: vedantmgoyal2009/winget-releaser@v2
with:
identifier: PrismLauncher.PrismLauncher
version: ${{ github.event.release.tag_name }}
installers-regex: 'PrismLauncher-Windows-MSVC(:?-arm64|-Legacy)?-Setup-.+\.exe$'
token: ${{ secrets.WINGET_TOKEN }}

29
.github/workflows/stale.yml vendored Normal file
View File

@ -0,0 +1,29 @@
name: Stale
on:
schedule:
# run weekly on sunday
- cron: "0 0 * * 0"
workflow_dispatch:
jobs:
label:
name: Label issues and PRs
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v9
with:
days-before-stale: 60
days-before-close: -1 # Don't close anything
exempt-issue-labels: rfc,nostale,help wanted
exempt-all-milestones: true
exempt-all-assignees: true
operations-per-run: 1000
stale-issue-label: inactive
stale-pr-label: inactive

View File

@ -17,9 +17,9 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: cachix/install-nix-action@ba0dd844c9180cbf77aa72a116d6fbc515d0e87b # v27 - uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f # v31
- uses: DeterminateSystems/update-flake-lock@v23 - uses: DeterminateSystems/update-flake-lock@v24
with: with:
commit-msg: "chore(nix): update lockfile" commit-msg: "chore(nix): update lockfile"
pr-title: "chore(nix): update lockfile" pr-title: "chore(nix): update lockfile"

View File

@ -1,15 +0,0 @@
name: Publish to WinGet
on:
release:
types: [released]
jobs:
publish:
runs-on: windows-latest
steps:
- uses: vedantmgoyal2009/winget-releaser@v2
with:
identifier: PrismLauncher.PrismLauncher
version: ${{ github.event.release.tag_name }}
installers-regex: 'PrismLauncher-Windows-MSVC(:?-arm64|-Legacy)?-Setup-.+\.exe$'
token: ${{ secrets.WINGET_TOKEN }}

1
.gitignore vendored
View File

@ -21,6 +21,7 @@ CMakeCache.txt
/.vs /.vs
cmake-build-*/ cmake-build-*/
Debug Debug
compile_commands.json
# Build dirs # Build dirs
build build

View File

@ -78,6 +78,13 @@ else()
# ATL's pack list needs more than the default 1 Mib stack on windows # ATL's pack list needs more than the default 1 Mib stack on windows
if(WIN32) if(WIN32)
set(CMAKE_EXE_LINKER_FLAGS "-Wl,--stack,8388608 ${CMAKE_EXE_LINKER_FLAGS}") set(CMAKE_EXE_LINKER_FLAGS "-Wl,--stack,8388608 ${CMAKE_EXE_LINKER_FLAGS}")
# -ffunction-sections and -fdata-sections help reduce binary size
# -mguard=cf enables Control Flow Guard
# TODO: Look into -gc-sections to further reduce binary size
foreach(lang C CXX)
set("CMAKE_${lang}_FLAGS_RELEASE" "-ffunction-sections -fdata-sections -mguard=cf")
endforeach()
endif() endif()
endif() endif()
@ -92,6 +99,12 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTOML_ENABLE_FLOAT16=0")
# set CXXFLAGS for build targets # set CXXFLAGS for build targets
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}") set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}")
# Export compile commands for debug builds if we can (useful in LSPs like clangd)
# https://cmake.org/cmake/help/v3.31/variable/CMAKE_EXPORT_COMPILE_COMMANDS.html
if(CMAKE_GENERATOR STREQUAL "Unix Makefiles" OR CMAKE_GENERATOR STREQUAL "Ninja" AND CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
endif()
option(DEBUG_ADDRESS_SANITIZER "Enable Address Sanitizer in Debug builds" OFF) option(DEBUG_ADDRESS_SANITIZER "Enable Address Sanitizer in Debug builds" OFF)
# If this is a Debug build turn on address sanitiser # If this is a Debug build turn on address sanitiser
@ -99,21 +112,21 @@ if ((CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebI
message(STATUS "Address Sanitizer enabled for Debug builds, Turn it off with -DDEBUG_ADDRESS_SANITIZER=off") message(STATUS "Address Sanitizer enabled for Debug builds, Turn it off with -DDEBUG_ADDRESS_SANITIZER=off")
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
# using clang with clang-cl front end # using clang with clang-cl front end
message(STATUS "Address Sanitizer available on Clang MSVC frontend") message(STATUS "Address Sanitizer available on Clang MSVC frontend")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fsanitize=address /Oy-") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fsanitize=address /Oy-")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /fsanitize=address /Oy-") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /fsanitize=address /Oy-")
else() else()
# AppleClang and Clang # AppleClang and Clang
message(STATUS "Address Sanitizer available on Clang") message(STATUS "Address Sanitizer available on Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=null")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=null")
endif() endif()
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
# GCC # GCC
message(STATUS "Address Sanitizer available on GCC") message(STATUS "Address Sanitizer available on GCC")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover")
link_libraries("asan") link_libraries("asan")
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
message(STATUS "Address Sanitizer available on MSVC") message(STATUS "Address Sanitizer available on MSVC")
@ -176,9 +189,11 @@ 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_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_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_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 version numbers ########
set(Launcher_VERSION_MAJOR 9) set(Launcher_VERSION_MAJOR 10)
set(Launcher_VERSION_MINOR 0) set(Launcher_VERSION_MINOR 0)
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}") set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}")
@ -205,6 +220,7 @@ set(Launcher_BUG_TRACKER_URL "https://github.com/PrismLauncher/PrismLauncher/iss
# Translations Platform URL # Translations Platform URL
set(Launcher_TRANSLATIONS_URL "https://hosted.weblate.org/projects/prismlauncher/launcher/" CACHE STRING "URL for the translations platform.") 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 # Matrix Space
set(Launcher_MATRIX_URL "https://prismlauncher.org/matrix" CACHE STRING "URL to the Matrix Space") set(Launcher_MATRIX_URL "https://prismlauncher.org/matrix" CACHE STRING "URL to the Matrix Space")
@ -219,6 +235,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_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") 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 # Native libraries
if(UNIX AND APPLE) if(UNIX AND APPLE)
set(Launcher_GLFW_LIBRARY_NAME "libglfw.dylib" CACHE STRING "Name of native glfw library") set(Launcher_GLFW_LIBRARY_NAME "libglfw.dylib" CACHE STRING "Name of native glfw library")
@ -282,7 +311,9 @@ endif()
include(QtVersionlessBackport) include(QtVersionlessBackport)
if(Launcher_QT_VERSION_MAJOR EQUAL 5) if(Launcher_QT_VERSION_MAJOR EQUAL 5)
set(QT_VERSION_MAJOR 5) set(QT_VERSION_MAJOR 5)
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml NetworkAuth) find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml NetworkAuth OpenGL)
find_package(Qt5 COMPONENTS DBus)
list(APPEND Launcher_QT_DBUS Qt5::DBus)
if(NOT Launcher_FORCE_BUNDLED_LIBS) if(NOT Launcher_FORCE_BUNDLED_LIBS)
find_package(QuaZip-Qt5 1.3 QUIET) find_package(QuaZip-Qt5 1.3 QUIET)
@ -296,7 +327,9 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 5)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUNICODE -D_UNICODE") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUNICODE -D_UNICODE")
elseif(Launcher_QT_VERSION_MAJOR EQUAL 6) elseif(Launcher_QT_VERSION_MAJOR EQUAL 6)
set(QT_VERSION_MAJOR 6) set(QT_VERSION_MAJOR 6)
find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat NetworkAuth) find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat NetworkAuth OpenGL)
find_package(Qt6 COMPONENTS DBus)
list(APPEND Launcher_QT_DBUS Qt6::DBus)
list(APPEND Launcher_QT_LIBS Qt6::Core5Compat) list(APPEND Launcher_QT_LIBS Qt6::Core5Compat)
if(NOT Launcher_FORCE_BUNDLED_LIBS) if(NOT Launcher_FORCE_BUNDLED_LIBS)
@ -381,8 +414,8 @@ if(UNIX AND APPLE)
set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=" CACHE STRING "Public key for Sparkle update feed") set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=" CACHE STRING "Public key for Sparkle update feed")
set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml" CACHE STRING "URL for Sparkle update feed") set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml" CACHE STRING "URL for Sparkle update feed")
set(MACOSX_SPARKLE_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.5.2/Sparkle-2.5.2.tar.xz" CACHE STRING "URL to Sparkle release archive") set(MACOSX_SPARKLE_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.6.4/Sparkle-2.6.4.tar.xz" CACHE STRING "URL to Sparkle release archive")
set(MACOSX_SPARKLE_SHA256 "572dd67ae398a466f19f343a449e1890bac1ef74885b4739f68f979a8a89884b" CACHE STRING "SHA256 checksum for Sparkle release archive") set(MACOSX_SPARKLE_SHA256 "50612a06038abc931f16011d7903b8326a362c1074dabccb718404ce8e585f0b" CACHE STRING "SHA256 checksum for Sparkle release archive")
set(MACOSX_SPARKLE_DIR "${CMAKE_BINARY_DIR}/frameworks/Sparkle") set(MACOSX_SPARKLE_DIR "${CMAKE_BINARY_DIR}/frameworks/Sparkle")
# directories to look for dependencies # directories to look for dependencies
@ -422,10 +455,10 @@ elseif(UNIX)
set(PLUGIN_DEST_DIR "plugins") set(PLUGIN_DEST_DIR "plugins")
set(BUNDLE_DEST_DIR ".") set(BUNDLE_DEST_DIR ".")
set(RESOURCES_DEST_DIR ".") set(RESOURCES_DEST_DIR ".")
# Apps to bundle # Apps to bundle
set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/${Launcher_APP_BINARY_NAME}") set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/${Launcher_APP_BINARY_NAME}")
# directories to look for dependencies # directories to look for dependencies
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
endif() endif()
@ -479,7 +512,7 @@ if(FORCE_BUNDLED_ZLIB)
set(SKIP_INSTALL_ALL ON) set(SKIP_INSTALL_ALL ON)
add_subdirectory(libraries/zlib EXCLUDE_FROM_ALL) add_subdirectory(libraries/zlib EXCLUDE_FROM_ALL)
# On OS where unistd.h exists, zlib's generated header defines `Z_HAVE_UNISTD_H`, while the included header does not. # On OS where unistd.h exists, zlib's generated header defines `Z_HAVE_UNISTD_H`, while the included header does not.
# We cannot safely undo the rename on those systems, and they generally have packages for zlib anyway. # We cannot safely undo the rename on those systems, and they generally have packages for zlib anyway.
check_include_file(unistd.h NEED_GENERATED_ZCONF) check_include_file(unistd.h NEED_GENERATED_ZCONF)
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib/zconf.h.included" AND NOT NEED_GENERATED_ZCONF) if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib/zconf.h.included" AND NOT NEED_GENERATED_ZCONF)
@ -516,10 +549,12 @@ else()
endif() endif()
if(NOT cmark_FOUND) if(NOT cmark_FOUND)
message(STATUS "Using bundled cmark") message(STATUS "Using bundled cmark")
set(ORIGINAL_BUILD_TESTING ${BUILD_TESTING})
set(BUILD_TESTING 0) set(BUILD_TESTING 0)
set(BUILD_SHARED_LIBS 0) set(BUILD_SHARED_LIBS 0)
add_subdirectory(libraries/cmark EXCLUDE_FROM_ALL) # Markdown parser add_subdirectory(libraries/cmark EXCLUDE_FROM_ALL) # Markdown parser
add_library(cmark::cmark ALIAS cmark) add_library(cmark::cmark ALIAS cmark)
set(BUILD_TESTING ${ORIGINAL_BUILD_TESTING})
else() else()
message(STATUS "Using system cmark") message(STATUS "Using system cmark")
endif() endif()

View File

@ -2,16 +2,59 @@
## Code formatting ## Code formatting
Try to follow the existing formatting. All files are formatted with `clang-format` using the configuration in `.clang-format`. Ensure it is run on changed files before committing!
If there is no existing formatting, you may use `clang-format` with our included `.clang-format` configuration.
In general, in order of importance: Please also follow the project's conventions for C++:
- Make sure your IDE is not messing up line endings or whitespace and avoid using linters. - Class and type names should be formatted as `PascalCase`: `MyClass`.
- Prefer readability over dogma. - Private or protected class data members should be formatted as `camelCase` prefixed with `m_`: `m_myCounter`.
- Keep to the existing formatting. - Private or protected `static` class data members should be formatted as `camelCase` prefixed with `s_`: `s_instance`.
- Indent with 4 space unless it's in a submodule. - Public class data members should be formatted as `camelCase` without the prefix: `dateOfBirth`.
- Keep lists (of arguments, parameters, initializers...) as lists, not paragraphs. It should either read from top to bottom, or left to right. Not both. - Public, private or protected `static const` class data members should be formatted as `SCREAMING_SNAKE_CASE`: `MAX_VALUE`.
- Class function members should be formatted as `camelCase` without a prefix: `incrementCounter`.
- Global functions and non-`const` global variables should be formatted as `camelCase` without a prefix: `globalData`.
- `const` global variables, macros, and enum constants should be formatted as `SCREAMING_SNAKE_CASE`: `LIGHT_GRAY`.
- Avoid inventing acronyms or abbreviations especially for a name of multiple words - like `tp` for `texturePack`.
Most of these rules are included in the `.clang-tidy` file, so you can run `clang-tidy` to check for any violations.
Here is what these conventions with the formatting configuration look like:
```c++
#define AWESOMENESS 10
constexpr double PI = 3.14159;
enum class PizzaToppings { HAM_AND_PINEAPPLE, OREO_AND_KETCHUP };
struct Person {
QString name;
QDateTime dateOfBirth;
long daysOld() const { return dateOfBirth.daysTo(QDateTime::currentDateTime()); }
};
class ImportantClass {
public:
void incrementCounter()
{
if (m_counter + 1 > MAX_COUNTER_VALUE)
throw std::runtime_error("Counter has reached limit!");
++m_counter;
}
int counter() const { return m_counter; }
private:
static constexpr int MAX_COUNTER_VALUE = 100;
int m_counter;
};
ImportantClass importantClassInstance;
```
If you see any names which do not follow these conventions, it is preferred that you leave them be - renames increase the number of changes therefore make reviewing harder and make your PR more prone to conflicts. However, if you're refactoring a whole class anyway, it's fine.
## Signing your work ## Signing your work

View File

@ -1,7 +1,7 @@
## Prism Launcher ## Prism Launcher
Prism Launcher - Minecraft Launcher Prism Launcher - Minecraft Launcher
Copyright (C) 2022-2024 Prism Launcher Contributors Copyright (C) 2022-2025 Prism Launcher Contributors
This program is free software: you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by

View File

@ -49,7 +49,7 @@ Config::Config()
LAUNCHER_DOMAIN = "@Launcher_Domain@"; LAUNCHER_DOMAIN = "@Launcher_Domain@";
LAUNCHER_CONFIGFILE = "@Launcher_ConfigFile@"; LAUNCHER_CONFIGFILE = "@Launcher_ConfigFile@";
LAUNCHER_GIT = "@Launcher_Git@"; LAUNCHER_GIT = "@Launcher_Git@";
LAUNCHER_DESKTOPFILENAME = "@Launcher_DesktopFileName@"; LAUNCHER_APPID = "@Launcher_AppID@";
LAUNCHER_SVGFILENAME = "@Launcher_SVGFileName@"; LAUNCHER_SVGFILENAME = "@Launcher_SVGFileName@";
USER_AGENT = "@Launcher_UserAgent@"; USER_AGENT = "@Launcher_UserAgent@";
@ -81,6 +81,9 @@ Config::Config()
UPDATER_ENABLED = true; UPDATER_ENABLED = true;
} }
#cmakedefine01 Launcher_ENABLE_JAVA_DOWNLOADER
JAVA_DOWNLOADER_ENABLED = Launcher_ENABLE_JAVA_DOWNLOADER;
GIT_COMMIT = "@Launcher_GIT_COMMIT@"; GIT_COMMIT = "@Launcher_GIT_COMMIT@";
GIT_TAG = "@Launcher_GIT_TAG@"; GIT_TAG = "@Launcher_GIT_TAG@";
GIT_REFSPEC = "@Launcher_GIT_REFSPEC@"; GIT_REFSPEC = "@Launcher_GIT_REFSPEC@";
@ -113,16 +116,19 @@ Config::Config()
NEWS_RSS_URL = "@Launcher_NEWS_RSS_URL@"; NEWS_RSS_URL = "@Launcher_NEWS_RSS_URL@";
NEWS_OPEN_URL = "@Launcher_NEWS_OPEN_URL@"; NEWS_OPEN_URL = "@Launcher_NEWS_OPEN_URL@";
HELP_URL = "@Launcher_HELP_URL@"; HELP_URL = "@Launcher_HELP_URL@";
LOGIN_CALLBACK_URL = "@Launcher_LOGIN_CALLBACK_URL@";
IMGUR_CLIENT_ID = "@Launcher_IMGUR_CLIENT_ID@"; IMGUR_CLIENT_ID = "@Launcher_IMGUR_CLIENT_ID@";
MSA_CLIENT_ID = "@Launcher_MSA_CLIENT_ID@"; MSA_CLIENT_ID = "@Launcher_MSA_CLIENT_ID@";
FLAME_API_KEY = "@Launcher_CURSEFORGE_API_KEY@"; FLAME_API_KEY = "@Launcher_CURSEFORGE_API_KEY@";
META_URL = "@Launcher_META_URL@"; META_URL = "@Launcher_META_URL@";
FMLLIBS_BASE_URL = "@Launcher_FMLLIBS_BASE_URL@";
GLFW_LIBRARY_NAME = "@Launcher_GLFW_LIBRARY_NAME@"; GLFW_LIBRARY_NAME = "@Launcher_GLFW_LIBRARY_NAME@";
OPENAL_LIBRARY_NAME = "@Launcher_OPENAL_LIBRARY_NAME@"; OPENAL_LIBRARY_NAME = "@Launcher_OPENAL_LIBRARY_NAME@";
BUG_TRACKER_URL = "@Launcher_BUG_TRACKER_URL@"; BUG_TRACKER_URL = "@Launcher_BUG_TRACKER_URL@";
TRANSLATIONS_URL = "@Launcher_TRANSLATIONS_URL@"; TRANSLATIONS_URL = "@Launcher_TRANSLATIONS_URL@";
TRANSLATION_FILES_URL = "@Launcher_TRANSLATION_FILES_URL@";
MATRIX_URL = "@Launcher_MATRIX_URL@"; MATRIX_URL = "@Launcher_MATRIX_URL@";
DISCORD_URL = "@Launcher_DISCORD_URL@"; DISCORD_URL = "@Launcher_DISCORD_URL@";
SUBREDDIT_URL = "@Launcher_SUBREDDIT_URL@"; SUBREDDIT_URL = "@Launcher_SUBREDDIT_URL@";

View File

@ -52,7 +52,7 @@ class Config {
QString LAUNCHER_DOMAIN; QString LAUNCHER_DOMAIN;
QString LAUNCHER_CONFIGFILE; QString LAUNCHER_CONFIGFILE;
QString LAUNCHER_GIT; QString LAUNCHER_GIT;
QString LAUNCHER_DESKTOPFILENAME; QString LAUNCHER_APPID;
QString LAUNCHER_SVGFILENAME; QString LAUNCHER_SVGFILENAME;
/// The major version number. /// The major version number.
@ -67,6 +67,7 @@ class Config {
QString VERSION_CHANNEL; QString VERSION_CHANNEL;
bool UPDATER_ENABLED = false; bool UPDATER_ENABLED = false;
bool JAVA_DOWNLOADER_ENABLED = false;
/// A short string identifying this build's platform or distribution. /// A short string identifying this build's platform or distribution.
QString BUILD_PLATFORM; QString BUILD_PLATFORM;
@ -132,6 +133,11 @@ class Config {
*/ */
QString HELP_URL; 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 * Client ID you can get from Imgur when you register an application
*/ */
@ -164,8 +170,8 @@ class Config {
QString RESOURCE_BASE = "https://resources.download.minecraft.net/"; QString RESOURCE_BASE = "https://resources.download.minecraft.net/";
QString LIBRARY_BASE = "https://libraries.minecraft.net/"; QString LIBRARY_BASE = "https://libraries.minecraft.net/";
QString IMGUR_BASE_URL = "https://api.imgur.com/3/"; QString IMGUR_BASE_URL = "https://api.imgur.com/3/";
QString FMLLIBS_BASE_URL = "https://files.prismlauncher.org/fmllibs/"; // FIXME: move into CMakeLists QString FMLLIBS_BASE_URL;
QString TRANSLATIONS_BASE_URL = "https://i18n.prismlauncher.org/"; // FIXME: move into CMakeLists QString TRANSLATION_FILES_URL;
QString MODPACKSCH_API_BASE_URL = "https://api.modpacks.ch/"; QString MODPACKSCH_API_BASE_URL = "https://api.modpacks.ch/";

View File

@ -8,6 +8,8 @@
<string>A Minecraft mod wants to access your microphone.</string> <string>A Minecraft mod wants to access your microphone.</string>
<key>NSDownloadsFolderUsageDescription</key> <key>NSDownloadsFolderUsageDescription</key>
<string>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.</string> <string>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.</string>
<key>NSLocalNetworkUsageDescription</key>
<string>Minecraft uses the local network to find and connect to LAN servers.</string>
<key>NSPrincipalClass</key> <key>NSPrincipalClass</key>
<string>NSApplication</string> <string>NSApplication</string>
<key>NSHighResolutionCapable</key> <key>NSHighResolutionCapable</key>

View File

@ -1,14 +1,9 @@
( (import (
import let
( lock = builtins.fromJSON (builtins.readFile ./flake.lock);
let in
lock = builtins.fromJSON (builtins.readFile ./flake.lock); fetchTarball {
in url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
fetchTarball { sha256 = lock.nodes.flake-compat.locked.narHash;
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
}
)
{src = ./.;}
)
.defaultNix

100
flake.lock generated
View File

@ -3,11 +3,11 @@
"flake-compat": { "flake-compat": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1696426674, "lastModified": 1733328505,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
"owner": "edolstra", "owner": "edolstra",
"repo": "flake-compat", "repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -16,47 +16,6 @@
"type": "github" "type": "github"
} }
}, },
"flake-parts": {
"inputs": {
"nixpkgs-lib": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1722555600,
"narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "8471fe90ad337a8074e957b69ca4d0089218391d",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"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": { "libnbtplusplus": {
"flake": false, "flake": false,
"locked": { "locked": {
@ -73,13 +32,28 @@
"type": "github" "type": "github"
} }
}, },
"nix-filter": {
"locked": {
"lastModified": 1731533336,
"narHash": "sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms=",
"owner": "numtide",
"repo": "nix-filter",
"rev": "f7653272fd234696ae94229839a99b73c9ab7de0",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "nix-filter",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1723637854, "lastModified": 1742422364,
"narHash": "sha256-med8+5DSWa2UnOqtdICndjDAEjxr5D7zaIiK4pn0Q7c=", "narHash": "sha256-mNqIplmEohk5jRkqYqG19GA8MbQ/D4gQSK0Mu4LvfRQ=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "c3aa7b8938b17aebd2deecf7be0636000d62a2b9", "rev": "a84ebe20c6bc2ecbcfb000a50776219f48d134cc",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -89,40 +63,12 @@
"type": "github" "type": "github"
} }
}, },
"pre-commit-hooks": {
"inputs": {
"flake-compat": [
"flake-compat"
],
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
],
"nixpkgs-stable": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1723803910,
"narHash": "sha256-yezvUuFiEnCFbGuwj/bQcqg7RykIEqudOy/RBrId0pc=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "bfef0ada09e2c8ac55bbcd0831bd0c9d42e651ba",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": { "root": {
"inputs": { "inputs": {
"flake-compat": "flake-compat", "flake-compat": "flake-compat",
"flake-parts": "flake-parts",
"libnbtplusplus": "libnbtplusplus", "libnbtplusplus": "libnbtplusplus",
"nixpkgs": "nixpkgs", "nix-filter": "nix-filter",
"pre-commit-hooks": "pre-commit-hooks" "nixpkgs": "nixpkgs"
} }
} }
}, },

165
flake.nix
View File

@ -2,52 +2,147 @@
description = "A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once (Fork of MultiMC)"; description = "A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once (Fork of MultiMC)";
nixConfig = { nixConfig = {
extra-substituters = ["https://cache.garnix.io"]; extra-substituters = [ "https://prismlauncher.cachix.org" ];
extra-trusted-public-keys = ["cache.garnix.io:CTFPyKSLcx5RMJKfLo5EEPUObbA78b0YQ2DTCJXqr9g="]; extra-trusted-public-keys = [
"prismlauncher.cachix.org-1:9/n/FGyABA2jLUVfY+DEp4hKds/rwO+SCOtbOkDzd+c="
];
}; };
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-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;
};
libnbtplusplus = { libnbtplusplus = {
url = "github:PrismLauncher/libnbtplusplus"; url = "github:PrismLauncher/libnbtplusplus";
flake = false; 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 = { outputs =
flake-parts, {
pre-commit-hooks, self,
... nixpkgs,
} @ inputs: libnbtplusplus,
flake-parts.lib.mkFlake {inherit inputs;} { nix-filter,
imports = [ ...
pre-commit-hooks.flakeModule }:
let
inherit (nixpkgs) lib;
./nix/dev.nix # While we only officially support aarch and x86_64 on Linux and MacOS,
./nix/distribution.nix # we expose a reasonable amount of other systems for users who want to
]; # build for most exotic platforms
systems = lib.systems.flakeExposed;
systems = [ forAllSystems = lib.genAttrs systems;
"x86_64-linux" nixpkgsFor = forAllSystems (system: nixpkgs.legacyPackages.${system});
"aarch64-linux" in
"x86_64-darwin" {
"aarch64-darwin" 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
llvmPackages_19.clang-tools
];
cmakeFlags = self.packages.${system}.prismlauncher-unwrapped.cmakeFlags ++ [
"-GNinja"
"-Bbuild"
];
shellHook = ''
cmake $cmakeFlags -D CMAKE_BUILD_TYPE=Debug
ln -s {build/,}compile_commands.json
'';
};
}
);
formatter = forAllSystems (system: nixpkgsFor.${system}.nixfmt-rfc-style);
overlays.default = final: prev: {
prismlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix {
inherit
libnbtplusplus
nix-filter
self
;
};
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
);
# We put these under legacyPackages as they are meant for CI, not end user consumption
legacyPackages = forAllSystems (
system:
let
prismPackages = self.packages.${system};
legacyPackages = self.legacyPackages.${system};
in
{
prismlauncher-debug = prismPackages.prismlauncher.override {
prismlauncher-unwrapped = legacyPackages.prismlauncher-unwrapped-debug;
};
prismlauncher-unwrapped-debug = prismPackages.prismlauncher-unwrapped.overrideAttrs {
cmakeBuildType = "Debug";
dontStrip = true;
};
}
);
}; };
} }

20
flatpak/flite.json Normal file
View File

@ -0,0 +1,20 @@
{
"name": "flite",
"config-opts": [
"--enable-shared",
"--with-audio=pulseaudio"
],
"no-parallel-make": true,
"sources": [
{
"type": "git",
"url": "https://github.com/festvox/flite.git",
"tag": "v2.2",
"commit": "e9e2e37c329dbe98bfeb27a1828ef9a71fa84f88",
"x-checker-data": {
"type": "git",
"tag-pattern": "^v([\\d.]+)$"
}
}
]
}

View File

@ -1,22 +1,18 @@
{ {
"name": "libdecor", "name": "libdecor",
"buildsystem": "meson", "buildsystem": "meson",
"config-opts": [ "config-opts": [
"-Ddemo=false" "-Ddemo=false"
], ],
"sources": [ "sources": [
{ {
"type": "git", "type": "git",
"url": "https://gitlab.freedesktop.org/libdecor/libdecor.git", "url": "https://gitlab.freedesktop.org/libdecor/libdecor.git",
"commit": "73260393a97291c887e1074ab7f318e031be0ac6" "commit": "c2bd8ad6fa42c0cb17553ce77ad8a87d1f543b1f"
}, }
{ ],
"type": "patch", "cleanup": [
"path": "patches/weird_libdecor.patch" "/include",
} "/lib/pkgconfig"
], ]
"cleanup": [
"/include",
"/lib/pkgconfig"
]
} }

View File

@ -1,11 +1,9 @@
id: org.prismlauncher.PrismLauncher id: org.prismlauncher.PrismLauncher
runtime: org.kde.Platform runtime: org.kde.Platform
runtime-version: 6.7 runtime-version: '6.8'
sdk: org.kde.Sdk sdk: org.kde.Sdk
sdk-extensions: sdk-extensions:
- org.freedesktop.Sdk.Extension.openjdk21
- org.freedesktop.Sdk.Extension.openjdk17 - org.freedesktop.Sdk.Extension.openjdk17
- org.freedesktop.Sdk.Extension.openjdk8
command: prismlauncher command: prismlauncher
finish-args: finish-args:
@ -21,9 +19,12 @@ finish-args:
- --filesystem=xdg-download:ro - --filesystem=xdg-download:ro
# FTBApp import # FTBApp import
- --filesystem=~/.ftba:ro - --filesystem=~/.ftba:ro
# Userspace visibility for manual hugepages configuration
cleanup: # Required for -XX:+UseLargePages
- /lib/libGLU* - --filesystem=/sys/kernel/mm/hugepages:ro
# Userspace visibility for transparent hugepages configuration
# Required for -XX:+UseTransparentHugePages
- --filesystem=/sys/kernel/mm/transparent_hugepage:ro
modules: modules:
# Might be needed by some Controller mods (see https://github.com/isXander/Controlify/issues/31) # Might be needed by some Controller mods (see https://github.com/isXander/Controlify/issues/31)
@ -32,50 +33,39 @@ modules:
# Needed for proper Wayland support # Needed for proper Wayland support
- libdecor.json - libdecor.json
# Text to Speech in the game
- flite.json
- name: prismlauncher - name: prismlauncher
buildsystem: cmake-ninja buildsystem: cmake-ninja
builddir: true builddir: true
config-opts: config-opts:
- -DLauncher_BUILD_PLATFORM=flatpak - -DLauncher_BUILD_PLATFORM=flatpak
# This allows us to manage and update Java independently of this Flatpak
- -DLauncher_ENABLE_JAVA_DOWNLOADER=ON
- -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DCMAKE_BUILD_TYPE=RelWithDebInfo
build-options: build-options:
env: env:
JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17 JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17
JAVA_COMPILER: /usr/lib/sdk/openjdk17/jvm/openjdk-17/bin/javac JAVA_COMPILER: /usr/lib/sdk/openjdk17/jvm/openjdk-17/bin/javac
run-tests: true
sources: sources:
- type: dir - type: dir
path: ../ path: ../
- name: openjdk
buildsystem: simple
build-commands:
- mkdir -p /app/jdk/
- /usr/lib/sdk/openjdk21/install.sh
- mv /app/jre /app/jdk/21
- /usr/lib/sdk/openjdk17/install.sh
- mv /app/jre /app/jdk/17
- /usr/lib/sdk/openjdk8/install.sh
- mv /app/jre /app/jdk/8
cleanup:
- /jre
- name: glfw - name: glfw
buildsystem: cmake-ninja buildsystem: cmake-ninja
config-opts: config-opts:
- -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DCMAKE_BUILD_TYPE=RelWithDebInfo
- -DBUILD_SHARED_LIBS:BOOL=ON - -DBUILD_SHARED_LIBS:BOOL=ON
- -DGLFW_USE_WAYLAND:BOOL=ON - -DGLFW_BUILD_WAYLAND:BOOL=ON
- -DGLFW_BUILD_DOCS:BOOL=OFF - -DGLFW_BUILD_DOCS:BOOL=OFF
sources: sources:
- type: git - type: git
url: https://github.com/glfw/glfw.git url: https://github.com/glfw/glfw.git
commit: 3fa2360720eeba1964df3c0ecf4b5df8648a8e52 commit: 7b6aead9fb88b3623e3b3725ebb42670cbe4c579 # 3.4
- type: patch - type: patch
path: patches/0003-Don-t-crash-on-calls-to-focus-or-icon.patch path: patches/0009-Defer-setting-cursor-position-until-the-cursor-is-lo.patch
- type: patch
path: patches/0005-Add-warning-about-being-an-unofficial-patch.patch
- type: patch
path: patches/0007-Platform-Prefer-Wayland-over-X11.patch
cleanup: cleanup:
- /include - /include
- /lib/cmake - /lib/cmake
@ -85,8 +75,8 @@ modules:
buildsystem: autotools buildsystem: autotools
sources: sources:
- type: archive - type: archive
url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.2.tar.xz url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.3.tar.xz
sha256: c8bee4790d9058bacc4b6246456c58021db58a87ddda1a9d0139bf5f18f1f240 sha256: f8dd7566adb74147fab9964680b6bbadee87cf406a7fcff51718a5e6949b841c
x-checker-data: x-checker-data:
type: anitya type: anitya
project-id: 14957 project-id: 14957
@ -108,8 +98,8 @@ modules:
sources: sources:
- type: archive - type: archive
dest-filename: gamemode.tar.gz dest-filename: gamemode.tar.gz
url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.8.1 url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.8.2
sha256: 969cf85b5ca3944f3e315cd73a0ee9bea4f9c968cd7d485e9f4745bc1e679c4e sha256: 2886d4ce543c78bd2a364316d5e7fd59ef06b71de63f896b37c6d3dc97658f60
x-checker-data: x-checker-data:
type: json type: json
url: https://api.github.com/repos/FeralInteractive/gamemode/releases/latest url: https://api.github.com/repos/FeralInteractive/gamemode/releases/latest

View File

@ -1,24 +0,0 @@
diff --git a/src/wl_window.c b/src/wl_window.c
index 52d3b9eb..4ac4eb5d 100644
--- a/src/wl_window.c
+++ b/src/wl_window.c
@@ -2117,8 +2117,7 @@ void _glfwSetWindowTitleWayland(_GLFWwindow* window, const char* title)
void _glfwSetWindowIconWayland(_GLFWwindow* window,
int count, const GLFWimage* images)
{
- _glfwInputError(GLFW_FEATURE_UNAVAILABLE,
- "Wayland: The platform does not support setting the window icon");
+ fprintf(stderr, "!!! Ignoring Error: Wayland: The platform does not support setting the window icon\n");
}
void _glfwGetWindowPosWayland(_GLFWwindow* window, int* xpos, int* ypos)
@@ -2361,8 +2360,7 @@ void _glfwRequestWindowAttentionWayland(_GLFWwindow* window)
void _glfwFocusWindowWayland(_GLFWwindow* window)
{
- _glfwInputError(GLFW_FEATURE_UNAVAILABLE,
- "Wayland: The platform does not support setting the input focus");
+ fprintf(stderr, "!!! Ignoring Error: Wayland: The platform does not support setting the input focus\n");
}
void _glfwSetWindowMonitorWayland(_GLFWwindow* window,

View File

@ -1,17 +0,0 @@
diff --git a/src/init.c b/src/init.c
index 06dbb3f2..a7c6da86 100644
--- a/src/init.c
+++ b/src/init.c
@@ -449,6 +449,12 @@ GLFWAPI int glfwInit(void)
_glfw.initialized = GLFW_TRUE;
glfwDefaultWindowHints();
+
+ fprintf(stderr, "!!! Patched GLFW from https://github.com/Admicos/minecraft-wayland\n"
+ "!!! If any issues with the window, or some issues with rendering, occur, "
+ "first try with the built-in GLFW, and if that solves the issue, report there first.\n"
+ "!!! Use outside Minecraft is untested, and things might break.\n");
+
return GLFW_TRUE;
}

View File

@ -1,20 +0,0 @@
diff --git a/src/platform.c b/src/platform.c
index c5966ae7..3e7442f9 100644
--- a/src/platform.c
+++ b/src/platform.c
@@ -49,12 +49,12 @@ static const struct
#if defined(_GLFW_COCOA)
{ GLFW_PLATFORM_COCOA, _glfwConnectCocoa },
#endif
-#if defined(_GLFW_X11)
- { GLFW_PLATFORM_X11, _glfwConnectX11 },
-#endif
#if defined(_GLFW_WAYLAND)
{ GLFW_PLATFORM_WAYLAND, _glfwConnectWayland },
#endif
+#if defined(_GLFW_X11)
+ { GLFW_PLATFORM_X11, _glfwConnectX11 },
+#endif
};
GLFWbool _glfwSelectPlatform(int desiredID, _GLFWplatform* platform)

View File

@ -0,0 +1,59 @@
From 9997ae55a47de469ea26f8437c30b51483abda5f Mon Sep 17 00:00:00 2001
From: Dan Klishch <danilklishch@gmail.com>
Date: Sat, 30 Sep 2023 23:38:05 -0400
Subject: Defer setting cursor position until the cursor is locked
---
src/wl_platform.h | 3 +++
src/wl_window.c | 14 ++++++++++++--
2 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/src/wl_platform.h b/src/wl_platform.h
index ca34f66e..cd1f227f 100644
--- a/src/wl_platform.h
+++ b/src/wl_platform.h
@@ -403,6 +403,9 @@ typedef struct _GLFWwindowWayland
int scaleSize;
int compositorPreferredScale;
+ double askedCursorPosX, askedCursorPosY;
+ GLFWbool didAskForSetCursorPos;
+
struct zwp_relative_pointer_v1* relativePointer;
struct zwp_locked_pointer_v1* lockedPointer;
struct zwp_confined_pointer_v1* confinedPointer;
diff --git a/src/wl_window.c b/src/wl_window.c
index 1de26558..0df16747 100644
--- a/src/wl_window.c
+++ b/src/wl_window.c
@@ -2586,8 +2586,9 @@ void _glfwGetCursorPosWayland(_GLFWwindow* window, double* xpos, double* ypos)
void _glfwSetCursorPosWayland(_GLFWwindow* window, double x, double y)
{
- _glfwInputError(GLFW_FEATURE_UNAVAILABLE,
- "Wayland: The platform does not support setting the cursor position");
+ window->wl.didAskForSetCursorPos = true;
+ window->wl.askedCursorPosX = x;
+ window->wl.askedCursorPosY = y;
}
void _glfwSetCursorModeWayland(_GLFWwindow* window, int mode)
@@ -2819,6 +2820,15 @@ static const struct zwp_relative_pointer_v1_listener relativePointerListener =
static void lockedPointerHandleLocked(void* userData,
struct zwp_locked_pointer_v1* lockedPointer)
{
+ _GLFWwindow* window = userData;
+
+ if (window->wl.didAskForSetCursorPos)
+ {
+ window->wl.didAskForSetCursorPos = false;
+ zwp_locked_pointer_v1_set_cursor_position_hint(window->wl.lockedPointer,
+ wl_fixed_from_double(window->wl.askedCursorPosX),
+ wl_fixed_from_double(window->wl.askedCursorPosY));
+ }
}
static void lockedPointerHandleUnlocked(void* userData,
--
2.42.0

View File

@ -1,40 +0,0 @@
diff --git a/src/libdecor.c b/src/libdecor.c
index a9c1106..1aa38b3 100644
--- a/src/libdecor.c
+++ b/src/libdecor.c
@@ -1391,22 +1391,32 @@ calculate_priority(const struct libdecor_plugin_description *plugin_description)
static bool
check_symbol_conflicts(const struct libdecor_plugin_description *plugin_description)
{
+ bool ret = true;
char * const *symbol;
+ void* main_prog = dlopen(NULL, RTLD_LAZY);
+ if (!main_prog) {
+ fprintf(stderr, "Plugin \"%s\" couldn't check conflicting symbols: \"%s\".\n",
+ plugin_description->description, dlerror());
+ return false;
+ }
+
symbol = plugin_description->conflicting_symbols;
while (*symbol) {
dlerror();
- dlsym (RTLD_DEFAULT, *symbol);
+ dlsym (main_prog, *symbol);
if (!dlerror()) {
fprintf(stderr, "Plugin \"%s\" uses conflicting symbol \"%s\".\n",
plugin_description->description, *symbol);
- return false;
+ ret = false;
+ break;
}
symbol++;
}
- return true;
+ dlclose(main_prog);
+ return ret;
}
static struct plugin_loader *

@ -1 +1 @@
Subproject commit f2b0c16a2a217a1822ce5a6538ba8f755ed1dd32 Subproject commit f5d368a31d6ef046eb2955c74ec6f54f32ed5c4e

View File

@ -1,7 +0,0 @@
builds:
exclude:
- "*.x86_64-darwin.*"
include:
- "checks.x86_64-linux.*"
- "devShells.*.*"
- "packages.*.*"

View File

@ -44,9 +44,11 @@
#include "BuildConfig.h" #include "BuildConfig.h"
#include "DataMigrationTask.h" #include "DataMigrationTask.h"
#include "java/JavaInstallList.h"
#include "net/PasteUpload.h" #include "net/PasteUpload.h"
#include "pathmatcher/MultiMatcher.h" #include "pathmatcher/MultiMatcher.h"
#include "pathmatcher/SimplePrefixMatcher.h" #include "pathmatcher/SimplePrefixMatcher.h"
#include "tasks/Task.h"
#include "tools/GenericProfiler.h" #include "tools/GenericProfiler.h"
#include "ui/InstanceWindow.h" #include "ui/InstanceWindow.h"
#include "ui/MainWindow.h" #include "ui/MainWindow.h"
@ -57,8 +59,6 @@
#include "ui/pages/BasePageProvider.h" #include "ui/pages/BasePageProvider.h"
#include "ui/pages/global/APIPage.h" #include "ui/pages/global/APIPage.h"
#include "ui/pages/global/AccountListPage.h" #include "ui/pages/global/AccountListPage.h"
#include "ui/pages/global/CustomCommandsPage.h"
#include "ui/pages/global/EnvironmentVariablesPage.h"
#include "ui/pages/global/ExternalToolsPage.h" #include "ui/pages/global/ExternalToolsPage.h"
#include "ui/pages/global/JavaPage.h" #include "ui/pages/global/JavaPage.h"
#include "ui/pages/global/LanguagePage.h" #include "ui/pages/global/LanguagePage.h"
@ -66,8 +66,10 @@
#include "ui/pages/global/MinecraftPage.h" #include "ui/pages/global/MinecraftPage.h"
#include "ui/pages/global/ProxyPage.h" #include "ui/pages/global/ProxyPage.h"
#include "ui/setupwizard/AutoJavaWizardPage.h"
#include "ui/setupwizard/JavaWizardPage.h" #include "ui/setupwizard/JavaWizardPage.h"
#include "ui/setupwizard/LanguageWizardPage.h" #include "ui/setupwizard/LanguageWizardPage.h"
#include "ui/setupwizard/LoginWizardPage.h"
#include "ui/setupwizard/PasteWizardPage.h" #include "ui/setupwizard/PasteWizardPage.h"
#include "ui/setupwizard/SetupWizard.h" #include "ui/setupwizard/SetupWizard.h"
#include "ui/setupwizard/ThemeWizardPage.h" #include "ui/setupwizard/ThemeWizardPage.h"
@ -125,6 +127,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <sys.h> #include <sys.h>
#include "SysInfo.h"
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
#include <dlfcn.h> #include <dlfcn.h>
@ -151,6 +154,7 @@
#if defined Q_OS_WIN32 #if defined Q_OS_WIN32
#include <windows.h> #include <windows.h>
#include <QStyleHints>
#include "WindowsConsole.h" #include "WindowsConsole.h"
#endif #endif
@ -221,8 +225,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
setApplicationName(BuildConfig.LAUNCHER_NAME); setApplicationName(BuildConfig.LAUNCHER_NAME);
setApplicationDisplayName(QString("%1 %2").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString())); setApplicationDisplayName(QString("%1 %2").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString()));
setApplicationVersion(BuildConfig.printableVersionString() + "\n" + BuildConfig.GIT_COMMIT); setApplicationVersion(BuildConfig.printableVersionString() + "\n" + BuildConfig.GIT_COMMIT);
setDesktopFileName(BuildConfig.LAUNCHER_DESKTOPFILENAME); setDesktopFileName(BuildConfig.LAUNCHER_APPID);
startTime = QDateTime::currentDateTime(); m_startTime = QDateTime::currentDateTime();
// Don't quit on hiding the last window // Don't quit on hiding the last window
this->setQuitOnLastWindowClosed(false); this->setQuitOnLastWindowClosed(false);
@ -238,6 +242,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
{ { "s", "server" }, "Join the specified server on launch (only valid in combination with --launch)", "address" }, { { "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" }, { { "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" }, { { "a", "profile" }, "Use the account specified by its profile name (only valid in combination with --launch)", "profile" },
{ { "o", "offline" }, "Launch offline, with given player name (only valid in combination with --launch)", "offline" },
{ "alive", "Write a small '" + liveCheckFile + "' file after the launcher starts" }, { "alive", "Write a small '" + liveCheckFile + "' file after the launcher starts" },
{ { "I", "import" }, "Import instance or resource from specified local path or URL", "url" }, { { "I", "import" }, "Import instance or resource from specified local path or URL", "url" },
{ "show", "Opens the window for the specified instance (by instance ID)", "show" } }); { "show", "Opens the window for the specified instance (by instance ID)", "show" } });
@ -253,6 +258,10 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_serverToJoin = parser.value("server"); m_serverToJoin = parser.value("server");
m_worldToJoin = parser.value("world"); m_worldToJoin = parser.value("world");
m_profileToUse = parser.value("profile"); m_profileToUse = parser.value("profile");
if (parser.isSet("offline")) {
m_offline = true;
m_offlineName = parser.value("offline");
}
m_liveCheck = parser.isSet("alive"); m_liveCheck = parser.isSet("alive");
m_instanceIdToShowWindowOf = parser.value("show"); m_instanceIdToShowWindowOf = parser.value("show");
@ -267,8 +276,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
} }
// error if --launch is missing with --server or --profile // error if --launch is missing with --server or --profile
if (((!m_serverToJoin.isEmpty() || !m_worldToJoin.isEmpty()) || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty()) { if ((!m_serverToJoin.isEmpty() || !m_worldToJoin.isEmpty() || !m_profileToUse.isEmpty() || m_offline) &&
std::cerr << "--server and --profile can only be used in combination with --launch!" << std::endl; m_instanceIdToLaunch.isEmpty()) {
std::cerr << "--server, --profile and --offline can only be used in combination with --launch!" << std::endl;
m_status = Application::Failed; m_status = Application::Failed;
return; return;
} }
@ -393,6 +403,10 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
if (!m_profileToUse.isEmpty()) { if (!m_profileToUse.isEmpty()) {
launch.args["profile"] = m_profileToUse; launch.args["profile"] = m_profileToUse;
} }
if (m_offline) {
launch.args["offline_enabled"] = "true";
launch.args["offline_name"] = m_offlineName;
}
m_peerInstance->sendMessage(launch.serialize(), timeout); m_peerInstance->sendMessage(launch.serialize(), timeout);
} }
m_status = Application::Succeeded; m_status = Application::Succeeded;
@ -601,7 +615,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("IconsDir", "icons"); m_settings->registerSetting("IconsDir", "icons");
m_settings->registerSetting("DownloadsDir", QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); m_settings->registerSetting("DownloadsDir", QStandardPaths::writableLocation(QStandardPaths::DownloadLocation));
m_settings->registerSetting("DownloadsDirWatchRecursive", false); m_settings->registerSetting("DownloadsDirWatchRecursive", false);
m_settings->registerSetting("MoveModsFromDownloadsDir", false);
m_settings->registerSetting("SkinsDir", "skins"); m_settings->registerSetting("SkinsDir", "skins");
m_settings->registerSetting("JavaDir", "java");
// Editors // Editors
m_settings->registerSetting("JsonEditor", QString()); m_settings->registerSetting("JsonEditor", QString());
@ -630,7 +646,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
// Memory // Memory
m_settings->registerSetting({ "MinMemAlloc", "MinMemoryAlloc" }, 512); m_settings->registerSetting({ "MinMemAlloc", "MinMemoryAlloc" }, 512);
m_settings->registerSetting({ "MaxMemAlloc", "MaxMemoryAlloc" }, suitableMaxMem()); m_settings->registerSetting({ "MaxMemAlloc", "MaxMemoryAlloc" }, SysInfo::suitableMaxMem());
m_settings->registerSetting("PermGen", 128); m_settings->registerSetting("PermGen", 128);
// Java Settings // Java Settings
@ -644,6 +660,10 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("JvmArgs", ""); m_settings->registerSetting("JvmArgs", "");
m_settings->registerSetting("IgnoreJavaCompatibility", false); m_settings->registerSetting("IgnoreJavaCompatibility", false);
m_settings->registerSetting("IgnoreJavaWizard", 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 // Legacy settings
m_settings->registerSetting("OnlineFixes", false); m_settings->registerSetting("OnlineFixes", false);
@ -776,6 +796,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
// FTBApp instances // FTBApp instances
m_settings->registerSetting("FTBAppInstancesPath", ""); m_settings->registerSetting("FTBAppInstancesPath", "");
// Custom Technic Client ID
m_settings->registerSetting("TechnicClientID", "");
// Init page provider // Init page provider
{ {
m_globalSettingsProvider = std::make_shared<GenericPageProvider>(tr("Settings")); m_globalSettingsProvider = std::make_shared<GenericPageProvider>(tr("Settings"));
@ -783,8 +806,6 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_globalSettingsProvider->addPage<MinecraftPage>(); m_globalSettingsProvider->addPage<MinecraftPage>();
m_globalSettingsProvider->addPage<JavaPage>(); m_globalSettingsProvider->addPage<JavaPage>();
m_globalSettingsProvider->addPage<LanguagePage>(); m_globalSettingsProvider->addPage<LanguagePage>();
m_globalSettingsProvider->addPage<CustomCommandsPage>();
m_globalSettingsProvider->addPage<EnvironmentVariablesPage>();
m_globalSettingsProvider->addPage<ProxyPage>(); m_globalSettingsProvider->addPage<ProxyPage>();
m_globalSettingsProvider->addPage<ExternalToolsPage>(); m_globalSettingsProvider->addPage<ExternalToolsPage>();
m_globalSettingsProvider->addPage<AccountListPage>(); m_globalSettingsProvider->addPage<AccountListPage>();
@ -828,7 +849,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
":/icons/multimc/128x128/instances/", ":/icons/multimc/scalable/instances/" }; ":/icons/multimc/128x128/instances/", ":/icons/multimc/scalable/instances/" };
m_icons.reset(new IconList(instFolders, setting->get().toString())); m_icons.reset(new IconList(instFolders, setting->get().toString()));
connect(setting.get(), &Setting::SettingChanged, connect(setting.get(), &Setting::SettingChanged,
[&](const Setting&, QVariant value) { m_icons->directoryChanged(value.toString()); }); [this](const Setting&, QVariant value) { m_icons->directoryChanged(value.toString()); });
qDebug() << "<> Instance icons initialized."; qDebug() << "<> Instance icons initialized.";
} }
@ -878,6 +899,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_metacache->addBase("ModrinthModpacks", QDir("cache/ModrinthModpacks").absolutePath()); m_metacache->addBase("ModrinthModpacks", QDir("cache/ModrinthModpacks").absolutePath());
m_metacache->addBase("translations", QDir("translations").absolutePath()); m_metacache->addBase("translations", QDir("translations").absolutePath());
m_metacache->addBase("meta", QDir("meta").absolutePath()); m_metacache->addBase("meta", QDir("meta").absolutePath());
m_metacache->addBase("java", QDir("cache/java").absolutePath());
m_metacache->Load(); m_metacache->Load();
qDebug() << "<> Cache initialized."; qDebug() << "<> Cache initialized.";
} }
@ -1017,7 +1039,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
} }
// notify user if /tmp is mounted with `noexec` (#1693) // notify user if /tmp is mounted with `noexec` (#1693)
{ QString jvmArgs = m_settings->get("JvmArgs").toString();
if (jvmArgs.indexOf("java.io.tmpdir") == -1) { /* java.io.tmpdir is a valid workaround, so don't annoy */
bool is_tmp_noexec = false; bool is_tmp_noexec = false;
#if defined(Q_OS_LINUX) #if defined(Q_OS_LINUX)
@ -1037,7 +1060,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
if (is_tmp_noexec) { if (is_tmp_noexec) {
auto infoMsg = auto infoMsg =
tr("Your /tmp directory is currently mounted with the 'noexec' flag enabled.\n" tr("Your /tmp directory is currently mounted with the 'noexec' flag enabled.\n"
"Some versions of Minecraft may not launch.\n"); "Some versions of Minecraft may not launch.\n"
"\n"
"You may solve this issue by remounting /tmp as 'exec' or setting "
"the java.io.tmpdir JVM argument to a writeable directory in a "
"filesystem where the 'exec' flag is set (e.g., /home/user/.local/tmp)\n");
auto msgBox = new QMessageBox(QMessageBox::Information, tr("Incompatible system configuration"), infoMsg, QMessageBox::Ok); auto msgBox = new QMessageBox(QMessageBox::Information, tr("Incompatible system configuration"), infoMsg, QMessageBox::Ok);
msgBox->setDefaultButton(QMessageBox::Ok); msgBox->setDefaultButton(QMessageBox::Ok);
msgBox->setAttribute(Qt::WA_DeleteOnClose); msgBox->setAttribute(Qt::WA_DeleteOnClose);
@ -1057,8 +1084,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
bool Application::createSetupWizard() bool Application::createSetupWizard()
{ {
bool javaRequired = [&]() { bool javaRequired = [this]() {
bool ignoreJavaWizard = m_settings->get("IgnoreJavaWizard").toBool(); if (BuildConfig.JAVA_DOWNLOADER_ENABLED && settings()->get("AutomaticJavaDownload").toBool()) {
return false;
}
bool ignoreJavaWizard = settings()->get("IgnoreJavaWizard").toBool();
if (ignoreJavaWizard) { if (ignoreJavaWizard) {
return false; return false;
} }
@ -1070,24 +1100,31 @@ bool Application::createSetupWizard()
} }
QString currentJavaPath = settings()->get("JavaPath").toString(); QString currentJavaPath = settings()->get("JavaPath").toString();
QString actualPath = FS::ResolveExecutable(currentJavaPath); QString actualPath = FS::ResolveExecutable(currentJavaPath);
if (actualPath.isNull()) { return actualPath.isNull();
return true;
}
return false;
}(); }();
bool askjava = BuildConfig.JAVA_DOWNLOADER_ENABLED && !javaRequired && !settings()->get("AutomaticJavaDownload").toBool() &&
!settings()->get("AutomaticJavaSwitch").toBool() && !settings()->get("UserAskedAboutAutomaticJavaDownload").toBool();
bool languageRequired = settings()->get("Language").toString().isEmpty(); bool languageRequired = settings()->get("Language").toString().isEmpty();
bool pasteInterventionRequired = settings()->get("PastebinURL") != ""; bool pasteInterventionRequired = settings()->get("PastebinURL") != "";
bool validWidgets = m_themeManager->isValidApplicationTheme(settings()->get("ApplicationTheme").toString()); bool validWidgets = m_themeManager->isValidApplicationTheme(settings()->get("ApplicationTheme").toString());
bool validIcons = m_themeManager->isValidIconTheme(settings()->get("IconTheme").toString()); bool validIcons = m_themeManager->isValidIconTheme(settings()->get("IconTheme").toString());
bool login = !m_accounts->anyAccountIsValid() && capabilities() & Application::SupportsMSA;
bool themeInterventionRequired = !validWidgets || !validIcons; bool themeInterventionRequired = !validWidgets || !validIcons;
bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired || themeInterventionRequired; bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired || themeInterventionRequired || askjava || login;
if (wizardRequired) { if (wizardRequired) {
// set default theme after going into theme wizard // set default theme after going into theme wizard
if (!validIcons) if (!validIcons)
settings()->set("IconTheme", QString("pe_colored")); settings()->set("IconTheme", QString("pe_colored"));
if (!validWidgets) if (!validWidgets) {
settings()->set("ApplicationTheme", QString("system")); #if defined(Q_OS_WIN32) && QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
const QString style =
QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark ? QStringLiteral("dark") : QStringLiteral("bright");
#else
const QString style = QStringLiteral("system");
#endif
settings()->set("ApplicationTheme", style);
}
m_themeManager->applyCurrentlySelectedTheme(true); m_themeManager->applyCurrentlySelectedTheme(true);
@ -1098,6 +1135,8 @@ bool Application::createSetupWizard()
if (javaRequired) { if (javaRequired) {
m_setupWizard->addPage(new JavaWizardPage(m_setupWizard)); m_setupWizard->addPage(new JavaWizardPage(m_setupWizard));
} else if (askjava) {
m_setupWizard->addPage(new AutoJavaWizardPage(m_setupWizard));
} }
if (pasteInterventionRequired) { if (pasteInterventionRequired) {
@ -1108,11 +1147,14 @@ bool Application::createSetupWizard()
m_setupWizard->addPage(new ThemeWizardPage(m_setupWizard)); m_setupWizard->addPage(new ThemeWizardPage(m_setupWizard));
} }
if (login) {
m_setupWizard->addPage(new LoginWizardPage(m_setupWizard));
}
connect(m_setupWizard, &QDialog::finished, this, &Application::setupWizardFinished); connect(m_setupWizard, &QDialog::finished, this, &Application::setupWizardFinished);
m_setupWizard->show(); m_setupWizard->show();
return true;
} }
return false;
return wizardRequired || login;
} }
bool Application::updaterEnabled() bool Application::updaterEnabled()
@ -1149,6 +1191,9 @@ bool Application::event(QEvent* event)
#endif #endif
if (event->type() == QEvent::FileOpen) { if (event->type() == QEvent::FileOpen) {
if (!m_mainWindow) {
showMainWindow(false);
}
auto ev = static_cast<QFileOpenEvent*>(event); auto ev = static_cast<QFileOpenEvent*>(event);
m_mainWindow->processURLs({ ev->url() }); m_mainWindow->processURLs({ ev->url() });
} }
@ -1189,7 +1234,7 @@ void Application::performMainStartupAction()
qDebug() << " Launching with account" << m_profileToUse; qDebug() << " Launching with account" << m_profileToUse;
} }
launch(inst, true, false, targetToJoin, accountToUse); launch(inst, !m_offline, false, targetToJoin, accountToUse, m_offlineName);
return; return;
} }
} }
@ -1257,16 +1302,23 @@ Application::~Application()
void Application::messageReceived(const QByteArray& message) void Application::messageReceived(const QByteArray& message)
{ {
if (status() != Initialized) {
qDebug() << "Received message" << message << "while still initializing. It will be ignored.";
return;
}
ApplicationMessage received; ApplicationMessage received;
received.parse(message); received.parse(message);
auto& command = received.command; 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") { if (command == "activate") {
showMainWindow(); showMainWindow();
} else if (command == "import") { } else if (command == "import") {
@ -1275,12 +1327,17 @@ void Application::messageReceived(const QByteArray& message)
qWarning() << "Received" << command << "message without a zip path/URL."; qWarning() << "Received" << command << "message without a zip path/URL.";
return; return;
} }
if (!m_mainWindow) {
showMainWindow(false);
}
m_mainWindow->processURLs({ normalizeImportUrl(url) }); m_mainWindow->processURLs({ normalizeImportUrl(url) });
} else if (command == "launch") { } else if (command == "launch") {
QString id = received.args["id"]; QString id = received.args["id"];
QString server = received.args["server"]; QString server = received.args["server"];
QString world = received.args["world"]; QString world = received.args["world"];
QString profile = received.args["profile"]; QString profile = received.args["profile"];
bool offline = received.args["offline_enabled"] == "true";
QString offlineName = received.args["offline_name"];
InstancePtr instance; InstancePtr instance;
if (!id.isEmpty()) { if (!id.isEmpty()) {
@ -1310,7 +1367,7 @@ void Application::messageReceived(const QByteArray& message)
} }
} }
launch(instance, true, false, serverObject, accountObject); launch(instance, !offline, false, serverObject, accountObject, offlineName);
} else { } else {
qWarning() << "Received invalid message" << message; qWarning() << "Received invalid message" << message;
} }
@ -1348,11 +1405,17 @@ bool Application::openJsonEditor(const QString& filename)
} }
} }
bool Application::launch(InstancePtr instance, bool online, bool demo, MinecraftTarget::Ptr targetToJoin, MinecraftAccountPtr accountToUse) bool Application::launch(InstancePtr instance,
bool online,
bool demo,
MinecraftTarget::Ptr targetToJoin,
MinecraftAccountPtr accountToUse,
const QString& offlineName)
{ {
if (m_updateRunning) { if (m_updateRunning) {
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed."; qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
} else if (instance->canLaunch()) { } else if (instance->canLaunch()) {
QMutexLocker locker(&m_instanceExtrasMutex);
auto& extras = m_instanceExtras[instance->id()]; auto& extras = m_instanceExtras[instance->id()];
auto window = extras.window; auto window = extras.window;
if (window) { if (window) {
@ -1368,6 +1431,7 @@ bool Application::launch(InstancePtr instance, bool online, bool demo, Minecraft
controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get()); controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get());
controller->setTargetToJoin(targetToJoin); controller->setTargetToJoin(targetToJoin);
controller->setAccountToUse(accountToUse); controller->setAccountToUse(accountToUse);
controller->setOfflineName(offlineName);
if (window) { if (window) {
controller->setParentWidget(window); controller->setParentWidget(window);
} else if (m_mainWindow) { } else if (m_mainWindow) {
@ -1377,7 +1441,7 @@ bool Application::launch(InstancePtr instance, bool online, bool demo, Minecraft
connect(controller.get(), &LaunchController::failed, this, &Application::controllerFailed); connect(controller.get(), &LaunchController::failed, this, &Application::controllerFailed);
connect(controller.get(), &LaunchController::aborted, this, [this] { controllerFailed(tr("Aborted")); }); connect(controller.get(), &LaunchController::aborted, this, [this] { controllerFailed(tr("Aborted")); });
addRunningInstance(); addRunningInstance();
controller->start(); QMetaObject::invokeMethod(controller.get(), &Task::start, Qt::QueuedConnection);
return true; return true;
} else if (instance->isRunning()) { } else if (instance->isRunning()) {
showInstanceWindow(instance, "console"); showInstanceWindow(instance, "console");
@ -1395,9 +1459,11 @@ bool Application::kill(InstancePtr instance)
qWarning() << "Attempted to kill instance" << instance->id() << ", which isn't running."; qWarning() << "Attempted to kill instance" << instance->id() << ", which isn't running.";
return false; return false;
} }
QMutexLocker locker(&m_instanceExtrasMutex);
auto& extras = m_instanceExtras[instance->id()]; auto& extras = m_instanceExtras[instance->id()];
// NOTE: copy of the shared pointer keeps it alive // NOTE: copy of the shared pointer keeps it alive
auto controller = extras.controller; auto controller = extras.controller;
locker.unlock();
if (controller) { if (controller) {
return controller->abort(); return controller->abort();
} }
@ -1451,12 +1517,14 @@ void Application::controllerSucceeded()
if (!controller) if (!controller)
return; return;
auto id = controller->id(); auto id = controller->id();
QMutexLocker locker(&m_instanceExtrasMutex);
auto& extras = m_instanceExtras[id]; auto& extras = m_instanceExtras[id];
// on success, do... // on success, do...
if (controller->instance()->settings()->get("AutoCloseConsole").toBool()) { if (controller->instance()->settings()->get("AutoCloseConsole").toBool()) {
if (extras.window) { if (extras.window) {
extras.window->close(); QMetaObject::invokeMethod(extras.window, &QWidget::close, Qt::QueuedConnection);
} }
} }
extras.controller.reset(); extras.controller.reset();
@ -1476,6 +1544,7 @@ void Application::controllerFailed(const QString& error)
if (!controller) if (!controller)
return; return;
auto id = controller->id(); auto id = controller->id();
QMutexLocker locker(&m_instanceExtrasMutex);
auto& extras = m_instanceExtras[id]; auto& extras = m_instanceExtras[id];
// on failure, do... nothing // on failure, do... nothing
@ -1533,6 +1602,7 @@ InstanceWindow* Application::showInstanceWindow(InstancePtr instance, QString pa
if (!instance) if (!instance)
return nullptr; return nullptr;
auto id = instance->id(); auto id = instance->id();
QMutexLocker locker(&m_instanceExtrasMutex);
auto& extras = m_instanceExtras[id]; auto& extras = m_instanceExtras[id];
auto& window = extras.window; auto& window = extras.window;
@ -1570,6 +1640,7 @@ void Application::on_windowClose()
m_openWindows--; m_openWindows--;
auto instWindow = qobject_cast<InstanceWindow*>(QObject::sender()); auto instWindow = qobject_cast<InstanceWindow*>(QObject::sender());
if (instWindow) { if (instWindow) {
QMutexLocker locker(&m_instanceExtrasMutex);
auto& extras = m_instanceExtras[instWindow->instanceId()]; auto& extras = m_instanceExtras[instWindow->instanceId()];
extras.window = nullptr; extras.window = nullptr;
if (extras.controller) { if (extras.controller) {
@ -1745,20 +1816,6 @@ QString Application::getUserAgentUncached()
return BuildConfig.USER_AGENT_UNCACHED; 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, bool Application::handleDataMigration(const QString& currentData,
const QString& oldData, const QString& oldData,
const QString& name, const QString& name,
@ -1831,7 +1888,7 @@ bool Application::handleDataMigration(const QString& currentData,
matcher->add(std::make_shared<SimplePrefixMatcher>("themes/")); matcher->add(std::make_shared<SimplePrefixMatcher>("themes/"));
ProgressDialog diag; ProgressDialog diag;
DataMigrationTask task(nullptr, oldData, currentData, matcher); DataMigrationTask task(oldData, currentData, matcher);
if (diag.execWithTask(&task)) { if (diag.execWithTask(&task)) {
qDebug() << "<> Migration succeeded"; qDebug() << "<> Migration succeeded";
setDoNotMigrate(); setDoNotMigrate();
@ -1865,3 +1922,36 @@ QUrl Application::normalizeImportUrl(QString const& url)
return QUrl::fromUserInput(url); return QUrl::fromUserInput(url);
} }
} }
const QString Application::javaPath()
{
return m_settings->get("JavaDir").toString();
}
void Application::addQSavePath(QString path)
{
QMutexLocker locker(&m_qsaveResourcesMutex);
m_qsaveResources[path] = m_qsaveResources.value(path, 0) + 1;
}
void Application::removeQSavePath(QString path)
{
QMutexLocker locker(&m_qsaveResourcesMutex);
auto count = m_qsaveResources.value(path, 0) - 1;
if (count <= 0) {
m_qsaveResources.remove(path);
} else {
m_qsaveResources[path] = count;
}
}
bool Application::checkQSavePath(QString path)
{
QMutexLocker locker(&m_qsaveResourcesMutex);
for (auto partialPath : m_qsaveResources.keys()) {
if (path.startsWith(partialPath) && m_qsaveResources.value(partialPath, 0) > 0) {
return true;
}
}
return false;
}

View File

@ -42,6 +42,7 @@
#include <QDebug> #include <QDebug>
#include <QFlag> #include <QFlag>
#include <QIcon> #include <QIcon>
#include <QMutex>
#include <QUrl> #include <QUrl>
#include <memory> #include <memory>
@ -81,6 +82,12 @@ class Index;
#endif #endif
#define APPLICATION (static_cast<Application*>(QCoreApplication::instance())) #define APPLICATION (static_cast<Application*>(QCoreApplication::instance()))
// Used for checking if is a test
#if defined(APPLICATION_DYN)
#undef APPLICATION_DYN
#endif
#define APPLICATION_DYN (dynamic_cast<Application*>(QCoreApplication::instance()))
class Application : public QApplication { class Application : public QApplication {
// friends for the purpose of limiting access to deprecated stuff // friends for the purpose of limiting access to deprecated stuff
Q_OBJECT Q_OBJECT
@ -105,7 +112,7 @@ class Application : public QApplication {
std::shared_ptr<SettingsObject> settings() const { return m_settings; } std::shared_ptr<SettingsObject> settings() const { return m_settings; }
qint64 timeSinceStart() const { return startTime.msecsTo(QDateTime::currentDateTime()); } qint64 timeSinceStart() const { return m_startTime.msecsTo(QDateTime::currentDateTime()); }
QIcon getThemedIcon(const QString& name); QIcon getThemedIcon(const QString& name);
@ -161,6 +168,9 @@ class Application : public QApplication {
/// the data path the application is using /// the data path the application is using
const QString& dataRoot() { return m_dataPath; } const QString& dataRoot() { return m_dataPath; }
/// the java installed path the application is using
const QString javaPath();
bool isPortable() { return m_portable; } bool isPortable() { return m_portable; }
const Capabilities capabilities() { return m_capabilities; } const Capabilities capabilities() { return m_capabilities; }
@ -179,8 +189,6 @@ class Application : public QApplication {
void ShowGlobalSettings(class QWidget* parent, QString open_page = QString()); void ShowGlobalSettings(class QWidget* parent, QString open_page = QString());
int suitableMaxMem();
bool updaterEnabled(); bool updaterEnabled();
QString updaterBinaryName(); QString updaterBinaryName();
@ -203,7 +211,8 @@ class Application : public QApplication {
bool online = true, bool online = true,
bool demo = false, bool demo = false,
MinecraftTarget::Ptr targetToJoin = nullptr, MinecraftTarget::Ptr targetToJoin = nullptr,
MinecraftAccountPtr accountToUse = nullptr); MinecraftAccountPtr accountToUse = nullptr,
const QString& offlineName = QString());
bool kill(InstancePtr instance); bool kill(InstancePtr instance);
void closeCurrentWindow(); void closeCurrentWindow();
@ -228,7 +237,7 @@ class Application : public QApplication {
bool shouldExitNow() const; bool shouldExitNow() const;
private: private:
QDateTime startTime; QDateTime m_startTime;
shared_qobject_ptr<QNetworkAccessManager> m_network; shared_qobject_ptr<QNetworkAccessManager> m_network;
@ -271,6 +280,7 @@ class Application : public QApplication {
shared_qobject_ptr<LaunchController> controller; shared_qobject_ptr<LaunchController> controller;
}; };
std::map<QString, InstanceXtras> m_instanceExtras; std::map<QString, InstanceXtras> m_instanceExtras;
mutable QMutex m_instanceExtrasMutex;
// main state variables // main state variables
size_t m_openWindows = 0; size_t m_openWindows = 0;
@ -292,8 +302,19 @@ class Application : public QApplication {
QString m_serverToJoin; QString m_serverToJoin;
QString m_worldToJoin; QString m_worldToJoin;
QString m_profileToUse; QString m_profileToUse;
bool m_offline = false;
QString m_offlineName;
bool m_liveCheck = false; bool m_liveCheck = false;
QList<QUrl> m_urlsToImport; QList<QUrl> m_urlsToImport;
QString m_instanceIdToShowWindowOf; QString m_instanceIdToShowWindowOf;
std::unique_ptr<QFile> logFile; std::unique_ptr<QFile> logFile;
public:
void addQSavePath(QString);
void removeQSavePath(QString);
bool checkQSavePath(QString);
private:
QHash<QString, int> m_qsaveResources;
mutable QMutex m_qsaveResourcesMutex;
}; };

View File

@ -411,3 +411,8 @@ void BaseInstance::updateRuntimeContext()
{ {
// NOOP // NOOP
} }
bool BaseInstance::isLegacy()
{
return traits().contains("legacyLaunch") || traits().contains("alphaLaunch");
}

View File

@ -181,7 +181,7 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
virtual void loadSpecificSettings() = 0; virtual void loadSpecificSettings() = 0;
/// returns a valid update task /// returns a valid update task
virtual Task::Ptr createUpdateTask(Net::Mode mode) = 0; virtual QList<Task::Ptr> createUpdateTask() = 0;
/// returns a valid launcher (task container) /// returns a valid launcher (task container)
virtual shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account, MinecraftTarget::Ptr targetToJoin) = 0; virtual shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account, MinecraftTarget::Ptr targetToJoin) = 0;
@ -215,7 +215,7 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
virtual QString typeName() const = 0; virtual QString typeName() const = 0;
void updateRuntimeContext(); virtual void updateRuntimeContext();
RuntimeContext runtimeContext() const { return m_runtimeContext; } RuntimeContext runtimeContext() const { return m_runtimeContext; }
bool hasVersionBroken() const { return m_hasBrokenVersion; } bool hasVersionBroken() const { return m_hasBrokenVersion; }
@ -269,6 +269,8 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
bool removeLinkedInstanceId(const QString& id); bool removeLinkedInstanceId(const QString& id);
bool isLinkedToInstanceId(const QString& id) const; bool isLinkedToInstanceId(const QString& id) const;
bool isLegacy();
protected: protected:
void changeStatus(Status newStatus); void changeStatus(Status newStatus);

View File

@ -78,6 +78,14 @@ QVariant BaseVersionList::data(const QModelIndex& index, int role) const
case TypeRole: case TypeRole:
return version->typeString(); return version->typeString();
case JavaMajorRole: {
auto major = version->name();
if (major.startsWith("java")) {
major = "Java " + major.mid(4);
}
return major;
}
default: default:
return QVariant(); return QVariant();
} }
@ -110,6 +118,8 @@ QHash<int, QByteArray> BaseVersionList::roleNames() const
roles.insert(TypeRole, "type"); roles.insert(TypeRole, "type");
roles.insert(BranchRole, "branch"); roles.insert(BranchRole, "branch");
roles.insert(PathRole, "path"); roles.insert(PathRole, "path");
roles.insert(ArchitectureRole, "architecture"); roles.insert(JavaNameRole, "javaName");
roles.insert(CPUArchitectureRole, "architecture");
roles.insert(JavaMajorRole, "javaMajor");
return roles; return roles;
} }

View File

@ -48,7 +48,9 @@ class BaseVersionList : public QAbstractListModel {
TypeRole, TypeRole,
BranchRole, BranchRole,
PathRole, PathRole,
ArchitectureRole, JavaNameRole,
JavaMajorRole,
CPUArchitectureRole,
SortRole SortRole
}; };
using RoleList = QList<int>; using RoleList = QList<int>;

View File

@ -24,10 +24,13 @@ set(CORE_SOURCES
NullInstance.h NullInstance.h
MMCZip.h MMCZip.h
MMCZip.cpp MMCZip.cpp
Untar.h
Untar.cpp
StringUtils.h StringUtils.h
StringUtils.cpp StringUtils.cpp
QVariantUtils.h QVariantUtils.h
RuntimeContext.h RuntimeContext.h
PSaveFile.h
# Basic instance manipulation tasks (derived from InstanceTask) # Basic instance manipulation tasks (derived from InstanceTask)
InstanceCreationTask.h InstanceCreationTask.h
@ -158,8 +161,6 @@ set(LAUNCH_SOURCES
launch/steps/PreLaunchCommand.h launch/steps/PreLaunchCommand.h
launch/steps/TextPrint.cpp launch/steps/TextPrint.cpp
launch/steps/TextPrint.h launch/steps/TextPrint.h
launch/steps/Update.cpp
launch/steps/Update.h
launch/steps/QuitAfterGameStop.cpp launch/steps/QuitAfterGameStop.cpp
launch/steps/QuitAfterGameStop.h launch/steps/QuitAfterGameStop.h
launch/steps/PrintServers.cpp launch/steps/PrintServers.cpp
@ -170,6 +171,8 @@ set(LAUNCH_SOURCES
launch/LaunchTask.h launch/LaunchTask.h
launch/LogModel.cpp launch/LogModel.cpp
launch/LogModel.h launch/LogModel.h
launch/TaskStepWrapper.cpp
launch/TaskStepWrapper.h
) )
# Old update system # Old update system
@ -205,6 +208,11 @@ set(ICONS_SOURCES
# Support for Minecraft instances and launch # Support for Minecraft instances and launch
set(MINECRAFT_SOURCES set(MINECRAFT_SOURCES
# Logging
minecraft/Logging.h
minecraft/Logging.cpp
# Minecraft support # Minecraft support
minecraft/auth/AccountData.cpp minecraft/auth/AccountData.cpp
minecraft/auth/AccountData.h minecraft/auth/AccountData.h
@ -272,6 +280,8 @@ set(MINECRAFT_SOURCES
minecraft/launch/ScanModFolders.h minecraft/launch/ScanModFolders.h
minecraft/launch/VerifyJavaInstall.cpp minecraft/launch/VerifyJavaInstall.cpp
minecraft/launch/VerifyJavaInstall.h minecraft/launch/VerifyJavaInstall.h
minecraft/launch/AutoInstallJava.cpp
minecraft/launch/AutoInstallJava.h
minecraft/GradleSpecifier.h minecraft/GradleSpecifier.h
minecraft/MinecraftInstance.cpp minecraft/MinecraftInstance.cpp
@ -286,8 +296,6 @@ set(MINECRAFT_SOURCES
minecraft/ComponentUpdateTask.h minecraft/ComponentUpdateTask.h
minecraft/MinecraftLoadAndCheck.h minecraft/MinecraftLoadAndCheck.h
minecraft/MinecraftLoadAndCheck.cpp minecraft/MinecraftLoadAndCheck.cpp
minecraft/MinecraftUpdate.h
minecraft/MinecraftUpdate.cpp
minecraft/MojangVersionFormat.cpp minecraft/MojangVersionFormat.cpp
minecraft/MojangVersionFormat.h minecraft/MojangVersionFormat.h
minecraft/Rule.cpp minecraft/Rule.cpp
@ -339,17 +347,14 @@ set(MINECRAFT_SOURCES
minecraft/mod/TexturePackFolderModel.h minecraft/mod/TexturePackFolderModel.h
minecraft/mod/TexturePackFolderModel.cpp minecraft/mod/TexturePackFolderModel.cpp
minecraft/mod/ShaderPackFolderModel.h minecraft/mod/ShaderPackFolderModel.h
minecraft/mod/tasks/BasicFolderLoadTask.h minecraft/mod/tasks/ResourceFolderLoadTask.h
minecraft/mod/tasks/ModFolderLoadTask.h minecraft/mod/tasks/ResourceFolderLoadTask.cpp
minecraft/mod/tasks/ModFolderLoadTask.cpp
minecraft/mod/tasks/LocalModParseTask.h minecraft/mod/tasks/LocalModParseTask.h
minecraft/mod/tasks/LocalModParseTask.cpp minecraft/mod/tasks/LocalModParseTask.cpp
minecraft/mod/tasks/LocalModUpdateTask.h minecraft/mod/tasks/LocalResourceUpdateTask.h
minecraft/mod/tasks/LocalModUpdateTask.cpp minecraft/mod/tasks/LocalResourceUpdateTask.cpp
minecraft/mod/tasks/LocalDataPackParseTask.h minecraft/mod/tasks/LocalDataPackParseTask.h
minecraft/mod/tasks/LocalDataPackParseTask.cpp minecraft/mod/tasks/LocalDataPackParseTask.cpp
minecraft/mod/tasks/LocalResourcePackParseTask.h
minecraft/mod/tasks/LocalResourcePackParseTask.cpp
minecraft/mod/tasks/LocalTexturePackParseTask.h minecraft/mod/tasks/LocalTexturePackParseTask.h
minecraft/mod/tasks/LocalTexturePackParseTask.cpp minecraft/mod/tasks/LocalTexturePackParseTask.cpp
minecraft/mod/tasks/LocalShaderPackParseTask.h minecraft/mod/tasks/LocalShaderPackParseTask.h
@ -419,8 +424,6 @@ set(SETTINGS_SOURCES
set(JAVA_SOURCES set(JAVA_SOURCES
java/JavaChecker.h java/JavaChecker.h
java/JavaChecker.cpp java/JavaChecker.cpp
java/JavaCheckerJob.h
java/JavaCheckerJob.cpp
java/JavaInstall.h java/JavaInstall.h
java/JavaInstall.cpp java/JavaInstall.cpp
java/JavaInstallList.h java/JavaInstallList.h
@ -429,6 +432,20 @@ set(JAVA_SOURCES
java/JavaUtils.cpp java/JavaUtils.cpp
java/JavaVersion.h java/JavaVersion.h
java/JavaVersion.cpp 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 set(TRANSLATIONS_SOURCES
@ -591,7 +608,7 @@ set(PRISMUPDATER_SOURCES
updater/prismupdater/UpdaterDialogs.cpp updater/prismupdater/UpdaterDialogs.cpp
updater/prismupdater/GitHubRelease.h updater/prismupdater/GitHubRelease.h
updater/prismupdater/GitHubRelease.cpp updater/prismupdater/GitHubRelease.cpp
Json.h Json.h
Json.cpp Json.cpp
FileSystem.h FileSystem.h
@ -608,7 +625,7 @@ set(PRISMUPDATER_SOURCES
# Zip # Zip
MMCZip.h MMCZip.h
MMCZip.cpp MMCZip.cpp
# Time # Time
MMCTime.h MMCTime.h
MMCTime.cpp MMCTime.cpp
@ -651,6 +668,22 @@ ecm_qt_declare_logging_category(CORE_SOURCES
EXPORT "${Launcher_Name}" 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( ecm_qt_export_logging_category(
IDENTIFIER taskLogC IDENTIFIER taskLogC
CATEGORY_NAME "launcher.task" CATEGORY_NAME "launcher.task"
@ -663,7 +696,7 @@ ecm_qt_export_logging_category(
IDENTIFIER taskNetLogC IDENTIFIER taskNetLogC
CATEGORY_NAME "launcher.task.net" CATEGORY_NAME "launcher.task.net"
DEFAULT_SEVERITY Debug DEFAULT_SEVERITY Debug
DESCRIPTION "task network action" DESCRIPTION "Task network action"
EXPORT "${Launcher_Name}" EXPORT "${Launcher_Name}"
) )
@ -671,14 +704,14 @@ ecm_qt_export_logging_category(
IDENTIFIER taskDownloadLogC IDENTIFIER taskDownloadLogC
CATEGORY_NAME "launcher.task.net.download" CATEGORY_NAME "launcher.task.net.download"
DEFAULT_SEVERITY Debug DEFAULT_SEVERITY Debug
DESCRIPTION "task network download actions" DESCRIPTION "Task network download actions"
EXPORT "${Launcher_Name}" EXPORT "${Launcher_Name}"
) )
ecm_qt_export_logging_category( ecm_qt_export_logging_category(
IDENTIFIER taskUploadLogC IDENTIFIER taskUploadLogC
CATEGORY_NAME "launcher.task.net.upload" CATEGORY_NAME "launcher.task.net.upload"
DEFAULT_SEVERITY Debug DEFAULT_SEVERITY Debug
DESCRIPTION "task network upload actions" DESCRIPTION "Task network upload actions"
EXPORT "${Launcher_Name}" EXPORT "${Launcher_Name}"
) )
@ -748,6 +781,8 @@ SET(LAUNCHER_SOURCES
DataMigrationTask.cpp DataMigrationTask.cpp
ApplicationMessage.h ApplicationMessage.h
ApplicationMessage.cpp ApplicationMessage.cpp
SysInfo.h
SysInfo.cpp
# GUI - general utilities # GUI - general utilities
DesktopServices.h DesktopServices.h
@ -775,7 +810,8 @@ SET(LAUNCHER_SOURCES
resources/flat/flat.qrc resources/flat/flat.qrc
resources/flat_white/flat_white.qrc resources/flat_white/flat_white.qrc
resources/documents/documents.qrc resources/documents/documents.qrc
../${Launcher_Branding_LogoQRC} resources/shaders/shaders.qrc
"${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_LogoQRC}"
# Icons # Icons
icons/MMCIcon.h icons/MMCIcon.h
@ -786,8 +822,6 @@ SET(LAUNCHER_SOURCES
# GUI - windows # GUI - windows
ui/GuiUtil.h ui/GuiUtil.h
ui/GuiUtil.cpp ui/GuiUtil.cpp
ui/ColorCache.h
ui/ColorCache.cpp
ui/MainWindow.h ui/MainWindow.h
ui/MainWindow.cpp ui/MainWindow.cpp
ui/InstanceWindow.h ui/InstanceWindow.h
@ -811,6 +845,10 @@ SET(LAUNCHER_SOURCES
ui/setupwizard/PasteWizardPage.h ui/setupwizard/PasteWizardPage.h
ui/setupwizard/ThemeWizardPage.cpp ui/setupwizard/ThemeWizardPage.cpp
ui/setupwizard/ThemeWizardPage.h ui/setupwizard/ThemeWizardPage.h
ui/setupwizard/AutoJavaWizardPage.cpp
ui/setupwizard/AutoJavaWizardPage.h
ui/setupwizard/LoginWizardPage.cpp
ui/setupwizard/LoginWizardPage.h
# GUI - themes # GUI - themes
ui/themes/FusionTheme.cpp ui/themes/FusionTheme.cpp
@ -873,7 +911,6 @@ SET(LAUNCHER_SOURCES
ui/pages/instance/NotesPage.h ui/pages/instance/NotesPage.h
ui/pages/instance/LogPage.cpp ui/pages/instance/LogPage.cpp
ui/pages/instance/LogPage.h ui/pages/instance/LogPage.h
ui/pages/instance/InstanceSettingsPage.cpp
ui/pages/instance/InstanceSettingsPage.h ui/pages/instance/InstanceSettingsPage.h
ui/pages/instance/ScreenshotsPage.cpp ui/pages/instance/ScreenshotsPage.cpp
ui/pages/instance/ScreenshotsPage.h ui/pages/instance/ScreenshotsPage.h
@ -883,21 +920,22 @@ SET(LAUNCHER_SOURCES
ui/pages/instance/ServersPage.h ui/pages/instance/ServersPage.h
ui/pages/instance/WorldListPage.cpp ui/pages/instance/WorldListPage.cpp
ui/pages/instance/WorldListPage.h ui/pages/instance/WorldListPage.h
ui/pages/instance/McClient.cpp
ui/pages/instance/McClient.h
ui/pages/instance/McResolver.cpp
ui/pages/instance/McResolver.h
ui/pages/instance/ServerPingTask.cpp
ui/pages/instance/ServerPingTask.h
# GUI - global settings pages # GUI - global settings pages
ui/pages/global/AccountListPage.cpp ui/pages/global/AccountListPage.cpp
ui/pages/global/AccountListPage.h ui/pages/global/AccountListPage.h
ui/pages/global/CustomCommandsPage.cpp
ui/pages/global/CustomCommandsPage.h
ui/pages/global/EnvironmentVariablesPage.cpp
ui/pages/global/EnvironmentVariablesPage.h
ui/pages/global/ExternalToolsPage.cpp ui/pages/global/ExternalToolsPage.cpp
ui/pages/global/ExternalToolsPage.h ui/pages/global/ExternalToolsPage.h
ui/pages/global/JavaPage.cpp ui/pages/global/JavaPage.cpp
ui/pages/global/JavaPage.h ui/pages/global/JavaPage.h
ui/pages/global/LanguagePage.cpp ui/pages/global/LanguagePage.cpp
ui/pages/global/LanguagePage.h ui/pages/global/LanguagePage.h
ui/pages/global/MinecraftPage.cpp
ui/pages/global/MinecraftPage.h ui/pages/global/MinecraftPage.h
ui/pages/global/LauncherPage.cpp ui/pages/global/LauncherPage.cpp
ui/pages/global/LauncherPage.h ui/pages/global/LauncherPage.h
@ -933,6 +971,8 @@ SET(LAUNCHER_SOURCES
ui/pages/modplatform/DataPackPage.cpp ui/pages/modplatform/DataPackPage.cpp
ui/pages/modplatform/DataPackModel.cpp ui/pages/modplatform/DataPackModel.cpp
ui/pages/modplatform/ModpackProviderBasePage.h
ui/pages/modplatform/atlauncher/AtlFilterModel.cpp ui/pages/modplatform/atlauncher/AtlFilterModel.cpp
ui/pages/modplatform/atlauncher/AtlFilterModel.h ui/pages/modplatform/atlauncher/AtlFilterModel.h
ui/pages/modplatform/atlauncher/AtlListModel.cpp ui/pages/modplatform/atlauncher/AtlListModel.cpp
@ -995,8 +1035,6 @@ SET(LAUNCHER_SOURCES
ui/dialogs/CopyInstanceDialog.h ui/dialogs/CopyInstanceDialog.h
ui/dialogs/CustomMessageBox.cpp ui/dialogs/CustomMessageBox.cpp
ui/dialogs/CustomMessageBox.h ui/dialogs/CustomMessageBox.h
ui/dialogs/EditAccountDialog.cpp
ui/dialogs/EditAccountDialog.h
ui/dialogs/ExportInstanceDialog.cpp ui/dialogs/ExportInstanceDialog.cpp
ui/dialogs/ExportInstanceDialog.h ui/dialogs/ExportInstanceDialog.h
ui/dialogs/ExportPackDialog.cpp ui/dialogs/ExportPackDialog.cpp
@ -1033,14 +1071,21 @@ SET(LAUNCHER_SOURCES
ui/dialogs/BlockedModsDialog.h ui/dialogs/BlockedModsDialog.h
ui/dialogs/ChooseProviderDialog.h ui/dialogs/ChooseProviderDialog.h
ui/dialogs/ChooseProviderDialog.cpp ui/dialogs/ChooseProviderDialog.cpp
ui/dialogs/ModUpdateDialog.cpp ui/dialogs/ResourceUpdateDialog.cpp
ui/dialogs/ModUpdateDialog.h ui/dialogs/ResourceUpdateDialog.h
ui/dialogs/InstallLoaderDialog.cpp ui/dialogs/InstallLoaderDialog.cpp
ui/dialogs/InstallLoaderDialog.h ui/dialogs/InstallLoaderDialog.h
ui/dialogs/skins/SkinManageDialog.cpp ui/dialogs/skins/SkinManageDialog.cpp
ui/dialogs/skins/SkinManageDialog.h ui/dialogs/skins/SkinManageDialog.h
ui/dialogs/skins/draw/SkinOpenGLWindow.h
ui/dialogs/skins/draw/SkinOpenGLWindow.cpp
ui/dialogs/skins/draw/Scene.h
ui/dialogs/skins/draw/Scene.cpp
ui/dialogs/skins/draw/BoxGeometry.h
ui/dialogs/skins/draw/BoxGeometry.cpp
# GUI - widgets # GUI - widgets
ui/widgets/CheckComboBox.cpp ui/widgets/CheckComboBox.cpp
ui/widgets/CheckComboBox.h ui/widgets/CheckComboBox.h
@ -1050,14 +1095,12 @@ SET(LAUNCHER_SOURCES
ui/widgets/CustomCommands.h ui/widgets/CustomCommands.h
ui/widgets/EnvironmentVariables.cpp ui/widgets/EnvironmentVariables.cpp
ui/widgets/EnvironmentVariables.h ui/widgets/EnvironmentVariables.h
ui/widgets/DropLabel.cpp
ui/widgets/DropLabel.h
ui/widgets/FocusLineEdit.cpp ui/widgets/FocusLineEdit.cpp
ui/widgets/FocusLineEdit.h ui/widgets/FocusLineEdit.h
ui/widgets/IconLabel.cpp ui/widgets/IconLabel.cpp
ui/widgets/IconLabel.h ui/widgets/IconLabel.h
ui/widgets/JavaSettingsWidget.cpp ui/widgets/JavaWizardWidget.cpp
ui/widgets/JavaSettingsWidget.h ui/widgets/JavaWizardWidget.h
ui/widgets/LabeledToolButton.cpp ui/widgets/LabeledToolButton.cpp
ui/widgets/LabeledToolButton.h ui/widgets/LabeledToolButton.h
ui/widgets/LanguageSelectionWidget.cpp ui/widgets/LanguageSelectionWidget.cpp
@ -1093,6 +1136,10 @@ SET(LAUNCHER_SOURCES
ui/widgets/WideBar.cpp ui/widgets/WideBar.cpp
ui/widgets/ThemeCustomizationWidget.h ui/widgets/ThemeCustomizationWidget.h
ui/widgets/ThemeCustomizationWidget.cpp ui/widgets/ThemeCustomizationWidget.cpp
ui/widgets/MinecraftSettingsWidget.h
ui/widgets/MinecraftSettingsWidget.cpp
ui/widgets/JavaSettingsWidget.h
ui/widgets/JavaSettingsWidget.cpp
# GUI - instance group view # GUI - instance group view
ui/instanceview/InstanceProxyModel.cpp ui/instanceview/InstanceProxyModel.cpp
@ -1128,13 +1175,14 @@ endif()
qt_wrap_ui(LAUNCHER_UI qt_wrap_ui(LAUNCHER_UI
ui/MainWindow.ui ui/MainWindow.ui
ui/setupwizard/PasteWizardPage.ui ui/setupwizard/PasteWizardPage.ui
ui/setupwizard/AutoJavaWizardPage.ui
ui/setupwizard/LoginWizardPage.ui
ui/setupwizard/ThemeWizardPage.ui ui/setupwizard/ThemeWizardPage.ui
ui/pages/global/AccountListPage.ui ui/pages/global/AccountListPage.ui
ui/pages/global/JavaPage.ui ui/pages/global/JavaPage.ui
ui/pages/global/LauncherPage.ui ui/pages/global/LauncherPage.ui
ui/pages/global/APIPage.ui ui/pages/global/APIPage.ui
ui/pages/global/ProxyPage.ui ui/pages/global/ProxyPage.ui
ui/pages/global/MinecraftPage.ui
ui/pages/global/ExternalToolsPage.ui ui/pages/global/ExternalToolsPage.ui
ui/pages/instance/ExternalResourcesPage.ui ui/pages/instance/ExternalResourcesPage.ui
ui/pages/instance/NotesPage.ui ui/pages/instance/NotesPage.ui
@ -1142,7 +1190,6 @@ qt_wrap_ui(LAUNCHER_UI
ui/pages/instance/ServersPage.ui ui/pages/instance/ServersPage.ui
ui/pages/instance/GameOptionsPage.ui ui/pages/instance/GameOptionsPage.ui
ui/pages/instance/OtherLogsPage.ui ui/pages/instance/OtherLogsPage.ui
ui/pages/instance/InstanceSettingsPage.ui
ui/pages/instance/VersionPage.ui ui/pages/instance/VersionPage.ui
ui/pages/instance/ManagedPackPage.ui ui/pages/instance/ManagedPackPage.ui
ui/pages/instance/WorldListPage.ui ui/pages/instance/WorldListPage.ui
@ -1165,6 +1212,8 @@ qt_wrap_ui(LAUNCHER_UI
ui/widgets/ModFilterWidget.ui ui/widgets/ModFilterWidget.ui
ui/widgets/SubTaskProgressBar.ui ui/widgets/SubTaskProgressBar.ui
ui/widgets/ThemeCustomizationWidget.ui ui/widgets/ThemeCustomizationWidget.ui
ui/widgets/MinecraftSettingsWidget.ui
ui/widgets/JavaSettingsWidget.ui
ui/dialogs/CopyInstanceDialog.ui ui/dialogs/CopyInstanceDialog.ui
ui/dialogs/ProfileSetupDialog.ui ui/dialogs/ProfileSetupDialog.ui
ui/dialogs/ProgressDialog.ui ui/dialogs/ProgressDialog.ui
@ -1180,12 +1229,10 @@ qt_wrap_ui(LAUNCHER_UI
ui/dialogs/MSALoginDialog.ui ui/dialogs/MSALoginDialog.ui
ui/dialogs/OfflineLoginDialog.ui ui/dialogs/OfflineLoginDialog.ui
ui/dialogs/AboutDialog.ui ui/dialogs/AboutDialog.ui
ui/dialogs/EditAccountDialog.ui
ui/dialogs/ReviewMessageBox.ui ui/dialogs/ReviewMessageBox.ui
ui/dialogs/ScrollMessageBox.ui ui/dialogs/ScrollMessageBox.ui
ui/dialogs/BlockedModsDialog.ui ui/dialogs/BlockedModsDialog.ui
ui/dialogs/ChooseProviderDialog.ui ui/dialogs/ChooseProviderDialog.ui
ui/dialogs/skins/SkinManageDialog.ui ui/dialogs/skins/SkinManageDialog.ui
) )
@ -1210,7 +1257,8 @@ qt_add_resources(LAUNCHER_RESOURCES
resources/iOS/iOS.qrc resources/iOS/iOS.qrc
resources/flat/flat.qrc resources/flat/flat.qrc
resources/documents/documents.qrc resources/documents/documents.qrc
../${Launcher_Branding_LogoQRC} resources/shaders/shaders.qrc
"${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_LogoQRC}"
) )
qt_wrap_ui(PRISMUPDATER_UI qt_wrap_ui(PRISMUPDATER_UI
@ -1228,14 +1276,10 @@ include(CompilerWarnings)
# Add executable # Add executable
add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES}) add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES})
if(BUILD_TESTING)
target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_TEST)
endif()
set_project_warnings(Launcher_logic set_project_warnings(Launcher_logic
"${Launcher_MSVC_WARNINGS}" "${Launcher_MSVC_WARNINGS}"
"${Launcher_CLANG_WARNINGS}" "${Launcher_CLANG_WARNINGS}"
"${Launcher_GCC_WARNINGS}") "${Launcher_GCC_WARNINGS}")
target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION)
target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION) target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION)
target_link_libraries(Launcher_logic target_link_libraries(Launcher_logic
@ -1264,6 +1308,8 @@ target_link_libraries(Launcher_logic
Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Gui
Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Widgets
Qt${QT_VERSION_MAJOR}::NetworkAuth Qt${QT_VERSION_MAJOR}::NetworkAuth
Qt${QT_VERSION_MAJOR}::OpenGL
${Launcher_QT_DBUS}
${Launcher_QT_LIBS} ${Launcher_QT_LIBS}
) )
target_link_libraries(Launcher_logic target_link_libraries(Launcher_logic
@ -1272,6 +1318,10 @@ target_link_libraries(Launcher_logic
LocalPeer LocalPeer
Launcher_rainbow Launcher_rainbow
) )
if (TARGET ${Launcher_QT_DBUS})
add_compile_definitions(WITH_QTDBUS)
endif()
if(APPLE) if(APPLE)
set(CMAKE_MACOSX_RPATH 1) set(CMAKE_MACOSX_RPATH 1)
set(CMAKE_INSTALL_RPATH "@loader_path/../Frameworks/") set(CMAKE_INSTALL_RPATH "@loader_path/../Frameworks/")
@ -1339,7 +1389,7 @@ if(Launcher_BUILD_UPDATER)
add_executable("${Launcher_Name}_updater" WIN32 updater/prismupdater/updater_main.cpp) add_executable("${Launcher_Name}_updater" WIN32 updater/prismupdater/updater_main.cpp)
target_sources("${Launcher_Name}_updater" PRIVATE updater/prismupdater/updater.exe.manifest) target_sources("${Launcher_Name}_updater" PRIVATE updater/prismupdater/updater.exe.manifest)
target_link_libraries("${Launcher_Name}_updater" prism_updater_logic) target_link_libraries("${Launcher_Name}_updater" prism_updater_logic)
if(DEFINED Launcher_APP_BINARY_NAME) if(DEFINED Launcher_APP_BINARY_NAME)
set_target_properties("${Launcher_Name}_updater" PROPERTIES OUTPUT_NAME "${Launcher_APP_BINARY_NAME}_updater") set_target_properties("${Launcher_Name}_updater" PROPERTIES OUTPUT_NAME "${Launcher_APP_BINARY_NAME}_updater")
endif() endif()

View File

@ -12,11 +12,8 @@
#include <QtConcurrent> #include <QtConcurrent>
DataMigrationTask::DataMigrationTask(QObject* parent, DataMigrationTask::DataMigrationTask(const QString& sourcePath, const QString& targetPath, const IPathMatcher::Ptr pathMatcher)
const QString& sourcePath, : Task(), m_sourcePath(sourcePath), m_targetPath(targetPath), m_pathMatcher(pathMatcher), m_copy(sourcePath, targetPath)
const QString& targetPath,
const IPathMatcher::Ptr pathMatcher)
: Task(parent), m_sourcePath(sourcePath), m_targetPath(targetPath), m_pathMatcher(pathMatcher), m_copy(sourcePath, targetPath)
{ {
m_copy.matcher(m_pathMatcher.get()).whitelist(true); m_copy.matcher(m_pathMatcher.get()).whitelist(true);
} }
@ -27,7 +24,7 @@ void DataMigrationTask::executeTask()
// 1. Scan // 1. Scan
// Check how many files we gotta copy // Check how many files we gotta copy
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&] { m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] {
return m_copy(true); // dry run to collect amount of files return m_copy(true); // dry run to collect amount of files
}); });
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::dryRunFinished); connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::dryRunFinished);
@ -60,7 +57,7 @@ void DataMigrationTask::dryRunFinished()
setProgress(m_copy.totalCopied(), m_toCopy); setProgress(m_copy.totalCopied(), m_toCopy);
setStatus(tr("Copying %1…").arg(shortenedName)); setStatus(tr("Copying %1…").arg(shortenedName));
}); });
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&] { m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] {
return m_copy(false); // actually copy now return m_copy(false); // actually copy now
}); });
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::copyFinished); connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::copyFinished);

View File

@ -18,7 +18,7 @@
class DataMigrationTask : public Task { class DataMigrationTask : public Task {
Q_OBJECT Q_OBJECT
public: public:
explicit DataMigrationTask(QObject* parent, const QString& sourcePath, const QString& targetPath, IPathMatcher::Ptr pathmatcher); explicit DataMigrationTask(const QString& sourcePath, const QString& targetPath, IPathMatcher::Ptr pathmatcher);
~DataMigrationTask() override = default; ~DataMigrationTask() override = default;
protected: protected:

View File

@ -40,12 +40,11 @@
#include <QFileSystemModel> #include <QFileSystemModel>
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include <QStack> #include <QStack>
#include <algorithm>
#include "FileSystem.h" #include "FileSystem.h"
#include "SeparatorPrefixTree.h" #include "SeparatorPrefixTree.h"
#include "StringUtils.h" #include "StringUtils.h"
FileIgnoreProxy::FileIgnoreProxy(QString root, QObject* parent) : QSortFilterProxyModel(parent), root(root) {} FileIgnoreProxy::FileIgnoreProxy(QString root, QObject* parent) : QSortFilterProxyModel(parent), m_root(root) {}
// NOTE: Sadly, we have to do sorting ourselves. // NOTE: Sadly, we have to do sorting ourselves.
bool FileIgnoreProxy::lessThan(const QModelIndex& left, const QModelIndex& right) const bool FileIgnoreProxy::lessThan(const QModelIndex& left, const QModelIndex& right) const
{ {
@ -104,10 +103,10 @@ QVariant FileIgnoreProxy::data(const QModelIndex& index, int role) const
if (index.column() == 0 && role == Qt::CheckStateRole) { if (index.column() == 0 && role == Qt::CheckStateRole) {
QFileSystemModel* fsm = qobject_cast<QFileSystemModel*>(sourceModel()); QFileSystemModel* fsm = qobject_cast<QFileSystemModel*>(sourceModel());
auto blockedPath = relPath(fsm->filePath(sourceIndex)); auto blockedPath = relPath(fsm->filePath(sourceIndex));
auto cover = blocked.cover(blockedPath); auto cover = m_blocked.cover(blockedPath);
if (!cover.isNull()) { if (!cover.isNull()) {
return QVariant(Qt::Unchecked); return QVariant(Qt::Unchecked);
} else if (blocked.exists(blockedPath)) { } else if (m_blocked.exists(blockedPath)) {
return QVariant(Qt::PartiallyChecked); return QVariant(Qt::PartiallyChecked);
} else { } else {
return QVariant(Qt::Checked); return QVariant(Qt::Checked);
@ -130,7 +129,7 @@ bool FileIgnoreProxy::setData(const QModelIndex& index, const QVariant& value, i
QString FileIgnoreProxy::relPath(const QString& path) const QString FileIgnoreProxy::relPath(const QString& path) const
{ {
return QDir(root).relativeFilePath(path); return QDir(m_root).relativeFilePath(path);
} }
bool FileIgnoreProxy::setFilterState(QModelIndex index, Qt::CheckState state) bool FileIgnoreProxy::setFilterState(QModelIndex index, Qt::CheckState state)
@ -146,18 +145,18 @@ bool FileIgnoreProxy::setFilterState(QModelIndex index, Qt::CheckState state)
bool changed = false; bool changed = false;
if (state == Qt::Unchecked) { if (state == Qt::Unchecked) {
// blocking a path // blocking a path
auto& node = blocked.insert(blockedPath); auto& node = m_blocked.insert(blockedPath);
// get rid of all blocked nodes below // get rid of all blocked nodes below
node.clear(); node.clear();
changed = true; changed = true;
} else if (state == Qt::Checked || state == Qt::PartiallyChecked) { } else if (state == Qt::Checked || state == Qt::PartiallyChecked) {
if (!blocked.remove(blockedPath)) { if (!m_blocked.remove(blockedPath)) {
auto cover = blocked.cover(blockedPath); auto cover = m_blocked.cover(blockedPath);
qDebug() << "Blocked by cover" << cover; qDebug() << "Blocked by cover" << cover;
// uncover // uncover
blocked.remove(cover); m_blocked.remove(cover);
// block all contents, except for any cover // block all contents, except for any cover
QModelIndex rootIndex = fsm->index(FS::PathCombine(root, cover)); QModelIndex rootIndex = fsm->index(FS::PathCombine(m_root, cover));
QModelIndex doing = rootIndex; QModelIndex doing = rootIndex;
int row = 0; int row = 0;
QStack<QModelIndex> todo; QStack<QModelIndex> todo;
@ -179,7 +178,7 @@ bool FileIgnoreProxy::setFilterState(QModelIndex index, Qt::CheckState state)
todo.push(node); todo.push(node);
} else { } else {
// or just block this one. // or just block this one.
blocked.insert(relpath); m_blocked.insert(relpath);
} }
row++; row++;
} }
@ -229,7 +228,7 @@ bool FileIgnoreProxy::shouldExpand(QModelIndex index)
return false; return false;
} }
auto blockedPath = relPath(fsm->filePath(sourceIndex)); auto blockedPath = relPath(fsm->filePath(sourceIndex));
auto found = blocked.find(blockedPath); auto found = m_blocked.find(blockedPath);
if (found) { if (found) {
return !found->leaf(); return !found->leaf();
} }
@ -239,8 +238,8 @@ bool FileIgnoreProxy::shouldExpand(QModelIndex index)
void FileIgnoreProxy::setBlockedPaths(QStringList paths) void FileIgnoreProxy::setBlockedPaths(QStringList paths)
{ {
beginResetModel(); beginResetModel();
blocked.clear(); m_blocked.clear();
blocked.insert(paths); m_blocked.insert(paths);
endResetModel(); endResetModel();
} }
@ -272,5 +271,30 @@ bool FileIgnoreProxy::ignoreFile(QFileInfo fileInfo) const
bool FileIgnoreProxy::filterFile(const QString& fileName) const bool FileIgnoreProxy::filterFile(const QString& fileName) const
{ {
return blocked.covers(fileName) || ignoreFile(QFileInfo(QDir(root), fileName)); return m_blocked.covers(fileName) || ignoreFile(QFileInfo(QDir(m_root), fileName));
}
void FileIgnoreProxy::loadBlockedPathsFromFile(const QString& fileName)
{
QFile ignoreFile(fileName);
if (!ignoreFile.open(QIODevice::ReadOnly)) {
return;
}
auto ignoreData = ignoreFile.readAll();
auto string = QString::fromUtf8(ignoreData);
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
setBlockedPaths(string.split('\n', Qt::SkipEmptyParts));
#else
setBlockedPaths(string.split('\n', QString::SkipEmptyParts));
#endif
}
void FileIgnoreProxy::saveBlockedPathsToFile(const QString& fileName)
{
auto ignoreData = blockedPaths().toStringList().join('\n').toUtf8();
try {
FS::write(fileName, ignoreData);
} catch (const Exception& e) {
qWarning() << e.cause();
}
} }

View File

@ -61,8 +61,8 @@ class FileIgnoreProxy : public QSortFilterProxyModel {
void setBlockedPaths(QStringList paths); void setBlockedPaths(QStringList paths);
inline const SeparatorPrefixTree<'/'>& blockedPaths() const { return blocked; } inline const SeparatorPrefixTree<'/'>& blockedPaths() const { return m_blocked; }
inline SeparatorPrefixTree<'/'>& blockedPaths() { return blocked; } inline SeparatorPrefixTree<'/'>& blockedPaths() { return m_blocked; }
// list of file names that need to be removed completely from model // list of file names that need to be removed completely from model
inline QStringList& ignoreFilesWithName() { return m_ignoreFiles; } inline QStringList& ignoreFilesWithName() { return m_ignoreFiles; }
@ -71,6 +71,10 @@ class FileIgnoreProxy : public QSortFilterProxyModel {
bool filterFile(const QString& fileName) const; bool filterFile(const QString& fileName) const;
void loadBlockedPathsFromFile(const QString& fileName);
void saveBlockedPathsToFile(const QString& fileName);
protected: protected:
bool filterAcceptsColumn(int source_column, const QModelIndex& source_parent) const; bool filterAcceptsColumn(int source_column, const QModelIndex& source_parent) const;
bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const; bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const;
@ -78,8 +82,8 @@ class FileIgnoreProxy : public QSortFilterProxyModel {
bool ignoreFile(QFileInfo file) const; bool ignoreFile(QFileInfo file) const;
private: private:
const QString root; const QString m_root;
SeparatorPrefixTree<'/'> blocked; SeparatorPrefixTree<'/'> m_blocked;
QStringList m_ignoreFiles; QStringList m_ignoreFiles;
SeparatorPrefixTree<'/'> m_ignoreFilePaths; SeparatorPrefixTree<'/'> m_ignoreFilePaths;
}; };

View File

@ -45,7 +45,6 @@
#include <QDirIterator> #include <QDirIterator>
#include <QFile> #include <QFile>
#include <QFileInfo> #include <QFileInfo>
#include <QSaveFile>
#include <QStandardPaths> #include <QStandardPaths>
#include <QStorageInfo> #include <QStorageInfo>
#include <QTextStream> #include <QTextStream>
@ -54,6 +53,7 @@
#include <system_error> #include <system_error>
#include "DesktopServices.h" #include "DesktopServices.h"
#include "PSaveFile.h"
#include "StringUtils.h" #include "StringUtils.h"
#if defined Q_OS_WIN32 #if defined Q_OS_WIN32
@ -191,8 +191,8 @@ void ensureExists(const QDir& dir)
void write(const QString& filename, const QByteArray& data) void write(const QString& filename, const QByteArray& data)
{ {
ensureExists(QFileInfo(filename).dir()); ensureExists(QFileInfo(filename).dir());
QSaveFile file(filename); PSaveFile file(filename);
if (!file.open(QSaveFile::WriteOnly)) { if (!file.open(PSaveFile::WriteOnly)) {
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString()); throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
} }
if (data.size() != file.write(data)) { if (data.size() != file.write(data)) {
@ -213,8 +213,8 @@ void appendSafe(const QString& filename, const QByteArray& data)
buffer = QByteArray(); buffer = QByteArray();
} }
buffer.append(data); buffer.append(data);
QSaveFile file(filename); PSaveFile file(filename);
if (!file.open(QSaveFile::WriteOnly)) { if (!file.open(PSaveFile::WriteOnly)) {
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString()); throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
} }
if (buffer.size() != file.write(buffer)) { if (buffer.size() != file.write(buffer)) {
@ -276,6 +276,9 @@ bool ensureFolderPathExists(const QFileInfo folderPath)
{ {
QDir dir; QDir dir;
QString ensuredPath = folderPath.filePath(); QString ensuredPath = folderPath.filePath();
if (folderPath.exists())
return true;
bool success = dir.mkpath(ensuredPath); bool success = dir.mkpath(ensuredPath);
return success; return success;
} }
@ -338,7 +341,7 @@ bool copy::operator()(const QString& offset, bool dryRun)
opt |= copy_opts::overwrite_existing; opt |= copy_opts::overwrite_existing;
// Function that'll do the actual copying // Function that'll do the actual copying
auto copy_file = [&](QString src_path, QString relative_dst_path) { auto copy_file = [this, dryRun, src, dst, opt, &err](QString src_path, QString relative_dst_path) {
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist)) if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
return; return;
@ -425,7 +428,7 @@ void create_link::make_link_list(const QString& offset)
m_recursive = true; m_recursive = true;
// Function that'll do the actual linking // Function that'll do the actual linking
auto link_file = [&](QString src_path, QString relative_dst_path) { auto link_file = [this, dst](QString src_path, QString relative_dst_path) {
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist)) { if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist)) {
qDebug() << "path" << relative_dst_path << "in black list or not in whitelist"; qDebug() << "path" << relative_dst_path << "in black list or not in whitelist";
return; return;
@ -520,7 +523,7 @@ void create_link::runPrivileged(const QString& offset)
QString serverName = BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink_server" + StringUtils::getRandomAlphaNumeric(); QString serverName = BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink_server" + StringUtils::getRandomAlphaNumeric();
connect(&m_linkServer, &QLocalServer::newConnection, this, [&]() { connect(&m_linkServer, &QLocalServer::newConnection, this, [this, &gotResults]() {
qDebug() << "Client connected, sending out pairs"; qDebug() << "Client connected, sending out pairs";
// construct block of data to send // construct block of data to send
QByteArray block; QByteArray block;
@ -602,7 +605,7 @@ void create_link::runPrivileged(const QString& offset)
} }
ExternalLinkFileProcess* linkFileProcess = new ExternalLinkFileProcess(serverName, m_useHardLinks, this); ExternalLinkFileProcess* linkFileProcess = new ExternalLinkFileProcess(serverName, m_useHardLinks, this);
connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [&]() { emit finishedPrivileged(gotResults); }); connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [this, gotResults]() { emit finishedPrivileged(gotResults); });
connect(linkFileProcess, &ExternalLinkFileProcess::finished, linkFileProcess, &QObject::deleteLater); connect(linkFileProcess, &ExternalLinkFileProcess::finished, linkFileProcess, &QObject::deleteLater);
linkFileProcess->start(); linkFileProcess->start();
@ -918,6 +921,10 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
if (destination.isEmpty()) { if (destination.isEmpty()) {
destination = PathCombine(getDesktopDir(), RemoveInvalidFilenameChars(name)); destination = PathCombine(getDesktopDir(), RemoveInvalidFilenameChars(name));
} }
if (!ensureFilePathExists(destination)) {
qWarning() << "Destination path can't be created!";
return false;
}
#if defined(Q_OS_MACOS) #if defined(Q_OS_MACOS)
// Create the Application // Create the Application
QDir applicationDirectory = QDir applicationDirectory =
@ -964,8 +971,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
if (!args.empty()) if (!args.empty())
argstring = " \"" + args.join("\" \"") + "\""; argstring = " \"" + args.join("\" \"") + "\"";
stream << "#!/bin/bash" stream << "#!/bin/bash" << "\n";
<< "\n";
stream << "\"" << target << "\" " << argstring << "\n"; stream << "\"" << target << "\" " << argstring << "\n";
stream.flush(); stream.flush();
@ -1009,12 +1015,9 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
if (!args.empty()) if (!args.empty())
argstring = " '" + args.join("' '") + "'"; argstring = " '" + args.join("' '") + "'";
stream << "[Desktop Entry]" stream << "[Desktop Entry]" << "\n";
<< "\n"; stream << "Type=Application" << "\n";
stream << "Type=Application" stream << "Categories=Game;ActionGame;AdventureGame;Simulation" << "\n";
<< "\n";
stream << "Categories=Game;ActionGame;AdventureGame;Simulation"
<< "\n";
stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n"; stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n";
stream << "Name=" << name.toLocal8Bit() << "\n"; stream << "Name=" << name.toLocal8Bit() << "\n";
if (!icon.isEmpty()) { if (!icon.isEmpty()) {
@ -1292,7 +1295,7 @@ bool clone::operator()(const QString& offset, bool dryRun)
std::error_code err; std::error_code err;
// Function that'll do the actual cloneing // Function that'll do the actual cloneing
auto cloneFile = [&](QString src_path, QString relative_dst_path) { auto cloneFile = [this, dryRun, dst, &err](QString src_path, QString relative_dst_path) {
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist)) if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
return; return;

View File

@ -1,16 +1,12 @@
#include "Filter.h" #include "Filter.h"
Filter::~Filter() {}
ContainsFilter::ContainsFilter(const QString& pattern) : pattern(pattern) {} ContainsFilter::ContainsFilter(const QString& pattern) : pattern(pattern) {}
ContainsFilter::~ContainsFilter() {}
bool ContainsFilter::accepts(const QString& value) bool ContainsFilter::accepts(const QString& value)
{ {
return value.contains(pattern); return value.contains(pattern);
} }
ExactFilter::ExactFilter(const QString& pattern) : pattern(pattern) {} ExactFilter::ExactFilter(const QString& pattern) : pattern(pattern) {}
ExactFilter::~ExactFilter() {}
bool ExactFilter::accepts(const QString& value) bool ExactFilter::accepts(const QString& value)
{ {
return value == pattern; return value == pattern;
@ -27,10 +23,15 @@ RegexpFilter::RegexpFilter(const QString& regexp, bool invert) : invert(invert)
pattern.setPattern(regexp); pattern.setPattern(regexp);
pattern.optimize(); pattern.optimize();
} }
RegexpFilter::~RegexpFilter() {}
bool RegexpFilter::accepts(const QString& value) bool RegexpFilter::accepts(const QString& value)
{ {
auto match = pattern.match(value); auto match = pattern.match(value);
bool matched = match.hasMatch(); bool matched = match.hasMatch();
return invert ? (!matched) : (matched); 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);
}

View File

@ -5,14 +5,14 @@
class Filter { class Filter {
public: public:
virtual ~Filter(); virtual ~Filter() = default;
virtual bool accepts(const QString& value) = 0; virtual bool accepts(const QString& value) = 0;
}; };
class ContainsFilter : public Filter { class ContainsFilter : public Filter {
public: public:
ContainsFilter(const QString& pattern); ContainsFilter(const QString& pattern);
virtual ~ContainsFilter(); virtual ~ContainsFilter() = default;
bool accepts(const QString& value) override; bool accepts(const QString& value) override;
private: private:
@ -22,7 +22,7 @@ class ContainsFilter : public Filter {
class ExactFilter : public Filter { class ExactFilter : public Filter {
public: public:
ExactFilter(const QString& pattern); ExactFilter(const QString& pattern);
virtual ~ExactFilter(); virtual ~ExactFilter() = default;
bool accepts(const QString& value) override; bool accepts(const QString& value) override;
private: private:
@ -32,7 +32,7 @@ class ExactFilter : public Filter {
class ExactIfPresentFilter : public Filter { class ExactIfPresentFilter : public Filter {
public: public:
ExactIfPresentFilter(const QString& pattern); ExactIfPresentFilter(const QString& pattern);
~ExactIfPresentFilter() override = default; virtual ~ExactIfPresentFilter() override = default;
bool accepts(const QString& value) override; bool accepts(const QString& value) override;
private: private:
@ -42,10 +42,20 @@ class ExactIfPresentFilter : public Filter {
class RegexpFilter : public Filter { class RegexpFilter : public Filter {
public: public:
RegexpFilter(const QString& regexp, bool invert); RegexpFilter(const QString& regexp, bool invert);
virtual ~RegexpFilter(); virtual ~RegexpFilter() = default;
bool accepts(const QString& value) override; bool accepts(const QString& value) override;
private: private:
QRegularExpression pattern; QRegularExpression pattern;
bool invert = false; 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;
};

View File

@ -91,7 +91,7 @@ void InstanceCopyTask::executeTask()
QEventLoop loop; QEventLoop loop;
bool got_priv_results = false; bool got_priv_results = false;
connect(&folderLink, &FS::create_link::finishedPrivileged, this, [&](bool gotResults) { connect(&folderLink, &FS::create_link::finishedPrivileged, this, [&got_priv_results, &loop](bool gotResults) {
if (!gotResults) { if (!gotResults) {
qDebug() << "Privileged run exited without results!"; qDebug() << "Privileged run exited without results!";
} }
@ -173,7 +173,11 @@ void InstanceCopyTask::copyFinished()
allowed_symlinks_file allowed_symlinks_file
.filePath()); // we dont want to modify the original. also make sure the resulting file is not itself a link. .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(); emitSucceeded();

View File

@ -38,22 +38,29 @@ void InstanceCreationTask::executeTask()
// files scheduled to, and we'd better not let the user abort in the middle of it, since it'd // files scheduled to, and we'd better not let the user abort in the middle of it, since it'd
// put the instance in an invalid state. // put the instance in an invalid state.
if (shouldOverride()) { if (shouldOverride()) {
bool deleteFailed = false;
setAbortable(false); setAbortable(false);
setStatus(tr("Removing old conflicting files...")); setStatus(tr("Removing old conflicting files..."));
qDebug() << "Removing old files"; qDebug() << "Removing old files";
for (auto path : m_files_to_remove) { for (const QString& path : m_files_to_remove) {
if (!QFile::exists(path)) if (!QFile::exists(path))
continue; continue;
qDebug() << "Removing" << path; qDebug() << "Removing" << path;
if (!FS::deletePath(path)) {
qCritical() << "Couldn't remove the old conflicting files."; if (!QFile::remove(path)) {
emitFailed(tr("Failed to remove old conflicting files.")); qCritical() << "Could not remove" << path;
return; deleteFailed = true;
} }
} }
}
emitSucceeded(); if (deleteFailed) {
return; emitFailed(tr("Failed to remove old conflicting files."));
return;
}
}
if (!m_abort)
emitSucceeded();
} }

View File

@ -69,9 +69,11 @@ bool InstanceImportTask::abort()
if (!canAbort()) if (!canAbort())
return false; return false;
if (task) bool wasAborted = false;
task->abort(); if (m_task)
return Task::abort(); wasAborted = m_task->abort();
Task::abort();
return wasAborted;
} }
void InstanceImportTask::executeTask() void InstanceImportTask::executeTask()
@ -104,7 +106,7 @@ void InstanceImportTask::downloadFromUrl()
connect(filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propagateStepProgress); connect(filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propagateStepProgress);
connect(filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::emitFailed); connect(filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::emitFailed);
connect(filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::emitAborted); connect(filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::emitAborted);
task.reset(filesNetJob); m_task.reset(filesNetJob);
filesNetJob->start(); filesNetJob->start();
} }
@ -193,7 +195,7 @@ void InstanceImportTask::processZipPack()
stepProgress(*progressStep); stepProgress(*progressStep);
}); });
connect(zipTask.get(), &Task::succeeded, this, &InstanceImportTask::extractFinished); connect(zipTask.get(), &Task::succeeded, this, &InstanceImportTask::extractFinished, Qt::QueuedConnection);
connect(zipTask.get(), &Task::aborted, this, &InstanceImportTask::emitAborted); connect(zipTask.get(), &Task::aborted, this, &InstanceImportTask::emitAborted);
connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) { connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) {
progressStep->state = TaskStepState::Failed; progressStep->state = TaskStepState::Failed;
@ -210,12 +212,13 @@ void InstanceImportTask::processZipPack()
progressStep->status = status; progressStep->status = status;
stepProgress(*progressStep); stepProgress(*progressStep);
}); });
task.reset(zipTask); m_task.reset(zipTask);
zipTask->start(); zipTask->start();
} }
void InstanceImportTask::extractFinished() void InstanceImportTask::extractFinished()
{ {
setAbortable(false);
QDir extractDir(m_stagingPath); QDir extractDir(m_stagingPath);
qDebug() << "Fixing permissions for extracted pack files..."; qDebug() << "Fixing permissions for extracted pack files...";
@ -289,8 +292,11 @@ void InstanceImportTask::processFlame()
inst_creation_task->setGroup(m_instGroup); inst_creation_task->setGroup(m_instGroup);
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate()); inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
connect(inst_creation_task.get(), &Task::succeeded, this, [this, inst_creation_task] { auto weak = inst_creation_task.toWeakRef();
setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID()); connect(inst_creation_task.get(), &Task::succeeded, this, [this, weak] {
if (auto sp = weak.lock()) {
setOverride(sp->shouldOverride(), sp->originalInstanceID());
}
emitSucceeded(); emitSucceeded();
}); });
connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed); connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed);
@ -299,11 +305,12 @@ void InstanceImportTask::processFlame()
connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus); connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus);
connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails); connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails);
connect(this, &Task::aborted, inst_creation_task.get(), &InstanceCreationTask::abort);
connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort); connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort);
connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable); connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
inst_creation_task->start(); m_task.reset(inst_creation_task);
setAbortable(true);
m_task->start();
} }
void InstanceImportTask::processTechnic() void InstanceImportTask::processTechnic()
@ -350,7 +357,7 @@ void InstanceImportTask::processMultiMC()
void InstanceImportTask::processModrinth() void InstanceImportTask::processModrinth()
{ {
ModrinthCreationTask* inst_creation_task = nullptr; shared_qobject_ptr<ModrinthCreationTask> inst_creation_task = nullptr;
if (!m_extra_info.isEmpty()) { if (!m_extra_info.isEmpty()) {
auto pack_id_it = m_extra_info.constFind("pack_id"); auto pack_id_it = m_extra_info.constFind("pack_id");
Q_ASSERT(pack_id_it != m_extra_info.constEnd()); Q_ASSERT(pack_id_it != m_extra_info.constEnd());
@ -367,7 +374,7 @@ void InstanceImportTask::processModrinth()
original_instance_id = original_instance_id_it.value(); original_instance_id = original_instance_id_it.value();
inst_creation_task = inst_creation_task =
new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id); makeShared<ModrinthCreationTask>(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
} else { } else {
QString pack_id; QString pack_id;
if (!m_sourceUrl.isEmpty()) { if (!m_sourceUrl.isEmpty()) {
@ -376,7 +383,7 @@ void InstanceImportTask::processModrinth()
} }
// FIXME: Find a way to get the ID in directly imported ZIPs // FIXME: Find a way to get the ID in directly imported ZIPs
inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id); inst_creation_task = makeShared<ModrinthCreationTask>(m_stagingPath, m_globalSettings, m_parent, pack_id);
} }
inst_creation_task->setName(*this); inst_creation_task->setName(*this);
@ -384,20 +391,23 @@ void InstanceImportTask::processModrinth()
inst_creation_task->setGroup(m_instGroup); inst_creation_task->setGroup(m_instGroup);
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate()); inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] { auto weak = inst_creation_task.toWeakRef();
setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID()); connect(inst_creation_task.get(), &Task::succeeded, this, [this, weak] {
if (auto sp = weak.lock()) {
setOverride(sp->shouldOverride(), sp->originalInstanceID());
}
emitSucceeded(); emitSucceeded();
}); });
connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed); connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed);
connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress); connect(inst_creation_task.get(), &Task::progress, this, &InstanceImportTask::setProgress);
connect(inst_creation_task, &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress); connect(inst_creation_task.get(), &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress);
connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus); connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus);
connect(inst_creation_task, &Task::details, this, &InstanceImportTask::setDetails); connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails);
connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater);
connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort); connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort);
connect(inst_creation_task, &Task::aborted, this, &Task::abort); connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortable);
inst_creation_task->start(); m_task.reset(inst_creation_task);
setAbortable(true);
m_task->start();
} }

View File

@ -40,16 +40,13 @@
#include <QUrl> #include <QUrl>
#include "InstanceTask.h" #include "InstanceTask.h"
#include <memory>
#include <optional>
class QuaZip; class QuaZip;
class InstanceImportTask : public InstanceTask { class InstanceImportTask : public InstanceTask {
Q_OBJECT Q_OBJECT
public: public:
explicit InstanceImportTask(const QUrl& sourceUrl, QWidget* parent = nullptr, QMap<QString, QString>&& extra_info = {}); explicit InstanceImportTask(const QUrl& sourceUrl, QWidget* parent = nullptr, QMap<QString, QString>&& extra_info = {});
virtual ~InstanceImportTask() = default;
bool abort() override; bool abort() override;
protected: protected:
@ -70,7 +67,7 @@ class InstanceImportTask : public InstanceTask {
private: /* data */ private: /* data */
QUrl m_sourceUrl; QUrl m_sourceUrl;
QString m_archivePath; QString m_archivePath;
Task::Ptr task; Task::Ptr m_task;
enum class ModpackType { enum class ModpackType {
Unknown, Unknown,
MultiMC, MultiMC,

View File

@ -487,7 +487,7 @@ InstanceList::InstListError InstanceList::loadList()
int front_bookmark = -1; int front_bookmark = -1;
int back_bookmark = -1; int back_bookmark = -1;
int currentItem = -1; int currentItem = -1;
auto removeNow = [&]() { auto removeNow = [this, &front_bookmark, &back_bookmark, &currentItem]() {
beginRemoveRows(QModelIndex(), front_bookmark, back_bookmark); beginRemoveRows(QModelIndex(), front_bookmark, back_bookmark);
m_instances.erase(m_instances.begin() + front_bookmark, m_instances.begin() + back_bookmark + 1); m_instances.erase(m_instances.begin() + front_bookmark, m_instances.begin() + back_bookmark + 1);
endRemoveRows(); endRemoveRows();

View File

@ -43,7 +43,7 @@ class InstancePageProvider : protected QObject, public BasePageProvider {
values.append(new ServersPage(onesix)); values.append(new ServersPage(onesix));
// values.append(new GameOptionsPage(onesix.get())); // values.append(new GameOptionsPage(onesix.get()));
values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots"))); values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots")));
values.append(new InstanceSettingsPage(onesix.get())); values.append(new InstanceSettingsPage(onesix));
auto logMatcher = inst->getLogFileMatcher(); auto logMatcher = inst->getLogFileMatcher();
if (logMatcher) { if (logMatcher) {
values.append(new OtherLogsPage(inst->getLogFileRoot(), logMatcher)); values.append(new OtherLogsPage(inst->getLogFileRoot(), logMatcher));

View File

@ -63,7 +63,7 @@ bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget* parent)
return true; return true;
} }
void JavaCommon::javaWasOk(QWidget* parent, const JavaCheckResult& result) void JavaCommon::javaWasOk(QWidget* parent, const JavaChecker::Result& result)
{ {
QString text; QString text;
text += QObject::tr( 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(); 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; auto htmlError = result.errorLog;
QString text; 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(); 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; QString text;
text += QObject::tr( text += QObject::tr(
@ -116,34 +116,26 @@ void JavaCommon::TestCheck::run()
emit finished(); emit finished();
return; return;
} }
checker.reset(new JavaChecker()); checker.reset(new JavaChecker(m_path, "", 0, 0, 0, 0));
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinished); connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinished);
checker->m_path = m_path; checker->start();
checker->performCheck();
} }
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); javaBinaryWasBad(m_parent, result);
emit finished(); emit finished();
return; return;
} }
checker.reset(new JavaChecker()); checker.reset(new JavaChecker(m_path, m_args, m_maxMem, m_maxMem, result.javaVersion.requiresPermGen() ? m_permGen : 0, 0));
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinishedWithArgs); connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinishedWithArgs);
checker->m_path = m_path; checker->start();
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();
} }
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); javaWasOk(m_parent, result);
emit finished(); emit finished();
return; return;

View File

@ -10,11 +10,11 @@ namespace JavaCommon {
bool checkJVMArgs(QString args, QWidget* parent); bool checkJVMArgs(QString args, QWidget* parent);
// Show a dialog saying that the Java binary was usable // 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 // 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 // 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 // Show a dialog if we couldn't find Java Checker
void javaCheckNotFound(QWidget* parent); void javaCheckNotFound(QWidget* parent);
@ -32,11 +32,11 @@ class TestCheck : public QObject {
void finished(); void finished();
private slots: private slots:
void checkFinished(JavaCheckResult result); void checkFinished(const JavaChecker::Result& result);
void checkFinishedWithArgs(JavaCheckResult result); void checkFinishedWithArgs(const JavaChecker::Result& result);
private: private:
std::shared_ptr<JavaChecker> checker; JavaChecker::Ptr checker;
QWidget* m_parent = nullptr; QWidget* m_parent = nullptr;
QString m_path; QString m_path;
QString m_args; QString m_args;

View File

@ -43,6 +43,7 @@
#include "ui/InstanceWindow.h" #include "ui/InstanceWindow.h"
#include "ui/MainWindow.h" #include "ui/MainWindow.h"
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/MSALoginDialog.h"
#include "ui/dialogs/ProfileSelectDialog.h" #include "ui/dialogs/ProfileSelectDialog.h"
#include "ui/dialogs/ProfileSetupDialog.h" #include "ui/dialogs/ProfileSetupDialog.h"
#include "ui/dialogs/ProgressDialog.h" #include "ui/dialogs/ProgressDialog.h"
@ -53,6 +54,7 @@
#include <QLineEdit> #include <QLineEdit>
#include <QList> #include <QList>
#include <QPushButton> #include <QPushButton>
#include <QRegularExpression>
#include <QStringList> #include <QStringList>
#include "BuildConfig.h" #include "BuildConfig.h"
@ -60,7 +62,7 @@
#include "launch/steps/TextPrint.h" #include "launch/steps/TextPrint.h"
#include "tasks/Task.h" #include "tasks/Task.h"
LaunchController::LaunchController(QObject* parent) : Task(parent) {} LaunchController::LaunchController() : Task() {}
void LaunchController::executeTask() void LaunchController::executeTask()
{ {
@ -198,8 +200,7 @@ void LaunchController::login()
m_accountToUse->shouldRefresh()) { m_accountToUse->shouldRefresh()) {
// Force account refresh on the account used to launch the instance updating the AccountState // 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 // only on first try and if it is not meant to be offline
auto accounts = APPLICATION->accounts(); m_accountToUse->refresh();
accounts->requestRefresh(m_accountToUse->internalId());
} }
while (tryagain) { while (tryagain) {
if (tries > 0 && tries % 3 == 0) { if (tries > 0 && tries % 3 == 0) {
@ -218,13 +219,34 @@ void LaunchController::login()
m_session->demo = m_demo; m_session->demo = m_demo;
m_accountToUse->fillSession(m_session); m_accountToUse->fillSession(m_session);
// Launch immediately in true offline mode MinecraftAccountPtr accountToCheck;
if (m_accountToUse->accountType() == AccountType::Offline) {
launchInstance(); if (m_accountToUse->ownsMinecraft())
accountToCheck = m_accountToUse;
else if (const MinecraftAccountPtr defaultAccount = APPLICATION->accounts()->defaultAccount();
defaultAccount != nullptr && defaultAccount->ownsMinecraft()) {
accountToCheck = defaultAccount;
} else {
for (int i = 0; i < APPLICATION->accounts()->count(); i++) {
MinecraftAccountPtr account = APPLICATION->accounts()->at(i);
if (account->ownsMinecraft())
accountToCheck = account;
}
}
if (accountToCheck == nullptr) {
if (!m_session->demo)
m_session->demo = askPlayDemo();
if (m_session->demo)
launchInstance();
else
emitFailed(tr("Launch cancelled - account does not own Minecraft."));
return; return;
} }
switch (m_accountToUse->accountState()) { switch (accountToCheck->accountState()) {
case AccountState::Offline: { case AccountState::Offline: {
m_session->wants_online = false; m_session->wants_online = false;
} }
@ -233,46 +255,41 @@ void LaunchController::login()
if (!m_session->wants_online) { if (!m_session->wants_online) {
// we ask the user for a player name // we ask the user for a player name
bool ok = false; bool ok = false;
auto name = askOfflineName(m_session->player_name, m_session->demo, ok); QString name;
if (!ok) { if (m_offlineName.isEmpty()) {
tryagain = false; name = askOfflineName(m_session->player_name, m_session->demo, ok);
break; if (!ok) {
tryagain = false;
break;
}
} else {
name = m_offlineName;
} }
m_session->MakeOffline(name); m_session->MakeOffline(name);
// offline flavored game from here :3 // offline flavored game from here :3
} } else if (m_accountToUse == accountToCheck && !m_accountToUse->hasProfile()) {
if (m_accountToUse->ownsMinecraft()) { // Now handle setting up a profile name here...
if (!m_accountToUse->hasProfile()) { ProfileSetupDialog dialog(m_accountToUse, m_parentWidget);
// Now handle setting up a profile name here... if (dialog.exec() == QDialog::Accepted) {
ProfileSetupDialog dialog(m_accountToUse, m_parentWidget); tryagain = true;
if (dialog.exec() == QDialog::Accepted) { continue;
tryagain = true;
continue;
} else {
emitFailed(tr("Received undetermined session status during login."));
return;
}
}
// we own Minecraft, there is a profile, it's all ready to go!
launchInstance();
return;
} else {
// play demo ?
if (!m_session->demo) {
m_session->demo = askPlayDemo();
}
if (m_session->demo) { // play demo here
launchInstance();
} else { } else {
emitFailed(tr("Launch cancelled - account does not own Minecraft.")); emitFailed(tr("Received undetermined session status during login."));
return;
} }
} }
if (m_accountToUse->accountType() == AccountType::Offline)
m_session->wants_online = false;
// we own Minecraft, there is a profile, it's all ready to go!
launchInstance();
return; return;
} }
case AccountState::Errored: case AccountState::Errored:
// This means some sort of soft error that we can fix with a refresh ... so let's refresh. // This means some sort of soft error that we can fix with a refresh ... so let's refresh.
case AccountState::Unchecked: { case AccountState::Unchecked: {
m_accountToUse->refresh(); accountToCheck->refresh();
} }
/* fallthrough */ /* fallthrough */
case AccountState::Working: { case AccountState::Working: {
@ -281,19 +298,19 @@ void LaunchController::login()
if (m_online) { if (m_online) {
progDialog.setSkipButton(true, tr("Play Offline")); progDialog.setSkipButton(true, tr("Play Offline"));
} }
auto task = m_accountToUse->currentTask(); auto task = accountToCheck->currentTask();
progDialog.execWithTask(task.get()); progDialog.execWithTask(task.get());
continue; continue;
} }
case AccountState::Expired: { case AccountState::Expired: {
auto errorString = tr("The account has expired and needs to be logged into manually again."); if (reauthenticateAccount(accountToCheck))
QMessageBox::warning(m_parentWidget, tr("Account refresh failed"), errorString, QMessageBox::StandardButton::Ok, continue;
QMessageBox::StandardButton::Ok);
emitFailed(errorString);
return; return;
} }
case AccountState::Disabled: { case AccountState::Disabled: {
auto errorString = tr("The launcher's client identification has changed. Please remove this account and add it again."); auto errorString = tr("The launcher's client identification has changed. Please remove '%1' and try again.")
.arg(accountToCheck->profileName());
QMessageBox::warning(m_parentWidget, tr("Client identification changed"), errorString, QMessageBox::StandardButton::Ok, QMessageBox::warning(m_parentWidget, tr("Client identification changed"), errorString, QMessageBox::StandardButton::Ok,
QMessageBox::StandardButton::Ok); QMessageBox::StandardButton::Ok);
emitFailed(errorString); emitFailed(errorString);
@ -301,8 +318,9 @@ void LaunchController::login()
} }
case AccountState::Gone: { case AccountState::Gone: {
auto errorString = auto errorString =
tr("The account no longer exists on the servers. It may have been migrated, in which case please add the new account " tr("'%1' no longer exists on the servers. It may have been migrated, in which case please add the new account "
"you migrated this one to."); "you migrated this one to.")
.arg(accountToCheck->profileName());
QMessageBox::warning(m_parentWidget, tr("Account gone"), errorString, QMessageBox::StandardButton::Ok, QMessageBox::warning(m_parentWidget, tr("Account gone"), errorString, QMessageBox::StandardButton::Ok,
QMessageBox::StandardButton::Ok); QMessageBox::StandardButton::Ok);
emitFailed(errorString); emitFailed(errorString);
@ -313,6 +331,38 @@ void LaunchController::login()
emitFailed(tr("Failed to launch.")); emitFailed(tr("Failed to launch."));
} }
bool LaunchController::reauthenticateAccount(MinecraftAccountPtr account)
{
auto button = QMessageBox::warning(
m_parentWidget, tr("Account refresh failed"),
tr("'%1' has expired and needs to be reauthenticated. Do you want to reauthenticate this account?").arg(account->profileName()),
QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, QMessageBox::StandardButton::Yes);
if (button == QMessageBox::StandardButton::Yes) {
auto accounts = APPLICATION->accounts();
bool isDefault = accounts->defaultAccount() == account;
accounts->removeAccount(accounts->index(accounts->findAccountByProfileId(account->profileId())));
if (account->accountType() == AccountType::MSA) {
auto newAccount = MSALoginDialog::newAccount(m_parentWidget);
if (newAccount != nullptr) {
accounts->addAccount(newAccount);
if (isDefault)
accounts->setDefaultAccount(newAccount);
if (m_accountToUse == account) {
m_accountToUse = nullptr;
decideAccount();
}
return true;
}
}
}
emitFailed(tr("The account has expired and needs to be reauthenticated"));
return false;
}
void LaunchController::launchInstance() void LaunchController::launchInstance()
{ {
Q_ASSERT_X(m_instance != NULL, "launchInstance", "instance is NULL"); Q_ASSERT_X(m_instance != NULL, "launchInstance", "instance is NULL");

View File

@ -47,7 +47,7 @@ class LaunchController : public Task {
public: public:
void executeTask() override; void executeTask() override;
LaunchController(QObject* parent = nullptr); LaunchController();
virtual ~LaunchController() = default; virtual ~LaunchController() = default;
void setInstance(InstancePtr instance) { m_instance = instance; } void setInstance(InstancePtr instance) { m_instance = instance; }
@ -56,6 +56,8 @@ class LaunchController : public Task {
void setOnline(bool online) { m_online = online; } void setOnline(bool online) { m_online = online; }
void setOfflineName(const QString& offlineName) { m_offlineName = offlineName; }
void setDemo(bool demo) { m_demo = demo; } void setDemo(bool demo) { m_demo = demo; }
void setProfiler(BaseProfilerFactory* profiler) { m_profiler = profiler; } void setProfiler(BaseProfilerFactory* profiler) { m_profiler = profiler; }
@ -76,6 +78,7 @@ class LaunchController : public Task {
void decideAccount(); void decideAccount();
bool askPlayDemo(); bool askPlayDemo();
QString askOfflineName(QString playerName, bool demo, bool& ok); QString askOfflineName(QString playerName, bool demo, bool& ok);
bool reauthenticateAccount(MinecraftAccountPtr account);
private slots: private slots:
void readyForLaunch(); void readyForLaunch();
@ -87,6 +90,7 @@ class LaunchController : public Task {
private: private:
BaseProfilerFactory* m_profiler = nullptr; BaseProfilerFactory* m_profiler = nullptr;
bool m_online = true; bool m_online = true;
QString m_offlineName;
bool m_demo = false; bool m_demo = false;
InstancePtr m_instance; InstancePtr m_instance;
QWidget* m_parentWidget = nullptr; QWidget* m_parentWidget = nullptr;

View File

@ -39,8 +39,16 @@ if [ "x$DEPS_LIST" = "x" ]; then
# Just to be sure... # Just to be sure...
chmod +x "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" chmod +x "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}"
ARGS=("${LAUNCHER_DIR}/${LAUNCHER_NAME}" "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}")
if [ -f portable.txt ]; then
ARGS+=("-d" "${LAUNCHER_DIR}")
fi
ARGS+=("$@")
# Run the launcher # Run the launcher
exec -a "${LAUNCHER_DIR}/${LAUNCHER_NAME}" "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" -d "${LAUNCHER_DIR}" "$@" exec -a "${ARGS[@]}"
# Run the launcher in valgrind # Run the launcher in valgrind
# valgrind --log-file="valgrind.log" --leak-check=full --track-origins=yes "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" -d "${LAUNCHER_DIR}" "$@" # valgrind --log-file="valgrind.log" --leak-check=full --track-origins=yes "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" -d "${LAUNCHER_DIR}" "$@"

View File

@ -39,7 +39,8 @@
#include <QTextDecoder> #include <QTextDecoder>
#include "MessageLevel.h" #include "MessageLevel.h"
LoggedProcess::LoggedProcess(QObject* parent) : QProcess(parent) LoggedProcess::LoggedProcess(const QTextCodec* output_codec, QObject* parent)
: QProcess(parent), m_err_decoder(output_codec), m_out_decoder(output_codec)
{ {
// QProcess has a strange interface... let's map a lot of those into a few. // QProcess has a strange interface... let's map a lot of those into a few.
connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut); connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut);

View File

@ -49,7 +49,7 @@ class LoggedProcess : public QProcess {
enum State { NotRunning, Starting, FailedToStart, Running, Finished, Crashed, Aborted }; enum State { NotRunning, Starting, FailedToStart, Running, Finished, Crashed, Aborted };
public: public:
explicit LoggedProcess(QObject* parent = 0); explicit LoggedProcess(const QTextCodec* output_codec = QTextCodec::codecForLocale(), QObject* parent = 0);
virtual ~LoggedProcess(); virtual ~LoggedProcess();
State state() const; State state() const;
@ -80,8 +80,8 @@ class LoggedProcess : public QProcess {
QStringList reprocess(const QByteArray& data, QTextDecoder& decoder); QStringList reprocess(const QByteArray& data, QTextDecoder& decoder);
private: private:
QTextDecoder m_err_decoder = QTextDecoder(QTextCodec::codecForLocale()); QTextDecoder m_err_decoder;
QTextDecoder m_out_decoder = QTextDecoder(QTextCodec::codecForLocale()); QTextDecoder m_out_decoder;
QString m_leftover_line; QString m_leftover_line;
bool m_killed = false; bool m_killed = false;
State m_state = NotRunning; State m_state = NotRunning;

View File

@ -2,7 +2,7 @@
/* /*
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> * Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -378,7 +378,7 @@ std::optional<QStringList> extractDir(QString fileCompressed, QString dir)
if (fileInfo.size() == 22) { if (fileInfo.size() == 22) {
return QStringList(); return QStringList();
} }
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError(); qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError();
; ;
return std::nullopt; return std::nullopt;
} }
@ -395,7 +395,7 @@ std::optional<QStringList> extractDir(QString fileCompressed, QString subdir, QS
if (fileInfo.size() == 22) { if (fileInfo.size() == 22) {
return QStringList(); return QStringList();
} }
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError(); qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError();
; ;
return std::nullopt; return std::nullopt;
} }
@ -412,7 +412,7 @@ bool extractFile(QString fileCompressed, QString file, QString target)
if (fileInfo.size() == 22) { if (fileInfo.size() == 22) {
return true; return true;
} }
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError(); qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError();
return false; return false;
} }
return extractRelFile(&zip, file, target); return extractRelFile(&zip, file, target);
@ -536,6 +536,10 @@ bool ExportToZipTask::abort()
void ExtractZipTask::executeTask() 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(); }); m_zip_future = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return extractZip(); });
connect(&m_zip_watcher, &QFutureWatcher<ZipResult>::finished, this, &ExtractZipTask::finish); connect(&m_zip_watcher, &QFutureWatcher<ZipResult>::finished, this, &ExtractZipTask::finish);
m_zip_watcher.setFuture(m_zip_future); m_zip_watcher.setFuture(m_zip_future);
@ -573,7 +577,7 @@ auto ExtractZipTask::extractZip() -> ZipResult
auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(m_subdirectory.size())); auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(m_subdirectory.size()));
auto original_name = relative_file_name; auto original_name = relative_file_name;
setStatus("Unziping: " + relative_file_name); setStatus("Unpacking: " + relative_file_name);
// Fix subdirs/files ending with a / getting transformed into absolute paths // Fix subdirs/files ending with a / getting transformed into absolute paths
if (relative_file_name.startsWith('/')) if (relative_file_name.startsWith('/'))

View File

@ -2,7 +2,7 @@
/* /*
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> * Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -153,6 +153,7 @@ bool collectFileListRecursively(const QString& rootDir, const QString& subDir, Q
#if defined(LAUNCHER_APPLICATION) #if defined(LAUNCHER_APPLICATION)
class ExportToZipTask : public Task { class ExportToZipTask : public Task {
Q_OBJECT
public: public:
ExportToZipTask(QString outputPath, ExportToZipTask(QString outputPath,
QDir dir, QDir dir,
@ -207,7 +208,11 @@ class ExportToZipTask : public Task {
}; };
class ExtractZipTask : public Task { class ExtractZipTask : public Task {
Q_OBJECT
public: public:
ExtractZipTask(QString input, QDir outputDir, QString subdirectory = "")
: ExtractZipTask(std::make_shared<QuaZip>(input), outputDir, subdirectory)
{}
ExtractZipTask(std::shared_ptr<QuaZip> input, QDir outputDir, QString subdirectory = "") ExtractZipTask(std::shared_ptr<QuaZip> input, QDir outputDir, QString subdirectory = "")
: m_input(input), m_output_dir(outputDir), m_subdirectory(subdirectory) : m_input(input), m_output_dir(outputDir), m_subdirectory(subdirectory)
{} {}

View File

@ -101,7 +101,7 @@ class PixmapCache final : public QObject {
*/ */
bool _markCacheMissByEviciton() bool _markCacheMissByEviciton()
{ {
static constexpr uint maxInt = static_cast<uint>(std::numeric_limits<int>::max()); static constexpr uint maxCache = static_cast<uint>(std::numeric_limits<int>::max()) / 4;
static constexpr uint step = 10240; static constexpr uint step = 10240;
static constexpr int oneSecond = 1000; static constexpr int oneSecond = 1000;
@ -118,8 +118,8 @@ class PixmapCache final : public QObject {
if (m_consecutive_fast_evicitons >= m_consecutive_fast_evicitons_threshold) { if (m_consecutive_fast_evicitons >= m_consecutive_fast_evicitons_threshold) {
// increase the cache size // increase the cache size
uint newSize = _cacheLimit() + step; uint newSize = _cacheLimit() + step;
if (newSize >= maxInt) { // increase it until you overflow :D if (newSize >= maxCache) { // increase it until you overflow :D
newSize = maxInt; newSize = maxCache;
qDebug() << m_consecutive_fast_evicitons qDebug() << m_consecutive_fast_evicitons
<< tr("pixmap cache misses by eviction happened too fast, doing nothing as the cache size reached it's limit"); << tr("pixmap cache misses by eviction happened too fast, doing nothing as the cache size reached it's limit");
} else { } else {

View File

@ -108,24 +108,31 @@ QString getLibraryString()
if (filePath.isEmpty()) { if (filePath.isEmpty()) {
continue; 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); if (libraryName.isEmpty()) {
auto confObject = Json::requireObject(conf, vkLayer); continue;
auto layer = Json::ensureObject(confObject, "layer"); }
QString libraryName = Json::ensureString(layer, "library_path"); if (QFileInfo(libraryName).isAbsolute()) {
return libraryName;
}
#ifdef __GLIBC__ #ifdef __GLIBC__
// Check whether mangohud is usable on a glibc based system // Check whether mangohud is usable on a glibc based system
if (!libraryName.isEmpty()) {
QString libraryPath = findLibrary(libraryName); QString libraryPath = findLibrary(libraryName);
if (!libraryPath.isEmpty()) { if (!libraryPath.isEmpty()) {
return libraryPath; return libraryPath;
} }
}
#else #else
// Without glibc return recorded shared library as-is. // Without glibc return recorded shared library as-is.
return libraryName; return libraryName;
#endif #endif
} catch (const Exception& e) {
}
} }
return {}; return {};

View File

@ -53,7 +53,7 @@ class NullInstance : public BaseInstance {
QSet<QString> traits() const override { return {}; }; QSet<QString> traits() const override { return {}; };
QString instanceConfigFolder() const override { return instanceRoot(); }; QString instanceConfigFolder() const override { return instanceRoot(); };
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr, MinecraftTarget::Ptr) override { return nullptr; } shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr, MinecraftTarget::Ptr) override { return nullptr; }
shared_qobject_ptr<Task> createUpdateTask([[maybe_unused]] Net::Mode mode) override { return nullptr; } QList<Task::Ptr> createUpdateTask() override { return {}; }
QProcessEnvironment createEnvironment() override { return QProcessEnvironment(); } QProcessEnvironment createEnvironment() override { return QProcessEnvironment(); }
QProcessEnvironment createLaunchEnvironment() override { return QProcessEnvironment(); } QProcessEnvironment createLaunchEnvironment() override { return QProcessEnvironment(); }
QMap<QString, QString> getVariables() override { return QMap<QString, QString>(); } QMap<QString, QString> getVariables() override { return QMap<QString, QString>(); }

71
launcher/PSaveFile.h Normal file
View File

@ -0,0 +1,71 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QFileInfo>
#include <QSaveFile>
#include "Application.h"
#if defined(LAUNCHER_APPLICATION)
/* PSaveFile
* A class that mimics QSaveFile for Windows.
*
* When reading resources, we need to avoid accessing temporary files
* generated by QSaveFile. If we start reading such a file, we may
* inadvertently keep it open while QSaveFile is trying to remove it,
* or we might detect the file just before it is removed, leading to
* race conditions and errors.
*
* Unfortunately, QSaveFile doesn't provide a way to retrieve the
* temporary file name or to set a specific template for the temporary
* file name it uses. By default, QSaveFile appends a `.XXXXXX` suffix
* to the original file name, where the `XXXXXX` part is dynamically
* generated to ensure uniqueness.
*
* This class acts like a lock by adding and removing the target file
* name into/from a global string set, helping to manage access to
* files during critical operations.
*
* Note: Please do not use the `setFileName` function directly, as it
* is not virtual and cannot be overridden.
*/
class PSaveFile : public QSaveFile {
public:
PSaveFile(const QString& name) : QSaveFile(name) { addPath(name); }
PSaveFile(const QString& name, QObject* parent) : QSaveFile(name, parent) { addPath(name); }
virtual ~PSaveFile()
{
if (auto app = APPLICATION_DYN) {
app->removeQSavePath(m_absoluteFilePath);
}
}
private:
void addPath(const QString& path)
{
m_absoluteFilePath = QFileInfo(path).absoluteFilePath() + "."; // add dot for tmp files only
if (auto app = APPLICATION_DYN) {
app->addQSavePath(m_absoluteFilePath);
}
}
QString m_absoluteFilePath;
};
#else
#define PSaveFile QSaveFile
#endif

View File

@ -33,7 +33,7 @@ class shared_qobject_ptr : public QSharedPointer<T> {
{} {}
void reset() { QSharedPointer<T>::reset(); } void reset() { QSharedPointer<T>::reset(); }
void reset(T*&& other) void reset(T* other)
{ {
shared_qobject_ptr<T> t(other); shared_qobject_ptr<T> t(other);
this->swap(t); this->swap(t);

View File

@ -24,7 +24,9 @@
#include "minecraft/mod/ModFolderModel.h" #include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/ResourceFolderModel.h" #include "minecraft/mod/ResourceFolderModel.h"
#include "modplatform/helpers/HashUtils.h"
#include "net/ApiDownload.h" #include "net/ApiDownload.h"
#include "net/ChecksumValidator.h"
ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack, ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
ModPlatform::IndexedVersion version, ModPlatform::IndexedVersion version,
@ -33,9 +35,9 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
QString custom_target_folder) QString custom_target_folder)
: m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs), m_custom_target_folder(custom_target_folder) : m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs), m_custom_target_folder(custom_target_folder)
{ {
if (auto model = dynamic_cast<ModFolderModel*>(m_pack_model.get()); model && is_indexed) { if (is_indexed) {
m_update_task.reset(new LocalModUpdateTask(model->indexDir(), *m_pack, m_pack_version)); m_update_task.reset(new LocalResourceUpdateTask(m_pack_model->indexDir(), *m_pack, m_pack_version));
connect(m_update_task.get(), &LocalModUpdateTask::hasOldMod, this, &ResourceDownloadTask::hasOldResource); connect(m_update_task.get(), &LocalResourceUpdateTask::hasOldResource, this, &ResourceDownloadTask::hasOldResource);
addTask(m_update_task); addTask(m_update_task);
} }
@ -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::succeeded, this, &ResourceDownloadTask::downloadSucceeded);
connect(m_filesNetJob.get(), &NetJob::progress, this, &ResourceDownloadTask::downloadProgressChanged); connect(m_filesNetJob.get(), &NetJob::progress, this, &ResourceDownloadTask::downloadProgressChanged);
connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &ResourceDownloadTask::propagateStepProgress); connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &ResourceDownloadTask::propagateStepProgress);
@ -67,12 +91,8 @@ void ResourceDownloadTask::downloadSucceeded()
m_filesNetJob.reset(); m_filesNetJob.reset();
auto name = std::get<0>(to_delete); auto name = std::get<0>(to_delete);
auto filename = std::get<1>(to_delete); auto filename = std::get<1>(to_delete);
if (!name.isEmpty() && filename != m_pack_version.fileName) { if (!name.isEmpty() && filename != m_pack_version.fileName)
if (auto model = dynamic_cast<ModFolderModel*>(m_pack_model.get()); model) m_pack_model->uninstallResource(filename, true);
model->uninstallMod(filename, true);
else
m_pack_model->uninstallResource(filename);
}
} }
void ResourceDownloadTask::downloadFailed(QString reason) void ResourceDownloadTask::downloadFailed(QString reason)

View File

@ -22,7 +22,7 @@
#include "net/NetJob.h" #include "net/NetJob.h"
#include "tasks/SequentialTask.h" #include "tasks/SequentialTask.h"
#include "minecraft/mod/tasks/LocalModUpdateTask.h" #include "minecraft/mod/tasks/LocalResourceUpdateTask.h"
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
class ResourceFolderModel; class ResourceFolderModel;
@ -50,7 +50,7 @@ class ResourceDownloadTask : public SequentialTask {
QString m_custom_target_folder; QString m_custom_target_folder;
NetJob::Ptr m_filesNetJob; NetJob::Ptr m_filesNetJob;
LocalModUpdateTask::Ptr m_update_task; LocalResourceUpdateTask::Ptr m_update_task;
void downloadProgressChanged(qint64 current, qint64 total); void downloadProgressChanged(qint64 current, qint64 total);
void downloadFailed(QString reason); void downloadFailed(QString reason);

View File

@ -20,13 +20,13 @@
#include <QSet> #include <QSet>
#include <QString> #include <QString>
#include "SysInfo.h"
#include "settings/SettingsObject.h" #include "settings/SettingsObject.h"
struct RuntimeContext { struct RuntimeContext {
QString javaArchitecture; QString javaArchitecture;
QString javaRealArchitecture; QString javaRealArchitecture;
QString javaPath; QString system = SysInfo::currentSystem();
QString system;
QString mappedJavaRealArchitecture() const QString mappedJavaRealArchitecture() const
{ {
@ -45,8 +45,6 @@ struct RuntimeContext {
{ {
javaArchitecture = instanceSettings->get("JavaArchitecture").toString(); javaArchitecture = instanceSettings->get("JavaArchitecture").toString();
javaRealArchitecture = instanceSettings->get("JavaRealArchitecture").toString(); javaRealArchitecture = instanceSettings->get("JavaRealArchitecture").toString();
javaPath = instanceSettings->get("JavaPath").toString();
system = currentSystem();
} }
QString getClassifier() const { return system + "-" + mappedJavaRealArchitecture(); } QString getClassifier() const { return system + "-" + mappedJavaRealArchitecture(); }
@ -68,21 +66,4 @@ struct RuntimeContext {
return x; 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
}
}; };

99
launcher/SysInfo.cpp Normal file
View File

@ -0,0 +1,99 @@
#include <QDebug>
#include <QString>
#include "sys.h"
#ifdef Q_OS_MACOS
#include <sys/sysctl.h>
#endif
#include <QFile>
#include <QMap>
#include <QProcess>
#include <QStandardPaths>
#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-x64";
if (arch.contains("86"))
return "mac-os-x86";
// 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

8
launcher/SysInfo.h Normal file
View File

@ -0,0 +1,8 @@
#include <QString>
namespace SysInfo {
QString currentSystem();
QString useQTForArch();
QString getSupportedJavaArchitecture();
int suitableMaxMem();
} // namespace SysInfo

260
launcher/Untar.cpp Normal file
View File

@ -0,0 +1,260 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* 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 <https://www.gnu.org/licenses/>.
*
* 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 <quagzipfile.h>
#include <QByteArray>
#include <QFileInfo>
#include <QIODevice>
#include <QString>
#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);
}

46
launcher/Untar.h Normal file
View File

@ -0,0 +1,46 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* 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 <https://www.gnu.org/licenses/>.
*
* 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 <QIODevice>
// 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);
}

View File

@ -79,7 +79,7 @@ void Version::parse()
if (m_string.isEmpty()) if (m_string.isEmpty())
return; return;
auto classChange = [&](QChar lastChar, QChar currentChar) { auto classChange = [&currentSection](QChar lastChar, QChar currentChar) {
if (lastChar.isNull()) if (lastChar.isNull())
return false; return false;
if (lastChar.isDigit() != currentChar.isDigit()) if (lastChar.isDigit() != currentChar.isDigit())
@ -123,8 +123,7 @@ QDebug operator<<(QDebug debug, const Version& v)
first = false; first = false;
} }
debug.nospace() << " ]" debug.nospace() << " ]" << " }";
<< " }";
return debug; return debug;
} }

View File

@ -114,10 +114,14 @@ QVariant VersionProxyModel::headerData(int section, Qt::Orientation orientation,
return tr("Branch"); return tr("Branch");
case Type: case Type:
return tr("Type"); return tr("Type");
case Architecture: case CPUArchitecture:
return tr("Architecture"); return tr("Architecture");
case Path: case Path:
return tr("Path"); return tr("Path");
case JavaName:
return tr("Java Name");
case JavaMajor:
return tr("Major Version");
case Time: case Time:
return tr("Released"); return tr("Released");
} }
@ -131,10 +135,14 @@ QVariant VersionProxyModel::headerData(int section, Qt::Orientation orientation,
return tr("The version's branch"); return tr("The version's branch");
case Type: case Type:
return tr("The version's type"); return tr("The version's type");
case Architecture: case CPUArchitecture:
return tr("CPU Architecture"); return tr("CPU Architecture");
case Path: case Path:
return tr("Filesystem path to this version"); 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: case Time:
return tr("Release date of this version"); 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); return sourceModel()->data(parentIndex, BaseVersionList::BranchRole);
case Type: case Type:
return sourceModel()->data(parentIndex, BaseVersionList::TypeRole); return sourceModel()->data(parentIndex, BaseVersionList::TypeRole);
case Architecture: case CPUArchitecture:
return sourceModel()->data(parentIndex, BaseVersionList::ArchitectureRole); return sourceModel()->data(parentIndex, BaseVersionList::CPUArchitectureRole);
case Path: case Path:
return sourceModel()->data(parentIndex, BaseVersionList::PathRole); return sourceModel()->data(parentIndex, BaseVersionList::PathRole);
case JavaName:
return sourceModel()->data(parentIndex, BaseVersionList::JavaNameRole);
case JavaMajor:
return sourceModel()->data(parentIndex, BaseVersionList::JavaMajorRole);
case Time: case Time:
return sourceModel()->data(parentIndex, Meta::VersionList::TimeRole).toDate(); return sourceModel()->data(parentIndex, Meta::VersionList::TimeRole).toDate();
default: default:
@ -295,6 +307,7 @@ void VersionProxyModel::setSourceModel(QAbstractItemModel* replacingRaw)
if (!replacing) { if (!replacing) {
roles.clear(); roles.clear();
filterModel->setSourceModel(replacing); filterModel->setSourceModel(replacing);
endResetModel();
return; return;
} }
@ -308,12 +321,18 @@ void VersionProxyModel::setSourceModel(QAbstractItemModel* replacingRaw)
m_columns.push_back(ParentVersion); m_columns.push_back(ParentVersion);
} }
*/ */
if (roles.contains(BaseVersionList::ArchitectureRole)) { if (roles.contains(BaseVersionList::CPUArchitectureRole)) {
m_columns.push_back(Architecture); m_columns.push_back(CPUArchitecture);
} }
if (roles.contains(BaseVersionList::PathRole)) { if (roles.contains(BaseVersionList::PathRole)) {
m_columns.push_back(Path); 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)) { if (roles.contains(Meta::VersionList::TimeRole)) {
m_columns.push_back(Time); m_columns.push_back(Time);
} }

View File

@ -9,7 +9,7 @@ class VersionFilterModel;
class VersionProxyModel : public QAbstractProxyModel { class VersionProxyModel : public QAbstractProxyModel {
Q_OBJECT Q_OBJECT
public: public:
enum Column { Name, ParentVersion, Branch, Type, Architecture, Path, Time }; enum Column { Name, ParentVersion, Branch, Type, CPUArchitecture, Path, Time, JavaName, JavaMajor };
using FilterMap = QHash<BaseVersionList::ModelRoles, std::shared_ptr<Filter>>; using FilterMap = QHash<BaseVersionList::ModelRoles, std::shared_ptr<Filter>>;
public: public:

View File

@ -104,11 +104,11 @@ void FileLinkApp::joinServer(QString server)
in.setDevice(&socket); in.setDevice(&socket);
connect(&socket, &QLocalSocket::connected, this, [&]() { qDebug() << "connected to server"; }); connect(&socket, &QLocalSocket::connected, this, []() { qDebug() << "connected to server"; });
connect(&socket, &QLocalSocket::readyRead, this, &FileLinkApp::readPathPairs); connect(&socket, &QLocalSocket::readyRead, this, &FileLinkApp::readPathPairs);
connect(&socket, &QLocalSocket::errorOccurred, this, [&](QLocalSocket::LocalSocketError socketError) { connect(&socket, &QLocalSocket::errorOccurred, this, [this](QLocalSocket::LocalSocketError socketError) {
m_status = Failed; m_status = Failed;
switch (socketError) { switch (socketError) {
case QLocalSocket::ServerNotFoundError: case QLocalSocket::ServerNotFoundError:
@ -132,7 +132,7 @@ void FileLinkApp::joinServer(QString server)
} }
}); });
connect(&socket, &QLocalSocket::disconnected, this, [&]() { connect(&socket, &QLocalSocket::disconnected, this, [this]() {
qDebug() << "disconnected from server, should exit"; qDebug() << "disconnected from server, should exit";
m_status = Succeeded; m_status = Succeeded;
exit(); exit();

View File

@ -37,7 +37,6 @@
#include "IconList.h" #include "IconList.h"
#include <FileSystem.h> #include <FileSystem.h>
#include <QDebug> #include <QDebug>
#include <QEventLoop>
#include <QFileSystemWatcher> #include <QFileSystemWatcher>
#include <QMap> #include <QMap>
#include <QMimeData> #include <QMimeData>
@ -47,24 +46,24 @@
#define MAX_SIZE 1024 #define MAX_SIZE 1024
IconList::IconList(const QStringList& builtinPaths, QString path, QObject* parent) : QAbstractListModel(parent) IconList::IconList(const QStringList& builtinPaths, const QString& path, QObject* parent) : QAbstractListModel(parent)
{ {
QSet<QString> builtinNames; QSet<QString> builtinNames;
// add builtin icons // add builtin icons
for (auto& builtinPath : builtinPaths) { for (const auto& builtinPath : builtinPaths) {
QDir instance_icons(builtinPath); QDir instanceIcons(builtinPath);
auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name); auto fileInfoList = instanceIcons.entryInfoList(QDir::Files, QDir::Name);
for (auto file_info : file_info_list) { for (const auto& fileInfo : fileInfoList) {
builtinNames.insert(file_info.completeBaseName()); builtinNames.insert(fileInfo.baseName());
} }
} }
for (auto& builtinName : builtinNames) { for (const auto& builtinName : builtinNames) {
addThemeIcon(builtinName); addThemeIcon(builtinName);
} }
m_watcher.reset(new QFileSystemWatcher()); m_watcher.reset(new QFileSystemWatcher());
is_watching = false; m_isWatching = false;
connect(m_watcher.get(), &QFileSystemWatcher::directoryChanged, this, &IconList::directoryChanged); connect(m_watcher.get(), &QFileSystemWatcher::directoryChanged, this, &IconList::directoryChanged);
connect(m_watcher.get(), &QFileSystemWatcher::fileChanged, this, &IconList::fileChanged); connect(m_watcher.get(), &QFileSystemWatcher::fileChanged, this, &IconList::fileChanged);
@ -77,91 +76,131 @@ IconList::IconList(const QStringList& builtinPaths, QString path, QObject* paren
void IconList::sortIconList() void IconList::sortIconList()
{ {
qDebug() << "Sorting icon list..."; qDebug() << "Sorting icon list...";
std::sort(icons.begin(), icons.end(), [](const MMCIcon& a, const MMCIcon& b) { return a.m_key.localeAwareCompare(b.m_key) < 0; }); std::sort(m_icons.begin(), m_icons.end(), [](const MMCIcon& a, const MMCIcon& b) {
bool aIsSubdir = a.m_key.contains(QDir::separator());
bool bIsSubdir = b.m_key.contains(QDir::separator());
if (aIsSubdir != bIsSubdir) {
return !aIsSubdir; // root-level icons come first
}
return a.m_key.localeAwareCompare(b.m_key) < 0;
});
reindex(); reindex();
} }
// Helper function to add directories recursively
bool IconList::addPathRecursively(const QString& path)
{
QDir dir(path);
if (!dir.exists())
return false;
// Add the directory itself
bool watching = m_watcher->addPath(path);
// Add all subdirectories
QFileInfoList entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
for (const QFileInfo& entry : entries) {
if (addPathRecursively(entry.absoluteFilePath())) {
watching = true;
}
}
return watching;
}
QStringList IconList::getIconFilePaths() const
{
QStringList iconFiles{};
QStringList directories{ m_dir.absolutePath() };
while (!directories.isEmpty()) {
QString first = directories.takeFirst();
QDir dir(first);
for (QFileInfo& fileInfo : dir.entryInfoList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot, QDir::Name)) {
if (fileInfo.isDir())
directories.push_back(fileInfo.absoluteFilePath());
else
iconFiles.push_back(fileInfo.absoluteFilePath());
}
}
return iconFiles;
}
QString formatName(const QDir& iconsDir, const QFileInfo& iconFile)
{
if (iconFile.dir() == iconsDir)
return iconFile.baseName();
constexpr auto delimiter = " » ";
QString relativePathWithoutExtension = iconsDir.relativeFilePath(iconFile.dir().path()) + QDir::separator() + iconFile.baseName();
return relativePathWithoutExtension.replace(QDir::separator(), delimiter);
}
/// Split into a separate function because the preprocessing impedes readability
QSet<QString> toStringSet(const QList<QString>& list)
{
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
QSet<QString> set(list.begin(), list.end());
#else
QSet<QString> set = list.toSet();
#endif
return set;
}
void IconList::directoryChanged(const QString& path) void IconList::directoryChanged(const QString& path)
{ {
QDir new_dir(path); QDir newDir(path);
if (m_dir.absolutePath() != new_dir.absolutePath()) { if (m_dir.absolutePath() != newDir.absolutePath()) {
m_dir.setPath(path); if (!path.startsWith(m_dir.absolutePath()))
m_dir.setPath(path);
m_dir.refresh(); m_dir.refresh();
if (is_watching) if (m_isWatching)
stopWatching(); stopWatching();
startWatching(); startWatching();
} }
if (!m_dir.exists()) if (!m_dir.exists() && !FS::ensureFolderPathExists(m_dir.absolutePath()))
if (!FS::ensureFolderPathExists(m_dir.absolutePath())) return;
return;
m_dir.refresh(); m_dir.refresh();
auto new_list = m_dir.entryList(QDir::Files, QDir::Name); const QStringList newFileNamesList = getIconFilePaths();
for (auto it = new_list.begin(); it != new_list.end(); it++) { const QSet<QString> newSet = toStringSet(newFileNamesList);
QString& foo = (*it); QSet<QString> currentSet;
foo = m_dir.filePath(foo); for (const MMCIcon& it : m_icons) {
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
QSet<QString> new_set(new_list.begin(), new_list.end());
#else
auto new_set = new_list.toSet();
#endif
QList<QString> current_list;
for (auto& it : icons) {
if (!it.has(IconType::FileBased)) if (!it.has(IconType::FileBased))
continue; continue;
current_list.push_back(it.m_images[IconType::FileBased].filename); currentSet.insert(it.m_images[IconType::FileBased].filename);
} }
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) QSet<QString> toRemove = currentSet - newSet;
QSet<QString> current_set(current_list.begin(), current_list.end()); QSet<QString> toAdd = newSet - currentSet;
#else
QSet<QString> current_set = current_list.toSet();
#endif
QSet<QString> to_remove = current_set; for (const QString& removedPath : toRemove) {
to_remove -= new_set; qDebug() << "Removing icon " << removedPath;
QFileInfo removedFile(removedPath);
QSet<QString> to_add = new_set; QString key = m_dir.relativeFilePath(removedFile.absoluteFilePath());
to_add -= current_set;
for (auto remove : to_remove) {
qDebug() << "Removing " << remove;
QFileInfo rmfile(remove);
QString key = rmfile.completeBaseName();
QString suffix = rmfile.suffix();
// The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well
if (!IconUtils::isIconSuffix(suffix))
key = rmfile.fileName();
int idx = getIconIndex(key); int idx = getIconIndex(key);
if (idx == -1) if (idx == -1)
continue; continue;
icons[idx].remove(IconType::FileBased); m_icons[idx].remove(FileBased);
if (icons[idx].type() == IconType::ToBeDeleted) { if (m_icons[idx].type() == ToBeDeleted) {
beginRemoveRows(QModelIndex(), idx, idx); beginRemoveRows(QModelIndex(), idx, idx);
icons.remove(idx); m_icons.remove(idx);
reindex(); reindex();
endRemoveRows(); endRemoveRows();
} else { } else {
dataChanged(index(idx), index(idx)); dataChanged(index(idx), index(idx));
} }
m_watcher->removePath(remove); m_watcher->removePath(removedPath);
emit iconUpdated(key); emit iconUpdated(key);
} }
for (auto add : to_add) { for (const QString& addedPath : toAdd) {
qDebug() << "Adding " << add; qDebug() << "Adding icon " << addedPath;
QFileInfo addfile(add); QFileInfo addfile(addedPath);
QString key = addfile.completeBaseName(); QString relativePath = m_dir.relativeFilePath(addfile.absoluteFilePath());
QString key = QFileInfo(relativePath).completeBaseName();
QString name = formatName(m_dir, addfile);
QString suffix = addfile.suffix(); if (addIcon(key, name, addfile.filePath(), IconType::FileBased)) {
// The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well m_watcher->addPath(addedPath);
if (!IconUtils::isIconSuffix(suffix))
key = addfile.fileName();
if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased)) {
m_watcher->addPath(add);
emit iconUpdated(key); emit iconUpdated(key);
} }
} }
@ -171,24 +210,24 @@ void IconList::directoryChanged(const QString& path)
void IconList::fileChanged(const QString& path) void IconList::fileChanged(const QString& path)
{ {
qDebug() << "Checking " << path; qDebug() << "Checking icon " << path;
QFileInfo checkfile(path); QFileInfo checkfile(path);
if (!checkfile.exists()) if (!checkfile.exists())
return; return;
QString key = checkfile.completeBaseName(); QString key = m_dir.relativeFilePath(checkfile.absoluteFilePath());
int idx = getIconIndex(key); int idx = getIconIndex(key);
if (idx == -1) if (idx == -1)
return; return;
QIcon icon(path); QIcon icon(path);
if (!icon.availableSizes().size()) if (icon.availableSizes().empty())
return; return;
icons[idx].m_images[IconType::FileBased].icon = icon; m_icons[idx].m_images[IconType::FileBased].icon = icon;
dataChanged(index(idx), index(idx)); dataChanged(index(idx), index(idx));
emit iconUpdated(key); emit iconUpdated(key);
} }
void IconList::SettingChanged(const Setting& setting, QVariant value) void IconList::SettingChanged(const Setting& setting, const QVariant& value)
{ {
if (setting.id() != "IconsDir") if (setting.id() != "IconsDir")
return; return;
@ -200,8 +239,8 @@ void IconList::startWatching()
{ {
auto abs_path = m_dir.absolutePath(); auto abs_path = m_dir.absolutePath();
FS::ensureFolderPathExists(abs_path); FS::ensureFolderPathExists(abs_path);
is_watching = m_watcher->addPath(abs_path); m_isWatching = addPathRecursively(abs_path);
if (is_watching) { if (m_isWatching) {
qDebug() << "Started watching " << abs_path; qDebug() << "Started watching " << abs_path;
} else { } else {
qDebug() << "Failed to start watching " << abs_path; qDebug() << "Failed to start watching " << abs_path;
@ -212,7 +251,7 @@ void IconList::stopWatching()
{ {
m_watcher->removePaths(m_watcher->files()); m_watcher->removePaths(m_watcher->files());
m_watcher->removePaths(m_watcher->directories()); m_watcher->removePaths(m_watcher->directories());
is_watching = false; m_isWatching = false;
} }
QStringList IconList::mimeTypes() const QStringList IconList::mimeTypes() const
@ -242,7 +281,7 @@ bool IconList::dropMimeData(const QMimeData* data,
if (data->hasUrls()) { if (data->hasUrls()) {
auto urls = data->urls(); auto urls = data->urls();
QStringList iconFiles; QStringList iconFiles;
for (auto url : urls) { for (const auto& url : urls) {
// only local files may be dropped... // only local files may be dropped...
if (!url.isLocalFile()) if (!url.isLocalFile())
continue; continue;
@ -263,33 +302,33 @@ Qt::ItemFlags IconList::flags(const QModelIndex& index) const
QVariant IconList::data(const QModelIndex& index, int role) const QVariant IconList::data(const QModelIndex& index, int role) const
{ {
if (!index.isValid()) if (!index.isValid())
return QVariant(); return {};
int row = index.row(); int row = index.row();
if (row < 0 || row >= icons.size()) if (row < 0 || row >= m_icons.size())
return QVariant(); return {};
switch (role) { switch (role) {
case Qt::DecorationRole: case Qt::DecorationRole:
return icons[row].icon(); return m_icons[row].icon();
case Qt::DisplayRole: case Qt::DisplayRole:
return icons[row].name(); return m_icons[row].name();
case Qt::UserRole: case Qt::UserRole:
return icons[row].m_key; return m_icons[row].m_key;
default: default:
return QVariant(); return {};
} }
} }
int IconList::rowCount(const QModelIndex& parent) const int IconList::rowCount(const QModelIndex& parent) const
{ {
return parent.isValid() ? 0 : icons.size(); return parent.isValid() ? 0 : m_icons.size();
} }
void IconList::installIcons(const QStringList& iconFiles) void IconList::installIcons(const QStringList& iconFiles)
{ {
for (QString file : iconFiles) for (const QString& file : iconFiles)
installIcon(file, {}); installIcon(file, {});
} }
@ -312,12 +351,13 @@ bool IconList::iconFileExists(const QString& key) const
return iconEntry && iconEntry->has(IconType::FileBased); return iconEntry && iconEntry->has(IconType::FileBased);
} }
/// Returns the icon with the given key or nullptr if it doesn't exist.
const MMCIcon* IconList::icon(const QString& key) const const MMCIcon* IconList::icon(const QString& key) const
{ {
int iconIdx = getIconIndex(key); int iconIdx = getIconIndex(key);
if (iconIdx == -1) if (iconIdx == -1)
return nullptr; return nullptr;
return &icons[iconIdx]; return &m_icons[iconIdx];
} }
bool IconList::deleteIcon(const QString& key) bool IconList::deleteIcon(const QString& key)
@ -332,22 +372,22 @@ bool IconList::trashIcon(const QString& key)
bool IconList::addThemeIcon(const QString& key) bool IconList::addThemeIcon(const QString& key)
{ {
auto iter = name_index.find(key); auto iter = m_nameIndex.find(key);
if (iter != name_index.end()) { if (iter != m_nameIndex.end()) {
auto& oldOne = icons[*iter]; auto& oldOne = m_icons[*iter];
oldOne.replace(Builtin, key); oldOne.replace(Builtin, key);
dataChanged(index(*iter), index(*iter)); dataChanged(index(*iter), index(*iter));
return true; return true;
} }
// add a new icon // add a new icon
beginInsertRows(QModelIndex(), icons.size(), icons.size()); beginInsertRows(QModelIndex(), m_icons.size(), m_icons.size());
{ {
MMCIcon mmc_icon; MMCIcon mmc_icon;
mmc_icon.m_name = key; mmc_icon.m_name = key;
mmc_icon.m_key = key; mmc_icon.m_key = key;
mmc_icon.replace(Builtin, key); mmc_icon.replace(Builtin, key);
icons.push_back(mmc_icon); m_icons.push_back(mmc_icon);
name_index[key] = icons.size() - 1; m_nameIndex[key] = m_icons.size() - 1;
} }
endInsertRows(); endInsertRows();
return true; return true;
@ -359,22 +399,22 @@ bool IconList::addIcon(const QString& key, const QString& name, const QString& p
QIcon icon(path); QIcon icon(path);
if (icon.isNull()) if (icon.isNull())
return false; return false;
auto iter = name_index.find(key); auto iter = m_nameIndex.find(key);
if (iter != name_index.end()) { if (iter != m_nameIndex.end()) {
auto& oldOne = icons[*iter]; auto& oldOne = m_icons[*iter];
oldOne.replace(type, icon, path); oldOne.replace(type, icon, path);
dataChanged(index(*iter), index(*iter)); dataChanged(index(*iter), index(*iter));
return true; return true;
} }
// add a new icon // add a new icon
beginInsertRows(QModelIndex(), icons.size(), icons.size()); beginInsertRows(QModelIndex(), m_icons.size(), m_icons.size());
{ {
MMCIcon mmc_icon; MMCIcon mmc_icon;
mmc_icon.m_name = name; mmc_icon.m_name = name;
mmc_icon.m_key = key; mmc_icon.m_key = key;
mmc_icon.replace(type, icon, path); mmc_icon.replace(type, icon, path);
icons.push_back(mmc_icon); m_icons.push_back(mmc_icon);
name_index[key] = icons.size() - 1; m_nameIndex[key] = m_icons.size() - 1;
} }
endInsertRows(); endInsertRows();
return true; return true;
@ -389,33 +429,32 @@ void IconList::saveIcon(const QString& key, const QString& path, const char* for
void IconList::reindex() void IconList::reindex()
{ {
name_index.clear(); m_nameIndex.clear();
int i = 0; for (int i = 0; i < m_icons.size(); i++) {
for (auto& iter : icons) { m_nameIndex[m_icons[i].m_key] = i;
name_index[iter.m_key] = i; emit iconUpdated(m_icons[i].m_key); // prevents incorrect indices with proxy model
i++;
} }
} }
QIcon IconList::getIcon(const QString& key) const QIcon IconList::getIcon(const QString& key) const
{ {
int icon_index = getIconIndex(key); int iconIndex = getIconIndex(key);
if (icon_index != -1) if (iconIndex != -1)
return icons[icon_index].icon(); return m_icons[iconIndex].icon();
// Fallback for icons that don't exist. // Fallback for icons that don't exist.b
icon_index = getIconIndex("grass"); iconIndex = getIconIndex("grass");
if (icon_index != -1) if (iconIndex != -1)
return icons[icon_index].icon(); return m_icons[iconIndex].icon();
return QIcon(); return {};
} }
int IconList::getIconIndex(const QString& key) const int IconList::getIconIndex(const QString& key) const
{ {
auto iter = name_index.find(key == "default" ? "grass" : key); auto iter = m_nameIndex.find(key == "default" ? "grass" : key);
if (iter != name_index.end()) if (iter != m_nameIndex.end())
return *iter; return *iter;
return -1; return -1;
@ -425,3 +464,15 @@ QString IconList::getDirectory() const
{ {
return m_dir.absolutePath(); return m_dir.absolutePath();
} }
/// Returns the directory of the icon with the given key or the default directory if it's a builtin icon.
QString IconList::iconDirectory(const QString& key) const
{
for (const auto& mmcIcon : m_icons) {
if (mmcIcon.m_key == key && mmcIcon.has(IconType::FileBased)) {
QFileInfo iconFile(mmcIcon.getFilePath());
return iconFile.dir().path();
}
}
return getDirectory();
}

View File

@ -51,7 +51,7 @@ class QFileSystemWatcher;
class IconList : public QAbstractListModel { class IconList : public QAbstractListModel {
Q_OBJECT Q_OBJECT
public: public:
explicit IconList(const QStringList& builtinPaths, QString path, QObject* parent = 0); explicit IconList(const QStringList& builtinPaths, const QString& path, QObject* parent = 0);
virtual ~IconList() {}; virtual ~IconList() {};
QIcon getIcon(const QString& key) const; QIcon getIcon(const QString& key) const;
@ -72,6 +72,7 @@ class IconList : public QAbstractListModel {
bool deleteIcon(const QString& key); bool deleteIcon(const QString& key);
bool trashIcon(const QString& key); bool trashIcon(const QString& key);
bool iconFileExists(const QString& key) const; bool iconFileExists(const QString& key) const;
QString iconDirectory(const QString& key) const;
void installIcons(const QStringList& iconFiles); void installIcons(const QStringList& iconFiles);
void installIcon(const QString& file, const QString& name); void installIcon(const QString& file, const QString& name);
@ -91,18 +92,20 @@ class IconList : public QAbstractListModel {
IconList& operator=(const IconList&) = delete; IconList& operator=(const IconList&) = delete;
void reindex(); void reindex();
void sortIconList(); void sortIconList();
bool addPathRecursively(const QString& path);
QStringList getIconFilePaths() const;
public slots: public slots:
void directoryChanged(const QString& path); void directoryChanged(const QString& path);
protected slots: protected slots:
void fileChanged(const QString& path); void fileChanged(const QString& path);
void SettingChanged(const Setting& setting, QVariant value); void SettingChanged(const Setting& setting, const QVariant& value);
private: private:
shared_qobject_ptr<QFileSystemWatcher> m_watcher; shared_qobject_ptr<QFileSystemWatcher> m_watcher;
bool is_watching; bool m_isWatching;
QMap<QString, int> name_index; QMap<QString, int> m_nameIndex;
QVector<MMCIcon> icons; QVector<MMCIcon> m_icons;
QDir m_dir; QDir m_dir;
}; };

View File

@ -39,7 +39,7 @@
#include "FileSystem.h" #include "FileSystem.h"
namespace { namespace {
static const QStringList validIconExtensions = { { "svg", "png", "ico", "gif", "jpg", "jpeg" } }; static const QStringList validIconExtensions = { { "svg", "png", "ico", "gif", "jpg", "jpeg", "webp" } };
} }
namespace IconUtils { namespace IconUtils {

View File

@ -40,14 +40,15 @@
#include <QMap> #include <QMap>
#include <QProcess> #include <QProcess>
#include "Application.h"
#include "Commandline.h" #include "Commandline.h"
#include "FileSystem.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)
: Task(), 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(); QString checkerJar = JavaUtils::getJavaCheckPath();
@ -72,7 +73,7 @@ void JavaChecker::performCheck()
if (m_maxMem != 0) { if (m_maxMem != 0) {
args << QString("-Xmx%1m").arg(m_maxMem); 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); args << QString("-XX:PermSize=%1m").arg(m_permGen);
} }
@ -115,11 +116,10 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
QProcessPtr _process = process; QProcessPtr _process = process;
process.reset(); process.reset();
JavaCheckResult result; Result result = {
{ m_path,
result.path = m_path; m_id,
result.id = m_id; };
}
result.errorLog = m_stderr; result.errorLog = m_stderr;
result.outLog = m_stdout; result.outLog = m_stdout;
qDebug() << "STDOUT" << 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; qDebug() << "Java checker finished with status" << status << "exit code" << exitcode;
if (status == QProcess::CrashExit || exitcode == 1) { if (status == QProcess::CrashExit || exitcode == 1) {
result.validity = JavaCheckResult::Validity::Errored; result.validity = Result::Validity::Errored;
emit checkFinished(result); emit checkFinished(result);
emitSucceeded();
return; return;
} }
@ -161,17 +162,18 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
} }
if (!results.contains("os.arch") || !results.contains("java.version") || !results.contains("java.vendor") || !success) { 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); emit checkFinished(result);
emitSucceeded();
return; return;
} }
auto os_arch = results["os.arch"]; auto os_arch = results["os.arch"];
auto java_version = results["java.version"]; auto java_version = results["java.version"];
auto java_vendor = results["java.vendor"]; auto java_vendor = results["java.vendor"];
bool is_64 = os_arch == "x86_64" || os_arch == "amd64" || os_arch == "aarch64" || os_arch == "arm64"; bool is_64 = os_arch == "x86_64" || os_arch == "amd64" || os_arch == "aarch64" || os_arch == "arm64" || os_arch == "riscv64";
result.validity = JavaCheckResult::Validity::Valid; result.validity = Result::Validity::Valid;
result.is_64bit = is_64; result.is_64bit = is_64;
result.mojangPlatform = is_64 ? "64" : "32"; result.mojangPlatform = is_64 ? "64" : "32";
result.realPlatform = os_arch; result.realPlatform = os_arch;
@ -179,6 +181,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
result.javaVendor = java_vendor; result.javaVendor = java_vendor;
qDebug() << "Java checker succeeded."; qDebug() << "Java checker succeeded.";
emit checkFinished(result); emit checkFinished(result);
emitSucceeded();
} }
void JavaChecker::error(QProcess::ProcessError err) void JavaChecker::error(QProcess::ProcessError err)
@ -190,15 +193,9 @@ void JavaChecker::error(QProcess::ProcessError err)
qDebug() << "Native environment:"; qDebug() << "Native environment:";
qDebug() << QProcessEnvironment::systemEnvironment().toStringList(); qDebug() << QProcessEnvironment::systemEnvironment().toStringList();
killTimer.stop(); killTimer.stop();
JavaCheckResult result; emit checkFinished({ m_path, m_id });
{
result.path = m_path;
result.id = m_id;
}
emit checkFinished(result);
return;
} }
emitSucceeded();
} }
void JavaChecker::timeout() void JavaChecker::timeout()

View File

@ -1,51 +1,52 @@
#pragma once #pragma once
#include <QProcess> #include <QProcess>
#include <QTimer> #include <QTimer>
#include <memory>
#include "QObjectPtr.h"
#include "JavaVersion.h" #include "JavaVersion.h"
#include "QObjectPtr.h"
#include "tasks/Task.h"
class JavaChecker; class JavaChecker : public Task {
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<QProcess>;
using JavaCheckerPtr = shared_qobject_ptr<JavaChecker>;
class JavaChecker : public QObject {
Q_OBJECT Q_OBJECT
public: public:
explicit JavaChecker(QObject* parent = 0); using QProcessPtr = shared_qobject_ptr<QProcess>;
void performCheck(); using Ptr = shared_qobject_ptr<JavaChecker>;
QString m_path; struct Result {
QString m_args; QString path;
int m_id = 0; int id;
int m_minMem = 0; QString mojangPlatform;
int m_maxMem = 0; QString realPlatform;
int m_permGen = 64; 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);
signals: signals:
void checkFinished(JavaCheckResult result); void checkFinished(const Result& result);
protected:
virtual void executeTask() override;
private: private:
QProcessPtr process; QProcessPtr process;
QTimer killTimer; QTimer killTimer;
QString m_stdout; QString m_stdout;
QString m_stderr; 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 timeout();
void finished(int exitcode, QProcess::ExitStatus); void finished(int exitcode, QProcess::ExitStatus);
void error(QProcess::ProcessError); void error(QProcess::ProcessError);

View File

@ -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 <QDebug>
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();
}
}

View File

@ -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 <QtNetwork>
#include "JavaChecker.h"
#include "tasks/Task.h"
class JavaCheckerJob;
using JavaCheckerJobPtr = shared_qobject_ptr<JavaCheckerJob>;
// 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<JavaCheckResult> getResults() { return javaresults; }
private slots:
void partFinished(JavaCheckResult result);
protected:
virtual void executeTask() override;
private:
QString m_job_name;
QList<JavaCheckerPtr> javacheckers;
QList<JavaCheckResult> javaresults;
int num_finished = 0;
};

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> * Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View File

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> * Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -40,6 +40,7 @@ struct JavaInstall : public BaseVersion {
QString arch; QString arch;
QString path; QString path;
bool recommended = false; bool recommended = false;
bool is_64bit = false;
}; };
using JavaInstallPtr = std::shared_ptr<JavaInstall>; using JavaInstallPtr = std::shared_ptr<JavaInstall>;

View File

@ -38,13 +38,17 @@
#include <QtXml> #include <QtXml>
#include <QDebug> #include <QDebug>
#include <algorithm>
#include "java/JavaCheckerJob.h" #include "Application.h"
#include "java/JavaChecker.h"
#include "java/JavaInstallList.h" #include "java/JavaInstallList.h"
#include "java/JavaUtils.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() Task::Ptr JavaInstallList::getLoadTask()
{ {
@ -55,7 +59,7 @@ Task::Ptr JavaInstallList::getLoadTask()
Task::Ptr JavaInstallList::getCurrentTask() Task::Ptr JavaInstallList::getCurrentTask()
{ {
if (m_status == Status::InProgress) { if (m_status == Status::InProgress) {
return m_loadTask; return m_load_task;
} }
return nullptr; return nullptr;
} }
@ -64,8 +68,8 @@ void JavaInstallList::load()
{ {
if (m_status != Status::InProgress) { if (m_status != Status::InProgress) {
m_status = Status::InProgress; m_status = Status::InProgress;
m_loadTask.reset(new JavaListLoadTask(this)); m_load_task.reset(new JavaListLoadTask(this, m_only_managed_versions));
m_loadTask->start(); m_load_task->start();
} }
} }
@ -106,7 +110,7 @@ QVariant JavaInstallList::data(const QModelIndex& index, int role) const
return version->recommended; return version->recommended;
case PathRole: case PathRole:
return version->path; return version->path;
case ArchitectureRole: case CPUArchitectureRole:
return version->arch; return version->arch;
default: default:
return QVariant(); return QVariant();
@ -115,7 +119,7 @@ QVariant JavaInstallList::data(const QModelIndex& index, int role) const
BaseVersionList::RoleList JavaInstallList::providesRoles() const BaseVersionList::RoleList JavaInstallList::providesRoles() const
{ {
return { VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, PathRole, ArchitectureRole }; return { VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, PathRole, CPUArchitectureRole };
} }
void JavaInstallList::updateListData(QList<BaseVersion::Ptr> versions) void JavaInstallList::updateListData(QList<BaseVersion::Ptr> versions)
@ -129,7 +133,7 @@ void JavaInstallList::updateListData(QList<BaseVersion::Ptr> versions)
} }
endResetModel(); endResetModel();
m_status = Status::Done; m_status = Status::Done;
m_loadTask.reset(); m_load_task.reset();
} }
bool sortJavas(BaseVersion::Ptr left, BaseVersion::Ptr right) bool sortJavas(BaseVersion::Ptr left, BaseVersion::Ptr right)
@ -146,35 +150,30 @@ void JavaInstallList::sortVersions()
endResetModel(); endResetModel();
} }
JavaListLoadTask::JavaListLoadTask(JavaInstallList* vlist) : Task() JavaListLoadTask::JavaListLoadTask(JavaInstallList* vlist, bool onlyManagedVersions) : Task(), m_only_managed_versions(onlyManagedVersions)
{ {
m_list = vlist; m_list = vlist;
m_currentRecommended = NULL; m_current_recommended = NULL;
} }
JavaListLoadTask::~JavaListLoadTask() {}
void JavaListLoadTask::executeTask() void JavaListLoadTask::executeTask()
{ {
setStatus(tr("Detecting Java installations...")); setStatus(tr("Detecting Java installations..."));
JavaUtils ju; JavaUtils ju;
QList<QString> candidate_paths = ju.FindJavaPaths(); QList<QString> candidate_paths = m_only_managed_versions ? getPrismJavaBundle() : ju.FindJavaPaths();
m_job.reset(new JavaCheckerJob("Java detection")); ConcurrentTask::Ptr job(new ConcurrentTask("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::finished, this, &JavaListLoadTask::javaCheckerFinished);
connect(m_job.get(), &Task::progress, this, &Task::setProgress); connect(m_job.get(), &Task::progress, this, &Task::setProgress);
qDebug() << "Probing the following Java paths: "; qDebug() << "Probing the following Java paths: ";
int id = 0; int id = 0;
for (QString candidate : candidate_paths) { for (QString candidate : candidate_paths) {
qDebug() << " " << candidate; auto checker = new JavaChecker(candidate, "", 0, 0, 0, id);
connect(checker, &JavaChecker::checkFinished, [this](const JavaChecker::Result& result) { m_results << result; });
auto candidate_checker = new JavaChecker(); job->addTask(Task::Ptr(checker));
candidate_checker->m_path = candidate;
candidate_checker->m_id = id;
m_job->addJavaCheckerAction(JavaCheckerPtr(candidate_checker));
id++; id++;
} }
@ -184,16 +183,17 @@ void JavaListLoadTask::executeTask()
void JavaListLoadTask::javaCheckerFinished() void JavaListLoadTask::javaCheckerFinished()
{ {
QList<JavaInstallPtr> candidates; QList<JavaInstallPtr> 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:"; qDebug() << "Found the following valid Java installations:";
for (JavaCheckResult result : results) { for (auto result : m_results) {
if (result.validity == JavaCheckResult::Validity::Valid) { if (result.validity == JavaChecker::Result::Validity::Valid) {
JavaInstallPtr javaVersion(new JavaInstall()); JavaInstallPtr javaVersion(new JavaInstall());
javaVersion->id = result.javaVersion; javaVersion->id = result.javaVersion;
javaVersion->arch = result.realPlatform; javaVersion->arch = result.realPlatform;
javaVersion->path = result.path; javaVersion->path = result.path;
javaVersion->is_64bit = result.is_64bit;
candidates.append(javaVersion); candidates.append(javaVersion);
qDebug() << " " << javaVersion->id.toString() << javaVersion->arch << javaVersion->path; qDebug() << " " << javaVersion->id.toString() << javaVersion->arch << javaVersion->path;

View File

@ -19,9 +19,9 @@
#include <QObject> #include <QObject>
#include "BaseVersionList.h" #include "BaseVersionList.h"
#include "java/JavaChecker.h"
#include "tasks/Task.h" #include "tasks/Task.h"
#include "JavaCheckerJob.h"
#include "JavaInstall.h" #include "JavaInstall.h"
#include "QObjectPtr.h" #include "QObjectPtr.h"
@ -33,7 +33,7 @@ class JavaInstallList : public BaseVersionList {
enum class Status { NotDone, InProgress, Done }; enum class Status { NotDone, InProgress, Done };
public: public:
explicit JavaInstallList(QObject* parent = 0); explicit JavaInstallList(QObject* parent = 0, bool onlyManagedVersions = false);
[[nodiscard]] Task::Ptr getLoadTask() override; [[nodiscard]] Task::Ptr getLoadTask() override;
bool isLoaded() override; bool isLoaded() override;
@ -53,23 +53,27 @@ class JavaInstallList : public BaseVersionList {
protected: protected:
Status m_status = Status::NotDone; Status m_status = Status::NotDone;
shared_qobject_ptr<JavaListLoadTask> m_loadTask; shared_qobject_ptr<JavaListLoadTask> m_load_task;
QList<BaseVersion::Ptr> m_vlist; QList<BaseVersion::Ptr> m_vlist;
bool m_only_managed_versions;
}; };
class JavaListLoadTask : public Task { class JavaListLoadTask : public Task {
Q_OBJECT Q_OBJECT
public: public:
explicit JavaListLoadTask(JavaInstallList* vlist); explicit JavaListLoadTask(JavaInstallList* vlist, bool onlyManagedVersions = false);
virtual ~JavaListLoadTask(); virtual ~JavaListLoadTask() = default;
protected:
void executeTask() override; void executeTask() override;
public slots: public slots:
void javaCheckerFinished(); void javaCheckerFinished();
protected: protected:
shared_qobject_ptr<JavaCheckerJob> m_job; Task::Ptr m_job;
JavaInstallList* m_list; JavaInstallList* m_list;
JavaInstall* m_currentRecommended; JavaInstall* m_current_recommended;
QList<JavaChecker::Result> m_results;
bool m_only_managed_versions;
}; };

View File

@ -0,0 +1,128 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "java/JavaMetadata.h"
#include <memory>
#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<Metadata>();
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<Metadata&>(a));
} catch (const std::bad_cast& e) {
return BaseVersion::operator<(a);
}
}
bool Metadata::operator>(BaseVersion& a)
{
try {
return operator>(dynamic_cast<Metadata&>(a));
} catch (const std::bad_cast& e) {
return BaseVersion::operator>(a);
}
}
} // namespace Java

View File

@ -0,0 +1,64 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QDateTime>
#include <QJsonObject>
#include <QString>
#include <memory>
#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<Metadata>;
DownloadType parseDownloadType(QString javaDownload);
QString downloadTypeToString(DownloadType javaDownload);
MetadataPtr parseJavaMeta(const QJsonObject& libObj);
} // namespace Java

View File

@ -102,6 +102,8 @@ QProcessEnvironment CleanEnviroment()
QString newValue = stripVariableEntries(key, value, rawenv.value("LAUNCHER_" + key)); QString newValue = stripVariableEntries(key, value, rawenv.value("LAUNCHER_" + key));
qDebug() << "Env: stripped" << key << value << "to" << newValue; qDebug() << "Env: stripped" << key << value << "to" << newValue;
value = newValue;
} }
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
// Strip IBus // Strip IBus
@ -347,6 +349,7 @@ QList<QString> JavaUtils::FindJavaPaths()
} }
candidates.append(getMinecraftJavaBundle()); candidates.append(getMinecraftJavaBundle());
candidates.append(getPrismJavaBundle());
candidates = addJavasFromEnv(candidates); candidates = addJavasFromEnv(candidates);
candidates.removeDuplicates(); candidates.removeDuplicates();
return candidates; return candidates;
@ -391,6 +394,7 @@ QList<QString> JavaUtils::FindJavaPaths()
} }
javas.append(getMinecraftJavaBundle()); javas.append(getMinecraftJavaBundle());
javas.append(getPrismJavaBundle());
javas = addJavasFromEnv(javas); javas = addJavasFromEnv(javas);
javas.removeDuplicates(); javas.removeDuplicates();
return javas; return javas;
@ -401,12 +405,17 @@ QList<QString> JavaUtils::FindJavaPaths()
{ {
QList<QString> javas; QList<QString> javas;
javas.append(this->GetDefaultJava()->path); javas.append(this->GetDefaultJava()->path);
auto scanJavaDir = [&](const QString& dirPath) { auto scanJavaDir = [&javas](
const QString& dirPath,
const std::function<bool(const QFileInfo&)>& filter = [](const QFileInfo&) { return true; }) {
QDir dir(dirPath); QDir dir(dirPath);
if (!dir.exists()) if (!dir.exists())
return; return;
auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
for (auto& entry : entries) { for (auto& entry : entries) {
if (!filter(entry))
continue;
QString prefix; QString prefix;
prefix = entry.canonicalFilePath(); prefix = entry.canonicalFilePath();
javas.append(FS::PathCombine(prefix, "jre/bin/java")); javas.append(FS::PathCombine(prefix, "jre/bin/java"));
@ -415,7 +424,7 @@ QList<QString> JavaUtils::FindJavaPaths()
}; };
// java installed in a snap is installed in the standard directory, but underneath $SNAP // java installed in a snap is installed in the standard directory, but underneath $SNAP
auto snap = qEnvironmentVariable("SNAP"); auto snap = qEnvironmentVariable("SNAP");
auto scanJavaDirs = [&](const QString& dirPath) { auto scanJavaDirs = [scanJavaDir, snap](const QString& dirPath) {
scanJavaDir(dirPath); scanJavaDir(dirPath);
if (!snap.isNull()) { if (!snap.isNull()) {
scanJavaDir(snap + dirPath); scanJavaDir(snap + dirPath);
@ -429,9 +438,19 @@ QList<QString> JavaUtils::FindJavaPaths()
scanJavaDirs("/usr/lib64/jvm"); scanJavaDirs("/usr/lib64/jvm");
scanJavaDirs("/usr/lib32/jvm"); scanJavaDirs("/usr/lib32/jvm");
// Gentoo's locations for openjdk and openjdk-bin respectively // Gentoo's locations for openjdk and openjdk-bin respectively
scanJavaDir("/usr/lib64"); auto gentooFilter = [](const QFileInfo& info) {
scanJavaDir("/usr/lib"); QString fileName = info.fileName();
scanJavaDir("/opt"); return fileName.startsWith("openjdk-") || fileName.startsWith("openj9-");
};
// AOSC OS's locations for openjdk
auto aoscFilter = [](const QFileInfo& info) {
QString fileName = info.fileName();
return fileName == "java" || fileName.startsWith("java-");
};
scanJavaDir("/usr/lib64", gentooFilter);
scanJavaDir("/usr/lib", gentooFilter);
scanJavaDir("/opt", gentooFilter);
scanJavaDir("/usr/lib", aoscFilter);
// javas stored in Prism Launcher's folder // javas stored in Prism Launcher's folder
scanJavaDirs("java"); scanJavaDirs("java");
// manually installed JDKs in /opt // manually installed JDKs in /opt
@ -454,6 +473,7 @@ QList<QString> JavaUtils::FindJavaPaths()
scanJavaDirs(FS::PathCombine(home, ".gradle/jdks")); scanJavaDirs(FS::PathCombine(home, ".gradle/jdks"));
javas.append(getMinecraftJavaBundle()); javas.append(getMinecraftJavaBundle());
javas.append(getPrismJavaBundle());
javas = addJavasFromEnv(javas); javas = addJavasFromEnv(javas);
javas.removeDuplicates(); javas.removeDuplicates();
return javas; return javas;
@ -467,6 +487,8 @@ QList<QString> JavaUtils::FindJavaPaths()
javas.append(this->GetDefaultJava()->path); javas.append(this->GetDefaultJava()->path);
javas.append(getMinecraftJavaBundle()); javas.append(getMinecraftJavaBundle());
javas.append(getPrismJavaBundle());
javas.removeDuplicates();
return addJavasFromEnv(javas); return addJavasFromEnv(javas);
} }
#endif #endif
@ -478,12 +500,10 @@ QString JavaUtils::getJavaCheckPath()
QStringList getMinecraftJavaBundle() QStringList getMinecraftJavaBundle()
{ {
QString executable = "java";
QStringList processpaths; QStringList processpaths;
#if defined(Q_OS_OSX) #if defined(Q_OS_OSX)
processpaths << FS::PathCombine(QDir::homePath(), FS::PathCombine("Library", "Application Support", "minecraft", "runtime")); processpaths << FS::PathCombine(QDir::homePath(), FS::PathCombine("Library", "Application Support", "minecraft", "runtime"));
#elif defined(Q_OS_WIN32) #elif defined(Q_OS_WIN32)
executable += "w.exe";
auto appDataPath = QProcessEnvironment::systemEnvironment().value("APPDATA", ""); auto appDataPath = QProcessEnvironment::systemEnvironment().value("APPDATA", "");
processpaths << FS::PathCombine(QFileInfo(appDataPath).absoluteFilePath(), ".minecraft", "runtime"); processpaths << FS::PathCombine(QFileInfo(appDataPath).absoluteFilePath(), ".minecraft", "runtime");
@ -508,7 +528,7 @@ QStringList getMinecraftJavaBundle()
auto binFound = false; auto binFound = false;
for (auto& entry : entries) { for (auto& entry : entries) {
if (entry.baseName() == "bin") { if (entry.baseName() == "bin") {
javas.append(FS::PathCombine(entry.canonicalFilePath(), executable)); javas.append(FS::PathCombine(entry.canonicalFilePath(), JavaUtils::javaExecutable));
binFound = true; binFound = true;
break; break;
} }
@ -521,3 +541,33 @@ QStringList getMinecraftJavaBundle()
} }
return javas; return javas;
} }
#if defined(Q_OS_WIN32)
const QString JavaUtils::javaExecutable = "javaw.exe";
#else
const QString JavaUtils::javaExecutable = "java";
#endif
QStringList getPrismJavaBundle()
{
QList<QString> javas;
auto scanDir = [&javas](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 = [scanDir](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;
}

View File

@ -15,10 +15,9 @@
#pragma once #pragma once
#include <QProcess>
#include <QStringList> #include <QStringList>
#include "java/JavaInstall.h"
#include "JavaChecker.h"
#include "JavaInstallList.h"
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#include <windows.h> #include <windows.h>
@ -27,6 +26,7 @@
QString stripVariableEntries(QString name, QString target, QString remove); QString stripVariableEntries(QString name, QString target, QString remove);
QProcessEnvironment CleanEnviroment(); QProcessEnvironment CleanEnviroment();
QStringList getMinecraftJavaBundle(); QStringList getMinecraftJavaBundle();
QStringList getPrismJavaBundle();
class JavaUtils : public QObject { class JavaUtils : public QObject {
Q_OBJECT Q_OBJECT
@ -42,4 +42,5 @@ class JavaUtils : public QObject {
#endif #endif
static QString getJavaCheckPath(); static QString getJavaCheckPath();
static const QString javaExecutable;
}; };

View File

@ -43,12 +43,18 @@ QString JavaVersion::toString() const
return m_string; return m_string;
} }
bool JavaVersion::requiresPermGen() bool JavaVersion::requiresPermGen() const
{ {
return !m_parseable || m_major < 8; return !m_parseable || m_major < 8;
} }
bool JavaVersion::isModular() bool JavaVersion::defaultsToUtf8() const
{
// starting from Java 18, UTF-8 is the default charset: https://openjdk.org/jeps/400
return m_parseable && m_major >= 18;
}
bool JavaVersion::isModular() const
{ {
return m_parseable && m_major >= 9; return m_parseable && m_major >= 9;
} }
@ -59,12 +65,6 @@ bool JavaVersion::operator<(const JavaVersion& rhs)
auto major = m_major; auto major = m_major;
auto rmajor = rhs.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) if (major < rmajor)
return true; return true;
if (major > rmajor) if (major > rmajor)
@ -109,3 +109,24 @@ bool JavaVersion::operator>(const JavaVersion& rhs)
{ {
return (!operator<(rhs)) && (!operator==(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(".");
}

View File

@ -16,6 +16,7 @@ class JavaVersion {
public: public:
JavaVersion() {} JavaVersion() {}
JavaVersion(const QString& rhs); JavaVersion(const QString& rhs);
JavaVersion(int major, int minor, int security, int build = 0, QString name = "");
JavaVersion& operator=(const QString& rhs); JavaVersion& operator=(const QString& rhs);
@ -23,21 +24,24 @@ class JavaVersion {
bool operator==(const JavaVersion& rhs); bool operator==(const JavaVersion& rhs);
bool operator>(const JavaVersion& rhs); bool operator>(const JavaVersion& rhs);
bool requiresPermGen(); bool requiresPermGen() const;
bool defaultsToUtf8() const;
bool isModular(); bool isModular() const;
QString toString() const; QString toString() const;
int major() { return m_major; } int major() const { return m_major; }
int minor() { return m_minor; } int minor() const { return m_minor; }
int security() { return m_security; } int security() const { return m_security; }
QString build() const { return m_prerelease; }
QString name() const { return m_name; }
private: private:
QString m_string; QString m_string;
int m_major = 0; int m_major = 0;
int m_minor = 0; int m_minor = 0;
int m_security = 0; int m_security = 0;
QString m_name = "";
bool m_parseable = false; bool m_parseable = false;
QString m_prerelease; QString m_prerelease;
}; };

View File

@ -0,0 +1,141 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "java/download/ArchiveDownloadTask.h"
#include <quazip.h>
#include <memory>
#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<NetJob>(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<QuaZip>(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<MMCZip::ExtractZipTask>(zip, m_final_path, files[0]);
auto progressStep = std::make_shared<TaskStepProgress>();
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

View File

@ -0,0 +1,45 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QUrl>
#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

View File

@ -0,0 +1,137 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#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<NetJob>(QString("JRE::DownloadJava"), APPLICATION->network());
auto files = std::make_shared<QByteArray>();
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<File> 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 *nix only !
auto path = Json::ensureString(meta, "target");
if (!path.isEmpty()) {
QFile::link(path, 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<NetJob>("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

View File

@ -0,0 +1,46 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QUrl>
#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

View File

@ -0,0 +1,81 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "java/download/SymlinkTask.h"
#include <QFileInfo>
#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<FS::LinkPair> 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

View File

@ -0,0 +1,36 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#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

View File

@ -16,9 +16,8 @@
#include "LaunchStep.h" #include "LaunchStep.h"
#include "LaunchTask.h" #include "LaunchTask.h"
void LaunchStep::bind(LaunchTask* parent) LaunchStep::LaunchStep(LaunchTask* parent) : Task(), m_parent(parent)
{ {
m_parent = parent;
connect(this, &LaunchStep::readyForLaunch, parent, &LaunchTask::onReadyForLaunch); connect(this, &LaunchStep::readyForLaunch, parent, &LaunchTask::onReadyForLaunch);
connect(this, &LaunchStep::logLine, parent, &LaunchTask::onLogLine); connect(this, &LaunchStep::logLine, parent, &LaunchTask::onLogLine);
connect(this, &LaunchStep::logLines, parent, &LaunchTask::onLogLines); connect(this, &LaunchStep::logLines, parent, &LaunchTask::onLogLines);

Some files were not shown because too many files have changed in this diff Show More