Merge branch 'develop' into refactor-instanceview

This commit is contained in:
Sefa Eyeoglu 2022-12-07 15:22:33 +01:00
commit 05d9c2d864
No known key found for this signature in database
GPG Key ID: C10411294912A422
473 changed files with 25550 additions and 2685 deletions

View File

@ -7,10 +7,17 @@ on:
description: Type of build (Debug, Release, RelWithDebInfo, MinSizeRel) description: Type of build (Debug, Release, RelWithDebInfo, MinSizeRel)
type: string type: string
default: Debug default: Debug
is_qt_cached:
description: Enable Qt caching or not
type: string
default: true
secrets: secrets:
SPARKLE_ED25519_KEY: SPARKLE_ED25519_KEY:
description: Private key for signing Sparkle updates description: Private key for signing Sparkle updates
required: false required: false
CACHIX_AUTH_TOKEN:
description: Private token for authenticating against Cachix cache
required: false
jobs: jobs:
build: build:
@ -25,26 +32,60 @@ jobs:
- os: ubuntu-20.04 - os: ubuntu-20.04
qt_ver: 6 qt_ver: 6
qt_host: linux qt_host: linux
qt_arch: ''
qt_version: '6.2.4' qt_version: '6.2.4'
qt_modules: 'qt5compat qtimageformats' qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
- os: windows-2022 - os: windows-2022
name: "Windows-Legacy" name: "Windows-MinGW-w64"
msystem: mingw32 msystem: clang64
- os: windows-2022
name: "Windows-MSVC-Legacy"
msystem: ''
architecture: 'win32'
vcvars_arch: 'amd64_x86'
qt_ver: 5 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 - os: windows-2022
name: "Windows" name: "Windows-MSVC"
msystem: mingw32 msystem: ''
architecture: 'x64'
vcvars_arch: 'amd64'
qt_ver: 6 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 - os: macos-12
name: macOS name: macOS
macosx_deployment_target: 10.15 macosx_deployment_target: 10.15
qt_ver: 6 qt_ver: 6
qt_host: mac qt_host: mac
qt_arch: ''
qt_version: '6.3.0' qt_version: '6.3.0'
qt_modules: 'qt5compat qtimageformats' qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
- os: macos-12 - os: macos-12
name: macOS-Legacy name: macOS-Legacy
@ -53,6 +94,7 @@ jobs:
qt_host: mac qt_host: mac
qt_version: '5.15.2' qt_version: '5.15.2'
qt_modules: '' qt_modules: ''
qt_tools: ''
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
@ -73,43 +115,40 @@ jobs:
with: with:
submodules: 'true' 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' - name: 'Setup MSYS2'
if: runner.os == 'Windows' if: runner.os == 'Windows' && matrix.msystem != ''
uses: msys2/setup-msys2@v2 uses: msys2/setup-msys2@v2
with: with:
msystem: ${{ matrix.msystem }} msystem: ${{ matrix.msystem }}
update: true update: true
install: >- install: >-
git git
mingw-w64-x86_64-binutils
pacboy: >- pacboy: >-
toolchain:p toolchain:p
cmake:p cmake:p
extra-cmake-modules:p extra-cmake-modules:p
ninja:p ninja:p
qt${{ matrix.qt_ver }}-base:p qt6-base:p
qt${{ matrix.qt_ver }}-svg:p qt6-svg:p
qt${{ matrix.qt_ver }}-imageformats:p qt6-imageformats:p
quazip-qt${{ matrix.qt_ver }}:p quazip-qt6:p
ccache:p ccache:p
nsis:p qt6-5compat:p
${{ matrix.qt_ver == 6 && '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 - name: Setup ccache
if: runner.os != 'Windows' && inputs.build_type == 'Debug' if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug'
uses: hendrikmuhs/ccache-action@v1.2.3 uses: hendrikmuhs/ccache-action@v1.2.5
with: with:
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }} key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
- name: Setup ccache (Windows) - name: Setup ccache (Windows MinGW-w64)
if: runner.os == 'Windows' && inputs.build_type == 'Debug' if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
shell: msys2 {0} shell: msys2 {0}
run: | run: |
ccache --set-config=cache_dir='${{ github.workspace }}\.ccache' ccache --set-config=cache_dir='${{ github.workspace }}\.ccache'
@ -124,14 +163,14 @@ jobs:
run: | run: |
echo "CCACHE_VAR=ccache" >> $GITHUB_ENV echo "CCACHE_VAR=ccache" >> $GITHUB_ENV
- name: Retrieve ccache cache (Windows) - name: Retrieve ccache cache (Windows MinGW-w64)
if: runner.os == 'Windows' && inputs.build_type == 'Debug' if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
uses: actions/cache@v3.0.11 uses: actions/cache@v3.0.11
with: with:
path: '${{ github.workspace }}\.ccache' path: '${{ github.workspace }}\.ccache'
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }} key: ${{ matrix.os }}-mingw-w64
restore-keys: | restore-keys: |
${{ matrix.os }}-qt${{ matrix.qt_ver }} ${{ matrix.os }}-mingw-w64
- name: Set short version - name: Set short version
shell: bash shell: bash
@ -156,16 +195,39 @@ jobs:
run: | run: |
sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5
- name: Install Qt (macOS and AppImage) - name: Install host Qt (Windows MSVC arm64)
if: runner.os == 'Linux' && matrix.qt_ver == 6 || runner.os == 'macOS' 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 uses: jurplel/install-qt-action@v3
with: with:
version: ${{ matrix.qt_version }} version: ${{ matrix.qt_version }}
host: ${{ matrix.qt_host }} host: ${{ matrix.qt_host }}
target: 'desktop' target: 'desktop'
arch: ${{ matrix.qt_arch }}
modules: ${{ matrix.qt_modules }} modules: ${{ matrix.qt_modules }}
cache: true tools: ${{ matrix.qt_tools }}
cache-key-prefix: ${{ matrix.qt_host }}-${{ matrix.qt_version }}-"${{ matrix.qt_modules }}"-qt_cache 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) - name: Prepare AppImage (Linux)
if: runner.os == 'Linux' && matrix.qt_ver != 5 if: runner.os == 'Linux' && matrix.qt_ver != 5
@ -176,6 +238,11 @@ jobs:
${{ github.workspace }}/.github/scripts/prepare_JREs.sh ${{ 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 # CONFIGURE
## ##
@ -190,11 +257,26 @@ jobs:
run: | run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ 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 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) - name: Configure CMake (Windows MinGW-w64)
if: runner.os == 'Windows' if: runner.os == 'Windows' && matrix.msystem != ''
shell: msys2 {0} shell: msys2 {0}
run: | run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ 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) - name: Configure CMake (Linux)
if: runner.os == 'Linux' if: runner.os == 'Linux'
@ -210,12 +292,17 @@ jobs:
run: | run: |
cmake --build ${{ env.BUILD_DIR }} cmake --build ${{ env.BUILD_DIR }}
- name: Build (Windows) - name: Build (Windows MinGW-w64)
if: runner.os == 'Windows' if: runner.os == 'Windows' && matrix.msystem != ''
shell: msys2 {0} shell: msys2 {0}
run: | run: |
cmake --build ${{ env.BUILD_DIR }} 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 # TEST
## ##
@ -223,21 +310,18 @@ jobs:
- name: Test - name: Test
if: runner.os != 'Windows' if: runner.os != 'Windows'
run: | run: |
ctest --test-dir build --output-on-failure ctest -E "^example64|example$" --test-dir build --output-on-failure
- name: Test (Windows) - name: Test (Windows MinGW-w64)
if: runner.os == 'Windows' if: runner.os == 'Windows' && matrix.msystem != ''
shell: msys2 {0} shell: msys2 {0}
run: | run: |
ctest --test-dir build --output-on-failure ctest -E "^example64|example$" --test-dir build --output-on-failure
## - name: Test (Windows MSVC)
# CODE SCAN if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture != 'arm64'
## run: |
ctest -E "^example64|example$" --test-dir build --output-on-failure -C ${{ inputs.build_type }}
- name: Perform CodeQL Analysis
if: runner.os == 'Linux' && matrix.qt_ver == 6
uses: github/codeql-action/analyze@v2
## ##
# PACKAGE BUILDS # PACKAGE BUILDS
@ -251,6 +335,7 @@ jobs:
cd ${{ env.INSTALL_DIR }} cd ${{ env.INSTALL_DIR }}
chmod +x "PrismLauncher.app/Contents/MacOS/prismlauncher" chmod +x "PrismLauncher.app/Contents/MacOS/prismlauncher"
sudo codesign --sign - --deep --force --entitlements "../program_info/App.entitlements" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher" 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 * tar -czf ../PrismLauncher.tar.gz *
- name: Make Sparkle signature (macOS) - name: Make Sparkle signature (macOS)
@ -272,27 +357,39 @@ jobs:
EOF EOF
fi fi
- name: Package (Windows) - name: Package (Windows MinGW-w64)
if: runner.os == 'Windows' if: runner.os == 'Windows' && matrix.msystem != ''
shell: msys2 {0} shell: msys2 {0}
run: | run: |
cmake --install ${{ env.BUILD_DIR }} cmake --install ${{ env.BUILD_DIR }}
cd ${{ env.INSTALL_DIR }} - name: Package (Windows MSVC)
if [ "${{ matrix.qt_ver }}" == "5" ]; then if: runner.os == 'Windows' && matrix.msystem == ''
cp /mingw32/bin/libcrypto-1_1.dll /mingw32/bin/libssl-1_1.dll ./ run: |
fi cmake --install ${{ env.BUILD_DIR }} --config ${{ inputs.build_type }}
- name: Package (Windows, portable) cd ${{ env.INSTALL_DIR }}
if: runner.os == 'Windows' 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} shell: msys2 {0}
run: | run: |
cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead 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 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) - name: Package (Windows, installer)
if: runner.os == 'Windows' if: runner.os == 'Windows'
shell: msys2 {0}
run: | run: |
cd ${{ env.INSTALL_DIR }} cd ${{ env.INSTALL_DIR }}
makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi" makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi"
@ -411,5 +508,76 @@ jobs:
with: with:
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
path: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage path: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
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
View 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

View File

@ -8,7 +8,6 @@ on:
- '**.md' - '**.md'
- '**/LICENSE' - '**/LICENSE'
- 'flake.lock' - 'flake.lock'
- '**.nix'
- 'packages/**' - 'packages/**'
- '.github/ISSUE_TEMPLATE/**' - '.github/ISSUE_TEMPLATE/**'
- '.markdownlint**' - '.markdownlint**'
@ -17,7 +16,6 @@ on:
- '**.md' - '**.md'
- '**/LICENSE' - '**/LICENSE'
- 'flake.lock' - 'flake.lock'
- '**.nix'
- 'packages/**' - 'packages/**'
- '.github/ISSUE_TEMPLATE/**' - '.github/ISSUE_TEMPLATE/**'
- '.markdownlint**' - '.markdownlint**'
@ -30,5 +28,7 @@ jobs:
uses: ./.github/workflows/build.yml uses: ./.github/workflows/build.yml
with: with:
build_type: Debug build_type: Debug
is_qt_cached: true
secrets: secrets:
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }} SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}

View File

@ -12,6 +12,7 @@ jobs:
uses: ./.github/workflows/build.yml uses: ./.github/workflows/build.yml
with: with:
build_type: Release build_type: Release
is_qt_cached: false
secrets: secrets:
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }} SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
@ -45,13 +46,26 @@ jobs:
tar -czf PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }} 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 cd "${d}" || continue
LEGACY="$(echo -n ${d} | grep -o Legacy || true)" LEGACY="$(echo -n ${d} | grep -o Legacy || true)"
ARM64="$(echo -n ${d} | grep -o arm64 || true)"
INST="$(echo -n ${d} | grep -o Setup || true)" INST="$(echo -n ${d} | grep -o Setup || true)"
PORT="$(echo -n ${d} | grep -o Portable || true)" PORT="$(echo -n ${d} | grep -o Portable || true)"
NAME="PrismLauncher-Windows" NAME="PrismLauncher-Windows-MSVC"
test -z "${LEGACY}" || NAME="${NAME}-Legacy" 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 "${PORT}" || NAME="${NAME}-Portable"
test -z "${INST}" || mv PrismLauncher-*.exe ../${NAME}-Setup-${{ env.VERSION }}.exe test -z "${INST}" || mv PrismLauncher-*.exe ../${NAME}-Setup-${{ env.VERSION }}.exe
test -n "${INST}" || zip -r -9 "../${NAME}-${{ env.VERSION }}.zip" * test -n "${INST}" || zip -r -9 "../${NAME}-${{ env.VERSION }}.zip" *
@ -72,14 +86,20 @@ jobs:
PrismLauncher-Linux-${{ env.VERSION }}.tar.gz PrismLauncher-Linux-${{ env.VERSION }}.tar.gz
PrismLauncher-Linux-Portable-${{ env.VERSION }}.tar.gz PrismLauncher-Linux-Portable-${{ env.VERSION }}.tar.gz
PrismLauncher-Linux-${{ env.VERSION }}-x86_64.AppImage PrismLauncher-Linux-${{ env.VERSION }}-x86_64.AppImage
PrismLauncher-Windows-Legacy-${{ env.VERSION }}.zip
PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz
PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
PrismLauncher-Windows-Legacy-Portable-${{ env.VERSION }}.zip PrismLauncher-Windows-MinGW-w64-${{ env.VERSION }}.zip
PrismLauncher-Windows-Legacy-Setup-${{ env.VERSION }}.exe PrismLauncher-Windows-MinGW-w64-Portable-${{ env.VERSION }}.zip
PrismLauncher-Windows-${{ env.VERSION }}.zip PrismLauncher-Windows-MinGW-w64-Setup-${{ env.VERSION }}.exe
PrismLauncher-Windows-Portable-${{ env.VERSION }}.zip PrismLauncher-Windows-MSVC-Legacy-${{ env.VERSION }}.zip
PrismLauncher-Windows-Setup-${{ env.VERSION }}.exe 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-${{ env.VERSION }}.tar.gz
PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }}.tar.gz

3
.gitignore vendored
View File

@ -47,3 +47,6 @@ result/
# Flatpak # Flatpak
.flatpak-builder .flatpak-builder
flatbuild flatbuild
# Snap
*.snap

6
.gitmodules vendored
View File

@ -10,3 +10,9 @@
[submodule "libraries/libnbtplusplus"] [submodule "libraries/libnbtplusplus"]
path = libraries/libnbtplusplus path = libraries/libnbtplusplus
url = https://github.com/PrismLauncher/libnbtplusplus.git 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

View File

@ -1,10 +1,5 @@
cmake_minimum_required(VERSION 3.15) # minimum version required by QuaZip 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) project(Launcher)
string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BUILD_DIR}" IS_IN_SOURCE_BUILD) 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_CXX_STANDARD 17)
set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD 11)
include(GenerateExportHeader) 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 # 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_NO_DEPRECATED_WARNINGS=Y")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_DISABLE_DEPRECATED_BEFORE=0x050C00") 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 CXXFLAGS for build targets
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}") set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}")
@ -48,11 +82,18 @@ if(ENABLE_LTO)
include(CheckIPOSupported) include(CheckIPOSupported)
check_ipo_supported(RESULT ipo_supported OUTPUT ipo_error) check_ipo_supported(RESULT ipo_supported OUTPUT ipo_error)
if(ipo_supported AND (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")) if(ipo_supported)
message(STATUS "IPO / LTO enabled") set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_MINSIZEREL TRUE)
elseif(ipo_supported) if(CMAKE_BUILD_TYPE)
message(STATUS "Not enabling IPO / LTO on debug builds") 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() else()
message(STATUS "IPO / LTO not supported: <${ipo_error}>") message(STATUS "IPO / LTO not supported: <${ipo_error}>")
endif() endif()
@ -60,8 +101,20 @@ endif()
option(BUILD_TESTING "Build the testing tree." ON) option(BUILD_TESTING "Build the testing tree." ON)
find_package(ECM REQUIRED NO_MODULE) find_package(ECM QUIET NO_MODULE)
set(CMAKE_MODULE_PATH "${ECM_MODULE_PATH};${CMAKE_MODULE_PATH}") 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(CTest)
include(ECMAddTests) include(ECMAddTests)
if(BUILD_TESTING) 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(Launcher_HELP_URL "https://prismlauncher.org/wiki/help-pages/%1" CACHE STRING "URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help")
######## Set version numbers ######## ######## Set version numbers ########
set(Launcher_VERSION_MAJOR 5) set(Launcher_VERSION_MAJOR 6)
set(Launcher_VERSION_MINOR 0) set(Launcher_VERSION_MINOR 0)
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}") set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}")
@ -146,6 +199,10 @@ set(Launcher_BUILD_TIMESTAMP "${TODAY}")
################################ 3rd Party Libs ################################ ################################ 3rd Party Libs ################################
if(NOT Launcher_FORCE_BUNDLED_LIBS)
find_package(ZLIB QUIET)
endif()
# Find the required Qt parts # Find the required Qt parts
include(QtVersionlessBackport) include(QtVersionlessBackport)
if(Launcher_QT_VERSION_MAJOR EQUAL 5) 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") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUNICODE -D_UNICODE")
elseif(Launcher_QT_VERSION_MAJOR EQUAL 6) elseif(Launcher_QT_VERSION_MAJOR EQUAL 6)
set(QT_VERSION_MAJOR 6) set(QT_VERSION_MAJOR 6)
find_package(Qt6 REQUIRED COMPONENTS Core 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) list(APPEND Launcher_QT_LIBS Qt6::Core5Compat)
if(NOT Launcher_FORCE_BUNDLED_LIBS) if(NOT Launcher_FORCE_BUNDLED_LIBS)
@ -178,12 +235,16 @@ else()
message(FATAL_ERROR "Qt version ${Launcher_QT_VERSION_MAJOR} is not supported") message(FATAL_ERROR "Qt version ${Launcher_QT_VERSION_MAJOR} is not supported")
endif() endif()
include(ECMQueryQt) if(Launcher_QT_VERSION_MAJOR EQUAL 5)
ecm_query_qt(QT_PLUGINS_DIR QT_INSTALL_PLUGINS) include(ECMQueryQt)
ecm_query_qt(QT_LIBS_DIR QT_INSTALL_LIBS) ecm_query_qt(QT_PLUGINS_DIR QT_INSTALL_PLUGINS)
ecm_query_qt(QT_LIBEXECS_DIR QT_INSTALL_LIBEXECS) ecm_query_qt(QT_LIBS_DIR QT_INSTALL_LIBS)
ecm_query_qt(QT_DATA_DIR QT_HOST_DATA) ecm_query_qt(QT_LIBEXECS_DIR QT_INSTALL_LIBEXECS)
set(QT_MKSPECS_DIR ${QT_DATA_DIR}/mkspecs) 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 # NOTE: Qt 6 already sets this by default
if (Qt5_POSITION_INDEPENDENT_CODE) if (Qt5_POSITION_INDEPENDENT_CODE)
@ -222,14 +283,14 @@ if(UNIX AND APPLE)
set(APPS "\${CMAKE_INSTALL_PREFIX}/${Launcher_Name}.app") set(APPS "\${CMAKE_INSTALL_PREFIX}/${Launcher_Name}.app")
# Mac bundle settings # Mac bundle settings
set(MACOSX_BUNDLE_BUNDLE_NAME "${Launcher_Name}") set(MACOSX_BUNDLE_BUNDLE_NAME "${Launcher_DisplayName}")
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_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_GUI_IDENTIFIER "org.prismlauncher.${Launcher_Name}")
set(MACOSX_BUNDLE_BUNDLE_VERSION "${Launcher_VERSION_NAME}") set(MACOSX_BUNDLE_BUNDLE_VERSION "${Launcher_VERSION_NAME}")
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_NAME}") set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_NAME}")
set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_NAME}") set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_NAME}")
set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns) set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns)
set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2021-2022 ${Launcher_Copyright}") set(MACOSX_BUNDLE_COPYRIGHT "© 2022 ${Launcher_Copyright_Mac}")
set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=") set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=")
set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml") 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) install(FILES ${Launcher_Branding_ICNS} DESTINATION ${RESOURCES_DEST_DIR} RENAME ${Launcher_Name}.icns)
elseif(UNIX) elseif(UNIX)
include(KDEInstallDirs)
set(BINARY_DEST_DIR "bin") set(BINARY_DEST_DIR "bin")
set(LIBRARY_DEST_DIR "lib${LIB_SUFFIX}") set(LIBRARY_DEST_DIR "lib${LIB_SUFFIX}")
set(JARS_DEST_DIR "share/${Launcher_APP_BINARY_NAME}") 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 # install as bundle with no dependencies included
set(INSTALL_BUNDLE "nodeps") set(INSTALL_BUNDLE "nodeps")
@ -261,12 +320,13 @@ elseif(UNIX)
# Set RPATH # Set RPATH
SET(Launcher_BINARY_RPATH "$ORIGIN/") 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_Desktop} DESTINATION ${KDE_INSTALL_APPDIR})
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_MetaInfo} DESTINATION ${LAUNCHER_METAINFO_DEST_DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_MetaInfo} DESTINATION ${KDE_INSTALL_METAINFODIR})
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION ${LAUNCHER_ICON_DEST_DIR}) 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) 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() endif()
# Install basic runner script if component "portable" is selected # 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/hoedown) # markdown parser
add_subdirectory(libraries/launcher) # java based launcher part for Minecraft add_subdirectory(libraries/launcher) # java based launcher part for Minecraft
add_subdirectory(libraries/javacheck) # java compatibility checker 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) if (FORCE_BUNDLED_QUAZIP)
message(STATUS "Using bundled QuaZip") message(STATUS "Using bundled QuaZip")
set(BUILD_SHARED_LIBS 0) # link statically to avoid conflicts. set(BUILD_SHARED_LIBS 0) # link statically to avoid conflicts.

View File

@ -398,3 +398,45 @@
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. 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/>.

View File

@ -1,44 +1,52 @@
<p align="center"> <p align="left">
<img src="./program_info/org.prismlauncher.PrismLauncher.logo.svg#gh-light-mode-only" alt="Prism Launcher logo" width="50%"/> <picture>
<img src="./program_info/org.prismlauncher.PrismLauncher.logo-darkmode.svg#gh-dark-mode-only" alt="Prism Launcher logo" width="50%"/> <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> </p>
Prism Launcher is a custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once. 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 ## Installation
- All downloads and instructions for Prism Launcher can be found [on our website](https://prismlauncher.org/download/). <a href="https://repology.org/project/prismlauncher/versions">
- Last build status can be found [here](https://github.com/PrismLauncher/PrismLauncher/actions). <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 ### 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. 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: For **Arch**, **Debian**, **Fedora**, **OpenSUSE (Tumbleweed)** and **Gentoo**, respectively, 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
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. 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.
You can already join our Matrix space:
[![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 ## 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 ## 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 (`""`). 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. We thank all the wonderful backers over at Open Collective! Support Prism Launcher by [becoming a backer](https://opencollective.com/prismlauncher).
The logo and related assets are under the CC BY-SA 4.0 license. [![OpenCollective Backers](https://opencollective.com/prismlauncher/backers.svg?width=890&limit=1000)](https://opencollective.com/prismlauncher#backers)
## Sponsors
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/). 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! 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> <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.

View File

@ -49,6 +49,7 @@ Config::Config()
LAUNCHER_CONFIGFILE = "@Launcher_ConfigFile@"; LAUNCHER_CONFIGFILE = "@Launcher_ConfigFile@";
LAUNCHER_GIT = "@Launcher_Git@"; LAUNCHER_GIT = "@Launcher_Git@";
LAUNCHER_DESKTOPFILENAME = "@Launcher_DesktopFileName@"; LAUNCHER_DESKTOPFILENAME = "@Launcher_DesktopFileName@";
LAUNCHER_SVGFILENAME = "@Launcher_SVGFileName@";
USER_AGENT = "@Launcher_UserAgent@"; USER_AGENT = "@Launcher_UserAgent@";
USER_AGENT_UNCACHED = USER_AGENT + " (Uncached)"; USER_AGENT_UNCACHED = USER_AGENT + " (Uncached)";

View File

@ -51,6 +51,7 @@ class Config {
QString LAUNCHER_CONFIGFILE; QString LAUNCHER_CONFIGFILE;
QString LAUNCHER_GIT; QString LAUNCHER_GIT;
QString LAUNCHER_DESKTOPFILENAME; QString LAUNCHER_DESKTOPFILENAME;
QString LAUNCHER_SVGFILENAME;
/// The major version number. /// The major version number.
int VERSION_MAJOR; int VERSION_MAJOR;

View File

@ -44,5 +44,28 @@
<string>${MACOSX_SPARKLE_UPDATE_PUBLIC_KEY}</string> <string>${MACOSX_SPARKLE_UPDATE_PUBLIC_KEY}</string>
<key>SUFeedURL</key> <key>SUFeedURL</key>
<string>${MACOSX_SPARKLE_UPDATE_FEED_URL}</string> <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> </dict>
</plist> </plist>

View File

@ -23,8 +23,8 @@
pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system}); pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system});
packagesFn = pkgs: rec { packagesFn = pkgs: rec {
prismlauncher = pkgs.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus tomlplusplus; }; prismlauncher-qt5 = pkgs.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus tomlplusplus; };
prismlauncher-qt6 = pkgs.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus tomlplusplus; }; prismlauncher = pkgs.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus tomlplusplus; };
}; };
in in
{ {

View 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
View 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
View 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 "$@"

View File

@ -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 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 Lenny McLennington <lenny@sneed.church> * 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 * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -37,11 +41,16 @@
#include "Application.h" #include "Application.h"
#include "BuildConfig.h" #include "BuildConfig.h"
#include "DataMigrationTask.h"
#include "net/PasteUpload.h" #include "net/PasteUpload.h"
#include "pathmatcher/MultiMatcher.h"
#include "pathmatcher/SimplePrefixMatcher.h"
#include "ui/InstanceWindow.h" #include "ui/InstanceWindow.h"
#include "ui/MainWindow.h" #include "ui/MainWindow.h"
#include "ui/instanceview/InstancesView.h" #include "ui/instanceview/InstancesView.h"
#include "ui/dialogs/ProgressDialog.h"
#include "ui/pages/BasePageProvider.h" #include "ui/pages/BasePageProvider.h"
#include "ui/pages/global/LauncherPage.h" #include "ui/pages/global/LauncherPage.h"
#include "ui/pages/global/MinecraftPage.h" #include "ui/pages/global/MinecraftPage.h"
@ -53,14 +62,9 @@
#include "ui/pages/global/APIPage.h" #include "ui/pages/global/APIPage.h"
#include "ui/pages/global/CustomCommandsPage.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 #ifdef Q_OS_WIN
#include "ui/WinDarkmode.h" #include "ui/WinDarkmode.h"
#include <versionhelpers.h>
#endif #endif
#include "ui/setupwizard/SetupWizard.h" #include "ui/setupwizard/SetupWizard.h"
@ -72,6 +76,8 @@
#include "ui/pagedialog/PageDialog.h" #include "ui/pagedialog/PageDialog.h"
#include "ui/themes/ThemeManager.h"
#include "ApplicationMessage.h" #include "ApplicationMessage.h"
#include <iostream> #include <iostream>
@ -227,7 +233,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
setOrganizationDomain(BuildConfig.LAUNCHER_DOMAIN); setOrganizationDomain(BuildConfig.LAUNCHER_DOMAIN);
setApplicationName(BuildConfig.LAUNCHER_NAME); setApplicationName(BuildConfig.LAUNCHER_NAME);
setApplicationDisplayName(QString("%1 %2").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString())); setApplicationDisplayName(QString("%1 %2").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString()));
setApplicationVersion(BuildConfig.printableVersionString()); setApplicationVersion(BuildConfig.printableVersionString() + "\n" + BuildConfig.GIT_COMMIT);
setDesktopFileName(BuildConfig.LAUNCHER_DESKTOPFILENAME); setDesktopFileName(BuildConfig.LAUNCHER_DESKTOPFILENAME);
startTime = QDateTime::currentDateTime(); startTime = QDateTime::currentDateTime();
@ -302,22 +308,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
dataPath = foo.absolutePath(); dataPath = foo.absolutePath();
adjustedBy = "Persistent data path"; 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 #ifndef Q_OS_MACOS
if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) { if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) {
dataPath = m_rootPath; dataPath = m_rootPath;
@ -440,6 +430,15 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
qDebug() << "<> Log initialized."; 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; qDebug() << BuildConfig.LAUNCHER_DISPLAYNAME << ", (c) 2013-2021 " << BuildConfig.LAUNCHER_COPYRIGHT;
@ -499,6 +498,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
// Theming // Theming
m_settings->registerSetting("IconTheme", QString("pe_colored")); m_settings->registerSetting("IconTheme", QString("pe_colored"));
m_settings->registerSetting("ApplicationTheme", QString("system")); m_settings->registerSetting("ApplicationTheme", QString("system"));
m_settings->registerSetting("BackgroundCat", QString("kitteh"));
// Remembered state // Remembered state
m_settings->registerSetting("LastUsedGroupForNewInstance", QString()); m_settings->registerSetting("LastUsedGroupForNewInstance", QString());
@ -563,7 +563,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
// Memory // Memory
m_settings->registerSetting({"MinMemAlloc", "MinMemoryAlloc"}, 512); m_settings->registerSetting({"MinMemAlloc", "MinMemoryAlloc"}, 512);
m_settings->registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, 4096); m_settings->registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, suitableMaxMem());
m_settings->registerSetting("PermGen", 128); m_settings->registerSetting("PermGen", 128);
// Java Settings // Java Settings
@ -612,6 +612,8 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_settings->registerSetting("TheCat", false); m_settings->registerSetting("TheCat", false);
m_settings->registerSetting("InstanceDisplayMode", InstancesView::TableMode); m_settings->registerSetting("InstanceDisplayMode", InstancesView::TableMode);
m_settings->registerSetting("ToolbarsLocked", false);
m_settings->registerSetting("InstSortMode", "Name"); m_settings->registerSetting("InstSortMode", "Name");
m_settings->registerSetting("SelectedInstance", QString()); m_settings->registerSetting("SelectedInstance", QString());
@ -746,29 +748,8 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
qDebug() << "<> Instance icons intialized."; qDebug() << "<> Instance icons intialized.";
} }
// Icon themes // Themes
{ m_themeManager = std::make_unique<ThemeManager>(m_mainWindow);
// 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.";
}
// initialize and load all instances // initialize and load all instances
{ {
@ -933,18 +914,24 @@ bool Application::createSetupWizard()
return false; return false;
} }
bool Application::event(QEvent* event) { bool Application::event(QEvent* event)
{
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
if (event->type() == QEvent::ApplicationStateChange) { if (event->type() == QEvent::ApplicationStateChange) {
auto ev = static_cast<QApplicationStateChangeEvent*>(event); auto ev = static_cast<QApplicationStateChangeEvent*>(event);
if (m_prevAppState == Qt::ApplicationActive if (m_prevAppState == Qt::ApplicationActive && ev->applicationState() == Qt::ApplicationActive) {
&& ev->applicationState() == Qt::ApplicationActive) {
emit clickedOnDock(); emit clickedOnDock();
} }
m_prevAppState = ev->applicationState(); m_prevAppState = ev->applicationState();
} }
#endif #endif
if (event->type() == QEvent::FileOpen) {
auto ev = static_cast<QFileOpenEvent*>(event);
m_mainWindow->droppedURLs({ ev->url() });
}
return QApplication::event(event); return QApplication::event(event);
} }
@ -1122,60 +1109,25 @@ std::shared_ptr<JavaInstallList> Application::javalist()
return m_javalist; return m_javalist;
} }
std::vector<ITheme *> Application::getValidApplicationThemes() QList<ITheme*> Application::getValidApplicationThemes()
{ {
std::vector<ITheme *> ret; return m_themeManager->getValidApplicationThemes();
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
} }
void Application::setApplicationTheme(const QString& name, bool initial) void Application::setApplicationTheme(const QString& name, bool initial)
{ {
auto systemPalette = qApp->palette(); m_themeManager->setApplicationTheme(name, initial);
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;
}
} }
void Application::setIconTheme(const QString& name) void Application::setIconTheme(const QString& name)
{ {
QIcon::setThemeName(name); m_themeManager->setIconTheme(name);
} }
QIcon Application::getThemedIcon(const QString& name) QIcon Application::getThemedIcon(const QString& name)
{ {
if(name == "logo") { if(name == "logo") {
return QIcon(":/org.prismlauncher.PrismLauncher.svg"); // FIXME: Make this a BuildConfig variable return QIcon(":/" + BuildConfig.LAUNCHER_SVGFILENAME);
} }
return QIcon::fromTheme(name); 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->restoreState(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowState").toByteArray()));
m_mainWindow->restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowGeometry").toByteArray())); m_mainWindow->restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowGeometry").toByteArray()));
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
if (QString::compare(settings()->get("ApplicationTheme").toString(), "dark") == 0) { if (IsWindows10OrGreater())
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), true); {
} else { if (QString::compare(settings()->get("ApplicationTheme").toString(), "dark") == 0) {
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), false); WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), true);
} else {
WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), false);
}
} }
#endif #endif
if(minimized) if(minimized)
@ -1635,3 +1590,102 @@ QString Application::getUserAgentUncached()
return BuildConfig.USER_AGENT_UNCACHED; return BuildConfig.USER_AGENT_UNCACHED;
} }
int Application::suitableMaxMem()
{
float totalRAM = (float)Sys::getSystemRam() / (float)Sys::mebibyte;
int maxMemoryAlloc;
// If totalRAM < 6GB, use (totalRAM / 1.5), else 4GB
if (totalRAM < (4096 * 1.5))
maxMemoryAlloc = (int) (totalRAM / 1.5);
else
maxMemoryAlloc = 4096;
return maxMemoryAlloc;
}
bool Application::handleDataMigration(const QString& currentData,
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;
}

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only // 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 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 Tayou <tayou@gmx.net>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -68,6 +69,7 @@ class BaseDetachedToolFactory;
class TranslationsModel; class TranslationsModel;
class ITheme; class ITheme;
class MCEditTool; class MCEditTool;
class ThemeManager;
namespace Meta { namespace Meta {
class Index; class Index;
@ -116,11 +118,9 @@ public:
QIcon getThemedIcon(const QString& name); QIcon getThemedIcon(const QString& name);
bool isFlatpak();
void setIconTheme(const QString& name); void setIconTheme(const QString& name);
std::vector<ITheme *> getValidApplicationThemes(); QList<ITheme*> getValidApplicationThemes();
void setApplicationTheme(const QString& name, bool initial); void setApplicationTheme(const QString& name, bool initial);
@ -200,6 +200,8 @@ public:
void ShowGlobalSettings(class QWidget * parent, QString open_page = QString()); void ShowGlobalSettings(class QWidget * parent, QString open_page = QString());
int suitableMaxMem();
signals: signals:
void updateAllowedChanged(bool status); void updateAllowedChanged(bool status);
void globalSettingsAboutToOpen(); void globalSettingsAboutToOpen();
@ -229,6 +231,7 @@ private slots:
void setupWizardFinished(int status); void setupWizardFinished(int status);
private: private:
bool handleDataMigration(const QString & currentData, const QString & oldData, const QString & name, const QString & configFile) const;
bool createSetupWizard(); bool createSetupWizard();
void performMainStartupAction(); void performMainStartupAction();
@ -257,9 +260,9 @@ private:
std::shared_ptr<JavaInstallList> m_javalist; std::shared_ptr<JavaInstallList> m_javalist;
std::shared_ptr<TranslationsModel> m_translations; std::shared_ptr<TranslationsModel> m_translations;
std::shared_ptr<GenericPageProvider> m_globalSettingsProvider; std::shared_ptr<GenericPageProvider> m_globalSettingsProvider;
std::map<QString, std::unique_ptr<ITheme>> m_themes;
std::unique_ptr<MCEditTool> m_mcedit; std::unique_ptr<MCEditTool> m_mcedit;
QSet<QString> m_features; QSet<QString> m_features;
std::unique_ptr<ThemeManager> m_themeManager;
QMap<QString, std::shared_ptr<BaseProfilerFactory>> m_profilers; QMap<QString, std::shared_ptr<BaseProfilerFactory>> m_profilers;

View File

@ -17,13 +17,14 @@
#include <memory> #include <memory>
#include "BaseVersion.h"
class MinecraftInstance; class MinecraftInstance;
class QDir; class QDir;
class QString; class QString;
class QObject; class QObject;
class Task; class Task;
class BaseVersion; class BaseVersion;
typedef std::shared_ptr<BaseVersion> BaseVersionPtr;
class BaseInstaller class BaseInstaller
{ {
@ -35,7 +36,7 @@ public:
virtual bool add(MinecraftInstance *to); virtual bool add(MinecraftInstance *to);
virtual bool remove(MinecraftInstance *from); 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: protected:
virtual QString id() const = 0; virtual QString id() const = 0;

View File

@ -151,7 +151,7 @@ public:
void copyManagedPack(BaseInstance& other); void copyManagedPack(BaseInstance& other);
/// guess log level from a line of game log /// 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; return level;
}; };

View File

@ -25,6 +25,7 @@
class BaseVersion class BaseVersion
{ {
public: public:
using Ptr = std::shared_ptr<BaseVersion>;
virtual ~BaseVersion() {} virtual ~BaseVersion() {}
/*! /*!
* A string used to identify this version in config files. * A string used to identify this version in config files.
@ -54,6 +55,4 @@ public:
}; };
}; };
typedef std::shared_ptr<BaseVersion> BaseVersionPtr; Q_DECLARE_METATYPE(BaseVersion::Ptr)
Q_DECLARE_METATYPE(BaseVersionPtr)

View File

@ -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++) for (int i = 0; i < count(); i++)
{ {
if (at(i)->descriptor() == descriptor) if (at(i)->descriptor() == descriptor)
return at(i); return at(i);
} }
return BaseVersionPtr(); return nullptr;
} }
BaseVersionPtr BaseVersionList::getRecommended() const BaseVersion::Ptr BaseVersionList::getRecommended() const
{ {
if (count() <= 0) if (count() <= 0)
return BaseVersionPtr(); return nullptr;
else else
return at(0); return at(0);
} }
@ -66,7 +66,7 @@ QVariant BaseVersionList::data(const QModelIndex &index, int role) const
if (index.row() > count()) if (index.row() > count())
return QVariant(); return QVariant();
BaseVersionPtr version = at(index.row()); BaseVersion::Ptr version = at(index.row());
switch (role) switch (role)
{ {
@ -95,12 +95,12 @@ BaseVersionList::RoleList BaseVersionList::providesRoles() const
int BaseVersionList::rowCount(const QModelIndex &parent) const int BaseVersionList::rowCount(const QModelIndex &parent) const
{ {
// Return count // Return count
return count(); return parent.isValid() ? 0 : count();
} }
int BaseVersionList::columnCount(const QModelIndex &parent) const int BaseVersionList::columnCount(const QModelIndex &parent) const
{ {
return 1; return parent.isValid() ? 0 : 1;
} }
QHash<int, QByteArray> BaseVersionList::roleNames() const QHash<int, QByteArray> BaseVersionList::roleNames() const

View File

@ -70,7 +70,7 @@ public:
virtual bool isLoaded() = 0; virtual bool isLoaded() = 0;
//! Gets the version at the given index. //! 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. //! Returns the number of versions in the list.
virtual int count() const = 0; virtual int count() const = 0;
@ -90,13 +90,13 @@ public:
* \return A const pointer to the version with the given descriptor. NULL if * \return A const pointer to the version with the given descriptor. NULL if
* one doesn't exist. * 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 * \brief Gets the recommended version from this list
* If the list doesn't support recommended versions, this works exactly as getLatestStable * 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. * Sorts the version list.
@ -117,5 +117,5 @@ slots:
* then copies the versions and sets their parents correctly. * then copies the versions and sets their parents correctly.
* \param versions List of versions whose parents should be set. * \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;
}; };

View File

@ -24,13 +24,15 @@ set(CORE_SOURCES
NullInstance.h NullInstance.h
MMCZip.h MMCZip.h
MMCZip.cpp MMCZip.cpp
MMCStrings.h StringUtils.h
MMCStrings.cpp StringUtils.cpp
RuntimeContext.h RuntimeContext.h
# Basic instance manipulation tasks (derived from InstanceTask) # Basic instance manipulation tasks (derived from InstanceTask)
InstanceCreationTask.h InstanceCreationTask.h
InstanceCreationTask.cpp InstanceCreationTask.cpp
InstanceCopyPrefs.h
InstanceCopyPrefs.cpp
InstanceCopyTask.h InstanceCopyTask.h
InstanceCopyTask.cpp InstanceCopyTask.cpp
InstanceImportTask.h InstanceImportTask.h
@ -95,6 +97,7 @@ set(PATHMATCHER_SOURCES
pathmatcher/IPathMatcher.h pathmatcher/IPathMatcher.h
pathmatcher/MultiMatcher.h pathmatcher/MultiMatcher.h
pathmatcher/RegexpMatcher.h pathmatcher/RegexpMatcher.h
pathmatcher/SimplePrefixMatcher.h
) )
set(NET_SOURCES set(NET_SOURCES
@ -539,9 +542,6 @@ set(ATLAUNCHER_SOURCES
################################ COMPILE ################################ ################################ COMPILE ################################
# we need zlib
find_package(ZLIB REQUIRED)
set(LOGIC_SOURCES set(LOGIC_SOURCES
${CORE_SOURCES} ${CORE_SOURCES}
${PATHMATCHER_SOURCES} ${PATHMATCHER_SOURCES}
@ -576,6 +576,8 @@ SET(LAUNCHER_SOURCES
# Application base # Application base
Application.h Application.h
Application.cpp Application.cpp
DataMigrationTask.h
DataMigrationTask.cpp
UpdateController.cpp UpdateController.cpp
UpdateController.h UpdateController.h
ApplicationMessage.h ApplicationMessage.h
@ -595,9 +597,12 @@ SET(LAUNCHER_SOURCES
resources/pe_light/pe_light.qrc resources/pe_light/pe_light.qrc
resources/pe_colored/pe_colored.qrc resources/pe_colored/pe_colored.qrc
resources/pe_blue/pe_blue.qrc resources/pe_blue/pe_blue.qrc
resources/breeze_dark/breeze_dark.qrc
resources/breeze_light/breeze_light.qrc
resources/OSX/OSX.qrc resources/OSX/OSX.qrc
resources/iOS/iOS.qrc resources/iOS/iOS.qrc
resources/flat/flat.qrc resources/flat/flat.qrc
resources/flat_white/flat_white.qrc
resources/documents/documents.qrc resources/documents/documents.qrc
../${Launcher_Branding_LogoQRC} ../${Launcher_Branding_LogoQRC}
@ -645,6 +650,8 @@ SET(LAUNCHER_SOURCES
ui/themes/ITheme.h ui/themes/ITheme.h
ui/themes/SystemTheme.cpp ui/themes/SystemTheme.cpp
ui/themes/SystemTheme.h ui/themes/SystemTheme.h
ui/themes/ThemeManager.cpp
ui/themes/ThemeManager.h
# Processes # Processes
LaunchController.h LaunchController.h
@ -785,6 +792,8 @@ SET(LAUNCHER_SOURCES
ui/dialogs/ExportInstanceDialog.h ui/dialogs/ExportInstanceDialog.h
ui/dialogs/IconPickerDialog.cpp ui/dialogs/IconPickerDialog.cpp
ui/dialogs/IconPickerDialog.h ui/dialogs/IconPickerDialog.h
ui/dialogs/ImportResourcePackDialog.cpp
ui/dialogs/ImportResourcePackDialog.h
ui/dialogs/LoginDialog.cpp ui/dialogs/LoginDialog.cpp
ui/dialogs/LoginDialog.h ui/dialogs/LoginDialog.h
ui/dialogs/MSALoginDialog.cpp ui/dialogs/MSALoginDialog.cpp
@ -930,6 +939,7 @@ qt_wrap_ui(LAUNCHER_UI
ui/dialogs/SkinUploadDialog.ui ui/dialogs/SkinUploadDialog.ui
ui/dialogs/ExportInstanceDialog.ui ui/dialogs/ExportInstanceDialog.ui
ui/dialogs/IconPickerDialog.ui ui/dialogs/IconPickerDialog.ui
ui/dialogs/ImportResourcePackDialog.ui
ui/dialogs/MSALoginDialog.ui ui/dialogs/MSALoginDialog.ui
ui/dialogs/OfflineLoginDialog.ui ui/dialogs/OfflineLoginDialog.ui
ui/dialogs/AboutDialog.ui ui/dialogs/AboutDialog.ui
@ -948,6 +958,8 @@ qt_add_resources(LAUNCHER_RESOURCES
resources/pe_light/pe_light.qrc resources/pe_light/pe_light.qrc
resources/pe_colored/pe_colored.qrc resources/pe_colored/pe_colored.qrc
resources/pe_blue/pe_blue.qrc resources/pe_blue/pe_blue.qrc
resources/breeze_dark/breeze_dark.qrc
resources/breeze_light/breeze_light.qrc
resources/OSX/OSX.qrc resources/OSX/OSX.qrc
resources/iOS/iOS.qrc resources/iOS/iOS.qrc
resources/flat/flat.qrc resources/flat/flat.qrc
@ -1054,96 +1066,95 @@ if(INSTALL_BUNDLE STREQUAL "full")
COMPONENT Runtime COMPONENT Runtime
) )
# Bundle plugins # 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( install(
DIRECTORY "${QT_PLUGINS_DIR}/imageformats" DIRECTORY "${QT_PLUGINS_DIR}/styles"
CONFIGURATIONS Debug RelWithDebInfo ""
DESTINATION ${PLUGIN_DEST_DIR} DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime COMPONENT Runtime
REGEX "tga|tiff|mng" EXCLUDE
) )
# Icon engines
install( install(
DIRECTORY "${QT_PLUGINS_DIR}/iconengines" DIRECTORY "${QT_PLUGINS_DIR}/styles"
CONFIGURATIONS Release MinSizeRel
DESTINATION ${PLUGIN_DEST_DIR} DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime 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 "d\\." EXCLUDE
REGEX "_debug\\." EXCLUDE REGEX "_debug\\." EXCLUDE
REGEX "\\.dSYM" EXCLUDE REGEX "\\.dSYM" EXCLUDE
) )
# Icon engines endif()
# TLS plugins (Qt 6 only)
if(EXISTS "${QT_PLUGINS_DIR}/tls")
install( install(
DIRECTORY "${QT_PLUGINS_DIR}/iconengines" DIRECTORY "${QT_PLUGINS_DIR}/tls"
CONFIGURATIONS Debug RelWithDebInfo ""
DESTINATION ${PLUGIN_DEST_DIR} DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime 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 "_debug\\." EXCLUDE
REGEX "\\.dSYM" 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() endif()
configure_file( configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/install_prereqs.cmake.in" "${CMAKE_CURRENT_SOURCE_DIR}/install_prereqs.cmake.in"

View 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"));
}

View 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;
};

View File

@ -119,7 +119,7 @@ bool openDirectory(const QString &path, bool ensureExists)
return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath())); return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath()));
}; };
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if(!APPLICATION->isFlatpak()) if(!isFlatpak())
{ {
return IndirectOpen(f); return IndirectOpen(f);
} }
@ -140,7 +140,7 @@ bool openFile(const QString &path)
return QDesktopServices::openUrl(QUrl::fromLocalFile(path)); return QDesktopServices::openUrl(QUrl::fromLocalFile(path));
}; };
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if(!APPLICATION->isFlatpak()) if(!isFlatpak())
{ {
return IndirectOpen(f); return IndirectOpen(f);
} }
@ -158,7 +158,7 @@ bool openFile(const QString &application, const QString &path, const QString &wo
qDebug() << "Opening file" << path << "using" << application; qDebug() << "Opening file" << path << "using" << application;
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) #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 // FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
if(!APPLICATION->isFlatpak()) if(!isFlatpak())
{ {
return IndirectOpen([&]() return IndirectOpen([&]()
{ {
@ -178,7 +178,7 @@ bool run(const QString &application, const QStringList &args, const QString &wor
{ {
qDebug() << "Running" << application << "with args" << args.join(' '); qDebug() << "Running" << application << "with args" << args.join(' ');
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) #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 // FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
return IndirectOpen([&]() return IndirectOpen([&]()
@ -203,7 +203,7 @@ bool openUrl(const QUrl &url)
return QDesktopServices::openUrl(url); return QDesktopServices::openUrl(url);
}; };
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if(!APPLICATION->isFlatpak()) if(!isFlatpak())
{ {
return IndirectOpen(f); return IndirectOpen(f);
} }
@ -216,4 +216,13 @@ bool openUrl(const QUrl &url)
#endif #endif
} }
bool isFlatpak()
{
#ifdef Q_OS_LINUX
return QFile::exists("/.flatpak-info");
#else
return false;
#endif
}
} }

View File

@ -33,4 +33,6 @@ namespace DesktopServices
* Open the URL, most likely in a browser. Maybe. * Open the URL, most likely in a browser. Maybe.
*/ */
bool openUrl(const QUrl &url); bool openUrl(const QUrl &url);
bool isFlatpak();
} }

View File

@ -45,7 +45,11 @@
#include <QTextStream> #include <QTextStream>
#include <QUrl> #include <QUrl>
#include "DesktopServices.h"
#include "StringUtils.h"
#if defined Q_OS_WIN32 #if defined Q_OS_WIN32
#define WIN32_LEAN_AND_MEAN
#include <objbase.h> #include <objbase.h>
#include <objidl.h> #include <objidl.h>
#include <shlguid.h> #include <shlguid.h>
@ -78,22 +82,6 @@ namespace fs = std::filesystem;
namespace fs = ghc::filesystem; namespace fs = ghc::filesystem;
#endif #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 { namespace FS {
void ensureExists(const QDir& dir) void ensureExists(const QDir& dir)
@ -162,9 +150,13 @@ bool ensureFolderPathExists(QString foldernamepath)
return success; 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; using copy_opts = fs::copy_options;
m_copied = 0; // reset counter
// NOTE always deep copy on windows. the alternatives are too messy. // NOTE always deep copy on windows. the alternatives are too messy.
#if defined Q_OS_WIN32 #if defined Q_OS_WIN32
@ -182,6 +174,24 @@ bool copy::operator()(const QString& offset)
if (!m_followSymlinks) if (!m_followSymlinks)
opt |= copy_opts::copy_symlinks; 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 // 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 // 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 src_path = source_it.next();
auto relative_path = src_dir.relativeFilePath(src_path); auto relative_path = src_dir.relativeFilePath(src_path);
if (m_blacklist && m_blacklist->matches(relative_path)) copy_file(src_path, 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;
}
} }
// 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; return err.value() == 0;
} }
@ -214,7 +217,7 @@ bool deletePath(QString path)
{ {
std::error_code err; std::error_code err;
fs::remove_all(toStdString(path), err); fs::remove_all(StringUtils::toStdString(path), err);
if (err) { if (err) {
qWarning() << "Failed to remove files:" << QString::fromStdString(err.message()); 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) #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
return false; return false;
#else #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); return QFile::moveToTrash(path, pathInTrash);
#endif #endif
} }
@ -338,12 +344,37 @@ QString getDesktopDir()
} }
// Cross-platform Shortcut creation // 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) #if defined(Q_OS_MACOS)
location = PathCombine(location, name + ".desktop"); 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); f.open(QIODevice::WriteOnly | QIODevice::Text);
QTextStream stream(&f); QTextStream stream(&f);
@ -355,10 +386,12 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na
<< "\n"; << "\n";
stream << "Type=Application" stream << "Type=Application"
<< "\n"; << "\n";
stream << "TryExec=" << dest.toLocal8Bit() << "\n"; stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n";
stream << "Exec=" << dest.toLocal8Bit() << argstring.toLocal8Bit() << "\n";
stream << "Name=" << name.toLocal8Bit() << "\n"; stream << "Name=" << name.toLocal8Bit() << "\n";
stream << "Icon=" << icon.toLocal8Bit() << "\n"; if (!icon.isEmpty())
{
stream << "Icon=" << icon.toLocal8Bit() << "\n";
}
stream.flush(); stream.flush();
f.close(); 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); f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | QFileDevice::ExeOther);
return true; return true;
#elif defined Q_OS_WIN #elif defined(Q_OS_WIN)
// TODO: Fix QFileInfo targetInfo(target);
// QFile file(PathCombine(location, name + ".lnk"));
// WCHAR *file_w;
// WCHAR *dest_w;
// WCHAR *args_w;
// file.fileName().toWCharArray(file_w);
// dest.toWCharArray(dest_w);
// QString argStr; if (!targetInfo.exists())
// for (int i = 0; i < args.count(); i++) {
// { qWarning() << "Target file does not exist!";
// argStr.append(args[i]); return false;
// argStr.append(" "); }
// }
// argStr.toWCharArray(args_w);
// return SUCCEEDED(CreateLink(file_w, dest_w, args_w)); target = targetInfo.absoluteFilePath();
return false;
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 #else
qWarning("Desktop Shortcuts not supported on your platform!"); qWarning("Desktop Shortcuts not supported on your platform!");
return false; return false;
@ -401,7 +541,8 @@ bool overrideFolder(QString overwritten_path, QString override_path)
std::error_code err; std::error_code err;
fs::copy_options opt = copy_opts::recursive | copy_opts::overwrite_existing; 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) { if (err) {
qCritical() << QString("Failed to apply override from %1 to %2").arg(override_path, overwritten_path); qCritical() << QString("Failed to apply override from %1 to %2").arg(override_path, overwritten_path);

View File

@ -40,6 +40,7 @@
#include <QDir> #include <QDir>
#include <QFlags> #include <QFlags>
#include <QObject>
namespace FS { namespace FS {
@ -75,9 +76,11 @@ bool ensureFilePathExists(QString filenamepath);
*/ */
bool ensureFolderPathExists(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: 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_src.setPath(src);
m_dst.setPath(dst); m_dst.setPath(dst);
@ -87,21 +90,35 @@ class copy {
m_followSymlinks = follow; m_followSymlinks = follow;
return *this; return *this;
} }
copy& blacklist(const IPathMatcher* filter) copy& matcher(const IPathMatcher* filter)
{ {
m_blacklist = filter; m_matcher = filter;
return *this; 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: private:
bool operator()(const QString& offset); bool operator()(const QString& offset, bool dryRun = false);
private: private:
bool m_followSymlinks = true; bool m_followSymlinks = true;
const IPathMatcher* m_blacklist = nullptr; const IPathMatcher* m_matcher = nullptr;
bool m_whitelist = false;
QDir m_src; QDir m_src;
QDir m_dst; 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 // Overrides one folder with the contents of another, preserving items exclusive to the first folder
// Equivalent to doing QDir::rename, but allowing for overrides // Equivalent to doing QDir::rename, but allowing for overrides
bool overrideFolder(QString overwritten_path, QString override_path); 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);
} }

View 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;
}

View 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;
};

View File

@ -5,15 +5,17 @@
#include "pathmatcher/RegexpMatcher.h" #include "pathmatcher/RegexpMatcher.h"
#include <QtConcurrentRun> #include <QtConcurrentRun>
InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, bool copySaves, bool keepPlaytime) InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs)
{ {
m_origInstance = origInstance; 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... // FIXME: get this from the original instance type...
auto matcherReal = new RegexpMatcher("[.]?minecraft/saves"); auto matcherReal = new RegexpMatcher(filters);
matcherReal->caseSensitive(false); matcherReal->caseSensitive(false);
m_matcher.reset(matcherReal); m_matcher.reset(matcherReal);
} }
@ -23,10 +25,12 @@ void InstanceCopyTask::executeTask()
{ {
setStatus(tr("Copying instance %1").arg(m_origInstance->name())); setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath); m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]{
folderCopy.followSymlinks(false).blacklist(m_matcher.get()); 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>::finished, this, &InstanceCopyTask::copyFinished);
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &InstanceCopyTask::copyAborted); connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &InstanceCopyTask::copyAborted);
m_copyFutureWatcher.setFuture(m_copyFuture); m_copyFutureWatcher.setFuture(m_copyFuture);

View File

@ -1,20 +1,21 @@
#pragma once #pragma once
#include "tasks/Task.h"
#include "net/NetJob.h"
#include <QUrl>
#include <QFuture> #include <QFuture>
#include <QFutureWatcher> #include <QFutureWatcher>
#include "settings/SettingsObject.h" #include <QUrl>
#include "BaseVersion.h"
#include "BaseInstance.h" #include "BaseInstance.h"
#include "BaseVersion.h"
#include "InstanceCopyPrefs.h"
#include "InstanceTask.h" #include "InstanceTask.h"
#include "net/NetJob.h"
#include "settings/SettingsObject.h"
#include "tasks/Task.h"
class InstanceCopyTask : public InstanceTask class InstanceCopyTask : public InstanceTask
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit InstanceCopyTask(InstancePtr origInstance, bool copySaves, bool keepPlaytime); explicit InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs);
protected: protected:
//! Entry point for tasks. //! Entry point for tasks.
@ -22,7 +23,8 @@ protected:
void copyFinished(); void copyFinished();
void copyAborted(); void copyAborted();
private: /* data */ private:
/* data */
InstancePtr m_origInstance; InstancePtr m_origInstance;
QFuture<bool> m_copyFuture; QFuture<bool> m_copyFuture;
QFutureWatcher<bool> m_copyFutureWatcher; QFutureWatcher<bool> m_copyFutureWatcher;

View File

@ -25,9 +25,13 @@ void InstanceCreationTask::executeTask()
return; return;
qWarning() << "Instance creation failed!"; qWarning() << "Instance creation failed!";
if (!m_error_message.isEmpty()) if (!m_error_message.isEmpty()) {
qWarning() << "Reason: " << m_error_message; 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; return;
} }

View File

@ -164,18 +164,14 @@ void InstanceImportTask::processZipPack()
} }
else else
{ {
QString mmcRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg"); QStringList paths_to_ignore { "overrides/" };
QString flameRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json");
if (!mmcRoot.isNull()) if (QString mmcRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg", paths_to_ignore); !mmcRoot.isNull()) {
{
// process as MultiMC instance/pack // process as MultiMC instance/pack
qDebug() << "MultiMC:" << mmcRoot; qDebug() << "MultiMC:" << mmcRoot;
root = mmcRoot; root = mmcRoot;
m_modpackType = ModpackType::MultiMC; m_modpackType = ModpackType::MultiMC;
} } else if (QString flameRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json", paths_to_ignore); !flameRoot.isNull()) {
else if(!flameRoot.isNull())
{
// process as Flame pack // process as Flame pack
qDebug() << "Flame:" << flameRoot; qDebug() << "Flame:" << flameRoot;
root = flameRoot; root = flameRoot;

View File

@ -36,7 +36,7 @@
#include "JavaCommon.h" #include "JavaCommon.h"
#include "java/JavaUtils.h" #include "java/JavaUtils.h"
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"
#include <MMCStrings.h>
#include <QRegularExpression> #include <QRegularExpression>
bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget *parent) bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget *parent)

View File

@ -1,8 +0,0 @@
#pragma once
#include <QString>
namespace Strings
{
int naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs);
}

View File

@ -28,11 +28,11 @@ QString Time::prettifyDuration(int64_t duration) {
int days = (int) (duration / 24); int days = (int) (duration / 24);
if((hours == 0)&&(days == 0)) 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) 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);
} }

View File

@ -39,6 +39,7 @@
#include "MMCZip.h" #include "MMCZip.h"
#include "FileSystem.h" #include "FileSystem.h"
#include <QCoreApplication>
#include <QDebug> #include <QDebug>
// ours // ours
@ -228,23 +229,27 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
} }
// ours // 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); QuaZipDir rootDir(zip, root);
for(auto fileName: rootDir.entryList(QDir::Files)) for (auto&& fileName : rootDir.entryList(QDir::Files)) {
{ if (fileName == what)
if(fileName == what)
return root; return root;
QCoreApplication::processEvents();
} }
for(auto fileName: rootDir.entryList(QDir::Dirs))
{ // Recurse the search to non-ignored subfolders
QString result = findFolderOfFileInZip(zip, what, root + fileName); for (auto&& fileName : rootDir.entryList(QDir::Dirs)) {
if(!result.isEmpty()) if (ignore_paths.contains(fileName))
{ continue;
QString result = findFolderOfFileInZip(zip, what, ignore_paths, root + fileName);
if (!result.isEmpty())
return result; return result;
}
} }
return QString();
return {};
} }
// ours // ours

View File

@ -80,9 +80,11 @@ namespace MMCZip
/** /**
* Find a single file in archive by file name (not path) * 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 * \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 * Find a multiple files of the same name in archive by file name

View File

@ -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 /// 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(); return (location < s.length()) ? s.at(location) : QChar();
} }
/// TAKEN FROM Qt, because it doesn't expose it intelligently /// 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 // skip spaces, tabs and 0's
QChar c1 = getNextChar(s1, l1); QChar c1 = getNextChar(s1, l1);
while (c1.isSpace()) while (c1.isSpace())
c1 = getNextChar(s1, ++l1); c1 = getNextChar(s1, ++l1);
QChar c2 = getNextChar(s2, l2); QChar c2 = getNextChar(s2, l2);
while (c2.isSpace()) while (c2.isSpace())
c2 = getNextChar(s2, ++l2); c2 = getNextChar(s2, ++l2);
if (c1.isDigit() && c2.isDigit()) if (c1.isDigit() && c2.isDigit()) {
{
while (c1.digitValue() == 0) while (c1.digitValue() == 0)
c1 = getNextChar(s1, ++l1); c1 = getNextChar(s1, ++l1);
while (c2.digitValue() == 0) while (c2.digitValue() == 0)
@ -30,11 +32,8 @@ int Strings::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensit
int lookAheadLocation2 = l2; int lookAheadLocation2 = l2;
int currentReturnValue = 0; int currentReturnValue = 0;
// find the last digit, setting currentReturnValue as we go if it isn't equal // find the last digit, setting currentReturnValue as we go if it isn't equal
for (QChar lookAhead1 = c1, lookAhead2 = c2; for (QChar lookAhead1 = c1, lookAhead2 = c2; (lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length());
(lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length()); lookAhead1 = getNextChar(s1, ++lookAheadLocation1), lookAhead2 = getNextChar(s2, ++lookAheadLocation2)) {
lookAhead1 = getNextChar(s1, ++lookAheadLocation1),
lookAhead2 = getNextChar(s2, ++lookAheadLocation2))
{
bool is1ADigit = !lookAhead1.isNull() && lookAhead1.isDigit(); bool is1ADigit = !lookAhead1.isNull() && lookAhead1.isDigit();
bool is2ADigit = !lookAhead2.isNull() && lookAhead2.isDigit(); bool is2ADigit = !lookAhead2.isNull() && lookAhead2.isDigit();
if (!is1ADigit && !is2ADigit) if (!is1ADigit && !is2ADigit)
@ -43,14 +42,10 @@ int Strings::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensit
return -1; return -1;
if (!is2ADigit) if (!is2ADigit)
return 1; return 1;
if (currentReturnValue == 0) if (currentReturnValue == 0) {
{ if (lookAhead1 < lookAhead2) {
if (lookAhead1 < lookAhead2)
{
currentReturnValue = -1; currentReturnValue = -1;
} } else if (lookAhead1 > lookAhead2) {
else if (lookAhead1 > lookAhead2)
{
currentReturnValue = 1; currentReturnValue = 1;
} }
} }
@ -58,19 +53,24 @@ int Strings::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensit
if (currentReturnValue != 0) if (currentReturnValue != 0)
return currentReturnValue; return currentReturnValue;
} }
if (cs == Qt::CaseInsensitive)
{ if (cs == Qt::CaseInsensitive) {
if (!c1.isLower()) if (!c1.isLower())
c1 = c1.toLower(); c1 = c1.toLower();
if (!c2.isLower()) if (!c2.isLower())
c2 = c2.toLower(); c2 = c2.toLower();
} }
int r = QString::localeAwareCompare(c1, c2); int r = QString::localeAwareCompare(c1, c2);
if (r < 0) if (r < 0)
return -1; return -1;
if (r > 0) if (r > 0)
return 1; return 1;
l1 += 1;
l2 += 1;
} }
// The two strings are the same (02 == 2) so fall back to the normal sort // The two strings are the same (02 == 2) so fall back to the normal sort
return QString::compare(s1, s2, cs); return QString::compare(s1, s2, cs);
} }

32
launcher/StringUtils.h Normal file
View 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

View File

@ -311,14 +311,14 @@ QModelIndex VersionProxyModel::index(int row, int column, const QModelIndex &par
int VersionProxyModel::columnCount(const QModelIndex &parent) const 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 int VersionProxyModel::rowCount(const QModelIndex &parent) const
{ {
if(sourceModel()) if(sourceModel())
{ {
return sourceModel()->rowCount(); return sourceModel()->rowCount(parent);
} }
return 0; return 0;
} }

View File

@ -242,7 +242,7 @@ Qt::DropActions IconList::supportedDropActions() const
return Qt::CopyAction; 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) if (action == Qt::IgnoreAction)
return true; return true;
@ -302,7 +302,7 @@ QVariant IconList::data(const QModelIndex &index, int role) const
int IconList::rowCount(const QModelIndex &parent) const int IconList::rowCount(const QModelIndex &parent) const
{ {
return icons.size(); return parent.isValid() ? 0 : icons.size();
} }
void IconList::installIcons(const QStringList &iconFiles) void IconList::installIcons(const QStringList &iconFiles)

View File

@ -1,9 +1,10 @@
#include "JavaInstall.h" #include "JavaInstall.h"
#include <MMCStrings.h>
#include "StringUtils.h"
bool JavaInstall::operator<(const JavaInstall &rhs) 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) if(archCompare != 0)
return archCompare < 0; return archCompare < 0;
if(id < rhs.id) if(id < rhs.id)
@ -14,7 +15,7 @@ bool JavaInstall::operator<(const JavaInstall &rhs)
{ {
return false; 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) bool JavaInstall::operator==(const JavaInstall &rhs)

View File

@ -41,7 +41,6 @@
#include "java/JavaInstallList.h" #include "java/JavaInstallList.h"
#include "java/JavaCheckerJob.h" #include "java/JavaCheckerJob.h"
#include "java/JavaUtils.h" #include "java/JavaUtils.h"
#include "MMCStrings.h"
#include "minecraft/VersionFilterData.h" #include "minecraft/VersionFilterData.h"
JavaInstallList::JavaInstallList(QObject *parent) : BaseVersionList(parent) 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); 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(); beginResetModel();
m_vlist = versions; m_vlist = versions;
@ -137,7 +136,7 @@ void JavaInstallList::updateListData(QList<BaseVersionPtr> versions)
m_loadTask.reset(); 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 rleft = std::dynamic_pointer_cast<JavaInstall>(right);
auto rright = std::dynamic_pointer_cast<JavaInstall>(left); 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) for (auto java : candidates)
{ {
//qDebug() << java->id << java->arch << " at " << java->path; //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) if (bp_java)
{ {

View File

@ -42,7 +42,7 @@ public:
Task::Ptr getLoadTask() override; Task::Ptr getLoadTask() override;
bool isLoaded() override; bool isLoaded() override;
const BaseVersionPtr at(int i) const override; const BaseVersion::Ptr at(int i) const override;
int count() const override; int count() const override;
void sortVersions() override; void sortVersions() override;
@ -50,7 +50,7 @@ public:
RoleList providesRoles() const override; RoleList providesRoles() const override;
public slots: public slots:
void updateListData(QList<BaseVersionPtr> versions) override; void updateListData(QList<BaseVersion::Ptr> versions) override;
protected: protected:
void load(); void load();
@ -59,7 +59,7 @@ protected:
protected: protected:
Status m_status = Status::NotDone; Status m_status = Status::NotDone;
shared_qobject_ptr<JavaListLoadTask> m_loadTask; shared_qobject_ptr<JavaListLoadTask> m_loadTask;
QList<BaseVersionPtr> m_vlist; QList<BaseVersion::Ptr> m_vlist;
}; };
class JavaListLoadTask : public Task class JavaListLoadTask : public Task

View File

@ -439,19 +439,28 @@ QList<QString> JavaUtils::FindJavaPaths()
javas.append(FS::PathCombine(prefix, "bin/java")); 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 // oracle RPMs
scanJavaDir("/usr/java"); scanJavaDirs("/usr/java");
// general locations used by distro packaging // general locations used by distro packaging
scanJavaDir("/usr/lib/jvm"); scanJavaDirs("/usr/lib/jvm");
scanJavaDir("/usr/lib64/jvm"); scanJavaDirs("/usr/lib64/jvm");
scanJavaDir("/usr/lib32/jvm"); scanJavaDirs("/usr/lib32/jvm");
// javas stored in Prism Launcher's folder // javas stored in Prism Launcher's folder
scanJavaDir("java"); scanJavaDirs("java");
// manually installed JDKs in /opt // manually installed JDKs in /opt
scanJavaDir("/opt/jdk"); scanJavaDirs("/opt/jdk");
scanJavaDir("/opt/jdks"); scanJavaDirs("/opt/jdks");
// flatpak // flatpak
scanJavaDir("/app/jdk"); scanJavaDirs("/app/jdk");
javas = addJavasFromEnv(javas); javas = addJavasFromEnv(javas);
javas.removeDuplicates(); javas.removeDuplicates();
return javas; return javas;

View File

@ -1,5 +1,6 @@
#include "JavaVersion.h" #include "JavaVersion.h"
#include <MMCStrings.h>
#include "StringUtils.h"
#include <QRegularExpression> #include <QRegularExpression>
#include <QString> #include <QString>
@ -98,12 +99,12 @@ bool JavaVersion::operator<(const JavaVersion &rhs)
else if(thisPre && rhsPre) else if(thisPre && rhsPre)
{ {
// both are prereleases - use natural compare... // 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 // neither is prerelease, so they are the same -> this cannot be less than rhs
return false; 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) bool JavaVersion::operator==(const JavaVersion &rhs)

View File

@ -37,7 +37,6 @@
#include "launch/LaunchTask.h" #include "launch/LaunchTask.h"
#include "MessageLevel.h" #include "MessageLevel.h"
#include "MMCStrings.h"
#include "java/JavaChecker.h" #include "java/JavaChecker.h"
#include "tasks/Task.h" #include "tasks/Task.h"
#include <QDebug> #include <QDebug>

View File

@ -81,14 +81,19 @@ int main(int argc, char *argv[])
Q_INIT_RESOURCE(pe_light); Q_INIT_RESOURCE(pe_light);
Q_INIT_RESOURCE(pe_blue); Q_INIT_RESOURCE(pe_blue);
Q_INIT_RESOURCE(pe_colored); Q_INIT_RESOURCE(pe_colored);
Q_INIT_RESOURCE(breeze_dark);
Q_INIT_RESOURCE(breeze_light);
Q_INIT_RESOURCE(OSX); Q_INIT_RESOURCE(OSX);
Q_INIT_RESOURCE(iOS); Q_INIT_RESOURCE(iOS);
Q_INIT_RESOURCE(flat); Q_INIT_RESOURCE(flat);
Q_INIT_RESOURCE(flat_white);
return app.exec(); return app.exec();
} }
case Application::Failed: case Application::Failed:
return 1; return 1;
case Application::Succeeded: case Application::Succeeded:
return 0; return 0;
default:
return -1;
} }
} }

View File

@ -24,7 +24,7 @@ Index::Index(QObject *parent)
: QAbstractListModel(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) : QAbstractListModel(parent), m_lists(lists)
{ {
for (int i = 0; i < m_lists.size(); ++i) for (int i = 0; i < m_lists.size(); ++i)
@ -41,7 +41,7 @@ QVariant Index::data(const QModelIndex &index, int role) const
return QVariant(); return QVariant();
} }
VersionListPtr list = m_lists.at(index.row()); VersionList::Ptr list = m_lists.at(index.row());
switch (role) switch (role)
{ {
case Qt::DisplayRole: case Qt::DisplayRole:
@ -58,11 +58,11 @@ QVariant Index::data(const QModelIndex &index, int role) const
} }
int Index::rowCount(const QModelIndex &parent) 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 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 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); 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) if(!out)
{ {
out = std::make_shared<VersionList>(uid); out = std::make_shared<VersionList>(uid);
@ -92,7 +92,7 @@ VersionListPtr Index::get(const QString &uid)
return out; 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); auto list = get(uid);
return list->getVersion(version); return list->getVersion(version);
@ -105,7 +105,7 @@ void Index::parse(const QJsonObject& obj)
void Index::merge(const std::shared_ptr<Index> &other) void Index::merge(const std::shared_ptr<Index> &other)
{ {
const QVector<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 // initial load, no need to merge
if (m_lists.isEmpty()) if (m_lists.isEmpty())
{ {
@ -120,7 +120,7 @@ void Index::merge(const std::shared_ptr<Index> &other)
} }
else else
{ {
for (const VersionListPtr &list : lists) for (const VersionList::Ptr &list : lists)
{ {
if (m_uids.contains(list->uid())) 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]() connect(list.get(), &VersionList::nameChanged, this, [this, row]()
{ {

View File

@ -19,20 +19,19 @@
#include <memory> #include <memory>
#include "BaseEntity.h" #include "BaseEntity.h"
#include "meta/VersionList.h"
class Task; class Task;
namespace Meta namespace Meta
{ {
using VersionListPtr = std::shared_ptr<class VersionList>;
using VersionPtr = std::shared_ptr<class Version>;
class Index : public QAbstractListModel, public BaseEntity class Index : public QAbstractListModel, public BaseEntity
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit Index(QObject *parent = nullptr); 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 enum
{ {
@ -49,21 +48,21 @@ public:
QString localFilename() const override { return "index.json"; } QString localFilename() const override { return "index.json"; }
// queries // queries
VersionListPtr get(const QString &uid); VersionList::Ptr get(const QString &uid);
VersionPtr get(const QString &uid, const QString &version); Version::Ptr get(const QString &uid, const QString &version);
bool hasUid(const QString &uid) const; 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 public: // for usage by parsers only
void merge(const std::shared_ptr<Index> &other); void merge(const std::shared_ptr<Index> &other);
void parse(const QJsonObject &obj) override; void parse(const QJsonObject &obj) override;
private: private:
QVector<VersionListPtr> m_lists; QVector<VersionList::Ptr> m_lists;
QHash<QString, VersionListPtr> m_uids; QHash<QString, VersionList::Ptr> m_uids;
void connectVersionList(const int row, const VersionListPtr &list); void connectVersionList(const int row, const VersionList::Ptr &list);
}; };
} }

View File

@ -37,11 +37,11 @@ MetadataVersion currentFormatVersion()
static std::shared_ptr<Index> parseIndexInternal(const QJsonObject &obj) static std::shared_ptr<Index> parseIndexInternal(const QJsonObject &obj)
{ {
const QVector<QJsonObject> objects = requireIsArrayOf<QJsonObject>(obj, "packages"); const QVector<QJsonObject> objects = requireIsArrayOf<QJsonObject>(obj, "packages");
QVector<VersionListPtr> lists; QVector<VersionList::Ptr> lists;
lists.reserve(objects.size()); lists.reserve(objects.size());
std::transform(objects.begin(), objects.end(), std::back_inserter(lists), [](const QJsonObject &obj) std::transform(objects.begin(), objects.end(), std::back_inserter(lists), [](const QJsonObject &obj)
{ {
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())); list->setName(ensureString(obj, "name", QString()));
return list; return list;
}); });
@ -49,9 +49,9 @@ static std::shared_ptr<Index> parseIndexInternal(const QJsonObject &obj)
} }
// Version // 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->setTime(QDateTime::fromString(requireString(obj, "releaseTime"), Qt::ISODate).toMSecsSinceEpoch() / 1000);
version->setType(ensureString(obj, "type", QString())); version->setType(ensureString(obj, "type", QString()));
version->setRecommended(ensureBoolean(obj, QString("recommended"), false)); version->setRecommended(ensureBoolean(obj, QString("recommended"), false));
@ -63,9 +63,9 @@ static VersionPtr parseCommonVersion(const QString &uid, const QJsonObject &obj)
return version; 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), version->setData(OneSixVersionFormat::versionFileFromJson(QJsonDocument(obj),
QString("%1/%2.json").arg(version->uid(), version->version()), 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 // 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 QString uid = requireString(obj, "uid");
const QVector<QJsonObject> versionsRaw = requireIsArrayOf<QJsonObject>(obj, "versions"); const QVector<QJsonObject> versionsRaw = requireIsArrayOf<QJsonObject>(obj, "versions");
QVector<VersionPtr> versions; QVector<Version::Ptr> versions;
versions.reserve(versionsRaw.size()); versions.reserve(versionsRaw.size());
std::transform(versionsRaw.begin(), versionsRaw.end(), std::back_inserter(versions), [uid](const QJsonObject &vObj) 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; return version;
}); });
VersionListPtr list = std::make_shared<VersionList>(uid); VersionList::Ptr list = std::make_shared<VersionList>(uid);
list->setName(ensureString(obj, "name", QString())); list->setName(ensureString(obj, "name", QString()));
list->setVersions(versions); list->setVersions(versions);
return list; return list;

View File

@ -60,11 +60,6 @@ struct Require
QString suggests; 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>; using RequireSet = std::set<Require>;
void parseIndex(const QJsonObject &obj, Index *ptr); void parseIndex(const QJsonObject &obj, Index *ptr);

View File

@ -54,7 +54,7 @@ void Meta::Version::parse(const QJsonObject& obj)
parseVersion(obj, this); parseVersion(obj, this);
} }
void Meta::Version::mergeFromList(const Meta::VersionPtr& other) void Meta::Version::mergeFromList(const Meta::Version::Ptr& other)
{ {
if(other->m_providesRecommendations) 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); mergeFromList(other);
if(other->m_data) if(other->m_data)

View File

@ -30,13 +30,14 @@
namespace Meta namespace Meta
{ {
using VersionPtr = std::shared_ptr<class Version>;
class Version : public QObject, public BaseVersion, public BaseEntity class Version : public QObject, public BaseVersion, public BaseEntity
{ {
Q_OBJECT Q_OBJECT
public: /* con/des */ public:
using Ptr = std::shared_ptr<Version>;
explicit Version(const QString &uid, const QString &version); explicit Version(const QString &uid, const QString &version);
virtual ~Version(); virtual ~Version();
@ -78,8 +79,8 @@ public: /* con/des */
return m_data != nullptr; return m_data != nullptr;
} }
void merge(const VersionPtr &other); void merge(const Version::Ptr &other);
void mergeFromList(const VersionPtr &other); void mergeFromList(const Version::Ptr &other);
void parse(const QJsonObject &obj) override; void parse(const QJsonObject &obj) override;
QString localFilename() const override; QString localFilename() const override;
@ -113,4 +114,4 @@ private:
}; };
} }
Q_DECLARE_METATYPE(Meta::VersionPtr) Q_DECLARE_METATYPE(Meta::Version::Ptr)

View File

@ -40,7 +40,7 @@ bool VersionList::isLoaded()
return BaseEntity::isLoaded(); return BaseEntity::isLoaded();
} }
const BaseVersionPtr VersionList::at(int i) const const BaseVersion::Ptr VersionList::at(int i) const
{ {
return m_versions.at(i); return m_versions.at(i);
} }
@ -52,7 +52,7 @@ int VersionList::count() const
void VersionList::sortVersions() void VersionList::sortVersions()
{ {
beginResetModel(); 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(); return *a.get() < *b.get();
}); });
@ -66,7 +66,7 @@ QVariant VersionList::data(const QModelIndex &index, int role) const
return QVariant(); return QVariant();
} }
VersionPtr version = m_versions.at(index.row()); Version::Ptr version = m_versions.at(index.row());
switch (role) switch (role)
{ {
@ -129,9 +129,9 @@ QString VersionList::humanReadable() const
return m_name.isEmpty() ? m_uid : m_name; 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) if(!out)
{ {
out = std::make_shared<Version>(m_uid, version); out = std::make_shared<Version>(m_uid, version);
@ -143,7 +143,7 @@ VersionPtr VersionList::getVersion(const QString &version)
bool VersionList::hasVersion(QString version) const bool VersionList::hasVersion(QString version) const
{ {
auto ver = std::find_if(m_versions.constBegin(), m_versions.constEnd(), 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()); return (ver != m_versions.constEnd());
} }
@ -153,11 +153,11 @@ void VersionList::setName(const QString &name)
emit nameChanged(name); emit nameChanged(name);
} }
void VersionList::setVersions(const QVector<VersionPtr> &versions) void VersionList::setVersions(const QVector<Version::Ptr> &versions)
{ {
beginResetModel(); beginResetModel();
m_versions = versions; 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(); 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... // 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; m_recommended = recommendedIt == m_versions.constEnd() ? nullptr : *recommendedIt;
endResetModel(); endResetModel();
} }
@ -179,7 +179,7 @@ void VersionList::parse(const QJsonObject& obj)
} }
// FIXME: this is dumb, we have 'recommended' as part of the metadata already... // 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) if(!a)
return b; return b;
@ -194,7 +194,7 @@ static const Meta::VersionPtr &getBetterVersion(const Meta::VersionPtr &a, const
return (a->type() == "release" ? a : b); 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) 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) if (m_name != other->m_name)
{ {
@ -216,7 +216,7 @@ void VersionList::merge(const VersionListPtr &other)
{ {
qWarning() << "Empty list loaded ..."; 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 // we already have the version. merge the contents
if (m_lookup.contains(version->version())) if (m_lookup.contains(version->version()))
@ -235,7 +235,7 @@ void VersionList::merge(const VersionListPtr &other)
endResetModel(); 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 // FIXME: do not disconnect from everythin, disconnect only the lambdas here
version->disconnect(); 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); }); 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; return m_recommended;
} }

View File

@ -20,10 +20,10 @@
#include <QJsonObject> #include <QJsonObject>
#include <memory> #include <memory>
#include "meta/Version.h"
namespace Meta namespace Meta
{ {
using VersionPtr = std::shared_ptr<class Version>;
using VersionListPtr = std::shared_ptr<class VersionList>;
class VersionList : public BaseVersionList, public BaseEntity class VersionList : public BaseVersionList, public BaseEntity
{ {
@ -33,6 +33,8 @@ class VersionList : public BaseVersionList, public BaseEntity
public: public:
explicit VersionList(const QString &uid, QObject *parent = nullptr); explicit VersionList(const QString &uid, QObject *parent = nullptr);
using Ptr = std::shared_ptr<VersionList>;
enum Roles enum Roles
{ {
UidRole = Qt::UserRole + 100, UidRole = Qt::UserRole + 100,
@ -43,11 +45,11 @@ public:
Task::Ptr getLoadTask() override; Task::Ptr getLoadTask() override;
bool isLoaded() override; bool isLoaded() override;
const BaseVersionPtr at(int i) const override; const BaseVersion::Ptr at(int i) const override;
int count() const override; int count() const override;
void sortVersions() override; void sortVersions() override;
BaseVersionPtr getRecommended() const override; BaseVersion::Ptr getRecommended() const override;
QVariant data(const QModelIndex &index, int role) const override; QVariant data(const QModelIndex &index, int role) const override;
RoleList providesRoles() const override; RoleList providesRoles() const override;
@ -65,38 +67,38 @@ public:
} }
QString humanReadable() const; QString humanReadable() const;
VersionPtr getVersion(const QString &version); Version::Ptr getVersion(const QString &version);
bool hasVersion(QString version) const; bool hasVersion(QString version) const;
QVector<VersionPtr> versions() const QVector<Version::Ptr> versions() const
{ {
return m_versions; return m_versions;
} }
public: // for usage only by parsers public: // for usage only by parsers
void setName(const QString &name); void setName(const QString &name);
void setVersions(const QVector<VersionPtr> &versions); void setVersions(const QVector<Version::Ptr> &versions);
void merge(const VersionListPtr &other); void merge(const VersionList::Ptr &other);
void mergeFromIndex(const VersionListPtr &other); void mergeFromIndex(const VersionList::Ptr &other);
void parse(const QJsonObject &obj) override; void parse(const QJsonObject &obj) override;
signals: signals:
void nameChanged(const QString &name); void nameChanged(const QString &name);
protected slots: protected slots:
void updateListData(QList<BaseVersionPtr>) override void updateListData(QList<BaseVersion::Ptr>) override
{ {
} }
private: private:
QVector<VersionPtr> m_versions; QVector<Version::Ptr> m_versions;
QHash<QString, VersionPtr> m_lookup; QHash<QString, Version::Ptr> m_lookup;
QString m_uid; QString m_uid;
QString m_name; 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)

View File

@ -10,7 +10,7 @@ typedef std::shared_ptr<Agent> AgentPtr;
class Agent { class Agent {
public: public:
Agent(LibraryPtr library, QString &argument) Agent(LibraryPtr library, const QString &argument)
{ {
m_library = library; m_library = library;
m_argument = argument; m_argument = argument;

View File

@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only // 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 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * 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 * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -43,7 +44,6 @@
#include "settings/SettingsObject.h" #include "settings/SettingsObject.h"
#include "Application.h" #include "Application.h"
#include "MMCStrings.h"
#include "pathmatcher/RegexpMatcher.h" #include "pathmatcher/RegexpMatcher.h"
#include "pathmatcher/MultiMatcher.h" #include "pathmatcher/MultiMatcher.h"
#include "FileSystem.h" #include "FileSystem.h"
@ -439,6 +439,17 @@ QStringList MinecraftInstance::javaArguments()
return args; 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> MinecraftInstance::getVariables()
{ {
QMap<QString, QString> out; QMap<QString, QString> out;
@ -630,26 +641,13 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
launchScript += "sessionId " + session->session + "\n"; 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()) for (auto trait : profile->getTraits())
{ {
launchScript += "traits " + trait + "\n"; launchScript += "traits " + trait + "\n";
} }
launchScript += "launcher onesix\n";
launchScript += "launcher " + getLauncher() + "\n";
// qDebug() << "Generated launch script:" << launchScript; // qDebug() << "Generated launch script:" << launchScript;
return launchScript; return launchScript;
} }
@ -785,6 +783,8 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
out << "Window size: " + QString::number(width) + " x " + QString::number(height); out << "Window size: " + QString::number(width) + " x " + QString::number(height);
} }
out << ""; out << "";
out << "Launcher: " + getLauncher();
out << "";
return out; return out;
} }
@ -1096,8 +1096,6 @@ std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList() c
if (!m_resource_pack_list) if (!m_resource_pack_list)
{ {
m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir())); 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; return m_resource_pack_list;
} }
@ -1107,8 +1105,6 @@ std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList() con
if (!m_texture_pack_list) if (!m_texture_pack_list)
{ {
m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir())); 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; return m_texture_pack_list;
} }
@ -1118,8 +1114,6 @@ std::shared_ptr<ShaderPackFolderModel> MinecraftInstance::shaderPackList() const
if (!m_shader_pack_list) if (!m_shader_pack_list)
{ {
m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir())); 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; return m_shader_pack_list;
} }

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only // 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 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -130,6 +131,7 @@ public:
QString createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin); QString createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin);
/// get arguments passed to java /// get arguments passed to java
QStringList javaArguments(); QStringList javaArguments();
QString getLauncher();
/// get variables for launch command variable substitution/environment /// get variables for launch command variable substitution/environment
QMap<QString, QString> getVariables() override; QMap<QString, QString> getVariables() override;

View File

@ -135,7 +135,7 @@ QJsonObject libDownloadInfoToJson(MojangLibraryDownloadInfo::Ptr libinfo)
{ {
out.insert("artifact", downloadInfoToJson(libinfo->artifact)); out.insert("artifact", downloadInfoToJson(libinfo->artifact));
} }
if(libinfo->classifiers.size()) if(!libinfo->classifiers.isEmpty())
{ {
QJsonObject classifiersOut; QJsonObject classifiersOut;
for(auto iter = libinfo->classifiers.begin(); iter != libinfo->classifiers.end(); iter++) 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)); out.insert("assetIndex", assetIndexToJson(in->mojangAssetIndex));
} }
if(in->mojangDownloads.size()) if(!in->mojangDownloads.isEmpty())
{ {
QJsonObject downloadsOut; QJsonObject downloadsOut;
for(auto iter = in->mojangDownloads.begin(); iter != in->mojangDownloads.end(); iter++) 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); 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) QJsonDocument MojangVersionFormat::versionFileToJson(const VersionFilePtr &patch)
@ -396,7 +405,7 @@ QJsonObject MojangVersionFormat::libraryToJson(Library *library)
iter++; iter++;
} }
libRoot.insert("natives", nativeList); libRoot.insert("natives", nativeList);
if (library->m_extractExcludes.size()) if (!library->m_extractExcludes.isEmpty())
{ {
QJsonArray excludes; QJsonArray excludes;
QJsonObject extract; QJsonObject extract;
@ -408,7 +417,7 @@ QJsonObject MojangVersionFormat::libraryToJson(Library *library)
libRoot.insert("extract", extract); libRoot.insert("extract", extract);
} }
} }
if (library->m_rules.size()) if (!library->m_rules.isEmpty())
{ {
QJsonArray allRules; QJsonArray allRules;
for (auto &rule : library->m_rules) for (auto &rule : library->m_rules)

View File

@ -63,13 +63,13 @@ LibraryPtr OneSixVersionFormat::libraryFromJson(ProblemContainer & problems, con
QJsonObject OneSixVersionFormat::libraryToJson(Library *library) QJsonObject OneSixVersionFormat::libraryToJson(Library *library)
{ {
QJsonObject libRoot = MojangVersionFormat::libraryToJson(library); QJsonObject libRoot = MojangVersionFormat::libraryToJson(library);
if (library->m_absoluteURL.size()) if (!library->m_absoluteURL.isEmpty())
libRoot.insert("MMC-absoluteUrl", library->m_absoluteURL); libRoot.insert("MMC-absoluteUrl", library->m_absoluteURL);
if (library->m_hint.size()) if (!library->m_hint.isEmpty())
libRoot.insert("MMC-hint", library->m_hint); libRoot.insert("MMC-hint", library->m_hint);
if (library->m_filename.size()) if (!library->m_filename.isEmpty())
libRoot.insert("MMC-filename", library->m_filename); libRoot.insert("MMC-filename", library->m_filename);
if (library->m_displayname.size()) if (!library->m_displayname.isEmpty())
libRoot.insert("MMC-displayname", library->m_displayname); libRoot.insert("MMC-displayname", library->m_displayname);
return libRoot; return libRoot;
} }
@ -225,11 +225,10 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
{ {
QJsonObject agentObj = requireObject(agentVal); QJsonObject agentObj = requireObject(agentVal);
auto lib = libraryFromJson(*out, agentObj, filename); auto lib = libraryFromJson(*out, agentObj, filename);
QString arg = ""; QString arg = "";
if (agentObj.contains("argument")) readString(agentObj, "argument", arg);
{
readString(agentObj, "argument", arg);
}
AgentPtr agent(new Agent(lib, arg)); AgentPtr agent(new Agent(lib, arg));
out->agents.append(agent); out->agents.append(agent);
} }
@ -332,6 +331,20 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch
writeString(root, "appletClass", patch->appletClass); writeString(root, "appletClass", patch->appletClass);
writeStringList(root, "+tweakers", patch->addTweakers); writeStringList(root, "+tweakers", patch->addTweakers);
writeStringList(root, "+traits", patch->traits.values()); 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()) if (!patch->libraries.isEmpty())
{ {
QJsonArray array; QJsonArray array;

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only // 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 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -47,7 +48,6 @@
#include "Exception.h" #include "Exception.h"
#include "minecraft/OneSixVersionFormat.h" #include "minecraft/OneSixVersionFormat.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "meta/Index.h"
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftInstance.h"
#include "Json.h" #include "Json.h"
@ -55,7 +55,6 @@
#include "PackProfile_p.h" #include "PackProfile_p.h"
#include "ComponentUpdateTask.h" #include "ComponentUpdateTask.h"
#include "Application.h"
#include "modplatform/ModAPI.h" #include "modplatform/ModAPI.h"
static const QMap<QString, ModAPI::ModLoaderType> modloaderMapping{ 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) 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; return false;
} }
@ -675,12 +674,12 @@ Qt::ItemFlags PackProfile::flags(const QModelIndex &index) const
int PackProfile::rowCount(const QModelIndex &parent) 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 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) void PackProfile::move(const int index, const MoveDirection direction)
@ -738,6 +737,11 @@ void PackProfile::installCustomJar(QString selectedFile)
installCustomJar_internal(selectedFile); installCustomJar_internal(selectedFile);
} }
void PackProfile::installAgents(QStringList selectedFiles)
{
installAgents_internal(selectedFiles);
}
bool PackProfile::installEmpty(const QString& uid, const QString& name) bool PackProfile::installEmpty(const QString& uid, const QString& name)
{ {
QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches"); QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches");
@ -832,18 +836,14 @@ bool PackProfile::installJarMods_internal(QStringList filepaths)
for(auto filepath:filepaths) for(auto filepath:filepaths)
{ {
QFileInfo sourceInfo(filepath); QFileInfo sourceInfo(filepath);
auto uuid = QUuid::createUuid(); QString id = QUuid::createUuid().toString(QUuid::WithoutBraces);
QString id = uuid.toString().remove('{').remove('}');
QString target_filename = id + ".jar"; 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 target_name = sourceInfo.completeBaseName() + " (jar mod)";
QString finalPath = FS::PathCombine(d->m_instance->jarModsDir(), target_filename); QString finalPath = FS::PathCombine(d->m_instance->jarModsDir(), target_filename);
QFileInfo targetInfo(finalPath); QFileInfo targetInfo(finalPath);
if(targetInfo.exists()) Q_ASSERT(!targetInfo.exists());
{
return false;
}
if (!QFile::copy(sourceInfo.absoluteFilePath(),QFileInfo(finalPath).absoluteFilePath())) 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 f = std::make_shared<VersionFile>();
auto jarMod = std::make_shared<Library>(); 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->setFilename(target_filename);
jarMod->setDisplayName(sourceInfo.completeBaseName()); jarMod->setDisplayName(sourceInfo.completeBaseName());
jarMod->setHint("local"); jarMod->setHint("local");
@ -892,7 +892,7 @@ bool PackProfile::installCustomJar_internal(QString filepath)
return false; return false;
} }
auto specifier = GradleSpecifier("org.multimc:customjar:1"); auto specifier = GradleSpecifier("custom:customjar:1");
QFileInfo sourceInfo(filepath); QFileInfo sourceInfo(filepath);
QString target_filename = specifier.getFileName(); QString target_filename = specifier.getFileName();
QString target_id = specifier.artifactId(); QString target_id = specifier.artifactId();
@ -939,6 +939,64 @@ bool PackProfile::installCustomJar_internal(QString filepath)
return true; 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 std::shared_ptr<LaunchProfile> PackProfile::getProfile() const
{ {
if(!d->m_profile) if(!d->m_profile)

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only // 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 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -85,6 +86,9 @@ public:
/// install a jar/zip as a replacement for the main jar /// install a jar/zip as a replacement for the main jar
void installCustomJar(QString selectedFile); void installCustomJar(QString selectedFile);
/// install Java agent files
void installAgents(QStringList selectedFiles);
enum MoveDirection { MoveUp, MoveDown }; enum MoveDirection { MoveUp, MoveDown };
/// move component file # up or down the list /// move component file # up or down the list
void move(const int index, const MoveDirection direction); void move(const int index, const MoveDirection direction);
@ -167,6 +171,7 @@ private:
bool load(); bool load();
bool installJarMods_internal(QStringList filepaths); bool installJarMods_internal(QStringList filepaths);
bool installCustomJar_internal(QString filepath); bool installCustomJar_internal(QString filepath);
bool installAgents_internal(QStringList filepaths);
bool removeComponent_internal(ComponentPtr patch); bool removeComponent_internal(ComponentPtr patch);
private: /* data */ private: /* data */

View File

@ -104,7 +104,7 @@ public:
class ImplicitRule : public Rule class ImplicitRule : public Rule
{ {
protected: protected:
virtual bool applies(const Library *, const RuntimeContext & runtimeContext) virtual bool applies(const Library *, [[maybe_unused]] const RuntimeContext & runtimeContext)
{ {
return true; return true;
} }

View File

@ -7,7 +7,7 @@
#include "minecraft/PackProfile.h" #include "minecraft/PackProfile.h"
#include "settings/INISettingsObject.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)) : InstanceCreationTask(), m_version(std::move(version)), m_using_loader(true), m_loader(std::move(loader)), m_loader_version(std::move(loader_version))
{} {}

View File

@ -7,16 +7,16 @@
class VanillaCreationTask final : public InstanceCreationTask { class VanillaCreationTask final : public InstanceCreationTask {
Q_OBJECT Q_OBJECT
public: public:
VanillaCreationTask(BaseVersionPtr version) : InstanceCreationTask(), m_version(std::move(version)) {} VanillaCreationTask(BaseVersion::Ptr version) : InstanceCreationTask(), m_version(std::move(version)) {}
VanillaCreationTask(BaseVersionPtr version, QString loader, BaseVersionPtr loader_version); VanillaCreationTask(BaseVersion::Ptr version, QString loader, BaseVersion::Ptr loader_version);
bool createInstance() override; bool createInstance() override;
private: private:
// Version to update to / create of the instance. // Version to update to / create of the instance.
BaseVersionPtr m_version; BaseVersion::Ptr m_version;
bool m_using_loader = false; bool m_using_loader = false;
QString m_loader; QString m_loader;
BaseVersionPtr m_loader_version; BaseVersion::Ptr m_loader_version;
}; };

View File

@ -173,7 +173,7 @@ bool WorldList::resetIcon(int row)
int WorldList::columnCount(const QModelIndex &parent) const int WorldList::columnCount(const QModelIndex &parent) const
{ {
return 4; return parent.isValid()? 0 : 4;
} }
QVariant WorldList::data(const QModelIndex &index, int role) const QVariant WorldList::data(const QModelIndex &index, int role) const
@ -398,8 +398,8 @@ void WorldList::installWorld(QFileInfo filename)
w.install(m_dir.absolutePath()); w.install(m_dir.absolutePath());
} }
bool WorldList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, bool WorldList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[maybe_unused]] int row, [[maybe_unused]] int column,
const QModelIndex &parent) [[maybe_unused]] const QModelIndex &parent)
{ {
if (action == Qt::IgnoreAction) if (action == Qt::IgnoreAction)
return true; return true;

View File

@ -54,7 +54,7 @@ public:
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const 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, virtual QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const; int role = Qt::DisplayRole) const;

View File

@ -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 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 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; return Qt::NoItemFlags;
} }

View File

@ -71,5 +71,7 @@ void VerifyJavaInstall::executeTask() {
{ {
emit logLine(tr("Java version %1").arg(major), MessageLevel::Error); 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")); emitFailed(QString("Incompatible Java major version"));
} }

View File

@ -144,7 +144,7 @@ QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, in
int ModFolderModel::columnCount(const QModelIndex &parent) const int ModFolderModel::columnCount(const QModelIndex &parent) const
{ {
return NUM_COLUMNS; return parent.isValid() ? 0 : NUM_COLUMNS;
} }
Task* ModFolderModel::createUpdateTask() Task* ModFolderModel::createUpdateTask()

View File

@ -426,7 +426,7 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
bool ResourceFolderModel::setData(const QModelIndex& index, const QVariant& value, int role) bool ResourceFolderModel::setData(const QModelIndex& index, const QVariant& value, int role)
{ {
int row = index.row(); int row = index.row();
if (row < 0 || row >= rowCount(index) || !index.isValid()) if (row < 0 || row >= rowCount(index.parent()) || !index.isValid())
return false; return false;
if (role == Qt::CheckStateRole) if (role == Qt::CheckStateRole)

View File

@ -90,8 +90,8 @@ class ResourceFolderModel : public QAbstractListModel {
/* Basic columns */ /* Basic columns */
enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, NUM_COLUMNS }; enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, NUM_COLUMNS };
[[nodiscard]] int rowCount(const QModelIndex& = {}) const override { return size(); } [[nodiscard]] int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast<int>(size()); }
[[nodiscard]] int columnCount(const QModelIndex& = {}) const override { return NUM_COLUMNS; }; [[nodiscard]] int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NUM_COLUMNS; };
[[nodiscard]] Qt::DropActions supportedDropActions() const override; [[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. * if the resource is complex and has more stuff to parse.
*/ */
virtual void onParseSucceeded(int ticket, QString resource_id); 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: protected:
// Represents the relationship between a column's index (represented by the list index), and it's sorting key. // Represents the relationship between a column's index (represented by the list index), and it's sorting key.

View File

@ -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") } }, { 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") } }, { 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") } }, { 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) void ResourcePack::setPackFormat(int new_format_id)
@ -114,3 +114,8 @@ bool ResourcePack::applyFilter(QRegularExpression filter) const
return Resource::applyFilter(filter); return Resource::applyFilter(filter);
} }
bool ResourcePack::valid() const
{
return m_pack_format != 0;
}

View File

@ -42,6 +42,8 @@ class ResourcePack : public Resource {
/** Thread-safe. */ /** Thread-safe. */
void setImage(QImage new_image); void setImage(QImage new_image);
bool valid() const override;
[[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair<int, bool> override; [[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair<int, bool> override;
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override; [[nodiscard]] bool applyFilter(QRegularExpression filter) const override;

View File

@ -137,7 +137,7 @@ QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orient
int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const
{ {
return NUM_COLUMNS; return parent.isValid() ? 0 : NUM_COLUMNS;
} }
Task* ResourcePackFolderModel::createUpdateTask() Task* ResourcePackFolderModel::createUpdateTask()

View File

@ -62,3 +62,8 @@ QPixmap TexturePack::image(QSize size)
TexturePackUtils::process(*this); TexturePackUtils::process(*this);
return image(size); return image(size);
} }
bool TexturePack::valid() const
{
return m_description != nullptr;
}

View File

@ -48,6 +48,8 @@ class TexturePack : public Resource {
/** Thread-safe. */ /** Thread-safe. */
void setImage(QImage new_image); void setImage(QImage new_image);
bool valid() const override;
protected: protected:
mutable QMutex m_data_lock; mutable QMutex m_data_lock;

View File

@ -121,7 +121,7 @@ ModDetails ReadMCModTOML(QByteArray contents)
return {}; return {};
} }
auto modsTable = tomlModsTable0->as_table(); auto modsTable = tomlModsTable0->as_table();
if (!tomlModsTable0) { if (!modsTable) {
qWarning() << "Corrupted mods.toml? [[mods]] was not a table!"; qWarning() << "Corrupted mods.toml? [[mods]] was not a table!";
return {}; return {};
} }

View File

@ -36,7 +36,7 @@ LocalModUpdateTask::LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack&
} }
#ifdef Q_OS_WIN32 #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 #endif
} }

View File

@ -28,14 +28,14 @@
namespace ResourcePackUtils { namespace ResourcePackUtils {
bool process(ResourcePack& pack) bool process(ResourcePack& pack, ProcessingLevel level)
{ {
switch (pack.type()) { switch (pack.type()) {
case ResourceType::FOLDER: case ResourceType::FOLDER:
ResourcePackUtils::processFolder(pack); ResourcePackUtils::processFolder(pack, level);
return true; return true;
case ResourceType::ZIPFILE: case ResourceType::ZIPFILE:
ResourcePackUtils::processZIP(pack); ResourcePackUtils::processZIP(pack, level);
return true; return true;
default: default:
qWarning() << "Invalid type for resource pack parse task!"; 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); Q_ASSERT(pack.type() == ResourceType::FOLDER);
@ -60,6 +60,9 @@ void processFolder(ResourcePack& pack)
mcmeta_file.close(); mcmeta_file.close();
} }
if (level == ProcessingLevel::BasicInfoOnly)
return;
QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png")); QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
if (image_file_info.isFile()) { if (image_file_info.isFile()) {
QFile mcmeta_file(image_file_info.filePath()); 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); Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
@ -98,6 +101,11 @@ void processZIP(ResourcePack& pack)
file.close(); file.close();
} }
if (level == ProcessingLevel::BasicInfoOnly) {
zip.close();
return;
}
if (zip.setCurrentFile("pack.png")) { if (zip.setCurrentFile("pack.png")) {
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip."; qCritical() << "Failed to open file in zip.";
@ -138,6 +146,13 @@ void processPackPNG(ResourcePack& pack, QByteArray&& raw_data)
qWarning() << "Failed to parse pack.png."; qWarning() << "Failed to parse pack.png.";
} }
} }
bool validate(QFileInfo file)
{
ResourcePack rp{ file };
return ResourcePackUtils::process(rp, ProcessingLevel::BasicInfoOnly) && rp.valid();
}
} // namespace ResourcePackUtils } // namespace ResourcePackUtils
LocalResourcePackParseTask::LocalResourcePackParseTask(int token, ResourcePack& rp) LocalResourcePackParseTask::LocalResourcePackParseTask(int token, ResourcePack& rp)
@ -152,8 +167,6 @@ bool LocalResourcePackParseTask::abort()
void LocalResourcePackParseTask::executeTask() void LocalResourcePackParseTask::executeTask()
{ {
Q_ASSERT(m_resource_pack.valid());
if (!ResourcePackUtils::process(m_resource_pack)) if (!ResourcePackUtils::process(m_resource_pack))
return; return;

View File

@ -26,13 +26,19 @@
#include "tasks/Task.h" #include "tasks/Task.h"
namespace ResourcePackUtils { namespace ResourcePackUtils {
bool process(ResourcePack& pack);
void processZIP(ResourcePack& pack); enum class ProcessingLevel { Full, BasicInfoOnly };
void processFolder(ResourcePack& pack);
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 processMCMeta(ResourcePack& pack, QByteArray&& raw_data);
void processPackPNG(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 } // namespace ResourcePackUtils
class LocalResourcePackParseTask : public Task { class LocalResourcePackParseTask : public Task {

View File

@ -28,14 +28,14 @@
namespace TexturePackUtils { namespace TexturePackUtils {
bool process(TexturePack& pack) bool process(TexturePack& pack, ProcessingLevel level)
{ {
switch (pack.type()) { switch (pack.type()) {
case ResourceType::FOLDER: case ResourceType::FOLDER:
TexturePackUtils::processFolder(pack); TexturePackUtils::processFolder(pack, level);
return true; return true;
case ResourceType::ZIPFILE: case ResourceType::ZIPFILE:
TexturePackUtils::processZIP(pack); TexturePackUtils::processZIP(pack, level);
return true; return true;
default: default:
qWarning() << "Invalid type for resource pack parse task!"; 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); Q_ASSERT(pack.type() == ResourceType::FOLDER);
@ -60,6 +60,9 @@ void processFolder(TexturePack& pack)
mcmeta_file.close(); mcmeta_file.close();
} }
if (level == ProcessingLevel::BasicInfoOnly)
return;
QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png")); QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
if (image_file_info.isFile()) { if (image_file_info.isFile()) {
QFile mcmeta_file(image_file_info.filePath()); 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); Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
@ -98,6 +101,11 @@ void processZIP(TexturePack& pack)
file.close(); file.close();
} }
if (level == ProcessingLevel::BasicInfoOnly) {
zip.close();
return;
}
if (zip.setCurrentFile("pack.png")) { if (zip.setCurrentFile("pack.png")) {
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip."; qCritical() << "Failed to open file in zip.";
@ -129,6 +137,13 @@ void processPackPNG(TexturePack& pack, QByteArray&& raw_data)
qWarning() << "Failed to parse pack.png."; qWarning() << "Failed to parse pack.png.";
} }
} }
bool validate(QFileInfo file)
{
TexturePack rp{ file };
return TexturePackUtils::process(rp, ProcessingLevel::BasicInfoOnly) && rp.valid();
}
} // namespace TexturePackUtils } // namespace TexturePackUtils
LocalTexturePackParseTask::LocalTexturePackParseTask(int token, TexturePack& rp) LocalTexturePackParseTask::LocalTexturePackParseTask(int token, TexturePack& rp)
@ -143,8 +158,6 @@ bool LocalTexturePackParseTask::abort()
void LocalTexturePackParseTask::executeTask() void LocalTexturePackParseTask::executeTask()
{ {
Q_ASSERT(m_texture_pack.valid());
if (!TexturePackUtils::process(m_texture_pack)) if (!TexturePackUtils::process(m_texture_pack))
return; return;

View File

@ -27,13 +27,19 @@
#include "tasks/Task.h" #include "tasks/Task.h"
namespace TexturePackUtils { namespace TexturePackUtils {
bool process(TexturePack& pack);
void processZIP(TexturePack& pack); enum class ProcessingLevel { Full, BasicInfoOnly };
void processFolder(TexturePack& pack);
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 processPackTXT(TexturePack& pack, QByteArray&& raw_data);
void processPackPNG(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 } // namespace TexturePackUtils
class LocalTexturePackParseTask : public Task { class LocalTexturePackParseTask : public Task {

View File

@ -39,7 +39,7 @@
#include <QList> #include <QList>
#include <list> #include <list>
#include "Version.h" #include "../Version.h"
#include "net/NetJob.h" #include "net/NetJob.h"
namespace ModPlatform { namespace ModPlatform {

View File

@ -58,7 +58,7 @@
namespace ATLauncher { 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) PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString packName, QString version, InstallMode installMode)
{ {
@ -1037,7 +1037,7 @@ void PackInstallTask::install()
emitSucceeded(); 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); auto vlist = APPLICATION->metadataIndex()->get(uid);
if (!vlist) if (!vlist)

View File

@ -68,7 +68,7 @@ public:
* Requests a user interaction to select a component version from a given version list * Requests a user interaction to select a component version from a given version list
* and constrained to a given Minecraft version. * 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. * Requests a user interaction to display a message.
@ -137,8 +137,8 @@ private:
QString archivePath; QString archivePath;
QStringList jarmods; QStringList jarmods;
Meta::VersionPtr minecraftVersion; Meta::Version::Ptr minecraftVersion;
QMap<QString, Meta::VersionPtr> componentsToInstall; QMap<QString, Meta::Version::Ptr> componentsToInstall;
QFuture<std::optional<QStringList>> m_extractFuture; QFuture<std::optional<QStringList>> m_extractFuture;
QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher; QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;

View File

@ -3,6 +3,8 @@
#include "Json.h" #include "Json.h"
#include "net/Upload.h" #include "net/Upload.h"
#include "modplatform/modrinth/ModrinthPackIndex.h"
Flame::FileResolvingTask::FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest& toProcess) Flame::FileResolvingTask::FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest& toProcess)
: m_network(network), m_toProcess(toProcess) : m_network(network), m_toProcess(toProcess)
{} {}
@ -40,12 +42,25 @@ void Flame::FileResolvingTask::executeTask()
void Flame::FileResolvingTask::netJobFinished() void Flame::FileResolvingTask::netJobFinished()
{ {
setProgress(1, 3); setProgress(1, 3);
int index = 0;
// job to check modrinth for blocked projects // job to check modrinth for blocked projects
m_checkJob = new NetJob("Modrinth check", m_network); m_checkJob = new NetJob("Modrinth check", m_network);
blockedProjects = QMap<File *,QByteArray *>(); 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) { for (QJsonValueRef file : array) {
auto fileid = Json::requireInteger(Json::requireObject(file)["id"]); auto fileid = Json::requireInteger(Json::requireObject(file)["id"]);
auto& out = m_toProcess.files[fileid]; auto& out = m_toProcess.files[fileid];
@ -66,7 +81,6 @@ void Flame::FileResolvingTask::netJobFinished()
blockedProjects.insert(&out, output); blockedProjects.insert(&out, output);
} }
} }
index++;
} }
connect(m_checkJob.get(), &NetJob::finished, this, &Flame::FileResolvingTask::modrinthCheckFinished); connect(m_checkJob.get(), &NetJob::finished, this, &Flame::FileResolvingTask::modrinthCheckFinished);
@ -84,18 +98,21 @@ void Flame::FileResolvingTask::modrinthCheckFinished() {
delete bytes; delete bytes;
continue; continue;
} }
QJsonDocument doc = QJsonDocument::fromJson(*bytes); QJsonDocument doc = QJsonDocument::fromJson(*bytes);
auto obj = doc.object(); auto obj = doc.object();
auto array = Json::requireArray(obj,"files"); auto file = Modrinth::loadIndexedPackVersion(obj);
for (auto file: array) {
auto fileObj = Json::requireObject(file); // If there's more than one mod loader for this version, we can't know for sure
auto primary = Json::requireBoolean(fileObj,"primary"); // which file is relative to each loader, so it's best to not use any one and
if (primary) { // let the user download it manually.
out->url = Json::requireUrl(fileObj,"url"); if (file.loaders.size() <= 1) {
qDebug() << "Found alternative on modrinth " << out->fileName; out->url = file.downloadUrl;
break; qDebug() << "Found alternative on modrinth " << out->fileName;
} } else {
out->resolved = false;
} }
delete bytes; delete bytes;
} }
//copy to an output list and filter out projects found on modrinth //copy to an output list and filter out projects found on modrinth

View File

@ -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 "FlameInstanceCreationTask.h"
#include "modplatform/flame/FlameAPI.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 " 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 " "separate instance, or update the existing one?\n\nNOTE: Make sure you made a backup of your important instance data before "
"updating, as worlds can be corrupted and some configuration may be lost (due to pack overrides).") "updating, as worlds can be corrupted and some configuration may be lost (due to pack overrides).")
.arg(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::Ok, tr("Update existing instance"));
info->setButtonText(QMessageBox::Abort, tr("Create new instance")); info->setButtonText(QMessageBox::Abort, tr("Create new instance"));
info->setButtonText(QMessageBox::Reset, tr("Cancel")); info->setButtonText(QMessageBox::Reset, tr("Cancel"));
@ -197,10 +233,10 @@ bool FlameCreationTask::updateInstance()
m_process_update_file_info_job = nullptr; m_process_update_file_info_job = nullptr;
} else { } else {
// We don't have an old index file, so we may duplicate stuff! // We don't have an old index file, so we may duplicate stuff!
auto dialog = CustomMessageBox::selectable(m_parent, auto dialog = CustomMessageBox::selectable(m_parent, tr("No index file."),
tr("No index file."), tr("We couldn't find a suitable index file for the older version. This may cause some "
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?"), "of the files to be duplicated. Do you want to continue?"),
QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel); QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel);
if (dialog->exec() == QDialog::DialogCode::Rejected) { if (dialog->exec() == QDialog::DialogCode::Rejected) {
m_abort = true; m_abort = true;
@ -338,6 +374,7 @@ bool FlameCreationTask::createInstance()
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) { connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) {
m_mod_id_resolver.reset(); m_mod_id_resolver.reset();
setError(tr("Unable to resolve mod IDs:\n") + reason); 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::progress, this, &FlameCreationTask::setProgress);
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::status, this, &FlameCreationTask::setStatus); 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(); auto results = m_mod_id_resolver->getResults();
// first check for blocked mods // first check for blocked mods
QString text; QList<BlockedMod> blocked_mods;
QList<QUrl> urls;
auto anyBlocked = false; auto anyBlocked = false;
for (const auto& result : results.files.values()) { for (const auto& result : results.files.values()) {
if (!result.resolved || result.url.isEmpty()) { if (!result.resolved || result.url.isEmpty()) {
text += QString("%1: <a href='%2'>%2</a><br/>").arg(result.fileName, result.websiteUrl); BlockedMod blocked_mod;
urls.append(QUrl(result.websiteUrl)); 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; anyBlocked = true;
} }
} }
if (anyBlocked) { if (anyBlocked) {
qWarning() << "Blocked mods found, displaying mod list"; qWarning() << "Blocked mods found, displaying mod list";
auto message_dialog = new BlockedModsDialog(m_parent, tr("Blocked mods found"), BlockedModsDialog message_dialog(m_parent, tr("Blocked mods found"),
tr("The following mods were blocked on third party launchers.<br/>" 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 modpack"), "You will need to manually download them and add them to the instance."),
text, blocked_mods);
urls);
message_dialog->setModal(true);
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); setupDownloadJob(loop);
} else { } else {
m_mod_id_resolver.reset(); 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) void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
{ {
m_files_job = new NetJob(tr("Mod download"), APPLICATION->network()); m_files_job = new NetJob(tr("Mod download"), APPLICATION->network());
@ -442,14 +519,12 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
} }
m_mod_id_resolver.reset(); m_mod_id_resolver.reset();
connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { m_files_job.reset(); });
m_files_job.reset();
});
connect(m_files_job.get(), &NetJob::failed, [&](QString reason) { connect(m_files_job.get(), &NetJob::failed, [&](QString reason) {
m_files_job.reset(); m_files_job.reset();
setError(reason); 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); connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit);
setStatus(tr("Downloading mods...")); setStatus(tr("Downloading mods..."));

View File

@ -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 #pragma once
#include "InstanceCreationTask.h" #include "InstanceCreationTask.h"
@ -10,6 +45,8 @@
#include "net/NetJob.h" #include "net/NetJob.h"
#include "ui/dialogs/BlockedModsDialog.h"
class FlameCreationTask final : public InstanceCreationTask { class FlameCreationTask final : public InstanceCreationTask {
Q_OBJECT Q_OBJECT
@ -29,6 +66,7 @@ class FlameCreationTask final : public InstanceCreationTask {
private slots: private slots:
void idResolverSucceeded(QEventLoop&); void idResolverSucceeded(QEventLoop&);
void setupDownloadJob(QEventLoop&); void setupDownloadJob(QEventLoop&);
void copyBlockedMods(QList<BlockedMod> const& blocked_mods);
private: private:
QWidget* m_parent = nullptr; QWidget* m_parent = nullptr;

View File

@ -4,6 +4,7 @@
#include <QFile> #include <QFile>
#include "FileSystem.h" #include "FileSystem.h"
#include "StringUtils.h"
#include <MurmurHash2.h> #include <MurmurHash2.h>
@ -35,6 +36,18 @@ Hasher::Ptr createFlameHasher(QString file_path)
return new FlameHasher(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() void ModrinthHasher::executeTask()
{ {
QFile file(m_path); QFile file(m_path);
@ -66,7 +79,7 @@ void FlameHasher::executeTask()
// CF-specific // CF-specific
auto should_filter_out = [](char c) { return (c == 9 || c == 10 || c == 13 || c == 32); }; 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. // 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? // How do we make this non-blocking then?
m_hash = QString::number(MurmurHash2(std::move(file_stream), 4 * MiB, should_filter_out)); 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 } // namespace Hashing

View File

@ -40,8 +40,23 @@ class ModrinthHasher : public Hasher {
void executeTask() override; 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 createHasher(QString file_path, ModPlatform::Provider provider);
Hasher::Ptr createFlameHasher(QString file_path); Hasher::Ptr createFlameHasher(QString file_path);
Hasher::Ptr createModrinthHasher(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 } // namespace Hashing

View File

@ -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::started, caller, [caller, netJob] { caller->setActiveJob(netJob); });
QObject::connect(netJob, &NetJob::failed, caller, &CallerType::searchRequestFailed); QObject::connect(netJob, &NetJob::failed, caller, &CallerType::searchRequestFailed);
QObject::connect(netJob, &NetJob::aborted, caller, &CallerType::searchRequestAborted);
QObject::connect(netJob, &NetJob::succeeded, caller, [caller, response] { QObject::connect(netJob, &NetJob::succeeded, caller, [caller, response] {
QJsonParseError parse_error{}; QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 flowln <flowlnlnln@gmail.com> * Copyright (C) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
@ -176,8 +176,6 @@ void PackInstallTask::resolveMods()
void PackInstallTask::onResolveModsSucceeded() void PackInstallTask::onResolveModsSucceeded()
{ {
QString text;
QList<QUrl> urls;
auto anyBlocked = false; auto anyBlocked = false;
Flame::Manifest results = m_mod_id_resolver_task->getResults(); Flame::Manifest results = m_mod_id_resolver_task->getResults();
@ -191,11 +189,15 @@ void PackInstallTask::onResolveModsSucceeded()
// First check for blocked mods // First check for blocked mods
if (!results_file.resolved || results_file.url.isEmpty()) { 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; anyBlocked = true;
} else { } else {
local_file.url = results_file.url.toString(); local_file.url = results_file.url.toString();
@ -207,16 +209,20 @@ void PackInstallTask::onResolveModsSucceeded()
if (anyBlocked) { if (anyBlocked) {
qDebug() << "Blocked files found, displaying file list"; qDebug() << "Blocked files found, displaying file list";
auto message_dialog = new BlockedModsDialog(m_parent, tr("Blocked files found"), BlockedModsDialog message_dialog(m_parent, tr("Blocked files found"),
tr("The following files are not available for download in third party launchers.<br/>" 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."), "You will need to manually download them and add them to the instance."),
text, m_blocked_mods);
urls);
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(); createInstance();
else } else {
abort(); abort();
}
} else { } else {
createInstance(); createInstance();
} }
@ -320,6 +326,9 @@ void PackInstallTask::downloadPack()
void PackInstallTask::onModDownloadSucceeded() void PackInstallTask::onModDownloadSucceeded()
{ {
m_net_job.reset(); m_net_job.reset();
if (!m_blocked_mods.isEmpty()) {
copyBlockedMods();
}
emitSucceeded(); emitSucceeded();
} }
@ -343,4 +352,35 @@ void PackInstallTask::onModDownloadFailed(QString reason)
emitFailed(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 } // namespace ModpacksCH

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 flowln <flowlnlnln@gmail.com> * Copyright (C) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *
@ -43,6 +43,7 @@
#include "QObjectPtr.h" #include "QObjectPtr.h"
#include "modplatform/flame/FileResolvingTask.h" #include "modplatform/flame/FileResolvingTask.h"
#include "net/NetJob.h" #include "net/NetJob.h"
#include "ui/dialogs/BlockedModsDialog.h"
#include <QWidget> #include <QWidget>
@ -76,6 +77,7 @@ private:
void resolveMods(); void resolveMods();
void createInstance(); void createInstance();
void downloadPack(); void downloadPack();
void copyBlockedMods();
private: private:
NetJob::Ptr m_net_job = nullptr; NetJob::Ptr m_net_job = nullptr;
@ -90,6 +92,7 @@ private:
Version m_version; Version m_version;
QMap<QString, QString> m_files_to_copy; QMap<QString, QString> m_files_to_copy;
QList<BlockedMod> m_blocked_mods;
//FIXME: nuke //FIXME: nuke
QWidget* m_parent; QWidget* m_parent;

View File

@ -1,20 +1,20 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3. * the Free Software Foundation, version 3.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include "ModrinthPackIndex.h" #include "ModrinthPackIndex.h"
#include "ModrinthAPI.h" #include "ModrinthAPI.h"
@ -59,23 +59,23 @@ void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& obj) void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& obj)
{ {
pack.extraData.issuesUrl = Json::ensureString(obj, "issues_url"); 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.issuesUrl.chop(1);
pack.extraData.sourceUrl = Json::ensureString(obj, "source_url"); 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.sourceUrl.chop(1);
pack.extraData.wikiUrl = Json::ensureString(obj, "wiki_url"); 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.wikiUrl.chop(1);
pack.extraData.discordUrl = Json::ensureString(obj, "discord_url"); pack.extraData.discordUrl = Json::ensureString(obj, "discord_url");
if(pack.extraData.discordUrl.endsWith('/')) if (pack.extraData.discordUrl.endsWith('/'))
pack.extraData.discordUrl.chop(1); pack.extraData.discordUrl.chop(1);
auto donate_arr = Json::ensureArray(obj, "donation_urls"); auto donate_arr = Json::ensureArray(obj, "donation_urls");
for(auto d : donate_arr){ for (auto d : donate_arr) {
auto d_obj = Json::requireObject(d); auto d_obj = Json::requireObject(d);
ModPlatform::DonationData donate; ModPlatform::DonationData donate;
@ -104,7 +104,7 @@ void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
auto obj = versionIter.toObject(); auto obj = versionIter.toObject();
auto file = loadIndexedPackVersion(obj); 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); unsortedVersions.append(file);
} }
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool { auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
@ -116,7 +116,8 @@ void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
pack.versionsLoaded = true; 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; ModPlatform::IndexedVersion file;
@ -141,6 +142,12 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject &obj, QString preferred_hash_t
auto files = Json::requireArray(obj, "files"); auto files = Json::requireArray(obj, "files");
int i = 0; 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) // 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 // Will default to the last one if there's no primary (though I think Modrinth requires that
// at least one file is primary, idk) // at least one file is primary, idk)

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