mirror of
https://github.com/PrismLauncher/PrismLauncher.git
synced 2025-06-11 20:57:48 +02:00
Merge branch 'develop' into data-packs
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
This commit is contained in:
@ -1,4 +1,5 @@
|
|||||||
Checks:
|
Checks:
|
||||||
- modernize-use-using
|
- modernize-use-using
|
||||||
|
- readability-avoid-const-params-in-decls
|
||||||
|
|
||||||
SystemHeaders: false
|
SystemHeaders: false
|
||||||
|
41
.github/scripts/prepare_JREs.sh
vendored
41
.github/scripts/prepare_JREs.sh
vendored
@ -1,41 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
URL_JDK8="https://api.adoptium.net/v3/binary/version/jdk8u312-b07/linux/x64/jre/hotspot/normal/eclipse"
|
|
||||||
URL_JDK17="https://api.adoptium.net/v3/binary/latest/17/ga/linux/x64/jre/hotspot/normal/eclipse"
|
|
||||||
|
|
||||||
mkdir -p JREs
|
|
||||||
pushd JREs
|
|
||||||
|
|
||||||
wget --content-disposition "$URL_JDK8"
|
|
||||||
wget --content-disposition "$URL_JDK17"
|
|
||||||
|
|
||||||
for file in *;
|
|
||||||
do
|
|
||||||
mkdir temp
|
|
||||||
|
|
||||||
re='(OpenJDK([[:digit:]]+)U-jre_x64_linux_hotspot_([[:digit:]]+)(.*).tar.gz)'
|
|
||||||
if [[ $file =~ $re ]];
|
|
||||||
then
|
|
||||||
version_major=${BASH_REMATCH[2]}
|
|
||||||
version_trailing=${BASH_REMATCH[4]}
|
|
||||||
|
|
||||||
if [ $version_major = 17 ];
|
|
||||||
then
|
|
||||||
hyphen='-'
|
|
||||||
else
|
|
||||||
hyphen=''
|
|
||||||
fi
|
|
||||||
|
|
||||||
version_edit=$(echo $version_trailing | sed -e 's/_/+/g' | sed -e 's/b/-b/g')
|
|
||||||
dir_name=jdk$hyphen$version_major$version_edit-jre
|
|
||||||
mkdir jre$version_major
|
|
||||||
tar -xzf $file -C temp
|
|
||||||
pushd temp/$dir_name
|
|
||||||
cp -r . ../../jre$version_major
|
|
||||||
popd
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -rf temp
|
|
||||||
done
|
|
||||||
|
|
||||||
popd
|
|
3
.github/workflows/backport.yml
vendored
3
.github/workflows/backport.yml
vendored
@ -16,6 +16,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: write # for korthout/backport-action to create branch
|
contents: write # for korthout/backport-action to create branch
|
||||||
pull-requests: write # for korthout/backport-action to create PR to backport
|
pull-requests: write # for korthout/backport-action to create PR to backport
|
||||||
|
actions: write # for korthout/backport-action to create PR with workflow changes
|
||||||
name: Backport Pull Request
|
name: Backport Pull Request
|
||||||
if: github.repository_owner == 'PrismLauncher' && github.event.pull_request.merged == true && (github.event_name != 'labeled' || startsWith('backport', github.event.label.name))
|
if: github.repository_owner == 'PrismLauncher' && github.event.pull_request.merged == true && (github.event_name != 'labeled' || startsWith('backport', github.event.label.name))
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -24,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@v2.1.1
|
uses: korthout/backport-action@v3.0.2
|
||||||
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: |-
|
||||||
|
201
.github/workflows/build.yml
vendored
201
.github/workflows/build.yml
vendored
@ -21,8 +21,23 @@ on:
|
|||||||
WINDOWS_CODESIGN_PASSWORD:
|
WINDOWS_CODESIGN_PASSWORD:
|
||||||
description: Password for signing Windows builds
|
description: Password for signing Windows builds
|
||||||
required: false
|
required: false
|
||||||
CACHIX_AUTH_TOKEN:
|
APPLE_CODESIGN_CERT:
|
||||||
description: Private token for authenticating against Cachix cache
|
description: Certificate for signing macOS builds
|
||||||
|
required: false
|
||||||
|
APPLE_CODESIGN_PASSWORD:
|
||||||
|
description: Password for signing macOS builds
|
||||||
|
required: false
|
||||||
|
APPLE_CODESIGN_ID:
|
||||||
|
description: Certificate ID for signing macOS builds
|
||||||
|
required: false
|
||||||
|
APPLE_NOTARIZE_APPLE_ID:
|
||||||
|
description: Apple ID used for notarizing macOS builds
|
||||||
|
required: false
|
||||||
|
APPLE_NOTARIZE_TEAM_ID:
|
||||||
|
description: Team ID used for notarizing macOS builds
|
||||||
|
required: false
|
||||||
|
APPLE_NOTARIZE_PASSWORD:
|
||||||
|
description: Password used for notarizing macOS builds
|
||||||
required: false
|
required: false
|
||||||
GPG_PRIVATE_KEY:
|
GPG_PRIVATE_KEY:
|
||||||
description: Private key for AppImage signing
|
description: Private key for AppImage signing
|
||||||
@ -39,14 +54,17 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
- os: ubuntu-20.04
|
- os: ubuntu-20.04
|
||||||
qt_ver: 5
|
qt_ver: 5
|
||||||
|
qt_host: linux
|
||||||
|
qt_arch: ""
|
||||||
|
qt_version: "5.12.8"
|
||||||
|
qt_modules: "qtnetworkauth"
|
||||||
|
|
||||||
- os: ubuntu-20.04
|
- os: ubuntu-20.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.2.4"
|
||||||
qt_modules: "qt5compat qtimageformats"
|
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
||||||
qt_tools: ""
|
|
||||||
|
|
||||||
- os: windows-2022
|
- os: windows-2022
|
||||||
name: "Windows-MinGW-w64"
|
name: "Windows-MinGW-w64"
|
||||||
@ -60,10 +78,9 @@ jobs:
|
|||||||
vcvars_arch: "amd64"
|
vcvars_arch: "amd64"
|
||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: windows
|
qt_host: windows
|
||||||
qt_arch: ''
|
qt_arch: ""
|
||||||
qt_version: '6.6.0'
|
qt_version: "6.7.2"
|
||||||
qt_modules: 'qt5compat qtimageformats'
|
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
||||||
qt_tools: ''
|
|
||||||
|
|
||||||
- os: windows-2022
|
- os: windows-2022
|
||||||
name: "Windows-MSVC-arm64"
|
name: "Windows-MSVC-arm64"
|
||||||
@ -72,20 +89,18 @@ jobs:
|
|||||||
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_msvc2019_arm64"
|
||||||
qt_version: '6.6.0'
|
qt_version: "6.7.2"
|
||||||
qt_modules: 'qt5compat qtimageformats'
|
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
||||||
qt_tools: ''
|
|
||||||
|
|
||||||
- os: macos-12
|
- os: macos-12
|
||||||
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.6.0'
|
qt_version: "6.7.2"
|
||||||
qt_modules: 'qt5compat qtimageformats'
|
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
||||||
qt_tools: ''
|
|
||||||
|
|
||||||
- os: macos-12
|
- os: macos-12
|
||||||
name: macOS-Legacy
|
name: macOS-Legacy
|
||||||
@ -93,8 +108,7 @@ jobs:
|
|||||||
qt_ver: 5
|
qt_ver: 5
|
||||||
qt_host: mac
|
qt_host: mac
|
||||||
qt_version: "5.15.2"
|
qt_version: "5.15.2"
|
||||||
qt_modules: ""
|
qt_modules: "qtnetworkauth"
|
||||||
qt_tools: ""
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
@ -136,6 +150,7 @@ jobs:
|
|||||||
quazip-qt6:p
|
quazip-qt6:p
|
||||||
ccache:p
|
ccache:p
|
||||||
qt6-5compat:p
|
qt6-5compat:p
|
||||||
|
qt6-networkauth:p
|
||||||
cmark:p
|
cmark:p
|
||||||
|
|
||||||
- name: Force newer ccache
|
- name: Force newer ccache
|
||||||
@ -145,13 +160,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.10
|
uses: hendrikmuhs/ccache-action@v1.2.14
|
||||||
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@v3.3.2
|
uses: actions/cache@v4.0.2
|
||||||
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 }}
|
||||||
@ -192,11 +207,6 @@ jobs:
|
|||||||
brew update
|
brew update
|
||||||
brew install ninja extra-cmake-modules
|
brew install ninja extra-cmake-modules
|
||||||
|
|
||||||
- name: Install Qt (Linux)
|
|
||||||
if: runner.os == 'Linux' && matrix.qt_ver != 6
|
|
||||||
run: |
|
|
||||||
sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5
|
|
||||||
|
|
||||||
- name: Install host Qt (Windows MSVC arm64)
|
- 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@v3
|
||||||
@ -208,20 +218,18 @@ jobs:
|
|||||||
target: "desktop"
|
target: "desktop"
|
||||||
arch: ""
|
arch: ""
|
||||||
modules: ${{ matrix.qt_modules }}
|
modules: ${{ matrix.qt_modules }}
|
||||||
tools: ${{ matrix.qt_tools }}
|
|
||||||
cache: ${{ inputs.is_qt_cached }}
|
cache: ${{ inputs.is_qt_cached }}
|
||||||
cache-key-prefix: host-qt-arm64-windows
|
cache-key-prefix: host-qt-arm64-windows
|
||||||
dir: ${{ github.workspace }}\HostQt
|
dir: ${{ github.workspace }}\HostQt
|
||||||
set-env: false
|
set-env: false
|
||||||
|
|
||||||
- name: Install Qt (macOS, Linux, Qt 6 & Windows MSVC)
|
- name: Install Qt (macOS, Linux & Windows MSVC)
|
||||||
if: runner.os == 'Linux' && matrix.qt_ver == 6 || runner.os == 'macOS' || (runner.os == 'Windows' && matrix.msystem == '')
|
if: matrix.msystem == ''
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v3
|
||||||
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: ${{ matrix.qt_host }}
|
|
||||||
target: "desktop"
|
target: "desktop"
|
||||||
arch: ${{ matrix.qt_arch }}
|
arch: ${{ matrix.qt_arch }}
|
||||||
modules: ${{ matrix.qt_modules }}
|
modules: ${{ matrix.qt_modules }}
|
||||||
@ -244,7 +252,6 @@ 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"
|
||||||
|
|
||||||
${{ github.workspace }}/.github/scripts/prepare_JREs.sh
|
|
||||||
sudo apt install libopengl0
|
sudo apt install libopengl0
|
||||||
|
|
||||||
- name: Add QT_HOST_PATH var (Windows MSVC arm64)
|
- name: Add QT_HOST_PATH var (Windows MSVC arm64)
|
||||||
@ -336,6 +343,20 @@ jobs:
|
|||||||
# PACKAGE BUILDS
|
# PACKAGE BUILDS
|
||||||
##
|
##
|
||||||
|
|
||||||
|
- name: Fetch codesign certificate (macOS)
|
||||||
|
if: runner.os == 'macOS'
|
||||||
|
run: |
|
||||||
|
echo '${{ secrets.APPLE_CODESIGN_CERT }}' | base64 --decode > codesign.p12
|
||||||
|
if [ -n '${{ secrets.APPLE_CODESIGN_ID }}' ]; then
|
||||||
|
security create-keychain -p '${{ secrets.APPLE_CODESIGN_PASSWORD }}' build.keychain
|
||||||
|
security default-keychain -s build.keychain
|
||||||
|
security unlock-keychain -p '${{ secrets.APPLE_CODESIGN_PASSWORD }}' build.keychain
|
||||||
|
security import codesign.p12 -k build.keychain -P '${{ secrets.APPLE_CODESIGN_PASSWORD }}' -T /usr/bin/codesign
|
||||||
|
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k '${{ secrets.APPLE_CODESIGN_PASSWORD }}' build.keychain
|
||||||
|
else
|
||||||
|
echo ":warning: Using ad-hoc code signing for macOS, as certificate was not present." >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Package (macOS)
|
- name: Package (macOS)
|
||||||
if: runner.os == 'macOS'
|
if: runner.os == 'macOS'
|
||||||
run: |
|
run: |
|
||||||
@ -343,9 +364,34 @@ jobs:
|
|||||||
|
|
||||||
cd ${{ env.INSTALL_DIR }}
|
cd ${{ env.INSTALL_DIR }}
|
||||||
chmod +x "PrismLauncher.app/Contents/MacOS/prismlauncher"
|
chmod +x "PrismLauncher.app/Contents/MacOS/prismlauncher"
|
||||||
sudo codesign --sign - --deep --force --entitlements "../program_info/App.entitlements" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
|
|
||||||
|
if [ -n '${{ secrets.APPLE_CODESIGN_ID }}' ]; then
|
||||||
|
APPLE_CODESIGN_ID='${{ secrets.APPLE_CODESIGN_ID }}'
|
||||||
|
else
|
||||||
|
APPLE_CODESIGN_ID='-'
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo codesign --sign "$APPLE_CODESIGN_ID" --deep --force --entitlements "../program_info/App.entitlements" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
|
||||||
mv "PrismLauncher.app" "Prism Launcher.app"
|
mv "PrismLauncher.app" "Prism Launcher.app"
|
||||||
tar -czf ../PrismLauncher.tar.gz *
|
|
||||||
|
- name: Notarize (macOS)
|
||||||
|
if: runner.os == 'macOS'
|
||||||
|
run: |
|
||||||
|
cd ${{ env.INSTALL_DIR }}
|
||||||
|
|
||||||
|
if [ -n '${{ secrets.APPLE_NOTARIZE_PASSWORD }}' ]; then
|
||||||
|
ditto -c -k --sequesterRsrc --keepParent "Prism Launcher.app" ../PrismLauncher.zip
|
||||||
|
xcrun notarytool submit ../PrismLauncher.zip \
|
||||||
|
--wait --progress \
|
||||||
|
--apple-id '${{ secrets.APPLE_NOTARIZE_APPLE_ID }}' \
|
||||||
|
--team-id '${{ secrets.APPLE_NOTARIZE_TEAM_ID }}' \
|
||||||
|
--password '${{ secrets.APPLE_NOTARIZE_PASSWORD }}'
|
||||||
|
|
||||||
|
xcrun stapler staple "Prism Launcher.app"
|
||||||
|
else
|
||||||
|
echo ":warning: Skipping notarization as credentials are not present." >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
ditto -c -k --sequesterRsrc --keepParent "Prism Launcher.app" ../PrismLauncher.zip
|
||||||
|
|
||||||
- name: Make Sparkle signature (macOS)
|
- name: Make Sparkle signature (macOS)
|
||||||
if: matrix.name == 'macOS'
|
if: matrix.name == 'macOS'
|
||||||
@ -353,7 +399,7 @@ jobs:
|
|||||||
if [ '${{ secrets.SPARKLE_ED25519_KEY }}' != '' ]; then
|
if [ '${{ secrets.SPARKLE_ED25519_KEY }}' != '' ]; then
|
||||||
brew install openssl@3
|
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.tar.gz -inkey ed25519-priv.pem | openssl base64 | tr -d \\n)
|
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)
|
||||||
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:
|
||||||
@ -379,12 +425,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cmake --install ${{ env.BUILD_DIR }} --config ${{ inputs.build_type }}
|
cmake --install ${{ env.BUILD_DIR }} --config ${{ inputs.build_type }}
|
||||||
|
|
||||||
cd ${{ env.INSTALL_DIR }}
|
|
||||||
if ("${{ matrix.qt_ver }}" -eq "5")
|
|
||||||
{
|
|
||||||
Copy-Item ${{ runner.workspace }}/Qt/Tools/OpenSSL/Win_x86/bin/libcrypto-1_1.dll -Destination libcrypto-1_1.dll
|
|
||||||
Copy-Item ${{ runner.workspace }}/Qt/Tools/OpenSSL/Win_x86/bin/libssl-1_1.dll -Destination libssl-1_1.dll
|
|
||||||
}
|
|
||||||
cd ${{ github.workspace }}
|
cd ${{ github.workspace }}
|
||||||
|
|
||||||
Get-ChildItem ${{ env.INSTALL_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt
|
Get-ChildItem ${{ env.INSTALL_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt
|
||||||
@ -437,26 +477,6 @@ jobs:
|
|||||||
":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY
|
":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY
|
||||||
}
|
}
|
||||||
|
|
||||||
- name: Package (Linux)
|
|
||||||
if: runner.os == 'Linux'
|
|
||||||
run: |
|
|
||||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_DIR }}
|
|
||||||
for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_DIR }}/manifest.txt
|
|
||||||
|
|
||||||
cd ${{ env.INSTALL_DIR }}
|
|
||||||
tar --owner root --group root -czf ../PrismLauncher.tar.gz *
|
|
||||||
|
|
||||||
- name: Package (Linux, portable)
|
|
||||||
if: runner.os == 'Linux'
|
|
||||||
run: |
|
|
||||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }}
|
|
||||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
|
|
||||||
for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt
|
|
||||||
|
|
||||||
|
|
||||||
cd ${{ env.INSTALL_PORTABLE_DIR }}
|
|
||||||
tar -czf ../PrismLauncher-portable.tar.gz *
|
|
||||||
|
|
||||||
- name: Package AppImage (Linux)
|
- name: Package AppImage (Linux)
|
||||||
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||||
shell: bash
|
shell: bash
|
||||||
@ -472,13 +492,9 @@ jobs:
|
|||||||
|
|
||||||
chmod +x linuxdeploy-*.AppImage
|
chmod +x linuxdeploy-*.AppImage
|
||||||
|
|
||||||
mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-{8,17}-openjdk
|
mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib
|
||||||
mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines
|
mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines
|
||||||
|
|
||||||
cp -r ${{ github.workspace }}/JREs/jre8/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk
|
|
||||||
|
|
||||||
cp -r ${{ github.workspace }}/JREs/jre17/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk
|
|
||||||
|
|
||||||
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.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
||||||
@ -486,10 +502,6 @@ jobs:
|
|||||||
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"
|
||||||
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64/server"
|
|
||||||
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64"
|
|
||||||
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk/lib/server"
|
|
||||||
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk/lib"
|
|
||||||
export LD_LIBRARY_PATH
|
export LD_LIBRARY_PATH
|
||||||
|
|
||||||
chmod +x AppImageUpdate-x86_64.AppImage
|
chmod +x AppImageUpdate-x86_64.AppImage
|
||||||
@ -511,76 +523,81 @@ jobs:
|
|||||||
|
|
||||||
mv "PrismLauncher-Linux-x86_64.AppImage" "PrismLauncher-Linux-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage"
|
mv "PrismLauncher-Linux-x86_64.AppImage" "PrismLauncher-Linux-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage"
|
||||||
|
|
||||||
|
- name: Package (Linux, portable)
|
||||||
|
if: runner.os == 'Linux'
|
||||||
|
run: |
|
||||||
|
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_PORTABLE_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DLauncher_BUILD_ARTIFACT=Linux-Qt${{ matrix.qt_ver }} -DINSTALL_BUNDLE=full -G Ninja
|
||||||
|
cmake --install ${{ env.BUILD_DIR }}
|
||||||
|
cmake --install ${{ env.BUILD_DIR }} --component portable
|
||||||
|
|
||||||
|
mkdir ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
|
cp /lib/x86_64-linux-gnu/libbz2.so.1.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
|
cp /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
|
cp /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
|
cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
|
cp /usr/lib/x86_64-linux-gnu/libffi.so.7 ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
|
mv ${{ env.INSTALL_PORTABLE_DIR }}/bin/*.so* ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||||
|
|
||||||
|
for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt
|
||||||
|
cd ${{ env.INSTALL_PORTABLE_DIR }}
|
||||||
|
tar -czf ../PrismLauncher-portable.tar.gz *
|
||||||
|
|
||||||
##
|
##
|
||||||
# UPLOAD BUILDS
|
# UPLOAD BUILDS
|
||||||
##
|
##
|
||||||
|
|
||||||
- name: Upload binary tarball (macOS)
|
- name: Upload binary tarball (macOS)
|
||||||
if: runner.os == 'macOS'
|
if: runner.os == 'macOS'
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}
|
name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||||
path: PrismLauncher.tar.gz
|
path: PrismLauncher.zip
|
||||||
|
|
||||||
- name: Upload binary zip (Windows)
|
- name: Upload binary zip (Windows)
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}
|
name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||||
path: ${{ env.INSTALL_DIR }}/**
|
path: ${{ env.INSTALL_DIR }}/**
|
||||||
|
|
||||||
- name: Upload binary zip (Windows, portable)
|
- name: Upload binary zip (Windows, portable)
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: PrismLauncher-${{ matrix.name }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
name: PrismLauncher-${{ matrix.name }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||||
path: ${{ env.INSTALL_PORTABLE_DIR }}/**
|
path: ${{ env.INSTALL_PORTABLE_DIR }}/**
|
||||||
|
|
||||||
- name: Upload installer (Windows)
|
- name: Upload installer (Windows)
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: PrismLauncher-${{ matrix.name }}-Setup-${{ env.VERSION }}-${{ inputs.build_type }}
|
name: PrismLauncher-${{ matrix.name }}-Setup-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||||
path: PrismLauncher-Setup.exe
|
path: PrismLauncher-Setup.exe
|
||||||
|
|
||||||
- name: Upload binary tarball (Linux, Qt 5)
|
|
||||||
if: runner.os == 'Linux' && matrix.qt_ver != 6
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: PrismLauncher-${{ runner.os }}-Qt5-${{ env.VERSION }}-${{ inputs.build_type }}
|
|
||||||
path: PrismLauncher.tar.gz
|
|
||||||
|
|
||||||
- name: Upload binary tarball (Linux, portable, Qt 5)
|
- name: Upload binary tarball (Linux, portable, Qt 5)
|
||||||
if: runner.os == 'Linux' && matrix.qt_ver != 6
|
if: runner.os == 'Linux' && matrix.qt_ver != 6
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: PrismLauncher-${{ runner.os }}-Qt5-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
name: PrismLauncher-${{ runner.os }}-Qt5-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||||
path: PrismLauncher-portable.tar.gz
|
path: PrismLauncher-portable.tar.gz
|
||||||
|
|
||||||
- name: Upload binary tarball (Linux, Qt 6)
|
|
||||||
if: runner.os == 'Linux' && matrix.qt_ver !=5
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: PrismLauncher-${{ runner.os }}-Qt6-${{ env.VERSION }}-${{ inputs.build_type }}
|
|
||||||
path: PrismLauncher.tar.gz
|
|
||||||
|
|
||||||
- name: Upload binary tarball (Linux, portable, Qt 6)
|
- name: Upload binary tarball (Linux, portable, Qt 6)
|
||||||
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: PrismLauncher-${{ runner.os }}-Qt6-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
name: PrismLauncher-${{ runner.os }}-Qt6-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||||
path: PrismLauncher-portable.tar.gz
|
path: PrismLauncher-portable.tar.gz
|
||||||
|
|
||||||
- name: Upload AppImage (Linux)
|
- name: Upload AppImage (Linux)
|
||||||
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
|
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
|
||||||
path: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
|
path: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
|
||||||
|
|
||||||
- name: Upload AppImage Zsync (Linux)
|
- name: Upload AppImage Zsync (Linux)
|
||||||
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage.zsync
|
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage.zsync
|
||||||
path: PrismLauncher-Linux-x86_64.AppImage.zsync
|
path: PrismLauncher-Linux-x86_64.AppImage.zsync
|
||||||
@ -594,7 +611,7 @@ jobs:
|
|||||||
flatpak:
|
flatpak:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: bilelmoussaoui/flatpak-github-actions:kde-5.15-22.08
|
image: bilelmoussaoui/flatpak-github-actions:kde-6.7
|
||||||
options: --privileged
|
options: --privileged
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
6
.github/workflows/codeql.yml
vendored
6
.github/workflows/codeql.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
|||||||
submodules: 'true'
|
submodules: 'true'
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
uses: github/codeql-action/init@v3
|
||||||
with:
|
with:
|
||||||
config-file: ./.github/codeql/codeql-config.yml
|
config-file: ./.github/codeql/codeql-config.yml
|
||||||
queries: security-and-quality
|
queries: security-and-quality
|
||||||
@ -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
|
sudo apt-get -y install ninja-build extra-cmake-modules scdoc qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 libqt5networkauth5 libqt5networkauth5-dev
|
||||||
|
|
||||||
- name: Configure and Build
|
- name: Configure and Build
|
||||||
run: |
|
run: |
|
||||||
@ -32,4 +32,4 @@ jobs:
|
|||||||
cmake --build build
|
cmake --build build
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
uses: github/codeql-action/analyze@v3
|
||||||
|
7
.github/workflows/trigger_builds.yml
vendored
7
.github/workflows/trigger_builds.yml
vendored
@ -32,6 +32,11 @@ jobs:
|
|||||||
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
|
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
|
||||||
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
|
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
|
||||||
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
|
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
|
||||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
APPLE_CODESIGN_CERT: ${{ secrets.APPLE_CODESIGN_CERT }}
|
||||||
|
APPLE_CODESIGN_PASSWORD: ${{ secrets.APPLE_CODESIGN_PASSWORD }}
|
||||||
|
APPLE_CODESIGN_ID: ${{ secrets.APPLE_CODESIGN_ID }}
|
||||||
|
APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }}
|
||||||
|
APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }}
|
||||||
|
APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }}
|
||||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||||
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
|
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
|
||||||
|
23
.github/workflows/trigger_release.yml
vendored
23
.github/workflows/trigger_release.yml
vendored
@ -16,7 +16,12 @@ jobs:
|
|||||||
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
|
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
|
||||||
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
|
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
|
||||||
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
|
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
|
||||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
APPLE_CODESIGN_CERT: ${{ secrets.APPLE_CODESIGN_CERT }}
|
||||||
|
APPLE_CODESIGN_PASSWORD: ${{ secrets.APPLE_CODESIGN_PASSWORD }}
|
||||||
|
APPLE_CODESIGN_ID: ${{ secrets.APPLE_CODESIGN_ID }}
|
||||||
|
APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }}
|
||||||
|
APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }}
|
||||||
|
APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }}
|
||||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||||
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
|
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
|
||||||
|
|
||||||
@ -32,7 +37,7 @@ jobs:
|
|||||||
submodules: "true"
|
submodules: "true"
|
||||||
path: "PrismLauncher-source"
|
path: "PrismLauncher-source"
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
- name: Grab and store version
|
- name: Grab and store version
|
||||||
run: |
|
run: |
|
||||||
tag_name=$(echo ${{ github.ref }} | grep -oE "[^/]+$")
|
tag_name=$(echo ${{ github.ref }} | grep -oE "[^/]+$")
|
||||||
@ -41,13 +46,11 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
mv ${{ github.workspace }}/PrismLauncher-source PrismLauncher-${{ env.VERSION }}
|
mv ${{ github.workspace }}/PrismLauncher-source PrismLauncher-${{ env.VERSION }}
|
||||||
mv PrismLauncher-Linux-Qt6-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
|
mv PrismLauncher-Linux-Qt6-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
|
||||||
mv PrismLauncher-Linux-Qt6*/PrismLauncher.tar.gz PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz
|
|
||||||
mv PrismLauncher-Linux-Qt5-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt5-Portable-${{ env.VERSION }}.tar.gz
|
mv PrismLauncher-Linux-Qt5-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt5-Portable-${{ env.VERSION }}.tar.gz
|
||||||
mv PrismLauncher-Linux-Qt5*/PrismLauncher.tar.gz PrismLauncher-Linux-Qt5-${{ env.VERSION }}.tar.gz
|
|
||||||
mv PrismLauncher-*.AppImage/PrismLauncher-*.AppImage PrismLauncher-Linux-x86_64.AppImage
|
mv PrismLauncher-*.AppImage/PrismLauncher-*.AppImage PrismLauncher-Linux-x86_64.AppImage
|
||||||
mv PrismLauncher-*.AppImage.zsync/PrismLauncher-*.AppImage.zsync PrismLauncher-Linux-x86_64.AppImage.zsync
|
mv PrismLauncher-*.AppImage.zsync/PrismLauncher-*.AppImage.zsync PrismLauncher-Linux-x86_64.AppImage.zsync
|
||||||
mv PrismLauncher-macOS-Legacy*/PrismLauncher.tar.gz PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
|
mv PrismLauncher-macOS-Legacy*/PrismLauncher.zip PrismLauncher-macOS-Legacy-${{ env.VERSION }}.zip
|
||||||
mv PrismLauncher-macOS*/PrismLauncher.tar.gz PrismLauncher-macOS-${{ env.VERSION }}.tar.gz
|
mv PrismLauncher-macOS*/PrismLauncher.zip PrismLauncher-macOS-${{ env.VERSION }}.zip
|
||||||
|
|
||||||
tar --exclude='.git' -czf PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }}
|
tar --exclude='.git' -czf PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }}
|
||||||
|
|
||||||
@ -79,7 +82,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
id: create_release
|
id: create_release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
tag_name: ${{ github.ref }}
|
tag_name: ${{ github.ref }}
|
||||||
@ -87,11 +90,9 @@ jobs:
|
|||||||
draft: true
|
draft: true
|
||||||
prerelease: false
|
prerelease: false
|
||||||
files: |
|
files: |
|
||||||
PrismLauncher-Linux-Qt5-${{ env.VERSION }}.tar.gz
|
|
||||||
PrismLauncher-Linux-Qt5-Portable-${{ env.VERSION }}.tar.gz
|
PrismLauncher-Linux-Qt5-Portable-${{ env.VERSION }}.tar.gz
|
||||||
PrismLauncher-Linux-x86_64.AppImage
|
PrismLauncher-Linux-x86_64.AppImage
|
||||||
PrismLauncher-Linux-x86_64.AppImage.zsync
|
PrismLauncher-Linux-x86_64.AppImage.zsync
|
||||||
PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz
|
|
||||||
PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
|
PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
|
||||||
PrismLauncher-Windows-MinGW-w64-${{ env.VERSION }}.zip
|
PrismLauncher-Windows-MinGW-w64-${{ env.VERSION }}.zip
|
||||||
PrismLauncher-Windows-MinGW-w64-Portable-${{ env.VERSION }}.zip
|
PrismLauncher-Windows-MinGW-w64-Portable-${{ env.VERSION }}.zip
|
||||||
@ -102,6 +103,6 @@ jobs:
|
|||||||
PrismLauncher-Windows-MSVC-${{ env.VERSION }}.zip
|
PrismLauncher-Windows-MSVC-${{ env.VERSION }}.zip
|
||||||
PrismLauncher-Windows-MSVC-Portable-${{ env.VERSION }}.zip
|
PrismLauncher-Windows-MSVC-Portable-${{ env.VERSION }}.zip
|
||||||
PrismLauncher-Windows-MSVC-Setup-${{ env.VERSION }}.exe
|
PrismLauncher-Windows-MSVC-Setup-${{ env.VERSION }}.exe
|
||||||
PrismLauncher-macOS-${{ env.VERSION }}.tar.gz
|
PrismLauncher-macOS-${{ env.VERSION }}.zip
|
||||||
PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
|
PrismLauncher-macOS-Legacy-${{ env.VERSION }}.zip
|
||||||
PrismLauncher-${{ env.VERSION }}.tar.gz
|
PrismLauncher-${{ env.VERSION }}.tar.gz
|
||||||
|
4
.github/workflows/update-flake.yml
vendored
4
.github/workflows/update-flake.yml
vendored
@ -17,9 +17,9 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@6a9a9e84a173d90b3ffb42c5ddaf9ea033fad011 # v23
|
- uses: cachix/install-nix-action@ba0dd844c9180cbf77aa72a116d6fbc515d0e87b # v27
|
||||||
|
|
||||||
- uses: DeterminateSystems/update-flake-lock@v20
|
- uses: DeterminateSystems/update-flake-lock@v23
|
||||||
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"
|
||||||
|
3
BUILD.md
3
BUILD.md
@ -1,3 +0,0 @@
|
|||||||
# Build Instructions
|
|
||||||
|
|
||||||
Full build instructions are available on [the website](https://prismlauncher.org/wiki/development/build-instructions/).
|
|
@ -178,7 +178,7 @@ set(Launcher_NEWS_OPEN_URL "https://prismlauncher.org/news" CACHE STRING "URL th
|
|||||||
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 version numbers ########
|
######## Set version numbers ########
|
||||||
set(Launcher_VERSION_MAJOR 8)
|
set(Launcher_VERSION_MAJOR 9)
|
||||||
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}")
|
||||||
@ -282,7 +282,7 @@ 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)
|
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml NetworkAuth)
|
||||||
|
|
||||||
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 +296,7 @@ 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)
|
find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat NetworkAuth)
|
||||||
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)
|
||||||
@ -377,12 +377,12 @@ if(UNIX AND APPLE)
|
|||||||
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_NAME}")
|
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_NAME}")
|
||||||
set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_NAME}")
|
set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_NAME}")
|
||||||
set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns)
|
set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns)
|
||||||
set(MACOSX_BUNDLE_COPYRIGHT "© 2022-2023 ${Launcher_Copyright_Mac}")
|
set(MACOSX_BUNDLE_COPYRIGHT "${Launcher_Copyright_Mac}")
|
||||||
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.1.0/Sparkle-2.1.0.tar.xz" CACHE STRING "URL to Sparkle release archive")
|
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_SHA256 "bf6ac1caa9f8d321d5784859c88da874f28412f37fb327bc21b7b14c5d61ef94" CACHE STRING "SHA256 checksum for Sparkle release archive")
|
set(MACOSX_SPARKLE_SHA256 "572dd67ae398a466f19f343a449e1890bac1ef74885b4739f68f979a8a89884b" 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
|
||||||
@ -417,7 +417,19 @@ elseif(UNIX)
|
|||||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_mrpack_MIMEInfo} DESTINATION ${KDE_INSTALL_MIMEDIR})
|
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_mrpack_MIMEInfo} DESTINATION ${KDE_INSTALL_MIMEDIR})
|
||||||
|
|
||||||
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/launcher/qtlogging.ini" DESTINATION "share/${Launcher_Name}")
|
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/launcher/qtlogging.ini" DESTINATION "share/${Launcher_Name}")
|
||||||
|
|
||||||
|
if (INSTALL_BUNDLE STREQUAL full)
|
||||||
|
set(PLUGIN_DEST_DIR "plugins")
|
||||||
|
set(BUNDLE_DEST_DIR ".")
|
||||||
|
set(RESOURCES_DEST_DIR ".")
|
||||||
|
|
||||||
|
# Apps to bundle
|
||||||
|
set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/${Launcher_APP_BINARY_NAME}")
|
||||||
|
|
||||||
|
# directories to look for dependencies
|
||||||
|
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
|
||||||
|
endif()
|
||||||
|
|
||||||
if(Launcher_ManPage)
|
if(Launcher_ManPage)
|
||||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_ManPage} DESTINATION "${KDE_INSTALL_MANDIR}/man6")
|
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_ManPage} DESTINATION "${KDE_INSTALL_MANDIR}/man6")
|
||||||
endif()
|
endif()
|
||||||
@ -504,15 +516,13 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
if(NOT cmark_FOUND)
|
if(NOT cmark_FOUND)
|
||||||
message(STATUS "Using bundled cmark")
|
message(STATUS "Using bundled cmark")
|
||||||
set(CMARK_STATIC ON CACHE BOOL "Build static libcmark library" FORCE)
|
set(BUILD_TESTING 0)
|
||||||
set(CMARK_SHARED OFF CACHE BOOL "Build shared libcmark library" FORCE)
|
set(BUILD_SHARED_LIBS 0)
|
||||||
set(CMARK_TESTS OFF CACHE BOOL "Build cmark tests and enable testing" FORCE)
|
|
||||||
add_subdirectory(libraries/cmark EXCLUDE_FROM_ALL) # Markdown parser
|
add_subdirectory(libraries/cmark EXCLUDE_FROM_ALL) # Markdown parser
|
||||||
add_library(cmark::cmark ALIAS cmark_static)
|
add_library(cmark::cmark ALIAS cmark)
|
||||||
else()
|
else()
|
||||||
message(STATUS "Using system cmark")
|
message(STATUS "Using system cmark")
|
||||||
endif()
|
endif()
|
||||||
add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much
|
|
||||||
add_subdirectory(libraries/gamemode)
|
add_subdirectory(libraries/gamemode)
|
||||||
add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API
|
add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API
|
||||||
if (NOT ghc_filesystem_FOUND)
|
if (NOT ghc_filesystem_FOUND)
|
||||||
|
30
COPYING.md
30
COPYING.md
@ -1,7 +1,7 @@
|
|||||||
## Prism Launcher
|
## Prism Launcher
|
||||||
|
|
||||||
Prism Launcher - Minecraft Launcher
|
Prism Launcher - Minecraft Launcher
|
||||||
Copyright (C) 2022-2023 Prism Launcher Contributors
|
Copyright (C) 2022-2024 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
|
||||||
@ -333,32 +333,6 @@
|
|||||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
## O2 (Katabasis fork)
|
|
||||||
|
|
||||||
Copyright (c) 2012, Akos Polster
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
## Gamemode
|
## Gamemode
|
||||||
|
|
||||||
Copyright (c) 2017-2022, Feral Interactive
|
Copyright (c) 2017-2022, Feral Interactive
|
||||||
@ -436,7 +410,7 @@
|
|||||||
Copyright (C) 2007 Johann Ollivier Lapeyre <johann@oxygen-icons.org>
|
Copyright (C) 2007 Johann Ollivier Lapeyre <johann@oxygen-icons.org>
|
||||||
Copyright (C) 2007 Kenneth Wimer <kwwii@bootsplash.org>
|
Copyright (C) 2007 Kenneth Wimer <kwwii@bootsplash.org>
|
||||||
Copyright (C) 2007 Riccardo Iaconelli <riccardo@oxygen-icons.org>
|
Copyright (C) 2007 Riccardo Iaconelli <riccardo@oxygen-icons.org>
|
||||||
|
|
||||||
and others
|
and others
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
This library is free software; you can redistribute it and/or
|
||||||
|
@ -61,7 +61,12 @@ The translation effort for Prism Launcher is hosted on [Weblate](https://hosted.
|
|||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
If you want to build Prism Launcher yourself, check the [Build Instructions](https://prismlauncher.org/wiki/development/build-instructions/).
|
If you want to build Prism Launcher yourself, check the build instructions:
|
||||||
|
|
||||||
|
- [Windows](https://prismlauncher.org/wiki/development/build-instructions/windows/)
|
||||||
|
- [Linux](https://prismlauncher.org/wiki/development/build-instructions/linux/)
|
||||||
|
- [MacOS](https://prismlauncher.org/wiki/development/build-instructions/macos/)
|
||||||
|
- [OpenBSD](https://prismlauncher.org/wiki/development/build-instructions/openbsd/)
|
||||||
|
|
||||||
## Sponsors & Partners
|
## Sponsors & Partners
|
||||||
|
|
||||||
|
@ -163,7 +163,6 @@ 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 AUTH_BASE = "https://authserver.mojang.com/";
|
|
||||||
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 = "https://files.prismlauncher.org/fmllibs/"; // FIXME: move into CMakeLists
|
||||||
QString TRANSLATIONS_BASE_URL = "https://i18n.prismlauncher.org/"; // FIXME: move into CMakeLists
|
QString TRANSLATIONS_BASE_URL = "https://i18n.prismlauncher.org/"; // FIXME: move into CMakeLists
|
||||||
|
@ -68,6 +68,8 @@ function(
|
|||||||
/w14906 # string literal cast to 'LPWSTR'
|
/w14906 # string literal cast to 'LPWSTR'
|
||||||
/w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied
|
/w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied
|
||||||
/permissive- # standards conformance mode for MSVC compiler.
|
/permissive- # standards conformance mode for MSVC compiler.
|
||||||
|
|
||||||
|
/we4062 # forbid omitting a possible value of an enum in a switch statement
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@ -93,6 +95,8 @@ function(
|
|||||||
# in a lot of noise. This warning is only notifying us that clang is emulating the GCC behaviour
|
# in a lot of noise. This warning is only notifying us that clang is emulating the GCC behaviour
|
||||||
# instead of the exact standard wording so we can safely ignore it
|
# instead of the exact standard wording so we can safely ignore it
|
||||||
-Wno-gnu-zero-variadic-macro-arguments
|
-Wno-gnu-zero-variadic-macro-arguments
|
||||||
|
|
||||||
|
-Werror=switch # forbid omitting a possible value of an enum in a switch statement
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@ -104,6 +108,8 @@ function(
|
|||||||
-Wduplicated-branches # warn if if / else branches have duplicated code
|
-Wduplicated-branches # warn if if / else branches have duplicated code
|
||||||
-Wlogical-op # warn about logical operations being used where bitwise were probably wanted
|
-Wlogical-op # warn about logical operations being used where bitwise were probably wanted
|
||||||
-Wuseless-cast # warn if you perform a cast to the same type
|
-Wuseless-cast # warn if you perform a cast to the same type
|
||||||
|
|
||||||
|
-Werror=switch # forbid omitting a possible value of an enum in a switch statement
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@ -128,6 +134,8 @@ function(
|
|||||||
-Woverloaded-virtual
|
-Woverloaded-virtual
|
||||||
-Wuseless-cast
|
-Wuseless-cast
|
||||||
-Wextra-semi
|
-Wextra-semi
|
||||||
|
|
||||||
|
-Werror=switch # forbid omitting a possible value of an enum in a switch statement
|
||||||
)
|
)
|
||||||
|
|
||||||
target_compile_options(
|
target_compile_options(
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
<string>A Minecraft mod wants to access your camera.</string>
|
<string>A Minecraft mod wants to access your camera.</string>
|
||||||
<key>NSMicrophoneUsageDescription</key>
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
<string>A Minecraft mod wants to access your microphone.</string>
|
<string>A Minecraft mod wants to access your microphone.</string>
|
||||||
|
<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>
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
<string>NSApplication</string>
|
<string>NSApplication</string>
|
||||||
<key>NSHighResolutionCapable</key>
|
<key>NSHighResolutionCapable</key>
|
||||||
@ -77,6 +79,14 @@
|
|||||||
<string>curseforge</string>
|
<string>curseforge</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string>Prismlauncher</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>prismlauncher</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
96
flake.lock
generated
96
flake.lock
generated
@ -18,14 +18,16 @@
|
|||||||
},
|
},
|
||||||
"flake-parts": {
|
"flake-parts": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs-lib": "nixpkgs-lib"
|
"nixpkgs-lib": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1698882062,
|
"lastModified": 1722555600,
|
||||||
"narHash": "sha256-HkhafUayIqxXyHH1X8d9RDl1M2CkFgZLjKD3MzabiEo=",
|
"narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "8c9fa2545007b49a5db5f650ae91f227672c3877",
|
"rev": "8471fe90ad337a8074e957b69ca4d0089218391d",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -34,24 +36,6 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-utils": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1685518550,
|
|
||||||
"narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"gitignore": {
|
"gitignore": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
@ -60,11 +44,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1660459072,
|
"lastModified": 1709087332,
|
||||||
"narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=",
|
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "gitignore.nix",
|
"repo": "gitignore.nix",
|
||||||
"rev": "a20de23b925fd8264fd7fad6454652e142fd7f73",
|
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -89,49 +73,16 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nix-filter": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1694857738,
|
|
||||||
"narHash": "sha256-bxxNyLHjhu0N8T3REINXQ2ZkJco0ABFPn6PIe2QUfqo=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "nix-filter",
|
|
||||||
"rev": "41fd48e00c22b4ced525af521ead8792402de0ea",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "nix-filter",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1699343069,
|
"lastModified": 1723637854,
|
||||||
"narHash": "sha256-s7BBhyLA6MI6FuJgs4F/SgpntHBzz40/qV0xLPW6A1Q=",
|
"narHash": "sha256-med8+5DSWa2UnOqtdICndjDAEjxr5D7zaIiK4pn0Q7c=",
|
||||||
"owner": "nixos",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "ec750fd01963ab6b20ee1f0cb488754e8036d89d",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nixos",
|
|
||||||
"ref": "nixpkgs-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs-lib": {
|
|
||||||
"locked": {
|
|
||||||
"dir": "lib",
|
|
||||||
"lastModified": 1698611440,
|
|
||||||
"narHash": "sha256-jPjHjrerhYDy3q9+s5EAsuhyhuknNfowY6yt6pjn9pc=",
|
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "0cbe9f69c234a7700596e943bfae7ef27a31b735",
|
"rev": "c3aa7b8938b17aebd2deecf7be0636000d62a2b9",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"dir": "lib",
|
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"ref": "nixos-unstable",
|
"ref": "nixos-unstable",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
@ -143,7 +94,6 @@
|
|||||||
"flake-compat": [
|
"flake-compat": [
|
||||||
"flake-compat"
|
"flake-compat"
|
||||||
],
|
],
|
||||||
"flake-utils": "flake-utils",
|
|
||||||
"gitignore": "gitignore",
|
"gitignore": "gitignore",
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
@ -153,11 +103,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1699271226,
|
"lastModified": 1723803910,
|
||||||
"narHash": "sha256-8Jt1KW3xTjolD6c6OjJm9USx/jmL+VVmbooADCkdDfU=",
|
"narHash": "sha256-yezvUuFiEnCFbGuwj/bQcqg7RykIEqudOy/RBrId0pc=",
|
||||||
"owner": "cachix",
|
"owner": "cachix",
|
||||||
"repo": "pre-commit-hooks.nix",
|
"repo": "pre-commit-hooks.nix",
|
||||||
"rev": "ea758da1a6dcde6dc36db348ed690d09b9864128",
|
"rev": "bfef0ada09e2c8ac55bbcd0831bd0c9d42e651ba",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -171,25 +121,9 @@
|
|||||||
"flake-compat": "flake-compat",
|
"flake-compat": "flake-compat",
|
||||||
"flake-parts": "flake-parts",
|
"flake-parts": "flake-parts",
|
||||||
"libnbtplusplus": "libnbtplusplus",
|
"libnbtplusplus": "libnbtplusplus",
|
||||||
"nix-filter": "nix-filter",
|
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
"pre-commit-hooks": "pre-commit-hooks"
|
"pre-commit-hooks": "pre-commit-hooks"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"systems": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
21
flake.nix
21
flake.nix
@ -1,15 +1,24 @@
|
|||||||
{
|
{
|
||||||
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 = {
|
||||||
|
extra-substituters = ["https://cache.garnix.io"];
|
||||||
|
extra-trusted-public-keys = ["cache.garnix.io:CTFPyKSLcx5RMJKfLo5EEPUObbA78b0YQ2DTCJXqr9g="];
|
||||||
|
};
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
flake-parts = {
|
||||||
nix-filter.url = "github:numtide/nix-filter";
|
url = "github:hercules-ci/flake-parts";
|
||||||
|
inputs.nixpkgs-lib.follows = "nixpkgs";
|
||||||
|
};
|
||||||
pre-commit-hooks = {
|
pre-commit-hooks = {
|
||||||
url = "github:cachix/pre-commit-hooks.nix";
|
url = "github:cachix/pre-commit-hooks.nix";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs = {
|
||||||
inputs.nixpkgs-stable.follows = "nixpkgs";
|
nixpkgs.follows = "nixpkgs";
|
||||||
inputs.flake-compat.follows = "flake-compat";
|
nixpkgs-stable.follows = "nixpkgs";
|
||||||
|
flake-compat.follows = "flake-compat";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
flake-compat = {
|
flake-compat = {
|
||||||
url = "github:edolstra/flake-compat";
|
url = "github:edolstra/flake-compat";
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
id: org.prismlauncher.PrismLauncher
|
id: org.prismlauncher.PrismLauncher
|
||||||
runtime: org.kde.Platform
|
runtime: org.kde.Platform
|
||||||
runtime-version: "5.15-22.08"
|
runtime-version: 6.7
|
||||||
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
|
- org.freedesktop.Sdk.Extension.openjdk8
|
||||||
|
|
||||||
@ -37,7 +38,6 @@ modules:
|
|||||||
config-opts:
|
config-opts:
|
||||||
- -DLauncher_BUILD_PLATFORM=flatpak
|
- -DLauncher_BUILD_PLATFORM=flatpak
|
||||||
- -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
- -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||||
- -DLauncher_QT_VERSION_MAJOR=5
|
|
||||||
build-options:
|
build-options:
|
||||||
env:
|
env:
|
||||||
JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17
|
JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17
|
||||||
@ -50,6 +50,8 @@ modules:
|
|||||||
buildsystem: simple
|
buildsystem: simple
|
||||||
build-commands:
|
build-commands:
|
||||||
- mkdir -p /app/jdk/
|
- mkdir -p /app/jdk/
|
||||||
|
- /usr/lib/sdk/openjdk21/install.sh
|
||||||
|
- mv /app/jre /app/jdk/21
|
||||||
- /usr/lib/sdk/openjdk17/install.sh
|
- /usr/lib/sdk/openjdk17/install.sh
|
||||||
- mv /app/jre /app/jdk/17
|
- mv /app/jre /app/jdk/17
|
||||||
- /usr/lib/sdk/openjdk8/install.sh
|
- /usr/lib/sdk/openjdk8/install.sh
|
||||||
@ -62,7 +64,8 @@ modules:
|
|||||||
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=ON
|
- -DGLFW_USE_WAYLAND:BOOL=ON
|
||||||
|
- -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
|
||||||
@ -104,9 +107,9 @@ modules:
|
|||||||
- install -Dm755 ../data/gamemoderun -t /app/bin
|
- install -Dm755 ../data/gamemoderun -t /app/bin
|
||||||
sources:
|
sources:
|
||||||
- type: archive
|
- type: archive
|
||||||
archive-type: tar-gzip
|
dest-filename: gamemode.tar.gz
|
||||||
url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.7
|
url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.8.1
|
||||||
sha256: 57ce73ba605d1cf12f8d13725006a895182308d93eba0f69f285648449641803
|
sha256: 969cf85b5ca3944f3e315cd73a0ee9bea4f9c968cd7d485e9f4745bc1e679c4e
|
||||||
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
|
||||||
|
Submodule flatpak/shared-modules updated: 45094ca570...f2b0c16a2a
@ -1,5 +1,6 @@
|
|||||||
builds:
|
builds:
|
||||||
exclude: []
|
exclude:
|
||||||
|
- "*.x86_64-darwin.*"
|
||||||
include:
|
include:
|
||||||
- "checks.x86_64-linux.*"
|
- "checks.x86_64-linux.*"
|
||||||
- "devShells.*.*"
|
- "devShells.*.*"
|
||||||
|
@ -47,7 +47,7 @@
|
|||||||
#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 "settings/INIFile.h"
|
#include "tools/GenericProfiler.h"
|
||||||
#include "ui/InstanceWindow.h"
|
#include "ui/InstanceWindow.h"
|
||||||
#include "ui/MainWindow.h"
|
#include "ui/MainWindow.h"
|
||||||
|
|
||||||
@ -105,7 +105,7 @@
|
|||||||
#include "icons/IconList.h"
|
#include "icons/IconList.h"
|
||||||
#include "net/HttpMetaCache.h"
|
#include "net/HttpMetaCache.h"
|
||||||
|
|
||||||
#include "java/JavaUtils.h"
|
#include "java/JavaInstallList.h"
|
||||||
|
|
||||||
#include "updater/ExternalUpdater.h"
|
#include "updater/ExternalUpdater.h"
|
||||||
|
|
||||||
@ -132,6 +132,15 @@
|
|||||||
#include "gamemode_client.h"
|
#include "gamemode_client.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(Q_OS_LINUX)
|
||||||
|
#include <sys/statvfs.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||||
|
#include <sys/mount.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(Q_OS_MAC)
|
#if defined(Q_OS_MAC)
|
||||||
#if defined(SPARKLE_ENABLED)
|
#if defined(SPARKLE_ENABLED)
|
||||||
#include "updater/MacSparkleUpdater.h"
|
#include "updater/MacSparkleUpdater.h"
|
||||||
@ -141,6 +150,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined Q_OS_WIN32
|
#if defined Q_OS_WIN32
|
||||||
|
#include <windows.h>
|
||||||
#include "WindowsConsole.h"
|
#include "WindowsConsole.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -216,6 +226,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
|
|
||||||
// Don't quit on hiding the last window
|
// Don't quit on hiding the last window
|
||||||
this->setQuitOnLastWindowClosed(false);
|
this->setQuitOnLastWindowClosed(false);
|
||||||
|
this->setQuitLockEnabled(false);
|
||||||
|
|
||||||
// Commandline parsing
|
// Commandline parsing
|
||||||
QCommandLineParser parser;
|
QCommandLineParser parser;
|
||||||
@ -225,6 +236,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
{ { { "d", "dir" }, "Use a custom path as application root (use '.' for current directory)", "directory" },
|
{ { { "d", "dir" }, "Use a custom path as application root (use '.' for current directory)", "directory" },
|
||||||
{ { "l", "launch" }, "Launch the specified instance (by instance ID)", "instance" },
|
{ { "l", "launch" }, "Launch the specified instance (by instance ID)", "instance" },
|
||||||
{ { "s", "server" }, "Join the specified server on launch (only valid in combination with --launch)", "address" },
|
{ { "s", "server" }, "Join the specified server on launch (only valid in combination with --launch)", "address" },
|
||||||
|
{ { "w", "world" }, "Join the specified world on launch (only valid in combination with --launch)", "world" },
|
||||||
{ { "a", "profile" }, "Use the account specified by its profile name (only valid in combination with --launch)", "profile" },
|
{ { "a", "profile" }, "Use the account specified by its profile name (only valid in combination with --launch)", "profile" },
|
||||||
{ "alive", "Write a small '" + liveCheckFile + "' file after the launcher starts" },
|
{ "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" },
|
||||||
@ -239,6 +251,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
|
|
||||||
m_instanceIdToLaunch = parser.value("launch");
|
m_instanceIdToLaunch = parser.value("launch");
|
||||||
m_serverToJoin = parser.value("server");
|
m_serverToJoin = parser.value("server");
|
||||||
|
m_worldToJoin = parser.value("world");
|
||||||
m_profileToUse = parser.value("profile");
|
m_profileToUse = parser.value("profile");
|
||||||
m_liveCheck = parser.isSet("alive");
|
m_liveCheck = parser.isSet("alive");
|
||||||
|
|
||||||
@ -254,7 +267,7 @@ 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_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty()) {
|
if (((!m_serverToJoin.isEmpty() || !m_worldToJoin.isEmpty()) || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty()) {
|
||||||
std::cerr << "--server and --profile can only be used in combination with --launch!" << std::endl;
|
std::cerr << "--server and --profile can only be used in combination with --launch!" << std::endl;
|
||||||
m_status = Application::Failed;
|
m_status = Application::Failed;
|
||||||
return;
|
return;
|
||||||
@ -281,12 +294,17 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
QString adjustedBy;
|
QString adjustedBy;
|
||||||
QString dataPath;
|
QString dataPath;
|
||||||
// change folder
|
// change folder
|
||||||
|
QString dataDirEnv;
|
||||||
QString dirParam = parser.value("dir");
|
QString dirParam = parser.value("dir");
|
||||||
if (!dirParam.isEmpty()) {
|
if (!dirParam.isEmpty()) {
|
||||||
// the dir param. it makes multimc data path point to whatever the user specified
|
// the dir param. it makes multimc data path point to whatever the user specified
|
||||||
// on command line
|
// on command line
|
||||||
adjustedBy = "Command line";
|
adjustedBy = "Command line";
|
||||||
dataPath = dirParam;
|
dataPath = dirParam;
|
||||||
|
} else if (dataDirEnv = QProcessEnvironment::systemEnvironment().value(QString("%1_DATA_DIR").arg(BuildConfig.LAUNCHER_NAME.toUpper()));
|
||||||
|
!dataDirEnv.isEmpty()) {
|
||||||
|
adjustedBy = "System environment";
|
||||||
|
dataPath = dataDirEnv;
|
||||||
} else {
|
} else {
|
||||||
QDir foo;
|
QDir foo;
|
||||||
if (DesktopServices::isSnap()) {
|
if (DesktopServices::isSnap()) {
|
||||||
@ -299,7 +317,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
adjustedBy = "Persistent data path";
|
adjustedBy = "Persistent data path";
|
||||||
|
|
||||||
#ifndef Q_OS_MACOS
|
#ifndef Q_OS_MACOS
|
||||||
if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) {
|
if (auto portableUserData = FS::PathCombine(m_rootPath, "UserData"); QDir(portableUserData).exists()) {
|
||||||
|
dataPath = portableUserData;
|
||||||
|
adjustedBy = "Portable user data path";
|
||||||
|
m_portable = true;
|
||||||
|
} else if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) {
|
||||||
dataPath = m_rootPath;
|
dataPath = m_rootPath;
|
||||||
adjustedBy = "Portable data path";
|
adjustedBy = "Portable data path";
|
||||||
m_portable = true;
|
m_portable = true;
|
||||||
@ -365,6 +387,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
|
|
||||||
if (!m_serverToJoin.isEmpty()) {
|
if (!m_serverToJoin.isEmpty()) {
|
||||||
launch.args["server"] = m_serverToJoin;
|
launch.args["server"] = m_serverToJoin;
|
||||||
|
} else if (!m_worldToJoin.isEmpty()) {
|
||||||
|
launch.args["world"] = m_worldToJoin;
|
||||||
}
|
}
|
||||||
if (!m_profileToUse.isEmpty()) {
|
if (!m_profileToUse.isEmpty()) {
|
||||||
launch.args["profile"] = m_profileToUse;
|
launch.args["profile"] = m_profileToUse;
|
||||||
@ -380,20 +404,15 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
{
|
{
|
||||||
static const QString baseLogFile = BuildConfig.LAUNCHER_NAME + "-%0.log";
|
static const QString baseLogFile = BuildConfig.LAUNCHER_NAME + "-%0.log";
|
||||||
static const QString logBase = FS::PathCombine("logs", baseLogFile);
|
static const QString logBase = FS::PathCombine("logs", baseLogFile);
|
||||||
auto moveFile = [](const QString& oldName, const QString& newName) {
|
|
||||||
QFile::remove(newName);
|
|
||||||
QFile::copy(oldName, newName);
|
|
||||||
QFile::remove(oldName);
|
|
||||||
};
|
|
||||||
if (FS::ensureFolderPathExists("logs")) { // if this did not fail
|
if (FS::ensureFolderPathExists("logs")) { // if this did not fail
|
||||||
for (auto i = 0; i <= 4; i++)
|
for (auto i = 0; i <= 4; i++)
|
||||||
if (auto oldName = baseLogFile.arg(i);
|
if (auto oldName = baseLogFile.arg(i);
|
||||||
QFile::exists(oldName)) // do not pointlessly delete new files if the old ones are not there
|
QFile::exists(oldName)) // do not pointlessly delete new files if the old ones are not there
|
||||||
moveFile(oldName, logBase.arg(i));
|
FS::move(oldName, logBase.arg(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto i = 4; i > 0; i--)
|
for (auto i = 4; i > 0; i--)
|
||||||
moveFile(logBase.arg(i - 1), logBase.arg(i));
|
FS::move(logBase.arg(i - 1), logBase.arg(i));
|
||||||
|
|
||||||
logFile = std::unique_ptr<QFile>(new QFile(logBase.arg(0)));
|
logFile = std::unique_ptr<QFile>(new QFile(logBase.arg(0)));
|
||||||
if (!logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
|
if (!logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
|
||||||
@ -433,7 +452,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
|
|
||||||
// search the dataPath()
|
// search the dataPath()
|
||||||
// seach app data standard path
|
// seach app data standard path
|
||||||
if (!foundLoggingRules && !isPortable() && dirParam.isEmpty()) {
|
if (!foundLoggingRules && !isPortable() && dirParam.isEmpty() && dataDirEnv.isEmpty()) {
|
||||||
logRulesPath = QStandardPaths::locate(QStandardPaths::AppDataLocation, FS::PathCombine("..", logRulesFile));
|
logRulesPath = QStandardPaths::locate(QStandardPaths::AppDataLocation, FS::PathCombine("..", logRulesFile));
|
||||||
if (!logRulesPath.isEmpty()) {
|
if (!logRulesPath.isEmpty()) {
|
||||||
qDebug() << "Found" << logRulesPath << "...";
|
qDebug() << "Found" << logRulesPath << "...";
|
||||||
@ -485,8 +504,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
qDebug() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME) << ", (c) 2022-2023 "
|
qDebug() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME + ", " + QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", "));
|
||||||
<< qPrintable(QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", "));
|
|
||||||
qDebug() << "Version : " << BuildConfig.printableVersionString();
|
qDebug() << "Version : " << BuildConfig.printableVersionString();
|
||||||
qDebug() << "Platform : " << BuildConfig.BUILD_PLATFORM;
|
qDebug() << "Platform : " << BuildConfig.BUILD_PLATFORM;
|
||||||
qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT;
|
qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT;
|
||||||
@ -509,6 +527,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
}
|
}
|
||||||
if (!m_serverToJoin.isEmpty()) {
|
if (!m_serverToJoin.isEmpty()) {
|
||||||
qDebug() << "Address of server to join :" << m_serverToJoin;
|
qDebug() << "Address of server to join :" << m_serverToJoin;
|
||||||
|
} else if (!m_worldToJoin.isEmpty()) {
|
||||||
|
qDebug() << "Name of the world to join :" << m_worldToJoin;
|
||||||
}
|
}
|
||||||
qDebug() << "<> Paths set.";
|
qDebug() << "<> Paths set.";
|
||||||
}
|
}
|
||||||
@ -545,6 +565,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
|
|
||||||
m_settings->registerSetting("NumberOfConcurrentTasks", 10);
|
m_settings->registerSetting("NumberOfConcurrentTasks", 10);
|
||||||
m_settings->registerSetting("NumberOfConcurrentDownloads", 6);
|
m_settings->registerSetting("NumberOfConcurrentDownloads", 6);
|
||||||
|
m_settings->registerSetting("NumberOfManualRetries", 1);
|
||||||
|
m_settings->registerSetting("RequestTimeout", 60);
|
||||||
|
|
||||||
QString defaultMonospace;
|
QString defaultMonospace;
|
||||||
int defaultSize = 11;
|
int defaultSize = 11;
|
||||||
@ -579,6 +601,7 @@ 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("SkinsDir", "skins");
|
||||||
|
|
||||||
// Editors
|
// Editors
|
||||||
m_settings->registerSetting("JsonEditor", QString());
|
m_settings->registerSetting("JsonEditor", QString());
|
||||||
@ -631,10 +654,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
m_settings->registerSetting("UseNativeGLFW", false);
|
m_settings->registerSetting("UseNativeGLFW", false);
|
||||||
m_settings->registerSetting("CustomGLFWPath", "");
|
m_settings->registerSetting("CustomGLFWPath", "");
|
||||||
|
|
||||||
// Peformance related options
|
// Performance related options
|
||||||
m_settings->registerSetting("EnableFeralGamemode", false);
|
m_settings->registerSetting("EnableFeralGamemode", false);
|
||||||
m_settings->registerSetting("EnableMangoHud", false);
|
m_settings->registerSetting("EnableMangoHud", false);
|
||||||
m_settings->registerSetting("UseDiscreteGpu", false);
|
m_settings->registerSetting("UseDiscreteGpu", false);
|
||||||
|
m_settings->registerSetting("UseZink", false);
|
||||||
|
|
||||||
// Game time
|
// Game time
|
||||||
m_settings->registerSetting("ShowGameTime", true);
|
m_settings->registerSetting("ShowGameTime", true);
|
||||||
@ -645,6 +669,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
// Minecraft mods
|
// Minecraft mods
|
||||||
m_settings->registerSetting("ModMetadataDisabled", false);
|
m_settings->registerSetting("ModMetadataDisabled", false);
|
||||||
m_settings->registerSetting("ModDependenciesDisabled", false);
|
m_settings->registerSetting("ModDependenciesDisabled", false);
|
||||||
|
m_settings->registerSetting("SkipModpackUpdatePrompt", false);
|
||||||
|
|
||||||
// Minecraft offline player name
|
// Minecraft offline player name
|
||||||
m_settings->registerSetting("LastOfflinePlayerName", "");
|
m_settings->registerSetting("LastOfflinePlayerName", "");
|
||||||
@ -658,6 +683,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
|
|
||||||
// The cat
|
// The cat
|
||||||
m_settings->registerSetting("TheCat", false);
|
m_settings->registerSetting("TheCat", false);
|
||||||
|
m_settings->registerSetting("CatOpacity", 100);
|
||||||
|
|
||||||
|
m_settings->registerSetting("StatusBarVisible", true);
|
||||||
|
|
||||||
m_settings->registerSetting("ToolbarsLocked", false);
|
m_settings->registerSetting("ToolbarsLocked", false);
|
||||||
|
|
||||||
@ -745,6 +773,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
m_settings->registerSetting("ModrinthToken", "");
|
m_settings->registerSetting("ModrinthToken", "");
|
||||||
m_settings->registerSetting("UserAgentOverride", "");
|
m_settings->registerSetting("UserAgentOverride", "");
|
||||||
|
|
||||||
|
// FTBApp instances
|
||||||
|
m_settings->registerSetting("FTBAppInstancesPath", "");
|
||||||
|
|
||||||
// Init page provider
|
// Init page provider
|
||||||
{
|
{
|
||||||
m_globalSettingsProvider = std::make_shared<GenericPageProvider>(tr("Settings"));
|
m_globalSettingsProvider = std::make_shared<GenericPageProvider>(tr("Settings"));
|
||||||
@ -798,7 +829,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
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()); });
|
[&](const Setting&, QVariant value) { m_icons->directoryChanged(value.toString()); });
|
||||||
qDebug() << "<> Instance icons intialized.";
|
qDebug() << "<> Instance icons initialized.";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Themes
|
// Themes
|
||||||
@ -835,24 +866,17 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
{
|
{
|
||||||
m_metacache.reset(new HttpMetaCache("metacache"));
|
m_metacache.reset(new HttpMetaCache("metacache"));
|
||||||
m_metacache->addBase("asset_indexes", QDir("assets/indexes").absolutePath());
|
m_metacache->addBase("asset_indexes", QDir("assets/indexes").absolutePath());
|
||||||
m_metacache->addBase("asset_objects", QDir("assets/objects").absolutePath());
|
|
||||||
m_metacache->addBase("versions", QDir("versions").absolutePath());
|
|
||||||
m_metacache->addBase("libraries", QDir("libraries").absolutePath());
|
m_metacache->addBase("libraries", QDir("libraries").absolutePath());
|
||||||
m_metacache->addBase("minecraftforge", QDir("mods/minecraftforge").absolutePath());
|
|
||||||
m_metacache->addBase("fmllibs", QDir("mods/minecraftforge/libs").absolutePath());
|
m_metacache->addBase("fmllibs", QDir("mods/minecraftforge/libs").absolutePath());
|
||||||
m_metacache->addBase("liteloader", QDir("mods/liteloader").absolutePath());
|
|
||||||
m_metacache->addBase("general", QDir("cache").absolutePath());
|
m_metacache->addBase("general", QDir("cache").absolutePath());
|
||||||
m_metacache->addBase("ATLauncherPacks", QDir("cache/ATLauncherPacks").absolutePath());
|
m_metacache->addBase("ATLauncherPacks", QDir("cache/ATLauncherPacks").absolutePath());
|
||||||
m_metacache->addBase("FTBPacks", QDir("cache/FTBPacks").absolutePath());
|
m_metacache->addBase("FTBPacks", QDir("cache/FTBPacks").absolutePath());
|
||||||
m_metacache->addBase("ModpacksCHPacks", QDir("cache/ModpacksCHPacks").absolutePath());
|
|
||||||
m_metacache->addBase("TechnicPacks", QDir("cache/TechnicPacks").absolutePath());
|
m_metacache->addBase("TechnicPacks", QDir("cache/TechnicPacks").absolutePath());
|
||||||
m_metacache->addBase("FlamePacks", QDir("cache/FlamePacks").absolutePath());
|
m_metacache->addBase("FlamePacks", QDir("cache/FlamePacks").absolutePath());
|
||||||
m_metacache->addBase("FlameMods", QDir("cache/FlameMods").absolutePath());
|
m_metacache->addBase("FlameMods", QDir("cache/FlameMods").absolutePath());
|
||||||
m_metacache->addBase("ModrinthPacks", QDir("cache/ModrinthPacks").absolutePath());
|
m_metacache->addBase("ModrinthPacks", QDir("cache/ModrinthPacks").absolutePath());
|
||||||
m_metacache->addBase("ModrinthModpacks", QDir("cache/ModrinthModpacks").absolutePath());
|
m_metacache->addBase("ModrinthModpacks", QDir("cache/ModrinthModpacks").absolutePath());
|
||||||
m_metacache->addBase("root", QDir::currentPath());
|
|
||||||
m_metacache->addBase("translations", QDir("translations").absolutePath());
|
m_metacache->addBase("translations", QDir("translations").absolutePath());
|
||||||
m_metacache->addBase("icons", QDir("cache/icons").absolutePath());
|
|
||||||
m_metacache->addBase("meta", QDir("meta").absolutePath());
|
m_metacache->addBase("meta", QDir("meta").absolutePath());
|
||||||
m_metacache->Load();
|
m_metacache->Load();
|
||||||
qDebug() << "<> Cache initialized.";
|
qDebug() << "<> Cache initialized.";
|
||||||
@ -864,6 +888,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
// FIXME: what to do with these?
|
// FIXME: what to do with these?
|
||||||
m_profilers.insert("jprofiler", std::shared_ptr<BaseProfilerFactory>(new JProfilerFactory()));
|
m_profilers.insert("jprofiler", std::shared_ptr<BaseProfilerFactory>(new JProfilerFactory()));
|
||||||
m_profilers.insert("jvisualvm", std::shared_ptr<BaseProfilerFactory>(new JVisualVMFactory()));
|
m_profilers.insert("jvisualvm", std::shared_ptr<BaseProfilerFactory>(new JVisualVMFactory()));
|
||||||
|
m_profilers.insert("generic", std::shared_ptr<BaseProfilerFactory>(new GenericProfilerFactory()));
|
||||||
for (auto profiler : m_profilers.values()) {
|
for (auto profiler : m_profilers.values()) {
|
||||||
profiler->registerSettings(m_settings);
|
profiler->registerSettings(m_settings);
|
||||||
}
|
}
|
||||||
@ -932,8 +957,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
default: {
|
default: {
|
||||||
qDebug() << "Exiting because update lockfile is present";
|
qDebug() << "Exiting because update lockfile is present";
|
||||||
QMetaObject::invokeMethod(
|
QMetaObject::invokeMethod(this, []() { exit(1); }, Qt::QueuedConnection);
|
||||||
this, []() { exit(1); }, Qt::QueuedConnection);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -965,8 +989,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
default: {
|
default: {
|
||||||
qDebug() << "Exiting because update lockfile is present";
|
qDebug() << "Exiting because update lockfile is present";
|
||||||
QMetaObject::invokeMethod(
|
QMetaObject::invokeMethod(this, []() { exit(1); }, Qt::QueuedConnection);
|
||||||
this, []() { exit(1); }, Qt::QueuedConnection);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -978,7 +1001,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
"\n"
|
"\n"
|
||||||
"You are now running %1 .\n"
|
"You are now running %1 .\n"
|
||||||
"Check the Prism Launcher updater log at: \n"
|
"Check the Prism Launcher updater log at: \n"
|
||||||
"%1\n"
|
"%2\n"
|
||||||
"for details.")
|
"for details.")
|
||||||
.arg(BuildConfig.printableVersionString())
|
.arg(BuildConfig.printableVersionString())
|
||||||
.arg(update_log_path);
|
.arg(update_log_path);
|
||||||
@ -993,6 +1016,37 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// notify user if /tmp is mounted with `noexec` (#1693)
|
||||||
|
{
|
||||||
|
bool is_tmp_noexec = false;
|
||||||
|
|
||||||
|
#if defined(Q_OS_LINUX)
|
||||||
|
|
||||||
|
struct statvfs tmp_stat;
|
||||||
|
statvfs("/tmp", &tmp_stat);
|
||||||
|
is_tmp_noexec = tmp_stat.f_flag & ST_NOEXEC;
|
||||||
|
|
||||||
|
#elif defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||||
|
|
||||||
|
struct statfs tmp_stat;
|
||||||
|
statfs("/tmp", &tmp_stat);
|
||||||
|
is_tmp_noexec = tmp_stat.f_flags & MNT_NOEXEC;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (is_tmp_noexec) {
|
||||||
|
auto infoMsg =
|
||||||
|
tr("Your /tmp directory is currently mounted with the 'noexec' flag enabled.\n"
|
||||||
|
"Some versions of Minecraft may not launch.\n");
|
||||||
|
auto msgBox = new QMessageBox(QMessageBox::Information, tr("Incompatible system configuration"), infoMsg, QMessageBox::Ok);
|
||||||
|
msgBox->setDefaultButton(QMessageBox::Ok);
|
||||||
|
msgBox->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
msgBox->setMinimumWidth(460);
|
||||||
|
msgBox->adjustSize();
|
||||||
|
msgBox->open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (createSetupWizard()) {
|
if (createSetupWizard()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1114,14 +1168,17 @@ void Application::performMainStartupAction()
|
|||||||
if (!m_instanceIdToLaunch.isEmpty()) {
|
if (!m_instanceIdToLaunch.isEmpty()) {
|
||||||
auto inst = instances()->getInstanceById(m_instanceIdToLaunch);
|
auto inst = instances()->getInstanceById(m_instanceIdToLaunch);
|
||||||
if (inst) {
|
if (inst) {
|
||||||
MinecraftServerTargetPtr serverToJoin = nullptr;
|
MinecraftTarget::Ptr targetToJoin = nullptr;
|
||||||
MinecraftAccountPtr accountToUse = nullptr;
|
MinecraftAccountPtr accountToUse = nullptr;
|
||||||
|
|
||||||
qDebug() << "<> Instance" << m_instanceIdToLaunch << "launching";
|
qDebug() << "<> Instance" << m_instanceIdToLaunch << "launching";
|
||||||
if (!m_serverToJoin.isEmpty()) {
|
if (!m_serverToJoin.isEmpty()) {
|
||||||
// FIXME: validate the server string
|
// FIXME: validate the server string
|
||||||
serverToJoin.reset(new MinecraftServerTarget(MinecraftServerTarget::parse(m_serverToJoin)));
|
targetToJoin.reset(new MinecraftTarget(MinecraftTarget::parse(m_serverToJoin, false)));
|
||||||
qDebug() << " Launching with server" << m_serverToJoin;
|
qDebug() << " Launching with server" << m_serverToJoin;
|
||||||
|
} else if (!m_worldToJoin.isEmpty()) {
|
||||||
|
targetToJoin.reset(new MinecraftTarget(MinecraftTarget::parse(m_worldToJoin, true)));
|
||||||
|
qDebug() << " Launching with world" << m_worldToJoin;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_profileToUse.isEmpty()) {
|
if (!m_profileToUse.isEmpty()) {
|
||||||
@ -1132,7 +1189,7 @@ void Application::performMainStartupAction()
|
|||||||
qDebug() << " Launching with account" << m_profileToUse;
|
qDebug() << " Launching with account" << m_profileToUse;
|
||||||
}
|
}
|
||||||
|
|
||||||
launch(inst, true, false, serverToJoin, accountToUse);
|
launch(inst, true, false, targetToJoin, accountToUse);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1163,6 +1220,12 @@ void Application::performMainStartupAction()
|
|||||||
qDebug() << "<> Updater started.";
|
qDebug() << "<> Updater started.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{ // delete instances tmp dirctory
|
||||||
|
auto instDir = m_settings->get("InstanceDir").toString();
|
||||||
|
const QString tempRoot = FS::PathCombine(instDir, ".tmp");
|
||||||
|
FS::deletePath(tempRoot);
|
||||||
|
}
|
||||||
|
|
||||||
if (!m_urlsToImport.isEmpty()) {
|
if (!m_urlsToImport.isEmpty()) {
|
||||||
qDebug() << "<> Importing from url:" << m_urlsToImport;
|
qDebug() << "<> Importing from url:" << m_urlsToImport;
|
||||||
m_mainWindow->processURLs(m_urlsToImport);
|
m_mainWindow->processURLs(m_urlsToImport);
|
||||||
@ -1216,6 +1279,7 @@ void Application::messageReceived(const QByteArray& message)
|
|||||||
} 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 profile = received.args["profile"];
|
QString profile = received.args["profile"];
|
||||||
|
|
||||||
InstancePtr instance;
|
InstancePtr instance;
|
||||||
@ -1230,11 +1294,12 @@ void Application::messageReceived(const QByteArray& message)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MinecraftServerTargetPtr serverObject = nullptr;
|
MinecraftTarget::Ptr serverObject = nullptr;
|
||||||
if (!server.isEmpty()) {
|
if (!server.isEmpty()) {
|
||||||
serverObject = std::make_shared<MinecraftServerTarget>(MinecraftServerTarget::parse(server));
|
serverObject = std::make_shared<MinecraftTarget>(MinecraftTarget::parse(server, false));
|
||||||
|
} else if (!world.isEmpty()) {
|
||||||
|
serverObject = std::make_shared<MinecraftTarget>(MinecraftTarget::parse(world, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
MinecraftAccountPtr accountObject;
|
MinecraftAccountPtr accountObject;
|
||||||
if (!profile.isEmpty()) {
|
if (!profile.isEmpty()) {
|
||||||
accountObject = accounts()->getAccountByProfileName(profile);
|
accountObject = accounts()->getAccountByProfileName(profile);
|
||||||
@ -1283,11 +1348,7 @@ bool Application::openJsonEditor(const QString& filename)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Application::launch(InstancePtr instance,
|
bool Application::launch(InstancePtr instance, bool online, bool demo, MinecraftTarget::Ptr targetToJoin, MinecraftAccountPtr accountToUse)
|
||||||
bool online,
|
|
||||||
bool demo,
|
|
||||||
MinecraftServerTargetPtr serverToJoin,
|
|
||||||
MinecraftAccountPtr accountToUse)
|
|
||||||
{
|
{
|
||||||
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.";
|
||||||
@ -1305,7 +1366,7 @@ bool Application::launch(InstancePtr instance,
|
|||||||
controller->setOnline(online);
|
controller->setOnline(online);
|
||||||
controller->setDemo(demo);
|
controller->setDemo(demo);
|
||||||
controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get());
|
controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get());
|
||||||
controller->setServerToJoin(serverToJoin);
|
controller->setTargetToJoin(targetToJoin);
|
||||||
controller->setAccountToUse(accountToUse);
|
controller->setAccountToUse(accountToUse);
|
||||||
if (window) {
|
if (window) {
|
||||||
controller->setParentWidget(window);
|
controller->setParentWidget(window);
|
||||||
@ -1476,6 +1537,17 @@ InstanceWindow* Application::showInstanceWindow(InstancePtr instance, QString pa
|
|||||||
auto& window = extras.window;
|
auto& window = extras.window;
|
||||||
|
|
||||||
if (window) {
|
if (window) {
|
||||||
|
// If the window is minimized on macOS or Windows, activate and bring it up
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
if (window->isMinimized()) {
|
||||||
|
window->setWindowState(window->windowState() & ~Qt::WindowMinimized);
|
||||||
|
}
|
||||||
|
#elif defined(Q_OS_WIN)
|
||||||
|
if (window->isMinimized()) {
|
||||||
|
window->showNormal();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
window->raise();
|
window->raise();
|
||||||
window->activateWindow();
|
window->activateWindow();
|
||||||
} else {
|
} else {
|
||||||
@ -1483,6 +1555,7 @@ InstanceWindow* Application::showInstanceWindow(InstancePtr instance, QString pa
|
|||||||
m_openWindows++;
|
m_openWindows++;
|
||||||
connect(window, &InstanceWindow::isClosing, this, &Application::on_windowClose);
|
connect(window, &InstanceWindow::isClosing, this, &Application::on_windowClose);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!page.isEmpty()) {
|
if (!page.isEmpty()) {
|
||||||
window->selectPage(page);
|
window->selectPage(page);
|
||||||
}
|
}
|
||||||
@ -1611,8 +1684,7 @@ QString Application::getJarPath(QString jarFile)
|
|||||||
#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)
|
||||||
FS::PathCombine(m_rootPath, "share", BuildConfig.LAUNCHER_NAME),
|
FS::PathCombine(m_rootPath, "share", BuildConfig.LAUNCHER_NAME),
|
||||||
#endif
|
#endif
|
||||||
FS::PathCombine(m_rootPath, "jars"),
|
FS::PathCombine(m_rootPath, "jars"), FS::PathCombine(applicationDirPath(), "jars"),
|
||||||
FS::PathCombine(applicationDirPath(), "jars"),
|
|
||||||
FS::PathCombine(applicationDirPath(), "..", "jars") // from inside build dir, for debuging
|
FS::PathCombine(applicationDirPath(), "..", "jars") // from inside build dir, for debuging
|
||||||
};
|
};
|
||||||
for (QString p : potentialPaths) {
|
for (QString p : potentialPaths) {
|
||||||
|
@ -47,8 +47,7 @@
|
|||||||
|
|
||||||
#include <BaseInstance.h>
|
#include <BaseInstance.h>
|
||||||
|
|
||||||
#include "minecraft/launch/MinecraftServerTarget.h"
|
#include "minecraft/launch/MinecraftTarget.h"
|
||||||
#include "ui/themes/CatPack.h"
|
|
||||||
|
|
||||||
class LaunchController;
|
class LaunchController;
|
||||||
class LocalPeer;
|
class LocalPeer;
|
||||||
@ -193,6 +192,8 @@ class Application : public QApplication {
|
|||||||
void globalSettingsClosed();
|
void globalSettingsClosed();
|
||||||
int currentCatChanged(int index);
|
int currentCatChanged(int index);
|
||||||
|
|
||||||
|
void oauthReplyRecieved(QVariantMap);
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
void clickedOnDock();
|
void clickedOnDock();
|
||||||
#endif
|
#endif
|
||||||
@ -201,7 +202,7 @@ class Application : public QApplication {
|
|||||||
bool launch(InstancePtr instance,
|
bool launch(InstancePtr instance,
|
||||||
bool online = true,
|
bool online = true,
|
||||||
bool demo = false,
|
bool demo = false,
|
||||||
MinecraftServerTargetPtr serverToJoin = nullptr,
|
MinecraftTarget::Ptr targetToJoin = nullptr,
|
||||||
MinecraftAccountPtr accountToUse = nullptr);
|
MinecraftAccountPtr accountToUse = nullptr);
|
||||||
bool kill(InstancePtr instance);
|
bool kill(InstancePtr instance);
|
||||||
void closeCurrentWindow();
|
void closeCurrentWindow();
|
||||||
@ -289,6 +290,7 @@ class Application : public QApplication {
|
|||||||
QString m_detectedOpenALPath;
|
QString m_detectedOpenALPath;
|
||||||
QString m_instanceIdToLaunch;
|
QString m_instanceIdToLaunch;
|
||||||
QString m_serverToJoin;
|
QString m_serverToJoin;
|
||||||
|
QString m_worldToJoin;
|
||||||
QString m_profileToUse;
|
QString m_profileToUse;
|
||||||
bool m_liveCheck = false;
|
bool m_liveCheck = false;
|
||||||
QList<QUrl> m_urlsToImport;
|
QList<QUrl> m_urlsToImport;
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
|
||||||
#include "BaseInstaller.h"
|
#include "BaseInstaller.h"
|
||||||
|
#include "FileSystem.h"
|
||||||
#include "minecraft/MinecraftInstance.h"
|
#include "minecraft/MinecraftInstance.h"
|
||||||
|
|
||||||
BaseInstaller::BaseInstaller() {}
|
BaseInstaller::BaseInstaller() {}
|
||||||
@ -42,7 +43,7 @@ bool BaseInstaller::add(MinecraftInstance* to)
|
|||||||
|
|
||||||
bool BaseInstaller::remove(MinecraftInstance* from)
|
bool BaseInstaller::remove(MinecraftInstance* from)
|
||||||
{
|
{
|
||||||
return QFile::remove(filename(from->instanceRoot()));
|
return FS::deletePath(filename(from->instanceRoot()));
|
||||||
}
|
}
|
||||||
|
|
||||||
QString BaseInstaller::filename(const QString& root) const
|
QString BaseInstaller::filename(const QString& root) const
|
||||||
|
@ -29,7 +29,7 @@ class BaseVersion;
|
|||||||
class BaseInstaller {
|
class BaseInstaller {
|
||||||
public:
|
public:
|
||||||
BaseInstaller();
|
BaseInstaller();
|
||||||
virtual ~BaseInstaller(){};
|
virtual ~BaseInstaller() {};
|
||||||
bool isApplied(MinecraftInstance* on);
|
bool isApplied(MinecraftInstance* on);
|
||||||
|
|
||||||
virtual bool add(MinecraftInstance* to);
|
virtual bool add(MinecraftInstance* to);
|
||||||
|
@ -64,6 +64,8 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
|
|||||||
|
|
||||||
m_settings->registerSetting("lastLaunchTime", 0);
|
m_settings->registerSetting("lastLaunchTime", 0);
|
||||||
m_settings->registerSetting("totalTimePlayed", 0);
|
m_settings->registerSetting("totalTimePlayed", 0);
|
||||||
|
if (m_settings->get("totalTimePlayed").toLongLong() < 0)
|
||||||
|
m_settings->reset("totalTimePlayed");
|
||||||
m_settings->registerSetting("lastTimePlayed", 0);
|
m_settings->registerSetting("lastTimePlayed", 0);
|
||||||
|
|
||||||
m_settings->registerSetting("linkedInstances", "[]");
|
m_settings->registerSetting("linkedInstances", "[]");
|
||||||
@ -267,13 +269,18 @@ void BaseInstance::setRunning(bool running)
|
|||||||
|
|
||||||
m_isRunning = running;
|
m_isRunning = running;
|
||||||
|
|
||||||
if (!m_settings->get("RecordGameTime").toBool()) {
|
emit runningStatusChanged(running);
|
||||||
emit runningStatusChanged(running);
|
}
|
||||||
|
|
||||||
|
void BaseInstance::setMinecraftRunning(bool running)
|
||||||
|
{
|
||||||
|
if (!settings()->get("RecordGameTime").toBool()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (running) {
|
if (running) {
|
||||||
m_timeStarted = QDateTime::currentDateTime();
|
m_timeStarted = QDateTime::currentDateTime();
|
||||||
|
setLastLaunch(m_timeStarted.toMSecsSinceEpoch());
|
||||||
} else {
|
} else {
|
||||||
QDateTime timeEnded = QDateTime::currentDateTime();
|
QDateTime timeEnded = QDateTime::currentDateTime();
|
||||||
|
|
||||||
@ -283,8 +290,6 @@ void BaseInstance::setRunning(bool running)
|
|||||||
|
|
||||||
emit propertiesChanged(this);
|
emit propertiesChanged(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
emit runningStatusChanged(running);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t BaseInstance::totalTimePlayed() const
|
int64_t BaseInstance::totalTimePlayed() const
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
#include "net/Mode.h"
|
#include "net/Mode.h"
|
||||||
|
|
||||||
#include "RuntimeContext.h"
|
#include "RuntimeContext.h"
|
||||||
#include "minecraft/launch/MinecraftServerTarget.h"
|
#include "minecraft/launch/MinecraftTarget.h"
|
||||||
|
|
||||||
class QDir;
|
class QDir;
|
||||||
class Task;
|
class Task;
|
||||||
@ -104,6 +104,7 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
|
|||||||
/// be unique.
|
/// be unique.
|
||||||
virtual QString id() const;
|
virtual QString id() const;
|
||||||
|
|
||||||
|
void setMinecraftRunning(bool running);
|
||||||
void setRunning(bool running);
|
void setRunning(bool running);
|
||||||
bool isRunning() const;
|
bool isRunning() const;
|
||||||
int64_t totalTimePlayed() const;
|
int64_t totalTimePlayed() const;
|
||||||
@ -183,7 +184,7 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
|
|||||||
virtual Task::Ptr createUpdateTask(Net::Mode mode) = 0;
|
virtual Task::Ptr createUpdateTask(Net::Mode mode) = 0;
|
||||||
|
|
||||||
/// returns a valid launcher (task container)
|
/// returns a valid launcher (task container)
|
||||||
virtual shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) = 0;
|
virtual shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account, MinecraftTarget::Ptr targetToJoin) = 0;
|
||||||
|
|
||||||
/// returns the current launch task (if any)
|
/// returns the current launch task (if any)
|
||||||
shared_qobject_ptr<LaunchTask> getLaunchTask();
|
shared_qobject_ptr<LaunchTask> getLaunchTask();
|
||||||
@ -255,7 +256,7 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
|
|||||||
/**
|
/**
|
||||||
* 'print' a verbose description of the instance into a QStringList
|
* 'print' a verbose description of the instance into a QStringList
|
||||||
*/
|
*/
|
||||||
virtual QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) = 0;
|
virtual QStringList verboseDescription(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) = 0;
|
||||||
|
|
||||||
Status currentStatus() const;
|
Status currentStatus() const;
|
||||||
|
|
||||||
|
@ -126,7 +126,6 @@ set(NET_SOURCES
|
|||||||
net/MetaCacheSink.h
|
net/MetaCacheSink.h
|
||||||
net/Logging.h
|
net/Logging.h
|
||||||
net/Logging.cpp
|
net/Logging.cpp
|
||||||
net/NetAction.h
|
|
||||||
net/NetJob.cpp
|
net/NetJob.cpp
|
||||||
net/NetJob.h
|
net/NetJob.h
|
||||||
net/NetUtils.h
|
net/NetUtils.h
|
||||||
@ -139,7 +138,6 @@ set(NET_SOURCES
|
|||||||
net/HeaderProxy.h
|
net/HeaderProxy.h
|
||||||
net/RawHeaderProxy.h
|
net/RawHeaderProxy.h
|
||||||
net/ApiHeaderProxy.h
|
net/ApiHeaderProxy.h
|
||||||
net/StaticHeaderProxy.h
|
|
||||||
net/ApiDownload.h
|
net/ApiDownload.h
|
||||||
net/ApiDownload.cpp
|
net/ApiDownload.cpp
|
||||||
net/ApiUpload.cpp
|
net/ApiUpload.cpp
|
||||||
@ -164,6 +162,8 @@ set(LAUNCH_SOURCES
|
|||||||
launch/steps/Update.h
|
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.h
|
||||||
launch/LaunchStep.cpp
|
launch/LaunchStep.cpp
|
||||||
launch/LaunchStep.h
|
launch/LaunchStep.h
|
||||||
launch/LaunchTask.cpp
|
launch/LaunchTask.cpp
|
||||||
@ -210,28 +210,17 @@ set(MINECRAFT_SOURCES
|
|||||||
minecraft/auth/AccountData.h
|
minecraft/auth/AccountData.h
|
||||||
minecraft/auth/AccountList.cpp
|
minecraft/auth/AccountList.cpp
|
||||||
minecraft/auth/AccountList.h
|
minecraft/auth/AccountList.h
|
||||||
minecraft/auth/AccountTask.cpp
|
|
||||||
minecraft/auth/AccountTask.h
|
|
||||||
minecraft/auth/AuthRequest.cpp
|
|
||||||
minecraft/auth/AuthRequest.h
|
|
||||||
minecraft/auth/AuthSession.cpp
|
minecraft/auth/AuthSession.cpp
|
||||||
minecraft/auth/AuthSession.h
|
minecraft/auth/AuthSession.h
|
||||||
minecraft/auth/AuthStep.cpp
|
|
||||||
minecraft/auth/AuthStep.h
|
minecraft/auth/AuthStep.h
|
||||||
minecraft/auth/MinecraftAccount.cpp
|
minecraft/auth/MinecraftAccount.cpp
|
||||||
minecraft/auth/MinecraftAccount.h
|
minecraft/auth/MinecraftAccount.h
|
||||||
minecraft/auth/Parsers.cpp
|
minecraft/auth/Parsers.cpp
|
||||||
minecraft/auth/Parsers.h
|
minecraft/auth/Parsers.h
|
||||||
|
|
||||||
minecraft/auth/flows/AuthFlow.cpp
|
minecraft/auth/AuthFlow.cpp
|
||||||
minecraft/auth/flows/AuthFlow.h
|
minecraft/auth/AuthFlow.h
|
||||||
minecraft/auth/flows/MSA.cpp
|
|
||||||
minecraft/auth/flows/MSA.h
|
|
||||||
minecraft/auth/flows/Offline.cpp
|
|
||||||
minecraft/auth/flows/Offline.h
|
|
||||||
|
|
||||||
minecraft/auth/steps/OfflineStep.cpp
|
|
||||||
minecraft/auth/steps/OfflineStep.h
|
|
||||||
minecraft/auth/steps/EntitlementsStep.cpp
|
minecraft/auth/steps/EntitlementsStep.cpp
|
||||||
minecraft/auth/steps/EntitlementsStep.h
|
minecraft/auth/steps/EntitlementsStep.h
|
||||||
minecraft/auth/steps/GetSkinStep.cpp
|
minecraft/auth/steps/GetSkinStep.cpp
|
||||||
@ -240,6 +229,8 @@ set(MINECRAFT_SOURCES
|
|||||||
minecraft/auth/steps/LauncherLoginStep.h
|
minecraft/auth/steps/LauncherLoginStep.h
|
||||||
minecraft/auth/steps/MinecraftProfileStep.cpp
|
minecraft/auth/steps/MinecraftProfileStep.cpp
|
||||||
minecraft/auth/steps/MinecraftProfileStep.h
|
minecraft/auth/steps/MinecraftProfileStep.h
|
||||||
|
minecraft/auth/steps/MSADeviceCodeStep.cpp
|
||||||
|
minecraft/auth/steps/MSADeviceCodeStep.h
|
||||||
minecraft/auth/steps/MSAStep.cpp
|
minecraft/auth/steps/MSAStep.cpp
|
||||||
minecraft/auth/steps/MSAStep.h
|
minecraft/auth/steps/MSAStep.h
|
||||||
minecraft/auth/steps/XboxAuthorizationStep.cpp
|
minecraft/auth/steps/XboxAuthorizationStep.cpp
|
||||||
@ -271,8 +262,8 @@ set(MINECRAFT_SOURCES
|
|||||||
minecraft/launch/ExtractNatives.h
|
minecraft/launch/ExtractNatives.h
|
||||||
minecraft/launch/LauncherPartLaunch.cpp
|
minecraft/launch/LauncherPartLaunch.cpp
|
||||||
minecraft/launch/LauncherPartLaunch.h
|
minecraft/launch/LauncherPartLaunch.h
|
||||||
minecraft/launch/MinecraftServerTarget.cpp
|
minecraft/launch/MinecraftTarget.cpp
|
||||||
minecraft/launch/MinecraftServerTarget.h
|
minecraft/launch/MinecraftTarget.h
|
||||||
minecraft/launch/PrintInstanceInfo.cpp
|
minecraft/launch/PrintInstanceInfo.cpp
|
||||||
minecraft/launch/PrintInstanceInfo.h
|
minecraft/launch/PrintInstanceInfo.h
|
||||||
minecraft/launch/ReconstructAssets.cpp
|
minecraft/launch/ReconstructAssets.cpp
|
||||||
@ -374,13 +365,17 @@ set(MINECRAFT_SOURCES
|
|||||||
minecraft/AssetsUtils.h
|
minecraft/AssetsUtils.h
|
||||||
minecraft/AssetsUtils.cpp
|
minecraft/AssetsUtils.cpp
|
||||||
|
|
||||||
# Minecraft services
|
# Minecraft skins
|
||||||
minecraft/services/CapeChange.cpp
|
minecraft/skins/CapeChange.cpp
|
||||||
minecraft/services/CapeChange.h
|
minecraft/skins/CapeChange.h
|
||||||
minecraft/services/SkinUpload.cpp
|
minecraft/skins/SkinUpload.cpp
|
||||||
minecraft/services/SkinUpload.h
|
minecraft/skins/SkinUpload.h
|
||||||
minecraft/services/SkinDelete.cpp
|
minecraft/skins/SkinDelete.cpp
|
||||||
minecraft/services/SkinDelete.h
|
minecraft/skins/SkinDelete.h
|
||||||
|
minecraft/skins/SkinModel.cpp
|
||||||
|
minecraft/skins/SkinModel.h
|
||||||
|
minecraft/skins/SkinList.cpp
|
||||||
|
minecraft/skins/SkinList.h
|
||||||
|
|
||||||
minecraft/Agent.h)
|
minecraft/Agent.h)
|
||||||
|
|
||||||
@ -455,6 +450,8 @@ set(TOOLS_SOURCES
|
|||||||
tools/JVisualVM.h
|
tools/JVisualVM.h
|
||||||
tools/MCEditTool.cpp
|
tools/MCEditTool.cpp
|
||||||
tools/MCEditTool.h
|
tools/MCEditTool.h
|
||||||
|
tools/GenericProfiler.cpp
|
||||||
|
tools/GenericProfiler.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(META_SOURCES
|
set(META_SOURCES
|
||||||
@ -626,7 +623,6 @@ set(PRISMUPDATER_SOURCES
|
|||||||
net/HttpMetaCache.h
|
net/HttpMetaCache.h
|
||||||
net/Logging.h
|
net/Logging.h
|
||||||
net/Logging.cpp
|
net/Logging.cpp
|
||||||
net/NetAction.h
|
|
||||||
net/NetRequest.cpp
|
net/NetRequest.cpp
|
||||||
net/NetRequest.h
|
net/NetRequest.h
|
||||||
net/NetJob.cpp
|
net/NetJob.cpp
|
||||||
@ -798,8 +794,6 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/InstanceWindow.cpp
|
ui/InstanceWindow.cpp
|
||||||
|
|
||||||
# FIXME: maybe find a better home for this.
|
# FIXME: maybe find a better home for this.
|
||||||
SkinUtils.cpp
|
|
||||||
SkinUtils.h
|
|
||||||
FileIgnoreProxy.cpp
|
FileIgnoreProxy.cpp
|
||||||
FileIgnoreProxy.h
|
FileIgnoreProxy.h
|
||||||
FastFileIconProvider.cpp
|
FastFileIconProvider.cpp
|
||||||
@ -829,6 +823,8 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/themes/DarkTheme.h
|
ui/themes/DarkTheme.h
|
||||||
ui/themes/ITheme.cpp
|
ui/themes/ITheme.cpp
|
||||||
ui/themes/ITheme.h
|
ui/themes/ITheme.h
|
||||||
|
ui/themes/HintOverrideProxyStyle.cpp
|
||||||
|
ui/themes/HintOverrideProxyStyle.h
|
||||||
ui/themes/SystemTheme.cpp
|
ui/themes/SystemTheme.cpp
|
||||||
ui/themes/SystemTheme.h
|
ui/themes/SystemTheme.h
|
||||||
ui/themes/IconTheme.cpp
|
ui/themes/IconTheme.cpp
|
||||||
@ -1029,8 +1025,6 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/dialogs/ReviewMessageBox.h
|
ui/dialogs/ReviewMessageBox.h
|
||||||
ui/dialogs/VersionSelectDialog.cpp
|
ui/dialogs/VersionSelectDialog.cpp
|
||||||
ui/dialogs/VersionSelectDialog.h
|
ui/dialogs/VersionSelectDialog.h
|
||||||
ui/dialogs/SkinUploadDialog.cpp
|
|
||||||
ui/dialogs/SkinUploadDialog.h
|
|
||||||
ui/dialogs/ResourceDownloadDialog.cpp
|
ui/dialogs/ResourceDownloadDialog.cpp
|
||||||
ui/dialogs/ResourceDownloadDialog.h
|
ui/dialogs/ResourceDownloadDialog.h
|
||||||
ui/dialogs/ScrollMessageBox.cpp
|
ui/dialogs/ScrollMessageBox.cpp
|
||||||
@ -1044,7 +1038,12 @@ SET(LAUNCHER_SOURCES
|
|||||||
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.h
|
||||||
|
|
||||||
# GUI - widgets
|
# GUI - widgets
|
||||||
|
ui/widgets/CheckComboBox.cpp
|
||||||
|
ui/widgets/CheckComboBox.h
|
||||||
ui/widgets/Common.cpp
|
ui/widgets/Common.cpp
|
||||||
ui/widgets/Common.h
|
ui/widgets/Common.h
|
||||||
ui/widgets/CustomCommands.cpp
|
ui/widgets/CustomCommands.cpp
|
||||||
@ -1173,7 +1172,6 @@ qt_wrap_ui(LAUNCHER_UI
|
|||||||
ui/dialogs/NewComponentDialog.ui
|
ui/dialogs/NewComponentDialog.ui
|
||||||
ui/dialogs/NewsDialog.ui
|
ui/dialogs/NewsDialog.ui
|
||||||
ui/dialogs/ProfileSelectDialog.ui
|
ui/dialogs/ProfileSelectDialog.ui
|
||||||
ui/dialogs/SkinUploadDialog.ui
|
|
||||||
ui/dialogs/ExportInstanceDialog.ui
|
ui/dialogs/ExportInstanceDialog.ui
|
||||||
ui/dialogs/ExportPackDialog.ui
|
ui/dialogs/ExportPackDialog.ui
|
||||||
ui/dialogs/ExportToModListDialog.ui
|
ui/dialogs/ExportToModListDialog.ui
|
||||||
@ -1187,6 +1185,8 @@ qt_wrap_ui(LAUNCHER_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
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_wrap_ui(PRISM_UPDATE_UI
|
qt_wrap_ui(PRISM_UPDATE_UI
|
||||||
@ -1246,7 +1246,6 @@ target_link_libraries(Launcher_logic
|
|||||||
tomlplusplus::tomlplusplus
|
tomlplusplus::tomlplusplus
|
||||||
qdcss
|
qdcss
|
||||||
BuildConfig
|
BuildConfig
|
||||||
Katabasis
|
|
||||||
Qt${QT_VERSION_MAJOR}::Widgets
|
Qt${QT_VERSION_MAJOR}::Widgets
|
||||||
ghcFilesystem::ghc_filesystem
|
ghcFilesystem::ghc_filesystem
|
||||||
)
|
)
|
||||||
@ -1264,6 +1263,7 @@ target_link_libraries(Launcher_logic
|
|||||||
Qt${QT_VERSION_MAJOR}::Concurrent
|
Qt${QT_VERSION_MAJOR}::Concurrent
|
||||||
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
|
||||||
${Launcher_QT_LIBS}
|
${Launcher_QT_LIBS}
|
||||||
)
|
)
|
||||||
target_link_libraries(Launcher_logic
|
target_link_libraries(Launcher_logic
|
||||||
@ -1334,7 +1334,6 @@ if(Launcher_BUILD_UPDATER)
|
|||||||
Qt${QT_VERSION_MAJOR}::Network
|
Qt${QT_VERSION_MAJOR}::Network
|
||||||
${Launcher_QT_LIBS}
|
${Launcher_QT_LIBS}
|
||||||
cmark::cmark
|
cmark::cmark
|
||||||
Katabasis
|
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable("${Launcher_Name}_updater" WIN32 updater/prismupdater/updater_main.cpp)
|
add_executable("${Launcher_Name}_updater" WIN32 updater/prismupdater/updater_main.cpp)
|
||||||
@ -1499,7 +1498,6 @@ if(INSTALL_BUNDLE STREQUAL "full")
|
|||||||
CONFIGURATIONS Debug RelWithDebInfo ""
|
CONFIGURATIONS Debug RelWithDebInfo ""
|
||||||
DESTINATION ${PLUGIN_DEST_DIR}
|
DESTINATION ${PLUGIN_DEST_DIR}
|
||||||
COMPONENT Runtime
|
COMPONENT Runtime
|
||||||
PATTERN "*qopensslbackend*" EXCLUDE
|
|
||||||
PATTERN "*qcertonlybackend*" EXCLUDE
|
PATTERN "*qcertonlybackend*" EXCLUDE
|
||||||
)
|
)
|
||||||
install(
|
install(
|
||||||
@ -1510,10 +1508,78 @@ if(INSTALL_BUNDLE STREQUAL "full")
|
|||||||
REGEX "dd\\." EXCLUDE
|
REGEX "dd\\." EXCLUDE
|
||||||
REGEX "_debug\\." EXCLUDE
|
REGEX "_debug\\." EXCLUDE
|
||||||
REGEX "\\.dSYM" EXCLUDE
|
REGEX "\\.dSYM" EXCLUDE
|
||||||
PATTERN "*qopensslbackend*" EXCLUDE
|
|
||||||
PATTERN "*qcertonlybackend*" EXCLUDE
|
PATTERN "*qcertonlybackend*" EXCLUDE
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
# Wayland support
|
||||||
|
if(EXISTS "${QT_PLUGINS_DIR}/wayland-graphics-integration-client")
|
||||||
|
install(
|
||||||
|
DIRECTORY "${QT_PLUGINS_DIR}/wayland-graphics-integration-client"
|
||||||
|
CONFIGURATIONS Debug RelWithDebInfo ""
|
||||||
|
DESTINATION ${PLUGIN_DEST_DIR}
|
||||||
|
COMPONENT Runtime
|
||||||
|
)
|
||||||
|
install(
|
||||||
|
DIRECTORY "${QT_PLUGINS_DIR}/wayland-graphics-integration-client"
|
||||||
|
CONFIGURATIONS Release MinSizeRel
|
||||||
|
DESTINATION ${PLUGIN_DEST_DIR}
|
||||||
|
COMPONENT Runtime
|
||||||
|
REGEX "dd\\." EXCLUDE
|
||||||
|
REGEX "_debug\\." EXCLUDE
|
||||||
|
REGEX "\\.dSYM" EXCLUDE
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
if(EXISTS "${QT_PLUGINS_DIR}/wayland-graphics-integration-server")
|
||||||
|
install(
|
||||||
|
DIRECTORY "${QT_PLUGINS_DIR}/wayland-graphics-integration-server"
|
||||||
|
CONFIGURATIONS Debug RelWithDebInfo ""
|
||||||
|
DESTINATION ${PLUGIN_DEST_DIR}
|
||||||
|
COMPONENT Runtime
|
||||||
|
)
|
||||||
|
install(
|
||||||
|
DIRECTORY "${QT_PLUGINS_DIR}/wayland-graphics-integration-server"
|
||||||
|
CONFIGURATIONS Release MinSizeRel
|
||||||
|
DESTINATION ${PLUGIN_DEST_DIR}
|
||||||
|
COMPONENT Runtime
|
||||||
|
REGEX "dd\\." EXCLUDE
|
||||||
|
REGEX "_debug\\." EXCLUDE
|
||||||
|
REGEX "\\.dSYM" EXCLUDE
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
if(EXISTS "${QT_PLUGINS_DIR}/wayland-decoration-client")
|
||||||
|
install(
|
||||||
|
DIRECTORY "${QT_PLUGINS_DIR}/wayland-decoration-client"
|
||||||
|
CONFIGURATIONS Debug RelWithDebInfo ""
|
||||||
|
DESTINATION ${PLUGIN_DEST_DIR}
|
||||||
|
COMPONENT Runtime
|
||||||
|
)
|
||||||
|
install(
|
||||||
|
DIRECTORY "${QT_PLUGINS_DIR}/wayland-decoration-client"
|
||||||
|
CONFIGURATIONS Release MinSizeRel
|
||||||
|
DESTINATION ${PLUGIN_DEST_DIR}
|
||||||
|
COMPONENT Runtime
|
||||||
|
REGEX "dd\\." EXCLUDE
|
||||||
|
REGEX "_debug\\." EXCLUDE
|
||||||
|
REGEX "\\.dSYM" EXCLUDE
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
if(EXISTS "${QT_PLUGINS_DIR}/wayland-shell-integration")
|
||||||
|
install(
|
||||||
|
DIRECTORY "${QT_PLUGINS_DIR}/wayland-shell-integration"
|
||||||
|
CONFIGURATIONS Debug RelWithDebInfo ""
|
||||||
|
DESTINATION ${PLUGIN_DEST_DIR}
|
||||||
|
COMPONENT Runtime
|
||||||
|
)
|
||||||
|
install(
|
||||||
|
DIRECTORY "${QT_PLUGINS_DIR}/wayland-shell-integration"
|
||||||
|
CONFIGURATIONS Release MinSizeRel
|
||||||
|
DESTINATION ${PLUGIN_DEST_DIR}
|
||||||
|
COMPONENT Runtime
|
||||||
|
REGEX "dd\\." EXCLUDE
|
||||||
|
REGEX "_debug\\." EXCLUDE
|
||||||
|
REGEX "\\.dSYM" EXCLUDE
|
||||||
|
)
|
||||||
|
endif()
|
||||||
configure_file(
|
configure_file(
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/install_prereqs.cmake.in"
|
"${CMAKE_CURRENT_SOURCE_DIR}/install_prereqs.cmake.in"
|
||||||
"${CMAKE_CURRENT_BINARY_DIR}/install_prereqs.cmake"
|
"${CMAKE_CURRENT_BINARY_DIR}/install_prereqs.cmake"
|
||||||
|
@ -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, const IPathMatcher::Ptr pathmatcher);
|
explicit DataMigrationTask(QObject* parent, const QString& sourcePath, const QString& targetPath, IPathMatcher::Ptr pathmatcher);
|
||||||
~DataMigrationTask() override = default;
|
~DataMigrationTask() override = default;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -37,143 +37,33 @@
|
|||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
|
#include "FileSystem.h"
|
||||||
/**
|
|
||||||
* This shouldn't exist, but until QTBUG-9328 and other unreported bugs are fixed, it needs to be a thing.
|
|
||||||
*/
|
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
bool IndirectOpen(T callable, qint64* pid_forked = nullptr)
|
|
||||||
{
|
|
||||||
auto pid = fork();
|
|
||||||
if (pid_forked) {
|
|
||||||
if (pid > 0)
|
|
||||||
*pid_forked = pid;
|
|
||||||
else
|
|
||||||
*pid_forked = 0;
|
|
||||||
}
|
|
||||||
if (pid == -1) {
|
|
||||||
qWarning() << "IndirectOpen failed to fork: " << errno;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// child - do the stuff
|
|
||||||
if (pid == 0) {
|
|
||||||
// unset all this garbage so it doesn't get passed to the child process
|
|
||||||
qunsetenv("LD_PRELOAD");
|
|
||||||
qunsetenv("LD_LIBRARY_PATH");
|
|
||||||
qunsetenv("LD_DEBUG");
|
|
||||||
qunsetenv("QT_PLUGIN_PATH");
|
|
||||||
qunsetenv("QT_FONTPATH");
|
|
||||||
|
|
||||||
// open the URL
|
|
||||||
auto status = callable();
|
|
||||||
|
|
||||||
// detach from the parent process group.
|
|
||||||
setsid();
|
|
||||||
|
|
||||||
// die. now. do not clean up anything, it would just hang forever.
|
|
||||||
_exit(status ? 0 : 1);
|
|
||||||
} else {
|
|
||||||
// parent - assume it worked.
|
|
||||||
int status;
|
|
||||||
while (waitpid(pid, &status, 0)) {
|
|
||||||
if (WIFEXITED(status)) {
|
|
||||||
return WEXITSTATUS(status) == 0;
|
|
||||||
}
|
|
||||||
if (WIFSIGNALED(status)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace DesktopServices {
|
namespace DesktopServices {
|
||||||
bool openDirectory(const QString& path, [[maybe_unused]] bool ensureExists)
|
bool openPath(const QFileInfo& path, bool ensureFolderPathExists)
|
||||||
{
|
{
|
||||||
qDebug() << "Opening directory" << path;
|
qDebug() << "Opening path" << path;
|
||||||
QDir parentPath;
|
if (ensureFolderPathExists) {
|
||||||
QDir dir(path);
|
FS::ensureFolderPathExists(path);
|
||||||
if (ensureExists && !dir.exists()) {
|
|
||||||
parentPath.mkpath(dir.absolutePath());
|
|
||||||
}
|
}
|
||||||
auto f = [&]() { return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath())); };
|
return openUrl(QUrl::fromLocalFile(QFileInfo(path).absoluteFilePath()));
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
|
||||||
if (!isSandbox()) {
|
|
||||||
return IndirectOpen(f);
|
|
||||||
} else {
|
|
||||||
return f();
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
return f();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool openFile(const QString& path)
|
bool openPath(const QString& path, bool ensureFolderPathExists)
|
||||||
{
|
{
|
||||||
qDebug() << "Opening file" << path;
|
return openPath(QFileInfo(path), ensureFolderPathExists);
|
||||||
auto f = [&]() { return QDesktopServices::openUrl(QUrl::fromLocalFile(path)); };
|
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
|
||||||
if (!isSandbox()) {
|
|
||||||
return IndirectOpen(f);
|
|
||||||
} else {
|
|
||||||
return f();
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
return f();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool openFile(const QString& application, const QString& path, const QString& workingDirectory, qint64* pid)
|
|
||||||
{
|
|
||||||
qDebug() << "Opening file" << path << "using" << application;
|
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
|
||||||
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
|
|
||||||
if (!isSandbox()) {
|
|
||||||
return IndirectOpen([&]() { return QProcess::startDetached(application, QStringList() << path, workingDirectory); }, pid);
|
|
||||||
} else {
|
|
||||||
return QProcess::startDetached(application, QStringList() << path, workingDirectory, pid);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
return QProcess::startDetached(application, QStringList() << path, workingDirectory, pid);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool run(const QString& application, const QStringList& args, const QString& workingDirectory, qint64* pid)
|
bool run(const QString& application, const QStringList& args, const QString& workingDirectory, qint64* pid)
|
||||||
{
|
{
|
||||||
qDebug() << "Running" << application << "with args" << args.join(' ');
|
qDebug() << "Running" << application << "with args" << args.join(' ');
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
|
||||||
if (!isSandbox()) {
|
|
||||||
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
|
|
||||||
return IndirectOpen([&]() { return QProcess::startDetached(application, args, workingDirectory); }, pid);
|
|
||||||
} else {
|
|
||||||
return QProcess::startDetached(application, args, workingDirectory, pid);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
return QProcess::startDetached(application, args, workingDirectory, pid);
|
return QProcess::startDetached(application, args, workingDirectory, pid);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool openUrl(const QUrl& url)
|
bool openUrl(const QUrl& url)
|
||||||
{
|
{
|
||||||
qDebug() << "Opening URL" << url.toString();
|
qDebug() << "Opening URL" << url.toString();
|
||||||
auto f = [&]() { return QDesktopServices::openUrl(url); };
|
return QDesktopServices::openUrl(url);
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
|
||||||
if (!isSandbox()) {
|
|
||||||
return IndirectOpen(f);
|
|
||||||
} else {
|
|
||||||
return f();
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
return f();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isFlatpak()
|
bool isFlatpak()
|
||||||
@ -194,9 +84,4 @@ bool isSnap()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isSandbox()
|
|
||||||
{
|
|
||||||
return isSnap() || isFlatpak();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace DesktopServices
|
} // namespace DesktopServices
|
||||||
|
@ -3,31 +3,30 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
|
class QFileInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This wraps around QDesktopServices and adds workarounds where needed
|
* This wraps around QDesktopServices and adds workarounds where needed
|
||||||
* Use this instead of QDesktopServices!
|
* Use this instead of QDesktopServices!
|
||||||
*/
|
*/
|
||||||
namespace DesktopServices {
|
namespace DesktopServices {
|
||||||
/**
|
/**
|
||||||
* Open a file in whatever application is applicable
|
* Open a path in whatever application is applicable.
|
||||||
|
* @param ensureFolderPathExists Make sure the path exists
|
||||||
*/
|
*/
|
||||||
bool openFile(const QString& path);
|
bool openPath(const QFileInfo& path, bool ensureFolderPathExists = false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a file in the specified application
|
* Open a path in whatever application is applicable.
|
||||||
|
* @param ensureFolderPathExists Make sure the path exists
|
||||||
*/
|
*/
|
||||||
bool openFile(const QString& application, const QString& path, const QString& workingDirectory = QString(), qint64* pid = 0);
|
bool openPath(const QString& path, bool ensureFolderPathExists = false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run an application
|
* Run an application
|
||||||
*/
|
*/
|
||||||
bool run(const QString& application, const QStringList& args, const QString& workingDirectory = QString(), qint64* pid = 0);
|
bool run(const QString& application, const QStringList& args, const QString& workingDirectory = QString(), qint64* pid = 0);
|
||||||
|
|
||||||
/**
|
|
||||||
* Open a directory
|
|
||||||
*/
|
|
||||||
bool openDirectory(const QString& path, bool ensureExists = false);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the URL, most likely in a browser. Maybe.
|
* Open the URL, most likely in a browser. Maybe.
|
||||||
*/
|
*/
|
||||||
@ -42,9 +41,4 @@ bool isFlatpak();
|
|||||||
* Determine whether the launcher is running in a Snap environment
|
* Determine whether the launcher is running in a Snap environment
|
||||||
*/
|
*/
|
||||||
bool isSnap();
|
bool isSnap();
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine whether the launcher is running in a sandboxed (Flatpak or Snap) environment
|
|
||||||
*/
|
|
||||||
bool isSandbox();
|
|
||||||
} // namespace DesktopServices
|
} // namespace DesktopServices
|
||||||
|
@ -1,4 +1,37 @@
|
|||||||
// Licensed under the Apache-2.0 license. See README.md for details.
|
// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2024 TheKodeToad <TheKodeToad@proton.me>
|
||||||
|
*
|
||||||
|
* 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
|
#pragma once
|
||||||
|
|
||||||
@ -8,12 +41,12 @@
|
|||||||
|
|
||||||
class Exception : public std::exception {
|
class Exception : public std::exception {
|
||||||
public:
|
public:
|
||||||
Exception(const QString& message) : std::exception(), m_message(message) { qCritical() << "Exception:" << message; }
|
Exception(const QString& message) : std::exception(), m_message(message.toUtf8()) { qCritical() << "Exception:" << message; }
|
||||||
Exception(const Exception& other) : std::exception(), m_message(other.cause()) {}
|
Exception(const Exception& other) : std::exception(), m_message(other.m_message) {}
|
||||||
virtual ~Exception() noexcept {}
|
virtual ~Exception() noexcept {}
|
||||||
const char* what() const noexcept { return m_message.toLatin1().constData(); }
|
const char* what() const noexcept { return m_message.constData(); }
|
||||||
QString cause() const { return m_message; }
|
QString cause() const { return QString::fromUtf8(m_message); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_message;
|
QByteArray m_message;
|
||||||
};
|
};
|
||||||
|
@ -272,15 +272,19 @@ bool ensureFilePathExists(QString filenamepath)
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ensureFolderPathExists(QString foldernamepath)
|
bool ensureFolderPathExists(const QFileInfo folderPath)
|
||||||
{
|
{
|
||||||
QFileInfo a(foldernamepath);
|
|
||||||
QDir dir;
|
QDir dir;
|
||||||
QString ensuredPath = a.filePath();
|
QString ensuredPath = folderPath.filePath();
|
||||||
bool success = dir.mkpath(ensuredPath);
|
bool success = dir.mkpath(ensuredPath);
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ensureFolderPathExists(const QString folderPathName)
|
||||||
|
{
|
||||||
|
return ensureFolderPathExists(QFileInfo(folderPathName));
|
||||||
|
}
|
||||||
|
|
||||||
bool copyFileAttributes(QString src, QString dst)
|
bool copyFileAttributes(QString src, QString dst)
|
||||||
{
|
{
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
@ -643,6 +647,19 @@ void ExternalLinkFileProcess::runLinkFile()
|
|||||||
qDebug() << "Process exited";
|
qDebug() << "Process exited";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool moveByCopy(const QString& source, const QString& dest)
|
||||||
|
{
|
||||||
|
if (!copy(source, dest)()) { // copy
|
||||||
|
qDebug() << "Copy of" << source << "to" << dest << "failed!";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!deletePath(source)) { // remove original
|
||||||
|
qDebug() << "Deletion of" << source << "failed!";
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool move(const QString& source, const QString& dest)
|
bool move(const QString& source, const QString& dest)
|
||||||
{
|
{
|
||||||
std::error_code err;
|
std::error_code err;
|
||||||
@ -650,13 +667,14 @@ bool move(const QString& source, const QString& dest)
|
|||||||
ensureFilePathExists(dest);
|
ensureFilePathExists(dest);
|
||||||
fs::rename(StringUtils::toStdString(source), StringUtils::toStdString(dest), err);
|
fs::rename(StringUtils::toStdString(source), StringUtils::toStdString(dest), err);
|
||||||
|
|
||||||
if (err) {
|
if (err.value() != 0) {
|
||||||
qWarning() << "Failed to move file:" << QString::fromStdString(err.message());
|
if (moveByCopy(source, dest))
|
||||||
qDebug() << "Source file:" << source;
|
return true;
|
||||||
qDebug() << "Destination file:" << dest;
|
qDebug() << "Move of" << source << "to" << dest << "failed!";
|
||||||
|
qWarning() << "Failed to move file:" << QString::fromStdString(err.message()) << QString::number(err.value());
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
return err.value() == 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool deletePath(QString path)
|
bool deletePath(QString path)
|
||||||
@ -797,16 +815,68 @@ QString NormalizePath(QString path)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString badFilenameChars = "\"\\/?<>:;*|!+\r\n";
|
static const QString BAD_WIN_CHARS = "<>:\"|?*\r\n";
|
||||||
|
static const QString BAD_NTFS_CHARS = "<>:\"|?*";
|
||||||
|
static const QString BAD_HFS_CHARS = ":";
|
||||||
|
|
||||||
|
static const QString BAD_FILENAME_CHARS = BAD_WIN_CHARS + "\\/";
|
||||||
|
|
||||||
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith)
|
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < string.length(); i++) {
|
for (int i = 0; i < string.length(); i++)
|
||||||
if (badFilenameChars.contains(string[i])) {
|
if (string.at(i) < ' ' || BAD_FILENAME_CHARS.contains(string.at(i)))
|
||||||
string[i] = replaceWith;
|
string[i] = replaceWith;
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString RemoveInvalidPathChars(QString path, QChar replaceWith)
|
||||||
|
{
|
||||||
|
QString invalidChars;
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
invalidChars = BAD_WIN_CHARS;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// the null character is ignored in this check as it was not a problem until now
|
||||||
|
switch (statFS(path).fsType) {
|
||||||
|
case FilesystemType::FAT: // similar to NTFS
|
||||||
|
/* fallthrough */
|
||||||
|
case FilesystemType::NTFS:
|
||||||
|
/* fallthrough */
|
||||||
|
case FilesystemType::REFS: // similar to NTFS(should be available only on windows)
|
||||||
|
invalidChars += BAD_NTFS_CHARS;
|
||||||
|
break;
|
||||||
|
// case FilesystemType::EXT:
|
||||||
|
// case FilesystemType::EXT_2_OLD:
|
||||||
|
// case FilesystemType::EXT_2_3_4:
|
||||||
|
// case FilesystemType::XFS:
|
||||||
|
// case FilesystemType::BTRFS:
|
||||||
|
// case FilesystemType::NFS:
|
||||||
|
// case FilesystemType::ZFS:
|
||||||
|
case FilesystemType::APFS:
|
||||||
|
/* fallthrough */
|
||||||
|
case FilesystemType::HFS:
|
||||||
|
/* fallthrough */
|
||||||
|
case FilesystemType::HFSPLUS:
|
||||||
|
/* fallthrough */
|
||||||
|
case FilesystemType::HFSX:
|
||||||
|
invalidChars += BAD_HFS_CHARS;
|
||||||
|
break;
|
||||||
|
// case FilesystemType::FUSEBLK:
|
||||||
|
// case FilesystemType::F2FS:
|
||||||
|
// case FilesystemType::UNKNOWN:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invalidChars.size() != 0) {
|
||||||
|
for (int i = 0; i < path.length(); i++) {
|
||||||
|
if (path.at(i) < ' ' || invalidChars.contains(path.at(i))) {
|
||||||
|
path[i] = replaceWith;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return string;
|
|
||||||
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString DirNameFromString(QString string, QString inDir)
|
QString DirNameFromString(QString string, QString inDir)
|
||||||
@ -1581,4 +1651,70 @@ uintmax_t hardLinkCount(const QString& path)
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
// returns 8.3 file format from long path
|
||||||
|
QString shortPathName(const QString& file)
|
||||||
|
{
|
||||||
|
auto input = file.toStdWString();
|
||||||
|
std::wstring output;
|
||||||
|
long length = GetShortPathNameW(input.c_str(), NULL, 0);
|
||||||
|
if (length == 0)
|
||||||
|
return {};
|
||||||
|
// NOTE: this resizing might seem weird...
|
||||||
|
// when GetShortPathNameW fails, it returns length including null character
|
||||||
|
// when it succeeds, it returns length excluding null character
|
||||||
|
// See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa364989(v=vs.85).aspx
|
||||||
|
output.resize(length);
|
||||||
|
if (GetShortPathNameW(input.c_str(), (LPWSTR)output.c_str(), length) == 0)
|
||||||
|
return {};
|
||||||
|
output.resize(length - 1);
|
||||||
|
QString ret = QString::fromStdWString(output);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the string survives roundtrip through local 8bit encoding...
|
||||||
|
bool fitsInLocal8bit(const QString& string)
|
||||||
|
{
|
||||||
|
return string == QString::fromLocal8Bit(string.toLocal8Bit());
|
||||||
|
}
|
||||||
|
|
||||||
|
QString getPathNameInLocal8bit(const QString& file)
|
||||||
|
{
|
||||||
|
if (!fitsInLocal8bit(file)) {
|
||||||
|
auto path = shortPathName(file);
|
||||||
|
if (!path.isEmpty()) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
// in case shortPathName fails just return the path as is
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QString getUniqueResourceName(const QString& filePath)
|
||||||
|
{
|
||||||
|
auto newFileName = filePath;
|
||||||
|
if (!newFileName.endsWith(".disabled")) {
|
||||||
|
return newFileName; // prioritize enabled mods
|
||||||
|
}
|
||||||
|
newFileName.chop(9);
|
||||||
|
if (!QFile::exists(newFileName)) {
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
QFileInfo fileInfo(filePath);
|
||||||
|
auto baseName = fileInfo.completeBaseName();
|
||||||
|
auto path = fileInfo.absolutePath();
|
||||||
|
|
||||||
|
int counter = 1;
|
||||||
|
do {
|
||||||
|
if (counter == 1) {
|
||||||
|
newFileName = FS::PathCombine(path, baseName + ".duplicate");
|
||||||
|
} else {
|
||||||
|
newFileName = FS::PathCombine(path, baseName + ".duplicate" + QString::number(counter));
|
||||||
|
}
|
||||||
|
counter++;
|
||||||
|
} while (QFile::exists(newFileName));
|
||||||
|
|
||||||
|
return newFileName;
|
||||||
|
}
|
||||||
} // namespace FS
|
} // namespace FS
|
||||||
|
@ -72,7 +72,7 @@ void appendSafe(const QString& filename, const QByteArray& data);
|
|||||||
void append(const QString& filename, const QByteArray& data);
|
void append(const QString& filename, const QByteArray& data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* read data from a file safely\
|
* read data from a file safely
|
||||||
*/
|
*/
|
||||||
QByteArray read(const QString& filename);
|
QByteArray read(const QString& filename);
|
||||||
|
|
||||||
@ -91,7 +91,13 @@ bool ensureFilePathExists(QString filenamepath);
|
|||||||
* Creates all the folders in a path for the specified path
|
* Creates all the folders in a path for the specified path
|
||||||
* last segment of the path is treated as a folder name and is created!
|
* last segment of the path is treated as a folder name and is created!
|
||||||
*/
|
*/
|
||||||
bool ensureFolderPathExists(QString filenamepath);
|
bool ensureFolderPathExists(const QFileInfo folderPath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates all the folders in a path for the specified path
|
||||||
|
* last segment of the path is treated as a folder name and is created!
|
||||||
|
*/
|
||||||
|
bool ensureFolderPathExists(const QString folderPathName);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Copies a directory and it's contents from src to dest
|
* @brief Copies a directory and it's contents from src to dest
|
||||||
@ -234,6 +240,7 @@ class create_link : public QObject {
|
|||||||
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
|
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
|
||||||
|
|
||||||
int totalLinked() { return m_linked; }
|
int totalLinked() { return m_linked; }
|
||||||
|
int totalToLink() { return static_cast<int>(m_links_to_make.size()); }
|
||||||
|
|
||||||
void runPrivileged() { runPrivileged(QString()); }
|
void runPrivileged() { runPrivileged(QString()); }
|
||||||
void runPrivileged(const QString& offset);
|
void runPrivileged(const QString& offset);
|
||||||
@ -336,6 +343,8 @@ QString NormalizePath(QString path);
|
|||||||
|
|
||||||
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith = '-');
|
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith = '-');
|
||||||
|
|
||||||
|
QString RemoveInvalidPathChars(QString string, QChar replaceWith = '-');
|
||||||
|
|
||||||
QString DirNameFromString(QString string, QString inDir = ".");
|
QString DirNameFromString(QString string, QString inDir = ".");
|
||||||
|
|
||||||
/// Checks if the a given Path contains "!"
|
/// Checks if the a given Path contains "!"
|
||||||
@ -370,6 +379,7 @@ enum class FilesystemType {
|
|||||||
HFSX,
|
HFSX,
|
||||||
FUSEBLK,
|
FUSEBLK,
|
||||||
F2FS,
|
F2FS,
|
||||||
|
BCACHEFS,
|
||||||
UNKNOWN
|
UNKNOWN
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -398,6 +408,7 @@ static const QMap<FilesystemType, QStringList> s_filesystem_type_names = { { Fil
|
|||||||
{ FilesystemType::HFSX, { "HFSX" } },
|
{ FilesystemType::HFSX, { "HFSX" } },
|
||||||
{ FilesystemType::FUSEBLK, { "FUSEBLK" } },
|
{ FilesystemType::FUSEBLK, { "FUSEBLK" } },
|
||||||
{ FilesystemType::F2FS, { "F2FS" } },
|
{ FilesystemType::F2FS, { "F2FS" } },
|
||||||
|
{ FilesystemType::BCACHEFS, { "BCACHEFS" } },
|
||||||
{ FilesystemType::UNKNOWN, { "UNKNOWN" } } };
|
{ FilesystemType::UNKNOWN, { "UNKNOWN" } } };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -450,7 +461,7 @@ QString nearestExistentAncestor(const QString& path);
|
|||||||
FilesystemInfo statFS(const QString& path);
|
FilesystemInfo statFS(const QString& path);
|
||||||
|
|
||||||
static const QList<FilesystemType> s_clone_filesystems = { FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS,
|
static const QList<FilesystemType> s_clone_filesystems = { FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS,
|
||||||
FilesystemType::XFS, FilesystemType::REFS };
|
FilesystemType::XFS, FilesystemType::REFS, FilesystemType::BCACHEFS };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief if the Filesystem is reflink/clone capable
|
* @brief if the Filesystem is reflink/clone capable
|
||||||
@ -545,4 +556,10 @@ bool canLink(const QString& src, const QString& dst);
|
|||||||
|
|
||||||
uintmax_t hardLinkCount(const QString& path);
|
uintmax_t hardLinkCount(const QString& path);
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
QString getPathNameInLocal8bit(const QString& file);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QString getUniqueResourceName(const QString& filePath);
|
||||||
|
|
||||||
} // namespace FS
|
} // namespace FS
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
#include "InstanceCopyTask.h"
|
#include "InstanceCopyTask.h"
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QtConcurrentRun>
|
#include <QtConcurrentRun>
|
||||||
|
#include <memory>
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
#include "NullInstance.h"
|
#include "NullInstance.h"
|
||||||
#include "pathmatcher/RegexpMatcher.h"
|
#include "pathmatcher/RegexpMatcher.h"
|
||||||
#include "settings/INISettingsObject.h"
|
#include "settings/INISettingsObject.h"
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs)
|
InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs)
|
||||||
{
|
{
|
||||||
@ -38,38 +40,50 @@ void InstanceCopyTask::executeTask()
|
|||||||
{
|
{
|
||||||
setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
|
setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
|
||||||
|
|
||||||
auto copySaves = [&]() {
|
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] {
|
||||||
QFileInfo mcDir(FS::PathCombine(m_stagingPath, "minecraft"));
|
|
||||||
QFileInfo dotMCDir(FS::PathCombine(m_stagingPath, ".minecraft"));
|
|
||||||
|
|
||||||
QString staging_mc_dir;
|
|
||||||
if (mcDir.exists() && !dotMCDir.exists())
|
|
||||||
staging_mc_dir = mcDir.filePath();
|
|
||||||
else
|
|
||||||
staging_mc_dir = dotMCDir.filePath();
|
|
||||||
|
|
||||||
FS::copy savesCopy(FS::PathCombine(m_origInstance->gameRoot(), "saves"), FS::PathCombine(staging_mc_dir, "saves"));
|
|
||||||
savesCopy.followSymlinks(true);
|
|
||||||
|
|
||||||
return savesCopy();
|
|
||||||
};
|
|
||||||
|
|
||||||
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this, copySaves] {
|
|
||||||
if (m_useClone) {
|
if (m_useClone) {
|
||||||
FS::clone folderClone(m_origInstance->instanceRoot(), m_stagingPath);
|
FS::clone folderClone(m_origInstance->instanceRoot(), m_stagingPath);
|
||||||
folderClone.matcher(m_matcher.get());
|
folderClone.matcher(m_matcher.get());
|
||||||
|
|
||||||
|
folderClone(true);
|
||||||
|
setProgress(0, folderClone.totalCloned());
|
||||||
|
connect(&folderClone, &FS::clone::fileCloned,
|
||||||
|
[this](QString src, QString dst) { setProgress(m_progress + 1, m_progressTotal); });
|
||||||
return folderClone();
|
return folderClone();
|
||||||
} else if (m_useLinks || m_useHardLinks) {
|
}
|
||||||
|
if (m_useLinks || m_useHardLinks) {
|
||||||
|
std::unique_ptr<FS::copy> savesCopy;
|
||||||
|
if (m_copySaves) {
|
||||||
|
QFileInfo mcDir(FS::PathCombine(m_stagingPath, "minecraft"));
|
||||||
|
QFileInfo dotMCDir(FS::PathCombine(m_stagingPath, ".minecraft"));
|
||||||
|
|
||||||
|
QString staging_mc_dir;
|
||||||
|
if (dotMCDir.exists() && !mcDir.exists())
|
||||||
|
staging_mc_dir = dotMCDir.filePath();
|
||||||
|
else
|
||||||
|
staging_mc_dir = mcDir.filePath();
|
||||||
|
|
||||||
|
savesCopy = std::make_unique<FS::copy>(FS::PathCombine(m_origInstance->gameRoot(), "saves"),
|
||||||
|
FS::PathCombine(staging_mc_dir, "saves"));
|
||||||
|
savesCopy->followSymlinks(true);
|
||||||
|
(*savesCopy)(true);
|
||||||
|
setProgress(0, savesCopy->totalCopied());
|
||||||
|
connect(savesCopy.get(), &FS::copy::fileCopied, [this](QString src) { setProgress(m_progress + 1, m_progressTotal); });
|
||||||
|
}
|
||||||
FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath);
|
FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath);
|
||||||
int depth = m_linkRecursively ? -1 : 0; // we need to at least link the top level instead of the instance folder
|
int depth = m_linkRecursively ? -1 : 0; // we need to at least link the top level instead of the instance folder
|
||||||
folderLink.linkRecursively(true).setMaxDepth(depth).useHardLinks(m_useHardLinks).matcher(m_matcher.get());
|
folderLink.linkRecursively(true).setMaxDepth(depth).useHardLinks(m_useHardLinks).matcher(m_matcher.get());
|
||||||
|
|
||||||
|
folderLink(true);
|
||||||
|
setProgress(0, m_progressTotal + folderLink.totalToLink());
|
||||||
|
connect(&folderLink, &FS::create_link::fileLinked,
|
||||||
|
[this](QString src, QString dst) { setProgress(m_progress + 1, m_progressTotal); });
|
||||||
bool there_were_errors = false;
|
bool there_were_errors = false;
|
||||||
|
|
||||||
if (!folderLink()) {
|
if (!folderLink()) {
|
||||||
#if defined Q_OS_WIN32
|
#if defined Q_OS_WIN32
|
||||||
if (!m_useHardLinks) {
|
if (!m_useHardLinks) {
|
||||||
|
setProgress(0, m_progressTotal);
|
||||||
qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks";
|
qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks";
|
||||||
|
|
||||||
qDebug() << "attempting to run with privelage";
|
qDebug() << "attempting to run with privelage";
|
||||||
@ -94,13 +108,11 @@ void InstanceCopyTask::executeTask()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_copySaves) {
|
if (savesCopy) {
|
||||||
there_were_errors |= !copySaves();
|
there_were_errors |= !(*savesCopy)();
|
||||||
}
|
}
|
||||||
|
|
||||||
return got_priv_results && !there_were_errors;
|
return got_priv_results && !there_were_errors;
|
||||||
} else {
|
|
||||||
qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str();
|
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str();
|
qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str();
|
||||||
@ -108,17 +120,19 @@ void InstanceCopyTask::executeTask()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_copySaves) {
|
if (savesCopy) {
|
||||||
there_were_errors |= !copySaves();
|
there_were_errors |= !(*savesCopy)();
|
||||||
}
|
}
|
||||||
|
|
||||||
return !there_were_errors;
|
return !there_were_errors;
|
||||||
} else {
|
|
||||||
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
|
|
||||||
folderCopy.followSymlinks(false).matcher(m_matcher.get());
|
|
||||||
|
|
||||||
return folderCopy();
|
|
||||||
}
|
}
|
||||||
|
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
|
||||||
|
folderCopy.followSymlinks(false).matcher(m_matcher.get());
|
||||||
|
|
||||||
|
folderCopy(true);
|
||||||
|
setProgress(0, folderCopy.totalCopied());
|
||||||
|
connect(&folderCopy, &FS::copy::fileCopied, [this](QString src) { setProgress(m_progress + 1, m_progressTotal); });
|
||||||
|
return folderCopy();
|
||||||
});
|
});
|
||||||
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &InstanceCopyTask::copyFinished);
|
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &InstanceCopyTask::copyFinished);
|
||||||
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &InstanceCopyTask::copyAborted);
|
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &InstanceCopyTask::copyAborted);
|
||||||
@ -142,9 +156,8 @@ void InstanceCopyTask::copyFinished()
|
|||||||
if (!m_keepPlaytime) {
|
if (!m_keepPlaytime) {
|
||||||
inst->resetTimePlayed();
|
inst->resetTimePlayed();
|
||||||
}
|
}
|
||||||
if (m_useLinks)
|
|
||||||
inst->addLinkedInstanceId(m_origInstance->id());
|
|
||||||
if (m_useLinks) {
|
if (m_useLinks) {
|
||||||
|
inst->addLinkedInstanceId(m_origInstance->id());
|
||||||
auto allowed_symlinks_file = QFileInfo(FS::PathCombine(inst->gameRoot(), "allowed_symlinks.txt"));
|
auto allowed_symlinks_file = QFileInfo(FS::PathCombine(inst->gameRoot(), "allowed_symlinks.txt"));
|
||||||
|
|
||||||
QByteArray allowed_symlinks;
|
QByteArray allowed_symlinks;
|
||||||
@ -171,3 +184,14 @@ void InstanceCopyTask::copyAborted()
|
|||||||
emitFailed(tr("Instance folder copy has been aborted."));
|
emitFailed(tr("Instance folder copy has been aborted."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool InstanceCopyTask::abort()
|
||||||
|
{
|
||||||
|
if (m_copyFutureWatcher.isRunning()) {
|
||||||
|
m_copyFutureWatcher.cancel();
|
||||||
|
// NOTE: Here we don't do `emitAborted()` because it will be done when `m_copyFutureWatcher` actually cancels, which may not occur
|
||||||
|
// immediately.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
@ -19,6 +19,7 @@ class InstanceCopyTask : public InstanceTask {
|
|||||||
protected:
|
protected:
|
||||||
//! Entry point for tasks.
|
//! Entry point for tasks.
|
||||||
virtual void executeTask() override;
|
virtual void executeTask() override;
|
||||||
|
bool abort() override;
|
||||||
void copyFinished();
|
void copyFinished();
|
||||||
void copyAborted();
|
void copyAborted();
|
||||||
|
|
||||||
|
@ -2,8 +2,7 @@
|
|||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
#include "FileSystem.h"
|
||||||
InstanceCreationTask::InstanceCreationTask() = default;
|
|
||||||
|
|
||||||
void InstanceCreationTask::executeTask()
|
void InstanceCreationTask::executeTask()
|
||||||
{
|
{
|
||||||
@ -47,7 +46,7 @@ void InstanceCreationTask::executeTask()
|
|||||||
if (!QFile::exists(path))
|
if (!QFile::exists(path))
|
||||||
continue;
|
continue;
|
||||||
qDebug() << "Removing" << path;
|
qDebug() << "Removing" << path;
|
||||||
if (!QFile::remove(path)) {
|
if (!FS::deletePath(path)) {
|
||||||
qCritical() << "Couldn't remove the old conflicting files.";
|
qCritical() << "Couldn't remove the old conflicting files.";
|
||||||
emitFailed(tr("Failed to remove old conflicting files."));
|
emitFailed(tr("Failed to remove old conflicting files."));
|
||||||
return;
|
return;
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
class InstanceCreationTask : public InstanceTask {
|
class InstanceCreationTask : public InstanceTask {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
InstanceCreationTask();
|
InstanceCreationTask() = default;
|
||||||
virtual ~InstanceCreationTask() = default;
|
virtual ~InstanceCreationTask() = default;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -56,10 +56,11 @@
|
|||||||
|
|
||||||
#include <QtConcurrentRun>
|
#include <QtConcurrentRun>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include <quazip/quazipdir.h>
|
#include <quazip/quazipdir.h>
|
||||||
|
|
||||||
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent, QMap<QString, QString>&& extra_info)
|
InstanceImportTask::InstanceImportTask(const QUrl& sourceUrl, QWidget* parent, QMap<QString, QString>&& extra_info)
|
||||||
: m_sourceUrl(sourceUrl), m_extra_info(extra_info), m_parent(parent)
|
: m_sourceUrl(sourceUrl), m_extra_info(extra_info), m_parent(parent)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
@ -68,15 +69,8 @@ bool InstanceImportTask::abort()
|
|||||||
if (!canAbort())
|
if (!canAbort())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (m_filesNetJob)
|
if (task)
|
||||||
m_filesNetJob->abort();
|
task->abort();
|
||||||
if (m_extractFuture.isRunning()) {
|
|
||||||
// NOTE: The tasks created by QtConcurrent::run() can't actually get cancelled,
|
|
||||||
// but we can use this call to check the state when the extraction finishes.
|
|
||||||
m_extractFuture.cancel();
|
|
||||||
m_extractFuture.waitForFinished();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task::abort();
|
return Task::abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,7 +83,6 @@ void InstanceImportTask::executeTask()
|
|||||||
processZipPack();
|
processZipPack();
|
||||||
} else {
|
} else {
|
||||||
setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString()));
|
setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString()));
|
||||||
m_downloadRequired = true;
|
|
||||||
|
|
||||||
downloadFromUrl();
|
downloadFromUrl();
|
||||||
}
|
}
|
||||||
@ -97,115 +90,132 @@ void InstanceImportTask::executeTask()
|
|||||||
|
|
||||||
void InstanceImportTask::downloadFromUrl()
|
void InstanceImportTask::downloadFromUrl()
|
||||||
{
|
{
|
||||||
const QString path = m_sourceUrl.host() + '/' + m_sourceUrl.path();
|
const QString path(m_sourceUrl.host() + '/' + m_sourceUrl.path());
|
||||||
|
|
||||||
auto entry = APPLICATION->metacache()->resolveEntry("general", path);
|
auto entry = APPLICATION->metacache()->resolveEntry("general", path);
|
||||||
entry->setStale(true);
|
entry->setStale(true);
|
||||||
m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network()));
|
|
||||||
m_filesNetJob->addNetAction(Net::ApiDownload::makeCached(m_sourceUrl, entry));
|
|
||||||
m_archivePath = entry->getFullPath();
|
m_archivePath = entry->getFullPath();
|
||||||
|
|
||||||
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
|
auto filesNetJob = makeShared<NetJob>(tr("Modpack download"), APPLICATION->network());
|
||||||
connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
|
filesNetJob->addNetAction(Net::ApiDownload::makeCached(m_sourceUrl, entry));
|
||||||
connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propagateStepProgress);
|
|
||||||
connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed);
|
connect(filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::processZipPack);
|
||||||
connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted);
|
connect(filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::setProgress);
|
||||||
m_filesNetJob->start();
|
connect(filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propagateStepProgress);
|
||||||
|
connect(filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::emitFailed);
|
||||||
|
connect(filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::emitAborted);
|
||||||
|
task.reset(filesNetJob);
|
||||||
|
filesNetJob->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstanceImportTask::downloadSucceeded()
|
QString InstanceImportTask::getRootFromZip(QuaZip* zip, const QString& root)
|
||||||
{
|
{
|
||||||
processZipPack();
|
if (!isRunning()) {
|
||||||
m_filesNetJob.reset();
|
return {};
|
||||||
}
|
}
|
||||||
|
QuaZipDir rootDir(zip, root);
|
||||||
|
for (auto&& fileName : rootDir.entryList(QDir::Files)) {
|
||||||
|
setDetails(fileName);
|
||||||
|
if (fileName == "instance.cfg") {
|
||||||
|
qDebug() << "MultiMC:" << true;
|
||||||
|
m_modpackType = ModpackType::MultiMC;
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
if (fileName == "manifest.json") {
|
||||||
|
qDebug() << "Flame:" << true;
|
||||||
|
m_modpackType = ModpackType::Flame;
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
void InstanceImportTask::downloadFailed(QString reason)
|
QCoreApplication::processEvents();
|
||||||
{
|
}
|
||||||
emitFailed(reason);
|
|
||||||
m_filesNetJob.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total)
|
// Recurse the search to non-ignored subfolders
|
||||||
{
|
for (auto&& fileName : rootDir.entryList(QDir::Dirs)) {
|
||||||
setProgress(current, total);
|
if ("overrides/" == fileName)
|
||||||
}
|
continue;
|
||||||
|
|
||||||
void InstanceImportTask::downloadAborted()
|
QString result = getRootFromZip(zip, root + fileName);
|
||||||
{
|
if (!result.isEmpty())
|
||||||
emitAborted();
|
return result;
|
||||||
m_filesNetJob.reset();
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstanceImportTask::processZipPack()
|
void InstanceImportTask::processZipPack()
|
||||||
{
|
{
|
||||||
setStatus(tr("Extracting modpack"));
|
setStatus(tr("Attempting to determine instance type"));
|
||||||
QDir extractDir(m_stagingPath);
|
QDir extractDir(m_stagingPath);
|
||||||
qDebug() << "Attempting to create instance from" << m_archivePath;
|
qDebug() << "Attempting to create instance from" << m_archivePath;
|
||||||
|
|
||||||
// open the zip and find relevant files in it
|
// open the zip and find relevant files in it
|
||||||
m_packZip.reset(new QuaZip(m_archivePath));
|
auto packZip = std::make_shared<QuaZip>(m_archivePath);
|
||||||
if (!m_packZip->open(QuaZip::mdUnzip)) {
|
if (!packZip->open(QuaZip::mdUnzip)) {
|
||||||
emitFailed(tr("Unable to open supplied modpack zip file."));
|
emitFailed(tr("Unable to open supplied modpack zip file."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QuaZipDir packZipDir(m_packZip.get());
|
QuaZipDir packZipDir(packZip.get());
|
||||||
|
qDebug() << "Attempting to determine instance type";
|
||||||
|
|
||||||
// https://docs.modrinth.com/docs/modpacks/format_definition/#storage
|
|
||||||
bool modrinthFound = packZipDir.exists("/modrinth.index.json");
|
|
||||||
bool technicFound = packZipDir.exists("/bin/modpack.jar") || packZipDir.exists("/bin/version.json");
|
|
||||||
QString root;
|
QString root;
|
||||||
|
|
||||||
// NOTE: Prioritize modpack platforms that aren't searched for recursively.
|
// NOTE: Prioritize modpack platforms that aren't searched for recursively.
|
||||||
// Especially Flame has a very common filename for its manifest, which may appear inside overrides for example
|
// Especially Flame has a very common filename for its manifest, which may appear inside overrides for example
|
||||||
if (modrinthFound) {
|
// https://docs.modrinth.com/docs/modpacks/format_definition/#storage
|
||||||
|
if (packZipDir.exists("/modrinth.index.json")) {
|
||||||
// process as Modrinth pack
|
// process as Modrinth pack
|
||||||
qDebug() << "Modrinth:" << modrinthFound;
|
qDebug() << "Modrinth:" << true;
|
||||||
m_modpackType = ModpackType::Modrinth;
|
m_modpackType = ModpackType::Modrinth;
|
||||||
} else if (technicFound) {
|
} else if (packZipDir.exists("/bin/modpack.jar") || packZipDir.exists("/bin/version.json")) {
|
||||||
// process as Technic pack
|
// process as Technic pack
|
||||||
qDebug() << "Technic:" << technicFound;
|
qDebug() << "Technic:" << true;
|
||||||
extractDir.mkpath(".minecraft");
|
extractDir.mkpath("minecraft");
|
||||||
extractDir.cd(".minecraft");
|
extractDir.cd("minecraft");
|
||||||
m_modpackType = ModpackType::Technic;
|
m_modpackType = ModpackType::Technic;
|
||||||
} else {
|
} else {
|
||||||
QStringList paths_to_ignore{ "overrides/" };
|
root = getRootFromZip(packZip.get());
|
||||||
|
setDetails("");
|
||||||
if (QString mmcRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg", paths_to_ignore); !mmcRoot.isNull()) {
|
|
||||||
// process as MultiMC instance/pack
|
|
||||||
qDebug() << "MultiMC:" << mmcRoot;
|
|
||||||
root = mmcRoot;
|
|
||||||
m_modpackType = ModpackType::MultiMC;
|
|
||||||
} else if (QString flameRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json", paths_to_ignore);
|
|
||||||
!flameRoot.isNull()) {
|
|
||||||
// process as Flame pack
|
|
||||||
qDebug() << "Flame:" << flameRoot;
|
|
||||||
root = flameRoot;
|
|
||||||
m_modpackType = ModpackType::Flame;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (m_modpackType == ModpackType::Unknown) {
|
if (m_modpackType == ModpackType::Unknown) {
|
||||||
emitFailed(tr("Archive does not contain a recognized modpack type."));
|
emitFailed(tr("Archive does not contain a recognized modpack type."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setStatus(tr("Extracting modpack"));
|
||||||
|
|
||||||
// make sure we extract just the pack
|
// make sure we extract just the pack
|
||||||
m_extractFuture =
|
auto zipTask = makeShared<MMCZip::ExtractZipTask>(packZip, extractDir, root);
|
||||||
QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), root, extractDir.absolutePath());
|
|
||||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &InstanceImportTask::extractFinished);
|
auto progressStep = std::make_shared<TaskStepProgress>();
|
||||||
m_extractFutureWatcher.setFuture(m_extractFuture);
|
connect(zipTask.get(), &Task::finished, this, [this, progressStep] {
|
||||||
|
progressStep->state = TaskStepState::Succeeded;
|
||||||
|
stepProgress(*progressStep);
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(zipTask.get(), &Task::succeeded, this, &InstanceImportTask::extractFinished);
|
||||||
|
connect(zipTask.get(), &Task::aborted, this, &InstanceImportTask::emitAborted);
|
||||||
|
connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) {
|
||||||
|
progressStep->state = TaskStepState::Failed;
|
||||||
|
stepProgress(*progressStep);
|
||||||
|
emitFailed(reason);
|
||||||
|
});
|
||||||
|
connect(zipTask.get(), &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress);
|
||||||
|
|
||||||
|
connect(zipTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) {
|
||||||
|
progressStep->update(current, total);
|
||||||
|
stepProgress(*progressStep);
|
||||||
|
});
|
||||||
|
connect(zipTask.get(), &Task::status, this, [this, progressStep](QString status) {
|
||||||
|
progressStep->status = status;
|
||||||
|
stepProgress(*progressStep);
|
||||||
|
});
|
||||||
|
task.reset(zipTask);
|
||||||
|
zipTask->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstanceImportTask::extractFinished()
|
void InstanceImportTask::extractFinished()
|
||||||
{
|
{
|
||||||
m_packZip.reset();
|
|
||||||
|
|
||||||
if (m_extractFuture.isCanceled())
|
|
||||||
return;
|
|
||||||
if (!m_extractFuture.result().has_value()) {
|
|
||||||
emitFailed(tr("Failed to extract modpack"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QDir extractDir(m_stagingPath);
|
QDir extractDir(m_stagingPath);
|
||||||
|
|
||||||
qDebug() << "Fixing permissions for extracted pack files...";
|
qDebug() << "Fixing permissions for extracted pack files...";
|
||||||
@ -324,13 +334,15 @@ void InstanceImportTask::processMultiMC()
|
|||||||
m_instIcon = instance.iconKey();
|
m_instIcon = instance.iconKey();
|
||||||
|
|
||||||
auto importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), m_instIcon);
|
auto importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), m_instIcon);
|
||||||
|
if (importIconPath.isNull() || !QFile::exists(importIconPath))
|
||||||
|
importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), "icon.png");
|
||||||
if (!importIconPath.isNull() && QFile::exists(importIconPath)) {
|
if (!importIconPath.isNull() && QFile::exists(importIconPath)) {
|
||||||
// import icon
|
// import icon
|
||||||
auto iconList = APPLICATION->icons();
|
auto iconList = APPLICATION->icons();
|
||||||
if (iconList->iconFileExists(m_instIcon)) {
|
if (iconList->iconFileExists(m_instIcon)) {
|
||||||
iconList->deleteIcon(m_instIcon);
|
iconList->deleteIcon(m_instIcon);
|
||||||
}
|
}
|
||||||
iconList->installIcons({ importIconPath });
|
iconList->installIcon(importIconPath, m_instIcon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
|
@ -39,54 +39,38 @@
|
|||||||
#include <QFutureWatcher>
|
#include <QFutureWatcher>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include "InstanceTask.h"
|
#include "InstanceTask.h"
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "modplatform/flame/PackManifest.h"
|
|
||||||
#include "net/NetJob.h"
|
|
||||||
#include "settings/SettingsObject.h"
|
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
class QuaZip;
|
class QuaZip;
|
||||||
namespace Flame {
|
|
||||||
class FileResolvingTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = {});
|
||||||
|
|
||||||
bool abort() override;
|
bool abort() override;
|
||||||
const QVector<Flame::File>& getBlockedFiles() const { return m_blockedMods; }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
//! Entry point for tasks.
|
//! Entry point for tasks.
|
||||||
virtual void executeTask() override;
|
virtual void executeTask() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void processZipPack();
|
|
||||||
void processMultiMC();
|
void processMultiMC();
|
||||||
void processTechnic();
|
void processTechnic();
|
||||||
void processFlame();
|
void processFlame();
|
||||||
void processModrinth();
|
void processModrinth();
|
||||||
|
QString getRootFromZip(QuaZip* zip, const QString& root = "");
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void downloadSucceeded();
|
void processZipPack();
|
||||||
void downloadFailed(QString reason);
|
|
||||||
void downloadProgressChanged(qint64 current, qint64 total);
|
|
||||||
void downloadAborted();
|
|
||||||
void extractFinished();
|
void extractFinished();
|
||||||
|
|
||||||
private: /* data */
|
private: /* data */
|
||||||
NetJob::Ptr m_filesNetJob;
|
|
||||||
shared_qobject_ptr<Flame::FileResolvingTask> m_modIdResolver;
|
|
||||||
QUrl m_sourceUrl;
|
QUrl m_sourceUrl;
|
||||||
QString m_archivePath;
|
QString m_archivePath;
|
||||||
bool m_downloadRequired = false;
|
Task::Ptr task;
|
||||||
std::unique_ptr<QuaZip> m_packZip;
|
|
||||||
QFuture<std::optional<QStringList>> m_extractFuture;
|
|
||||||
QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;
|
|
||||||
QVector<Flame::File> m_blockedMods;
|
|
||||||
enum class ModpackType {
|
enum class ModpackType {
|
||||||
Unknown,
|
Unknown,
|
||||||
MultiMC,
|
MultiMC,
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QDirIterator>
|
#include <QDirIterator>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
#include <QFileSystemWatcher>
|
#include <QFileSystemWatcher>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
@ -371,13 +372,13 @@ void InstanceList::undoTrashInstance()
|
|||||||
|
|
||||||
auto top = m_trashHistory.pop();
|
auto top = m_trashHistory.pop();
|
||||||
|
|
||||||
while (QDir(top.polyPath).exists()) {
|
while (QDir(top.path).exists()) {
|
||||||
top.id += "1";
|
top.id += "1";
|
||||||
top.polyPath += "1";
|
top.path += "1";
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "Moving" << top.trashPath << "back to" << top.polyPath;
|
qDebug() << "Moving" << top.trashPath << "back to" << top.path;
|
||||||
QFile(top.trashPath).rename(top.polyPath);
|
QFile(top.trashPath).rename(top.path);
|
||||||
|
|
||||||
m_instanceGroupIndex[top.id] = top.groupName;
|
m_instanceGroupIndex[top.id] = top.groupName;
|
||||||
increaseGroupCount(top.groupName);
|
increaseGroupCount(top.groupName);
|
||||||
@ -634,8 +635,8 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id)
|
|||||||
|
|
||||||
QString inst_type = instanceSettings->get("InstanceType").toString();
|
QString inst_type = instanceSettings->get("InstanceType").toString();
|
||||||
|
|
||||||
// NOTE: Some PolyMC versions didn't save the InstanceType properly. We will just bank on the probability that this is probably a OneSix
|
// NOTE: Some launcher versions didn't save the InstanceType properly. We will just bank on the probability that this is probably a
|
||||||
// instance
|
// OneSix instance
|
||||||
if (inst_type == "OneSix" || inst_type.isEmpty()) {
|
if (inst_type == "OneSix" || inst_type.isEmpty()) {
|
||||||
inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot));
|
inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot));
|
||||||
} else {
|
} else {
|
||||||
@ -709,6 +710,12 @@ void InstanceList::saveGroupList()
|
|||||||
groupsArr.insert(name, groupObj);
|
groupsArr.insert(name, groupObj);
|
||||||
}
|
}
|
||||||
toplevel.insert("groups", groupsArr);
|
toplevel.insert("groups", groupsArr);
|
||||||
|
// empty string represents ungrouped "group"
|
||||||
|
if (m_collapsedGroups.contains("")) {
|
||||||
|
QJsonObject ungrouped;
|
||||||
|
ungrouped.insert("hidden", QJsonValue(true));
|
||||||
|
toplevel.insert("ungrouped", ungrouped);
|
||||||
|
}
|
||||||
QJsonDocument doc(toplevel);
|
QJsonDocument doc(toplevel);
|
||||||
try {
|
try {
|
||||||
FS::write(groupFileName, doc.toJson());
|
FS::write(groupFileName, doc.toJson());
|
||||||
@ -804,6 +811,16 @@ void InstanceList::loadGroupList()
|
|||||||
increaseGroupCount(groupName);
|
increaseGroupCount(groupName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ungroupedHidden = false;
|
||||||
|
if (rootObj.value("ungrouped").isObject()) {
|
||||||
|
QJsonObject ungrouped = rootObj.value("ungrouped").toObject();
|
||||||
|
ungroupedHidden = ungrouped.value("hidden").toBool(false);
|
||||||
|
}
|
||||||
|
if (ungroupedHidden) {
|
||||||
|
// empty string represents ungrouped "group"
|
||||||
|
m_collapsedGroups.insert("");
|
||||||
|
}
|
||||||
m_groupsLoaded = true;
|
m_groupsLoaded = true;
|
||||||
qDebug() << "Group list loaded.";
|
qDebug() << "Group list loaded.";
|
||||||
}
|
}
|
||||||
@ -823,6 +840,9 @@ void InstanceList::on_InstFolderChanged([[maybe_unused]] const Setting& setting,
|
|||||||
}
|
}
|
||||||
m_instDir = newInstDir;
|
m_instDir = newInstDir;
|
||||||
m_groupsLoaded = false;
|
m_groupsLoaded = false;
|
||||||
|
beginRemoveRows(QModelIndex(), 0, count());
|
||||||
|
m_instances.erase(m_instances.begin(), m_instances.end());
|
||||||
|
endRemoveRows();
|
||||||
emit instancesChanged();
|
emit instancesChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -844,14 +864,16 @@ class InstanceStaging : public Task {
|
|||||||
const unsigned maxBackoff = 16;
|
const unsigned maxBackoff = 16;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
InstanceStaging(InstanceList* parent, InstanceTask* child, QString stagingPath, InstanceName const& instanceName, QString groupName)
|
InstanceStaging(InstanceList* parent, InstanceTask* child, SettingsObjectPtr settings)
|
||||||
: m_parent(parent)
|
: m_parent(parent), backoff(minBackoff, maxBackoff)
|
||||||
, backoff(minBackoff, maxBackoff)
|
|
||||||
, m_stagingPath(std::move(stagingPath))
|
|
||||||
, m_instance_name(std::move(instanceName))
|
|
||||||
, m_groupName(std::move(groupName))
|
|
||||||
{
|
{
|
||||||
|
m_stagingPath = parent->getStagedInstancePath();
|
||||||
|
|
||||||
m_child.reset(child);
|
m_child.reset(child);
|
||||||
|
|
||||||
|
m_child->setStagingPath(m_stagingPath);
|
||||||
|
m_child->setParentSettings(std::move(settings));
|
||||||
|
|
||||||
connect(child, &Task::succeeded, this, &InstanceStaging::childSucceeded);
|
connect(child, &Task::succeeded, this, &InstanceStaging::childSucceeded);
|
||||||
connect(child, &Task::failed, this, &InstanceStaging::childFailed);
|
connect(child, &Task::failed, this, &InstanceStaging::childFailed);
|
||||||
connect(child, &Task::aborted, this, &InstanceStaging::childAborted);
|
connect(child, &Task::aborted, this, &InstanceStaging::childAborted);
|
||||||
@ -863,7 +885,7 @@ class InstanceStaging : public Task {
|
|||||||
connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceeded);
|
connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceeded);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~InstanceStaging(){};
|
virtual ~InstanceStaging() {}
|
||||||
|
|
||||||
// FIXME/TODO: add ability to abort during instance commit retries
|
// FIXME/TODO: add ability to abort during instance commit retries
|
||||||
bool abort() override
|
bool abort() override
|
||||||
@ -878,14 +900,22 @@ class InstanceStaging : public Task {
|
|||||||
bool canAbort() const override { return (m_child && m_child->canAbort()); }
|
bool canAbort() const override { return (m_child && m_child->canAbort()); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void executeTask() override { m_child->start(); }
|
virtual void executeTask() override
|
||||||
|
{
|
||||||
|
if (m_stagingPath.isNull()) {
|
||||||
|
emitFailed(tr("Could not create staging folder"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_child->start();
|
||||||
|
}
|
||||||
QStringList warnings() const override { return m_child->warnings(); }
|
QStringList warnings() const override { return m_child->warnings(); }
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void childSucceeded()
|
void childSucceeded()
|
||||||
{
|
{
|
||||||
unsigned sleepTime = backoff();
|
unsigned sleepTime = backoff();
|
||||||
if (m_parent->commitStagedInstance(m_stagingPath, m_instance_name, m_groupName, *m_child.get())) {
|
if (m_parent->commitStagedInstance(m_stagingPath, *m_child.get(), m_child->group(), *m_child.get())) {
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -894,7 +924,7 @@ class InstanceStaging : public Task {
|
|||||||
emitFailed(tr("Failed to commit instance, even after multiple retries. It is being blocked by something."));
|
emitFailed(tr("Failed to commit instance, even after multiple retries. It is being blocked by something."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
qDebug() << "Failed to commit instance" << m_instance_name.name() << "Initiating backoff:" << sleepTime;
|
qDebug() << "Failed to commit instance" << m_child->name() << "Initiating backoff:" << sleepTime;
|
||||||
m_backoffTimer.start(sleepTime * 500);
|
m_backoffTimer.start(sleepTime * 500);
|
||||||
}
|
}
|
||||||
void childFailed(const QString& reason)
|
void childFailed(const QString& reason)
|
||||||
@ -903,7 +933,11 @@ class InstanceStaging : public Task {
|
|||||||
emitFailed(reason);
|
emitFailed(reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
void childAborted() { emitAborted(); }
|
void childAborted()
|
||||||
|
{
|
||||||
|
m_parent->destroyStagingPath(m_stagingPath);
|
||||||
|
emitAborted();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
InstanceList* m_parent;
|
InstanceList* m_parent;
|
||||||
@ -915,34 +949,35 @@ class InstanceStaging : public Task {
|
|||||||
ExponentialSeries backoff;
|
ExponentialSeries backoff;
|
||||||
QString m_stagingPath;
|
QString m_stagingPath;
|
||||||
unique_qobject_ptr<InstanceTask> m_child;
|
unique_qobject_ptr<InstanceTask> m_child;
|
||||||
InstanceName m_instance_name;
|
|
||||||
QString m_groupName;
|
|
||||||
QTimer m_backoffTimer;
|
QTimer m_backoffTimer;
|
||||||
};
|
};
|
||||||
|
|
||||||
Task* InstanceList::wrapInstanceTask(InstanceTask* task)
|
Task* InstanceList::wrapInstanceTask(InstanceTask* task)
|
||||||
{
|
{
|
||||||
auto stagingPath = getStagedInstancePath();
|
return new InstanceStaging(this, task, m_globalSettings);
|
||||||
task->setStagingPath(stagingPath);
|
|
||||||
task->setParentSettings(m_globalSettings);
|
|
||||||
return new InstanceStaging(this, task, stagingPath, *task, task->group());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString InstanceList::getStagedInstancePath()
|
QString InstanceList::getStagedInstancePath()
|
||||||
{
|
{
|
||||||
QString key = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
const QString tempRoot = FS::PathCombine(m_instDir, ".tmp");
|
||||||
QString tempDir = ".LAUNCHER_TEMP/";
|
|
||||||
QString relPath = FS::PathCombine(tempDir, key);
|
QString result;
|
||||||
QDir rootPath(m_instDir);
|
int tries = 0;
|
||||||
auto path = FS::PathCombine(m_instDir, relPath);
|
|
||||||
if (!rootPath.mkpath(relPath)) {
|
do {
|
||||||
return QString();
|
if (++tries > 256)
|
||||||
}
|
return {};
|
||||||
|
|
||||||
|
const QString key = QUuid::createUuid().toString(QUuid::Id128).left(6);
|
||||||
|
result = FS::PathCombine(tempRoot, key);
|
||||||
|
} while (QFileInfo::exists(result));
|
||||||
|
|
||||||
|
if (!QDir::current().mkpath(result))
|
||||||
|
return {};
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
auto tempPath = FS::PathCombine(m_instDir, tempDir);
|
SetFileAttributesA(tempRoot.toStdString().c_str(), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED);
|
||||||
SetFileAttributesA(tempPath.toStdString().c_str(), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED);
|
|
||||||
#endif
|
#endif
|
||||||
return path;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InstanceList::commitStagedInstance(const QString& path,
|
bool InstanceList::commitStagedInstance(const QString& path,
|
||||||
@ -953,7 +988,6 @@ bool InstanceList::commitStagedInstance(const QString& path,
|
|||||||
if (groupName.isEmpty() && !groupName.isNull())
|
if (groupName.isEmpty() && !groupName.isNull())
|
||||||
groupName = QString();
|
groupName = QString();
|
||||||
|
|
||||||
QDir dir;
|
|
||||||
QString instID;
|
QString instID;
|
||||||
InstancePtr inst;
|
InstancePtr inst;
|
||||||
|
|
||||||
@ -977,7 +1011,7 @@ bool InstanceList::commitStagedInstance(const QString& path,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!dir.rename(path, destination)) {
|
if (!FS::move(path, destination)) {
|
||||||
qWarning() << "Failed to move" << path << "to" << destination;
|
qWarning() << "Failed to move" << path << "to" << destination;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ enum class GroupsState { NotLoaded, Steady, Dirty };
|
|||||||
|
|
||||||
struct TrashHistoryItem {
|
struct TrashHistoryItem {
|
||||||
QString id;
|
QString id;
|
||||||
QString polyPath;
|
QString path;
|
||||||
QString trashPath;
|
QString trashPath;
|
||||||
QString groupName;
|
QString groupName;
|
||||||
};
|
};
|
||||||
|
@ -22,7 +22,7 @@ class InstancePageProvider : protected QObject, public BasePageProvider {
|
|||||||
public:
|
public:
|
||||||
explicit InstancePageProvider(InstancePtr parent) { inst = parent; }
|
explicit InstancePageProvider(InstancePtr parent) { inst = parent; }
|
||||||
|
|
||||||
virtual ~InstancePageProvider(){};
|
virtual ~InstancePageProvider() = default;
|
||||||
virtual QList<BasePage*> getPages() override
|
virtual QList<BasePage*> getPages() override
|
||||||
{
|
{
|
||||||
QList<BasePage*> values;
|
QList<BasePage*> values;
|
||||||
@ -39,7 +39,7 @@ class InstancePageProvider : protected QObject, public BasePageProvider {
|
|||||||
values.append(new TexturePackPage(onesix.get(), onesix->texturePackList()));
|
values.append(new TexturePackPage(onesix.get(), onesix->texturePackList()));
|
||||||
values.append(new ShaderPackPage(onesix.get(), onesix->shaderPackList()));
|
values.append(new ShaderPackPage(onesix.get(), onesix->shaderPackList()));
|
||||||
values.append(new NotesPage(onesix.get()));
|
values.append(new NotesPage(onesix.get()));
|
||||||
values.append(new WorldListPage(onesix.get(), onesix->worldList()));
|
values.append(new WorldListPage(onesix, onesix->worldList()));
|
||||||
values.append(new ServersPage(onesix));
|
values.append(new 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")));
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
#include "InstanceTask.h"
|
#include "InstanceTask.h"
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
|
#include "settings/SettingsObject.h"
|
||||||
#include "ui/dialogs/CustomMessageBox.h"
|
#include "ui/dialogs/CustomMessageBox.h"
|
||||||
|
|
||||||
|
#include <QPushButton>
|
||||||
|
|
||||||
InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& old_name, const QString& new_name)
|
InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& old_name, const QString& new_name)
|
||||||
{
|
{
|
||||||
auto dialog =
|
auto dialog =
|
||||||
@ -20,6 +24,9 @@ InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& ol
|
|||||||
|
|
||||||
ShouldUpdate askIfShouldUpdate(QWidget* parent, QString original_version_name)
|
ShouldUpdate askIfShouldUpdate(QWidget* parent, QString original_version_name)
|
||||||
{
|
{
|
||||||
|
if (APPLICATION->settings()->get("SkipModpackUpdatePrompt").toBool())
|
||||||
|
return ShouldUpdate::SkipUpdating;
|
||||||
|
|
||||||
auto info = CustomMessageBox::selectable(
|
auto info = CustomMessageBox::selectable(
|
||||||
parent, QObject::tr("Similar modpack was found!"),
|
parent, QObject::tr("Similar modpack was found!"),
|
||||||
QObject::tr(
|
QObject::tr(
|
||||||
@ -27,16 +34,15 @@ ShouldUpdate askIfShouldUpdate(QWidget* parent, QString original_version_name)
|
|||||||
"separate instance, or update the existing one?\n\nNOTE: Make sure you made a backup of your important instance data before "
|
"separate instance, or update the existing one?\n\nNOTE: Make sure you made a backup of your important instance data before "
|
||||||
"updating, as worlds can be corrupted and some configuration may be lost (due to pack overrides).")
|
"updating, as worlds can be corrupted and some configuration may be lost (due to pack overrides).")
|
||||||
.arg(original_version_name),
|
.arg(original_version_name),
|
||||||
QMessageBox::Information, QMessageBox::Ok | QMessageBox::Reset | QMessageBox::Abort);
|
QMessageBox::Information, QMessageBox::Cancel);
|
||||||
info->setButtonText(QMessageBox::Ok, QObject::tr("Update existing instance"));
|
QAbstractButton* update = info->addButton(QObject::tr("Update existing instance"), QMessageBox::AcceptRole);
|
||||||
info->setButtonText(QMessageBox::Abort, QObject::tr("Create new instance"));
|
QAbstractButton* skip = info->addButton(QObject::tr("Create new instance"), QMessageBox::ResetRole);
|
||||||
info->setButtonText(QMessageBox::Reset, QObject::tr("Cancel"));
|
|
||||||
|
|
||||||
info->exec();
|
info->exec();
|
||||||
|
|
||||||
if (info->clickedButton() == info->button(QMessageBox::Ok))
|
if (info->clickedButton() == update)
|
||||||
return ShouldUpdate::Update;
|
return ShouldUpdate::Update;
|
||||||
if (info->clickedButton() == info->button(QMessageBox::Abort))
|
if (info->clickedButton() == skip)
|
||||||
return ShouldUpdate::SkipUpdating;
|
return ShouldUpdate::SkipUpdating;
|
||||||
return ShouldUpdate::Cancel;
|
return ShouldUpdate::Cancel;
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ class TestCheck : public QObject {
|
|||||||
TestCheck(QWidget* parent, QString path, QString args, int minMem, int maxMem, int permGen)
|
TestCheck(QWidget* parent, QString path, QString args, int minMem, int maxMem, int permGen)
|
||||||
: m_parent(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen)
|
: m_parent(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen)
|
||||||
{}
|
{}
|
||||||
virtual ~TestCheck(){};
|
virtual ~TestCheck() {};
|
||||||
|
|
||||||
void run();
|
void run();
|
||||||
|
|
||||||
|
@ -36,12 +36,13 @@
|
|||||||
|
|
||||||
#include "LaunchController.h"
|
#include "LaunchController.h"
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
#include "launch/steps/PrintServers.h"
|
||||||
|
#include "minecraft/auth/AccountData.h"
|
||||||
#include "minecraft/auth/AccountList.h"
|
#include "minecraft/auth/AccountList.h"
|
||||||
|
|
||||||
#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/EditAccountDialog.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"
|
||||||
@ -57,7 +58,6 @@
|
|||||||
#include "BuildConfig.h"
|
#include "BuildConfig.h"
|
||||||
#include "JavaCommon.h"
|
#include "JavaCommon.h"
|
||||||
#include "launch/steps/TextPrint.h"
|
#include "launch/steps/TextPrint.h"
|
||||||
#include "minecraft/auth/AccountTask.h"
|
|
||||||
#include "tasks/Task.h"
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
LaunchController::LaunchController(QObject* parent) : Task(parent) {}
|
LaunchController::LaunchController(QObject* parent) : Task(parent) {}
|
||||||
@ -85,7 +85,7 @@ void LaunchController::decideAccount()
|
|||||||
|
|
||||||
// Find an account to use.
|
// Find an account to use.
|
||||||
auto accounts = APPLICATION->accounts();
|
auto accounts = APPLICATION->accounts();
|
||||||
if (accounts->count() <= 0) {
|
if (accounts->count() <= 0 || !accounts->anyAccountIsValid()) {
|
||||||
// Tell the user they need to log in at least one account in order to play.
|
// Tell the user they need to log in at least one account in order to play.
|
||||||
auto reply = CustomMessageBox::selectable(m_parentWidget, tr("No Accounts"),
|
auto reply = CustomMessageBox::selectable(m_parentWidget, tr("No Accounts"),
|
||||||
tr("In order to play Minecraft, you must have at least one Microsoft "
|
tr("In order to play Minecraft, you must have at least one Microsoft "
|
||||||
@ -129,12 +129,63 @@ void LaunchController::decideAccount()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool LaunchController::askPlayDemo()
|
||||||
|
{
|
||||||
|
QMessageBox box(m_parentWidget);
|
||||||
|
box.setWindowTitle(tr("Play demo?"));
|
||||||
|
box.setText(
|
||||||
|
tr("This account does not own Minecraft.\nYou need to purchase the game first to play it.\n\nDo you want to play "
|
||||||
|
"the demo?"));
|
||||||
|
box.setIcon(QMessageBox::Warning);
|
||||||
|
auto demoButton = box.addButton(tr("Play Demo"), QMessageBox::ButtonRole::YesRole);
|
||||||
|
auto cancelButton = box.addButton(tr("Cancel"), QMessageBox::ButtonRole::NoRole);
|
||||||
|
box.setDefaultButton(cancelButton);
|
||||||
|
|
||||||
|
box.exec();
|
||||||
|
return box.clickedButton() == demoButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString LaunchController::askOfflineName(QString playerName, bool demo, bool& ok)
|
||||||
|
{
|
||||||
|
// we ask the user for a player name
|
||||||
|
QString message = tr("Choose your offline mode player name.");
|
||||||
|
if (demo) {
|
||||||
|
message = tr("Choose your demo mode player name.");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString lastOfflinePlayerName = APPLICATION->settings()->get("LastOfflinePlayerName").toString();
|
||||||
|
QString usedname = lastOfflinePlayerName.isEmpty() ? playerName : lastOfflinePlayerName;
|
||||||
|
QString name = QInputDialog::getText(m_parentWidget, tr("Player name"), message, QLineEdit::Normal, usedname, &ok);
|
||||||
|
if (!ok)
|
||||||
|
return {};
|
||||||
|
if (name.length()) {
|
||||||
|
usedname = name;
|
||||||
|
APPLICATION->settings()->set("LastOfflinePlayerName", usedname);
|
||||||
|
}
|
||||||
|
return usedname;
|
||||||
|
}
|
||||||
|
|
||||||
void LaunchController::login()
|
void LaunchController::login()
|
||||||
{
|
{
|
||||||
decideAccount();
|
decideAccount();
|
||||||
|
|
||||||
// if no account is selected, we bail
|
|
||||||
if (!m_accountToUse) {
|
if (!m_accountToUse) {
|
||||||
|
// if no account is selected, ask about demo
|
||||||
|
if (!m_demo) {
|
||||||
|
m_demo = askPlayDemo();
|
||||||
|
}
|
||||||
|
if (m_demo) {
|
||||||
|
// we ask the user for a player name
|
||||||
|
bool ok = false;
|
||||||
|
auto name = askOfflineName("Player", m_demo, ok);
|
||||||
|
if (ok) {
|
||||||
|
m_session = std::make_shared<AuthSession>();
|
||||||
|
m_session->MakeDemo(name, MinecraftAccount::uuidFromUsername(name).toString().remove(QRegularExpression("[{}-]")));
|
||||||
|
launchInstance();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if no account is selected, we bail
|
||||||
emitFailed(tr("No account selected for launch."));
|
emitFailed(tr("No account selected for launch."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -143,6 +194,13 @@ void LaunchController::login()
|
|||||||
bool tryagain = true;
|
bool tryagain = true;
|
||||||
unsigned int tries = 0;
|
unsigned int tries = 0;
|
||||||
|
|
||||||
|
if ((m_accountToUse->accountType() != AccountType::Offline && m_accountToUse->accountState() == AccountState::Offline) ||
|
||||||
|
m_accountToUse->shouldRefresh()) {
|
||||||
|
// Force account refresh on the account used to launch the instance updating the AccountState
|
||||||
|
// only on first try and if it is not meant to be offline
|
||||||
|
auto accounts = APPLICATION->accounts();
|
||||||
|
accounts->requestRefresh(m_accountToUse->internalId());
|
||||||
|
}
|
||||||
while (tryagain) {
|
while (tryagain) {
|
||||||
if (tries > 0 && tries % 3 == 0) {
|
if (tries > 0 && tries % 3 == 0) {
|
||||||
auto result =
|
auto result =
|
||||||
@ -161,7 +219,7 @@ void LaunchController::login()
|
|||||||
m_accountToUse->fillSession(m_session);
|
m_accountToUse->fillSession(m_session);
|
||||||
|
|
||||||
// Launch immediately in true offline mode
|
// Launch immediately in true offline mode
|
||||||
if (m_accountToUse->isOffline()) {
|
if (m_accountToUse->accountType() == AccountType::Offline) {
|
||||||
launchInstance();
|
launchInstance();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -175,24 +233,12 @@ 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 message = tr("Choose your offline mode player name.");
|
|
||||||
if (m_session->demo) {
|
|
||||||
message = tr("Choose your demo mode player name.");
|
|
||||||
}
|
|
||||||
|
|
||||||
QString lastOfflinePlayerName = APPLICATION->settings()->get("LastOfflinePlayerName").toString();
|
|
||||||
QString usedname = lastOfflinePlayerName.isEmpty() ? m_session->player_name : lastOfflinePlayerName;
|
|
||||||
QString name = QInputDialog::getText(m_parentWidget, tr("Player name"), message, QLineEdit::Normal, usedname, &ok);
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
tryagain = false;
|
tryagain = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (name.length()) {
|
m_session->MakeOffline(name);
|
||||||
usedname = name;
|
|
||||||
APPLICATION->settings()->set("LastOfflinePlayerName", usedname);
|
|
||||||
}
|
|
||||||
m_session->MakeOffline(usedname);
|
|
||||||
// offline flavored game from here :3
|
// offline flavored game from here :3
|
||||||
}
|
}
|
||||||
if (m_accountToUse->ownsMinecraft()) {
|
if (m_accountToUse->ownsMinecraft()) {
|
||||||
@ -212,20 +258,10 @@ void LaunchController::login()
|
|||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
// play demo ?
|
// play demo ?
|
||||||
QMessageBox box(m_parentWidget);
|
if (!m_session->demo) {
|
||||||
box.setWindowTitle(tr("Play demo?"));
|
m_session->demo = askPlayDemo();
|
||||||
box.setText(
|
}
|
||||||
tr("This account does not own Minecraft.\nYou need to purchase the game first to play it.\n\nDo you want to play "
|
if (m_session->demo) { // play demo here
|
||||||
"the demo?"));
|
|
||||||
box.setIcon(QMessageBox::Warning);
|
|
||||||
auto demoButton = box.addButton(tr("Play Demo"), QMessageBox::ButtonRole::YesRole);
|
|
||||||
auto cancelButton = box.addButton(tr("Cancel"), QMessageBox::ButtonRole::NoRole);
|
|
||||||
box.setDefaultButton(cancelButton);
|
|
||||||
|
|
||||||
box.exec();
|
|
||||||
if (box.clickedButton() == demoButton) {
|
|
||||||
// play demo here
|
|
||||||
m_session->MakeDemo();
|
|
||||||
launchInstance();
|
launchInstance();
|
||||||
} else {
|
} else {
|
||||||
emitFailed(tr("Launch cancelled - account does not own Minecraft."));
|
emitFailed(tr("Launch cancelled - account does not own Minecraft."));
|
||||||
@ -249,12 +285,6 @@ void LaunchController::login()
|
|||||||
progDialog.execWithTask(task.get());
|
progDialog.execWithTask(task.get());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// FIXME: this is missing - the meaning is that the account is queued for refresh and we should wait for that
|
|
||||||
/*
|
|
||||||
case AccountState::Queued: {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
case AccountState::Expired: {
|
case AccountState::Expired: {
|
||||||
auto errorString = tr("The account has expired and needs to be logged into manually again.");
|
auto errorString = tr("The account has expired and needs to be logged into manually again.");
|
||||||
QMessageBox::warning(m_parentWidget, tr("Account refresh failed"), errorString, QMessageBox::StandardButton::Ok,
|
QMessageBox::warning(m_parentWidget, tr("Account refresh failed"), errorString, QMessageBox::StandardButton::Ok,
|
||||||
@ -294,7 +324,7 @@ void LaunchController::launchInstance()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_launcher = m_instance->createLaunchTask(m_session, m_serverToJoin);
|
m_launcher = m_instance->createLaunchTask(m_session, m_targetToJoin);
|
||||||
if (!m_launcher) {
|
if (!m_launcher) {
|
||||||
emitFailed(tr("Couldn't instantiate a launcher."));
|
emitFailed(tr("Couldn't instantiate a launcher."));
|
||||||
return;
|
return;
|
||||||
@ -316,26 +346,9 @@ void LaunchController::launchInstance()
|
|||||||
online_mode = "online";
|
online_mode = "online";
|
||||||
|
|
||||||
// Prepend Server Status
|
// Prepend Server Status
|
||||||
QStringList servers = { "authserver.mojang.com", "session.minecraft.net", "textures.minecraft.net", "api.mojang.com" };
|
QStringList servers = { "login.microsoftonline.com", "session.minecraft.net", "textures.minecraft.net", "api.mojang.com" };
|
||||||
QString resolved_servers = "";
|
|
||||||
QHostInfo host_info;
|
|
||||||
|
|
||||||
for (QString server : servers) {
|
m_launcher->prependStep(makeShared<PrintServers>(m_launcher.get(), servers));
|
||||||
host_info = QHostInfo::fromName(server);
|
|
||||||
resolved_servers = resolved_servers + server + " resolves to:\n [";
|
|
||||||
if (!host_info.addresses().isEmpty()) {
|
|
||||||
for (QHostAddress address : host_info.addresses()) {
|
|
||||||
resolved_servers = resolved_servers + address.toString();
|
|
||||||
if (!host_info.addresses().endsWith(address)) {
|
|
||||||
resolved_servers = resolved_servers + ", ";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resolved_servers = resolved_servers + "N/A";
|
|
||||||
}
|
|
||||||
resolved_servers = resolved_servers + "]\n\n";
|
|
||||||
}
|
|
||||||
m_launcher->prependStep(makeShared<TextPrint>(m_launcher.get(), resolved_servers, MessageLevel::Launcher));
|
|
||||||
} else {
|
} else {
|
||||||
online_mode = m_demo ? "demo" : "offline";
|
online_mode = m_demo ? "demo" : "offline";
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
#include "minecraft/auth/MinecraftAccount.h"
|
#include "minecraft/auth/MinecraftAccount.h"
|
||||||
#include "minecraft/launch/MinecraftServerTarget.h"
|
#include "minecraft/launch/MinecraftTarget.h"
|
||||||
|
|
||||||
class InstanceWindow;
|
class InstanceWindow;
|
||||||
class LaunchController : public Task {
|
class LaunchController : public Task {
|
||||||
@ -48,7 +48,7 @@ class LaunchController : public Task {
|
|||||||
void executeTask() override;
|
void executeTask() override;
|
||||||
|
|
||||||
LaunchController(QObject* parent = nullptr);
|
LaunchController(QObject* parent = nullptr);
|
||||||
virtual ~LaunchController(){};
|
virtual ~LaunchController() = default;
|
||||||
|
|
||||||
void setInstance(InstancePtr instance) { m_instance = instance; }
|
void setInstance(InstancePtr instance) { m_instance = instance; }
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ class LaunchController : public Task {
|
|||||||
|
|
||||||
void setParentWidget(QWidget* widget) { m_parentWidget = widget; }
|
void setParentWidget(QWidget* widget) { m_parentWidget = widget; }
|
||||||
|
|
||||||
void setServerToJoin(MinecraftServerTargetPtr serverToJoin) { m_serverToJoin = std::move(serverToJoin); }
|
void setTargetToJoin(MinecraftTarget::Ptr targetToJoin) { m_targetToJoin = std::move(targetToJoin); }
|
||||||
|
|
||||||
void setAccountToUse(MinecraftAccountPtr accountToUse) { m_accountToUse = std::move(accountToUse); }
|
void setAccountToUse(MinecraftAccountPtr accountToUse) { m_accountToUse = std::move(accountToUse); }
|
||||||
|
|
||||||
@ -74,6 +74,8 @@ class LaunchController : public Task {
|
|||||||
void login();
|
void login();
|
||||||
void launchInstance();
|
void launchInstance();
|
||||||
void decideAccount();
|
void decideAccount();
|
||||||
|
bool askPlayDemo();
|
||||||
|
QString askOfflineName(QString playerName, bool demo, bool& ok);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void readyForLaunch();
|
void readyForLaunch();
|
||||||
@ -92,5 +94,5 @@ class LaunchController : public Task {
|
|||||||
MinecraftAccountPtr m_accountToUse = nullptr;
|
MinecraftAccountPtr m_accountToUse = nullptr;
|
||||||
AuthSessionPtr m_session;
|
AuthSessionPtr m_session;
|
||||||
shared_qobject_ptr<LaunchTask> m_launcher;
|
shared_qobject_ptr<LaunchTask> m_launcher;
|
||||||
MinecraftServerTargetPtr m_serverToJoin;
|
MinecraftTarget::Ptr m_targetToJoin;
|
||||||
};
|
};
|
||||||
|
@ -42,6 +42,7 @@
|
|||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QFileInfo>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
#if defined(LAUNCHER_APPLICATION)
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
@ -50,7 +51,7 @@
|
|||||||
|
|
||||||
namespace MMCZip {
|
namespace MMCZip {
|
||||||
// ours
|
// ours
|
||||||
bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& contained, const FilterFunction filter)
|
bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& contained, const FilterFunction& filter)
|
||||||
{
|
{
|
||||||
QuaZip modZip(from.filePath());
|
QuaZip modZip(from.filePath());
|
||||||
modZip.open(QuaZip::mdUnzip);
|
modZip.open(QuaZip::mdUnzip);
|
||||||
@ -119,9 +120,10 @@ bool compressDirFiles(QuaZip* zip, QString dir, QFileInfoList files, bool follow
|
|||||||
bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks)
|
bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks)
|
||||||
{
|
{
|
||||||
QuaZip zip(fileCompressed);
|
QuaZip zip(fileCompressed);
|
||||||
|
zip.setUtf8Enabled(true);
|
||||||
QDir().mkpath(QFileInfo(fileCompressed).absolutePath());
|
QDir().mkpath(QFileInfo(fileCompressed).absolutePath());
|
||||||
if (!zip.open(QuaZip::mdCreate)) {
|
if (!zip.open(QuaZip::mdCreate)) {
|
||||||
QFile::remove(fileCompressed);
|
FS::deletePath(fileCompressed);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,7 +131,7 @@ bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files,
|
|||||||
|
|
||||||
zip.close();
|
zip.close();
|
||||||
if (zip.getZipError() != 0) {
|
if (zip.getZipError() != 0) {
|
||||||
QFile::remove(fileCompressed);
|
FS::deletePath(fileCompressed);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,8 +143,9 @@ bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files,
|
|||||||
bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods)
|
bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods)
|
||||||
{
|
{
|
||||||
QuaZip zipOut(targetJarPath);
|
QuaZip zipOut(targetJarPath);
|
||||||
|
zipOut.setUtf8Enabled(true);
|
||||||
if (!zipOut.open(QuaZip::mdCreate)) {
|
if (!zipOut.open(QuaZip::mdCreate)) {
|
||||||
QFile::remove(targetJarPath);
|
FS::deletePath(targetJarPath);
|
||||||
qCritical() << "Failed to open the minecraft.jar for modding";
|
qCritical() << "Failed to open the minecraft.jar for modding";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -160,7 +163,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<M
|
|||||||
if (mod->type() == ResourceType::ZIPFILE) {
|
if (mod->type() == ResourceType::ZIPFILE) {
|
||||||
if (!mergeZipFiles(&zipOut, mod->fileinfo(), addedFiles)) {
|
if (!mergeZipFiles(&zipOut, mod->fileinfo(), addedFiles)) {
|
||||||
zipOut.close();
|
zipOut.close();
|
||||||
QFile::remove(targetJarPath);
|
FS::deletePath(targetJarPath);
|
||||||
qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar.";
|
qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -169,7 +172,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<M
|
|||||||
auto filename = mod->fileinfo();
|
auto filename = mod->fileinfo();
|
||||||
if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName())) {
|
if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName())) {
|
||||||
zipOut.close();
|
zipOut.close();
|
||||||
QFile::remove(targetJarPath);
|
FS::deletePath(targetJarPath);
|
||||||
qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar.";
|
qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -192,7 +195,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<M
|
|||||||
|
|
||||||
if (!compressDirFiles(&zipOut, parent_dir, files)) {
|
if (!compressDirFiles(&zipOut, parent_dir, files)) {
|
||||||
zipOut.close();
|
zipOut.close();
|
||||||
QFile::remove(targetJarPath);
|
FS::deletePath(targetJarPath);
|
||||||
qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar.";
|
qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -200,7 +203,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<M
|
|||||||
} else {
|
} else {
|
||||||
// Make sure we do not continue launching when something is missing or undefined...
|
// Make sure we do not continue launching when something is missing or undefined...
|
||||||
zipOut.close();
|
zipOut.close();
|
||||||
QFile::remove(targetJarPath);
|
FS::deletePath(targetJarPath);
|
||||||
qCritical() << "Failed to add unknown mod type" << mod->fileinfo().fileName() << "to the jar.";
|
qCritical() << "Failed to add unknown mod type" << mod->fileinfo().fileName() << "to the jar.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -208,7 +211,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<M
|
|||||||
|
|
||||||
if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, [](const QString key) { return !key.contains("META-INF"); })) {
|
if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, [](const QString key) { return !key.contains("META-INF"); })) {
|
||||||
zipOut.close();
|
zipOut.close();
|
||||||
QFile::remove(targetJarPath);
|
FS::deletePath(targetJarPath);
|
||||||
qCritical() << "Failed to insert minecraft.jar contents.";
|
qCritical() << "Failed to insert minecraft.jar contents.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -216,7 +219,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<M
|
|||||||
// Recompress the jar
|
// Recompress the jar
|
||||||
zipOut.close();
|
zipOut.close();
|
||||||
if (zipOut.getZipError() != 0) {
|
if (zipOut.getZipError() != 0) {
|
||||||
QFile::remove(targetJarPath);
|
FS::deletePath(targetJarPath);
|
||||||
qCritical() << "Failed to finalize minecraft.jar!";
|
qCritical() << "Failed to finalize minecraft.jar!";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -286,10 +289,11 @@ std::optional<QStringList> extractSubDir(QuaZip* zip, const QString& subdir, con
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
QString file_name = zip->getCurrentFileName();
|
QString file_name = zip->getCurrentFileName();
|
||||||
|
file_name = FS::RemoveInvalidPathChars(file_name);
|
||||||
if (!file_name.startsWith(subdir))
|
if (!file_name.startsWith(subdir))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
auto relative_file_name = QDir::fromNativeSeparators(file_name.remove(0, subdir.size()));
|
auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(subdir.size()));
|
||||||
auto original_name = relative_file_name;
|
auto original_name = 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
|
||||||
@ -327,9 +331,31 @@ std::optional<QStringList> extractSubDir(QuaZip* zip, const QString& subdir, con
|
|||||||
}
|
}
|
||||||
|
|
||||||
extracted.append(target_file_path);
|
extracted.append(target_file_path);
|
||||||
QFile::setPermissions(target_file_path,
|
auto fileInfo = QFileInfo(target_file_path);
|
||||||
QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser);
|
if (fileInfo.isFile()) {
|
||||||
|
auto permissions = fileInfo.permissions();
|
||||||
|
auto maxPermisions = QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser |
|
||||||
|
QFileDevice::Permission::ReadGroup | QFileDevice::Permission::ReadOther;
|
||||||
|
auto minPermisions = QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser;
|
||||||
|
|
||||||
|
auto newPermisions = (permissions & maxPermisions) | minPermisions;
|
||||||
|
if (newPermisions != permissions) {
|
||||||
|
if (!QFile::setPermissions(target_file_path, newPermisions)) {
|
||||||
|
qWarning() << (QObject::tr("Could not fix permissions for %1").arg(target_file_path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (fileInfo.isDir()) {
|
||||||
|
// Ensure the folder has the minimal required permissions
|
||||||
|
QFile::Permissions minimalPermissions = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadGroup |
|
||||||
|
QFile::ExeGroup | QFile::ReadOther | QFile::ExeOther;
|
||||||
|
|
||||||
|
QFile::Permissions currentPermissions = fileInfo.permissions();
|
||||||
|
if ((currentPermissions & minimalPermissions) != minimalPermissions) {
|
||||||
|
if (!QFile::setPermissions(target_file_path, minimalPermissions)) {
|
||||||
|
qWarning() << (QObject::tr("Could not fix permissions for %1").arg(target_file_path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path;
|
qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path;
|
||||||
} while (zip->goToNextFile());
|
} while (zip->goToNextFile());
|
||||||
|
|
||||||
@ -463,7 +489,7 @@ auto ExportToZipTask::exportZip() -> ZipResult
|
|||||||
|
|
||||||
auto absolute = file.absoluteFilePath();
|
auto absolute = file.absoluteFilePath();
|
||||||
auto relative = m_dir.relativeFilePath(absolute);
|
auto relative = m_dir.relativeFilePath(absolute);
|
||||||
setStatus("Compresing: " + relative);
|
setStatus("Compressing: " + relative);
|
||||||
setProgress(m_progress + 1, m_progressTotal);
|
setProgress(m_progress + 1, m_progressTotal);
|
||||||
if (m_follow_symlinks) {
|
if (m_follow_symlinks) {
|
||||||
if (file.isSymLink())
|
if (file.isSymLink())
|
||||||
@ -487,10 +513,10 @@ auto ExportToZipTask::exportZip() -> ZipResult
|
|||||||
void ExportToZipTask::finish()
|
void ExportToZipTask::finish()
|
||||||
{
|
{
|
||||||
if (m_build_zip_future.isCanceled()) {
|
if (m_build_zip_future.isCanceled()) {
|
||||||
QFile::remove(m_output_path);
|
FS::deletePath(m_output_path);
|
||||||
emitAborted();
|
emitAborted();
|
||||||
} else if (auto result = m_build_zip_future.result(); result.has_value()) {
|
} else if (auto result = m_build_zip_future.result(); result.has_value()) {
|
||||||
QFile::remove(m_output_path);
|
FS::deletePath(m_output_path);
|
||||||
emitFailed(result.value());
|
emitFailed(result.value());
|
||||||
} else {
|
} else {
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
@ -507,6 +533,134 @@ bool ExportToZipTask::abort()
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
|
void ExtractZipTask::executeTask()
|
||||||
|
{
|
||||||
|
m_zip_future = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return extractZip(); });
|
||||||
|
connect(&m_zip_watcher, &QFutureWatcher<ZipResult>::finished, this, &ExtractZipTask::finish);
|
||||||
|
m_zip_watcher.setFuture(m_zip_future);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ExtractZipTask::extractZip() -> ZipResult
|
||||||
|
{
|
||||||
|
auto target = m_output_dir.absolutePath();
|
||||||
|
auto target_top_dir = QUrl::fromLocalFile(target);
|
||||||
|
|
||||||
|
QStringList extracted;
|
||||||
|
|
||||||
|
qDebug() << "Extracting subdir" << m_subdirectory << "from" << m_input->getZipName() << "to" << target;
|
||||||
|
auto numEntries = m_input->getEntriesCount();
|
||||||
|
if (numEntries < 0) {
|
||||||
|
return ZipResult(tr("Failed to enumerate files in archive"));
|
||||||
|
}
|
||||||
|
if (numEntries == 0) {
|
||||||
|
logWarning(tr("Extracting empty archives seems odd..."));
|
||||||
|
return ZipResult();
|
||||||
|
}
|
||||||
|
if (!m_input->goToFirstFile()) {
|
||||||
|
return ZipResult(tr("Failed to seek to first file in zip"));
|
||||||
|
}
|
||||||
|
|
||||||
|
setStatus("Extracting files...");
|
||||||
|
setProgress(0, numEntries);
|
||||||
|
do {
|
||||||
|
if (m_zip_future.isCanceled())
|
||||||
|
return ZipResult();
|
||||||
|
setProgress(m_progress + 1, m_progressTotal);
|
||||||
|
QString file_name = m_input->getCurrentFileName();
|
||||||
|
if (!file_name.startsWith(m_subdirectory))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(m_subdirectory.size()));
|
||||||
|
auto original_name = relative_file_name;
|
||||||
|
setStatus("Unziping: " + relative_file_name);
|
||||||
|
|
||||||
|
// Fix subdirs/files ending with a / getting transformed into absolute paths
|
||||||
|
if (relative_file_name.startsWith('/'))
|
||||||
|
relative_file_name = relative_file_name.mid(1);
|
||||||
|
|
||||||
|
// Fix weird "folders with a single file get squashed" thing
|
||||||
|
QString sub_path;
|
||||||
|
if (relative_file_name.contains('/') && !relative_file_name.endsWith('/')) {
|
||||||
|
sub_path = relative_file_name.section('/', 0, -2) + '/';
|
||||||
|
FS::ensureFolderPathExists(FS::PathCombine(target, sub_path));
|
||||||
|
|
||||||
|
relative_file_name = relative_file_name.split('/').last();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString target_file_path;
|
||||||
|
if (relative_file_name.isEmpty()) {
|
||||||
|
target_file_path = target + '/';
|
||||||
|
} else {
|
||||||
|
target_file_path = FS::PathCombine(target_top_dir.toLocalFile(), sub_path, relative_file_name);
|
||||||
|
if (relative_file_name.endsWith('/') && !target_file_path.endsWith('/'))
|
||||||
|
target_file_path += '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!target_top_dir.isParentOf(QUrl::fromLocalFile(target_file_path))) {
|
||||||
|
return ZipResult(tr("Extracting %1 was cancelled, because it was effectively outside of the target path %2")
|
||||||
|
.arg(relative_file_name, target));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!JlCompress::extractFile(m_input.get(), "", target_file_path)) {
|
||||||
|
JlCompress::removeFile(extracted);
|
||||||
|
return ZipResult(tr("Failed to extract file %1 to %2").arg(original_name, target_file_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
extracted.append(target_file_path);
|
||||||
|
auto fileInfo = QFileInfo(target_file_path);
|
||||||
|
if (fileInfo.isFile()) {
|
||||||
|
auto permissions = fileInfo.permissions();
|
||||||
|
auto maxPermisions = QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser |
|
||||||
|
QFileDevice::Permission::ReadGroup | QFileDevice::Permission::ReadOther;
|
||||||
|
auto minPermisions = QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser;
|
||||||
|
|
||||||
|
auto newPermisions = (permissions & maxPermisions) | minPermisions;
|
||||||
|
if (newPermisions != permissions) {
|
||||||
|
if (!QFile::setPermissions(target_file_path, newPermisions)) {
|
||||||
|
logWarning(tr("Could not fix permissions for %1").arg(target_file_path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (fileInfo.isDir()) {
|
||||||
|
// Ensure the folder has the minimal required permissions
|
||||||
|
QFile::Permissions minimalPermissions = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadGroup |
|
||||||
|
QFile::ExeGroup | QFile::ReadOther | QFile::ExeOther;
|
||||||
|
|
||||||
|
QFile::Permissions currentPermissions = fileInfo.permissions();
|
||||||
|
if ((currentPermissions & minimalPermissions) != minimalPermissions) {
|
||||||
|
if (!QFile::setPermissions(target_file_path, minimalPermissions)) {
|
||||||
|
logWarning(tr("Could not fix permissions for %1").arg(target_file_path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path;
|
||||||
|
} while (m_input->goToNextFile());
|
||||||
|
|
||||||
|
return ZipResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExtractZipTask::finish()
|
||||||
|
{
|
||||||
|
if (m_zip_future.isCanceled()) {
|
||||||
|
emitAborted();
|
||||||
|
} else if (auto result = m_zip_future.result(); result.has_value()) {
|
||||||
|
emitFailed(result.value());
|
||||||
|
} else {
|
||||||
|
emitSucceeded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExtractZipTask::abort()
|
||||||
|
{
|
||||||
|
if (m_zip_future.isRunning()) {
|
||||||
|
m_zip_future.cancel();
|
||||||
|
// NOTE: Here we don't do `emitAborted()` because it will be done when `m_build_zip_future` actually cancels, which may not occur
|
||||||
|
// immediately.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
} // namespace MMCZip
|
} // namespace MMCZip
|
||||||
|
@ -60,7 +60,7 @@ using FilterFunction = std::function<bool(const QString&)>;
|
|||||||
/**
|
/**
|
||||||
* Merge two zip files, using a filter function
|
* Merge two zip files, using a filter function
|
||||||
*/
|
*/
|
||||||
bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& contained, const FilterFunction filter = nullptr);
|
bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& contained, const FilterFunction& filter = nullptr);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compress directory, by providing a list of files to compress
|
* Compress directory, by providing a list of files to compress
|
||||||
@ -154,7 +154,12 @@ 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 {
|
||||||
public:
|
public:
|
||||||
ExportToZipTask(QString outputPath, QDir dir, QFileInfoList files, QString destinationPrefix = "", bool followSymlinks = false)
|
ExportToZipTask(QString outputPath,
|
||||||
|
QDir dir,
|
||||||
|
QFileInfoList files,
|
||||||
|
QString destinationPrefix = "",
|
||||||
|
bool followSymlinks = false,
|
||||||
|
bool utf8Enabled = false)
|
||||||
: m_output_path(outputPath)
|
: m_output_path(outputPath)
|
||||||
, m_output(outputPath)
|
, m_output(outputPath)
|
||||||
, m_dir(dir)
|
, m_dir(dir)
|
||||||
@ -163,9 +168,15 @@ class ExportToZipTask : public Task {
|
|||||||
, m_follow_symlinks(followSymlinks)
|
, m_follow_symlinks(followSymlinks)
|
||||||
{
|
{
|
||||||
setAbortable(true);
|
setAbortable(true);
|
||||||
|
m_output.setUtf8Enabled(utf8Enabled);
|
||||||
};
|
};
|
||||||
ExportToZipTask(QString outputPath, QString dir, QFileInfoList files, QString destinationPrefix = "", bool followSymlinks = false)
|
ExportToZipTask(QString outputPath,
|
||||||
: ExportToZipTask(outputPath, QDir(dir), files, destinationPrefix, followSymlinks){};
|
QString dir,
|
||||||
|
QFileInfoList files,
|
||||||
|
QString destinationPrefix = "",
|
||||||
|
bool followSymlinks = false,
|
||||||
|
bool utf8Enabled = false)
|
||||||
|
: ExportToZipTask(outputPath, QDir(dir), files, destinationPrefix, followSymlinks, utf8Enabled) {};
|
||||||
|
|
||||||
virtual ~ExportToZipTask() = default;
|
virtual ~ExportToZipTask() = default;
|
||||||
|
|
||||||
@ -194,5 +205,30 @@ class ExportToZipTask : public Task {
|
|||||||
QFuture<ZipResult> m_build_zip_future;
|
QFuture<ZipResult> m_build_zip_future;
|
||||||
QFutureWatcher<ZipResult> m_build_zip_watcher;
|
QFutureWatcher<ZipResult> m_build_zip_watcher;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ExtractZipTask : public Task {
|
||||||
|
public:
|
||||||
|
ExtractZipTask(std::shared_ptr<QuaZip> input, QDir outputDir, QString subdirectory = "")
|
||||||
|
: m_input(input), m_output_dir(outputDir), m_subdirectory(subdirectory)
|
||||||
|
{}
|
||||||
|
virtual ~ExtractZipTask() = default;
|
||||||
|
|
||||||
|
using ZipResult = std::optional<QString>;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void executeTask() override;
|
||||||
|
bool abort() override;
|
||||||
|
|
||||||
|
ZipResult extractZip();
|
||||||
|
void finish();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<QuaZip> m_input;
|
||||||
|
QDir m_output_dir;
|
||||||
|
QString m_subdirectory;
|
||||||
|
|
||||||
|
QFuture<ZipResult> m_zip_future;
|
||||||
|
QFutureWatcher<ZipResult> m_zip_watcher;
|
||||||
|
};
|
||||||
#endif
|
#endif
|
||||||
} // namespace MMCZip
|
} // namespace MMCZip
|
||||||
|
@ -40,8 +40,8 @@ namespace MangoHud {
|
|||||||
|
|
||||||
QString getLibraryString()
|
QString getLibraryString()
|
||||||
{
|
{
|
||||||
/*
|
/**
|
||||||
* Check for vulkan layers in this order:
|
* Guess MangoHud install location by searching for vulkan layers in this order:
|
||||||
*
|
*
|
||||||
* $VK_LAYER_PATH
|
* $VK_LAYER_PATH
|
||||||
* $XDG_DATA_DIRS (/usr/local/share/:/usr/share/)
|
* $XDG_DATA_DIRS (/usr/local/share/:/usr/share/)
|
||||||
@ -49,8 +49,9 @@ QString getLibraryString()
|
|||||||
* /etc
|
* /etc
|
||||||
* $XDG_CONFIG_DIRS (/etc/xdg)
|
* $XDG_CONFIG_DIRS (/etc/xdg)
|
||||||
* $XDG_CONFIG_HOME (~/.config)
|
* $XDG_CONFIG_HOME (~/.config)
|
||||||
|
*
|
||||||
|
* @returns Absolute path of libMangoHud.so if found and empty QString otherwise.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
QStringList vkLayerList;
|
QStringList vkLayerList;
|
||||||
{
|
{
|
||||||
QString home = QDir::homePath();
|
QString home = QDir::homePath();
|
||||||
@ -85,7 +86,7 @@ QString getLibraryString()
|
|||||||
vkLayerList << FS::PathCombine(xdgConfigHome, "vulkan", "implicit_layer.d");
|
vkLayerList << FS::PathCombine(xdgConfigHome, "vulkan", "implicit_layer.d");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (QString vkLayer : vkLayerList) {
|
for (const QString& vkLayer : vkLayerList) {
|
||||||
// prefer to use architecture specific vulkan layers
|
// prefer to use architecture specific vulkan layers
|
||||||
QString currentArch = QSysInfo::currentCpuArchitecture();
|
QString currentArch = QSysInfo::currentCpuArchitecture();
|
||||||
|
|
||||||
@ -95,8 +96,8 @@ QString getLibraryString()
|
|||||||
|
|
||||||
QStringList manifestNames = { QString("MangoHud.%1.json").arg(currentArch), "MangoHud.json" };
|
QStringList manifestNames = { QString("MangoHud.%1.json").arg(currentArch), "MangoHud.json" };
|
||||||
|
|
||||||
QString filePath = "";
|
QString filePath{};
|
||||||
for (QString manifestName : manifestNames) {
|
for (const QString& manifestName : manifestNames) {
|
||||||
QString tryPath = FS::PathCombine(vkLayer, manifestName);
|
QString tryPath = FS::PathCombine(vkLayer, manifestName);
|
||||||
if (QFile::exists(tryPath)) {
|
if (QFile::exists(tryPath)) {
|
||||||
filePath = tryPath;
|
filePath = tryPath;
|
||||||
@ -111,10 +112,23 @@ QString getLibraryString()
|
|||||||
auto conf = Json::requireDocument(filePath, vkLayer);
|
auto conf = Json::requireDocument(filePath, vkLayer);
|
||||||
auto confObject = Json::requireObject(conf, vkLayer);
|
auto confObject = Json::requireObject(conf, vkLayer);
|
||||||
auto layer = Json::ensureObject(confObject, "layer");
|
auto layer = Json::ensureObject(confObject, "layer");
|
||||||
return Json::ensureString(layer, "library_path");
|
QString libraryName = Json::ensureString(layer, "library_path");
|
||||||
|
|
||||||
|
#ifdef __GLIBC__
|
||||||
|
// Check whether mangohud is usable on a glibc based system
|
||||||
|
if (!libraryName.isEmpty()) {
|
||||||
|
QString libraryPath = findLibrary(libraryName);
|
||||||
|
if (!libraryPath.isEmpty()) {
|
||||||
|
return libraryPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// Without glibc return recorded shared library as-is.
|
||||||
|
return libraryName;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
return QString();
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
QString findLibrary(QString libName)
|
QString findLibrary(QString libName)
|
||||||
|
@ -46,13 +46,13 @@ class NullInstance : public BaseInstance {
|
|||||||
{
|
{
|
||||||
setVersionBroken(true);
|
setVersionBroken(true);
|
||||||
}
|
}
|
||||||
virtual ~NullInstance(){};
|
virtual ~NullInstance() = default;
|
||||||
void saveNow() override {}
|
void saveNow() override {}
|
||||||
void loadSpecificSettings() override { setSpecificSettingsLoaded(true); }
|
void loadSpecificSettings() override { setSpecificSettingsLoaded(true); }
|
||||||
QString getStatusbarDescription() override { return tr("Unknown instance type"); };
|
QString getStatusbarDescription() override { return tr("Unknown instance type"); };
|
||||||
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, MinecraftServerTargetPtr) 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; }
|
shared_qobject_ptr<Task> createUpdateTask([[maybe_unused]] Net::Mode mode) override { return nullptr; }
|
||||||
QProcessEnvironment createEnvironment() override { return QProcessEnvironment(); }
|
QProcessEnvironment createEnvironment() override { return QProcessEnvironment(); }
|
||||||
QProcessEnvironment createLaunchEnvironment() override { return QProcessEnvironment(); }
|
QProcessEnvironment createLaunchEnvironment() override { return QProcessEnvironment(); }
|
||||||
@ -64,7 +64,7 @@ class NullInstance : public BaseInstance {
|
|||||||
bool canEdit() const override { return false; }
|
bool canEdit() const override { return false; }
|
||||||
bool canLaunch() const override { return false; }
|
bool canLaunch() const override { return false; }
|
||||||
void populateLaunchMenu(QMenu* menu) override {}
|
void populateLaunchMenu(QMenu* menu) override {}
|
||||||
QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override
|
QStringList verboseDescription(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) override
|
||||||
{
|
{
|
||||||
QStringList out;
|
QStringList out;
|
||||||
out << "Null instance - placeholder.";
|
out << "Null instance - placeholder.";
|
||||||
|
@ -13,7 +13,7 @@ class RecursiveFileSystemWatcher : public QObject {
|
|||||||
QDir rootDir() const { return m_root; }
|
QDir rootDir() const { return m_root; }
|
||||||
|
|
||||||
// WARNING: setting this to true may be bad for performance
|
// WARNING: setting this to true may be bad for performance
|
||||||
void setWatchFiles(const bool watchFiles);
|
void setWatchFiles(bool watchFiles);
|
||||||
bool watchFiles() const { return m_watchFiles; }
|
bool watchFiles() const { return m_watchFiles; }
|
||||||
|
|
||||||
void setMatcher(IPathMatcher::Ptr matcher) { m_matcher = matcher; }
|
void setMatcher(IPathMatcher::Ptr matcher) { m_matcher = matcher; }
|
||||||
|
@ -32,7 +32,7 @@ class ResourceDownloadTask : public SequentialTask {
|
|||||||
public:
|
public:
|
||||||
explicit ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
|
explicit ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
|
||||||
ModPlatform::IndexedVersion version,
|
ModPlatform::IndexedVersion version,
|
||||||
const std::shared_ptr<ResourceFolderModel> packs,
|
std::shared_ptr<ResourceFolderModel> packs,
|
||||||
bool is_indexed = true,
|
bool is_indexed = true,
|
||||||
QString custom_target_folder = {});
|
QString custom_target_folder = {});
|
||||||
const QString& getFilename() const { return m_pack_version.fileName; }
|
const QString& getFilename() const { return m_pack_version.fileName; }
|
||||||
|
@ -1,52 +0,0 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "SkinUtils.h"
|
|
||||||
#include "Application.h"
|
|
||||||
#include "net/HttpMetaCache.h"
|
|
||||||
|
|
||||||
#include <QFile>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QPainter>
|
|
||||||
|
|
||||||
namespace SkinUtils {
|
|
||||||
/*
|
|
||||||
* Given a username, return a pixmap of the cached skin (if it exists), QPixmap() otherwise
|
|
||||||
*/
|
|
||||||
QPixmap getFaceFromCache(QString username, int height, int width)
|
|
||||||
{
|
|
||||||
QFile fskin(APPLICATION->metacache()->resolveEntry("skins", username + ".png")->getFullPath());
|
|
||||||
|
|
||||||
if (fskin.exists()) {
|
|
||||||
QPixmap skinTexture(fskin.fileName());
|
|
||||||
if (!skinTexture.isNull()) {
|
|
||||||
QPixmap skin = QPixmap(8, 8);
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
|
||||||
skin.fill(QColorConstants::Transparent);
|
|
||||||
#else
|
|
||||||
skin.fill(QColor(0, 0, 0, 0));
|
|
||||||
#endif
|
|
||||||
QPainter painter(&skin);
|
|
||||||
painter.drawPixmap(0, 0, skinTexture.copy(8, 8, 8, 8));
|
|
||||||
painter.drawPixmap(0, 0, skinTexture.copy(40, 8, 8, 8));
|
|
||||||
return skin.scaled(height, width, Qt::KeepAspectRatio);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return QPixmap();
|
|
||||||
}
|
|
||||||
} // namespace SkinUtils
|
|
@ -1,22 +0,0 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QPixmap>
|
|
||||||
|
|
||||||
namespace SkinUtils {
|
|
||||||
QPixmap getFaceFromCache(QString id, int height = 64, int width = 64);
|
|
||||||
}
|
|
@ -212,3 +212,25 @@ QPair<QString, QString> StringUtils::splitFirst(const QString& s, const QRegular
|
|||||||
right = s.mid(end);
|
right = s.mid(end);
|
||||||
return qMakePair(left, right);
|
return qMakePair(left, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const QRegularExpression ulMatcher("<\\s*/\\s*ul\\s*>");
|
||||||
|
|
||||||
|
QString StringUtils::htmlListPatch(QString htmlStr)
|
||||||
|
{
|
||||||
|
int pos = htmlStr.indexOf(ulMatcher);
|
||||||
|
int imgPos;
|
||||||
|
while (pos != -1) {
|
||||||
|
pos = htmlStr.indexOf(">", pos) + 1; // Get the size of the </ul> tag. Add one for zeroeth index
|
||||||
|
imgPos = htmlStr.indexOf("<img ", pos);
|
||||||
|
if (imgPos == -1)
|
||||||
|
break; // no image after the tag
|
||||||
|
|
||||||
|
auto textBetween = htmlStr.mid(pos, imgPos - pos).trimmed(); // trim all white spaces
|
||||||
|
|
||||||
|
if (textBetween.isEmpty())
|
||||||
|
htmlStr.insert(pos, "<br>");
|
||||||
|
|
||||||
|
pos = htmlStr.indexOf(ulMatcher, pos);
|
||||||
|
}
|
||||||
|
return htmlStr;
|
||||||
|
}
|
@ -85,4 +85,6 @@ QPair<QString, QString> splitFirst(const QString& s, const QString& sep, Qt::Cas
|
|||||||
QPair<QString, QString> splitFirst(const QString& s, QChar sep, Qt::CaseSensitivity cs = Qt::CaseSensitive);
|
QPair<QString, QString> splitFirst(const QString& s, QChar sep, Qt::CaseSensitivity cs = Qt::CaseSensitive);
|
||||||
QPair<QString, QString> splitFirst(const QString& s, const QRegularExpression& re);
|
QPair<QString, QString> splitFirst(const QString& s, const QRegularExpression& re);
|
||||||
|
|
||||||
|
QString htmlListPatch(QString htmlStr);
|
||||||
|
|
||||||
} // namespace StringUtils
|
} // namespace StringUtils
|
||||||
|
@ -14,7 +14,7 @@ class VersionProxyModel : public QAbstractProxyModel {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
VersionProxyModel(QObject* parent = 0);
|
VersionProxyModel(QObject* parent = 0);
|
||||||
virtual ~VersionProxyModel(){};
|
virtual ~VersionProxyModel() {};
|
||||||
|
|
||||||
virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override;
|
virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||||
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||||
@ -28,7 +28,7 @@ class VersionProxyModel : public QAbstractProxyModel {
|
|||||||
|
|
||||||
const FilterMap& filters() const;
|
const FilterMap& filters() const;
|
||||||
const QString& search() const;
|
const QString& search() const;
|
||||||
void setFilter(const BaseVersionList::ModelRoles column, Filter* filter);
|
void setFilter(BaseVersionList::ModelRoles column, Filter* filter);
|
||||||
void setSearch(const QString& search);
|
void setSearch(const QString& search);
|
||||||
void clearFilters();
|
void clearFilters();
|
||||||
QModelIndex getRecommended() const;
|
QModelIndex getRecommended() const;
|
||||||
|
@ -322,7 +322,7 @@ const MMCIcon* IconList::icon(const QString& key) const
|
|||||||
|
|
||||||
bool IconList::deleteIcon(const QString& key)
|
bool IconList::deleteIcon(const QString& key)
|
||||||
{
|
{
|
||||||
return iconFileExists(key) && QFile::remove(icon(key)->getFilePath());
|
return iconFileExists(key) && FS::deletePath(icon(key)->getFilePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IconList::trashIcon(const QString& key)
|
bool IconList::trashIcon(const QString& key)
|
||||||
|
@ -52,7 +52,7 @@ 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, QString path, QObject* parent = 0);
|
||||||
virtual ~IconList(){};
|
virtual ~IconList() {};
|
||||||
|
|
||||||
QIcon getIcon(const QString& key) const;
|
QIcon getIcon(const QString& key) const;
|
||||||
int getIconIndex(const QString& key) const;
|
int getIconIndex(const QString& key) const;
|
||||||
@ -67,7 +67,7 @@ class IconList : public QAbstractListModel {
|
|||||||
virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
|
virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
|
||||||
|
|
||||||
bool addThemeIcon(const QString& key);
|
bool addThemeIcon(const QString& key);
|
||||||
bool addIcon(const QString& key, const QString& name, const QString& path, const IconType type);
|
bool addIcon(const QString& key, const QString& name, const QString& path, IconType type);
|
||||||
void saveIcon(const QString& key, const QString& path, const char* format) const;
|
void saveIcon(const QString& key, const QString& path, const char* format) const;
|
||||||
bool deleteIcon(const QString& key);
|
bool deleteIcon(const QString& key);
|
||||||
bool trashIcon(const QString& key);
|
bool trashIcon(const QString& key);
|
||||||
|
@ -52,8 +52,7 @@ QString findBestIconIn(const QString& folder, const QString& iconKey)
|
|||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
it.next();
|
it.next();
|
||||||
auto fileInfo = it.fileInfo();
|
auto fileInfo = it.fileInfo();
|
||||||
|
if ((fileInfo.completeBaseName() == iconKey || fileInfo.fileName() == iconKey) && isIconSuffix(fileInfo.suffix()))
|
||||||
if (fileInfo.completeBaseName() == iconKey && isIconSuffix(fileInfo.suffix()))
|
|
||||||
return fileInfo.absoluteFilePath();
|
return fileInfo.absoluteFilePath();
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
set(CMAKE_MODULE_PATH "@CMAKE_MODULE_PATH@")
|
set(CMAKE_MODULE_PATH "@CMAKE_MODULE_PATH@")
|
||||||
|
|
||||||
file(GLOB_RECURSE QTPLUGINS "${CMAKE_INSTALL_PREFIX}/@PLUGIN_DEST_DIR@/*@CMAKE_SHARED_LIBRARY_SUFFIX@")
|
file(GLOB_RECURSE QTPLUGINS "${CMAKE_INSTALL_PREFIX}/@PLUGIN_DEST_DIR@/*@CMAKE_SHARED_LIBRARY_SUFFIX@")
|
||||||
function(gp_resolved_file_type_override resolved_file type_var)
|
function(gp_resolved_file_type_override resolved_file type_var)
|
||||||
if(resolved_file MATCHES "^/(usr/)?lib/libQt")
|
if(resolved_file MATCHES "^/(usr/)?lib/libQt")
|
||||||
|
@ -55,6 +55,9 @@ void JavaChecker::performCheck()
|
|||||||
qDebug() << "Java checker library could not be found. Please check your installation.";
|
qDebug() << "Java checker library could not be found. Please check your installation.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
checkerJar = FS::getPathNameInLocal8bit(checkerJar);
|
||||||
|
#endif
|
||||||
|
|
||||||
QStringList args;
|
QStringList args;
|
||||||
|
|
||||||
|
@ -26,8 +26,8 @@ using JavaCheckerJobPtr = shared_qobject_ptr<JavaCheckerJob>;
|
|||||||
class JavaCheckerJob : public Task {
|
class JavaCheckerJob : public Task {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit JavaCheckerJob(QString job_name) : Task(), m_job_name(job_name){};
|
explicit JavaCheckerJob(QString job_name) : Task(), m_job_name(job_name) {};
|
||||||
virtual ~JavaCheckerJob(){};
|
virtual ~JavaCheckerJob() {};
|
||||||
|
|
||||||
bool addJavaCheckerAction(JavaCheckerPtr base)
|
bool addJavaCheckerAction(JavaCheckerPtr base)
|
||||||
{
|
{
|
||||||
|
@ -35,7 +35,7 @@ class JavaInstallList : public BaseVersionList {
|
|||||||
public:
|
public:
|
||||||
explicit JavaInstallList(QObject* parent = 0);
|
explicit JavaInstallList(QObject* parent = 0);
|
||||||
|
|
||||||
Task::Ptr getLoadTask() override;
|
[[nodiscard]] Task::Ptr getLoadTask() override;
|
||||||
bool isLoaded() override;
|
bool isLoaded() override;
|
||||||
const BaseVersion::Ptr at(int i) const override;
|
const BaseVersion::Ptr at(int i) const override;
|
||||||
int count() const override;
|
int count() const override;
|
||||||
|
@ -79,11 +79,9 @@ QProcessEnvironment CleanEnviroment()
|
|||||||
|
|
||||||
QStringList stripped = {
|
QStringList stripped = {
|
||||||
#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)
|
||||||
"LD_LIBRARY_PATH",
|
"LD_LIBRARY_PATH", "LD_PRELOAD",
|
||||||
"LD_PRELOAD",
|
|
||||||
#endif
|
#endif
|
||||||
"QT_PLUGIN_PATH",
|
"QT_PLUGIN_PATH", "QT_FONTPATH"
|
||||||
"QT_FONTPATH"
|
|
||||||
};
|
};
|
||||||
for (auto key : rawenv.keys()) {
|
for (auto key : rawenv.keys()) {
|
||||||
auto value = rawenv.value(key);
|
auto value = rawenv.value(key);
|
||||||
@ -184,56 +182,58 @@ QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString
|
|||||||
else if (keyType == KEY_WOW64_32KEY)
|
else if (keyType == KEY_WOW64_32KEY)
|
||||||
archType = "32";
|
archType = "32";
|
||||||
|
|
||||||
HKEY jreKey;
|
for (HKEY baseRegistry : { HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE }) {
|
||||||
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, keyName.toStdWString().c_str(), 0, KEY_READ | keyType | KEY_ENUMERATE_SUB_KEYS, &jreKey) ==
|
HKEY jreKey;
|
||||||
ERROR_SUCCESS) {
|
if (RegOpenKeyExW(baseRegistry, keyName.toStdWString().c_str(), 0, KEY_READ | keyType | KEY_ENUMERATE_SUB_KEYS, &jreKey) ==
|
||||||
// Read the current type version from the registry.
|
ERROR_SUCCESS) {
|
||||||
// This will be used to find any key that contains the JavaHome value.
|
// Read the current type version from the registry.
|
||||||
|
// This will be used to find any key that contains the JavaHome value.
|
||||||
|
|
||||||
WCHAR subKeyName[255];
|
WCHAR subKeyName[255];
|
||||||
DWORD subKeyNameSize, numSubKeys, retCode;
|
DWORD subKeyNameSize, numSubKeys, retCode;
|
||||||
|
|
||||||
// Get the number of subkeys
|
// Get the number of subkeys
|
||||||
RegQueryInfoKeyW(jreKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
RegQueryInfoKeyW(jreKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||||
|
|
||||||
// Iterate until RegEnumKeyEx fails
|
// Iterate until RegEnumKeyEx fails
|
||||||
if (numSubKeys > 0) {
|
if (numSubKeys > 0) {
|
||||||
for (DWORD i = 0; i < numSubKeys; i++) {
|
for (DWORD i = 0; i < numSubKeys; i++) {
|
||||||
subKeyNameSize = 255;
|
subKeyNameSize = 255;
|
||||||
retCode = RegEnumKeyExW(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL, NULL);
|
retCode = RegEnumKeyExW(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL, NULL);
|
||||||
QString newSubkeyName = QString::fromWCharArray(subKeyName);
|
QString newSubkeyName = QString::fromWCharArray(subKeyName);
|
||||||
if (retCode == ERROR_SUCCESS) {
|
if (retCode == ERROR_SUCCESS) {
|
||||||
// Now open the registry key for the version that we just got.
|
// Now open the registry key for the version that we just got.
|
||||||
QString newKeyName = keyName + "\\" + newSubkeyName + subkeySuffix;
|
QString newKeyName = keyName + "\\" + newSubkeyName + subkeySuffix;
|
||||||
|
|
||||||
HKEY newKey;
|
HKEY newKey;
|
||||||
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, newKeyName.toStdWString().c_str(), 0, KEY_READ | KEY_WOW64_64KEY, &newKey) ==
|
if (RegOpenKeyExW(baseRegistry, newKeyName.toStdWString().c_str(), 0, KEY_READ | keyType, &newKey) ==
|
||||||
ERROR_SUCCESS) {
|
ERROR_SUCCESS) {
|
||||||
// Read the JavaHome value to find where Java is installed.
|
// Read the JavaHome value to find where Java is installed.
|
||||||
DWORD valueSz = 0;
|
DWORD valueSz = 0;
|
||||||
if (RegQueryValueExW(newKey, keyJavaDir.toStdWString().c_str(), NULL, NULL, NULL, &valueSz) == ERROR_SUCCESS) {
|
if (RegQueryValueExW(newKey, keyJavaDir.toStdWString().c_str(), NULL, NULL, NULL, &valueSz) == ERROR_SUCCESS) {
|
||||||
WCHAR* value = new WCHAR[valueSz];
|
WCHAR* value = new WCHAR[valueSz];
|
||||||
RegQueryValueExW(newKey, keyJavaDir.toStdWString().c_str(), NULL, NULL, (BYTE*)value, &valueSz);
|
RegQueryValueExW(newKey, keyJavaDir.toStdWString().c_str(), NULL, NULL, (BYTE*)value, &valueSz);
|
||||||
|
|
||||||
QString newValue = QString::fromWCharArray(value);
|
QString newValue = QString::fromWCharArray(value);
|
||||||
delete[] value;
|
delete[] value;
|
||||||
|
|
||||||
// Now, we construct the version object and add it to the list.
|
// Now, we construct the version object and add it to the list.
|
||||||
JavaInstallPtr javaVersion(new JavaInstall());
|
JavaInstallPtr javaVersion(new JavaInstall());
|
||||||
|
|
||||||
javaVersion->id = newSubkeyName;
|
javaVersion->id = newSubkeyName;
|
||||||
javaVersion->arch = archType;
|
javaVersion->arch = archType;
|
||||||
javaVersion->path = QDir(FS::PathCombine(newValue, "bin")).absoluteFilePath("javaw.exe");
|
javaVersion->path = QDir(FS::PathCombine(newValue, "bin")).absoluteFilePath("javaw.exe");
|
||||||
javas.append(javaVersion);
|
javas.append(javaVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
RegCloseKey(newKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
RegCloseKey(newKey);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
RegCloseKey(jreKey);
|
RegCloseKey(jreKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return javas;
|
return javas;
|
||||||
@ -283,6 +283,12 @@ QList<QString> JavaUtils::FindJavaPaths()
|
|||||||
QList<JavaInstallPtr> ADOPTIUMJDK64s =
|
QList<JavaInstallPtr> ADOPTIUMJDK64s =
|
||||||
this->FindJavaFromRegistryKey(KEY_WOW64_64KEY, "SOFTWARE\\Eclipse Adoptium\\JDK", "Path", "\\hotspot\\MSI");
|
this->FindJavaFromRegistryKey(KEY_WOW64_64KEY, "SOFTWARE\\Eclipse Adoptium\\JDK", "Path", "\\hotspot\\MSI");
|
||||||
|
|
||||||
|
// IBM Semeru
|
||||||
|
QList<JavaInstallPtr> SEMERUJRE32s = this->FindJavaFromRegistryKey(KEY_WOW64_32KEY, "SOFTWARE\\Semeru\\JRE", "Path", "\\openj9\\MSI");
|
||||||
|
QList<JavaInstallPtr> SEMERUJRE64s = this->FindJavaFromRegistryKey(KEY_WOW64_64KEY, "SOFTWARE\\Semeru\\JRE", "Path", "\\openj9\\MSI");
|
||||||
|
QList<JavaInstallPtr> SEMERUJDK32s = this->FindJavaFromRegistryKey(KEY_WOW64_32KEY, "SOFTWARE\\Semeru\\JDK", "Path", "\\openj9\\MSI");
|
||||||
|
QList<JavaInstallPtr> SEMERUJDK64s = this->FindJavaFromRegistryKey(KEY_WOW64_64KEY, "SOFTWARE\\Semeru\\JDK", "Path", "\\openj9\\MSI");
|
||||||
|
|
||||||
// Microsoft
|
// Microsoft
|
||||||
QList<JavaInstallPtr> MICROSOFTJDK64s =
|
QList<JavaInstallPtr> MICROSOFTJDK64s =
|
||||||
this->FindJavaFromRegistryKey(KEY_WOW64_64KEY, "SOFTWARE\\Microsoft\\JDK", "Path", "\\hotspot\\MSI");
|
this->FindJavaFromRegistryKey(KEY_WOW64_64KEY, "SOFTWARE\\Microsoft\\JDK", "Path", "\\hotspot\\MSI");
|
||||||
@ -300,6 +306,7 @@ QList<QString> JavaUtils::FindJavaPaths()
|
|||||||
java_candidates.append(NEWJRE64s);
|
java_candidates.append(NEWJRE64s);
|
||||||
java_candidates.append(ADOPTOPENJRE64s);
|
java_candidates.append(ADOPTOPENJRE64s);
|
||||||
java_candidates.append(ADOPTIUMJRE64s);
|
java_candidates.append(ADOPTIUMJRE64s);
|
||||||
|
java_candidates.append(SEMERUJRE64s);
|
||||||
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre8/bin/javaw.exe"));
|
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre8/bin/javaw.exe"));
|
||||||
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre7/bin/javaw.exe"));
|
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre7/bin/javaw.exe"));
|
||||||
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre6/bin/javaw.exe"));
|
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre6/bin/javaw.exe"));
|
||||||
@ -308,6 +315,7 @@ QList<QString> JavaUtils::FindJavaPaths()
|
|||||||
java_candidates.append(ADOPTOPENJDK64s);
|
java_candidates.append(ADOPTOPENJDK64s);
|
||||||
java_candidates.append(FOUNDATIONJDK64s);
|
java_candidates.append(FOUNDATIONJDK64s);
|
||||||
java_candidates.append(ADOPTIUMJDK64s);
|
java_candidates.append(ADOPTIUMJDK64s);
|
||||||
|
java_candidates.append(SEMERUJDK64s);
|
||||||
java_candidates.append(MICROSOFTJDK64s);
|
java_candidates.append(MICROSOFTJDK64s);
|
||||||
java_candidates.append(ZULU64s);
|
java_candidates.append(ZULU64s);
|
||||||
java_candidates.append(LIBERICA64s);
|
java_candidates.append(LIBERICA64s);
|
||||||
@ -316,6 +324,7 @@ QList<QString> JavaUtils::FindJavaPaths()
|
|||||||
java_candidates.append(NEWJRE32s);
|
java_candidates.append(NEWJRE32s);
|
||||||
java_candidates.append(ADOPTOPENJRE32s);
|
java_candidates.append(ADOPTOPENJRE32s);
|
||||||
java_candidates.append(ADOPTIUMJRE32s);
|
java_candidates.append(ADOPTIUMJRE32s);
|
||||||
|
java_candidates.append(SEMERUJRE32s);
|
||||||
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre8/bin/javaw.exe"));
|
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre8/bin/javaw.exe"));
|
||||||
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre7/bin/javaw.exe"));
|
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre7/bin/javaw.exe"));
|
||||||
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre6/bin/javaw.exe"));
|
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre6/bin/javaw.exe"));
|
||||||
@ -324,6 +333,7 @@ QList<QString> JavaUtils::FindJavaPaths()
|
|||||||
java_candidates.append(ADOPTOPENJDK32s);
|
java_candidates.append(ADOPTOPENJDK32s);
|
||||||
java_candidates.append(FOUNDATIONJDK32s);
|
java_candidates.append(FOUNDATIONJDK32s);
|
||||||
java_candidates.append(ADOPTIUMJDK32s);
|
java_candidates.append(ADOPTIUMJDK32s);
|
||||||
|
java_candidates.append(SEMERUJDK32s);
|
||||||
java_candidates.append(ZULU32s);
|
java_candidates.append(ZULU32s);
|
||||||
java_candidates.append(LIBERICA32s);
|
java_candidates.append(LIBERICA32s);
|
||||||
|
|
||||||
@ -362,13 +372,31 @@ QList<QString> JavaUtils::FindJavaPaths()
|
|||||||
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
|
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
|
||||||
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java");
|
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto home = qEnvironmentVariable("HOME");
|
||||||
|
|
||||||
|
// javas downloaded by sdkman
|
||||||
|
QDir sdkmanDir(FS::PathCombine(home, ".sdkman/candidates/java"));
|
||||||
|
QStringList sdkmanJavas = sdkmanDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||||
|
foreach (const QString& java, sdkmanJavas) {
|
||||||
|
javas.append(sdkmanDir.absolutePath() + "/" + java + "/bin/java");
|
||||||
|
}
|
||||||
|
|
||||||
|
// java in user library folder (like from intellij downloads)
|
||||||
|
QDir userLibraryJVMDir(FS::PathCombine(home, "Library/Java/JavaVirtualMachines/"));
|
||||||
|
QStringList userLibraryJVMJavas = userLibraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||||
|
foreach (const QString& java, userLibraryJVMJavas) {
|
||||||
|
javas.append(userLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
|
||||||
|
javas.append(userLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java");
|
||||||
|
}
|
||||||
|
|
||||||
javas.append(getMinecraftJavaBundle());
|
javas.append(getMinecraftJavaBundle());
|
||||||
javas = addJavasFromEnv(javas);
|
javas = addJavasFromEnv(javas);
|
||||||
javas.removeDuplicates();
|
javas.removeDuplicates();
|
||||||
return javas;
|
return javas;
|
||||||
}
|
}
|
||||||
|
|
||||||
#elif defined(Q_OS_LINUX)
|
#elif defined(Q_OS_LINUX) || defined(Q_OS_OPENBSD) || defined(Q_OS_FREEBSD)
|
||||||
QList<QString> JavaUtils::FindJavaPaths()
|
QList<QString> JavaUtils::FindJavaPaths()
|
||||||
{
|
{
|
||||||
QList<QString> javas;
|
QList<QString> javas;
|
||||||
@ -393,26 +421,37 @@ QList<QString> JavaUtils::FindJavaPaths()
|
|||||||
scanJavaDir(snap + dirPath);
|
scanJavaDir(snap + dirPath);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
#if defined(Q_OS_LINUX)
|
||||||
// oracle RPMs
|
// oracle RPMs
|
||||||
scanJavaDirs("/usr/java");
|
scanJavaDirs("/usr/java");
|
||||||
// general locations used by distro packaging
|
// general locations used by distro packaging
|
||||||
scanJavaDirs("/usr/lib/jvm");
|
scanJavaDirs("/usr/lib/jvm");
|
||||||
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
|
||||||
|
scanJavaDir("/usr/lib64");
|
||||||
|
scanJavaDir("/usr/lib");
|
||||||
|
scanJavaDir("/opt");
|
||||||
// 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
|
||||||
scanJavaDirs("/opt/jdk");
|
scanJavaDirs("/opt/jdk");
|
||||||
scanJavaDirs("/opt/jdks");
|
scanJavaDirs("/opt/jdks");
|
||||||
|
scanJavaDirs("/opt/ibm"); // IBM Semeru Certified Edition
|
||||||
// flatpak
|
// flatpak
|
||||||
scanJavaDirs("/app/jdk");
|
scanJavaDirs("/app/jdk");
|
||||||
|
#elif defined(Q_OS_OPENBSD) || defined(Q_OS_FREEBSD)
|
||||||
|
// ports install to /usr/local on OpenBSD & FreeBSD
|
||||||
|
scanJavaDirs("/usr/local");
|
||||||
|
#endif
|
||||||
auto home = qEnvironmentVariable("HOME");
|
auto home = qEnvironmentVariable("HOME");
|
||||||
|
|
||||||
// javas downloaded by IntelliJ
|
// javas downloaded by IntelliJ
|
||||||
scanJavaDirs(FS::PathCombine(home, ".jdks"));
|
scanJavaDirs(FS::PathCombine(home, ".jdks"));
|
||||||
// javas downloaded by sdkman
|
// javas downloaded by sdkman
|
||||||
scanJavaDirs(FS::PathCombine(home, ".sdkman/candidates/java"));
|
scanJavaDirs(FS::PathCombine(home, ".sdkman/candidates/java"));
|
||||||
|
// javas downloaded by gradle (toolchains)
|
||||||
|
scanJavaDirs(FS::PathCombine(home, ".gradle/jdks"));
|
||||||
|
|
||||||
javas.append(getMinecraftJavaBundle());
|
javas.append(getMinecraftJavaBundle());
|
||||||
javas = addJavasFromEnv(javas);
|
javas = addJavasFromEnv(javas);
|
||||||
@ -439,26 +478,25 @@ QString JavaUtils::getJavaCheckPath()
|
|||||||
|
|
||||||
QStringList getMinecraftJavaBundle()
|
QStringList getMinecraftJavaBundle()
|
||||||
{
|
{
|
||||||
QString partialPath;
|
|
||||||
QString executable = "java";
|
QString executable = "java";
|
||||||
QStringList processpaths;
|
QStringList processpaths;
|
||||||
#if defined(Q_OS_OSX)
|
#if defined(Q_OS_OSX)
|
||||||
partialPath = FS::PathCombine(QDir::homePath(), "Library/Application Support");
|
processpaths << FS::PathCombine(QDir::homePath(), FS::PathCombine("Library", "Application Support", "minecraft", "runtime"));
|
||||||
#elif defined(Q_OS_WIN32)
|
#elif defined(Q_OS_WIN32)
|
||||||
partialPath = QProcessEnvironment::systemEnvironment().value("LOCALAPPDATA", "");
|
|
||||||
executable += "w.exe";
|
executable += "w.exe";
|
||||||
|
|
||||||
|
auto appDataPath = QProcessEnvironment::systemEnvironment().value("APPDATA", "");
|
||||||
|
processpaths << FS::PathCombine(QFileInfo(appDataPath).absoluteFilePath(), ".minecraft", "runtime");
|
||||||
|
|
||||||
// add the microsoft store version of the launcher to the search. the current path is:
|
// add the microsoft store version of the launcher to the search. the current path is:
|
||||||
// C:\Users\USERNAME\AppData\Local\Packages\Microsoft.4297127D64EC6_8wekyb3d8bbwe\LocalCache\Local\runtime
|
// C:\Users\USERNAME\AppData\Local\Packages\Microsoft.4297127D64EC6_8wekyb3d8bbwe\LocalCache\Local\runtime
|
||||||
|
auto localAppDataPath = QProcessEnvironment::systemEnvironment().value("LOCALAPPDATA", "");
|
||||||
auto minecraftMSStorePath =
|
auto minecraftMSStorePath =
|
||||||
FS::PathCombine(QFileInfo(partialPath).absolutePath(), "Local", "Packages", "Microsoft.4297127D64EC6_8wekyb3d8bbwe");
|
FS::PathCombine(QFileInfo(localAppDataPath).absoluteFilePath(), "Packages", "Microsoft.4297127D64EC6_8wekyb3d8bbwe");
|
||||||
minecraftMSStorePath = FS::PathCombine(minecraftMSStorePath, "LocalCache", "Local", "runtime");
|
processpaths << FS::PathCombine(minecraftMSStorePath, "LocalCache", "Local", "runtime");
|
||||||
processpaths << minecraftMSStorePath;
|
|
||||||
#else
|
#else
|
||||||
partialPath = QDir::homePath();
|
processpaths << FS::PathCombine(QDir::homePath(), ".minecraft", "runtime");
|
||||||
#endif
|
#endif
|
||||||
auto minecraftDataPath = FS::PathCombine(partialPath, ".minecraft", "runtime");
|
|
||||||
processpaths << minecraftDataPath;
|
|
||||||
|
|
||||||
QStringList javas;
|
QStringList javas;
|
||||||
while (!processpaths.isEmpty()) {
|
while (!processpaths.isEmpty()) {
|
||||||
|
@ -25,7 +25,7 @@ class LaunchStep : public Task {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public: /* methods */
|
public: /* methods */
|
||||||
explicit LaunchStep(LaunchTask* parent) : Task(nullptr), m_parent(parent) { bind(parent); };
|
explicit LaunchStep(LaunchTask* parent) : Task(nullptr), m_parent(parent) { bind(parent); };
|
||||||
virtual ~LaunchStep(){};
|
virtual ~LaunchStep() {};
|
||||||
|
|
||||||
private: /* methods */
|
private: /* methods */
|
||||||
void bind(LaunchTask* parent);
|
void bind(LaunchTask* parent);
|
||||||
@ -37,9 +37,9 @@ class LaunchStep : public Task {
|
|||||||
void progressReportingRequest();
|
void progressReportingRequest();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
virtual void proceed(){};
|
virtual void proceed() {};
|
||||||
// called in the opposite order than the Task launch(), used to clean up or otherwise undo things after the launch ends
|
// called in the opposite order than the Task launch(), used to clean up or otherwise undo things after the launch ends
|
||||||
virtual void finalize(){};
|
virtual void finalize() {};
|
||||||
|
|
||||||
protected: /* data */
|
protected: /* data */
|
||||||
LaunchTask* m_parent;
|
LaunchTask* m_parent;
|
||||||
|
@ -55,7 +55,7 @@ class LaunchTask : public Task {
|
|||||||
|
|
||||||
public: /* methods */
|
public: /* methods */
|
||||||
static shared_qobject_ptr<LaunchTask> create(InstancePtr inst);
|
static shared_qobject_ptr<LaunchTask> create(InstancePtr inst);
|
||||||
virtual ~LaunchTask(){};
|
virtual ~LaunchTask() {};
|
||||||
|
|
||||||
void appendStep(shared_qobject_ptr<LaunchStep> step);
|
void appendStep(shared_qobject_ptr<LaunchStep> step);
|
||||||
void prependStep(shared_qobject_ptr<LaunchStep> step);
|
void prependStep(shared_qobject_ptr<LaunchStep> step);
|
||||||
|
@ -22,8 +22,8 @@
|
|||||||
class CheckJava : public LaunchStep {
|
class CheckJava : public LaunchStep {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit CheckJava(LaunchTask* parent) : LaunchStep(parent){};
|
explicit CheckJava(LaunchTask* parent) : LaunchStep(parent) {};
|
||||||
virtual ~CheckJava(){};
|
virtual ~CheckJava() {};
|
||||||
|
|
||||||
virtual void executeTask();
|
virtual void executeTask();
|
||||||
virtual bool canAbort() const { return false; }
|
virtual bool canAbort() const { return false; }
|
||||||
|
@ -30,7 +30,7 @@ void LookupServerAddress::setLookupAddress(const QString& lookupAddress)
|
|||||||
m_dnsLookup->setName(QString("_minecraft._tcp.%1").arg(lookupAddress));
|
m_dnsLookup->setName(QString("_minecraft._tcp.%1").arg(lookupAddress));
|
||||||
}
|
}
|
||||||
|
|
||||||
void LookupServerAddress::setOutputAddressPtr(MinecraftServerTargetPtr output)
|
void LookupServerAddress::setOutputAddressPtr(MinecraftTarget::Ptr output)
|
||||||
{
|
{
|
||||||
m_output = std::move(output);
|
m_output = std::move(output);
|
||||||
}
|
}
|
||||||
|
@ -19,20 +19,20 @@
|
|||||||
#include <launch/LaunchStep.h>
|
#include <launch/LaunchStep.h>
|
||||||
#include <QDnsLookup>
|
#include <QDnsLookup>
|
||||||
|
|
||||||
#include "minecraft/launch/MinecraftServerTarget.h"
|
#include "minecraft/launch/MinecraftTarget.h"
|
||||||
|
|
||||||
class LookupServerAddress : public LaunchStep {
|
class LookupServerAddress : public LaunchStep {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit LookupServerAddress(LaunchTask* parent);
|
explicit LookupServerAddress(LaunchTask* parent);
|
||||||
virtual ~LookupServerAddress(){};
|
virtual ~LookupServerAddress() = default;
|
||||||
|
|
||||||
virtual void executeTask();
|
virtual void executeTask();
|
||||||
virtual bool abort();
|
virtual bool abort();
|
||||||
virtual bool canAbort() const { return true; }
|
virtual bool canAbort() const { return true; }
|
||||||
|
|
||||||
void setLookupAddress(const QString& lookupAddress);
|
void setLookupAddress(const QString& lookupAddress);
|
||||||
void setOutputAddressPtr(MinecraftServerTargetPtr output);
|
void setOutputAddressPtr(MinecraftTarget::Ptr output);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void on_dnsLookupFinished();
|
void on_dnsLookupFinished();
|
||||||
@ -42,5 +42,5 @@ class LookupServerAddress : public LaunchStep {
|
|||||||
|
|
||||||
QDnsLookup* m_dnsLookup;
|
QDnsLookup* m_dnsLookup;
|
||||||
QString m_lookupAddress;
|
QString m_lookupAddress;
|
||||||
MinecraftServerTargetPtr m_output;
|
MinecraftTarget::Ptr m_output;
|
||||||
};
|
};
|
||||||
|
@ -22,7 +22,7 @@ class PostLaunchCommand : public LaunchStep {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit PostLaunchCommand(LaunchTask* parent);
|
explicit PostLaunchCommand(LaunchTask* parent);
|
||||||
virtual ~PostLaunchCommand(){};
|
virtual ~PostLaunchCommand() {};
|
||||||
|
|
||||||
virtual void executeTask();
|
virtual void executeTask();
|
||||||
virtual bool abort();
|
virtual bool abort();
|
||||||
|
@ -22,7 +22,7 @@ class PreLaunchCommand : public LaunchStep {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit PreLaunchCommand(LaunchTask* parent);
|
explicit PreLaunchCommand(LaunchTask* parent);
|
||||||
virtual ~PreLaunchCommand(){};
|
virtual ~PreLaunchCommand() {};
|
||||||
|
|
||||||
virtual void executeTask();
|
virtual void executeTask();
|
||||||
virtual bool abort();
|
virtual bool abort();
|
||||||
|
65
launcher/launch/steps/PrintServers.cpp
Normal file
65
launcher/launch/steps/PrintServers.cpp
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2024 Leia uwu <leia@tutamail.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 "PrintServers.h"
|
||||||
|
#include "QHostInfo"
|
||||||
|
|
||||||
|
PrintServers::PrintServers(LaunchTask* parent, const QStringList& servers) : LaunchStep(parent)
|
||||||
|
{
|
||||||
|
m_servers = servers;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintServers::executeTask()
|
||||||
|
{
|
||||||
|
for (QString server : m_servers) {
|
||||||
|
QHostInfo::lookupHost(server, this, &PrintServers::resolveServer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintServers::resolveServer(const QHostInfo& host_info)
|
||||||
|
{
|
||||||
|
QString server = host_info.hostName();
|
||||||
|
QString addresses = server + " resolves to:\n [";
|
||||||
|
|
||||||
|
if (!host_info.addresses().isEmpty()) {
|
||||||
|
for (QHostAddress address : host_info.addresses()) {
|
||||||
|
addresses += address.toString();
|
||||||
|
if (!host_info.addresses().endsWith(address)) {
|
||||||
|
addresses += ", ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addresses += "N/A";
|
||||||
|
}
|
||||||
|
addresses += "]\n\n";
|
||||||
|
|
||||||
|
m_server_to_address.insert(server, addresses);
|
||||||
|
|
||||||
|
// print server info in order once all servers are resolved
|
||||||
|
if (m_server_to_address.size() >= m_servers.size()) {
|
||||||
|
for (QString serv : m_servers) {
|
||||||
|
emit logLine(m_server_to_address.value(serv), MessageLevel::Launcher);
|
||||||
|
}
|
||||||
|
emitSucceeded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PrintServers::canAbort() const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
37
launcher/launch/steps/PrintServers.h
Normal file
37
launcher/launch/steps/PrintServers.h
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2024 Leia uwu <leia@tutamail.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 <LoggedProcess.h>
|
||||||
|
#include <java/JavaChecker.h>
|
||||||
|
#include <launch/LaunchStep.h>
|
||||||
|
#include <QHostInfo>
|
||||||
|
|
||||||
|
class PrintServers : public LaunchStep {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
PrintServers(LaunchTask* parent, const QStringList& servers);
|
||||||
|
|
||||||
|
virtual void executeTask();
|
||||||
|
virtual bool canAbort() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void resolveServer(const QHostInfo& host_info);
|
||||||
|
QMap<QString, QString> m_server_to_address;
|
||||||
|
QStringList m_servers;
|
||||||
|
};
|
@ -23,8 +23,8 @@
|
|||||||
class QuitAfterGameStop : public LaunchStep {
|
class QuitAfterGameStop : public LaunchStep {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit QuitAfterGameStop(LaunchTask* parent) : LaunchStep(parent){};
|
explicit QuitAfterGameStop(LaunchTask* parent) : LaunchStep(parent) {};
|
||||||
virtual ~QuitAfterGameStop(){};
|
virtual ~QuitAfterGameStop() {};
|
||||||
|
|
||||||
virtual void executeTask();
|
virtual void executeTask();
|
||||||
virtual bool canAbort() const { return false; }
|
virtual bool canAbort() const { return false; }
|
||||||
|
@ -28,7 +28,7 @@ class TextPrint : public LaunchStep {
|
|||||||
public:
|
public:
|
||||||
explicit TextPrint(LaunchTask* parent, const QStringList& lines, MessageLevel::Enum level);
|
explicit TextPrint(LaunchTask* parent, const QStringList& lines, MessageLevel::Enum level);
|
||||||
explicit TextPrint(LaunchTask* parent, const QString& line, MessageLevel::Enum level);
|
explicit TextPrint(LaunchTask* parent, const QString& line, MessageLevel::Enum level);
|
||||||
virtual ~TextPrint(){};
|
virtual ~TextPrint() {};
|
||||||
|
|
||||||
virtual void executeTask();
|
virtual void executeTask();
|
||||||
virtual bool canAbort() const;
|
virtual bool canAbort() const;
|
||||||
|
@ -25,8 +25,8 @@
|
|||||||
class Update : public LaunchStep {
|
class Update : public LaunchStep {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit Update(LaunchTask* parent, Net::Mode mode) : LaunchStep(parent), m_mode(mode){};
|
explicit Update(LaunchTask* parent, Net::Mode mode) : LaunchStep(parent), m_mode(mode) {};
|
||||||
virtual ~Update(){};
|
virtual ~Update() {};
|
||||||
|
|
||||||
void executeTask() override;
|
void executeTask() override;
|
||||||
bool canAbort() const override;
|
bool canAbort() const override;
|
||||||
|
@ -15,27 +15,43 @@
|
|||||||
|
|
||||||
#include "BaseEntity.h"
|
#include "BaseEntity.h"
|
||||||
|
|
||||||
|
#include "Exception.h"
|
||||||
|
#include "FileSystem.h"
|
||||||
#include "Json.h"
|
#include "Json.h"
|
||||||
|
#include "modplatform/helpers/HashUtils.h"
|
||||||
#include "net/ApiDownload.h"
|
#include "net/ApiDownload.h"
|
||||||
|
#include "net/ChecksumValidator.h"
|
||||||
#include "net/HttpMetaCache.h"
|
#include "net/HttpMetaCache.h"
|
||||||
|
#include "net/Mode.h"
|
||||||
#include "net/NetJob.h"
|
#include "net/NetJob.h"
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "BuildConfig.h"
|
#include "BuildConfig.h"
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
|
namespace Meta {
|
||||||
|
|
||||||
class ParsingValidator : public Net::Validator {
|
class ParsingValidator : public Net::Validator {
|
||||||
public: /* con/des */
|
public: /* con/des */
|
||||||
ParsingValidator(Meta::BaseEntity* entity) : m_entity(entity){};
|
ParsingValidator(BaseEntity* entity) : m_entity(entity) {};
|
||||||
virtual ~ParsingValidator(){};
|
virtual ~ParsingValidator() = default;
|
||||||
|
|
||||||
public: /* methods */
|
public: /* methods */
|
||||||
bool init(QNetworkRequest&) override { return true; }
|
bool init(QNetworkRequest&) override
|
||||||
|
{
|
||||||
|
m_data.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
bool write(QByteArray& data) override
|
bool write(QByteArray& data) override
|
||||||
{
|
{
|
||||||
this->m_data.append(data);
|
this->m_data.append(data);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
bool abort() override { return true; }
|
bool abort() override
|
||||||
|
{
|
||||||
|
m_data.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
bool validate(QNetworkReply&) override
|
bool validate(QNetworkReply&) override
|
||||||
{
|
{
|
||||||
auto fname = m_entity->localFilename();
|
auto fname = m_entity->localFilename();
|
||||||
@ -52,93 +68,131 @@ class ParsingValidator : public Net::Validator {
|
|||||||
|
|
||||||
private: /* data */
|
private: /* data */
|
||||||
QByteArray m_data;
|
QByteArray m_data;
|
||||||
Meta::BaseEntity* m_entity;
|
BaseEntity* m_entity;
|
||||||
};
|
};
|
||||||
|
|
||||||
Meta::BaseEntity::~BaseEntity() {}
|
QUrl BaseEntity::url() const
|
||||||
|
|
||||||
QUrl Meta::BaseEntity::url() const
|
|
||||||
{
|
{
|
||||||
auto s = APPLICATION->settings();
|
auto s = APPLICATION->settings();
|
||||||
QString metaOverride = s->get("MetaURLOverride").toString();
|
QString metaOverride = s->get("MetaURLOverride").toString();
|
||||||
if (metaOverride.isEmpty()) {
|
if (metaOverride.isEmpty()) {
|
||||||
return QUrl(BuildConfig.META_URL).resolved(localFilename());
|
return QUrl(BuildConfig.META_URL).resolved(localFilename());
|
||||||
} else {
|
|
||||||
return QUrl(metaOverride).resolved(localFilename());
|
|
||||||
}
|
}
|
||||||
|
return QUrl(metaOverride).resolved(localFilename());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Meta::BaseEntity::loadLocalFile()
|
Task::Ptr BaseEntity::loadTask(Net::Mode mode)
|
||||||
{
|
{
|
||||||
const QString fname = QDir("meta").absoluteFilePath(localFilename());
|
if (m_task && m_task->isRunning()) {
|
||||||
if (!QFile::exists(fname)) {
|
return m_task;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// TODO: check if the file has the expected checksum
|
|
||||||
try {
|
|
||||||
auto doc = Json::requireDocument(fname, fname);
|
|
||||||
auto obj = Json::requireObject(doc, fname);
|
|
||||||
parse(obj);
|
|
||||||
return true;
|
|
||||||
} catch (const Exception& e) {
|
|
||||||
qDebug() << QString("Unable to parse file %1: %2").arg(fname, e.cause());
|
|
||||||
// just make sure it's gone and we never consider it again.
|
|
||||||
QFile::remove(fname);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
m_task.reset(new BaseEntityLoadTask(this, mode));
|
||||||
|
return m_task;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Meta::BaseEntity::load(Net::Mode loadType)
|
bool BaseEntity::isLoaded() const
|
||||||
{
|
{
|
||||||
// load local file if nothing is loaded yet
|
// consider it loaded only if the main hash is either empty and was remote loadded or the hashes match and was loaded
|
||||||
if (!isLoaded()) {
|
return m_sha256.isEmpty() ? m_load_status == LoadStatus::Remote : m_load_status != LoadStatus::NotLoaded && m_sha256 == m_file_sha256;
|
||||||
if (loadLocalFile()) {
|
}
|
||||||
m_loadStatus = LoadStatus::Local;
|
|
||||||
|
void BaseEntity::setSha256(QString sha256)
|
||||||
|
{
|
||||||
|
m_sha256 = sha256;
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseEntity::LoadStatus BaseEntity::status() const
|
||||||
|
{
|
||||||
|
return m_load_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseEntityLoadTask::BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode) : m_entity(parent), m_mode(mode) {}
|
||||||
|
|
||||||
|
void BaseEntityLoadTask::executeTask()
|
||||||
|
{
|
||||||
|
const QString fname = QDir("meta").absoluteFilePath(m_entity->localFilename());
|
||||||
|
auto hashMatches = false;
|
||||||
|
// the file exists on disk try to load it
|
||||||
|
if (QFile::exists(fname)) {
|
||||||
|
try {
|
||||||
|
QByteArray fileData;
|
||||||
|
// read local file if nothing is loaded yet
|
||||||
|
if (m_entity->m_load_status == BaseEntity::LoadStatus::NotLoaded || m_entity->m_file_sha256.isEmpty()) {
|
||||||
|
setStatus(tr("Loading local file"));
|
||||||
|
fileData = FS::read(fname);
|
||||||
|
m_entity->m_file_sha256 = Hashing::hash(fileData, Hashing::Algorithm::Sha256);
|
||||||
|
}
|
||||||
|
|
||||||
|
// on online the hash needs to match
|
||||||
|
hashMatches = m_entity->m_sha256 == m_entity->m_file_sha256;
|
||||||
|
if (m_mode == Net::Mode::Online && !m_entity->m_sha256.isEmpty() && !hashMatches) {
|
||||||
|
throw Exception("mismatched checksum");
|
||||||
|
}
|
||||||
|
|
||||||
|
// load local file
|
||||||
|
if (m_entity->m_load_status == BaseEntity::LoadStatus::NotLoaded) {
|
||||||
|
auto doc = Json::requireDocument(fileData, fname);
|
||||||
|
auto obj = Json::requireObject(doc, fname);
|
||||||
|
m_entity->parse(obj);
|
||||||
|
m_entity->m_load_status = BaseEntity::LoadStatus::Local;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (const Exception& e) {
|
||||||
|
qDebug() << QString("Unable to parse file %1: %2").arg(fname, e.cause());
|
||||||
|
// just make sure it's gone and we never consider it again.
|
||||||
|
FS::deletePath(fname);
|
||||||
|
m_entity->m_load_status = BaseEntity::LoadStatus::NotLoaded;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if we need remote update, run the update task
|
// if we need remote update, run the update task
|
||||||
if (loadType == Net::Mode::Offline || !shouldStartRemoteUpdate()) {
|
auto wasLoadedOffline = m_entity->m_load_status != BaseEntity::LoadStatus::NotLoaded && m_mode == Net::Mode::Offline;
|
||||||
|
// if has is not present allways fetch from remote(e.g. the main index file), else only fetch if hash doesn't match
|
||||||
|
auto wasLoadedRemote = m_entity->m_sha256.isEmpty() ? m_entity->m_load_status == BaseEntity::LoadStatus::Remote : hashMatches;
|
||||||
|
if (wasLoadedOffline || wasLoadedRemote) {
|
||||||
|
emitSucceeded();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_updateTask.reset(new NetJob(QObject::tr("Download of meta file %1").arg(localFilename()), APPLICATION->network()));
|
m_task.reset(new NetJob(QObject::tr("Download of meta file %1").arg(m_entity->localFilename()), APPLICATION->network()));
|
||||||
auto url = this->url();
|
auto url = m_entity->url();
|
||||||
auto entry = APPLICATION->metacache()->resolveEntry("meta", localFilename());
|
auto entry = APPLICATION->metacache()->resolveEntry("meta", m_entity->localFilename());
|
||||||
entry->setStale(true);
|
entry->setStale(true);
|
||||||
auto dl = Net::ApiDownload::makeCached(url, entry);
|
auto dl = Net::ApiDownload::makeCached(url, entry);
|
||||||
/*
|
/*
|
||||||
* The validator parses the file and loads it into the object.
|
* The validator parses the file and loads it into the object.
|
||||||
* If that fails, the file is not written to storage.
|
* If that fails, the file is not written to storage.
|
||||||
*/
|
*/
|
||||||
dl->addValidator(new ParsingValidator(this));
|
if (!m_entity->m_sha256.isEmpty())
|
||||||
m_updateTask->addNetAction(dl);
|
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Algorithm::Sha256, m_entity->m_sha256));
|
||||||
m_updateStatus = UpdateStatus::InProgress;
|
dl->addValidator(new ParsingValidator(m_entity));
|
||||||
QObject::connect(m_updateTask.get(), &NetJob::succeeded, [&]() {
|
m_task->addNetAction(dl);
|
||||||
m_loadStatus = LoadStatus::Remote;
|
m_task->setAskRetry(false);
|
||||||
m_updateStatus = UpdateStatus::Succeeded;
|
connect(m_task.get(), &Task::failed, this, &BaseEntityLoadTask::emitFailed);
|
||||||
m_updateTask.reset();
|
connect(m_task.get(), &Task::succeeded, this, &BaseEntityLoadTask::emitSucceeded);
|
||||||
|
connect(m_task.get(), &Task::succeeded, this, [this]() {
|
||||||
|
m_entity->m_load_status = BaseEntity::LoadStatus::Remote;
|
||||||
|
m_entity->m_file_sha256 = m_entity->m_sha256;
|
||||||
});
|
});
|
||||||
QObject::connect(m_updateTask.get(), &NetJob::failed, [&]() {
|
|
||||||
m_updateStatus = UpdateStatus::Failed;
|
connect(m_task.get(), &Task::progress, this, &Task::setProgress);
|
||||||
m_updateTask.reset();
|
connect(m_task.get(), &Task::stepProgress, this, &BaseEntityLoadTask::propagateStepProgress);
|
||||||
});
|
connect(m_task.get(), &Task::status, this, &Task::setStatus);
|
||||||
m_updateTask->start();
|
connect(m_task.get(), &Task::details, this, &Task::setDetails);
|
||||||
|
|
||||||
|
m_task->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Meta::BaseEntity::isLoaded() const
|
bool BaseEntityLoadTask::canAbort() const
|
||||||
{
|
{
|
||||||
return m_loadStatus > LoadStatus::NotLoaded;
|
return m_task ? m_task->canAbort() : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Meta::BaseEntity::shouldStartRemoteUpdate() const
|
bool BaseEntityLoadTask::abort()
|
||||||
{
|
{
|
||||||
// TODO: version-locks and offline mode?
|
if (m_task) {
|
||||||
return m_updateStatus != UpdateStatus::InProgress;
|
Task::abort();
|
||||||
}
|
return m_task->abort();
|
||||||
|
|
||||||
Task::Ptr Meta::BaseEntity::getCurrentTask()
|
|
||||||
{
|
|
||||||
if (m_updateStatus == UpdateStatus::InProgress) {
|
|
||||||
return m_updateTask;
|
|
||||||
}
|
}
|
||||||
return nullptr;
|
return Task::abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace Meta
|
||||||
|
@ -17,38 +17,57 @@
|
|||||||
|
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include "QObjectPtr.h"
|
|
||||||
|
|
||||||
#include "net/Mode.h"
|
#include "net/Mode.h"
|
||||||
#include "net/NetJob.h"
|
#include "net/NetJob.h"
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
namespace Meta {
|
namespace Meta {
|
||||||
|
class BaseEntityLoadTask;
|
||||||
class BaseEntity {
|
class BaseEntity {
|
||||||
|
friend BaseEntityLoadTask;
|
||||||
|
|
||||||
public: /* types */
|
public: /* types */
|
||||||
using Ptr = std::shared_ptr<BaseEntity>;
|
using Ptr = std::shared_ptr<BaseEntity>;
|
||||||
enum class LoadStatus { NotLoaded, Local, Remote };
|
enum class LoadStatus { NotLoaded, Local, Remote };
|
||||||
enum class UpdateStatus { NotDone, InProgress, Failed, Succeeded };
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual ~BaseEntity();
|
virtual ~BaseEntity() = default;
|
||||||
|
|
||||||
virtual void parse(const QJsonObject& obj) = 0;
|
|
||||||
|
|
||||||
virtual QString localFilename() const = 0;
|
virtual QString localFilename() const = 0;
|
||||||
virtual QUrl url() const;
|
virtual QUrl url() const;
|
||||||
|
|
||||||
bool isLoaded() const;
|
bool isLoaded() const;
|
||||||
bool shouldStartRemoteUpdate() const;
|
LoadStatus status() const;
|
||||||
|
|
||||||
void load(Net::Mode loadType);
|
/* for parsers */
|
||||||
Task::Ptr getCurrentTask();
|
void setSha256(QString sha256);
|
||||||
|
|
||||||
protected: /* methods */
|
virtual void parse(const QJsonObject& obj) = 0;
|
||||||
bool loadLocalFile();
|
[[nodiscard]] Task::Ptr loadTask(Net::Mode loadType = Net::Mode::Online);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QString m_sha256; // the expected sha256
|
||||||
|
QString m_file_sha256; // the file sha256
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LoadStatus m_loadStatus = LoadStatus::NotLoaded;
|
LoadStatus m_load_status = LoadStatus::NotLoaded;
|
||||||
UpdateStatus m_updateStatus = UpdateStatus::NotDone;
|
Task::Ptr m_task;
|
||||||
NetJob::Ptr m_updateTask;
|
};
|
||||||
|
|
||||||
|
class BaseEntityLoadTask : public Task {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode);
|
||||||
|
~BaseEntityLoadTask() override = default;
|
||||||
|
|
||||||
|
virtual void executeTask() override;
|
||||||
|
virtual bool canAbort() const override;
|
||||||
|
virtual bool abort() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
BaseEntity* m_entity;
|
||||||
|
Net::Mode m_mode;
|
||||||
|
NetJob::Ptr m_task;
|
||||||
};
|
};
|
||||||
} // namespace Meta
|
} // namespace Meta
|
||||||
|
@ -16,7 +16,10 @@
|
|||||||
#include "Index.h"
|
#include "Index.h"
|
||||||
|
|
||||||
#include "JsonFormat.h"
|
#include "JsonFormat.h"
|
||||||
|
#include "QObjectPtr.h"
|
||||||
#include "VersionList.h"
|
#include "VersionList.h"
|
||||||
|
#include "meta/BaseEntity.h"
|
||||||
|
#include "tasks/SequentialTask.h"
|
||||||
|
|
||||||
namespace Meta {
|
namespace Meta {
|
||||||
Index::Index(QObject* parent) : QAbstractListModel(parent) {}
|
Index::Index(QObject* parent) : QAbstractListModel(parent) {}
|
||||||
@ -51,14 +54,17 @@ QVariant Index::data(const QModelIndex& index, int role) const
|
|||||||
}
|
}
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
int Index::rowCount(const QModelIndex& parent) const
|
int Index::rowCount(const QModelIndex& parent) const
|
||||||
{
|
{
|
||||||
return parent.isValid() ? 0 : m_lists.size();
|
return parent.isValid() ? 0 : m_lists.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
int Index::columnCount(const QModelIndex& parent) const
|
int Index::columnCount(const QModelIndex& parent) const
|
||||||
{
|
{
|
||||||
return parent.isValid() ? 0 : 1;
|
return parent.isValid() ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant Index::headerData(int section, Qt::Orientation orientation, int role) const
|
QVariant Index::headerData(int section, Qt::Orientation orientation, int role) const
|
||||||
{
|
{
|
||||||
if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0) {
|
if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0) {
|
||||||
@ -79,6 +85,7 @@ VersionList::Ptr Index::get(const QString& uid)
|
|||||||
if (!out) {
|
if (!out) {
|
||||||
out = std::make_shared<VersionList>(uid);
|
out = std::make_shared<VersionList>(uid);
|
||||||
m_uids[uid] = out;
|
m_uids[uid] = out;
|
||||||
|
m_lists.append(out);
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
@ -96,7 +103,7 @@ void Index::parse(const QJsonObject& obj)
|
|||||||
|
|
||||||
void Index::merge(const std::shared_ptr<Index>& other)
|
void Index::merge(const std::shared_ptr<Index>& other)
|
||||||
{
|
{
|
||||||
const QVector<VersionList::Ptr> lists = std::dynamic_pointer_cast<Index>(other)->m_lists;
|
const QVector<VersionList::Ptr> lists = other->m_lists;
|
||||||
// initial load, no need to merge
|
// initial load, no need to merge
|
||||||
if (m_lists.isEmpty()) {
|
if (m_lists.isEmpty()) {
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
@ -123,7 +130,33 @@ void Index::merge(const std::shared_ptr<Index>& other)
|
|||||||
|
|
||||||
void Index::connectVersionList(const int row, const VersionList::Ptr& list)
|
void Index::connectVersionList(const int row, const VersionList::Ptr& list)
|
||||||
{
|
{
|
||||||
connect(list.get(), &VersionList::nameChanged, this,
|
connect(list.get(), &VersionList::nameChanged, this, [this, row] { emit dataChanged(index(row), index(row), { Qt::DisplayRole }); });
|
||||||
[this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << Qt::DisplayRole); });
|
}
|
||||||
|
|
||||||
|
Task::Ptr Index::loadVersion(const QString& uid, const QString& version, Net::Mode mode, bool force)
|
||||||
|
{
|
||||||
|
if (mode == Net::Mode::Offline) {
|
||||||
|
return get(uid, version)->loadTask(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto versionList = get(uid);
|
||||||
|
auto loadTask = makeShared<SequentialTask>(
|
||||||
|
this, tr("Load meta for %1:%2", "This is for the task name that loads the meta index.").arg(uid, version));
|
||||||
|
if (status() != BaseEntity::LoadStatus::Remote || force) {
|
||||||
|
loadTask->addTask(this->loadTask(mode));
|
||||||
|
}
|
||||||
|
loadTask->addTask(versionList->loadTask(mode));
|
||||||
|
loadTask->addTask(versionList->getVersion(version)->loadTask(mode));
|
||||||
|
return loadTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
Version::Ptr Index::getLoadedVersion(const QString& uid, const QString& version)
|
||||||
|
{
|
||||||
|
QEventLoop ev;
|
||||||
|
auto task = loadVersion(uid, version);
|
||||||
|
QObject::connect(task.get(), &Task::finished, &ev, &QEventLoop::quit);
|
||||||
|
task->start();
|
||||||
|
ev.exec();
|
||||||
|
return get(uid, version);
|
||||||
}
|
}
|
||||||
} // namespace Meta
|
} // namespace Meta
|
||||||
|
@ -16,10 +16,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "BaseEntity.h"
|
#include "BaseEntity.h"
|
||||||
#include "meta/VersionList.h"
|
#include "meta/VersionList.h"
|
||||||
|
#include "net/Mode.h"
|
||||||
|
|
||||||
class Task;
|
class Task;
|
||||||
|
|
||||||
@ -30,6 +30,7 @@ class Index : public QAbstractListModel, public BaseEntity {
|
|||||||
public:
|
public:
|
||||||
explicit Index(QObject* parent = nullptr);
|
explicit Index(QObject* parent = nullptr);
|
||||||
explicit Index(const QVector<VersionList::Ptr>& lists, QObject* parent = nullptr);
|
explicit Index(const QVector<VersionList::Ptr>& lists, QObject* parent = nullptr);
|
||||||
|
virtual ~Index() = default;
|
||||||
|
|
||||||
enum { UidRole = Qt::UserRole, NameRole, ListPtrRole };
|
enum { UidRole = Qt::UserRole, NameRole, ListPtrRole };
|
||||||
|
|
||||||
@ -47,14 +48,21 @@ class Index : public QAbstractListModel, public BaseEntity {
|
|||||||
|
|
||||||
QVector<VersionList::Ptr> lists() const { return m_lists; }
|
QVector<VersionList::Ptr> lists() const { return m_lists; }
|
||||||
|
|
||||||
|
Task::Ptr loadVersion(const QString& uid, const QString& version = {}, Net::Mode mode = Net::Mode::Online, bool force = false);
|
||||||
|
|
||||||
|
// this blocks until the version is loaded
|
||||||
|
Version::Ptr getLoadedVersion(const QString& uid, const QString& version);
|
||||||
|
|
||||||
public: // for usage by parsers only
|
public: // for usage by parsers only
|
||||||
void merge(const std::shared_ptr<Index>& other);
|
void merge(const std::shared_ptr<Index>& other);
|
||||||
|
|
||||||
|
protected:
|
||||||
void parse(const QJsonObject& obj) override;
|
void parse(const QJsonObject& obj) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QVector<VersionList::Ptr> m_lists;
|
QVector<VersionList::Ptr> m_lists;
|
||||||
QHash<QString, VersionList::Ptr> m_uids;
|
QHash<QString, VersionList::Ptr> m_uids;
|
||||||
|
|
||||||
void connectVersionList(const int row, const VersionList::Ptr& list);
|
void connectVersionList(int row, const VersionList::Ptr& list);
|
||||||
};
|
};
|
||||||
} // namespace Meta
|
} // namespace Meta
|
||||||
|
@ -41,6 +41,7 @@ static std::shared_ptr<Index> parseIndexInternal(const QJsonObject& obj)
|
|||||||
std::transform(objects.begin(), objects.end(), std::back_inserter(lists), [](const QJsonObject& obj) {
|
std::transform(objects.begin(), objects.end(), std::back_inserter(lists), [](const QJsonObject& obj) {
|
||||||
VersionList::Ptr list = std::make_shared<VersionList>(requireString(obj, "uid"));
|
VersionList::Ptr list = std::make_shared<VersionList>(requireString(obj, "uid"));
|
||||||
list->setName(ensureString(obj, "name", QString()));
|
list->setName(ensureString(obj, "name", QString()));
|
||||||
|
list->setSha256(ensureString(obj, "sha256", QString()));
|
||||||
return list;
|
return list;
|
||||||
});
|
});
|
||||||
return std::make_shared<Index>(lists);
|
return std::make_shared<Index>(lists);
|
||||||
@ -58,6 +59,9 @@ static Version::Ptr parseCommonVersion(const QString& uid, const QJsonObject& ob
|
|||||||
parseRequires(obj, &reqs, "requires");
|
parseRequires(obj, &reqs, "requires");
|
||||||
parseRequires(obj, &conflicts, "conflicts");
|
parseRequires(obj, &conflicts, "conflicts");
|
||||||
version->setRequires(reqs, conflicts);
|
version->setRequires(reqs, conflicts);
|
||||||
|
if (auto sha256 = ensureString(obj, "sha256", QString()); !sha256.isEmpty()) {
|
||||||
|
version->setSha256(sha256);
|
||||||
|
}
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,11 +16,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include <set>
|
#include <set>
|
||||||
#include "Exception.h"
|
#include "Exception.h"
|
||||||
#include "meta/BaseEntity.h"
|
|
||||||
|
|
||||||
namespace Meta {
|
namespace Meta {
|
||||||
class Index;
|
class Index;
|
||||||
|
@ -18,12 +18,9 @@
|
|||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
|
|
||||||
#include "JsonFormat.h"
|
#include "JsonFormat.h"
|
||||||
#include "minecraft/PackProfile.h"
|
|
||||||
|
|
||||||
Meta::Version::Version(const QString& uid, const QString& version) : BaseVersion(), m_uid(uid), m_version(version) {}
|
Meta::Version::Version(const QString& uid, const QString& version) : BaseVersion(), m_uid(uid), m_version(version) {}
|
||||||
|
|
||||||
Meta::Version::~Version() {}
|
|
||||||
|
|
||||||
QString Meta::Version::descriptor()
|
QString Meta::Version::descriptor()
|
||||||
{
|
{
|
||||||
return m_version;
|
return m_version;
|
||||||
@ -71,6 +68,9 @@ void Meta::Version::mergeFromList(const Meta::Version::Ptr& other)
|
|||||||
if (m_volatile != other->m_volatile) {
|
if (m_volatile != other->m_volatile) {
|
||||||
setVolatile(other->m_volatile);
|
setVolatile(other->m_volatile);
|
||||||
}
|
}
|
||||||
|
if (!other->m_sha256.isEmpty()) {
|
||||||
|
m_sha256 = other->m_sha256;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Meta::Version::merge(const Version::Ptr& other)
|
void Meta::Version::merge(const Version::Ptr& other)
|
||||||
|
@ -38,7 +38,7 @@ class Version : public QObject, public BaseVersion, public BaseEntity {
|
|||||||
using Ptr = std::shared_ptr<Version>;
|
using Ptr = std::shared_ptr<Version>;
|
||||||
|
|
||||||
explicit Version(const QString& uid, const QString& version);
|
explicit Version(const QString& uid, const QString& version);
|
||||||
virtual ~Version();
|
virtual ~Version() = default;
|
||||||
|
|
||||||
QString descriptor() override;
|
QString descriptor() override;
|
||||||
QString name() override;
|
QString name() override;
|
||||||
@ -52,7 +52,7 @@ class Version : public QObject, public BaseVersion, public BaseEntity {
|
|||||||
const Meta::RequireSet& requiredSet() const { return m_requires; }
|
const Meta::RequireSet& requiredSet() const { return m_requires; }
|
||||||
VersionFilePtr data() const { return m_data; }
|
VersionFilePtr data() const { return m_data; }
|
||||||
bool isRecommended() const { return m_recommended; }
|
bool isRecommended() const { return m_recommended; }
|
||||||
bool isLoaded() const { return m_data != nullptr; }
|
bool isLoaded() const { return m_data != nullptr && BaseEntity::isLoaded(); }
|
||||||
|
|
||||||
void merge(const Version::Ptr& other);
|
void merge(const Version::Ptr& other);
|
||||||
void mergeFromList(const Version::Ptr& other);
|
void mergeFromList(const Version::Ptr& other);
|
||||||
@ -64,7 +64,7 @@ class Version : public QObject, public BaseVersion, public BaseEntity {
|
|||||||
|
|
||||||
public: // for usage by format parsers only
|
public: // for usage by format parsers only
|
||||||
void setType(const QString& type);
|
void setType(const QString& type);
|
||||||
void setTime(const qint64 time);
|
void setTime(qint64 time);
|
||||||
void setRequires(const Meta::RequireSet& reqs, const Meta::RequireSet& conflicts);
|
void setRequires(const Meta::RequireSet& reqs, const Meta::RequireSet& conflicts);
|
||||||
void setVolatile(bool volatile_);
|
void setVolatile(bool volatile_);
|
||||||
void setRecommended(bool recommended);
|
void setRecommended(bool recommended);
|
||||||
|
@ -17,8 +17,13 @@
|
|||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
|
#include "Index.h"
|
||||||
#include "JsonFormat.h"
|
#include "JsonFormat.h"
|
||||||
#include "Version.h"
|
#include "Version.h"
|
||||||
|
#include "meta/BaseEntity.h"
|
||||||
|
#include "net/Mode.h"
|
||||||
|
#include "tasks/SequentialTask.h"
|
||||||
|
|
||||||
namespace Meta {
|
namespace Meta {
|
||||||
VersionList::VersionList(const QString& uid, QObject* parent) : BaseVersionList(parent), m_uid(uid)
|
VersionList::VersionList(const QString& uid, QObject* parent) : BaseVersionList(parent), m_uid(uid)
|
||||||
@ -28,8 +33,11 @@ VersionList::VersionList(const QString& uid, QObject* parent) : BaseVersionList(
|
|||||||
|
|
||||||
Task::Ptr VersionList::getLoadTask()
|
Task::Ptr VersionList::getLoadTask()
|
||||||
{
|
{
|
||||||
load(Net::Mode::Online);
|
auto loadTask =
|
||||||
return getCurrentTask();
|
makeShared<SequentialTask>(this, tr("Load meta for %1", "This is for the task name that loads the meta index.").arg(m_uid));
|
||||||
|
loadTask->addTask(APPLICATION->metadataIndex()->loadTask(Net::Mode::Online));
|
||||||
|
loadTask->addTask(this->loadTask(Net::Mode::Online));
|
||||||
|
return loadTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VersionList::isLoaded()
|
bool VersionList::isLoaded()
|
||||||
@ -131,6 +139,8 @@ Version::Ptr VersionList::getVersion(const QString& version)
|
|||||||
if (!out) {
|
if (!out) {
|
||||||
out = std::make_shared<Version>(m_uid, version);
|
out = std::make_shared<Version>(m_uid, version);
|
||||||
m_lookup[version] = out;
|
m_lookup[version] = out;
|
||||||
|
setupAddedVersion(m_versions.size(), out);
|
||||||
|
m_versions.append(out);
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
@ -191,6 +201,9 @@ void VersionList::mergeFromIndex(const VersionList::Ptr& other)
|
|||||||
if (m_name != other->m_name) {
|
if (m_name != other->m_name) {
|
||||||
setName(other->m_name);
|
setName(other->m_name);
|
||||||
}
|
}
|
||||||
|
if (!other->m_sha256.isEmpty()) {
|
||||||
|
m_sha256 = other->m_sha256;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VersionList::merge(const VersionList::Ptr& other)
|
void VersionList::merge(const VersionList::Ptr& other)
|
||||||
@ -198,23 +211,27 @@ void VersionList::merge(const VersionList::Ptr& other)
|
|||||||
if (m_name != other->m_name) {
|
if (m_name != other->m_name) {
|
||||||
setName(other->m_name);
|
setName(other->m_name);
|
||||||
}
|
}
|
||||||
|
if (!other->m_sha256.isEmpty()) {
|
||||||
|
m_sha256 = other->m_sha256;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: do not reset the whole model. maybe?
|
// TODO: do not reset the whole model. maybe?
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
m_versions.clear();
|
|
||||||
if (other->m_versions.isEmpty()) {
|
if (other->m_versions.isEmpty()) {
|
||||||
qWarning() << "Empty list loaded ...";
|
qWarning() << "Empty list loaded ...";
|
||||||
}
|
}
|
||||||
for (const Version::Ptr& version : other->m_versions) {
|
for (auto version : other->m_versions) {
|
||||||
// we already have the version. merge the contents
|
// we already have the version. merge the contents
|
||||||
if (m_lookup.contains(version->version())) {
|
if (m_lookup.contains(version->version())) {
|
||||||
m_lookup.value(version->version())->mergeFromList(version);
|
auto existing = m_lookup.value(version->version());
|
||||||
|
existing->mergeFromList(version);
|
||||||
|
version = existing;
|
||||||
} else {
|
} else {
|
||||||
m_lookup.insert(version->uid(), version);
|
m_lookup.insert(version->version(), version);
|
||||||
|
// connect it.
|
||||||
|
setupAddedVersion(m_versions.size(), version);
|
||||||
|
m_versions.append(version);
|
||||||
}
|
}
|
||||||
// connect it.
|
|
||||||
setupAddedVersion(m_versions.size(), version);
|
|
||||||
m_versions.append(version);
|
|
||||||
m_recommended = getBetterVersion(m_recommended, version);
|
m_recommended = getBetterVersion(m_recommended, version);
|
||||||
}
|
}
|
||||||
endResetModel();
|
endResetModel();
|
||||||
@ -222,14 +239,15 @@ void VersionList::merge(const VersionList::Ptr& other)
|
|||||||
|
|
||||||
void VersionList::setupAddedVersion(const int row, const Version::Ptr& version)
|
void VersionList::setupAddedVersion(const int row, const Version::Ptr& version)
|
||||||
{
|
{
|
||||||
// FIXME: do not disconnect from everythin, disconnect only the lambdas here
|
disconnect(version.get(), &Version::requiresChanged, this, nullptr);
|
||||||
version->disconnect();
|
disconnect(version.get(), &Version::timeChanged, this, nullptr);
|
||||||
|
disconnect(version.get(), &Version::typeChanged, this, nullptr);
|
||||||
|
|
||||||
connect(version.get(), &Version::requiresChanged, this,
|
connect(version.get(), &Version::requiresChanged, this,
|
||||||
[this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << RequiresRole); });
|
[this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << RequiresRole); });
|
||||||
connect(version.get(), &Version::timeChanged, this,
|
connect(version.get(), &Version::timeChanged, this,
|
||||||
[this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << TimeRole << SortRole); });
|
[this, row]() { emit dataChanged(index(row), index(row), { TimeRole, SortRole }); });
|
||||||
connect(version.get(), &Version::typeChanged, this,
|
connect(version.get(), &Version::typeChanged, this, [this, row]() { emit dataChanged(index(row), index(row), { TypeRole }); });
|
||||||
[this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << TypeRole); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BaseVersion::Ptr VersionList::getRecommended() const
|
BaseVersion::Ptr VersionList::getRecommended() const
|
||||||
@ -237,4 +255,14 @@ BaseVersion::Ptr VersionList::getRecommended() const
|
|||||||
return m_recommended;
|
return m_recommended;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VersionList::waitToLoad()
|
||||||
|
{
|
||||||
|
if (isLoaded())
|
||||||
|
return;
|
||||||
|
QEventLoop ev;
|
||||||
|
auto task = getLoadTask();
|
||||||
|
QObject::connect(task.get(), &Task::finished, &ev, &QEventLoop::quit);
|
||||||
|
task->start();
|
||||||
|
ev.exec();
|
||||||
|
}
|
||||||
} // namespace Meta
|
} // namespace Meta
|
||||||
|
@ -30,13 +30,14 @@ class VersionList : public BaseVersionList, public BaseEntity {
|
|||||||
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
||||||
public:
|
public:
|
||||||
explicit VersionList(const QString& uid, QObject* parent = nullptr);
|
explicit VersionList(const QString& uid, QObject* parent = nullptr);
|
||||||
|
virtual ~VersionList() = default;
|
||||||
|
|
||||||
using Ptr = std::shared_ptr<VersionList>;
|
using Ptr = std::shared_ptr<VersionList>;
|
||||||
|
|
||||||
enum Roles { UidRole = Qt::UserRole + 100, TimeRole, RequiresRole, VersionPtrRole };
|
enum Roles { UidRole = Qt::UserRole + 100, TimeRole, RequiresRole, VersionPtrRole };
|
||||||
|
|
||||||
Task::Ptr getLoadTask() override;
|
|
||||||
bool isLoaded() override;
|
bool isLoaded() override;
|
||||||
|
[[nodiscard]] Task::Ptr getLoadTask() override;
|
||||||
const BaseVersion::Ptr at(int i) const override;
|
const BaseVersion::Ptr at(int i) const override;
|
||||||
int count() const override;
|
int count() const override;
|
||||||
void sortVersions() override;
|
void sortVersions() override;
|
||||||
@ -58,6 +59,9 @@ class VersionList : public BaseVersionList, public BaseEntity {
|
|||||||
|
|
||||||
QVector<Version::Ptr> versions() const { return m_versions; }
|
QVector<Version::Ptr> versions() const { return m_versions; }
|
||||||
|
|
||||||
|
// this blocks until the version list is loaded
|
||||||
|
void waitToLoad();
|
||||||
|
|
||||||
public: // for usage only by parsers
|
public: // for usage only by parsers
|
||||||
void setName(const QString& name);
|
void setName(const QString& name);
|
||||||
void setVersions(const QVector<Version::Ptr>& versions);
|
void setVersions(const QVector<Version::Ptr>& versions);
|
||||||
@ -79,7 +83,7 @@ class VersionList : public BaseVersionList, public BaseEntity {
|
|||||||
|
|
||||||
Version::Ptr m_recommended;
|
Version::Ptr m_recommended;
|
||||||
|
|
||||||
void setupAddedVersion(const int row, const Version::Ptr& version);
|
void setupAddedVersion(int row, const Version::Ptr& version);
|
||||||
};
|
};
|
||||||
} // namespace Meta
|
} // namespace Meta
|
||||||
Q_DECLARE_METATYPE(Meta::VersionList::Ptr)
|
Q_DECLARE_METATYPE(Meta::VersionList::Ptr)
|
||||||
|
@ -51,6 +51,7 @@
|
|||||||
#include "net/Download.h"
|
#include "net/Download.h"
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
#include "net/NetRequest.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
QSet<QString> collectPathsFromDir(QString dirPath)
|
QSet<QString> collectPathsFromDir(QString dirPath)
|
||||||
@ -276,14 +277,13 @@ bool reconstructAssets(QString assetsId, QString resourcesFolder)
|
|||||||
|
|
||||||
} // namespace AssetsUtils
|
} // namespace AssetsUtils
|
||||||
|
|
||||||
NetAction::Ptr AssetObject::getDownloadAction()
|
Net::NetRequest::Ptr AssetObject::getDownloadAction()
|
||||||
{
|
{
|
||||||
QFileInfo objectFile(getLocalPath());
|
QFileInfo objectFile(getLocalPath());
|
||||||
if ((!objectFile.isFile()) || (objectFile.size() != size)) {
|
if ((!objectFile.isFile()) || (objectFile.size() != size)) {
|
||||||
auto objectDL = Net::ApiDownload::makeFile(getUrl(), objectFile.filePath());
|
auto objectDL = Net::ApiDownload::makeFile(getUrl(), objectFile.filePath());
|
||||||
if (hash.size()) {
|
if (hash.size()) {
|
||||||
auto rawHash = QByteArray::fromHex(hash.toLatin1());
|
objectDL->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, hash));
|
||||||
objectDL->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash));
|
|
||||||
}
|
}
|
||||||
objectDL->setProgress(objectDL->getProgress(), size);
|
objectDL->setProgress(objectDL->getProgress(), size);
|
||||||
return objectDL;
|
return objectDL;
|
||||||
|
@ -17,14 +17,14 @@
|
|||||||
|
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include "net/NetAction.h"
|
|
||||||
#include "net/NetJob.h"
|
#include "net/NetJob.h"
|
||||||
|
#include "net/NetRequest.h"
|
||||||
|
|
||||||
struct AssetObject {
|
struct AssetObject {
|
||||||
QString getRelPath();
|
QString getRelPath();
|
||||||
QUrl getUrl();
|
QUrl getUrl();
|
||||||
QString getLocalPath();
|
QString getLocalPath();
|
||||||
NetAction::Ptr getDownloadAction();
|
Net::NetRequest::Ptr getDownloadAction();
|
||||||
|
|
||||||
QString hash;
|
QString hash;
|
||||||
qint64 size;
|
qint64 size;
|
||||||
|
@ -56,18 +56,6 @@ Component::Component(PackProfile* parent, const QString& uid)
|
|||||||
m_uid = uid;
|
m_uid = uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
Component::Component(PackProfile* parent, std::shared_ptr<Meta::Version> version)
|
|
||||||
{
|
|
||||||
assert(parent);
|
|
||||||
m_parent = parent;
|
|
||||||
|
|
||||||
m_metaVersion = version;
|
|
||||||
m_uid = version->uid();
|
|
||||||
m_version = m_cachedVersion = version->version();
|
|
||||||
m_cachedName = version->name();
|
|
||||||
m_loaded = version->isLoaded();
|
|
||||||
}
|
|
||||||
|
|
||||||
Component::Component(PackProfile* parent, const QString& uid, std::shared_ptr<VersionFile> file)
|
Component::Component(PackProfile* parent, const QString& uid, std::shared_ptr<VersionFile> file)
|
||||||
{
|
{
|
||||||
assert(parent);
|
assert(parent);
|
||||||
@ -102,9 +90,6 @@ void Component::applyTo(LaunchProfile* profile)
|
|||||||
std::shared_ptr<class VersionFile> Component::getVersionFile() const
|
std::shared_ptr<class VersionFile> Component::getVersionFile() const
|
||||||
{
|
{
|
||||||
if (m_metaVersion) {
|
if (m_metaVersion) {
|
||||||
if (!m_metaVersion->isLoaded()) {
|
|
||||||
m_metaVersion->load(Net::Mode::Online);
|
|
||||||
}
|
|
||||||
return m_metaVersion->data();
|
return m_metaVersion->data();
|
||||||
} else {
|
} else {
|
||||||
return m_file;
|
return m_file;
|
||||||
@ -131,29 +116,35 @@ int Component::getOrder()
|
|||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Component::setOrder(int order)
|
void Component::setOrder(int order)
|
||||||
{
|
{
|
||||||
m_orderOverride = true;
|
m_orderOverride = true;
|
||||||
m_order = order;
|
m_order = order;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Component::getID()
|
QString Component::getID()
|
||||||
{
|
{
|
||||||
return m_uid;
|
return m_uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Component::getName()
|
QString Component::getName()
|
||||||
{
|
{
|
||||||
if (!m_cachedName.isEmpty())
|
if (!m_cachedName.isEmpty())
|
||||||
return m_cachedName;
|
return m_cachedName;
|
||||||
return m_uid;
|
return m_uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Component::getVersion()
|
QString Component::getVersion()
|
||||||
{
|
{
|
||||||
return m_cachedVersion;
|
return m_cachedVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Component::getFilename()
|
QString Component::getFilename()
|
||||||
{
|
{
|
||||||
return m_parent->patchFilePathForUid(m_uid);
|
return m_parent->patchFilePathForUid(m_uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
QDateTime Component::getReleaseDateTime()
|
QDateTime Component::getReleaseDateTime()
|
||||||
{
|
{
|
||||||
if (m_metaVersion) {
|
if (m_metaVersion) {
|
||||||
@ -198,17 +189,14 @@ bool Component::isCustom()
|
|||||||
|
|
||||||
bool Component::isCustomizable()
|
bool Component::isCustomizable()
|
||||||
{
|
{
|
||||||
if (m_metaVersion) {
|
return m_metaVersion && getVersionFile();
|
||||||
if (getVersionFile()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Component::isRemovable()
|
bool Component::isRemovable()
|
||||||
{
|
{
|
||||||
return !m_important;
|
return !m_important;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Component::isRevertible()
|
bool Component::isRevertible()
|
||||||
{
|
{
|
||||||
if (isCustom()) {
|
if (isCustom()) {
|
||||||
@ -218,18 +206,18 @@ bool Component::isRevertible()
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Component::isMoveable()
|
bool Component::isMoveable()
|
||||||
{
|
{
|
||||||
// HACK, FIXME: this was too dumb and wouldn't follow dependency constraints anyway. For now hardcoded to 'true'.
|
// HACK, FIXME: this was too dumb and wouldn't follow dependency constraints anyway. For now hardcoded to 'true'.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Component::isVersionChangeable()
|
bool Component::isVersionChangeable()
|
||||||
{
|
{
|
||||||
auto list = getVersionList();
|
auto list = getVersionList();
|
||||||
if (list) {
|
if (list) {
|
||||||
if (!list->isLoaded()) {
|
list->waitToLoad();
|
||||||
list->load(Net::Mode::Online);
|
|
||||||
}
|
|
||||||
return list->count() != 0;
|
return list->count() != 0;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -336,7 +324,7 @@ bool Component::revert()
|
|||||||
bool result = true;
|
bool result = true;
|
||||||
// just kill the file and reload
|
// just kill the file and reload
|
||||||
if (QFile::exists(filename)) {
|
if (QFile::exists(filename)) {
|
||||||
result = QFile::remove(filename);
|
result = FS::deletePath(filename);
|
||||||
}
|
}
|
||||||
if (result) {
|
if (result) {
|
||||||
// file gone...
|
// file gone...
|
||||||
|
@ -22,7 +22,6 @@ class Component : public QObject, public ProblemProvider {
|
|||||||
Component(PackProfile* parent, const QString& uid);
|
Component(PackProfile* parent, const QString& uid);
|
||||||
|
|
||||||
// DEPRECATED: remove these constructors?
|
// DEPRECATED: remove these constructors?
|
||||||
Component(PackProfile* parent, std::shared_ptr<Meta::Version> version);
|
|
||||||
Component(PackProfile* parent, const QString& uid, std::shared_ptr<VersionFile> file);
|
Component(PackProfile* parent, const QString& uid, std::shared_ptr<VersionFile> file);
|
||||||
|
|
||||||
virtual ~Component() {}
|
virtual ~Component() {}
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
#include "net/Mode.h"
|
#include "net/Mode.h"
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This is responsible for loading the components of a component list AND resolving dependency issues between them
|
* This is responsible for loading the components of a component list AND resolving dependency issues between them
|
||||||
@ -93,9 +94,9 @@ static LoadResult loadComponent(ComponentPtr component, Task::Ptr& loadTask, Net
|
|||||||
component->m_loaded = true;
|
component->m_loaded = true;
|
||||||
result = LoadResult::LoadedLocal;
|
result = LoadResult::LoadedLocal;
|
||||||
} else {
|
} else {
|
||||||
metaVersion->load(netmode);
|
loadTask = APPLICATION->metadataIndex()->loadVersion(component->m_uid, component->m_version, netmode);
|
||||||
loadTask = metaVersion->getCurrentTask();
|
loadTask->start();
|
||||||
if (loadTask)
|
if (netmode == Net::Mode::Online)
|
||||||
result = LoadResult::RequiresRemote;
|
result = LoadResult::RequiresRemote;
|
||||||
else if (metaVersion->isLoaded())
|
else if (metaVersion->isLoaded())
|
||||||
result = LoadResult::LoadedLocal;
|
result = LoadResult::LoadedLocal;
|
||||||
@ -133,21 +134,6 @@ static LoadResult loadPackProfile(ComponentPtr component, Task::Ptr& loadTask, N
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static LoadResult loadIndex(Task::Ptr& loadTask, Net::Mode netmode)
|
|
||||||
{
|
|
||||||
// FIXME: DECIDE. do we want to run the update task anyway?
|
|
||||||
if (APPLICATION->metadataIndex()->isLoaded()) {
|
|
||||||
qDebug() << "Index is already loaded";
|
|
||||||
return LoadResult::LoadedLocal;
|
|
||||||
}
|
|
||||||
APPLICATION->metadataIndex()->load(netmode);
|
|
||||||
loadTask = APPLICATION->metadataIndex()->getCurrentTask();
|
|
||||||
if (loadTask) {
|
|
||||||
return LoadResult::RequiresRemote;
|
|
||||||
}
|
|
||||||
// FIXME: this is assuming the load succeeded... did it really?
|
|
||||||
return LoadResult::LoadedLocal;
|
|
||||||
}
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void ComponentUpdateTask::loadComponents()
|
void ComponentUpdateTask::loadComponents()
|
||||||
@ -156,23 +142,7 @@ void ComponentUpdateTask::loadComponents()
|
|||||||
size_t taskIndex = 0;
|
size_t taskIndex = 0;
|
||||||
size_t componentIndex = 0;
|
size_t componentIndex = 0;
|
||||||
d->remoteLoadSuccessful = true;
|
d->remoteLoadSuccessful = true;
|
||||||
// load the main index (it is needed to determine if components can revert)
|
|
||||||
{
|
|
||||||
// FIXME: tear out as a method? or lambda?
|
|
||||||
Task::Ptr indexLoadTask;
|
|
||||||
auto singleResult = loadIndex(indexLoadTask, d->netmode);
|
|
||||||
result = composeLoadResult(result, singleResult);
|
|
||||||
if (indexLoadTask) {
|
|
||||||
qDebug() << "Remote loading is being run for metadata index";
|
|
||||||
RemoteLoadStatus status;
|
|
||||||
status.type = RemoteLoadStatus::Type::Index;
|
|
||||||
d->remoteLoadStatusList.append(status);
|
|
||||||
connect(indexLoadTask.get(), &Task::succeeded, [=]() { remoteLoadSucceeded(taskIndex); });
|
|
||||||
connect(indexLoadTask.get(), &Task::failed, [=](const QString& error) { remoteLoadFailed(taskIndex, error); });
|
|
||||||
connect(indexLoadTask.get(), &Task::aborted, [=]() { remoteLoadFailed(taskIndex, tr("Aborted")); });
|
|
||||||
taskIndex++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// load all the components OR their lists...
|
// load all the components OR their lists...
|
||||||
for (auto component : d->m_list->d->components) {
|
for (auto component : d->m_list->d->components) {
|
||||||
Task::Ptr loadTask;
|
Task::Ptr loadTask;
|
||||||
@ -206,12 +176,13 @@ void ComponentUpdateTask::loadComponents()
|
|||||||
result = composeLoadResult(result, singleResult);
|
result = composeLoadResult(result, singleResult);
|
||||||
if (loadTask) {
|
if (loadTask) {
|
||||||
qDebug() << "Remote loading is being run for" << component->getName();
|
qDebug() << "Remote loading is being run for" << component->getName();
|
||||||
connect(loadTask.get(), &Task::succeeded, [=]() { remoteLoadSucceeded(taskIndex); });
|
connect(loadTask.get(), &Task::succeeded, this, [this, taskIndex]() { remoteLoadSucceeded(taskIndex); });
|
||||||
connect(loadTask.get(), &Task::failed, [=](const QString& error) { remoteLoadFailed(taskIndex, error); });
|
connect(loadTask.get(), &Task::failed, this, [this, taskIndex](const QString& error) { remoteLoadFailed(taskIndex, error); });
|
||||||
connect(loadTask.get(), &Task::aborted, [=]() { remoteLoadFailed(taskIndex, tr("Aborted")); });
|
connect(loadTask.get(), &Task::aborted, this, [this, taskIndex]() { remoteLoadFailed(taskIndex, tr("Aborted")); });
|
||||||
RemoteLoadStatus status;
|
RemoteLoadStatus status;
|
||||||
status.type = loadType;
|
status.type = loadType;
|
||||||
status.PackProfileIndex = componentIndex;
|
status.PackProfileIndex = componentIndex;
|
||||||
|
status.task = loadTask;
|
||||||
d->remoteLoadStatusList.append(status);
|
d->remoteLoadStatusList.append(status);
|
||||||
taskIndex++;
|
taskIndex++;
|
||||||
}
|
}
|
||||||
@ -518,7 +489,14 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
|
|||||||
|
|
||||||
void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex)
|
void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex)
|
||||||
{
|
{
|
||||||
|
if (static_cast<size_t>(d->remoteLoadStatusList.size()) < taskIndex) {
|
||||||
|
qWarning() << "Got task index outside of results" << taskIndex;
|
||||||
|
return;
|
||||||
|
}
|
||||||
auto& taskSlot = d->remoteLoadStatusList[taskIndex];
|
auto& taskSlot = d->remoteLoadStatusList[taskIndex];
|
||||||
|
disconnect(taskSlot.task.get(), &Task::succeeded, this, nullptr);
|
||||||
|
disconnect(taskSlot.task.get(), &Task::failed, this, nullptr);
|
||||||
|
disconnect(taskSlot.task.get(), &Task::aborted, this, nullptr);
|
||||||
if (taskSlot.finished) {
|
if (taskSlot.finished) {
|
||||||
qWarning() << "Got multiple results from remote load task" << taskIndex;
|
qWarning() << "Got multiple results from remote load task" << taskIndex;
|
||||||
return;
|
return;
|
||||||
@ -538,7 +516,14 @@ void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex)
|
|||||||
|
|
||||||
void ComponentUpdateTask::remoteLoadFailed(size_t taskIndex, const QString& msg)
|
void ComponentUpdateTask::remoteLoadFailed(size_t taskIndex, const QString& msg)
|
||||||
{
|
{
|
||||||
|
if (static_cast<size_t>(d->remoteLoadStatusList.size()) < taskIndex) {
|
||||||
|
qWarning() << "Got task index outside of results" << taskIndex;
|
||||||
|
return;
|
||||||
|
}
|
||||||
auto& taskSlot = d->remoteLoadStatusList[taskIndex];
|
auto& taskSlot = d->remoteLoadStatusList[taskIndex];
|
||||||
|
disconnect(taskSlot.task.get(), &Task::succeeded, this, nullptr);
|
||||||
|
disconnect(taskSlot.task.get(), &Task::failed, this, nullptr);
|
||||||
|
disconnect(taskSlot.task.get(), &Task::aborted, this, nullptr);
|
||||||
if (taskSlot.finished) {
|
if (taskSlot.finished) {
|
||||||
qWarning() << "Got multiple results from remote load task" << taskIndex;
|
qWarning() << "Got multiple results from remote load task" << taskIndex;
|
||||||
return;
|
return;
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include "net/Mode.h"
|
#include "net/Mode.h"
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
class PackProfile;
|
class PackProfile;
|
||||||
|
|
||||||
@ -13,6 +14,7 @@ struct RemoteLoadStatus {
|
|||||||
bool finished = false;
|
bool finished = false;
|
||||||
bool succeeded = false;
|
bool succeeded = false;
|
||||||
QString error;
|
QString error;
|
||||||
|
Task::Ptr task;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ComponentUpdateTaskData {
|
struct ComponentUpdateTaskData {
|
||||||
|
@ -35,12 +35,27 @@
|
|||||||
|
|
||||||
#include "Library.h"
|
#include "Library.h"
|
||||||
#include "MinecraftInstance.h"
|
#include "MinecraftInstance.h"
|
||||||
|
#include "net/NetRequest.h"
|
||||||
|
|
||||||
#include <BuildConfig.h>
|
#include <BuildConfig.h>
|
||||||
#include <FileSystem.h>
|
#include <FileSystem.h>
|
||||||
#include <net/ApiDownload.h>
|
#include <net/ApiDownload.h>
|
||||||
#include <net/ChecksumValidator.h>
|
#include <net/ChecksumValidator.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Collect applicable files for the library.
|
||||||
|
*
|
||||||
|
* Depending on whether the library is native or not, it adds paths to the
|
||||||
|
* appropriate lists for jar files, native libraries for 32-bit, and native
|
||||||
|
* libraries for 64-bit.
|
||||||
|
*
|
||||||
|
* @param runtimeContext The current runtime context.
|
||||||
|
* @param jar List to store paths for jar files.
|
||||||
|
* @param native List to store paths for native libraries.
|
||||||
|
* @param native32 List to store paths for 32-bit native libraries.
|
||||||
|
* @param native64 List to store paths for 64-bit native libraries.
|
||||||
|
* @param overridePath Optional path to override the default storage path.
|
||||||
|
*/
|
||||||
void Library::getApplicableFiles(const RuntimeContext& runtimeContext,
|
void Library::getApplicableFiles(const RuntimeContext& runtimeContext,
|
||||||
QStringList& jar,
|
QStringList& jar,
|
||||||
QStringList& native,
|
QStringList& native,
|
||||||
@ -49,7 +64,9 @@ void Library::getApplicableFiles(const RuntimeContext& runtimeContext,
|
|||||||
const QString& overridePath) const
|
const QString& overridePath) const
|
||||||
{
|
{
|
||||||
bool local = isLocal();
|
bool local = isLocal();
|
||||||
|
// Lambda function to get the absolute file path
|
||||||
auto actualPath = [&](QString relPath) {
|
auto actualPath = [&](QString relPath) {
|
||||||
|
relPath = FS::RemoveInvalidPathChars(relPath);
|
||||||
QFileInfo out(FS::PathCombine(storagePrefix(), relPath));
|
QFileInfo out(FS::PathCombine(storagePrefix(), relPath));
|
||||||
if (local && !overridePath.isEmpty()) {
|
if (local && !overridePath.isEmpty()) {
|
||||||
QString fileName = out.fileName();
|
QString fileName = out.fileName();
|
||||||
@ -57,6 +74,7 @@ void Library::getApplicableFiles(const RuntimeContext& runtimeContext,
|
|||||||
}
|
}
|
||||||
return out.absoluteFilePath();
|
return out.absoluteFilePath();
|
||||||
};
|
};
|
||||||
|
|
||||||
QString raw_storage = storageSuffix(runtimeContext);
|
QString raw_storage = storageSuffix(runtimeContext);
|
||||||
if (isNative()) {
|
if (isNative()) {
|
||||||
if (raw_storage.contains("${arch}")) {
|
if (raw_storage.contains("${arch}")) {
|
||||||
@ -74,15 +92,29 @@ void Library::getApplicableFiles(const RuntimeContext& runtimeContext,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<NetAction::Ptr> Library::getDownloads(const RuntimeContext& runtimeContext,
|
/**
|
||||||
class HttpMetaCache* cache,
|
* @brief Get download requests for the library files.
|
||||||
QStringList& failedLocalFiles,
|
*
|
||||||
const QString& overridePath) const
|
* Depending on whether the library is native or not, and the current runtime context,
|
||||||
|
* this function prepares download requests for the necessary files. It handles both local
|
||||||
|
* and remote files, checks for stale cache entries, and adds checksummed downloads.
|
||||||
|
*
|
||||||
|
* @param runtimeContext The current runtime context.
|
||||||
|
* @param cache Pointer to the HTTP meta cache.
|
||||||
|
* @param failedLocalFiles List to store paths for failed local files.
|
||||||
|
* @param overridePath Optional path to override the default storage path.
|
||||||
|
* @return QList<Net::NetRequest::Ptr> List of download requests.
|
||||||
|
*/
|
||||||
|
QList<Net::NetRequest::Ptr> Library::getDownloads(const RuntimeContext& runtimeContext,
|
||||||
|
class HttpMetaCache* cache,
|
||||||
|
QStringList& failedLocalFiles,
|
||||||
|
const QString& overridePath) const
|
||||||
{
|
{
|
||||||
QList<NetAction::Ptr> out;
|
QList<Net::NetRequest::Ptr> out;
|
||||||
bool stale = isAlwaysStale();
|
bool stale = isAlwaysStale();
|
||||||
bool local = isLocal();
|
bool local = isLocal();
|
||||||
|
|
||||||
|
// Lambda function to check if a local file exists
|
||||||
auto check_local_file = [&](QString storage) {
|
auto check_local_file = [&](QString storage) {
|
||||||
QFileInfo fileinfo(storage);
|
QFileInfo fileinfo(storage);
|
||||||
QString fileName = fileinfo.fileName();
|
QString fileName = fileinfo.fileName();
|
||||||
@ -95,6 +127,7 @@ QList<NetAction::Ptr> Library::getDownloads(const RuntimeContext& runtimeContext
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Lambda function to add a download request
|
||||||
auto add_download = [&](QString storage, QString url, QString sha1) {
|
auto add_download = [&](QString storage, QString url, QString sha1) {
|
||||||
if (local) {
|
if (local) {
|
||||||
return check_local_file(storage);
|
return check_local_file(storage);
|
||||||
@ -114,9 +147,8 @@ QList<NetAction::Ptr> Library::getDownloads(const RuntimeContext& runtimeContext
|
|||||||
options |= Net::Download::Option::MakeEternal;
|
options |= Net::Download::Option::MakeEternal;
|
||||||
|
|
||||||
if (sha1.size()) {
|
if (sha1.size()) {
|
||||||
auto rawSha1 = QByteArray::fromHex(sha1.toLatin1());
|
|
||||||
auto dl = Net::ApiDownload::makeCached(url, entry, options);
|
auto dl = Net::ApiDownload::makeCached(url, entry, options);
|
||||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
|
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, sha1));
|
||||||
qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url;
|
qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url;
|
||||||
out.append(dl);
|
out.append(dl);
|
||||||
} else {
|
} else {
|
||||||
@ -195,6 +227,15 @@ QList<NetAction::Ptr> Library::getDownloads(const RuntimeContext& runtimeContext
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if the library is active in the given runtime context.
|
||||||
|
*
|
||||||
|
* This function evaluates rules to determine if the library should be active,
|
||||||
|
* considering both general rules and native compatibility.
|
||||||
|
*
|
||||||
|
* @param runtimeContext The current runtime context.
|
||||||
|
* @return bool True if the library is active, false otherwise.
|
||||||
|
*/
|
||||||
bool Library::isActive(const RuntimeContext& runtimeContext) const
|
bool Library::isActive(const RuntimeContext& runtimeContext) const
|
||||||
{
|
{
|
||||||
bool result = true;
|
bool result = true;
|
||||||
@ -215,16 +256,35 @@ bool Library::isActive(const RuntimeContext& runtimeContext) const
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if the library is considered local.
|
||||||
|
*
|
||||||
|
* @return bool True if the library is local, false otherwise.
|
||||||
|
*/
|
||||||
bool Library::isLocal() const
|
bool Library::isLocal() const
|
||||||
{
|
{
|
||||||
return m_hint == "local";
|
return m_hint == "local";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if the library is always considered stale.
|
||||||
|
*
|
||||||
|
* @return bool True if the library is always stale, false otherwise.
|
||||||
|
*/
|
||||||
bool Library::isAlwaysStale() const
|
bool Library::isAlwaysStale() const
|
||||||
{
|
{
|
||||||
return m_hint == "always-stale";
|
return m_hint == "always-stale";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the compatible native classifier for the current runtime context.
|
||||||
|
*
|
||||||
|
* This function attempts to match the current runtime context with the appropriate
|
||||||
|
* native classifier.
|
||||||
|
*
|
||||||
|
* @param runtimeContext The current runtime context.
|
||||||
|
* @return QString The compatible native classifier, or an empty string if none is found.
|
||||||
|
*/
|
||||||
QString Library::getCompatibleNative(const RuntimeContext& runtimeContext) const
|
QString Library::getCompatibleNative(const RuntimeContext& runtimeContext) const
|
||||||
{
|
{
|
||||||
// try to match precise classifier "[os]-[arch]"
|
// try to match precise classifier "[os]-[arch]"
|
||||||
@ -239,16 +299,31 @@ QString Library::getCompatibleNative(const RuntimeContext& runtimeContext) const
|
|||||||
return entry.value();
|
return entry.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the storage prefix for the library.
|
||||||
|
*
|
||||||
|
* @param prefix The storage prefix to set.
|
||||||
|
*/
|
||||||
void Library::setStoragePrefix(QString prefix)
|
void Library::setStoragePrefix(QString prefix)
|
||||||
{
|
{
|
||||||
m_storagePrefix = prefix;
|
m_storagePrefix = prefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the default storage prefix for libraries.
|
||||||
|
*
|
||||||
|
* @return QString The default storage prefix.
|
||||||
|
*/
|
||||||
QString Library::defaultStoragePrefix()
|
QString Library::defaultStoragePrefix()
|
||||||
{
|
{
|
||||||
return "libraries/";
|
return "libraries/";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the current storage prefix for the library.
|
||||||
|
*
|
||||||
|
* @return QString The current storage prefix.
|
||||||
|
*/
|
||||||
QString Library::storagePrefix() const
|
QString Library::storagePrefix() const
|
||||||
{
|
{
|
||||||
if (m_storagePrefix.isEmpty()) {
|
if (m_storagePrefix.isEmpty()) {
|
||||||
@ -257,6 +332,15 @@ QString Library::storagePrefix() const
|
|||||||
return m_storagePrefix;
|
return m_storagePrefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the filename for the library in the current runtime context.
|
||||||
|
*
|
||||||
|
* This function determines the appropriate filename for the library, taking into
|
||||||
|
* account native classifiers if applicable.
|
||||||
|
*
|
||||||
|
* @param runtimeContext The current runtime context.
|
||||||
|
* @return QString The filename of the library.
|
||||||
|
*/
|
||||||
QString Library::filename(const RuntimeContext& runtimeContext) const
|
QString Library::filename(const RuntimeContext& runtimeContext) const
|
||||||
{
|
{
|
||||||
if (!m_filename.isEmpty()) {
|
if (!m_filename.isEmpty()) {
|
||||||
@ -278,6 +362,15 @@ QString Library::filename(const RuntimeContext& runtimeContext) const
|
|||||||
return nativeSpec.getFileName();
|
return nativeSpec.getFileName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the display name for the library in the current runtime context.
|
||||||
|
*
|
||||||
|
* This function returns the display name for the library, defaulting to the filename
|
||||||
|
* if no display name is set.
|
||||||
|
*
|
||||||
|
* @param runtimeContext The current runtime context.
|
||||||
|
* @return QString The display name of the library.
|
||||||
|
*/
|
||||||
QString Library::displayName(const RuntimeContext& runtimeContext) const
|
QString Library::displayName(const RuntimeContext& runtimeContext) const
|
||||||
{
|
{
|
||||||
if (!m_displayname.isEmpty())
|
if (!m_displayname.isEmpty())
|
||||||
@ -285,6 +378,15 @@ QString Library::displayName(const RuntimeContext& runtimeContext) const
|
|||||||
return filename(runtimeContext);
|
return filename(runtimeContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the storage suffix for the library in the current runtime context.
|
||||||
|
*
|
||||||
|
* This function determines the appropriate storage suffix for the library, taking into
|
||||||
|
* account native classifiers if applicable.
|
||||||
|
*
|
||||||
|
* @param runtimeContext The current runtime context.
|
||||||
|
* @return QString The storage suffix of the library.
|
||||||
|
*/
|
||||||
QString Library::storageSuffix(const RuntimeContext& runtimeContext) const
|
QString Library::storageSuffix(const RuntimeContext& runtimeContext) const
|
||||||
{
|
{
|
||||||
// non-native? use only the gradle specifier
|
// non-native? use only the gradle specifier
|
||||||
|
@ -34,7 +34,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <net/NetAction.h>
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
@ -48,6 +47,7 @@
|
|||||||
#include "MojangDownloadInfo.h"
|
#include "MojangDownloadInfo.h"
|
||||||
#include "Rule.h"
|
#include "Rule.h"
|
||||||
#include "RuntimeContext.h"
|
#include "RuntimeContext.h"
|
||||||
|
#include "net/NetRequest.h"
|
||||||
|
|
||||||
class Library;
|
class Library;
|
||||||
class MinecraftInstance;
|
class MinecraftInstance;
|
||||||
@ -144,10 +144,10 @@ class Library {
|
|||||||
bool isForge() const;
|
bool isForge() const;
|
||||||
|
|
||||||
// Get a list of downloads for this library
|
// Get a list of downloads for this library
|
||||||
QList<NetAction::Ptr> getDownloads(const RuntimeContext& runtimeContext,
|
QList<Net::NetRequest::Ptr> getDownloads(const RuntimeContext& runtimeContext,
|
||||||
class HttpMetaCache* cache,
|
class HttpMetaCache* cache,
|
||||||
QStringList& failedLocalFiles,
|
QStringList& failedLocalFiles,
|
||||||
const QString& overridePath) const;
|
const QString& overridePath) const;
|
||||||
|
|
||||||
QString getCompatibleNative(const RuntimeContext& runtimeContext) const;
|
QString getCompatibleNative(const RuntimeContext& runtimeContext) const;
|
||||||
|
|
||||||
|
@ -173,11 +173,12 @@ void MinecraftInstance::loadSpecificSettings()
|
|||||||
m_settings->registerOverride(global_settings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride);
|
m_settings->registerOverride(global_settings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride);
|
||||||
m_settings->registerOverride(global_settings->getSetting("CustomGLFWPath"), nativeLibraryWorkaroundsOverride);
|
m_settings->registerOverride(global_settings->getSetting("CustomGLFWPath"), nativeLibraryWorkaroundsOverride);
|
||||||
|
|
||||||
// Peformance related options
|
// Performance related options
|
||||||
auto performanceOverride = m_settings->registerSetting("OverridePerformance", false);
|
auto performanceOverride = m_settings->registerSetting("OverridePerformance", false);
|
||||||
m_settings->registerOverride(global_settings->getSetting("EnableFeralGamemode"), performanceOverride);
|
m_settings->registerOverride(global_settings->getSetting("EnableFeralGamemode"), performanceOverride);
|
||||||
m_settings->registerOverride(global_settings->getSetting("EnableMangoHud"), performanceOverride);
|
m_settings->registerOverride(global_settings->getSetting("EnableMangoHud"), performanceOverride);
|
||||||
m_settings->registerOverride(global_settings->getSetting("UseDiscreteGpu"), performanceOverride);
|
m_settings->registerOverride(global_settings->getSetting("UseDiscreteGpu"), performanceOverride);
|
||||||
|
m_settings->registerOverride(global_settings->getSetting("UseZink"), performanceOverride);
|
||||||
|
|
||||||
// Miscellaneous
|
// Miscellaneous
|
||||||
auto miscellaneousOverride = m_settings->registerSetting("OverrideMiscellaneous", false);
|
auto miscellaneousOverride = m_settings->registerSetting("OverrideMiscellaneous", false);
|
||||||
@ -195,8 +196,9 @@ void MinecraftInstance::loadSpecificSettings()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Join server on launch, this does not have a global override
|
// Join server on launch, this does not have a global override
|
||||||
m_settings->registerSetting("JoinServerOnLaunch", false);
|
m_settings->registerSetting({ "JoinServerOnLaunch", "JoinOnLaunch" }, false);
|
||||||
m_settings->registerSetting("JoinServerOnLaunchAddress", "");
|
m_settings->registerSetting("JoinServerOnLaunchAddress", "");
|
||||||
|
m_settings->registerSetting("JoinWorldOnLaunch", "");
|
||||||
|
|
||||||
// Use account for instance, this does not have a global override
|
// Use account for instance, this does not have a global override
|
||||||
m_settings->registerSetting("UseAccountForInstance", false);
|
m_settings->registerSetting("UseAccountForInstance", false);
|
||||||
@ -292,10 +294,10 @@ QString MinecraftInstance::gameRoot() const
|
|||||||
QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft"));
|
QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft"));
|
||||||
QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft"));
|
QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft"));
|
||||||
|
|
||||||
if (mcDir.exists() && !dotMCDir.exists())
|
if (dotMCDir.exists() && !mcDir.exists())
|
||||||
return mcDir.filePath();
|
|
||||||
else
|
|
||||||
return dotMCDir.filePath();
|
return dotMCDir.filePath();
|
||||||
|
else
|
||||||
|
return mcDir.filePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MinecraftInstance::binRoot() const
|
QString MinecraftInstance::binRoot() const
|
||||||
@ -522,8 +524,7 @@ QStringList MinecraftInstance::javaArguments()
|
|||||||
|
|
||||||
if (javaVersion.isModular() && shouldApplyOnlineFixes())
|
if (javaVersion.isModular() && shouldApplyOnlineFixes())
|
||||||
// allow reflective access to java.net - required by the skin fix
|
// allow reflective access to java.net - required by the skin fix
|
||||||
args << "--add-opens"
|
args << "--add-opens" << "java.base/java.net=ALL-UNNAMED";
|
||||||
<< "java.base/java.net=ALL-UNNAMED";
|
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
@ -594,22 +595,23 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
|
|||||||
QStringList preloadList;
|
QStringList preloadList;
|
||||||
if (auto value = env.value("LD_PRELOAD"); !value.isEmpty())
|
if (auto value = env.value("LD_PRELOAD"); !value.isEmpty())
|
||||||
preloadList = value.split(QLatin1String(":"));
|
preloadList = value.split(QLatin1String(":"));
|
||||||
QStringList libPaths;
|
|
||||||
if (auto value = env.value("LD_LIBRARY_PATH"); !value.isEmpty())
|
|
||||||
libPaths = value.split(QLatin1String(":"));
|
|
||||||
|
|
||||||
auto mangoHudLibString = MangoHud::getLibraryString();
|
auto mangoHudLibString = MangoHud::getLibraryString();
|
||||||
if (!mangoHudLibString.isEmpty()) {
|
if (!mangoHudLibString.isEmpty()) {
|
||||||
QFileInfo mangoHudLib(mangoHudLibString);
|
QFileInfo mangoHudLib(mangoHudLibString);
|
||||||
|
QString libPath = mangoHudLib.absolutePath();
|
||||||
|
auto appendLib = [libPath, &preloadList](QString fileName) {
|
||||||
|
if (QFileInfo(FS::PathCombine(libPath, fileName)).exists())
|
||||||
|
preloadList << FS::PathCombine(libPath, fileName);
|
||||||
|
};
|
||||||
|
|
||||||
// dlsym variant is only needed for OpenGL and not included in the vulkan layer
|
// dlsym variant is only needed for OpenGL and not included in the vulkan layer
|
||||||
preloadList << "libMangoHud_dlsym.so"
|
appendLib("libMangoHud_dlsym.so");
|
||||||
<< "libMangoHud_opengl.so" << mangoHudLib.fileName();
|
appendLib("libMangoHud_opengl.so");
|
||||||
libPaths << mangoHudLib.absolutePath();
|
preloadList << mangoHudLibString;
|
||||||
}
|
}
|
||||||
|
|
||||||
env.insert("LD_PRELOAD", preloadList.join(QLatin1String(":")));
|
env.insert("LD_PRELOAD", preloadList.join(QLatin1String(":")));
|
||||||
env.insert("LD_LIBRARY_PATH", libPaths.join(QLatin1String(":")));
|
|
||||||
env.insert("MANGOHUD", "1");
|
env.insert("MANGOHUD", "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -621,6 +623,13 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
|
|||||||
env.insert("__VK_LAYER_NV_optimus", "NVIDIA_only");
|
env.insert("__VK_LAYER_NV_optimus", "NVIDIA_only");
|
||||||
env.insert("__GLX_VENDOR_LIBRARY_NAME", "nvidia");
|
env.insert("__GLX_VENDOR_LIBRARY_NAME", "nvidia");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (settings()->get("UseZink").toBool()) {
|
||||||
|
// taken from https://wiki.archlinux.org/title/OpenGL#OpenGL_over_Vulkan_(Zink)
|
||||||
|
env.insert("__GLX_VENDOR_LIBRARY_NAME", "mesa");
|
||||||
|
env.insert("MESA_LOADER_DRIVER_OVERRIDE", "zink");
|
||||||
|
env.insert("GALLIUM_DRIVER", "zink");
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
return env;
|
return env;
|
||||||
}
|
}
|
||||||
@ -647,7 +656,7 @@ static QString replaceTokensIn(QString text, QMap<QString, QString> with)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) const
|
QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) const
|
||||||
{
|
{
|
||||||
auto profile = m_components->getProfile();
|
auto profile = m_components->getProfile();
|
||||||
QString args_pattern = profile->getMinecraftArguments();
|
QString args_pattern = profile->getMinecraftArguments();
|
||||||
@ -655,9 +664,17 @@ QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session, Mine
|
|||||||
args_pattern += " --tweakClass " + tweaker;
|
args_pattern += " --tweakClass " + tweaker;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serverToJoin && !serverToJoin->address.isEmpty()) {
|
if (targetToJoin) {
|
||||||
args_pattern += " --server " + serverToJoin->address;
|
if (!targetToJoin->address.isEmpty()) {
|
||||||
args_pattern += " --port " + QString::number(serverToJoin->port);
|
if (profile->hasTrait("feature:is_quick_play_multiplayer")) {
|
||||||
|
args_pattern += " --quickPlayMultiplayer " + targetToJoin->address + ':' + QString::number(targetToJoin->port);
|
||||||
|
} else {
|
||||||
|
args_pattern += " --server " + targetToJoin->address;
|
||||||
|
args_pattern += " --port " + QString::number(targetToJoin->port);
|
||||||
|
}
|
||||||
|
} else if (!targetToJoin->world.isEmpty() && profile->hasTrait("feature:is_quick_play_singleplayer")) {
|
||||||
|
args_pattern += " --quickPlaySingleplayer " + targetToJoin->world;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QMap<QString, QString> token_mapping;
|
QMap<QString, QString> token_mapping;
|
||||||
@ -700,7 +717,7 @@ QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session, Mine
|
|||||||
return parts;
|
return parts;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin)
|
QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin)
|
||||||
{
|
{
|
||||||
QString launchScript;
|
QString launchScript;
|
||||||
|
|
||||||
@ -719,9 +736,13 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
|
|||||||
launchScript += "appletClass " + appletClass + "\n";
|
launchScript += "appletClass " + appletClass + "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serverToJoin && !serverToJoin->address.isEmpty()) {
|
if (targetToJoin) {
|
||||||
launchScript += "serverAddress " + serverToJoin->address + "\n";
|
if (!targetToJoin->address.isEmpty()) {
|
||||||
launchScript += "serverPort " + QString::number(serverToJoin->port) + "\n";
|
launchScript += "serverAddress " + targetToJoin->address + "\n";
|
||||||
|
launchScript += "serverPort " + QString::number(targetToJoin->port) + "\n";
|
||||||
|
} else if (!targetToJoin->world.isEmpty()) {
|
||||||
|
launchScript += "worldName " + targetToJoin->world + "\n";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// generic minecraft params
|
// generic minecraft params
|
||||||
@ -774,16 +795,15 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
|
|||||||
return launchScript;
|
return launchScript;
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin)
|
QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin)
|
||||||
{
|
{
|
||||||
QStringList out;
|
QStringList out;
|
||||||
out << "Main Class:"
|
out << "Main Class:" << " " + getMainClass() << "";
|
||||||
<< " " + getMainClass() << "";
|
out << "Native path:" << " " + getNativePath() << "";
|
||||||
out << "Native path:"
|
|
||||||
<< " " + getNativePath() << "";
|
|
||||||
|
|
||||||
auto profile = m_components->getProfile();
|
auto profile = m_components->getProfile();
|
||||||
|
|
||||||
|
// traits
|
||||||
auto alltraits = traits();
|
auto alltraits = traits();
|
||||||
if (alltraits.size()) {
|
if (alltraits.size()) {
|
||||||
out << "Traits:";
|
out << "Traits:";
|
||||||
@ -793,6 +813,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
|
|||||||
out << "";
|
out << "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// native libraries
|
||||||
auto settings = this->settings();
|
auto settings = this->settings();
|
||||||
bool nativeOpenAL = settings->get("UseNativeOpenAL").toBool();
|
bool nativeOpenAL = settings->get("UseNativeOpenAL").toBool();
|
||||||
bool nativeGLFW = settings->get("UseNativeGLFW").toBool();
|
bool nativeGLFW = settings->get("UseNativeGLFW").toBool();
|
||||||
@ -828,6 +849,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
|
|||||||
out << "";
|
out << "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mods and core mods
|
||||||
auto printModList = [&](const QString& label, ModFolderModel& model) {
|
auto printModList = [&](const QString& label, ModFolderModel& model) {
|
||||||
if (model.size()) {
|
if (model.size()) {
|
||||||
out << QString("%1:").arg(label);
|
out << QString("%1:").arg(label);
|
||||||
@ -856,6 +878,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
|
|||||||
printModList("Mods", *(loaderModList().get()));
|
printModList("Mods", *(loaderModList().get()));
|
||||||
printModList("Core Mods", *(coreModList().get()));
|
printModList("Core Mods", *(coreModList().get()));
|
||||||
|
|
||||||
|
// jar mods
|
||||||
auto& jarMods = profile->getJarMods();
|
auto& jarMods = profile->getJarMods();
|
||||||
if (jarMods.size()) {
|
if (jarMods.size()) {
|
||||||
out << "Jar Mods:";
|
out << "Jar Mods:";
|
||||||
@ -871,11 +894,13 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
|
|||||||
out << "";
|
out << "";
|
||||||
}
|
}
|
||||||
|
|
||||||
auto params = processMinecraftArgs(nullptr, serverToJoin);
|
// minecraft arguments
|
||||||
|
auto params = processMinecraftArgs(nullptr, targetToJoin);
|
||||||
out << "Params:";
|
out << "Params:";
|
||||||
out << " " + params.join(' ');
|
out << " " + params.join(' ');
|
||||||
out << "";
|
out << "";
|
||||||
|
|
||||||
|
// window size
|
||||||
QString windowParams;
|
QString windowParams;
|
||||||
if (settings->get("LaunchMaximized").toBool()) {
|
if (settings->get("LaunchMaximized").toBool()) {
|
||||||
out << "Window size: max (if available)";
|
out << "Window size: max (if available)";
|
||||||
@ -987,7 +1012,7 @@ QString MinecraftInstance::getStatusbarDescription()
|
|||||||
QString description;
|
QString description;
|
||||||
description.append(tr("Minecraft %1").arg(mcVersion));
|
description.append(tr("Minecraft %1").arg(mcVersion));
|
||||||
if (m_settings->get("ShowGameTime").toBool()) {
|
if (m_settings->get("ShowGameTime").toBool()) {
|
||||||
if (lastTimePlayed() > 0) {
|
if (lastTimePlayed() > 0 && lastLaunch() > 0) {
|
||||||
QDateTime lastLaunchTime = QDateTime::fromMSecsSinceEpoch(lastLaunch());
|
QDateTime lastLaunchTime = QDateTime::fromMSecsSinceEpoch(lastLaunch());
|
||||||
description.append(
|
description.append(
|
||||||
tr(", last played on %1 for %2")
|
tr(", last played on %1 for %2")
|
||||||
@ -1021,7 +1046,7 @@ Task::Ptr MinecraftInstance::createUpdateTask(Net::Mode mode)
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin)
|
shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin)
|
||||||
{
|
{
|
||||||
updateRuntimeContext();
|
updateRuntimeContext();
|
||||||
// FIXME: get rid of shared_from_this ...
|
// FIXME: get rid of shared_from_this ...
|
||||||
@ -1045,16 +1070,23 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
|
|||||||
process->appendStep(makeShared<CreateGameFolders>(pptr));
|
process->appendStep(makeShared<CreateGameFolders>(pptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!serverToJoin && settings()->get("JoinServerOnLaunch").toBool()) {
|
if (!targetToJoin && settings()->get("JoinOnLaunch").toBool()) {
|
||||||
QString fullAddress = settings()->get("JoinServerOnLaunchAddress").toString();
|
QString fullAddress = settings()->get("JoinServerOnLaunchAddress").toString();
|
||||||
serverToJoin.reset(new MinecraftServerTarget(MinecraftServerTarget::parse(fullAddress)));
|
if (!fullAddress.isEmpty()) {
|
||||||
|
targetToJoin.reset(new MinecraftTarget(MinecraftTarget::parse(fullAddress, false)));
|
||||||
|
} else {
|
||||||
|
QString world = settings()->get("JoinWorldOnLaunch").toString();
|
||||||
|
if (!world.isEmpty()) {
|
||||||
|
targetToJoin.reset(new MinecraftTarget(MinecraftTarget::parse(world, true)));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serverToJoin && serverToJoin->port == 25565) {
|
if (targetToJoin && targetToJoin->port == 25565) {
|
||||||
// Resolve server address to join on launch
|
// Resolve server address to join on launch
|
||||||
auto step = makeShared<LookupServerAddress>(pptr);
|
auto step = makeShared<LookupServerAddress>(pptr);
|
||||||
step->setLookupAddress(serverToJoin->address);
|
step->setLookupAddress(targetToJoin->address);
|
||||||
step->setOutputAddressPtr(serverToJoin);
|
step->setOutputAddressPtr(targetToJoin);
|
||||||
process->appendStep(step);
|
process->appendStep(step);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1087,7 +1119,7 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
|
|||||||
|
|
||||||
// print some instance info here...
|
// print some instance info here...
|
||||||
{
|
{
|
||||||
process->appendStep(makeShared<PrintInstanceInfo>(pptr, session, serverToJoin));
|
process->appendStep(makeShared<PrintInstanceInfo>(pptr, session, targetToJoin));
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract native jars if needed
|
// extract native jars if needed
|
||||||
@ -1110,7 +1142,7 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
|
|||||||
auto step = makeShared<LauncherPartLaunch>(pptr);
|
auto step = makeShared<LauncherPartLaunch>(pptr);
|
||||||
step->setWorkingDirectory(gameRoot());
|
step->setWorkingDirectory(gameRoot());
|
||||||
step->setAuthSession(session);
|
step->setAuthSession(session);
|
||||||
step->setServerToJoin(serverToJoin);
|
step->setTargetToJoin(targetToJoin);
|
||||||
process->appendStep(step);
|
process->appendStep(step);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include "BaseInstance.h"
|
#include "BaseInstance.h"
|
||||||
#include "minecraft/launch/MinecraftServerTarget.h"
|
#include "minecraft/launch/MinecraftTarget.h"
|
||||||
#include "minecraft/mod/Mod.h"
|
#include "minecraft/mod/Mod.h"
|
||||||
|
|
||||||
class ModFolderModel;
|
class ModFolderModel;
|
||||||
@ -56,7 +56,7 @@ class MinecraftInstance : public BaseInstance {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir);
|
MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir);
|
||||||
virtual ~MinecraftInstance(){};
|
virtual ~MinecraftInstance() = default;
|
||||||
virtual void saveNow() override;
|
virtual void saveNow() override;
|
||||||
|
|
||||||
void loadSpecificSettings() override;
|
void loadSpecificSettings() override;
|
||||||
@ -121,11 +121,11 @@ class MinecraftInstance : public BaseInstance {
|
|||||||
|
|
||||||
////// Launch stuff //////
|
////// Launch stuff //////
|
||||||
Task::Ptr createUpdateTask(Net::Mode mode) override;
|
Task::Ptr createUpdateTask(Net::Mode mode) override;
|
||||||
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) override;
|
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account, MinecraftTarget::Ptr targetToJoin) override;
|
||||||
QStringList extraArguments() override;
|
QStringList extraArguments() override;
|
||||||
QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override;
|
QStringList verboseDescription(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) override;
|
||||||
QList<Mod*> getJarMods() const;
|
QList<Mod*> getJarMods() const;
|
||||||
QString createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin);
|
QString createLaunchScript(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin);
|
||||||
/// get arguments passed to java
|
/// get arguments passed to java
|
||||||
QStringList javaArguments();
|
QStringList javaArguments();
|
||||||
QString getLauncher();
|
QString getLauncher();
|
||||||
@ -155,7 +155,7 @@ class MinecraftInstance : public BaseInstance {
|
|||||||
virtual QString getMainClass() const;
|
virtual QString getMainClass() const;
|
||||||
|
|
||||||
// FIXME: remove
|
// FIXME: remove
|
||||||
virtual QStringList processMinecraftArgs(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) const;
|
virtual QStringList processMinecraftArgs(AuthSessionPtr account, MinecraftTarget::Ptr targetToJoin) const;
|
||||||
|
|
||||||
virtual JavaVersion getJavaVersion();
|
virtual JavaVersion getJavaVersion();
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ class MinecraftLoadAndCheck : public Task {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit MinecraftLoadAndCheck(MinecraftInstance* inst, QObject* parent = 0);
|
explicit MinecraftLoadAndCheck(MinecraftInstance* inst, QObject* parent = 0);
|
||||||
virtual ~MinecraftLoadAndCheck(){};
|
virtual ~MinecraftLoadAndCheck() {};
|
||||||
void executeTask() override;
|
void executeTask() override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
@ -16,32 +16,22 @@
|
|||||||
#include "MinecraftUpdate.h"
|
#include "MinecraftUpdate.h"
|
||||||
#include "MinecraftInstance.h"
|
#include "MinecraftInstance.h"
|
||||||
|
|
||||||
#include <QDataStream>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QTextStream>
|
|
||||||
|
|
||||||
#include <FileSystem.h>
|
|
||||||
#include "BaseInstance.h"
|
|
||||||
#include "minecraft/Library.h"
|
|
||||||
#include "minecraft/PackProfile.h"
|
#include "minecraft/PackProfile.h"
|
||||||
|
|
||||||
|
#include "tasks/SequentialTask.h"
|
||||||
#include "update/AssetUpdateTask.h"
|
#include "update/AssetUpdateTask.h"
|
||||||
#include "update/FMLLibrariesTask.h"
|
#include "update/FMLLibrariesTask.h"
|
||||||
#include "update/FoldersTask.h"
|
#include "update/FoldersTask.h"
|
||||||
#include "update/LibrariesTask.h"
|
#include "update/LibrariesTask.h"
|
||||||
|
|
||||||
#include <meta/Index.h>
|
MinecraftUpdate::MinecraftUpdate(MinecraftInstance* inst, QObject* parent) : SequentialTask(parent), m_inst(inst) {}
|
||||||
#include <meta/Version.h>
|
|
||||||
|
|
||||||
MinecraftUpdate::MinecraftUpdate(MinecraftInstance* inst, QObject* parent) : Task(parent), m_inst(inst) {}
|
|
||||||
|
|
||||||
void MinecraftUpdate::executeTask()
|
void MinecraftUpdate::executeTask()
|
||||||
{
|
{
|
||||||
m_tasks.clear();
|
m_queue.clear();
|
||||||
// create folders
|
// create folders
|
||||||
{
|
{
|
||||||
m_tasks.append(makeShared<FoldersTask>(m_inst));
|
addTask(makeShared<FoldersTask>(m_inst));
|
||||||
}
|
}
|
||||||
|
|
||||||
// add metadata update task if necessary
|
// add metadata update task if necessary
|
||||||
@ -50,121 +40,24 @@ void MinecraftUpdate::executeTask()
|
|||||||
components->reload(Net::Mode::Online);
|
components->reload(Net::Mode::Online);
|
||||||
auto task = components->getCurrentTask();
|
auto task = components->getCurrentTask();
|
||||||
if (task) {
|
if (task) {
|
||||||
m_tasks.append(task);
|
addTask(task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// libraries download
|
// libraries download
|
||||||
{
|
{
|
||||||
m_tasks.append(makeShared<LibrariesTask>(m_inst));
|
addTask(makeShared<LibrariesTask>(m_inst));
|
||||||
}
|
}
|
||||||
|
|
||||||
// FML libraries download and copy into the instance
|
// FML libraries download and copy into the instance
|
||||||
{
|
{
|
||||||
m_tasks.append(makeShared<FMLLibrariesTask>(m_inst));
|
addTask(makeShared<FMLLibrariesTask>(m_inst));
|
||||||
}
|
}
|
||||||
|
|
||||||
// assets update
|
// assets update
|
||||||
{
|
{
|
||||||
m_tasks.append(makeShared<AssetUpdateTask>(m_inst));
|
addTask(makeShared<AssetUpdateTask>(m_inst));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_preFailure.isEmpty()) {
|
SequentialTask::executeTask();
|
||||||
emitFailed(m_preFailure);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MinecraftUpdate::next()
|
|
||||||
{
|
|
||||||
if (m_abort) {
|
|
||||||
emitFailed(tr("Aborted by user."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (m_failed_out_of_order) {
|
|
||||||
emitFailed(m_fail_reason);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_currentTask++;
|
|
||||||
if (m_currentTask > 0) {
|
|
||||||
auto task = m_tasks[m_currentTask - 1];
|
|
||||||
disconnect(task.get(), &Task::succeeded, this, &MinecraftUpdate::subtaskSucceeded);
|
|
||||||
disconnect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed);
|
|
||||||
disconnect(task.get(), &Task::aborted, this, &Task::abort);
|
|
||||||
disconnect(task.get(), &Task::progress, this, &MinecraftUpdate::progress);
|
|
||||||
disconnect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propagateStepProgress);
|
|
||||||
disconnect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus);
|
|
||||||
disconnect(task.get(), &Task::details, this, &MinecraftUpdate::setDetails);
|
|
||||||
}
|
|
||||||
if (m_currentTask == m_tasks.size()) {
|
|
||||||
emitSucceeded();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto task = m_tasks[m_currentTask];
|
|
||||||
// if the task is already finished by the time we look at it, skip it
|
|
||||||
if (task->isFinished()) {
|
|
||||||
qCritical() << "MinecraftUpdate: Skipping finished subtask" << m_currentTask << ":" << task.get();
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
connect(task.get(), &Task::succeeded, this, &MinecraftUpdate::subtaskSucceeded);
|
|
||||||
connect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed);
|
|
||||||
connect(task.get(), &Task::aborted, this, &Task::abort);
|
|
||||||
connect(task.get(), &Task::progress, this, &MinecraftUpdate::progress);
|
|
||||||
connect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propagateStepProgress);
|
|
||||||
connect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus);
|
|
||||||
connect(task.get(), &Task::details, this, &MinecraftUpdate::setDetails);
|
|
||||||
// if the task is already running, do not start it again
|
|
||||||
if (!task->isRunning()) {
|
|
||||||
task->start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MinecraftUpdate::subtaskSucceeded()
|
|
||||||
{
|
|
||||||
if (isFinished()) {
|
|
||||||
qCritical() << "MinecraftUpdate: Subtask" << sender() << "succeeded, but work was already done!";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto senderTask = QObject::sender();
|
|
||||||
auto currentTask = m_tasks[m_currentTask].get();
|
|
||||||
if (senderTask != currentTask) {
|
|
||||||
qDebug() << "MinecraftUpdate: Subtask" << sender() << "succeeded out of order.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MinecraftUpdate::subtaskFailed(QString error)
|
|
||||||
{
|
|
||||||
if (isFinished()) {
|
|
||||||
qCritical() << "MinecraftUpdate: Subtask" << sender() << "failed, but work was already done!";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto senderTask = QObject::sender();
|
|
||||||
auto currentTask = m_tasks[m_currentTask].get();
|
|
||||||
if (senderTask != currentTask) {
|
|
||||||
qDebug() << "MinecraftUpdate: Subtask" << sender() << "failed out of order.";
|
|
||||||
m_failed_out_of_order = true;
|
|
||||||
m_fail_reason = error;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
emitFailed(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MinecraftUpdate::abort()
|
|
||||||
{
|
|
||||||
if (!m_abort) {
|
|
||||||
m_abort = true;
|
|
||||||
auto task = m_tasks[m_currentTask];
|
|
||||||
if (task->canAbort()) {
|
|
||||||
return task->abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MinecraftUpdate::canAbort() const
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
@ -15,43 +15,19 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QList>
|
#include "tasks/SequentialTask.h"
|
||||||
#include <QObject>
|
|
||||||
#include <QUrl>
|
|
||||||
|
|
||||||
#include <quazip/quazip.h>
|
|
||||||
#include "minecraft/VersionFilterData.h"
|
|
||||||
#include "net/NetJob.h"
|
|
||||||
#include "tasks/Task.h"
|
|
||||||
|
|
||||||
class MinecraftVersion;
|
|
||||||
class MinecraftInstance;
|
class MinecraftInstance;
|
||||||
|
|
||||||
// FIXME: This looks very similar to a SequentialTask. Maybe we can reduce code duplications? :^)
|
// this needs to be a task because components->reload does stuff that may block
|
||||||
|
class MinecraftUpdate : public SequentialTask {
|
||||||
class MinecraftUpdate : public Task {
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit MinecraftUpdate(MinecraftInstance* inst, QObject* parent = 0);
|
explicit MinecraftUpdate(MinecraftInstance* inst, QObject* parent = 0);
|
||||||
virtual ~MinecraftUpdate(){};
|
virtual ~MinecraftUpdate() = default;
|
||||||
|
|
||||||
void executeTask() override;
|
void executeTask() override;
|
||||||
bool canAbort() const override;
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
bool abort() override;
|
|
||||||
void subtaskSucceeded();
|
|
||||||
void subtaskFailed(QString error);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void next();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MinecraftInstance* m_inst = nullptr;
|
MinecraftInstance* m_inst = nullptr;
|
||||||
QList<Task::Ptr> m_tasks;
|
|
||||||
QString m_preFailure;
|
|
||||||
int m_currentTask = -1;
|
|
||||||
bool m_abort = false;
|
|
||||||
bool m_failed_out_of_order = false;
|
|
||||||
QString m_fail_reason;
|
|
||||||
};
|
};
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user