Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into instance_copy_progress
This commit is contained in:
commit
b6d87fc090
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.4.1
|
uses: korthout/backport-action@v2.5.0
|
||||||
with:
|
with:
|
||||||
# Config README: https://github.com/korthout/backport-action#backport-action
|
# Config README: https://github.com/korthout/backport-action#backport-action
|
||||||
pull_description: |-
|
pull_description: |-
|
||||||
|
102
.github/workflows/build.yml
vendored
102
.github/workflows/build.yml
vendored
@ -54,6 +54,10 @@ 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: ""
|
||||||
|
|
||||||
- os: ubuntu-20.04
|
- os: ubuntu-20.04
|
||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
@ -61,7 +65,6 @@ jobs:
|
|||||||
qt_arch: ""
|
qt_arch: ""
|
||||||
qt_version: "6.2.4"
|
qt_version: "6.2.4"
|
||||||
qt_modules: "qt5compat qtimageformats"
|
qt_modules: "qt5compat qtimageformats"
|
||||||
qt_tools: ""
|
|
||||||
|
|
||||||
- os: windows-2022
|
- os: windows-2022
|
||||||
name: "Windows-MinGW-w64"
|
name: "Windows-MinGW-w64"
|
||||||
@ -76,9 +79,8 @@ jobs:
|
|||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: windows
|
qt_host: windows
|
||||||
qt_arch: ''
|
qt_arch: ''
|
||||||
qt_version: '6.6.1'
|
qt_version: '6.7.0'
|
||||||
qt_modules: 'qt5compat qtimageformats'
|
qt_modules: 'qt5compat qtimageformats'
|
||||||
qt_tools: ''
|
|
||||||
|
|
||||||
- os: windows-2022
|
- os: windows-2022
|
||||||
name: "Windows-MSVC-arm64"
|
name: "Windows-MSVC-arm64"
|
||||||
@ -88,9 +90,8 @@ jobs:
|
|||||||
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.1'
|
qt_version: '6.7.0'
|
||||||
qt_modules: 'qt5compat qtimageformats'
|
qt_modules: 'qt5compat qtimageformats'
|
||||||
qt_tools: ''
|
|
||||||
|
|
||||||
- os: macos-12
|
- os: macos-12
|
||||||
name: macOS
|
name: macOS
|
||||||
@ -98,9 +99,8 @@ jobs:
|
|||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: mac
|
qt_host: mac
|
||||||
qt_arch: ''
|
qt_arch: ''
|
||||||
qt_version: '6.6.1'
|
qt_version: '6.7.0'
|
||||||
qt_modules: 'qt5compat qtimageformats'
|
qt_modules: 'qt5compat qtimageformats'
|
||||||
qt_tools: ''
|
|
||||||
|
|
||||||
- os: macos-12
|
- os: macos-12
|
||||||
name: macOS-Legacy
|
name: macOS-Legacy
|
||||||
@ -109,7 +109,6 @@ jobs:
|
|||||||
qt_host: mac
|
qt_host: mac
|
||||||
qt_version: "5.15.2"
|
qt_version: "5.15.2"
|
||||||
qt_modules: ""
|
qt_modules: ""
|
||||||
qt_tools: ""
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
@ -160,13 +159,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.12
|
uses: hendrikmuhs/ccache-action@v1.2.13
|
||||||
with:
|
with:
|
||||||
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
|
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
|
||||||
|
|
||||||
- name: Retrieve ccache cache (Windows MinGW-w64)
|
- name: Retrieve ccache cache (Windows MinGW-w64)
|
||||||
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
|
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
|
||||||
uses: actions/cache@v4.0.0
|
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 }}
|
||||||
@ -207,11 +206,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
|
||||||
@ -223,20 +217,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 }}
|
||||||
@ -259,7 +251,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)
|
||||||
@ -407,7 +398,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:
|
||||||
@ -433,12 +424,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
|
||||||
@ -491,26 +476,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
|
||||||
@ -526,13 +491,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/
|
||||||
@ -540,10 +501,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
|
||||||
@ -565,6 +522,25 @@ 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
|
||||||
##
|
##
|
||||||
@ -597,13 +573,6 @@ jobs:
|
|||||||
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@v4
|
|
||||||
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@v4
|
uses: actions/upload-artifact@v4
|
||||||
@ -611,13 +580,6 @@ jobs:
|
|||||||
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@v4
|
|
||||||
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@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
6
.github/workflows/trigger_release.yml
vendored
6
.github/workflows/trigger_release.yml
vendored
@ -46,9 +46,7 @@ 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.zip PrismLauncher-macOS-Legacy-${{ env.VERSION }}.zip
|
mv PrismLauncher-macOS-Legacy*/PrismLauncher.zip PrismLauncher-macOS-Legacy-${{ env.VERSION }}.zip
|
||||||
@ -84,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 }}
|
||||||
@ -92,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
|
||||||
|
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@6004951b182f8860210c8d6f0d808ec5b1a33d28 # v25
|
- uses: cachix/install-nix-action@8887e596b4ee1134dae06b98d573bd674693f47c # v26
|
||||||
|
|
||||||
- uses: DeterminateSystems/update-flake-lock@v20
|
- uses: DeterminateSystems/update-flake-lock@v21
|
||||||
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"
|
||||||
|
@ -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}")
|
||||||
@ -381,8 +381,8 @@ if(UNIX AND APPLE)
|
|||||||
set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=" CACHE STRING "Public key for Sparkle update feed")
|
set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=" CACHE STRING "Public key for Sparkle update feed")
|
||||||
set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml" CACHE STRING "URL for Sparkle update feed")
|
set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml" CACHE STRING "URL for Sparkle update feed")
|
||||||
|
|
||||||
set(MACOSX_SPARKLE_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.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,11 +516,10 @@ 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()
|
||||||
|
@ -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>
|
||||||
|
30
flake.lock
generated
30
flake.lock
generated
@ -23,11 +23,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1706830856,
|
"lastModified": 1714641030,
|
||||||
"narHash": "sha256-a0NYyp+h9hlb7ddVz4LUn1vT/PLwqfrWYcHMvFB1xYg=",
|
"narHash": "sha256-yzcRNDoyVP7+SCNX0wmuDju1NUCt8Dz9+lyUXEI0dbI=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "b253292d9c0a5ead9bc98c4e9a26c6312e27d69f",
|
"rev": "e5d10a24b66c3ea8f150e47dfdb0416ab7c3390e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -41,11 +41,11 @@
|
|||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1701680307,
|
"lastModified": 1710146030,
|
||||||
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
|
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
|
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -62,11 +62,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1703887061,
|
"lastModified": 1709087332,
|
||||||
"narHash": "sha256-gGPa9qWNc6eCXT/+Z5/zMkyYOuRZqeFZBDbopNZQkuY=",
|
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "gitignore.nix",
|
"repo": "gitignore.nix",
|
||||||
"rev": "43e1aa1308018f37118e34d3a9cb4f5e75dc11d5",
|
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -93,11 +93,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1706925685,
|
"lastModified": 1715413075,
|
||||||
"narHash": "sha256-hVInjWMmgH4yZgA4ZtbgJM1qEAel72SYhP5nOWX4UIM=",
|
"narHash": "sha256-FCi3R1MeS5bVp0M0xTheveP6hhcCYfW/aghSTPebYL4=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "79a13f1437e149dc7be2d1290c74d378dad60814",
|
"rev": "e4e7a43a9db7e22613accfeb1005cca1b2b1ee0d",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -122,11 +122,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1706424699,
|
"lastModified": 1714478972,
|
||||||
"narHash": "sha256-Q3RBuOpZNH2eFA1e+IHgZLAOqDD9SKhJ/sszrL8bQD4=",
|
"narHash": "sha256-q//cgb52vv81uOuwz1LaXElp3XAe1TqrABXODAEF6Sk=",
|
||||||
"owner": "cachix",
|
"owner": "cachix",
|
||||||
"repo": "pre-commit-hooks.nix",
|
"repo": "pre-commit-hooks.nix",
|
||||||
"rev": "7c54e08a689b53c8a1e5d70169f2ec9e2a68ffaf",
|
"rev": "2849da033884f54822af194400f8dff435ada242",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -3,6 +3,7 @@ runtime: org.kde.Platform
|
|||||||
runtime-version: 5.15-23.08
|
runtime-version: 5.15-23.08
|
||||||
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
|
||||||
|
|
||||||
@ -50,6 +51,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
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 55a8e460c6343229597a13e973ba4855c27a1c4c
|
Subproject commit f2b0c16a2a217a1822ce5a6538ba8f755ed1dd32
|
@ -225,6 +225,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;
|
||||||
@ -308,7 +309,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;
|
||||||
@ -639,10 +644,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);
|
||||||
|
@ -827,6 +827,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
|
||||||
@ -1492,7 +1494,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(
|
||||||
@ -1503,10 +1504,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"
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -801,15 +801,24 @@ QString NormalizePath(QString path)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString badFilenameChars = "\"\\/?<>:;*|!+\r\n";
|
static const QString BAD_PATH_CHARS = "\"?<>:;*|!+\r\n";
|
||||||
|
static const QString BAD_FILENAME_CHARS = BAD_PATH_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 string, QChar replaceWith)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < string.length(); i++)
|
||||||
|
if (string.at(i) < ' ' || BAD_PATH_CHARS.contains(string.at(i)))
|
||||||
|
string[i] = replaceWith;
|
||||||
|
|
||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1585,4 +1594,44 @@ 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
|
||||||
|
|
||||||
} // namespace FS
|
} // namespace FS
|
||||||
|
@ -343,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 "!"
|
||||||
@ -552,4 +554,8 @@ 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
|
||||||
|
|
||||||
} // namespace FS
|
} // namespace FS
|
||||||
|
@ -47,9 +47,6 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
class QuaZip;
|
class QuaZip;
|
||||||
namespace Flame {
|
|
||||||
class FileResolvingTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
class InstanceImportTask : public InstanceTask {
|
class InstanceImportTask : public InstanceTask {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -79,7 +76,6 @@ class InstanceImportTask : public InstanceTask {
|
|||||||
|
|
||||||
private: /* data */
|
private: /* data */
|
||||||
NetJob::Ptr m_filesNetJob;
|
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;
|
bool m_downloadRequired = false;
|
||||||
|
@ -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>
|
||||||
@ -847,14 +848,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);
|
||||||
@ -866,7 +869,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
|
||||||
@ -881,14 +884,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;
|
||||||
}
|
}
|
||||||
@ -897,7 +908,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)
|
||||||
@ -906,7 +917,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;
|
||||||
@ -918,34 +933,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,
|
||||||
|
@ -119,6 +119,7 @@ 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);
|
QFile::remove(fileCompressed);
|
||||||
@ -141,6 +142,7 @@ 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);
|
QFile::remove(targetJarPath);
|
||||||
qCritical() << "Failed to open the minecraft.jar for modding";
|
qCritical() << "Failed to open the minecraft.jar for modding";
|
||||||
@ -286,10 +288,13 @@ std::optional<QStringList> extractSubDir(QuaZip* zip, const QString& subdir, con
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
QString file_name = zip->getCurrentFileName();
|
QString file_name = zip->getCurrentFileName();
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
file_name = FS::RemoveInvalidPathChars(file_name);
|
||||||
|
#endif
|
||||||
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
|
||||||
@ -463,7 +468,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())
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -413,6 +413,8 @@ QList<QString> JavaUtils::FindJavaPaths()
|
|||||||
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 +441,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()) {
|
||||||
|
@ -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);
|
||||||
@ -594,9 +595,6 @@ 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()) {
|
||||||
@ -604,18 +602,16 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
|
|||||||
QString libPath = mangoHudLib.absolutePath();
|
QString libPath = mangoHudLib.absolutePath();
|
||||||
auto appendLib = [libPath, &preloadList](QString fileName) {
|
auto appendLib = [libPath, &preloadList](QString fileName) {
|
||||||
if (QFileInfo(FS::PathCombine(libPath, fileName)).exists())
|
if (QFileInfo(FS::PathCombine(libPath, fileName)).exists())
|
||||||
preloadList << fileName;
|
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
|
||||||
appendLib("libMangoHud_dlsym.so");
|
appendLib("libMangoHud_dlsym.so");
|
||||||
appendLib("libMangoHud_opengl.so");
|
appendLib("libMangoHud_opengl.so");
|
||||||
appendLib(mangoHudLib.fileName());
|
appendLib(mangoHudLib.fileName());
|
||||||
libPaths << libPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -627,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;
|
||||||
}
|
}
|
||||||
@ -662,8 +665,12 @@ QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session, Mine
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (serverToJoin && !serverToJoin->address.isEmpty()) {
|
if (serverToJoin && !serverToJoin->address.isEmpty()) {
|
||||||
args_pattern += " --server " + serverToJoin->address;
|
if (profile->hasTrait("feature:is_quick_play_multiplayer")) {
|
||||||
args_pattern += " --port " + QString::number(serverToJoin->port);
|
args_pattern += " --quickPlayMultiplayer " + serverToJoin->address + ':' + QString::number(serverToJoin->port);
|
||||||
|
} else {
|
||||||
|
args_pattern += " --server " + serverToJoin->address;
|
||||||
|
args_pattern += " --port " + QString::number(serverToJoin->port);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QMap<QString, QString> token_mapping;
|
QMap<QString, QString> token_mapping;
|
||||||
|
@ -126,7 +126,35 @@ bool XboxAuthorizationStep::processSTSError(QNetworkReply::NetworkError error, Q
|
|||||||
emit finished(
|
emit finished(
|
||||||
AccountTaskState::STATE_FAILED_SOFT,
|
AccountTaskState::STATE_FAILED_SOFT,
|
||||||
tr("This Microsoft account is underaged and is not linked to a family.\n\nPlease set up your account according to %1.")
|
tr("This Microsoft account is underaged and is not linked to a family.\n\nPlease set up your account according to %1.")
|
||||||
.arg("<a href=\"https://help.minecraft.net/hc/en-us/articles/4403181904525\">help.minecraft.net</a>"));
|
.arg("<a href=\"https://help.minecraft.net/hc/en-us/articles/4408968616077\">help.minecraft.net</a>"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// the following codes where copied from: https://github.com/PrismarineJS/prismarine-auth/pull/44
|
||||||
|
case 2148916236: {
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||||
|
tr("This Microsoft account requires proof of age to play. Please login to %1 to provide proof of age.")
|
||||||
|
.arg("<a href=\"https://login.live.com/login.srf\">login.live.com</a>"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 2148916237:
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("This Microsoft account has reached its limit for playtime. This "
|
||||||
|
"Microsoft account has been blocked from logging in."));
|
||||||
|
return true;
|
||||||
|
case 2148916227: {
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("This Microsoft account was banned by Xbox for violating one or more "
|
||||||
|
"Community Standards for Xbox and is unable to be used."));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 2148916229: {
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||||
|
tr("This Microsoft account is currently restricted and your guardian has not given you permission to play "
|
||||||
|
"online. Login to %1 and have your guardian change your permissions.")
|
||||||
|
.arg("<a href=\"https://account.microsoft.com/family/\">account.microsoft.com</a>"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 2148916234: {
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||||
|
tr("This Microsoft account has not accepted Xbox's Terms of Service. Please login and accept them."));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
@ -79,6 +79,7 @@ void ExtractNatives::executeTask()
|
|||||||
auto settings = minecraftInstance->settings();
|
auto settings = minecraftInstance->settings();
|
||||||
|
|
||||||
auto outputPath = minecraftInstance->getNativePath();
|
auto outputPath = minecraftInstance->getNativePath();
|
||||||
|
FS::ensureFolderPathExists(outputPath);
|
||||||
auto javaVersion = minecraftInstance->getJavaVersion();
|
auto javaVersion = minecraftInstance->getJavaVersion();
|
||||||
bool jniHackEnabled = javaVersion.major() >= 8;
|
bool jniHackEnabled = javaVersion.major() >= 8;
|
||||||
for (const auto& source : toExtract) {
|
for (const auto& source : toExtract) {
|
||||||
|
@ -16,8 +16,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <launch/LaunchStep.h>
|
#include <launch/LaunchStep.h>
|
||||||
#include <memory>
|
|
||||||
#include "minecraft/auth/AuthSession.h"
|
|
||||||
|
|
||||||
// FIXME: temporary wrapper for existing task.
|
// FIXME: temporary wrapper for existing task.
|
||||||
class ExtractNatives : public LaunchStep {
|
class ExtractNatives : public LaunchStep {
|
||||||
|
@ -66,32 +66,6 @@ LauncherPartLaunch::LauncherPartLaunch(LaunchTask* parent) : LaunchStep(parent)
|
|||||||
connect(&m_process, &LoggedProcess::stateChanged, this, &LauncherPartLaunch::on_state);
|
connect(&m_process, &LoggedProcess::stateChanged, this, &LauncherPartLaunch::on_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
// returns 8.3 file format from long path
|
|
||||||
#include <windows.h>
|
|
||||||
QString shortPathName(const QString& file)
|
|
||||||
{
|
|
||||||
auto input = file.toStdWString();
|
|
||||||
std::wstring output;
|
|
||||||
long length = GetShortPathNameW(input.c_str(), NULL, 0);
|
|
||||||
// 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);
|
|
||||||
GetShortPathNameW(input.c_str(), (LPWSTR)output.c_str(), length);
|
|
||||||
output.resize(length - 1);
|
|
||||||
QString ret = QString::fromStdWString(output);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// if the string survives roundtrip through local 8bit encoding...
|
|
||||||
bool fitsInLocal8bit(const QString& string)
|
|
||||||
{
|
|
||||||
return string == QString::fromLocal8Bit(string.toLocal8Bit());
|
|
||||||
}
|
|
||||||
|
|
||||||
void LauncherPartLaunch::executeTask()
|
void LauncherPartLaunch::executeTask()
|
||||||
{
|
{
|
||||||
QString jarPath = APPLICATION->getJarPath("NewLaunch.jar");
|
QString jarPath = APPLICATION->getJarPath("NewLaunch.jar");
|
||||||
@ -136,24 +110,15 @@ void LauncherPartLaunch::executeTask()
|
|||||||
|
|
||||||
auto natPath = minecraftInstance->getNativePath();
|
auto natPath = minecraftInstance->getNativePath();
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
if (!fitsInLocal8bit(natPath)) {
|
natPath = FS::getPathNameInLocal8bit(natPath);
|
||||||
args << "-Djava.library.path=" + shortPathName(natPath);
|
|
||||||
} else {
|
|
||||||
args << "-Djava.library.path=" + natPath;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
args << "-Djava.library.path=" + natPath;
|
|
||||||
#endif
|
#endif
|
||||||
|
args << "-Djava.library.path=" + natPath;
|
||||||
|
|
||||||
args << "-cp";
|
args << "-cp";
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
QStringList processed;
|
QStringList processed;
|
||||||
for (auto& item : classPath) {
|
for (auto& item : classPath) {
|
||||||
if (!fitsInLocal8bit(item)) {
|
processed << FS::getPathNameInLocal8bit(item);
|
||||||
processed << shortPathName(item);
|
|
||||||
} else {
|
|
||||||
processed << item;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
args << processed.join(';');
|
args << processed.join(';');
|
||||||
#else
|
#else
|
||||||
|
@ -274,7 +274,7 @@ QPixmap Mod::icon(QSize size, Qt::AspectRatioMode mode) const
|
|||||||
return {};
|
return {};
|
||||||
|
|
||||||
if (m_pack_image_cache_key.was_ever_used) {
|
if (m_pack_image_cache_key.was_ever_used) {
|
||||||
qDebug() << "Mod" << name() << "Had it's icon evicted form the cache. reloading...";
|
qDebug() << "Mod" << name() << "Had it's icon evicted from the cache. reloading...";
|
||||||
PixmapCache::markCacheMissByEviciton();
|
PixmapCache::markCacheMissByEviciton();
|
||||||
}
|
}
|
||||||
// Image got evicted from the cache or an attempt to load it has not been made. load it and retry.
|
// Image got evicted from the cache or an attempt to load it has not been made. load it and retry.
|
||||||
|
@ -57,9 +57,11 @@ GetModDependenciesTask::GetModDependenciesTask(QObject* parent,
|
|||||||
, m_version(mcVersion(instance))
|
, m_version(mcVersion(instance))
|
||||||
, m_loaderType(mcLoaders(instance))
|
, m_loaderType(mcLoaders(instance))
|
||||||
{
|
{
|
||||||
for (auto mod : folder->allMods())
|
for (auto mod : folder->allMods()) {
|
||||||
|
m_mods_file_names << mod->fileinfo().fileName();
|
||||||
if (auto meta = mod->metadata(); meta)
|
if (auto meta = mod->metadata(); meta)
|
||||||
m_mods.append(meta);
|
m_mods.append(meta);
|
||||||
|
}
|
||||||
prepare();
|
prepare();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,8 +233,13 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
|
|||||||
if (dep_.addonId != pDep->version.addonId) {
|
if (dep_.addonId != pDep->version.addonId) {
|
||||||
removePack(pDep->version.addonId);
|
removePack(pDep->version.addonId);
|
||||||
addTask(prepareDependencyTask(dep_, provider.name, level));
|
addTask(prepareDependencyTask(dep_, provider.name, level));
|
||||||
} else
|
} else {
|
||||||
addTask(getProjectInfoTask(pDep));
|
addTask(getProjectInfoTask(pDep));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isLocalyInstalled(pDep)) {
|
||||||
|
removePack(pDep->version.addonId);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
for (auto dep_ : getDependenciesForVersion(pDep->version, provider.name)) {
|
for (auto dep_ : getDependenciesForVersion(pDep->version, provider.name)) {
|
||||||
addTask(prepareDependencyTask(dep_, provider.name, level - 1));
|
addTask(prepareDependencyTask(dep_, provider.name, level - 1));
|
||||||
@ -258,9 +265,9 @@ void GetModDependenciesTask::removePack(const QVariant& addonId)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
QHash<QString, QStringList> GetModDependenciesTask::getRequiredBy()
|
auto GetModDependenciesTask::getExtraInfo() -> QHash<QString, PackDependencyExtraInfo>
|
||||||
{
|
{
|
||||||
QHash<QString, QStringList> rby;
|
QHash<QString, PackDependencyExtraInfo> rby;
|
||||||
auto fullList = m_selected + m_pack_dependencies;
|
auto fullList = m_selected + m_pack_dependencies;
|
||||||
for (auto& mod : fullList) {
|
for (auto& mod : fullList) {
|
||||||
auto addonId = mod->pack->addonId;
|
auto addonId = mod->pack->addonId;
|
||||||
@ -282,7 +289,61 @@ QHash<QString, QStringList> GetModDependenciesTask::getRequiredBy()
|
|||||||
req.append(smod->pack->name);
|
req.append(smod->pack->name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rby[addonId.toString()] = req;
|
rby[addonId.toString()] = { maybeInstalled(mod), req };
|
||||||
}
|
}
|
||||||
return rby;
|
return rby;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// super lax compare (but not fuzzy)
|
||||||
|
// convert to lowercase
|
||||||
|
// convert all speratores to whitespace
|
||||||
|
// simplify sequence of internal whitespace to a single space
|
||||||
|
// efectivly compare two strings ignoring all separators and case
|
||||||
|
auto laxCompare = [](QString fsfilename, QString metadataFilename, bool excludeDigits = false) {
|
||||||
|
// allowed character seperators
|
||||||
|
QList<QChar> allowedSeperators = { '-', '+', '.', '_' };
|
||||||
|
if (excludeDigits)
|
||||||
|
allowedSeperators.append({ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' });
|
||||||
|
|
||||||
|
// copy in lowercase
|
||||||
|
auto fsName = fsfilename.toLower();
|
||||||
|
auto metaName = metadataFilename.toLower();
|
||||||
|
|
||||||
|
// replace all potential allowed seperatores with whitespace
|
||||||
|
for (auto sep : allowedSeperators) {
|
||||||
|
fsName = fsName.replace(sep, ' ');
|
||||||
|
metaName = metaName.replace(sep, ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove extraneous whitespace
|
||||||
|
fsName = fsName.simplified();
|
||||||
|
metaName = metaName.simplified();
|
||||||
|
|
||||||
|
return fsName.compare(metaName) == 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool GetModDependenciesTask::isLocalyInstalled(std::shared_ptr<PackDependency> pDep)
|
||||||
|
{
|
||||||
|
return pDep->version.fileName.isEmpty() ||
|
||||||
|
|
||||||
|
std::find_if(m_selected.begin(), m_selected.end(),
|
||||||
|
[pDep](std::shared_ptr<PackDependency> i) {
|
||||||
|
return !i->version.fileName.isEmpty() && laxCompare(i->version.fileName, pDep->version.fileName);
|
||||||
|
}) != m_selected.end() || // check the selected versions
|
||||||
|
|
||||||
|
std::find_if(m_mods_file_names.begin(), m_mods_file_names.end(),
|
||||||
|
[pDep](QString i) { return !i.isEmpty() && laxCompare(i, pDep->version.fileName); }) !=
|
||||||
|
m_mods_file_names.end() || // check the existing mods
|
||||||
|
|
||||||
|
std::find_if(m_pack_dependencies.begin(), m_pack_dependencies.end(), [pDep](std::shared_ptr<PackDependency> i) {
|
||||||
|
return pDep->pack->addonId != i->pack->addonId && !i->version.fileName.isEmpty() &&
|
||||||
|
laxCompare(pDep->version.fileName, i->version.fileName);
|
||||||
|
}) != m_pack_dependencies.end(); // check loaded dependencies
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetModDependenciesTask::maybeInstalled(std::shared_ptr<PackDependency> pDep)
|
||||||
|
{
|
||||||
|
return std::find_if(m_mods_file_names.begin(), m_mods_file_names.end(), [pDep](QString i) {
|
||||||
|
return !i.isEmpty() && laxCompare(i, pDep->version.fileName, true);
|
||||||
|
}) != m_mods_file_names.end(); // check the existing mods
|
||||||
|
}
|
||||||
|
@ -50,6 +50,11 @@ class GetModDependenciesTask : public SequentialTask {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct PackDependencyExtraInfo {
|
||||||
|
bool maybe_installed;
|
||||||
|
QStringList required_by;
|
||||||
|
};
|
||||||
|
|
||||||
struct Provider {
|
struct Provider {
|
||||||
ModPlatform::ResourceProvider name;
|
ModPlatform::ResourceProvider name;
|
||||||
std::shared_ptr<ResourceDownload::ModModel> mod;
|
std::shared_ptr<ResourceDownload::ModModel> mod;
|
||||||
@ -62,7 +67,7 @@ class GetModDependenciesTask : public SequentialTask {
|
|||||||
QList<std::shared_ptr<PackDependency>> selected);
|
QList<std::shared_ptr<PackDependency>> selected);
|
||||||
|
|
||||||
auto getDependecies() const -> QList<std::shared_ptr<PackDependency>> { return m_pack_dependencies; }
|
auto getDependecies() const -> QList<std::shared_ptr<PackDependency>> { return m_pack_dependencies; }
|
||||||
QHash<QString, QStringList> getRequiredBy();
|
QHash<QString, PackDependencyExtraInfo> getExtraInfo();
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
Task::Ptr prepareDependencyTask(const ModPlatform::Dependency&, ModPlatform::ResourceProvider, int);
|
Task::Ptr prepareDependencyTask(const ModPlatform::Dependency&, ModPlatform::ResourceProvider, int);
|
||||||
@ -73,10 +78,14 @@ class GetModDependenciesTask : public SequentialTask {
|
|||||||
ModPlatform::Dependency getOverride(const ModPlatform::Dependency&, ModPlatform::ResourceProvider providerName);
|
ModPlatform::Dependency getOverride(const ModPlatform::Dependency&, ModPlatform::ResourceProvider providerName);
|
||||||
void removePack(const QVariant& addonId);
|
void removePack(const QVariant& addonId);
|
||||||
|
|
||||||
|
bool isLocalyInstalled(std::shared_ptr<PackDependency> pDep);
|
||||||
|
bool maybeInstalled(std::shared_ptr<PackDependency> pDep);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QList<std::shared_ptr<PackDependency>> m_pack_dependencies;
|
QList<std::shared_ptr<PackDependency>> m_pack_dependencies;
|
||||||
QList<std::shared_ptr<Metadata::ModStruct>> m_mods;
|
QList<std::shared_ptr<Metadata::ModStruct>> m_mods;
|
||||||
QList<std::shared_ptr<PackDependency>> m_selected;
|
QList<std::shared_ptr<PackDependency>> m_selected;
|
||||||
|
QStringList m_mods_file_names;
|
||||||
Provider m_flame_provider;
|
Provider m_flame_provider;
|
||||||
Provider m_modrinth_provider;
|
Provider m_modrinth_provider;
|
||||||
|
|
||||||
|
@ -469,7 +469,7 @@ bool processZIP(Mod& mod, [[maybe_unused]] ProcessingLevel level)
|
|||||||
|
|
||||||
QuaZipFile file(&zip);
|
QuaZipFile file(&zip);
|
||||||
|
|
||||||
if (zip.setCurrentFile("META-INF/mods.toml")) {
|
if (zip.setCurrentFile("META-INF/mods.toml") || zip.setCurrentFile("META-INF/neoforge.mods.toml")) {
|
||||||
if (!file.open(QIODevice::ReadOnly)) {
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
zip.close();
|
zip.close();
|
||||||
return false;
|
return false;
|
||||||
|
@ -28,6 +28,7 @@ class CheckUpdateTask : public Task {
|
|||||||
QString changelog;
|
QString changelog;
|
||||||
ModPlatform::ResourceProvider provider;
|
ModPlatform::ResourceProvider provider;
|
||||||
shared_qobject_ptr<ResourceDownloadTask> download;
|
shared_qobject_ptr<ResourceDownloadTask> download;
|
||||||
|
bool enabled = true;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UpdatableMod(QString name,
|
UpdatableMod(QString name,
|
||||||
@ -37,7 +38,8 @@ class CheckUpdateTask : public Task {
|
|||||||
std::optional<ModPlatform::IndexedVersionType> new_v_type,
|
std::optional<ModPlatform::IndexedVersionType> new_v_type,
|
||||||
QString changelog,
|
QString changelog,
|
||||||
ModPlatform::ResourceProvider p,
|
ModPlatform::ResourceProvider p,
|
||||||
shared_qobject_ptr<ResourceDownloadTask> t)
|
shared_qobject_ptr<ResourceDownloadTask> t,
|
||||||
|
bool enabled = true)
|
||||||
: name(name)
|
: name(name)
|
||||||
, old_hash(old_h)
|
, old_hash(old_h)
|
||||||
, old_version(old_v)
|
, old_version(old_v)
|
||||||
@ -46,6 +48,7 @@ class CheckUpdateTask : public Task {
|
|||||||
, changelog(changelog)
|
, changelog(changelog)
|
||||||
, provider(p)
|
, provider(p)
|
||||||
, download(t)
|
, download(t)
|
||||||
|
, enabled(enabled)
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1031,6 +1031,12 @@ void PackInstallTask::install()
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
components->setComponentVersion("net.minecraftforge", version);
|
components->setComponentVersion("net.minecraftforge", version);
|
||||||
|
} else if (m_version.loader.type == QString("neoforge")) {
|
||||||
|
auto version = getVersionForLoader("net.neoforged");
|
||||||
|
if (version == Q_NULLPTR)
|
||||||
|
return;
|
||||||
|
|
||||||
|
components->setComponentVersion("net.neoforged", version);
|
||||||
} else if (m_version.loader.type == QString("fabric")) {
|
} else if (m_version.loader.type == QString("fabric")) {
|
||||||
auto version = getVersionForLoader("net.fabricmc.fabric-loader");
|
auto version = getVersionForLoader("net.fabricmc.fabric-loader");
|
||||||
if (version == Q_NULLPTR)
|
if (version == Q_NULLPTR)
|
||||||
|
@ -354,6 +354,8 @@ bool FlameCreationTask::createInstance()
|
|||||||
auto id = loader.id;
|
auto id = loader.id;
|
||||||
if (id.startsWith("neoforge-")) {
|
if (id.startsWith("neoforge-")) {
|
||||||
id.remove("neoforge-");
|
id.remove("neoforge-");
|
||||||
|
if (id.startsWith("1.20.1-"))
|
||||||
|
id.remove("1.20.1-"); // this is a mess for curseforge
|
||||||
loaderType = "neoforge";
|
loaderType = "neoforge";
|
||||||
loaderUid = "net.neoforged";
|
loaderUid = "net.neoforged";
|
||||||
} else if (id.startsWith("forge-")) {
|
} else if (id.startsWith("forge-")) {
|
||||||
@ -535,7 +537,12 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
|
|||||||
selectedOptionalMods = optionalModDialog.getResult();
|
selectedOptionalMods = optionalModDialog.getResult();
|
||||||
}
|
}
|
||||||
for (const auto& result : results) {
|
for (const auto& result : results) {
|
||||||
auto relpath = FS::PathCombine(result.targetFolder, result.fileName);
|
auto fileName = result.fileName;
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
fileName = FS::RemoveInvalidPathChars(fileName);
|
||||||
|
#endif
|
||||||
|
auto relpath = FS::PathCombine(result.targetFolder, fileName);
|
||||||
|
|
||||||
if (!result.required && !selectedOptionalMods.contains(relpath)) {
|
if (!result.required && !selectedOptionalMods.contains(relpath)) {
|
||||||
relpath += ".disabled";
|
relpath += ".disabled";
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "FlameModIndex.h"
|
#include "FlameModIndex.h"
|
||||||
|
|
||||||
|
#include "FileSystem.h"
|
||||||
#include "Json.h"
|
#include "Json.h"
|
||||||
#include "minecraft/MinecraftInstance.h"
|
#include "minecraft/MinecraftInstance.h"
|
||||||
#include "minecraft/PackProfile.h"
|
#include "minecraft/PackProfile.h"
|
||||||
@ -138,6 +139,9 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
|
|||||||
file.version = Json::requireString(obj, "displayName");
|
file.version = Json::requireString(obj, "displayName");
|
||||||
file.downloadUrl = Json::ensureString(obj, "downloadUrl");
|
file.downloadUrl = Json::ensureString(obj, "downloadUrl");
|
||||||
file.fileName = Json::requireString(obj, "fileName");
|
file.fileName = Json::requireString(obj, "fileName");
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
file.fileName = FS::RemoveInvalidPathChars(file.fileName);
|
||||||
|
#endif
|
||||||
|
|
||||||
ModPlatform::IndexedVersionType::VersionType ver_type;
|
ModPlatform::IndexedVersionType::VersionType ver_type;
|
||||||
switch (Json::requireInteger(obj, "releaseType")) {
|
switch (Json::requireInteger(obj, "releaseType")) {
|
||||||
|
@ -201,7 +201,7 @@ void FlamePackExportTask::makeApiRequest()
|
|||||||
<< " reason: " << parseError.errorString();
|
<< " reason: " << parseError.errorString();
|
||||||
qWarning() << *response;
|
qWarning() << *response;
|
||||||
|
|
||||||
failed(parseError.errorString());
|
emitFailed(parseError.errorString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,6 +213,7 @@ void FlamePackExportTask::makeApiRequest()
|
|||||||
if (dataArr.isEmpty()) {
|
if (dataArr.isEmpty()) {
|
||||||
qWarning() << "No matches found for fingerprint search!";
|
qWarning() << "No matches found for fingerprint search!";
|
||||||
|
|
||||||
|
getProjectsInfo();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (auto match : dataArr) {
|
for (auto match : dataArr) {
|
||||||
@ -243,9 +244,9 @@ void FlamePackExportTask::makeApiRequest()
|
|||||||
qDebug() << doc;
|
qDebug() << doc;
|
||||||
}
|
}
|
||||||
pendingHashes.clear();
|
pendingHashes.clear();
|
||||||
|
getProjectsInfo();
|
||||||
});
|
});
|
||||||
connect(task.get(), &Task::finished, this, &FlamePackExportTask::getProjectsInfo);
|
connect(task.get(), &NetJob::failed, this, &FlamePackExportTask::getProjectsInfo);
|
||||||
connect(task.get(), &NetJob::failed, this, &FlamePackExportTask::emitFailed);
|
|
||||||
task->start();
|
task->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,7 +280,7 @@ void FlamePackExportTask::getProjectsInfo()
|
|||||||
qWarning() << "Error while parsing JSON response from CurseForge projects task at " << parseError.offset
|
qWarning() << "Error while parsing JSON response from CurseForge projects task at " << parseError.offset
|
||||||
<< " reason: " << parseError.errorString();
|
<< " reason: " << parseError.errorString();
|
||||||
qWarning() << *response;
|
qWarning() << *response;
|
||||||
failed(parseError.errorString());
|
emitFailed(parseError.errorString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,7 +334,7 @@ void FlamePackExportTask::buildZip()
|
|||||||
setStatus(tr("Adding files..."));
|
setStatus(tr("Adding files..."));
|
||||||
setProgress(4, 5);
|
setProgress(4, 5);
|
||||||
|
|
||||||
auto zipTask = makeShared<MMCZip::ExportToZipTask>(output, gameRoot, files, "overrides/", true);
|
auto zipTask = makeShared<MMCZip::ExportToZipTask>(output, gameRoot, files, "overrides/", true, false);
|
||||||
zipTask->addExtraFile("manifest.json", generateIndex());
|
zipTask->addExtraFile("manifest.json", generateIndex());
|
||||||
zipTask->addExtraFile("modlist.html", generateHTML());
|
zipTask->addExtraFile("modlist.html", generateHTML());
|
||||||
|
|
||||||
@ -393,13 +394,17 @@ QByteArray FlamePackExportTask::generateIndex()
|
|||||||
version["version"] = minecraft->m_version;
|
version["version"] = minecraft->m_version;
|
||||||
QString id;
|
QString id;
|
||||||
if (quilt != nullptr)
|
if (quilt != nullptr)
|
||||||
id = "quilt-" + quilt->getVersion();
|
id = "quilt-" + quilt->m_version;
|
||||||
else if (fabric != nullptr)
|
else if (fabric != nullptr)
|
||||||
id = "fabric-" + fabric->getVersion();
|
id = "fabric-" + fabric->m_version;
|
||||||
else if (forge != nullptr)
|
else if (forge != nullptr)
|
||||||
id = "forge-" + forge->getVersion();
|
id = "forge-" + forge->m_version;
|
||||||
else if (neoforge != nullptr)
|
else if (neoforge != nullptr) {
|
||||||
id = "neoforge-" + neoforge->getVersion();
|
id = "neoforge-";
|
||||||
|
if (minecraft->m_version == "1.20.1")
|
||||||
|
id += "1.20.1-";
|
||||||
|
id += neoforge->m_version;
|
||||||
|
}
|
||||||
version["modLoaders"] = QJsonArray();
|
version["modLoaders"] = QJsonArray();
|
||||||
if (!id.isEmpty()) {
|
if (!id.isEmpty()) {
|
||||||
QJsonObject loader;
|
QJsonObject loader;
|
||||||
|
@ -43,7 +43,7 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&
|
|||||||
callbacks.on_succeed(doc);
|
callbacks.on_succeed(doc);
|
||||||
});
|
});
|
||||||
|
|
||||||
QObject::connect(netJob.get(), &NetJob::failed, [&netJob, callbacks](QString reason) {
|
QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) {
|
||||||
int network_error_code = -1;
|
int network_error_code = -1;
|
||||||
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply)
|
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply)
|
||||||
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
@ -102,7 +102,7 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi
|
|||||||
|
|
||||||
callbacks.on_succeed(doc, args.pack);
|
callbacks.on_succeed(doc, args.pack);
|
||||||
});
|
});
|
||||||
QObject::connect(netJob.get(), &NetJob::failed, [&netJob, callbacks](QString reason) {
|
QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) {
|
||||||
int network_error_code = -1;
|
int network_error_code = -1;
|
||||||
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply)
|
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply)
|
||||||
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
@ -153,7 +153,7 @@ Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args,
|
|||||||
|
|
||||||
callbacks.on_succeed(doc, args.dependency);
|
callbacks.on_succeed(doc, args.dependency);
|
||||||
});
|
});
|
||||||
QObject::connect(netJob.get(), &NetJob::failed, [&netJob, callbacks](QString reason) {
|
QObject::connect(netJob.get(), &NetJob::failed, [netJob, callbacks](const QString& reason) {
|
||||||
int network_error_code = -1;
|
int network_error_code = -1;
|
||||||
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply)
|
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply)
|
||||||
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
@ -43,6 +43,7 @@ Modpack parseDirectory(QString path)
|
|||||||
modpack.version = Json::requireString(root, "version", "version");
|
modpack.version = Json::requireString(root, "version", "version");
|
||||||
modpack.mcVersion = Json::requireString(root, "mcVersion", "mcVersion");
|
modpack.mcVersion = Json::requireString(root, "mcVersion", "mcVersion");
|
||||||
modpack.jvmArgs = Json::ensureVariant(root, "jvmArgs", {}, "jvmArgs");
|
modpack.jvmArgs = Json::ensureVariant(root, "jvmArgs", {}, "jvmArgs");
|
||||||
|
modpack.totalPlayTime = Json::requireInteger(root, "totalPlayTime", "totalPlayTime");
|
||||||
} catch (const Exception& e) {
|
} catch (const Exception& e) {
|
||||||
qDebug() << "Couldn't load ftb instance json: " << e.cause();
|
qDebug() << "Couldn't load ftb instance json: " << e.cause();
|
||||||
return {};
|
return {};
|
||||||
|
@ -36,6 +36,7 @@ struct Modpack {
|
|||||||
QString name;
|
QString name;
|
||||||
QString version;
|
QString version;
|
||||||
QString mcVersion;
|
QString mcVersion;
|
||||||
|
int totalPlayTime;
|
||||||
// not needed for instance creation
|
// not needed for instance creation
|
||||||
QVariant jvmArgs;
|
QVariant jvmArgs;
|
||||||
|
|
||||||
|
@ -55,6 +55,7 @@ void PackInstallTask::copySettings()
|
|||||||
instanceSettings->suspendSave();
|
instanceSettings->suspendSave();
|
||||||
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
|
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
|
||||||
instance.settings()->set("InstanceType", "OneSix");
|
instance.settings()->set("InstanceType", "OneSix");
|
||||||
|
instance.settings()->set("totalTimePlayed", m_pack.totalPlayTime / 1000);
|
||||||
|
|
||||||
if (m_pack.jvmArgs.isValid() && !m_pack.jvmArgs.toString().isEmpty()) {
|
if (m_pack.jvmArgs.isValid() && !m_pack.jvmArgs.toString().isEmpty()) {
|
||||||
instance.settings()->set("OverrideJavaArgs", true);
|
instance.settings()->set("OverrideJavaArgs", true);
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#include "ui/pages/modplatform/OptionalModDialog.h"
|
#include "ui/pages/modplatform/OptionalModDialog.h"
|
||||||
|
|
||||||
#include <QAbstractButton>
|
#include <QAbstractButton>
|
||||||
|
#include <QFileInfo>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
bool ModrinthCreationTask::abort()
|
bool ModrinthCreationTask::abort()
|
||||||
@ -58,6 +59,7 @@ bool ModrinthCreationTask::updateInstance()
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
auto version_name = inst->getManagedPackVersionName();
|
auto version_name = inst->getManagedPackVersionName();
|
||||||
|
m_root_path = QFileInfo(inst->gameRoot()).fileName();
|
||||||
auto version_str = !version_name.isEmpty() ? tr(" (version %1)").arg(version_name) : "";
|
auto version_str = !version_name.isEmpty() ? tr(" (version %1)").arg(version_name) : "";
|
||||||
|
|
||||||
if (shouldConfirmUpdate()) {
|
if (shouldConfirmUpdate()) {
|
||||||
@ -173,7 +175,7 @@ bool ModrinthCreationTask::createInstance()
|
|||||||
FS::ensureFilePathExists(new_index_place);
|
FS::ensureFilePathExists(new_index_place);
|
||||||
QFile::rename(index_path, new_index_place);
|
QFile::rename(index_path, new_index_place);
|
||||||
|
|
||||||
auto mcPath = FS::PathCombine(m_stagingPath, "minecraft");
|
auto mcPath = FS::PathCombine(m_stagingPath, m_root_path);
|
||||||
|
|
||||||
auto override_path = FS::PathCombine(m_stagingPath, "overrides");
|
auto override_path = FS::PathCombine(m_stagingPath, "overrides");
|
||||||
if (QFile::exists(override_path)) {
|
if (QFile::exists(override_path)) {
|
||||||
@ -234,15 +236,19 @@ bool ModrinthCreationTask::createInstance()
|
|||||||
|
|
||||||
m_files_job.reset(new NetJob(tr("Mod Download Modrinth"), APPLICATION->network()));
|
m_files_job.reset(new NetJob(tr("Mod Download Modrinth"), APPLICATION->network()));
|
||||||
|
|
||||||
auto root_modpack_path = FS::PathCombine(m_stagingPath, "minecraft");
|
auto root_modpack_path = FS::PathCombine(m_stagingPath, m_root_path);
|
||||||
auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path);
|
auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path);
|
||||||
|
|
||||||
for (auto file : m_files) {
|
for (auto file : m_files) {
|
||||||
auto file_path = FS::PathCombine(root_modpack_path, file.path);
|
auto fileName = file.path;
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
fileName = FS::RemoveInvalidPathChars(fileName);
|
||||||
|
#endif
|
||||||
|
auto file_path = FS::PathCombine(root_modpack_path, fileName);
|
||||||
if (!root_modpack_url.isParentOf(QUrl::fromLocalFile(file_path))) {
|
if (!root_modpack_url.isParentOf(QUrl::fromLocalFile(file_path))) {
|
||||||
// This means we somehow got out of the root folder, so abort here to prevent exploits
|
// This means we somehow got out of the root folder, so abort here to prevent exploits
|
||||||
setError(tr("One of the files has a path that leads to an arbitrary location (%1). This is a security risk and isn't allowed.")
|
setError(tr("One of the files has a path that leads to an arbitrary location (%1). This is a security risk and isn't allowed.")
|
||||||
.arg(file.path));
|
.arg(fileName));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,4 +46,6 @@ class ModrinthCreationTask final : public InstanceCreationTask {
|
|||||||
NetJob::Ptr m_files_job;
|
NetJob::Ptr m_files_job;
|
||||||
|
|
||||||
std::optional<InstancePtr> m_instance;
|
std::optional<InstancePtr> m_instance;
|
||||||
|
|
||||||
|
QString m_root_path = "minecraft";
|
||||||
};
|
};
|
||||||
|
@ -200,7 +200,7 @@ void ModrinthPackExportTask::buildZip()
|
|||||||
{
|
{
|
||||||
setStatus(tr("Adding files..."));
|
setStatus(tr("Adding files..."));
|
||||||
|
|
||||||
auto zipTask = makeShared<MMCZip::ExportToZipTask>(output, gameRoot, files, "overrides/", true);
|
auto zipTask = makeShared<MMCZip::ExportToZipTask>(output, gameRoot, files, "overrides/", true, true);
|
||||||
zipTask->addExtraFile("modrinth.index.json", generateIndex());
|
zipTask->addExtraFile("modrinth.index.json", generateIndex());
|
||||||
|
|
||||||
zipTask->setExcludeFiles(resolvedFiles.keys());
|
zipTask->setExcludeFiles(resolvedFiles.keys());
|
||||||
@ -287,16 +287,12 @@ QByteArray ModrinthPackExportTask::generateIndex()
|
|||||||
env["client"] = "required";
|
env["client"] = "required";
|
||||||
env["server"] = "required";
|
env["server"] = "required";
|
||||||
}
|
}
|
||||||
switch (iterator->side) {
|
|
||||||
case Metadata::ModSide::ClientSide:
|
// a server side mod does not imply that the mod does not work on the client
|
||||||
env["server"] = "unsupported";
|
// however, if a mrpack mod is marked as server-only it will not install on the client
|
||||||
break;
|
if (iterator->side == Metadata::ModSide::ClientSide)
|
||||||
case Metadata::ModSide::ServerSide:
|
env["server"] = "unsupported";
|
||||||
env["client"] = "unsupported";
|
|
||||||
break;
|
|
||||||
case Metadata::ModSide::UniversalSide:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
fileOut["env"] = env;
|
fileOut["env"] = env;
|
||||||
|
|
||||||
fileOut["path"] = path;
|
fileOut["path"] = path;
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ModrinthPackIndex.h"
|
#include "ModrinthPackIndex.h"
|
||||||
|
#include "FileSystem.h"
|
||||||
#include "ModrinthAPI.h"
|
#include "ModrinthAPI.h"
|
||||||
|
|
||||||
#include "Json.h"
|
#include "Json.h"
|
||||||
@ -226,6 +227,9 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
|
|||||||
if (parent.contains("url")) {
|
if (parent.contains("url")) {
|
||||||
file.downloadUrl = Json::requireString(parent, "url");
|
file.downloadUrl = Json::requireString(parent, "url");
|
||||||
file.fileName = Json::requireString(parent, "filename");
|
file.fileName = Json::requireString(parent, "filename");
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
file.fileName = FS::RemoveInvalidPathChars(file.fileName);
|
||||||
|
#endif
|
||||||
file.is_preferred = Json::requireBoolean(parent, "primary") || (files.count() == 1);
|
file.is_preferred = Json::requireBoolean(parent, "primary") || (files.count() == 1);
|
||||||
auto hash_list = Json::requireObject(parent, "hashes");
|
auto hash_list = Json::requireObject(parent, "hashes");
|
||||||
|
|
||||||
|
@ -155,8 +155,26 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings,
|
|||||||
auto libraryObject = Json::ensureObject(library, {}, "");
|
auto libraryObject = Json::ensureObject(library, {}, "");
|
||||||
auto libraryName = Json::ensureString(libraryObject, "name", "", "");
|
auto libraryName = Json::ensureString(libraryObject, "name", "", "");
|
||||||
|
|
||||||
if ((libraryName.startsWith("net.minecraftforge:forge:") || libraryName.startsWith("net.minecraftforge:fmlloader:")) &&
|
if (libraryName.startsWith("net.neoforged.fancymodloader:")) { // it is neoforge
|
||||||
libraryName.contains('-')) {
|
// no easy way to get the version from the libs so use the arguments
|
||||||
|
auto arguments = Json::ensureObject(root, "arguments", {});
|
||||||
|
bool isVersionArg = false;
|
||||||
|
QString neoforgeVersion;
|
||||||
|
for (auto arg : Json::ensureArray(arguments, "game", {})) {
|
||||||
|
auto argument = Json::ensureString(arg, "");
|
||||||
|
if (isVersionArg) {
|
||||||
|
neoforgeVersion = argument;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
isVersionArg = "--fml.neoForgeVersion" == argument || "--fml.forgeVersion" == argument;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!neoforgeVersion.isEmpty()) {
|
||||||
|
components->setComponentVersion("net.neoforged", neoforgeVersion);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else if ((libraryName.startsWith("net.minecraftforge:forge:") || libraryName.startsWith("net.minecraftforge:fmlloader:")) &&
|
||||||
|
libraryName.contains('-')) {
|
||||||
QString libraryVersion = libraryName.section(':', 2);
|
QString libraryVersion = libraryName.section(':', 2);
|
||||||
if (!libraryVersion.startsWith("1.7.10-")) {
|
if (!libraryVersion.startsWith("1.7.10-")) {
|
||||||
components->setComponentVersion("net.minecraftforge", libraryName.section('-', 1));
|
components->setComponentVersion("net.minecraftforge", libraryName.section('-', 1));
|
||||||
@ -164,6 +182,7 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings,
|
|||||||
// 1.7.10 versions sometimes look like 1.7.10-10.13.4.1614-1.7.10, this filters out the 10.13.4.1614 part
|
// 1.7.10 versions sometimes look like 1.7.10-10.13.4.1614-1.7.10, this filters out the 10.13.4.1614 part
|
||||||
components->setComponentVersion("net.minecraftforge", libraryName.section('-', 1, 1));
|
components->setComponentVersion("net.minecraftforge", libraryName.section('-', 1, 1));
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
} else {
|
} else {
|
||||||
// <Technic library name prefix> -> <our component name>
|
// <Technic library name prefix> -> <our component name>
|
||||||
static QMap<QString, QString> loaderMap{ { "net.minecraftforge:minecraftforge:", "net.minecraftforge" },
|
static QMap<QString, QString> loaderMap{ { "net.minecraftforge:minecraftforge:", "net.minecraftforge" },
|
||||||
|
@ -84,6 +84,9 @@ auto HttpMetaCache::getEntry(QString base, QString resource_path) -> MetaEntryPt
|
|||||||
|
|
||||||
auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString expected_etag) -> MetaEntryPtr
|
auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString expected_etag) -> MetaEntryPtr
|
||||||
{
|
{
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
resource_path = FS::RemoveInvalidPathChars(resource_path);
|
||||||
|
#endif
|
||||||
auto entry = getEntry(base, resource_path);
|
auto entry = getEntry(base, resource_path);
|
||||||
// it's not present? generate a default stale entry
|
// it's not present? generate a default stale entry
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
|
@ -68,7 +68,8 @@ void NetRequest::executeTask()
|
|||||||
|
|
||||||
if (getState() == Task::State::AbortedByUser) {
|
if (getState() == Task::State::AbortedByUser) {
|
||||||
qCWarning(logCat) << getUid().toString() << "Attempt to start an aborted Request:" << m_url.toString();
|
qCWarning(logCat) << getUid().toString() << "Attempt to start an aborted Request:" << m_url.toString();
|
||||||
emitAborted();
|
emit aborted();
|
||||||
|
emit finished();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,10 +86,12 @@ void NetRequest::executeTask()
|
|||||||
break;
|
break;
|
||||||
case State::Inactive:
|
case State::Inactive:
|
||||||
case State::Failed:
|
case State::Failed:
|
||||||
emitFailed();
|
emit failed("Failed to initilize sink");
|
||||||
|
emit finished();
|
||||||
return;
|
return;
|
||||||
case State::AbortedByUser:
|
case State::AbortedByUser:
|
||||||
emitAborted();
|
emit aborted();
|
||||||
|
emit finished();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,7 +231,8 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
|
|||||||
setInstanceActionsEnabled(false);
|
setInstanceActionsEnabled(false);
|
||||||
|
|
||||||
// add a close button at the end of the main toolbar when running on gamescope / steam deck
|
// add a close button at the end of the main toolbar when running on gamescope / steam deck
|
||||||
// FIXME: detect if we don't have server side decorations instead
|
// this is only needed on gamescope because it defaults to an X11/XWayland session and
|
||||||
|
// does not implement decorations
|
||||||
if (qgetenv("XDG_CURRENT_DESKTOP") == "gamescope") {
|
if (qgetenv("XDG_CURRENT_DESKTOP") == "gamescope") {
|
||||||
ui->mainToolBar->addAction(ui->actionCloseWindow);
|
ui->mainToolBar->addAction(ui->actionCloseWindow);
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList<BlockedMod>& mods, QString hash_type)
|
BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList<BlockedMod>& mods, QString hash_type)
|
||||||
: QDialog(parent), ui(new Ui::BlockedModsDialog), m_mods(mods), m_hash_type(hash_type)
|
: QDialog(parent), ui(new Ui::BlockedModsDialog), m_mods(mods), m_hash_type(hash_type)
|
||||||
@ -60,8 +61,13 @@ BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, cons
|
|||||||
|
|
||||||
qDebug() << "[Blocked Mods Dialog] Mods List: " << mods;
|
qDebug() << "[Blocked Mods Dialog] Mods List: " << mods;
|
||||||
|
|
||||||
setupWatch();
|
// defer setup of file system watchers until after the dialog is shown
|
||||||
scanPaths();
|
// this allows OS (namely macOS) permission prompts to show after the relevant dialog appears
|
||||||
|
QTimer::singleShot(0, this, [this] {
|
||||||
|
setupWatch();
|
||||||
|
scanPaths();
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
|
||||||
this->setWindowTitle(title);
|
this->setWindowTitle(title);
|
||||||
ui->labelDescription->setText(text);
|
ui->labelDescription->setText(text);
|
||||||
@ -158,7 +164,8 @@ void BlockedModsDialog::update()
|
|||||||
|
|
||||||
QString watching;
|
QString watching;
|
||||||
for (auto& dir : m_watcher.directories()) {
|
for (auto& dir : m_watcher.directories()) {
|
||||||
watching += QString("<a href=\"%1\">%1</a><br/>").arg(dir);
|
QUrl fileURL = QUrl::fromLocalFile(dir);
|
||||||
|
watching += QString("<a href=\"%1\">%2</a><br/>").arg(fileURL.toString(), dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
ui->textBrowserWatched->setText(watching);
|
ui->textBrowserWatched->setText(watching);
|
||||||
@ -194,6 +201,10 @@ void BlockedModsDialog::setupWatch()
|
|||||||
void BlockedModsDialog::watchPath(QString path, bool watch_recursive)
|
void BlockedModsDialog::watchPath(QString path, bool watch_recursive)
|
||||||
{
|
{
|
||||||
auto to_watch = QFileInfo(path);
|
auto to_watch = QFileInfo(path);
|
||||||
|
if (!to_watch.isReadable()) {
|
||||||
|
qWarning() << "[Blocked Mods Dialog] Failed to add Watch Path (unable to read):" << path;
|
||||||
|
return;
|
||||||
|
}
|
||||||
auto to_watch_path = to_watch.canonicalFilePath();
|
auto to_watch_path = to_watch.canonicalFilePath();
|
||||||
if (m_watcher.directories().contains(to_watch_path))
|
if (m_watcher.directories().contains(to_watch_path))
|
||||||
return; // don't watch the same path twice (no loops!)
|
return; // don't watch the same path twice (no loops!)
|
||||||
|
@ -146,7 +146,7 @@ void ExportInstanceDialog::doExport()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto task = makeShared<MMCZip::ExportToZipTask>(output, m_instance->instanceRoot(), files, "", true);
|
auto task = makeShared<MMCZip::ExportToZipTask>(output, m_instance->instanceRoot(), files, "", true, true);
|
||||||
|
|
||||||
connect(task.get(), &Task::failed, this,
|
connect(task.get(), &Task::failed, this,
|
||||||
[this, output](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
|
[this, output](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
|
||||||
|
@ -47,11 +47,18 @@ ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPla
|
|||||||
|
|
||||||
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
|
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
|
||||||
setWindowTitle(tr("Export Modrinth Pack"));
|
setWindowTitle(tr("Export Modrinth Pack"));
|
||||||
ui->summary->setText(instance->settings()->get("ExportSummary").toString());
|
|
||||||
|
ui->authorLabel->hide();
|
||||||
|
ui->author->hide();
|
||||||
|
|
||||||
|
ui->summary->setPlainText(instance->settings()->get("ExportSummary").toString());
|
||||||
} else {
|
} else {
|
||||||
setWindowTitle(tr("Export CurseForge Pack"));
|
setWindowTitle(tr("Export CurseForge Pack"));
|
||||||
ui->summaryLabel->setText(tr("&Author"));
|
|
||||||
ui->summary->setText(instance->settings()->get("ExportAuthor").toString());
|
ui->summaryLabel->hide();
|
||||||
|
ui->summary->hide();
|
||||||
|
|
||||||
|
ui->author->setText(instance->settings()->get("ExportAuthor").toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure a valid pack is generated
|
// ensure a valid pack is generated
|
||||||
@ -108,9 +115,13 @@ void ExportPackDialog::done(int result)
|
|||||||
auto settings = instance->settings();
|
auto settings = instance->settings();
|
||||||
settings->set("ExportName", ui->name->text());
|
settings->set("ExportName", ui->name->text());
|
||||||
settings->set("ExportVersion", ui->version->text());
|
settings->set("ExportVersion", ui->version->text());
|
||||||
settings->set(m_provider == ModPlatform::ResourceProvider::FLAME ? "ExportAuthor" : "ExportSummary", ui->summary->text());
|
|
||||||
settings->set("ExportOptionalFiles", ui->optionalFiles->isChecked());
|
settings->set("ExportOptionalFiles", ui->optionalFiles->isChecked());
|
||||||
|
|
||||||
|
if (m_provider == ModPlatform::ResourceProvider::MODRINTH)
|
||||||
|
settings->set("ExportSummary", ui->summary->toPlainText());
|
||||||
|
else
|
||||||
|
settings->set("ExportAuthor", ui->author->text());
|
||||||
|
|
||||||
if (result == Accepted) {
|
if (result == Accepted) {
|
||||||
const QString name = ui->name->text().isEmpty() ? instance->name() : ui->name->text();
|
const QString name = ui->name->text().isEmpty() ? instance->name() : ui->name->text();
|
||||||
const QString filename = FS::RemoveInvalidFilenameChars(name);
|
const QString filename = FS::RemoveInvalidFilenameChars(name);
|
||||||
@ -134,10 +145,10 @@ void ExportPackDialog::done(int result)
|
|||||||
|
|
||||||
Task* task;
|
Task* task;
|
||||||
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
|
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
|
||||||
task = new ModrinthPackExportTask(name, ui->version->text(), ui->summary->text(), ui->optionalFiles->isChecked(), instance,
|
task = new ModrinthPackExportTask(name, ui->version->text(), ui->summary->toPlainText(), ui->optionalFiles->isChecked(),
|
||||||
output, std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
|
instance, output, std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
|
||||||
} else {
|
} else {
|
||||||
task = new FlamePackExportTask(name, ui->version->text(), ui->summary->text(), ui->optionalFiles->isChecked(), instance, output,
|
task = new FlamePackExportTask(name, ui->version->text(), ui->author->text(), ui->optionalFiles->isChecked(), instance, output,
|
||||||
std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
|
std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>650</width>
|
<width>650</width>
|
||||||
<height>510</height>
|
<height>532</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeGripEnabled">
|
<property name="sizeGripEnabled">
|
||||||
@ -19,21 +19,8 @@
|
|||||||
<property name="title">
|
<property name="title">
|
||||||
<string>&Description</string>
|
<string>&Description</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
<item row="3" column="0">
|
<item>
|
||||||
<widget class="QLabel" name="summaryLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>&Summary</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>summary</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="1">
|
|
||||||
<widget class="QLineEdit" name="summary"/>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QLabel" name="nameLabel">
|
<widget class="QLabel" name="nameLabel">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Name</string>
|
<string>&Name</string>
|
||||||
@ -43,7 +30,10 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0">
|
<item>
|
||||||
|
<widget class="QLineEdit" name="name"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
<widget class="QLabel" name="versionLabel">
|
<widget class="QLabel" name="versionLabel">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Version</string>
|
<string>&Version</string>
|
||||||
@ -53,16 +43,43 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="1">
|
<item>
|
||||||
<widget class="QLineEdit" name="name"/>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="1">
|
|
||||||
<widget class="QLineEdit" name="version">
|
<widget class="QLineEdit" name="version">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>1.0.0</string>
|
<string>1.0.0</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="summaryLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Summary</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>summary</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPlainTextEdit" name="summary">
|
||||||
|
<property name="tabChangesFocus">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="authorLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Author</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>author</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="author"/>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -124,6 +141,7 @@
|
|||||||
<tabstop>name</tabstop>
|
<tabstop>name</tabstop>
|
||||||
<tabstop>version</tabstop>
|
<tabstop>version</tabstop>
|
||||||
<tabstop>summary</tabstop>
|
<tabstop>summary</tabstop>
|
||||||
|
<tabstop>author</tabstop>
|
||||||
<tabstop>files</tabstop>
|
<tabstop>files</tabstop>
|
||||||
<tabstop>optionalFiles</tabstop>
|
<tabstop>optionalFiles</tabstop>
|
||||||
</tabstops>
|
</tabstops>
|
||||||
|
@ -214,19 +214,25 @@ void ModUpdateDialog::checkCandidates()
|
|||||||
}
|
}
|
||||||
static FlameAPI api;
|
static FlameAPI api;
|
||||||
|
|
||||||
auto getRequiredBy = depTask->getRequiredBy();
|
auto dependencyExtraInfo = depTask->getExtraInfo();
|
||||||
|
|
||||||
for (auto dep : depTask->getDependecies()) {
|
for (auto dep : depTask->getDependecies()) {
|
||||||
auto changelog = dep->version.changelog;
|
auto changelog = dep->version.changelog;
|
||||||
if (dep->pack->provider == ModPlatform::ResourceProvider::FLAME)
|
if (dep->pack->provider == ModPlatform::ResourceProvider::FLAME)
|
||||||
changelog = api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt());
|
changelog = api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt());
|
||||||
auto download_task = makeShared<ResourceDownloadTask>(dep->pack, dep->version, m_mod_model);
|
auto download_task = makeShared<ResourceDownloadTask>(dep->pack, dep->version, m_mod_model);
|
||||||
CheckUpdateTask::UpdatableMod updatable = {
|
auto extraInfo = dependencyExtraInfo.value(dep->version.addonId.toString());
|
||||||
dep->pack->name, dep->version.hash, "", dep->version.version, dep->version.version_type,
|
CheckUpdateTask::UpdatableMod updatable = { dep->pack->name,
|
||||||
changelog, dep->pack->provider, download_task
|
dep->version.hash,
|
||||||
};
|
"",
|
||||||
|
dep->version.version,
|
||||||
|
dep->version.version_type,
|
||||||
|
changelog,
|
||||||
|
dep->pack->provider,
|
||||||
|
download_task,
|
||||||
|
!extraInfo.maybe_installed };
|
||||||
|
|
||||||
appendMod(updatable, getRequiredBy.value(dep->version.addonId.toString()));
|
appendMod(updatable, extraInfo.required_by);
|
||||||
m_tasks.insert(updatable.name, updatable.download);
|
m_tasks.insert(updatable.name, updatable.download);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -412,7 +418,10 @@ void ModUpdateDialog::onMetadataFailed(Mod* mod, bool try_others, ModPlatform::R
|
|||||||
void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStringList requiredBy)
|
void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStringList requiredBy)
|
||||||
{
|
{
|
||||||
auto item_top = new QTreeWidgetItem(ui->modTreeWidget);
|
auto item_top = new QTreeWidgetItem(ui->modTreeWidget);
|
||||||
item_top->setCheckState(0, Qt::CheckState::Checked);
|
item_top->setCheckState(0, info.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
|
||||||
|
if (!info.enabled) {
|
||||||
|
item_top->setToolTip(0, tr("Mod was disabled as it may be already instaled."));
|
||||||
|
}
|
||||||
item_top->setText(0, info.name);
|
item_top->setText(0, info.name);
|
||||||
item_top->setExpanded(true);
|
item_top->setExpanded(true);
|
||||||
|
|
||||||
|
@ -97,6 +97,9 @@ NewInstanceDialog::NewInstanceDialog(const QString& initialGroup,
|
|||||||
ui->verticalLayout->insertWidget(2, m_container);
|
ui->verticalLayout->insertWidget(2, m_container);
|
||||||
|
|
||||||
m_container->addButtons(m_buttons);
|
m_container->addButtons(m_buttons);
|
||||||
|
connect(m_container, &PageContainer::selectedPageChanged, this, [this](BasePage* previous, BasePage* selected) {
|
||||||
|
m_buttons->button(QDialogButtonBox::Ok)->setEnabled(creationTask && !instName().isEmpty());
|
||||||
|
});
|
||||||
|
|
||||||
// Bonk Qt over its stupid head and make sure it understands which button is the default one...
|
// Bonk Qt over its stupid head and make sure it understands which button is the default one...
|
||||||
// See: https://stackoverflow.com/questions/24556831/qbuttonbox-set-default-button
|
// See: https://stackoverflow.com/questions/24556831/qbuttonbox-set-default-button
|
||||||
|
@ -132,7 +132,7 @@ void ResourceDownloadDialog::confirm()
|
|||||||
auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourcesString()));
|
auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourcesString()));
|
||||||
confirm_dialog->retranslateUi(resourcesString());
|
confirm_dialog->retranslateUi(resourcesString());
|
||||||
|
|
||||||
QHash<QString, QStringList> getRequiredBy;
|
QHash<QString, GetModDependenciesTask::PackDependencyExtraInfo> dependencyExtraInfo;
|
||||||
if (auto task = getModDependenciesTask(); task) {
|
if (auto task = getModDependenciesTask(); task) {
|
||||||
connect(task.get(), &Task::failed, this,
|
connect(task.get(), &Task::failed, this,
|
||||||
[&](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
|
[&](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
|
||||||
@ -157,7 +157,7 @@ void ResourceDownloadDialog::confirm()
|
|||||||
} else {
|
} else {
|
||||||
for (auto dep : task->getDependecies())
|
for (auto dep : task->getDependecies())
|
||||||
addResource(dep->pack, dep->version);
|
addResource(dep->pack, dep->version);
|
||||||
getRequiredBy = task->getRequiredBy();
|
dependencyExtraInfo = task->getExtraInfo();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,9 +166,10 @@ void ResourceDownloadDialog::confirm()
|
|||||||
return QString::compare(a->getName(), b->getName(), Qt::CaseInsensitive) < 0;
|
return QString::compare(a->getName(), b->getName(), Qt::CaseInsensitive) < 0;
|
||||||
});
|
});
|
||||||
for (auto& task : selected) {
|
for (auto& task : selected) {
|
||||||
|
auto extraInfo = dependencyExtraInfo.value(task->getPack()->addonId.toString());
|
||||||
confirm_dialog->appendResource({ task->getName(), task->getFilename(), task->getCustomPath(),
|
confirm_dialog->appendResource({ task->getName(), task->getFilename(), task->getCustomPath(),
|
||||||
ProviderCaps.name(task->getProvider()), getRequiredBy.value(task->getPack()->addonId.toString()),
|
ProviderCaps.name(task->getProvider()), extraInfo.required_by,
|
||||||
task->getVersion().version_type.toString() });
|
task->getVersion().version_type.toString(), !extraInfo.maybe_installed });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (confirm_dialog->exec()) {
|
if (confirm_dialog->exec()) {
|
||||||
|
@ -35,8 +35,11 @@ auto ReviewMessageBox::create(QWidget* parent, QString&& title, QString&& icon)
|
|||||||
void ReviewMessageBox::appendResource(ResourceInformation&& info)
|
void ReviewMessageBox::appendResource(ResourceInformation&& info)
|
||||||
{
|
{
|
||||||
auto itemTop = new QTreeWidgetItem(ui->modTreeWidget);
|
auto itemTop = new QTreeWidgetItem(ui->modTreeWidget);
|
||||||
itemTop->setCheckState(0, Qt::CheckState::Checked);
|
itemTop->setCheckState(0, info.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
|
||||||
itemTop->setText(0, info.name);
|
itemTop->setText(0, info.name);
|
||||||
|
if (!info.enabled) {
|
||||||
|
itemTop->setToolTip(0, tr("Mod was disabled as it may be already instaled."));
|
||||||
|
}
|
||||||
|
|
||||||
auto filenameItem = new QTreeWidgetItem(itemTop);
|
auto filenameItem = new QTreeWidgetItem(itemTop);
|
||||||
filenameItem->setText(0, tr("Filename: %1").arg(info.filename));
|
filenameItem->setText(0, tr("Filename: %1").arg(info.filename));
|
||||||
|
@ -20,6 +20,7 @@ class ReviewMessageBox : public QDialog {
|
|||||||
QString provider;
|
QString provider;
|
||||||
QStringList required_by;
|
QStringList required_by;
|
||||||
QString version_type;
|
QString version_type;
|
||||||
|
bool enabled = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
void appendResource(ResourceInformation&& info);
|
void appendResource(ResourceInformation&& info);
|
||||||
|
@ -465,7 +465,7 @@ void InstanceView::paintEvent([[maybe_unused]] QPaintEvent* event)
|
|||||||
widWidth = m_catPixmap.width();
|
widWidth = m_catPixmap.width();
|
||||||
if (m_catPixmap.height() < widHeight)
|
if (m_catPixmap.height() < widHeight)
|
||||||
widHeight = m_catPixmap.height();
|
widHeight = m_catPixmap.height();
|
||||||
auto pixmap = m_catPixmap.scaled(widWidth, widHeight, Qt::KeepAspectRatio);
|
auto pixmap = m_catPixmap.scaled(widWidth, widHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||||
QRect rectOfPixmap = pixmap.rect();
|
QRect rectOfPixmap = pixmap.rect();
|
||||||
rectOfPixmap.moveBottomRight(this->viewport()->rect().bottomRight());
|
rectOfPixmap.moveBottomRight(this->viewport()->rect().bottomRight());
|
||||||
painter.drawPixmap(rectOfPixmap.topLeft(), pixmap);
|
painter.drawPixmap(rectOfPixmap.topLeft(), pixmap);
|
||||||
@ -482,32 +482,42 @@ void InstanceView::paintEvent([[maybe_unused]] QPaintEvent* event)
|
|||||||
|
|
||||||
if (model()->rowCount() == 0) {
|
if (model()->rowCount() == 0) {
|
||||||
painter.save();
|
painter.save();
|
||||||
const QString line1 = tr("Welcome!");
|
QString emptyString = tr("Welcome!") + "\n" + tr("Click \"Add Instance\" to get started.");
|
||||||
const QString line2 = tr("Click \"Add Instance\" to get started.");
|
|
||||||
auto rect = this->viewport()->rect();
|
|
||||||
auto font = option.font;
|
|
||||||
font.setPointSize(37);
|
|
||||||
painter.setFont(font);
|
|
||||||
auto fm = painter.fontMetrics();
|
|
||||||
|
|
||||||
if (rect.height() <= (fm.height() * 5) || rect.width() <= fm.horizontalAdvance(line2)) {
|
// calculate the rect for the overlay
|
||||||
auto s = rect.height() / (5. * fm.height());
|
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||||
auto sx = rect.width() * 1. / fm.horizontalAdvance(line2);
|
QFont font("sans", 20);
|
||||||
if (s >= sx)
|
font.setBold(true);
|
||||||
s = sx;
|
|
||||||
auto ps = font.pointSize() * s;
|
QRect bounds = viewport()->geometry();
|
||||||
if (ps <= 0)
|
bounds.moveTop(0);
|
||||||
ps = 1;
|
auto innerBounds = bounds;
|
||||||
font.setPointSize(ps);
|
innerBounds.adjust(10, 10, -10, -10);
|
||||||
painter.setFont(font);
|
|
||||||
fm = painter.fontMetrics();
|
QColor background = QApplication::palette().color(QPalette::WindowText);
|
||||||
|
QColor foreground = QApplication::palette().color(QPalette::Base);
|
||||||
|
foreground.setAlpha(190);
|
||||||
|
painter.setFont(font);
|
||||||
|
auto fontMetrics = painter.fontMetrics();
|
||||||
|
auto textRect = fontMetrics.boundingRect(innerBounds, Qt::AlignHCenter | Qt::TextWordWrap, emptyString);
|
||||||
|
textRect.moveCenter(bounds.center());
|
||||||
|
|
||||||
|
auto wrapRect = textRect;
|
||||||
|
wrapRect.adjust(-10, -10, 10, 10);
|
||||||
|
|
||||||
|
// check if we are allowed to draw in our area
|
||||||
|
if (!event->rect().intersects(wrapRect)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// text
|
painter.setBrush(QBrush(background));
|
||||||
rect.setTop(rect.top() + fm.height() * 1.5);
|
painter.setPen(foreground);
|
||||||
painter.drawText(rect, Qt::AlignHCenter, line1);
|
painter.drawRoundedRect(wrapRect, 5.0, 5.0);
|
||||||
rect.setTop(rect.top() + fm.height());
|
|
||||||
painter.drawText(rect, Qt::AlignHCenter, line2);
|
painter.setPen(foreground);
|
||||||
|
painter.setFont(font);
|
||||||
|
painter.drawText(textRect, Qt::AlignHCenter | Qt::TextWordWrap, emptyString);
|
||||||
|
|
||||||
painter.restore();
|
painter.restore();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -237,7 +237,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<spacer name="verticalSpacer_2">
|
<spacer name="verticalSpacer_FeaturesTab">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
@ -251,7 +251,7 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="generalTab">
|
<widget class="QWidget" name="userInterfaceTab">
|
||||||
<attribute name="title">
|
<attribute name="title">
|
||||||
<string>User Interface</string>
|
<string>User Interface</string>
|
||||||
</attribute>
|
</attribute>
|
||||||
@ -374,7 +374,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<spacer name="generalTabSpacer">
|
<spacer name="verticalSpacer_UserInterfaceTab">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
|
@ -109,6 +109,7 @@ void MinecraftPage::applySettings()
|
|||||||
s->set("EnableFeralGamemode", ui->enableFeralGamemodeCheck->isChecked());
|
s->set("EnableFeralGamemode", ui->enableFeralGamemodeCheck->isChecked());
|
||||||
s->set("EnableMangoHud", ui->enableMangoHud->isChecked());
|
s->set("EnableMangoHud", ui->enableMangoHud->isChecked());
|
||||||
s->set("UseDiscreteGpu", ui->useDiscreteGpuCheck->isChecked());
|
s->set("UseDiscreteGpu", ui->useDiscreteGpuCheck->isChecked());
|
||||||
|
s->set("UseZink", ui->useZink->isChecked());
|
||||||
|
|
||||||
// Game time
|
// Game time
|
||||||
s->set("ShowGameTime", ui->showGameTime->isChecked());
|
s->set("ShowGameTime", ui->showGameTime->isChecked());
|
||||||
@ -151,6 +152,7 @@ void MinecraftPage::loadSettings()
|
|||||||
ui->enableFeralGamemodeCheck->setChecked(s->get("EnableFeralGamemode").toBool());
|
ui->enableFeralGamemodeCheck->setChecked(s->get("EnableFeralGamemode").toBool());
|
||||||
ui->enableMangoHud->setChecked(s->get("EnableMangoHud").toBool());
|
ui->enableMangoHud->setChecked(s->get("EnableMangoHud").toBool());
|
||||||
ui->useDiscreteGpuCheck->setChecked(s->get("UseDiscreteGpu").toBool());
|
ui->useDiscreteGpuCheck->setChecked(s->get("UseDiscreteGpu").toBool());
|
||||||
|
ui->useZink->setChecked(s->get("UseZink").toBool());
|
||||||
|
|
||||||
#if !defined(Q_OS_LINUX)
|
#if !defined(Q_OS_LINUX)
|
||||||
ui->perfomanceGroupBox->setVisible(false);
|
ui->perfomanceGroupBox->setVisible(false);
|
||||||
|
@ -309,6 +309,16 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="useZink">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p>Use Zink, a Mesa OpenGL driver that implements OpenGL on top of Vulkan. Performance may vary depending on the situation. Note: If no suitable Vulkan driver is found, software rendering will be used.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Use Zink</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -232,10 +232,13 @@ void InstanceSettingsPage::applySettings()
|
|||||||
m_settings->set("EnableFeralGamemode", ui->enableFeralGamemodeCheck->isChecked());
|
m_settings->set("EnableFeralGamemode", ui->enableFeralGamemodeCheck->isChecked());
|
||||||
m_settings->set("EnableMangoHud", ui->enableMangoHud->isChecked());
|
m_settings->set("EnableMangoHud", ui->enableMangoHud->isChecked());
|
||||||
m_settings->set("UseDiscreteGpu", ui->useDiscreteGpuCheck->isChecked());
|
m_settings->set("UseDiscreteGpu", ui->useDiscreteGpuCheck->isChecked());
|
||||||
|
m_settings->set("UseZink", ui->useZink->isChecked());
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
m_settings->reset("EnableFeralGamemode");
|
m_settings->reset("EnableFeralGamemode");
|
||||||
m_settings->reset("EnableMangoHud");
|
m_settings->reset("EnableMangoHud");
|
||||||
m_settings->reset("UseDiscreteGpu");
|
m_settings->reset("UseDiscreteGpu");
|
||||||
|
m_settings->reset("UseZink");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Game time
|
// Game time
|
||||||
@ -354,6 +357,7 @@ void InstanceSettingsPage::loadSettings()
|
|||||||
ui->enableFeralGamemodeCheck->setChecked(m_settings->get("EnableFeralGamemode").toBool());
|
ui->enableFeralGamemodeCheck->setChecked(m_settings->get("EnableFeralGamemode").toBool());
|
||||||
ui->enableMangoHud->setChecked(m_settings->get("EnableMangoHud").toBool());
|
ui->enableMangoHud->setChecked(m_settings->get("EnableMangoHud").toBool());
|
||||||
ui->useDiscreteGpuCheck->setChecked(m_settings->get("UseDiscreteGpu").toBool());
|
ui->useDiscreteGpuCheck->setChecked(m_settings->get("UseDiscreteGpu").toBool());
|
||||||
|
ui->useZink->setChecked(m_settings->get("UseZink").toBool());
|
||||||
|
|
||||||
#if !defined(Q_OS_LINUX)
|
#if !defined(Q_OS_LINUX)
|
||||||
ui->settingsTabs->setTabVisible(ui->settingsTabs->indexOf(ui->performancePage), false);
|
ui->settingsTabs->setTabVisible(ui->settingsTabs->indexOf(ui->performancePage), false);
|
||||||
|
@ -567,6 +567,16 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="useZink">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Use Zink, a Mesa OpenGL driver that implements OpenGL on top of Vulkan. Performance may vary depending on the situation. Note: If no suitable Vulkan driver is found, software rendering will be used.</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Use Zink</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -55,7 +55,6 @@ CustomPage::CustomPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(par
|
|||||||
connect(ui->alphaFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged);
|
connect(ui->alphaFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged);
|
||||||
connect(ui->betaFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged);
|
connect(ui->betaFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged);
|
||||||
connect(ui->snapshotFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged);
|
connect(ui->snapshotFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged);
|
||||||
connect(ui->oldSnapshotFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged);
|
|
||||||
connect(ui->releaseFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged);
|
connect(ui->releaseFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged);
|
||||||
connect(ui->experimentsFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged);
|
connect(ui->experimentsFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged);
|
||||||
connect(ui->refreshBtn, &QPushButton::clicked, this, &CustomPage::refresh);
|
connect(ui->refreshBtn, &QPushButton::clicked, this, &CustomPage::refresh);
|
||||||
@ -96,13 +95,11 @@ void CustomPage::filterChanged()
|
|||||||
{
|
{
|
||||||
QStringList out;
|
QStringList out;
|
||||||
if (ui->alphaFilter->isChecked())
|
if (ui->alphaFilter->isChecked())
|
||||||
out << "(old_alpha)";
|
out << "(alpha)";
|
||||||
if (ui->betaFilter->isChecked())
|
if (ui->betaFilter->isChecked())
|
||||||
out << "(old_beta)";
|
out << "(beta)";
|
||||||
if (ui->snapshotFilter->isChecked())
|
if (ui->snapshotFilter->isChecked())
|
||||||
out << "(snapshot)";
|
out << "(snapshot)";
|
||||||
if (ui->oldSnapshotFilter->isChecked())
|
|
||||||
out << "(old_snapshot)";
|
|
||||||
if (ui->releaseFilter->isChecked())
|
if (ui->releaseFilter->isChecked())
|
||||||
out << "(release)";
|
out << "(release)";
|
||||||
if (ui->experimentsFilter->isChecked())
|
if (ui->experimentsFilter->isChecked())
|
||||||
|
@ -93,16 +93,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<widget class="QCheckBox" name="oldSnapshotFilter">
|
|
||||||
<property name="text">
|
|
||||||
<string>Old Snapshots</string>
|
|
||||||
</property>
|
|
||||||
<property name="checkable">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="betaFilter">
|
<widget class="QCheckBox" name="betaFilter">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@ -286,7 +276,6 @@
|
|||||||
<tabstop>tabWidget</tabstop>
|
<tabstop>tabWidget</tabstop>
|
||||||
<tabstop>releaseFilter</tabstop>
|
<tabstop>releaseFilter</tabstop>
|
||||||
<tabstop>snapshotFilter</tabstop>
|
<tabstop>snapshotFilter</tabstop>
|
||||||
<tabstop>oldSnapshotFilter</tabstop>
|
|
||||||
<tabstop>betaFilter</tabstop>
|
<tabstop>betaFilter</tabstop>
|
||||||
<tabstop>alphaFilter</tabstop>
|
<tabstop>alphaFilter</tabstop>
|
||||||
<tabstop>experimentsFilter</tabstop>
|
<tabstop>experimentsFilter</tabstop>
|
||||||
|
@ -123,6 +123,10 @@ void ImportPage::updateState()
|
|||||||
// need to find the download link for the modpack
|
// need to find the download link for the modpack
|
||||||
// format of url curseforge://install?addonId=IDHERE&fileId=IDHERE
|
// format of url curseforge://install?addonId=IDHERE&fileId=IDHERE
|
||||||
QUrlQuery query(url);
|
QUrlQuery query(url);
|
||||||
|
if (query.allQueryItemValues("addonId").isEmpty() || query.allQueryItemValues("fileId").isEmpty()) {
|
||||||
|
qDebug() << "Invalid curseforge link:" << url;
|
||||||
|
return;
|
||||||
|
}
|
||||||
auto addonId = query.allQueryItemValues("addonId")[0];
|
auto addonId = query.allQueryItemValues("addonId")[0];
|
||||||
auto fileId = query.allQueryItemValues("fileId")[0];
|
auto fileId = query.allQueryItemValues("fileId")[0];
|
||||||
auto array = std::make_shared<QByteArray>();
|
auto array = std::make_shared<QByteArray>();
|
||||||
@ -200,7 +204,9 @@ void ImportPage::setExtraInfo(const QMap<QString, QString>& extra_info)
|
|||||||
|
|
||||||
void ImportPage::on_modpackBtn_clicked()
|
void ImportPage::on_modpackBtn_clicked()
|
||||||
{
|
{
|
||||||
auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString();
|
const QMimeType zip = QMimeDatabase().mimeTypeForName("application/zip");
|
||||||
|
auto filter = tr("Supported files") + QString(" (%1 *.mrpack)").arg(zip.globPatterns().join(" "));
|
||||||
|
filter += ";;" + zip.filterString();
|
||||||
//: Option for filtering for *.mrpack files when importing
|
//: Option for filtering for *.mrpack files when importing
|
||||||
filter += ";;" + tr("Modrinth pack") + " (*.mrpack)";
|
filter += ";;" + tr("Modrinth pack") + " (*.mrpack)";
|
||||||
const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), filter);
|
const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), filter);
|
||||||
|
@ -90,6 +90,7 @@ void ModPage::filterMods()
|
|||||||
void ModPage::triggerSearch()
|
void ModPage::triggerSearch()
|
||||||
{
|
{
|
||||||
m_filter = m_filter_widget->getFilter();
|
m_filter = m_filter_widget->getFilter();
|
||||||
|
m_ui->packView->selectionModel()->setCurrentIndex({}, QItemSelectionModel::SelectionFlag::ClearAndSelect);
|
||||||
m_ui->packView->clearSelection();
|
m_ui->packView->clearSelection();
|
||||||
m_ui->packDescription->clear();
|
m_ui->packDescription->clear();
|
||||||
m_ui->versionSelectionBox->clear();
|
m_ui->versionSelectionBox->clear();
|
||||||
|
@ -209,7 +209,8 @@ void ResourceModel::loadEntry(QModelIndex& entry)
|
|||||||
};
|
};
|
||||||
if (!callbacks.on_fail)
|
if (!callbacks.on_fail)
|
||||||
callbacks.on_fail = [](QString reason, int) {
|
callbacks.on_fail = [](QString reason, int) {
|
||||||
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project versions:%1").arg(reason));
|
QMessageBox::critical(nullptr, tr("Error"),
|
||||||
|
tr("A network error occurred. Could not load project versions: %1").arg(reason));
|
||||||
};
|
};
|
||||||
|
|
||||||
if (auto job = m_api->getProjectVersions(std::move(args), std::move(callbacks)); job)
|
if (auto job = m_api->getProjectVersions(std::move(args), std::move(callbacks)); job)
|
||||||
@ -232,7 +233,7 @@ void ResourceModel::loadEntry(QModelIndex& entry)
|
|||||||
callbacks.on_fail = [this](QString reason) {
|
callbacks.on_fail = [this](QString reason) {
|
||||||
if (!s_running_models.constFind(this).value())
|
if (!s_running_models.constFind(this).value())
|
||||||
return;
|
return;
|
||||||
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project info:%1").arg(reason));
|
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project info: %1").arg(reason));
|
||||||
};
|
};
|
||||||
if (!callbacks.on_abort)
|
if (!callbacks.on_abort)
|
||||||
callbacks.on_abort = [this] {
|
callbacks.on_abort = [this] {
|
||||||
|
@ -23,6 +23,7 @@ ResourcePackResourcePage::ResourcePackResourcePage(ResourceDownloadDialog* dialo
|
|||||||
|
|
||||||
void ResourcePackResourcePage::triggerSearch()
|
void ResourcePackResourcePage::triggerSearch()
|
||||||
{
|
{
|
||||||
|
m_ui->packView->selectionModel()->setCurrentIndex({}, QItemSelectionModel::SelectionFlag::ClearAndSelect);
|
||||||
m_ui->packView->clearSelection();
|
m_ui->packView->clearSelection();
|
||||||
m_ui->packDescription->clear();
|
m_ui->packDescription->clear();
|
||||||
m_ui->versionSelectionBox->clear();
|
m_ui->versionSelectionBox->clear();
|
||||||
|
@ -24,6 +24,7 @@ ShaderPackResourcePage::ShaderPackResourcePage(ShaderPackDownloadDialog* dialog,
|
|||||||
|
|
||||||
void ShaderPackResourcePage::triggerSearch()
|
void ShaderPackResourcePage::triggerSearch()
|
||||||
{
|
{
|
||||||
|
m_ui->packView->selectionModel()->setCurrentIndex({}, QItemSelectionModel::SelectionFlag::ClearAndSelect);
|
||||||
m_ui->packView->clearSelection();
|
m_ui->packView->clearSelection();
|
||||||
m_ui->packDescription->clear();
|
m_ui->packDescription->clear();
|
||||||
m_ui->versionSelectionBox->clear();
|
m_ui->versionSelectionBox->clear();
|
||||||
|
@ -104,6 +104,7 @@ void ModrinthPage::retranslate()
|
|||||||
void ModrinthPage::openedImpl()
|
void ModrinthPage::openedImpl()
|
||||||
{
|
{
|
||||||
BasePage::openedImpl();
|
BasePage::openedImpl();
|
||||||
|
suggestCurrent();
|
||||||
triggerSearch();
|
triggerSearch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +36,10 @@
|
|||||||
#include "ui/themes/CatPack.h"
|
#include "ui/themes/CatPack.h"
|
||||||
#include <QDate>
|
#include <QDate>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
#include <QDirIterator>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <QImageReader>
|
||||||
|
#include <QRandomGenerator>
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
#include "Json.h"
|
#include "Json.h"
|
||||||
|
|
||||||
@ -79,7 +82,7 @@ JsonCatPack::JsonCatPack(QFileInfo& manifestInfo) : BasicCatPack(manifestInfo.di
|
|||||||
auto doc = Json::requireDocument(manifestInfo.absoluteFilePath(), "CatPack JSON file");
|
auto doc = Json::requireDocument(manifestInfo.absoluteFilePath(), "CatPack JSON file");
|
||||||
const auto root = doc.object();
|
const auto root = doc.object();
|
||||||
m_name = Json::requireString(root, "name", "Catpack name");
|
m_name = Json::requireString(root, "name", "Catpack name");
|
||||||
m_defaultPath = FS::PathCombine(path, Json::requireString(root, "default", "Default Cat"));
|
m_default_path = FS::PathCombine(path, Json::requireString(root, "default", "Default Cat"));
|
||||||
auto variants = Json::ensureArray(root, "variants", QJsonArray(), "Catpack Variants");
|
auto variants = Json::ensureArray(root, "variants", QJsonArray(), "Catpack Variants");
|
||||||
for (auto v : variants) {
|
for (auto v : variants) {
|
||||||
auto variant = Json::ensureObject(v, QJsonObject(), "Cat variant");
|
auto variant = Json::ensureObject(v, QJsonObject(), "Cat variant");
|
||||||
@ -117,5 +120,21 @@ QString JsonCatPack::path(QDate now)
|
|||||||
if (startDate <= now && now <= endDate)
|
if (startDate <= now && now <= endDate)
|
||||||
return var.path;
|
return var.path;
|
||||||
}
|
}
|
||||||
return m_defaultPath;
|
auto dInfo = QFileInfo(m_default_path);
|
||||||
|
if (!dInfo.isDir())
|
||||||
|
return m_default_path;
|
||||||
|
|
||||||
|
QStringList supportedImageFormats;
|
||||||
|
for (auto format : QImageReader::supportedImageFormats()) {
|
||||||
|
supportedImageFormats.append("*." + format);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto files = QDir(m_default_path).entryInfoList(supportedImageFormats, QDir::Files, QDir::Name);
|
||||||
|
if (files.length() == 0)
|
||||||
|
return "";
|
||||||
|
auto idx = (now.dayOfYear() - 1) % files.length();
|
||||||
|
auto isRandom = dInfo.fileName().compare("random", Qt::CaseInsensitive) == 0;
|
||||||
|
if (isRandom)
|
||||||
|
idx = QRandomGenerator::global()->bounded(0, files.length());
|
||||||
|
return files[idx].absoluteFilePath();
|
||||||
}
|
}
|
||||||
|
@ -87,6 +87,6 @@ class JsonCatPack : public BasicCatPack {
|
|||||||
QString path(QDate now);
|
QString path(QDate now);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_defaultPath;
|
QString m_default_path;
|
||||||
QList<Variant> m_variants;
|
QList<Variant> m_variants;
|
||||||
};
|
};
|
||||||
|
30
launcher/ui/themes/HintOverrideProxyStyle.cpp
Normal file
30
launcher/ui/themes/HintOverrideProxyStyle.cpp
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "HintOverrideProxyStyle.h"
|
||||||
|
|
||||||
|
int HintOverrideProxyStyle::styleHint(QStyle::StyleHint hint,
|
||||||
|
const QStyleOption* option,
|
||||||
|
const QWidget* widget,
|
||||||
|
QStyleHintReturn* returnData) const
|
||||||
|
{
|
||||||
|
if (hint == QStyle::SH_ItemView_ActivateItemOnSingleClick)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return QProxyStyle::styleHint(hint, option, widget, returnData);
|
||||||
|
}
|
34
launcher/ui/themes/HintOverrideProxyStyle.h
Normal file
34
launcher/ui/themes/HintOverrideProxyStyle.h
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QProxyStyle>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
/// Used to override platform-specific behaviours which the launcher does work well with.
|
||||||
|
class HintOverrideProxyStyle : public QProxyStyle {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
HintOverrideProxyStyle(QStyle* style) : QProxyStyle(style) {}
|
||||||
|
|
||||||
|
int styleHint(QStyle::StyleHint hint,
|
||||||
|
const QStyleOption* option = nullptr,
|
||||||
|
const QWidget* widget = nullptr,
|
||||||
|
QStyleHintReturn* returnData = nullptr) const override;
|
||||||
|
};
|
@ -2,6 +2,7 @@
|
|||||||
/*
|
/*
|
||||||
* Prism Launcher - Minecraft Launcher
|
* Prism Launcher - Minecraft Launcher
|
||||||
* Copyright (C) 2022 Tayou <git@tayou.org>
|
* Copyright (C) 2022 Tayou <git@tayou.org>
|
||||||
|
* Copyright (C) 2024 TheKodeToad <TheKodeToad@proton.me>
|
||||||
*
|
*
|
||||||
* 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
|
||||||
@ -36,12 +37,13 @@
|
|||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QStyleFactory>
|
#include <QStyleFactory>
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
#include "HintOverrideProxyStyle.h"
|
||||||
#include "rainbow.h"
|
#include "rainbow.h"
|
||||||
|
|
||||||
void ITheme::apply(bool)
|
void ITheme::apply(bool)
|
||||||
{
|
{
|
||||||
APPLICATION->setStyleSheet(QString());
|
APPLICATION->setStyleSheet(QString());
|
||||||
QApplication::setStyle(QStyleFactory::create(qtTheme()));
|
QApplication::setStyle(new HintOverrideProxyStyle(QStyleFactory::create(qtTheme())));
|
||||||
if (hasColorScheme()) {
|
if (hasColorScheme()) {
|
||||||
QApplication::setPalette(colorScheme());
|
QApplication::setPalette(colorScheme());
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
/*
|
/*
|
||||||
* Prism Launcher - Minecraft Launcher
|
* Prism Launcher - Minecraft Launcher
|
||||||
* Copyright (C) 2022 Tayou <git@tayou.org>
|
* Copyright (C) 2022 Tayou <git@tayou.org>
|
||||||
|
* Copyright (C) 2024 TheKodeToad <TheKodeToad@proton.me>
|
||||||
*
|
*
|
||||||
* 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
|
||||||
@ -37,6 +38,7 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QStyle>
|
#include <QStyle>
|
||||||
#include <QStyleFactory>
|
#include <QStyleFactory>
|
||||||
|
#include "HintOverrideProxyStyle.h"
|
||||||
#include "ThemeManager.h"
|
#include "ThemeManager.h"
|
||||||
|
|
||||||
SystemTheme::SystemTheme()
|
SystemTheme::SystemTheme()
|
||||||
@ -64,8 +66,11 @@ void SystemTheme::apply(bool initial)
|
|||||||
{
|
{
|
||||||
// See https://github.com/MultiMC/Launcher/issues/1790
|
// See https://github.com/MultiMC/Launcher/issues/1790
|
||||||
// or https://github.com/PrismLauncher/PrismLauncher/issues/490
|
// or https://github.com/PrismLauncher/PrismLauncher/issues/490
|
||||||
if (initial)
|
if (initial) {
|
||||||
|
QApplication::setStyle(new HintOverrideProxyStyle(QStyleFactory::create(qtTheme())));
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ITheme::apply(initial);
|
ITheme::apply(initial);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,8 +178,8 @@ QList<ITheme*> ThemeManager::getValidApplicationThemes()
|
|||||||
QList<CatPack*> ThemeManager::getValidCatPacks()
|
QList<CatPack*> ThemeManager::getValidCatPacks()
|
||||||
{
|
{
|
||||||
QList<CatPack*> ret;
|
QList<CatPack*> ret;
|
||||||
ret.reserve(m_catPacks.size());
|
ret.reserve(m_cat_packs.size());
|
||||||
for (auto&& [id, theme] : m_catPacks) {
|
for (auto&& [id, theme] : m_cat_packs) {
|
||||||
ret.append(theme.get());
|
ret.append(theme.get());
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
@ -244,8 +244,8 @@ void ThemeManager::applyCurrentlySelectedTheme(bool initial)
|
|||||||
|
|
||||||
QString ThemeManager::getCatPack(QString catName)
|
QString ThemeManager::getCatPack(QString catName)
|
||||||
{
|
{
|
||||||
auto catIter = m_catPacks.find(!catName.isEmpty() ? catName : APPLICATION->settings()->get("BackgroundCat").toString());
|
auto catIter = m_cat_packs.find(!catName.isEmpty() ? catName : APPLICATION->settings()->get("BackgroundCat").toString());
|
||||||
if (catIter != m_catPacks.end()) {
|
if (catIter != m_cat_packs.end()) {
|
||||||
auto& catPack = catIter->second;
|
auto& catPack = catIter->second;
|
||||||
themeDebugLog() << "applying catpack" << catPack->id();
|
themeDebugLog() << "applying catpack" << catPack->id();
|
||||||
return catPack->path();
|
return catPack->path();
|
||||||
@ -253,14 +253,14 @@ QString ThemeManager::getCatPack(QString catName)
|
|||||||
themeWarningLog() << "Tried to get invalid catPack:" << catName;
|
themeWarningLog() << "Tried to get invalid catPack:" << catName;
|
||||||
}
|
}
|
||||||
|
|
||||||
return m_catPacks.begin()->second->path();
|
return m_cat_packs.begin()->second->path();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ThemeManager::addCatPack(std::unique_ptr<CatPack> catPack)
|
QString ThemeManager::addCatPack(std::unique_ptr<CatPack> catPack)
|
||||||
{
|
{
|
||||||
QString id = catPack->id();
|
QString id = catPack->id();
|
||||||
if (m_catPacks.find(id) == m_catPacks.end())
|
if (m_cat_packs.find(id) == m_cat_packs.end())
|
||||||
m_catPacks.emplace(id, std::move(catPack));
|
m_cat_packs.emplace(id, std::move(catPack));
|
||||||
else
|
else
|
||||||
themeWarningLog() << "CatPack(" << id << ") not added to prevent id duplication";
|
themeWarningLog() << "CatPack(" << id << ") not added to prevent id duplication";
|
||||||
return id;
|
return id;
|
||||||
|
@ -61,7 +61,7 @@ class ThemeManager {
|
|||||||
QDir m_iconThemeFolder{ "iconthemes" };
|
QDir m_iconThemeFolder{ "iconthemes" };
|
||||||
QDir m_applicationThemeFolder{ "themes" };
|
QDir m_applicationThemeFolder{ "themes" };
|
||||||
QDir m_catPacksFolder{ "catpacks" };
|
QDir m_catPacksFolder{ "catpacks" };
|
||||||
std::map<QString, std::unique_ptr<CatPack>> m_catPacks;
|
std::map<QString, std::unique_ptr<CatPack>> m_cat_packs;
|
||||||
|
|
||||||
void initializeThemes();
|
void initializeThemes();
|
||||||
void initializeCatPacks();
|
void initializeCatPacks();
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
#include "LogView.h"
|
#include "LogView.h"
|
||||||
#include <QScrollBar>
|
#include <QScrollBar>
|
||||||
#include <QTextBlock>
|
#include <QTextBlock>
|
||||||
|
#include <QTextDocumentFragment>
|
||||||
|
|
||||||
LogView::LogView(QWidget* parent) : QPlainTextEdit(parent)
|
LogView::LogView(QWidget* parent) : QPlainTextEdit(parent)
|
||||||
{
|
{
|
||||||
@ -117,6 +118,9 @@ void LogView::rowsAboutToBeInserted(const QModelIndex& parent, int first, int la
|
|||||||
|
|
||||||
void LogView::rowsInserted(const QModelIndex& parent, int first, int last)
|
void LogView::rowsInserted(const QModelIndex& parent, int first, int last)
|
||||||
{
|
{
|
||||||
|
QTextDocument document;
|
||||||
|
QTextCursor cursor(&document);
|
||||||
|
|
||||||
for (int i = first; i <= last; i++) {
|
for (int i = first; i <= last; i++) {
|
||||||
auto idx = m_model->index(i, 0, parent);
|
auto idx = m_model->index(i, 0, parent);
|
||||||
auto text = m_model->data(idx, Qt::DisplayRole).toString();
|
auto text = m_model->data(idx, Qt::DisplayRole).toString();
|
||||||
@ -133,11 +137,16 @@ void LogView::rowsInserted(const QModelIndex& parent, int first, int last)
|
|||||||
if (bg.isValid()) {
|
if (bg.isValid()) {
|
||||||
format.setBackground(bg.value<QColor>());
|
format.setBackground(bg.value<QColor>());
|
||||||
}
|
}
|
||||||
auto workCursor = textCursor();
|
cursor.movePosition(QTextCursor::End);
|
||||||
workCursor.movePosition(QTextCursor::End);
|
cursor.insertText(text, format);
|
||||||
workCursor.insertText(text, format);
|
cursor.insertBlock();
|
||||||
workCursor.insertBlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QTextDocumentFragment fragment(&document);
|
||||||
|
QTextCursor workCursor = textCursor();
|
||||||
|
workCursor.movePosition(QTextCursor::End);
|
||||||
|
workCursor.insertFragment(fragment);
|
||||||
|
|
||||||
if (m_scroll && !m_scrolling) {
|
if (m_scroll && !m_scrolling) {
|
||||||
m_scrolling = true;
|
m_scrolling = true;
|
||||||
QMetaObject::invokeMethod(this, "scrollToBottom", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(this, "scrollToBottom", Qt::QueuedConnection);
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QTextObject>
|
#include <QTextObject>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
|
||||||
@ -36,6 +37,30 @@ QSizeF VariableSizedImageObject::intrinsicSize(QTextDocument* doc, int posInDocu
|
|||||||
|
|
||||||
auto image = qvariant_cast<QImage>(format.property(ImageData));
|
auto image = qvariant_cast<QImage>(format.property(ImageData));
|
||||||
auto size = image.size();
|
auto size = image.size();
|
||||||
|
if (size.isEmpty()) // can't resize an empty image
|
||||||
|
return { size };
|
||||||
|
|
||||||
|
// calculate the new image size based on the properties
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
auto widthVar = format.property(QTextFormat::ImageWidth);
|
||||||
|
if (widthVar.isValid()) {
|
||||||
|
width = widthVar.toInt();
|
||||||
|
}
|
||||||
|
auto heigthVar = format.property(QTextFormat::ImageHeight);
|
||||||
|
if (heigthVar.isValid()) {
|
||||||
|
height = heigthVar.toInt();
|
||||||
|
}
|
||||||
|
if (width != 0 && height != 0) {
|
||||||
|
size.setWidth(width);
|
||||||
|
size.setHeight(height);
|
||||||
|
} else if (width != 0) {
|
||||||
|
size.setHeight((width * size.height()) / size.width());
|
||||||
|
size.setWidth(width);
|
||||||
|
} else if (height != 0) {
|
||||||
|
size.setWidth((height * size.width()) / size.height());
|
||||||
|
size.setHeight(height);
|
||||||
|
}
|
||||||
|
|
||||||
// Get the width of the text content to make the image similar sized.
|
// Get the width of the text content to make the image similar sized.
|
||||||
// doc->textWidth() includes the margin, so we need to remove it.
|
// doc->textWidth() includes the margin, so we need to remove it.
|
||||||
@ -46,6 +71,7 @@ QSizeF VariableSizedImageObject::intrinsicSize(QTextDocument* doc, int posInDocu
|
|||||||
|
|
||||||
return { size };
|
return { size };
|
||||||
}
|
}
|
||||||
|
|
||||||
void VariableSizedImageObject::drawObject(QPainter* painter,
|
void VariableSizedImageObject::drawObject(QPainter* painter,
|
||||||
const QRectF& rect,
|
const QRectF& rect,
|
||||||
QTextDocument* doc,
|
QTextDocument* doc,
|
||||||
@ -57,7 +83,20 @@ void VariableSizedImageObject::drawObject(QPainter* painter,
|
|||||||
if (m_fetching_images.contains(image_url))
|
if (m_fetching_images.contains(image_url))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
loadImage(doc, image_url, posInDocument);
|
auto meta = std::make_shared<ImageMetadata>();
|
||||||
|
meta->posInDocument = posInDocument;
|
||||||
|
meta->url = image_url;
|
||||||
|
|
||||||
|
auto widthVar = format.property(QTextFormat::ImageWidth);
|
||||||
|
if (widthVar.isValid()) {
|
||||||
|
meta->width = widthVar.toInt();
|
||||||
|
}
|
||||||
|
auto heigthVar = format.property(QTextFormat::ImageHeight);
|
||||||
|
if (heigthVar.isValid()) {
|
||||||
|
meta->height = heigthVar.toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage(doc, meta);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,16 +111,19 @@ void VariableSizedImageObject::flush()
|
|||||||
m_fetching_images.clear();
|
m_fetching_images.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VariableSizedImageObject::parseImage(QTextDocument* doc, QImage image, int posInDocument)
|
void VariableSizedImageObject::parseImage(QTextDocument* doc, std::shared_ptr<ImageMetadata> meta)
|
||||||
{
|
{
|
||||||
QTextCursor cursor(doc);
|
QTextCursor cursor(doc);
|
||||||
cursor.setPosition(posInDocument);
|
cursor.setPosition(meta->posInDocument);
|
||||||
cursor.setKeepPositionOnInsert(true);
|
cursor.setKeepPositionOnInsert(true);
|
||||||
|
|
||||||
auto image_char_format = cursor.charFormat();
|
auto image_char_format = cursor.charFormat();
|
||||||
|
|
||||||
image_char_format.setObjectType(QTextFormat::ImageObject);
|
image_char_format.setObjectType(QTextFormat::ImageObject);
|
||||||
image_char_format.setProperty(ImageData, image);
|
image_char_format.setProperty(ImageData, meta->image);
|
||||||
|
image_char_format.setProperty(QTextFormat::ImageName, meta->url.toDisplayString());
|
||||||
|
image_char_format.setProperty(QTextFormat::ImageWidth, meta->width);
|
||||||
|
image_char_format.setProperty(QTextFormat::ImageHeight, meta->height);
|
||||||
|
|
||||||
// Qt doesn't allow us to modify the properties of an existing object in the document.
|
// Qt doesn't allow us to modify the properties of an existing object in the document.
|
||||||
// So we remove the old one and add the new one with the ImageData property set.
|
// So we remove the old one and add the new one with the ImageData property set.
|
||||||
@ -89,23 +131,24 @@ void VariableSizedImageObject::parseImage(QTextDocument* doc, QImage image, int
|
|||||||
cursor.insertText(QString(QChar::ObjectReplacementCharacter), image_char_format);
|
cursor.insertText(QString(QChar::ObjectReplacementCharacter), image_char_format);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VariableSizedImageObject::loadImage(QTextDocument* doc, const QUrl& source, int posInDocument)
|
void VariableSizedImageObject::loadImage(QTextDocument* doc, std::shared_ptr<ImageMetadata> meta)
|
||||||
{
|
{
|
||||||
m_fetching_images.insert(source);
|
m_fetching_images.insert(meta->url);
|
||||||
|
|
||||||
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry(
|
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry(
|
||||||
m_meta_entry,
|
m_meta_entry,
|
||||||
QString("images/%1").arg(QString(QCryptographicHash::hash(source.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex())));
|
QString("images/%1").arg(QString(QCryptographicHash::hash(meta->url.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex())));
|
||||||
|
|
||||||
auto job = new NetJob(QString("Load Image: %1").arg(source.fileName()), APPLICATION->network());
|
auto job = new NetJob(QString("Load Image: %1").arg(meta->url.fileName()), APPLICATION->network());
|
||||||
job->addNetAction(Net::ApiDownload::makeCached(source, entry));
|
job->addNetAction(Net::ApiDownload::makeCached(meta->url, entry));
|
||||||
|
|
||||||
auto full_entry_path = entry->getFullPath();
|
auto full_entry_path = entry->getFullPath();
|
||||||
auto source_url = source;
|
auto source_url = meta->url;
|
||||||
auto loadImage = [this, doc, full_entry_path, source_url, posInDocument](const QImage& image) {
|
auto loadImage = [this, doc, full_entry_path, source_url, meta](const QImage& image) {
|
||||||
doc->addResource(QTextDocument::ImageResource, source_url, image);
|
doc->addResource(QTextDocument::ImageResource, source_url, image);
|
||||||
|
|
||||||
parseImage(doc, image, posInDocument);
|
meta->image = image;
|
||||||
|
parseImage(doc, meta);
|
||||||
|
|
||||||
// This size hack is needed to prevent the content from being laid out in an area smaller
|
// This size hack is needed to prevent the content from being laid out in an area smaller
|
||||||
// than the total width available (weird).
|
// than the total width available (weird).
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QTextObjectInterface>
|
#include <QTextObjectInterface>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
/** Custom image text object to be used instead of the normal one in ProjectDescriptionPage.
|
/** Custom image text object to be used instead of the normal one in ProjectDescriptionPage.
|
||||||
*
|
*
|
||||||
@ -32,6 +33,14 @@ class VariableSizedImageObject final : public QObject, public QTextObjectInterfa
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_INTERFACES(QTextObjectInterface)
|
Q_INTERFACES(QTextObjectInterface)
|
||||||
|
|
||||||
|
struct ImageMetadata {
|
||||||
|
int posInDocument;
|
||||||
|
QUrl url;
|
||||||
|
QImage image;
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
QSizeF intrinsicSize(QTextDocument* doc, int posInDocument, const QTextFormat& format) override;
|
QSizeF intrinsicSize(QTextDocument* doc, int posInDocument, const QTextFormat& format) override;
|
||||||
void drawObject(QPainter* painter, const QRectF& rect, QTextDocument* doc, int posInDocument, const QTextFormat& format) override;
|
void drawObject(QPainter* painter, const QRectF& rect, QTextDocument* doc, int posInDocument, const QTextFormat& format) override;
|
||||||
@ -49,13 +58,13 @@ class VariableSizedImageObject final : public QObject, public QTextObjectInterfa
|
|||||||
private:
|
private:
|
||||||
/** Adds the image to the document, in the given position.
|
/** Adds the image to the document, in the given position.
|
||||||
*/
|
*/
|
||||||
void parseImage(QTextDocument* doc, QImage image, int posInDocument);
|
void parseImage(QTextDocument* doc, std::shared_ptr<ImageMetadata> meta);
|
||||||
|
|
||||||
/** Loads an image from an external source, and adds it to the document.
|
/** Loads an image from an external source, and adds it to the document.
|
||||||
*
|
*
|
||||||
* This uses m_meta_entry to cache the image.
|
* This uses m_meta_entry to cache the image.
|
||||||
*/
|
*/
|
||||||
void loadImage(QTextDocument* doc, const QUrl& source, int posInDocument);
|
void loadImage(QTextDocument* doc, std::shared_ptr<ImageMetadata> meta);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_meta_entry;
|
QString m_meta_entry;
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 5ba25ff40eba44c811f79ab6a792baf945b8307c
|
Subproject commit 8fbf029685482827828b5858444157052f1b0a5f
|
@ -1 +1 @@
|
|||||||
Subproject commit 8a2edd6d92ed820521d42c94d179462bf06b5ed3
|
Subproject commit 2fc4b463759e043476fc0036da094e5877e3dd50
|
@ -58,10 +58,17 @@ import org.prismlauncher.utils.Parameters;
|
|||||||
import org.prismlauncher.utils.ReflectionUtils;
|
import org.prismlauncher.utils.ReflectionUtils;
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandle;
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public final class StandardLauncher extends AbstractLauncher {
|
public final class StandardLauncher extends AbstractLauncher {
|
||||||
|
private final boolean quickPlaySupported;
|
||||||
|
|
||||||
public StandardLauncher(Parameters params) {
|
public StandardLauncher(Parameters params) {
|
||||||
super(params);
|
super(params);
|
||||||
|
|
||||||
|
List<String> traits = params.getList("traits", Collections.<String>emptyList());
|
||||||
|
quickPlaySupported = traits.contains("feature:is_quick_play_multiplayer");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -76,10 +83,16 @@ public final class StandardLauncher extends AbstractLauncher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (serverAddress != null) {
|
if (serverAddress != null) {
|
||||||
gameArgs.add("--server");
|
if (quickPlaySupported) {
|
||||||
gameArgs.add(serverAddress);
|
// as of 23w14a
|
||||||
gameArgs.add("--port");
|
gameArgs.add("--quickPlayMultiplayer");
|
||||||
gameArgs.add(serverPort);
|
gameArgs.add(serverAddress + ':' + serverPort);
|
||||||
|
} else {
|
||||||
|
gameArgs.add("--server");
|
||||||
|
gameArgs.add(serverAddress);
|
||||||
|
gameArgs.add("--port");
|
||||||
|
gameArgs.add(serverPort);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// find and invoke the main method
|
// find and invoke the main method
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit a5e8fd52b8bf4ab5d5bcc042b2a247867589985f
|
Subproject commit 23b955121b8217c1c348a9ed2483167a6f3ff4ad
|
@ -1 +1 @@
|
|||||||
Subproject commit 6117161af08e366c37499895b00ef62f93adc345
|
Subproject commit 9d3aa3ee948c1cde5a9f873ecbc3bb229c1182ee
|
@ -15,6 +15,7 @@
|
|||||||
openal,
|
openal,
|
||||||
jdk8,
|
jdk8,
|
||||||
jdk17,
|
jdk17,
|
||||||
|
jdk21,
|
||||||
gamemode,
|
gamemode,
|
||||||
flite,
|
flite,
|
||||||
mesa-demos,
|
mesa-demos,
|
||||||
@ -24,7 +25,7 @@
|
|||||||
gamemodeSupport ? stdenv.isLinux,
|
gamemodeSupport ? stdenv.isLinux,
|
||||||
textToSpeechSupport ? stdenv.isLinux,
|
textToSpeechSupport ? stdenv.isLinux,
|
||||||
controllerSupport ? stdenv.isLinux,
|
controllerSupport ? stdenv.isLinux,
|
||||||
jdks ? [jdk17 jdk8],
|
jdks ? [jdk21 jdk17 jdk8],
|
||||||
additionalLibs ? [],
|
additionalLibs ? [],
|
||||||
additionalPrograms ? [],
|
additionalPrograms ? [],
|
||||||
}: let
|
}: let
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
|
||||||
|
<ws2:longPathAware>true</ws2:longPathAware>
|
||||||
|
</windowsSettings>
|
||||||
|
</application>
|
||||||
<assemblyIdentity name="PrismLauncher.Application.1" type="win32" version="@Launcher_VERSION_NAME4@" />
|
<assemblyIdentity name="PrismLauncher.Application.1" type="win32" version="@Launcher_VERSION_NAME4@" />
|
||||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
<security>
|
<security>
|
||||||
|
Loading…
Reference in New Issue
Block a user