Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into develop
110
.github/workflows/build.yml
vendored
@ -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:
|
||||||
@ -31,17 +38,11 @@ jobs:
|
|||||||
qt_tools: ''
|
qt_tools: ''
|
||||||
|
|
||||||
- os: windows-2022
|
- os: windows-2022
|
||||||
name: "Windows-Legacy"
|
name: "Windows-MinGW-w64"
|
||||||
msystem: clang32
|
|
||||||
qt_ver: 5
|
|
||||||
|
|
||||||
- os: windows-2022
|
|
||||||
name: "Windows"
|
|
||||||
msystem: clang64
|
msystem: clang64
|
||||||
qt_ver: 6
|
|
||||||
|
|
||||||
- os: windows-2022
|
- os: windows-2022
|
||||||
name: "Windows-Legacy-MSVC"
|
name: "Windows-MSVC-Legacy"
|
||||||
msystem: ''
|
msystem: ''
|
||||||
architecture: 'win32'
|
architecture: 'win32'
|
||||||
vcvars_arch: 'amd64_x86'
|
vcvars_arch: 'amd64_x86'
|
||||||
@ -64,6 +65,18 @@ jobs:
|
|||||||
qt_modules: 'qt5compat qtimageformats'
|
qt_modules: 'qt5compat qtimageformats'
|
||||||
qt_tools: ''
|
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
|
||||||
@ -102,14 +115,6 @@ 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' && matrix.msystem != ''
|
if: runner.os == 'Windows' && matrix.msystem != ''
|
||||||
uses: msys2/setup-msys2@v2
|
uses: msys2/setup-msys2@v2
|
||||||
@ -124,12 +129,12 @@ jobs:
|
|||||||
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
|
||||||
${{ matrix.qt_ver == 6 && 'qt6-5compat:p' || '' }}
|
qt6-5compat:p
|
||||||
|
|
||||||
- name: Force newer ccache
|
- name: Force newer ccache
|
||||||
if: runner.os == 'Windows' && matrix.msystem == '' && inputs.build_type == 'Debug'
|
if: runner.os == 'Windows' && matrix.msystem == '' && inputs.build_type == 'Debug'
|
||||||
@ -140,7 +145,7 @@ jobs:
|
|||||||
if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug'
|
if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug'
|
||||||
uses: hendrikmuhs/ccache-action@v1.2.5
|
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 MinGW-w64)
|
- name: Setup ccache (Windows MinGW-w64)
|
||||||
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
|
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
|
||||||
@ -163,9 +168,9 @@ jobs:
|
|||||||
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
|
||||||
@ -190,7 +195,22 @@ 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, AppImage & Windows MSVC)
|
- name: Install host Qt (Windows MSVC arm64)
|
||||||
|
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
|
||||||
|
uses: jurplel/install-qt-action@v3
|
||||||
|
with:
|
||||||
|
version: ${{ matrix.qt_version }}
|
||||||
|
host: 'windows'
|
||||||
|
target: 'desktop'
|
||||||
|
arch: ''
|
||||||
|
modules: ${{ matrix.qt_modules }}
|
||||||
|
tools: ${{ matrix.qt_tools }}
|
||||||
|
cache: ${{ inputs.is_qt_cached }}
|
||||||
|
cache-key-prefix: host-qt-arm64-windows
|
||||||
|
dir: ${{ github.workspace }}\HostQt
|
||||||
|
set-env: false
|
||||||
|
|
||||||
|
- name: Install Qt (macOS, Linux, Qt 6 & Windows MSVC)
|
||||||
if: runner.os == 'Linux' && matrix.qt_ver == 6 || runner.os == 'macOS' || (runner.os == 'Windows' && matrix.msystem == '')
|
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:
|
||||||
@ -200,8 +220,14 @@ jobs:
|
|||||||
arch: ${{ matrix.qt_arch }}
|
arch: ${{ matrix.qt_arch }}
|
||||||
modules: ${{ matrix.qt_modules }}
|
modules: ${{ matrix.qt_modules }}
|
||||||
tools: ${{ matrix.qt_tools }}
|
tools: ${{ matrix.qt_tools }}
|
||||||
cache: true
|
cache: ${{ inputs.is_qt_cached }}
|
||||||
cache-key-prefix: ${{ matrix.qt_host }}-${{ matrix.qt_version }}-"${{ matrix.qt_modules }}"-qt_cache
|
|
||||||
|
- 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
|
||||||
@ -212,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
|
||||||
##
|
##
|
||||||
@ -230,12 +261,12 @@ jobs:
|
|||||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
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 }} -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -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)
|
- name: Configure CMake (Windows MSVC)
|
||||||
if: runner.os == 'Windows' && matrix.msystem == ''
|
if: runner.os == 'Windows' && matrix.msystem == ''
|
||||||
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 }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}}
|
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)
|
# https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix)
|
||||||
if ("${{ env.CCACHE_VAR }}")
|
if ("${{ env.CCACHE_VAR }}")
|
||||||
{
|
{
|
||||||
@ -288,18 +319,10 @@ jobs:
|
|||||||
ctest -E "^example64|example$" --test-dir build --output-on-failure
|
ctest -E "^example64|example$" --test-dir build --output-on-failure
|
||||||
|
|
||||||
- name: Test (Windows MSVC)
|
- name: Test (Windows MSVC)
|
||||||
if: runner.os == 'Windows' && matrix.msystem == ''
|
if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture != 'arm64'
|
||||||
run: |
|
run: |
|
||||||
ctest -E "^example64|example$" --test-dir build --output-on-failure -C ${{ inputs.build_type }}
|
ctest -E "^example64|example$" --test-dir build --output-on-failure -C ${{ inputs.build_type }}
|
||||||
|
|
||||||
##
|
|
||||||
# CODE SCAN
|
|
||||||
##
|
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
|
||||||
if: runner.os == 'Linux' && matrix.qt_ver == 6
|
|
||||||
uses: github/codeql-action/analyze@v2
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# PACKAGE BUILDS
|
# PACKAGE BUILDS
|
||||||
##
|
##
|
||||||
@ -333,23 +356,12 @@ jobs:
|
|||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Add VC Enviroment Variables
|
|
||||||
if: runner.os == 'Windows' && matrix.msystem == ''
|
|
||||||
uses: ilammy/msvc-dev-cmd@v1
|
|
||||||
with:
|
|
||||||
arch: ${{ matrix.vcvars_arch }}
|
|
||||||
|
|
||||||
- name: Package (Windows MinGW-w64)
|
- name: Package (Windows MinGW-w64)
|
||||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
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 }}
|
|
||||||
if [ "${{ matrix.qt_ver }}" == "5" ]; then
|
|
||||||
cp /clang32/bin/libcrypto-1_1.dll /clang32/bin/libssl-1_1.dll ./
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Package (Windows MSVC)
|
- name: Package (Windows MSVC)
|
||||||
if: runner.os == 'Windows' && matrix.msystem == ''
|
if: runner.os == 'Windows' && matrix.msystem == ''
|
||||||
run: |
|
run: |
|
||||||
|
35
.github/workflows/codeql.yml
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
name: "CodeQL Code Scanning"
|
||||||
|
|
||||||
|
on: [ push, pull_request, workflow_dispatch ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
CodeQL:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: 'true'
|
||||||
|
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v2
|
||||||
|
with:
|
||||||
|
config-file: ./.github/codeql/codeql-config.yml
|
||||||
|
queries: security-and-quality
|
||||||
|
languages: cpp, java
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
run:
|
||||||
|
sudo apt-get -y update
|
||||||
|
|
||||||
|
sudo apt-get -y install ninja-build extra-cmake-modules scdoc qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5
|
||||||
|
|
||||||
|
- name: Configure and Build
|
||||||
|
run: |
|
||||||
|
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr -DLauncher_QT_VERSION_MAJOR=5 -G Ninja
|
||||||
|
|
||||||
|
cmake --build build
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v2
|
4
.github/workflows/trigger_builds.yml
vendored
@ -8,7 +8,6 @@ on:
|
|||||||
- '**.md'
|
- '**.md'
|
||||||
- '**/LICENSE'
|
- '**/LICENSE'
|
||||||
- 'flake.lock'
|
- 'flake.lock'
|
||||||
- '**.nix'
|
|
||||||
- 'packages/**'
|
- 'packages/**'
|
||||||
- '.github/ISSUE_TEMPLATE/**'
|
- '.github/ISSUE_TEMPLATE/**'
|
||||||
pull_request:
|
pull_request:
|
||||||
@ -16,7 +15,6 @@ on:
|
|||||||
- '**.md'
|
- '**.md'
|
||||||
- '**/LICENSE'
|
- '**/LICENSE'
|
||||||
- 'flake.lock'
|
- 'flake.lock'
|
||||||
- '**.nix'
|
|
||||||
- 'packages/**'
|
- 'packages/**'
|
||||||
- '.github/ISSUE_TEMPLATE/**'
|
- '.github/ISSUE_TEMPLATE/**'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@ -28,5 +26,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 }}
|
||||||
|
33
.github/workflows/trigger_release.yml
vendored
@ -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,15 +46,26 @@ jobs:
|
|||||||
|
|
||||||
tar -czf PollyMC-${{ env.VERSION }}.tar.gz PollyMC-${{ env.VERSION }}
|
tar -czf PollyMC-${{ env.VERSION }}.tar.gz PollyMC-${{ env.VERSION }}
|
||||||
|
|
||||||
for d in PollyMC-Windows-*; do
|
for d in PollyMC-Windows-MSVC*; do
|
||||||
cd "${d}" || continue
|
cd "${d}" || continue
|
||||||
MSVC="$(echo -n ${d} | grep -o MSVC || true)"
|
|
||||||
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="PollyMC-Windows"
|
NAME="PollyMC-Windows-MSVC"
|
||||||
test -z "${MSVC}" && NAME="${NAME}-MinGW" || NAME="${NAME}-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 PollyMC-*.exe ../${NAME}-Setup-${{ env.VERSION }}.exe
|
||||||
|
test -n "${INST}" || zip -r -9 "../${NAME}-${{ env.VERSION }}.zip" *
|
||||||
|
cd ..
|
||||||
|
done
|
||||||
|
|
||||||
|
for d in PollyMC-Windows-MinGW-w64*; do
|
||||||
|
cd "${d}" || continue
|
||||||
|
INST="$(echo -n ${d} | grep -o Setup || true)"
|
||||||
|
PORT="$(echo -n ${d} | grep -o Portable || true)"
|
||||||
|
NAME="PollyMC-Windows-MinGW-w64"
|
||||||
test -z "${PORT}" || NAME="${NAME}-Portable"
|
test -z "${PORT}" || NAME="${NAME}-Portable"
|
||||||
test -z "${INST}" || mv PollyMC-*.exe ../${NAME}-Setup-${{ env.VERSION }}.exe
|
test -z "${INST}" || mv PollyMC-*.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" *
|
||||||
@ -76,18 +88,19 @@ jobs:
|
|||||||
PollyMC-Linux-${{ env.VERSION }}-x86_64.AppImage
|
PollyMC-Linux-${{ env.VERSION }}-x86_64.AppImage
|
||||||
PollyMC-Linux-Qt6-${{ env.VERSION }}.tar.gz
|
PollyMC-Linux-Qt6-${{ env.VERSION }}.tar.gz
|
||||||
PollyMC-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
|
PollyMC-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
|
||||||
PollyMC-Windows-MinGW-Legacy-${{ env.VERSION }}.zip
|
PollyMC-Windows-MinGW-w64-${{ env.VERSION }}.zip
|
||||||
PollyMC-Windows-MinGW-Legacy-Portable-${{ env.VERSION }}.zip
|
PollyMC-Windows-MinGW-w64-Portable-${{ env.VERSION }}.zip
|
||||||
PollyMC-Windows-MinGW-Legacy-Setup-${{ env.VERSION }}.exe
|
PollyMC-Windows-MinGW-w64-Setup-${{ env.VERSION }}.exe
|
||||||
PollyMC-Windows-MinGW-${{ env.VERSION }}.zip
|
|
||||||
PollyMC-Windows-MinGW-Portable-${{ env.VERSION }}.zip
|
|
||||||
PollyMC-Windows-MinGW-Setup-${{ env.VERSION }}.exe
|
|
||||||
PollyMC-Windows-MSVC-Legacy-${{ env.VERSION }}.zip
|
PollyMC-Windows-MSVC-Legacy-${{ env.VERSION }}.zip
|
||||||
PollyMC-Windows-MSVC-Legacy-Portable-${{ env.VERSION }}.zip
|
PollyMC-Windows-MSVC-Legacy-Portable-${{ env.VERSION }}.zip
|
||||||
PollyMC-Windows-MSVC-Legacy-Setup-${{ env.VERSION }}.exe
|
PollyMC-Windows-MSVC-Legacy-Setup-${{ env.VERSION }}.exe
|
||||||
|
PollyMC-Windows-MSVC-arm64-${{ env.VERSION }}.zip
|
||||||
|
PollyMC-Windows-MSVC-arm64-Portable-${{ env.VERSION }}.zip
|
||||||
|
PollyMC-Windows-MSVC-arm64-Setup-${{ env.VERSION }}.exe
|
||||||
PollyMC-Windows-MSVC-${{ env.VERSION }}.zip
|
PollyMC-Windows-MSVC-${{ env.VERSION }}.zip
|
||||||
PollyMC-Windows-MSVC-Portable-${{ env.VERSION }}.zip
|
PollyMC-Windows-MSVC-Portable-${{ env.VERSION }}.zip
|
||||||
PollyMC-Windows-MSVC-Setup-${{ env.VERSION }}.exe
|
PollyMC-Windows-MSVC-Setup-${{ env.VERSION }}.exe
|
||||||
PollyMC-macOS-${{ env.VERSION }}.tar.gz
|
PollyMC-macOS-${{ env.VERSION }}.tar.gz
|
||||||
PollyMC-macOS-Legacy-${{ env.VERSION }}.tar.gz
|
PollyMC-macOS-Legacy-${{ env.VERSION }}.tar.gz
|
||||||
PollyMC-${{ env.VERSION }}.tar.gz
|
PollyMC-${{ env.VERSION }}.tar.gz
|
||||||
|
|
||||||
|
2
.github/workflows/winget.yml
vendored
@ -11,5 +11,5 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
identifier: PrismLauncher.PrismLauncher
|
identifier: PrismLauncher.PrismLauncher
|
||||||
version: ${{ github.event.release.tag_name }}
|
version: ${{ github.event.release.tag_name }}
|
||||||
installers-regex: 'PrismLauncher-Windows-Setup-.+\.exe$'
|
installers-regex: 'PrismLauncher-Windows-MSVC(:?-arm64|-Legacy)?-Setup-.+\.exe$'
|
||||||
token: ${{ secrets.WINGET_TOKEN }}
|
token: ${{ secrets.WINGET_TOKEN }}
|
||||||
|
@ -28,19 +28,28 @@ set(CMAKE_CXX_STANDARD 17)
|
|||||||
set(CMAKE_C_STANDARD 11)
|
set(CMAKE_C_STANDARD 11)
|
||||||
include(GenerateExportHeader)
|
include(GenerateExportHeader)
|
||||||
if(MSVC)
|
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
|
# /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}")
|
# /permissive- specify standards-conforming compiler behavior, also enabled by Qt6, default on with std:c++20
|
||||||
|
# Use /W4 as /Wall includes unnesserey warnings such as added padding to structs
|
||||||
|
set(CMAKE_CXX_FLAGS "/GS /permissive- /W4 ${CMAKE_CXX_FLAGS}")
|
||||||
|
|
||||||
# LINK accepts /SUBSYSTEM whics sets if we are a WINDOWS (gui) or a CONSOLE programs
|
# 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
|
# This implicitly selects an entrypoint specific to the subsystem selected
|
||||||
# qtmain/QtEntryPointLib provides the correct entrypoint (wWinMain) for gui programs
|
# qtmain/QtEntryPointLib provides the correct entrypoint (wWinMain) for gui programs
|
||||||
# Additinaly LINK autodetects we use a GUI so we can omit /SUBSYSTEM
|
# 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
|
# This allows tests to still use have console without using seperate linker flags
|
||||||
|
# /LTCG allows for linking wholy optimizated programs
|
||||||
# /MANIFEST:NO disables generating a manifest file, we instead provide our own
|
# /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
|
# /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}")
|
set(CMAKE_EXE_LINKER_FLAGS "/LTCG /MANIFEST:NO /STACK:8388608 ${CMAKE_EXE_LINKER_FLAGS}")
|
||||||
|
|
||||||
|
# /GL enables whole program optimizations
|
||||||
|
# /Gw helps reduce binary size
|
||||||
|
# /Gy allows the compiler to package individual functions
|
||||||
|
# /guard:cf enables control flow guard
|
||||||
|
foreach(lang C CXX)
|
||||||
|
set("CMAKE_${lang}_FLAGS_RELEASE" "/GL /Gw /Gy /guard:cf")
|
||||||
|
endforeach()
|
||||||
|
|
||||||
# See https://github.com/ccache/ccache/issues/1040
|
# See https://github.com/ccache/ccache/issues/1040
|
||||||
# Note, CMake 3.25 replaces this with CMAKE_MSVC_DEBUG_INFORMATION_FORMAT
|
# Note, CMake 3.25 replaces this with CMAKE_MSVC_DEBUG_INFORMATION_FORMAT
|
||||||
@ -70,6 +79,9 @@ 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}")
|
||||||
|
|
||||||
@ -126,7 +138,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 6)
|
set(Launcher_VERSION_MAJOR 7)
|
||||||
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}")
|
||||||
@ -217,7 +229,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)
|
||||||
@ -231,12 +243,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)
|
||||||
@ -364,11 +380,11 @@ if(NOT ZLIB_FOUND)
|
|||||||
set(SKIP_INSTALL_ALL ON)
|
set(SKIP_INSTALL_ALL ON)
|
||||||
add_subdirectory(libraries/zlib EXCLUDE_FROM_ALL)
|
add_subdirectory(libraries/zlib EXCLUDE_FROM_ALL)
|
||||||
|
|
||||||
set(ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib" "${CMAKE_CURRENT_BINARY_DIR}/libraries/zlib")
|
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}")
|
set_target_properties(zlibstatic PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${ZLIB_INCLUDE_DIR}")
|
||||||
add_library(ZLIB::ZLIB ALIAS zlibstatic)
|
add_library(ZLIB::ZLIB ALIAS zlibstatic)
|
||||||
set(ZLIB_LIBRARY ZLIB::ZLIB)
|
set(ZLIB_LIBRARY ZLIB::ZLIB CACHE STRING "zlib library name")
|
||||||
set(ZLIB_FOUND true)
|
|
||||||
find_package(ZLIB REQUIRED)
|
find_package(ZLIB REQUIRED)
|
||||||
else()
|
else()
|
||||||
message(STATUS "Using system zlib")
|
message(STATUS "Using system zlib")
|
||||||
|
42
COPYING.md
@ -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/>.
|
||||||
|
@ -13,3 +13,4 @@ Binaries can be found in releases.
|
|||||||
Workaround for downloading from CurseForge and FTB not working [here](https://github.com/fn2006/PollyMC/wiki/CurseForge-Workaround).
|
Workaround for downloading from CurseForge and FTB not working [here](https://github.com/fn2006/PollyMC/wiki/CurseForge-Workaround).
|
||||||
|
|
||||||
To build this yourself, follow the instructions on the Prism Launcher website but clone this repo instead.
|
To build this yourself, follow the instructions on the Prism Launcher website but clone this repo instead.
|
||||||
|
|
||||||
|
@ -161,6 +161,8 @@ class Config {
|
|||||||
QString MODRINTH_STAGING_URL = "https://staging-api.modrinth.com/v2";
|
QString MODRINTH_STAGING_URL = "https://staging-api.modrinth.com/v2";
|
||||||
QString MODRINTH_PROD_URL = "https://api.modrinth.com/v2";
|
QString MODRINTH_PROD_URL = "https://api.modrinth.com/v2";
|
||||||
|
|
||||||
|
QString FLAME_BASE_URL = "https://api.curseforge.com/v1";
|
||||||
|
|
||||||
QString versionString() const;
|
QString versionString() const;
|
||||||
/**
|
/**
|
||||||
* \brief Converts the Version to a string.
|
* \brief Converts the Version to a string.
|
||||||
|
31
flake.lock
generated
@ -3,11 +3,11 @@
|
|||||||
"flake-compat": {
|
"flake-compat": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1650374568,
|
"lastModified": 1668681692,
|
||||||
"narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=",
|
"narHash": "sha256-Ht91NGdewz8IQLtWZ9LCeNXMSXHUss+9COoqu6JLmXU=",
|
||||||
"owner": "edolstra",
|
"owner": "edolstra",
|
||||||
"repo": "flake-compat",
|
"repo": "flake-compat",
|
||||||
"rev": "b4a34015c698c7793d592d66adbab377907a2be8",
|
"rev": "009399224d5e398d03b22badca40a37ac85412a1",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -34,11 +34,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1666057921,
|
"lastModified": 1671417167,
|
||||||
"narHash": "sha256-VpQqtXdj6G7cH//SvoprjR7XT3KS7p+tCVebGK1N6tE=",
|
"narHash": "sha256-JkHam6WQOwZN1t2C2sbp1TqMv3TVRjzrdoejqfefwrM=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "88eab1e431cabd0ed621428d8b40d425a07af39f",
|
"rev": "bb31220cca6d044baa6dc2715b07497a2a7c4bc7",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -52,24 +52,7 @@
|
|||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-compat": "flake-compat",
|
"flake-compat": "flake-compat",
|
||||||
"libnbtplusplus": "libnbtplusplus",
|
"libnbtplusplus": "libnbtplusplus",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs"
|
||||||
"tomlplusplus": "tomlplusplus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tomlplusplus": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1666091090,
|
|
||||||
"narHash": "sha256-djpMCFPvkJcfynV8WnsYdtwLq+J7jpV1iM4C6TojiyM=",
|
|
||||||
"owner": "marzer",
|
|
||||||
"repo": "tomlplusplus",
|
|
||||||
"rev": "1e4a3833d013aee08f58c5b31c69f709afc69f73",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "marzer",
|
|
||||||
"repo": "tomlplusplus",
|
|
||||||
"type": "github"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -5,10 +5,9 @@
|
|||||||
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
|
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
|
||||||
flake-compat = { url = "github:edolstra/flake-compat"; flake = false; };
|
flake-compat = { url = "github:edolstra/flake-compat"; flake = false; };
|
||||||
libnbtplusplus = { url = "github:PrismLauncher/libnbtplusplus"; flake = false; };
|
libnbtplusplus = { url = "github:PrismLauncher/libnbtplusplus"; flake = false; };
|
||||||
tomlplusplus = { url = "github:marzer/tomlplusplus"; flake = false; };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, libnbtplusplus, tomlplusplus, ... }:
|
outputs = { self, nixpkgs, libnbtplusplus, ... }:
|
||||||
let
|
let
|
||||||
# User-friendly version number.
|
# User-friendly version number.
|
||||||
version = builtins.substring 0 8 self.lastModifiedDate;
|
version = builtins.substring 0 8 self.lastModifiedDate;
|
||||||
@ -23,8 +22,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; };
|
||||||
prismlauncher-qt6 = pkgs.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus tomlplusplus; };
|
prismlauncher = pkgs.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus; };
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
83
flatpak/org.prismlauncher.PrismLauncher.yml
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
id: org.prismlauncher.PrismLauncher
|
||||||
|
runtime: org.kde.Platform
|
||||||
|
runtime-version: "5.15-22.08"
|
||||||
|
sdk: org.kde.Sdk
|
||||||
|
sdk-extensions:
|
||||||
|
- org.freedesktop.Sdk.Extension.openjdk17
|
||||||
|
- org.freedesktop.Sdk.Extension.openjdk8
|
||||||
|
add-extensions:
|
||||||
|
com.valvesoftware.Steam.Utility.gamescope:
|
||||||
|
version: stable
|
||||||
|
add-ld-path: lib
|
||||||
|
no-autodownload: true
|
||||||
|
autodelete: false
|
||||||
|
directory: utils/gamescope
|
||||||
|
|
||||||
|
command: prismlauncher
|
||||||
|
finish-args:
|
||||||
|
- --share=ipc
|
||||||
|
- --socket=x11
|
||||||
|
- --socket=wayland
|
||||||
|
- --device=all
|
||||||
|
- --share=network
|
||||||
|
- --socket=pulseaudio
|
||||||
|
# for Discord RPC mods
|
||||||
|
- --filesystem=xdg-run/app/com.discordapp.Discord:create
|
||||||
|
# Mod drag&drop
|
||||||
|
- --filesystem=xdg-download:ro
|
||||||
|
|
||||||
|
modules:
|
||||||
|
- name: prismlauncher
|
||||||
|
buildsystem: cmake-ninja
|
||||||
|
config-opts:
|
||||||
|
- -DLauncher_BUILD_PLATFORM=flatpak
|
||||||
|
- -DCMAKE_BUILD_TYPE=Debug
|
||||||
|
build-options:
|
||||||
|
env:
|
||||||
|
JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17
|
||||||
|
JAVA_COMPILER: /usr/lib/sdk/openjdk17/jvm/openjdk-17/bin/javac
|
||||||
|
sources:
|
||||||
|
- type: dir
|
||||||
|
path: ../
|
||||||
|
- name: openjdk
|
||||||
|
buildsystem: simple
|
||||||
|
build-commands:
|
||||||
|
- mkdir -p /app/jdk/
|
||||||
|
- /usr/lib/sdk/openjdk17/install.sh
|
||||||
|
- mv /app/jre /app/jdk/17
|
||||||
|
- /usr/lib/sdk/openjdk8/install.sh
|
||||||
|
- mv /app/jre /app/jdk/8
|
||||||
|
cleanup: [/jre]
|
||||||
|
- name: xrandr
|
||||||
|
buildsystem: autotools
|
||||||
|
sources:
|
||||||
|
- type: archive
|
||||||
|
url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.1.tar.xz
|
||||||
|
sha256: 7bc76daf9d72f8aff885efad04ce06b90488a1a169d118dea8a2b661832e8762
|
||||||
|
cleanup: [/share/man, /bin/xkeystone]
|
||||||
|
- name: gamemode
|
||||||
|
buildsystem: meson
|
||||||
|
config-opts:
|
||||||
|
- -Dwith-sd-bus-provider=no-daemon
|
||||||
|
- -Dwith-examples=false
|
||||||
|
post-install:
|
||||||
|
# gamemoderun is installed for users who want to use wrapper commands
|
||||||
|
# post-install is running inside the build dir, we need it from the source though
|
||||||
|
- install -Dm755 ../data/gamemoderun -t /app/bin
|
||||||
|
sources:
|
||||||
|
- type: git
|
||||||
|
url: https://github.com/FeralInteractive/gamemode
|
||||||
|
tag: "1.7"
|
||||||
|
commit: 4dc99dff76218718763a6b07fc1900fa6d1dafd9
|
||||||
|
- name: enhance
|
||||||
|
buildsystem: simple
|
||||||
|
build-commands:
|
||||||
|
- mkdir -p /app/utils/gamescope
|
||||||
|
- install -Dm755 prime-run /app/bin/prime-run
|
||||||
|
- mv /app/bin/prismlauncher /app/bin/prismrun
|
||||||
|
- install -Dm755 prismlauncher /app/bin/prismlauncher
|
||||||
|
sources:
|
||||||
|
- type: file
|
||||||
|
path: ../flatpak/prime-run
|
||||||
|
- type: file
|
||||||
|
path: ../flatpak/prismlauncher
|
4
flatpak/prime-run
Normal file
@ -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
@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# discord RPC
|
||||||
|
for i in {0..9}; do
|
||||||
|
test -S "$XDG_RUNTIME_DIR"/discord-ipc-"$i" || ln -sf {app/com.discordapp.Discord,"$XDG_RUNTIME_DIR"}/discord-ipc-"$i";
|
||||||
|
done
|
||||||
|
|
||||||
|
export PATH="${PATH}${PATH:+:}/app/utils/gamescope/bin:/usr/lib/extensions/vulkan/MangoHud/bin"
|
||||||
|
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}${LD_LIBRARY_PATH:+:}/usr/lib/extensions/vulkan/MangoHud/\$LIB/"
|
||||||
|
|
||||||
|
exec /app/bin/prismrun "$@"
|
@ -1,4 +1,7 @@
|
|||||||
// 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
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Prism Launcher - Minecraft Launcher
|
* Prism Launcher - Minecraft Launcher
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
@ -38,10 +41,14 @@
|
|||||||
#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/MainWindow.h"
|
#include "ui/MainWindow.h"
|
||||||
#include "ui/InstanceWindow.h"
|
#include "ui/InstanceWindow.h"
|
||||||
|
|
||||||
|
#include "ui/dialogs/ProgressDialog.h"
|
||||||
#include "ui/instanceview/AccessibleInstanceView.h"
|
#include "ui/instanceview/AccessibleInstanceView.h"
|
||||||
|
|
||||||
#include "ui/pages/BasePageProvider.h"
|
#include "ui/pages/BasePageProvider.h"
|
||||||
@ -90,6 +97,7 @@
|
|||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
|
|
||||||
#include "InstanceList.h"
|
#include "InstanceList.h"
|
||||||
|
#include "MTPixmapCache.h"
|
||||||
|
|
||||||
#include <minecraft/auth/AccountList.h>
|
#include <minecraft/auth/AccountList.h>
|
||||||
#include "icons/IconList.h"
|
#include "icons/IconList.h"
|
||||||
@ -118,6 +126,7 @@
|
|||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
#include "gamemode_client.h"
|
#include "gamemode_client.h"
|
||||||
|
#include "MangoHud.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
@ -134,6 +143,8 @@
|
|||||||
|
|
||||||
static const QLatin1String liveCheckFile("live.check");
|
static const QLatin1String liveCheckFile("live.check");
|
||||||
|
|
||||||
|
PixmapCache* PixmapCache::s_instance = nullptr;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
||||||
{
|
{
|
||||||
@ -226,7 +237,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();
|
||||||
|
|
||||||
@ -301,24 +312,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;
|
||||||
@ -441,6 +434,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;
|
||||||
@ -695,6 +697,9 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
m_globalSettingsProvider->addPage<AccountListPage>();
|
m_globalSettingsProvider->addPage<AccountListPage>();
|
||||||
m_globalSettingsProvider->addPage<APIPage>();
|
m_globalSettingsProvider->addPage<APIPage>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PixmapCache::setInstance(new PixmapCache(this));
|
||||||
|
|
||||||
qDebug() << "<> Settings loaded.";
|
qDebug() << "<> Settings loaded.";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -918,13 +923,13 @@ 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();
|
||||||
@ -1522,17 +1527,8 @@ void Application::updateCapabilities()
|
|||||||
if (gamemode_query_status() >= 0)
|
if (gamemode_query_status() >= 0)
|
||||||
m_capabilities |= SupportsGameMode;
|
m_capabilities |= SupportsGameMode;
|
||||||
|
|
||||||
{
|
if (!MangoHud::getLibraryString().isEmpty())
|
||||||
void *dummy = dlopen("libMangoHud_dlsym.so", RTLD_LAZY);
|
|
||||||
// try normal variant as well
|
|
||||||
if (dummy == NULL)
|
|
||||||
dummy = dlopen("libMangoHud.so", RTLD_LAZY);
|
|
||||||
|
|
||||||
if (dummy != NULL) {
|
|
||||||
dlclose(dummy);
|
|
||||||
m_capabilities |= SupportsMangoHud;
|
m_capabilities |= SupportsMangoHud;
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1608,3 +1604,89 @@ int Application::suitableMaxMem()
|
|||||||
|
|
||||||
return maxMemoryAlloc;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -231,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();
|
||||||
|
|
||||||
|
@ -47,8 +47,8 @@ void ApplicationMessage::parse(const QByteArray & input) {
|
|||||||
args.clear();
|
args.clear();
|
||||||
|
|
||||||
auto parsedArgs = root.value("args").toObject();
|
auto parsedArgs = root.value("args").toObject();
|
||||||
for(auto iter = parsedArgs.begin(); iter != parsedArgs.end(); iter++) {
|
for(auto iter = parsedArgs.constBegin(); iter != parsedArgs.constEnd(); iter++) {
|
||||||
args[iter.key()] = iter.value().toString();
|
args.insert(iter.key(), iter.value().toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,8 +56,8 @@ QByteArray ApplicationMessage::serialize() {
|
|||||||
QJsonObject root;
|
QJsonObject root;
|
||||||
root.insert("command", command);
|
root.insert("command", command);
|
||||||
QJsonObject outArgs;
|
QJsonObject outArgs;
|
||||||
for (auto iter = args.begin(); iter != args.end(); iter++) {
|
for (auto iter = args.constBegin(); iter != args.constEnd(); iter++) {
|
||||||
outArgs[iter.key()] = iter.value();
|
outArgs.insert(iter.key(), iter.value());
|
||||||
}
|
}
|
||||||
root.insert("args", outArgs);
|
root.insert("args", outArgs);
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QMap>
|
#include <QHash>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
|
|
||||||
struct ApplicationMessage {
|
struct ApplicationMessage {
|
||||||
QString command;
|
QString command;
|
||||||
QMap<QString, QString> args;
|
QHash<QString, QString> args;
|
||||||
|
|
||||||
QByteArray serialize();
|
QByteArray serialize();
|
||||||
void parse(const QByteArray & input);
|
void parse(const QByteArray & input);
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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
|
||||||
|
@ -89,7 +89,18 @@ set(CORE_SOURCES
|
|||||||
# Time
|
# Time
|
||||||
MMCTime.h
|
MMCTime.h
|
||||||
MMCTime.cpp
|
MMCTime.cpp
|
||||||
|
|
||||||
|
MTPixmapCache.h
|
||||||
)
|
)
|
||||||
|
if (UNIX AND NOT CYGWIN AND NOT APPLE)
|
||||||
|
set(CORE_SOURCES
|
||||||
|
${CORE_SOURCES}
|
||||||
|
|
||||||
|
# MangoHud
|
||||||
|
MangoHud.h
|
||||||
|
MangoHud.cpp
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
set(PATHMATCHER_SOURCES
|
set(PATHMATCHER_SOURCES
|
||||||
# Path matchers
|
# Path matchers
|
||||||
@ -97,6 +108,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
|
||||||
@ -583,6 +595,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
|
||||||
@ -606,6 +620,8 @@ 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
|
||||||
@ -683,6 +699,8 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/pages/instance/GameOptionsPage.h
|
ui/pages/instance/GameOptionsPage.h
|
||||||
ui/pages/instance/VersionPage.cpp
|
ui/pages/instance/VersionPage.cpp
|
||||||
ui/pages/instance/VersionPage.h
|
ui/pages/instance/VersionPage.h
|
||||||
|
ui/pages/instance/ManagedPackPage.cpp
|
||||||
|
ui/pages/instance/ManagedPackPage.h
|
||||||
ui/pages/instance/TexturePackPage.h
|
ui/pages/instance/TexturePackPage.h
|
||||||
ui/pages/instance/ResourcePackPage.h
|
ui/pages/instance/ResourcePackPage.h
|
||||||
ui/pages/instance/ShaderPackPage.h
|
ui/pages/instance/ShaderPackPage.h
|
||||||
@ -801,6 +819,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
|
||||||
@ -922,6 +942,7 @@ qt_wrap_ui(LAUNCHER_UI
|
|||||||
ui/pages/instance/OtherLogsPage.ui
|
ui/pages/instance/OtherLogsPage.ui
|
||||||
ui/pages/instance/InstanceSettingsPage.ui
|
ui/pages/instance/InstanceSettingsPage.ui
|
||||||
ui/pages/instance/VersionPage.ui
|
ui/pages/instance/VersionPage.ui
|
||||||
|
ui/pages/instance/ManagedPackPage.ui
|
||||||
ui/pages/instance/WorldListPage.ui
|
ui/pages/instance/WorldListPage.ui
|
||||||
ui/pages/instance/ScreenshotsPage.ui
|
ui/pages/instance/ScreenshotsPage.ui
|
||||||
ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui
|
ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui
|
||||||
@ -949,6 +970,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/ElybyLoginDialog.ui
|
ui/dialogs/ElybyLoginDialog.ui
|
||||||
@ -968,6 +990,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
|
||||||
@ -1153,6 +1177,8 @@ if(INSTALL_BUNDLE STREQUAL "full")
|
|||||||
CONFIGURATIONS Debug RelWithDebInfo ""
|
CONFIGURATIONS Debug RelWithDebInfo ""
|
||||||
DESTINATION ${PLUGIN_DEST_DIR}
|
DESTINATION ${PLUGIN_DEST_DIR}
|
||||||
COMPONENT Runtime
|
COMPONENT Runtime
|
||||||
|
PATTERN "*qopensslbackend*" EXCLUDE
|
||||||
|
PATTERN "*qcertonlybackend*" EXCLUDE
|
||||||
)
|
)
|
||||||
install(
|
install(
|
||||||
DIRECTORY "${QT_PLUGINS_DIR}/tls"
|
DIRECTORY "${QT_PLUGINS_DIR}/tls"
|
||||||
@ -1162,6 +1188,8 @@ if(INSTALL_BUNDLE STREQUAL "full")
|
|||||||
REGEX "dd\\." EXCLUDE
|
REGEX "dd\\." EXCLUDE
|
||||||
REGEX "_debug\\." EXCLUDE
|
REGEX "_debug\\." EXCLUDE
|
||||||
REGEX "\\.dSYM" EXCLUDE
|
REGEX "\\.dSYM" EXCLUDE
|
||||||
|
PATTERN "*qopensslbackend*" EXCLUDE
|
||||||
|
PATTERN "*qcertonlybackend*" EXCLUDE
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
configure_file(
|
configure_file(
|
||||||
|
96
launcher/DataMigrationTask.cpp
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
#include "DataMigrationTask.h"
|
||||||
|
|
||||||
|
#include "FileSystem.h"
|
||||||
|
|
||||||
|
#include <QDirIterator>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QMap>
|
||||||
|
|
||||||
|
#include <QtConcurrent>
|
||||||
|
|
||||||
|
DataMigrationTask::DataMigrationTask(QObject* parent,
|
||||||
|
const QString& sourcePath,
|
||||||
|
const QString& targetPath,
|
||||||
|
const IPathMatcher::Ptr pathMatcher)
|
||||||
|
: Task(parent), m_sourcePath(sourcePath), m_targetPath(targetPath), m_pathMatcher(pathMatcher), m_copy(sourcePath, targetPath)
|
||||||
|
{
|
||||||
|
m_copy.matcher(m_pathMatcher.get()).whitelist(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataMigrationTask::executeTask()
|
||||||
|
{
|
||||||
|
setStatus(tr("Scanning files..."));
|
||||||
|
|
||||||
|
// 1. Scan
|
||||||
|
// Check how many files we gotta copy
|
||||||
|
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&] {
|
||||||
|
return m_copy(true); // dry run to collect amount of files
|
||||||
|
});
|
||||||
|
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::dryRunFinished);
|
||||||
|
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &DataMigrationTask::dryRunAborted);
|
||||||
|
m_copyFutureWatcher.setFuture(m_copyFuture);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataMigrationTask::dryRunFinished()
|
||||||
|
{
|
||||||
|
disconnect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::dryRunFinished);
|
||||||
|
disconnect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &DataMigrationTask::dryRunAborted);
|
||||||
|
|
||||||
|
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||||
|
if (!m_copyFuture.isValid() || !m_copyFuture.result()) {
|
||||||
|
#else
|
||||||
|
if (!m_copyFuture.result()) {
|
||||||
|
#endif
|
||||||
|
emitFailed(tr("Failed to scan source path."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Copy
|
||||||
|
// Actually copy all files now.
|
||||||
|
m_toCopy = m_copy.totalCopied();
|
||||||
|
connect(&m_copy, &FS::copy::fileCopied, [&, this](const QString& relativeName) {
|
||||||
|
QString shortenedName = relativeName;
|
||||||
|
// shorten the filename to hopefully fit into one line
|
||||||
|
if (shortenedName.length() > 50)
|
||||||
|
shortenedName = relativeName.left(20) + "…" + relativeName.right(29);
|
||||||
|
setProgress(m_copy.totalCopied(), m_toCopy);
|
||||||
|
setStatus(tr("Copying %1…").arg(shortenedName));
|
||||||
|
});
|
||||||
|
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&] {
|
||||||
|
return m_copy(false); // actually copy now
|
||||||
|
});
|
||||||
|
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::copyFinished);
|
||||||
|
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &DataMigrationTask::copyAborted);
|
||||||
|
m_copyFutureWatcher.setFuture(m_copyFuture);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataMigrationTask::dryRunAborted()
|
||||||
|
{
|
||||||
|
emitFailed(tr("Aborted"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataMigrationTask::copyFinished()
|
||||||
|
{
|
||||||
|
disconnect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::copyFinished);
|
||||||
|
disconnect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &DataMigrationTask::copyAborted);
|
||||||
|
|
||||||
|
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||||
|
if (!m_copyFuture.isValid() || !m_copyFuture.result()) {
|
||||||
|
#else
|
||||||
|
if (!m_copyFuture.result()) {
|
||||||
|
#endif
|
||||||
|
emitFailed(tr("Some paths could not be copied!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emitSucceeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataMigrationTask::copyAborted()
|
||||||
|
{
|
||||||
|
emitFailed(tr("Aborted"));
|
||||||
|
}
|
42
launcher/DataMigrationTask.h
Normal file
@ -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;
|
||||||
|
};
|
@ -49,6 +49,7 @@
|
|||||||
#include "StringUtils.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>
|
||||||
@ -152,9 +153,10 @@ bool ensureFolderPathExists(QString foldernamepath)
|
|||||||
/// @brief Copies a directory and it's contents from src to dest
|
/// @brief Copies a directory and it's contents from src to dest
|
||||||
/// @param offset subdirectory form src to copy to dest
|
/// @param offset subdirectory form src to copy to dest
|
||||||
/// @return if there was an error during the filecopy
|
/// @return if there was an error during the filecopy
|
||||||
bool copy::operator()(const QString& offset)
|
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
|
||||||
@ -174,18 +176,21 @@ bool copy::operator()(const QString& offset)
|
|||||||
|
|
||||||
// Function that'll do the actual copying
|
// Function that'll do the actual copying
|
||||||
auto copy_file = [&](QString src_path, QString relative_dst_path) {
|
auto copy_file = [&](QString src_path, QString relative_dst_path) {
|
||||||
if (m_blacklist && m_blacklist->matches(relative_dst_path))
|
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto dst_path = PathCombine(dst, relative_dst_path);
|
auto dst_path = PathCombine(dst, relative_dst_path);
|
||||||
|
if (!dryRun) {
|
||||||
ensureFilePathExists(dst_path);
|
ensureFilePathExists(dst_path);
|
||||||
|
|
||||||
fs::copy(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), opt, err);
|
fs::copy(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), opt, err);
|
||||||
|
}
|
||||||
if (err) {
|
if (err) {
|
||||||
qWarning() << "Failed to copy files:" << QString::fromStdString(err.message());
|
qWarning() << "Failed to copy files:" << QString::fromStdString(err.message());
|
||||||
qDebug() << "Source file:" << src_path;
|
qDebug() << "Source file:" << src_path;
|
||||||
qDebug() << "Destination file:" << dst_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
|
||||||
@ -339,12 +344,35 @@ 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)
|
||||||
|
QFile f(destination);
|
||||||
f.open(QIODevice::WriteOnly | QIODevice::Text);
|
f.open(QIODevice::WriteOnly | QIODevice::Text);
|
||||||
QTextStream stream(&f);
|
QTextStream stream(&f);
|
||||||
|
|
||||||
@ -356,10 +384,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";
|
||||||
|
if (!icon.isEmpty())
|
||||||
|
{
|
||||||
stream << "Icon=" << icon.toLocal8Bit() << "\n";
|
stream << "Icon=" << icon.toLocal8Bit() << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
stream.flush();
|
stream.flush();
|
||||||
f.close();
|
f.close();
|
||||||
@ -367,25 +397,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]);
|
|
||||||
// argStr.append(" ");
|
|
||||||
// }
|
|
||||||
// argStr.toWCharArray(args_w);
|
|
||||||
|
|
||||||
// return SUCCEEDED(CreateLink(file_w, dest_w, args_w));
|
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
target = targetInfo.absoluteFilePath();
|
||||||
|
|
||||||
|
if (target.length() >= MAX_PATH)
|
||||||
|
{
|
||||||
|
qWarning() << "Target file path is too long!";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!icon.isEmpty() && icon.length() >= MAX_PATH)
|
||||||
|
{
|
||||||
|
qWarning() << "Icon path is too long!";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
destination += ".lnk";
|
||||||
|
|
||||||
|
if (destination.length() >= MAX_PATH)
|
||||||
|
{
|
||||||
|
qWarning() << "Destination path is too long!";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString argStr;
|
||||||
|
int argCount = args.count();
|
||||||
|
for (int i = 0; i < argCount; i++)
|
||||||
|
{
|
||||||
|
if (args[i].contains(' '))
|
||||||
|
{
|
||||||
|
argStr.append('"').append(args[i]).append('"');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
argStr.append(args[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < argCount - 1)
|
||||||
|
{
|
||||||
|
argStr.append(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argStr.length() >= MAX_PATH)
|
||||||
|
{
|
||||||
|
qWarning() << "Arguments string is too long!";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT hres;
|
||||||
|
|
||||||
|
// ...yes, you need to initialize the entire COM stack just to make a shortcut
|
||||||
|
hres = CoInitialize(nullptr);
|
||||||
|
if (FAILED(hres))
|
||||||
|
{
|
||||||
|
qWarning() << "Failed to initialize COM!";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
WCHAR wsz[MAX_PATH];
|
||||||
|
|
||||||
|
IShellLink* psl;
|
||||||
|
|
||||||
|
// create an IShellLink instance - this stores the shortcut's attributes
|
||||||
|
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
|
||||||
|
if (SUCCEEDED(hres))
|
||||||
|
{
|
||||||
|
wmemset(wsz, 0, MAX_PATH);
|
||||||
|
target.toWCharArray(wsz);
|
||||||
|
psl->SetPath(wsz);
|
||||||
|
|
||||||
|
wmemset(wsz, 0, MAX_PATH);
|
||||||
|
argStr.toWCharArray(wsz);
|
||||||
|
psl->SetArguments(wsz);
|
||||||
|
|
||||||
|
wmemset(wsz, 0, MAX_PATH);
|
||||||
|
targetInfo.absolutePath().toWCharArray(wsz);
|
||||||
|
psl->SetWorkingDirectory(wsz); // "Starts in" attribute
|
||||||
|
|
||||||
|
if (!icon.isEmpty())
|
||||||
|
{
|
||||||
|
wmemset(wsz, 0, MAX_PATH);
|
||||||
|
icon.toWCharArray(wsz);
|
||||||
|
psl->SetIconLocation(wsz, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// query an IPersistFile interface from our IShellLink instance
|
||||||
|
// this is the interface that will actually let us save the shortcut to disk!
|
||||||
|
IPersistFile* ppf;
|
||||||
|
hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf);
|
||||||
|
if (SUCCEEDED(hres))
|
||||||
|
{
|
||||||
|
wmemset(wsz, 0, MAX_PATH);
|
||||||
|
destination.toWCharArray(wsz);
|
||||||
|
hres = ppf->Save(wsz, TRUE);
|
||||||
|
if (FAILED(hres))
|
||||||
|
{
|
||||||
|
qWarning() << "IPresistFile->Save() failed";
|
||||||
|
qWarning() << "hres = " << hres;
|
||||||
|
}
|
||||||
|
ppf->Release();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qWarning() << "Failed to query IPersistFile interface from IShellLink instance";
|
||||||
|
qWarning() << "hres = " << hres;
|
||||||
|
}
|
||||||
|
psl->Release();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qWarning() << "Failed to create IShellLink instance";
|
||||||
|
qWarning() << "hres = " << hres;
|
||||||
|
}
|
||||||
|
|
||||||
|
// go away COM, nobody likes you
|
||||||
|
CoUninitialize();
|
||||||
|
|
||||||
|
return SUCCEEDED(hres);
|
||||||
#else
|
#else
|
||||||
qWarning("Desktop Shortcuts not supported on your platform!");
|
qWarning("Desktop Shortcuts not supported on your platform!");
|
||||||
return false;
|
return false;
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFlags>
|
#include <QFlags>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
namespace FS {
|
namespace FS {
|
||||||
|
|
||||||
@ -76,9 +77,10 @@ bool ensureFilePathExists(QString filenamepath);
|
|||||||
bool ensureFolderPathExists(QString filenamepath);
|
bool ensureFolderPathExists(QString filenamepath);
|
||||||
|
|
||||||
/// @brief Copies a directory and it's contents from src to dest
|
/// @brief Copies a directory and it's contents from src to dest
|
||||||
class copy {
|
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);
|
||||||
@ -88,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;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -156,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);
|
||||||
}
|
}
|
||||||
|
@ -25,10 +25,12 @@ void InstanceCopyTask::executeTask()
|
|||||||
{
|
{
|
||||||
setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
|
setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
|
||||||
|
|
||||||
|
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]{
|
||||||
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
|
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
|
||||||
folderCopy.followSymlinks(false).blacklist(m_matcher.get());
|
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);
|
||||||
|
@ -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:\n%1").arg(m_error_message));
|
||||||
|
} else {
|
||||||
emitFailed(tr("Error while creating new instance."));
|
emitFailed(tr("Error while creating new instance."));
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,11 +55,9 @@
|
|||||||
|
|
||||||
#include <quazip/quazipdir.h>
|
#include <quazip/quazipdir.h>
|
||||||
|
|
||||||
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent)
|
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent, QMap<QString, QString>&& extra_info)
|
||||||
{
|
: m_sourceUrl(sourceUrl), m_extra_info(extra_info), m_parent(parent)
|
||||||
m_sourceUrl = sourceUrl;
|
{}
|
||||||
m_parent = parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool InstanceImportTask::abort()
|
bool InstanceImportTask::abort()
|
||||||
{
|
{
|
||||||
@ -164,18 +162,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;
|
||||||
@ -263,14 +257,34 @@ void InstanceImportTask::extractAborted()
|
|||||||
|
|
||||||
void InstanceImportTask::processFlame()
|
void InstanceImportTask::processFlame()
|
||||||
{
|
{
|
||||||
auto* inst_creation_task = new FlameCreationTask(m_stagingPath, m_globalSettings, m_parent);
|
FlameCreationTask* inst_creation_task = nullptr;
|
||||||
|
if (!m_extra_info.isEmpty()) {
|
||||||
|
auto pack_id_it = m_extra_info.constFind("pack_id");
|
||||||
|
Q_ASSERT(pack_id_it != m_extra_info.constEnd());
|
||||||
|
auto pack_id = pack_id_it.value();
|
||||||
|
|
||||||
|
auto pack_version_id_it = m_extra_info.constFind("pack_version_id");
|
||||||
|
Q_ASSERT(pack_version_id_it != m_extra_info.constEnd());
|
||||||
|
auto pack_version_id = pack_version_id_it.value();
|
||||||
|
|
||||||
|
QString original_instance_id;
|
||||||
|
auto original_instance_id_it = m_extra_info.constFind("original_instance_id");
|
||||||
|
if (original_instance_id_it != m_extra_info.constEnd())
|
||||||
|
original_instance_id = original_instance_id_it.value();
|
||||||
|
|
||||||
|
inst_creation_task = new FlameCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
|
||||||
|
} else {
|
||||||
|
// FIXME: Find a way to get IDs in directly imported ZIPs
|
||||||
|
inst_creation_task = new FlameCreationTask(m_stagingPath, m_globalSettings, m_parent, {}, {});
|
||||||
|
}
|
||||||
|
|
||||||
inst_creation_task->setName(*this);
|
inst_creation_task->setName(*this);
|
||||||
inst_creation_task->setIcon(m_instIcon);
|
inst_creation_task->setIcon(m_instIcon);
|
||||||
inst_creation_task->setGroup(m_instGroup);
|
inst_creation_task->setGroup(m_instGroup);
|
||||||
|
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
|
||||||
|
|
||||||
connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] {
|
connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] {
|
||||||
setOverride(inst_creation_task->shouldOverride());
|
setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID());
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
});
|
});
|
||||||
connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed);
|
connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed);
|
||||||
@ -327,14 +341,41 @@ void InstanceImportTask::processMultiMC()
|
|||||||
|
|
||||||
void InstanceImportTask::processModrinth()
|
void InstanceImportTask::processModrinth()
|
||||||
{
|
{
|
||||||
auto* inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, m_sourceUrl.toString());
|
ModrinthCreationTask* inst_creation_task = nullptr;
|
||||||
|
if (!m_extra_info.isEmpty()) {
|
||||||
|
auto pack_id_it = m_extra_info.constFind("pack_id");
|
||||||
|
Q_ASSERT(pack_id_it != m_extra_info.constEnd());
|
||||||
|
auto pack_id = pack_id_it.value();
|
||||||
|
|
||||||
|
QString pack_version_id;
|
||||||
|
auto pack_version_id_it = m_extra_info.constFind("pack_version_id");
|
||||||
|
if (pack_version_id_it != m_extra_info.constEnd())
|
||||||
|
pack_version_id = pack_version_id_it.value();
|
||||||
|
|
||||||
|
QString original_instance_id;
|
||||||
|
auto original_instance_id_it = m_extra_info.constFind("original_instance_id");
|
||||||
|
if (original_instance_id_it != m_extra_info.constEnd())
|
||||||
|
original_instance_id = original_instance_id_it.value();
|
||||||
|
|
||||||
|
inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
|
||||||
|
} else {
|
||||||
|
QString pack_id;
|
||||||
|
if (!m_sourceUrl.isEmpty()) {
|
||||||
|
QRegularExpression regex(R"(data\/(.*)\/versions)");
|
||||||
|
pack_id = regex.match(m_sourceUrl.toString()).captured(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Find a way to get the ID in directly imported ZIPs
|
||||||
|
inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id);
|
||||||
|
}
|
||||||
|
|
||||||
inst_creation_task->setName(*this);
|
inst_creation_task->setName(*this);
|
||||||
inst_creation_task->setIcon(m_instIcon);
|
inst_creation_task->setIcon(m_instIcon);
|
||||||
inst_creation_task->setGroup(m_instGroup);
|
inst_creation_task->setGroup(m_instGroup);
|
||||||
|
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
|
||||||
|
|
||||||
connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] {
|
connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] {
|
||||||
setOverride(inst_creation_task->shouldOverride());
|
setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID());
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
});
|
});
|
||||||
connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed);
|
connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed);
|
||||||
|
@ -56,7 +56,7 @@ class InstanceImportTask : public InstanceTask
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit InstanceImportTask(const QUrl sourceUrl, QWidget* parent = nullptr);
|
explicit InstanceImportTask(const QUrl sourceUrl, QWidget* parent = nullptr, QMap<QString, QString>&& extra_info = {});
|
||||||
|
|
||||||
bool abort() override;
|
bool abort() override;
|
||||||
const QVector<Flame::File> &getBlockedFiles() const
|
const QVector<Flame::File> &getBlockedFiles() const
|
||||||
@ -101,6 +101,10 @@ private: /* data */
|
|||||||
Modrinth,
|
Modrinth,
|
||||||
} m_modpackType = ModpackType::Unknown;
|
} m_modpackType = ModpackType::Unknown;
|
||||||
|
|
||||||
|
// Extra info we might need, that's available before, but can't be derived from
|
||||||
|
// the source URL / the resource it points to alone.
|
||||||
|
QMap<QString, QString> m_extra_info;
|
||||||
|
|
||||||
//FIXME: nuke
|
//FIXME: nuke
|
||||||
QWidget* m_parent;
|
QWidget* m_parent;
|
||||||
};
|
};
|
||||||
|
@ -816,7 +816,7 @@ class InstanceStaging : public Task {
|
|||||||
void childSucceded()
|
void childSucceded()
|
||||||
{
|
{
|
||||||
unsigned sleepTime = backoff();
|
unsigned sleepTime = backoff();
|
||||||
if (m_parent->commitStagedInstance(m_stagingPath, m_instance_name, m_groupName, m_child->shouldOverride()))
|
if (m_parent->commitStagedInstance(m_stagingPath, m_instance_name, m_groupName, *m_child.get()))
|
||||||
{
|
{
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
return;
|
return;
|
||||||
@ -880,25 +880,22 @@ QString InstanceList::getStagedInstancePath()
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InstanceList::commitStagedInstance(const QString& path, InstanceName const& instanceName, const QString& groupName, bool should_override)
|
bool InstanceList::commitStagedInstance(const QString& path, InstanceName const& instanceName, const QString& groupName, InstanceTask const& commiting)
|
||||||
{
|
{
|
||||||
QDir dir;
|
QDir dir;
|
||||||
QString instID;
|
QString instID;
|
||||||
InstancePtr inst;
|
InstancePtr inst;
|
||||||
|
|
||||||
|
auto should_override = commiting.shouldOverride();
|
||||||
|
|
||||||
if (should_override) {
|
if (should_override) {
|
||||||
// This is to avoid problems when the instance folder gets manually renamed
|
instID = commiting.originalInstanceID();
|
||||||
if ((inst = getInstanceByManagedName(instanceName.originalName()))) {
|
|
||||||
instID = QFileInfo(inst->instanceRoot()).fileName();
|
|
||||||
} else if ((inst = getInstanceByManagedName(instanceName.modifiedName()))) {
|
|
||||||
instID = QFileInfo(inst->instanceRoot()).fileName();
|
|
||||||
} else {
|
|
||||||
instID = FS::RemoveInvalidFilenameChars(instanceName.modifiedName(), '-');
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
instID = FS::DirNameFromString(instanceName.modifiedName(), m_instDir);
|
instID = FS::DirNameFromString(instanceName.modifiedName(), m_instDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Q_ASSERT(!instID.isEmpty());
|
||||||
|
|
||||||
{
|
{
|
||||||
WatchLock lock(m_watcher, m_instDir);
|
WatchLock lock(m_watcher, m_instDir);
|
||||||
QString destination = FS::PathCombine(m_instDir, instID);
|
QString destination = FS::PathCombine(m_instDir, instID);
|
||||||
|
@ -133,7 +133,7 @@ public:
|
|||||||
* should_override is used when another similar instance already exists, and we want to override it
|
* should_override is used when another similar instance already exists, and we want to override it
|
||||||
* - for instance, when updating it.
|
* - for instance, when updating it.
|
||||||
*/
|
*/
|
||||||
bool commitStagedInstance(const QString& keyPath, const InstanceName& instanceName, const QString& groupName, bool should_override);
|
bool commitStagedInstance(const QString& keyPath, const InstanceName& instanceName, const QString& groupName, const InstanceTask&);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroy a previously created staging area given by @keyPath - used when creation fails.
|
* Destroy a previously created staging area given by @keyPath - used when creation fails.
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include "ui/pages/BasePageProvider.h"
|
#include "ui/pages/BasePageProvider.h"
|
||||||
#include "ui/pages/instance/LogPage.h"
|
#include "ui/pages/instance/LogPage.h"
|
||||||
#include "ui/pages/instance/VersionPage.h"
|
#include "ui/pages/instance/VersionPage.h"
|
||||||
|
#include "ui/pages/instance/ManagedPackPage.h"
|
||||||
#include "ui/pages/instance/ModFolderPage.h"
|
#include "ui/pages/instance/ModFolderPage.h"
|
||||||
#include "ui/pages/instance/ResourcePackPage.h"
|
#include "ui/pages/instance/ResourcePackPage.h"
|
||||||
#include "ui/pages/instance/TexturePackPage.h"
|
#include "ui/pages/instance/TexturePackPage.h"
|
||||||
@ -33,6 +34,7 @@ public:
|
|||||||
values.append(new LogPage(inst));
|
values.append(new LogPage(inst));
|
||||||
std::shared_ptr<MinecraftInstance> onesix = std::dynamic_pointer_cast<MinecraftInstance>(inst);
|
std::shared_ptr<MinecraftInstance> onesix = std::dynamic_pointer_cast<MinecraftInstance>(inst);
|
||||||
values.append(new VersionPage(onesix.get()));
|
values.append(new VersionPage(onesix.get()));
|
||||||
|
values.append(ManagedPackPage::createPage(onesix.get()));
|
||||||
auto modsPage = new ModFolderPage(onesix.get(), onesix->loaderModList());
|
auto modsPage = new ModFolderPage(onesix.get(), onesix->loaderModList());
|
||||||
modsPage->setFilter("%1 (*.zip *.jar *.litemod)");
|
modsPage->setFilter("%1 (*.zip *.jar *.litemod)");
|
||||||
values.append(modsPage);
|
values.append(modsPage);
|
||||||
|
@ -18,6 +18,29 @@ InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& ol
|
|||||||
return InstanceNameChange::ShouldKeep;
|
return InstanceNameChange::ShouldKeep;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ShouldUpdate askIfShouldUpdate(QWidget *parent, QString original_version_name)
|
||||||
|
{
|
||||||
|
auto info = CustomMessageBox::selectable(
|
||||||
|
parent, QObject::tr("Similar modpack was found!"),
|
||||||
|
QObject::tr("One or more of your instances are from this same modpack%1. Do you want to create a "
|
||||||
|
"separate instance, or update the existing one?\n\nNOTE: Make sure you made a backup of your important instance data before "
|
||||||
|
"updating, as worlds can be corrupted and some configuration may be lost (due to pack overrides).")
|
||||||
|
.arg(original_version_name),
|
||||||
|
QMessageBox::Information, QMessageBox::Ok | QMessageBox::Reset | QMessageBox::Abort);
|
||||||
|
info->setButtonText(QMessageBox::Ok, QObject::tr("Update existing instance"));
|
||||||
|
info->setButtonText(QMessageBox::Abort, QObject::tr("Create new instance"));
|
||||||
|
info->setButtonText(QMessageBox::Reset, QObject::tr("Cancel"));
|
||||||
|
|
||||||
|
info->exec();
|
||||||
|
|
||||||
|
if (info->clickedButton() == info->button(QMessageBox::Ok))
|
||||||
|
return ShouldUpdate::Update;
|
||||||
|
if (info->clickedButton() == info->button(QMessageBox::Abort))
|
||||||
|
return ShouldUpdate::SkipUpdating;
|
||||||
|
return ShouldUpdate::Cancel;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
QString InstanceName::name() const
|
QString InstanceName::name() const
|
||||||
{
|
{
|
||||||
if (!m_modified_name.isEmpty())
|
if (!m_modified_name.isEmpty())
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
/* Helpers */
|
/* Helpers */
|
||||||
enum class InstanceNameChange { ShouldChange, ShouldKeep };
|
enum class InstanceNameChange { ShouldChange, ShouldKeep };
|
||||||
[[nodiscard]] InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& old_name, const QString& new_name);
|
[[nodiscard]] InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& old_name, const QString& new_name);
|
||||||
|
enum class ShouldUpdate { Update, SkipUpdating, Cancel };
|
||||||
|
[[nodiscard]] ShouldUpdate askIfShouldUpdate(QWidget* parent, QString original_version_name);
|
||||||
|
|
||||||
struct InstanceName {
|
struct InstanceName {
|
||||||
public:
|
public:
|
||||||
@ -42,10 +44,20 @@ class InstanceTask : public Task, public InstanceName {
|
|||||||
void setGroup(const QString& group) { m_instGroup = group; }
|
void setGroup(const QString& group) { m_instGroup = group; }
|
||||||
QString group() const { return m_instGroup; }
|
QString group() const { return m_instGroup; }
|
||||||
|
|
||||||
|
[[nodiscard]] bool shouldConfirmUpdate() const { return m_confirm_update; }
|
||||||
|
void setConfirmUpdate(bool confirm) { m_confirm_update = confirm; }
|
||||||
|
|
||||||
bool shouldOverride() const { return m_override_existing; }
|
bool shouldOverride() const { return m_override_existing; }
|
||||||
|
|
||||||
|
[[nodiscard]] QString originalInstanceID() const { return m_original_instance_id; };
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void setOverride(bool override) { m_override_existing = override; }
|
void setOverride(bool override, QString instance_id_to_override = {})
|
||||||
|
{
|
||||||
|
m_override_existing = override;
|
||||||
|
if (!instance_id_to_override.isEmpty())
|
||||||
|
m_original_instance_id = instance_id_to_override;
|
||||||
|
}
|
||||||
|
|
||||||
protected: /* data */
|
protected: /* data */
|
||||||
SettingsObjectPtr m_globalSettings;
|
SettingsObjectPtr m_globalSettings;
|
||||||
@ -54,4 +66,7 @@ class InstanceTask : public Task, public InstanceName {
|
|||||||
QString m_stagingPath;
|
QString m_stagingPath;
|
||||||
|
|
||||||
bool m_override_existing = false;
|
bool m_override_existing = false;
|
||||||
|
bool m_confirm_update = true;
|
||||||
|
|
||||||
|
QString m_original_instance_id;
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
95
launcher/MTPixmapCache.h
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QPixmapCache>
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
|
#define GET_TYPE() \
|
||||||
|
Qt::ConnectionType type; \
|
||||||
|
if (QThread::currentThread() != QCoreApplication::instance()->thread()) \
|
||||||
|
type = Qt::BlockingQueuedConnection; \
|
||||||
|
else \
|
||||||
|
type = Qt::DirectConnection;
|
||||||
|
|
||||||
|
#define DEFINE_FUNC_NO_PARAM(NAME, RET_TYPE) \
|
||||||
|
static RET_TYPE NAME() \
|
||||||
|
{ \
|
||||||
|
RET_TYPE ret; \
|
||||||
|
GET_TYPE() \
|
||||||
|
QMetaObject::invokeMethod(s_instance, "_" #NAME, type, Q_RETURN_ARG(RET_TYPE, ret)); \
|
||||||
|
return ret; \
|
||||||
|
}
|
||||||
|
#define DEFINE_FUNC_ONE_PARAM(NAME, RET_TYPE, PARAM_1_TYPE) \
|
||||||
|
static RET_TYPE NAME(PARAM_1_TYPE p1) \
|
||||||
|
{ \
|
||||||
|
RET_TYPE ret; \
|
||||||
|
GET_TYPE() \
|
||||||
|
QMetaObject::invokeMethod(s_instance, "_" #NAME, type, Q_RETURN_ARG(RET_TYPE, ret), Q_ARG(PARAM_1_TYPE, p1)); \
|
||||||
|
return ret; \
|
||||||
|
}
|
||||||
|
#define DEFINE_FUNC_TWO_PARAM(NAME, RET_TYPE, PARAM_1_TYPE, PARAM_2_TYPE) \
|
||||||
|
static RET_TYPE NAME(PARAM_1_TYPE p1, PARAM_2_TYPE p2) \
|
||||||
|
{ \
|
||||||
|
RET_TYPE ret; \
|
||||||
|
GET_TYPE() \
|
||||||
|
QMetaObject::invokeMethod(s_instance, "_" #NAME, type, Q_RETURN_ARG(RET_TYPE, ret), Q_ARG(PARAM_1_TYPE, p1), \
|
||||||
|
Q_ARG(PARAM_2_TYPE, p2)); \
|
||||||
|
return ret; \
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A wrapper around QPixmapCache with thread affinity with the main thread.
|
||||||
|
*/
|
||||||
|
class PixmapCache final : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
PixmapCache(QObject* parent) : QObject(parent) {}
|
||||||
|
~PixmapCache() override = default;
|
||||||
|
|
||||||
|
static PixmapCache& instance() { return *s_instance; }
|
||||||
|
static void setInstance(PixmapCache* i) { s_instance = i; }
|
||||||
|
|
||||||
|
public:
|
||||||
|
DEFINE_FUNC_NO_PARAM(cacheLimit, int)
|
||||||
|
DEFINE_FUNC_NO_PARAM(clear, bool)
|
||||||
|
DEFINE_FUNC_TWO_PARAM(find, bool, const QString&, QPixmap*)
|
||||||
|
DEFINE_FUNC_TWO_PARAM(find, bool, const QPixmapCache::Key&, QPixmap*)
|
||||||
|
DEFINE_FUNC_TWO_PARAM(insert, bool, const QString&, const QPixmap&)
|
||||||
|
DEFINE_FUNC_ONE_PARAM(insert, QPixmapCache::Key, const QPixmap&)
|
||||||
|
DEFINE_FUNC_ONE_PARAM(remove, bool, const QString&)
|
||||||
|
DEFINE_FUNC_ONE_PARAM(remove, bool, const QPixmapCache::Key&)
|
||||||
|
DEFINE_FUNC_TWO_PARAM(replace, bool, const QPixmapCache::Key&, const QPixmap&)
|
||||||
|
DEFINE_FUNC_ONE_PARAM(setCacheLimit, bool, int)
|
||||||
|
|
||||||
|
// NOTE: Every function returns something non-void to simplify the macros.
|
||||||
|
private slots:
|
||||||
|
int _cacheLimit() { return QPixmapCache::cacheLimit(); }
|
||||||
|
bool _clear()
|
||||||
|
{
|
||||||
|
QPixmapCache::clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool _find(const QString& key, QPixmap* pixmap) { return QPixmapCache::find(key, pixmap); }
|
||||||
|
bool _find(const QPixmapCache::Key& key, QPixmap* pixmap) { return QPixmapCache::find(key, pixmap); }
|
||||||
|
bool _insert(const QString& key, const QPixmap& pixmap) { return QPixmapCache::insert(key, pixmap); }
|
||||||
|
QPixmapCache::Key _insert(const QPixmap& pixmap) { return QPixmapCache::insert(pixmap); }
|
||||||
|
bool _remove(const QString& key)
|
||||||
|
{
|
||||||
|
QPixmapCache::remove(key);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool _remove(const QPixmapCache::Key& key)
|
||||||
|
{
|
||||||
|
QPixmapCache::remove(key);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool _replace(const QPixmapCache::Key& key, const QPixmap& pixmap) { return QPixmapCache::replace(key, pixmap); }
|
||||||
|
bool _setCacheLimit(int n)
|
||||||
|
{
|
||||||
|
QPixmapCache::setCacheLimit(n);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static PixmapCache* s_instance;
|
||||||
|
};
|
90
launcher/MangoHud.cpp
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PrismLauncher - Minecraft Launcher
|
||||||
|
* Copyright (C) 2022 Jan Drögehoff <sentrycraft123@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QString>
|
||||||
|
#include <QtGlobal>
|
||||||
|
|
||||||
|
#include "MangoHud.h"
|
||||||
|
#include "FileSystem.h"
|
||||||
|
#include "Json.h"
|
||||||
|
|
||||||
|
namespace MangoHud {
|
||||||
|
|
||||||
|
QString getLibraryString()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Check for vulkan layers in this order:
|
||||||
|
*
|
||||||
|
* $VK_LAYER_PATH
|
||||||
|
* $XDG_DATA_DIRS (/usr/local/share/:/usr/share/)
|
||||||
|
* $XDG_DATA_HOME (~/.local/share)
|
||||||
|
* /etc
|
||||||
|
* $XDG_CONFIG_DIRS (/etc/xdg)
|
||||||
|
* $XDG_CONFIG_HOME (~/.config)
|
||||||
|
*/
|
||||||
|
|
||||||
|
QStringList vkLayerList;
|
||||||
|
{
|
||||||
|
QString home = QDir::homePath();
|
||||||
|
|
||||||
|
QString vkLayerPath = qEnvironmentVariable("VK_LAYER_PATH");
|
||||||
|
if (!vkLayerPath.isEmpty()) {
|
||||||
|
vkLayerList << vkLayerPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList xdgDataDirs = qEnvironmentVariable("XDG_DATA_DIRS", "/usr/local/share/:/usr/share/").split(QLatin1String(":"));
|
||||||
|
for (QString dir : xdgDataDirs) {
|
||||||
|
vkLayerList << FS::PathCombine(dir, "vulkan", "implicit_layer.d");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString xdgDataHome = qEnvironmentVariable("XDG_DATA_HOME");
|
||||||
|
if (xdgDataHome.isEmpty()) {
|
||||||
|
xdgDataHome = FS::PathCombine(home, ".local", "share");
|
||||||
|
}
|
||||||
|
vkLayerList << FS::PathCombine(xdgDataHome, "vulkan", "implicit_layer.d");
|
||||||
|
|
||||||
|
vkLayerList << "/etc";
|
||||||
|
|
||||||
|
QStringList xdgConfigDirs = qEnvironmentVariable("XDG_CONFIG_DIRS", "/etc/xdg").split(QLatin1String(":"));
|
||||||
|
for (QString dir : xdgConfigDirs) {
|
||||||
|
vkLayerList << FS::PathCombine(dir, "vulkan", "implicit_layer.d");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString xdgConfigHome = qEnvironmentVariable("XDG_CONFIG_HOME");
|
||||||
|
if (xdgConfigHome.isEmpty()) {
|
||||||
|
xdgConfigHome = FS::PathCombine(home, ".config");
|
||||||
|
}
|
||||||
|
vkLayerList << FS::PathCombine(xdgConfigHome, "vulkan", "implicit_layer.d");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (QString vkLayer : vkLayerList) {
|
||||||
|
QString filePath = FS::PathCombine(vkLayer, "MangoHud.json");
|
||||||
|
if (!QFile::exists(filePath))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto conf = Json::requireDocument(filePath, vkLayer);
|
||||||
|
auto confObject = Json::requireObject(conf, vkLayer);
|
||||||
|
auto layer = Json::ensureObject(confObject, "layer");
|
||||||
|
return Json::ensureString(layer, "library_path");
|
||||||
|
}
|
||||||
|
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
} // namespace MangoHud
|
27
launcher/MangoHud.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PrismLauncher - Minecraft Launcher
|
||||||
|
* Copyright (C) 2022 Jan Drögehoff <sentrycraft123@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
namespace MangoHud {
|
||||||
|
|
||||||
|
QString getLibraryString();
|
||||||
|
}
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
@ -354,15 +354,18 @@ const MMCIcon *IconList::icon(const QString &key) const
|
|||||||
|
|
||||||
bool IconList::deleteIcon(const QString &key)
|
bool IconList::deleteIcon(const QString &key)
|
||||||
{
|
{
|
||||||
int iconIdx = getIconIndex(key);
|
if (!iconFileExists(key))
|
||||||
if (iconIdx == -1)
|
|
||||||
return false;
|
return false;
|
||||||
auto &iconEntry = icons[iconIdx];
|
|
||||||
if (iconEntry.has(IconType::FileBased))
|
return QFile::remove(icon(key)->getFilePath());
|
||||||
{
|
}
|
||||||
return QFile::remove(iconEntry.m_images[IconType::FileBased].filename);
|
|
||||||
}
|
bool IconList::trashIcon(const QString &key)
|
||||||
|
{
|
||||||
|
if (!iconFileExists(key))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
return FS::trash(icon(key)->getFilePath(), nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IconList::addThemeIcon(const QString& key)
|
bool IconList::addThemeIcon(const QString& key)
|
||||||
|
@ -52,6 +52,7 @@ public:
|
|||||||
bool addIcon(const QString &key, const QString &name, const QString &path, const IconType type);
|
bool addIcon(const QString &key, const QString &name, const QString &path, const IconType type);
|
||||||
void saveIcon(const QString &key, const QString &path, const char * format) const;
|
void saveIcon(const QString &key, const QString &path, const char * format) const;
|
||||||
bool deleteIcon(const QString &key);
|
bool deleteIcon(const QString &key);
|
||||||
|
bool trashIcon(const QString &key);
|
||||||
bool iconFileExists(const QString &key) const;
|
bool iconFileExists(const QString &key) const;
|
||||||
|
|
||||||
void installIcons(const QStringList &iconFiles);
|
void installIcons(const QStringList &iconFiles);
|
||||||
|
@ -81,6 +81,8 @@ 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);
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
@ -87,6 +88,10 @@
|
|||||||
#include "minecraft/gameoptions/GameOptions.h"
|
#include "minecraft/gameoptions/GameOptions.h"
|
||||||
#include "minecraft/update/FoldersTask.h"
|
#include "minecraft/update/FoldersTask.h"
|
||||||
|
|
||||||
|
#ifdef Q_OS_LINUX
|
||||||
|
#include "MangoHud.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#define IBUS "@im=ibus"
|
#define IBUS "@im=ibus"
|
||||||
|
|
||||||
// all of this because keeping things compatible with deprecated old settings
|
// all of this because keeping things compatible with deprecated old settings
|
||||||
@ -444,6 +449,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;
|
||||||
@ -478,9 +494,22 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
|
|||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
if (settings()->get("EnableMangoHud").toBool() && APPLICATION->capabilities() & Application::SupportsMangoHud)
|
if (settings()->get("EnableMangoHud").toBool() && APPLICATION->capabilities() & Application::SupportsMangoHud)
|
||||||
{
|
{
|
||||||
auto preload = env.value("LD_PRELOAD", "") + ":libMangoHud_dlsym.so:libMangoHud.so";
|
|
||||||
|
|
||||||
env.insert("LD_PRELOAD", preload);
|
auto preloadList = env.value("LD_PRELOAD").split(QLatin1String(":"));
|
||||||
|
auto libPaths = env.value("LD_LIBRARY_PATH").split(QLatin1String(":"));
|
||||||
|
|
||||||
|
auto mangoHudLibString = MangoHud::getLibraryString();
|
||||||
|
if (!mangoHudLibString.isEmpty())
|
||||||
|
{
|
||||||
|
QFileInfo mangoHudLib(mangoHudLibString);
|
||||||
|
|
||||||
|
// dlsym variant is only needed for OpenGL and not included in the vulkan layer
|
||||||
|
preloadList << "libMangoHud_dlsym.so" << mangoHudLib.fileName();
|
||||||
|
libPaths << mangoHudLib.absolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
env.insert("LD_PRELOAD", preloadList.join(QLatin1String(":")));
|
||||||
|
env.insert("LD_LIBRARY_PATH", libPaths.join(QLatin1String(":")));
|
||||||
env.insert("MANGOHUD", "1");
|
env.insert("MANGOHUD", "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -635,26 +664,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;
|
||||||
}
|
}
|
||||||
@ -790,6 +806,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1124,8 +1142,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;
|
||||||
}
|
}
|
||||||
@ -1135,8 +1151,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;
|
||||||
}
|
}
|
||||||
@ -1146,8 +1160,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;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
@ -131,6 +132,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;
|
||||||
|
@ -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)
|
||||||
|
@ -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;
|
||||||
|
@ -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)
|
||||||
|
@ -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 */
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -20,6 +20,7 @@ ResourceFolderModel::ResourceFolderModel(QDir dir, QObject* parent) : QAbstractL
|
|||||||
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
|
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
|
||||||
|
|
||||||
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &ResourceFolderModel::directoryChanged);
|
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &ResourceFolderModel::directoryChanged);
|
||||||
|
connect(&m_helper_thread_task, &ConcurrentTask::finished, this, [this]{ m_helper_thread_task.clear(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourceFolderModel::~ResourceFolderModel()
|
ResourceFolderModel::~ResourceFolderModel()
|
||||||
@ -275,7 +276,11 @@ void ResourceFolderModel::resolveResource(Resource* res)
|
|||||||
connect(
|
connect(
|
||||||
task, &Task::finished, this, [=] { m_active_parse_tasks.remove(ticket); }, Qt::ConnectionType::QueuedConnection);
|
task, &Task::finished, this, [=] { m_active_parse_tasks.remove(ticket); }, Qt::ConnectionType::QueuedConnection);
|
||||||
|
|
||||||
QThreadPool::globalInstance()->start(task);
|
m_helper_thread_task.addTask(task);
|
||||||
|
|
||||||
|
if (!m_helper_thread_task.isRunning()) {
|
||||||
|
QThreadPool::globalInstance()->start(&m_helper_thread_task);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ResourceFolderModel::onUpdateSucceeded()
|
void ResourceFolderModel::onUpdateSucceeded()
|
||||||
@ -426,7 +431,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)
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#include "Resource.h"
|
#include "Resource.h"
|
||||||
|
|
||||||
#include "tasks/Task.h"
|
#include "tasks/Task.h"
|
||||||
|
#include "tasks/ConcurrentTask.h"
|
||||||
|
|
||||||
class QSortFilterProxyModel;
|
class QSortFilterProxyModel;
|
||||||
|
|
||||||
@ -90,8 +91,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 +177,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.
|
||||||
@ -197,6 +198,7 @@ class ResourceFolderModel : public QAbstractListModel {
|
|||||||
// Represents the relationship between a resource's internal ID and it's row position on the model.
|
// Represents the relationship between a resource's internal ID and it's row position on the model.
|
||||||
QMap<QString, int> m_resources_index;
|
QMap<QString, int> m_resources_index;
|
||||||
|
|
||||||
|
ConcurrentTask m_helper_thread_task;
|
||||||
QMap<int, Task::Ptr> m_active_parse_tasks;
|
QMap<int, Task::Ptr> m_active_parse_tasks;
|
||||||
std::atomic<int> m_next_resolution_ticket = 0;
|
std::atomic<int> m_next_resolution_ticket = 0;
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
#include "ResourcePack.h"
|
#include "ResourcePack.h"
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
|
|
||||||
|
#include "MTPixmapCache.h"
|
||||||
#include "Version.h"
|
#include "Version.h"
|
||||||
|
|
||||||
#include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
|
#include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
|
||||||
@ -15,7 +17,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)
|
||||||
@ -43,16 +45,22 @@ void ResourcePack::setImage(QImage new_image)
|
|||||||
Q_ASSERT(!new_image.isNull());
|
Q_ASSERT(!new_image.isNull());
|
||||||
|
|
||||||
if (m_pack_image_cache_key.key.isValid())
|
if (m_pack_image_cache_key.key.isValid())
|
||||||
QPixmapCache::remove(m_pack_image_cache_key.key);
|
PixmapCache::instance().remove(m_pack_image_cache_key.key);
|
||||||
|
|
||||||
m_pack_image_cache_key.key = QPixmapCache::insert(QPixmap::fromImage(new_image));
|
m_pack_image_cache_key.key = PixmapCache::instance().insert(QPixmap::fromImage(new_image));
|
||||||
m_pack_image_cache_key.was_ever_used = true;
|
m_pack_image_cache_key.was_ever_used = true;
|
||||||
|
|
||||||
|
// This can happen if the pixmap is too big to fit in the cache :c
|
||||||
|
if (!m_pack_image_cache_key.key.isValid()) {
|
||||||
|
qWarning() << "Could not insert a image cache entry! Ignoring it.";
|
||||||
|
m_pack_image_cache_key.was_ever_used = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QPixmap ResourcePack::image(QSize size)
|
QPixmap ResourcePack::image(QSize size)
|
||||||
{
|
{
|
||||||
QPixmap cached_image;
|
QPixmap cached_image;
|
||||||
if (QPixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
|
if (PixmapCache::instance().find(m_pack_image_cache_key.key, &cached_image)) {
|
||||||
if (size.isNull())
|
if (size.isNull())
|
||||||
return cached_image;
|
return cached_image;
|
||||||
return cached_image.scaled(size);
|
return cached_image.scaled(size);
|
||||||
@ -114,3 +122,8 @@ bool ResourcePack::applyFilter(QRegularExpression filter) const
|
|||||||
|
|
||||||
return Resource::applyFilter(filter);
|
return Resource::applyFilter(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ResourcePack::valid() const
|
||||||
|
{
|
||||||
|
return m_pack_format != 0;
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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 {};
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -12,7 +12,7 @@ LibrariesTask::LibrariesTask(MinecraftInstance * inst)
|
|||||||
|
|
||||||
void LibrariesTask::executeTask()
|
void LibrariesTask::executeTask()
|
||||||
{
|
{
|
||||||
setStatus(tr("Getting the library files from Mojang..."));
|
setStatus(tr("Downloading required library files..."));
|
||||||
qDebug() << m_inst->name() << ": downloading libraries";
|
qDebug() << m_inst->name() << ": downloading libraries";
|
||||||
MinecraftInstance *inst = (MinecraftInstance *)m_inst;
|
MinecraftInstance *inst = (MinecraftInstance *)m_inst;
|
||||||
|
|
||||||
|
@ -42,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];
|
||||||
@ -68,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);
|
||||||
|
|
||||||
|
@ -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"
|
||||||
@ -46,7 +81,12 @@ bool FlameCreationTask::updateInstance()
|
|||||||
auto instance_list = APPLICATION->instances();
|
auto instance_list = APPLICATION->instances();
|
||||||
|
|
||||||
// FIXME: How to handle situations when there's more than one install already for a given modpack?
|
// FIXME: How to handle situations when there's more than one install already for a given modpack?
|
||||||
auto inst = instance_list->getInstanceByManagedName(originalName());
|
InstancePtr inst;
|
||||||
|
if (auto original_id = originalInstanceID(); !original_id.isEmpty()) {
|
||||||
|
inst = instance_list->getInstanceById(original_id);
|
||||||
|
Q_ASSERT(inst);
|
||||||
|
} else {
|
||||||
|
inst = instance_list->getInstanceByManagedName(originalName());
|
||||||
|
|
||||||
if (!inst) {
|
if (!inst) {
|
||||||
inst = instance_list->getInstanceById(originalName());
|
inst = instance_list->getInstanceById(originalName());
|
||||||
@ -54,6 +94,7 @@ bool FlameCreationTask::updateInstance()
|
|||||||
if (!inst)
|
if (!inst)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QString index_path(FS::PathCombine(m_stagingPath, "manifest.json"));
|
QString index_path(FS::PathCombine(m_stagingPath, "manifest.json"));
|
||||||
|
|
||||||
@ -67,25 +108,15 @@ bool FlameCreationTask::updateInstance()
|
|||||||
auto version_id = inst->getManagedPackVersionName();
|
auto version_id = inst->getManagedPackVersionName();
|
||||||
auto version_str = !version_id.isEmpty() ? tr(" (version %1)").arg(version_id) : "";
|
auto version_str = !version_id.isEmpty() ? tr(" (version %1)").arg(version_id) : "";
|
||||||
|
|
||||||
auto info = CustomMessageBox::selectable(
|
if (shouldConfirmUpdate()) {
|
||||||
m_parent, tr("Similar modpack was found!"),
|
auto should_update = askIfShouldUpdate(m_parent, version_str);
|
||||||
tr("One or more of your instances are from this same modpack%1. Do you want to create a "
|
if (should_update == ShouldUpdate::SkipUpdating)
|
||||||
"separate instance, or update the existing one?\n\nNOTE: Make sure you made a backup of your important instance data before "
|
|
||||||
"updating, as worlds can be corrupted and some configuration may be lost (due to pack overrides).")
|
|
||||||
.arg(version_str), QMessageBox::Information, QMessageBox::Ok | QMessageBox::Reset | QMessageBox::Abort);
|
|
||||||
info->setButtonText(QMessageBox::Ok, tr("Update existing instance"));
|
|
||||||
info->setButtonText(QMessageBox::Abort, tr("Create new instance"));
|
|
||||||
info->setButtonText(QMessageBox::Reset, tr("Cancel"));
|
|
||||||
|
|
||||||
info->exec();
|
|
||||||
|
|
||||||
if (info->clickedButton() == info->button(QMessageBox::Abort))
|
|
||||||
return false;
|
return false;
|
||||||
|
if (should_update == ShouldUpdate::Cancel) {
|
||||||
if (info->clickedButton() == info->button(QMessageBox::Reset)) {
|
|
||||||
m_abort = true;
|
m_abort = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QDir old_inst_dir(inst->instanceRoot());
|
QDir old_inst_dir(inst->instanceRoot());
|
||||||
|
|
||||||
@ -197,9 +228,9 @@ 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) {
|
||||||
@ -208,7 +239,7 @@ bool FlameCreationTask::updateInstance()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setOverride(true);
|
setOverride(true, inst->id());
|
||||||
qDebug() << "Will override instance!";
|
qDebug() << "Will override instance!";
|
||||||
|
|
||||||
m_instance = inst;
|
m_instance = inst;
|
||||||
@ -330,7 +361,9 @@ bool FlameCreationTask::createInstance()
|
|||||||
FS::deletePath(jarmodsPath);
|
FS::deletePath(jarmodsPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
instance.setManagedPack("flame", {}, m_pack.name, {}, m_pack.version);
|
// Don't add managed info to packs without an ID (most likely imported from ZIP)
|
||||||
|
if (!m_managed_id.isEmpty())
|
||||||
|
instance.setManagedPack("flame", m_managed_id, m_pack.name, m_managed_version_id, m_pack.version);
|
||||||
instance.setName(name());
|
instance.setName(name());
|
||||||
|
|
||||||
m_mod_id_resolver = new Flame::FileResolvingTask(APPLICATION->network(), m_pack);
|
m_mod_id_resolver = new Flame::FileResolvingTask(APPLICATION->network(), m_pack);
|
||||||
@ -338,6 +371,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);
|
||||||
@ -353,14 +387,6 @@ bool FlameCreationTask::createInstance()
|
|||||||
setAbortable(false);
|
setAbortable(false);
|
||||||
auto inst = m_instance.value();
|
auto inst = m_instance.value();
|
||||||
|
|
||||||
// Only change the name if it didn't use a custom name, so that the previous custom name
|
|
||||||
// is preserved, but if we're using the original one, we update the version string.
|
|
||||||
// NOTE: This needs to come before the copyManagedPack call!
|
|
||||||
if (inst->name().contains(inst->getManagedPackVersionName())) {
|
|
||||||
if (askForChangingInstanceName(m_parent, inst->name(), instance.name()) == InstanceNameChange::ShouldChange)
|
|
||||||
inst->setName(instance.name());
|
|
||||||
}
|
|
||||||
|
|
||||||
inst->copyManagedPack(instance);
|
inst->copyManagedPack(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,13 +402,13 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
|
|||||||
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()) {
|
||||||
|
|
||||||
BlockedMod blocked_mod;
|
BlockedMod blocked_mod;
|
||||||
blocked_mod.name = result.fileName;
|
blocked_mod.name = result.fileName;
|
||||||
blocked_mod.websiteUrl = result.websiteUrl;
|
blocked_mod.websiteUrl = result.websiteUrl;
|
||||||
blocked_mod.hash = result.hash;
|
blocked_mod.hash = result.hash;
|
||||||
blocked_mod.matched = false;
|
blocked_mod.matched = false;
|
||||||
blocked_mod.localPath = "";
|
blocked_mod.localPath = "";
|
||||||
|
blocked_mod.targetFolder = result.targetFolder;
|
||||||
|
|
||||||
blocked_mods.append(blocked_mod);
|
blocked_mods.append(blocked_mod);
|
||||||
|
|
||||||
@ -392,13 +418,14 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
|
|||||||
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."),
|
||||||
blocked_mods);
|
blocked_mods);
|
||||||
message_dialog->setModal(true);
|
|
||||||
|
|
||||||
if (message_dialog->exec()) {
|
message_dialog.setModal(true);
|
||||||
|
|
||||||
|
if (message_dialog.exec()) {
|
||||||
qDebug() << "Post dialog blocked mods list: " << blocked_mods;
|
qDebug() << "Post dialog blocked mods list: " << blocked_mods;
|
||||||
copyBlockedMods(blocked_mods);
|
copyBlockedMods(blocked_mods);
|
||||||
setupDownloadJob(loop);
|
setupDownloadJob(loop);
|
||||||
@ -427,7 +454,7 @@ void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto dest_path = FS::PathCombine(m_stagingPath, "minecraft", "mods", mod.name);
|
auto dest_path = FS::PathCombine(m_stagingPath, "minecraft", mod.targetFolder, mod.name);
|
||||||
|
|
||||||
setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total)));
|
setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total)));
|
||||||
|
|
||||||
@ -482,9 +509,7 @@ 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);
|
||||||
|
@ -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"
|
||||||
@ -16,11 +51,21 @@ class FlameCreationTask final : public InstanceCreationTask {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FlameCreationTask(const QString& staging_path, SettingsObjectPtr global_settings, QWidget* parent)
|
FlameCreationTask(const QString& staging_path,
|
||||||
: InstanceCreationTask(), m_parent(parent)
|
SettingsObjectPtr global_settings,
|
||||||
|
QWidget* parent,
|
||||||
|
QString id,
|
||||||
|
QString version_id,
|
||||||
|
QString original_instance_id = {})
|
||||||
|
: InstanceCreationTask()
|
||||||
|
, m_parent(parent)
|
||||||
|
, m_managed_id(std::move(id))
|
||||||
|
, m_managed_version_id(std::move(version_id))
|
||||||
{
|
{
|
||||||
setStagingPath(staging_path);
|
setStagingPath(staging_path);
|
||||||
setParentSettings(global_settings);
|
setParentSettings(global_settings);
|
||||||
|
|
||||||
|
m_original_instance_id = std::move(original_instance_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool abort() override;
|
bool abort() override;
|
||||||
@ -43,5 +88,7 @@ class FlameCreationTask final : public InstanceCreationTask {
|
|||||||
NetJob* m_process_update_file_info_job = nullptr;
|
NetJob* m_process_update_file_info_job = nullptr;
|
||||||
NetJob::Ptr m_files_job = nullptr;
|
NetJob::Ptr m_files_job = nullptr;
|
||||||
|
|
||||||
|
QString m_managed_id, m_managed_version_id;
|
||||||
|
|
||||||
std::optional<InstancePtr> m_instance;
|
std::optional<InstancePtr> m_instance;
|
||||||
};
|
};
|
||||||
|
@ -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>
|
||||||
@ -189,13 +189,13 @@ 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()) {
|
||||||
|
|
||||||
BlockedMod blocked_mod;
|
BlockedMod blocked_mod;
|
||||||
blocked_mod.name = local_file.name;
|
blocked_mod.name = local_file.name;
|
||||||
blocked_mod.websiteUrl = results_file.websiteUrl;
|
blocked_mod.websiteUrl = results_file.websiteUrl;
|
||||||
blocked_mod.hash = results_file.hash;
|
blocked_mod.hash = results_file.hash;
|
||||||
blocked_mod.matched = false;
|
blocked_mod.matched = false;
|
||||||
blocked_mod.localPath = "";
|
blocked_mod.localPath = "";
|
||||||
|
blocked_mod.targetFolder = results_file.targetFolder;
|
||||||
|
|
||||||
m_blocked_mods.append(blocked_mod);
|
m_blocked_mods.append(blocked_mod);
|
||||||
|
|
||||||
@ -210,16 +210,17 @@ 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."),
|
||||||
m_blocked_mods);
|
m_blocked_mods);
|
||||||
|
|
||||||
if (message_dialog->exec() == QDialog::Accepted) {
|
message_dialog.setModal(true);
|
||||||
|
|
||||||
|
if (message_dialog.exec() == QDialog::Accepted) {
|
||||||
qDebug() << "Post dialog blocked mods list: " << m_blocked_mods;
|
qDebug() << "Post dialog blocked mods list: " << m_blocked_mods;
|
||||||
createInstance();
|
createInstance();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,7 +367,7 @@ void PackInstallTask::copyBlockedMods()
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto dest_path = FS::PathCombine(m_stagingPath, ".minecraft", "mods", mod.name);
|
auto dest_path = FS::PathCombine(m_stagingPath, ".minecraft", mod.targetFolder, mod.name);
|
||||||
|
|
||||||
setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total)));
|
setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total)));
|
||||||
|
|
||||||
|
@ -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>
|
||||||
*
|
*
|
||||||
|
@ -33,7 +33,12 @@ bool ModrinthCreationTask::updateInstance()
|
|||||||
auto instance_list = APPLICATION->instances();
|
auto instance_list = APPLICATION->instances();
|
||||||
|
|
||||||
// FIXME: How to handle situations when there's more than one install already for a given modpack?
|
// FIXME: How to handle situations when there's more than one install already for a given modpack?
|
||||||
auto inst = instance_list->getInstanceByManagedName(originalName());
|
InstancePtr inst;
|
||||||
|
if (auto original_id = originalInstanceID(); !original_id.isEmpty()) {
|
||||||
|
inst = instance_list->getInstanceById(original_id);
|
||||||
|
Q_ASSERT(inst);
|
||||||
|
} else {
|
||||||
|
inst = instance_list->getInstanceByManagedName(originalName());
|
||||||
|
|
||||||
if (!inst) {
|
if (!inst) {
|
||||||
inst = instance_list->getInstanceById(originalName());
|
inst = instance_list->getInstanceById(originalName());
|
||||||
@ -41,6 +46,7 @@ bool ModrinthCreationTask::updateInstance()
|
|||||||
if (!inst)
|
if (!inst)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json");
|
QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json");
|
||||||
if (!parseManifest(index_path, m_files, true, false))
|
if (!parseManifest(index_path, m_files, true, false))
|
||||||
@ -49,26 +55,15 @@ bool ModrinthCreationTask::updateInstance()
|
|||||||
auto version_name = inst->getManagedPackVersionName();
|
auto version_name = inst->getManagedPackVersionName();
|
||||||
auto version_str = !version_name.isEmpty() ? tr(" (version %1)").arg(version_name) : "";
|
auto version_str = !version_name.isEmpty() ? tr(" (version %1)").arg(version_name) : "";
|
||||||
|
|
||||||
auto info = CustomMessageBox::selectable(
|
if (shouldConfirmUpdate()) {
|
||||||
m_parent, tr("Similar modpack was found!"),
|
auto should_update = askIfShouldUpdate(m_parent, version_str);
|
||||||
tr("One or more of your instances are from this same modpack%1. Do you want to create a "
|
if (should_update == ShouldUpdate::SkipUpdating)
|
||||||
"separate instance, or update the existing one?\n\nNOTE: Make sure you made a backup of your important instance data before "
|
|
||||||
"updating, as worlds can be corrupted and some configuration may be lost (due to pack overrides).")
|
|
||||||
.arg(version_str),
|
|
||||||
QMessageBox::Information, QMessageBox::Ok | QMessageBox::Reset | QMessageBox::Abort);
|
|
||||||
info->setButtonText(QMessageBox::Ok, tr("Create new instance"));
|
|
||||||
info->setButtonText(QMessageBox::Abort, tr("Update existing instance"));
|
|
||||||
info->setButtonText(QMessageBox::Reset, tr("Cancel"));
|
|
||||||
|
|
||||||
info->exec();
|
|
||||||
|
|
||||||
if (info->clickedButton() == info->button(QMessageBox::Ok))
|
|
||||||
return false;
|
return false;
|
||||||
|
if (should_update == ShouldUpdate::Cancel) {
|
||||||
if (info->clickedButton() == info->button(QMessageBox::Reset)) {
|
|
||||||
m_abort = true;
|
m_abort = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Remove repeated files, we don't need to download them!
|
// Remove repeated files, we don't need to download them!
|
||||||
QDir old_inst_dir(inst->instanceRoot());
|
QDir old_inst_dir(inst->instanceRoot());
|
||||||
@ -149,7 +144,7 @@ bool ModrinthCreationTask::updateInstance()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
setOverride(true);
|
setOverride(true, inst->id());
|
||||||
qDebug() << "Will override instance!";
|
qDebug() << "Will override instance!";
|
||||||
|
|
||||||
m_instance = inst;
|
m_instance = inst;
|
||||||
@ -207,14 +202,14 @@ bool ModrinthCreationTask::createInstance()
|
|||||||
|
|
||||||
auto components = instance.getPackProfile();
|
auto components = instance.getPackProfile();
|
||||||
components->buildingFromScratch();
|
components->buildingFromScratch();
|
||||||
components->setComponentVersion("net.minecraft", minecraftVersion, true);
|
components->setComponentVersion("net.minecraft", m_minecraft_version, true);
|
||||||
|
|
||||||
if (!fabricVersion.isEmpty())
|
if (!m_fabric_version.isEmpty())
|
||||||
components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion);
|
components->setComponentVersion("net.fabricmc.fabric-loader", m_fabric_version);
|
||||||
if (!quiltVersion.isEmpty())
|
if (!m_quilt_version.isEmpty())
|
||||||
components->setComponentVersion("org.quiltmc.quilt-loader", quiltVersion);
|
components->setComponentVersion("org.quiltmc.quilt-loader", m_quilt_version);
|
||||||
if (!forgeVersion.isEmpty())
|
if (!m_forge_version.isEmpty())
|
||||||
components->setComponentVersion("net.minecraftforge", forgeVersion);
|
components->setComponentVersion("net.minecraftforge", m_forge_version);
|
||||||
|
|
||||||
if (m_instIcon != "default") {
|
if (m_instIcon != "default") {
|
||||||
instance.setIconKey(m_instIcon);
|
instance.setIconKey(m_instIcon);
|
||||||
@ -222,7 +217,9 @@ bool ModrinthCreationTask::createInstance()
|
|||||||
instance.setIconKey("modrinth");
|
instance.setIconKey("modrinth");
|
||||||
}
|
}
|
||||||
|
|
||||||
instance.setManagedPack("modrinth", getManagedPackID(), m_managed_name, m_managed_version_id, version());
|
// Don't add managed info to packs without an ID (most likely imported from ZIP)
|
||||||
|
if (!m_managed_id.isEmpty())
|
||||||
|
instance.setManagedPack("modrinth", m_managed_id, m_managed_name, m_managed_version_id, version());
|
||||||
instance.setName(name());
|
instance.setName(name());
|
||||||
instance.saveNow();
|
instance.saveNow();
|
||||||
|
|
||||||
@ -282,7 +279,7 @@ bool ModrinthCreationTask::createInstance()
|
|||||||
return ended_well;
|
return ended_well;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector<Modrinth::File>& files, bool set_managed_info, bool show_optional_dialog)
|
bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector<Modrinth::File>& files, bool set_internal_data, bool show_optional_dialog)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
auto doc = Json::requireDocument(index_path);
|
auto doc = Json::requireDocument(index_path);
|
||||||
@ -294,7 +291,8 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector<
|
|||||||
throw JSONValidationError("Unknown game: " + game);
|
throw JSONValidationError("Unknown game: " + game);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (set_managed_info) {
|
if (set_internal_data) {
|
||||||
|
if (m_managed_version_id.isEmpty())
|
||||||
m_managed_version_id = Json::ensureString(obj, "versionId", {}, "Managed ID");
|
m_managed_version_id = Json::ensureString(obj, "versionId", {}, "Managed ID");
|
||||||
m_managed_name = Json::ensureString(obj, "name", {}, "Managed Name");
|
m_managed_name = Json::ensureString(obj, "name", {}, "Managed Name");
|
||||||
}
|
}
|
||||||
@ -369,21 +367,23 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector<
|
|||||||
files.push_back(file);
|
files.push_back(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (set_internal_data) {
|
||||||
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
|
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
|
||||||
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
|
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
|
||||||
QString name = it.key();
|
QString name = it.key();
|
||||||
if (name == "minecraft") {
|
if (name == "minecraft") {
|
||||||
minecraftVersion = Json::requireString(*it, "Minecraft version");
|
m_minecraft_version = Json::requireString(*it, "Minecraft version");
|
||||||
} else if (name == "fabric-loader") {
|
} else if (name == "fabric-loader") {
|
||||||
fabricVersion = Json::requireString(*it, "Fabric Loader version");
|
m_fabric_version = Json::requireString(*it, "Fabric Loader version");
|
||||||
} else if (name == "quilt-loader") {
|
} else if (name == "quilt-loader") {
|
||||||
quiltVersion = Json::requireString(*it, "Quilt Loader version");
|
m_quilt_version = Json::requireString(*it, "Quilt Loader version");
|
||||||
} else if (name == "forge") {
|
} else if (name == "forge") {
|
||||||
forgeVersion = Json::requireString(*it, "Forge version");
|
m_forge_version = Json::requireString(*it, "Forge version");
|
||||||
} else {
|
} else {
|
||||||
throw JSONValidationError("Unknown dependency type: " + name);
|
throw JSONValidationError("Unknown dependency type: " + name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw JSONValidationError(QStringLiteral("Unknown format version: %s").arg(formatVersion));
|
throw JSONValidationError(QStringLiteral("Unknown format version: %s").arg(formatVersion));
|
||||||
}
|
}
|
||||||
@ -395,13 +395,3 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector<
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ModrinthCreationTask::getManagedPackID() const
|
|
||||||
{
|
|
||||||
if (!m_source_url.isEmpty()) {
|
|
||||||
QRegularExpression regex(R"(data\/(.*)\/versions)");
|
|
||||||
return regex.match(m_source_url).captured(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
@ -14,11 +14,21 @@ class ModrinthCreationTask final : public InstanceCreationTask {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ModrinthCreationTask(QString staging_path, SettingsObjectPtr global_settings, QWidget* parent, QString source_url = {})
|
ModrinthCreationTask(QString staging_path,
|
||||||
: InstanceCreationTask(), m_parent(parent), m_source_url(std::move(source_url))
|
SettingsObjectPtr global_settings,
|
||||||
|
QWidget* parent,
|
||||||
|
QString id,
|
||||||
|
QString version_id = {},
|
||||||
|
QString original_instance_id = {})
|
||||||
|
: InstanceCreationTask()
|
||||||
|
, m_parent(parent)
|
||||||
|
, m_managed_id(std::move(id))
|
||||||
|
, m_managed_version_id(std::move(version_id))
|
||||||
{
|
{
|
||||||
setStagingPath(staging_path);
|
setStagingPath(staging_path);
|
||||||
setParentSettings(global_settings);
|
setParentSettings(global_settings);
|
||||||
|
|
||||||
|
m_original_instance_id = std::move(original_instance_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool abort() override;
|
bool abort() override;
|
||||||
@ -27,15 +37,13 @@ class ModrinthCreationTask final : public InstanceCreationTask {
|
|||||||
bool createInstance() override;
|
bool createInstance() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool parseManifest(const QString&, std::vector<Modrinth::File>&, bool set_managed_info = true, bool show_optional_dialog = true);
|
bool parseManifest(const QString&, std::vector<Modrinth::File>&, bool set_internal_data = true, bool show_optional_dialog = true);
|
||||||
QString getManagedPackID() const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QWidget* m_parent = nullptr;
|
QWidget* m_parent = nullptr;
|
||||||
|
|
||||||
QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion;
|
QString m_minecraft_version, m_fabric_version, m_quilt_version, m_forge_version;
|
||||||
QString m_managed_id, m_managed_version_id, m_managed_name;
|
QString m_managed_id, m_managed_version_id, m_managed_name;
|
||||||
QString m_source_url;
|
|
||||||
|
|
||||||
std::vector<Modrinth::File> m_files;
|
std::vector<Modrinth::File> m_files;
|
||||||
NetJob::Ptr m_files_job;
|
NetJob::Ptr m_files_job;
|
||||||
|
@ -128,6 +128,7 @@ auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion
|
|||||||
|
|
||||||
file.name = Json::requireString(obj, "name");
|
file.name = Json::requireString(obj, "name");
|
||||||
file.version = Json::requireString(obj, "version_number");
|
file.version = Json::requireString(obj, "version_number");
|
||||||
|
file.changelog = Json::ensureString(obj, "changelog");
|
||||||
|
|
||||||
file.id = Json::requireString(obj, "id");
|
file.id = Json::requireString(obj, "id");
|
||||||
file.project_id = Json::requireString(obj, "project_id");
|
file.project_id = Json::requireString(obj, "project_id");
|
||||||
|
@ -80,6 +80,7 @@ struct ModpackExtra {
|
|||||||
struct ModpackVersion {
|
struct ModpackVersion {
|
||||||
QString name;
|
QString name;
|
||||||
QString version;
|
QString version;
|
||||||
|
QString changelog;
|
||||||
|
|
||||||
QString id;
|
QString id;
|
||||||
QString project_id;
|
QString project_id;
|
||||||
|
@ -47,7 +47,7 @@
|
|||||||
auto MetaEntry::getFullPath() -> QString
|
auto MetaEntry::getFullPath() -> QString
|
||||||
{
|
{
|
||||||
// FIXME: make local?
|
// FIXME: make local?
|
||||||
return FS::PathCombine(basePath, relativePath);
|
return FS::PathCombine(m_basePath, m_relativePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpMetaCache::HttpMetaCache(QString path) : QObject(), m_index_file(path)
|
HttpMetaCache::HttpMetaCache(QString path) : QObject(), m_index_file(path)
|
||||||
@ -99,7 +99,7 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex
|
|||||||
return staleEntry(base, resource_path);
|
return staleEntry(base, resource_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!expected_etag.isEmpty() && expected_etag != entry->etag) {
|
if (!expected_etag.isEmpty() && expected_etag != entry->m_etag) {
|
||||||
// if the etag doesn't match expected, we disown the entry
|
// if the etag doesn't match expected, we disown the entry
|
||||||
selected_base.entry_list.remove(resource_path);
|
selected_base.entry_list.remove(resource_path);
|
||||||
return staleEntry(base, resource_path);
|
return staleEntry(base, resource_path);
|
||||||
@ -107,17 +107,17 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex
|
|||||||
|
|
||||||
// if the file changed, check md5sum
|
// if the file changed, check md5sum
|
||||||
qint64 file_last_changed = finfo.lastModified().toUTC().toMSecsSinceEpoch();
|
qint64 file_last_changed = finfo.lastModified().toUTC().toMSecsSinceEpoch();
|
||||||
if (file_last_changed != entry->local_changed_timestamp) {
|
if (file_last_changed != entry->m_local_changed_timestamp) {
|
||||||
QFile input(real_path);
|
QFile input(real_path);
|
||||||
input.open(QIODevice::ReadOnly);
|
input.open(QIODevice::ReadOnly);
|
||||||
QString md5sum = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Md5).toHex().constData();
|
QString md5sum = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Md5).toHex().constData();
|
||||||
if (entry->md5sum != md5sum) {
|
if (entry->m_md5sum != md5sum) {
|
||||||
selected_base.entry_list.remove(resource_path);
|
selected_base.entry_list.remove(resource_path);
|
||||||
return staleEntry(base, resource_path);
|
return staleEntry(base, resource_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// md5sums matched... keep entry and save the new state to file
|
// md5sums matched... keep entry and save the new state to file
|
||||||
entry->local_changed_timestamp = file_last_changed;
|
entry->m_local_changed_timestamp = file_last_changed;
|
||||||
SaveEventually();
|
SaveEventually();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,23 +130,23 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex
|
|||||||
}
|
}
|
||||||
|
|
||||||
// entry passed all the checks we cared about.
|
// entry passed all the checks we cared about.
|
||||||
entry->basePath = getBasePath(base);
|
entry->m_basePath = getBasePath(base);
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto HttpMetaCache::updateEntry(MetaEntryPtr stale_entry) -> bool
|
auto HttpMetaCache::updateEntry(MetaEntryPtr stale_entry) -> bool
|
||||||
{
|
{
|
||||||
if (!m_entries.contains(stale_entry->baseId)) {
|
if (!m_entries.contains(stale_entry->m_baseId)) {
|
||||||
qCritical() << "Cannot add entry with unknown base: " << stale_entry->baseId.toLocal8Bit();
|
qCritical() << "Cannot add entry with unknown base: " << stale_entry->m_baseId.toLocal8Bit();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stale_entry->stale) {
|
if (stale_entry->m_stale) {
|
||||||
qCritical() << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit();
|
qCritical() << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_entries[stale_entry->baseId].entry_list[stale_entry->relativePath] = stale_entry;
|
m_entries[stale_entry->m_baseId].entry_list[stale_entry->m_relativePath] = stale_entry;
|
||||||
SaveEventually();
|
SaveEventually();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -157,7 +157,7 @@ auto HttpMetaCache::evictEntry(MetaEntryPtr entry) -> bool
|
|||||||
if (!entry)
|
if (!entry)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
entry->stale = true;
|
entry->m_stale = true;
|
||||||
SaveEventually();
|
SaveEventually();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -169,7 +169,7 @@ void HttpMetaCache::evictAll()
|
|||||||
qDebug() << "Evicting base" << base;
|
qDebug() << "Evicting base" << base;
|
||||||
for (MetaEntryPtr entry : map.entry_list) {
|
for (MetaEntryPtr entry : map.entry_list) {
|
||||||
if (!evictEntry(entry))
|
if (!evictEntry(entry))
|
||||||
qWarning() << "Unexpected missing cache entry" << entry->basePath;
|
qWarning() << "Unexpected missing cache entry" << entry->m_basePath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -177,10 +177,10 @@ void HttpMetaCache::evictAll()
|
|||||||
auto HttpMetaCache::staleEntry(QString base, QString resource_path) -> MetaEntryPtr
|
auto HttpMetaCache::staleEntry(QString base, QString resource_path) -> MetaEntryPtr
|
||||||
{
|
{
|
||||||
auto foo = new MetaEntry();
|
auto foo = new MetaEntry();
|
||||||
foo->baseId = base;
|
foo->m_baseId = base;
|
||||||
foo->basePath = getBasePath(base);
|
foo->m_basePath = getBasePath(base);
|
||||||
foo->relativePath = resource_path;
|
foo->m_relativePath = resource_path;
|
||||||
foo->stale = true;
|
foo->m_stale = true;
|
||||||
|
|
||||||
return MetaEntryPtr(foo);
|
return MetaEntryPtr(foo);
|
||||||
}
|
}
|
||||||
@ -235,23 +235,23 @@ void HttpMetaCache::Load()
|
|||||||
auto& entrymap = m_entries[base];
|
auto& entrymap = m_entries[base];
|
||||||
|
|
||||||
auto foo = new MetaEntry();
|
auto foo = new MetaEntry();
|
||||||
foo->baseId = base;
|
foo->m_baseId = base;
|
||||||
foo->relativePath = Json::ensureString(element_obj, "path");
|
foo->m_relativePath = Json::ensureString(element_obj, "path");
|
||||||
foo->md5sum = Json::ensureString(element_obj, "md5sum");
|
foo->m_md5sum = Json::ensureString(element_obj, "md5sum");
|
||||||
foo->etag = Json::ensureString(element_obj, "etag");
|
foo->m_etag = Json::ensureString(element_obj, "etag");
|
||||||
foo->local_changed_timestamp = Json::ensureDouble(element_obj, "last_changed_timestamp");
|
foo->m_local_changed_timestamp = Json::ensureDouble(element_obj, "last_changed_timestamp");
|
||||||
foo->remote_changed_timestamp = Json::ensureString(element_obj, "remote_changed_timestamp");
|
foo->m_remote_changed_timestamp = Json::ensureString(element_obj, "remote_changed_timestamp");
|
||||||
|
|
||||||
foo->makeEternal(Json::ensureBoolean(element_obj, (const QString)QStringLiteral("eternal"), false));
|
foo->makeEternal(Json::ensureBoolean(element_obj, (const QString)QStringLiteral("eternal"), false));
|
||||||
if (!foo->isEternal()) {
|
if (!foo->isEternal()) {
|
||||||
foo->current_age = Json::ensureDouble(element_obj, "current_age");
|
foo->m_current_age = Json::ensureDouble(element_obj, "current_age");
|
||||||
foo->max_age = Json::ensureDouble(element_obj, "max_age");
|
foo->m_max_age = Json::ensureDouble(element_obj, "max_age");
|
||||||
}
|
}
|
||||||
|
|
||||||
// presumed innocent until closer examination
|
// presumed innocent until closer examination
|
||||||
foo->stale = false;
|
foo->m_stale = false;
|
||||||
|
|
||||||
entrymap.entry_list[foo->relativePath] = MetaEntryPtr(foo);
|
entrymap.entry_list[foo->m_relativePath] = MetaEntryPtr(foo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,23 +276,23 @@ void HttpMetaCache::SaveNow()
|
|||||||
for (auto group : m_entries) {
|
for (auto group : m_entries) {
|
||||||
for (auto entry : group.entry_list) {
|
for (auto entry : group.entry_list) {
|
||||||
// do not save stale entries. they are dead.
|
// do not save stale entries. they are dead.
|
||||||
if (entry->stale) {
|
if (entry->m_stale) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject entryObj;
|
QJsonObject entryObj;
|
||||||
Json::writeString(entryObj, "base", entry->baseId);
|
Json::writeString(entryObj, "base", entry->m_baseId);
|
||||||
Json::writeString(entryObj, "path", entry->relativePath);
|
Json::writeString(entryObj, "path", entry->m_relativePath);
|
||||||
Json::writeString(entryObj, "md5sum", entry->md5sum);
|
Json::writeString(entryObj, "md5sum", entry->m_md5sum);
|
||||||
Json::writeString(entryObj, "etag", entry->etag);
|
Json::writeString(entryObj, "etag", entry->m_etag);
|
||||||
entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->local_changed_timestamp)));
|
entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->m_local_changed_timestamp)));
|
||||||
if (!entry->remote_changed_timestamp.isEmpty())
|
if (!entry->m_remote_changed_timestamp.isEmpty())
|
||||||
entryObj.insert("remote_changed_timestamp", QJsonValue(entry->remote_changed_timestamp));
|
entryObj.insert("remote_changed_timestamp", QJsonValue(entry->m_remote_changed_timestamp));
|
||||||
if (entry->isEternal()) {
|
if (entry->isEternal()) {
|
||||||
entryObj.insert("eternal", true);
|
entryObj.insert("eternal", true);
|
||||||
} else {
|
} else {
|
||||||
entryObj.insert("current_age", QJsonValue(double(entry->current_age)));
|
entryObj.insert("current_age", QJsonValue(double(entry->m_current_age)));
|
||||||
entryObj.insert("max_age", QJsonValue(double(entry->max_age)));
|
entryObj.insert("max_age", QJsonValue(double(entry->m_max_age)));
|
||||||
}
|
}
|
||||||
entriesArr.append(entryObj);
|
entriesArr.append(entryObj);
|
||||||
}
|
}
|
||||||
|
@ -49,47 +49,47 @@ class MetaEntry {
|
|||||||
MetaEntry() = default;
|
MetaEntry() = default;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
auto isStale() -> bool { return stale; }
|
auto isStale() -> bool { return m_stale; }
|
||||||
void setStale(bool stale) { this->stale = stale; }
|
void setStale(bool stale) { m_stale = stale; }
|
||||||
|
|
||||||
auto getFullPath() -> QString;
|
auto getFullPath() -> QString;
|
||||||
|
|
||||||
auto getRemoteChangedTimestamp() -> QString { return remote_changed_timestamp; }
|
auto getRemoteChangedTimestamp() -> QString { return m_remote_changed_timestamp; }
|
||||||
void setRemoteChangedTimestamp(QString remote_changed_timestamp) { this->remote_changed_timestamp = remote_changed_timestamp; }
|
void setRemoteChangedTimestamp(QString remote_changed_timestamp) { m_remote_changed_timestamp = remote_changed_timestamp; }
|
||||||
void setLocalChangedTimestamp(qint64 timestamp) { local_changed_timestamp = timestamp; }
|
void setLocalChangedTimestamp(qint64 timestamp) { m_local_changed_timestamp = timestamp; }
|
||||||
|
|
||||||
auto getETag() -> QString { return etag; }
|
auto getETag() -> QString { return m_etag; }
|
||||||
void setETag(QString etag) { this->etag = etag; }
|
void setETag(QString etag) { m_etag = etag; }
|
||||||
|
|
||||||
auto getMD5Sum() -> QString { return md5sum; }
|
auto getMD5Sum() -> QString { return m_md5sum; }
|
||||||
void setMD5Sum(QString md5sum) { this->md5sum = md5sum; }
|
void setMD5Sum(QString md5sum) { m_md5sum = md5sum; }
|
||||||
|
|
||||||
/* Whether the entry expires after some time (false) or not (true). */
|
/* Whether the entry expires after some time (false) or not (true). */
|
||||||
void makeEternal(bool eternal) { is_eternal = eternal; }
|
void makeEternal(bool eternal) { m_is_eternal = eternal; }
|
||||||
[[nodiscard]] bool isEternal() const { return is_eternal; }
|
[[nodiscard]] bool isEternal() const { return m_is_eternal; }
|
||||||
|
|
||||||
auto getCurrentAge() -> qint64 { return current_age; }
|
auto getCurrentAge() -> qint64 { return m_current_age; }
|
||||||
void setCurrentAge(qint64 age) { current_age = age; }
|
void setCurrentAge(qint64 age) { m_current_age = age; }
|
||||||
|
|
||||||
auto getMaximumAge() -> qint64 { return max_age; }
|
auto getMaximumAge() -> qint64 { return m_max_age; }
|
||||||
void setMaximumAge(qint64 age) { max_age = age; }
|
void setMaximumAge(qint64 age) { m_max_age = age; }
|
||||||
|
|
||||||
bool isExpired(qint64 offset) { return !is_eternal && (current_age >= max_age - offset); };
|
bool isExpired(qint64 offset) { return !m_is_eternal && (m_current_age >= m_max_age - offset); };
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QString baseId;
|
QString m_baseId;
|
||||||
QString basePath;
|
QString m_basePath;
|
||||||
QString relativePath;
|
QString m_relativePath;
|
||||||
QString md5sum;
|
QString m_md5sum;
|
||||||
QString etag;
|
QString m_etag;
|
||||||
|
|
||||||
qint64 local_changed_timestamp = 0;
|
qint64 m_local_changed_timestamp = 0;
|
||||||
QString remote_changed_timestamp; // QString for now, RFC 2822 encoded time
|
QString m_remote_changed_timestamp; // QString for now, RFC 2822 encoded time
|
||||||
qint64 current_age = 0;
|
qint64 m_current_age = 0;
|
||||||
qint64 max_age = 0;
|
qint64 m_max_age = 0;
|
||||||
bool is_eternal = false;
|
bool m_is_eternal = false;
|
||||||
|
|
||||||
bool stale = true;
|
bool m_stale = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
using MetaEntryPtr = std::shared_ptr<MetaEntry>;
|
using MetaEntryPtr = std::shared_ptr<MetaEntry>;
|
||||||
|
@ -123,7 +123,7 @@ auto NetJob::getFailedFiles() -> QList<QString>
|
|||||||
|
|
||||||
void NetJob::updateState()
|
void NetJob::updateState()
|
||||||
{
|
{
|
||||||
emit progress(m_done.count(), m_total_size);
|
emit progress(m_done.count(), totalSize());
|
||||||
setStatus(tr("Executing %1 task(s) (%2 out of %3 are done)")
|
setStatus(tr("Executing %1 task(s) (%2 out of %3 are done)")
|
||||||
.arg(QString::number(m_doing.count()), QString::number(m_done.count()), QString::number(m_total_size)));
|
.arg(QString::number(m_doing.count()), QString::number(m_done.count()), QString::number(totalSize())));
|
||||||
}
|
}
|
||||||
|
25
launcher/pathmatcher/SimplePrefixMatcher.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include "IPathMatcher.h"
|
||||||
|
|
||||||
|
class SimplePrefixMatcher : public IPathMatcher {
|
||||||
|
public:
|
||||||
|
virtual ~SimplePrefixMatcher(){};
|
||||||
|
SimplePrefixMatcher(const QString& prefix)
|
||||||
|
{
|
||||||
|
m_prefix = prefix;
|
||||||
|
m_isPrefix = prefix.endsWith('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool matches(const QString& string) const override
|
||||||
|
{
|
||||||
|
if (m_isPrefix)
|
||||||
|
return string.startsWith(m_prefix);
|
||||||
|
return string == m_prefix;
|
||||||
|
}
|
||||||
|
QString m_prefix;
|
||||||
|
bool m_isPrefix = false;
|
||||||
|
};
|
@ -39,5 +39,6 @@
|
|||||||
<file>scalable/export.svg</file>
|
<file>scalable/export.svg</file>
|
||||||
<file>scalable/rename.svg</file>
|
<file>scalable/rename.svg</file>
|
||||||
<file>scalable/launch.svg</file>
|
<file>scalable/launch.svg</file>
|
||||||
|
<file>scalable/shortcut.svg</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
14
launcher/resources/OSX/scalable/shortcut.svg
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||||
|
<rect fill="none" width="24" height="24"/>
|
||||||
|
<g id="_x35__1_">
|
||||||
|
<g>
|
||||||
|
<path fill="#585858" d="M9.5,9.5C9.8,9.5,10,9.2,10,9l0-2.4l7.6,7.3c0.2,0.2,0.5,0.2,0.7,0c0.2-0.2,0.2-0.5,0-0.7L10.8,6L13,6
|
||||||
|
c0.3,0,0.5-0.2,0.5-0.5S13.3,5,13,5H9.5C9.2,5,9,5.2,9,5.5V9C9,9.2,9.2,9.5,9.5,9.5z M21,5h-5.5v1H21c0.5,0,1,0.5,1,1l0,10
|
||||||
|
c0,0.5-0.4,1-1,1l-10,0c-0.5,0-1-0.5-1-1v-5.5H9V17c0,1.1,1.1,2,2.2,2H21c1.1,0,2-0.9,2-2V7.2C23,6.1,22.1,5,21,5z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 886 B |
46
launcher/resources/breeze_dark/breeze_dark.qrc
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<RCC>
|
||||||
|
<qresource prefix="/icons/breeze_dark">
|
||||||
|
<file>index.theme</file>
|
||||||
|
<file>scalable/about.svg</file>
|
||||||
|
<file>scalable/accounts.svg</file>
|
||||||
|
<file>scalable/bug.svg</file>
|
||||||
|
<file>scalable/centralmods.svg</file>
|
||||||
|
<file>scalable/checkupdate.svg</file>
|
||||||
|
<file>scalable/copy.svg</file>
|
||||||
|
<file>scalable/coremods.svg</file>
|
||||||
|
<file>scalable/custom-commands.svg</file>
|
||||||
|
<file>scalable/discord.svg</file>
|
||||||
|
<file>scalable/externaltools.svg</file>
|
||||||
|
<file>scalable/help.svg</file>
|
||||||
|
<file>scalable/instance-settings.svg</file>
|
||||||
|
<file>scalable/jarmods.svg</file>
|
||||||
|
<file>scalable/java.svg</file>
|
||||||
|
<file>scalable/language.svg</file>
|
||||||
|
<file>scalable/loadermods.svg</file>
|
||||||
|
<file>scalable/log.svg</file>
|
||||||
|
<file>scalable/minecraft.svg</file>
|
||||||
|
<file>scalable/matrix.svg</file>
|
||||||
|
<file>scalable/new.svg</file>
|
||||||
|
<file>scalable/news.svg</file>
|
||||||
|
<file>scalable/notes.svg</file>
|
||||||
|
<file>scalable/proxy.svg</file>
|
||||||
|
<file>scalable/reddit-alien.svg</file>
|
||||||
|
<file>scalable/refresh.svg</file>
|
||||||
|
<file>scalable/resourcepacks.svg</file>
|
||||||
|
<file>scalable/shaderpacks.svg</file>
|
||||||
|
<file>scalable/shortcut.svg</file>
|
||||||
|
<file>scalable/screenshots.svg</file>
|
||||||
|
<file>scalable/settings.svg</file>
|
||||||
|
<file>scalable/status-bad.svg</file>
|
||||||
|
<file>scalable/status-good.svg</file>
|
||||||
|
<file>scalable/status-yellow.svg</file>
|
||||||
|
<file>scalable/viewfolder.svg</file>
|
||||||
|
<file>scalable/worlds.svg</file>
|
||||||
|
<file>scalable/delete.svg</file>
|
||||||
|
<file>scalable/tag.svg</file>
|
||||||
|
<file>scalable/export.svg</file>
|
||||||
|
<file>scalable/rename.svg</file>
|
||||||
|
<file>scalable/launch.svg</file>
|
||||||
|
<file>scalable/server.svg</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
11
launcher/resources/breeze_dark/index.theme
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[Icon Theme]
|
||||||
|
Name=Breeze Dark
|
||||||
|
Comment=Breeze Dark Icons
|
||||||
|
Inherits=multimc
|
||||||
|
Directories=scalable
|
||||||
|
|
||||||
|
[scalable]
|
||||||
|
Size=48
|
||||||
|
Type=Scalable
|
||||||
|
MinSize=16
|
||||||
|
MaxSize=256
|
12
launcher/resources/breeze_dark/scalable/about.svg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#eff0f1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<g class="ColorScheme-Text" fill="currentColor" fill-rule="evenodd">
|
||||||
|
<path d="m8 2a6 6 0 0 0 -6 6 6 6 0 0 0 6 6 6 6 0 0 0 6-6 6 6 0 0 0 -6-6zm0 1a5 5 0 0 1 5 5 5 5 0 0 1 -5 5 5 5 0 0 1 -5-5 5 5 0 0 1 5-5z"/>
|
||||||
|
<path d="m7 4h2v2h-2z"/>
|
||||||
|
<path d="m7 7h2v5h-2z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 495 B |
17
launcher/resources/breeze_dark/scalable/accounts.svg
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||||
|
<defs
|
||||||
|
id="defs3051">
|
||||||
|
<style
|
||||||
|
type="text/css"
|
||||||
|
id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#eff0f1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path
|
||||||
|
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||||
|
d="M 20.5,4 A 4.5,4.5 0 0 0 16,8.5 4.5,4.5 0 0 0 20.5,13 4.5,4.5 0 0 0 25,8.5 4.5,4.5 0 0 0 20.5,4 Z m 0,1 A 3.5,3.5 0 0 1 24,8.5 3.5,3.5 0 0 1 20.5,12 3.5,3.5 0 0 1 17,8.5 3.5,3.5 0 0 1 20.5,5 Z m -9,4 C 9.014719,9 7,11.01472 7,13.5 7,15.98528 9.014719,18 11.5,18 13.985281,18 16,15.98528 16,13.5 16,11.01472 13.985281,9 11.5,9 Z m 0,1 A 3.5,3.5 0 0 1 15,13.5 3.5,3.5 0 0 1 11.5,17 3.5,3.5 0 0 1 8,13.5 3.5,3.5 0 0 1 11.5,10 Z m 9,4 c -0.88285,0.003 -1.758266,0.17228 -2.585938,0.5 -0.06618,0.42368 -0.174132,0.83977 -0.322265,1.24219 C 18.494507,15.25488 19.490227,15.00077 20.5,15 c 3.589851,0 6.5,3.13401 6.5,7 l -8.341797,0 c 0.170323,0.32329 0.325499,0.65711 0.464844,1 L 28,23 28,22 c 0,-4.41828 -3.357864,-8 -7.5,-8 z m -9,5 C 7.357864,19 4,22.58172 4,27 l 0,1 15,0 0,-1 c 0,-4.41828 -3.357864,-8 -7.5,-8 z m 0,1 c 3.589851,0 6.5,3.13401 6.5,7 L 5,27 c 0,-3.86599 2.910149,-7 6.5,-7 z"
|
||||||
|
class="ColorScheme-Text"
|
||||||
|
/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
13
launcher/resources/breeze_dark/scalable/bug.svg
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<defs id="defs3051">
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#eff0f1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||||
|
d="m2 2v12h12v-12zm1 1h10v10h-10zm1 2v2h2v-2zm6 0v2h2v-2zm-4 4v1h4v-1zm4 1v1h1v-1zm1 1v1h1v-1zm-5-1h-1v1h1zm-1 1h-1v1h1z"
|
||||||
|
class="ColorScheme-Text"
|
||||||
|
/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 453 B |
1
launcher/resources/breeze_dark/scalable/centralmods.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="8" height="8" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3 3.5v.25h-.5V6h3V3.75H5V3.5h-.75v.25h-.5V3.5H3Zm-.25.5h2.5v1.75h-2.5V4Z" fill="#EFF0F1"/><path d="M1 1v6h6l-.25-.25h-5.5V3.5H2.5l.75-.75h3.5v4L7 7V1.75H4.5L3.75 1H1Z" fill="#EFF0F1"/></svg>
|
After Width: | Height: | Size: 273 B |
14
launcher/resources/breeze_dark/scalable/checkupdate.svg
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<defs id="defs3051">
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#eff0f1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path
|
||||||
|
style="fill:currentColor"
|
||||||
|
d="M 6 2 L 6 3 L 6 6 L 7 6 L 7 3 L 9 3 L 9 6 L 10 6 L 10 3 L 10 2 L 6 2 z M 3.7 6 L 3 6.7 L 6.3 10 L 8 11.7 L 9.7 10 L 13 6.7 L 12.3 6 L 9 9.3 L 8 10.3 L 7 9.3 L 3.7 6 z M 2 12 L 2 14 L 3 14 L 14 14 L 14 13 L 14 12 L 13 12 L 13 13 L 3 13 L 3 12 L 2 12 z "
|
||||||
|
class="ColorScheme-Text"
|
||||||
|
/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 577 B |
11
launcher/resources/breeze_dark/scalable/copy.svg
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<!DOCTYPE svg>
|
||||||
|
<svg viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#eff0f1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 3 3 L 3 17 L 7 17 L 7 19 L 17 19 L 17 10 L 13 6 L 12 6 L 9 3 L 3 3 Z M 4 4 L 8 4 L 8 6 L 7 6 L 7 16 L 4 16 L 4 4 Z M 8 7 L 12 7 L 12 11 L 16 11 L 16 18 L 8 18 L 8 7 Z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 537 B |
1
launcher/resources/breeze_dark/scalable/coremods.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg width="8" height="8" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.167 2.46v.208H2.75v1.875h2.5V2.668h-.417V2.46h-.625v.208h-.416V2.46h-.625Zm-.209.417h2.084v1.458H2.958V2.877Z" fill="#EFF0F1"/><path d="M1.5 1v6h5V1h-5Zm1.5.5h2a1 1 0 0 1 1 1V3a.5.5 0 1 0 0 1v1l-.5.5h-3L2 5V4a.5.5 0 1 0 0-1v-.5a1 1 0 0 1 1-1ZM2 6h2v.5H2V6Zm3 0h1v.5H5V6Z" fill="#EFF0F1"/></svg>
|
After Width: | Height: | Size: 379 B |
13
launcher/resources/breeze_dark/scalable/custom-commands.svg
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<defs id="defs3051">
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#eff0f1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||||
|
d="M 2 2 L 2 14 L 14 14 L 14 2 L 2 2 z M 3 3 L 13 3 L 13 13 L 3 13 L 3 3 z M 4.71875 4 L 4 4.6875 L 6.625 7.5 L 4.03125 10.3125 L 4.71875 11 L 7.6875 7.84375 L 8 7.5 L 7.6875 7.15625 L 4.71875 4 z M 8 11 L 8 12 L 12 12 L 12 11 L 8 11 z "
|
||||||
|
class="ColorScheme-Text"
|
||||||
|
/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 569 B |
13
launcher/resources/breeze_dark/scalable/delete.svg
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<defs id="defs3051">
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#eff0f1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path
|
||||||
|
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||||
|
d="M 6 2 L 6 3 L 2 3 L 2 4 L 3 4 L 3 14 L 4 14 L 13 14 L 13 13 L 13 4 L 14 4 L 14 3 L 10 3 L 10 2 L 6 2 z M 7 3 L 9 3 L 9 4 L 10 4 L 12 4 L 12 13 L 4 13 L 4 4 L 7 4 L 7 3 z M 6 6 L 6 11 L 7 11 L 7 6 L 6 6 z M 9 6 L 9 11 L 10 11 L 10 6 L 9 6 z "
|
||||||
|
class="ColorScheme-Text"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 589 B |
1
launcher/resources/breeze_dark/scalable/discord.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36"><defs><style>.cls-1{fill:#eff0f1;}</style></defs><g id="图层_2" data-name="图层 2"><g id="Discord_Logos" data-name="Discord Logos"><g id="Discord_Logo_-_Large_-_White" data-name="Discord Logo - Large - White"><path class="cls-1" d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"/></g></g></g></svg>
|
After Width: | Height: | Size: 988 B |
11
launcher/resources/breeze_dark/scalable/export.svg
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<!DOCTYPE svg>
|
||||||
|
<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#eff0f1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 7 12 L 11.0859 12 L 9.46484 13.6211 L 10.1719 14.3281 L 13 11.5 L 10.1719 8.67187 L 9.46484 9.37891 L 11.0859 11 L 7 11 L 7 12 Z M 4 13 L 4 3 L 9 3 L 9 6 L 12 6 L 12 9 L 13 9 L 13 5 L 10 2 L 3 2 L 3 14 L 8 14 L 8 13 L 4 13 Z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 595 B |
13
launcher/resources/breeze_dark/scalable/externaltools.svg
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<defs id="defs3051">
|
||||||
|
<style type="text/css" id="current-color-scheme">
|
||||||
|
.ColorScheme-Text {
|
||||||
|
color:#eff0f1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||||
|
d="M 4 5 L 4 7 L 2 7 L 2 8 L 4 8 L 4 10 L 5 10 L 7 10 L 7 9 L 5 9 L 5 6 L 7 6 L 7 5 L 5 5 L 4 5 z M 9 5 L 9 6 L 11 6 L 11 9 L 9 9 L 9 10 L 11 10 L 12 10 L 12 9 L 12 8 L 14 8 L 14 7 L 12 7 L 12 6 L 12 5 L 11 5 L 9 5 z M 7 7 L 7 8 L 9 8 L 9 7 L 7 7 z "
|
||||||
|
class="ColorScheme-Text"
|
||||||
|
/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 584 B |