Merge branch 'develop' into refactor-instanceview
This commit is contained in:
commit
05d9c2d864
284
.github/workflows/build.yml
vendored
284
.github/workflows/build.yml
vendored
@ -7,10 +7,17 @@ on:
|
||||
description: Type of build (Debug, Release, RelWithDebInfo, MinSizeRel)
|
||||
type: string
|
||||
default: Debug
|
||||
is_qt_cached:
|
||||
description: Enable Qt caching or not
|
||||
type: string
|
||||
default: true
|
||||
secrets:
|
||||
SPARKLE_ED25519_KEY:
|
||||
description: Private key for signing Sparkle updates
|
||||
required: false
|
||||
CACHIX_AUTH_TOKEN:
|
||||
description: Private token for authenticating against Cachix cache
|
||||
required: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@ -25,26 +32,60 @@ jobs:
|
||||
- os: ubuntu-20.04
|
||||
qt_ver: 6
|
||||
qt_host: linux
|
||||
qt_arch: ''
|
||||
qt_version: '6.2.4'
|
||||
qt_modules: 'qt5compat qtimageformats'
|
||||
qt_tools: ''
|
||||
|
||||
- os: windows-2022
|
||||
name: "Windows-Legacy"
|
||||
msystem: mingw32
|
||||
name: "Windows-MinGW-w64"
|
||||
msystem: clang64
|
||||
|
||||
- os: windows-2022
|
||||
name: "Windows-MSVC-Legacy"
|
||||
msystem: ''
|
||||
architecture: 'win32'
|
||||
vcvars_arch: 'amd64_x86'
|
||||
qt_ver: 5
|
||||
qt_host: windows
|
||||
qt_arch: 'win32_msvc2019'
|
||||
qt_version: '5.15.2'
|
||||
qt_modules: ''
|
||||
qt_tools: 'tools_openssl_x86'
|
||||
|
||||
- os: windows-2022
|
||||
name: "Windows"
|
||||
msystem: mingw32
|
||||
name: "Windows-MSVC"
|
||||
msystem: ''
|
||||
architecture: 'x64'
|
||||
vcvars_arch: 'amd64'
|
||||
qt_ver: 6
|
||||
qt_host: windows
|
||||
qt_arch: ''
|
||||
qt_version: '6.4.0'
|
||||
qt_modules: 'qt5compat qtimageformats'
|
||||
qt_tools: ''
|
||||
|
||||
- os: windows-2022
|
||||
name: "Windows-MSVC-arm64"
|
||||
msystem: ''
|
||||
architecture: 'arm64'
|
||||
vcvars_arch: 'amd64_arm64'
|
||||
qt_ver: 6
|
||||
qt_host: windows
|
||||
qt_arch: 'win64_msvc2019_arm64'
|
||||
qt_version: '6.4.0'
|
||||
qt_modules: 'qt5compat qtimageformats'
|
||||
qt_tools: ''
|
||||
|
||||
- os: macos-12
|
||||
name: macOS
|
||||
macosx_deployment_target: 10.15
|
||||
qt_ver: 6
|
||||
qt_host: mac
|
||||
qt_arch: ''
|
||||
qt_version: '6.3.0'
|
||||
qt_modules: 'qt5compat qtimageformats'
|
||||
qt_tools: ''
|
||||
|
||||
- os: macos-12
|
||||
name: macOS-Legacy
|
||||
@ -53,6 +94,7 @@ jobs:
|
||||
qt_host: mac
|
||||
qt_version: '5.15.2'
|
||||
qt_modules: ''
|
||||
qt_tools: ''
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
@ -73,43 +115,40 @@ jobs:
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
- name: Initialize CodeQL
|
||||
if: runner.os == 'Linux' && matrix.qt_ver == 6
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
config-file: ./.github/codeql/codeql-config.yml
|
||||
queries: security-and-quality
|
||||
languages: cpp, java
|
||||
|
||||
- name: 'Setup MSYS2'
|
||||
if: runner.os == 'Windows'
|
||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: ${{ matrix.msystem }}
|
||||
update: true
|
||||
install: >-
|
||||
git
|
||||
mingw-w64-x86_64-binutils
|
||||
pacboy: >-
|
||||
toolchain:p
|
||||
cmake:p
|
||||
extra-cmake-modules:p
|
||||
ninja:p
|
||||
qt${{ matrix.qt_ver }}-base:p
|
||||
qt${{ matrix.qt_ver }}-svg:p
|
||||
qt${{ matrix.qt_ver }}-imageformats:p
|
||||
quazip-qt${{ matrix.qt_ver }}:p
|
||||
qt6-base:p
|
||||
qt6-svg:p
|
||||
qt6-imageformats:p
|
||||
quazip-qt6:p
|
||||
ccache:p
|
||||
nsis:p
|
||||
${{ matrix.qt_ver == 6 && 'qt6-5compat:p' || '' }}
|
||||
qt6-5compat: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' && inputs.build_type == 'Debug'
|
||||
uses: hendrikmuhs/ccache-action@v1.2.3
|
||||
if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug'
|
||||
uses: hendrikmuhs/ccache-action@v1.2.5
|
||||
with:
|
||||
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}
|
||||
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
|
||||
|
||||
- name: Setup ccache (Windows)
|
||||
if: runner.os == 'Windows' && inputs.build_type == 'Debug'
|
||||
- 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'
|
||||
@ -124,14 +163,14 @@ jobs:
|
||||
run: |
|
||||
echo "CCACHE_VAR=ccache" >> $GITHUB_ENV
|
||||
|
||||
- name: Retrieve ccache cache (Windows)
|
||||
if: runner.os == 'Windows' && inputs.build_type == 'Debug'
|
||||
- name: Retrieve ccache cache (Windows MinGW-w64)
|
||||
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
|
||||
uses: actions/cache@v3.0.11
|
||||
with:
|
||||
path: '${{ github.workspace }}\.ccache'
|
||||
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}
|
||||
key: ${{ matrix.os }}-mingw-w64
|
||||
restore-keys: |
|
||||
${{ matrix.os }}-qt${{ matrix.qt_ver }}
|
||||
${{ matrix.os }}-mingw-w64
|
||||
|
||||
- name: Set short version
|
||||
shell: bash
|
||||
@ -155,17 +194,40 @@ jobs:
|
||||
if: runner.os == 'Linux' && matrix.qt_ver != 6
|
||||
run: |
|
||||
sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5
|
||||
|
||||
- name: Install Qt (macOS and AppImage)
|
||||
if: runner.os == 'Linux' && matrix.qt_ver == 6 || runner.os == 'macOS'
|
||||
|
||||
- name: Install host Qt (Windows MSVC arm64)
|
||||
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ matrix.qt_version }}
|
||||
host: 'windows'
|
||||
target: 'desktop'
|
||||
arch: ''
|
||||
modules: ${{ matrix.qt_modules }}
|
||||
tools: ${{ matrix.qt_tools }}
|
||||
cache: ${{ inputs.is_qt_cached }}
|
||||
cache-key-prefix: host-qt-arm64-windows
|
||||
dir: ${{ github.workspace }}\HostQt
|
||||
set-env: false
|
||||
|
||||
- name: Install Qt (macOS, Linux, Qt 6 & Windows MSVC)
|
||||
if: runner.os == 'Linux' && matrix.qt_ver == 6 || runner.os == 'macOS' || (runner.os == 'Windows' && matrix.msystem == '')
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ matrix.qt_version }}
|
||||
host: ${{ matrix.qt_host }}
|
||||
target: 'desktop'
|
||||
arch: ${{ matrix.qt_arch }}
|
||||
modules: ${{ matrix.qt_modules }}
|
||||
cache: true
|
||||
cache-key-prefix: ${{ matrix.qt_host }}-${{ matrix.qt_version }}-"${{ matrix.qt_modules }}"-qt_cache
|
||||
tools: ${{ matrix.qt_tools }}
|
||||
cache: ${{ inputs.is_qt_cached }}
|
||||
|
||||
- name: Install MSVC (Windows MSVC)
|
||||
if: runner.os == 'Windows' && matrix.msystem == ''
|
||||
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
|
||||
@ -176,6 +238,11 @@ jobs:
|
||||
|
||||
${{ github.workspace }}/.github/scripts/prepare_JREs.sh
|
||||
|
||||
- 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 }}\msvc2019_64" >> $env:GITHUB_ENV
|
||||
|
||||
##
|
||||
# CONFIGURE
|
||||
##
|
||||
@ -190,11 +257,26 @@ jobs:
|
||||
run: |
|
||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DMACOSX_SPARKLE_UPDATE_PUBLIC_KEY="" -DMACOSX_SPARKLE_UPDATE_FEED_URL="" -G Ninja
|
||||
|
||||
- name: Configure CMake (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
- name: Configure CMake (Windows MinGW-w64)
|
||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -G Ninja
|
||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -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 -G Ninja
|
||||
|
||||
- name: Configure CMake (Windows MSVC)
|
||||
if: runner.os == 'Windows' && matrix.msystem == ''
|
||||
run: |
|
||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON
|
||||
# https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix)
|
||||
if ("${{ env.CCACHE_VAR }}")
|
||||
{
|
||||
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: Configure CMake (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
@ -210,12 +292,17 @@ jobs:
|
||||
run: |
|
||||
cmake --build ${{ env.BUILD_DIR }}
|
||||
|
||||
- name: Build (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
- name: Build (Windows MinGW-w64)
|
||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
||||
shell: msys2 {0}
|
||||
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 }}
|
||||
|
||||
##
|
||||
# TEST
|
||||
##
|
||||
@ -223,21 +310,18 @@ jobs:
|
||||
- name: Test
|
||||
if: runner.os != 'Windows'
|
||||
run: |
|
||||
ctest --test-dir build --output-on-failure
|
||||
ctest -E "^example64|example$" --test-dir build --output-on-failure
|
||||
|
||||
- name: Test (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
- name: Test (Windows MinGW-w64)
|
||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
ctest --test-dir build --output-on-failure
|
||||
ctest -E "^example64|example$" --test-dir build --output-on-failure
|
||||
|
||||
##
|
||||
# CODE SCAN
|
||||
##
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
if: runner.os == 'Linux' && matrix.qt_ver == 6
|
||||
uses: github/codeql-action/analyze@v2
|
||||
- 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
|
||||
@ -251,6 +335,7 @@ jobs:
|
||||
cd ${{ env.INSTALL_DIR }}
|
||||
chmod +x "PrismLauncher.app/Contents/MacOS/prismlauncher"
|
||||
sudo codesign --sign - --deep --force --entitlements "../program_info/App.entitlements" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
|
||||
mv "PrismLauncher.app" "Prism Launcher.app"
|
||||
tar -czf ../PrismLauncher.tar.gz *
|
||||
|
||||
- name: Make Sparkle signature (macOS)
|
||||
@ -272,27 +357,39 @@ jobs:
|
||||
EOF
|
||||
fi
|
||||
|
||||
- name: Package (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
- name: Package (Windows MinGW-w64)
|
||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
cmake --install ${{ env.BUILD_DIR }}
|
||||
|
||||
cd ${{ env.INSTALL_DIR }}
|
||||
if [ "${{ matrix.qt_ver }}" == "5" ]; then
|
||||
cp /mingw32/bin/libcrypto-1_1.dll /mingw32/bin/libssl-1_1.dll ./
|
||||
fi
|
||||
- name: Package (Windows MSVC)
|
||||
if: runner.os == 'Windows' && matrix.msystem == ''
|
||||
run: |
|
||||
cmake --install ${{ env.BUILD_DIR }} --config ${{ inputs.build_type }}
|
||||
|
||||
- name: Package (Windows, portable)
|
||||
if: runner.os == 'Windows'
|
||||
cd ${{ env.INSTALL_DIR }}
|
||||
if ("${{ matrix.qt_ver }}" -eq "5")
|
||||
{
|
||||
Copy-Item D:/a/PrismLauncher/Qt/Tools/OpenSSL/Win_x86/bin/libcrypto-1_1.dll -Destination libcrypto-1_1.dll
|
||||
Copy-Item D:/a/PrismLauncher/Qt/Tools/OpenSSL/Win_x86/bin/libssl-1_1.dll -Destination libssl-1_1.dll
|
||||
}
|
||||
|
||||
- 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
|
||||
|
||||
- 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
|
||||
|
||||
- name: Package (Windows, installer)
|
||||
if: runner.os == 'Windows'
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
cd ${{ env.INSTALL_DIR }}
|
||||
makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi"
|
||||
@ -411,5 +508,76 @@ jobs:
|
||||
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
|
||||
snap:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
if: inputs.build_type == 'Debug'
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: 'true'
|
||||
- name: Set short version
|
||||
shell: bash
|
||||
if: inputs.build_type == 'Debug'
|
||||
run: |
|
||||
ver_short=`git rev-parse --short HEAD`
|
||||
echo "VERSION=$ver_short" >> $GITHUB_ENV
|
||||
- name: Package Snap (Linux)
|
||||
id: snapcraft
|
||||
if: inputs.build_type == 'Debug'
|
||||
uses: snapcore/action-build@v1
|
||||
- name: Upload Snap (Linux)
|
||||
if: inputs.build_type == 'Debug'
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: prismlauncher_${{ env.VERSION }}_amd64.snap
|
||||
path: ${{ steps.snapcraft.outputs.snap }}
|
||||
|
||||
flatpak:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: bilelmoussaoui/flatpak-github-actions:kde-5.15-22.08
|
||||
options: --privileged
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
if: inputs.build_type == 'Debug'
|
||||
with:
|
||||
submodules: 'true'
|
||||
- name: Build Flatpak (Linux)
|
||||
if: inputs.build_type == 'Debug'
|
||||
uses: flatpak/flatpak-github-actions/flatpak-builder@v4
|
||||
with:
|
||||
bundle: "Prism Launcher.flatpak"
|
||||
manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml
|
||||
cache-key: flatpak-${{ github.sha }}-x86_64
|
||||
|
||||
nix:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
package:
|
||||
- prismlauncher
|
||||
- prismlauncher-qt5
|
||||
steps:
|
||||
- name: Clone repository
|
||||
if: inputs.build_type == 'Debug'
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: 'true'
|
||||
- name: Install nix
|
||||
if: inputs.build_type == 'Debug'
|
||||
uses: cachix/install-nix-action@v18
|
||||
with:
|
||||
install_url: https://nixos.org/nix/install
|
||||
extra_nix_config: |
|
||||
auto-optimise-store = true
|
||||
experimental-features = nix-command flakes
|
||||
- uses: cachix/cachix-action@v12
|
||||
if: inputs.build_type == 'Debug'
|
||||
with:
|
||||
name: prismlauncher
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
- name: Build
|
||||
if: inputs.build_type == 'Debug'
|
||||
run: nix build .#${{ matrix.package }} --print-build-logs
|
||||
|
35
.github/workflows/codeql.yml
vendored
Normal file
35
.github/workflows/codeql.yml
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
name: "CodeQL Code Scanning"
|
||||
|
||||
on: [ push, pull_request, workflow_dispatch ]
|
||||
|
||||
jobs:
|
||||
CodeQL:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
config-file: ./.github/codeql/codeql-config.yml
|
||||
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
|
||||
|
||||
- name: Configure and Build
|
||||
run: |
|
||||
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr -DLauncher_QT_VERSION_MAJOR=5 -G Ninja
|
||||
|
||||
cmake --build build
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
4
.github/workflows/trigger_builds.yml
vendored
4
.github/workflows/trigger_builds.yml
vendored
@ -8,7 +8,6 @@ on:
|
||||
- '**.md'
|
||||
- '**/LICENSE'
|
||||
- 'flake.lock'
|
||||
- '**.nix'
|
||||
- 'packages/**'
|
||||
- '.github/ISSUE_TEMPLATE/**'
|
||||
- '.markdownlint**'
|
||||
@ -17,7 +16,6 @@ on:
|
||||
- '**.md'
|
||||
- '**/LICENSE'
|
||||
- 'flake.lock'
|
||||
- '**.nix'
|
||||
- 'packages/**'
|
||||
- '.github/ISSUE_TEMPLATE/**'
|
||||
- '.markdownlint**'
|
||||
@ -30,5 +28,7 @@ jobs:
|
||||
uses: ./.github/workflows/build.yml
|
||||
with:
|
||||
build_type: Debug
|
||||
is_qt_cached: true
|
||||
secrets:
|
||||
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
|
||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
|
36
.github/workflows/trigger_release.yml
vendored
36
.github/workflows/trigger_release.yml
vendored
@ -12,6 +12,7 @@ jobs:
|
||||
uses: ./.github/workflows/build.yml
|
||||
with:
|
||||
build_type: Release
|
||||
is_qt_cached: false
|
||||
secrets:
|
||||
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
|
||||
|
||||
@ -45,13 +46,26 @@ jobs:
|
||||
|
||||
tar -czf PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }}
|
||||
|
||||
for d in PrismLauncher-Windows-*; do
|
||||
for d in PrismLauncher-Windows-MSVC*; do
|
||||
cd "${d}" || continue
|
||||
LEGACY="$(echo -n ${d} | grep -o Legacy || true)"
|
||||
ARM64="$(echo -n ${d} | grep -o arm64 || true)"
|
||||
INST="$(echo -n ${d} | grep -o Setup || true)"
|
||||
PORT="$(echo -n ${d} | grep -o Portable || true)"
|
||||
NAME="PrismLauncher-Windows"
|
||||
NAME="PrismLauncher-Windows-MSVC"
|
||||
test -z "${LEGACY}" || NAME="${NAME}-Legacy"
|
||||
test -z "${ARM64}" || NAME="${NAME}-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
|
||||
|
||||
for d in PrismLauncher-Windows-MinGW-w64*; do
|
||||
cd "${d}" || continue
|
||||
INST="$(echo -n ${d} | grep -o Setup || true)"
|
||||
PORT="$(echo -n ${d} | grep -o Portable || true)"
|
||||
NAME="PrismLauncher-Windows-MinGW-w64"
|
||||
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" *
|
||||
@ -72,14 +86,20 @@ jobs:
|
||||
PrismLauncher-Linux-${{ env.VERSION }}.tar.gz
|
||||
PrismLauncher-Linux-Portable-${{ env.VERSION }}.tar.gz
|
||||
PrismLauncher-Linux-${{ env.VERSION }}-x86_64.AppImage
|
||||
PrismLauncher-Windows-Legacy-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz
|
||||
PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
|
||||
PrismLauncher-Windows-Legacy-Portable-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-Legacy-Setup-${{ env.VERSION }}.exe
|
||||
PrismLauncher-Windows-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-Portable-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-Setup-${{ env.VERSION }}.exe
|
||||
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-MSVC-Legacy-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-MSVC-Legacy-Portable-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-MSVC-Legacy-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
|
||||
PrismLauncher-Windows-MSVC-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-MSVC-Portable-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-MSVC-Setup-${{ env.VERSION }}.exe
|
||||
PrismLauncher-macOS-${{ env.VERSION }}.tar.gz
|
||||
PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
|
||||
PrismLauncher-${{ env.VERSION }}.tar.gz
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -47,3 +47,6 @@ result/
|
||||
# Flatpak
|
||||
.flatpak-builder
|
||||
flatbuild
|
||||
|
||||
# Snap
|
||||
*.snap
|
||||
|
6
.gitmodules
vendored
6
.gitmodules
vendored
@ -10,3 +10,9 @@
|
||||
[submodule "libraries/libnbtplusplus"]
|
||||
path = libraries/libnbtplusplus
|
||||
url = https://github.com/PrismLauncher/libnbtplusplus.git
|
||||
[submodule "libraries/zlib"]
|
||||
path = libraries/zlib
|
||||
url = https://github.com/madler/zlib.git
|
||||
[submodule "libraries/extra-cmake-modules"]
|
||||
path = libraries/extra-cmake-modules
|
||||
url = https://github.com/KDE/extra-cmake-modules
|
||||
|
139
CMakeLists.txt
139
CMakeLists.txt
@ -1,10 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.15) # minimum version required by QuaZip
|
||||
|
||||
if(WIN32)
|
||||
# In Qt 5.1+ we have our own main() function, don't autolink to qtmain on Windows
|
||||
cmake_policy(SET CMP0020 OLD)
|
||||
endif()
|
||||
|
||||
project(Launcher)
|
||||
|
||||
string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BUILD_DIR}" IS_IN_SOURCE_BUILD)
|
||||
@ -32,13 +27,52 @@ set(CMAKE_C_STANDARD_REQUIRED true)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
include(GenerateExportHeader)
|
||||
set(CMAKE_CXX_FLAGS "-Wall -pedantic -fstack-protector-strong --param=ssp-buffer-size=4 ${CMAKE_CXX_FLAGS}")
|
||||
if(MSVC)
|
||||
# Use /W4 as /Wall includes unnesserey warnings such as added padding to structs
|
||||
# /permissive- specify standards-conforming compiler behavior, also enabled by Qt6, default on with std:c++20
|
||||
# /GS Adds buffer security checks, default on but incuded anyway to mirror gcc's fstack-protector flag
|
||||
set(CMAKE_CXX_FLAGS "/W4 /permissive- /GS ${CMAKE_CXX_FLAGS}")
|
||||
|
||||
# LINK accepts /SUBSYSTEM whics sets if we are a WINDOWS (gui) or a CONSOLE programs
|
||||
# This implicitly selects an entrypoint specific to the subsystem selected
|
||||
# qtmain/QtEntryPointLib provides the correct entrypoint (wWinMain) for gui programs
|
||||
# Additinaly LINK autodetects we use a GUI so we can omit /SUBSYSTEM
|
||||
# This allows tests to still use have console without using seperate linker flags
|
||||
# /MANIFEST:NO disables generating a manifest file, we instead provide our own
|
||||
# /STACK sets the stack reserve size, ATL's pack list needs 3-4 MiB as of November 2022, provide 8 MiB
|
||||
set(CMAKE_EXE_LINKER_FLAGS "/MANIFEST:NO /STACK:8388608 ${CMAKE_EXE_LINKER_FLAGS}")
|
||||
|
||||
# See https://github.com/ccache/ccache/issues/1040
|
||||
# Note, CMake 3.25 replaces this with CMAKE_MSVC_DEBUG_INFORMATION_FORMAT
|
||||
# See https://cmake.org/cmake/help/v3.25/variable/CMAKE_MSVC_DEBUG_INFORMATION_FORMAT.html
|
||||
foreach(config DEBUG RELWITHDEBINFO)
|
||||
foreach(lang C CXX)
|
||||
set(flags_var "CMAKE_${lang}_FLAGS_${config}")
|
||||
string(REGEX REPLACE "/Z[Ii]" "/Z7" ${flags_var} "${${flags_var}}")
|
||||
endforeach()
|
||||
endforeach()
|
||||
|
||||
if(CMAKE_MSVC_RUNTIME_LIBRARY STREQUAL "MultiThreadedDLL")
|
||||
set(CMAKE_MAP_IMPORTED_CONFIG_DEBUG Release "")
|
||||
set(CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO Release "")
|
||||
endif()
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "-Wall -pedantic -fstack-protector-strong --param=ssp-buffer-size=4 ${CMAKE_CXX_FLAGS}")
|
||||
|
||||
# ATL's pack list needs more than the default 1 Mib stack on windows
|
||||
if(WIN32)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "-Wl,--stack,8388608 ${CMAKE_EXE_LINKER_FLAGS}")
|
||||
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")
|
||||
|
||||
# Fix aarch64 build for toml++
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTOML_ENABLE_FLOAT16=0")
|
||||
|
||||
# set CXXFLAGS for build targets
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}")
|
||||
|
||||
@ -48,11 +82,18 @@ if(ENABLE_LTO)
|
||||
include(CheckIPOSupported)
|
||||
check_ipo_supported(RESULT ipo_supported OUTPUT ipo_error)
|
||||
|
||||
if(ipo_supported AND (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel"))
|
||||
message(STATUS "IPO / LTO enabled")
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||
elseif(ipo_supported)
|
||||
message(STATUS "Not enabling IPO / LTO on debug builds")
|
||||
if(ipo_supported)
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_MINSIZEREL TRUE)
|
||||
if(CMAKE_BUILD_TYPE)
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
|
||||
message(STATUS "IPO / LTO enabled")
|
||||
else()
|
||||
message(STATUS "Not enabling IPO / LTO on debug builds")
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "IPO / LTO will only be enabled for release builds")
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "IPO / LTO not supported: <${ipo_error}>")
|
||||
endif()
|
||||
@ -60,8 +101,20 @@ endif()
|
||||
|
||||
option(BUILD_TESTING "Build the testing tree." ON)
|
||||
|
||||
find_package(ECM REQUIRED NO_MODULE)
|
||||
set(CMAKE_MODULE_PATH "${ECM_MODULE_PATH};${CMAKE_MODULE_PATH}")
|
||||
find_package(ECM QUIET NO_MODULE)
|
||||
if(NOT ECM_FOUND)
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/libraries/extra-cmake-modules/CMakeLists.txt")
|
||||
message(STATUS "Using bundled ECM")
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/libraries/extra-cmake-modules/modules;${CMAKE_MODULE_PATH}")
|
||||
else()
|
||||
message(FATAL_ERROR
|
||||
" Could not find ECM\n \n"
|
||||
" Either install ECM using the system package manager or clone submodules\n"
|
||||
" Submodules can be cloned with 'git submodule update --init --recursive'")
|
||||
endif()
|
||||
else()
|
||||
set(CMAKE_MODULE_PATH "${ECM_MODULE_PATH};${CMAKE_MODULE_PATH}")
|
||||
endif()
|
||||
include(CTest)
|
||||
include(ECMAddTests)
|
||||
if(BUILD_TESTING)
|
||||
@ -76,7 +129,7 @@ set(Launcher_NEWS_OPEN_URL "https://prismlauncher.org/news" CACHE STRING "URL th
|
||||
set(Launcher_HELP_URL "https://prismlauncher.org/wiki/help-pages/%1" CACHE STRING "URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help")
|
||||
|
||||
######## Set version numbers ########
|
||||
set(Launcher_VERSION_MAJOR 5)
|
||||
set(Launcher_VERSION_MAJOR 6)
|
||||
set(Launcher_VERSION_MINOR 0)
|
||||
|
||||
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}")
|
||||
@ -146,6 +199,10 @@ set(Launcher_BUILD_TIMESTAMP "${TODAY}")
|
||||
|
||||
################################ 3rd Party Libs ################################
|
||||
|
||||
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
||||
find_package(ZLIB QUIET)
|
||||
endif()
|
||||
|
||||
# Find the required Qt parts
|
||||
include(QtVersionlessBackport)
|
||||
if(Launcher_QT_VERSION_MAJOR EQUAL 5)
|
||||
@ -164,7 +221,7 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 5)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUNICODE -D_UNICODE")
|
||||
elseif(Launcher_QT_VERSION_MAJOR EQUAL 6)
|
||||
set(QT_VERSION_MAJOR 6)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml Core5Compat)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat)
|
||||
list(APPEND Launcher_QT_LIBS Qt6::Core5Compat)
|
||||
|
||||
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
||||
@ -178,12 +235,16 @@ else()
|
||||
message(FATAL_ERROR "Qt version ${Launcher_QT_VERSION_MAJOR} is not supported")
|
||||
endif()
|
||||
|
||||
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)
|
||||
ecm_query_qt(QT_DATA_DIR QT_HOST_DATA)
|
||||
set(QT_MKSPECS_DIR ${QT_DATA_DIR}/mkspecs)
|
||||
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()
|
||||
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)
|
||||
@ -222,14 +283,14 @@ if(UNIX AND APPLE)
|
||||
set(APPS "\${CMAKE_INSTALL_PREFIX}/${Launcher_Name}.app")
|
||||
|
||||
# Mac bundle settings
|
||||
set(MACOSX_BUNDLE_BUNDLE_NAME "${Launcher_Name}")
|
||||
set(MACOSX_BUNDLE_INFO_STRING "${Launcher_Name}: A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once.")
|
||||
set(MACOSX_BUNDLE_BUNDLE_NAME "${Launcher_DisplayName}")
|
||||
set(MACOSX_BUNDLE_INFO_STRING "${Launcher_DisplayName}: A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once.")
|
||||
set(MACOSX_BUNDLE_GUI_IDENTIFIER "org.prismlauncher.${Launcher_Name}")
|
||||
set(MACOSX_BUNDLE_BUNDLE_VERSION "${Launcher_VERSION_NAME}")
|
||||
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_NAME}")
|
||||
set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_NAME}")
|
||||
set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns)
|
||||
set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2021-2022 ${Launcher_Copyright}")
|
||||
set(MACOSX_BUNDLE_COPYRIGHT "© 2022 ${Launcher_Copyright_Mac}")
|
||||
set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=")
|
||||
set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml")
|
||||
|
||||
@ -247,13 +308,11 @@ if(UNIX AND APPLE)
|
||||
install(FILES ${Launcher_Branding_ICNS} DESTINATION ${RESOURCES_DEST_DIR} RENAME ${Launcher_Name}.icns)
|
||||
|
||||
elseif(UNIX)
|
||||
include(KDEInstallDirs)
|
||||
|
||||
set(BINARY_DEST_DIR "bin")
|
||||
set(LIBRARY_DEST_DIR "lib${LIB_SUFFIX}")
|
||||
set(JARS_DEST_DIR "share/${Launcher_APP_BINARY_NAME}")
|
||||
set(LAUNCHER_DESKTOP_DEST_DIR "share/applications" CACHE STRING "Path to the desktop file directory")
|
||||
set(LAUNCHER_METAINFO_DEST_DIR "share/metainfo" CACHE STRING "Path to the metainfo directory")
|
||||
set(LAUNCHER_ICON_DEST_DIR "share/icons/hicolor/scalable/apps" CACHE STRING "Path to the scalable icon directory")
|
||||
set(LAUNCHER_MAN_DEST_DIR "share/man/man6" CACHE STRING "Path to the man page directory")
|
||||
|
||||
# install as bundle with no dependencies included
|
||||
set(INSTALL_BUNDLE "nodeps")
|
||||
@ -261,12 +320,13 @@ elseif(UNIX)
|
||||
# Set RPATH
|
||||
SET(Launcher_BINARY_RPATH "$ORIGIN/")
|
||||
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_Desktop} DESTINATION ${LAUNCHER_DESKTOP_DEST_DIR})
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_MetaInfo} DESTINATION ${LAUNCHER_METAINFO_DEST_DIR})
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION ${LAUNCHER_ICON_DEST_DIR})
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_Desktop} DESTINATION ${KDE_INSTALL_APPDIR})
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_MetaInfo} DESTINATION ${KDE_INSTALL_METAINFODIR})
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION "${KDE_INSTALL_ICONDIR}/hicolor/scalable/apps")
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_mrpack_MIMEInfo} DESTINATION ${KDE_INSTALL_MIMEDIR})
|
||||
|
||||
if(Launcher_ManPage)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_ManPage} DESTINATION ${LAUNCHER_MAN_DEST_DIR})
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_ManPage} DESTINATION "${KDE_INSTALL_MANDIR}/man6")
|
||||
endif()
|
||||
|
||||
# Install basic runner script if component "portable" is selected
|
||||
@ -306,6 +366,21 @@ add_subdirectory(libraries/systeminfo) # system information library
|
||||
add_subdirectory(libraries/hoedown) # markdown parser
|
||||
add_subdirectory(libraries/launcher) # java based launcher part for Minecraft
|
||||
add_subdirectory(libraries/javacheck) # java compatibility checker
|
||||
if(NOT ZLIB_FOUND)
|
||||
message(STATUS "Using bundled zlib")
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) # Suppress cmake warnings and allow INTERPROCEDURAL_OPTIMIZATION for zlib
|
||||
set(SKIP_INSTALL_ALL ON)
|
||||
add_subdirectory(libraries/zlib EXCLUDE_FROM_ALL)
|
||||
|
||||
set(ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib" "${CMAKE_CURRENT_BINARY_DIR}/libraries/zlib" CACHE STRING "")
|
||||
set_target_properties(zlibstatic PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${ZLIB_INCLUDE_DIR}")
|
||||
add_library(ZLIB::ZLIB ALIAS zlibstatic)
|
||||
set(ZLIB_LIBRARY ZLIB::ZLIB CACHE STRING "zlib library name")
|
||||
|
||||
find_package(ZLIB REQUIRED)
|
||||
else()
|
||||
message(STATUS "Using system zlib")
|
||||
endif()
|
||||
if (FORCE_BUNDLED_QUAZIP)
|
||||
message(STATUS "Using bundled QuaZip")
|
||||
set(BUILD_SHARED_LIBS 0) # link statically to avoid conflicts.
|
||||
|
42
COPYING.md
42
COPYING.md
@ -398,3 +398,45 @@
|
||||
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.
|
||||
|
||||
## Breeze icons
|
||||
|
||||
Copyright (C) 2014 Uri Herrera <uri_herrera@nitrux.in> and others
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Lesser General Public License for more details.
|
||||
|
||||
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/>.
|
||||
|
||||
## Oxygen Icons
|
||||
|
||||
The Oxygen Icon Theme
|
||||
Copyright (C) 2007 Nuno Pinheiro <nuno@oxygen-icons.org>
|
||||
Copyright (C) 2007 David Vignoni <david@icon-king.com>
|
||||
Copyright (C) 2007 David Miller <miller@oxygen-icons.org>
|
||||
Copyright (C) 2007 Johann Ollivier Lapeyre <johann@oxygen-icons.org>
|
||||
Copyright (C) 2007 Kenneth Wimer <kwwii@bootsplash.org>
|
||||
Copyright (C) 2007 Riccardo Iaconelli <riccardo@oxygen-icons.org>
|
||||
|
||||
and others
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Lesser General Public License for more details.
|
||||
|
||||
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/>.
|
||||
|
63
README.md
63
README.md
@ -1,44 +1,52 @@
|
||||
<p align="center">
|
||||
<img src="./program_info/org.prismlauncher.PrismLauncher.logo.svg#gh-light-mode-only" alt="Prism Launcher logo" width="50%"/>
|
||||
<img src="./program_info/org.prismlauncher.PrismLauncher.logo-darkmode.svg#gh-dark-mode-only" alt="Prism Launcher logo" width="50%"/>
|
||||
<p align="left">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="/program_info/org.prismlauncher.PrismLauncher.logo-darkmode.svg">
|
||||
<source media="(prefers-color-scheme: light)" srcset="/program_info/org.prismlauncher.PrismLauncher.logo.svg">
|
||||
<img alt="Prism Launcher" src="/program_info/org.prismlauncher.PrismLauncher.logo.svg" width="50%">
|
||||
</picture>
|
||||
</p>
|
||||
|
||||
|
||||
Prism Launcher is a custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once.
|
||||
|
||||
This is a **fork** of the MultiMC Launcher and not endorsed by MultiMC.
|
||||
This is a **fork** of the MultiMC Launcher and is not endorsed by MultiMC.
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
- All downloads and instructions for Prism Launcher can be found [on our website](https://prismlauncher.org/download/).
|
||||
- Last build status can be found [here](https://github.com/PrismLauncher/PrismLauncher/actions).
|
||||
<a href="https://repology.org/project/prismlauncher/versions">
|
||||
<img src="https://repology.org/badge/vertical-allrepos/prismlauncher.svg" alt="Packaging status" align="right">
|
||||
</a>
|
||||
|
||||
- All downloads and instructions for Prism Launcher can be found on our [Website](https://prismlauncher.org/download/).
|
||||
- Last build status can be found in the [GitHub Actions](https://github.com/PrismLauncher/PrismLauncher/actions).
|
||||
|
||||
### Development Builds
|
||||
|
||||
There are development builds available [here](https://github.com/PrismLauncher/PrismLauncher/actions). These have debug information in the binaries, so their file sizes are relatively larger.
|
||||
|
||||
Portable builds are provided for on Linux, Windows, and macOS.
|
||||
Prebuilt Development builds are provided for **Linux**, **Windows** and **macOS**.
|
||||
|
||||
For Debian and Arch, you can use these packages for the latest development versions:
|
||||
[![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--git-blue)](https://aur.archlinux.org/packages/prismlauncher-git/)
|
||||
[![prismlauncher-git](https://img.shields.io/badge/mpr-prismlauncher--git-orange)](https://mpr.makedeb.org/packages/prismlauncher-git)
|
||||
## Help & Support
|
||||
For **Arch**, **Debian**, **Fedora**, **OpenSUSE (Tumbleweed)** and **Gentoo**, respectively, you can use these packages for the latest development versions:
|
||||
|
||||
Feel free to create an issue if you need help. However, you might find it easier to ask in the Discord server.
|
||||
[![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--git-1793D1?style=flat-square&logo=archlinux&logoColor=white)](https://aur.archlinux.org/packages/prismlauncher-git/) [![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--qt5--git-1793D1?style=flat-square&logo=archlinux&logoColor=white)](https://aur.archlinux.org/packages/prismlauncher-qt5-git/) [![prismlauncher-git](https://img.shields.io/badge/mpr-prismlauncher--git-A80030?style=flat-square&logo=debian&logoColor=white)](https://mpr.makedeb.org/packages/prismlauncher-git) [![prismlauncher-nightly](https://img.shields.io/badge/copr-prismlauncher--nightly-51A2DA?style=flat-square&logo=fedora&logoColor=white)](https://copr.fedorainfracloud.org/coprs/g3tchoo/prismlauncher/) [![prismlauncher-nightly](https://img.shields.io/badge/OBS-prismlauncher--nightly-3AB6A9?style=flat-square&logo=opensuse&logoColor=white)](https://build.opensuse.org/project/show/home:getchoo) [![prismlauncher-9999](https://img.shields.io/badge/gentoo-prismlauncher--9999-4D4270?style=flat-square&logo=gentoo&logoColor=white)](https://packages.gentoo.org/packages/games-action/prismlauncher)
|
||||
|
||||
[![Prism Launcher Discord server](https://discordapp.com/api/guilds/1031648380885147709/widget.png?style=banner3)](https://discord.gg/prismlauncher)
|
||||
## Community & Support
|
||||
|
||||
We will also soon be opening up our Matrix channels.
|
||||
You can already join our Matrix space:
|
||||
Feel free to create a GitHub issue if you find a bug or want to suggest a new feature. We have multiple community spaces where other community members can help you.
|
||||
|
||||
[![PrismLauncher Space](https://img.shields.io/matrix/prismlauncher:matrix.org?label=PrismLauncher%20space)](https://matrix.to/#/#prismlauncher:matrix.org)
|
||||
#### Join our Discord server:
|
||||
[![Prism Launcher Discord server](https://discordapp.com/api/guilds/1031648380885147709/widget.png?style=banner2)](https://discord.gg/prismlauncher)
|
||||
|
||||
We also have a subreddit you can post your issues and suggestions on:
|
||||
#### Join our Matrix space:
|
||||
[![PrismLauncher Space](https://img.shields.io/matrix/prismlauncher:matrix.org?style=for-the-badge&logo=matrix)](https://matrix.to/#/#prismlauncher:matrix.org)
|
||||
|
||||
[r/PrismLauncher](https://www.reddit.com/r/PrismLauncher/)
|
||||
#### Join our Subreddit:
|
||||
[![r/PrismLauncher](https://img.shields.io/reddit/subreddit-subscribers/prismlauncher?style=for-the-badge&logo=reddit)](https://www.reddit.com/r/PrismLauncher/)
|
||||
|
||||
## Building
|
||||
|
||||
If you want to build Prism Launcher yourself, check [Build Instructions](https://prismlauncher.org/wiki/development/build-instructions/) for build instructions.
|
||||
If you want to build Prism Launcher yourself, check the [Build Instructions](https://prismlauncher.org/wiki/development/build-instructions/).
|
||||
|
||||
## Translations
|
||||
|
||||
@ -60,13 +68,11 @@ Be aware that if you build this software without removing the provided API keys
|
||||
|
||||
If you do not agree with these terms and conditions, then remove the associated API keys from the [CMakeLists.txt](CMakeLists.txt) file by setting them to an empty string (`""`).
|
||||
|
||||
## License
|
||||
## Sponsors & Partners
|
||||
|
||||
All launcher code is available under the GPL-3.0-only license.
|
||||
|
||||
The logo and related assets are under the CC BY-SA 4.0 license.
|
||||
We thank all the wonderful backers over at Open Collective! Support Prism Launcher by [becoming a backer](https://opencollective.com/prismlauncher).
|
||||
|
||||
## Sponsors
|
||||
[![OpenCollective Backers](https://opencollective.com/prismlauncher/backers.svg?width=890&limit=1000)](https://opencollective.com/prismlauncher#backers)
|
||||
|
||||
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/).
|
||||
|
||||
@ -85,3 +91,12 @@ Thanks to Netlify for providing us their excellent web services, as part of thei
|
||||
Thanks to the awesome people over at [MacStadium](https://www.macstadium.com/), for providing M1-Macs for development purposes!
|
||||
|
||||
<a href="https://www.macstadium.com"><img src="https://uploads-ssl.webflow.com/5ac3c046c82724970fc60918/5c019d917bba312af7553b49_MacStadium-developerlogo.png" alt="Powered by MacStadium" width="300"></a>
|
||||
|
||||
|
||||
## License
|
||||
|
||||
All launcher code is available under the GPL-3.0-only license.
|
||||
|
||||
![https://github.com/PrismLauncher/PrismLauncher/blob/develop/LICENSE](https://img.shields.io/github/license/PrismLauncher/PrismLauncher?style=for-the-badge&logo=gnu&color=C4282D)
|
||||
|
||||
The logo and related assets are under the CC BY-SA 4.0 license.
|
||||
|
@ -49,6 +49,7 @@ Config::Config()
|
||||
LAUNCHER_CONFIGFILE = "@Launcher_ConfigFile@";
|
||||
LAUNCHER_GIT = "@Launcher_Git@";
|
||||
LAUNCHER_DESKTOPFILENAME = "@Launcher_DesktopFileName@";
|
||||
LAUNCHER_SVGFILENAME = "@Launcher_SVGFileName@";
|
||||
|
||||
USER_AGENT = "@Launcher_UserAgent@";
|
||||
USER_AGENT_UNCACHED = USER_AGENT + " (Uncached)";
|
||||
|
@ -51,6 +51,7 @@ class Config {
|
||||
QString LAUNCHER_CONFIGFILE;
|
||||
QString LAUNCHER_GIT;
|
||||
QString LAUNCHER_DESKTOPFILENAME;
|
||||
QString LAUNCHER_SVGFILENAME;
|
||||
|
||||
/// The major version number.
|
||||
int VERSION_MAJOR;
|
||||
|
@ -44,5 +44,28 @@
|
||||
<string>${MACOSX_SPARKLE_UPDATE_PUBLIC_KEY}</string>
|
||||
<key>SUFeedURL</key>
|
||||
<string>${MACOSX_SPARKLE_UPDATE_FEED_URL}</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>zip</string>
|
||||
<string>mrpack</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Prism Launcher instance</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>TEXT</string>
|
||||
<string>utxt</string>
|
||||
<string>TUTX</string>
|
||||
<string>****</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -23,8 +23,8 @@
|
||||
pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system});
|
||||
|
||||
packagesFn = pkgs: rec {
|
||||
prismlauncher = pkgs.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus tomlplusplus; };
|
||||
prismlauncher-qt6 = pkgs.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus tomlplusplus; };
|
||||
prismlauncher-qt5 = pkgs.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus tomlplusplus; };
|
||||
prismlauncher = pkgs.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus tomlplusplus; };
|
||||
};
|
||||
in
|
||||
{
|
||||
|
83
flatpak/org.prismlauncher.PrismLauncher.yml
Normal file
83
flatpak/org.prismlauncher.PrismLauncher.yml
Normal file
@ -0,0 +1,83 @@
|
||||
id: org.prismlauncher.PrismLauncher
|
||||
runtime: org.kde.Platform
|
||||
runtime-version: "5.15-22.08"
|
||||
sdk: org.kde.Sdk
|
||||
sdk-extensions:
|
||||
- org.freedesktop.Sdk.Extension.openjdk17
|
||||
- org.freedesktop.Sdk.Extension.openjdk8
|
||||
add-extensions:
|
||||
com.valvesoftware.Steam.Utility.gamescope:
|
||||
version: stable
|
||||
add-ld-path: lib
|
||||
no-autodownload: true
|
||||
autodelete: false
|
||||
directory: utils/gamescope
|
||||
|
||||
command: prismlauncher
|
||||
finish-args:
|
||||
- --share=ipc
|
||||
- --socket=x11
|
||||
- --socket=wayland
|
||||
- --device=all
|
||||
- --share=network
|
||||
- --socket=pulseaudio
|
||||
# for Discord RPC mods
|
||||
- --filesystem=xdg-run/app/com.discordapp.Discord:create
|
||||
# Mod drag&drop
|
||||
- --filesystem=xdg-download:ro
|
||||
|
||||
modules:
|
||||
- name: prismlauncher
|
||||
buildsystem: cmake-ninja
|
||||
config-opts:
|
||||
- -DLauncher_BUILD_PLATFORM=flatpak
|
||||
- -DCMAKE_BUILD_TYPE=Debug
|
||||
build-options:
|
||||
env:
|
||||
JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17
|
||||
JAVA_COMPILER: /usr/lib/sdk/openjdk17/jvm/openjdk-17/bin/javac
|
||||
sources:
|
||||
- type: dir
|
||||
path: ../
|
||||
- name: openjdk
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- mkdir -p /app/jdk/
|
||||
- /usr/lib/sdk/openjdk17/install.sh
|
||||
- mv /app/jre /app/jdk/17
|
||||
- /usr/lib/sdk/openjdk8/install.sh
|
||||
- mv /app/jre /app/jdk/8
|
||||
cleanup: [/jre]
|
||||
- name: xrandr
|
||||
buildsystem: autotools
|
||||
sources:
|
||||
- type: archive
|
||||
url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.1.tar.xz
|
||||
sha256: 7bc76daf9d72f8aff885efad04ce06b90488a1a169d118dea8a2b661832e8762
|
||||
cleanup: [/share/man, /bin/xkeystone]
|
||||
- name: gamemode
|
||||
buildsystem: meson
|
||||
config-opts:
|
||||
- -Dwith-sd-bus-provider=no-daemon
|
||||
- -Dwith-examples=false
|
||||
post-install:
|
||||
# gamemoderun is installed for users who want to use wrapper commands
|
||||
# post-install is running inside the build dir, we need it from the source though
|
||||
- install -Dm755 ../data/gamemoderun -t /app/bin
|
||||
sources:
|
||||
- type: git
|
||||
url: https://github.com/FeralInteractive/gamemode
|
||||
tag: "1.7"
|
||||
commit: 4dc99dff76218718763a6b07fc1900fa6d1dafd9
|
||||
- name: enhance
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
- mkdir -p /app/utils/gamescope
|
||||
- install -Dm755 prime-run /app/bin/prime-run
|
||||
- mv /app/bin/prismlauncher /app/bin/prismrun
|
||||
- install -Dm755 prismlauncher /app/bin/prismlauncher
|
||||
sources:
|
||||
- type: file
|
||||
path: ../flatpak/prime-run
|
||||
- type: file
|
||||
path: ../flatpak/prismlauncher
|
4
flatpak/prime-run
Normal file
4
flatpak/prime-run
Normal file
@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
export __NV_PRIME_RENDER_OFFLOAD=1 __VK_LAYER_NV_optimus=NVIDIA_only __GLX_VENDOR_LIBRARY_NAME=nvidia
|
||||
exec "$@"
|
11
flatpak/prismlauncher
Normal file
11
flatpak/prismlauncher
Normal file
@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
# discord RPC
|
||||
for i in {0..9}; do
|
||||
test -S "$XDG_RUNTIME_DIR"/discord-ipc-"$i" || ln -sf {app/com.discordapp.Discord,"$XDG_RUNTIME_DIR"}/discord-ipc-"$i";
|
||||
done
|
||||
|
||||
export PATH="${PATH}${PATH:+:}/app/utils/gamescope/bin:/usr/lib/extensions/vulkan/MangoHud/bin"
|
||||
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}${LD_LIBRARY_PATH:+:}/usr/lib/extensions/vulkan/MangoHud/\$LIB/"
|
||||
|
||||
exec /app/bin/prismrun "$@"
|
@ -1,8 +1,12 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
// SPDX-FileCopyrightText: 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0
|
||||
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
|
||||
* Copyright (C) 2022 Tayou <tayou@gmx.net>
|
||||
*
|
||||
* 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
|
||||
@ -37,11 +41,16 @@
|
||||
#include "Application.h"
|
||||
#include "BuildConfig.h"
|
||||
|
||||
#include "DataMigrationTask.h"
|
||||
#include "net/PasteUpload.h"
|
||||
#include "pathmatcher/MultiMatcher.h"
|
||||
#include "pathmatcher/SimplePrefixMatcher.h"
|
||||
#include "ui/InstanceWindow.h"
|
||||
#include "ui/MainWindow.h"
|
||||
#include "ui/instanceview/InstancesView.h"
|
||||
|
||||
#include "ui/dialogs/ProgressDialog.h"
|
||||
|
||||
#include "ui/pages/BasePageProvider.h"
|
||||
#include "ui/pages/global/LauncherPage.h"
|
||||
#include "ui/pages/global/MinecraftPage.h"
|
||||
@ -53,14 +62,9 @@
|
||||
#include "ui/pages/global/APIPage.h"
|
||||
#include "ui/pages/global/CustomCommandsPage.h"
|
||||
|
||||
#include "ui/themes/ITheme.h"
|
||||
#include "ui/themes/SystemTheme.h"
|
||||
#include "ui/themes/DarkTheme.h"
|
||||
#include "ui/themes/BrightTheme.h"
|
||||
#include "ui/themes/CustomTheme.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include "ui/WinDarkmode.h"
|
||||
#include <versionhelpers.h>
|
||||
#endif
|
||||
|
||||
#include "ui/setupwizard/SetupWizard.h"
|
||||
@ -72,6 +76,8 @@
|
||||
|
||||
#include "ui/pagedialog/PageDialog.h"
|
||||
|
||||
#include "ui/themes/ThemeManager.h"
|
||||
|
||||
#include "ApplicationMessage.h"
|
||||
|
||||
#include <iostream>
|
||||
@ -227,7 +233,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
setOrganizationDomain(BuildConfig.LAUNCHER_DOMAIN);
|
||||
setApplicationName(BuildConfig.LAUNCHER_NAME);
|
||||
setApplicationDisplayName(QString("%1 %2").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString()));
|
||||
setApplicationVersion(BuildConfig.printableVersionString());
|
||||
setApplicationVersion(BuildConfig.printableVersionString() + "\n" + BuildConfig.GIT_COMMIT);
|
||||
setDesktopFileName(BuildConfig.LAUNCHER_DESKTOPFILENAME);
|
||||
startTime = QDateTime::currentDateTime();
|
||||
|
||||
@ -302,22 +308,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
dataPath = foo.absolutePath();
|
||||
adjustedBy = "Persistent data path";
|
||||
|
||||
QDir polymcData(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation), "PolyMC"));
|
||||
if (polymcData.exists()) {
|
||||
dataPath = polymcData.absolutePath();
|
||||
adjustedBy = "PolyMC data path";
|
||||
}
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
// TODO: this should be removed in a future version
|
||||
// TODO: provide a migration path similar to macOS migration
|
||||
QDir bar(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation), "polymc"));
|
||||
if (bar.exists()) {
|
||||
dataPath = bar.absolutePath();
|
||||
adjustedBy = "Legacy data path";
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef Q_OS_MACOS
|
||||
if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) {
|
||||
dataPath = m_rootPath;
|
||||
@ -440,6 +430,15 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
qDebug() << "<> Log initialized.";
|
||||
}
|
||||
|
||||
{
|
||||
bool migrated = false;
|
||||
|
||||
if (!migrated)
|
||||
migrated = handleDataMigration(dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../PolyMC"), "PolyMC", "polymc.cfg");
|
||||
if (!migrated)
|
||||
migrated = handleDataMigration(dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../multimc"), "MultiMC", "multimc.cfg");
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
qDebug() << BuildConfig.LAUNCHER_DISPLAYNAME << ", (c) 2013-2021 " << BuildConfig.LAUNCHER_COPYRIGHT;
|
||||
@ -499,6 +498,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
// Theming
|
||||
m_settings->registerSetting("IconTheme", QString("pe_colored"));
|
||||
m_settings->registerSetting("ApplicationTheme", QString("system"));
|
||||
m_settings->registerSetting("BackgroundCat", QString("kitteh"));
|
||||
|
||||
// Remembered state
|
||||
m_settings->registerSetting("LastUsedGroupForNewInstance", QString());
|
||||
@ -563,7 +563,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
|
||||
// Memory
|
||||
m_settings->registerSetting({"MinMemAlloc", "MinMemoryAlloc"}, 512);
|
||||
m_settings->registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, 4096);
|
||||
m_settings->registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, suitableMaxMem());
|
||||
m_settings->registerSetting("PermGen", 128);
|
||||
|
||||
// Java Settings
|
||||
@ -612,6 +612,8 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
m_settings->registerSetting("TheCat", false);
|
||||
m_settings->registerSetting("InstanceDisplayMode", InstancesView::TableMode);
|
||||
|
||||
m_settings->registerSetting("ToolbarsLocked", false);
|
||||
|
||||
m_settings->registerSetting("InstSortMode", "Name");
|
||||
m_settings->registerSetting("SelectedInstance", QString());
|
||||
|
||||
@ -746,29 +748,8 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
qDebug() << "<> Instance icons intialized.";
|
||||
}
|
||||
|
||||
// Icon themes
|
||||
{
|
||||
// TODO: icon themes and instance icons do not mesh well together. Rearrange and fix discrepancies!
|
||||
// set icon theme search path!
|
||||
auto searchPaths = QIcon::themeSearchPaths();
|
||||
searchPaths.append("iconthemes");
|
||||
QIcon::setThemeSearchPaths(searchPaths);
|
||||
qDebug() << "<> Icon themes initialized.";
|
||||
}
|
||||
|
||||
// Initialize widget themes
|
||||
{
|
||||
auto insertTheme = [this](ITheme * theme)
|
||||
{
|
||||
m_themes.insert(std::make_pair(theme->id(), std::unique_ptr<ITheme>(theme)));
|
||||
};
|
||||
auto darkTheme = new DarkTheme();
|
||||
insertTheme(new SystemTheme());
|
||||
insertTheme(darkTheme);
|
||||
insertTheme(new BrightTheme());
|
||||
insertTheme(new CustomTheme(darkTheme, "custom"));
|
||||
qDebug() << "<> Widget themes initialized.";
|
||||
}
|
||||
// Themes
|
||||
m_themeManager = std::make_unique<ThemeManager>(m_mainWindow);
|
||||
|
||||
// initialize and load all instances
|
||||
{
|
||||
@ -933,18 +914,24 @@ bool Application::createSetupWizard()
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Application::event(QEvent* event) {
|
||||
bool Application::event(QEvent* event)
|
||||
{
|
||||
#ifdef Q_OS_MACOS
|
||||
if (event->type() == QEvent::ApplicationStateChange) {
|
||||
auto ev = static_cast<QApplicationStateChangeEvent*>(event);
|
||||
|
||||
if (m_prevAppState == Qt::ApplicationActive
|
||||
&& ev->applicationState() == Qt::ApplicationActive) {
|
||||
if (m_prevAppState == Qt::ApplicationActive && ev->applicationState() == Qt::ApplicationActive) {
|
||||
emit clickedOnDock();
|
||||
}
|
||||
m_prevAppState = ev->applicationState();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (event->type() == QEvent::FileOpen) {
|
||||
auto ev = static_cast<QFileOpenEvent*>(event);
|
||||
m_mainWindow->droppedURLs({ ev->url() });
|
||||
}
|
||||
|
||||
return QApplication::event(event);
|
||||
}
|
||||
|
||||
@ -1122,60 +1109,25 @@ std::shared_ptr<JavaInstallList> Application::javalist()
|
||||
return m_javalist;
|
||||
}
|
||||
|
||||
std::vector<ITheme *> Application::getValidApplicationThemes()
|
||||
QList<ITheme*> Application::getValidApplicationThemes()
|
||||
{
|
||||
std::vector<ITheme *> ret;
|
||||
auto iter = m_themes.cbegin();
|
||||
while (iter != m_themes.cend())
|
||||
{
|
||||
ret.push_back((*iter).second.get());
|
||||
iter++;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Application::isFlatpak()
|
||||
{
|
||||
#ifdef Q_OS_LINUX
|
||||
return QFile::exists("/.flatpak-info");
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
return m_themeManager->getValidApplicationThemes();
|
||||
}
|
||||
|
||||
void Application::setApplicationTheme(const QString& name, bool initial)
|
||||
{
|
||||
auto systemPalette = qApp->palette();
|
||||
auto themeIter = m_themes.find(name);
|
||||
if(themeIter != m_themes.end())
|
||||
{
|
||||
auto & theme = (*themeIter).second;
|
||||
theme->apply(initial);
|
||||
#ifdef Q_OS_WIN
|
||||
if (m_mainWindow) {
|
||||
if (QString::compare(theme->id(), "dark") == 0) {
|
||||
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), true);
|
||||
} else {
|
||||
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
qWarning() << "Tried to set invalid theme:" << name;
|
||||
}
|
||||
m_themeManager->setApplicationTheme(name, initial);
|
||||
}
|
||||
|
||||
void Application::setIconTheme(const QString& name)
|
||||
{
|
||||
QIcon::setThemeName(name);
|
||||
m_themeManager->setIconTheme(name);
|
||||
}
|
||||
|
||||
QIcon Application::getThemedIcon(const QString& name)
|
||||
{
|
||||
if(name == "logo") {
|
||||
return QIcon(":/org.prismlauncher.PrismLauncher.svg"); // FIXME: Make this a BuildConfig variable
|
||||
return QIcon(":/" + BuildConfig.LAUNCHER_SVGFILENAME);
|
||||
}
|
||||
return QIcon::fromTheme(name);
|
||||
}
|
||||
@ -1393,10 +1345,13 @@ MainWindow* Application::showMainWindow(bool minimized)
|
||||
m_mainWindow->restoreState(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowState").toByteArray()));
|
||||
m_mainWindow->restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowGeometry").toByteArray()));
|
||||
#ifdef Q_OS_WIN
|
||||
if (QString::compare(settings()->get("ApplicationTheme").toString(), "dark") == 0) {
|
||||
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), true);
|
||||
} else {
|
||||
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), false);
|
||||
if (IsWindows10OrGreater())
|
||||
{
|
||||
if (QString::compare(settings()->get("ApplicationTheme").toString(), "dark") == 0) {
|
||||
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), true);
|
||||
} else {
|
||||
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if(minimized)
|
||||
@ -1635,3 +1590,102 @@ QString Application::getUserAgentUncached()
|
||||
|
||||
return BuildConfig.USER_AGENT_UNCACHED;
|
||||
}
|
||||
|
||||
int Application::suitableMaxMem()
|
||||
{
|
||||
float totalRAM = (float)Sys::getSystemRam() / (float)Sys::mebibyte;
|
||||
int maxMemoryAlloc;
|
||||
|
||||
// If totalRAM < 6GB, use (totalRAM / 1.5), else 4GB
|
||||
if (totalRAM < (4096 * 1.5))
|
||||
maxMemoryAlloc = (int) (totalRAM / 1.5);
|
||||
else
|
||||
maxMemoryAlloc = 4096;
|
||||
|
||||
return maxMemoryAlloc;
|
||||
}
|
||||
|
||||
bool Application::handleDataMigration(const QString& currentData,
|
||||
const QString& oldData,
|
||||
const QString& name,
|
||||
const QString& configFile) const
|
||||
{
|
||||
QString nomigratePath = FS::PathCombine(currentData, name + "_nomigrate.txt");
|
||||
QStringList configPaths = { FS::PathCombine(oldData, configFile), FS::PathCombine(oldData, BuildConfig.LAUNCHER_CONFIGFILE) };
|
||||
|
||||
QLocale locale;
|
||||
|
||||
// Is there a valid config at the old location?
|
||||
bool configExists = false;
|
||||
for (QString configPath : configPaths) {
|
||||
configExists |= QFileInfo::exists(configPath);
|
||||
}
|
||||
|
||||
if (!configExists || QFileInfo::exists(nomigratePath)) {
|
||||
qDebug() << "<> No migration needed from" << name;
|
||||
return false;
|
||||
}
|
||||
|
||||
QString message;
|
||||
bool currentExists = QFileInfo::exists(FS::PathCombine(currentData, BuildConfig.LAUNCHER_CONFIGFILE));
|
||||
|
||||
if (currentExists) {
|
||||
message = tr("Old data from %1 was found, but you already have existing data for %2. Sadly you will need to migrate yourself. Do "
|
||||
"you want to be reminded of the pending data migration next time you start %2?")
|
||||
.arg(name, BuildConfig.LAUNCHER_DISPLAYNAME);
|
||||
} else {
|
||||
message = tr("It looks like you used %1 before. Do you want to migrate your data to the new location of %2?")
|
||||
.arg(name, BuildConfig.LAUNCHER_DISPLAYNAME);
|
||||
|
||||
QFileInfo logInfo(FS::PathCombine(oldData, name + "-0.log"));
|
||||
if (logInfo.exists()) {
|
||||
QString lastModified = logInfo.lastModified().toString(locale.dateFormat());
|
||||
message = tr("It looks like you used %1 on %2 before. Do you want to migrate your data to the new location of %3?")
|
||||
.arg(name, lastModified, BuildConfig.LAUNCHER_DISPLAYNAME);
|
||||
}
|
||||
}
|
||||
|
||||
QMessageBox::StandardButton askMoveDialogue =
|
||||
QMessageBox::question(nullptr, BuildConfig.LAUNCHER_DISPLAYNAME, message, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||
|
||||
auto setDoNotMigrate = [&nomigratePath] {
|
||||
QFile file(nomigratePath);
|
||||
file.open(QIODevice::WriteOnly);
|
||||
};
|
||||
|
||||
// create no-migrate file if user doesn't want to migrate
|
||||
if (askMoveDialogue != QMessageBox::Yes) {
|
||||
qDebug() << "<> Migration declined for" << name;
|
||||
setDoNotMigrate();
|
||||
return currentExists; // cancel further migrations, if we already have a data directory
|
||||
}
|
||||
|
||||
if (!currentExists) {
|
||||
// Migrate!
|
||||
auto matcher = std::make_shared<MultiMatcher>();
|
||||
matcher->add(std::make_shared<SimplePrefixMatcher>(configFile));
|
||||
matcher->add(std::make_shared<SimplePrefixMatcher>(
|
||||
BuildConfig.LAUNCHER_CONFIGFILE)); // it's possible that we already used that directory before
|
||||
matcher->add(std::make_shared<SimplePrefixMatcher>("accounts.json"));
|
||||
matcher->add(std::make_shared<SimplePrefixMatcher>("accounts/"));
|
||||
matcher->add(std::make_shared<SimplePrefixMatcher>("assets/"));
|
||||
matcher->add(std::make_shared<SimplePrefixMatcher>("icons/"));
|
||||
matcher->add(std::make_shared<SimplePrefixMatcher>("instances/"));
|
||||
matcher->add(std::make_shared<SimplePrefixMatcher>("libraries/"));
|
||||
matcher->add(std::make_shared<SimplePrefixMatcher>("mods/"));
|
||||
matcher->add(std::make_shared<SimplePrefixMatcher>("themes/"));
|
||||
|
||||
ProgressDialog diag;
|
||||
DataMigrationTask task(nullptr, oldData, currentData, matcher);
|
||||
if (diag.execWithTask(&task)) {
|
||||
qDebug() << "<> Migration succeeded";
|
||||
setDoNotMigrate();
|
||||
} else {
|
||||
QString reason = task.failReason();
|
||||
QMessageBox::critical(nullptr, BuildConfig.LAUNCHER_DISPLAYNAME, tr("Migration failed! Reason: %1").arg(reason));
|
||||
}
|
||||
} else {
|
||||
qWarning() << "<> Migration was skipped, due to existing data";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2022 Tayou <tayou@gmx.net>
|
||||
*
|
||||
* 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
|
||||
@ -68,6 +69,7 @@ class BaseDetachedToolFactory;
|
||||
class TranslationsModel;
|
||||
class ITheme;
|
||||
class MCEditTool;
|
||||
class ThemeManager;
|
||||
|
||||
namespace Meta {
|
||||
class Index;
|
||||
@ -116,11 +118,9 @@ public:
|
||||
|
||||
QIcon getThemedIcon(const QString& name);
|
||||
|
||||
bool isFlatpak();
|
||||
|
||||
void setIconTheme(const QString& name);
|
||||
|
||||
std::vector<ITheme *> getValidApplicationThemes();
|
||||
QList<ITheme*> getValidApplicationThemes();
|
||||
|
||||
void setApplicationTheme(const QString& name, bool initial);
|
||||
|
||||
@ -200,6 +200,8 @@ public:
|
||||
|
||||
void ShowGlobalSettings(class QWidget * parent, QString open_page = QString());
|
||||
|
||||
int suitableMaxMem();
|
||||
|
||||
signals:
|
||||
void updateAllowedChanged(bool status);
|
||||
void globalSettingsAboutToOpen();
|
||||
@ -229,6 +231,7 @@ private slots:
|
||||
void setupWizardFinished(int status);
|
||||
|
||||
private:
|
||||
bool handleDataMigration(const QString & currentData, const QString & oldData, const QString & name, const QString & configFile) const;
|
||||
bool createSetupWizard();
|
||||
void performMainStartupAction();
|
||||
|
||||
@ -257,9 +260,9 @@ private:
|
||||
std::shared_ptr<JavaInstallList> m_javalist;
|
||||
std::shared_ptr<TranslationsModel> m_translations;
|
||||
std::shared_ptr<GenericPageProvider> m_globalSettingsProvider;
|
||||
std::map<QString, std::unique_ptr<ITheme>> m_themes;
|
||||
std::unique_ptr<MCEditTool> m_mcedit;
|
||||
QSet<QString> m_features;
|
||||
std::unique_ptr<ThemeManager> m_themeManager;
|
||||
|
||||
QMap<QString, std::shared_ptr<BaseProfilerFactory>> m_profilers;
|
||||
|
||||
|
@ -17,13 +17,14 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "BaseVersion.h"
|
||||
|
||||
class MinecraftInstance;
|
||||
class QDir;
|
||||
class QString;
|
||||
class QObject;
|
||||
class Task;
|
||||
class BaseVersion;
|
||||
typedef std::shared_ptr<BaseVersion> BaseVersionPtr;
|
||||
|
||||
class BaseInstaller
|
||||
{
|
||||
@ -35,7 +36,7 @@ public:
|
||||
virtual bool add(MinecraftInstance *to);
|
||||
virtual bool remove(MinecraftInstance *from);
|
||||
|
||||
virtual Task *createInstallTask(MinecraftInstance *instance, BaseVersionPtr version, QObject *parent) = 0;
|
||||
virtual Task *createInstallTask(MinecraftInstance *instance, BaseVersion::Ptr version, QObject *parent) = 0;
|
||||
|
||||
protected:
|
||||
virtual QString id() const = 0;
|
||||
|
@ -151,7 +151,7 @@ public:
|
||||
void copyManagedPack(BaseInstance& other);
|
||||
|
||||
/// guess log level from a line of game log
|
||||
virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level)
|
||||
virtual MessageLevel::Enum guessLevel([[maybe_unused]] const QString &line, MessageLevel::Enum level)
|
||||
{
|
||||
return level;
|
||||
};
|
||||
|
@ -25,6 +25,7 @@
|
||||
class BaseVersion
|
||||
{
|
||||
public:
|
||||
using Ptr = std::shared_ptr<BaseVersion>;
|
||||
virtual ~BaseVersion() {}
|
||||
/*!
|
||||
* A string used to identify this version in config files.
|
||||
@ -54,6 +55,4 @@ public:
|
||||
};
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<BaseVersion> BaseVersionPtr;
|
||||
|
||||
Q_DECLARE_METATYPE(BaseVersionPtr)
|
||||
Q_DECLARE_METATYPE(BaseVersion::Ptr)
|
||||
|
@ -40,20 +40,20 @@ BaseVersionList::BaseVersionList(QObject *parent) : QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
BaseVersionPtr BaseVersionList::findVersion(const QString &descriptor)
|
||||
BaseVersion::Ptr BaseVersionList::findVersion(const QString &descriptor)
|
||||
{
|
||||
for (int i = 0; i < count(); i++)
|
||||
{
|
||||
if (at(i)->descriptor() == descriptor)
|
||||
return at(i);
|
||||
}
|
||||
return BaseVersionPtr();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BaseVersionPtr BaseVersionList::getRecommended() const
|
||||
BaseVersion::Ptr BaseVersionList::getRecommended() const
|
||||
{
|
||||
if (count() <= 0)
|
||||
return BaseVersionPtr();
|
||||
return nullptr;
|
||||
else
|
||||
return at(0);
|
||||
}
|
||||
@ -66,7 +66,7 @@ QVariant BaseVersionList::data(const QModelIndex &index, int role) const
|
||||
if (index.row() > count())
|
||||
return QVariant();
|
||||
|
||||
BaseVersionPtr version = at(index.row());
|
||||
BaseVersion::Ptr version = at(index.row());
|
||||
|
||||
switch (role)
|
||||
{
|
||||
@ -95,12 +95,12 @@ BaseVersionList::RoleList BaseVersionList::providesRoles() const
|
||||
int BaseVersionList::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
// Return count
|
||||
return count();
|
||||
return parent.isValid() ? 0 : count();
|
||||
}
|
||||
|
||||
int BaseVersionList::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return 1;
|
||||
return parent.isValid() ? 0 : 1;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> BaseVersionList::roleNames() const
|
||||
|
@ -70,7 +70,7 @@ public:
|
||||
virtual bool isLoaded() = 0;
|
||||
|
||||
//! Gets the version at the given index.
|
||||
virtual const BaseVersionPtr at(int i) const = 0;
|
||||
virtual const BaseVersion::Ptr at(int i) const = 0;
|
||||
|
||||
//! Returns the number of versions in the list.
|
||||
virtual int count() const = 0;
|
||||
@ -90,13 +90,13 @@ public:
|
||||
* \return A const pointer to the version with the given descriptor. NULL if
|
||||
* one doesn't exist.
|
||||
*/
|
||||
virtual BaseVersionPtr findVersion(const QString &descriptor);
|
||||
virtual BaseVersion::Ptr findVersion(const QString &descriptor);
|
||||
|
||||
/*!
|
||||
* \brief Gets the recommended version from this list
|
||||
* If the list doesn't support recommended versions, this works exactly as getLatestStable
|
||||
*/
|
||||
virtual BaseVersionPtr getRecommended() const;
|
||||
virtual BaseVersion::Ptr getRecommended() const;
|
||||
|
||||
/*!
|
||||
* Sorts the version list.
|
||||
@ -117,5 +117,5 @@ slots:
|
||||
* then copies the versions and sets their parents correctly.
|
||||
* \param versions List of versions whose parents should be set.
|
||||
*/
|
||||
virtual void updateListData(QList<BaseVersionPtr> versions) = 0;
|
||||
virtual void updateListData(QList<BaseVersion::Ptr> versions) = 0;
|
||||
};
|
||||
|
@ -24,13 +24,15 @@ set(CORE_SOURCES
|
||||
NullInstance.h
|
||||
MMCZip.h
|
||||
MMCZip.cpp
|
||||
MMCStrings.h
|
||||
MMCStrings.cpp
|
||||
StringUtils.h
|
||||
StringUtils.cpp
|
||||
RuntimeContext.h
|
||||
|
||||
# Basic instance manipulation tasks (derived from InstanceTask)
|
||||
InstanceCreationTask.h
|
||||
InstanceCreationTask.cpp
|
||||
InstanceCopyPrefs.h
|
||||
InstanceCopyPrefs.cpp
|
||||
InstanceCopyTask.h
|
||||
InstanceCopyTask.cpp
|
||||
InstanceImportTask.h
|
||||
@ -95,6 +97,7 @@ set(PATHMATCHER_SOURCES
|
||||
pathmatcher/IPathMatcher.h
|
||||
pathmatcher/MultiMatcher.h
|
||||
pathmatcher/RegexpMatcher.h
|
||||
pathmatcher/SimplePrefixMatcher.h
|
||||
)
|
||||
|
||||
set(NET_SOURCES
|
||||
@ -539,9 +542,6 @@ set(ATLAUNCHER_SOURCES
|
||||
|
||||
################################ COMPILE ################################
|
||||
|
||||
# we need zlib
|
||||
find_package(ZLIB REQUIRED)
|
||||
|
||||
set(LOGIC_SOURCES
|
||||
${CORE_SOURCES}
|
||||
${PATHMATCHER_SOURCES}
|
||||
@ -576,6 +576,8 @@ SET(LAUNCHER_SOURCES
|
||||
# Application base
|
||||
Application.h
|
||||
Application.cpp
|
||||
DataMigrationTask.h
|
||||
DataMigrationTask.cpp
|
||||
UpdateController.cpp
|
||||
UpdateController.h
|
||||
ApplicationMessage.h
|
||||
@ -595,9 +597,12 @@ SET(LAUNCHER_SOURCES
|
||||
resources/pe_light/pe_light.qrc
|
||||
resources/pe_colored/pe_colored.qrc
|
||||
resources/pe_blue/pe_blue.qrc
|
||||
resources/breeze_dark/breeze_dark.qrc
|
||||
resources/breeze_light/breeze_light.qrc
|
||||
resources/OSX/OSX.qrc
|
||||
resources/iOS/iOS.qrc
|
||||
resources/flat/flat.qrc
|
||||
resources/flat_white/flat_white.qrc
|
||||
resources/documents/documents.qrc
|
||||
../${Launcher_Branding_LogoQRC}
|
||||
|
||||
@ -645,6 +650,8 @@ SET(LAUNCHER_SOURCES
|
||||
ui/themes/ITheme.h
|
||||
ui/themes/SystemTheme.cpp
|
||||
ui/themes/SystemTheme.h
|
||||
ui/themes/ThemeManager.cpp
|
||||
ui/themes/ThemeManager.h
|
||||
|
||||
# Processes
|
||||
LaunchController.h
|
||||
@ -785,6 +792,8 @@ SET(LAUNCHER_SOURCES
|
||||
ui/dialogs/ExportInstanceDialog.h
|
||||
ui/dialogs/IconPickerDialog.cpp
|
||||
ui/dialogs/IconPickerDialog.h
|
||||
ui/dialogs/ImportResourcePackDialog.cpp
|
||||
ui/dialogs/ImportResourcePackDialog.h
|
||||
ui/dialogs/LoginDialog.cpp
|
||||
ui/dialogs/LoginDialog.h
|
||||
ui/dialogs/MSALoginDialog.cpp
|
||||
@ -930,6 +939,7 @@ qt_wrap_ui(LAUNCHER_UI
|
||||
ui/dialogs/SkinUploadDialog.ui
|
||||
ui/dialogs/ExportInstanceDialog.ui
|
||||
ui/dialogs/IconPickerDialog.ui
|
||||
ui/dialogs/ImportResourcePackDialog.ui
|
||||
ui/dialogs/MSALoginDialog.ui
|
||||
ui/dialogs/OfflineLoginDialog.ui
|
||||
ui/dialogs/AboutDialog.ui
|
||||
@ -948,6 +958,8 @@ qt_add_resources(LAUNCHER_RESOURCES
|
||||
resources/pe_light/pe_light.qrc
|
||||
resources/pe_colored/pe_colored.qrc
|
||||
resources/pe_blue/pe_blue.qrc
|
||||
resources/breeze_dark/breeze_dark.qrc
|
||||
resources/breeze_light/breeze_light.qrc
|
||||
resources/OSX/OSX.qrc
|
||||
resources/iOS/iOS.qrc
|
||||
resources/flat/flat.qrc
|
||||
@ -1054,96 +1066,95 @@ if(INSTALL_BUNDLE STREQUAL "full")
|
||||
COMPONENT Runtime
|
||||
)
|
||||
# Bundle plugins
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
|
||||
# Image formats
|
||||
# Image formats
|
||||
install(
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/imageformats"
|
||||
CONFIGURATIONS Debug RelWithDebInfo ""
|
||||
DESTINATION ${PLUGIN_DEST_DIR}
|
||||
COMPONENT Runtime
|
||||
REGEX "tga|tiff|mng" EXCLUDE
|
||||
)
|
||||
install(
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/imageformats"
|
||||
CONFIGURATIONS Release MinSizeRel
|
||||
DESTINATION ${PLUGIN_DEST_DIR}
|
||||
COMPONENT Runtime
|
||||
REGEX "tga|tiff|mng" EXCLUDE
|
||||
REGEX "d\\." EXCLUDE
|
||||
REGEX "_debug\\." EXCLUDE
|
||||
REGEX "\\.dSYM" EXCLUDE
|
||||
)
|
||||
# Icon engines
|
||||
install(
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/iconengines"
|
||||
CONFIGURATIONS Debug RelWithDebInfo ""
|
||||
DESTINATION ${PLUGIN_DEST_DIR}
|
||||
COMPONENT Runtime
|
||||
REGEX "fontawesome" EXCLUDE
|
||||
)
|
||||
install(
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/iconengines"
|
||||
CONFIGURATIONS Release MinSizeRel
|
||||
DESTINATION ${PLUGIN_DEST_DIR}
|
||||
COMPONENT Runtime
|
||||
REGEX "fontawesome" EXCLUDE
|
||||
REGEX "d\\." EXCLUDE
|
||||
REGEX "_debug\\." EXCLUDE
|
||||
REGEX "\\.dSYM" EXCLUDE
|
||||
)
|
||||
# Platform plugins
|
||||
install(
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/platforms"
|
||||
CONFIGURATIONS Debug RelWithDebInfo ""
|
||||
DESTINATION ${PLUGIN_DEST_DIR}
|
||||
COMPONENT Runtime
|
||||
REGEX "minimal|linuxfb|offscreen" EXCLUDE
|
||||
)
|
||||
install(
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/platforms"
|
||||
CONFIGURATIONS Release MinSizeRel
|
||||
DESTINATION ${PLUGIN_DEST_DIR}
|
||||
COMPONENT Runtime
|
||||
REGEX "minimal|linuxfb|offscreen" EXCLUDE
|
||||
REGEX "[^2]d\\." EXCLUDE
|
||||
REGEX "_debug\\." EXCLUDE
|
||||
REGEX "\\.dSYM" EXCLUDE
|
||||
)
|
||||
# Style plugins
|
||||
if(EXISTS "${QT_PLUGINS_DIR}/styles")
|
||||
install(
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/imageformats"
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/styles"
|
||||
CONFIGURATIONS Debug RelWithDebInfo ""
|
||||
DESTINATION ${PLUGIN_DEST_DIR}
|
||||
COMPONENT Runtime
|
||||
REGEX "tga|tiff|mng" EXCLUDE
|
||||
)
|
||||
# Icon engines
|
||||
install(
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/iconengines"
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/styles"
|
||||
CONFIGURATIONS Release MinSizeRel
|
||||
DESTINATION ${PLUGIN_DEST_DIR}
|
||||
COMPONENT Runtime
|
||||
REGEX "fontawesome" EXCLUDE
|
||||
)
|
||||
# Platform plugins
|
||||
install(
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/platforms"
|
||||
DESTINATION ${PLUGIN_DEST_DIR}
|
||||
COMPONENT Runtime
|
||||
REGEX "minimal|linuxfb|offscreen" EXCLUDE
|
||||
)
|
||||
# Style plugins
|
||||
if(EXISTS "${QT_PLUGINS_DIR}/styles")
|
||||
install(
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/styles"
|
||||
DESTINATION ${PLUGIN_DEST_DIR}
|
||||
COMPONENT Runtime
|
||||
)
|
||||
endif()
|
||||
# TLS plugins (Qt 6 only)
|
||||
if(EXISTS "${QT_PLUGINS_DIR}/tls")
|
||||
install(
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/tls"
|
||||
DESTINATION ${PLUGIN_DEST_DIR}
|
||||
COMPONENT Runtime
|
||||
)
|
||||
endif()
|
||||
else()
|
||||
# Image formats
|
||||
install(
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/imageformats"
|
||||
DESTINATION ${PLUGIN_DEST_DIR}
|
||||
COMPONENT Runtime
|
||||
REGEX "tga|tiff|mng" EXCLUDE
|
||||
REGEX "d\\." EXCLUDE
|
||||
REGEX "_debug\\." EXCLUDE
|
||||
REGEX "\\.dSYM" EXCLUDE
|
||||
)
|
||||
# Icon engines
|
||||
endif()
|
||||
# TLS plugins (Qt 6 only)
|
||||
if(EXISTS "${QT_PLUGINS_DIR}/tls")
|
||||
install(
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/iconengines"
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/tls"
|
||||
CONFIGURATIONS Debug RelWithDebInfo ""
|
||||
DESTINATION ${PLUGIN_DEST_DIR}
|
||||
COMPONENT Runtime
|
||||
REGEX "fontawesome" EXCLUDE
|
||||
REGEX "d\\." EXCLUDE
|
||||
)
|
||||
install(
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/tls"
|
||||
CONFIGURATIONS Release MinSizeRel
|
||||
DESTINATION ${PLUGIN_DEST_DIR}
|
||||
COMPONENT Runtime
|
||||
REGEX "dd\\." EXCLUDE
|
||||
REGEX "_debug\\." EXCLUDE
|
||||
REGEX "\\.dSYM" EXCLUDE
|
||||
)
|
||||
# Platform plugins
|
||||
install(
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/platforms"
|
||||
DESTINATION ${PLUGIN_DEST_DIR}
|
||||
COMPONENT Runtime
|
||||
REGEX "minimal|linuxfb|offscreen" EXCLUDE
|
||||
REGEX "d\\." EXCLUDE
|
||||
REGEX "_debug\\." EXCLUDE
|
||||
REGEX "\\.dSYM" EXCLUDE
|
||||
)
|
||||
# Style plugins
|
||||
if(EXISTS "${QT_PLUGINS_DIR}/styles")
|
||||
install(
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/styles"
|
||||
DESTINATION ${PLUGIN_DEST_DIR}
|
||||
COMPONENT Runtime
|
||||
REGEX "d\\." EXCLUDE
|
||||
REGEX "_debug\\." EXCLUDE
|
||||
REGEX "\\.dSYM" EXCLUDE
|
||||
)
|
||||
endif()
|
||||
# TLS plugins (Qt 6 only)
|
||||
if(EXISTS "${QT_PLUGINS_DIR}/tls")
|
||||
install(
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/tls"
|
||||
DESTINATION ${PLUGIN_DEST_DIR}
|
||||
COMPONENT Runtime
|
||||
REGEX "_debug\\." EXCLUDE
|
||||
REGEX "\\.dSYM" EXCLUDE
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
configure_file(
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/install_prereqs.cmake.in"
|
||||
|
96
launcher/DataMigrationTask.cpp
Normal file
96
launcher/DataMigrationTask.cpp
Normal file
@ -0,0 +1,96 @@
|
||||
// SPDX-FileCopyrightText: 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#include "DataMigrationTask.h"
|
||||
|
||||
#include "FileSystem.h"
|
||||
|
||||
#include <QDirIterator>
|
||||
#include <QFileInfo>
|
||||
#include <QMap>
|
||||
|
||||
#include <QtConcurrent>
|
||||
|
||||
DataMigrationTask::DataMigrationTask(QObject* parent,
|
||||
const QString& sourcePath,
|
||||
const QString& targetPath,
|
||||
const IPathMatcher::Ptr pathMatcher)
|
||||
: Task(parent), m_sourcePath(sourcePath), m_targetPath(targetPath), m_pathMatcher(pathMatcher), m_copy(sourcePath, targetPath)
|
||||
{
|
||||
m_copy.matcher(m_pathMatcher.get()).whitelist(true);
|
||||
}
|
||||
|
||||
void DataMigrationTask::executeTask()
|
||||
{
|
||||
setStatus(tr("Scanning files..."));
|
||||
|
||||
// 1. Scan
|
||||
// Check how many files we gotta copy
|
||||
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&] {
|
||||
return m_copy(true); // dry run to collect amount of files
|
||||
});
|
||||
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::dryRunFinished);
|
||||
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &DataMigrationTask::dryRunAborted);
|
||||
m_copyFutureWatcher.setFuture(m_copyFuture);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 2. Copy
|
||||
// Actually copy all files now.
|
||||
m_toCopy = m_copy.totalCopied();
|
||||
connect(&m_copy, &FS::copy::fileCopied, [&, this](const QString& relativeName) {
|
||||
QString shortenedName = relativeName;
|
||||
// shorten the filename to hopefully fit into one line
|
||||
if (shortenedName.length() > 50)
|
||||
shortenedName = relativeName.left(20) + "…" + relativeName.right(29);
|
||||
setProgress(m_copy.totalCopied(), m_toCopy);
|
||||
setStatus(tr("Copying %1…").arg(shortenedName));
|
||||
});
|
||||
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&] {
|
||||
return m_copy(false); // actually copy now
|
||||
});
|
||||
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::copyFinished);
|
||||
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &DataMigrationTask::copyAborted);
|
||||
m_copyFutureWatcher.setFuture(m_copyFuture);
|
||||
}
|
||||
|
||||
void DataMigrationTask::dryRunAborted()
|
||||
{
|
||||
emitFailed(tr("Aborted"));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
emitSucceeded();
|
||||
}
|
||||
|
||||
void DataMigrationTask::copyAborted()
|
||||
{
|
||||
emitFailed(tr("Aborted"));
|
||||
}
|
42
launcher/DataMigrationTask.h
Normal file
42
launcher/DataMigrationTask.h
Normal file
@ -0,0 +1,42 @@
|
||||
// SPDX-FileCopyrightText: 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include "pathmatcher/IPathMatcher.h"
|
||||
#include "tasks/Task.h"
|
||||
|
||||
#include <QFuture>
|
||||
#include <QFutureWatcher>
|
||||
|
||||
/*
|
||||
* Migrate existing data from other MMC-like launchers.
|
||||
*/
|
||||
|
||||
class DataMigrationTask : public Task {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DataMigrationTask(QObject* parent, const QString& sourcePath, const QString& targetPath, const IPathMatcher::Ptr pathmatcher);
|
||||
~DataMigrationTask() override = default;
|
||||
|
||||
protected:
|
||||
virtual void executeTask() override;
|
||||
|
||||
protected slots:
|
||||
void dryRunFinished();
|
||||
void dryRunAborted();
|
||||
void copyFinished();
|
||||
void copyAborted();
|
||||
|
||||
private:
|
||||
const QString& m_sourcePath;
|
||||
const QString& m_targetPath;
|
||||
const IPathMatcher::Ptr m_pathMatcher;
|
||||
|
||||
FS::copy m_copy;
|
||||
int m_toCopy = 0;
|
||||
QFuture<bool> m_copyFuture;
|
||||
QFutureWatcher<bool> m_copyFutureWatcher;
|
||||
};
|
@ -119,7 +119,7 @@ bool openDirectory(const QString &path, bool ensureExists)
|
||||
return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath()));
|
||||
};
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||
if(!APPLICATION->isFlatpak())
|
||||
if(!isFlatpak())
|
||||
{
|
||||
return IndirectOpen(f);
|
||||
}
|
||||
@ -140,7 +140,7 @@ bool openFile(const QString &path)
|
||||
return QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
||||
};
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||
if(!APPLICATION->isFlatpak())
|
||||
if(!isFlatpak())
|
||||
{
|
||||
return IndirectOpen(f);
|
||||
}
|
||||
@ -158,7 +158,7 @@ bool openFile(const QString &application, const QString &path, const QString &wo
|
||||
qDebug() << "Opening file" << path << "using" << application;
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
|
||||
if(!APPLICATION->isFlatpak())
|
||||
if(!isFlatpak())
|
||||
{
|
||||
return IndirectOpen([&]()
|
||||
{
|
||||
@ -178,7 +178,7 @@ bool run(const QString &application, const QStringList &args, const QString &wor
|
||||
{
|
||||
qDebug() << "Running" << application << "with args" << args.join(' ');
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||
if(!APPLICATION->isFlatpak())
|
||||
if(!isFlatpak())
|
||||
{
|
||||
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
|
||||
return IndirectOpen([&]()
|
||||
@ -203,7 +203,7 @@ bool openUrl(const QUrl &url)
|
||||
return QDesktopServices::openUrl(url);
|
||||
};
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||
if(!APPLICATION->isFlatpak())
|
||||
if(!isFlatpak())
|
||||
{
|
||||
return IndirectOpen(f);
|
||||
}
|
||||
@ -216,4 +216,13 @@ bool openUrl(const QUrl &url)
|
||||
#endif
|
||||
}
|
||||
|
||||
bool isFlatpak()
|
||||
{
|
||||
#ifdef Q_OS_LINUX
|
||||
return QFile::exists("/.flatpak-info");
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -33,4 +33,6 @@ namespace DesktopServices
|
||||
* Open the URL, most likely in a browser. Maybe.
|
||||
*/
|
||||
bool openUrl(const QUrl &url);
|
||||
|
||||
bool isFlatpak();
|
||||
}
|
||||
|
@ -45,7 +45,11 @@
|
||||
#include <QTextStream>
|
||||
#include <QUrl>
|
||||
|
||||
#include "DesktopServices.h"
|
||||
#include "StringUtils.h"
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <objbase.h>
|
||||
#include <objidl.h>
|
||||
#include <shlguid.h>
|
||||
@ -78,22 +82,6 @@ namespace fs = std::filesystem;
|
||||
namespace fs = ghc::filesystem;
|
||||
#endif
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
|
||||
std::wstring toStdString(QString s)
|
||||
{
|
||||
return s.toStdWString();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
std::string toStdString(QString s)
|
||||
{
|
||||
return s.toStdString();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
namespace FS {
|
||||
|
||||
void ensureExists(const QDir& dir)
|
||||
@ -162,9 +150,13 @@ bool ensureFolderPathExists(QString foldernamepath)
|
||||
return success;
|
||||
}
|
||||
|
||||
bool copy::operator()(const QString& offset)
|
||||
/// @brief Copies a directory and it's contents from src to dest
|
||||
/// @param offset subdirectory form src to copy to dest
|
||||
/// @return if there was an error during the filecopy
|
||||
bool copy::operator()(const QString& offset, bool dryRun)
|
||||
{
|
||||
using copy_opts = fs::copy_options;
|
||||
m_copied = 0; // reset counter
|
||||
|
||||
// NOTE always deep copy on windows. the alternatives are too messy.
|
||||
#if defined Q_OS_WIN32
|
||||
@ -182,6 +174,24 @@ bool copy::operator()(const QString& offset)
|
||||
if (!m_followSymlinks)
|
||||
opt |= copy_opts::copy_symlinks;
|
||||
|
||||
// Function that'll do the actual copying
|
||||
auto copy_file = [&](QString src_path, QString relative_dst_path) {
|
||||
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
|
||||
return;
|
||||
|
||||
auto dst_path = PathCombine(dst, relative_dst_path);
|
||||
if (!dryRun) {
|
||||
ensureFilePathExists(dst_path);
|
||||
fs::copy(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), opt, err);
|
||||
}
|
||||
if (err) {
|
||||
qWarning() << "Failed to copy files:" << QString::fromStdString(err.message());
|
||||
qDebug() << "Source file:" << src_path;
|
||||
qDebug() << "Destination file:" << dst_path;
|
||||
}
|
||||
m_copied++;
|
||||
emit fileCopied(relative_dst_path);
|
||||
};
|
||||
|
||||
// We can't use copy_opts::recursive because we need to take into account the
|
||||
// blacklisted paths, so we iterate over the source directory, and if there's no blacklist
|
||||
@ -193,20 +203,13 @@ bool copy::operator()(const QString& offset)
|
||||
auto src_path = source_it.next();
|
||||
auto relative_path = src_dir.relativeFilePath(src_path);
|
||||
|
||||
if (m_blacklist && m_blacklist->matches(relative_path))
|
||||
continue;
|
||||
|
||||
auto dst_path = PathCombine(dst, relative_path);
|
||||
ensureFilePathExists(dst_path);
|
||||
|
||||
fs::copy(toStdString(src_path), toStdString(dst_path), opt, err);
|
||||
if (err) {
|
||||
qWarning() << "Failed to copy files:" << QString::fromStdString(err.message());
|
||||
qDebug() << "Source file:" << src_path;
|
||||
qDebug() << "Destination file:" << dst_path;
|
||||
}
|
||||
copy_file(src_path, relative_path);
|
||||
}
|
||||
|
||||
// If the root src is not a directory, the previous iterator won't run.
|
||||
if (!fs::is_directory(StringUtils::toStdString(src)))
|
||||
copy_file(src, "");
|
||||
|
||||
return err.value() == 0;
|
||||
}
|
||||
|
||||
@ -214,7 +217,7 @@ bool deletePath(QString path)
|
||||
{
|
||||
std::error_code err;
|
||||
|
||||
fs::remove_all(toStdString(path), err);
|
||||
fs::remove_all(StringUtils::toStdString(path), err);
|
||||
|
||||
if (err) {
|
||||
qWarning() << "Failed to remove files:" << QString::fromStdString(err.message());
|
||||
@ -228,6 +231,9 @@ bool trash(QString path, QString *pathInTrash = nullptr)
|
||||
#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;
|
||||
return QFile::moveToTrash(path, pathInTrash);
|
||||
#endif
|
||||
}
|
||||
@ -338,12 +344,37 @@ QString getDesktopDir()
|
||||
}
|
||||
|
||||
// Cross-platform Shortcut creation
|
||||
bool createShortCut(QString location, QString dest, QStringList args, QString name, QString icon)
|
||||
bool createShortcut(QString destination, QString target, QStringList args, QString name, QString icon)
|
||||
{
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||
location = PathCombine(location, name + ".desktop");
|
||||
#if defined(Q_OS_MACOS)
|
||||
destination += ".command";
|
||||
|
||||
QFile f(location);
|
||||
QFile f(destination);
|
||||
f.open(QIODevice::WriteOnly | QIODevice::Text);
|
||||
QTextStream stream(&f);
|
||||
|
||||
QString argstring;
|
||||
if (!args.empty())
|
||||
argstring = " \"" + args.join("\" \"") + "\"";
|
||||
|
||||
stream << "#!/bin/bash"
|
||||
<< "\n";
|
||||
stream << "\""
|
||||
<< target
|
||||
<< "\" "
|
||||
<< argstring
|
||||
<< "\n";
|
||||
|
||||
stream.flush();
|
||||
f.close();
|
||||
|
||||
f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | QFileDevice::ExeOther);
|
||||
|
||||
return true;
|
||||
#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||
destination += ".desktop";
|
||||
|
||||
QFile f(destination);
|
||||
f.open(QIODevice::WriteOnly | QIODevice::Text);
|
||||
QTextStream stream(&f);
|
||||
|
||||
@ -355,10 +386,12 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na
|
||||
<< "\n";
|
||||
stream << "Type=Application"
|
||||
<< "\n";
|
||||
stream << "TryExec=" << dest.toLocal8Bit() << "\n";
|
||||
stream << "Exec=" << dest.toLocal8Bit() << argstring.toLocal8Bit() << "\n";
|
||||
stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n";
|
||||
stream << "Name=" << name.toLocal8Bit() << "\n";
|
||||
stream << "Icon=" << icon.toLocal8Bit() << "\n";
|
||||
if (!icon.isEmpty())
|
||||
{
|
||||
stream << "Icon=" << icon.toLocal8Bit() << "\n";
|
||||
}
|
||||
|
||||
stream.flush();
|
||||
f.close();
|
||||
@ -366,25 +399,132 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na
|
||||
f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | QFileDevice::ExeOther);
|
||||
|
||||
return true;
|
||||
#elif defined Q_OS_WIN
|
||||
// TODO: Fix
|
||||
// QFile file(PathCombine(location, name + ".lnk"));
|
||||
// WCHAR *file_w;
|
||||
// WCHAR *dest_w;
|
||||
// WCHAR *args_w;
|
||||
// file.fileName().toWCharArray(file_w);
|
||||
// dest.toWCharArray(dest_w);
|
||||
#elif defined(Q_OS_WIN)
|
||||
QFileInfo targetInfo(target);
|
||||
|
||||
// QString argStr;
|
||||
// for (int i = 0; i < args.count(); i++)
|
||||
// {
|
||||
// argStr.append(args[i]);
|
||||
// argStr.append(" ");
|
||||
// }
|
||||
// argStr.toWCharArray(args_w);
|
||||
if (!targetInfo.exists())
|
||||
{
|
||||
qWarning() << "Target file does not exist!";
|
||||
return false;
|
||||
}
|
||||
|
||||
// return SUCCEEDED(CreateLink(file_w, dest_w, args_w));
|
||||
return false;
|
||||
target = targetInfo.absoluteFilePath();
|
||||
|
||||
if (target.length() >= MAX_PATH)
|
||||
{
|
||||
qWarning() << "Target file path is too long!";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!icon.isEmpty() && icon.length() >= MAX_PATH)
|
||||
{
|
||||
qWarning() << "Icon path is too long!";
|
||||
return false;
|
||||
}
|
||||
|
||||
destination += ".lnk";
|
||||
|
||||
if (destination.length() >= MAX_PATH)
|
||||
{
|
||||
qWarning() << "Destination path is too long!";
|
||||
return false;
|
||||
}
|
||||
|
||||
QString argStr;
|
||||
int argCount = args.count();
|
||||
for (int i = 0; i < argCount; i++)
|
||||
{
|
||||
if (args[i].contains(' '))
|
||||
{
|
||||
argStr.append('"').append(args[i]).append('"');
|
||||
}
|
||||
else
|
||||
{
|
||||
argStr.append(args[i]);
|
||||
}
|
||||
|
||||
if (i < argCount - 1)
|
||||
{
|
||||
argStr.append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
if (argStr.length() >= MAX_PATH)
|
||||
{
|
||||
qWarning() << "Arguments string is too long!";
|
||||
return false;
|
||||
}
|
||||
|
||||
HRESULT hres;
|
||||
|
||||
// ...yes, you need to initialize the entire COM stack just to make a shortcut
|
||||
hres = CoInitialize(nullptr);
|
||||
if (FAILED(hres))
|
||||
{
|
||||
qWarning() << "Failed to initialize COM!";
|
||||
return false;
|
||||
}
|
||||
|
||||
WCHAR wsz[MAX_PATH];
|
||||
|
||||
IShellLink* psl;
|
||||
|
||||
// create an IShellLink instance - this stores the shortcut's attributes
|
||||
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
|
||||
if (SUCCEEDED(hres))
|
||||
{
|
||||
wmemset(wsz, 0, MAX_PATH);
|
||||
target.toWCharArray(wsz);
|
||||
psl->SetPath(wsz);
|
||||
|
||||
wmemset(wsz, 0, MAX_PATH);
|
||||
argStr.toWCharArray(wsz);
|
||||
psl->SetArguments(wsz);
|
||||
|
||||
wmemset(wsz, 0, MAX_PATH);
|
||||
targetInfo.absolutePath().toWCharArray(wsz);
|
||||
psl->SetWorkingDirectory(wsz); // "Starts in" attribute
|
||||
|
||||
if (!icon.isEmpty())
|
||||
{
|
||||
wmemset(wsz, 0, MAX_PATH);
|
||||
icon.toWCharArray(wsz);
|
||||
psl->SetIconLocation(wsz, 0);
|
||||
}
|
||||
|
||||
// query an IPersistFile interface from our IShellLink instance
|
||||
// this is the interface that will actually let us save the shortcut to disk!
|
||||
IPersistFile* ppf;
|
||||
hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf);
|
||||
if (SUCCEEDED(hres))
|
||||
{
|
||||
wmemset(wsz, 0, MAX_PATH);
|
||||
destination.toWCharArray(wsz);
|
||||
hres = ppf->Save(wsz, TRUE);
|
||||
if (FAILED(hres))
|
||||
{
|
||||
qWarning() << "IPresistFile->Save() failed";
|
||||
qWarning() << "hres = " << hres;
|
||||
}
|
||||
ppf->Release();
|
||||
}
|
||||
else
|
||||
{
|
||||
qWarning() << "Failed to query IPersistFile interface from IShellLink instance";
|
||||
qWarning() << "hres = " << hres;
|
||||
}
|
||||
psl->Release();
|
||||
}
|
||||
else
|
||||
{
|
||||
qWarning() << "Failed to create IShellLink instance";
|
||||
qWarning() << "hres = " << hres;
|
||||
}
|
||||
|
||||
// go away COM, nobody likes you
|
||||
CoUninitialize();
|
||||
|
||||
return SUCCEEDED(hres);
|
||||
#else
|
||||
qWarning("Desktop Shortcuts not supported on your platform!");
|
||||
return false;
|
||||
@ -401,7 +541,8 @@ bool overrideFolder(QString overwritten_path, QString override_path)
|
||||
std::error_code err;
|
||||
fs::copy_options opt = copy_opts::recursive | copy_opts::overwrite_existing;
|
||||
|
||||
fs::copy(toStdString(override_path), toStdString(overwritten_path), opt, err);
|
||||
// FIXME: hello traveller! Apparently std::copy does NOT overwrite existing files on GNU libstdc++ on Windows?
|
||||
fs::copy(StringUtils::toStdString(override_path), StringUtils::toStdString(overwritten_path), opt, err);
|
||||
|
||||
if (err) {
|
||||
qCritical() << QString("Failed to apply override from %1 to %2").arg(override_path, overwritten_path);
|
||||
|
@ -40,6 +40,7 @@
|
||||
|
||||
#include <QDir>
|
||||
#include <QFlags>
|
||||
#include <QObject>
|
||||
|
||||
namespace FS {
|
||||
|
||||
@ -75,9 +76,11 @@ bool ensureFilePathExists(QString filenamepath);
|
||||
*/
|
||||
bool ensureFolderPathExists(QString filenamepath);
|
||||
|
||||
class copy {
|
||||
/// @brief Copies a directory and it's contents from src to dest
|
||||
class copy : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
copy(const QString& src, const QString& dst)
|
||||
copy(const QString& src, const QString& dst, QObject* parent = nullptr) : QObject(parent)
|
||||
{
|
||||
m_src.setPath(src);
|
||||
m_dst.setPath(dst);
|
||||
@ -87,21 +90,35 @@ class copy {
|
||||
m_followSymlinks = follow;
|
||||
return *this;
|
||||
}
|
||||
copy& blacklist(const IPathMatcher* filter)
|
||||
copy& matcher(const IPathMatcher* filter)
|
||||
{
|
||||
m_blacklist = filter;
|
||||
m_matcher = filter;
|
||||
return *this;
|
||||
}
|
||||
bool operator()() { return operator()(QString()); }
|
||||
copy& whitelist(bool whitelist)
|
||||
{
|
||||
m_whitelist = whitelist;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
|
||||
|
||||
int totalCopied() { return m_copied; }
|
||||
|
||||
signals:
|
||||
void fileCopied(const QString& relativeName);
|
||||
// TODO: maybe add a "shouldCopy" signal in the future?
|
||||
|
||||
private:
|
||||
bool operator()(const QString& offset);
|
||||
bool operator()(const QString& offset, bool dryRun = false);
|
||||
|
||||
private:
|
||||
bool m_followSymlinks = true;
|
||||
const IPathMatcher* m_blacklist = nullptr;
|
||||
const IPathMatcher* m_matcher = nullptr;
|
||||
bool m_whitelist = false;
|
||||
QDir m_src;
|
||||
QDir m_dst;
|
||||
int m_copied;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -155,4 +172,9 @@ QString getDesktopDir();
|
||||
// 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);
|
||||
|
||||
/**
|
||||
* Creates a shortcut to the specified target file at the specified destination path.
|
||||
*/
|
||||
bool createShortcut(QString destination, QString target, QStringList args, QString name, QString icon);
|
||||
}
|
||||
|
135
launcher/InstanceCopyPrefs.cpp
Normal file
135
launcher/InstanceCopyPrefs.cpp
Normal file
@ -0,0 +1,135 @@
|
||||
//
|
||||
// Created by marcelohdez on 10/22/22.
|
||||
//
|
||||
|
||||
#include "InstanceCopyPrefs.h"
|
||||
|
||||
bool InstanceCopyPrefs::allTrue() const
|
||||
{
|
||||
return copySaves &&
|
||||
keepPlaytime &&
|
||||
copyGameOptions &&
|
||||
copyResourcePacks &&
|
||||
copyShaderPacks &&
|
||||
copyServers &&
|
||||
copyMods &&
|
||||
copyScreenshots;
|
||||
}
|
||||
|
||||
// Returns a single RegEx string of the selected folders/files to filter out (ex: ".minecraft/saves|.minecraft/server.dat")
|
||||
QString InstanceCopyPrefs::getSelectedFiltersAsRegex() const
|
||||
{
|
||||
QStringList filters;
|
||||
|
||||
if(!copySaves)
|
||||
filters << "saves";
|
||||
|
||||
if(!copyGameOptions)
|
||||
filters << "options.txt";
|
||||
|
||||
if(!copyResourcePacks)
|
||||
filters << "resourcepacks" << "texturepacks";
|
||||
|
||||
if(!copyShaderPacks)
|
||||
filters << "shaderpacks";
|
||||
|
||||
if(!copyServers)
|
||||
filters << "servers.dat" << "servers.dat_old" << "server-resource-packs";
|
||||
|
||||
if(!copyMods)
|
||||
filters << "coremods" << "mods" << "config";
|
||||
|
||||
if(!copyScreenshots)
|
||||
filters << "screenshots";
|
||||
|
||||
// If we have any filters to add, join them as a single regex string to return:
|
||||
if (!filters.isEmpty()) {
|
||||
const QString MC_ROOT = "[.]?minecraft/";
|
||||
// Ensure first filter starts with root, then join other filters with OR regex before root (ex: ".minecraft/saves|.minecraft/mods"):
|
||||
return MC_ROOT + filters.join("|" + MC_ROOT);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// ======= Getters =======
|
||||
bool InstanceCopyPrefs::isCopySavesEnabled() const
|
||||
{
|
||||
return copySaves;
|
||||
}
|
||||
|
||||
bool InstanceCopyPrefs::isKeepPlaytimeEnabled() const
|
||||
{
|
||||
return keepPlaytime;
|
||||
}
|
||||
|
||||
bool InstanceCopyPrefs::isCopyGameOptionsEnabled() const
|
||||
{
|
||||
return copyGameOptions;
|
||||
}
|
||||
|
||||
bool InstanceCopyPrefs::isCopyResourcePacksEnabled() const
|
||||
{
|
||||
return copyResourcePacks;
|
||||
}
|
||||
|
||||
bool InstanceCopyPrefs::isCopyShaderPacksEnabled() const
|
||||
{
|
||||
return copyShaderPacks;
|
||||
}
|
||||
|
||||
bool InstanceCopyPrefs::isCopyServersEnabled() const
|
||||
{
|
||||
return copyServers;
|
||||
}
|
||||
|
||||
bool InstanceCopyPrefs::isCopyModsEnabled() const
|
||||
{
|
||||
return copyMods;
|
||||
}
|
||||
|
||||
bool InstanceCopyPrefs::isCopyScreenshotsEnabled() const
|
||||
{
|
||||
return copyScreenshots;
|
||||
}
|
||||
|
||||
// ======= Setters =======
|
||||
void InstanceCopyPrefs::enableCopySaves(bool b)
|
||||
{
|
||||
copySaves = b;
|
||||
}
|
||||
|
||||
void InstanceCopyPrefs::enableKeepPlaytime(bool b)
|
||||
{
|
||||
keepPlaytime = b;
|
||||
}
|
||||
|
||||
void InstanceCopyPrefs::enableCopyGameOptions(bool b)
|
||||
{
|
||||
copyGameOptions = b;
|
||||
}
|
||||
|
||||
void InstanceCopyPrefs::enableCopyResourcePacks(bool b)
|
||||
{
|
||||
copyResourcePacks = b;
|
||||
}
|
||||
|
||||
void InstanceCopyPrefs::enableCopyShaderPacks(bool b)
|
||||
{
|
||||
copyShaderPacks = b;
|
||||
}
|
||||
|
||||
void InstanceCopyPrefs::enableCopyServers(bool b)
|
||||
{
|
||||
copyServers = b;
|
||||
}
|
||||
|
||||
void InstanceCopyPrefs::enableCopyMods(bool b)
|
||||
{
|
||||
copyMods = b;
|
||||
}
|
||||
|
||||
void InstanceCopyPrefs::enableCopyScreenshots(bool b)
|
||||
{
|
||||
copyScreenshots = b;
|
||||
}
|
41
launcher/InstanceCopyPrefs.h
Normal file
41
launcher/InstanceCopyPrefs.h
Normal file
@ -0,0 +1,41 @@
|
||||
//
|
||||
// Created by marcelohdez on 10/22/22.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
struct InstanceCopyPrefs {
|
||||
public:
|
||||
[[nodiscard]] bool allTrue() const;
|
||||
[[nodiscard]] QString getSelectedFiltersAsRegex() const;
|
||||
// Getters
|
||||
[[nodiscard]] bool isCopySavesEnabled() const;
|
||||
[[nodiscard]] bool isKeepPlaytimeEnabled() const;
|
||||
[[nodiscard]] bool isCopyGameOptionsEnabled() const;
|
||||
[[nodiscard]] bool isCopyResourcePacksEnabled() const;
|
||||
[[nodiscard]] bool isCopyShaderPacksEnabled() const;
|
||||
[[nodiscard]] bool isCopyServersEnabled() const;
|
||||
[[nodiscard]] bool isCopyModsEnabled() const;
|
||||
[[nodiscard]] bool isCopyScreenshotsEnabled() const;
|
||||
// Setters
|
||||
void enableCopySaves(bool b);
|
||||
void enableKeepPlaytime(bool b);
|
||||
void enableCopyGameOptions(bool b);
|
||||
void enableCopyResourcePacks(bool b);
|
||||
void enableCopyShaderPacks(bool b);
|
||||
void enableCopyServers(bool b);
|
||||
void enableCopyMods(bool b);
|
||||
void enableCopyScreenshots(bool b);
|
||||
|
||||
protected: // data
|
||||
bool copySaves = true;
|
||||
bool keepPlaytime = true;
|
||||
bool copyGameOptions = true;
|
||||
bool copyResourcePacks = true;
|
||||
bool copyShaderPacks = true;
|
||||
bool copyServers = true;
|
||||
bool copyMods = true;
|
||||
bool copyScreenshots = true;
|
||||
};
|
@ -5,15 +5,17 @@
|
||||
#include "pathmatcher/RegexpMatcher.h"
|
||||
#include <QtConcurrentRun>
|
||||
|
||||
InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, bool copySaves, bool keepPlaytime)
|
||||
InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs)
|
||||
{
|
||||
m_origInstance = origInstance;
|
||||
m_keepPlaytime = keepPlaytime;
|
||||
m_keepPlaytime = prefs.isKeepPlaytimeEnabled();
|
||||
|
||||
if(!copySaves)
|
||||
QString filters = prefs.getSelectedFiltersAsRegex();
|
||||
if (!filters.isEmpty())
|
||||
{
|
||||
// Set regex filter:
|
||||
// FIXME: get this from the original instance type...
|
||||
auto matcherReal = new RegexpMatcher("[.]?minecraft/saves");
|
||||
auto matcherReal = new RegexpMatcher(filters);
|
||||
matcherReal->caseSensitive(false);
|
||||
m_matcher.reset(matcherReal);
|
||||
}
|
||||
@ -23,10 +25,12 @@ void InstanceCopyTask::executeTask()
|
||||
{
|
||||
setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
|
||||
|
||||
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
|
||||
folderCopy.followSymlinks(false).blacklist(m_matcher.get());
|
||||
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]{
|
||||
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
|
||||
folderCopy.followSymlinks(false).matcher(m_matcher.get());
|
||||
|
||||
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), folderCopy);
|
||||
return folderCopy();
|
||||
});
|
||||
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &InstanceCopyTask::copyFinished);
|
||||
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &InstanceCopyTask::copyAborted);
|
||||
m_copyFutureWatcher.setFuture(m_copyFuture);
|
||||
|
@ -1,20 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "tasks/Task.h"
|
||||
#include "net/NetJob.h"
|
||||
#include <QUrl>
|
||||
#include <QFuture>
|
||||
#include <QFutureWatcher>
|
||||
#include "settings/SettingsObject.h"
|
||||
#include "BaseVersion.h"
|
||||
#include <QUrl>
|
||||
#include "BaseInstance.h"
|
||||
#include "BaseVersion.h"
|
||||
#include "InstanceCopyPrefs.h"
|
||||
#include "InstanceTask.h"
|
||||
#include "net/NetJob.h"
|
||||
#include "settings/SettingsObject.h"
|
||||
#include "tasks/Task.h"
|
||||
|
||||
class InstanceCopyTask : public InstanceTask
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit InstanceCopyTask(InstancePtr origInstance, bool copySaves, bool keepPlaytime);
|
||||
explicit InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs);
|
||||
|
||||
protected:
|
||||
//! Entry point for tasks.
|
||||
@ -22,7 +23,8 @@ protected:
|
||||
void copyFinished();
|
||||
void copyAborted();
|
||||
|
||||
private: /* data */
|
||||
private:
|
||||
/* data */
|
||||
InstancePtr m_origInstance;
|
||||
QFuture<bool> m_copyFuture;
|
||||
QFutureWatcher<bool> m_copyFutureWatcher;
|
||||
|
@ -25,9 +25,13 @@ void InstanceCreationTask::executeTask()
|
||||
return;
|
||||
|
||||
qWarning() << "Instance creation failed!";
|
||||
if (!m_error_message.isEmpty())
|
||||
if (!m_error_message.isEmpty()) {
|
||||
qWarning() << "Reason: " << m_error_message;
|
||||
emitFailed(tr("Error while creating new instance."));
|
||||
emitFailed(tr("Error while creating new instance:\n%1").arg(m_error_message));
|
||||
} else {
|
||||
emitFailed(tr("Error while creating new instance."));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -164,18 +164,14 @@ void InstanceImportTask::processZipPack()
|
||||
}
|
||||
else
|
||||
{
|
||||
QString mmcRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg");
|
||||
QString flameRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json");
|
||||
QStringList paths_to_ignore { "overrides/" };
|
||||
|
||||
if (!mmcRoot.isNull())
|
||||
{
|
||||
if (QString mmcRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg", paths_to_ignore); !mmcRoot.isNull()) {
|
||||
// process as MultiMC instance/pack
|
||||
qDebug() << "MultiMC:" << mmcRoot;
|
||||
root = mmcRoot;
|
||||
m_modpackType = ModpackType::MultiMC;
|
||||
}
|
||||
else if(!flameRoot.isNull())
|
||||
{
|
||||
} else if (QString flameRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json", paths_to_ignore); !flameRoot.isNull()) {
|
||||
// process as Flame pack
|
||||
qDebug() << "Flame:" << flameRoot;
|
||||
root = flameRoot;
|
||||
|
@ -36,7 +36,7 @@
|
||||
#include "JavaCommon.h"
|
||||
#include "java/JavaUtils.h"
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include <MMCStrings.h>
|
||||
|
||||
#include <QRegularExpression>
|
||||
|
||||
bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget *parent)
|
||||
|
@ -1,8 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace Strings
|
||||
{
|
||||
int naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs);
|
||||
}
|
@ -28,11 +28,11 @@ QString Time::prettifyDuration(int64_t duration) {
|
||||
int days = (int) (duration / 24);
|
||||
if((hours == 0)&&(days == 0))
|
||||
{
|
||||
return QObject::tr("%1m %2s").arg(minutes).arg(seconds);
|
||||
return QObject::tr("%1min %2s").arg(minutes).arg(seconds);
|
||||
}
|
||||
if (days == 0)
|
||||
{
|
||||
return QObject::tr("%1h %2m").arg(hours).arg(minutes);
|
||||
return QObject::tr("%1h %2min").arg(hours).arg(minutes);
|
||||
}
|
||||
return QObject::tr("%1d %2h %3m").arg(days).arg(hours).arg(minutes);
|
||||
return QObject::tr("%1d %2h %3min").arg(days).arg(hours).arg(minutes);
|
||||
}
|
||||
|
@ -39,6 +39,7 @@
|
||||
#include "MMCZip.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
|
||||
// ours
|
||||
@ -228,23 +229,27 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
|
||||
}
|
||||
|
||||
// ours
|
||||
QString MMCZip::findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root)
|
||||
QString MMCZip::findFolderOfFileInZip(QuaZip* zip, const QString& what, const QStringList& ignore_paths, const QString& root)
|
||||
{
|
||||
QuaZipDir rootDir(zip, root);
|
||||
for(auto fileName: rootDir.entryList(QDir::Files))
|
||||
{
|
||||
if(fileName == what)
|
||||
for (auto&& fileName : rootDir.entryList(QDir::Files)) {
|
||||
if (fileName == what)
|
||||
return root;
|
||||
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
for(auto fileName: rootDir.entryList(QDir::Dirs))
|
||||
{
|
||||
QString result = findFolderOfFileInZip(zip, what, root + fileName);
|
||||
if(!result.isEmpty())
|
||||
{
|
||||
|
||||
// Recurse the search to non-ignored subfolders
|
||||
for (auto&& fileName : rootDir.entryList(QDir::Dirs)) {
|
||||
if (ignore_paths.contains(fileName))
|
||||
continue;
|
||||
|
||||
QString result = findFolderOfFileInZip(zip, what, ignore_paths, root + fileName);
|
||||
if (!result.isEmpty())
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// ours
|
||||
|
@ -80,9 +80,11 @@ namespace MMCZip
|
||||
/**
|
||||
* Find a single file in archive by file name (not path)
|
||||
*
|
||||
* \param ignore_paths paths to skip when recursing the search
|
||||
*
|
||||
* \return the path prefix where the file is
|
||||
*/
|
||||
QString findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root = QString(""));
|
||||
QString findFolderOfFileInZip(QuaZip * zip, const QString & what, const QStringList& ignore_paths = {}, const QString &root = QString(""));
|
||||
|
||||
/**
|
||||
* Find a multiple files of the same name in archive by file name
|
||||
|
@ -1,26 +1,28 @@
|
||||
#include "MMCStrings.h"
|
||||
#include "StringUtils.h"
|
||||
|
||||
/// If you're wondering where these came from exactly, then know you're not the only one =D
|
||||
|
||||
/// TAKEN FROM Qt, because it doesn't expose it intelligently
|
||||
static inline QChar getNextChar(const QString &s, int location)
|
||||
static inline QChar getNextChar(const QString& s, int location)
|
||||
{
|
||||
return (location < s.length()) ? s.at(location) : QChar();
|
||||
}
|
||||
|
||||
/// TAKEN FROM Qt, because it doesn't expose it intelligently
|
||||
int Strings::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs)
|
||||
int StringUtils::naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs)
|
||||
{
|
||||
for (int l1 = 0, l2 = 0; l1 <= s1.count() && l2 <= s2.count(); ++l1, ++l2)
|
||||
{
|
||||
int l1 = 0, l2 = 0;
|
||||
while (l1 <= s1.count() && l2 <= s2.count()) {
|
||||
// skip spaces, tabs and 0's
|
||||
QChar c1 = getNextChar(s1, l1);
|
||||
while (c1.isSpace())
|
||||
c1 = getNextChar(s1, ++l1);
|
||||
|
||||
QChar c2 = getNextChar(s2, l2);
|
||||
while (c2.isSpace())
|
||||
c2 = getNextChar(s2, ++l2);
|
||||
|
||||
if (c1.isDigit() && c2.isDigit())
|
||||
{
|
||||
if (c1.isDigit() && c2.isDigit()) {
|
||||
while (c1.digitValue() == 0)
|
||||
c1 = getNextChar(s1, ++l1);
|
||||
while (c2.digitValue() == 0)
|
||||
@ -30,11 +32,8 @@ int Strings::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensit
|
||||
int lookAheadLocation2 = l2;
|
||||
int currentReturnValue = 0;
|
||||
// find the last digit, setting currentReturnValue as we go if it isn't equal
|
||||
for (QChar lookAhead1 = c1, lookAhead2 = c2;
|
||||
(lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length());
|
||||
lookAhead1 = getNextChar(s1, ++lookAheadLocation1),
|
||||
lookAhead2 = getNextChar(s2, ++lookAheadLocation2))
|
||||
{
|
||||
for (QChar lookAhead1 = c1, lookAhead2 = c2; (lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length());
|
||||
lookAhead1 = getNextChar(s1, ++lookAheadLocation1), lookAhead2 = getNextChar(s2, ++lookAheadLocation2)) {
|
||||
bool is1ADigit = !lookAhead1.isNull() && lookAhead1.isDigit();
|
||||
bool is2ADigit = !lookAhead2.isNull() && lookAhead2.isDigit();
|
||||
if (!is1ADigit && !is2ADigit)
|
||||
@ -43,14 +42,10 @@ int Strings::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensit
|
||||
return -1;
|
||||
if (!is2ADigit)
|
||||
return 1;
|
||||
if (currentReturnValue == 0)
|
||||
{
|
||||
if (lookAhead1 < lookAhead2)
|
||||
{
|
||||
if (currentReturnValue == 0) {
|
||||
if (lookAhead1 < lookAhead2) {
|
||||
currentReturnValue = -1;
|
||||
}
|
||||
else if (lookAhead1 > lookAhead2)
|
||||
{
|
||||
} else if (lookAhead1 > lookAhead2) {
|
||||
currentReturnValue = 1;
|
||||
}
|
||||
}
|
||||
@ -58,19 +53,24 @@ int Strings::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensit
|
||||
if (currentReturnValue != 0)
|
||||
return currentReturnValue;
|
||||
}
|
||||
if (cs == Qt::CaseInsensitive)
|
||||
{
|
||||
|
||||
if (cs == Qt::CaseInsensitive) {
|
||||
if (!c1.isLower())
|
||||
c1 = c1.toLower();
|
||||
if (!c2.isLower())
|
||||
c2 = c2.toLower();
|
||||
}
|
||||
|
||||
int r = QString::localeAwareCompare(c1, c2);
|
||||
if (r < 0)
|
||||
return -1;
|
||||
if (r > 0)
|
||||
return 1;
|
||||
|
||||
l1 += 1;
|
||||
l2 += 1;
|
||||
}
|
||||
|
||||
// The two strings are the same (02 == 2) so fall back to the normal sort
|
||||
return QString::compare(s1, s2, cs);
|
||||
}
|
32
launcher/StringUtils.h
Normal file
32
launcher/StringUtils.h
Normal file
@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace StringUtils {
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
using string = std::wstring;
|
||||
|
||||
inline string toStdString(QString s)
|
||||
{
|
||||
return s.toStdWString();
|
||||
}
|
||||
inline QString fromStdString(string s)
|
||||
{
|
||||
return QString::fromStdWString(s);
|
||||
}
|
||||
#else
|
||||
using string = std::string;
|
||||
|
||||
inline string toStdString(QString s)
|
||||
{
|
||||
return s.toStdString();
|
||||
}
|
||||
inline QString fromStdString(string s)
|
||||
{
|
||||
return QString::fromStdString(s);
|
||||
}
|
||||
#endif
|
||||
|
||||
int naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs);
|
||||
} // namespace StringUtils
|
@ -311,14 +311,14 @@ QModelIndex VersionProxyModel::index(int row, int column, const QModelIndex &par
|
||||
|
||||
int VersionProxyModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return m_columns.size();
|
||||
return parent.isValid() ? 0 : m_columns.size();
|
||||
}
|
||||
|
||||
int VersionProxyModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
if(sourceModel())
|
||||
{
|
||||
return sourceModel()->rowCount();
|
||||
return sourceModel()->rowCount(parent);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -242,7 +242,7 @@ Qt::DropActions IconList::supportedDropActions() const
|
||||
return Qt::CopyAction;
|
||||
}
|
||||
|
||||
bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
|
||||
bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[maybe_unused]] int row, [[maybe_unused]] int column, [[maybe_unused]] const QModelIndex &parent)
|
||||
{
|
||||
if (action == Qt::IgnoreAction)
|
||||
return true;
|
||||
@ -302,7 +302,7 @@ QVariant IconList::data(const QModelIndex &index, int role) const
|
||||
|
||||
int IconList::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return icons.size();
|
||||
return parent.isValid() ? 0 : icons.size();
|
||||
}
|
||||
|
||||
void IconList::installIcons(const QStringList &iconFiles)
|
||||
|
@ -1,9 +1,10 @@
|
||||
#include "JavaInstall.h"
|
||||
#include <MMCStrings.h>
|
||||
|
||||
#include "StringUtils.h"
|
||||
|
||||
bool JavaInstall::operator<(const JavaInstall &rhs)
|
||||
{
|
||||
auto archCompare = Strings::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive);
|
||||
auto archCompare = StringUtils::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive);
|
||||
if(archCompare != 0)
|
||||
return archCompare < 0;
|
||||
if(id < rhs.id)
|
||||
@ -14,7 +15,7 @@ bool JavaInstall::operator<(const JavaInstall &rhs)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return Strings::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0;
|
||||
return StringUtils::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0;
|
||||
}
|
||||
|
||||
bool JavaInstall::operator==(const JavaInstall &rhs)
|
||||
|
@ -41,7 +41,6 @@
|
||||
#include "java/JavaInstallList.h"
|
||||
#include "java/JavaCheckerJob.h"
|
||||
#include "java/JavaUtils.h"
|
||||
#include "MMCStrings.h"
|
||||
#include "minecraft/VersionFilterData.h"
|
||||
|
||||
JavaInstallList::JavaInstallList(QObject *parent) : BaseVersionList(parent)
|
||||
@ -73,7 +72,7 @@ void JavaInstallList::load()
|
||||
}
|
||||
}
|
||||
|
||||
const BaseVersionPtr JavaInstallList::at(int i) const
|
||||
const BaseVersion::Ptr JavaInstallList::at(int i) const
|
||||
{
|
||||
return m_vlist.at(i);
|
||||
}
|
||||
@ -122,7 +121,7 @@ BaseVersionList::RoleList JavaInstallList::providesRoles() const
|
||||
}
|
||||
|
||||
|
||||
void JavaInstallList::updateListData(QList<BaseVersionPtr> versions)
|
||||
void JavaInstallList::updateListData(QList<BaseVersion::Ptr> versions)
|
||||
{
|
||||
beginResetModel();
|
||||
m_vlist = versions;
|
||||
@ -137,7 +136,7 @@ void JavaInstallList::updateListData(QList<BaseVersionPtr> versions)
|
||||
m_loadTask.reset();
|
||||
}
|
||||
|
||||
bool sortJavas(BaseVersionPtr left, BaseVersionPtr right)
|
||||
bool sortJavas(BaseVersion::Ptr left, BaseVersion::Ptr right)
|
||||
{
|
||||
auto rleft = std::dynamic_pointer_cast<JavaInstall>(right);
|
||||
auto rright = std::dynamic_pointer_cast<JavaInstall>(left);
|
||||
@ -210,11 +209,11 @@ void JavaListLoadTask::javaCheckerFinished()
|
||||
}
|
||||
}
|
||||
|
||||
QList<BaseVersionPtr> javas_bvp;
|
||||
QList<BaseVersion::Ptr> javas_bvp;
|
||||
for (auto java : candidates)
|
||||
{
|
||||
//qDebug() << java->id << java->arch << " at " << java->path;
|
||||
BaseVersionPtr bp_java = std::dynamic_pointer_cast<BaseVersion>(java);
|
||||
BaseVersion::Ptr bp_java = std::dynamic_pointer_cast<BaseVersion>(java);
|
||||
|
||||
if (bp_java)
|
||||
{
|
||||
|
@ -42,7 +42,7 @@ public:
|
||||
|
||||
Task::Ptr getLoadTask() override;
|
||||
bool isLoaded() override;
|
||||
const BaseVersionPtr at(int i) const override;
|
||||
const BaseVersion::Ptr at(int i) const override;
|
||||
int count() const override;
|
||||
void sortVersions() override;
|
||||
|
||||
@ -50,7 +50,7 @@ public:
|
||||
RoleList providesRoles() const override;
|
||||
|
||||
public slots:
|
||||
void updateListData(QList<BaseVersionPtr> versions) override;
|
||||
void updateListData(QList<BaseVersion::Ptr> versions) override;
|
||||
|
||||
protected:
|
||||
void load();
|
||||
@ -59,7 +59,7 @@ protected:
|
||||
protected:
|
||||
Status m_status = Status::NotDone;
|
||||
shared_qobject_ptr<JavaListLoadTask> m_loadTask;
|
||||
QList<BaseVersionPtr> m_vlist;
|
||||
QList<BaseVersion::Ptr> m_vlist;
|
||||
};
|
||||
|
||||
class JavaListLoadTask : public Task
|
||||
|
@ -439,19 +439,28 @@ QList<QString> JavaUtils::FindJavaPaths()
|
||||
javas.append(FS::PathCombine(prefix, "bin/java"));
|
||||
}
|
||||
};
|
||||
// java installed in a snap is installed in the standard directory, but underneath $SNAP
|
||||
auto snap = qEnvironmentVariable("SNAP");
|
||||
auto scanJavaDirs = [&](const QString & dirPath)
|
||||
{
|
||||
scanJavaDir(dirPath);
|
||||
if (!snap.isNull()) {
|
||||
scanJavaDir(snap + dirPath);
|
||||
}
|
||||
};
|
||||
// oracle RPMs
|
||||
scanJavaDir("/usr/java");
|
||||
scanJavaDirs("/usr/java");
|
||||
// general locations used by distro packaging
|
||||
scanJavaDir("/usr/lib/jvm");
|
||||
scanJavaDir("/usr/lib64/jvm");
|
||||
scanJavaDir("/usr/lib32/jvm");
|
||||
scanJavaDirs("/usr/lib/jvm");
|
||||
scanJavaDirs("/usr/lib64/jvm");
|
||||
scanJavaDirs("/usr/lib32/jvm");
|
||||
// javas stored in Prism Launcher's folder
|
||||
scanJavaDir("java");
|
||||
scanJavaDirs("java");
|
||||
// manually installed JDKs in /opt
|
||||
scanJavaDir("/opt/jdk");
|
||||
scanJavaDir("/opt/jdks");
|
||||
scanJavaDirs("/opt/jdk");
|
||||
scanJavaDirs("/opt/jdks");
|
||||
// flatpak
|
||||
scanJavaDir("/app/jdk");
|
||||
scanJavaDirs("/app/jdk");
|
||||
javas = addJavasFromEnv(javas);
|
||||
javas.removeDuplicates();
|
||||
return javas;
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "JavaVersion.h"
|
||||
#include <MMCStrings.h>
|
||||
|
||||
#include "StringUtils.h"
|
||||
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
@ -98,12 +99,12 @@ bool JavaVersion::operator<(const JavaVersion &rhs)
|
||||
else if(thisPre && rhsPre)
|
||||
{
|
||||
// both are prereleases - use natural compare...
|
||||
return Strings::naturalCompare(m_prerelease, rhs.m_prerelease, Qt::CaseSensitive) < 0;
|
||||
return StringUtils::naturalCompare(m_prerelease, rhs.m_prerelease, Qt::CaseSensitive) < 0;
|
||||
}
|
||||
// neither is prerelease, so they are the same -> this cannot be less than rhs
|
||||
return false;
|
||||
}
|
||||
else return Strings::naturalCompare(m_string, rhs.m_string, Qt::CaseSensitive) < 0;
|
||||
else return StringUtils::naturalCompare(m_string, rhs.m_string, Qt::CaseSensitive) < 0;
|
||||
}
|
||||
|
||||
bool JavaVersion::operator==(const JavaVersion &rhs)
|
||||
|
@ -37,7 +37,6 @@
|
||||
|
||||
#include "launch/LaunchTask.h"
|
||||
#include "MessageLevel.h"
|
||||
#include "MMCStrings.h"
|
||||
#include "java/JavaChecker.h"
|
||||
#include "tasks/Task.h"
|
||||
#include <QDebug>
|
||||
|
@ -81,14 +81,19 @@ int main(int argc, char *argv[])
|
||||
Q_INIT_RESOURCE(pe_light);
|
||||
Q_INIT_RESOURCE(pe_blue);
|
||||
Q_INIT_RESOURCE(pe_colored);
|
||||
Q_INIT_RESOURCE(breeze_dark);
|
||||
Q_INIT_RESOURCE(breeze_light);
|
||||
Q_INIT_RESOURCE(OSX);
|
||||
Q_INIT_RESOURCE(iOS);
|
||||
Q_INIT_RESOURCE(flat);
|
||||
Q_INIT_RESOURCE(flat_white);
|
||||
return app.exec();
|
||||
}
|
||||
case Application::Failed:
|
||||
return 1;
|
||||
case Application::Succeeded:
|
||||
return 0;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ Index::Index(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
Index::Index(const QVector<VersionListPtr> &lists, QObject *parent)
|
||||
Index::Index(const QVector<VersionList::Ptr> &lists, QObject *parent)
|
||||
: QAbstractListModel(parent), m_lists(lists)
|
||||
{
|
||||
for (int i = 0; i < m_lists.size(); ++i)
|
||||
@ -41,7 +41,7 @@ QVariant Index::data(const QModelIndex &index, int role) const
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
VersionListPtr list = m_lists.at(index.row());
|
||||
VersionList::Ptr list = m_lists.at(index.row());
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
@ -58,11 +58,11 @@ QVariant Index::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
int Index::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return m_lists.size();
|
||||
return parent.isValid() ? 0 : m_lists.size();
|
||||
}
|
||||
int Index::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return 1;
|
||||
return parent.isValid() ? 0 : 1;
|
||||
}
|
||||
QVariant Index::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
@ -81,9 +81,9 @@ bool Index::hasUid(const QString &uid) const
|
||||
return m_uids.contains(uid);
|
||||
}
|
||||
|
||||
VersionListPtr Index::get(const QString &uid)
|
||||
VersionList::Ptr Index::get(const QString &uid)
|
||||
{
|
||||
VersionListPtr out = m_uids.value(uid, nullptr);
|
||||
VersionList::Ptr out = m_uids.value(uid, nullptr);
|
||||
if(!out)
|
||||
{
|
||||
out = std::make_shared<VersionList>(uid);
|
||||
@ -92,7 +92,7 @@ VersionListPtr Index::get(const QString &uid)
|
||||
return out;
|
||||
}
|
||||
|
||||
VersionPtr Index::get(const QString &uid, const QString &version)
|
||||
Version::Ptr Index::get(const QString &uid, const QString &version)
|
||||
{
|
||||
auto list = get(uid);
|
||||
return list->getVersion(version);
|
||||
@ -105,7 +105,7 @@ void Index::parse(const QJsonObject& obj)
|
||||
|
||||
void Index::merge(const std::shared_ptr<Index> &other)
|
||||
{
|
||||
const QVector<VersionListPtr> lists = std::dynamic_pointer_cast<Index>(other)->m_lists;
|
||||
const QVector<VersionList::Ptr> lists = std::dynamic_pointer_cast<Index>(other)->m_lists;
|
||||
// initial load, no need to merge
|
||||
if (m_lists.isEmpty())
|
||||
{
|
||||
@ -120,7 +120,7 @@ void Index::merge(const std::shared_ptr<Index> &other)
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const VersionListPtr &list : lists)
|
||||
for (const VersionList::Ptr &list : lists)
|
||||
{
|
||||
if (m_uids.contains(list->uid()))
|
||||
{
|
||||
@ -138,7 +138,7 @@ void Index::merge(const std::shared_ptr<Index> &other)
|
||||
}
|
||||
}
|
||||
|
||||
void Index::connectVersionList(const int row, const VersionListPtr &list)
|
||||
void Index::connectVersionList(const int row, const VersionList::Ptr &list)
|
||||
{
|
||||
connect(list.get(), &VersionList::nameChanged, this, [this, row]()
|
||||
{
|
||||
|
@ -19,20 +19,19 @@
|
||||
#include <memory>
|
||||
|
||||
#include "BaseEntity.h"
|
||||
#include "meta/VersionList.h"
|
||||
|
||||
class Task;
|
||||
|
||||
namespace Meta
|
||||
{
|
||||
using VersionListPtr = std::shared_ptr<class VersionList>;
|
||||
using VersionPtr = std::shared_ptr<class Version>;
|
||||
|
||||
class Index : public QAbstractListModel, public BaseEntity
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Index(QObject *parent = nullptr);
|
||||
explicit Index(const QVector<VersionListPtr> &lists, QObject *parent = nullptr);
|
||||
explicit Index(const QVector<VersionList::Ptr> &lists, QObject *parent = nullptr);
|
||||
|
||||
enum
|
||||
{
|
||||
@ -49,21 +48,21 @@ public:
|
||||
QString localFilename() const override { return "index.json"; }
|
||||
|
||||
// queries
|
||||
VersionListPtr get(const QString &uid);
|
||||
VersionPtr get(const QString &uid, const QString &version);
|
||||
VersionList::Ptr get(const QString &uid);
|
||||
Version::Ptr get(const QString &uid, const QString &version);
|
||||
bool hasUid(const QString &uid) const;
|
||||
|
||||
QVector<VersionListPtr> lists() const { return m_lists; }
|
||||
QVector<VersionList::Ptr> lists() const { return m_lists; }
|
||||
|
||||
public: // for usage by parsers only
|
||||
void merge(const std::shared_ptr<Index> &other);
|
||||
void parse(const QJsonObject &obj) override;
|
||||
|
||||
private:
|
||||
QVector<VersionListPtr> m_lists;
|
||||
QHash<QString, VersionListPtr> m_uids;
|
||||
QVector<VersionList::Ptr> m_lists;
|
||||
QHash<QString, VersionList::Ptr> m_uids;
|
||||
|
||||
void connectVersionList(const int row, const VersionListPtr &list);
|
||||
void connectVersionList(const int row, const VersionList::Ptr &list);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -37,11 +37,11 @@ MetadataVersion currentFormatVersion()
|
||||
static std::shared_ptr<Index> parseIndexInternal(const QJsonObject &obj)
|
||||
{
|
||||
const QVector<QJsonObject> objects = requireIsArrayOf<QJsonObject>(obj, "packages");
|
||||
QVector<VersionListPtr> lists;
|
||||
QVector<VersionList::Ptr> lists;
|
||||
lists.reserve(objects.size());
|
||||
std::transform(objects.begin(), objects.end(), std::back_inserter(lists), [](const QJsonObject &obj)
|
||||
{
|
||||
VersionListPtr list = std::make_shared<VersionList>(requireString(obj, "uid"));
|
||||
VersionList::Ptr list = std::make_shared<VersionList>(requireString(obj, "uid"));
|
||||
list->setName(ensureString(obj, "name", QString()));
|
||||
return list;
|
||||
});
|
||||
@ -49,9 +49,9 @@ static std::shared_ptr<Index> parseIndexInternal(const QJsonObject &obj)
|
||||
}
|
||||
|
||||
// Version
|
||||
static VersionPtr parseCommonVersion(const QString &uid, const QJsonObject &obj)
|
||||
static Version::Ptr parseCommonVersion(const QString &uid, const QJsonObject &obj)
|
||||
{
|
||||
VersionPtr version = std::make_shared<Version>(uid, requireString(obj, "version"));
|
||||
Version::Ptr version = std::make_shared<Version>(uid, requireString(obj, "version"));
|
||||
version->setTime(QDateTime::fromString(requireString(obj, "releaseTime"), Qt::ISODate).toMSecsSinceEpoch() / 1000);
|
||||
version->setType(ensureString(obj, "type", QString()));
|
||||
version->setRecommended(ensureBoolean(obj, QString("recommended"), false));
|
||||
@ -63,9 +63,9 @@ static VersionPtr parseCommonVersion(const QString &uid, const QJsonObject &obj)
|
||||
return version;
|
||||
}
|
||||
|
||||
static std::shared_ptr<Version> parseVersionInternal(const QJsonObject &obj)
|
||||
static Version::Ptr parseVersionInternal(const QJsonObject &obj)
|
||||
{
|
||||
VersionPtr version = parseCommonVersion(requireString(obj, "uid"), obj);
|
||||
Version::Ptr version = parseCommonVersion(requireString(obj, "uid"), obj);
|
||||
|
||||
version->setData(OneSixVersionFormat::versionFileFromJson(QJsonDocument(obj),
|
||||
QString("%1/%2.json").arg(version->uid(), version->version()),
|
||||
@ -74,12 +74,12 @@ static std::shared_ptr<Version> parseVersionInternal(const QJsonObject &obj)
|
||||
}
|
||||
|
||||
// Version list / package
|
||||
static std::shared_ptr<VersionList> parseVersionListInternal(const QJsonObject &obj)
|
||||
static VersionList::Ptr parseVersionListInternal(const QJsonObject &obj)
|
||||
{
|
||||
const QString uid = requireString(obj, "uid");
|
||||
|
||||
const QVector<QJsonObject> versionsRaw = requireIsArrayOf<QJsonObject>(obj, "versions");
|
||||
QVector<VersionPtr> versions;
|
||||
QVector<Version::Ptr> versions;
|
||||
versions.reserve(versionsRaw.size());
|
||||
std::transform(versionsRaw.begin(), versionsRaw.end(), std::back_inserter(versions), [uid](const QJsonObject &vObj)
|
||||
{
|
||||
@ -88,7 +88,7 @@ static std::shared_ptr<VersionList> parseVersionListInternal(const QJsonObject &
|
||||
return version;
|
||||
});
|
||||
|
||||
VersionListPtr list = std::make_shared<VersionList>(uid);
|
||||
VersionList::Ptr list = std::make_shared<VersionList>(uid);
|
||||
list->setName(ensureString(obj, "name", QString()));
|
||||
list->setVersions(versions);
|
||||
return list;
|
||||
|
@ -60,11 +60,6 @@ struct Require
|
||||
QString suggests;
|
||||
};
|
||||
|
||||
inline Q_DECL_PURE_FUNCTION uint qHash(const Require &key, uint seed = 0) Q_DECL_NOTHROW
|
||||
{
|
||||
return qHash(key.uid, seed);
|
||||
}
|
||||
|
||||
using RequireSet = std::set<Require>;
|
||||
|
||||
void parseIndex(const QJsonObject &obj, Index *ptr);
|
||||
|
@ -54,7 +54,7 @@ void Meta::Version::parse(const QJsonObject& obj)
|
||||
parseVersion(obj, this);
|
||||
}
|
||||
|
||||
void Meta::Version::mergeFromList(const Meta::VersionPtr& other)
|
||||
void Meta::Version::mergeFromList(const Meta::Version::Ptr& other)
|
||||
{
|
||||
if(other->m_providesRecommendations)
|
||||
{
|
||||
@ -85,7 +85,7 @@ void Meta::Version::mergeFromList(const Meta::VersionPtr& other)
|
||||
}
|
||||
}
|
||||
|
||||
void Meta::Version::merge(const VersionPtr &other)
|
||||
void Meta::Version::merge(const Version::Ptr &other)
|
||||
{
|
||||
mergeFromList(other);
|
||||
if(other->m_data)
|
||||
|
@ -30,13 +30,14 @@
|
||||
|
||||
namespace Meta
|
||||
{
|
||||
using VersionPtr = std::shared_ptr<class Version>;
|
||||
|
||||
class Version : public QObject, public BaseVersion, public BaseEntity
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public: /* con/des */
|
||||
public:
|
||||
using Ptr = std::shared_ptr<Version>;
|
||||
|
||||
explicit Version(const QString &uid, const QString &version);
|
||||
virtual ~Version();
|
||||
|
||||
@ -78,8 +79,8 @@ public: /* con/des */
|
||||
return m_data != nullptr;
|
||||
}
|
||||
|
||||
void merge(const VersionPtr &other);
|
||||
void mergeFromList(const VersionPtr &other);
|
||||
void merge(const Version::Ptr &other);
|
||||
void mergeFromList(const Version::Ptr &other);
|
||||
void parse(const QJsonObject &obj) override;
|
||||
|
||||
QString localFilename() const override;
|
||||
@ -113,4 +114,4 @@ private:
|
||||
};
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(Meta::VersionPtr)
|
||||
Q_DECLARE_METATYPE(Meta::Version::Ptr)
|
||||
|
@ -40,7 +40,7 @@ bool VersionList::isLoaded()
|
||||
return BaseEntity::isLoaded();
|
||||
}
|
||||
|
||||
const BaseVersionPtr VersionList::at(int i) const
|
||||
const BaseVersion::Ptr VersionList::at(int i) const
|
||||
{
|
||||
return m_versions.at(i);
|
||||
}
|
||||
@ -52,7 +52,7 @@ int VersionList::count() const
|
||||
void VersionList::sortVersions()
|
||||
{
|
||||
beginResetModel();
|
||||
std::sort(m_versions.begin(), m_versions.end(), [](const VersionPtr &a, const VersionPtr &b)
|
||||
std::sort(m_versions.begin(), m_versions.end(), [](const Version::Ptr &a, const Version::Ptr &b)
|
||||
{
|
||||
return *a.get() < *b.get();
|
||||
});
|
||||
@ -66,7 +66,7 @@ QVariant VersionList::data(const QModelIndex &index, int role) const
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
VersionPtr version = m_versions.at(index.row());
|
||||
Version::Ptr version = m_versions.at(index.row());
|
||||
|
||||
switch (role)
|
||||
{
|
||||
@ -129,9 +129,9 @@ QString VersionList::humanReadable() const
|
||||
return m_name.isEmpty() ? m_uid : m_name;
|
||||
}
|
||||
|
||||
VersionPtr VersionList::getVersion(const QString &version)
|
||||
Version::Ptr VersionList::getVersion(const QString &version)
|
||||
{
|
||||
VersionPtr out = m_lookup.value(version, nullptr);
|
||||
Version::Ptr out = m_lookup.value(version, nullptr);
|
||||
if(!out)
|
||||
{
|
||||
out = std::make_shared<Version>(m_uid, version);
|
||||
@ -143,7 +143,7 @@ VersionPtr VersionList::getVersion(const QString &version)
|
||||
bool VersionList::hasVersion(QString version) const
|
||||
{
|
||||
auto ver = std::find_if(m_versions.constBegin(), m_versions.constEnd(),
|
||||
[&](Meta::VersionPtr const& a){ return a->version() == version; });
|
||||
[&](Meta::Version::Ptr const& a){ return a->version() == version; });
|
||||
return (ver != m_versions.constEnd());
|
||||
}
|
||||
|
||||
@ -153,11 +153,11 @@ void VersionList::setName(const QString &name)
|
||||
emit nameChanged(name);
|
||||
}
|
||||
|
||||
void VersionList::setVersions(const QVector<VersionPtr> &versions)
|
||||
void VersionList::setVersions(const QVector<Version::Ptr> &versions)
|
||||
{
|
||||
beginResetModel();
|
||||
m_versions = versions;
|
||||
std::sort(m_versions.begin(), m_versions.end(), [](const VersionPtr &a, const VersionPtr &b)
|
||||
std::sort(m_versions.begin(), m_versions.end(), [](const Version::Ptr &a, const Version::Ptr &b)
|
||||
{
|
||||
return a->rawTime() > b->rawTime();
|
||||
});
|
||||
@ -168,7 +168,7 @@ void VersionList::setVersions(const QVector<VersionPtr> &versions)
|
||||
}
|
||||
|
||||
// FIXME: this is dumb, we have 'recommended' as part of the metadata already...
|
||||
auto recommendedIt = std::find_if(m_versions.constBegin(), m_versions.constEnd(), [](const VersionPtr &ptr) { return ptr->type() == "release"; });
|
||||
auto recommendedIt = std::find_if(m_versions.constBegin(), m_versions.constEnd(), [](const Version::Ptr &ptr) { return ptr->type() == "release"; });
|
||||
m_recommended = recommendedIt == m_versions.constEnd() ? nullptr : *recommendedIt;
|
||||
endResetModel();
|
||||
}
|
||||
@ -179,7 +179,7 @@ void VersionList::parse(const QJsonObject& obj)
|
||||
}
|
||||
|
||||
// FIXME: this is dumb, we have 'recommended' as part of the metadata already...
|
||||
static const Meta::VersionPtr &getBetterVersion(const Meta::VersionPtr &a, const Meta::VersionPtr &b)
|
||||
static const Meta::Version::Ptr &getBetterVersion(const Meta::Version::Ptr &a, const Meta::Version::Ptr &b)
|
||||
{
|
||||
if(!a)
|
||||
return b;
|
||||
@ -194,7 +194,7 @@ static const Meta::VersionPtr &getBetterVersion(const Meta::VersionPtr &a, const
|
||||
return (a->type() == "release" ? a : b);
|
||||
}
|
||||
|
||||
void VersionList::mergeFromIndex(const VersionListPtr &other)
|
||||
void VersionList::mergeFromIndex(const VersionList::Ptr &other)
|
||||
{
|
||||
if (m_name != other->m_name)
|
||||
{
|
||||
@ -202,7 +202,7 @@ void VersionList::mergeFromIndex(const VersionListPtr &other)
|
||||
}
|
||||
}
|
||||
|
||||
void VersionList::merge(const VersionListPtr &other)
|
||||
void VersionList::merge(const VersionList::Ptr &other)
|
||||
{
|
||||
if (m_name != other->m_name)
|
||||
{
|
||||
@ -216,7 +216,7 @@ void VersionList::merge(const VersionListPtr &other)
|
||||
{
|
||||
qWarning() << "Empty list loaded ...";
|
||||
}
|
||||
for (const VersionPtr &version : other->m_versions)
|
||||
for (const Version::Ptr &version : other->m_versions)
|
||||
{
|
||||
// we already have the version. merge the contents
|
||||
if (m_lookup.contains(version->version()))
|
||||
@ -235,7 +235,7 @@ void VersionList::merge(const VersionListPtr &other)
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void VersionList::setupAddedVersion(const int row, const VersionPtr &version)
|
||||
void VersionList::setupAddedVersion(const int row, const Version::Ptr &version)
|
||||
{
|
||||
// FIXME: do not disconnect from everythin, disconnect only the lambdas here
|
||||
version->disconnect();
|
||||
@ -244,7 +244,7 @@ void VersionList::setupAddedVersion(const int row, const VersionPtr &version)
|
||||
connect(version.get(), &Version::typeChanged, this, [this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << TypeRole); });
|
||||
}
|
||||
|
||||
BaseVersionPtr VersionList::getRecommended() const
|
||||
BaseVersion::Ptr VersionList::getRecommended() const
|
||||
{
|
||||
return m_recommended;
|
||||
}
|
||||
|
@ -20,10 +20,10 @@
|
||||
#include <QJsonObject>
|
||||
#include <memory>
|
||||
|
||||
#include "meta/Version.h"
|
||||
|
||||
namespace Meta
|
||||
{
|
||||
using VersionPtr = std::shared_ptr<class Version>;
|
||||
using VersionListPtr = std::shared_ptr<class VersionList>;
|
||||
|
||||
class VersionList : public BaseVersionList, public BaseEntity
|
||||
{
|
||||
@ -33,6 +33,8 @@ class VersionList : public BaseVersionList, public BaseEntity
|
||||
public:
|
||||
explicit VersionList(const QString &uid, QObject *parent = nullptr);
|
||||
|
||||
using Ptr = std::shared_ptr<VersionList>;
|
||||
|
||||
enum Roles
|
||||
{
|
||||
UidRole = Qt::UserRole + 100,
|
||||
@ -43,11 +45,11 @@ public:
|
||||
|
||||
Task::Ptr getLoadTask() override;
|
||||
bool isLoaded() override;
|
||||
const BaseVersionPtr at(int i) const override;
|
||||
const BaseVersion::Ptr at(int i) const override;
|
||||
int count() const override;
|
||||
void sortVersions() override;
|
||||
|
||||
BaseVersionPtr getRecommended() const override;
|
||||
BaseVersion::Ptr getRecommended() const override;
|
||||
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
RoleList providesRoles() const override;
|
||||
@ -65,38 +67,38 @@ public:
|
||||
}
|
||||
QString humanReadable() const;
|
||||
|
||||
VersionPtr getVersion(const QString &version);
|
||||
Version::Ptr getVersion(const QString &version);
|
||||
bool hasVersion(QString version) const;
|
||||
|
||||
QVector<VersionPtr> versions() const
|
||||
QVector<Version::Ptr> versions() const
|
||||
{
|
||||
return m_versions;
|
||||
}
|
||||
|
||||
public: // for usage only by parsers
|
||||
void setName(const QString &name);
|
||||
void setVersions(const QVector<VersionPtr> &versions);
|
||||
void merge(const VersionListPtr &other);
|
||||
void mergeFromIndex(const VersionListPtr &other);
|
||||
void setVersions(const QVector<Version::Ptr> &versions);
|
||||
void merge(const VersionList::Ptr &other);
|
||||
void mergeFromIndex(const VersionList::Ptr &other);
|
||||
void parse(const QJsonObject &obj) override;
|
||||
|
||||
signals:
|
||||
void nameChanged(const QString &name);
|
||||
|
||||
protected slots:
|
||||
void updateListData(QList<BaseVersionPtr>) override
|
||||
void updateListData(QList<BaseVersion::Ptr>) override
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
QVector<VersionPtr> m_versions;
|
||||
QHash<QString, VersionPtr> m_lookup;
|
||||
QVector<Version::Ptr> m_versions;
|
||||
QHash<QString, Version::Ptr> m_lookup;
|
||||
QString m_uid;
|
||||
QString m_name;
|
||||
|
||||
VersionPtr m_recommended;
|
||||
Version::Ptr m_recommended;
|
||||
|
||||
void setupAddedVersion(const int row, const VersionPtr &version);
|
||||
void setupAddedVersion(const int row, const Version::Ptr &version);
|
||||
};
|
||||
}
|
||||
Q_DECLARE_METATYPE(Meta::VersionListPtr)
|
||||
Q_DECLARE_METATYPE(Meta::VersionList::Ptr)
|
||||
|
@ -10,7 +10,7 @@ typedef std::shared_ptr<Agent> AgentPtr;
|
||||
|
||||
class Agent {
|
||||
public:
|
||||
Agent(LibraryPtr library, QString &argument)
|
||||
Agent(LibraryPtr library, const QString &argument)
|
||||
{
|
||||
m_library = library;
|
||||
m_argument = argument;
|
||||
|
@ -1,8 +1,9 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -43,7 +44,6 @@
|
||||
#include "settings/SettingsObject.h"
|
||||
#include "Application.h"
|
||||
|
||||
#include "MMCStrings.h"
|
||||
#include "pathmatcher/RegexpMatcher.h"
|
||||
#include "pathmatcher/MultiMatcher.h"
|
||||
#include "FileSystem.h"
|
||||
@ -439,6 +439,17 @@ QStringList MinecraftInstance::javaArguments()
|
||||
return args;
|
||||
}
|
||||
|
||||
QString MinecraftInstance::getLauncher()
|
||||
{
|
||||
auto profile = m_components->getProfile();
|
||||
|
||||
// use legacy launcher if the traits are set
|
||||
if (profile->getTraits().contains("legacyLaunch") || profile->getTraits().contains("alphaLaunch"))
|
||||
return "legacy";
|
||||
|
||||
return "standard";
|
||||
}
|
||||
|
||||
QMap<QString, QString> MinecraftInstance::getVariables()
|
||||
{
|
||||
QMap<QString, QString> out;
|
||||
@ -630,26 +641,13 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
|
||||
launchScript += "sessionId " + session->session + "\n";
|
||||
}
|
||||
|
||||
// libraries and class path.
|
||||
{
|
||||
QStringList jars, nativeJars;
|
||||
profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot());
|
||||
for(auto file: jars)
|
||||
{
|
||||
launchScript += "cp " + file + "\n";
|
||||
}
|
||||
for(auto file: nativeJars)
|
||||
{
|
||||
launchScript += "ext " + file + "\n";
|
||||
}
|
||||
launchScript += "natives " + getNativePath() + "\n";
|
||||
}
|
||||
|
||||
for (auto trait : profile->getTraits())
|
||||
{
|
||||
launchScript += "traits " + trait + "\n";
|
||||
}
|
||||
launchScript += "launcher onesix\n";
|
||||
|
||||
launchScript += "launcher " + getLauncher() + "\n";
|
||||
|
||||
// qDebug() << "Generated launch script:" << launchScript;
|
||||
return launchScript;
|
||||
}
|
||||
@ -785,6 +783,8 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
|
||||
out << "Window size: " + QString::number(width) + " x " + QString::number(height);
|
||||
}
|
||||
out << "";
|
||||
out << "Launcher: " + getLauncher();
|
||||
out << "";
|
||||
return out;
|
||||
}
|
||||
|
||||
@ -1096,8 +1096,6 @@ std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList() c
|
||||
if (!m_resource_pack_list)
|
||||
{
|
||||
m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir()));
|
||||
m_resource_pack_list->enableInteraction(!isRunning());
|
||||
connect(this, &BaseInstance::runningStatusChanged, m_resource_pack_list.get(), &ResourcePackFolderModel::disableInteraction);
|
||||
}
|
||||
return m_resource_pack_list;
|
||||
}
|
||||
@ -1107,8 +1105,6 @@ std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList() con
|
||||
if (!m_texture_pack_list)
|
||||
{
|
||||
m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir()));
|
||||
m_texture_pack_list->disableInteraction(isRunning());
|
||||
connect(this, &BaseInstance::runningStatusChanged, m_texture_pack_list.get(), &ModFolderModel::disableInteraction);
|
||||
}
|
||||
return m_texture_pack_list;
|
||||
}
|
||||
@ -1118,8 +1114,6 @@ std::shared_ptr<ShaderPackFolderModel> MinecraftInstance::shaderPackList() const
|
||||
if (!m_shader_pack_list)
|
||||
{
|
||||
m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir()));
|
||||
m_shader_pack_list->disableInteraction(isRunning());
|
||||
connect(this, &BaseInstance::runningStatusChanged, m_shader_pack_list.get(), &ModFolderModel::disableInteraction);
|
||||
}
|
||||
return m_shader_pack_list;
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -130,6 +131,7 @@ public:
|
||||
QString createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin);
|
||||
/// get arguments passed to java
|
||||
QStringList javaArguments();
|
||||
QString getLauncher();
|
||||
|
||||
/// get variables for launch command variable substitution/environment
|
||||
QMap<QString, QString> getVariables() override;
|
||||
|
@ -135,7 +135,7 @@ QJsonObject libDownloadInfoToJson(MojangLibraryDownloadInfo::Ptr libinfo)
|
||||
{
|
||||
out.insert("artifact", downloadInfoToJson(libinfo->artifact));
|
||||
}
|
||||
if(libinfo->classifiers.size())
|
||||
if(!libinfo->classifiers.isEmpty())
|
||||
{
|
||||
QJsonObject classifiersOut;
|
||||
for(auto iter = libinfo->classifiers.begin(); iter != libinfo->classifiers.end(); iter++)
|
||||
@ -297,7 +297,7 @@ void MojangVersionFormat::writeVersionProperties(const VersionFile* in, QJsonObj
|
||||
{
|
||||
out.insert("assetIndex", assetIndexToJson(in->mojangAssetIndex));
|
||||
}
|
||||
if(in->mojangDownloads.size())
|
||||
if(!in->mojangDownloads.isEmpty())
|
||||
{
|
||||
QJsonObject downloadsOut;
|
||||
for(auto iter = in->mojangDownloads.begin(); iter != in->mojangDownloads.end(); iter++)
|
||||
@ -306,6 +306,15 @@ void MojangVersionFormat::writeVersionProperties(const VersionFile* in, QJsonObj
|
||||
}
|
||||
out.insert("downloads", downloadsOut);
|
||||
}
|
||||
if(!in->compatibleJavaMajors.isEmpty())
|
||||
{
|
||||
QJsonArray compatibleJavaMajorsOut;
|
||||
for(auto compatibleJavaMajor : in->compatibleJavaMajors)
|
||||
{
|
||||
compatibleJavaMajorsOut.append(compatibleJavaMajor);
|
||||
}
|
||||
out.insert("compatibleJavaMajors", compatibleJavaMajorsOut);
|
||||
}
|
||||
}
|
||||
|
||||
QJsonDocument MojangVersionFormat::versionFileToJson(const VersionFilePtr &patch)
|
||||
@ -396,7 +405,7 @@ QJsonObject MojangVersionFormat::libraryToJson(Library *library)
|
||||
iter++;
|
||||
}
|
||||
libRoot.insert("natives", nativeList);
|
||||
if (library->m_extractExcludes.size())
|
||||
if (!library->m_extractExcludes.isEmpty())
|
||||
{
|
||||
QJsonArray excludes;
|
||||
QJsonObject extract;
|
||||
@ -408,7 +417,7 @@ QJsonObject MojangVersionFormat::libraryToJson(Library *library)
|
||||
libRoot.insert("extract", extract);
|
||||
}
|
||||
}
|
||||
if (library->m_rules.size())
|
||||
if (!library->m_rules.isEmpty())
|
||||
{
|
||||
QJsonArray allRules;
|
||||
for (auto &rule : library->m_rules)
|
||||
|
@ -63,13 +63,13 @@ LibraryPtr OneSixVersionFormat::libraryFromJson(ProblemContainer & problems, con
|
||||
QJsonObject OneSixVersionFormat::libraryToJson(Library *library)
|
||||
{
|
||||
QJsonObject libRoot = MojangVersionFormat::libraryToJson(library);
|
||||
if (library->m_absoluteURL.size())
|
||||
if (!library->m_absoluteURL.isEmpty())
|
||||
libRoot.insert("MMC-absoluteUrl", library->m_absoluteURL);
|
||||
if (library->m_hint.size())
|
||||
if (!library->m_hint.isEmpty())
|
||||
libRoot.insert("MMC-hint", library->m_hint);
|
||||
if (library->m_filename.size())
|
||||
if (!library->m_filename.isEmpty())
|
||||
libRoot.insert("MMC-filename", library->m_filename);
|
||||
if (library->m_displayname.size())
|
||||
if (!library->m_displayname.isEmpty())
|
||||
libRoot.insert("MMC-displayname", library->m_displayname);
|
||||
return libRoot;
|
||||
}
|
||||
@ -225,11 +225,10 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
|
||||
{
|
||||
QJsonObject agentObj = requireObject(agentVal);
|
||||
auto lib = libraryFromJson(*out, agentObj, filename);
|
||||
|
||||
QString arg = "";
|
||||
if (agentObj.contains("argument"))
|
||||
{
|
||||
readString(agentObj, "argument", arg);
|
||||
}
|
||||
readString(agentObj, "argument", arg);
|
||||
|
||||
AgentPtr agent(new Agent(lib, arg));
|
||||
out->agents.append(agent);
|
||||
}
|
||||
@ -332,6 +331,20 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch
|
||||
writeString(root, "appletClass", patch->appletClass);
|
||||
writeStringList(root, "+tweakers", patch->addTweakers);
|
||||
writeStringList(root, "+traits", patch->traits.values());
|
||||
writeStringList(root, "+jvmArgs", patch->addnJvmArguments);
|
||||
if (!patch->agents.isEmpty())
|
||||
{
|
||||
QJsonArray array;
|
||||
for (auto value: patch->agents)
|
||||
{
|
||||
QJsonObject agentOut = OneSixVersionFormat::libraryToJson(value->library().get());
|
||||
if (!value->argument().isEmpty())
|
||||
agentOut.insert("argument", value->argument());
|
||||
|
||||
array.append(agentOut);
|
||||
}
|
||||
root.insert("+agents", array);
|
||||
}
|
||||
if (!patch->libraries.isEmpty())
|
||||
{
|
||||
QJsonArray array;
|
||||
|
@ -1,7 +1,8 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -47,7 +48,6 @@
|
||||
#include "Exception.h"
|
||||
#include "minecraft/OneSixVersionFormat.h"
|
||||
#include "FileSystem.h"
|
||||
#include "meta/Index.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "Json.h"
|
||||
|
||||
@ -55,7 +55,6 @@
|
||||
#include "PackProfile_p.h"
|
||||
#include "ComponentUpdateTask.h"
|
||||
|
||||
#include "Application.h"
|
||||
#include "modplatform/ModAPI.h"
|
||||
|
||||
static const QMap<QString, ModAPI::ModLoaderType> modloaderMapping{
|
||||
@ -613,7 +612,7 @@ QVariant PackProfile::data(const QModelIndex &index, int role) const
|
||||
|
||||
bool PackProfile::setData(const QModelIndex& index, const QVariant& value, int role)
|
||||
{
|
||||
if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index))
|
||||
if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index.parent()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -675,12 +674,12 @@ Qt::ItemFlags PackProfile::flags(const QModelIndex &index) const
|
||||
|
||||
int PackProfile::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return d->components.size();
|
||||
return parent.isValid() ? 0 : d->components.size();
|
||||
}
|
||||
|
||||
int PackProfile::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return NUM_COLUMNS;
|
||||
return parent.isValid() ? 0 : NUM_COLUMNS;
|
||||
}
|
||||
|
||||
void PackProfile::move(const int index, const MoveDirection direction)
|
||||
@ -738,6 +737,11 @@ void PackProfile::installCustomJar(QString selectedFile)
|
||||
installCustomJar_internal(selectedFile);
|
||||
}
|
||||
|
||||
void PackProfile::installAgents(QStringList selectedFiles)
|
||||
{
|
||||
installAgents_internal(selectedFiles);
|
||||
}
|
||||
|
||||
bool PackProfile::installEmpty(const QString& uid, const QString& name)
|
||||
{
|
||||
QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches");
|
||||
@ -832,18 +836,14 @@ bool PackProfile::installJarMods_internal(QStringList filepaths)
|
||||
for(auto filepath:filepaths)
|
||||
{
|
||||
QFileInfo sourceInfo(filepath);
|
||||
auto uuid = QUuid::createUuid();
|
||||
QString id = uuid.toString().remove('{').remove('}');
|
||||
QString id = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
QString target_filename = id + ".jar";
|
||||
QString target_id = "org.multimc.jarmod." + id;
|
||||
QString target_id = "custom.jarmod." + id;
|
||||
QString target_name = sourceInfo.completeBaseName() + " (jar mod)";
|
||||
QString finalPath = FS::PathCombine(d->m_instance->jarModsDir(), target_filename);
|
||||
|
||||
QFileInfo targetInfo(finalPath);
|
||||
if(targetInfo.exists())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Q_ASSERT(!targetInfo.exists());
|
||||
|
||||
if (!QFile::copy(sourceInfo.absoluteFilePath(),QFileInfo(finalPath).absoluteFilePath()))
|
||||
{
|
||||
@ -852,7 +852,7 @@ bool PackProfile::installJarMods_internal(QStringList filepaths)
|
||||
|
||||
auto f = std::make_shared<VersionFile>();
|
||||
auto jarMod = std::make_shared<Library>();
|
||||
jarMod->setRawName(GradleSpecifier("org.multimc.jarmods:" + id + ":1"));
|
||||
jarMod->setRawName(GradleSpecifier("custom.jarmods:" + id + ":1"));
|
||||
jarMod->setFilename(target_filename);
|
||||
jarMod->setDisplayName(sourceInfo.completeBaseName());
|
||||
jarMod->setHint("local");
|
||||
@ -892,7 +892,7 @@ bool PackProfile::installCustomJar_internal(QString filepath)
|
||||
return false;
|
||||
}
|
||||
|
||||
auto specifier = GradleSpecifier("org.multimc:customjar:1");
|
||||
auto specifier = GradleSpecifier("custom:customjar:1");
|
||||
QFileInfo sourceInfo(filepath);
|
||||
QString target_filename = specifier.getFileName();
|
||||
QString target_id = specifier.artifactId();
|
||||
@ -939,6 +939,64 @@ bool PackProfile::installCustomJar_internal(QString filepath)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PackProfile::installAgents_internal(QStringList filepaths)
|
||||
{
|
||||
// FIXME code duplication
|
||||
const QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches");
|
||||
if (!FS::ensureFolderPathExists(patchDir))
|
||||
return false;
|
||||
|
||||
const QString libDir = d->m_instance->getLocalLibraryPath();
|
||||
if (!FS::ensureFolderPathExists(libDir))
|
||||
return false;
|
||||
|
||||
for (const QString& source : filepaths) {
|
||||
const QFileInfo sourceInfo(source);
|
||||
const QString id = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
const QString targetBaseName = id + ".jar";
|
||||
const QString targetId = "custom.agent." + id;
|
||||
const QString targetName = sourceInfo.completeBaseName() + " (agent)";
|
||||
const QString target = FS::PathCombine(d->m_instance->getLocalLibraryPath(), targetBaseName);
|
||||
|
||||
const QFileInfo targetInfo(target);
|
||||
Q_ASSERT(!targetInfo.exists());
|
||||
|
||||
if (!QFile::copy(source, target))
|
||||
return false;
|
||||
|
||||
auto versionFile = std::make_shared<VersionFile>();
|
||||
|
||||
auto agent = std::make_shared<Library>();
|
||||
|
||||
agent->setRawName("custom.agents:" + id + ":1");
|
||||
agent->setFilename(targetBaseName);
|
||||
agent->setDisplayName(sourceInfo.completeBaseName());
|
||||
agent->setHint("local");
|
||||
|
||||
versionFile->agents.append(std::make_shared<Agent>(agent, QString()));
|
||||
|
||||
versionFile->name = targetName;
|
||||
versionFile->uid = targetId;
|
||||
|
||||
QFile patchFile(FS::PathCombine(patchDir, targetId + ".json"));
|
||||
|
||||
if (!patchFile.open(QFile::WriteOnly)) {
|
||||
qCritical() << "Error opening" << patchFile.fileName() << "for reading:" << patchFile.errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
patchFile.write(OneSixVersionFormat::versionFileToJson(versionFile).toJson());
|
||||
patchFile.close();
|
||||
|
||||
appendComponent(new Component(this, versionFile->uid, versionFile));
|
||||
}
|
||||
|
||||
scheduleSave();
|
||||
invalidateLaunchProfile();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<LaunchProfile> PackProfile::getProfile() const
|
||||
{
|
||||
if(!d->m_profile)
|
||||
|
@ -1,7 +1,8 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -85,6 +86,9 @@ public:
|
||||
/// install a jar/zip as a replacement for the main jar
|
||||
void installCustomJar(QString selectedFile);
|
||||
|
||||
/// install Java agent files
|
||||
void installAgents(QStringList selectedFiles);
|
||||
|
||||
enum MoveDirection { MoveUp, MoveDown };
|
||||
/// move component file # up or down the list
|
||||
void move(const int index, const MoveDirection direction);
|
||||
@ -167,6 +171,7 @@ private:
|
||||
bool load();
|
||||
bool installJarMods_internal(QStringList filepaths);
|
||||
bool installCustomJar_internal(QString filepath);
|
||||
bool installAgents_internal(QStringList filepaths);
|
||||
bool removeComponent_internal(ComponentPtr patch);
|
||||
|
||||
private: /* data */
|
||||
|
@ -104,7 +104,7 @@ public:
|
||||
class ImplicitRule : public Rule
|
||||
{
|
||||
protected:
|
||||
virtual bool applies(const Library *, const RuntimeContext & runtimeContext)
|
||||
virtual bool applies(const Library *, [[maybe_unused]] const RuntimeContext & runtimeContext)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
|
||||
VanillaCreationTask::VanillaCreationTask(BaseVersionPtr version, QString loader, BaseVersionPtr loader_version)
|
||||
VanillaCreationTask::VanillaCreationTask(BaseVersion::Ptr version, QString loader, BaseVersion::Ptr loader_version)
|
||||
: InstanceCreationTask(), m_version(std::move(version)), m_using_loader(true), m_loader(std::move(loader)), m_loader_version(std::move(loader_version))
|
||||
{}
|
||||
|
||||
|
@ -7,16 +7,16 @@
|
||||
class VanillaCreationTask final : public InstanceCreationTask {
|
||||
Q_OBJECT
|
||||
public:
|
||||
VanillaCreationTask(BaseVersionPtr version) : InstanceCreationTask(), m_version(std::move(version)) {}
|
||||
VanillaCreationTask(BaseVersionPtr version, QString loader, BaseVersionPtr loader_version);
|
||||
VanillaCreationTask(BaseVersion::Ptr version) : InstanceCreationTask(), m_version(std::move(version)) {}
|
||||
VanillaCreationTask(BaseVersion::Ptr version, QString loader, BaseVersion::Ptr loader_version);
|
||||
|
||||
bool createInstance() override;
|
||||
|
||||
private:
|
||||
// Version to update to / create of the instance.
|
||||
BaseVersionPtr m_version;
|
||||
BaseVersion::Ptr m_version;
|
||||
|
||||
bool m_using_loader = false;
|
||||
QString m_loader;
|
||||
BaseVersionPtr m_loader_version;
|
||||
BaseVersion::Ptr m_loader_version;
|
||||
};
|
||||
|
@ -173,7 +173,7 @@ bool WorldList::resetIcon(int row)
|
||||
|
||||
int WorldList::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return 4;
|
||||
return parent.isValid()? 0 : 4;
|
||||
}
|
||||
|
||||
QVariant WorldList::data(const QModelIndex &index, int role) const
|
||||
@ -398,8 +398,8 @@ void WorldList::installWorld(QFileInfo filename)
|
||||
w.install(m_dir.absolutePath());
|
||||
}
|
||||
|
||||
bool WorldList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
|
||||
const QModelIndex &parent)
|
||||
bool WorldList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[maybe_unused]] int row, [[maybe_unused]] int column,
|
||||
[[maybe_unused]] const QModelIndex &parent)
|
||||
{
|
||||
if (action == Qt::IgnoreAction)
|
||||
return true;
|
||||
|
@ -54,7 +54,7 @@ public:
|
||||
|
||||
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const
|
||||
{
|
||||
return size();
|
||||
return parent.isValid() ? 0 : static_cast<int>(size());
|
||||
};
|
||||
virtual QVariant headerData(int section, Qt::Orientation orientation,
|
||||
int role = Qt::DisplayRole) const;
|
||||
|
@ -408,20 +408,20 @@ QVariant AccountList::headerData(int section, Qt::Orientation orientation, int r
|
||||
}
|
||||
}
|
||||
|
||||
int AccountList::rowCount(const QModelIndex &) const
|
||||
int AccountList::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
// Return count
|
||||
return count();
|
||||
return parent.isValid() ? 0 : count();
|
||||
}
|
||||
|
||||
int AccountList::columnCount(const QModelIndex &) const
|
||||
int AccountList::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return NUM_COLUMNS;
|
||||
return parent.isValid() ? 0 : NUM_COLUMNS;
|
||||
}
|
||||
|
||||
Qt::ItemFlags AccountList::flags(const QModelIndex &index) const
|
||||
{
|
||||
if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
|
||||
if (index.row() < 0 || index.row() >= rowCount(index.parent()) || !index.isValid())
|
||||
{
|
||||
return Qt::NoItemFlags;
|
||||
}
|
||||
|
@ -71,5 +71,7 @@ void VerifyJavaInstall::executeTask() {
|
||||
{
|
||||
emit logLine(tr("Java version %1").arg(major), MessageLevel::Error);
|
||||
}
|
||||
emit logLine(tr("Go to instance Java settings to change your Java version or disable the Java compatibility check if you know what you're doing."), MessageLevel::Error);
|
||||
|
||||
emitFailed(QString("Incompatible Java major version"));
|
||||
}
|
||||
|
@ -144,7 +144,7 @@ QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, in
|
||||
|
||||
int ModFolderModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return NUM_COLUMNS;
|
||||
return parent.isValid() ? 0 : NUM_COLUMNS;
|
||||
}
|
||||
|
||||
Task* ModFolderModel::createUpdateTask()
|
||||
|
@ -426,7 +426,7 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
|
||||
bool ResourceFolderModel::setData(const QModelIndex& index, const QVariant& value, int role)
|
||||
{
|
||||
int row = index.row();
|
||||
if (row < 0 || row >= rowCount(index) || !index.isValid())
|
||||
if (row < 0 || row >= rowCount(index.parent()) || !index.isValid())
|
||||
return false;
|
||||
|
||||
if (role == Qt::CheckStateRole)
|
||||
|
@ -90,8 +90,8 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||
/* Basic columns */
|
||||
enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, NUM_COLUMNS };
|
||||
|
||||
[[nodiscard]] int rowCount(const QModelIndex& = {}) const override { return size(); }
|
||||
[[nodiscard]] int columnCount(const QModelIndex& = {}) const override { return NUM_COLUMNS; };
|
||||
[[nodiscard]] int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast<int>(size()); }
|
||||
[[nodiscard]] int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NUM_COLUMNS; };
|
||||
|
||||
[[nodiscard]] Qt::DropActions supportedDropActions() const override;
|
||||
|
||||
@ -176,7 +176,7 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||
* if the resource is complex and has more stuff to parse.
|
||||
*/
|
||||
virtual void onParseSucceeded(int ticket, QString resource_id);
|
||||
virtual void onParseFailed(int ticket, QString resource_id) {}
|
||||
virtual void onParseFailed(int ticket, QString resource_id) { Q_UNUSED(ticket); Q_UNUSED(resource_id); }
|
||||
|
||||
protected:
|
||||
// Represents the relationship between a column's index (represented by the list index), and it's sorting key.
|
||||
|
@ -15,7 +15,7 @@ static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
|
||||
{ 3, { Version("1.11"), Version("1.12.2") } }, { 4, { Version("1.13"), Version("1.14.4") } },
|
||||
{ 5, { Version("1.15"), Version("1.16.1") } }, { 6, { Version("1.16.2"), Version("1.16.5") } },
|
||||
{ 7, { Version("1.17"), Version("1.17.1") } }, { 8, { Version("1.18"), Version("1.18.2") } },
|
||||
{ 9, { Version("1.19"), Version("1.19.2") } },
|
||||
{ 9, { Version("1.19"), Version("1.19.2") } }, { 11, { Version("1.19.3"), Version("1.19.3") } },
|
||||
};
|
||||
|
||||
void ResourcePack::setPackFormat(int new_format_id)
|
||||
@ -114,3 +114,8 @@ bool ResourcePack::applyFilter(QRegularExpression filter) const
|
||||
|
||||
return Resource::applyFilter(filter);
|
||||
}
|
||||
|
||||
bool ResourcePack::valid() const
|
||||
{
|
||||
return m_pack_format != 0;
|
||||
}
|
||||
|
@ -42,6 +42,8 @@ class ResourcePack : public Resource {
|
||||
/** Thread-safe. */
|
||||
void setImage(QImage new_image);
|
||||
|
||||
bool valid() const override;
|
||||
|
||||
[[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair<int, bool> override;
|
||||
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
|
||||
|
||||
|
@ -137,7 +137,7 @@ QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orient
|
||||
|
||||
int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const
|
||||
{
|
||||
return NUM_COLUMNS;
|
||||
return parent.isValid() ? 0 : NUM_COLUMNS;
|
||||
}
|
||||
|
||||
Task* ResourcePackFolderModel::createUpdateTask()
|
||||
|
@ -62,3 +62,8 @@ QPixmap TexturePack::image(QSize size)
|
||||
TexturePackUtils::process(*this);
|
||||
return image(size);
|
||||
}
|
||||
|
||||
bool TexturePack::valid() const
|
||||
{
|
||||
return m_description != nullptr;
|
||||
}
|
||||
|
@ -48,6 +48,8 @@ class TexturePack : public Resource {
|
||||
/** Thread-safe. */
|
||||
void setImage(QImage new_image);
|
||||
|
||||
bool valid() const override;
|
||||
|
||||
protected:
|
||||
mutable QMutex m_data_lock;
|
||||
|
||||
|
@ -121,7 +121,7 @@ ModDetails ReadMCModTOML(QByteArray contents)
|
||||
return {};
|
||||
}
|
||||
auto modsTable = tomlModsTable0->as_table();
|
||||
if (!tomlModsTable0) {
|
||||
if (!modsTable) {
|
||||
qWarning() << "Corrupted mods.toml? [[mods]] was not a table!";
|
||||
return {};
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ LocalModUpdateTask::LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack&
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
SetFileAttributesA(index_dir.path().toStdString().c_str(), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED);
|
||||
SetFileAttributesW(index_dir.path().toStdWString().c_str(), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -28,14 +28,14 @@
|
||||
|
||||
namespace ResourcePackUtils {
|
||||
|
||||
bool process(ResourcePack& pack)
|
||||
bool process(ResourcePack& pack, ProcessingLevel level)
|
||||
{
|
||||
switch (pack.type()) {
|
||||
case ResourceType::FOLDER:
|
||||
ResourcePackUtils::processFolder(pack);
|
||||
ResourcePackUtils::processFolder(pack, level);
|
||||
return true;
|
||||
case ResourceType::ZIPFILE:
|
||||
ResourcePackUtils::processZIP(pack);
|
||||
ResourcePackUtils::processZIP(pack, level);
|
||||
return true;
|
||||
default:
|
||||
qWarning() << "Invalid type for resource pack parse task!";
|
||||
@ -43,7 +43,7 @@ bool process(ResourcePack& pack)
|
||||
}
|
||||
}
|
||||
|
||||
void processFolder(ResourcePack& pack)
|
||||
void processFolder(ResourcePack& pack, ProcessingLevel level)
|
||||
{
|
||||
Q_ASSERT(pack.type() == ResourceType::FOLDER);
|
||||
|
||||
@ -60,6 +60,9 @@ void processFolder(ResourcePack& pack)
|
||||
mcmeta_file.close();
|
||||
}
|
||||
|
||||
if (level == ProcessingLevel::BasicInfoOnly)
|
||||
return;
|
||||
|
||||
QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
|
||||
if (image_file_info.isFile()) {
|
||||
QFile mcmeta_file(image_file_info.filePath());
|
||||
@ -74,7 +77,7 @@ void processFolder(ResourcePack& pack)
|
||||
}
|
||||
}
|
||||
|
||||
void processZIP(ResourcePack& pack)
|
||||
void processZIP(ResourcePack& pack, ProcessingLevel level)
|
||||
{
|
||||
Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
|
||||
|
||||
@ -98,6 +101,11 @@ void processZIP(ResourcePack& pack)
|
||||
file.close();
|
||||
}
|
||||
|
||||
if (level == ProcessingLevel::BasicInfoOnly) {
|
||||
zip.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (zip.setCurrentFile("pack.png")) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Failed to open file in zip.";
|
||||
@ -138,6 +146,13 @@ void processPackPNG(ResourcePack& pack, QByteArray&& raw_data)
|
||||
qWarning() << "Failed to parse pack.png.";
|
||||
}
|
||||
}
|
||||
|
||||
bool validate(QFileInfo file)
|
||||
{
|
||||
ResourcePack rp{ file };
|
||||
return ResourcePackUtils::process(rp, ProcessingLevel::BasicInfoOnly) && rp.valid();
|
||||
}
|
||||
|
||||
} // namespace ResourcePackUtils
|
||||
|
||||
LocalResourcePackParseTask::LocalResourcePackParseTask(int token, ResourcePack& rp)
|
||||
@ -152,8 +167,6 @@ bool LocalResourcePackParseTask::abort()
|
||||
|
||||
void LocalResourcePackParseTask::executeTask()
|
||||
{
|
||||
Q_ASSERT(m_resource_pack.valid());
|
||||
|
||||
if (!ResourcePackUtils::process(m_resource_pack))
|
||||
return;
|
||||
|
||||
|
@ -26,13 +26,19 @@
|
||||
#include "tasks/Task.h"
|
||||
|
||||
namespace ResourcePackUtils {
|
||||
bool process(ResourcePack& pack);
|
||||
|
||||
void processZIP(ResourcePack& pack);
|
||||
void processFolder(ResourcePack& pack);
|
||||
enum class ProcessingLevel { Full, BasicInfoOnly };
|
||||
|
||||
bool process(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
|
||||
void processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
void processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
|
||||
void processMCMeta(ResourcePack& pack, QByteArray&& raw_data);
|
||||
void processPackPNG(ResourcePack& pack, QByteArray&& raw_data);
|
||||
|
||||
/** Checks whether a file is valid as a resource pack or not. */
|
||||
bool validate(QFileInfo file);
|
||||
} // namespace ResourcePackUtils
|
||||
|
||||
class LocalResourcePackParseTask : public Task {
|
||||
|
@ -28,14 +28,14 @@
|
||||
|
||||
namespace TexturePackUtils {
|
||||
|
||||
bool process(TexturePack& pack)
|
||||
bool process(TexturePack& pack, ProcessingLevel level)
|
||||
{
|
||||
switch (pack.type()) {
|
||||
case ResourceType::FOLDER:
|
||||
TexturePackUtils::processFolder(pack);
|
||||
TexturePackUtils::processFolder(pack, level);
|
||||
return true;
|
||||
case ResourceType::ZIPFILE:
|
||||
TexturePackUtils::processZIP(pack);
|
||||
TexturePackUtils::processZIP(pack, level);
|
||||
return true;
|
||||
default:
|
||||
qWarning() << "Invalid type for resource pack parse task!";
|
||||
@ -43,7 +43,7 @@ bool process(TexturePack& pack)
|
||||
}
|
||||
}
|
||||
|
||||
void processFolder(TexturePack& pack)
|
||||
void processFolder(TexturePack& pack, ProcessingLevel level)
|
||||
{
|
||||
Q_ASSERT(pack.type() == ResourceType::FOLDER);
|
||||
|
||||
@ -60,6 +60,9 @@ void processFolder(TexturePack& pack)
|
||||
mcmeta_file.close();
|
||||
}
|
||||
|
||||
if (level == ProcessingLevel::BasicInfoOnly)
|
||||
return;
|
||||
|
||||
QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
|
||||
if (image_file_info.isFile()) {
|
||||
QFile mcmeta_file(image_file_info.filePath());
|
||||
@ -74,7 +77,7 @@ void processFolder(TexturePack& pack)
|
||||
}
|
||||
}
|
||||
|
||||
void processZIP(TexturePack& pack)
|
||||
void processZIP(TexturePack& pack, ProcessingLevel level)
|
||||
{
|
||||
Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
|
||||
|
||||
@ -98,6 +101,11 @@ void processZIP(TexturePack& pack)
|
||||
file.close();
|
||||
}
|
||||
|
||||
if (level == ProcessingLevel::BasicInfoOnly) {
|
||||
zip.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (zip.setCurrentFile("pack.png")) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Failed to open file in zip.";
|
||||
@ -129,6 +137,13 @@ void processPackPNG(TexturePack& pack, QByteArray&& raw_data)
|
||||
qWarning() << "Failed to parse pack.png.";
|
||||
}
|
||||
}
|
||||
|
||||
bool validate(QFileInfo file)
|
||||
{
|
||||
TexturePack rp{ file };
|
||||
return TexturePackUtils::process(rp, ProcessingLevel::BasicInfoOnly) && rp.valid();
|
||||
}
|
||||
|
||||
} // namespace TexturePackUtils
|
||||
|
||||
LocalTexturePackParseTask::LocalTexturePackParseTask(int token, TexturePack& rp)
|
||||
@ -143,8 +158,6 @@ bool LocalTexturePackParseTask::abort()
|
||||
|
||||
void LocalTexturePackParseTask::executeTask()
|
||||
{
|
||||
Q_ASSERT(m_texture_pack.valid());
|
||||
|
||||
if (!TexturePackUtils::process(m_texture_pack))
|
||||
return;
|
||||
|
||||
|
@ -27,13 +27,19 @@
|
||||
#include "tasks/Task.h"
|
||||
|
||||
namespace TexturePackUtils {
|
||||
bool process(TexturePack& pack);
|
||||
|
||||
void processZIP(TexturePack& pack);
|
||||
void processFolder(TexturePack& pack);
|
||||
enum class ProcessingLevel { Full, BasicInfoOnly };
|
||||
|
||||
bool process(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
|
||||
void processZIP(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
void processFolder(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
|
||||
void processPackTXT(TexturePack& pack, QByteArray&& raw_data);
|
||||
void processPackPNG(TexturePack& pack, QByteArray&& raw_data);
|
||||
|
||||
/** Checks whether a file is valid as a texture pack or not. */
|
||||
bool validate(QFileInfo file);
|
||||
} // namespace TexturePackUtils
|
||||
|
||||
class LocalTexturePackParseTask : public Task {
|
||||
|
@ -39,7 +39,7 @@
|
||||
#include <QList>
|
||||
#include <list>
|
||||
|
||||
#include "Version.h"
|
||||
#include "../Version.h"
|
||||
#include "net/NetJob.h"
|
||||
|
||||
namespace ModPlatform {
|
||||
|
@ -58,7 +58,7 @@
|
||||
|
||||
namespace ATLauncher {
|
||||
|
||||
static Meta::VersionPtr getComponentVersion(const QString& uid, const QString& version);
|
||||
static Meta::Version::Ptr getComponentVersion(const QString& uid, const QString& version);
|
||||
|
||||
PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString packName, QString version, InstallMode installMode)
|
||||
{
|
||||
@ -1037,7 +1037,7 @@ void PackInstallTask::install()
|
||||
emitSucceeded();
|
||||
}
|
||||
|
||||
static Meta::VersionPtr getComponentVersion(const QString& uid, const QString& version)
|
||||
static Meta::Version::Ptr getComponentVersion(const QString& uid, const QString& version)
|
||||
{
|
||||
auto vlist = APPLICATION->metadataIndex()->get(uid);
|
||||
if (!vlist)
|
||||
|
@ -68,7 +68,7 @@ public:
|
||||
* Requests a user interaction to select a component version from a given version list
|
||||
* and constrained to a given Minecraft version.
|
||||
*/
|
||||
virtual QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) = 0;
|
||||
virtual QString chooseVersion(Meta::VersionList::Ptr vlist, QString minecraftVersion) = 0;
|
||||
|
||||
/**
|
||||
* Requests a user interaction to display a message.
|
||||
@ -137,8 +137,8 @@ private:
|
||||
|
||||
QString archivePath;
|
||||
QStringList jarmods;
|
||||
Meta::VersionPtr minecraftVersion;
|
||||
QMap<QString, Meta::VersionPtr> componentsToInstall;
|
||||
Meta::Version::Ptr minecraftVersion;
|
||||
QMap<QString, Meta::Version::Ptr> componentsToInstall;
|
||||
|
||||
QFuture<std::optional<QStringList>> m_extractFuture;
|
||||
QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;
|
||||
|
@ -3,6 +3,8 @@
|
||||
#include "Json.h"
|
||||
#include "net/Upload.h"
|
||||
|
||||
#include "modplatform/modrinth/ModrinthPackIndex.h"
|
||||
|
||||
Flame::FileResolvingTask::FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest& toProcess)
|
||||
: m_network(network), m_toProcess(toProcess)
|
||||
{}
|
||||
@ -40,12 +42,25 @@ void Flame::FileResolvingTask::executeTask()
|
||||
void Flame::FileResolvingTask::netJobFinished()
|
||||
{
|
||||
setProgress(1, 3);
|
||||
int index = 0;
|
||||
// job to check modrinth for blocked projects
|
||||
m_checkJob = new NetJob("Modrinth check", m_network);
|
||||
blockedProjects = QMap<File *,QByteArray *>();
|
||||
auto doc = Json::requireDocument(*result);
|
||||
auto array = Json::requireArray(doc.object()["data"]);
|
||||
|
||||
QJsonDocument doc;
|
||||
QJsonArray array;
|
||||
|
||||
try {
|
||||
doc = Json::requireDocument(*result);
|
||||
array = Json::requireArray(doc.object()["data"]);
|
||||
} catch (Json::JsonException& e) {
|
||||
qCritical() << "Non-JSON data returned from the CF API";
|
||||
qCritical() << e.cause();
|
||||
|
||||
emitFailed(tr("Invalid data returned from the API."));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (QJsonValueRef file : array) {
|
||||
auto fileid = Json::requireInteger(Json::requireObject(file)["id"]);
|
||||
auto& out = m_toProcess.files[fileid];
|
||||
@ -66,7 +81,6 @@ void Flame::FileResolvingTask::netJobFinished()
|
||||
blockedProjects.insert(&out, output);
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
connect(m_checkJob.get(), &NetJob::finished, this, &Flame::FileResolvingTask::modrinthCheckFinished);
|
||||
|
||||
@ -84,18 +98,21 @@ void Flame::FileResolvingTask::modrinthCheckFinished() {
|
||||
delete bytes;
|
||||
continue;
|
||||
}
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(*bytes);
|
||||
auto obj = doc.object();
|
||||
auto array = Json::requireArray(obj,"files");
|
||||
for (auto file: array) {
|
||||
auto fileObj = Json::requireObject(file);
|
||||
auto primary = Json::requireBoolean(fileObj,"primary");
|
||||
if (primary) {
|
||||
out->url = Json::requireUrl(fileObj,"url");
|
||||
qDebug() << "Found alternative on modrinth " << out->fileName;
|
||||
break;
|
||||
}
|
||||
auto file = Modrinth::loadIndexedPackVersion(obj);
|
||||
|
||||
// If there's more than one mod loader for this version, we can't know for sure
|
||||
// which file is relative to each loader, so it's best to not use any one and
|
||||
// let the user download it manually.
|
||||
if (file.loaders.size() <= 1) {
|
||||
out->url = file.downloadUrl;
|
||||
qDebug() << "Found alternative on modrinth " << out->fileName;
|
||||
} else {
|
||||
out->resolved = false;
|
||||
}
|
||||
|
||||
delete bytes;
|
||||
}
|
||||
//copy to an output list and filter out projects found on modrinth
|
||||
|
@ -1,3 +1,38 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
*
|
||||
* 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 "FlameInstanceCreationTask.h"
|
||||
|
||||
#include "modplatform/flame/FlameAPI.h"
|
||||
@ -72,7 +107,8 @@ bool FlameCreationTask::updateInstance()
|
||||
tr("One or more of your instances are from this same modpack%1. Do you want to create a "
|
||||
"separate instance, or update the existing one?\n\nNOTE: Make sure you made a backup of your important instance data before "
|
||||
"updating, as worlds can be corrupted and some configuration may be lost (due to pack overrides).")
|
||||
.arg(version_str), QMessageBox::Information, QMessageBox::Ok | QMessageBox::Reset | QMessageBox::Abort);
|
||||
.arg(version_str),
|
||||
QMessageBox::Information, QMessageBox::Ok | QMessageBox::Reset | QMessageBox::Abort);
|
||||
info->setButtonText(QMessageBox::Ok, tr("Update existing instance"));
|
||||
info->setButtonText(QMessageBox::Abort, tr("Create new instance"));
|
||||
info->setButtonText(QMessageBox::Reset, tr("Cancel"));
|
||||
@ -197,10 +233,10 @@ bool FlameCreationTask::updateInstance()
|
||||
m_process_update_file_info_job = nullptr;
|
||||
} else {
|
||||
// We don't have an old index file, so we may duplicate stuff!
|
||||
auto dialog = CustomMessageBox::selectable(m_parent,
|
||||
tr("No index file."),
|
||||
tr("We couldn't find a suitable index file for the older version. This may cause some of the files to be duplicated. Do you want to continue?"),
|
||||
QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel);
|
||||
auto dialog = CustomMessageBox::selectable(m_parent, tr("No index file."),
|
||||
tr("We couldn't find a suitable index file for the older version. This may cause some "
|
||||
"of the files to be duplicated. Do you want to continue?"),
|
||||
QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel);
|
||||
|
||||
if (dialog->exec() == QDialog::DialogCode::Rejected) {
|
||||
m_abort = true;
|
||||
@ -338,6 +374,7 @@ bool FlameCreationTask::createInstance()
|
||||
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) {
|
||||
m_mod_id_resolver.reset();
|
||||
setError(tr("Unable to resolve mod IDs:\n") + reason);
|
||||
loop.quit();
|
||||
});
|
||||
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::progress, this, &FlameCreationTask::setProgress);
|
||||
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::status, this, &FlameCreationTask::setStatus);
|
||||
@ -372,27 +409,35 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
|
||||
auto results = m_mod_id_resolver->getResults();
|
||||
|
||||
// first check for blocked mods
|
||||
QString text;
|
||||
QList<QUrl> urls;
|
||||
QList<BlockedMod> blocked_mods;
|
||||
auto anyBlocked = false;
|
||||
for (const auto& result : results.files.values()) {
|
||||
if (!result.resolved || result.url.isEmpty()) {
|
||||
text += QString("%1: <a href='%2'>%2</a><br/>").arg(result.fileName, result.websiteUrl);
|
||||
urls.append(QUrl(result.websiteUrl));
|
||||
BlockedMod blocked_mod;
|
||||
blocked_mod.name = result.fileName;
|
||||
blocked_mod.websiteUrl = result.websiteUrl;
|
||||
blocked_mod.hash = result.hash;
|
||||
blocked_mod.matched = false;
|
||||
blocked_mod.localPath = "";
|
||||
|
||||
blocked_mods.append(blocked_mod);
|
||||
|
||||
anyBlocked = true;
|
||||
}
|
||||
}
|
||||
if (anyBlocked) {
|
||||
qWarning() << "Blocked mods found, displaying mod list";
|
||||
|
||||
auto message_dialog = new BlockedModsDialog(m_parent, tr("Blocked mods found"),
|
||||
tr("The following mods were blocked on third party launchers.<br/>"
|
||||
"You will need to manually download them and add them to the modpack"),
|
||||
text,
|
||||
urls);
|
||||
message_dialog->setModal(true);
|
||||
BlockedModsDialog message_dialog(m_parent, tr("Blocked mods found"),
|
||||
tr("The following files are not available for download in third party launchers.<br/>"
|
||||
"You will need to manually download them and add them to the instance."),
|
||||
blocked_mods);
|
||||
|
||||
if (message_dialog->exec()) {
|
||||
message_dialog.setModal(true);
|
||||
|
||||
if (message_dialog.exec()) {
|
||||
qDebug() << "Post dialog blocked mods list: " << blocked_mods;
|
||||
copyBlockedMods(blocked_mods);
|
||||
setupDownloadJob(loop);
|
||||
} else {
|
||||
m_mod_id_resolver.reset();
|
||||
@ -404,6 +449,38 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief copy the matched blocked mods to the instance staging area
|
||||
/// @param blocked_mods list of the blocked mods and their matched paths
|
||||
void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods)
|
||||
{
|
||||
setStatus(tr("Copying Blocked Mods..."));
|
||||
setAbortable(false);
|
||||
int i = 0;
|
||||
int total = blocked_mods.length();
|
||||
setProgress(i, total);
|
||||
for (auto const& mod : blocked_mods) {
|
||||
if (!mod.matched) {
|
||||
qDebug() << mod.name << "was not matched to a local file, skipping copy";
|
||||
continue;
|
||||
}
|
||||
|
||||
auto dest_path = FS::PathCombine(m_stagingPath, "minecraft", "mods", mod.name);
|
||||
|
||||
setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total)));
|
||||
|
||||
qDebug() << "Will try to copy" << mod.localPath << "to" << dest_path;
|
||||
|
||||
if (!FS::copy(mod.localPath, dest_path)()) {
|
||||
qDebug() << "Copy of" << mod.localPath << "to" << dest_path << "Failed";
|
||||
}
|
||||
|
||||
i++;
|
||||
setProgress(i, total);
|
||||
}
|
||||
|
||||
setAbortable(true);
|
||||
}
|
||||
|
||||
void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
|
||||
{
|
||||
m_files_job = new NetJob(tr("Mod download"), APPLICATION->network());
|
||||
@ -442,14 +519,12 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
|
||||
}
|
||||
|
||||
m_mod_id_resolver.reset();
|
||||
connect(m_files_job.get(), &NetJob::succeeded, this, [&]() {
|
||||
m_files_job.reset();
|
||||
});
|
||||
connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { m_files_job.reset(); });
|
||||
connect(m_files_job.get(), &NetJob::failed, [&](QString reason) {
|
||||
m_files_job.reset();
|
||||
setError(reason);
|
||||
});
|
||||
connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) { setProgress(current, total); });
|
||||
connect(m_files_job.get(), &NetJob::progress, this, &FlameCreationTask::setProgress);
|
||||
connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit);
|
||||
|
||||
setStatus(tr("Downloading mods..."));
|
||||
|
@ -1,3 +1,38 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
*
|
||||
* 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 "InstanceCreationTask.h"
|
||||
@ -10,6 +45,8 @@
|
||||
|
||||
#include "net/NetJob.h"
|
||||
|
||||
#include "ui/dialogs/BlockedModsDialog.h"
|
||||
|
||||
class FlameCreationTask final : public InstanceCreationTask {
|
||||
Q_OBJECT
|
||||
|
||||
@ -29,6 +66,7 @@ class FlameCreationTask final : public InstanceCreationTask {
|
||||
private slots:
|
||||
void idResolverSucceeded(QEventLoop&);
|
||||
void setupDownloadJob(QEventLoop&);
|
||||
void copyBlockedMods(QList<BlockedMod> const& blocked_mods);
|
||||
|
||||
private:
|
||||
QWidget* m_parent = nullptr;
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include <QFile>
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include "StringUtils.h"
|
||||
|
||||
#include <MurmurHash2.h>
|
||||
|
||||
@ -35,6 +36,18 @@ Hasher::Ptr createFlameHasher(QString file_path)
|
||||
return new FlameHasher(file_path);
|
||||
}
|
||||
|
||||
Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider)
|
||||
{
|
||||
return new BlockedModHasher(file_path, provider);
|
||||
}
|
||||
|
||||
Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider, QString type)
|
||||
{
|
||||
auto hasher = new BlockedModHasher(file_path, provider);
|
||||
hasher->useHashType(type);
|
||||
return hasher;
|
||||
}
|
||||
|
||||
void ModrinthHasher::executeTask()
|
||||
{
|
||||
QFile file(m_path);
|
||||
@ -66,7 +79,7 @@ void FlameHasher::executeTask()
|
||||
// CF-specific
|
||||
auto should_filter_out = [](char c) { return (c == 9 || c == 10 || c == 13 || c == 32); };
|
||||
|
||||
std::ifstream file_stream(m_path.toStdString(), std::ifstream::binary);
|
||||
std::ifstream file_stream(StringUtils::toStdString(m_path), std::ifstream::binary);
|
||||
// TODO: This is very heavy work, but apparently QtConcurrent can't use move semantics, so we can't boop this to another thread.
|
||||
// How do we make this non-blocking then?
|
||||
m_hash = QString::number(MurmurHash2(std::move(file_stream), 4 * MiB, should_filter_out));
|
||||
@ -78,4 +91,50 @@ void FlameHasher::executeTask()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BlockedModHasher::BlockedModHasher(QString file_path, ModPlatform::Provider provider)
|
||||
: Hasher(file_path), provider(provider) {
|
||||
setObjectName(QString("BlockedModHasher: %1").arg(file_path));
|
||||
hash_type = ProviderCaps.hashType(provider).first();
|
||||
}
|
||||
|
||||
void BlockedModHasher::executeTask()
|
||||
{
|
||||
QFile file(m_path);
|
||||
|
||||
try {
|
||||
file.open(QFile::ReadOnly);
|
||||
} catch (FS::FileSystemException& e) {
|
||||
qCritical() << QString("Failed to open JAR file in %1").arg(m_path);
|
||||
qCritical() << QString("Reason: ") << e.cause();
|
||||
|
||||
emitFailed("Failed to open file for hashing.");
|
||||
return;
|
||||
}
|
||||
|
||||
m_hash = ProviderCaps.hash(provider, &file, hash_type);
|
||||
|
||||
file.close();
|
||||
|
||||
if (m_hash.isEmpty()) {
|
||||
emitFailed("Empty hash!");
|
||||
} else {
|
||||
emitSucceeded();
|
||||
}
|
||||
}
|
||||
|
||||
QStringList BlockedModHasher::getHashTypes() {
|
||||
return ProviderCaps.hashType(provider);
|
||||
}
|
||||
|
||||
bool BlockedModHasher::useHashType(QString type) {
|
||||
auto types = ProviderCaps.hashType(provider);
|
||||
if (types.contains(type)) {
|
||||
hash_type = type;
|
||||
return true;
|
||||
}
|
||||
qDebug() << "Bad hash type " << type << " for provider";
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Hashing
|
||||
|
@ -40,8 +40,23 @@ class ModrinthHasher : public Hasher {
|
||||
void executeTask() override;
|
||||
};
|
||||
|
||||
class BlockedModHasher : public Hasher {
|
||||
public:
|
||||
BlockedModHasher(QString file_path, ModPlatform::Provider provider);
|
||||
|
||||
void executeTask() override;
|
||||
|
||||
QStringList getHashTypes();
|
||||
bool useHashType(QString type);
|
||||
private:
|
||||
ModPlatform::Provider provider;
|
||||
QString hash_type;
|
||||
};
|
||||
|
||||
Hasher::Ptr createHasher(QString file_path, ModPlatform::Provider provider);
|
||||
Hasher::Ptr createFlameHasher(QString file_path);
|
||||
Hasher::Ptr createModrinthHasher(QString file_path);
|
||||
Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider);
|
||||
Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider, QString type);
|
||||
|
||||
} // namespace Hashing
|
||||
|
@ -15,6 +15,7 @@ void NetworkModAPI::searchMods(CallerType* caller, SearchArgs&& args) const
|
||||
|
||||
QObject::connect(netJob, &NetJob::started, caller, [caller, netJob] { caller->setActiveJob(netJob); });
|
||||
QObject::connect(netJob, &NetJob::failed, caller, &CallerType::searchRequestFailed);
|
||||
QObject::connect(netJob, &NetJob::aborted, caller, &CallerType::searchRequestAborted);
|
||||
QObject::connect(netJob, &NetJob::succeeded, caller, [caller, response] {
|
||||
QJsonParseError parse_error{};
|
||||
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||
|
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 flowln <flowlnlnln@gmail.com>
|
||||
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
@ -176,8 +176,6 @@ void PackInstallTask::resolveMods()
|
||||
|
||||
void PackInstallTask::onResolveModsSucceeded()
|
||||
{
|
||||
QString text;
|
||||
QList<QUrl> urls;
|
||||
auto anyBlocked = false;
|
||||
|
||||
Flame::Manifest results = m_mod_id_resolver_task->getResults();
|
||||
@ -191,11 +189,15 @@ void PackInstallTask::onResolveModsSucceeded()
|
||||
|
||||
// First check for blocked mods
|
||||
if (!results_file.resolved || results_file.url.isEmpty()) {
|
||||
QString type(local_file.type);
|
||||
BlockedMod blocked_mod;
|
||||
blocked_mod.name = local_file.name;
|
||||
blocked_mod.websiteUrl = results_file.websiteUrl;
|
||||
blocked_mod.hash = results_file.hash;
|
||||
blocked_mod.matched = false;
|
||||
blocked_mod.localPath = "";
|
||||
|
||||
m_blocked_mods.append(blocked_mod);
|
||||
|
||||
type[0] = type[0].toUpper();
|
||||
text += QString("%1: %2 - <a href='%3'>%3</a><br/>").arg(type, local_file.name, results_file.websiteUrl);
|
||||
urls.append(QUrl(results_file.websiteUrl));
|
||||
anyBlocked = true;
|
||||
} else {
|
||||
local_file.url = results_file.url.toString();
|
||||
@ -207,16 +209,20 @@ void PackInstallTask::onResolveModsSucceeded()
|
||||
if (anyBlocked) {
|
||||
qDebug() << "Blocked files found, displaying file list";
|
||||
|
||||
auto message_dialog = new BlockedModsDialog(m_parent, tr("Blocked files found"),
|
||||
tr("The following files are not available for download in third party launchers.<br/>"
|
||||
"You will need to manually download them and add them to the instance."),
|
||||
text,
|
||||
urls);
|
||||
BlockedModsDialog message_dialog(m_parent, tr("Blocked files found"),
|
||||
tr("The following files are not available for download in third party launchers.<br/>"
|
||||
"You will need to manually download them and add them to the instance."),
|
||||
m_blocked_mods);
|
||||
|
||||
if (message_dialog->exec() == QDialog::Accepted)
|
||||
message_dialog.setModal(true);
|
||||
|
||||
if (message_dialog.exec() == QDialog::Accepted) {
|
||||
qDebug() << "Post dialog blocked mods list: " << m_blocked_mods;
|
||||
createInstance();
|
||||
else
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
|
||||
} else {
|
||||
createInstance();
|
||||
}
|
||||
@ -320,6 +326,9 @@ void PackInstallTask::downloadPack()
|
||||
void PackInstallTask::onModDownloadSucceeded()
|
||||
{
|
||||
m_net_job.reset();
|
||||
if (!m_blocked_mods.isEmpty()) {
|
||||
copyBlockedMods();
|
||||
}
|
||||
emitSucceeded();
|
||||
}
|
||||
|
||||
@ -343,4 +352,35 @@ void PackInstallTask::onModDownloadFailed(QString reason)
|
||||
emitFailed(reason);
|
||||
}
|
||||
|
||||
/// @brief copy the matched blocked mods to the instance staging area
|
||||
void PackInstallTask::copyBlockedMods()
|
||||
{
|
||||
setStatus(tr("Copying Blocked Mods..."));
|
||||
setAbortable(false);
|
||||
int i = 0;
|
||||
int total = m_blocked_mods.length();
|
||||
setProgress(i, total);
|
||||
for (auto const& mod : m_blocked_mods) {
|
||||
if (!mod.matched) {
|
||||
qDebug() << mod.name << "was not matched to a local file, skipping copy";
|
||||
continue;
|
||||
}
|
||||
|
||||
auto dest_path = FS::PathCombine(m_stagingPath, ".minecraft", "mods", mod.name);
|
||||
|
||||
setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total)));
|
||||
|
||||
qDebug() << "Will try to copy" << mod.localPath << "to" << dest_path;
|
||||
|
||||
if (!FS::copy(mod.localPath, dest_path)()) {
|
||||
qDebug() << "Copy of" << mod.localPath << "to" << dest_path << "Failed";
|
||||
}
|
||||
|
||||
i++;
|
||||
setProgress(i, total);
|
||||
}
|
||||
|
||||
setAbortable(true);
|
||||
}
|
||||
|
||||
} // namespace ModpacksCH
|
||||
|
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 flowln <flowlnlnln@gmail.com>
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
*
|
||||
@ -43,6 +43,7 @@
|
||||
#include "QObjectPtr.h"
|
||||
#include "modplatform/flame/FileResolvingTask.h"
|
||||
#include "net/NetJob.h"
|
||||
#include "ui/dialogs/BlockedModsDialog.h"
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
@ -76,6 +77,7 @@ private:
|
||||
void resolveMods();
|
||||
void createInstance();
|
||||
void downloadPack();
|
||||
void copyBlockedMods();
|
||||
|
||||
private:
|
||||
NetJob::Ptr m_net_job = nullptr;
|
||||
@ -90,6 +92,7 @@ private:
|
||||
Version m_version;
|
||||
|
||||
QMap<QString, QString> m_files_to_copy;
|
||||
QList<BlockedMod> m_blocked_mods;
|
||||
|
||||
//FIXME: nuke
|
||||
QWidget* m_parent;
|
||||
|
@ -1,20 +1,20 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Copyright (c) 2022 flowln <flowlnlnln@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/>.
|
||||
*/
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ModrinthPackIndex.h"
|
||||
#include "ModrinthAPI.h"
|
||||
@ -35,7 +35,7 @@ void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
|
||||
|
||||
pack.provider = ModPlatform::Provider::MODRINTH;
|
||||
pack.name = Json::requireString(obj, "title");
|
||||
|
||||
|
||||
pack.slug = Json::ensureString(obj, "slug", "");
|
||||
if (!pack.slug.isEmpty())
|
||||
pack.websiteUrl = "https://modrinth.com/mod/" + pack.slug;
|
||||
@ -59,23 +59,23 @@ void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
|
||||
void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& obj)
|
||||
{
|
||||
pack.extraData.issuesUrl = Json::ensureString(obj, "issues_url");
|
||||
if(pack.extraData.issuesUrl.endsWith('/'))
|
||||
if (pack.extraData.issuesUrl.endsWith('/'))
|
||||
pack.extraData.issuesUrl.chop(1);
|
||||
|
||||
pack.extraData.sourceUrl = Json::ensureString(obj, "source_url");
|
||||
if(pack.extraData.sourceUrl.endsWith('/'))
|
||||
if (pack.extraData.sourceUrl.endsWith('/'))
|
||||
pack.extraData.sourceUrl.chop(1);
|
||||
|
||||
pack.extraData.wikiUrl = Json::ensureString(obj, "wiki_url");
|
||||
if(pack.extraData.wikiUrl.endsWith('/'))
|
||||
if (pack.extraData.wikiUrl.endsWith('/'))
|
||||
pack.extraData.wikiUrl.chop(1);
|
||||
|
||||
pack.extraData.discordUrl = Json::ensureString(obj, "discord_url");
|
||||
if(pack.extraData.discordUrl.endsWith('/'))
|
||||
if (pack.extraData.discordUrl.endsWith('/'))
|
||||
pack.extraData.discordUrl.chop(1);
|
||||
|
||||
auto donate_arr = Json::ensureArray(obj, "donation_urls");
|
||||
for(auto d : donate_arr){
|
||||
for (auto d : donate_arr) {
|
||||
auto d_obj = Json::requireObject(d);
|
||||
|
||||
ModPlatform::DonationData donate;
|
||||
@ -104,7 +104,7 @@ void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
|
||||
auto obj = versionIter.toObject();
|
||||
auto file = loadIndexedPackVersion(obj);
|
||||
|
||||
if(file.fileId.isValid()) // Heuristic to check if the returned value is valid
|
||||
if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
|
||||
unsortedVersions.append(file);
|
||||
}
|
||||
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
|
||||
@ -116,7 +116,8 @@ void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
|
||||
pack.versionsLoaded = true;
|
||||
}
|
||||
|
||||
auto Modrinth::loadIndexedPackVersion(QJsonObject &obj, QString preferred_hash_type, QString preferred_file_name) -> ModPlatform::IndexedVersion
|
||||
auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_type, QString preferred_file_name)
|
||||
-> ModPlatform::IndexedVersion
|
||||
{
|
||||
ModPlatform::IndexedVersion file;
|
||||
|
||||
@ -141,6 +142,12 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject &obj, QString preferred_hash_t
|
||||
auto files = Json::requireArray(obj, "files");
|
||||
int i = 0;
|
||||
|
||||
if (files.empty()) {
|
||||
// This should not happen normally, but check just in case
|
||||
qWarning() << "Modrinth returned an unexpected empty list of files:" << obj;
|
||||
return {};
|
||||
}
|
||||
|
||||
// Find correct file (needed in cases where one version may have multiple files)
|
||||
// Will default to the last one if there's no primary (though I think Modrinth requires that
|
||||
// at least one file is primary, idk)
|
||||
@ -167,7 +174,7 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject &obj, QString preferred_hash_t
|
||||
file.fileName = Json::requireString(parent, "filename");
|
||||
file.is_preferred = Json::requireBoolean(parent, "primary") || (files.count() == 1);
|
||||
auto hash_list = Json::requireObject(parent, "hashes");
|
||||
|
||||
|
||||
if (hash_list.contains(preferred_hash_type)) {
|
||||
file.hash = Json::requireString(hash_list, preferred_hash_type);
|
||||
file.hash_type = preferred_hash_type;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user