Merge remote-tracking branch 'upstream/develop' into rework-settings
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
This commit is contained in:
commit
618e6bd96b
295 changed files with 11681 additions and 2609 deletions
2
.envrc
2
.envrc
|
@ -1,2 +1,2 @@
|
|||
use flake
|
||||
use nix
|
||||
watch_file nix/*.nix
|
||||
|
|
|
@ -5,3 +5,9 @@ bbb3b3e6f6e3c0f95873f22e6d0a4aaf350f49d9
|
|||
|
||||
# (nix) alejandra -> nixfmt
|
||||
4c81d8c53d09196426568c4a31a4e752ed05397a
|
||||
|
||||
# reformat codebase
|
||||
1d468ac35ad88d8c77cc83f25e3704d9bd7df01b
|
||||
|
||||
# format a part of codebase
|
||||
5c8481a118c8fefbfe901001d7828eaf6866eac4
|
||||
|
|
103
.github/actions/get-merge-commit/action.yml
vendored
Normal file
103
.github/actions/get-merge-commit/action.yml
vendored
Normal file
|
@ -0,0 +1,103 @@
|
|||
# This file incorporates work covered by the following copyright and
|
||||
# permission notice
|
||||
#
|
||||
# Copyright (c) 2003-2025 Eelco Dolstra and the Nixpkgs/NixOS contributors
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
name: Get merge commit
|
||||
description: Get a merge commit of a given pull request
|
||||
|
||||
inputs:
|
||||
repository:
|
||||
description: Repository containing the pull request
|
||||
required: false
|
||||
pull-request-id:
|
||||
description: ID of a pull request
|
||||
required: true
|
||||
|
||||
outputs:
|
||||
merge-commit-sha:
|
||||
description: Git SHA of a merge commit
|
||||
value: ${{ steps.query.outputs.merge-commit-sha }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
|
||||
steps:
|
||||
- name: Wait for GitHub to report merge commit
|
||||
id: query
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_REPO: ${{ inputs.repository || github.repository }}
|
||||
PR_ID: ${{ inputs.pull-request-id }}
|
||||
# https://github.com/NixOS/nixpkgs/blob/8f77f3600f1ee775b85dc2c72fd842768e486ec9/ci/get-merge-commit.sh
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
log() {
|
||||
echo "$@" >&2
|
||||
}
|
||||
|
||||
# Retry the API query this many times
|
||||
retryCount=5
|
||||
# Start with 5 seconds, but double every retry
|
||||
retryInterval=5
|
||||
|
||||
while true; do
|
||||
log "Checking whether the pull request can be merged"
|
||||
prInfo=$(gh api \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"/repos/$GITHUB_REPO/pulls/$PR_ID")
|
||||
|
||||
# Non-open PRs won't have their mergeability computed no matter what
|
||||
state=$(jq -r .state <<<"$prInfo")
|
||||
if [[ "$state" != open ]]; then
|
||||
log "PR is not open anymore"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mergeable=$(jq -r .mergeable <<<"$prInfo")
|
||||
if [[ "$mergeable" == "null" ]]; then
|
||||
if ((retryCount == 0)); then
|
||||
log "Not retrying anymore. It's likely that GitHub is having internal issues: check https://www.githubstatus.com/"
|
||||
exit 3
|
||||
else
|
||||
((retryCount -= 1)) || true
|
||||
|
||||
# null indicates that GitHub is still computing whether it's mergeable
|
||||
# Wait a couple seconds before trying again
|
||||
log "GitHub is still computing whether this PR can be merged, waiting $retryInterval seconds before trying again ($retryCount retries left)"
|
||||
sleep "$retryInterval"
|
||||
|
||||
((retryInterval *= 2)) || true
|
||||
fi
|
||||
else
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$mergeable" == "true" ]]; then
|
||||
echo "merge-commit-sha=$(jq -r .merge_commit_sha <<<"$prInfo")" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "# 🚨 The PR has a merge conflict!" >> "$GITHUB_STEP_SUMMARY"
|
||||
exit 2
|
||||
fi
|
124
.github/actions/package/linux/action.yml
vendored
Normal file
124
.github/actions/package/linux/action.yml
vendored
Normal file
|
@ -0,0 +1,124 @@
|
|||
name: Package for Linux
|
||||
description: Create Linux packages for Prism Launcher
|
||||
|
||||
inputs:
|
||||
version:
|
||||
description: Launcher version
|
||||
required: true
|
||||
build-type:
|
||||
description: Type for the build
|
||||
required: true
|
||||
default: Debug
|
||||
artifact-name:
|
||||
description: Name of the uploaded artifact
|
||||
required: true
|
||||
default: Linux
|
||||
cmake-preset:
|
||||
description: Base CMake preset previously used for the build
|
||||
required: true
|
||||
default: linux
|
||||
qt-version:
|
||||
description: Version of Qt to use
|
||||
required: true
|
||||
gpg-private-key:
|
||||
description: Private key for AppImage signing
|
||||
required: false
|
||||
gpg-private-key-id:
|
||||
description: ID for the gpg-private-key, to select the signing key
|
||||
required: false
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
|
||||
steps:
|
||||
- name: Package AppImage
|
||||
shell: bash
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
BUILD_DIR: build
|
||||
INSTALL_APPIMAGE_DIR: install-appdir
|
||||
|
||||
GPG_PRIVATE_KEY: ${{ inputs.gpg-private-key }}
|
||||
run: |
|
||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_APPIMAGE_DIR }}/usr
|
||||
|
||||
mv ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.metainfo.xml ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.appdata.xml
|
||||
export "NO_APPSTREAM=1" # we have to skip appstream checking because appstream on ubuntu 20.04 is outdated
|
||||
|
||||
export OUTPUT="PrismLauncher-Linux-x86_64.AppImage"
|
||||
|
||||
chmod +x linuxdeploy-*.AppImage
|
||||
|
||||
mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib
|
||||
mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines
|
||||
|
||||
cp -r ${{ runner.workspace }}/Qt/${{ inputs.qt-version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines
|
||||
|
||||
cp /usr/lib/x86_64-linux-gnu/libcrypto.so.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
||||
cp /usr/lib/x86_64-linux-gnu/libssl.so.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
||||
cp /usr/lib/x86_64-linux-gnu/libOpenGL.so.0* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
||||
|
||||
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib"
|
||||
export LD_LIBRARY_PATH
|
||||
|
||||
chmod +x AppImageUpdate-x86_64.AppImage
|
||||
cp AppImageUpdate-x86_64.AppImage ${{ env.INSTALL_APPIMAGE_DIR }}/usr/bin
|
||||
|
||||
export UPDATE_INFORMATION="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|PrismLauncher-Linux-x86_64.AppImage.zsync"
|
||||
|
||||
if [ '${{ inputs.gpg-private-key-id }}' != '' ]; then
|
||||
export SIGN=1
|
||||
export SIGN_KEY=${{ inputs.gpg-private-key-id }}
|
||||
mkdir -p ~/.gnupg/
|
||||
echo "$GPG_PRIVATE_KEY" > ~/.gnupg/private.key
|
||||
gpg --import ~/.gnupg/private.key
|
||||
else
|
||||
echo ":warning: Skipped code signing for Linux AppImage, as gpg key was not present." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
./linuxdeploy-x86_64.AppImage --appdir ${{ env.INSTALL_APPIMAGE_DIR }} --output appimage --plugin qt -i ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/icons/hicolor/scalable/apps/org.prismlauncher.PrismLauncher.svg
|
||||
|
||||
mv "PrismLauncher-Linux-x86_64.AppImage" "PrismLauncher-Linux-${{ env.VERSION }}-${{ inputs.build-type }}-x86_64.AppImage"
|
||||
|
||||
- name: Package portable tarball
|
||||
shell: bash
|
||||
env:
|
||||
BUILD_DIR: build
|
||||
|
||||
CMAKE_PRESET: ${{ inputs.cmake-preset }}
|
||||
|
||||
INSTALL_PORTABLE_DIR: install-portable
|
||||
run: |
|
||||
cmake --preset "$CMAKE_PRESET" -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_PORTABLE_DIR }} -DINSTALL_BUNDLE=full
|
||||
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.* ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||
cp /usr/lib/x86_64-linux-gnu/libssl.so.* ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||
cp /usr/lib/x86_64-linux-gnu/libffi.so.*.* ${{ 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 *
|
||||
|
||||
- name: Upload binary tarball
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PrismLauncher-${{ inputs.artifact-name }}-Qt6-Portable-${{ inputs.version }}-${{ inputs.build-type }}
|
||||
path: PrismLauncher-portable.tar.gz
|
||||
|
||||
- name: Upload AppImage
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PrismLauncher-${{ inputs.artifact-name }}-${{ inputs.version }}-${{ inputs.build-type }}-x86_64.AppImage
|
||||
path: PrismLauncher-${{ runner.os }}-${{ inputs.version }}-${{ inputs.build-type }}-x86_64.AppImage
|
||||
|
||||
- name: Upload AppImage Zsync
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PrismLauncher-${{ inputs.artifact-name }}-${{ inputs.version }}-${{ inputs.build-type }}-x86_64.AppImage.zsync
|
||||
path: PrismLauncher-Linux-x86_64.AppImage.zsync
|
121
.github/actions/package/macos/action.yml
vendored
Normal file
121
.github/actions/package/macos/action.yml
vendored
Normal file
|
@ -0,0 +1,121 @@
|
|||
name: Package for macOS
|
||||
description: Create a macOS package for Prism Launcher
|
||||
|
||||
inputs:
|
||||
version:
|
||||
description: Launcher version
|
||||
required: true
|
||||
build-type:
|
||||
description: Type for the build
|
||||
required: true
|
||||
default: Debug
|
||||
artifact-name:
|
||||
description: Name of the uploaded artifact
|
||||
required: true
|
||||
default: macOS
|
||||
apple-codesign-cert:
|
||||
description: Certificate for signing macOS builds
|
||||
required: false
|
||||
apple-codesign-password:
|
||||
description: Password for signing macOS builds
|
||||
required: false
|
||||
apple-codesign-id:
|
||||
description: Certificate ID for signing macOS builds
|
||||
required: false
|
||||
apple-notarize-apple-id:
|
||||
description: Apple ID used for notarizing macOS builds
|
||||
required: false
|
||||
apple-notarize-team-id:
|
||||
description: Team ID used for notarizing macOS builds
|
||||
required: false
|
||||
apple-notarize-password:
|
||||
description: Password used for notarizing macOS builds
|
||||
required: false
|
||||
sparkle-ed25519-key:
|
||||
description: Private key for signing Sparkle updates
|
||||
required: false
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
|
||||
steps:
|
||||
- name: Fetch codesign certificate
|
||||
shell: bash
|
||||
run: |
|
||||
echo '${{ inputs.apple-codesign-cert }}' | base64 --decode > codesign.p12
|
||||
if [ -n '${{ inputs.apple-codesign-id }}' ]; then
|
||||
security create-keychain -p '${{ inputs.apple-codesign-password }}' build.keychain
|
||||
security default-keychain -s build.keychain
|
||||
security unlock-keychain -p '${{ inputs.apple-codesign-password }}' build.keychain
|
||||
security import codesign.p12 -k build.keychain -P '${{ inputs.apple-codesign-password }}' -T /usr/bin/codesign
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k '${{ inputs.apple-codesign-password }}' build.keychain
|
||||
else
|
||||
echo ":warning: Using ad-hoc code signing for macOS, as certificate was not present." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
- name: Package
|
||||
shell: bash
|
||||
env:
|
||||
BUILD_DIR: build
|
||||
INSTALL_DIR: install
|
||||
run: |
|
||||
cmake --install ${{ env.BUILD_DIR }}
|
||||
|
||||
cd ${{ env.INSTALL_DIR }}
|
||||
chmod +x "PrismLauncher.app/Contents/MacOS/prismlauncher"
|
||||
|
||||
if [ -n '${{ inputs.apple-codesign-id }}' ]; then
|
||||
APPLE_CODESIGN_ID='${{ inputs.apple-codesign-id }}'
|
||||
ENTITLEMENTS_FILE='../program_info/App.entitlements'
|
||||
else
|
||||
APPLE_CODESIGN_ID='-'
|
||||
ENTITLEMENTS_FILE='../program_info/AdhocSignedApp.entitlements'
|
||||
fi
|
||||
|
||||
sudo codesign --sign "$APPLE_CODESIGN_ID" --deep --force --entitlements "$ENTITLEMENTS_FILE" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
|
||||
mv "PrismLauncher.app" "Prism Launcher.app"
|
||||
|
||||
- name: Notarize
|
||||
shell: bash
|
||||
env:
|
||||
INSTALL_DIR: install
|
||||
run: |
|
||||
cd ${{ env.INSTALL_DIR }}
|
||||
|
||||
if [ -n '${{ inputs.apple-notarize-password }}' ]; then
|
||||
ditto -c -k --sequesterRsrc --keepParent "Prism Launcher.app" ../PrismLauncher.zip
|
||||
xcrun notarytool submit ../PrismLauncher.zip \
|
||||
--wait --progress \
|
||||
--apple-id '${{ inputs.apple-notarize-apple-id }}' \
|
||||
--team-id '${{ inputs.apple-notarize-team-id }}' \
|
||||
--password '${{ inputs.apple-notarize-password }}'
|
||||
|
||||
xcrun stapler staple "Prism Launcher.app"
|
||||
else
|
||||
echo ":warning: Skipping notarization as credentials are not present." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
ditto -c -k --sequesterRsrc --keepParent "Prism Launcher.app" ../PrismLauncher.zip
|
||||
|
||||
- name: Make Sparkle signature
|
||||
shell: bash
|
||||
run: |
|
||||
if [ '${{ inputs.sparkle-ed25519-key }}' != '' ]; then
|
||||
echo '${{ inputs.sparkle-ed25519-key }}' > ed25519-priv.pem
|
||||
signature=$(/opt/homebrew/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.zip -inkey ed25519-priv.pem | openssl base64 | tr -d \\n)
|
||||
rm ed25519-priv.pem
|
||||
cat >> $GITHUB_STEP_SUMMARY << EOF
|
||||
### Artifact Information :information_source:
|
||||
- :memo: Sparkle Signature (ed25519): \`$signature\`
|
||||
EOF
|
||||
else
|
||||
cat >> $GITHUB_STEP_SUMMARY << EOF
|
||||
### Artifact Information :information_source:
|
||||
- :warning: Sparkle Signature (ed25519): No private key available (likely a pull request or fork)
|
||||
EOF
|
||||
fi
|
||||
|
||||
- name: Upload binary tarball
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PrismLauncher-${{ inputs.artifact-name }}-${{ inputs.version }}-${{ inputs.build-type }}
|
||||
path: PrismLauncher.zip
|
143
.github/actions/package/windows/action.yml
vendored
Normal file
143
.github/actions/package/windows/action.yml
vendored
Normal file
|
@ -0,0 +1,143 @@
|
|||
name: Package for Windows
|
||||
description: Create a Windows package for Prism Launcher
|
||||
|
||||
inputs:
|
||||
version:
|
||||
description: Launcher version
|
||||
required: true
|
||||
build-type:
|
||||
description: Type for the build
|
||||
required: true
|
||||
default: Debug
|
||||
artifact-name:
|
||||
description: Name of the uploaded artifact
|
||||
required: true
|
||||
msystem:
|
||||
description: MSYS2 subsystem to use
|
||||
required: true
|
||||
default: false
|
||||
windows-codesign-cert:
|
||||
description: Certificate for signing Windows builds
|
||||
required: false
|
||||
windows-codesign-password:
|
||||
description: Password for signing Windows builds
|
||||
required: false
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
|
||||
steps:
|
||||
- name: Package (MinGW)
|
||||
if: ${{ inputs.msystem != '' }}
|
||||
shell: msys2 {0}
|
||||
env:
|
||||
BUILD_DIR: build
|
||||
INSTALL_DIR: install
|
||||
run: |
|
||||
cmake --install ${{ env.BUILD_DIR }}
|
||||
touch ${{ env.INSTALL_DIR }}/manifest.txt
|
||||
for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_DIR }}/manifest.txt
|
||||
|
||||
- name: Package (MSVC)
|
||||
if: ${{ inputs.msystem == '' }}
|
||||
shell: pwsh
|
||||
env:
|
||||
BUILD_DIR: build
|
||||
INSTALL_DIR: install
|
||||
run: |
|
||||
cmake --install ${{ env.BUILD_DIR }} --config ${{ inputs.build-type }}
|
||||
|
||||
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
|
||||
|
||||
- name: Fetch codesign certificate
|
||||
shell: bash # yes, we are not using MSYS2 or PowerShell here
|
||||
run: |
|
||||
echo '${{ inputs.windows-codesign-cert }}' | base64 --decode > codesign.pfx
|
||||
|
||||
- name: Sign executable
|
||||
shell: pwsh
|
||||
env:
|
||||
INSTALL_DIR: install
|
||||
run: |
|
||||
if (Get-Content ./codesign.pfx){
|
||||
cd ${{ env.INSTALL_DIR }}
|
||||
# We ship the exact same executable for portable and non-portable editions, so signing just once is fine
|
||||
SignTool sign /fd sha256 /td sha256 /f ../codesign.pfx /p '${{ inputs.windows-codesign-password }}' /tr http://timestamp.digicert.com prismlauncher.exe prismlauncher_updater.exe prismlauncher_filelink.exe
|
||||
} else {
|
||||
":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY
|
||||
}
|
||||
|
||||
- name: Package (MinGW, portable)
|
||||
if: ${{ inputs.msystem != '' }}
|
||||
shell: msys2 {0}
|
||||
env:
|
||||
BUILD_DIR: build
|
||||
INSTALL_DIR: install
|
||||
INSTALL_PORTABLE_DIR: install-portable
|
||||
run: |
|
||||
cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead
|
||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
|
||||
for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt
|
||||
|
||||
- name: Package (MSVC, portable)
|
||||
if: ${{ inputs.msystem == '' }}
|
||||
shell: pwsh
|
||||
env:
|
||||
BUILD_DIR: build
|
||||
INSTALL_DIR: install
|
||||
INSTALL_PORTABLE_DIR: install-portable
|
||||
run: |
|
||||
cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead
|
||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
|
||||
|
||||
Get-ChildItem ${{ env.INSTALL_PORTABLE_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_PORTABLE_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt
|
||||
|
||||
- name: Package (installer)
|
||||
shell: pwsh
|
||||
env:
|
||||
BUILD_DIR: build
|
||||
INSTALL_DIR: install
|
||||
|
||||
NSCURL_VERSION: "v24.9.26.122"
|
||||
NSCURL_SHA256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0"
|
||||
run: |
|
||||
New-Item -Name NSISPlugins -ItemType Directory
|
||||
Invoke-Webrequest https://github.com/negrutiu/nsis-nscurl/releases/download/"${{ env.NSCURL_VERSION }}"/NScurl.zip -OutFile NSISPlugins\NScurl.zip
|
||||
$nscurl_hash = Get-FileHash NSISPlugins\NScurl.zip -Algorithm Sha256 | Select-Object -ExpandProperty Hash
|
||||
if ( $nscurl_hash -ne "${{ env.nscurl_sha256 }}") {
|
||||
echo "::error:: NSCurl.zip sha256 mismatch"
|
||||
exit 1
|
||||
}
|
||||
Expand-Archive -Path NSISPlugins\NScurl.zip -DestinationPath NSISPlugins\NScurl
|
||||
|
||||
cd ${{ env.INSTALL_DIR }}
|
||||
makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi"
|
||||
|
||||
- name: Sign installer
|
||||
shell: pwsh
|
||||
run: |
|
||||
if (Get-Content ./codesign.pfx){
|
||||
SignTool sign /fd sha256 /td sha256 /f codesign.pfx /p '${{ inputs.windows-codesign-password }}' /tr http://timestamp.digicert.com PrismLauncher-Setup.exe
|
||||
} else {
|
||||
":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY
|
||||
}
|
||||
|
||||
- name: Upload binary zip
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PrismLauncher-${{ inputs.artifact-name }}-${{ inputs.version }}-${{ inputs.build-type }}
|
||||
path: install/**
|
||||
|
||||
- name: Upload portable zip
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PrismLauncher-${{ inputs.artifact-name }}-Portable-${{ inputs.version }}-${{ inputs.build-type }}
|
||||
path: install-portable/**
|
||||
|
||||
- name: Upload installer
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PrismLauncher-${{ inputs.artifact-name }}-Setup-${{ inputs.version }}-${{ inputs.build-type }}
|
||||
path: PrismLauncher-Setup.exe
|
78
.github/actions/setup-dependencies/action.yml
vendored
Normal file
78
.github/actions/setup-dependencies/action.yml
vendored
Normal file
|
@ -0,0 +1,78 @@
|
|||
name: Setup Dependencies
|
||||
description: Install and setup dependencies for building Prism Launcher
|
||||
|
||||
inputs:
|
||||
build-type:
|
||||
description: Type for the build
|
||||
required: true
|
||||
default: Debug
|
||||
msystem:
|
||||
description: MSYS2 subsystem to use
|
||||
required: false
|
||||
vcvars-arch:
|
||||
description: Visual Studio architecture to use
|
||||
required: false
|
||||
qt-architecture:
|
||||
description: Qt architecture
|
||||
required: false
|
||||
qt-version:
|
||||
description: Version of Qt to use
|
||||
required: true
|
||||
default: 6.8.1
|
||||
|
||||
outputs:
|
||||
build-type:
|
||||
description: Type of build used
|
||||
value: ${{ inputs.build-type }}
|
||||
qt-version:
|
||||
description: Version of Qt used
|
||||
value: ${{ inputs.qt-version }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
|
||||
steps:
|
||||
- name: Setup Linux dependencies
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
uses: ./.github/actions/setup-dependencies/linux
|
||||
|
||||
- name: Setup macOS dependencies
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
uses: ./.github/actions/setup-dependencies/macos
|
||||
|
||||
- name: Setup Windows dependencies
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
uses: ./.github/actions/setup-dependencies/windows
|
||||
with:
|
||||
build-type: ${{ inputs.build-type }}
|
||||
msystem: ${{ inputs.msystem }}
|
||||
vcvars-arch: ${{ inputs.vcvars-arch }}
|
||||
|
||||
# TODO(@getchoo): Get this working on MSYS2!
|
||||
- name: Setup ccache
|
||||
if: ${{ (runner.os != 'Windows' || inputs.msystem == '') && inputs.build-type == 'Debug' }}
|
||||
uses: hendrikmuhs/ccache-action@v1.2.18
|
||||
with:
|
||||
variant: ${{ runner.os == 'Windows' && 'sccache' || 'ccache' }}
|
||||
create-symlink: ${{ runner.os != 'Windows' }}
|
||||
key: ${{ runner.os }}-qt${{ inputs.qt_ver }}-${{ inputs.architecture }}
|
||||
|
||||
- name: Use ccache on debug builds
|
||||
if: ${{ inputs.build-type == 'Debug' }}
|
||||
shell: bash
|
||||
env:
|
||||
# Only use sccache on MSVC
|
||||
CCACHE_VARIANT: ${{ (runner.os == 'Windows' && inputs.msystem == '') && 'sccache' || 'ccache' }}
|
||||
run: |
|
||||
echo "CMAKE_C_COMPILER_LAUNCHER=$CCACHE_VARIANT" >> "$GITHUB_ENV"
|
||||
echo "CMAKE_CXX_COMPILER_LAUNCHER=$CCACHE_VARIANT" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Install Qt
|
||||
if: ${{ inputs.msystem == '' }}
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
aqtversion: "==3.1.*"
|
||||
version: ${{ inputs.qt-version }}
|
||||
arch: ${{ inputs.qt-architecture }}
|
||||
modules: qt5compat qtimageformats qtnetworkauth
|
||||
cache: ${{ inputs.build-type == 'Debug' }}
|
26
.github/actions/setup-dependencies/linux/action.yml
vendored
Normal file
26
.github/actions/setup-dependencies/linux/action.yml
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
name: Setup Linux dependencies
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
|
||||
steps:
|
||||
- name: Install host dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
sudo apt-get -y install ninja-build extra-cmake-modules scdoc appstream libxcb-cursor-dev
|
||||
|
||||
- name: Setup AppImage tooling
|
||||
shell: bash
|
||||
run: |
|
||||
declare -A appimage_deps
|
||||
appimage_deps["https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20250213-2/linuxdeploy-x86_64.AppImage"]="4648f278ab3ef31f819e67c30d50f462640e5365a77637d7e6f2ad9fd0b4522a linuxdeploy-x86_64.AppImage"
|
||||
appimage_deps["https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/1-alpha-20250213-1/linuxdeploy-plugin-qt-x86_64.AppImage"]="15106be885c1c48a021198e7e1e9a48ce9d02a86dd0a1848f00bdbf3c1c92724 linuxdeploy-plugin-qt-x86_64.AppImage"
|
||||
appimage_deps["https://github.com/AppImageCommunity/AppImageUpdate/releases/download/2.0.0-alpha-1-20241225/AppImageUpdate-x86_64.AppImage"]="f1747cf60058e99f1bb9099ee9787d16c10241313b7acec81810ea1b1e568c11 AppImageUpdate-x86_64.AppImage"
|
||||
|
||||
for url in "${!appimage_deps[@]}"; do
|
||||
curl -LO "$url"
|
||||
sha256sum -c - <<< "${appimage_deps[$url]}"
|
||||
done
|
||||
|
||||
sudo apt -y install libopengl0
|
16
.github/actions/setup-dependencies/macos/action.yml
vendored
Normal file
16
.github/actions/setup-dependencies/macos/action.yml
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
name: Setup macOS dependencies
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
brew update
|
||||
brew install ninja extra-cmake-modules temurin@17
|
||||
|
||||
- name: Set JAVA_HOME
|
||||
shell: bash
|
||||
run: |
|
||||
echo "JAVA_HOME=$(/usr/libexec/java_home -v 17)" >> "$GITHUB_ENV"
|
73
.github/actions/setup-dependencies/windows/action.yml
vendored
Normal file
73
.github/actions/setup-dependencies/windows/action.yml
vendored
Normal file
|
@ -0,0 +1,73 @@
|
|||
name: Setup Windows Dependencies
|
||||
|
||||
inputs:
|
||||
build-type:
|
||||
description: Type for the build
|
||||
required: true
|
||||
default: Debug
|
||||
msystem:
|
||||
description: MSYS2 subsystem to use
|
||||
required: false
|
||||
vcvars-arch:
|
||||
description: Visual Studio architecture to use
|
||||
required: true
|
||||
default: amd64
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
|
||||
steps:
|
||||
# NOTE: Installed on MinGW as well for SignTool
|
||||
- name: Enter VS Developer shell
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
arch: ${{ inputs.vcvars-arch }}
|
||||
vsversion: 2022
|
||||
|
||||
- name: Setup MSYS2 (MinGW)
|
||||
if: ${{ inputs.msystem != '' }}
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: ${{ inputs.msystem }}
|
||||
update: true
|
||||
install: >-
|
||||
git
|
||||
pacboy: >-
|
||||
toolchain:p
|
||||
ccache:p
|
||||
cmake:p
|
||||
extra-cmake-modules:p
|
||||
ninja:p
|
||||
qt6-base:p
|
||||
qt6-svg:p
|
||||
qt6-imageformats:p
|
||||
qt6-5compat:p
|
||||
qt6-networkauth:p
|
||||
cmark:p
|
||||
tomlplusplus:p
|
||||
quazip-qt6:p
|
||||
|
||||
- name: List pacman packages (MinGW)
|
||||
if: ${{ inputs.msystem != '' }}
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
pacman -Qe
|
||||
|
||||
- name: Retrieve ccache cache (MinGW)
|
||||
if: ${{ inputs.msystem != '' && inputs.build-type == 'Debug' }}
|
||||
uses: actions/cache@v4.2.3
|
||||
with:
|
||||
path: '${{ github.workspace }}\.ccache'
|
||||
key: ${{ runner.os }}-mingw-w64-ccache-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-mingw-w64-ccache
|
||||
|
||||
- name: Setup ccache (MinGW)
|
||||
if: ${{ inputs.msystem != '' && inputs.build-type == 'Debug' }}
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
ccache --set-config=cache_dir='${{ github.workspace }}\.ccache'
|
||||
ccache --set-config=max_size='500M'
|
||||
ccache --set-config=compression=true
|
||||
ccache -p # Show config
|
30
.github/workflows/blocked-prs.yml
vendored
30
.github/workflows/blocked-prs.yml
vendored
|
@ -64,7 +64,7 @@ jobs:
|
|||
"prNumber": .number,
|
||||
"prHeadSha": .head.sha,
|
||||
"prHeadLabel": .head.label,
|
||||
"prBody": .body,
|
||||
"prBody": (.body // ""),
|
||||
"prLabels": (reduce .labels[].name as $l ([]; . + [$l]))
|
||||
}
|
||||
' <<< "$PR_JSON")"
|
||||
|
@ -125,6 +125,7 @@ jobs:
|
|||
"type": $type,
|
||||
"number": .number,
|
||||
"merged": .merged,
|
||||
"state": (if .state == "open" then "Open" elif .merged then "Merged" else "Closed" end),
|
||||
"labels": (reduce .labels[].name as $l ([]; . + [$l])),
|
||||
"basePrUrl": .html_url,
|
||||
"baseRepoName": .head.repo.name,
|
||||
|
@ -138,11 +139,16 @@ jobs:
|
|||
)
|
||||
{
|
||||
echo "data=$blocked_pr_data";
|
||||
echo "all_merged=$(jq -r 'all(.[].merged; .)' <<< "$blocked_pr_data")";
|
||||
echo "current_blocking=$(jq -c 'map( select( .merged | not ) | .number )' <<< "$blocked_pr_data" )";
|
||||
echo "all_merged=$(jq -r 'all(.[] | (.type == "Stacked on" and .merged) or (.type == "Blocked on" and (.state != "Open")); .)' <<< "$blocked_pr_data")";
|
||||
echo "current_blocking=$(jq -c 'map(
|
||||
select(
|
||||
(.type == "Stacked on" and (.merged | not)) or
|
||||
(.type == "Blocked on" and (.state == "Open"))
|
||||
) | .number
|
||||
)' <<< "$blocked_pr_data" )";
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Add 'blocked' Label is Missing
|
||||
- name: Add 'blocked' Label if Missing
|
||||
id: label_blocked
|
||||
if: (fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0) && !contains(fromJSON(env.JOB_DATA).prLabels, 'blocked') && !fromJSON(steps.blocking_data.outputs.all_merged)
|
||||
continue-on-error: true
|
||||
|
@ -184,14 +190,18 @@ jobs:
|
|||
# create commit Status, overwrites previous identical context
|
||||
while read -r pr_data ; do
|
||||
DESC=$(
|
||||
jq -r ' "Blocking PR #" + (.number | tostring) + " is " + (if .merged then "" else "not yet " end) + "merged"' <<< "$pr_data"
|
||||
jq -r 'if .type == "Stacked on" then
|
||||
"Stacked PR #" + (.number | tostring) + " is " + (if .merged then "" else "not yet " end) + "merged"
|
||||
else
|
||||
"Blocking PR #" + (.number | tostring) + " is " + (if .state == "Open" then "" else "not yet " end) + "merged or closed"
|
||||
end ' <<< "$pr_data"
|
||||
)
|
||||
gh api \
|
||||
--method POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"/repos/${OWNER}/${REPO}/statuses/${pr_head_sha}" \
|
||||
-f "state=$(jq -r 'if .merged then "success" else "failure" end' <<< "$pr_data")" \
|
||||
-f "state=$(jq -r 'if (.type == "Stacked on" and .merged) or (.type == "Blocked on" and (.state != "Open")) then "success" else "failure" end' <<< "$pr_data")" \
|
||||
-f "target_url=$(jq -r '.basePrUrl' <<< "$pr_data" )" \
|
||||
-f "description=$DESC" \
|
||||
-f "context=ci/blocking-pr-check:$(jq '.number' <<< "$pr_data")"
|
||||
|
@ -214,7 +224,13 @@ jobs:
|
|||
base_repo_owner=$(jq -r '.baseRepoOwner' <<< "$pr_data")
|
||||
base_repo_name=$(jq -r '.baseRepoName' <<< "$pr_data")
|
||||
compare_url="https://github.com/$base_repo_owner/$base_repo_name/compare/$base_ref_name...$pr_head_label"
|
||||
status=$(jq -r 'if .merged then ":heavy_check_mark: Merged" else ":x: Not Merged" end' <<< "$pr_data")
|
||||
status=$(jq -r '
|
||||
if .type == "Stacked on" then
|
||||
if .merged then ":heavy_check_mark: Merged" else ":x: Not Merged (" + .state + ")" end
|
||||
else
|
||||
if .state != "Open" then ":white_check_mark: " + .state else ":x: Open" end
|
||||
end
|
||||
' <<< "$pr_data")
|
||||
type=$(jq -r '.type' <<< "$pr_data")
|
||||
echo " - $type #$base_pr $status [(compare)]($compare_url)" >> "$COMMENT_PATH"
|
||||
done < <(jq -c '.[]' <<< "$BLOCKING_DATA")
|
||||
|
|
714
.github/workflows/build.yml
vendored
714
.github/workflows/build.yml
vendored
|
@ -1,633 +1,199 @@
|
|||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- "renovate/**"
|
||||
paths:
|
||||
# File types
|
||||
- "**.cpp"
|
||||
- "**.h"
|
||||
- "**.java"
|
||||
|
||||
# Directories
|
||||
- "buildconfig/"
|
||||
- "cmake/"
|
||||
- "launcher/"
|
||||
- "libraries/"
|
||||
- "program_info/"
|
||||
- "tests/"
|
||||
|
||||
# Files
|
||||
- "CMakeLists.txt"
|
||||
- "COPYING.md"
|
||||
|
||||
# Workflows
|
||||
- ".github/workflows/build.yml"
|
||||
- ".github/actions/package/"
|
||||
- ".github/actions/setup-dependencies/"
|
||||
pull_request:
|
||||
paths:
|
||||
# File types
|
||||
- "**.cpp"
|
||||
- "**.h"
|
||||
|
||||
# Directories
|
||||
- "buildconfig/"
|
||||
- "cmake/"
|
||||
- "launcher/"
|
||||
- "libraries/"
|
||||
- "program_info/"
|
||||
- "tests/"
|
||||
|
||||
# Files
|
||||
- "CMakeLists.txt"
|
||||
- "COPYING.md"
|
||||
|
||||
# Workflows
|
||||
- ".github/workflows/build.yml"
|
||||
- ".github/actions/package/"
|
||||
- ".github/actions/setup-dependencies/"
|
||||
workflow_call:
|
||||
inputs:
|
||||
build_type:
|
||||
description: Type of build (Debug, Release, RelWithDebInfo, MinSizeRel)
|
||||
build-type:
|
||||
description: Type of build (Debug or Release)
|
||||
type: string
|
||||
default: Debug
|
||||
is_qt_cached:
|
||||
description: Enable Qt caching or not
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
build-type:
|
||||
description: Type of build (Debug or Release)
|
||||
type: string
|
||||
default: true
|
||||
secrets:
|
||||
SPARKLE_ED25519_KEY:
|
||||
description: Private key for signing Sparkle updates
|
||||
required: false
|
||||
WINDOWS_CODESIGN_CERT:
|
||||
description: Certificate for signing Windows builds
|
||||
required: false
|
||||
WINDOWS_CODESIGN_PASSWORD:
|
||||
description: Password for signing Windows builds
|
||||
required: false
|
||||
APPLE_CODESIGN_CERT:
|
||||
description: Certificate for signing macOS builds
|
||||
required: false
|
||||
APPLE_CODESIGN_PASSWORD:
|
||||
description: Password for signing macOS builds
|
||||
required: false
|
||||
APPLE_CODESIGN_ID:
|
||||
description: Certificate ID for signing macOS builds
|
||||
required: false
|
||||
APPLE_NOTARIZE_APPLE_ID:
|
||||
description: Apple ID used for notarizing macOS builds
|
||||
required: false
|
||||
APPLE_NOTARIZE_TEAM_ID:
|
||||
description: Team ID used for notarizing macOS builds
|
||||
required: false
|
||||
APPLE_NOTARIZE_PASSWORD:
|
||||
description: Password used for notarizing macOS builds
|
||||
required: false
|
||||
GPG_PRIVATE_KEY:
|
||||
description: Private key for AppImage signing
|
||||
required: false
|
||||
GPG_PRIVATE_KEY_ID:
|
||||
description: ID for the GPG_PRIVATE_KEY, to select the signing key
|
||||
required: false
|
||||
default: Debug
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build (${{ matrix.artifact-name }})
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-22.04
|
||||
qt_ver: 5
|
||||
qt_host: linux
|
||||
qt_arch: ""
|
||||
qt_version: "5.15.2"
|
||||
qt_modules: "qtnetworkauth"
|
||||
|
||||
- os: ubuntu-22.04
|
||||
qt_ver: 6
|
||||
qt_host: linux
|
||||
qt_arch: ""
|
||||
qt_version: "6.8.1"
|
||||
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
||||
linuxdeploy_hash: "4648f278ab3ef31f819e67c30d50f462640e5365a77637d7e6f2ad9fd0b4522a linuxdeploy-x86_64.AppImage"
|
||||
linuxdeploy_qt_hash: "15106be885c1c48a021198e7e1e9a48ce9d02a86dd0a1848f00bdbf3c1c92724 linuxdeploy-plugin-qt-x86_64.AppImage"
|
||||
appimageupdate_hash: "f1747cf60058e99f1bb9099ee9787d16c10241313b7acec81810ea1b1e568c11 AppImageUpdate-x86_64.AppImage"
|
||||
artifact-name: Linux
|
||||
base-cmake-preset: linux
|
||||
|
||||
- os: windows-2022
|
||||
name: "Windows-MinGW-w64"
|
||||
msystem: clang64
|
||||
vcvars_arch: "amd64_x86"
|
||||
artifact-name: Windows-MinGW-w64
|
||||
base-cmake-preset: windows_mingw
|
||||
msystem: CLANG64
|
||||
vcvars-arch: amd64_x86
|
||||
|
||||
- os: windows-11-arm
|
||||
artifact-name: Windows-MinGW-arm64
|
||||
base-cmake-preset: windows_mingw
|
||||
msystem: CLANGARM64
|
||||
vcvars-arch: arm64
|
||||
|
||||
- os: windows-2022
|
||||
name: "Windows-MSVC"
|
||||
msystem: ""
|
||||
architecture: "x64"
|
||||
vcvars_arch: "amd64"
|
||||
qt_ver: 6
|
||||
qt_host: "windows"
|
||||
qt_arch: "win64_msvc2022_64"
|
||||
qt_version: "6.8.1"
|
||||
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
||||
nscurl_tag: "v24.9.26.122"
|
||||
nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0"
|
||||
artifact-name: Windows-MSVC
|
||||
base-cmake-preset: windows_msvc
|
||||
# TODO(@getchoo): This is the default in setup-dependencies/windows. Why isn't it working?!?!
|
||||
vcvars-arch: amd64
|
||||
|
||||
- os: windows-2022
|
||||
name: "Windows-MSVC-arm64"
|
||||
msystem: ""
|
||||
architecture: "arm64"
|
||||
vcvars_arch: "amd64_arm64"
|
||||
qt_ver: 6
|
||||
qt_host: "windows"
|
||||
qt_arch: "win64_msvc2022_arm64_cross_compiled"
|
||||
qt_version: "6.8.1"
|
||||
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
||||
nscurl_tag: "v24.9.26.122"
|
||||
nscurl_sha256: "AEE6C4BE3CB6455858E9C1EE4B3AFE0DB9960FA03FE99CCDEDC28390D57CCBB0"
|
||||
artifact-name: Windows-MSVC-arm64
|
||||
base-cmake-preset: windows_msvc_arm64_cross
|
||||
vcvars-arch: amd64_arm64
|
||||
qt-architecture: win64_msvc2022_arm64_cross_compiled
|
||||
|
||||
- os: macos-14
|
||||
name: macOS
|
||||
macosx_deployment_target: 11.0
|
||||
qt_ver: 6
|
||||
qt_host: mac
|
||||
qt_arch: ""
|
||||
qt_version: "6.8.1"
|
||||
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
||||
artifact-name: macOS
|
||||
base-cmake-preset: ${{ (inputs.build-type || 'Debug') == 'Debug' && 'macos_universal' || 'macos' }}
|
||||
macosx-deployment-target: 12.0
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ matrix.msystem != '' && 'msys2 {0}' || 'bash' }}
|
||||
|
||||
env:
|
||||
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
|
||||
INSTALL_DIR: "install"
|
||||
INSTALL_PORTABLE_DIR: "install-portable"
|
||||
INSTALL_APPIMAGE_DIR: "install-appdir"
|
||||
BUILD_DIR: "build"
|
||||
CCACHE_VAR: ""
|
||||
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
|
||||
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx-deployment-target }}
|
||||
|
||||
steps:
|
||||
##
|
||||
# PREPARE
|
||||
# SETUP
|
||||
##
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: "true"
|
||||
submodules: true
|
||||
|
||||
- name: "Setup MSYS2"
|
||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
||||
uses: msys2/setup-msys2@v2
|
||||
- name: Setup dependencies
|
||||
id: setup-dependencies
|
||||
uses: ./.github/actions/setup-dependencies
|
||||
with:
|
||||
build-type: ${{ inputs.build-type || 'Debug' }}
|
||||
msystem: ${{ matrix.msystem }}
|
||||
update: true
|
||||
install: >-
|
||||
git
|
||||
mingw-w64-x86_64-binutils
|
||||
pacboy: >-
|
||||
toolchain:p
|
||||
cmake:p
|
||||
extra-cmake-modules:p
|
||||
ninja:p
|
||||
qt6-base:p
|
||||
qt6-svg:p
|
||||
qt6-imageformats:p
|
||||
quazip-qt6:p
|
||||
ccache:p
|
||||
qt6-5compat:p
|
||||
qt6-networkauth:p
|
||||
cmark:p
|
||||
|
||||
- name: Force newer ccache
|
||||
if: runner.os == 'Windows' && matrix.msystem == '' && inputs.build_type == 'Debug'
|
||||
run: |
|
||||
choco install ccache --version 4.7.1
|
||||
|
||||
- name: Setup ccache
|
||||
if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug'
|
||||
uses: hendrikmuhs/ccache-action@v1.2.17
|
||||
with:
|
||||
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
|
||||
|
||||
- name: Use ccache on Debug builds only
|
||||
if: inputs.build_type == 'Debug'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "CCACHE_VAR=ccache" >> $GITHUB_ENV
|
||||
|
||||
- name: Retrieve ccache cache (Windows MinGW-w64)
|
||||
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
|
||||
uses: actions/cache@v4.2.3
|
||||
with:
|
||||
path: '${{ github.workspace }}\.ccache'
|
||||
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
${{ matrix.os }}-mingw-w64-ccache
|
||||
|
||||
- name: Setup ccache (Windows MinGW-w64)
|
||||
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
ccache --set-config=cache_dir='${{ github.workspace }}\.ccache'
|
||||
ccache --set-config=max_size='500M'
|
||||
ccache --set-config=compression=true
|
||||
ccache -p # Show config
|
||||
ccache -z # Zero stats
|
||||
|
||||
- name: Configure ccache (Windows MSVC)
|
||||
if: ${{ runner.os == 'Windows' && matrix.msystem == '' && inputs.build_type == 'Debug' }}
|
||||
run: |
|
||||
# https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix)
|
||||
Copy-Item C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/ccache.exe -Destination C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/cl.exe
|
||||
echo "CLToolExe=cl.exe" >> $env:GITHUB_ENV
|
||||
echo "CLToolPath=C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/" >> $env:GITHUB_ENV
|
||||
echo "TrackFileAccess=false" >> $env:GITHUB_ENV
|
||||
# Needed for ccache, but also speeds up compile
|
||||
echo "UseMultiToolTask=true" >> $env:GITHUB_ENV
|
||||
|
||||
- name: Set short version
|
||||
shell: bash
|
||||
run: |
|
||||
ver_short=`git rev-parse --short HEAD`
|
||||
echo "VERSION=$ver_short" >> $GITHUB_ENV
|
||||
|
||||
- name: Install Dependencies (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
sudo apt-get -y install ninja-build extra-cmake-modules scdoc appstream libxcb-cursor-dev
|
||||
|
||||
- name: Install Dependencies (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
brew update
|
||||
brew install ninja extra-cmake-modules
|
||||
|
||||
- name: Install host Qt (Windows MSVC arm64)
|
||||
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
aqtversion: "==3.1.*"
|
||||
py7zrversion: ">=0.20.2"
|
||||
version: ${{ matrix.qt_version }}
|
||||
host: "windows"
|
||||
target: "desktop"
|
||||
arch: ${{ matrix.qt_arch }}
|
||||
modules: ${{ matrix.qt_modules }}
|
||||
cache: ${{ inputs.is_qt_cached }}
|
||||
cache-key-prefix: host-qt-arm64-windows
|
||||
dir: ${{ github.workspace }}\HostQt
|
||||
set-env: false
|
||||
|
||||
- name: Install Qt (macOS, Linux & Windows MSVC)
|
||||
if: matrix.msystem == ''
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
aqtversion: "==3.1.*"
|
||||
py7zrversion: ">=0.20.2"
|
||||
version: ${{ matrix.qt_version }}
|
||||
target: "desktop"
|
||||
arch: ${{ matrix.qt_arch }}
|
||||
modules: ${{ matrix.qt_modules }}
|
||||
tools: ${{ matrix.qt_tools }}
|
||||
cache: ${{ inputs.is_qt_cached }}
|
||||
|
||||
- name: Install MSVC (Windows MSVC)
|
||||
if: runner.os == 'Windows' # We want this for MinGW builds as well, as we need SignTool
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
vsversion: 2022
|
||||
arch: ${{ matrix.vcvars_arch }}
|
||||
|
||||
- name: Prepare AppImage (Linux)
|
||||
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||
env:
|
||||
APPIMAGEUPDATE_HASH: ${{ matrix.appimageupdate_hash }}
|
||||
LINUXDEPLOY_HASH: ${{ matrix.linuxdeploy_hash }}
|
||||
LINUXDEPLOY_QT_HASH: ${{ matrix.linuxdeploy_qt_hash }}
|
||||
run: |
|
||||
wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20250213-2/linuxdeploy-x86_64.AppImage"
|
||||
wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/1-alpha-20250213-1/linuxdeploy-plugin-qt-x86_64.AppImage"
|
||||
|
||||
wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/2.0.0-alpha-1-20241225/AppImageUpdate-x86_64.AppImage"
|
||||
|
||||
sha256sum -c - <<< "$LINUXDEPLOY_HASH"
|
||||
sha256sum -c - <<< "$LINUXDEPLOY_QT_HASH"
|
||||
sha256sum -c - <<< "$APPIMAGEUPDATE_HASH"
|
||||
|
||||
sudo apt install libopengl0
|
||||
|
||||
- name: Add QT_HOST_PATH var (Windows MSVC arm64)
|
||||
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
|
||||
run: |
|
||||
echo "QT_HOST_PATH=${{ github.workspace }}\HostQt\Qt\${{ matrix.qt_version }}\msvc2022_64" >> $env:GITHUB_ENV
|
||||
|
||||
- name: Setup java (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: "17"
|
||||
##
|
||||
# CONFIGURE
|
||||
##
|
||||
|
||||
- name: Configure CMake (macOS)
|
||||
if: runner.os == 'macOS' && matrix.qt_ver == 6
|
||||
run: |
|
||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -G Ninja
|
||||
|
||||
- name: Configure CMake (Windows MinGW-w64)
|
||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=6 -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} -G Ninja
|
||||
|
||||
- name: Configure CMake (Windows MSVC)
|
||||
if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture != 'arm64'
|
||||
run: |
|
||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -DLauncher_FORCE_BUNDLED_LIBS=ON -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} -G Ninja
|
||||
|
||||
- name: Configure CMake (Windows MSVC arm64)
|
||||
if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture == 'arm64'
|
||||
run: |
|
||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }}
|
||||
|
||||
- name: Configure CMake (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DLauncher_BUILD_ARTIFACT=Linux-Qt${{ matrix.qt_ver }} -G Ninja
|
||||
vcvars-arch: ${{ matrix.vcvars-arch }}
|
||||
qt-architecture: ${{ matrix.qt-architecture }}
|
||||
|
||||
##
|
||||
# BUILD
|
||||
##
|
||||
|
||||
- name: Build
|
||||
if: runner.os != 'Windows'
|
||||
- name: Get CMake preset
|
||||
id: cmake-preset
|
||||
env:
|
||||
BASE_CMAKE_PRESET: ${{ matrix.base-cmake-preset }}
|
||||
PRESET_TYPE: ${{ (inputs.build-type || 'Debug') == 'Debug' && 'debug' || 'ci' }}
|
||||
run: |
|
||||
cmake --build ${{ env.BUILD_DIR }}
|
||||
echo preset="$BASE_CMAKE_PRESET"_"$PRESET_TYPE" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build (Windows MinGW-w64)
|
||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
||||
shell: msys2 {0}
|
||||
- name: Run CMake workflow
|
||||
env:
|
||||
CMAKE_PRESET: ${{ steps.cmake-preset.outputs.preset }}
|
||||
run: |
|
||||
cmake --build ${{ env.BUILD_DIR }}
|
||||
|
||||
- name: Build (Windows MSVC)
|
||||
if: runner.os == 'Windows' && matrix.msystem == ''
|
||||
run: |
|
||||
cmake --build ${{ env.BUILD_DIR }} --config ${{ inputs.build_type }}
|
||||
cmake --workflow --preset "$CMAKE_PRESET"
|
||||
|
||||
##
|
||||
# TEST
|
||||
# PACKAGE
|
||||
##
|
||||
|
||||
- name: Test
|
||||
if: runner.os != 'Windows'
|
||||
- name: Get short version
|
||||
id: short-version
|
||||
shell: bash
|
||||
run: |
|
||||
ctest -E "^example64|example$" --test-dir build --output-on-failure
|
||||
echo "version=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Test (Windows MinGW-w64)
|
||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
ctest -E "^example64|example$" --test-dir build --output-on-failure
|
||||
- name: Package (Linux)
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
uses: ./.github/actions/package/linux
|
||||
with:
|
||||
version: ${{ steps.short-version.outputs.version }}
|
||||
build-type: ${{ steps.setup-dependencies.outputs.build-type }}
|
||||
cmake-preset: ${{ steps.cmake-preset.outputs.preset }}
|
||||
qt-version: ${{ steps.setup-dependencies.outputs.qt-version }}
|
||||
|
||||
- name: Test (Windows MSVC)
|
||||
if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture != 'arm64'
|
||||
run: |
|
||||
ctest -E "^example64|example$" --test-dir build --output-on-failure -C ${{ inputs.build_type }}
|
||||
|
||||
##
|
||||
# PACKAGE BUILDS
|
||||
##
|
||||
|
||||
- name: Fetch codesign certificate (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
echo '${{ secrets.APPLE_CODESIGN_CERT }}' | base64 --decode > codesign.p12
|
||||
if [ -n '${{ secrets.APPLE_CODESIGN_ID }}' ]; then
|
||||
security create-keychain -p '${{ secrets.APPLE_CODESIGN_PASSWORD }}' build.keychain
|
||||
security default-keychain -s build.keychain
|
||||
security unlock-keychain -p '${{ secrets.APPLE_CODESIGN_PASSWORD }}' build.keychain
|
||||
security import codesign.p12 -k build.keychain -P '${{ secrets.APPLE_CODESIGN_PASSWORD }}' -T /usr/bin/codesign
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k '${{ secrets.APPLE_CODESIGN_PASSWORD }}' build.keychain
|
||||
else
|
||||
echo ":warning: Using ad-hoc code signing for macOS, as certificate was not present." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
gpg-private-key-id: ${{ secrets.GPG_PRIVATE_KEY_ID }}
|
||||
|
||||
- name: Package (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
cmake --install ${{ env.BUILD_DIR }}
|
||||
|
||||
cd ${{ env.INSTALL_DIR }}
|
||||
chmod +x "PrismLauncher.app/Contents/MacOS/prismlauncher"
|
||||
|
||||
if [ -n '${{ secrets.APPLE_CODESIGN_ID }}' ]; then
|
||||
APPLE_CODESIGN_ID='${{ secrets.APPLE_CODESIGN_ID }}'
|
||||
ENTITLEMENTS_FILE='../program_info/App.entitlements'
|
||||
else
|
||||
APPLE_CODESIGN_ID='-'
|
||||
ENTITLEMENTS_FILE='../program_info/AdhocSignedApp.entitlements'
|
||||
fi
|
||||
|
||||
sudo codesign --sign "$APPLE_CODESIGN_ID" --deep --force --entitlements "$ENTITLEMENTS_FILE" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
|
||||
mv "PrismLauncher.app" "Prism Launcher.app"
|
||||
|
||||
- name: Notarize (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
cd ${{ env.INSTALL_DIR }}
|
||||
|
||||
if [ -n '${{ secrets.APPLE_NOTARIZE_PASSWORD }}' ]; then
|
||||
ditto -c -k --sequesterRsrc --keepParent "Prism Launcher.app" ../PrismLauncher.zip
|
||||
xcrun notarytool submit ../PrismLauncher.zip \
|
||||
--wait --progress \
|
||||
--apple-id '${{ secrets.APPLE_NOTARIZE_APPLE_ID }}' \
|
||||
--team-id '${{ secrets.APPLE_NOTARIZE_TEAM_ID }}' \
|
||||
--password '${{ secrets.APPLE_NOTARIZE_PASSWORD }}'
|
||||
|
||||
xcrun stapler staple "Prism Launcher.app"
|
||||
else
|
||||
echo ":warning: Skipping notarization as credentials are not present." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
ditto -c -k --sequesterRsrc --keepParent "Prism Launcher.app" ../PrismLauncher.zip
|
||||
|
||||
- name: Make Sparkle signature (macOS)
|
||||
if: matrix.name == 'macOS'
|
||||
run: |
|
||||
if [ '${{ secrets.SPARKLE_ED25519_KEY }}' != '' ]; then
|
||||
echo '${{ secrets.SPARKLE_ED25519_KEY }}' > ed25519-priv.pem
|
||||
signature=$(/opt/homebrew/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.zip -inkey ed25519-priv.pem | openssl base64 | tr -d \\n)
|
||||
rm ed25519-priv.pem
|
||||
cat >> $GITHUB_STEP_SUMMARY << EOF
|
||||
### Artifact Information :information_source:
|
||||
- :memo: Sparkle Signature (ed25519): \`$signature\`
|
||||
EOF
|
||||
else
|
||||
cat >> $GITHUB_STEP_SUMMARY << EOF
|
||||
### Artifact Information :information_source:
|
||||
- :warning: Sparkle Signature (ed25519): No private key available (likely a pull request or fork)
|
||||
EOF
|
||||
fi
|
||||
|
||||
- name: Package (Windows MinGW-w64)
|
||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
cmake --install ${{ env.BUILD_DIR }}
|
||||
touch ${{ env.INSTALL_DIR }}/manifest.txt
|
||||
for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_DIR }}/manifest.txt
|
||||
|
||||
- name: Package (Windows MSVC)
|
||||
if: runner.os == 'Windows' && matrix.msystem == ''
|
||||
run: |
|
||||
cmake --install ${{ env.BUILD_DIR }} --config ${{ inputs.build_type }}
|
||||
|
||||
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
|
||||
|
||||
- name: Fetch codesign certificate (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: bash # yes, we are not using MSYS2 or PowerShell here
|
||||
run: |
|
||||
echo '${{ secrets.WINDOWS_CODESIGN_CERT }}' | base64 --decode > codesign.pfx
|
||||
|
||||
- name: Sign executable (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
if (Get-Content ./codesign.pfx){
|
||||
cd ${{ env.INSTALL_DIR }}
|
||||
# We ship the exact same executable for portable and non-portable editions, so signing just once is fine
|
||||
SignTool sign /fd sha256 /td sha256 /f ../codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com prismlauncher.exe prismlauncher_updater.exe prismlauncher_filelink.exe
|
||||
} else {
|
||||
":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY
|
||||
}
|
||||
|
||||
- name: Package (Windows MinGW-w64, portable)
|
||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead
|
||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
|
||||
for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt
|
||||
|
||||
- name: Package (Windows MSVC, portable)
|
||||
if: runner.os == 'Windows' && matrix.msystem == ''
|
||||
run: |
|
||||
cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead
|
||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
|
||||
|
||||
Get-ChildItem ${{ env.INSTALL_PORTABLE_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_PORTABLE_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt
|
||||
|
||||
- name: Package (Windows, installer)
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
if ('${{ matrix.nscurl_tag }}') {
|
||||
New-Item -Name NSISPlugins -ItemType Directory
|
||||
Invoke-Webrequest https://github.com/negrutiu/nsis-nscurl/releases/download/${{ matrix.nscurl_tag }}/NScurl.zip -OutFile NSISPlugins\NScurl.zip
|
||||
$nscurl_hash = Get-FileHash NSISPlugins\NScurl.zip -Algorithm Sha256 | Select-Object -ExpandProperty Hash
|
||||
if ( $nscurl_hash -ne "${{ matrix.nscurl_sha256 }}") {
|
||||
echo "::error:: NSCurl.zip sha256 mismatch"
|
||||
exit 1
|
||||
}
|
||||
Expand-Archive -Path NSISPlugins\NScurl.zip -DestinationPath NSISPlugins\NScurl
|
||||
}
|
||||
cd ${{ env.INSTALL_DIR }}
|
||||
makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi"
|
||||
|
||||
- name: Sign installer (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
if (Get-Content ./codesign.pfx){
|
||||
SignTool sign /fd sha256 /td sha256 /f codesign.pfx /p '${{ secrets.WINDOWS_CODESIGN_PASSWORD }}' /tr http://timestamp.digicert.com PrismLauncher-Setup.exe
|
||||
} else {
|
||||
":warning: Skipped code signing for Windows, as certificate was not present." >> $env:GITHUB_STEP_SUMMARY
|
||||
}
|
||||
|
||||
- name: Package AppImage (Linux)
|
||||
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||
shell: bash
|
||||
env:
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
run: |
|
||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_APPIMAGE_DIR }}/usr
|
||||
|
||||
mv ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.metainfo.xml ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.appdata.xml
|
||||
export "NO_APPSTREAM=1" # we have to skip appstream checking because appstream on ubuntu 20.04 is outdated
|
||||
|
||||
export OUTPUT="PrismLauncher-Linux-x86_64.AppImage"
|
||||
|
||||
chmod +x linuxdeploy-*.AppImage
|
||||
|
||||
mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib
|
||||
mkdir -p ${{ 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.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
||||
cp /usr/lib/x86_64-linux-gnu/libssl.so.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
||||
cp /usr/lib/x86_64-linux-gnu/libOpenGL.so.0* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
||||
|
||||
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib"
|
||||
export LD_LIBRARY_PATH
|
||||
|
||||
chmod +x AppImageUpdate-x86_64.AppImage
|
||||
cp AppImageUpdate-x86_64.AppImage ${{ env.INSTALL_APPIMAGE_DIR }}/usr/bin
|
||||
|
||||
export UPDATE_INFORMATION="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|PrismLauncher-Linux-x86_64.AppImage.zsync"
|
||||
|
||||
if [ '${{ secrets.GPG_PRIVATE_KEY_ID }}' != '' ]; then
|
||||
export SIGN=1
|
||||
export SIGN_KEY=${{ secrets.GPG_PRIVATE_KEY_ID }}
|
||||
mkdir -p ~/.gnupg/
|
||||
echo "$GPG_PRIVATE_KEY" > ~/.gnupg/private.key
|
||||
gpg --import ~/.gnupg/private.key
|
||||
else
|
||||
echo ":warning: Skipped code signing for Linux AppImage, as gpg key was not present." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
./linuxdeploy-x86_64.AppImage --appdir ${{ env.INSTALL_APPIMAGE_DIR }} --output appimage --plugin qt -i ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/icons/hicolor/scalable/apps/org.prismlauncher.PrismLauncher.svg
|
||||
|
||||
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.* ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||
cp /usr/lib/x86_64-linux-gnu/libssl.so.* ${{ env.INSTALL_PORTABLE_DIR }}/lib
|
||||
cp /usr/lib/x86_64-linux-gnu/libffi.so.*.* ${{ 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
|
||||
##
|
||||
|
||||
- name: Upload binary tarball (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
uses: ./.github/actions/package/macos
|
||||
with:
|
||||
name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||
path: PrismLauncher.zip
|
||||
version: ${{ steps.short-version.outputs.version }}
|
||||
build-type: ${{ steps.setup-dependencies.outputs.build-type }}
|
||||
artifact-name: ${{ matrix.artifact-name }}
|
||||
|
||||
- name: Upload binary zip (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
uses: actions/upload-artifact@v4
|
||||
apple-codesign-cert: ${{ secrets.APPLE-CODESIGN-CERT }}
|
||||
apple-codesign-password: ${{ secrets.APPLE-CODESIGN_PASSWORD }}
|
||||
apple-codesign-id: ${{ secrets.APPLE-CODESIGN_ID }}
|
||||
apple-notarize-apple-id: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }}
|
||||
apple-notarize-team-id: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }}
|
||||
apple-notarize-password: ${{ secrets.APPLE-NOTARIZE_PASSWORD }}
|
||||
sparkle-ed25519-key: ${{ secrets.SPARKLE-ED25519_KEY }}
|
||||
|
||||
- name: Package (Windows)
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
uses: ./.github/actions/package/windows
|
||||
with:
|
||||
name: PrismLauncher-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||
path: ${{ env.INSTALL_DIR }}/**
|
||||
version: ${{ steps.short-version.outputs.version }}
|
||||
build-type: ${{ steps.setup-dependencies.outputs.build-type }}
|
||||
artifact-name: ${{ matrix.artifact-name }}
|
||||
msystem: ${{ matrix.msystem }}
|
||||
|
||||
- name: Upload binary zip (Windows, portable)
|
||||
if: runner.os == 'Windows'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PrismLauncher-${{ matrix.name }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||
path: ${{ env.INSTALL_PORTABLE_DIR }}/**
|
||||
|
||||
- name: Upload installer (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PrismLauncher-${{ matrix.name }}-Setup-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||
path: PrismLauncher-Setup.exe
|
||||
|
||||
- name: Upload binary tarball (Linux, portable, Qt 5)
|
||||
if: runner.os == 'Linux' && matrix.qt_ver != 6
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PrismLauncher-${{ runner.os }}-Qt5-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||
path: PrismLauncher-portable.tar.gz
|
||||
|
||||
- name: Upload binary tarball (Linux, portable, Qt 6)
|
||||
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PrismLauncher-${{ runner.os }}-Qt6-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||
path: PrismLauncher-portable.tar.gz
|
||||
|
||||
- name: Upload AppImage (Linux)
|
||||
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
|
||||
path: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
|
||||
|
||||
- name: Upload AppImage Zsync (Linux)
|
||||
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage.zsync
|
||||
path: PrismLauncher-Linux-x86_64.AppImage.zsync
|
||||
|
||||
- name: ccache stats (Windows MinGW-w64)
|
||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
ccache -s
|
||||
windows-codesign-cert: ${{ secrets.WINDOWS_CODESIGN_CERT }}
|
||||
windows-codesign-password: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
|
||||
|
|
66
.github/workflows/codeql.yml
vendored
66
.github/workflows/codeql.yml
vendored
|
@ -1,16 +1,62 @@
|
|||
name: "CodeQL Code Scanning"
|
||||
|
||||
on: [ push, pull_request, workflow_dispatch ]
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
# File types
|
||||
- "**.cpp"
|
||||
- "**.h"
|
||||
- "**.java"
|
||||
|
||||
# Directories
|
||||
- "buildconfig/"
|
||||
- "cmake/"
|
||||
- "launcher/"
|
||||
- "libraries/"
|
||||
- "program_info/"
|
||||
- "tests/"
|
||||
|
||||
# Files
|
||||
- "CMakeLists.txt"
|
||||
- "COPYING.md"
|
||||
|
||||
# Workflows
|
||||
- ".github/codeql"
|
||||
- ".github/workflows/codeql.yml"
|
||||
- ".github/actions/setup-dependencies/"
|
||||
pull_request:
|
||||
paths:
|
||||
# File types
|
||||
- "**.cpp"
|
||||
- "**.h"
|
||||
|
||||
# Directories
|
||||
- "buildconfig/"
|
||||
- "cmake/"
|
||||
- "launcher/"
|
||||
- "libraries/"
|
||||
- "program_info/"
|
||||
- "tests/"
|
||||
|
||||
# Files
|
||||
- "CMakeLists.txt"
|
||||
- "COPYING.md"
|
||||
|
||||
# Workflows
|
||||
- ".github/codeql"
|
||||
- ".github/workflows/codeql.yml"
|
||||
- ".github/actions/setup-dependencies/"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
CodeQL:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
submodules: "true"
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
|
@ -19,17 +65,15 @@ jobs:
|
|||
queries: security-and-quality
|
||||
languages: cpp, java
|
||||
|
||||
- name: Install Dependencies
|
||||
run:
|
||||
sudo apt-get -y update
|
||||
|
||||
sudo apt-get -y install ninja-build extra-cmake-modules scdoc qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 libqt5networkauth5 libqt5networkauth5-dev libqt5opengl5 libqt5opengl5-dev
|
||||
- name: Setup dependencies
|
||||
uses: ./.github/actions/setup-dependencies
|
||||
with:
|
||||
build-type: Debug
|
||||
|
||||
- name: Configure and Build
|
||||
run: |
|
||||
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr -DLauncher_QT_VERSION_MAJOR=5 -G Ninja
|
||||
|
||||
cmake --build build
|
||||
cmake --preset linux_debug
|
||||
cmake --build --preset linux_debug
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
|
|
57
.github/workflows/flatpak.yml
vendored
57
.github/workflows/flatpak.yml
vendored
|
@ -2,22 +2,55 @@ name: Flatpak
|
|||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- "**.md"
|
||||
- "**/LICENSE"
|
||||
- ".github/ISSUE_TEMPLATE/**"
|
||||
- ".markdownlint**"
|
||||
- "nix/**"
|
||||
# We don't do anything with these artifacts on releases. They go to Flathub
|
||||
tags-ignore:
|
||||
- "*"
|
||||
paths:
|
||||
# File types
|
||||
- "**.cpp"
|
||||
- "**.h"
|
||||
- "**.java"
|
||||
|
||||
# Build files
|
||||
- "flatpak/"
|
||||
|
||||
# Directories
|
||||
- "buildconfig/"
|
||||
- "cmake/"
|
||||
- "launcher/"
|
||||
- "libraries/"
|
||||
- "program_info/"
|
||||
- "tests/"
|
||||
|
||||
# Files
|
||||
- "CMakeLists.txt"
|
||||
- "COPYING.md"
|
||||
|
||||
# Workflows
|
||||
- ".github/workflows/flatpak.yml"
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- "**.md"
|
||||
- "**/LICENSE"
|
||||
- ".github/ISSUE_TEMPLATE/**"
|
||||
- ".markdownlint**"
|
||||
- "nix/**"
|
||||
paths:
|
||||
# File types
|
||||
- "**.cpp"
|
||||
- "**.h"
|
||||
|
||||
# Build files
|
||||
- "flatpak/"
|
||||
|
||||
# Directories
|
||||
- "buildconfig/"
|
||||
- "cmake/"
|
||||
- "launcher/"
|
||||
- "libraries/"
|
||||
- "program_info/"
|
||||
- "tests/"
|
||||
|
||||
# Files
|
||||
- "CMakeLists.txt"
|
||||
- "COPYING.md"
|
||||
|
||||
# Workflows
|
||||
- ".github/workflows/flatpak.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
|
|
14
.github/workflows/merge-blocking-pr.yml
vendored
14
.github/workflows/merge-blocking-pr.yml
vendored
|
@ -1,9 +1,15 @@
|
|||
name: Merged Blocking Pull Request Automation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
types:
|
||||
- closed
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_id:
|
||||
description: Local Pull Request number to work on
|
||||
required: true
|
||||
type: number
|
||||
|
||||
jobs:
|
||||
update-blocked-status:
|
||||
|
@ -12,7 +18,7 @@ jobs:
|
|||
|
||||
# a pr that was a `blocking:<id>` label was merged.
|
||||
# find the open pr's it was blocked by and trigger a refresh of their state
|
||||
if: github.event.pull_request.merged == true && contains( join( github.event.pull_request.labels.*.name, ',' ), 'blocking' )
|
||||
if: ${{ github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'blocking') }}
|
||||
|
||||
steps:
|
||||
- name: Generate token
|
||||
|
@ -26,11 +32,11 @@ jobs:
|
|||
id: gather_deps
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
PR_NUMBER: ${{ inputs.pr_id || github.event.pull_request.number }}
|
||||
run: |
|
||||
blocked_prs=$(
|
||||
gh -R ${{ github.repository }} pr list --label 'blocked' --json 'number,body' \
|
||||
| jq -c --argjson pr "${{ github.event.pull_request.number }}" '
|
||||
| jq -c --argjson pr "$PR_NUMBER" '
|
||||
reduce ( .[] | select(
|
||||
.body |
|
||||
scan("(?:blocked (?:by|on)|stacked on):? #([0-9]+)") |
|
||||
|
|
79
.github/workflows/nix.yml
vendored
79
.github/workflows/nix.yml
vendored
|
@ -4,28 +4,56 @@ on:
|
|||
push:
|
||||
tags:
|
||||
- "*"
|
||||
paths-ignore:
|
||||
- ".github/**"
|
||||
- "!.github/workflows/nix.yml"
|
||||
- "flatpak/"
|
||||
- "scripts/"
|
||||
paths:
|
||||
# File types
|
||||
- "**.cpp"
|
||||
- "**.h"
|
||||
- "**.java"
|
||||
|
||||
- ".git*"
|
||||
- ".envrc"
|
||||
- "**.md"
|
||||
- "!COPYING.md"
|
||||
- "renovate.json"
|
||||
# Build files
|
||||
- "**.nix"
|
||||
- "nix/"
|
||||
- "flake.lock"
|
||||
|
||||
# Directories
|
||||
- "buildconfig/"
|
||||
- "cmake/"
|
||||
- "launcher/"
|
||||
- "libraries/"
|
||||
- "program_info/"
|
||||
- "tests/"
|
||||
|
||||
# Files
|
||||
- "CMakeLists.txt"
|
||||
- "COPYING.md"
|
||||
|
||||
# Workflows
|
||||
- ".github/workflows/nix.yml"
|
||||
pull_request_target:
|
||||
paths-ignore:
|
||||
- ".github/**"
|
||||
- "flatpak/"
|
||||
- "scripts/"
|
||||
paths:
|
||||
# File types
|
||||
- "**.cpp"
|
||||
- "**.h"
|
||||
|
||||
- ".git*"
|
||||
- ".envrc"
|
||||
- "**.md"
|
||||
- "!COPYING.md"
|
||||
- "renovate.json"
|
||||
# Build files
|
||||
- "**.nix"
|
||||
- "nix/"
|
||||
- "flake.lock"
|
||||
|
||||
# Directories
|
||||
- "buildconfig/"
|
||||
- "cmake/"
|
||||
- "launcher/"
|
||||
- "libraries/"
|
||||
- "program_info/"
|
||||
- "tests/"
|
||||
|
||||
# Files
|
||||
- "CMakeLists.txt"
|
||||
- "COPYING.md"
|
||||
|
||||
# Workflows
|
||||
- ".github/workflows/nix.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
|
@ -61,11 +89,22 @@ jobs:
|
|||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Get merge commit
|
||||
if: ${{ github.event_name == 'pull_request_target' }}
|
||||
id: merge-commit
|
||||
uses: PrismLauncher/PrismLauncher/.github/actions/get-merge-commit@develop
|
||||
with:
|
||||
pull-request-id: ${{ github.event.number }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ steps.merge-commit.outputs.merge-commit-sha || github.sha }}
|
||||
|
||||
- name: Install Nix
|
||||
uses: DeterminateSystems/nix-installer-action@v16
|
||||
uses: DeterminateSystems/nix-installer-action@v17
|
||||
with:
|
||||
determinate: ${{ env.USE_DETERMINATE }}
|
||||
|
||||
|
|
|
@ -10,20 +10,8 @@ jobs:
|
|||
name: Build Release
|
||||
uses: ./.github/workflows/build.yml
|
||||
with:
|
||||
build_type: Release
|
||||
is_qt_cached: false
|
||||
secrets:
|
||||
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
|
||||
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
|
||||
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
|
||||
APPLE_CODESIGN_CERT: ${{ secrets.APPLE_CODESIGN_CERT }}
|
||||
APPLE_CODESIGN_PASSWORD: ${{ secrets.APPLE_CODESIGN_PASSWORD }}
|
||||
APPLE_CODESIGN_ID: ${{ secrets.APPLE_CODESIGN_ID }}
|
||||
APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }}
|
||||
APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }}
|
||||
APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }}
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
|
||||
build-type: Release
|
||||
secrets: inherit
|
||||
|
||||
create_release:
|
||||
needs: build_release
|
||||
|
@ -46,7 +34,6 @@ jobs:
|
|||
run: |
|
||||
mv ${{ github.workspace }}/PrismLauncher-source PrismLauncher-${{ env.VERSION }}
|
||||
mv PrismLauncher-Linux-Qt6-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
|
||||
mv PrismLauncher-Linux-Qt5-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt5-Portable-${{ env.VERSION }}.tar.gz
|
||||
mv PrismLauncher-*.AppImage/PrismLauncher-*.AppImage PrismLauncher-Linux-x86_64.AppImage
|
||||
mv PrismLauncher-*.AppImage.zsync/PrismLauncher-*.AppImage.zsync PrismLauncher-Linux-x86_64.AppImage.zsync
|
||||
mv PrismLauncher-macOS*/PrismLauncher.zip PrismLauncher-macOS-${{ env.VERSION }}.zip
|
||||
|
@ -79,6 +66,17 @@ jobs:
|
|||
cd ..
|
||||
done
|
||||
|
||||
for d in PrismLauncher-Windows-MinGW-arm64*; do
|
||||
cd "${d}" || continue
|
||||
INST="$(echo -n ${d} | grep -o Setup || true)"
|
||||
PORT="$(echo -n ${d} | grep -o Portable || true)"
|
||||
NAME="PrismLauncher-Windows-MinGW-arm64"
|
||||
test -z "${PORT}" || NAME="${NAME}-Portable"
|
||||
test -z "${INST}" || mv PrismLauncher-*.exe ../${NAME}-Setup-${{ env.VERSION }}.exe
|
||||
test -n "${INST}" || zip -r -9 "../${NAME}-${{ env.VERSION }}.zip" *
|
||||
cd ..
|
||||
done
|
||||
|
||||
- name: Create release
|
||||
id: create_release
|
||||
uses: softprops/action-gh-release@v2
|
||||
|
@ -89,13 +87,15 @@ jobs:
|
|||
draft: true
|
||||
prerelease: false
|
||||
files: |
|
||||
PrismLauncher-Linux-Qt5-Portable-${{ env.VERSION }}.tar.gz
|
||||
PrismLauncher-Linux-x86_64.AppImage
|
||||
PrismLauncher-Linux-x86_64.AppImage.zsync
|
||||
PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
|
||||
PrismLauncher-Windows-MinGW-w64-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-MinGW-w64-Portable-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-MinGW-w64-Setup-${{ env.VERSION }}.exe
|
||||
PrismLauncher-Windows-MinGW-arm64-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-MinGW-arm64-Portable-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-MinGW-arm64-Setup-${{ env.VERSION }}.exe
|
||||
PrismLauncher-Windows-MSVC-arm64-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-MSVC-arm64-Portable-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-MSVC-arm64-Setup-${{ env.VERSION }}.exe
|
42
.github/workflows/trigger_builds.yml
vendored
42
.github/workflows/trigger_builds.yml
vendored
|
@ -1,42 +0,0 @@
|
|||
name: Build Application
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- "renovate/**"
|
||||
paths-ignore:
|
||||
- "**.md"
|
||||
- "**/LICENSE"
|
||||
- "flake.lock"
|
||||
- "packages/**"
|
||||
- ".github/ISSUE_TEMPLATE/**"
|
||||
- ".markdownlint**"
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- "**.md"
|
||||
- "**/LICENSE"
|
||||
- "flake.lock"
|
||||
- "packages/**"
|
||||
- ".github/ISSUE_TEMPLATE/**"
|
||||
- ".markdownlint**"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build_debug:
|
||||
name: Build Debug
|
||||
uses: ./.github/workflows/build.yml
|
||||
with:
|
||||
build_type: Debug
|
||||
is_qt_cached: true
|
||||
secrets:
|
||||
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
|
||||
WINDOWS_CODESIGN_CERT: ${{ secrets.WINDOWS_CODESIGN_CERT }}
|
||||
WINDOWS_CODESIGN_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PASSWORD }}
|
||||
APPLE_CODESIGN_CERT: ${{ secrets.APPLE_CODESIGN_CERT }}
|
||||
APPLE_CODESIGN_PASSWORD: ${{ secrets.APPLE_CODESIGN_PASSWORD }}
|
||||
APPLE_CODESIGN_ID: ${{ secrets.APPLE_CODESIGN_ID }}
|
||||
APPLE_NOTARIZE_APPLE_ID: ${{ secrets.APPLE_NOTARIZE_APPLE_ID }}
|
||||
APPLE_NOTARIZE_TEAM_ID: ${{ secrets.APPLE_NOTARIZE_TEAM_ID }}
|
||||
APPLE_NOTARIZE_PASSWORD: ${{ secrets.APPLE_NOTARIZE_PASSWORD }}
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
GPG_PRIVATE_KEY_ID: ${{ secrets.GPG_PRIVATE_KEY_ID }}
|
2
.github/workflows/update-flake.yml
vendored
2
.github/workflows/update-flake.yml
vendored
|
@ -17,7 +17,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@d1ca217b388ee87b2507a9a93bf01368bde7cec2 # v31
|
||||
- uses: cachix/install-nix-action@526118121621777ccd86f79b04685a9319637641 # v31
|
||||
|
||||
- uses: DeterminateSystems/update-flake-lock@v24
|
||||
with:
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -14,6 +14,7 @@ CMakeLists.txt.user.*
|
|||
CMakeSettings.json
|
||||
/CMakeFiles
|
||||
CMakeCache.txt
|
||||
CMakeUserPresets.json
|
||||
/.project
|
||||
/.settings
|
||||
/.idea
|
||||
|
|
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -19,3 +19,6 @@
|
|||
[submodule "flatpak/shared-modules"]
|
||||
path = flatpak/shared-modules
|
||||
url = https://github.com/flathub/shared-modules.git
|
||||
[submodule "libraries/qt-qrcodegenerator/QR-Code-generator"]
|
||||
path = libraries/qt-qrcodegenerator/QR-Code-generator
|
||||
url = https://github.com/nayuki/QR-Code-generator
|
||||
|
|
|
@ -88,10 +88,8 @@ else()
|
|||
endif()
|
||||
endif()
|
||||
|
||||
# Fix build with Qt 5.13
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y")
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_DISABLE_DEPRECATED_BEFORE=0x050C00")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_WARN_DEPRECATED_UP_TO=0x060200")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_DISABLE_DEPRECATED_UP_TO=0x060000")
|
||||
|
||||
# Fix aarch64 build for toml++
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTOML_ENABLE_FLOAT16=0")
|
||||
|
@ -310,23 +308,7 @@ endif()
|
|||
|
||||
# Find the required Qt parts
|
||||
include(QtVersionlessBackport)
|
||||
if(Launcher_QT_VERSION_MAJOR EQUAL 5)
|
||||
set(QT_VERSION_MAJOR 5)
|
||||
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml NetworkAuth OpenGL)
|
||||
find_package(Qt5 COMPONENTS DBus)
|
||||
list(APPEND Launcher_QT_DBUS Qt5::DBus)
|
||||
|
||||
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
||||
find_package(QuaZip-Qt5 1.3 QUIET)
|
||||
endif()
|
||||
if (NOT QuaZip-Qt5_FOUND)
|
||||
set(QUAZIP_QT_MAJOR_VERSION ${QT_VERSION_MAJOR} CACHE STRING "Qt version to use (4, 5 or 6), defaults to ${QT_VERSION_MAJOR}" FORCE)
|
||||
set(FORCE_BUNDLED_QUAZIP 1)
|
||||
endif()
|
||||
|
||||
# Qt 6 sets these by default. Notably causes Windows APIs to use UNICODE strings.
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUNICODE -D_UNICODE")
|
||||
elseif(Launcher_QT_VERSION_MAJOR EQUAL 6)
|
||||
if(Launcher_QT_VERSION_MAJOR EQUAL 6)
|
||||
set(QT_VERSION_MAJOR 6)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat NetworkAuth OpenGL)
|
||||
find_package(Qt6 COMPONENTS DBus)
|
||||
|
@ -344,22 +326,12 @@ else()
|
|||
message(FATAL_ERROR "Qt version ${Launcher_QT_VERSION_MAJOR} is not supported")
|
||||
endif()
|
||||
|
||||
if(Launcher_QT_VERSION_MAJOR EQUAL 5)
|
||||
include(ECMQueryQt)
|
||||
ecm_query_qt(QT_PLUGINS_DIR QT_INSTALL_PLUGINS)
|
||||
ecm_query_qt(QT_LIBS_DIR QT_INSTALL_LIBS)
|
||||
ecm_query_qt(QT_LIBEXECS_DIR QT_INSTALL_LIBEXECS)
|
||||
else()
|
||||
if(Launcher_QT_VERSION_MAJOR EQUAL 6)
|
||||
set(QT_PLUGINS_DIR ${QT${QT_VERSION_MAJOR}_INSTALL_PREFIX}/${QT${QT_VERSION_MAJOR}_INSTALL_PLUGINS})
|
||||
set(QT_LIBS_DIR ${QT${QT_VERSION_MAJOR}_INSTALL_PREFIX}/${QT${QT_VERSION_MAJOR}_INSTALL_LIBS})
|
||||
set(QT_LIBEXECS_DIR ${QT${QT_VERSION_MAJOR}_INSTALL_PREFIX}/${QT${QT_VERSION_MAJOR}_INSTALL_LIBEXECS})
|
||||
endif()
|
||||
|
||||
# NOTE: Qt 6 already sets this by default
|
||||
if (Qt5_POSITION_INDEPENDENT_CODE)
|
||||
SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
|
||||
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
||||
# Find toml++
|
||||
find_package(tomlplusplus 3.2.0 QUIET)
|
||||
|
@ -503,6 +475,7 @@ add_subdirectory(libraries/libnbtplusplus)
|
|||
add_subdirectory(libraries/systeminfo) # system information library
|
||||
add_subdirectory(libraries/launcher) # java based launcher part for Minecraft
|
||||
add_subdirectory(libraries/javacheck) # java compatibility checker
|
||||
add_subdirectory(libraries/qt-qrcodegenerator) # qr code generator
|
||||
if(FORCE_BUNDLED_ZLIB)
|
||||
message(STATUS "Using bundled zlib")
|
||||
|
||||
|
|
14
CMakePresets.json
Normal file
14
CMakePresets.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json",
|
||||
"version": 8,
|
||||
"cmakeMinimumRequired": {
|
||||
"major": 3,
|
||||
"minor": 28
|
||||
},
|
||||
"include": [
|
||||
"cmake/linuxPreset.json",
|
||||
"cmake/macosPreset.json",
|
||||
"cmake/windowsMinGWPreset.json",
|
||||
"cmake/windowsMSVCPreset.json"
|
||||
]
|
||||
}
|
11
COPYING.md
11
COPYING.md
|
@ -108,7 +108,7 @@
|
|||
|
||||
Information on third party licenses used in MinGW-w64 can be found in its COPYING.MinGW-w64-runtime.txt.
|
||||
|
||||
## Qt 5/6
|
||||
## Qt 6
|
||||
|
||||
Copyright (C) 2022 The Qt Company Ltd and other contributors.
|
||||
Contact: https://www.qt.io/licensing
|
||||
|
@ -403,3 +403,12 @@
|
|||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
## qt-qrcodegenerator (`libraries/qt-qrcodegenerator`)
|
||||
|
||||
Copyright © 2024 Project Nayuki. (MIT License)
|
||||
https://www.nayuki.io/page/qr-code-generator-library
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
- The Software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the Software or the use or other dealings in the Software.
|
||||
|
|
|
@ -76,7 +76,13 @@ We thank all the wonderful backers over at Open Collective! Support Prism Launch
|
|||
|
||||
Thanks to JetBrains for providing us a few licenses for all their products, as part of their [Open Source program](https://www.jetbrains.com/opensource/).
|
||||
|
||||
[](https://www.jetbrains.com/opensource/)
|
||||
<a href="https://jb.gg/OpenSource">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://www.jetbrains.com/company/brand/img/logo_jb_dos_4.svg">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg">
|
||||
<img alt="JetBrains logo" src="https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg" width="40%">
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
Thanks to Weblate for hosting our translation efforts.
|
||||
|
||||
|
|
|
@ -53,7 +53,6 @@ Config::Config()
|
|||
LAUNCHER_SVGFILENAME = "@Launcher_SVGFileName@";
|
||||
|
||||
USER_AGENT = "@Launcher_UserAgent@";
|
||||
USER_AGENT_UNCACHED = USER_AGENT + " (Uncached)";
|
||||
|
||||
// Version information
|
||||
VERSION_MAJOR = @Launcher_VERSION_MAJOR@;
|
||||
|
|
|
@ -107,9 +107,6 @@ class Config {
|
|||
/// User-Agent to use.
|
||||
QString USER_AGENT;
|
||||
|
||||
/// User-Agent to use for uncached requests.
|
||||
QString USER_AGENT_UNCACHED;
|
||||
|
||||
/// The git commit hash of this build
|
||||
QString GIT_COMMIT;
|
||||
|
||||
|
|
81
cmake/commonPresets.json
Normal file
81
cmake/commonPresets.json
Normal file
|
@ -0,0 +1,81 @@
|
|||
{
|
||||
"$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json",
|
||||
"version": 8,
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "base",
|
||||
"hidden": true,
|
||||
"binaryDir": "build",
|
||||
"installDir": "install",
|
||||
"cacheVariables": {
|
||||
"Launcher_BUILD_PLATFORM": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "base_debug",
|
||||
"hidden": true,
|
||||
"inherits": [
|
||||
"base"
|
||||
],
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "base_release",
|
||||
"hidden": true,
|
||||
"inherits": [
|
||||
"base"
|
||||
],
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release",
|
||||
"ENABLE_LTO": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "base_ci",
|
||||
"hidden": true,
|
||||
"inherits": [
|
||||
"base_release"
|
||||
],
|
||||
"cacheVariables": {
|
||||
"Launcher_BUILD_PLATFORM": "official",
|
||||
"Launcher_FORCE_BUNDLED_LIBS": "ON"
|
||||
}
|
||||
}
|
||||
],
|
||||
"testPresets": [
|
||||
{
|
||||
"name": "base",
|
||||
"hidden": true,
|
||||
"output": {
|
||||
"outputOnFailure": true
|
||||
},
|
||||
"execution": {
|
||||
"noTestsAction": "error"
|
||||
},
|
||||
"filter": {
|
||||
"exclude": {
|
||||
"name": "^example64|example$"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "base_debug",
|
||||
"hidden": true,
|
||||
"inherits": [
|
||||
"base"
|
||||
],
|
||||
"output": {
|
||||
"debug": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "base_release",
|
||||
"hidden": true,
|
||||
"inherits": [
|
||||
"base"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
180
cmake/linuxPreset.json
Normal file
180
cmake/linuxPreset.json
Normal file
|
@ -0,0 +1,180 @@
|
|||
{
|
||||
"$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json",
|
||||
"version": 8,
|
||||
"include": [
|
||||
"commonPresets.json"
|
||||
],
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "linux_base",
|
||||
"hidden": true,
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Linux"
|
||||
},
|
||||
"generator": "Ninja",
|
||||
"cacheVariables": {
|
||||
"Launcher_BUILD_ARTIFACT": "Linux-Qt6",
|
||||
"Launcher_ENABLE_JAVA_DOWNLOADER": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "linux_debug",
|
||||
"inherits": [
|
||||
"base_debug",
|
||||
"linux_base"
|
||||
],
|
||||
"displayName": "Linux (Debug)"
|
||||
},
|
||||
{
|
||||
"name": "linux_release",
|
||||
"inherits": [
|
||||
"base_release",
|
||||
"linux_base"
|
||||
],
|
||||
"displayName": "Linux (Release)"
|
||||
},
|
||||
{
|
||||
"name": "linux_ci",
|
||||
"inherits": [
|
||||
"base_ci",
|
||||
"linux_base"
|
||||
],
|
||||
"displayName": "Linux (CI)",
|
||||
"cacheVariables": {
|
||||
"Launcher_BUILD_ARTIFACT": "Linux-Qt6"
|
||||
},
|
||||
"installDir": "/usr"
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
{
|
||||
"name": "linux_base",
|
||||
"hidden": true,
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Linux"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "linux_debug",
|
||||
"inherits": [
|
||||
"linux_base"
|
||||
],
|
||||
"displayName": "Linux (Debug)",
|
||||
"configurePreset": "linux_debug"
|
||||
},
|
||||
{
|
||||
"name": "linux_release",
|
||||
"inherits": [
|
||||
"linux_base"
|
||||
],
|
||||
"displayName": "Linux (Release)",
|
||||
"configurePreset": "linux_release"
|
||||
},
|
||||
{
|
||||
"name": "linux_ci",
|
||||
"inherits": [
|
||||
"linux_base"
|
||||
],
|
||||
"displayName": "Linux (CI)",
|
||||
"configurePreset": "linux_ci"
|
||||
}
|
||||
],
|
||||
"testPresets": [
|
||||
{
|
||||
"name": "linux_base",
|
||||
"hidden": true,
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Linux"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "linux_debug",
|
||||
"inherits": [
|
||||
"base_debug",
|
||||
"linux_base"
|
||||
],
|
||||
"displayName": "Linux (Debug)",
|
||||
"configurePreset": "linux_debug"
|
||||
},
|
||||
{
|
||||
"name": "linux_release",
|
||||
"inherits": [
|
||||
"base_release",
|
||||
"linux_base"
|
||||
],
|
||||
"displayName": "Linux (Release)",
|
||||
"configurePreset": "linux_release"
|
||||
},
|
||||
{
|
||||
"name": "linux_ci",
|
||||
"inherits": [
|
||||
"base_release",
|
||||
"linux_base"
|
||||
],
|
||||
"displayName": "Linux (CI)",
|
||||
"configurePreset": "linux_ci"
|
||||
}
|
||||
],
|
||||
"workflowPresets": [
|
||||
{
|
||||
"name": "linux_debug",
|
||||
"displayName": "Linux (Debug)",
|
||||
"steps": [
|
||||
{
|
||||
"type": "configure",
|
||||
"name": "linux_debug"
|
||||
},
|
||||
{
|
||||
"type": "build",
|
||||
"name": "linux_debug"
|
||||
},
|
||||
{
|
||||
"type": "test",
|
||||
"name": "linux_debug"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "linux",
|
||||
"displayName": "Linux (Release)",
|
||||
"steps": [
|
||||
{
|
||||
"type": "configure",
|
||||
"name": "linux_release"
|
||||
},
|
||||
{
|
||||
"type": "build",
|
||||
"name": "linux_release"
|
||||
},
|
||||
{
|
||||
"type": "test",
|
||||
"name": "linux_release"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "linux_ci",
|
||||
"displayName": "Linux (CI)",
|
||||
"steps": [
|
||||
{
|
||||
"type": "configure",
|
||||
"name": "linux_ci"
|
||||
},
|
||||
{
|
||||
"type": "build",
|
||||
"name": "linux_ci"
|
||||
},
|
||||
{
|
||||
"type": "test",
|
||||
"name": "linux_ci"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
272
cmake/macosPreset.json
Normal file
272
cmake/macosPreset.json
Normal file
|
@ -0,0 +1,272 @@
|
|||
{
|
||||
"$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json",
|
||||
"version": 8,
|
||||
"include": [
|
||||
"commonPresets.json"
|
||||
],
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "macos_base",
|
||||
"hidden": true,
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Darwin"
|
||||
},
|
||||
"generator": "Ninja"
|
||||
},
|
||||
{
|
||||
"name": "macos_universal_base",
|
||||
"hidden": true,
|
||||
"inherits": [
|
||||
"macos_base"
|
||||
],
|
||||
"cacheVariables": {
|
||||
"CMAKE_OSX_ARCHITECTURES": "x86_64;arm64",
|
||||
"Launcher_BUILD_ARTIFACT": "macOS-Qt6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "macos_debug",
|
||||
"inherits": [
|
||||
"base_debug",
|
||||
"macos_base"
|
||||
],
|
||||
"displayName": "macOS (Debug)"
|
||||
},
|
||||
{
|
||||
"name": "macos_release",
|
||||
"inherits": [
|
||||
"base_release",
|
||||
"macos_base"
|
||||
],
|
||||
"displayName": "macOS (Release)"
|
||||
},
|
||||
{
|
||||
"name": "macos_universal_debug",
|
||||
"inherits": [
|
||||
"base_debug",
|
||||
"macos_universal_base"
|
||||
],
|
||||
"displayName": "macOS (Universal Binary, Debug)"
|
||||
},
|
||||
{
|
||||
"name": "macos_universal_release",
|
||||
"inherits": [
|
||||
"base_release",
|
||||
"macos_universal_base"
|
||||
],
|
||||
"displayName": "macOS (Universal Binary, Release)"
|
||||
},
|
||||
{
|
||||
"name": "macos_ci",
|
||||
"inherits": [
|
||||
"base_ci",
|
||||
"macos_universal_base"
|
||||
],
|
||||
"displayName": "macOS (CI)",
|
||||
"cacheVariables": {
|
||||
"Launcher_BUILD_ARTIFACT": "macOS-Qt6"
|
||||
}
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
{
|
||||
"name": "macos_base",
|
||||
"hidden": true,
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Darwin"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "macos_debug",
|
||||
"inherits": [
|
||||
"macos_base"
|
||||
],
|
||||
"displayName": "macOS (Debug)",
|
||||
"configurePreset": "macos_debug"
|
||||
},
|
||||
{
|
||||
"name": "macos_release",
|
||||
"inherits": [
|
||||
"macos_base"
|
||||
],
|
||||
"displayName": "macOS (Release)",
|
||||
"configurePreset": "macos_release"
|
||||
},
|
||||
{
|
||||
"name": "macos_universal_debug",
|
||||
"inherits": [
|
||||
"macos_base"
|
||||
],
|
||||
"displayName": "macOS (Universal Binary, Debug)",
|
||||
"configurePreset": "macos_universal_debug"
|
||||
},
|
||||
{
|
||||
"name": "macos_universal_release",
|
||||
"inherits": [
|
||||
"macos_base"
|
||||
],
|
||||
"displayName": "macOS (Universal Binary, Release)",
|
||||
"configurePreset": "macos_universal_release"
|
||||
},
|
||||
{
|
||||
"name": "macos_ci",
|
||||
"inherits": [
|
||||
"macos_base"
|
||||
],
|
||||
"displayName": "macOS (CI)",
|
||||
"configurePreset": "macos_ci"
|
||||
}
|
||||
],
|
||||
"testPresets": [
|
||||
{
|
||||
"name": "macos_base",
|
||||
"hidden": true,
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Darwin"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "macos_debug",
|
||||
"inherits": [
|
||||
"base_debug",
|
||||
"macos_base"
|
||||
],
|
||||
"displayName": "MacOS (Debug)",
|
||||
"configurePreset": "macos_debug"
|
||||
},
|
||||
{
|
||||
"name": "macos_release",
|
||||
"inherits": [
|
||||
"base_release",
|
||||
"macos_base"
|
||||
],
|
||||
"displayName": "macOS (Release)",
|
||||
"configurePreset": "macos_release"
|
||||
},
|
||||
{
|
||||
"name": "macos_universal_debug",
|
||||
"inherits": [
|
||||
"base_debug",
|
||||
"macos_base"
|
||||
],
|
||||
"displayName": "MacOS (Universal Binary, Debug)",
|
||||
"configurePreset": "macos_universal_debug"
|
||||
},
|
||||
{
|
||||
"name": "macos_universal_release",
|
||||
"inherits": [
|
||||
"base_release",
|
||||
"macos_base"
|
||||
],
|
||||
"displayName": "macOS (Universal Binary, Release)",
|
||||
"configurePreset": "macos_universal_release"
|
||||
},
|
||||
{
|
||||
"name": "macos_ci",
|
||||
"inherits": [
|
||||
"base_release",
|
||||
"macos_base"
|
||||
],
|
||||
"displayName": "macOS (CI)",
|
||||
"configurePreset": "macos_ci"
|
||||
}
|
||||
],
|
||||
"workflowPresets": [
|
||||
{
|
||||
"name": "macos_debug",
|
||||
"displayName": "macOS (Debug)",
|
||||
"steps": [
|
||||
{
|
||||
"type": "configure",
|
||||
"name": "macos_debug"
|
||||
},
|
||||
{
|
||||
"type": "build",
|
||||
"name": "macos_debug"
|
||||
},
|
||||
{
|
||||
"type": "test",
|
||||
"name": "macos_debug"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "macos",
|
||||
"displayName": "macOS (Release)",
|
||||
"steps": [
|
||||
{
|
||||
"type": "configure",
|
||||
"name": "macos_release"
|
||||
},
|
||||
{
|
||||
"type": "build",
|
||||
"name": "macos_release"
|
||||
},
|
||||
{
|
||||
"type": "test",
|
||||
"name": "macos_release"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "macos_universal_debug",
|
||||
"displayName": "macOS (Universal Binary, Debug)",
|
||||
"steps": [
|
||||
{
|
||||
"type": "configure",
|
||||
"name": "macos_universal_debug"
|
||||
},
|
||||
{
|
||||
"type": "build",
|
||||
"name": "macos_universal_debug"
|
||||
},
|
||||
{
|
||||
"type": "test",
|
||||
"name": "macos_universal_debug"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "macos_universal",
|
||||
"displayName": "macOS (Universal Binary, Release)",
|
||||
"steps": [
|
||||
{
|
||||
"type": "configure",
|
||||
"name": "macos_universal_release"
|
||||
},
|
||||
{
|
||||
"type": "build",
|
||||
"name": "macos_universal_release"
|
||||
},
|
||||
{
|
||||
"type": "test",
|
||||
"name": "macos_universal_release"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "macos_ci",
|
||||
"displayName": "macOS (CI)",
|
||||
"steps": [
|
||||
{
|
||||
"type": "configure",
|
||||
"name": "macos_ci"
|
||||
},
|
||||
{
|
||||
"type": "build",
|
||||
"name": "macos_ci"
|
||||
},
|
||||
{
|
||||
"type": "test",
|
||||
"name": "macos_ci"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
311
cmake/windowsMSVCPreset.json
Normal file
311
cmake/windowsMSVCPreset.json
Normal file
|
@ -0,0 +1,311 @@
|
|||
{
|
||||
"$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json",
|
||||
"version": 8,
|
||||
"include": [
|
||||
"commonPresets.json"
|
||||
],
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "windows_msvc_base",
|
||||
"hidden": true,
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Windows"
|
||||
},
|
||||
"cacheVariables": {
|
||||
"Launcher_BUILD_ARTIFACT": "Windows-MSVC-Qt6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows_msvc_arm64_cross_base",
|
||||
"hidden": true,
|
||||
"inherits": [
|
||||
"windows_msvc_base"
|
||||
],
|
||||
"architecture": "arm64",
|
||||
"cacheVariables": {
|
||||
"Launcher_BUILD_ARTIFACT": "Windows-MSVC-arm64-Qt6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows_msvc_debug",
|
||||
"inherits": [
|
||||
"base_debug",
|
||||
"windows_msvc_base"
|
||||
],
|
||||
"displayName": "Windows MSVC (Debug)",
|
||||
"generator": "Ninja"
|
||||
},
|
||||
{
|
||||
"name": "windows_msvc_release",
|
||||
"inherits": [
|
||||
"base_release",
|
||||
"windows_msvc_base"
|
||||
],
|
||||
"displayName": "Windows MSVC (Release)"
|
||||
},
|
||||
{
|
||||
"name": "windows_msvc_arm64_cross_debug",
|
||||
"inherits": [
|
||||
"base_debug",
|
||||
"windows_msvc_arm64_cross_base"
|
||||
],
|
||||
"displayName": "Windows MSVC (ARM64 cross, Debug)"
|
||||
},
|
||||
{
|
||||
"name": "windows_msvc_arm64_cross_release",
|
||||
"inherits": [
|
||||
"base_release",
|
||||
"windows_msvc_arm64_cross_base"
|
||||
],
|
||||
"displayName": "Windows MSVC (ARM64 cross, Release)"
|
||||
},
|
||||
{
|
||||
"name": "windows_msvc_ci",
|
||||
"inherits": [
|
||||
"base_ci",
|
||||
"windows_msvc_base"
|
||||
],
|
||||
"displayName": "Windows MSVC (CI)",
|
||||
"cacheVariables": {
|
||||
"Launcher_BUILD_ARTIFACT": "Windows-MSVC-Qt6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows_msvc_arm64_cross_ci",
|
||||
"inherits": [
|
||||
"base_ci",
|
||||
"windows_msvc_arm64_cross_base"
|
||||
],
|
||||
"displayName": "Windows MSVC (ARM64 cross, CI)",
|
||||
"cacheVariables": {
|
||||
"Launcher_BUILD_ARTIFACT": "Windows-MSVC-arm64-Qt6"
|
||||
}
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
{
|
||||
"name": "windows_msvc_base",
|
||||
"hidden": true,
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Windows"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows_msvc_debug",
|
||||
"inherits": [
|
||||
"windows_msvc_base"
|
||||
],
|
||||
"displayName": "Windows MSVC (Debug)",
|
||||
"configurePreset": "windows_msvc_debug",
|
||||
"configuration": "Debug"
|
||||
},
|
||||
{
|
||||
"name": "windows_msvc_release",
|
||||
"inherits": [
|
||||
"windows_msvc_base"
|
||||
],
|
||||
"displayName": "Windows MSVC (Release)",
|
||||
"configurePreset": "windows_msvc_release",
|
||||
"configuration": "Release",
|
||||
"nativeToolOptions": [
|
||||
"/p:UseMultiToolTask=true",
|
||||
"/p:EnforceProcessCountAcrossBuilds=true"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "windows_msvc_arm64_cross_debug",
|
||||
"inherits": [
|
||||
"windows_msvc_base"
|
||||
],
|
||||
"displayName": "Windows MSVC (ARM64 cross, Debug)",
|
||||
"configurePreset": "windows_msvc_arm64_cross_debug",
|
||||
"configuration": "Debug",
|
||||
"nativeToolOptions": [
|
||||
"/p:UseMultiToolTask=true",
|
||||
"/p:EnforceProcessCountAcrossBuilds=true"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "windows_msvc_arm64_cross_release",
|
||||
"inherits": [
|
||||
"windows_msvc_base"
|
||||
],
|
||||
"displayName": "Windows MSVC (ARM64 cross, Release)",
|
||||
"configurePreset": "windows_msvc_arm64_cross_release",
|
||||
"configuration": "Release",
|
||||
"nativeToolOptions": [
|
||||
"/p:UseMultiToolTask=true",
|
||||
"/p:EnforceProcessCountAcrossBuilds=true"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "windows_msvc_ci",
|
||||
"inherits": [
|
||||
"windows_msvc_base"
|
||||
],
|
||||
"displayName": "Windows MSVC (CI)",
|
||||
"configurePreset": "windows_msvc_ci",
|
||||
"configuration": "Release",
|
||||
"nativeToolOptions": [
|
||||
"/p:UseMultiToolTask=true",
|
||||
"/p:EnforceProcessCountAcrossBuilds=true"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "windows_msvc_arm64_cross_ci",
|
||||
"inherits": [
|
||||
"windows_msvc_base"
|
||||
],
|
||||
"displayName": "Windows MSVC (ARM64 cross, CI)",
|
||||
"configurePreset": "windows_msvc_arm64_cross_ci",
|
||||
"configuration": "Release",
|
||||
"nativeToolOptions": [
|
||||
"/p:UseMultiToolTask=true",
|
||||
"/p:EnforceProcessCountAcrossBuilds=true"
|
||||
]
|
||||
}
|
||||
],
|
||||
"testPresets": [
|
||||
{
|
||||
"name": "windows_msvc_base",
|
||||
"hidden": true,
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Windows"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows_msvc_debug",
|
||||
"inherits": [
|
||||
"base_debug",
|
||||
"windows_msvc_base"
|
||||
],
|
||||
"displayName": "Windows MSVC (Debug)",
|
||||
"configurePreset": "windows_msvc_debug",
|
||||
"configuration": "Debug"
|
||||
},
|
||||
{
|
||||
"name": "windows_msvc_release",
|
||||
"inherits": [
|
||||
"base_release",
|
||||
"windows_msvc_base"
|
||||
],
|
||||
"displayName": "Windows MSVC (Release)",
|
||||
"configurePreset": "windows_msvc_release",
|
||||
"configuration": "Release"
|
||||
},
|
||||
{
|
||||
"name": "windows_msvc_ci",
|
||||
"inherits": [
|
||||
"base_release",
|
||||
"windows_msvc_base"
|
||||
],
|
||||
"displayName": "Windows MSVC (CI)",
|
||||
"configurePreset": "windows_msvc_ci",
|
||||
"configuration": "Release"
|
||||
}
|
||||
],
|
||||
"workflowPresets": [
|
||||
{
|
||||
"name": "windows_msvc_debug",
|
||||
"displayName": "Windows MSVC (Debug)",
|
||||
"steps": [
|
||||
{
|
||||
"type": "configure",
|
||||
"name": "windows_msvc_debug"
|
||||
},
|
||||
{
|
||||
"type": "build",
|
||||
"name": "windows_msvc_debug"
|
||||
},
|
||||
{
|
||||
"type": "test",
|
||||
"name": "windows_msvc_debug"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "windows_msvc",
|
||||
"displayName": "Windows MSVC (Release)",
|
||||
"steps": [
|
||||
{
|
||||
"type": "configure",
|
||||
"name": "windows_msvc_release"
|
||||
},
|
||||
{
|
||||
"type": "build",
|
||||
"name": "windows_msvc_release"
|
||||
},
|
||||
{
|
||||
"type": "test",
|
||||
"name": "windows_msvc_release"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "windows_msvc_arm64_cross_debug",
|
||||
"displayName": "Windows MSVC (ARM64 cross, Debug)",
|
||||
"steps": [
|
||||
{
|
||||
"type": "configure",
|
||||
"name": "windows_msvc_arm64_cross_debug"
|
||||
},
|
||||
{
|
||||
"type": "build",
|
||||
"name": "windows_msvc_arm64_cross_debug"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "windows_msvc_arm64_cross",
|
||||
"displayName": "Windows MSVC (ARM64 cross, Release)",
|
||||
"steps": [
|
||||
{
|
||||
"type": "configure",
|
||||
"name": "windows_msvc_arm64_cross_release"
|
||||
},
|
||||
{
|
||||
"type": "build",
|
||||
"name": "windows_msvc_arm64_cross_release"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "windows_msvc_ci",
|
||||
"displayName": "Windows MSVC (CI)",
|
||||
"steps": [
|
||||
{
|
||||
"type": "configure",
|
||||
"name": "windows_msvc_ci"
|
||||
},
|
||||
{
|
||||
"type": "build",
|
||||
"name": "windows_msvc_ci"
|
||||
},
|
||||
{
|
||||
"type": "test",
|
||||
"name": "windows_msvc_ci"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "windows_msvc_arm64_cross_ci",
|
||||
"displayName": "Windows MSVC (ARM64 cross, CI)",
|
||||
"steps": [
|
||||
{
|
||||
"type": "configure",
|
||||
"name": "windows_msvc_arm64_cross_ci"
|
||||
},
|
||||
{
|
||||
"type": "build",
|
||||
"name": "windows_msvc_arm64_cross_ci"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
183
cmake/windowsMinGWPreset.json
Normal file
183
cmake/windowsMinGWPreset.json
Normal file
|
@ -0,0 +1,183 @@
|
|||
{
|
||||
"$schema": "https://cmake.org/cmake/help/latest/_downloads/3e2d73bff478d88a7de0de736ba5e361/schema.json",
|
||||
"version": 8,
|
||||
"include": [
|
||||
"commonPresets.json"
|
||||
],
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "windows_mingw_base",
|
||||
"hidden": true,
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Windows"
|
||||
},
|
||||
"generator": "Ninja",
|
||||
"cacheVariables": {
|
||||
"Launcher_BUILD_ARTIFACT": "Windows-MinGW-w64-Qt6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows_mingw_debug",
|
||||
"inherits": [
|
||||
"base_debug",
|
||||
"windows_mingw_base"
|
||||
],
|
||||
"displayName": "Windows MinGW (Debug)"
|
||||
},
|
||||
{
|
||||
"name": "windows_mingw_release",
|
||||
"inherits": [
|
||||
"base_release",
|
||||
"windows_mingw_base"
|
||||
],
|
||||
"displayName": "Windows MinGW (Release)"
|
||||
},
|
||||
{
|
||||
"name": "windows_mingw_ci",
|
||||
"inherits": [
|
||||
"base_ci",
|
||||
"windows_mingw_base"
|
||||
],
|
||||
"displayName": "Windows MinGW (CI)",
|
||||
"cacheVariables": {
|
||||
"Launcher_BUILD_ARTIFACT": "Windows-MinGW-w64-Qt6"
|
||||
}
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
{
|
||||
"name": "windows_mingw_base",
|
||||
"hidden": true,
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Windows"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows_mingw_debug",
|
||||
"inherits": [
|
||||
"windows_mingw_base"
|
||||
],
|
||||
"displayName": "Windows MinGW (Debug)",
|
||||
"configurePreset": "windows_mingw_debug"
|
||||
},
|
||||
{
|
||||
"name": "windows_mingw_release",
|
||||
"inherits": [
|
||||
"windows_mingw_base"
|
||||
],
|
||||
"displayName": "Windows MinGW (Release)",
|
||||
"configurePreset": "windows_mingw_release"
|
||||
},
|
||||
{
|
||||
"name": "windows_mingw_ci",
|
||||
"inherits": [
|
||||
"windows_mingw_base"
|
||||
],
|
||||
"displayName": "Windows MinGW (CI)",
|
||||
"configurePreset": "windows_mingw_ci"
|
||||
}
|
||||
],
|
||||
"testPresets": [
|
||||
{
|
||||
"name": "windows_mingw_base",
|
||||
"hidden": true,
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Windows"
|
||||
},
|
||||
"filter": {
|
||||
"exclude": {
|
||||
"name": "^example64|example$"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows_mingw_debug",
|
||||
"inherits": [
|
||||
"base_debug",
|
||||
"windows_mingw_base"
|
||||
],
|
||||
"displayName": "Windows MinGW (Debug)",
|
||||
"configurePreset": "windows_mingw_debug"
|
||||
},
|
||||
{
|
||||
"name": "windows_mingw_release",
|
||||
"inherits": [
|
||||
"base_release",
|
||||
"windows_mingw_base"
|
||||
],
|
||||
"displayName": "Windows MinGW (Release)",
|
||||
"configurePreset": "windows_mingw_release"
|
||||
},
|
||||
{
|
||||
"name": "windows_mingw_ci",
|
||||
"inherits": [
|
||||
"base_release",
|
||||
"windows_mingw_base"
|
||||
],
|
||||
"displayName": "Windows MinGW (CI)",
|
||||
"configurePreset": "windows_mingw_ci"
|
||||
}
|
||||
],
|
||||
"workflowPresets": [
|
||||
{
|
||||
"name": "windows_mingw_debug",
|
||||
"displayName": "Windows MinGW (Debug)",
|
||||
"steps": [
|
||||
{
|
||||
"type": "configure",
|
||||
"name": "windows_mingw_debug"
|
||||
},
|
||||
{
|
||||
"type": "build",
|
||||
"name": "windows_mingw_debug"
|
||||
},
|
||||
{
|
||||
"type": "test",
|
||||
"name": "windows_mingw_debug"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "windows_mingw",
|
||||
"displayName": "Windows MinGW (Release)",
|
||||
"steps": [
|
||||
{
|
||||
"type": "configure",
|
||||
"name": "windows_mingw_release"
|
||||
},
|
||||
{
|
||||
"type": "build",
|
||||
"name": "windows_mingw_release"
|
||||
},
|
||||
{
|
||||
"type": "test",
|
||||
"name": "windows_mingw_release"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "windows_mingw_ci",
|
||||
"displayName": "Windows MinGW (CI)",
|
||||
"steps": [
|
||||
{
|
||||
"type": "configure",
|
||||
"name": "windows_mingw_ci"
|
||||
},
|
||||
{
|
||||
"type": "build",
|
||||
"name": "windows_mingw_ci"
|
||||
},
|
||||
{
|
||||
"type": "test",
|
||||
"name": "windows_mingw_ci"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
31
flake.lock
generated
31
flake.lock
generated
|
@ -3,11 +3,11 @@
|
|||
"libnbtplusplus": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1699286814,
|
||||
"narHash": "sha256-yy0q+bky80LtK1GWzz7qpM+aAGrOqLuewbid8WT1ilk=",
|
||||
"lastModified": 1744811532,
|
||||
"narHash": "sha256-qhmjaRkt+O7A+gu6HjUkl7QzOEb4r8y8vWZMG2R/C6o=",
|
||||
"owner": "PrismLauncher",
|
||||
"repo": "libnbtplusplus",
|
||||
"rev": "23b955121b8217c1c348a9ed2483167a6f3ff4ad",
|
||||
"rev": "531449ba1c930c98e0bcf5d332b237a8566f9d78",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -18,11 +18,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1744463964,
|
||||
"narHash": "sha256-LWqduOgLHCFxiTNYi3Uj5Lgz0SR+Xhw3kr/3Xd0GPTM=",
|
||||
"lastModified": 1746663147,
|
||||
"narHash": "sha256-Ua0drDHawlzNqJnclTJGf87dBmaO/tn7iZ+TCkTRpRc=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2631b0b7abcea6e640ce31cd78ea58910d31e650",
|
||||
"rev": "dda3dcd3fe03e991015e9a74b22d35950f264a54",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -32,10 +32,27 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"qt-qrcodegenerator": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1737616857,
|
||||
"narHash": "sha256-6SugPt0lp1Gz7nV23FLmsmpfzgFItkSw7jpGftsDPWc=",
|
||||
"owner": "nayuki",
|
||||
"repo": "QR-Code-generator",
|
||||
"rev": "2c9044de6b049ca25cb3cd1649ed7e27aa055138",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nayuki",
|
||||
"repo": "QR-Code-generator",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"libnbtplusplus": "libnbtplusplus",
|
||||
"nixpkgs": "nixpkgs"
|
||||
"nixpkgs": "nixpkgs",
|
||||
"qt-qrcodegenerator": "qt-qrcodegenerator"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -15,6 +15,11 @@
|
|||
url = "github:PrismLauncher/libnbtplusplus";
|
||||
flake = false;
|
||||
};
|
||||
|
||||
qt-qrcodegenerator = {
|
||||
url = "github:nayuki/QR-Code-generator";
|
||||
flake = false;
|
||||
};
|
||||
};
|
||||
|
||||
outputs =
|
||||
|
@ -22,6 +27,7 @@
|
|||
self,
|
||||
nixpkgs,
|
||||
libnbtplusplus,
|
||||
qt-qrcodegenerator,
|
||||
}:
|
||||
|
||||
let
|
||||
|
@ -132,6 +138,8 @@
|
|||
|
||||
{
|
||||
default = pkgs.mkShell {
|
||||
name = "prism-launcher";
|
||||
|
||||
inputsFrom = [ packages'.prismlauncher-unwrapped ];
|
||||
|
||||
packages = with pkgs; [
|
||||
|
@ -167,6 +175,7 @@
|
|||
prismlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix {
|
||||
inherit
|
||||
libnbtplusplus
|
||||
qt-qrcodegenerator
|
||||
self
|
||||
;
|
||||
};
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit f5d368a31d6ef046eb2955c74ec6f54f32ed5c4e
|
||||
Subproject commit 73f08ed2c3187f6648ca04ebef030930a6c9f0be
|
|
@ -97,6 +97,7 @@
|
|||
#include <QList>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QStringList>
|
||||
#include <QStringLiteral>
|
||||
#include <QStyleFactory>
|
||||
#include <QTranslator>
|
||||
#include <QWindow>
|
||||
|
@ -128,6 +129,7 @@
|
|||
|
||||
#include <stdlib.h>
|
||||
#include <sys.h>
|
||||
#include <QStringLiteral>
|
||||
#include "SysInfo.h"
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
|
@ -154,11 +156,16 @@
|
|||
#endif
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <QStyleHints>
|
||||
#include "WindowsConsole.h"
|
||||
#include "console/WindowsConsole.h"
|
||||
#endif
|
||||
|
||||
#include "console/Console.h"
|
||||
|
||||
#define STRINGIFY(x) #x
|
||||
#define TOSTRING(x) STRINGIFY(x)
|
||||
|
||||
|
@ -166,6 +173,63 @@ static const QLatin1String liveCheckFile("live.check");
|
|||
|
||||
PixmapCache* PixmapCache::s_instance = nullptr;
|
||||
|
||||
static bool isANSIColorConsole;
|
||||
|
||||
static QString defaultLogFormat = QStringLiteral(
|
||||
"%{time process}"
|
||||
" "
|
||||
"%{if-debug}Debug:%{endif}"
|
||||
"%{if-info}Info:%{endif}"
|
||||
"%{if-warning}Warning:%{endif}"
|
||||
"%{if-critical}Critical:%{endif}"
|
||||
"%{if-fatal}Fatal:%{endif}"
|
||||
" "
|
||||
"%{if-category}[%{category}] %{endif}"
|
||||
"%{message}"
|
||||
" "
|
||||
"(%{function}:%{line})");
|
||||
|
||||
#define ansi_reset "\x1b[0m"
|
||||
#define ansi_bold "\x1b[1m"
|
||||
#define ansi_reset_bold "\x1b[22m"
|
||||
#define ansi_faint "\x1b[2m"
|
||||
#define ansi_italic "\x1b[3m"
|
||||
#define ansi_red_fg "\x1b[31m"
|
||||
#define ansi_green_fg "\x1b[32m"
|
||||
#define ansi_yellow_fg "\x1b[33m"
|
||||
#define ansi_blue_fg "\x1b[34m"
|
||||
#define ansi_purple_fg "\x1b[35m"
|
||||
#define ansi_inverse "\x1b[7m"
|
||||
|
||||
// clang-format off
|
||||
static QString ansiLogFormat = QStringLiteral(
|
||||
ansi_faint "%{time process}" ansi_reset
|
||||
" "
|
||||
"%{if-debug}" ansi_bold ansi_green_fg "D:" ansi_reset "%{endif}"
|
||||
"%{if-info}" ansi_bold ansi_blue_fg "I:" ansi_reset "%{endif}"
|
||||
"%{if-warning}" ansi_bold ansi_yellow_fg "W:" ansi_reset_bold "%{endif}"
|
||||
"%{if-critical}" ansi_bold ansi_red_fg "C:" ansi_reset_bold "%{endif}"
|
||||
"%{if-fatal}" ansi_bold ansi_inverse ansi_red_fg "F:" ansi_reset_bold "%{endif}"
|
||||
" "
|
||||
"%{if-category}" ansi_bold "[%{category}]" ansi_reset_bold " %{endif}"
|
||||
"%{message}"
|
||||
" "
|
||||
ansi_reset ansi_faint "(%{function}:%{line})" ansi_reset
|
||||
);
|
||||
// clang-format on
|
||||
|
||||
#undef ansi_inverse
|
||||
#undef ansi_purple_fg
|
||||
#undef ansi_blue_fg
|
||||
#undef ansi_yellow_fg
|
||||
#undef ansi_green_fg
|
||||
#undef ansi_red_fg
|
||||
#undef ansi_italic
|
||||
#undef ansi_faint
|
||||
#undef ansi_bold
|
||||
#undef ansi_reset_bold
|
||||
#undef ansi_reset
|
||||
|
||||
namespace {
|
||||
|
||||
/** This is used so that we can output to the log file in addition to the CLI. */
|
||||
|
@ -174,11 +238,24 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext& context, const QSt
|
|||
static std::mutex loggerMutex;
|
||||
const std::lock_guard<std::mutex> lock(loggerMutex); // synchronized, QFile logFile is not thread-safe
|
||||
|
||||
if (isANSIColorConsole) {
|
||||
// ensure default is set for log file
|
||||
qSetMessagePattern(defaultLogFormat);
|
||||
}
|
||||
|
||||
QString out = qFormatLogMessage(type, context, msg);
|
||||
out += QChar::LineFeed;
|
||||
|
||||
APPLICATION->logFile->write(out.toUtf8());
|
||||
APPLICATION->logFile->flush();
|
||||
|
||||
if (isANSIColorConsole) {
|
||||
// format ansi for console;
|
||||
qSetMessagePattern(ansiLogFormat);
|
||||
out = qFormatLogMessage(type, context, msg);
|
||||
out += QChar::LineFeed;
|
||||
}
|
||||
|
||||
QTextStream(stderr) << out.toLocal8Bit();
|
||||
fflush(stderr);
|
||||
}
|
||||
|
@ -219,8 +296,18 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||
// attach the parent console if stdout not already captured
|
||||
if (AttachWindowsConsole()) {
|
||||
consoleAttached = true;
|
||||
if (auto err = EnableAnsiSupport(); !err) {
|
||||
isANSIColorConsole = true;
|
||||
} else {
|
||||
std::cout << "Error setting up ansi console" << err.message() << std::endl;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (console::isConsole()) {
|
||||
isANSIColorConsole = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
setOrganizationName(BuildConfig.LAUNCHER_NAME);
|
||||
setOrganizationDomain(BuildConfig.LAUNCHER_DOMAIN);
|
||||
setApplicationName(BuildConfig.LAUNCHER_NAME);
|
||||
|
@ -449,27 +536,14 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||
return;
|
||||
}
|
||||
qInstallMessageHandler(appDebugOutput);
|
||||
|
||||
qSetMessagePattern(
|
||||
"%{time process}"
|
||||
" "
|
||||
"%{if-debug}D%{endif}"
|
||||
"%{if-info}I%{endif}"
|
||||
"%{if-warning}W%{endif}"
|
||||
"%{if-critical}C%{endif}"
|
||||
"%{if-fatal}F%{endif}"
|
||||
" "
|
||||
"|"
|
||||
" "
|
||||
"%{if-category}[%{category}]: %{endif}"
|
||||
"%{message}");
|
||||
qSetMessagePattern(defaultLogFormat);
|
||||
|
||||
bool foundLoggingRules = false;
|
||||
|
||||
auto logRulesFile = QStringLiteral("qtlogging.ini");
|
||||
auto logRulesPath = FS::PathCombine(dataPath, logRulesFile);
|
||||
|
||||
qDebug() << "Testing" << logRulesPath << "...";
|
||||
qInfo() << "Testing" << logRulesPath << "...";
|
||||
foundLoggingRules = QFile::exists(logRulesPath);
|
||||
|
||||
// search the dataPath()
|
||||
|
@ -477,7 +551,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||
if (!foundLoggingRules && !isPortable() && dirParam.isEmpty() && dataDirEnv.isEmpty()) {
|
||||
logRulesPath = QStandardPaths::locate(QStandardPaths::AppDataLocation, FS::PathCombine("..", logRulesFile));
|
||||
if (!logRulesPath.isEmpty()) {
|
||||
qDebug() << "Found" << logRulesPath << "...";
|
||||
qInfo() << "Found" << logRulesPath << "...";
|
||||
foundLoggingRules = true;
|
||||
}
|
||||
}
|
||||
|
@ -488,28 +562,28 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||
#else
|
||||
logRulesPath = FS::PathCombine(m_rootPath, logRulesFile);
|
||||
#endif
|
||||
qDebug() << "Testing" << logRulesPath << "...";
|
||||
qInfo() << "Testing" << logRulesPath << "...";
|
||||
foundLoggingRules = QFile::exists(logRulesPath);
|
||||
}
|
||||
|
||||
if (foundLoggingRules) {
|
||||
// load and set logging rules
|
||||
qDebug() << "Loading logging rules from:" << logRulesPath;
|
||||
qInfo() << "Loading logging rules from:" << logRulesPath;
|
||||
QSettings loggingRules(logRulesPath, QSettings::IniFormat);
|
||||
loggingRules.beginGroup("Rules");
|
||||
QStringList rule_names = loggingRules.childKeys();
|
||||
QStringList rules;
|
||||
qDebug() << "Setting log rules:";
|
||||
qInfo() << "Setting log rules:";
|
||||
for (auto rule_name : rule_names) {
|
||||
auto rule = QString("%1=%2").arg(rule_name).arg(loggingRules.value(rule_name).toString());
|
||||
rules.append(rule);
|
||||
qDebug() << " " << rule;
|
||||
qInfo() << " " << rule;
|
||||
}
|
||||
auto rules_str = rules.join("\n");
|
||||
QLoggingCategory::setFilterRules(rules_str);
|
||||
}
|
||||
|
||||
qDebug() << "<> Log initialized.";
|
||||
qInfo() << "<> Log initialized.";
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -526,33 +600,33 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||
}
|
||||
|
||||
{
|
||||
qDebug() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME + ", " + QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", "));
|
||||
qDebug() << "Version : " << BuildConfig.printableVersionString();
|
||||
qDebug() << "Platform : " << BuildConfig.BUILD_PLATFORM;
|
||||
qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT;
|
||||
qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC;
|
||||
qDebug() << "Compiled for : " << BuildConfig.systemID();
|
||||
qDebug() << "Compiled by : " << BuildConfig.compilerID();
|
||||
qDebug() << "Build Artifact : " << BuildConfig.BUILD_ARTIFACT;
|
||||
qDebug() << "Updates Enabled : " << (updaterEnabled() ? "Yes" : "No");
|
||||
qInfo() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME + ", " + QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", "));
|
||||
qInfo() << "Version : " << BuildConfig.printableVersionString();
|
||||
qInfo() << "Platform : " << BuildConfig.BUILD_PLATFORM;
|
||||
qInfo() << "Git commit : " << BuildConfig.GIT_COMMIT;
|
||||
qInfo() << "Git refspec : " << BuildConfig.GIT_REFSPEC;
|
||||
qInfo() << "Compiled for : " << BuildConfig.systemID();
|
||||
qInfo() << "Compiled by : " << BuildConfig.compilerID();
|
||||
qInfo() << "Build Artifact : " << BuildConfig.BUILD_ARTIFACT;
|
||||
qInfo() << "Updates Enabled : " << (updaterEnabled() ? "Yes" : "No");
|
||||
if (adjustedBy.size()) {
|
||||
qDebug() << "Work dir before adjustment : " << origcwdPath;
|
||||
qDebug() << "Work dir after adjustment : " << QDir::currentPath();
|
||||
qDebug() << "Adjusted by : " << adjustedBy;
|
||||
qInfo() << "Work dir before adjustment : " << origcwdPath;
|
||||
qInfo() << "Work dir after adjustment : " << QDir::currentPath();
|
||||
qInfo() << "Adjusted by : " << adjustedBy;
|
||||
} else {
|
||||
qDebug() << "Work dir : " << QDir::currentPath();
|
||||
qInfo() << "Work dir : " << QDir::currentPath();
|
||||
}
|
||||
qDebug() << "Binary path : " << binPath;
|
||||
qDebug() << "Application root path : " << m_rootPath;
|
||||
qInfo() << "Binary path : " << binPath;
|
||||
qInfo() << "Application root path : " << m_rootPath;
|
||||
if (!m_instanceIdToLaunch.isEmpty()) {
|
||||
qDebug() << "ID of instance to launch : " << m_instanceIdToLaunch;
|
||||
qInfo() << "ID of instance to launch : " << m_instanceIdToLaunch;
|
||||
}
|
||||
if (!m_serverToJoin.isEmpty()) {
|
||||
qDebug() << "Address of server to join :" << m_serverToJoin;
|
||||
qInfo() << "Address of server to join :" << m_serverToJoin;
|
||||
} else if (!m_worldToJoin.isEmpty()) {
|
||||
qDebug() << "Name of the world to join :" << m_worldToJoin;
|
||||
qInfo() << "Name of the world to join :" << m_worldToJoin;
|
||||
}
|
||||
qDebug() << "<> Paths set.";
|
||||
qInfo() << "<> Paths set.";
|
||||
}
|
||||
|
||||
if (m_liveCheck) {
|
||||
|
@ -820,7 +894,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||
|
||||
PixmapCache::setInstance(new PixmapCache(this));
|
||||
|
||||
qDebug() << "<> Settings loaded.";
|
||||
qInfo() << "<> Settings loaded.";
|
||||
}
|
||||
|
||||
#ifndef QT_NO_ACCESSIBILITY
|
||||
|
@ -836,7 +910,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||
QString user = settings()->get("ProxyUser").toString();
|
||||
QString pass = settings()->get("ProxyPass").toString();
|
||||
updateProxySettings(proxyTypeStr, addr, port, user, pass);
|
||||
qDebug() << "<> Network done.";
|
||||
qInfo() << "<> Network done.";
|
||||
}
|
||||
|
||||
// load translations
|
||||
|
@ -844,8 +918,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||
m_translations.reset(new TranslationsModel("translations"));
|
||||
auto bcp47Name = m_settings->get("Language").toString();
|
||||
m_translations->selectLanguage(bcp47Name);
|
||||
qDebug() << "Your language is" << bcp47Name;
|
||||
qDebug() << "<> Translations loaded.";
|
||||
qInfo() << "Your language is" << bcp47Name;
|
||||
qInfo() << "<> Translations loaded.";
|
||||
}
|
||||
|
||||
// Instance icons
|
||||
|
@ -856,7 +930,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||
m_icons.reset(new IconList(instFolders, setting->get().toString()));
|
||||
connect(setting.get(), &Setting::SettingChanged,
|
||||
[this](const Setting&, QVariant value) { m_icons->directoryChanged(value.toString()); });
|
||||
qDebug() << "<> Instance icons initialized.";
|
||||
qInfo() << "<> Instance icons initialized.";
|
||||
}
|
||||
|
||||
// Themes
|
||||
|
@ -868,25 +942,25 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||
// instance path: check for problems with '!' in instance path and warn the user in the log
|
||||
// and remember that we have to show him a dialog when the gui starts (if it does so)
|
||||
QString instDir = InstDirSetting->get().toString();
|
||||
qDebug() << "Instance path : " << instDir;
|
||||
qInfo() << "Instance path : " << instDir;
|
||||
if (FS::checkProblemticPathJava(QDir(instDir))) {
|
||||
qWarning() << "Your instance path contains \'!\' and this is known to cause java problems!";
|
||||
}
|
||||
m_instances.reset(new InstanceList(m_settings, instDir, this));
|
||||
connect(InstDirSetting.get(), &Setting::SettingChanged, m_instances.get(), &InstanceList::on_InstFolderChanged);
|
||||
qDebug() << "Loading Instances...";
|
||||
qInfo() << "Loading Instances...";
|
||||
m_instances->loadList();
|
||||
qDebug() << "<> Instances loaded.";
|
||||
qInfo() << "<> Instances loaded.";
|
||||
}
|
||||
|
||||
// and accounts
|
||||
{
|
||||
m_accounts.reset(new AccountList(this));
|
||||
qDebug() << "Loading accounts...";
|
||||
qInfo() << "Loading accounts...";
|
||||
m_accounts->setListFilePath("accounts.json", true);
|
||||
m_accounts->loadList();
|
||||
m_accounts->fillQueue();
|
||||
qDebug() << "<> Accounts loaded.";
|
||||
qInfo() << "<> Accounts loaded.";
|
||||
}
|
||||
|
||||
// init the http meta cache
|
||||
|
@ -907,7 +981,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||
m_metacache->addBase("meta", QDir("meta").absolutePath());
|
||||
m_metacache->addBase("java", QDir("cache/java").absolutePath());
|
||||
m_metacache->Load();
|
||||
qDebug() << "<> Cache initialized.";
|
||||
qInfo() << "<> Cache initialized.";
|
||||
}
|
||||
|
||||
// now we have network, download translation updates
|
||||
|
@ -1811,17 +1885,6 @@ QString Application::getUserAgent()
|
|||
return BuildConfig.USER_AGENT;
|
||||
}
|
||||
|
||||
QString Application::getUserAgentUncached()
|
||||
{
|
||||
QString uaOverride = m_settings->get("UserAgentOverride").toString();
|
||||
if (!uaOverride.isEmpty()) {
|
||||
uaOverride += " (Uncached)";
|
||||
return uaOverride.replace("$LAUNCHER_VER", BuildConfig.printableVersionString());
|
||||
}
|
||||
|
||||
return BuildConfig.USER_AGENT_UNCACHED;
|
||||
}
|
||||
|
||||
bool Application::handleDataMigration(const QString& currentData,
|
||||
const QString& oldData,
|
||||
const QString& name,
|
||||
|
|
|
@ -160,7 +160,6 @@ class Application : public QApplication {
|
|||
QString getFlameAPIKey();
|
||||
QString getModrinthAPIToken();
|
||||
QString getUserAgent();
|
||||
QString getUserAgentUncached();
|
||||
|
||||
/// this is the root of the 'installation'. Used for automatic updates
|
||||
const QString& root() { return m_rootPath; }
|
||||
|
|
|
@ -42,8 +42,8 @@
|
|||
#include <QFileInfo>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include "Application.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
#include "settings/OverrideSetting.h"
|
||||
#include "settings/Setting.h"
|
||||
|
@ -174,6 +174,12 @@ void BaseInstance::copyManagedPack(BaseInstance& other)
|
|||
m_settings->set("ManagedPackName", other.getManagedPackName());
|
||||
m_settings->set("ManagedPackVersionID", other.getManagedPackVersionID());
|
||||
m_settings->set("ManagedPackVersionName", other.getManagedPackVersionName());
|
||||
|
||||
if (APPLICATION->settings()->get("AutomaticJavaSwitch").toBool() && m_settings->get("AutomaticJava").toBool() &&
|
||||
m_settings->get("OverrideJavaLocation").toBool()) {
|
||||
m_settings->set("OverrideJavaLocation", false);
|
||||
m_settings->set("JavaPath", "");
|
||||
}
|
||||
}
|
||||
|
||||
int BaseInstance::getConsoleMaxLines() const
|
||||
|
|
|
@ -151,9 +151,6 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
|
|||
void setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version);
|
||||
void copyManagedPack(BaseInstance& other);
|
||||
|
||||
/// guess log level from a line of game log
|
||||
virtual MessageLevel::Enum guessLevel([[maybe_unused]] const QString& line, MessageLevel::Enum level) { return level; }
|
||||
|
||||
virtual QStringList extraArguments();
|
||||
|
||||
/// Traits. Normally inside the version, depends on instance implementation.
|
||||
|
@ -198,15 +195,10 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
|
|||
virtual QProcessEnvironment createEnvironment() = 0;
|
||||
virtual QProcessEnvironment createLaunchEnvironment() = 0;
|
||||
|
||||
/*!
|
||||
* Returns a matcher that can maps relative paths within the instance to whether they are 'log files'
|
||||
*/
|
||||
virtual IPathMatcher::Ptr getLogFileMatcher() = 0;
|
||||
|
||||
/*!
|
||||
* Returns the root folder to use for looking up log files
|
||||
*/
|
||||
virtual QString getLogFileRoot() = 0;
|
||||
virtual QStringList getLogFileSearchPaths() = 0;
|
||||
|
||||
virtual QString getStatusbarDescription() = 0;
|
||||
|
||||
|
|
|
@ -99,7 +99,7 @@ set(CORE_SOURCES
|
|||
MTPixmapCache.h
|
||||
)
|
||||
if (UNIX AND NOT CYGWIN AND NOT APPLE)
|
||||
set(CORE_SOURCES
|
||||
set(CORE_SOURCES
|
||||
${CORE_SOURCES}
|
||||
|
||||
# MangoHud
|
||||
|
@ -175,6 +175,8 @@ set(LAUNCH_SOURCES
|
|||
launch/LogModel.h
|
||||
launch/TaskStepWrapper.cpp
|
||||
launch/TaskStepWrapper.h
|
||||
logs/LogParser.cpp
|
||||
logs/LogParser.h
|
||||
)
|
||||
|
||||
# Old update system
|
||||
|
@ -308,6 +310,8 @@ set(MINECRAFT_SOURCES
|
|||
minecraft/ParseUtils.h
|
||||
minecraft/ProfileUtils.cpp
|
||||
minecraft/ProfileUtils.h
|
||||
minecraft/ShortcutUtils.cpp
|
||||
minecraft/ShortcutUtils.h
|
||||
minecraft/Library.cpp
|
||||
minecraft/Library.h
|
||||
minecraft/MojangDownloadInfo.h
|
||||
|
@ -589,8 +593,8 @@ set(ATLAUNCHER_SOURCES
|
|||
)
|
||||
|
||||
set(LINKEXE_SOURCES
|
||||
WindowsConsole.cpp
|
||||
WindowsConsole.h
|
||||
console/WindowsConsole.h
|
||||
console/WindowsConsole.cpp
|
||||
|
||||
filelink/FileLink.h
|
||||
filelink/FileLink.cpp
|
||||
|
@ -659,6 +663,14 @@ set(PRISMUPDATER_SOURCES
|
|||
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
set(PRISMUPDATER_SOURCES
|
||||
console/WindowsConsole.h
|
||||
console/WindowsConsole.cpp
|
||||
${PRISMUPDATER_SOURCES}
|
||||
)
|
||||
endif()
|
||||
|
||||
######## Logging categories ########
|
||||
|
||||
ecm_qt_declare_logging_category(CORE_SOURCES
|
||||
|
@ -786,6 +798,9 @@ SET(LAUNCHER_SOURCES
|
|||
SysInfo.h
|
||||
SysInfo.cpp
|
||||
|
||||
# console utils
|
||||
console/Console.h
|
||||
|
||||
# GUI - general utilities
|
||||
DesktopServices.h
|
||||
DesktopServices.cpp
|
||||
|
@ -821,6 +836,10 @@ SET(LAUNCHER_SOURCES
|
|||
icons/IconList.h
|
||||
icons/IconList.cpp
|
||||
|
||||
# log utils
|
||||
logs/AnonymizeLog.cpp
|
||||
logs/AnonymizeLog.h
|
||||
|
||||
# GUI - windows
|
||||
ui/GuiUtil.h
|
||||
ui/GuiUtil.cpp
|
||||
|
@ -1031,6 +1050,8 @@ SET(LAUNCHER_SOURCES
|
|||
ui/dialogs/ProfileSetupDialog.h
|
||||
ui/dialogs/CopyInstanceDialog.cpp
|
||||
ui/dialogs/CopyInstanceDialog.h
|
||||
ui/dialogs/CreateShortcutDialog.cpp
|
||||
ui/dialogs/CreateShortcutDialog.h
|
||||
ui/dialogs/CustomMessageBox.cpp
|
||||
ui/dialogs/CustomMessageBox.h
|
||||
ui/dialogs/ExportInstanceDialog.cpp
|
||||
|
@ -1154,7 +1175,7 @@ SET(LAUNCHER_SOURCES
|
|||
)
|
||||
|
||||
if (NOT Apple)
|
||||
set(LAUNCHER_SOURCES
|
||||
set(LAUNCHER_SOURCES
|
||||
${LAUNCHER_SOURCES}
|
||||
|
||||
ui/dialogs/UpdateAvailableDialog.h
|
||||
|
@ -1164,8 +1185,8 @@ endif()
|
|||
|
||||
if(WIN32)
|
||||
set(LAUNCHER_SOURCES
|
||||
WindowsConsole.cpp
|
||||
WindowsConsole.h
|
||||
console/WindowsConsole.h
|
||||
console/WindowsConsole.cpp
|
||||
${LAUNCHER_SOURCES}
|
||||
)
|
||||
endif()
|
||||
|
@ -1212,6 +1233,7 @@ qt_wrap_ui(LAUNCHER_UI
|
|||
ui/widgets/MinecraftSettingsWidget.ui
|
||||
ui/widgets/JavaSettingsWidget.ui
|
||||
ui/dialogs/CopyInstanceDialog.ui
|
||||
ui/dialogs/CreateShortcutDialog.ui
|
||||
ui/dialogs/ProfileSetupDialog.ui
|
||||
ui/dialogs/ProgressDialog.ui
|
||||
ui/dialogs/NewInstanceDialog.ui
|
||||
|
@ -1288,6 +1310,7 @@ target_link_libraries(Launcher_logic
|
|||
qdcss
|
||||
BuildConfig
|
||||
Qt${QT_VERSION_MAJOR}::Widgets
|
||||
qrcode
|
||||
)
|
||||
|
||||
if (UNIX AND NOT CYGWIN AND NOT APPLE)
|
||||
|
@ -1323,11 +1346,11 @@ if(APPLE)
|
|||
set(CMAKE_INSTALL_RPATH "@loader_path/../Frameworks/")
|
||||
|
||||
if(Launcher_ENABLE_UPDATER)
|
||||
file(DOWNLOAD ${MACOSX_SPARKLE_DOWNLOAD_URL} ${CMAKE_BINARY_DIR}/Sparkle.tar.xz EXPECTED_HASH SHA256=${MACOSX_SPARKLE_SHA256})
|
||||
file(ARCHIVE_EXTRACT INPUT ${CMAKE_BINARY_DIR}/Sparkle.tar.xz DESTINATION ${CMAKE_BINARY_DIR}/frameworks/Sparkle)
|
||||
file(DOWNLOAD ${MACOSX_SPARKLE_DOWNLOAD_URL} ${CMAKE_BINARY_DIR}/Sparkle.tar.xz EXPECTED_HASH SHA256=${MACOSX_SPARKLE_SHA256})
|
||||
file(ARCHIVE_EXTRACT INPUT ${CMAKE_BINARY_DIR}/Sparkle.tar.xz DESTINATION ${CMAKE_BINARY_DIR}/frameworks/Sparkle)
|
||||
|
||||
find_library(SPARKLE_FRAMEWORK Sparkle "${CMAKE_BINARY_DIR}/frameworks/Sparkle")
|
||||
add_compile_definitions(SPARKLE_ENABLED)
|
||||
find_library(SPARKLE_FRAMEWORK Sparkle "${CMAKE_BINARY_DIR}/frameworks/Sparkle")
|
||||
add_compile_definitions(SPARKLE_ENABLED)
|
||||
endif()
|
||||
|
||||
target_link_libraries(Launcher_logic
|
||||
|
@ -1337,7 +1360,7 @@ if(APPLE)
|
|||
"-framework ApplicationServices"
|
||||
)
|
||||
if(Launcher_ENABLE_UPDATER)
|
||||
target_link_libraries(Launcher_logic ${SPARKLE_FRAMEWORK})
|
||||
target_link_libraries(Launcher_logic ${SPARKLE_FRAMEWORK})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
|
|
@ -37,11 +37,7 @@ void DataMigrationTask::dryRunFinished()
|
|||
disconnect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::dryRunFinished);
|
||||
disconnect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &DataMigrationTask::dryRunAborted);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
if (!m_copyFuture.isValid() || !m_copyFuture.result()) {
|
||||
#else
|
||||
if (!m_copyFuture.result()) {
|
||||
#endif
|
||||
emitFailed(tr("Failed to scan source path."));
|
||||
return;
|
||||
}
|
||||
|
@ -75,11 +71,7 @@ void DataMigrationTask::copyFinished()
|
|||
disconnect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::copyFinished);
|
||||
disconnect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &DataMigrationTask::copyAborted);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
if (!m_copyFuture.isValid() || !m_copyFuture.result()) {
|
||||
#else
|
||||
if (!m_copyFuture.result()) {
|
||||
#endif
|
||||
emitFailed(tr("Some paths could not be copied!"));
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -269,9 +269,9 @@ bool FileIgnoreProxy::ignoreFile(QFileInfo fileInfo) const
|
|||
return m_ignoreFiles.contains(fileInfo.fileName()) || m_ignoreFilePaths.covers(relPath(fileInfo.absoluteFilePath()));
|
||||
}
|
||||
|
||||
bool FileIgnoreProxy::filterFile(const QString& fileName) const
|
||||
bool FileIgnoreProxy::filterFile(const QFileInfo& file) const
|
||||
{
|
||||
return m_blocked.covers(fileName) || ignoreFile(QFileInfo(QDir(m_root), fileName));
|
||||
return m_blocked.covers(relPath(file.absoluteFilePath())) || ignoreFile(file);
|
||||
}
|
||||
|
||||
void FileIgnoreProxy::loadBlockedPathsFromFile(const QString& fileName)
|
||||
|
@ -282,11 +282,7 @@ void FileIgnoreProxy::loadBlockedPathsFromFile(const QString& fileName)
|
|||
}
|
||||
auto ignoreData = ignoreFile.readAll();
|
||||
auto string = QString::fromUtf8(ignoreData);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
setBlockedPaths(string.split('\n', Qt::SkipEmptyParts));
|
||||
#else
|
||||
setBlockedPaths(string.split('\n', QString::SkipEmptyParts));
|
||||
#endif
|
||||
}
|
||||
|
||||
void FileIgnoreProxy::saveBlockedPathsToFile(const QString& fileName)
|
||||
|
|
|
@ -69,7 +69,7 @@ class FileIgnoreProxy : public QSortFilterProxyModel {
|
|||
// list of relative paths that need to be removed completely from model
|
||||
inline SeparatorPrefixTree<'/'>& ignoreFilesWithPath() { return m_ignoreFilePaths; }
|
||||
|
||||
bool filterFile(const QString& fileName) const;
|
||||
bool filterFile(const QFileInfo& fileName) const;
|
||||
|
||||
void loadBlockedPathsFromFile(const QString& fileName);
|
||||
|
||||
|
|
|
@ -107,6 +107,10 @@ namespace fs = std::filesystem;
|
|||
|
||||
#if defined(__MINGW32__)
|
||||
|
||||
// Avoid re-defining structs retroactively added to MinGW
|
||||
// https://github.com/mingw-w64/mingw-w64/issues/90#issuecomment-2829284729
|
||||
#if __MINGW64_VERSION_MAJOR < 13
|
||||
|
||||
struct _DUPLICATE_EXTENTS_DATA {
|
||||
HANDLE FileHandle;
|
||||
LARGE_INTEGER SourceFileOffset;
|
||||
|
@ -116,6 +120,7 @@ struct _DUPLICATE_EXTENTS_DATA {
|
|||
|
||||
using DUPLICATE_EXTENTS_DATA = _DUPLICATE_EXTENTS_DATA;
|
||||
using PDUPLICATE_EXTENTS_DATA = _DUPLICATE_EXTENTS_DATA*;
|
||||
#endif
|
||||
|
||||
struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER {
|
||||
WORD ChecksumAlgorithm; // Checksum algorithm. e.g. CHECKSUM_TYPE_UNCHANGED, CHECKSUM_TYPE_NONE, CHECKSUM_TYPE_CRC32
|
||||
|
@ -679,9 +684,6 @@ bool deletePath(QString path)
|
|||
|
||||
bool trash(QString path, QString* pathInTrash)
|
||||
{
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
|
||||
return false;
|
||||
#else
|
||||
// FIXME: Figure out trash in Flatpak. Qt seemingly doesn't use the Trash portal
|
||||
if (DesktopServices::isFlatpak())
|
||||
return false;
|
||||
|
@ -690,7 +692,6 @@ bool trash(QString path, QString* pathInTrash)
|
|||
return false;
|
||||
#endif
|
||||
return QFile::moveToTrash(path, pathInTrash);
|
||||
#endif
|
||||
}
|
||||
|
||||
QString PathCombine(const QString& path1, const QString& path2)
|
||||
|
@ -724,11 +725,7 @@ int pathDepth(const QString& path)
|
|||
|
||||
QFileInfo info(path);
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
|
||||
auto parts = QDir::toNativeSeparators(info.path()).split(QDir::separator(), QString::SkipEmptyParts);
|
||||
#else
|
||||
auto parts = QDir::toNativeSeparators(info.path()).split(QDir::separator(), Qt::SkipEmptyParts);
|
||||
#endif
|
||||
|
||||
int numParts = parts.length();
|
||||
numParts -= parts.count(".");
|
||||
|
@ -748,11 +745,7 @@ QString pathTruncate(const QString& path, int depth)
|
|||
return pathTruncate(trunc, depth);
|
||||
}
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
|
||||
auto parts = QDir::toNativeSeparators(trunc).split(QDir::separator(), QString::SkipEmptyParts);
|
||||
#else
|
||||
auto parts = QDir::toNativeSeparators(trunc).split(QDir::separator(), Qt::SkipEmptyParts);
|
||||
#endif
|
||||
|
||||
if (parts.startsWith(".") && !path.startsWith(".")) {
|
||||
parts.removeFirst();
|
||||
|
@ -899,6 +892,11 @@ QString getDesktopDir()
|
|||
return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
|
||||
}
|
||||
|
||||
QString getApplicationsDir()
|
||||
{
|
||||
return QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation);
|
||||
}
|
||||
|
||||
// Cross-platform Shortcut creation
|
||||
bool createShortcut(QString destination, QString target, QStringList args, QString name, QString icon)
|
||||
{
|
||||
|
@ -910,16 +908,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
|
|||
return false;
|
||||
}
|
||||
#if defined(Q_OS_MACOS)
|
||||
// Create the Application
|
||||
QDir applicationDirectory =
|
||||
QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + "/" + BuildConfig.LAUNCHER_NAME + " Instances/";
|
||||
|
||||
if (!applicationDirectory.mkpath(".")) {
|
||||
qWarning() << "Couldn't create application directory";
|
||||
return false;
|
||||
}
|
||||
|
||||
QDir application = applicationDirectory.path() + "/" + name + ".app/";
|
||||
QDir application = destination + ".app/";
|
||||
|
||||
if (application.exists()) {
|
||||
qWarning() << "Application already exists!";
|
||||
|
|
|
@ -353,6 +353,9 @@ bool checkProblemticPathJava(QDir folder);
|
|||
// Get the Directory representing the User's Desktop
|
||||
QString getDesktopDir();
|
||||
|
||||
// Get the Directory representing the User's Applications directory
|
||||
QString getApplicationsDir();
|
||||
|
||||
// Overrides one folder with the contents of another, preserving items exclusive to the first folder
|
||||
// Equivalent to doing QDir::rename, but allowing for overrides
|
||||
bool overrideFolder(QString overwritten_path, QString override_path);
|
||||
|
|
|
@ -36,6 +36,8 @@
|
|||
#include "GZip.h"
|
||||
#include <zlib.h>
|
||||
#include <QByteArray>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
|
||||
bool GZip::unzip(const QByteArray& compressedBytes, QByteArray& uncompressedBytes)
|
||||
{
|
||||
|
@ -136,3 +138,81 @@ bool GZip::zip(const QByteArray& uncompressedBytes, QByteArray& compressedBytes)
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int inf(QFile* source, std::function<bool(const QByteArray&)> handleBlock)
|
||||
{
|
||||
constexpr auto CHUNK = 16384;
|
||||
int ret;
|
||||
unsigned have;
|
||||
z_stream strm;
|
||||
memset(&strm, 0, sizeof(strm));
|
||||
char in[CHUNK];
|
||||
unsigned char out[CHUNK];
|
||||
|
||||
ret = inflateInit2(&strm, (16 + MAX_WBITS));
|
||||
if (ret != Z_OK)
|
||||
return ret;
|
||||
|
||||
/* decompress until deflate stream ends or end of file */
|
||||
do {
|
||||
strm.avail_in = source->read(in, CHUNK);
|
||||
if (source->error()) {
|
||||
(void)inflateEnd(&strm);
|
||||
return Z_ERRNO;
|
||||
}
|
||||
if (strm.avail_in == 0)
|
||||
break;
|
||||
strm.next_in = reinterpret_cast<Bytef*>(in);
|
||||
|
||||
/* run inflate() on input until output buffer not full */
|
||||
do {
|
||||
strm.avail_out = CHUNK;
|
||||
strm.next_out = out;
|
||||
ret = inflate(&strm, Z_NO_FLUSH);
|
||||
assert(ret != Z_STREAM_ERROR); /* state not clobbered */
|
||||
switch (ret) {
|
||||
case Z_NEED_DICT:
|
||||
ret = Z_DATA_ERROR; /* and fall through */
|
||||
case Z_DATA_ERROR:
|
||||
case Z_MEM_ERROR:
|
||||
(void)inflateEnd(&strm);
|
||||
return ret;
|
||||
}
|
||||
have = CHUNK - strm.avail_out;
|
||||
if (!handleBlock(QByteArray(reinterpret_cast<const char*>(out), have))) {
|
||||
(void)inflateEnd(&strm);
|
||||
return Z_OK;
|
||||
}
|
||||
|
||||
} while (strm.avail_out == 0);
|
||||
|
||||
/* done when inflate() says it's done */
|
||||
} while (ret != Z_STREAM_END);
|
||||
|
||||
/* clean up and return */
|
||||
(void)inflateEnd(&strm);
|
||||
return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;
|
||||
}
|
||||
|
||||
QString zerr(int ret)
|
||||
{
|
||||
switch (ret) {
|
||||
case Z_ERRNO:
|
||||
return QObject::tr("error handling file");
|
||||
case Z_STREAM_ERROR:
|
||||
return QObject::tr("invalid compression level");
|
||||
case Z_DATA_ERROR:
|
||||
return QObject::tr("invalid or incomplete deflate data");
|
||||
case Z_MEM_ERROR:
|
||||
return QObject::tr("out of memory");
|
||||
case Z_VERSION_ERROR:
|
||||
return QObject::tr("zlib version mismatch!");
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QString GZip::readGzFileByBlocks(QFile* source, std::function<bool(const QByteArray&)> handleBlock)
|
||||
{
|
||||
auto ret = inf(source, handleBlock);
|
||||
return zerr(ret);
|
||||
}
|
|
@ -1,8 +1,11 @@
|
|||
#pragma once
|
||||
#include <QByteArray>
|
||||
#include <QFile>
|
||||
|
||||
class GZip {
|
||||
public:
|
||||
static bool unzip(const QByteArray& compressedBytes, QByteArray& uncompressedBytes);
|
||||
static bool zip(const QByteArray& uncompressedBytes, QByteArray& compressedBytes);
|
||||
};
|
||||
namespace GZip {
|
||||
|
||||
bool unzip(const QByteArray& compressedBytes, QByteArray& uncompressedBytes);
|
||||
bool zip(const QByteArray& uncompressedBytes, QByteArray& compressedBytes);
|
||||
QString readGzFileByBlocks(QFile* source, std::function<bool(const QByteArray&)> handleBlock);
|
||||
|
||||
} // namespace GZip
|
||||
|
|
|
@ -72,7 +72,6 @@ bool InstanceImportTask::abort()
|
|||
bool wasAborted = false;
|
||||
if (m_task)
|
||||
wasAborted = m_task->abort();
|
||||
Task::abort();
|
||||
return wasAborted;
|
||||
}
|
||||
|
||||
|
@ -263,6 +262,25 @@ void InstanceImportTask::extractFinished()
|
|||
}
|
||||
}
|
||||
|
||||
bool installIcon(QString root, QString instIcon)
|
||||
{
|
||||
auto importIconPath = IconUtils::findBestIconIn(root, instIcon);
|
||||
if (importIconPath.isNull() || !QFile::exists(importIconPath))
|
||||
importIconPath = IconUtils::findBestIconIn(root, "icon.png");
|
||||
if (importIconPath.isNull() || !QFile::exists(importIconPath))
|
||||
importIconPath = IconUtils::findBestIconIn(FS::PathCombine(root, "overrides"), "icon.png");
|
||||
if (!importIconPath.isNull() && QFile::exists(importIconPath)) {
|
||||
// import icon
|
||||
auto iconList = APPLICATION->icons();
|
||||
if (iconList->iconFileExists(instIcon)) {
|
||||
iconList->deleteIcon(instIcon);
|
||||
}
|
||||
iconList->installIcon(importIconPath, instIcon);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void InstanceImportTask::processFlame()
|
||||
{
|
||||
shared_qobject_ptr<FlameCreationTask> inst_creation_task = nullptr;
|
||||
|
@ -288,6 +306,14 @@ void InstanceImportTask::processFlame()
|
|||
}
|
||||
|
||||
inst_creation_task->setName(*this);
|
||||
// if the icon was specified by user, use that. otherwise pull icon from the pack
|
||||
if (m_instIcon == "default") {
|
||||
auto iconKey = QString("Flame_%1_Icon").arg(name());
|
||||
|
||||
if (installIcon(m_stagingPath, iconKey)) {
|
||||
m_instIcon = iconKey;
|
||||
}
|
||||
}
|
||||
inst_creation_task->setIcon(m_instIcon);
|
||||
inst_creation_task->setGroup(m_instGroup);
|
||||
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
|
||||
|
@ -305,7 +331,7 @@ void InstanceImportTask::processFlame()
|
|||
connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus);
|
||||
connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails);
|
||||
|
||||
connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort);
|
||||
connect(inst_creation_task.get(), &Task::aborted, this, &InstanceImportTask::emitAborted);
|
||||
connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
|
||||
|
||||
m_task.reset(inst_creation_task);
|
||||
|
@ -340,17 +366,7 @@ void InstanceImportTask::processMultiMC()
|
|||
} else {
|
||||
m_instIcon = instance.iconKey();
|
||||
|
||||
auto importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), m_instIcon);
|
||||
if (importIconPath.isNull() || !QFile::exists(importIconPath))
|
||||
importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), "icon.png");
|
||||
if (!importIconPath.isNull() && QFile::exists(importIconPath)) {
|
||||
// import icon
|
||||
auto iconList = APPLICATION->icons();
|
||||
if (iconList->iconFileExists(m_instIcon)) {
|
||||
iconList->deleteIcon(m_instIcon);
|
||||
}
|
||||
iconList->installIcon(importIconPath, m_instIcon);
|
||||
}
|
||||
installIcon(instance.instanceRoot(), m_instIcon);
|
||||
}
|
||||
emitSucceeded();
|
||||
}
|
||||
|
@ -378,8 +394,8 @@ void InstanceImportTask::processModrinth()
|
|||
} else {
|
||||
QString pack_id;
|
||||
if (!m_sourceUrl.isEmpty()) {
|
||||
QRegularExpression regex(R"(data\/([^\/]*)\/versions)");
|
||||
pack_id = regex.match(m_sourceUrl.toString()).captured(1);
|
||||
static const QRegularExpression s_regex(R"(data\/([^\/]*)\/versions)");
|
||||
pack_id = s_regex.match(m_sourceUrl.toString()).captured(1);
|
||||
}
|
||||
|
||||
// FIXME: Find a way to get the ID in directly imported ZIPs
|
||||
|
@ -387,6 +403,14 @@ void InstanceImportTask::processModrinth()
|
|||
}
|
||||
|
||||
inst_creation_task->setName(*this);
|
||||
// if the icon was specified by user, use that. otherwise pull icon from the pack
|
||||
if (m_instIcon == "default") {
|
||||
auto iconKey = QString("Modrinth_%1_Icon").arg(name());
|
||||
|
||||
if (installIcon(m_stagingPath, iconKey)) {
|
||||
m_instIcon = iconKey;
|
||||
}
|
||||
}
|
||||
inst_creation_task->setIcon(m_instIcon);
|
||||
inst_creation_task->setGroup(m_instGroup);
|
||||
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
|
||||
|
@ -404,7 +428,7 @@ void InstanceImportTask::processModrinth()
|
|||
connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus);
|
||||
connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails);
|
||||
|
||||
connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort);
|
||||
connect(inst_creation_task.get(), &Task::aborted, this, &InstanceImportTask::emitAborted);
|
||||
connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
|
||||
|
||||
m_task.reset(inst_creation_task);
|
||||
|
|
|
@ -428,7 +428,7 @@ static QMap<InstanceId, InstanceLocator> getIdMapping(const QList<InstancePtr>&
|
|||
|
||||
QList<InstanceId> InstanceList::discoverInstances()
|
||||
{
|
||||
qDebug() << "Discovering instances in" << m_instDir;
|
||||
qInfo() << "Discovering instances in" << m_instDir;
|
||||
QList<InstanceId> out;
|
||||
QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks);
|
||||
while (iter.hasNext()) {
|
||||
|
@ -447,13 +447,9 @@ QList<InstanceId> InstanceList::discoverInstances()
|
|||
}
|
||||
auto id = dirInfo.fileName();
|
||||
out.append(id);
|
||||
qDebug() << "Found instance ID" << id;
|
||||
qInfo() << "Found instance ID" << id;
|
||||
}
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
instanceSet = QSet<QString>(out.begin(), out.end());
|
||||
#else
|
||||
instanceSet = out.toSet();
|
||||
#endif
|
||||
m_instancesProbed = true;
|
||||
return out;
|
||||
}
|
||||
|
@ -468,7 +464,7 @@ InstanceList::InstListError InstanceList::loadList()
|
|||
if (existingIds.contains(id)) {
|
||||
auto instPair = existingIds[id];
|
||||
existingIds.remove(id);
|
||||
qDebug() << "Should keep and soft-reload" << id;
|
||||
qInfo() << "Should keep and soft-reload" << id;
|
||||
} else {
|
||||
InstancePtr instPtr = loadInstance(id);
|
||||
if (instPtr) {
|
||||
|
|
|
@ -44,10 +44,7 @@ class InstancePageProvider : protected QObject, public BasePageProvider {
|
|||
// values.append(new GameOptionsPage(onesix.get()));
|
||||
values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots")));
|
||||
values.append(new InstanceSettingsPage(onesix));
|
||||
auto logMatcher = inst->getLogFileMatcher();
|
||||
if (logMatcher) {
|
||||
values.append(new OtherLogsPage(inst->getLogFileRoot(), logMatcher));
|
||||
}
|
||||
values.append(new OtherLogsPage(inst));
|
||||
return values;
|
||||
}
|
||||
|
||||
|
|
|
@ -41,7 +41,9 @@
|
|||
|
||||
bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget* parent)
|
||||
{
|
||||
if (jvmargs.contains("-XX:PermSize=") || jvmargs.contains(QRegularExpression("-Xm[sx]")) || jvmargs.contains("-XX-MaxHeapSize") ||
|
||||
static const QRegularExpression s_memRegex("-Xm[sx]");
|
||||
static const QRegularExpression s_versionRegex("-version:.*");
|
||||
if (jvmargs.contains("-XX:PermSize=") || jvmargs.contains(s_memRegex) || jvmargs.contains("-XX-MaxHeapSize") ||
|
||||
jvmargs.contains("-XX:InitialHeapSize")) {
|
||||
auto warnStr = QObject::tr(
|
||||
"You tried to manually set a JVM memory option (using \"-XX:PermSize\", \"-XX-MaxHeapSize\", \"-XX:InitialHeapSize\", \"-Xmx\" "
|
||||
|
@ -52,7 +54,7 @@ bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget* parent)
|
|||
return false;
|
||||
}
|
||||
// block lunacy with passing required version to the JVM
|
||||
if (jvmargs.contains(QRegularExpression("-version:.*"))) {
|
||||
if (jvmargs.contains(s_versionRegex)) {
|
||||
auto warnStr = QObject::tr(
|
||||
"You tried to pass required Java version argument to the JVM (using \"-version:xxx\"). This is not safe and will not be "
|
||||
"allowed.\n"
|
||||
|
|
|
@ -188,10 +188,10 @@ T ensureIsType(const QJsonObject& parent, const QString& key, const T default_ =
|
|||
}
|
||||
|
||||
template <typename T>
|
||||
QVector<T> requireIsArrayOf(const QJsonDocument& doc)
|
||||
QList<T> requireIsArrayOf(const QJsonDocument& doc)
|
||||
{
|
||||
const QJsonArray array = requireArray(doc);
|
||||
QVector<T> out;
|
||||
QList<T> out;
|
||||
for (const QJsonValue val : array) {
|
||||
out.append(requireIsType<T>(val, "Document"));
|
||||
}
|
||||
|
@ -199,10 +199,10 @@ QVector<T> requireIsArrayOf(const QJsonDocument& doc)
|
|||
}
|
||||
|
||||
template <typename T>
|
||||
QVector<T> ensureIsArrayOf(const QJsonValue& value, const QString& what = "Value")
|
||||
QList<T> ensureIsArrayOf(const QJsonValue& value, const QString& what = "Value")
|
||||
{
|
||||
const QJsonArray array = ensureIsType<QJsonArray>(value, QJsonArray(), what);
|
||||
QVector<T> out;
|
||||
QList<T> out;
|
||||
for (const QJsonValue val : array) {
|
||||
out.append(requireIsType<T>(val, what));
|
||||
}
|
||||
|
@ -210,7 +210,7 @@ QVector<T> ensureIsArrayOf(const QJsonValue& value, const QString& what = "Value
|
|||
}
|
||||
|
||||
template <typename T>
|
||||
QVector<T> ensureIsArrayOf(const QJsonValue& value, const QVector<T> default_, const QString& what = "Value")
|
||||
QList<T> ensureIsArrayOf(const QJsonValue& value, const QList<T> default_, const QString& what = "Value")
|
||||
{
|
||||
if (value.isUndefined()) {
|
||||
return default_;
|
||||
|
@ -220,7 +220,7 @@ QVector<T> ensureIsArrayOf(const QJsonValue& value, const QVector<T> default_, c
|
|||
|
||||
/// @throw JsonException
|
||||
template <typename T>
|
||||
QVector<T> requireIsArrayOf(const QJsonObject& parent, const QString& key, const QString& what = "__placeholder__")
|
||||
QList<T> requireIsArrayOf(const QJsonObject& parent, const QString& key, const QString& what = "__placeholder__")
|
||||
{
|
||||
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
|
||||
if (!parent.contains(key)) {
|
||||
|
@ -230,10 +230,10 @@ QVector<T> requireIsArrayOf(const QJsonObject& parent, const QString& key, const
|
|||
}
|
||||
|
||||
template <typename T>
|
||||
QVector<T> ensureIsArrayOf(const QJsonObject& parent,
|
||||
const QString& key,
|
||||
const QVector<T>& default_ = QVector<T>(),
|
||||
const QString& what = "__placeholder__")
|
||||
QList<T> ensureIsArrayOf(const QJsonObject& parent,
|
||||
const QString& key,
|
||||
const QList<T>& default_ = QList<T>(),
|
||||
const QString& what = "__placeholder__")
|
||||
{
|
||||
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
|
||||
if (!parent.contains(key)) {
|
||||
|
|
|
@ -182,7 +182,8 @@ void LaunchController::login()
|
|||
auto name = askOfflineName("Player", m_demo, ok);
|
||||
if (ok) {
|
||||
m_session = std::make_shared<AuthSession>();
|
||||
m_session->MakeDemo(name, MinecraftAccount::uuidFromUsername(name).toString().remove(QRegularExpression("[{}-]")));
|
||||
static const QRegularExpression s_removeChars("[{}-]");
|
||||
m_session->MakeDemo(name, MinecraftAccount::uuidFromUsername(name).toString().remove(s_removeChars));
|
||||
launchInstance();
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -418,7 +418,7 @@ bool extractFile(QString fileCompressed, QString file, QString target)
|
|||
return extractRelFile(&zip, file, target);
|
||||
}
|
||||
|
||||
bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFunction excludeFilter)
|
||||
bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFileFunction excludeFilter)
|
||||
{
|
||||
QDir rootDirectory(rootDir);
|
||||
if (!rootDirectory.exists())
|
||||
|
@ -443,8 +443,8 @@ bool collectFileListRecursively(const QString& rootDir, const QString& subDir, Q
|
|||
// collect files
|
||||
entries = directory.entryInfoList(QDir::Files);
|
||||
for (const auto& e : entries) {
|
||||
QString relativeFilePath = rootDirectory.relativeFilePath(e.absoluteFilePath());
|
||||
if (excludeFilter && excludeFilter(relativeFilePath)) {
|
||||
if (excludeFilter && excludeFilter(e)) {
|
||||
QString relativeFilePath = rootDirectory.relativeFilePath(e.absoluteFilePath());
|
||||
qDebug() << "Skipping file " << relativeFilePath;
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
|
||||
namespace MMCZip {
|
||||
using FilterFunction = std::function<bool(const QString&)>;
|
||||
using FilterFileFunction = std::function<bool(const QFileInfo&)>;
|
||||
|
||||
/**
|
||||
* Merge two zip files, using a filter function
|
||||
|
@ -149,7 +150,7 @@ bool extractFile(QString fileCompressed, QString file, QString dir);
|
|||
* \param excludeFilter function to excludeFilter which files shouldn't be included (returning true means to excude)
|
||||
* \return true for success or false for failure
|
||||
*/
|
||||
bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFunction excludeFilter);
|
||||
bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFileFunction excludeFilter);
|
||||
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
class ExportToZipTask : public Task {
|
||||
|
|
|
@ -2,19 +2,22 @@
|
|||
|
||||
MessageLevel::Enum MessageLevel::getLevel(const QString& levelName)
|
||||
{
|
||||
if (levelName == "Launcher")
|
||||
QString name = levelName.toUpper();
|
||||
if (name == "LAUNCHER")
|
||||
return MessageLevel::Launcher;
|
||||
else if (levelName == "Debug")
|
||||
else if (name == "TRACE")
|
||||
return MessageLevel::Trace;
|
||||
else if (name == "DEBUG")
|
||||
return MessageLevel::Debug;
|
||||
else if (levelName == "Info")
|
||||
else if (name == "INFO")
|
||||
return MessageLevel::Info;
|
||||
else if (levelName == "Message")
|
||||
else if (name == "MESSAGE")
|
||||
return MessageLevel::Message;
|
||||
else if (levelName == "Warning")
|
||||
else if (name == "WARNING" || name == "WARN")
|
||||
return MessageLevel::Warning;
|
||||
else if (levelName == "Error")
|
||||
else if (name == "ERROR")
|
||||
return MessageLevel::Error;
|
||||
else if (levelName == "Fatal")
|
||||
else if (name == "FATAL")
|
||||
return MessageLevel::Fatal;
|
||||
// Skip PrePost, it's not exposed to !![]!
|
||||
// Also skip StdErr and StdOut
|
||||
|
|
|
@ -12,6 +12,7 @@ enum Enum {
|
|||
StdOut, /**< Undetermined stderr messages */
|
||||
StdErr, /**< Undetermined stdout messages */
|
||||
Launcher, /**< Launcher Messages */
|
||||
Trace, /**< Trace Messages */
|
||||
Debug, /**< Debug Messages */
|
||||
Info, /**< Info Messages */
|
||||
Message, /**< Standard Messages */
|
||||
|
|
|
@ -57,8 +57,7 @@ class NullInstance : public BaseInstance {
|
|||
QProcessEnvironment createEnvironment() override { return QProcessEnvironment(); }
|
||||
QProcessEnvironment createLaunchEnvironment() override { return QProcessEnvironment(); }
|
||||
QMap<QString, QString> getVariables() override { return QMap<QString, QString>(); }
|
||||
IPathMatcher::Ptr getLogFileMatcher() override { return nullptr; }
|
||||
QString getLogFileRoot() override { return instanceRoot(); }
|
||||
QStringList getLogFileSearchPaths() override { return {}; }
|
||||
QString typeName() const override { return "Null"; }
|
||||
bool canExport() const override { return false; }
|
||||
bool canEdit() const override { return false; }
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#include "RecursiveFileSystemWatcher.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QRegularExpression>
|
||||
|
||||
RecursiveFileSystemWatcher::RecursiveFileSystemWatcher(QObject* parent) : QObject(parent), m_watcher(new QFileSystemWatcher(this))
|
||||
{
|
||||
|
|
|
@ -53,7 +53,7 @@ static inline QChar getNextChar(const QString& s, int location)
|
|||
int StringUtils::naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs)
|
||||
{
|
||||
int l1 = 0, l2 = 0;
|
||||
while (l1 <= s1.count() && l2 <= s2.count()) {
|
||||
while (l1 <= s1.size() && l2 <= s2.size()) {
|
||||
// skip spaces, tabs and 0's
|
||||
QChar c1 = getNextChar(s1, l1);
|
||||
while (c1.isSpace())
|
||||
|
@ -213,11 +213,10 @@ QPair<QString, QString> StringUtils::splitFirst(const QString& s, const QRegular
|
|||
return qMakePair(left, right);
|
||||
}
|
||||
|
||||
static const QRegularExpression ulMatcher("<\\s*/\\s*ul\\s*>");
|
||||
|
||||
QString StringUtils::htmlListPatch(QString htmlStr)
|
||||
{
|
||||
int pos = htmlStr.indexOf(ulMatcher);
|
||||
static const QRegularExpression s_ulMatcher("<\\s*/\\s*ul\\s*>");
|
||||
int pos = htmlStr.indexOf(s_ulMatcher);
|
||||
int imgPos;
|
||||
while (pos != -1) {
|
||||
pos = htmlStr.indexOf(">", pos) + 1; // Get the size of the </ul> tag. Add one for zeroeth index
|
||||
|
@ -230,7 +229,7 @@ QString StringUtils::htmlListPatch(QString htmlStr)
|
|||
if (textBetween.isEmpty())
|
||||
htmlStr.insert(pos, "<br>");
|
||||
|
||||
pos = htmlStr.indexOf(ulMatcher, pos);
|
||||
pos = htmlStr.indexOf(s_ulMatcher, pos);
|
||||
}
|
||||
return htmlStr;
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
#include "Version.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QRegularExpression>
|
||||
#include <QRegularExpressionMatch>
|
||||
#include <QUrl>
|
||||
|
||||
|
|
|
@ -72,22 +72,14 @@ class Version {
|
|||
}
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
auto numPart = QStringView{ m_fullString }.left(cutoff);
|
||||
#else
|
||||
auto numPart = m_fullString.leftRef(cutoff);
|
||||
#endif
|
||||
|
||||
if (!numPart.isEmpty()) {
|
||||
m_isNull = false;
|
||||
m_numPart = numPart.toInt();
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
auto stringPart = QStringView{ m_fullString }.mid(cutoff);
|
||||
#else
|
||||
auto stringPart = m_fullString.midRef(cutoff);
|
||||
#endif
|
||||
|
||||
if (!stringPart.isEmpty()) {
|
||||
m_isNull = false;
|
||||
|
|
|
@ -295,13 +295,11 @@ void VersionProxyModel::sourceDataChanged(const QModelIndex& source_top_left, co
|
|||
void VersionProxyModel::setSourceModel(QAbstractItemModel* replacingRaw)
|
||||
{
|
||||
auto replacing = dynamic_cast<BaseVersionList*>(replacingRaw);
|
||||
beginResetModel();
|
||||
|
||||
m_columns.clear();
|
||||
if (!replacing) {
|
||||
roles.clear();
|
||||
filterModel->setSourceModel(replacing);
|
||||
endResetModel();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -343,8 +341,6 @@ void VersionProxyModel::setSourceModel(QAbstractItemModel* replacingRaw)
|
|||
hasLatest = true;
|
||||
}
|
||||
filterModel->setSourceModel(replacing);
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
QModelIndex VersionProxyModel::getRecommended() const
|
||||
|
|
33
launcher/console/Console.h
Normal file
33
launcher/console/Console.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include <ostream>
|
||||
#if defined Q_OS_WIN32
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <cstdio>
|
||||
#endif
|
||||
|
||||
namespace console {
|
||||
|
||||
inline bool isConsole()
|
||||
{
|
||||
#if defined Q_OS_WIN32
|
||||
DWORD procIDs[2];
|
||||
DWORD maxCount = 2;
|
||||
DWORD result = GetConsoleProcessList((LPDWORD)procIDs, maxCount);
|
||||
return result > 1;
|
||||
#else
|
||||
if (isatty(fileno(stdout))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace console
|
|
@ -16,13 +16,18 @@
|
|||
*
|
||||
*/
|
||||
|
||||
#include "WindowsConsole.h"
|
||||
#include <system_error>
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#include <stdio.h>
|
||||
#include <windows.h>
|
||||
#include <cstddef>
|
||||
#include <iostream>
|
||||
|
||||
void RedirectHandle(DWORD handle, FILE* stream, const char* mode)
|
||||
|
@ -126,3 +131,29 @@ bool AttachWindowsConsole()
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::error_code EnableAnsiSupport()
|
||||
{
|
||||
// ref: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
|
||||
// Using `CreateFileW("CONOUT$", ...)` to retrieve the console handle works correctly even if STDOUT and/or STDERR are redirected
|
||||
HANDLE console_handle = CreateFileW(L"CONOUT$", FILE_GENERIC_READ | FILE_GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0);
|
||||
if (console_handle == INVALID_HANDLE_VALUE) {
|
||||
return std::error_code(GetLastError(), std::system_category());
|
||||
}
|
||||
|
||||
// ref: https://docs.microsoft.com/en-us/windows/console/getconsolemode
|
||||
DWORD console_mode;
|
||||
if (0 == GetConsoleMode(console_handle, &console_mode)) {
|
||||
return std::error_code(GetLastError(), std::system_category());
|
||||
}
|
||||
|
||||
// VT processing not already enabled?
|
||||
if ((console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0) {
|
||||
// https://docs.microsoft.com/en-us/windows/console/setconsolemode
|
||||
if (0 == SetConsoleMode(console_handle, console_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) {
|
||||
return std::error_code(GetLastError(), std::system_category());
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
|
@ -21,5 +21,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <system_error>
|
||||
|
||||
void BindCrtHandlesToStdHandles(bool bindStdIn, bool bindStdOut, bool bindStdErr);
|
||||
bool AttachWindowsConsole();
|
||||
std::error_code EnableAnsiSupport();
|
|
@ -37,7 +37,10 @@
|
|||
#include <sys.h>
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
#include "WindowsConsole.h"
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include "console/WindowsConsole.h"
|
||||
#endif
|
||||
|
||||
#include <filesystem>
|
||||
|
|
|
@ -137,11 +137,7 @@ QString formatName(const QDir& iconsDir, const QFileInfo& iconFile)
|
|||
/// Split into a separate function because the preprocessing impedes readability
|
||||
QSet<QString> toStringSet(const QList<QString>& list)
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
QSet<QString> set(list.begin(), list.end());
|
||||
#else
|
||||
QSet<QString> set = list.toSet();
|
||||
#endif
|
||||
return set;
|
||||
}
|
||||
|
||||
|
@ -165,7 +161,8 @@ void IconList::directoryChanged(const QString& path)
|
|||
for (const MMCIcon& it : m_icons) {
|
||||
if (!it.has(IconType::FileBased))
|
||||
continue;
|
||||
currentSet.insert(it.m_images[IconType::FileBased].filename);
|
||||
QFileInfo icon(it.getFilePath());
|
||||
currentSet.insert(icon.absoluteFilePath());
|
||||
}
|
||||
QSet<QString> toRemove = currentSet - newSet;
|
||||
QSet<QString> toAdd = newSet - currentSet;
|
||||
|
@ -173,7 +170,8 @@ void IconList::directoryChanged(const QString& path)
|
|||
for (const QString& removedPath : toRemove) {
|
||||
qDebug() << "Removing icon " << removedPath;
|
||||
QFileInfo removedFile(removedPath);
|
||||
QString key = m_dir.relativeFilePath(removedFile.absoluteFilePath());
|
||||
QString relativePath = m_dir.relativeFilePath(removedFile.absoluteFilePath());
|
||||
QString key = QFileInfo(relativePath).completeBaseName();
|
||||
|
||||
int idx = getIconIndex(key);
|
||||
if (idx == -1)
|
||||
|
@ -475,4 +473,4 @@ QString IconList::iconDirectory(const QString& key) const
|
|||
}
|
||||
}
|
||||
return getDirectory();
|
||||
}
|
||||
}
|
|
@ -106,6 +106,6 @@ class IconList : public QAbstractListModel {
|
|||
shared_qobject_ptr<QFileSystemWatcher> m_watcher;
|
||||
bool m_isWatching;
|
||||
QMap<QString, int> m_nameIndex;
|
||||
QVector<MMCIcon> m_icons;
|
||||
QList<MMCIcon> m_icons;
|
||||
QDir m_dir;
|
||||
};
|
||||
|
|
|
@ -137,11 +137,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
|
|||
|
||||
QMap<QString, QString> results;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
QStringList lines = m_stdout.split("\n", Qt::SkipEmptyParts);
|
||||
#else
|
||||
QStringList lines = m_stdout.split("\n", QString::SkipEmptyParts);
|
||||
#endif
|
||||
for (QString line : lines) {
|
||||
line = line.trimmed();
|
||||
// NOTE: workaround for GH-4125, where garbage is getting printed into stdout on bedrock linux
|
||||
|
@ -149,11 +145,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
|
|||
continue;
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
auto parts = line.split('=', Qt::SkipEmptyParts);
|
||||
#else
|
||||
auto parts = line.split('=', QString::SkipEmptyParts);
|
||||
#endif
|
||||
if (parts.size() != 2 || parts[0].isEmpty() || parts[1].isEmpty()) {
|
||||
continue;
|
||||
} else {
|
||||
|
|
|
@ -365,13 +365,13 @@ QList<QString> JavaUtils::FindJavaPaths()
|
|||
javas.append("/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java");
|
||||
QDir libraryJVMDir("/Library/Java/JavaVirtualMachines/");
|
||||
QStringList libraryJVMJavas = libraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
foreach (const QString& java, libraryJVMJavas) {
|
||||
for (const QString& java : libraryJVMJavas) {
|
||||
javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
|
||||
javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/jre/bin/java");
|
||||
}
|
||||
QDir systemLibraryJVMDir("/System/Library/Java/JavaVirtualMachines/");
|
||||
QStringList systemLibraryJVMJavas = systemLibraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
foreach (const QString& java, systemLibraryJVMJavas) {
|
||||
for (const QString& java : systemLibraryJVMJavas) {
|
||||
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
|
||||
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java");
|
||||
}
|
||||
|
@ -381,14 +381,14 @@ QList<QString> JavaUtils::FindJavaPaths()
|
|||
// javas downloaded by sdkman
|
||||
QDir sdkmanDir(FS::PathCombine(home, ".sdkman/candidates/java"));
|
||||
QStringList sdkmanJavas = sdkmanDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
foreach (const QString& java, sdkmanJavas) {
|
||||
for (const QString& java : sdkmanJavas) {
|
||||
javas.append(sdkmanDir.absolutePath() + "/" + java + "/bin/java");
|
||||
}
|
||||
|
||||
// java in user library folder (like from intellij downloads)
|
||||
QDir userLibraryJVMDir(FS::PathCombine(home, "Library/Java/JavaVirtualMachines/"));
|
||||
QStringList userLibraryJVMJavas = userLibraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
foreach (const QString& java, userLibraryJVMJavas) {
|
||||
for (const QString& java : userLibraryJVMJavas) {
|
||||
javas.append(userLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
|
||||
javas.append(userLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java");
|
||||
}
|
||||
|
|
|
@ -19,9 +19,13 @@ JavaVersion& JavaVersion::operator=(const QString& javaVersionString)
|
|||
|
||||
QRegularExpression pattern;
|
||||
if (javaVersionString.startsWith("1.")) {
|
||||
pattern = QRegularExpression("1[.](?<major>[0-9]+)([.](?<minor>[0-9]+))?(_(?<security>[0-9]+)?)?(-(?<prerelease>[a-zA-Z0-9]+))?");
|
||||
static const QRegularExpression s_withOne(
|
||||
"1[.](?<major>[0-9]+)([.](?<minor>[0-9]+))?(_(?<security>[0-9]+)?)?(-(?<prerelease>[a-zA-Z0-9]+))?");
|
||||
pattern = s_withOne;
|
||||
} else {
|
||||
pattern = QRegularExpression("(?<major>[0-9]+)([.](?<minor>[0-9]+))?([.](?<security>[0-9]+))?(-(?<prerelease>[a-zA-Z0-9]+))?");
|
||||
static const QRegularExpression s_withoutOne(
|
||||
"(?<major>[0-9]+)([.](?<minor>[0-9]+))?([.](?<security>[0-9]+))?(-(?<prerelease>[a-zA-Z0-9]+))?");
|
||||
pattern = s_withoutOne;
|
||||
}
|
||||
|
||||
auto match = pattern.match(m_string);
|
||||
|
|
|
@ -55,6 +55,7 @@ void ArchiveDownloadTask::executeTask()
|
|||
connect(download.get(), &Task::stepProgress, this, &ArchiveDownloadTask::propagateStepProgress);
|
||||
connect(download.get(), &Task::status, this, &ArchiveDownloadTask::setStatus);
|
||||
connect(download.get(), &Task::details, this, &ArchiveDownloadTask::setDetails);
|
||||
connect(download.get(), &Task::aborted, this, &ArchiveDownloadTask::emitAborted);
|
||||
connect(download.get(), &Task::succeeded, [this, fullPath] {
|
||||
// This should do all of the extracting and creating folders
|
||||
extractJava(fullPath);
|
||||
|
@ -135,7 +136,6 @@ bool ArchiveDownloadTask::abort()
|
|||
auto aborted = canAbort();
|
||||
if (m_task)
|
||||
aborted = m_task->abort();
|
||||
emitAborted();
|
||||
return aborted;
|
||||
};
|
||||
} // namespace Java
|
|
@ -37,11 +37,12 @@
|
|||
|
||||
#include "launch/LaunchTask.h"
|
||||
#include <assert.h>
|
||||
#include <QAnyStringView>
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QRegularExpression>
|
||||
#include <QStandardPaths>
|
||||
#include <variant>
|
||||
#include "MessageLevel.h"
|
||||
#include "tasks/Task.h"
|
||||
|
||||
|
@ -213,6 +214,52 @@ shared_qobject_ptr<LogModel> LaunchTask::getLogModel()
|
|||
return m_logModel;
|
||||
}
|
||||
|
||||
bool LaunchTask::parseXmlLogs(QString const& line, MessageLevel::Enum level)
|
||||
{
|
||||
LogParser* parser;
|
||||
switch (level) {
|
||||
case MessageLevel::StdErr:
|
||||
parser = &m_stderrParser;
|
||||
break;
|
||||
case MessageLevel::StdOut:
|
||||
parser = &m_stdoutParser;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
parser->appendLine(line);
|
||||
auto items = parser->parseAvailable();
|
||||
if (auto err = parser->getError(); err.has_value()) {
|
||||
auto& model = *getLogModel();
|
||||
model.append(MessageLevel::Error, tr("[Log4j Parse Error] Failed to parse log4j log event: %1").arg(err.value().errMessage));
|
||||
return false;
|
||||
} else {
|
||||
if (!items.isEmpty()) {
|
||||
auto& model = *getLogModel();
|
||||
for (auto const& item : items) {
|
||||
if (std::holds_alternative<LogParser::LogEntry>(item)) {
|
||||
auto entry = std::get<LogParser::LogEntry>(item);
|
||||
auto msg = QString("[%1] [%2/%3] [%4]: %5")
|
||||
.arg(entry.timestamp.toString("HH:mm:ss"))
|
||||
.arg(entry.thread)
|
||||
.arg(entry.levelText)
|
||||
.arg(entry.logger)
|
||||
.arg(entry.message);
|
||||
msg = censorPrivateInfo(msg);
|
||||
model.append(entry.level, msg);
|
||||
} else if (std::holds_alternative<LogParser::PlainText>(item)) {
|
||||
auto msg = std::get<LogParser::PlainText>(item).message;
|
||||
level = LogParser::guessLevel(msg, model.previousLevel());
|
||||
msg = censorPrivateInfo(msg);
|
||||
model.append(level, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void LaunchTask::onLogLines(const QStringList& lines, MessageLevel::Enum defaultLevel)
|
||||
{
|
||||
for (auto& line : lines) {
|
||||
|
@ -222,21 +269,26 @@ void LaunchTask::onLogLines(const QStringList& lines, MessageLevel::Enum default
|
|||
|
||||
void LaunchTask::onLogLine(QString line, MessageLevel::Enum level)
|
||||
{
|
||||
if (parseXmlLogs(line, level)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if the launcher part set a log level, use it
|
||||
auto innerLevel = MessageLevel::fromLine(line);
|
||||
if (innerLevel != MessageLevel::Unknown) {
|
||||
level = innerLevel;
|
||||
}
|
||||
|
||||
auto& model = *getLogModel();
|
||||
|
||||
// If the level is still undetermined, guess level
|
||||
if (level == MessageLevel::StdErr || level == MessageLevel::StdOut || level == MessageLevel::Unknown) {
|
||||
level = m_instance->guessLevel(line, level);
|
||||
if (level == MessageLevel::Unknown) {
|
||||
level = LogParser::guessLevel(line, model.previousLevel());
|
||||
}
|
||||
|
||||
// censor private user info
|
||||
line = censorPrivateInfo(line);
|
||||
|
||||
auto& model = *getLogModel();
|
||||
model.append(level, line);
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
#include "LaunchStep.h"
|
||||
#include "LogModel.h"
|
||||
#include "MessageLevel.h"
|
||||
#include "logs/LogParser.h"
|
||||
|
||||
class LaunchTask : public Task {
|
||||
Q_OBJECT
|
||||
|
@ -114,6 +115,9 @@ class LaunchTask : public Task {
|
|||
private: /*methods */
|
||||
void finalizeSteps(bool successful, const QString& error);
|
||||
|
||||
protected:
|
||||
bool parseXmlLogs(QString const& line, MessageLevel::Enum level);
|
||||
|
||||
protected: /* data */
|
||||
MinecraftInstancePtr m_instance;
|
||||
shared_qobject_ptr<LogModel> m_logModel;
|
||||
|
@ -122,4 +126,6 @@ class LaunchTask : public Task {
|
|||
int currentStep = -1;
|
||||
State state = NotStarted;
|
||||
qint64 m_pid = -1;
|
||||
LogParser m_stdoutParser;
|
||||
LogParser m_stderrParser;
|
||||
};
|
||||
|
|
|
@ -100,7 +100,7 @@ void LogModel::setMaxLines(int maxLines)
|
|||
return;
|
||||
}
|
||||
// otherwise, we need to reorganize the data because it crosses the wrap boundary
|
||||
QVector<entry> newContent;
|
||||
QList<entry> newContent;
|
||||
newContent.resize(maxLines);
|
||||
if (m_numLines <= maxLines) {
|
||||
// if it all fits in the new buffer, just copy it over
|
||||
|
@ -149,3 +149,28 @@ bool LogModel::wrapLines() const
|
|||
{
|
||||
return m_lineWrap;
|
||||
}
|
||||
|
||||
void LogModel::setColorLines(bool state)
|
||||
{
|
||||
if (m_colorLines != state) {
|
||||
m_colorLines = state;
|
||||
}
|
||||
}
|
||||
|
||||
bool LogModel::colorLines() const
|
||||
{
|
||||
return m_colorLines;
|
||||
}
|
||||
|
||||
bool LogModel::isOverFlow()
|
||||
{
|
||||
return m_numLines >= m_maxLines && m_stopOnOverflow;
|
||||
}
|
||||
|
||||
MessageLevel::Enum LogModel::previousLevel()
|
||||
{
|
||||
if (!m_content.isEmpty()) {
|
||||
return m_content.last().level;
|
||||
}
|
||||
return MessageLevel::Unknown;
|
||||
}
|
||||
|
|
|
@ -24,9 +24,14 @@ class LogModel : public QAbstractListModel {
|
|||
void setMaxLines(int maxLines);
|
||||
void setStopOnOverflow(bool stop);
|
||||
void setOverflowMessage(const QString& overflowMessage);
|
||||
bool isOverFlow();
|
||||
|
||||
void setLineWrap(bool state);
|
||||
bool wrapLines() const;
|
||||
void setColorLines(bool state);
|
||||
bool colorLines() const;
|
||||
|
||||
MessageLevel::Enum previousLevel();
|
||||
|
||||
enum Roles { LevelRole = Qt::UserRole };
|
||||
|
||||
|
@ -37,7 +42,7 @@ class LogModel : public QAbstractListModel {
|
|||
};
|
||||
|
||||
private: /* data */
|
||||
QVector<entry> m_content;
|
||||
QList<entry> m_content;
|
||||
int m_maxLines = 1000;
|
||||
// first line in the circular buffer
|
||||
int m_firstLine = 0;
|
||||
|
@ -47,6 +52,7 @@ class LogModel : public QAbstractListModel {
|
|||
QString m_overflowMessage = "OVERFLOW";
|
||||
bool m_suspended = false;
|
||||
bool m_lineWrap = true;
|
||||
bool m_colorLines = true;
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY(LogModel)
|
||||
|
|
|
@ -49,14 +49,10 @@ void PostLaunchCommand::executeTask()
|
|||
{
|
||||
auto cmd = m_parent->substituteVariables(m_command);
|
||||
emit logLine(tr("Running Post-Launch command: %1").arg(cmd), MessageLevel::Launcher);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
auto args = QProcess::splitCommand(cmd);
|
||||
|
||||
const QString program = args.takeFirst();
|
||||
m_process.start(program, args);
|
||||
#else
|
||||
m_process.start(cmd);
|
||||
#endif
|
||||
}
|
||||
|
||||
void PostLaunchCommand::on_state(LoggedProcess::State state)
|
||||
|
|
|
@ -49,13 +49,9 @@ void PreLaunchCommand::executeTask()
|
|||
{
|
||||
auto cmd = m_parent->substituteVariables(m_command);
|
||||
emit logLine(tr("Running Pre-Launch command: %1").arg(cmd), MessageLevel::Launcher);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
auto args = QProcess::splitCommand(cmd);
|
||||
const QString program = args.takeFirst();
|
||||
m_process.start(program, args);
|
||||
#else
|
||||
m_process.start(cmd);
|
||||
#endif
|
||||
}
|
||||
|
||||
void PreLaunchCommand::on_state(LoggedProcess::State state)
|
||||
|
|
68
launcher/logs/AnonymizeLog.cpp
Normal file
68
launcher/logs/AnonymizeLog.cpp
Normal file
|
@ -0,0 +1,68 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2025 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include "AnonymizeLog.h"
|
||||
|
||||
#include <QRegularExpression>
|
||||
|
||||
struct RegReplace {
|
||||
RegReplace(QRegularExpression r, QString w) : reg(r), with(w) { reg.optimize(); }
|
||||
QRegularExpression reg;
|
||||
QString with;
|
||||
};
|
||||
|
||||
static const QVector<RegReplace> anonymizeRules = {
|
||||
RegReplace(QRegularExpression("C:\\\\Users\\\\([^\\\\]+)\\\\", QRegularExpression::CaseInsensitiveOption),
|
||||
"C:\\Users\\********\\"), // windows
|
||||
RegReplace(QRegularExpression("C:\\/Users\\/([^\\/]+)\\/", QRegularExpression::CaseInsensitiveOption),
|
||||
"C:/Users/********/"), // windows with forward slashes
|
||||
RegReplace(QRegularExpression("(?<!\\\\w)\\/home\\/[^\\/]+\\/", QRegularExpression::CaseInsensitiveOption),
|
||||
"/home/********/"), // linux
|
||||
RegReplace(QRegularExpression("(?<!\\\\w)\\/Users\\/[^\\/]+\\/", QRegularExpression::CaseInsensitiveOption),
|
||||
"/Users/********/"), // macos
|
||||
RegReplace(QRegularExpression("\\(Session ID is [^\\)]+\\)", QRegularExpression::CaseInsensitiveOption),
|
||||
"(Session ID is <SESSION_TOKEN>)"), // SESSION_TOKEN
|
||||
RegReplace(QRegularExpression("new refresh token: \"[^\"]+\"", QRegularExpression::CaseInsensitiveOption),
|
||||
"new refresh token: \"<TOKEN>\""), // refresh token
|
||||
RegReplace(QRegularExpression("\"device_code\" : \"[^\"]+\"", QRegularExpression::CaseInsensitiveOption),
|
||||
"\"device_code\" : \"<DEVICE_CODE>\""), // device code
|
||||
};
|
||||
|
||||
void anonymizeLog(QString& log)
|
||||
{
|
||||
for (auto rule : anonymizeRules) {
|
||||
log.replace(rule.reg, rule.with);
|
||||
}
|
||||
}
|
40
launcher/logs/AnonymizeLog.h
Normal file
40
launcher/logs/AnonymizeLog.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2025 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
void anonymizeLog(QString& log);
|
351
launcher/logs/LogParser.cpp
Normal file
351
launcher/logs/LogParser.cpp
Normal file
|
@ -0,0 +1,351 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2025 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "LogParser.h"
|
||||
|
||||
#include <QRegularExpression>
|
||||
#include "MessageLevel.h"
|
||||
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
|
||||
void LogParser::appendLine(QAnyStringView data)
|
||||
{
|
||||
if (!m_partialData.isEmpty()) {
|
||||
m_buffer = QString(m_partialData);
|
||||
m_buffer.append("\n");
|
||||
m_partialData.clear();
|
||||
}
|
||||
m_buffer.append(data.toString());
|
||||
}
|
||||
|
||||
std::optional<LogParser::Error> LogParser::getError()
|
||||
{
|
||||
return m_error;
|
||||
}
|
||||
|
||||
std::optional<LogParser::LogEntry> LogParser::parseAttributes()
|
||||
{
|
||||
LogParser::LogEntry entry{
|
||||
"",
|
||||
MessageLevel::Info,
|
||||
};
|
||||
auto attributes = m_parser.attributes();
|
||||
|
||||
for (const auto& attr : attributes) {
|
||||
auto name = attr.name();
|
||||
auto value = attr.value();
|
||||
if (name == "logger"_L1) {
|
||||
entry.logger = value.trimmed().toString();
|
||||
} else if (name == "timestamp"_L1) {
|
||||
if (value.trimmed().isEmpty()) {
|
||||
m_parser.raiseError("log4j:Event Missing required attribute: timestamp");
|
||||
return {};
|
||||
}
|
||||
entry.timestamp = QDateTime::fromSecsSinceEpoch(value.trimmed().toLongLong());
|
||||
} else if (name == "level"_L1) {
|
||||
entry.levelText = value.trimmed().toString();
|
||||
entry.level = MessageLevel::getLevel(entry.levelText);
|
||||
} else if (name == "thread"_L1) {
|
||||
entry.thread = value.trimmed().toString();
|
||||
}
|
||||
}
|
||||
if (entry.logger.isEmpty()) {
|
||||
m_parser.raiseError("log4j:Event Missing required attribute: logger");
|
||||
return {};
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
void LogParser::setError()
|
||||
{
|
||||
m_error = {
|
||||
m_parser.errorString(),
|
||||
m_parser.error(),
|
||||
};
|
||||
}
|
||||
|
||||
void LogParser::clearError()
|
||||
{
|
||||
m_error = {}; // clear previous error
|
||||
}
|
||||
|
||||
bool isPotentialLog4JStart(QStringView buffer)
|
||||
{
|
||||
static QString target = QStringLiteral("<log4j:event");
|
||||
if (buffer.isEmpty() || buffer[0] != '<') {
|
||||
return false;
|
||||
}
|
||||
auto bufLower = buffer.toString().toLower();
|
||||
return target.startsWith(bufLower) || bufLower.startsWith(target);
|
||||
}
|
||||
|
||||
std::optional<LogParser::ParsedItem> LogParser::parseNext()
|
||||
{
|
||||
clearError();
|
||||
|
||||
if (m_buffer.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (m_buffer.trimmed().isEmpty()) {
|
||||
auto text = QString(m_buffer);
|
||||
m_buffer.clear();
|
||||
return LogParser::PlainText{ text };
|
||||
}
|
||||
|
||||
// check if we have a full xml log4j event
|
||||
bool isCompleteLog4j = false;
|
||||
m_parser.clear();
|
||||
m_parser.setNamespaceProcessing(false);
|
||||
m_parser.addData(m_buffer);
|
||||
if (m_parser.readNextStartElement()) {
|
||||
if (m_parser.qualifiedName().compare("log4j:Event"_L1, Qt::CaseInsensitive) == 0) {
|
||||
int depth = 1;
|
||||
bool eod = false;
|
||||
while (depth > 0 && !eod) {
|
||||
auto tok = m_parser.readNext();
|
||||
switch (tok) {
|
||||
case QXmlStreamReader::TokenType::StartElement: {
|
||||
depth += 1;
|
||||
} break;
|
||||
case QXmlStreamReader::TokenType::EndElement: {
|
||||
depth -= 1;
|
||||
} break;
|
||||
case QXmlStreamReader::TokenType::EndDocument: {
|
||||
eod = true; // break outer while loop
|
||||
} break;
|
||||
default: {
|
||||
// no op
|
||||
}
|
||||
}
|
||||
if (m_parser.hasError()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
isCompleteLog4j = depth == 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (isCompleteLog4j) {
|
||||
return parseLog4J();
|
||||
} else {
|
||||
if (isPotentialLog4JStart(m_buffer)) {
|
||||
m_partialData = QString(m_buffer);
|
||||
return LogParser::Partial{ QString(m_buffer) };
|
||||
}
|
||||
|
||||
int start = 0;
|
||||
auto bufView = QStringView(m_buffer);
|
||||
while (start < bufView.length()) {
|
||||
if (qsizetype pos = bufView.right(bufView.length() - start).indexOf('<'); pos != -1) {
|
||||
auto slicestart = start + pos;
|
||||
auto slice = bufView.right(bufView.length() - slicestart);
|
||||
if (isPotentialLog4JStart(slice)) {
|
||||
if (slicestart > 0) {
|
||||
auto text = m_buffer.left(slicestart);
|
||||
m_buffer = m_buffer.right(m_buffer.length() - slicestart);
|
||||
if (!text.trimmed().isEmpty()) {
|
||||
return LogParser::PlainText{ text };
|
||||
}
|
||||
}
|
||||
m_partialData = QString(m_buffer);
|
||||
return LogParser::Partial{ QString(m_buffer) };
|
||||
}
|
||||
start = slicestart + 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// no log4j found, all plain text
|
||||
auto text = QString(m_buffer);
|
||||
m_buffer.clear();
|
||||
return LogParser::PlainText{ text };
|
||||
}
|
||||
}
|
||||
|
||||
QList<LogParser::ParsedItem> LogParser::parseAvailable()
|
||||
{
|
||||
QList<LogParser::ParsedItem> items;
|
||||
bool doNext = true;
|
||||
while (doNext) {
|
||||
auto item_ = parseNext();
|
||||
if (m_error.has_value()) {
|
||||
return {};
|
||||
}
|
||||
if (item_.has_value()) {
|
||||
auto item = item_.value();
|
||||
if (std::holds_alternative<LogParser::Partial>(item)) {
|
||||
break;
|
||||
} else {
|
||||
items.push_back(item);
|
||||
}
|
||||
} else {
|
||||
doNext = false;
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
std::optional<LogParser::ParsedItem> LogParser::parseLog4J()
|
||||
{
|
||||
m_parser.clear();
|
||||
m_parser.setNamespaceProcessing(false);
|
||||
m_parser.addData(m_buffer);
|
||||
|
||||
m_parser.readNextStartElement();
|
||||
if (m_parser.qualifiedName().compare("log4j:Event"_L1, Qt::CaseInsensitive) == 0) {
|
||||
auto entry_ = parseAttributes();
|
||||
if (!entry_.has_value()) {
|
||||
setError();
|
||||
return {};
|
||||
}
|
||||
auto entry = entry_.value();
|
||||
|
||||
bool foundMessage = false;
|
||||
int depth = 1;
|
||||
|
||||
enum parseOp { noOp, entryReady, parseError };
|
||||
|
||||
auto foundStart = [&]() -> parseOp {
|
||||
depth += 1;
|
||||
if (m_parser.qualifiedName().compare("log4j:Message"_L1, Qt::CaseInsensitive) == 0) {
|
||||
QString message;
|
||||
bool messageComplete = false;
|
||||
|
||||
while (!messageComplete) {
|
||||
auto tok = m_parser.readNext();
|
||||
|
||||
switch (tok) {
|
||||
case QXmlStreamReader::TokenType::Characters: {
|
||||
message.append(m_parser.text());
|
||||
} break;
|
||||
case QXmlStreamReader::TokenType::EndElement: {
|
||||
if (m_parser.qualifiedName().compare("log4j:Message"_L1, Qt::CaseInsensitive) == 0) {
|
||||
messageComplete = true;
|
||||
}
|
||||
} break;
|
||||
case QXmlStreamReader::TokenType::EndDocument: {
|
||||
return parseError; // parse fail
|
||||
} break;
|
||||
default: {
|
||||
// no op
|
||||
}
|
||||
}
|
||||
|
||||
if (m_parser.hasError()) {
|
||||
return parseError;
|
||||
}
|
||||
}
|
||||
|
||||
entry.message = message;
|
||||
foundMessage = true;
|
||||
depth -= 1;
|
||||
}
|
||||
return noOp;
|
||||
};
|
||||
|
||||
auto foundEnd = [&]() -> parseOp {
|
||||
depth -= 1;
|
||||
if (depth == 0 && m_parser.qualifiedName().compare("log4j:Event"_L1, Qt::CaseInsensitive) == 0) {
|
||||
if (foundMessage) {
|
||||
auto consumed = m_parser.characterOffset();
|
||||
if (consumed > 0 && consumed <= m_buffer.length()) {
|
||||
m_buffer = m_buffer.right(m_buffer.length() - consumed);
|
||||
// potential whitespace preserved for next item
|
||||
}
|
||||
clearError();
|
||||
return entryReady;
|
||||
}
|
||||
m_parser.raiseError("log4j:Event Missing required attribute: message");
|
||||
setError();
|
||||
return parseError;
|
||||
}
|
||||
return noOp;
|
||||
};
|
||||
|
||||
while (!m_parser.atEnd()) {
|
||||
auto tok = m_parser.readNext();
|
||||
parseOp op = noOp;
|
||||
switch (tok) {
|
||||
case QXmlStreamReader::TokenType::StartElement: {
|
||||
op = foundStart();
|
||||
} break;
|
||||
case QXmlStreamReader::TokenType::EndElement: {
|
||||
op = foundEnd();
|
||||
} break;
|
||||
case QXmlStreamReader::TokenType::EndDocument: {
|
||||
return {};
|
||||
} break;
|
||||
default: {
|
||||
// no op
|
||||
}
|
||||
}
|
||||
|
||||
switch (op) {
|
||||
case parseError:
|
||||
return {}; // parse fail or error
|
||||
case entryReady:
|
||||
return entry;
|
||||
case noOp:
|
||||
default: {
|
||||
// no op
|
||||
}
|
||||
}
|
||||
|
||||
if (m_parser.hasError()) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error("unreachable: already verified this was a complete log4j:Event");
|
||||
}
|
||||
|
||||
MessageLevel::Enum LogParser::guessLevel(const QString& line, MessageLevel::Enum level)
|
||||
{
|
||||
static const QRegularExpression LINE_WITH_LEVEL("^\\[(?<timestamp>[0-9:]+)\\] \\[[^/]+/(?<level>[^\\]]+)\\]");
|
||||
auto match = LINE_WITH_LEVEL.match(line);
|
||||
if (match.hasMatch()) {
|
||||
// New style logs from log4j
|
||||
QString timestamp = match.captured("timestamp");
|
||||
QString levelStr = match.captured("level");
|
||||
level = MessageLevel::getLevel(levelStr);
|
||||
} else {
|
||||
// Old style forge logs
|
||||
if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || line.contains("[FINER]") ||
|
||||
line.contains("[FINEST]"))
|
||||
level = MessageLevel::Info;
|
||||
if (line.contains("[SEVERE]") || line.contains("[STDERR]"))
|
||||
level = MessageLevel::Error;
|
||||
if (line.contains("[WARNING]"))
|
||||
level = MessageLevel::Warning;
|
||||
if (line.contains("[DEBUG]"))
|
||||
level = MessageLevel::Debug;
|
||||
}
|
||||
if (level != MessageLevel::Unknown)
|
||||
return level;
|
||||
|
||||
if (line.contains("overwriting existing"))
|
||||
return MessageLevel::Fatal;
|
||||
|
||||
return MessageLevel::Info;
|
||||
}
|
76
launcher/logs/LogParser.h
Normal file
76
launcher/logs/LogParser.h
Normal file
|
@ -0,0 +1,76 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2025 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QAnyStringView>
|
||||
#include <QDateTime>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QStringView>
|
||||
#include <QXmlStreamReader>
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
#include "MessageLevel.h"
|
||||
|
||||
class LogParser {
|
||||
public:
|
||||
struct LogEntry {
|
||||
QString logger;
|
||||
MessageLevel::Enum level;
|
||||
QString levelText;
|
||||
QDateTime timestamp;
|
||||
QString thread;
|
||||
QString message;
|
||||
};
|
||||
struct Partial {
|
||||
QString data;
|
||||
};
|
||||
struct PlainText {
|
||||
QString message;
|
||||
};
|
||||
struct Error {
|
||||
QString errMessage;
|
||||
QXmlStreamReader::Error error;
|
||||
};
|
||||
|
||||
using ParsedItem = std::variant<LogEntry, PlainText, Partial>;
|
||||
|
||||
public:
|
||||
LogParser() = default;
|
||||
|
||||
void appendLine(QAnyStringView data);
|
||||
std::optional<ParsedItem> parseNext();
|
||||
QList<ParsedItem> parseAvailable();
|
||||
std::optional<Error> getError();
|
||||
|
||||
/// guess log level from a line of game log
|
||||
static MessageLevel::Enum guessLevel(const QString& line, MessageLevel::Enum level);
|
||||
|
||||
protected:
|
||||
std::optional<LogEntry> parseAttributes();
|
||||
void setError();
|
||||
void clearError();
|
||||
|
||||
std::optional<ParsedItem> parseLog4J();
|
||||
|
||||
private:
|
||||
QString m_buffer;
|
||||
QString m_partialData;
|
||||
QXmlStreamReader m_parser;
|
||||
std::optional<Error> m_error;
|
||||
};
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
namespace Meta {
|
||||
Index::Index(QObject* parent) : QAbstractListModel(parent) {}
|
||||
Index::Index(const QVector<VersionList::Ptr>& lists, QObject* parent) : QAbstractListModel(parent), m_lists(lists)
|
||||
Index::Index(const QList<VersionList::Ptr>& lists, QObject* parent) : QAbstractListModel(parent), m_lists(lists)
|
||||
{
|
||||
for (int i = 0; i < m_lists.size(); ++i) {
|
||||
m_uids.insert(m_lists.at(i)->uid(), m_lists.at(i));
|
||||
|
@ -103,7 +103,7 @@ void Index::parse(const QJsonObject& obj)
|
|||
|
||||
void Index::merge(const std::shared_ptr<Index>& other)
|
||||
{
|
||||
const QVector<VersionList::Ptr> lists = other->m_lists;
|
||||
const QList<VersionList::Ptr> lists = other->m_lists;
|
||||
// initial load, no need to merge
|
||||
if (m_lists.isEmpty()) {
|
||||
beginResetModel();
|
||||
|
|
|
@ -29,7 +29,7 @@ class Index : public QAbstractListModel, public BaseEntity {
|
|||
Q_OBJECT
|
||||
public:
|
||||
explicit Index(QObject* parent = nullptr);
|
||||
explicit Index(const QVector<VersionList::Ptr>& lists, QObject* parent = nullptr);
|
||||
explicit Index(const QList<VersionList::Ptr>& lists, QObject* parent = nullptr);
|
||||
virtual ~Index() = default;
|
||||
|
||||
enum { UidRole = Qt::UserRole, NameRole, ListPtrRole };
|
||||
|
@ -46,7 +46,7 @@ class Index : public QAbstractListModel, public BaseEntity {
|
|||
Version::Ptr get(const QString& uid, const QString& version);
|
||||
bool hasUid(const QString& uid) const;
|
||||
|
||||
QVector<VersionList::Ptr> lists() const { return m_lists; }
|
||||
QList<VersionList::Ptr> lists() const { return m_lists; }
|
||||
|
||||
Task::Ptr loadVersion(const QString& uid, const QString& version = {}, Net::Mode mode = Net::Mode::Online, bool force = false);
|
||||
|
||||
|
@ -60,7 +60,7 @@ class Index : public QAbstractListModel, public BaseEntity {
|
|||
void parse(const QJsonObject& obj) override;
|
||||
|
||||
private:
|
||||
QVector<VersionList::Ptr> m_lists;
|
||||
QList<VersionList::Ptr> m_lists;
|
||||
QHash<QString, VersionList::Ptr> m_uids;
|
||||
|
||||
void connectVersionList(int row, const VersionList::Ptr& list);
|
||||
|
|
|
@ -35,8 +35,8 @@ MetadataVersion currentFormatVersion()
|
|||
// Index
|
||||
static std::shared_ptr<Index> parseIndexInternal(const QJsonObject& obj)
|
||||
{
|
||||
const QVector<QJsonObject> objects = requireIsArrayOf<QJsonObject>(obj, "packages");
|
||||
QVector<VersionList::Ptr> lists;
|
||||
const QList<QJsonObject> objects = requireIsArrayOf<QJsonObject>(obj, "packages");
|
||||
QList<VersionList::Ptr> lists;
|
||||
lists.reserve(objects.size());
|
||||
std::transform(objects.begin(), objects.end(), std::back_inserter(lists), [](const QJsonObject& obj) {
|
||||
VersionList::Ptr list = std::make_shared<VersionList>(requireString(obj, "uid"));
|
||||
|
@ -79,8 +79,8 @@ static VersionList::Ptr parseVersionListInternal(const QJsonObject& obj)
|
|||
{
|
||||
const QString uid = requireString(obj, "uid");
|
||||
|
||||
const QVector<QJsonObject> versionsRaw = requireIsArrayOf<QJsonObject>(obj, "versions");
|
||||
QVector<Version::Ptr> versions;
|
||||
const QList<QJsonObject> versionsRaw = requireIsArrayOf<QJsonObject>(obj, "versions");
|
||||
QList<Version::Ptr> versions;
|
||||
versions.reserve(versionsRaw.size());
|
||||
std::transform(versionsRaw.begin(), versionsRaw.end(), std::back_inserter(versions), [uid](const QJsonObject& vObj) {
|
||||
auto version = parseCommonVersion(uid, vObj);
|
||||
|
|
|
@ -19,8 +19,8 @@
|
|||
#include "BaseVersion.h"
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QList>
|
||||
#include <QStringList>
|
||||
#include <QVector>
|
||||
#include <memory>
|
||||
|
||||
#include "minecraft/VersionFile.h"
|
||||
|
|
|
@ -169,7 +169,7 @@ void VersionList::setName(const QString& name)
|
|||
emit nameChanged(name);
|
||||
}
|
||||
|
||||
void VersionList::setVersions(const QVector<Version::Ptr>& versions)
|
||||
void VersionList::setVersions(const QList<Version::Ptr>& versions)
|
||||
{
|
||||
beginResetModel();
|
||||
m_versions = versions;
|
||||
|
@ -265,7 +265,7 @@ void VersionList::setupAddedVersion(const int row, const Version::Ptr& version)
|
|||
disconnect(version.get(), &Version::typeChanged, this, nullptr);
|
||||
|
||||
connect(version.get(), &Version::requiresChanged, this,
|
||||
[this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << RequiresRole); });
|
||||
[this, row]() { emit dataChanged(index(row), index(row), QList<int>() << RequiresRole); });
|
||||
connect(version.get(), &Version::timeChanged, this,
|
||||
[this, row]() { emit dataChanged(index(row), index(row), { TimeRole, SortRole }); });
|
||||
connect(version.get(), &Version::typeChanged, this, [this, row]() { emit dataChanged(index(row), index(row), { TypeRole }); });
|
||||
|
|
|
@ -61,14 +61,14 @@ class VersionList : public BaseVersionList, public BaseEntity {
|
|||
Version::Ptr getVersion(const QString& version);
|
||||
bool hasVersion(QString version) const;
|
||||
|
||||
QVector<Version::Ptr> versions() const { return m_versions; }
|
||||
QList<Version::Ptr> versions() const { return m_versions; }
|
||||
|
||||
// this blocks until the version list is loaded
|
||||
void waitToLoad();
|
||||
|
||||
public: // for usage only by parsers
|
||||
void setName(const QString& name);
|
||||
void setVersions(const QVector<Version::Ptr>& versions);
|
||||
void setVersions(const QList<Version::Ptr>& versions);
|
||||
void merge(const VersionList::Ptr& other);
|
||||
void mergeFromIndex(const VersionList::Ptr& other);
|
||||
void parse(const QJsonObject& obj) override;
|
||||
|
@ -82,7 +82,7 @@ class VersionList : public BaseVersionList, public BaseEntity {
|
|||
void updateListData(QList<BaseVersion::Ptr>) override {}
|
||||
|
||||
private:
|
||||
QVector<Version::Ptr> m_versions;
|
||||
QList<Version::Ptr> m_versions;
|
||||
QStringList m_externalRecommendsVersions;
|
||||
QHash<QString, Version::Ptr> m_lookup;
|
||||
QString m_uid;
|
||||
|
|
|
@ -54,11 +54,11 @@ struct GradleSpecifier {
|
|||
4 "jdk15"
|
||||
5 "jar"
|
||||
*/
|
||||
QRegularExpression matcher(
|
||||
static const QRegularExpression s_matcher(
|
||||
QRegularExpression::anchoredPattern("([^:@]+):([^:@]+):([^:@]+)"
|
||||
"(?::([^:@]+))?"
|
||||
"(?:@([^:@]+))?"));
|
||||
QRegularExpressionMatch match = matcher.match(value);
|
||||
QRegularExpressionMatch match = s_matcher.match(value);
|
||||
m_valid = match.hasMatch();
|
||||
if (!m_valid) {
|
||||
m_invalidValue = value;
|
||||
|
|
|
@ -53,7 +53,6 @@
|
|||
#include "MMCTime.h"
|
||||
#include "java/JavaVersion.h"
|
||||
#include "pathmatcher/MultiMatcher.h"
|
||||
#include "pathmatcher/RegexpMatcher.h"
|
||||
|
||||
#include "launch/LaunchTask.h"
|
||||
#include "launch/TaskStepWrapper.h"
|
||||
|
@ -689,9 +688,9 @@ static QString replaceTokensIn(QString text, QMap<QString, QString> with)
|
|||
{
|
||||
// TODO: does this still work??
|
||||
QString result;
|
||||
QRegularExpression token_regexp("\\$\\{(.+)\\}", QRegularExpression::InvertedGreedinessOption);
|
||||
static const QRegularExpression s_token_regexp("\\$\\{(.+)\\}", QRegularExpression::InvertedGreedinessOption);
|
||||
QStringList list;
|
||||
QRegularExpressionMatchIterator i = token_regexp.globalMatch(text);
|
||||
QRegularExpressionMatchIterator i = s_token_regexp.globalMatch(text);
|
||||
int lastCapturedEnd = 0;
|
||||
while (i.hasNext()) {
|
||||
QRegularExpressionMatch match = i.next();
|
||||
|
@ -757,11 +756,7 @@ QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session, Mine
|
|||
token_mapping["assets_root"] = absAssetsDir;
|
||||
token_mapping["assets_index_name"] = assets->id;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
QStringList parts = args_pattern.split(' ', Qt::SkipEmptyParts);
|
||||
#else
|
||||
QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts);
|
||||
#endif
|
||||
for (int i = 0; i < parts.length(); i++) {
|
||||
parts[i] = replaceTokensIn(parts[i], token_mapping);
|
||||
}
|
||||
|
@ -816,11 +811,7 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftT
|
|||
auto mainWindow = qobject_cast<QMainWindow*>(w);
|
||||
if (mainWindow) {
|
||||
auto m = mainWindow->windowHandle()->frameMargins();
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
||||
screenGeometry = screenGeometry.shrunkBy(m);
|
||||
#else
|
||||
screenGeometry = { screenGeometry.width() - m.left() - m.right(), screenGeometry.height() - m.top() - m.bottom() };
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1012,61 +1003,9 @@ QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSess
|
|||
return filter;
|
||||
}
|
||||
|
||||
MessageLevel::Enum MinecraftInstance::guessLevel(const QString& line, MessageLevel::Enum level)
|
||||
QStringList MinecraftInstance::getLogFileSearchPaths()
|
||||
{
|
||||
QRegularExpression re("\\[(?<timestamp>[0-9:]+)\\] \\[[^/]+/(?<level>[^\\]]+)\\]");
|
||||
auto match = re.match(line);
|
||||
if (match.hasMatch()) {
|
||||
// New style logs from log4j
|
||||
QString timestamp = match.captured("timestamp");
|
||||
QString levelStr = match.captured("level");
|
||||
if (levelStr == "INFO")
|
||||
level = MessageLevel::Message;
|
||||
if (levelStr == "WARN")
|
||||
level = MessageLevel::Warning;
|
||||
if (levelStr == "ERROR")
|
||||
level = MessageLevel::Error;
|
||||
if (levelStr == "FATAL")
|
||||
level = MessageLevel::Fatal;
|
||||
if (levelStr == "TRACE" || levelStr == "DEBUG")
|
||||
level = MessageLevel::Debug;
|
||||
} else {
|
||||
// Old style forge logs
|
||||
if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || line.contains("[FINER]") ||
|
||||
line.contains("[FINEST]"))
|
||||
level = MessageLevel::Message;
|
||||
if (line.contains("[SEVERE]") || line.contains("[STDERR]"))
|
||||
level = MessageLevel::Error;
|
||||
if (line.contains("[WARNING]"))
|
||||
level = MessageLevel::Warning;
|
||||
if (line.contains("[DEBUG]"))
|
||||
level = MessageLevel::Debug;
|
||||
}
|
||||
if (line.contains("overwriting existing"))
|
||||
return MessageLevel::Fatal;
|
||||
// NOTE: this diverges from the real regexp. no unicode, the first section is + instead of *
|
||||
static const QString javaSymbol = "([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$][a-zA-Z\\d_$]*";
|
||||
if (line.contains("Exception in thread") || line.contains(QRegularExpression("\\s+at " + javaSymbol)) ||
|
||||
line.contains(QRegularExpression("Caused by: " + javaSymbol)) ||
|
||||
line.contains(QRegularExpression("([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$]?[a-zA-Z\\d_$]*(Exception|Error|Throwable)")) ||
|
||||
line.contains(QRegularExpression("... \\d+ more$")))
|
||||
return MessageLevel::Error;
|
||||
return level;
|
||||
}
|
||||
|
||||
IPathMatcher::Ptr MinecraftInstance::getLogFileMatcher()
|
||||
{
|
||||
auto combined = std::make_shared<MultiMatcher>();
|
||||
combined->add(std::make_shared<RegexpMatcher>(".*\\.log(\\.[0-9]*)?(\\.gz)?$"));
|
||||
combined->add(std::make_shared<RegexpMatcher>("crash-.*\\.txt"));
|
||||
combined->add(std::make_shared<RegexpMatcher>("IDMap dump.*\\.txt$"));
|
||||
combined->add(std::make_shared<RegexpMatcher>("ModLoader\\.txt(\\..*)?$"));
|
||||
return combined;
|
||||
}
|
||||
|
||||
QString MinecraftInstance::getLogFileRoot()
|
||||
{
|
||||
return gameRoot();
|
||||
return { FS::PathCombine(gameRoot(), "crash-reports"), FS::PathCombine(gameRoot(), "logs"), gameRoot() };
|
||||
}
|
||||
|
||||
QString MinecraftInstance::getStatusbarDescription()
|
||||
|
|
|
@ -139,12 +139,7 @@ class MinecraftInstance : public BaseInstance {
|
|||
QProcessEnvironment createEnvironment() override;
|
||||
QProcessEnvironment createLaunchEnvironment() override;
|
||||
|
||||
/// guess log level from a line of minecraft log
|
||||
MessageLevel::Enum guessLevel(const QString& line, MessageLevel::Enum level) override;
|
||||
|
||||
IPathMatcher::Ptr getLogFileMatcher() override;
|
||||
|
||||
QString getLogFileRoot() override;
|
||||
QStringList getLogFileSearchPaths() override;
|
||||
|
||||
QString getStatusbarDescription() override;
|
||||
|
||||
|
|
|
@ -114,9 +114,9 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument& doc
|
|||
out->uid = root.value("fileId").toString();
|
||||
}
|
||||
|
||||
const QRegularExpression valid_uid_regex{ QRegularExpression::anchoredPattern(
|
||||
static const QRegularExpression s_validUidRegex{ QRegularExpression::anchoredPattern(
|
||||
QStringLiteral(R"([a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]+)*)")) };
|
||||
if (!valid_uid_regex.match(out->uid).hasMatch()) {
|
||||
if (!s_validUidRegex.match(out->uid).hasMatch()) {
|
||||
qCritical() << "The component's 'uid' contains illegal characters! UID:" << out->uid;
|
||||
out->addProblem(ProblemSeverity::Error,
|
||||
QObject::tr("The component's 'uid' contains illegal characters! This can cause security issues."));
|
||||
|
|
|
@ -645,11 +645,7 @@ void PackProfile::move(const int index, const MoveDirection direction)
|
|||
return;
|
||||
}
|
||||
beginMoveRows(QModelIndex(), index, index, QModelIndex(), togap);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
|
||||
d->components.swapItemsAt(index, theirIndex);
|
||||
#else
|
||||
d->components.swap(index, theirIndex);
|
||||
#endif
|
||||
endMoveRows();
|
||||
invalidateLaunchProfile();
|
||||
scheduleSave();
|
||||
|
|
|
@ -41,7 +41,6 @@
|
|||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QRegularExpression>
|
||||
#include <QSaveFile>
|
||||
|
||||
namespace ProfileUtils {
|
||||
|
|
237
launcher/minecraft/ShortcutUtils.cpp
Normal file
237
launcher/minecraft/ShortcutUtils.cpp
Normal file
|
@ -0,0 +1,237 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
* Copyright (C) 2025 Yihe Li <winmikedows@hotmail.com>
|
||||
*
|
||||
* parent 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.
|
||||
*
|
||||
* parent 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 parent program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* parent 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 parent file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "ShortcutUtils.h"
|
||||
|
||||
#include "FileSystem.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QFileDialog>
|
||||
|
||||
#include <BuildConfig.h>
|
||||
#include <DesktopServices.h>
|
||||
#include <icons/IconList.h>
|
||||
|
||||
namespace ShortcutUtils {
|
||||
|
||||
void createInstanceShortcut(const Shortcut& shortcut, const QString& filePath)
|
||||
{
|
||||
if (!shortcut.instance)
|
||||
return;
|
||||
|
||||
QString appPath = QApplication::applicationFilePath();
|
||||
auto icon = APPLICATION->icons()->icon(shortcut.iconKey.isEmpty() ? shortcut.instance->iconKey() : shortcut.iconKey);
|
||||
if (icon == nullptr) {
|
||||
icon = APPLICATION->icons()->icon("grass");
|
||||
}
|
||||
QString iconPath;
|
||||
QStringList args;
|
||||
#if defined(Q_OS_MACOS)
|
||||
if (appPath.startsWith("/private/var/")) {
|
||||
QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"),
|
||||
QObject::tr("The launcher is in the folder it was extracted from, therefore it cannot create shortcuts."));
|
||||
return;
|
||||
}
|
||||
|
||||
iconPath = FS::PathCombine(shortcut.instance->instanceRoot(), "Icon.icns");
|
||||
|
||||
QFile iconFile(iconPath);
|
||||
if (!iconFile.open(QFile::WriteOnly)) {
|
||||
QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for application."));
|
||||
return;
|
||||
}
|
||||
|
||||
QIcon iconObj = icon->icon();
|
||||
bool success = iconObj.pixmap(1024, 1024).save(iconPath, "ICNS");
|
||||
iconFile.close();
|
||||
|
||||
if (!success) {
|
||||
iconFile.remove();
|
||||
QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for application."));
|
||||
return;
|
||||
}
|
||||
#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||
if (appPath.startsWith("/tmp/.mount_")) {
|
||||
// AppImage!
|
||||
appPath = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE"));
|
||||
if (appPath.isEmpty()) {
|
||||
QMessageBox::critical(
|
||||
shortcut.parent, QObject::tr("Create Shortcut"),
|
||||
QObject::tr("Launcher is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)"));
|
||||
} else if (appPath.endsWith("/")) {
|
||||
appPath.chop(1);
|
||||
}
|
||||
}
|
||||
|
||||
iconPath = FS::PathCombine(shortcut.instance->instanceRoot(), "icon.png");
|
||||
|
||||
QFile iconFile(iconPath);
|
||||
if (!iconFile.open(QFile::WriteOnly)) {
|
||||
QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut."));
|
||||
return;
|
||||
}
|
||||
bool success = icon->icon().pixmap(64, 64).save(&iconFile, "PNG");
|
||||
iconFile.close();
|
||||
|
||||
if (!success) {
|
||||
iconFile.remove();
|
||||
QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (DesktopServices::isFlatpak()) {
|
||||
appPath = "flatpak";
|
||||
args.append({ "run", BuildConfig.LAUNCHER_APPID });
|
||||
}
|
||||
|
||||
#elif defined(Q_OS_WIN)
|
||||
iconPath = FS::PathCombine(shortcut.instance->instanceRoot(), "icon.ico");
|
||||
|
||||
// part of fix for weird bug involving the window icon being replaced
|
||||
// dunno why it happens, but parent 2-line fix seems to be enough, so w/e
|
||||
auto appIcon = APPLICATION->getThemedIcon("logo");
|
||||
|
||||
QFile iconFile(iconPath);
|
||||
if (!iconFile.open(QFile::WriteOnly)) {
|
||||
QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut."));
|
||||
return;
|
||||
}
|
||||
bool success = icon->icon().pixmap(64, 64).save(&iconFile, "ICO");
|
||||
iconFile.close();
|
||||
|
||||
// restore original window icon
|
||||
QGuiApplication::setWindowIcon(appIcon);
|
||||
|
||||
if (!success) {
|
||||
iconFile.remove();
|
||||
QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Failed to create icon for shortcut."));
|
||||
return;
|
||||
}
|
||||
|
||||
#else
|
||||
QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Not supported on your platform!"));
|
||||
return;
|
||||
#endif
|
||||
args.append({ "--launch", shortcut.instance->id() });
|
||||
args.append(shortcut.extraArgs);
|
||||
|
||||
if (!FS::createShortcut(filePath, appPath, args, shortcut.name, iconPath)) {
|
||||
#if not defined(Q_OS_MACOS)
|
||||
iconFile.remove();
|
||||
#endif
|
||||
QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"),
|
||||
QObject::tr("Failed to create %1 shortcut!").arg(shortcut.targetString));
|
||||
}
|
||||
}
|
||||
|
||||
void createInstanceShortcutOnDesktop(const Shortcut& shortcut)
|
||||
{
|
||||
if (!shortcut.instance)
|
||||
return;
|
||||
|
||||
QString desktopDir = FS::getDesktopDir();
|
||||
if (desktopDir.isEmpty()) {
|
||||
QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Couldn't find desktop?!"));
|
||||
return;
|
||||
}
|
||||
|
||||
QString shortcutFilePath = FS::PathCombine(desktopDir, FS::RemoveInvalidFilenameChars(shortcut.name));
|
||||
createInstanceShortcut(shortcut, shortcutFilePath);
|
||||
QMessageBox::information(shortcut.parent, QObject::tr("Create Shortcut"),
|
||||
QObject::tr("Created a shortcut to this %1 on your desktop!").arg(shortcut.targetString));
|
||||
}
|
||||
|
||||
void createInstanceShortcutInApplications(const Shortcut& shortcut)
|
||||
{
|
||||
if (!shortcut.instance)
|
||||
return;
|
||||
|
||||
QString applicationsDir = FS::getApplicationsDir();
|
||||
if (applicationsDir.isEmpty()) {
|
||||
QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Couldn't find applications folder?!"));
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
|
||||
applicationsDir = FS::PathCombine(applicationsDir, BuildConfig.LAUNCHER_DISPLAYNAME + " Instances");
|
||||
|
||||
QDir applicationsDirQ(applicationsDir);
|
||||
if (!applicationsDirQ.mkpath(".")) {
|
||||
QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"),
|
||||
QObject::tr("Failed to create instances folder in applications folder!"));
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
QString shortcutFilePath = FS::PathCombine(applicationsDir, FS::RemoveInvalidFilenameChars(shortcut.name));
|
||||
createInstanceShortcut(shortcut, shortcutFilePath);
|
||||
QMessageBox::information(shortcut.parent, QObject::tr("Create Shortcut"),
|
||||
QObject::tr("Created a shortcut to this %1 in your applications folder!").arg(shortcut.targetString));
|
||||
}
|
||||
|
||||
void createInstanceShortcutInOther(const Shortcut& shortcut)
|
||||
{
|
||||
if (!shortcut.instance)
|
||||
return;
|
||||
|
||||
QString defaultedDir = FS::getDesktopDir();
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||
QString extension = ".desktop";
|
||||
#elif defined(Q_OS_WINDOWS)
|
||||
QString extension = ".lnk";
|
||||
#else
|
||||
QString extension = "";
|
||||
#endif
|
||||
|
||||
QString shortcutFilePath = FS::PathCombine(defaultedDir, FS::RemoveInvalidFilenameChars(shortcut.name) + extension);
|
||||
QFileDialog fileDialog;
|
||||
// workaround to make sure the portal file dialog opens in the desktop directory
|
||||
fileDialog.setDirectoryUrl(defaultedDir);
|
||||
|
||||
shortcutFilePath = fileDialog.getSaveFileName(shortcut.parent, QObject::tr("Create Shortcut"), shortcutFilePath,
|
||||
QObject::tr("Desktop Entries") + " (*" + extension + ")");
|
||||
if (shortcutFilePath.isEmpty())
|
||||
return; // file dialog canceled by user
|
||||
|
||||
if (shortcutFilePath.endsWith(extension))
|
||||
shortcutFilePath = shortcutFilePath.mid(0, shortcutFilePath.length() - extension.length());
|
||||
createInstanceShortcut(shortcut, shortcutFilePath);
|
||||
QMessageBox::information(shortcut.parent, QObject::tr("Create Shortcut"),
|
||||
QObject::tr("Created a shortcut to this %1!").arg(shortcut.targetString));
|
||||
}
|
||||
|
||||
} // namespace ShortcutUtils
|
66
launcher/minecraft/ShortcutUtils.h
Normal file
66
launcher/minecraft/ShortcutUtils.h
Normal file
|
@ -0,0 +1,66 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
* Copyright (C) 2025 Yihe Li <winmikedows@hotmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "Application.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
namespace ShortcutUtils {
|
||||
/// A struct to hold parameters for creating a shortcut
|
||||
struct Shortcut {
|
||||
BaseInstance* instance;
|
||||
QString name;
|
||||
QString targetString;
|
||||
QWidget* parent = nullptr;
|
||||
QStringList extraArgs = {};
|
||||
QString iconKey = "";
|
||||
};
|
||||
|
||||
/// Create an instance shortcut on the specified file path
|
||||
void createInstanceShortcut(const Shortcut& shortcut, const QString& filePath);
|
||||
|
||||
/// Create an instance shortcut on the desktop
|
||||
void createInstanceShortcutOnDesktop(const Shortcut& shortcut);
|
||||
|
||||
/// Create an instance shortcut in the Applications directory
|
||||
void createInstanceShortcutInApplications(const Shortcut& shortcut);
|
||||
|
||||
/// Create an instance shortcut in other directories
|
||||
void createInstanceShortcutInOther(const Shortcut& shortcut);
|
||||
|
||||
} // namespace ShortcutUtils
|
|
@ -198,22 +198,6 @@ bool putLevelDatDataToFS(const QFileInfo& file, QByteArray& data)
|
|||
return f.commit();
|
||||
}
|
||||
|
||||
int64_t calculateWorldSize(const QFileInfo& file)
|
||||
{
|
||||
if (file.isFile() && file.suffix() == "zip") {
|
||||
return file.size();
|
||||
} else if (file.isDir()) {
|
||||
QDirIterator it(file.absoluteFilePath(), QDir::Files, QDirIterator::Subdirectories);
|
||||
int64_t total = 0;
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
total += it.fileInfo().size();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
World::World(const QFileInfo& file)
|
||||
{
|
||||
repath(file);
|
||||
|
@ -223,7 +207,6 @@ void World::repath(const QFileInfo& file)
|
|||
{
|
||||
m_containerFile = file;
|
||||
m_folderName = file.fileName();
|
||||
m_size = calculateWorldSize(file);
|
||||
if (file.isFile() && file.suffix() == "zip") {
|
||||
m_iconFile = QString();
|
||||
readFromZip(file);
|
||||
|
@ -252,41 +235,41 @@ void World::readFromFS(const QFileInfo& file)
|
|||
{
|
||||
auto bytes = getLevelDatDataFromFS(file);
|
||||
if (bytes.isEmpty()) {
|
||||
is_valid = false;
|
||||
m_isValid = false;
|
||||
return;
|
||||
}
|
||||
loadFromLevelDat(bytes);
|
||||
levelDatTime = file.lastModified();
|
||||
m_levelDatTime = file.lastModified();
|
||||
}
|
||||
|
||||
void World::readFromZip(const QFileInfo& file)
|
||||
{
|
||||
QuaZip zip(file.absoluteFilePath());
|
||||
is_valid = zip.open(QuaZip::mdUnzip);
|
||||
if (!is_valid) {
|
||||
m_isValid = zip.open(QuaZip::mdUnzip);
|
||||
if (!m_isValid) {
|
||||
return;
|
||||
}
|
||||
auto location = MMCZip::findFolderOfFileInZip(&zip, "level.dat");
|
||||
is_valid = !location.isEmpty();
|
||||
if (!is_valid) {
|
||||
m_isValid = !location.isEmpty();
|
||||
if (!m_isValid) {
|
||||
return;
|
||||
}
|
||||
m_containerOffsetPath = location;
|
||||
QuaZipFile zippedFile(&zip);
|
||||
// read the install profile
|
||||
is_valid = zip.setCurrentFile(location + "level.dat");
|
||||
if (!is_valid) {
|
||||
m_isValid = zip.setCurrentFile(location + "level.dat");
|
||||
if (!m_isValid) {
|
||||
return;
|
||||
}
|
||||
is_valid = zippedFile.open(QIODevice::ReadOnly);
|
||||
m_isValid = zippedFile.open(QIODevice::ReadOnly);
|
||||
QuaZipFileInfo64 levelDatInfo;
|
||||
zippedFile.getFileInfo(&levelDatInfo);
|
||||
auto modTime = levelDatInfo.getNTFSmTime();
|
||||
if (!modTime.isValid()) {
|
||||
modTime = levelDatInfo.dateTime;
|
||||
}
|
||||
levelDatTime = modTime;
|
||||
if (!is_valid) {
|
||||
m_levelDatTime = modTime;
|
||||
if (!m_isValid) {
|
||||
return;
|
||||
}
|
||||
loadFromLevelDat(zippedFile.readAll());
|
||||
|
@ -430,7 +413,7 @@ void World::loadFromLevelDat(QByteArray data)
|
|||
{
|
||||
auto levelData = parseLevelDat(data);
|
||||
if (!levelData) {
|
||||
is_valid = false;
|
||||
m_isValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -439,20 +422,20 @@ void World::loadFromLevelDat(QByteArray data)
|
|||
valPtr = &levelData->at("Data");
|
||||
} catch (const std::out_of_range& e) {
|
||||
qWarning() << "Unable to read NBT tags from " << m_folderName << ":" << e.what();
|
||||
is_valid = false;
|
||||
m_isValid = false;
|
||||
return;
|
||||
}
|
||||
nbt::value& val = *valPtr;
|
||||
|
||||
is_valid = val.get_type() == nbt::tag_type::Compound;
|
||||
if (!is_valid)
|
||||
m_isValid = val.get_type() == nbt::tag_type::Compound;
|
||||
if (!m_isValid)
|
||||
return;
|
||||
|
||||
auto name = read_string(val, "LevelName");
|
||||
m_actualName = name ? *name : m_folderName;
|
||||
|
||||
auto timestamp = read_long(val, "LastPlayed");
|
||||
m_lastPlayed = timestamp ? QDateTime::fromMSecsSinceEpoch(*timestamp) : levelDatTime;
|
||||
m_lastPlayed = timestamp ? QDateTime::fromMSecsSinceEpoch(*timestamp) : m_levelDatTime;
|
||||
|
||||
m_gameType = read_gametype(val, "GameType");
|
||||
|
||||
|
@ -490,7 +473,7 @@ bool World::replace(World& with)
|
|||
|
||||
bool World::destroy()
|
||||
{
|
||||
if (!is_valid)
|
||||
if (!m_isValid)
|
||||
return false;
|
||||
|
||||
if (FS::trash(m_containerFile.filePath()))
|
||||
|
@ -508,7 +491,7 @@ bool World::destroy()
|
|||
|
||||
bool World::operator==(const World& other) const
|
||||
{
|
||||
return is_valid == other.is_valid && folderName() == other.folderName();
|
||||
return m_isValid == other.m_isValid && folderName() == other.folderName();
|
||||
}
|
||||
|
||||
bool World::isSymLinkUnder(const QString& instPath) const
|
||||
|
@ -531,3 +514,8 @@ bool World::isMoreThanOneHardLink() const
|
|||
}
|
||||
return FS::hardLinkCount(m_containerFile.absoluteFilePath()) > 1;
|
||||
}
|
||||
|
||||
void World::setSize(int64_t size)
|
||||
{
|
||||
m_size = size;
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ class World {
|
|||
QDateTime lastPlayed() const { return m_lastPlayed; }
|
||||
GameType gameType() const { return m_gameType; }
|
||||
int64_t seed() const { return m_randomSeed; }
|
||||
bool isValid() const { return is_valid; }
|
||||
bool isValid() const { return m_isValid; }
|
||||
bool isOnFS() const { return m_containerFile.isDir(); }
|
||||
QFileInfo container() const { return m_containerFile; }
|
||||
// delete all the files of this world
|
||||
|
@ -54,6 +54,8 @@ class World {
|
|||
bool rename(const QString& to);
|
||||
bool install(const QString& to, const QString& name = QString());
|
||||
|
||||
void setSize(int64_t size);
|
||||
|
||||
// WEAK compare operator - used for replacing worlds
|
||||
bool operator==(const World& other) const;
|
||||
|
||||
|
@ -83,10 +85,10 @@ class World {
|
|||
QString m_folderName;
|
||||
QString m_actualName;
|
||||
QString m_iconFile;
|
||||
QDateTime levelDatTime;
|
||||
QDateTime m_levelDatTime;
|
||||
QDateTime m_lastPlayed;
|
||||
int64_t m_size;
|
||||
int64_t m_randomSeed = 0;
|
||||
GameType m_gameType;
|
||||
bool is_valid = false;
|
||||
bool m_isValid = false;
|
||||
};
|
||||
|
|
|
@ -37,13 +37,14 @@
|
|||
|
||||
#include <FileSystem.h>
|
||||
#include <QDebug>
|
||||
#include <QDirIterator>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QMimeData>
|
||||
#include <QString>
|
||||
#include <QThreadPool>
|
||||
#include <QUrl>
|
||||
#include <QUuid>
|
||||
#include <Qt>
|
||||
#include "Application.h"
|
||||
|
||||
WorldList::WorldList(const QString& dir, BaseInstance* instance) : QAbstractListModel(), m_instance(instance), m_dir(dir)
|
||||
{
|
||||
|
@ -51,18 +52,18 @@ WorldList::WorldList(const QString& dir, BaseInstance* instance) : QAbstractList
|
|||
m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
|
||||
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
|
||||
m_watcher = new QFileSystemWatcher(this);
|
||||
is_watching = false;
|
||||
m_isWatching = false;
|
||||
connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &WorldList::directoryChanged);
|
||||
}
|
||||
|
||||
void WorldList::startWatching()
|
||||
{
|
||||
if (is_watching) {
|
||||
if (m_isWatching) {
|
||||
return;
|
||||
}
|
||||
update();
|
||||
is_watching = m_watcher->addPath(m_dir.absolutePath());
|
||||
if (is_watching) {
|
||||
m_isWatching = m_watcher->addPath(m_dir.absolutePath());
|
||||
if (m_isWatching) {
|
||||
qDebug() << "Started watching " << m_dir.absolutePath();
|
||||
} else {
|
||||
qDebug() << "Failed to start watching " << m_dir.absolutePath();
|
||||
|
@ -71,11 +72,11 @@ void WorldList::startWatching()
|
|||
|
||||
void WorldList::stopWatching()
|
||||
{
|
||||
if (!is_watching) {
|
||||
if (!m_isWatching) {
|
||||
return;
|
||||
}
|
||||
is_watching = !m_watcher->removePath(m_dir.absolutePath());
|
||||
if (!is_watching) {
|
||||
m_isWatching = !m_watcher->removePath(m_dir.absolutePath());
|
||||
if (!m_isWatching) {
|
||||
qDebug() << "Stopped watching " << m_dir.absolutePath();
|
||||
} else {
|
||||
qDebug() << "Failed to stop watching " << m_dir.absolutePath();
|
||||
|
@ -101,12 +102,13 @@ bool WorldList::update()
|
|||
}
|
||||
}
|
||||
beginResetModel();
|
||||
worlds.swap(newWorlds);
|
||||
m_worlds.swap(newWorlds);
|
||||
endResetModel();
|
||||
loadWorldsAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
void WorldList::directoryChanged(QString path)
|
||||
void WorldList::directoryChanged(QString)
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
@ -123,12 +125,12 @@ QString WorldList::instDirPath() const
|
|||
|
||||
bool WorldList::deleteWorld(int index)
|
||||
{
|
||||
if (index >= worlds.size() || index < 0)
|
||||
if (index >= m_worlds.size() || index < 0)
|
||||
return false;
|
||||
World& m = worlds[index];
|
||||
World& m = m_worlds[index];
|
||||
if (m.destroy()) {
|
||||
beginRemoveRows(QModelIndex(), index, index);
|
||||
worlds.removeAt(index);
|
||||
m_worlds.removeAt(index);
|
||||
endRemoveRows();
|
||||
emit changed();
|
||||
return true;
|
||||
|
@ -139,11 +141,11 @@ bool WorldList::deleteWorld(int index)
|
|||
bool WorldList::deleteWorlds(int first, int last)
|
||||
{
|
||||
for (int i = first; i <= last; i++) {
|
||||
World& m = worlds[i];
|
||||
World& m = m_worlds[i];
|
||||
m.destroy();
|
||||
}
|
||||
beginRemoveRows(QModelIndex(), first, last);
|
||||
worlds.erase(worlds.begin() + first, worlds.begin() + last + 1);
|
||||
m_worlds.erase(m_worlds.begin() + first, m_worlds.begin() + last + 1);
|
||||
endRemoveRows();
|
||||
emit changed();
|
||||
return true;
|
||||
|
@ -151,9 +153,9 @@ bool WorldList::deleteWorlds(int first, int last)
|
|||
|
||||
bool WorldList::resetIcon(int row)
|
||||
{
|
||||
if (row >= worlds.size() || row < 0)
|
||||
if (row >= m_worlds.size() || row < 0)
|
||||
return false;
|
||||
World& m = worlds[row];
|
||||
World& m = m_worlds[row];
|
||||
if (m.resetIcon()) {
|
||||
emit dataChanged(index(row), index(row), { WorldList::IconFileRole });
|
||||
return true;
|
||||
|
@ -174,12 +176,12 @@ QVariant WorldList::data(const QModelIndex& index, int role) const
|
|||
int row = index.row();
|
||||
int column = index.column();
|
||||
|
||||
if (row < 0 || row >= worlds.size())
|
||||
if (row < 0 || row >= m_worlds.size())
|
||||
return QVariant();
|
||||
|
||||
QLocale locale;
|
||||
|
||||
auto& world = worlds[row];
|
||||
auto& world = m_worlds[row];
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
switch (column) {
|
||||
|
@ -307,11 +309,7 @@ class WorldMimeData : public QMimeData {
|
|||
QStringList formats() const { return QMimeData::formats() << "text/uri-list"; }
|
||||
|
||||
protected:
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QVariant retrieveData(const QString& mimetype, QMetaType type) const
|
||||
#else
|
||||
QVariant retrieveData(const QString& mimetype, QVariant::Type type) const
|
||||
#endif
|
||||
{
|
||||
QList<QUrl> urls;
|
||||
for (auto& world : m_worlds) {
|
||||
|
@ -339,9 +337,9 @@ QMimeData* WorldList::mimeData(const QModelIndexList& indexes) const
|
|||
if (idx.column() != 0)
|
||||
continue;
|
||||
int row = idx.row();
|
||||
if (row < 0 || row >= this->worlds.size())
|
||||
if (row < 0 || row >= this->m_worlds.size())
|
||||
continue;
|
||||
worlds_.append(this->worlds[row]);
|
||||
worlds_.append(this->m_worlds[row]);
|
||||
}
|
||||
if (!worlds_.size()) {
|
||||
return new QMimeData();
|
||||
|
@ -393,7 +391,7 @@ bool WorldList::dropMimeData(const QMimeData* data,
|
|||
return false;
|
||||
// files dropped from outside?
|
||||
if (data->hasUrls()) {
|
||||
bool was_watching = is_watching;
|
||||
bool was_watching = m_isWatching;
|
||||
if (was_watching)
|
||||
stopWatching();
|
||||
auto urls = data->urls();
|
||||
|
@ -416,4 +414,44 @@ bool WorldList::dropMimeData(const QMimeData* data,
|
|||
return false;
|
||||
}
|
||||
|
||||
int64_t calculateWorldSize(const QFileInfo& file)
|
||||
{
|
||||
if (file.isFile() && file.suffix() == "zip") {
|
||||
return file.size();
|
||||
} else if (file.isDir()) {
|
||||
QDirIterator it(file.absoluteFilePath(), QDir::Files, QDirIterator::Subdirectories);
|
||||
int64_t total = 0;
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
total += it.fileInfo().size();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void WorldList::loadWorldsAsync()
|
||||
{
|
||||
for (int i = 0; i < m_worlds.size(); ++i) {
|
||||
auto file = m_worlds.at(i).container();
|
||||
int row = i;
|
||||
QThreadPool::globalInstance()->start([this, file, row]() mutable {
|
||||
auto size = calculateWorldSize(file);
|
||||
|
||||
QMetaObject::invokeMethod(
|
||||
this,
|
||||
[this, size, row, file]() {
|
||||
if (row < m_worlds.size() && m_worlds[row].container() == file) {
|
||||
m_worlds[row].setSize(size);
|
||||
|
||||
// Notify views
|
||||
QModelIndex modelIndex = index(row);
|
||||
emit dataChanged(modelIndex, modelIndex, { SizeRole });
|
||||
}
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#include "WorldList.moc"
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue