Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into develop
112
.github/workflows/build.yml
vendored
@ -7,10 +7,17 @@ on:
|
||||
description: Type of build (Debug, Release, RelWithDebInfo, MinSizeRel)
|
||||
type: string
|
||||
default: Debug
|
||||
is_qt_cached:
|
||||
description: Enable Qt caching or not
|
||||
type: string
|
||||
default: true
|
||||
secrets:
|
||||
SPARKLE_ED25519_KEY:
|
||||
description: Private key for signing Sparkle updates
|
||||
required: false
|
||||
CACHIX_AUTH_TOKEN:
|
||||
description: Private token for authenticating against Cachix cache
|
||||
required: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@ -31,17 +38,11 @@ jobs:
|
||||
qt_tools: ''
|
||||
|
||||
- os: windows-2022
|
||||
name: "Windows-Legacy"
|
||||
msystem: clang32
|
||||
qt_ver: 5
|
||||
|
||||
- os: windows-2022
|
||||
name: "Windows"
|
||||
name: "Windows-MinGW-w64"
|
||||
msystem: clang64
|
||||
qt_ver: 6
|
||||
|
||||
- os: windows-2022
|
||||
name: "Windows-Legacy-MSVC"
|
||||
name: "Windows-MSVC-Legacy"
|
||||
msystem: ''
|
||||
architecture: 'win32'
|
||||
vcvars_arch: 'amd64_x86'
|
||||
@ -64,6 +65,18 @@ jobs:
|
||||
qt_modules: 'qt5compat qtimageformats'
|
||||
qt_tools: ''
|
||||
|
||||
- os: windows-2022
|
||||
name: "Windows-MSVC-arm64"
|
||||
msystem: ''
|
||||
architecture: 'arm64'
|
||||
vcvars_arch: 'amd64_arm64'
|
||||
qt_ver: 6
|
||||
qt_host: windows
|
||||
qt_arch: 'win64_msvc2019_arm64'
|
||||
qt_version: '6.4.0'
|
||||
qt_modules: 'qt5compat qtimageformats'
|
||||
qt_tools: ''
|
||||
|
||||
- os: macos-12
|
||||
name: macOS
|
||||
macosx_deployment_target: 10.15
|
||||
@ -102,14 +115,6 @@ jobs:
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
- name: Initialize CodeQL
|
||||
if: runner.os == 'Linux' && matrix.qt_ver == 6
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
config-file: ./.github/codeql/codeql-config.yml
|
||||
queries: security-and-quality
|
||||
languages: cpp, java
|
||||
|
||||
- name: 'Setup MSYS2'
|
||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
||||
uses: msys2/setup-msys2@v2
|
||||
@ -124,12 +129,12 @@ jobs:
|
||||
cmake:p
|
||||
extra-cmake-modules:p
|
||||
ninja:p
|
||||
qt${{ matrix.qt_ver }}-base:p
|
||||
qt${{ matrix.qt_ver }}-svg:p
|
||||
qt${{ matrix.qt_ver }}-imageformats:p
|
||||
quazip-qt${{ matrix.qt_ver }}:p
|
||||
qt6-base:p
|
||||
qt6-svg:p
|
||||
qt6-imageformats:p
|
||||
quazip-qt6:p
|
||||
ccache:p
|
||||
${{ matrix.qt_ver == 6 && 'qt6-5compat:p' || '' }}
|
||||
qt6-5compat:p
|
||||
|
||||
- name: Force newer ccache
|
||||
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'
|
||||
uses: hendrikmuhs/ccache-action@v1.2.5
|
||||
with:
|
||||
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}
|
||||
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
|
||||
|
||||
- name: Setup ccache (Windows MinGW-w64)
|
||||
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
|
||||
@ -163,9 +168,9 @@ jobs:
|
||||
uses: actions/cache@v3.0.11
|
||||
with:
|
||||
path: '${{ github.workspace }}\.ccache'
|
||||
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}
|
||||
key: ${{ matrix.os }}-mingw-w64
|
||||
restore-keys: |
|
||||
${{ matrix.os }}-qt${{ matrix.qt_ver }}
|
||||
${{ matrix.os }}-mingw-w64
|
||||
|
||||
- name: Set short version
|
||||
shell: bash
|
||||
@ -189,8 +194,23 @@ jobs:
|
||||
if: runner.os == 'Linux' && matrix.qt_ver != 6
|
||||
run: |
|
||||
sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5
|
||||
|
||||
- name: Install Qt (macOS, 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 == '')
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
@ -200,8 +220,14 @@ jobs:
|
||||
arch: ${{ matrix.qt_arch }}
|
||||
modules: ${{ matrix.qt_modules }}
|
||||
tools: ${{ matrix.qt_tools }}
|
||||
cache: true
|
||||
cache-key-prefix: ${{ matrix.qt_host }}-${{ matrix.qt_version }}-"${{ matrix.qt_modules }}"-qt_cache
|
||||
cache: ${{ inputs.is_qt_cached }}
|
||||
|
||||
- name: Install MSVC (Windows MSVC)
|
||||
if: runner.os == 'Windows' && matrix.msystem == ''
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
vsversion: 2022
|
||||
arch: ${{ matrix.vcvars_arch }}
|
||||
|
||||
- name: Prepare AppImage (Linux)
|
||||
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||
@ -212,6 +238,11 @@ jobs:
|
||||
|
||||
${{ github.workspace }}/.github/scripts/prepare_JREs.sh
|
||||
|
||||
- name: Add QT_HOST_PATH var (Windows MSVC arm64)
|
||||
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
|
||||
run: |
|
||||
echo "QT_HOST_PATH=${{ github.workspace }}\HostQt\Qt\${{ matrix.qt_version }}\msvc2019_64" >> $env:GITHUB_ENV
|
||||
|
||||
##
|
||||
# CONFIGURE
|
||||
##
|
||||
@ -230,12 +261,12 @@ jobs:
|
||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -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)
|
||||
if: runner.os == 'Windows' && matrix.msystem == ''
|
||||
run: |
|
||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}}
|
||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON
|
||||
# https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix)
|
||||
if ("${{ env.CCACHE_VAR }}")
|
||||
{
|
||||
@ -288,18 +319,10 @@ jobs:
|
||||
ctest -E "^example64|example$" --test-dir build --output-on-failure
|
||||
|
||||
- name: Test (Windows MSVC)
|
||||
if: runner.os == 'Windows' && matrix.msystem == ''
|
||||
if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture != 'arm64'
|
||||
run: |
|
||||
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
|
||||
##
|
||||
@ -333,23 +356,12 @@ jobs:
|
||||
EOF
|
||||
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)
|
||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
cmake --install ${{ env.BUILD_DIR }}
|
||||
|
||||
cd ${{ env.INSTALL_DIR }}
|
||||
if [ "${{ matrix.qt_ver }}" == "5" ]; then
|
||||
cp /clang32/bin/libcrypto-1_1.dll /clang32/bin/libssl-1_1.dll ./
|
||||
fi
|
||||
|
||||
- name: Package (Windows MSVC)
|
||||
if: runner.os == 'Windows' && matrix.msystem == ''
|
||||
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'
|
||||
- '**/LICENSE'
|
||||
- 'flake.lock'
|
||||
- '**.nix'
|
||||
- 'packages/**'
|
||||
- '.github/ISSUE_TEMPLATE/**'
|
||||
pull_request:
|
||||
@ -16,7 +15,6 @@ on:
|
||||
- '**.md'
|
||||
- '**/LICENSE'
|
||||
- 'flake.lock'
|
||||
- '**.nix'
|
||||
- 'packages/**'
|
||||
- '.github/ISSUE_TEMPLATE/**'
|
||||
workflow_dispatch:
|
||||
@ -28,5 +26,7 @@ jobs:
|
||||
uses: ./.github/workflows/build.yml
|
||||
with:
|
||||
build_type: Debug
|
||||
is_qt_cached: true
|
||||
secrets:
|
||||
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
|
||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
|
35
.github/workflows/trigger_release.yml
vendored
@ -12,6 +12,7 @@ jobs:
|
||||
uses: ./.github/workflows/build.yml
|
||||
with:
|
||||
build_type: Release
|
||||
is_qt_cached: false
|
||||
secrets:
|
||||
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
|
||||
|
||||
@ -36,7 +37,7 @@ jobs:
|
||||
run: |
|
||||
mv ${{ github.workspace }}/PollyMC-source PollyMC-${{ env.VERSION }}
|
||||
mv PollyMC-Linux-Qt6-Portable*/PollyMC-portable.tar.gz PollyMC-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
|
||||
mv PollyMC-Linux-Qt6*/PollyMC.tar.gz PollyMC-Linux-Qt6-${{ env.VERSION }}.tar.gz
|
||||
mv PollyMC-Linux-Qt6*/PollyMC.tar.gz PollyMC-Linux-Qt6-${{ env.VERSION }}.tar.gz
|
||||
mv PollyMC-Linux-Portable*/PollyMC-portable.tar.gz PollyMC-Linux-Portable-${{ env.VERSION }}.tar.gz
|
||||
mv PollyMC-Linux*/PollyMC.tar.gz PollyMC-Linux-${{ env.VERSION }}.tar.gz
|
||||
mv PollyMC-*.AppImage/PollyMC-*.AppImage PollyMC-Linux-${{ env.VERSION }}-x86_64.AppImage
|
||||
@ -45,15 +46,26 @@ jobs:
|
||||
|
||||
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
|
||||
MSVC="$(echo -n ${d} | grep -o MSVC || 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)"
|
||||
PORT="$(echo -n ${d} | grep -o Portable || true)"
|
||||
NAME="PollyMC-Windows"
|
||||
test -z "${MSVC}" && NAME="${NAME}-MinGW" || NAME="${NAME}-MSVC"
|
||||
NAME="PollyMC-Windows-MSVC"
|
||||
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 "${INST}" || mv PollyMC-*.exe ../${NAME}-Setup-${{ env.VERSION }}.exe
|
||||
test -n "${INST}" || zip -r -9 "../${NAME}-${{ env.VERSION }}.zip" *
|
||||
@ -76,18 +88,19 @@ jobs:
|
||||
PollyMC-Linux-${{ env.VERSION }}-x86_64.AppImage
|
||||
PollyMC-Linux-Qt6-${{ env.VERSION }}.tar.gz
|
||||
PollyMC-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
|
||||
PollyMC-Windows-MinGW-Legacy-${{ env.VERSION }}.zip
|
||||
PollyMC-Windows-MinGW-Legacy-Portable-${{ env.VERSION }}.zip
|
||||
PollyMC-Windows-MinGW-Legacy-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-MinGW-w64-${{ env.VERSION }}.zip
|
||||
PollyMC-Windows-MinGW-w64-Portable-${{ env.VERSION }}.zip
|
||||
PollyMC-Windows-MinGW-w64-Setup-${{ env.VERSION }}.exe
|
||||
PollyMC-Windows-MSVC-Legacy-${{ env.VERSION }}.zip
|
||||
PollyMC-Windows-MSVC-Legacy-Portable-${{ env.VERSION }}.zip
|
||||
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-Portable-${{ env.VERSION }}.zip
|
||||
PollyMC-Windows-MSVC-Setup-${{ env.VERSION }}.exe
|
||||
PollyMC-macOS-${{ env.VERSION }}.tar.gz
|
||||
PollyMC-macOS-Legacy-${{ env.VERSION }}.tar.gz
|
||||
PollyMC-${{ env.VERSION }}.tar.gz
|
||||
|
||||
|
2
.github/workflows/winget.yml
vendored
@ -11,5 +11,5 @@ jobs:
|
||||
with:
|
||||
identifier: PrismLauncher.PrismLauncher
|
||||
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 }}
|
||||
|
@ -28,19 +28,28 @@ set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
include(GenerateExportHeader)
|
||||
if(MSVC)
|
||||
# Use /W4 as /Wall includes unnesserey warnings such as added padding to structs
|
||||
# /permissive- specify standards-conforming compiler behavior, also enabled by Qt6, default on with std:c++20
|
||||
# /GS Adds buffer security checks, default on but incuded anyway to mirror gcc's fstack-protector flag
|
||||
set(CMAKE_CXX_FLAGS "/W4 /permissive- /GS ${CMAKE_CXX_FLAGS}")
|
||||
# /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
|
||||
# This implicitly selects an entrypoint specific to the subsystem selected
|
||||
# qtmain/QtEntryPointLib provides the correct entrypoint (wWinMain) for gui programs
|
||||
# Additinaly LINK autodetects we use a GUI so we can omit /SUBSYSTEM
|
||||
# This allows tests to still use have console without using seperate linker flags
|
||||
# /LTCG allows for linking wholy optimizated programs
|
||||
# /MANIFEST:NO disables generating a manifest file, we instead provide our own
|
||||
# /STACK sets the stack reserve size, ATL's pack list needs 3-4 MiB as of November 2022, provide 8 MiB
|
||||
set(CMAKE_EXE_LINKER_FLAGS "/MANIFEST:NO /STACK:8388608 ${CMAKE_EXE_LINKER_FLAGS}")
|
||||
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
|
||||
# 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")
|
||||
|
||||
# Fix aarch64 build for toml++
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTOML_ENABLE_FLOAT16=0")
|
||||
|
||||
# set CXXFLAGS for build targets
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}")
|
||||
|
||||
@ -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 version numbers ########
|
||||
set(Launcher_VERSION_MAJOR 6)
|
||||
set(Launcher_VERSION_MAJOR 7)
|
||||
set(Launcher_VERSION_MINOR 0)
|
||||
|
||||
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")
|
||||
elseif(Launcher_QT_VERSION_MAJOR EQUAL 6)
|
||||
set(QT_VERSION_MAJOR 6)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml Core5Compat)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat)
|
||||
list(APPEND Launcher_QT_LIBS Qt6::Core5Compat)
|
||||
|
||||
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
||||
@ -231,12 +243,16 @@ else()
|
||||
message(FATAL_ERROR "Qt version ${Launcher_QT_VERSION_MAJOR} is not supported")
|
||||
endif()
|
||||
|
||||
include(ECMQueryQt)
|
||||
ecm_query_qt(QT_PLUGINS_DIR QT_INSTALL_PLUGINS)
|
||||
ecm_query_qt(QT_LIBS_DIR QT_INSTALL_LIBS)
|
||||
ecm_query_qt(QT_LIBEXECS_DIR QT_INSTALL_LIBEXECS)
|
||||
ecm_query_qt(QT_DATA_DIR QT_HOST_DATA)
|
||||
set(QT_MKSPECS_DIR ${QT_DATA_DIR}/mkspecs)
|
||||
if(Launcher_QT_VERSION_MAJOR EQUAL 5)
|
||||
include(ECMQueryQt)
|
||||
ecm_query_qt(QT_PLUGINS_DIR QT_INSTALL_PLUGINS)
|
||||
ecm_query_qt(QT_LIBS_DIR QT_INSTALL_LIBS)
|
||||
ecm_query_qt(QT_LIBEXECS_DIR QT_INSTALL_LIBEXECS)
|
||||
else()
|
||||
set(QT_PLUGINS_DIR ${QT${QT_VERSION_MAJOR}_INSTALL_PREFIX}/${QT${QT_VERSION_MAJOR}_INSTALL_PLUGINS})
|
||||
set(QT_LIBS_DIR ${QT${QT_VERSION_MAJOR}_INSTALL_PREFIX}/${QT${QT_VERSION_MAJOR}_INSTALL_LIBS})
|
||||
set(QT_LIBEXECS_DIR ${QT${QT_VERSION_MAJOR}_INSTALL_PREFIX}/${QT${QT_VERSION_MAJOR}_INSTALL_LIBEXECS})
|
||||
endif()
|
||||
|
||||
# NOTE: Qt 6 already sets this by default
|
||||
if (Qt5_POSITION_INDEPENDENT_CODE)
|
||||
@ -364,11 +380,11 @@ if(NOT ZLIB_FOUND)
|
||||
set(SKIP_INSTALL_ALL ON)
|
||||
add_subdirectory(libraries/zlib EXCLUDE_FROM_ALL)
|
||||
|
||||
set(ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib" "${CMAKE_CURRENT_BINARY_DIR}/libraries/zlib")
|
||||
set(ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib" "${CMAKE_CURRENT_BINARY_DIR}/libraries/zlib" CACHE STRING "")
|
||||
set_target_properties(zlibstatic PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${ZLIB_INCLUDE_DIR}")
|
||||
add_library(ZLIB::ZLIB ALIAS zlibstatic)
|
||||
set(ZLIB_LIBRARY ZLIB::ZLIB)
|
||||
set(ZLIB_FOUND true)
|
||||
set(ZLIB_LIBRARY ZLIB::ZLIB CACHE STRING "zlib library name")
|
||||
|
||||
find_package(ZLIB REQUIRED)
|
||||
else()
|
||||
message(STATUS "Using system zlib")
|
||||
|
42
COPYING.md
@ -398,3 +398,45 @@
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
## Breeze icons
|
||||
|
||||
Copyright (C) 2014 Uri Herrera <uri_herrera@nitrux.in> and others
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
## Oxygen Icons
|
||||
|
||||
The Oxygen Icon Theme
|
||||
Copyright (C) 2007 Nuno Pinheiro <nuno@oxygen-icons.org>
|
||||
Copyright (C) 2007 David Vignoni <david@icon-king.com>
|
||||
Copyright (C) 2007 David Miller <miller@oxygen-icons.org>
|
||||
Copyright (C) 2007 Johann Ollivier Lapeyre <johann@oxygen-icons.org>
|
||||
Copyright (C) 2007 Kenneth Wimer <kwwii@bootsplash.org>
|
||||
Copyright (C) 2007 Riccardo Iaconelli <riccardo@oxygen-icons.org>
|
||||
|
||||
and others
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
@ -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).
|
||||
|
||||
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_PROD_URL = "https://api.modrinth.com/v2";
|
||||
|
||||
QString FLAME_BASE_URL = "https://api.curseforge.com/v1";
|
||||
|
||||
QString versionString() const;
|
||||
/**
|
||||
* \brief Converts the Version to a string.
|
||||
|
31
flake.lock
@ -3,11 +3,11 @@
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1650374568,
|
||||
"narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=",
|
||||
"lastModified": 1668681692,
|
||||
"narHash": "sha256-Ht91NGdewz8IQLtWZ9LCeNXMSXHUss+9COoqu6JLmXU=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "b4a34015c698c7793d592d66adbab377907a2be8",
|
||||
"rev": "009399224d5e398d03b22badca40a37ac85412a1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -34,11 +34,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1666057921,
|
||||
"narHash": "sha256-VpQqtXdj6G7cH//SvoprjR7XT3KS7p+tCVebGK1N6tE=",
|
||||
"lastModified": 1671417167,
|
||||
"narHash": "sha256-JkHam6WQOwZN1t2C2sbp1TqMv3TVRjzrdoejqfefwrM=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "88eab1e431cabd0ed621428d8b40d425a07af39f",
|
||||
"rev": "bb31220cca6d044baa6dc2715b07497a2a7c4bc7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -52,24 +52,7 @@
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"libnbtplusplus": "libnbtplusplus",
|
||||
"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"
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -5,10 +5,9 @@
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
|
||||
flake-compat = { url = "github:edolstra/flake-compat"; 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
|
||||
# User-friendly version number.
|
||||
version = builtins.substring 0 8 self.lastModifiedDate;
|
||||
@ -23,8 +22,8 @@
|
||||
pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system});
|
||||
|
||||
packagesFn = pkgs: rec {
|
||||
prismlauncher = pkgs.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus tomlplusplus; };
|
||||
prismlauncher-qt6 = pkgs.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus tomlplusplus; };
|
||||
prismlauncher-qt5 = pkgs.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus; };
|
||||
prismlauncher = pkgs.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus; };
|
||||
};
|
||||
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
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
@ -38,10 +41,14 @@
|
||||
#include "Application.h"
|
||||
#include "BuildConfig.h"
|
||||
|
||||
#include "DataMigrationTask.h"
|
||||
#include "net/PasteUpload.h"
|
||||
#include "pathmatcher/MultiMatcher.h"
|
||||
#include "pathmatcher/SimplePrefixMatcher.h"
|
||||
#include "ui/MainWindow.h"
|
||||
#include "ui/InstanceWindow.h"
|
||||
|
||||
#include "ui/dialogs/ProgressDialog.h"
|
||||
#include "ui/instanceview/AccessibleInstanceView.h"
|
||||
|
||||
#include "ui/pages/BasePageProvider.h"
|
||||
@ -90,6 +97,7 @@
|
||||
#include <QIcon>
|
||||
|
||||
#include "InstanceList.h"
|
||||
#include "MTPixmapCache.h"
|
||||
|
||||
#include <minecraft/auth/AccountList.h>
|
||||
#include "icons/IconList.h"
|
||||
@ -118,6 +126,7 @@
|
||||
#ifdef Q_OS_LINUX
|
||||
#include <dlfcn.h>
|
||||
#include "gamemode_client.h"
|
||||
#include "MangoHud.h"
|
||||
#endif
|
||||
|
||||
|
||||
@ -134,6 +143,8 @@
|
||||
|
||||
static const QLatin1String liveCheckFile("live.check");
|
||||
|
||||
PixmapCache* PixmapCache::s_instance = nullptr;
|
||||
|
||||
namespace {
|
||||
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);
|
||||
setApplicationName(BuildConfig.LAUNCHER_NAME);
|
||||
setApplicationDisplayName(QString("%1 %2").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString()));
|
||||
setApplicationVersion(BuildConfig.printableVersionString());
|
||||
setApplicationVersion(BuildConfig.printableVersionString() + "\n" + BuildConfig.GIT_COMMIT);
|
||||
setDesktopFileName(BuildConfig.LAUNCHER_DESKTOPFILENAME);
|
||||
startTime = QDateTime::currentDateTime();
|
||||
|
||||
@ -301,24 +312,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
dataPath = foo.absolutePath();
|
||||
adjustedBy = "Persistent data path";
|
||||
|
||||
/*
|
||||
QDir polymcData(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation), "PolyMC"));
|
||||
if (polymcData.exists()) {
|
||||
dataPath = polymcData.absolutePath();
|
||||
adjustedBy = "PolyMC data path";
|
||||
}
|
||||
*/
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
// TODO: this should be removed in a future version
|
||||
// TODO: provide a migration path similar to macOS migration
|
||||
QDir bar(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation), "polymc"));
|
||||
if (bar.exists()) {
|
||||
dataPath = bar.absolutePath();
|
||||
adjustedBy = "Legacy data path";
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef Q_OS_MACOS
|
||||
if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) {
|
||||
dataPath = m_rootPath;
|
||||
@ -441,6 +434,15 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
qDebug() << "<> Log initialized.";
|
||||
}
|
||||
|
||||
{
|
||||
bool migrated = false;
|
||||
|
||||
if (!migrated)
|
||||
migrated = handleDataMigration(dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../PolyMC"), "PolyMC", "polymc.cfg");
|
||||
if (!migrated)
|
||||
migrated = handleDataMigration(dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../multimc"), "MultiMC", "multimc.cfg");
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
qDebug() << BuildConfig.LAUNCHER_DISPLAYNAME << ", (c) 2013-2021 " << BuildConfig.LAUNCHER_COPYRIGHT;
|
||||
@ -695,6 +697,9 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
m_globalSettingsProvider->addPage<AccountListPage>();
|
||||
m_globalSettingsProvider->addPage<APIPage>();
|
||||
}
|
||||
|
||||
PixmapCache::setInstance(new PixmapCache(this));
|
||||
|
||||
qDebug() << "<> Settings loaded.";
|
||||
}
|
||||
|
||||
@ -918,13 +923,13 @@ bool Application::createSetupWizard()
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Application::event(QEvent* event) {
|
||||
bool Application::event(QEvent* event)
|
||||
{
|
||||
#ifdef Q_OS_MACOS
|
||||
if (event->type() == QEvent::ApplicationStateChange) {
|
||||
auto ev = static_cast<QApplicationStateChangeEvent*>(event);
|
||||
|
||||
if (m_prevAppState == Qt::ApplicationActive
|
||||
&& ev->applicationState() == Qt::ApplicationActive) {
|
||||
if (m_prevAppState == Qt::ApplicationActive && ev->applicationState() == Qt::ApplicationActive) {
|
||||
emit clickedOnDock();
|
||||
}
|
||||
m_prevAppState = ev->applicationState();
|
||||
@ -1522,17 +1527,8 @@ void Application::updateCapabilities()
|
||||
if (gamemode_query_status() >= 0)
|
||||
m_capabilities |= SupportsGameMode;
|
||||
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (!MangoHud::getLibraryString().isEmpty())
|
||||
m_capabilities |= SupportsMangoHud;
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -1608,3 +1604,89 @@ int Application::suitableMaxMem()
|
||||
|
||||
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);
|
||||
|
||||
private:
|
||||
bool handleDataMigration(const QString & currentData, const QString & oldData, const QString & name, const QString & configFile) const;
|
||||
bool createSetupWizard();
|
||||
void performMainStartupAction();
|
||||
|
||||
|
@ -47,8 +47,8 @@ void ApplicationMessage::parse(const QByteArray & input) {
|
||||
args.clear();
|
||||
|
||||
auto parsedArgs = root.value("args").toObject();
|
||||
for(auto iter = parsedArgs.begin(); iter != parsedArgs.end(); iter++) {
|
||||
args[iter.key()] = iter.value().toString();
|
||||
for(auto iter = parsedArgs.constBegin(); iter != parsedArgs.constEnd(); iter++) {
|
||||
args.insert(iter.key(), iter.value().toString());
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,8 +56,8 @@ QByteArray ApplicationMessage::serialize() {
|
||||
QJsonObject root;
|
||||
root.insert("command", command);
|
||||
QJsonObject outArgs;
|
||||
for (auto iter = args.begin(); iter != args.end(); iter++) {
|
||||
outArgs[iter.key()] = iter.value();
|
||||
for (auto iter = args.constBegin(); iter != args.constEnd(); iter++) {
|
||||
outArgs.insert(iter.key(), iter.value());
|
||||
}
|
||||
root.insert("args", outArgs);
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QMap>
|
||||
#include <QHash>
|
||||
#include <QByteArray>
|
||||
|
||||
struct ApplicationMessage {
|
||||
QString command;
|
||||
QMap<QString, QString> args;
|
||||
QHash<QString, QString> args;
|
||||
|
||||
QByteArray serialize();
|
||||
void parse(const QByteArray & input);
|
||||
|
@ -151,7 +151,7 @@ public:
|
||||
void copyManagedPack(BaseInstance& other);
|
||||
|
||||
/// guess log level from a line of game log
|
||||
virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level)
|
||||
virtual MessageLevel::Enum guessLevel([[maybe_unused]] const QString &line, MessageLevel::Enum level)
|
||||
{
|
||||
return level;
|
||||
};
|
||||
|
@ -95,12 +95,12 @@ BaseVersionList::RoleList BaseVersionList::providesRoles() const
|
||||
int BaseVersionList::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
// Return count
|
||||
return count();
|
||||
return parent.isValid() ? 0 : count();
|
||||
}
|
||||
|
||||
int BaseVersionList::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return 1;
|
||||
return parent.isValid() ? 0 : 1;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> BaseVersionList::roleNames() const
|
||||
|
@ -89,7 +89,18 @@ set(CORE_SOURCES
|
||||
# Time
|
||||
MMCTime.h
|
||||
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
|
||||
# Path matchers
|
||||
@ -97,6 +108,7 @@ set(PATHMATCHER_SOURCES
|
||||
pathmatcher/IPathMatcher.h
|
||||
pathmatcher/MultiMatcher.h
|
||||
pathmatcher/RegexpMatcher.h
|
||||
pathmatcher/SimplePrefixMatcher.h
|
||||
)
|
||||
|
||||
set(NET_SOURCES
|
||||
@ -583,6 +595,8 @@ SET(LAUNCHER_SOURCES
|
||||
# Application base
|
||||
Application.h
|
||||
Application.cpp
|
||||
DataMigrationTask.h
|
||||
DataMigrationTask.cpp
|
||||
UpdateController.cpp
|
||||
UpdateController.h
|
||||
ApplicationMessage.h
|
||||
@ -606,6 +620,8 @@ SET(LAUNCHER_SOURCES
|
||||
resources/pe_light/pe_light.qrc
|
||||
resources/pe_colored/pe_colored.qrc
|
||||
resources/pe_blue/pe_blue.qrc
|
||||
resources/breeze_dark/breeze_dark.qrc
|
||||
resources/breeze_light/breeze_light.qrc
|
||||
resources/OSX/OSX.qrc
|
||||
resources/iOS/iOS.qrc
|
||||
resources/flat/flat.qrc
|
||||
@ -683,6 +699,8 @@ SET(LAUNCHER_SOURCES
|
||||
ui/pages/instance/GameOptionsPage.h
|
||||
ui/pages/instance/VersionPage.cpp
|
||||
ui/pages/instance/VersionPage.h
|
||||
ui/pages/instance/ManagedPackPage.cpp
|
||||
ui/pages/instance/ManagedPackPage.h
|
||||
ui/pages/instance/TexturePackPage.h
|
||||
ui/pages/instance/ResourcePackPage.h
|
||||
ui/pages/instance/ShaderPackPage.h
|
||||
@ -801,6 +819,8 @@ SET(LAUNCHER_SOURCES
|
||||
ui/dialogs/ExportInstanceDialog.h
|
||||
ui/dialogs/IconPickerDialog.cpp
|
||||
ui/dialogs/IconPickerDialog.h
|
||||
ui/dialogs/ImportResourcePackDialog.cpp
|
||||
ui/dialogs/ImportResourcePackDialog.h
|
||||
ui/dialogs/LoginDialog.cpp
|
||||
ui/dialogs/LoginDialog.h
|
||||
ui/dialogs/MSALoginDialog.cpp
|
||||
@ -922,6 +942,7 @@ qt_wrap_ui(LAUNCHER_UI
|
||||
ui/pages/instance/OtherLogsPage.ui
|
||||
ui/pages/instance/InstanceSettingsPage.ui
|
||||
ui/pages/instance/VersionPage.ui
|
||||
ui/pages/instance/ManagedPackPage.ui
|
||||
ui/pages/instance/WorldListPage.ui
|
||||
ui/pages/instance/ScreenshotsPage.ui
|
||||
ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui
|
||||
@ -949,6 +970,7 @@ qt_wrap_ui(LAUNCHER_UI
|
||||
ui/dialogs/SkinUploadDialog.ui
|
||||
ui/dialogs/ExportInstanceDialog.ui
|
||||
ui/dialogs/IconPickerDialog.ui
|
||||
ui/dialogs/ImportResourcePackDialog.ui
|
||||
ui/dialogs/MSALoginDialog.ui
|
||||
ui/dialogs/OfflineLoginDialog.ui
|
||||
ui/dialogs/ElybyLoginDialog.ui
|
||||
@ -968,6 +990,8 @@ qt_add_resources(LAUNCHER_RESOURCES
|
||||
resources/pe_light/pe_light.qrc
|
||||
resources/pe_colored/pe_colored.qrc
|
||||
resources/pe_blue/pe_blue.qrc
|
||||
resources/breeze_dark/breeze_dark.qrc
|
||||
resources/breeze_light/breeze_light.qrc
|
||||
resources/OSX/OSX.qrc
|
||||
resources/iOS/iOS.qrc
|
||||
resources/flat/flat.qrc
|
||||
@ -1153,6 +1177,8 @@ if(INSTALL_BUNDLE STREQUAL "full")
|
||||
CONFIGURATIONS Debug RelWithDebInfo ""
|
||||
DESTINATION ${PLUGIN_DEST_DIR}
|
||||
COMPONENT Runtime
|
||||
PATTERN "*qopensslbackend*" EXCLUDE
|
||||
PATTERN "*qcertonlybackend*" EXCLUDE
|
||||
)
|
||||
install(
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/tls"
|
||||
@ -1162,6 +1188,8 @@ if(INSTALL_BUNDLE STREQUAL "full")
|
||||
REGEX "dd\\." EXCLUDE
|
||||
REGEX "_debug\\." EXCLUDE
|
||||
REGEX "\\.dSYM" EXCLUDE
|
||||
PATTERN "*qopensslbackend*" EXCLUDE
|
||||
PATTERN "*qcertonlybackend*" EXCLUDE
|
||||
)
|
||||
endif()
|
||||
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"
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <objbase.h>
|
||||
#include <objidl.h>
|
||||
#include <shlguid.h>
|
||||
@ -152,9 +153,10 @@ bool ensureFolderPathExists(QString foldernamepath)
|
||||
/// @brief Copies a directory and it's contents from src to dest
|
||||
/// @param offset subdirectory form src to copy to dest
|
||||
/// @return if there was an error during the filecopy
|
||||
bool copy::operator()(const QString& offset)
|
||||
bool copy::operator()(const QString& offset, bool dryRun)
|
||||
{
|
||||
using copy_opts = fs::copy_options;
|
||||
m_copied = 0; // reset counter
|
||||
|
||||
// NOTE always deep copy on windows. the alternatives are too messy.
|
||||
#if defined Q_OS_WIN32
|
||||
@ -174,18 +176,21 @@ bool copy::operator()(const QString& offset)
|
||||
|
||||
// Function that'll do the actual copying
|
||||
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;
|
||||
|
||||
auto dst_path = PathCombine(dst, relative_dst_path);
|
||||
ensureFilePathExists(dst_path);
|
||||
|
||||
fs::copy(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), opt, err);
|
||||
if (!dryRun) {
|
||||
ensureFilePathExists(dst_path);
|
||||
fs::copy(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), opt, err);
|
||||
}
|
||||
if (err) {
|
||||
qWarning() << "Failed to copy files:" << QString::fromStdString(err.message());
|
||||
qDebug() << "Source file:" << src_path;
|
||||
qDebug() << "Destination file:" << dst_path;
|
||||
}
|
||||
m_copied++;
|
||||
emit fileCopied(relative_dst_path);
|
||||
};
|
||||
|
||||
// We can't use copy_opts::recursive because we need to take into account the
|
||||
@ -339,12 +344,35 @@ QString getDesktopDir()
|
||||
}
|
||||
|
||||
// Cross-platform Shortcut creation
|
||||
bool createShortCut(QString location, QString dest, QStringList args, QString name, QString icon)
|
||||
bool createShortcut(QString destination, QString target, QStringList args, QString name, QString icon)
|
||||
{
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||
location = PathCombine(location, name + ".desktop");
|
||||
#if defined(Q_OS_MACOS)
|
||||
destination += ".command";
|
||||
|
||||
QFile f(location);
|
||||
QFile f(destination);
|
||||
f.open(QIODevice::WriteOnly | QIODevice::Text);
|
||||
QTextStream stream(&f);
|
||||
|
||||
QString argstring;
|
||||
if (!args.empty())
|
||||
argstring = " \"" + args.join("\" \"") + "\"";
|
||||
|
||||
stream << "#!/bin/bash"
|
||||
<< "\n";
|
||||
stream << "\""
|
||||
<< target
|
||||
<< "\" "
|
||||
<< argstring
|
||||
<< "\n";
|
||||
|
||||
stream.flush();
|
||||
f.close();
|
||||
|
||||
f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | QFileDevice::ExeOther);
|
||||
|
||||
return true;
|
||||
#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||
QFile f(destination);
|
||||
f.open(QIODevice::WriteOnly | QIODevice::Text);
|
||||
QTextStream stream(&f);
|
||||
|
||||
@ -356,10 +384,12 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na
|
||||
<< "\n";
|
||||
stream << "Type=Application"
|
||||
<< "\n";
|
||||
stream << "TryExec=" << dest.toLocal8Bit() << "\n";
|
||||
stream << "Exec=" << dest.toLocal8Bit() << argstring.toLocal8Bit() << "\n";
|
||||
stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n";
|
||||
stream << "Name=" << name.toLocal8Bit() << "\n";
|
||||
stream << "Icon=" << icon.toLocal8Bit() << "\n";
|
||||
if (!icon.isEmpty())
|
||||
{
|
||||
stream << "Icon=" << icon.toLocal8Bit() << "\n";
|
||||
}
|
||||
|
||||
stream.flush();
|
||||
f.close();
|
||||
@ -367,25 +397,132 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na
|
||||
f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | QFileDevice::ExeOther);
|
||||
|
||||
return true;
|
||||
#elif defined Q_OS_WIN
|
||||
// TODO: Fix
|
||||
// QFile file(PathCombine(location, name + ".lnk"));
|
||||
// WCHAR *file_w;
|
||||
// WCHAR *dest_w;
|
||||
// WCHAR *args_w;
|
||||
// file.fileName().toWCharArray(file_w);
|
||||
// dest.toWCharArray(dest_w);
|
||||
#elif defined(Q_OS_WIN)
|
||||
QFileInfo targetInfo(target);
|
||||
|
||||
// QString argStr;
|
||||
// for (int i = 0; i < args.count(); i++)
|
||||
// {
|
||||
// argStr.append(args[i]);
|
||||
// argStr.append(" ");
|
||||
// }
|
||||
// argStr.toWCharArray(args_w);
|
||||
if (!targetInfo.exists())
|
||||
{
|
||||
qWarning() << "Target file does not exist!";
|
||||
return false;
|
||||
}
|
||||
|
||||
// return SUCCEEDED(CreateLink(file_w, dest_w, args_w));
|
||||
return false;
|
||||
target = targetInfo.absoluteFilePath();
|
||||
|
||||
if (target.length() >= MAX_PATH)
|
||||
{
|
||||
qWarning() << "Target file path is too long!";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!icon.isEmpty() && icon.length() >= MAX_PATH)
|
||||
{
|
||||
qWarning() << "Icon path is too long!";
|
||||
return false;
|
||||
}
|
||||
|
||||
destination += ".lnk";
|
||||
|
||||
if (destination.length() >= MAX_PATH)
|
||||
{
|
||||
qWarning() << "Destination path is too long!";
|
||||
return false;
|
||||
}
|
||||
|
||||
QString argStr;
|
||||
int argCount = args.count();
|
||||
for (int i = 0; i < argCount; i++)
|
||||
{
|
||||
if (args[i].contains(' '))
|
||||
{
|
||||
argStr.append('"').append(args[i]).append('"');
|
||||
}
|
||||
else
|
||||
{
|
||||
argStr.append(args[i]);
|
||||
}
|
||||
|
||||
if (i < argCount - 1)
|
||||
{
|
||||
argStr.append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
if (argStr.length() >= MAX_PATH)
|
||||
{
|
||||
qWarning() << "Arguments string is too long!";
|
||||
return false;
|
||||
}
|
||||
|
||||
HRESULT hres;
|
||||
|
||||
// ...yes, you need to initialize the entire COM stack just to make a shortcut
|
||||
hres = CoInitialize(nullptr);
|
||||
if (FAILED(hres))
|
||||
{
|
||||
qWarning() << "Failed to initialize COM!";
|
||||
return false;
|
||||
}
|
||||
|
||||
WCHAR wsz[MAX_PATH];
|
||||
|
||||
IShellLink* psl;
|
||||
|
||||
// create an IShellLink instance - this stores the shortcut's attributes
|
||||
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
|
||||
if (SUCCEEDED(hres))
|
||||
{
|
||||
wmemset(wsz, 0, MAX_PATH);
|
||||
target.toWCharArray(wsz);
|
||||
psl->SetPath(wsz);
|
||||
|
||||
wmemset(wsz, 0, MAX_PATH);
|
||||
argStr.toWCharArray(wsz);
|
||||
psl->SetArguments(wsz);
|
||||
|
||||
wmemset(wsz, 0, MAX_PATH);
|
||||
targetInfo.absolutePath().toWCharArray(wsz);
|
||||
psl->SetWorkingDirectory(wsz); // "Starts in" attribute
|
||||
|
||||
if (!icon.isEmpty())
|
||||
{
|
||||
wmemset(wsz, 0, MAX_PATH);
|
||||
icon.toWCharArray(wsz);
|
||||
psl->SetIconLocation(wsz, 0);
|
||||
}
|
||||
|
||||
// query an IPersistFile interface from our IShellLink instance
|
||||
// this is the interface that will actually let us save the shortcut to disk!
|
||||
IPersistFile* ppf;
|
||||
hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf);
|
||||
if (SUCCEEDED(hres))
|
||||
{
|
||||
wmemset(wsz, 0, MAX_PATH);
|
||||
destination.toWCharArray(wsz);
|
||||
hres = ppf->Save(wsz, TRUE);
|
||||
if (FAILED(hres))
|
||||
{
|
||||
qWarning() << "IPresistFile->Save() failed";
|
||||
qWarning() << "hres = " << hres;
|
||||
}
|
||||
ppf->Release();
|
||||
}
|
||||
else
|
||||
{
|
||||
qWarning() << "Failed to query IPersistFile interface from IShellLink instance";
|
||||
qWarning() << "hres = " << hres;
|
||||
}
|
||||
psl->Release();
|
||||
}
|
||||
else
|
||||
{
|
||||
qWarning() << "Failed to create IShellLink instance";
|
||||
qWarning() << "hres = " << hres;
|
||||
}
|
||||
|
||||
// go away COM, nobody likes you
|
||||
CoUninitialize();
|
||||
|
||||
return SUCCEEDED(hres);
|
||||
#else
|
||||
qWarning("Desktop Shortcuts not supported on your platform!");
|
||||
return false;
|
||||
|
@ -40,6 +40,7 @@
|
||||
|
||||
#include <QDir>
|
||||
#include <QFlags>
|
||||
#include <QObject>
|
||||
|
||||
namespace FS {
|
||||
|
||||
@ -76,9 +77,10 @@ bool ensureFilePathExists(QString filenamepath);
|
||||
bool ensureFolderPathExists(QString filenamepath);
|
||||
|
||||
/// @brief Copies a directory and it's contents from src to dest
|
||||
class copy {
|
||||
class copy : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
copy(const QString& src, const QString& dst)
|
||||
copy(const QString& src, const QString& dst, QObject* parent = nullptr) : QObject(parent)
|
||||
{
|
||||
m_src.setPath(src);
|
||||
m_dst.setPath(dst);
|
||||
@ -88,21 +90,35 @@ class copy {
|
||||
m_followSymlinks = follow;
|
||||
return *this;
|
||||
}
|
||||
copy& blacklist(const IPathMatcher* filter)
|
||||
copy& matcher(const IPathMatcher* filter)
|
||||
{
|
||||
m_blacklist = filter;
|
||||
m_matcher = filter;
|
||||
return *this;
|
||||
}
|
||||
bool operator()() { return operator()(QString()); }
|
||||
copy& whitelist(bool whitelist)
|
||||
{
|
||||
m_whitelist = whitelist;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
|
||||
|
||||
int totalCopied() { return m_copied; }
|
||||
|
||||
signals:
|
||||
void fileCopied(const QString& relativeName);
|
||||
// TODO: maybe add a "shouldCopy" signal in the future?
|
||||
|
||||
private:
|
||||
bool operator()(const QString& offset);
|
||||
bool operator()(const QString& offset, bool dryRun = false);
|
||||
|
||||
private:
|
||||
bool m_followSymlinks = true;
|
||||
const IPathMatcher* m_blacklist = nullptr;
|
||||
const IPathMatcher* m_matcher = nullptr;
|
||||
bool m_whitelist = false;
|
||||
QDir m_src;
|
||||
QDir m_dst;
|
||||
int m_copied;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -156,4 +172,9 @@ QString getDesktopDir();
|
||||
// Overrides one folder with the contents of another, preserving items exclusive to the first folder
|
||||
// Equivalent to doing QDir::rename, but allowing for overrides
|
||||
bool overrideFolder(QString overwritten_path, QString override_path);
|
||||
|
||||
/**
|
||||
* Creates a shortcut to the specified target file at the specified destination path.
|
||||
*/
|
||||
bool createShortcut(QString destination, QString target, QStringList args, QString name, QString icon);
|
||||
}
|
||||
|
@ -25,10 +25,12 @@ void InstanceCopyTask::executeTask()
|
||||
{
|
||||
setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
|
||||
|
||||
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
|
||||
folderCopy.followSymlinks(false).blacklist(m_matcher.get());
|
||||
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]{
|
||||
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
|
||||
folderCopy.followSymlinks(false).matcher(m_matcher.get());
|
||||
|
||||
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), folderCopy);
|
||||
return folderCopy();
|
||||
});
|
||||
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &InstanceCopyTask::copyFinished);
|
||||
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &InstanceCopyTask::copyAborted);
|
||||
m_copyFutureWatcher.setFuture(m_copyFuture);
|
||||
|
@ -25,9 +25,13 @@ void InstanceCreationTask::executeTask()
|
||||
return;
|
||||
|
||||
qWarning() << "Instance creation failed!";
|
||||
if (!m_error_message.isEmpty())
|
||||
if (!m_error_message.isEmpty()) {
|
||||
qWarning() << "Reason: " << m_error_message;
|
||||
emitFailed(tr("Error while creating new instance."));
|
||||
emitFailed(tr("Error while creating new instance:\n%1").arg(m_error_message));
|
||||
} else {
|
||||
emitFailed(tr("Error while creating new instance."));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -55,11 +55,9 @@
|
||||
|
||||
#include <quazip/quazipdir.h>
|
||||
|
||||
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent)
|
||||
{
|
||||
m_sourceUrl = sourceUrl;
|
||||
m_parent = parent;
|
||||
}
|
||||
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent, QMap<QString, QString>&& extra_info)
|
||||
: m_sourceUrl(sourceUrl), m_extra_info(extra_info), m_parent(parent)
|
||||
{}
|
||||
|
||||
bool InstanceImportTask::abort()
|
||||
{
|
||||
@ -164,18 +162,14 @@ void InstanceImportTask::processZipPack()
|
||||
}
|
||||
else
|
||||
{
|
||||
QString mmcRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg");
|
||||
QString flameRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json");
|
||||
QStringList paths_to_ignore { "overrides/" };
|
||||
|
||||
if (!mmcRoot.isNull())
|
||||
{
|
||||
if (QString mmcRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg", paths_to_ignore); !mmcRoot.isNull()) {
|
||||
// process as MultiMC instance/pack
|
||||
qDebug() << "MultiMC:" << mmcRoot;
|
||||
root = mmcRoot;
|
||||
m_modpackType = ModpackType::MultiMC;
|
||||
}
|
||||
else if(!flameRoot.isNull())
|
||||
{
|
||||
} else if (QString flameRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json", paths_to_ignore); !flameRoot.isNull()) {
|
||||
// process as Flame pack
|
||||
qDebug() << "Flame:" << flameRoot;
|
||||
root = flameRoot;
|
||||
@ -263,14 +257,34 @@ void InstanceImportTask::extractAborted()
|
||||
|
||||
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->setIcon(m_instIcon);
|
||||
inst_creation_task->setGroup(m_instGroup);
|
||||
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
|
||||
|
||||
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();
|
||||
});
|
||||
connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed);
|
||||
@ -327,14 +341,41 @@ void InstanceImportTask::processMultiMC()
|
||||
|
||||
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->setIcon(m_instIcon);
|
||||
inst_creation_task->setGroup(m_instGroup);
|
||||
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
|
||||
|
||||
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();
|
||||
});
|
||||
connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed);
|
||||
|
@ -56,7 +56,7 @@ class InstanceImportTask : public InstanceTask
|
||||
{
|
||||
Q_OBJECT
|
||||
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;
|
||||
const QVector<Flame::File> &getBlockedFiles() const
|
||||
@ -101,6 +101,10 @@ private: /* data */
|
||||
Modrinth,
|
||||
} 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
|
||||
QWidget* m_parent;
|
||||
};
|
||||
|
@ -816,7 +816,7 @@ class InstanceStaging : public Task {
|
||||
void childSucceded()
|
||||
{
|
||||
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();
|
||||
return;
|
||||
@ -880,25 +880,22 @@ QString InstanceList::getStagedInstancePath()
|
||||
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;
|
||||
QString instID;
|
||||
InstancePtr inst;
|
||||
|
||||
auto should_override = commiting.shouldOverride();
|
||||
|
||||
if (should_override) {
|
||||
// This is to avoid problems when the instance folder gets manually renamed
|
||||
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(), '-');
|
||||
}
|
||||
instID = commiting.originalInstanceID();
|
||||
} else {
|
||||
instID = FS::DirNameFromString(instanceName.modifiedName(), m_instDir);
|
||||
}
|
||||
|
||||
Q_ASSERT(!instID.isEmpty());
|
||||
|
||||
{
|
||||
WatchLock lock(m_watcher, m_instDir);
|
||||
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
|
||||
* - 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.
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "ui/pages/BasePageProvider.h"
|
||||
#include "ui/pages/instance/LogPage.h"
|
||||
#include "ui/pages/instance/VersionPage.h"
|
||||
#include "ui/pages/instance/ManagedPackPage.h"
|
||||
#include "ui/pages/instance/ModFolderPage.h"
|
||||
#include "ui/pages/instance/ResourcePackPage.h"
|
||||
#include "ui/pages/instance/TexturePackPage.h"
|
||||
@ -33,6 +34,7 @@ public:
|
||||
values.append(new LogPage(inst));
|
||||
std::shared_ptr<MinecraftInstance> onesix = std::dynamic_pointer_cast<MinecraftInstance>(inst);
|
||||
values.append(new VersionPage(onesix.get()));
|
||||
values.append(ManagedPackPage::createPage(onesix.get()));
|
||||
auto modsPage = new ModFolderPage(onesix.get(), onesix->loaderModList());
|
||||
modsPage->setFilter("%1 (*.zip *.jar *.litemod)");
|
||||
values.append(modsPage);
|
||||
|
@ -18,6 +18,29 @@ InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& ol
|
||||
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
|
||||
{
|
||||
if (!m_modified_name.isEmpty())
|
||||
|
@ -6,6 +6,8 @@
|
||||
/* Helpers */
|
||||
enum class InstanceNameChange { ShouldChange, ShouldKeep };
|
||||
[[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 {
|
||||
public:
|
||||
@ -42,10 +44,20 @@ class InstanceTask : public Task, public InstanceName {
|
||||
void setGroup(const QString& group) { m_instGroup = group; }
|
||||
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; }
|
||||
|
||||
[[nodiscard]] QString originalInstanceID() const { return m_original_instance_id; };
|
||||
|
||||
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 */
|
||||
SettingsObjectPtr m_globalSettings;
|
||||
@ -54,4 +66,7 @@ class InstanceTask : public Task, public InstanceName {
|
||||
QString m_stagingPath;
|
||||
|
||||
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);
|
||||
if((hours == 0)&&(days == 0))
|
||||
{
|
||||
return QObject::tr("%1m %2s").arg(minutes).arg(seconds);
|
||||
return QObject::tr("%1min %2s").arg(minutes).arg(seconds);
|
||||
}
|
||||
if (days == 0)
|
||||
{
|
||||
return QObject::tr("%1h %2m").arg(hours).arg(minutes);
|
||||
return QObject::tr("%1h %2min").arg(hours).arg(minutes);
|
||||
}
|
||||
return QObject::tr("%1d %2h %3m").arg(days).arg(hours).arg(minutes);
|
||||
return QObject::tr("%1d %2h %3min").arg(days).arg(hours).arg(minutes);
|
||||
}
|
||||
|
@ -39,6 +39,7 @@
|
||||
#include "MMCZip.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
|
||||
// ours
|
||||
@ -228,23 +229,27 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
|
||||
}
|
||||
|
||||
// ours
|
||||
QString MMCZip::findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root)
|
||||
QString MMCZip::findFolderOfFileInZip(QuaZip* zip, const QString& what, const QStringList& ignore_paths, const QString& root)
|
||||
{
|
||||
QuaZipDir rootDir(zip, root);
|
||||
for(auto fileName: rootDir.entryList(QDir::Files))
|
||||
{
|
||||
if(fileName == what)
|
||||
for (auto&& fileName : rootDir.entryList(QDir::Files)) {
|
||||
if (fileName == what)
|
||||
return root;
|
||||
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
for(auto fileName: rootDir.entryList(QDir::Dirs))
|
||||
{
|
||||
QString result = findFolderOfFileInZip(zip, what, root + fileName);
|
||||
if(!result.isEmpty())
|
||||
{
|
||||
|
||||
// Recurse the search to non-ignored subfolders
|
||||
for (auto&& fileName : rootDir.entryList(QDir::Dirs)) {
|
||||
if (ignore_paths.contains(fileName))
|
||||
continue;
|
||||
|
||||
QString result = findFolderOfFileInZip(zip, what, ignore_paths, root + fileName);
|
||||
if (!result.isEmpty())
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// ours
|
||||
|
@ -80,9 +80,11 @@ namespace MMCZip
|
||||
/**
|
||||
* Find a single file in archive by file name (not path)
|
||||
*
|
||||
* \param ignore_paths paths to skip when recursing the search
|
||||
*
|
||||
* \return the path prefix where the file is
|
||||
*/
|
||||
QString findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root = QString(""));
|
||||
QString findFolderOfFileInZip(QuaZip * zip, const QString & what, const QStringList& ignore_paths = {}, const QString &root = QString(""));
|
||||
|
||||
/**
|
||||
* Find a multiple files of the same name in archive by file name
|
||||
|
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
|
||||
{
|
||||
return m_columns.size();
|
||||
return parent.isValid() ? 0 : m_columns.size();
|
||||
}
|
||||
|
||||
int VersionProxyModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
if(sourceModel())
|
||||
{
|
||||
return sourceModel()->rowCount();
|
||||
return sourceModel()->rowCount(parent);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -242,7 +242,7 @@ Qt::DropActions IconList::supportedDropActions() const
|
||||
return Qt::CopyAction;
|
||||
}
|
||||
|
||||
bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
|
||||
bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[maybe_unused]] int row, [[maybe_unused]] int column, [[maybe_unused]] const QModelIndex &parent)
|
||||
{
|
||||
if (action == Qt::IgnoreAction)
|
||||
return true;
|
||||
@ -302,7 +302,7 @@ QVariant IconList::data(const QModelIndex &index, int role) const
|
||||
|
||||
int IconList::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return icons.size();
|
||||
return parent.isValid() ? 0 : icons.size();
|
||||
}
|
||||
|
||||
void IconList::installIcons(const QStringList &iconFiles)
|
||||
@ -354,15 +354,18 @@ const MMCIcon *IconList::icon(const QString &key) const
|
||||
|
||||
bool IconList::deleteIcon(const QString &key)
|
||||
{
|
||||
int iconIdx = getIconIndex(key);
|
||||
if (iconIdx == -1)
|
||||
if (!iconFileExists(key))
|
||||
return false;
|
||||
auto &iconEntry = icons[iconIdx];
|
||||
if (iconEntry.has(IconType::FileBased))
|
||||
{
|
||||
return QFile::remove(iconEntry.m_images[IconType::FileBased].filename);
|
||||
}
|
||||
return false;
|
||||
|
||||
return QFile::remove(icon(key)->getFilePath());
|
||||
}
|
||||
|
||||
bool IconList::trashIcon(const QString &key)
|
||||
{
|
||||
if (!iconFileExists(key))
|
||||
return false;
|
||||
|
||||
return FS::trash(icon(key)->getFilePath(), nullptr);
|
||||
}
|
||||
|
||||
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);
|
||||
void saveIcon(const QString &key, const QString &path, const char * format) const;
|
||||
bool deleteIcon(const QString &key);
|
||||
bool trashIcon(const QString &key);
|
||||
bool iconFileExists(const QString &key) const;
|
||||
|
||||
void installIcons(const QStringList &iconFiles);
|
||||
|
@ -81,6 +81,8 @@ int main(int argc, char *argv[])
|
||||
Q_INIT_RESOURCE(pe_light);
|
||||
Q_INIT_RESOURCE(pe_blue);
|
||||
Q_INIT_RESOURCE(pe_colored);
|
||||
Q_INIT_RESOURCE(breeze_dark);
|
||||
Q_INIT_RESOURCE(breeze_light);
|
||||
Q_INIT_RESOURCE(OSX);
|
||||
Q_INIT_RESOURCE(iOS);
|
||||
Q_INIT_RESOURCE(flat);
|
||||
|
@ -58,11 +58,11 @@ QVariant Index::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
int Index::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return m_lists.size();
|
||||
return parent.isValid() ? 0 : m_lists.size();
|
||||
}
|
||||
int Index::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return 1;
|
||||
return parent.isValid() ? 0 : 1;
|
||||
}
|
||||
QVariant Index::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
|
@ -60,11 +60,6 @@ struct Require
|
||||
QString suggests;
|
||||
};
|
||||
|
||||
inline Q_DECL_PURE_FUNCTION uint qHash(const Require &key, uint seed = 0) Q_DECL_NOTHROW
|
||||
{
|
||||
return qHash(key.uid, seed);
|
||||
}
|
||||
|
||||
using RequireSet = std::set<Require>;
|
||||
|
||||
void parseIndex(const QJsonObject &obj, Index *ptr);
|
||||
|
@ -10,7 +10,7 @@ typedef std::shared_ptr<Agent> AgentPtr;
|
||||
|
||||
class Agent {
|
||||
public:
|
||||
Agent(LibraryPtr library, QString &argument)
|
||||
Agent(LibraryPtr library, const QString &argument)
|
||||
{
|
||||
m_library = library;
|
||||
m_argument = argument;
|
||||
|
@ -1,8 +1,9 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -87,6 +88,10 @@
|
||||
#include "minecraft/gameoptions/GameOptions.h"
|
||||
#include "minecraft/update/FoldersTask.h"
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
#include "MangoHud.h"
|
||||
#endif
|
||||
|
||||
#define IBUS "@im=ibus"
|
||||
|
||||
// all of this because keeping things compatible with deprecated old settings
|
||||
@ -444,6 +449,17 @@ QStringList MinecraftInstance::javaArguments()
|
||||
return args;
|
||||
}
|
||||
|
||||
QString MinecraftInstance::getLauncher()
|
||||
{
|
||||
auto profile = m_components->getProfile();
|
||||
|
||||
// use legacy launcher if the traits are set
|
||||
if (profile->getTraits().contains("legacyLaunch") || profile->getTraits().contains("alphaLaunch"))
|
||||
return "legacy";
|
||||
|
||||
return "standard";
|
||||
}
|
||||
|
||||
QMap<QString, QString> MinecraftInstance::getVariables()
|
||||
{
|
||||
QMap<QString, QString> out;
|
||||
@ -478,9 +494,22 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
|
||||
#ifdef Q_OS_LINUX
|
||||
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");
|
||||
}
|
||||
|
||||
@ -635,26 +664,13 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
|
||||
launchScript += "sessionId " + session->session + "\n";
|
||||
}
|
||||
|
||||
// libraries and class path.
|
||||
{
|
||||
QStringList jars, nativeJars;
|
||||
profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot());
|
||||
for(auto file: jars)
|
||||
{
|
||||
launchScript += "cp " + file + "\n";
|
||||
}
|
||||
for(auto file: nativeJars)
|
||||
{
|
||||
launchScript += "ext " + file + "\n";
|
||||
}
|
||||
launchScript += "natives " + getNativePath() + "\n";
|
||||
}
|
||||
|
||||
for (auto trait : profile->getTraits())
|
||||
{
|
||||
launchScript += "traits " + trait + "\n";
|
||||
}
|
||||
launchScript += "launcher onesix\n";
|
||||
|
||||
launchScript += "launcher " + getLauncher() + "\n";
|
||||
|
||||
// qDebug() << "Generated launch script:" << launchScript;
|
||||
return launchScript;
|
||||
}
|
||||
@ -790,6 +806,8 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
|
||||
out << "Window size: " + QString::number(width) + " x " + QString::number(height);
|
||||
}
|
||||
out << "";
|
||||
out << "Launcher: " + getLauncher();
|
||||
out << "";
|
||||
return out;
|
||||
}
|
||||
|
||||
@ -1124,8 +1142,6 @@ std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList() c
|
||||
if (!m_resource_pack_list)
|
||||
{
|
||||
m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir()));
|
||||
m_resource_pack_list->enableInteraction(!isRunning());
|
||||
connect(this, &BaseInstance::runningStatusChanged, m_resource_pack_list.get(), &ResourcePackFolderModel::disableInteraction);
|
||||
}
|
||||
return m_resource_pack_list;
|
||||
}
|
||||
@ -1135,8 +1151,6 @@ std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList() con
|
||||
if (!m_texture_pack_list)
|
||||
{
|
||||
m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir()));
|
||||
m_texture_pack_list->disableInteraction(isRunning());
|
||||
connect(this, &BaseInstance::runningStatusChanged, m_texture_pack_list.get(), &ModFolderModel::disableInteraction);
|
||||
}
|
||||
return m_texture_pack_list;
|
||||
}
|
||||
@ -1146,8 +1160,6 @@ std::shared_ptr<ShaderPackFolderModel> MinecraftInstance::shaderPackList() const
|
||||
if (!m_shader_pack_list)
|
||||
{
|
||||
m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir()));
|
||||
m_shader_pack_list->disableInteraction(isRunning());
|
||||
connect(this, &BaseInstance::runningStatusChanged, m_shader_pack_list.get(), &ModFolderModel::disableInteraction);
|
||||
}
|
||||
return m_shader_pack_list;
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -131,6 +132,7 @@ public:
|
||||
QString createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin);
|
||||
/// get arguments passed to java
|
||||
QStringList javaArguments();
|
||||
QString getLauncher();
|
||||
|
||||
/// get variables for launch command variable substitution/environment
|
||||
QMap<QString, QString> getVariables() override;
|
||||
|
@ -135,7 +135,7 @@ QJsonObject libDownloadInfoToJson(MojangLibraryDownloadInfo::Ptr libinfo)
|
||||
{
|
||||
out.insert("artifact", downloadInfoToJson(libinfo->artifact));
|
||||
}
|
||||
if(libinfo->classifiers.size())
|
||||
if(!libinfo->classifiers.isEmpty())
|
||||
{
|
||||
QJsonObject classifiersOut;
|
||||
for(auto iter = libinfo->classifiers.begin(); iter != libinfo->classifiers.end(); iter++)
|
||||
@ -297,7 +297,7 @@ void MojangVersionFormat::writeVersionProperties(const VersionFile* in, QJsonObj
|
||||
{
|
||||
out.insert("assetIndex", assetIndexToJson(in->mojangAssetIndex));
|
||||
}
|
||||
if(in->mojangDownloads.size())
|
||||
if(!in->mojangDownloads.isEmpty())
|
||||
{
|
||||
QJsonObject downloadsOut;
|
||||
for(auto iter = in->mojangDownloads.begin(); iter != in->mojangDownloads.end(); iter++)
|
||||
@ -306,6 +306,15 @@ void MojangVersionFormat::writeVersionProperties(const VersionFile* in, QJsonObj
|
||||
}
|
||||
out.insert("downloads", downloadsOut);
|
||||
}
|
||||
if(!in->compatibleJavaMajors.isEmpty())
|
||||
{
|
||||
QJsonArray compatibleJavaMajorsOut;
|
||||
for(auto compatibleJavaMajor : in->compatibleJavaMajors)
|
||||
{
|
||||
compatibleJavaMajorsOut.append(compatibleJavaMajor);
|
||||
}
|
||||
out.insert("compatibleJavaMajors", compatibleJavaMajorsOut);
|
||||
}
|
||||
}
|
||||
|
||||
QJsonDocument MojangVersionFormat::versionFileToJson(const VersionFilePtr &patch)
|
||||
@ -396,7 +405,7 @@ QJsonObject MojangVersionFormat::libraryToJson(Library *library)
|
||||
iter++;
|
||||
}
|
||||
libRoot.insert("natives", nativeList);
|
||||
if (library->m_extractExcludes.size())
|
||||
if (!library->m_extractExcludes.isEmpty())
|
||||
{
|
||||
QJsonArray excludes;
|
||||
QJsonObject extract;
|
||||
@ -408,7 +417,7 @@ QJsonObject MojangVersionFormat::libraryToJson(Library *library)
|
||||
libRoot.insert("extract", extract);
|
||||
}
|
||||
}
|
||||
if (library->m_rules.size())
|
||||
if (!library->m_rules.isEmpty())
|
||||
{
|
||||
QJsonArray allRules;
|
||||
for (auto &rule : library->m_rules)
|
||||
|
@ -63,13 +63,13 @@ LibraryPtr OneSixVersionFormat::libraryFromJson(ProblemContainer & problems, con
|
||||
QJsonObject OneSixVersionFormat::libraryToJson(Library *library)
|
||||
{
|
||||
QJsonObject libRoot = MojangVersionFormat::libraryToJson(library);
|
||||
if (library->m_absoluteURL.size())
|
||||
if (!library->m_absoluteURL.isEmpty())
|
||||
libRoot.insert("MMC-absoluteUrl", library->m_absoluteURL);
|
||||
if (library->m_hint.size())
|
||||
if (!library->m_hint.isEmpty())
|
||||
libRoot.insert("MMC-hint", library->m_hint);
|
||||
if (library->m_filename.size())
|
||||
if (!library->m_filename.isEmpty())
|
||||
libRoot.insert("MMC-filename", library->m_filename);
|
||||
if (library->m_displayname.size())
|
||||
if (!library->m_displayname.isEmpty())
|
||||
libRoot.insert("MMC-displayname", library->m_displayname);
|
||||
return libRoot;
|
||||
}
|
||||
@ -225,11 +225,10 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
|
||||
{
|
||||
QJsonObject agentObj = requireObject(agentVal);
|
||||
auto lib = libraryFromJson(*out, agentObj, filename);
|
||||
|
||||
QString arg = "";
|
||||
if (agentObj.contains("argument"))
|
||||
{
|
||||
readString(agentObj, "argument", arg);
|
||||
}
|
||||
readString(agentObj, "argument", arg);
|
||||
|
||||
AgentPtr agent(new Agent(lib, arg));
|
||||
out->agents.append(agent);
|
||||
}
|
||||
@ -332,6 +331,20 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch
|
||||
writeString(root, "appletClass", patch->appletClass);
|
||||
writeStringList(root, "+tweakers", patch->addTweakers);
|
||||
writeStringList(root, "+traits", patch->traits.values());
|
||||
writeStringList(root, "+jvmArgs", patch->addnJvmArguments);
|
||||
if (!patch->agents.isEmpty())
|
||||
{
|
||||
QJsonArray array;
|
||||
for (auto value: patch->agents)
|
||||
{
|
||||
QJsonObject agentOut = OneSixVersionFormat::libraryToJson(value->library().get());
|
||||
if (!value->argument().isEmpty())
|
||||
agentOut.insert("argument", value->argument());
|
||||
|
||||
array.append(agentOut);
|
||||
}
|
||||
root.insert("+agents", array);
|
||||
}
|
||||
if (!patch->libraries.isEmpty())
|
||||
{
|
||||
QJsonArray array;
|
||||
|
@ -1,7 +1,8 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -47,7 +48,6 @@
|
||||
#include "Exception.h"
|
||||
#include "minecraft/OneSixVersionFormat.h"
|
||||
#include "FileSystem.h"
|
||||
#include "meta/Index.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "Json.h"
|
||||
|
||||
@ -55,7 +55,6 @@
|
||||
#include "PackProfile_p.h"
|
||||
#include "ComponentUpdateTask.h"
|
||||
|
||||
#include "Application.h"
|
||||
#include "modplatform/ModAPI.h"
|
||||
|
||||
static const QMap<QString, ModAPI::ModLoaderType> modloaderMapping{
|
||||
@ -613,7 +612,7 @@ QVariant PackProfile::data(const QModelIndex &index, int role) const
|
||||
|
||||
bool PackProfile::setData(const QModelIndex& index, const QVariant& value, int role)
|
||||
{
|
||||
if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index))
|
||||
if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index.parent()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -675,12 +674,12 @@ Qt::ItemFlags PackProfile::flags(const QModelIndex &index) const
|
||||
|
||||
int PackProfile::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return d->components.size();
|
||||
return parent.isValid() ? 0 : d->components.size();
|
||||
}
|
||||
|
||||
int PackProfile::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return NUM_COLUMNS;
|
||||
return parent.isValid() ? 0 : NUM_COLUMNS;
|
||||
}
|
||||
|
||||
void PackProfile::move(const int index, const MoveDirection direction)
|
||||
@ -738,6 +737,11 @@ void PackProfile::installCustomJar(QString selectedFile)
|
||||
installCustomJar_internal(selectedFile);
|
||||
}
|
||||
|
||||
void PackProfile::installAgents(QStringList selectedFiles)
|
||||
{
|
||||
installAgents_internal(selectedFiles);
|
||||
}
|
||||
|
||||
bool PackProfile::installEmpty(const QString& uid, const QString& name)
|
||||
{
|
||||
QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches");
|
||||
@ -832,18 +836,14 @@ bool PackProfile::installJarMods_internal(QStringList filepaths)
|
||||
for(auto filepath:filepaths)
|
||||
{
|
||||
QFileInfo sourceInfo(filepath);
|
||||
auto uuid = QUuid::createUuid();
|
||||
QString id = uuid.toString().remove('{').remove('}');
|
||||
QString id = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
QString target_filename = id + ".jar";
|
||||
QString target_id = "org.multimc.jarmod." + id;
|
||||
QString target_id = "custom.jarmod." + id;
|
||||
QString target_name = sourceInfo.completeBaseName() + " (jar mod)";
|
||||
QString finalPath = FS::PathCombine(d->m_instance->jarModsDir(), target_filename);
|
||||
|
||||
QFileInfo targetInfo(finalPath);
|
||||
if(targetInfo.exists())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Q_ASSERT(!targetInfo.exists());
|
||||
|
||||
if (!QFile::copy(sourceInfo.absoluteFilePath(),QFileInfo(finalPath).absoluteFilePath()))
|
||||
{
|
||||
@ -852,7 +852,7 @@ bool PackProfile::installJarMods_internal(QStringList filepaths)
|
||||
|
||||
auto f = std::make_shared<VersionFile>();
|
||||
auto jarMod = std::make_shared<Library>();
|
||||
jarMod->setRawName(GradleSpecifier("org.multimc.jarmods:" + id + ":1"));
|
||||
jarMod->setRawName(GradleSpecifier("custom.jarmods:" + id + ":1"));
|
||||
jarMod->setFilename(target_filename);
|
||||
jarMod->setDisplayName(sourceInfo.completeBaseName());
|
||||
jarMod->setHint("local");
|
||||
@ -892,7 +892,7 @@ bool PackProfile::installCustomJar_internal(QString filepath)
|
||||
return false;
|
||||
}
|
||||
|
||||
auto specifier = GradleSpecifier("org.multimc:customjar:1");
|
||||
auto specifier = GradleSpecifier("custom:customjar:1");
|
||||
QFileInfo sourceInfo(filepath);
|
||||
QString target_filename = specifier.getFileName();
|
||||
QString target_id = specifier.artifactId();
|
||||
@ -939,6 +939,64 @@ bool PackProfile::installCustomJar_internal(QString filepath)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PackProfile::installAgents_internal(QStringList filepaths)
|
||||
{
|
||||
// FIXME code duplication
|
||||
const QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches");
|
||||
if (!FS::ensureFolderPathExists(patchDir))
|
||||
return false;
|
||||
|
||||
const QString libDir = d->m_instance->getLocalLibraryPath();
|
||||
if (!FS::ensureFolderPathExists(libDir))
|
||||
return false;
|
||||
|
||||
for (const QString& source : filepaths) {
|
||||
const QFileInfo sourceInfo(source);
|
||||
const QString id = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
const QString targetBaseName = id + ".jar";
|
||||
const QString targetId = "custom.agent." + id;
|
||||
const QString targetName = sourceInfo.completeBaseName() + " (agent)";
|
||||
const QString target = FS::PathCombine(d->m_instance->getLocalLibraryPath(), targetBaseName);
|
||||
|
||||
const QFileInfo targetInfo(target);
|
||||
Q_ASSERT(!targetInfo.exists());
|
||||
|
||||
if (!QFile::copy(source, target))
|
||||
return false;
|
||||
|
||||
auto versionFile = std::make_shared<VersionFile>();
|
||||
|
||||
auto agent = std::make_shared<Library>();
|
||||
|
||||
agent->setRawName("custom.agents:" + id + ":1");
|
||||
agent->setFilename(targetBaseName);
|
||||
agent->setDisplayName(sourceInfo.completeBaseName());
|
||||
agent->setHint("local");
|
||||
|
||||
versionFile->agents.append(std::make_shared<Agent>(agent, QString()));
|
||||
|
||||
versionFile->name = targetName;
|
||||
versionFile->uid = targetId;
|
||||
|
||||
QFile patchFile(FS::PathCombine(patchDir, targetId + ".json"));
|
||||
|
||||
if (!patchFile.open(QFile::WriteOnly)) {
|
||||
qCritical() << "Error opening" << patchFile.fileName() << "for reading:" << patchFile.errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
patchFile.write(OneSixVersionFormat::versionFileToJson(versionFile).toJson());
|
||||
patchFile.close();
|
||||
|
||||
appendComponent(new Component(this, versionFile->uid, versionFile));
|
||||
}
|
||||
|
||||
scheduleSave();
|
||||
invalidateLaunchProfile();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<LaunchProfile> PackProfile::getProfile() const
|
||||
{
|
||||
if(!d->m_profile)
|
||||
|
@ -1,7 +1,8 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -85,6 +86,9 @@ public:
|
||||
/// install a jar/zip as a replacement for the main jar
|
||||
void installCustomJar(QString selectedFile);
|
||||
|
||||
/// install Java agent files
|
||||
void installAgents(QStringList selectedFiles);
|
||||
|
||||
enum MoveDirection { MoveUp, MoveDown };
|
||||
/// move component file # up or down the list
|
||||
void move(const int index, const MoveDirection direction);
|
||||
@ -167,6 +171,7 @@ private:
|
||||
bool load();
|
||||
bool installJarMods_internal(QStringList filepaths);
|
||||
bool installCustomJar_internal(QString filepath);
|
||||
bool installAgents_internal(QStringList filepaths);
|
||||
bool removeComponent_internal(ComponentPtr patch);
|
||||
|
||||
private: /* data */
|
||||
|
@ -104,7 +104,7 @@ public:
|
||||
class ImplicitRule : public Rule
|
||||
{
|
||||
protected:
|
||||
virtual bool applies(const Library *, const RuntimeContext & runtimeContext)
|
||||
virtual bool applies(const Library *, [[maybe_unused]] const RuntimeContext & runtimeContext)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -173,7 +173,7 @@ bool WorldList::resetIcon(int row)
|
||||
|
||||
int WorldList::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return 4;
|
||||
return parent.isValid()? 0 : 4;
|
||||
}
|
||||
|
||||
QVariant WorldList::data(const QModelIndex &index, int role) const
|
||||
@ -398,8 +398,8 @@ void WorldList::installWorld(QFileInfo filename)
|
||||
w.install(m_dir.absolutePath());
|
||||
}
|
||||
|
||||
bool WorldList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
|
||||
const QModelIndex &parent)
|
||||
bool WorldList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[maybe_unused]] int row, [[maybe_unused]] int column,
|
||||
[[maybe_unused]] const QModelIndex &parent)
|
||||
{
|
||||
if (action == Qt::IgnoreAction)
|
||||
return true;
|
||||
|
@ -54,7 +54,7 @@ public:
|
||||
|
||||
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const
|
||||
{
|
||||
return size();
|
||||
return parent.isValid() ? 0 : static_cast<int>(size());
|
||||
};
|
||||
virtual QVariant headerData(int section, Qt::Orientation orientation,
|
||||
int role = Qt::DisplayRole) const;
|
||||
|
@ -408,20 +408,20 @@ QVariant AccountList::headerData(int section, Qt::Orientation orientation, int r
|
||||
}
|
||||
}
|
||||
|
||||
int AccountList::rowCount(const QModelIndex &) const
|
||||
int AccountList::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
// Return count
|
||||
return count();
|
||||
return parent.isValid() ? 0 : count();
|
||||
}
|
||||
|
||||
int AccountList::columnCount(const QModelIndex &) const
|
||||
int AccountList::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return NUM_COLUMNS;
|
||||
return parent.isValid() ? 0 : NUM_COLUMNS;
|
||||
}
|
||||
|
||||
Qt::ItemFlags AccountList::flags(const QModelIndex &index) const
|
||||
{
|
||||
if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
|
||||
if (index.row() < 0 || index.row() >= rowCount(index.parent()) || !index.isValid())
|
||||
{
|
||||
return Qt::NoItemFlags;
|
||||
}
|
||||
|
@ -144,7 +144,7 @@ QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, in
|
||||
|
||||
int ModFolderModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return NUM_COLUMNS;
|
||||
return parent.isValid() ? 0 : NUM_COLUMNS;
|
||||
}
|
||||
|
||||
Task* ModFolderModel::createUpdateTask()
|
||||
|
@ -20,6 +20,7 @@ ResourceFolderModel::ResourceFolderModel(QDir dir, QObject* parent) : QAbstractL
|
||||
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
|
||||
|
||||
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &ResourceFolderModel::directoryChanged);
|
||||
connect(&m_helper_thread_task, &ConcurrentTask::finished, this, [this]{ m_helper_thread_task.clear(); });
|
||||
}
|
||||
|
||||
ResourceFolderModel::~ResourceFolderModel()
|
||||
@ -275,7 +276,11 @@ void ResourceFolderModel::resolveResource(Resource* res)
|
||||
connect(
|
||||
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()
|
||||
@ -426,7 +431,7 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
|
||||
bool ResourceFolderModel::setData(const QModelIndex& index, const QVariant& value, int role)
|
||||
{
|
||||
int row = index.row();
|
||||
if (row < 0 || row >= rowCount(index) || !index.isValid())
|
||||
if (row < 0 || row >= rowCount(index.parent()) || !index.isValid())
|
||||
return false;
|
||||
|
||||
if (role == Qt::CheckStateRole)
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "Resource.h"
|
||||
|
||||
#include "tasks/Task.h"
|
||||
#include "tasks/ConcurrentTask.h"
|
||||
|
||||
class QSortFilterProxyModel;
|
||||
|
||||
@ -90,8 +91,8 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||
/* Basic columns */
|
||||
enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, NUM_COLUMNS };
|
||||
|
||||
[[nodiscard]] int rowCount(const QModelIndex& = {}) const override { return size(); }
|
||||
[[nodiscard]] int columnCount(const QModelIndex& = {}) const override { return NUM_COLUMNS; };
|
||||
[[nodiscard]] int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast<int>(size()); }
|
||||
[[nodiscard]] int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NUM_COLUMNS; };
|
||||
|
||||
[[nodiscard]] Qt::DropActions supportedDropActions() const override;
|
||||
|
||||
@ -176,7 +177,7 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||
* if the resource is complex and has more stuff to parse.
|
||||
*/
|
||||
virtual void onParseSucceeded(int ticket, QString resource_id);
|
||||
virtual void onParseFailed(int ticket, QString resource_id) {}
|
||||
virtual void onParseFailed(int ticket, QString resource_id) { Q_UNUSED(ticket); Q_UNUSED(resource_id); }
|
||||
|
||||
protected:
|
||||
// Represents the relationship between a column's index (represented by the list index), and it's sorting key.
|
||||
@ -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.
|
||||
QMap<QString, int> m_resources_index;
|
||||
|
||||
ConcurrentTask m_helper_thread_task;
|
||||
QMap<int, Task::Ptr> m_active_parse_tasks;
|
||||
std::atomic<int> m_next_resolution_ticket = 0;
|
||||
};
|
||||
|
@ -1,9 +1,11 @@
|
||||
#include "ResourcePack.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QMap>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include "MTPixmapCache.h"
|
||||
#include "Version.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") } },
|
||||
{ 5, { Version("1.15"), Version("1.16.1") } }, { 6, { Version("1.16.2"), Version("1.16.5") } },
|
||||
{ 7, { Version("1.17"), Version("1.17.1") } }, { 8, { Version("1.18"), Version("1.18.2") } },
|
||||
{ 9, { Version("1.19"), Version("1.19.2") } },
|
||||
{ 9, { Version("1.19"), Version("1.19.2") } }, { 11, { Version("1.19.3"), Version("1.19.3") } },
|
||||
};
|
||||
|
||||
void ResourcePack::setPackFormat(int new_format_id)
|
||||
@ -43,16 +45,22 @@ void ResourcePack::setImage(QImage new_image)
|
||||
Q_ASSERT(!new_image.isNull());
|
||||
|
||||
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;
|
||||
|
||||
// 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 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())
|
||||
return cached_image;
|
||||
return cached_image.scaled(size);
|
||||
@ -114,3 +122,8 @@ bool ResourcePack::applyFilter(QRegularExpression filter) const
|
||||
|
||||
return Resource::applyFilter(filter);
|
||||
}
|
||||
|
||||
bool ResourcePack::valid() const
|
||||
{
|
||||
return m_pack_format != 0;
|
||||
}
|
||||
|
@ -42,6 +42,8 @@ class ResourcePack : public Resource {
|
||||
/** Thread-safe. */
|
||||
void setImage(QImage new_image);
|
||||
|
||||
bool valid() const override;
|
||||
|
||||
[[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair<int, bool> override;
|
||||
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
|
||||
|
||||
|
@ -137,7 +137,7 @@ QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orient
|
||||
|
||||
int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const
|
||||
{
|
||||
return NUM_COLUMNS;
|
||||
return parent.isValid() ? 0 : NUM_COLUMNS;
|
||||
}
|
||||
|
||||
Task* ResourcePackFolderModel::createUpdateTask()
|
||||
|
@ -62,3 +62,8 @@ QPixmap TexturePack::image(QSize size)
|
||||
TexturePackUtils::process(*this);
|
||||
return image(size);
|
||||
}
|
||||
|
||||
bool TexturePack::valid() const
|
||||
{
|
||||
return m_description != nullptr;
|
||||
}
|
||||
|
@ -48,6 +48,8 @@ class TexturePack : public Resource {
|
||||
/** Thread-safe. */
|
||||
void setImage(QImage new_image);
|
||||
|
||||
bool valid() const override;
|
||||
|
||||
protected:
|
||||
mutable QMutex m_data_lock;
|
||||
|
||||
|
@ -121,7 +121,7 @@ ModDetails ReadMCModTOML(QByteArray contents)
|
||||
return {};
|
||||
}
|
||||
auto modsTable = tomlModsTable0->as_table();
|
||||
if (!tomlModsTable0) {
|
||||
if (!modsTable) {
|
||||
qWarning() << "Corrupted mods.toml? [[mods]] was not a table!";
|
||||
return {};
|
||||
}
|
||||
|
@ -28,14 +28,14 @@
|
||||
|
||||
namespace ResourcePackUtils {
|
||||
|
||||
bool process(ResourcePack& pack)
|
||||
bool process(ResourcePack& pack, ProcessingLevel level)
|
||||
{
|
||||
switch (pack.type()) {
|
||||
case ResourceType::FOLDER:
|
||||
ResourcePackUtils::processFolder(pack);
|
||||
ResourcePackUtils::processFolder(pack, level);
|
||||
return true;
|
||||
case ResourceType::ZIPFILE:
|
||||
ResourcePackUtils::processZIP(pack);
|
||||
ResourcePackUtils::processZIP(pack, level);
|
||||
return true;
|
||||
default:
|
||||
qWarning() << "Invalid type for resource pack parse task!";
|
||||
@ -43,7 +43,7 @@ bool process(ResourcePack& pack)
|
||||
}
|
||||
}
|
||||
|
||||
void processFolder(ResourcePack& pack)
|
||||
void processFolder(ResourcePack& pack, ProcessingLevel level)
|
||||
{
|
||||
Q_ASSERT(pack.type() == ResourceType::FOLDER);
|
||||
|
||||
@ -60,6 +60,9 @@ void processFolder(ResourcePack& pack)
|
||||
mcmeta_file.close();
|
||||
}
|
||||
|
||||
if (level == ProcessingLevel::BasicInfoOnly)
|
||||
return;
|
||||
|
||||
QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
|
||||
if (image_file_info.isFile()) {
|
||||
QFile mcmeta_file(image_file_info.filePath());
|
||||
@ -74,7 +77,7 @@ void processFolder(ResourcePack& pack)
|
||||
}
|
||||
}
|
||||
|
||||
void processZIP(ResourcePack& pack)
|
||||
void processZIP(ResourcePack& pack, ProcessingLevel level)
|
||||
{
|
||||
Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
|
||||
|
||||
@ -98,6 +101,11 @@ void processZIP(ResourcePack& pack)
|
||||
file.close();
|
||||
}
|
||||
|
||||
if (level == ProcessingLevel::BasicInfoOnly) {
|
||||
zip.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (zip.setCurrentFile("pack.png")) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Failed to open file in zip.";
|
||||
@ -138,6 +146,13 @@ void processPackPNG(ResourcePack& pack, QByteArray&& raw_data)
|
||||
qWarning() << "Failed to parse pack.png.";
|
||||
}
|
||||
}
|
||||
|
||||
bool validate(QFileInfo file)
|
||||
{
|
||||
ResourcePack rp{ file };
|
||||
return ResourcePackUtils::process(rp, ProcessingLevel::BasicInfoOnly) && rp.valid();
|
||||
}
|
||||
|
||||
} // namespace ResourcePackUtils
|
||||
|
||||
LocalResourcePackParseTask::LocalResourcePackParseTask(int token, ResourcePack& rp)
|
||||
@ -152,8 +167,6 @@ bool LocalResourcePackParseTask::abort()
|
||||
|
||||
void LocalResourcePackParseTask::executeTask()
|
||||
{
|
||||
Q_ASSERT(m_resource_pack.valid());
|
||||
|
||||
if (!ResourcePackUtils::process(m_resource_pack))
|
||||
return;
|
||||
|
||||
|
@ -26,13 +26,19 @@
|
||||
#include "tasks/Task.h"
|
||||
|
||||
namespace ResourcePackUtils {
|
||||
bool process(ResourcePack& pack);
|
||||
|
||||
void processZIP(ResourcePack& pack);
|
||||
void processFolder(ResourcePack& pack);
|
||||
enum class ProcessingLevel { Full, BasicInfoOnly };
|
||||
|
||||
bool process(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
|
||||
void processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
void processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
|
||||
void processMCMeta(ResourcePack& pack, QByteArray&& raw_data);
|
||||
void processPackPNG(ResourcePack& pack, QByteArray&& raw_data);
|
||||
|
||||
/** Checks whether a file is valid as a resource pack or not. */
|
||||
bool validate(QFileInfo file);
|
||||
} // namespace ResourcePackUtils
|
||||
|
||||
class LocalResourcePackParseTask : public Task {
|
||||
|
@ -28,14 +28,14 @@
|
||||
|
||||
namespace TexturePackUtils {
|
||||
|
||||
bool process(TexturePack& pack)
|
||||
bool process(TexturePack& pack, ProcessingLevel level)
|
||||
{
|
||||
switch (pack.type()) {
|
||||
case ResourceType::FOLDER:
|
||||
TexturePackUtils::processFolder(pack);
|
||||
TexturePackUtils::processFolder(pack, level);
|
||||
return true;
|
||||
case ResourceType::ZIPFILE:
|
||||
TexturePackUtils::processZIP(pack);
|
||||
TexturePackUtils::processZIP(pack, level);
|
||||
return true;
|
||||
default:
|
||||
qWarning() << "Invalid type for resource pack parse task!";
|
||||
@ -43,7 +43,7 @@ bool process(TexturePack& pack)
|
||||
}
|
||||
}
|
||||
|
||||
void processFolder(TexturePack& pack)
|
||||
void processFolder(TexturePack& pack, ProcessingLevel level)
|
||||
{
|
||||
Q_ASSERT(pack.type() == ResourceType::FOLDER);
|
||||
|
||||
@ -60,6 +60,9 @@ void processFolder(TexturePack& pack)
|
||||
mcmeta_file.close();
|
||||
}
|
||||
|
||||
if (level == ProcessingLevel::BasicInfoOnly)
|
||||
return;
|
||||
|
||||
QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
|
||||
if (image_file_info.isFile()) {
|
||||
QFile mcmeta_file(image_file_info.filePath());
|
||||
@ -74,7 +77,7 @@ void processFolder(TexturePack& pack)
|
||||
}
|
||||
}
|
||||
|
||||
void processZIP(TexturePack& pack)
|
||||
void processZIP(TexturePack& pack, ProcessingLevel level)
|
||||
{
|
||||
Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
|
||||
|
||||
@ -98,6 +101,11 @@ void processZIP(TexturePack& pack)
|
||||
file.close();
|
||||
}
|
||||
|
||||
if (level == ProcessingLevel::BasicInfoOnly) {
|
||||
zip.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (zip.setCurrentFile("pack.png")) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Failed to open file in zip.";
|
||||
@ -129,6 +137,13 @@ void processPackPNG(TexturePack& pack, QByteArray&& raw_data)
|
||||
qWarning() << "Failed to parse pack.png.";
|
||||
}
|
||||
}
|
||||
|
||||
bool validate(QFileInfo file)
|
||||
{
|
||||
TexturePack rp{ file };
|
||||
return TexturePackUtils::process(rp, ProcessingLevel::BasicInfoOnly) && rp.valid();
|
||||
}
|
||||
|
||||
} // namespace TexturePackUtils
|
||||
|
||||
LocalTexturePackParseTask::LocalTexturePackParseTask(int token, TexturePack& rp)
|
||||
@ -143,8 +158,6 @@ bool LocalTexturePackParseTask::abort()
|
||||
|
||||
void LocalTexturePackParseTask::executeTask()
|
||||
{
|
||||
Q_ASSERT(m_texture_pack.valid());
|
||||
|
||||
if (!TexturePackUtils::process(m_texture_pack))
|
||||
return;
|
||||
|
||||
|
@ -27,13 +27,19 @@
|
||||
#include "tasks/Task.h"
|
||||
|
||||
namespace TexturePackUtils {
|
||||
bool process(TexturePack& pack);
|
||||
|
||||
void processZIP(TexturePack& pack);
|
||||
void processFolder(TexturePack& pack);
|
||||
enum class ProcessingLevel { Full, BasicInfoOnly };
|
||||
|
||||
bool process(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
|
||||
void processZIP(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
void processFolder(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
|
||||
void processPackTXT(TexturePack& pack, QByteArray&& raw_data);
|
||||
void processPackPNG(TexturePack& pack, QByteArray&& raw_data);
|
||||
|
||||
/** Checks whether a file is valid as a texture pack or not. */
|
||||
bool validate(QFileInfo file);
|
||||
} // namespace TexturePackUtils
|
||||
|
||||
class LocalTexturePackParseTask : public Task {
|
||||
|
@ -12,7 +12,7 @@ LibrariesTask::LibrariesTask(MinecraftInstance * inst)
|
||||
|
||||
void LibrariesTask::executeTask()
|
||||
{
|
||||
setStatus(tr("Getting the library files from Mojang..."));
|
||||
setStatus(tr("Downloading required library files..."));
|
||||
qDebug() << m_inst->name() << ": downloading libraries";
|
||||
MinecraftInstance *inst = (MinecraftInstance *)m_inst;
|
||||
|
||||
|
@ -42,12 +42,25 @@ void Flame::FileResolvingTask::executeTask()
|
||||
void Flame::FileResolvingTask::netJobFinished()
|
||||
{
|
||||
setProgress(1, 3);
|
||||
int index = 0;
|
||||
// job to check modrinth for blocked projects
|
||||
m_checkJob = new NetJob("Modrinth check", m_network);
|
||||
blockedProjects = QMap<File *,QByteArray *>();
|
||||
auto doc = Json::requireDocument(*result);
|
||||
auto array = Json::requireArray(doc.object()["data"]);
|
||||
|
||||
QJsonDocument doc;
|
||||
QJsonArray array;
|
||||
|
||||
try {
|
||||
doc = Json::requireDocument(*result);
|
||||
array = Json::requireArray(doc.object()["data"]);
|
||||
} catch (Json::JsonException& e) {
|
||||
qCritical() << "Non-JSON data returned from the CF API";
|
||||
qCritical() << e.cause();
|
||||
|
||||
emitFailed(tr("Invalid data returned from the API."));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (QJsonValueRef file : array) {
|
||||
auto fileid = Json::requireInteger(Json::requireObject(file)["id"]);
|
||||
auto& out = m_toProcess.files[fileid];
|
||||
@ -68,7 +81,6 @@ void Flame::FileResolvingTask::netJobFinished()
|
||||
blockedProjects.insert(&out, output);
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
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 "modplatform/flame/FlameAPI.h"
|
||||
@ -46,13 +81,19 @@ bool FlameCreationTask::updateInstance()
|
||||
auto instance_list = APPLICATION->instances();
|
||||
|
||||
// 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) {
|
||||
inst = instance_list->getInstanceById(originalName());
|
||||
if (!inst) {
|
||||
inst = instance_list->getInstanceById(originalName());
|
||||
|
||||
if (!inst)
|
||||
return false;
|
||||
if (!inst)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QString index_path(FS::PathCombine(m_stagingPath, "manifest.json"));
|
||||
@ -67,24 +108,14 @@ bool FlameCreationTask::updateInstance()
|
||||
auto version_id = inst->getManagedPackVersionName();
|
||||
auto version_str = !version_id.isEmpty() ? tr(" (version %1)").arg(version_id) : "";
|
||||
|
||||
auto info = CustomMessageBox::selectable(
|
||||
m_parent, tr("Similar modpack was found!"),
|
||||
tr("One or more of your instances are from this same modpack%1. Do you want to create a "
|
||||
"separate instance, or update the existing one?\n\nNOTE: Make sure you made a backup of your important instance data before "
|
||||
"updating, as worlds can be corrupted and some configuration may be lost (due to pack overrides).")
|
||||
.arg(version_str), QMessageBox::Information, QMessageBox::Ok | QMessageBox::Reset | QMessageBox::Abort);
|
||||
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;
|
||||
|
||||
if (info->clickedButton() == info->button(QMessageBox::Reset)) {
|
||||
m_abort = true;
|
||||
return false;
|
||||
if (shouldConfirmUpdate()) {
|
||||
auto should_update = askIfShouldUpdate(m_parent, version_str);
|
||||
if (should_update == ShouldUpdate::SkipUpdating)
|
||||
return false;
|
||||
if (should_update == ShouldUpdate::Cancel) {
|
||||
m_abort = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QDir old_inst_dir(inst->instanceRoot());
|
||||
@ -197,10 +228,10 @@ bool FlameCreationTask::updateInstance()
|
||||
m_process_update_file_info_job = nullptr;
|
||||
} else {
|
||||
// We don't have an old index file, so we may duplicate stuff!
|
||||
auto dialog = CustomMessageBox::selectable(m_parent,
|
||||
tr("No index file."),
|
||||
tr("We couldn't find a suitable index file for the older version. This may cause some of the files to be duplicated. Do you want to continue?"),
|
||||
QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel);
|
||||
auto dialog = CustomMessageBox::selectable(m_parent, tr("No index file."),
|
||||
tr("We couldn't find a suitable index file for the older version. This may cause some "
|
||||
"of the files to be duplicated. Do you want to continue?"),
|
||||
QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel);
|
||||
|
||||
if (dialog->exec() == QDialog::DialogCode::Rejected) {
|
||||
m_abort = true;
|
||||
@ -208,7 +239,7 @@ bool FlameCreationTask::updateInstance()
|
||||
}
|
||||
}
|
||||
|
||||
setOverride(true);
|
||||
setOverride(true, inst->id());
|
||||
qDebug() << "Will override instance!";
|
||||
|
||||
m_instance = inst;
|
||||
@ -330,7 +361,9 @@ bool FlameCreationTask::createInstance()
|
||||
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());
|
||||
|
||||
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) {
|
||||
m_mod_id_resolver.reset();
|
||||
setError(tr("Unable to resolve mod IDs:\n") + reason);
|
||||
loop.quit();
|
||||
});
|
||||
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::progress, this, &FlameCreationTask::setProgress);
|
||||
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::status, this, &FlameCreationTask::setStatus);
|
||||
@ -353,14 +387,6 @@ bool FlameCreationTask::createInstance()
|
||||
setAbortable(false);
|
||||
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);
|
||||
}
|
||||
|
||||
@ -376,13 +402,13 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
|
||||
auto anyBlocked = false;
|
||||
for (const auto& result : results.files.values()) {
|
||||
if (!result.resolved || result.url.isEmpty()) {
|
||||
|
||||
BlockedMod blocked_mod;
|
||||
blocked_mod.name = result.fileName;
|
||||
blocked_mod.websiteUrl = result.websiteUrl;
|
||||
blocked_mod.hash = result.hash;
|
||||
blocked_mod.matched = false;
|
||||
blocked_mod.localPath = "";
|
||||
blocked_mod.targetFolder = result.targetFolder;
|
||||
|
||||
blocked_mods.append(blocked_mod);
|
||||
|
||||
@ -392,13 +418,14 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
|
||||
if (anyBlocked) {
|
||||
qWarning() << "Blocked mods found, displaying mod list";
|
||||
|
||||
auto message_dialog = new BlockedModsDialog(m_parent, tr("Blocked mods found"),
|
||||
tr("The following mods were blocked on third party launchers.<br/>"
|
||||
"You will need to manually download them and add them to the modpack"),
|
||||
blocked_mods);
|
||||
message_dialog->setModal(true);
|
||||
BlockedModsDialog message_dialog(m_parent, tr("Blocked mods found"),
|
||||
tr("The following files are not available for download in third party launchers.<br/>"
|
||||
"You will need to manually download them and add them to the instance."),
|
||||
blocked_mods);
|
||||
|
||||
if (message_dialog->exec()) {
|
||||
message_dialog.setModal(true);
|
||||
|
||||
if (message_dialog.exec()) {
|
||||
qDebug() << "Post dialog blocked mods list: " << blocked_mods;
|
||||
copyBlockedMods(blocked_mods);
|
||||
setupDownloadJob(loop);
|
||||
@ -427,7 +454,7 @@ void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods)
|
||||
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)));
|
||||
|
||||
@ -482,9 +509,7 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
|
||||
}
|
||||
|
||||
m_mod_id_resolver.reset();
|
||||
connect(m_files_job.get(), &NetJob::succeeded, this, [&]() {
|
||||
m_files_job.reset();
|
||||
});
|
||||
connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { m_files_job.reset(); });
|
||||
connect(m_files_job.get(), &NetJob::failed, [&](QString reason) {
|
||||
m_files_job.reset();
|
||||
setError(reason);
|
||||
|
@ -1,3 +1,38 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "InstanceCreationTask.h"
|
||||
@ -16,11 +51,21 @@ class FlameCreationTask final : public InstanceCreationTask {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FlameCreationTask(const QString& staging_path, SettingsObjectPtr global_settings, QWidget* parent)
|
||||
: InstanceCreationTask(), m_parent(parent)
|
||||
FlameCreationTask(const QString& staging_path,
|
||||
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);
|
||||
setParentSettings(global_settings);
|
||||
|
||||
m_original_instance_id = std::move(original_instance_id);
|
||||
}
|
||||
|
||||
bool abort() override;
|
||||
@ -43,5 +88,7 @@ class FlameCreationTask final : public InstanceCreationTask {
|
||||
NetJob* m_process_update_file_info_job = nullptr;
|
||||
NetJob::Ptr m_files_job = nullptr;
|
||||
|
||||
QString m_managed_id, m_managed_version_id;
|
||||
|
||||
std::optional<InstancePtr> m_instance;
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 flowln <flowlnlnln@gmail.com>
|
||||
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
@ -189,13 +189,13 @@ void PackInstallTask::onResolveModsSucceeded()
|
||||
|
||||
// First check for blocked mods
|
||||
if (!results_file.resolved || results_file.url.isEmpty()) {
|
||||
|
||||
BlockedMod blocked_mod;
|
||||
blocked_mod.name = local_file.name;
|
||||
blocked_mod.websiteUrl = results_file.websiteUrl;
|
||||
blocked_mod.hash = results_file.hash;
|
||||
blocked_mod.matched = false;
|
||||
blocked_mod.localPath = "";
|
||||
blocked_mod.targetFolder = results_file.targetFolder;
|
||||
|
||||
m_blocked_mods.append(blocked_mod);
|
||||
|
||||
@ -210,19 +210,20 @@ void PackInstallTask::onResolveModsSucceeded()
|
||||
if (anyBlocked) {
|
||||
qDebug() << "Blocked files found, displaying file list";
|
||||
|
||||
auto message_dialog = new BlockedModsDialog(m_parent, tr("Blocked files found"),
|
||||
tr("The following files are not available for download in third party launchers.<br/>"
|
||||
"You will need to manually download them and add them to the instance."),
|
||||
m_blocked_mods);
|
||||
BlockedModsDialog message_dialog(m_parent, tr("Blocked files found"),
|
||||
tr("The following files are not available for download in third party launchers.<br/>"
|
||||
"You will need to manually download them and add them to the instance."),
|
||||
m_blocked_mods);
|
||||
|
||||
if (message_dialog->exec() == QDialog::Accepted) {
|
||||
message_dialog.setModal(true);
|
||||
|
||||
if (message_dialog.exec() == QDialog::Accepted) {
|
||||
qDebug() << "Post dialog blocked mods list: " << m_blocked_mods;
|
||||
createInstance();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
createInstance();
|
||||
}
|
||||
@ -366,7 +367,7 @@ void PackInstallTask::copyBlockedMods()
|
||||
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)));
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 flowln <flowlnlnln@gmail.com>
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
*
|
||||
|
@ -33,13 +33,19 @@ bool ModrinthCreationTask::updateInstance()
|
||||
auto instance_list = APPLICATION->instances();
|
||||
|
||||
// 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) {
|
||||
inst = instance_list->getInstanceById(originalName());
|
||||
if (!inst) {
|
||||
inst = instance_list->getInstanceById(originalName());
|
||||
|
||||
if (!inst)
|
||||
return false;
|
||||
if (!inst)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json");
|
||||
@ -49,25 +55,14 @@ bool ModrinthCreationTask::updateInstance()
|
||||
auto version_name = inst->getManagedPackVersionName();
|
||||
auto version_str = !version_name.isEmpty() ? tr(" (version %1)").arg(version_name) : "";
|
||||
|
||||
auto info = CustomMessageBox::selectable(
|
||||
m_parent, tr("Similar modpack was found!"),
|
||||
tr("One or more of your instances are from this same modpack%1. Do you want to create a "
|
||||
"separate instance, or update the existing one?\n\nNOTE: Make sure you made a backup of your important instance data before "
|
||||
"updating, as worlds can be corrupted and some configuration may be lost (due to pack overrides).")
|
||||
.arg(version_str),
|
||||
QMessageBox::Information, QMessageBox::Ok | QMessageBox::Reset | QMessageBox::Abort);
|
||||
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;
|
||||
|
||||
if (info->clickedButton() == info->button(QMessageBox::Reset)) {
|
||||
m_abort = true;
|
||||
return false;
|
||||
if (shouldConfirmUpdate()) {
|
||||
auto should_update = askIfShouldUpdate(m_parent, version_str);
|
||||
if (should_update == ShouldUpdate::SkipUpdating)
|
||||
return false;
|
||||
if (should_update == ShouldUpdate::Cancel) {
|
||||
m_abort = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove repeated files, we don't need to download them!
|
||||
@ -149,7 +144,7 @@ bool ModrinthCreationTask::updateInstance()
|
||||
}
|
||||
|
||||
|
||||
setOverride(true);
|
||||
setOverride(true, inst->id());
|
||||
qDebug() << "Will override instance!";
|
||||
|
||||
m_instance = inst;
|
||||
@ -207,14 +202,14 @@ bool ModrinthCreationTask::createInstance()
|
||||
|
||||
auto components = instance.getPackProfile();
|
||||
components->buildingFromScratch();
|
||||
components->setComponentVersion("net.minecraft", minecraftVersion, true);
|
||||
components->setComponentVersion("net.minecraft", m_minecraft_version, true);
|
||||
|
||||
if (!fabricVersion.isEmpty())
|
||||
components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion);
|
||||
if (!quiltVersion.isEmpty())
|
||||
components->setComponentVersion("org.quiltmc.quilt-loader", quiltVersion);
|
||||
if (!forgeVersion.isEmpty())
|
||||
components->setComponentVersion("net.minecraftforge", forgeVersion);
|
||||
if (!m_fabric_version.isEmpty())
|
||||
components->setComponentVersion("net.fabricmc.fabric-loader", m_fabric_version);
|
||||
if (!m_quilt_version.isEmpty())
|
||||
components->setComponentVersion("org.quiltmc.quilt-loader", m_quilt_version);
|
||||
if (!m_forge_version.isEmpty())
|
||||
components->setComponentVersion("net.minecraftforge", m_forge_version);
|
||||
|
||||
if (m_instIcon != "default") {
|
||||
instance.setIconKey(m_instIcon);
|
||||
@ -222,7 +217,9 @@ bool ModrinthCreationTask::createInstance()
|
||||
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.saveNow();
|
||||
|
||||
@ -282,7 +279,7 @@ bool ModrinthCreationTask::createInstance()
|
||||
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 {
|
||||
auto doc = Json::requireDocument(index_path);
|
||||
@ -294,8 +291,9 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector<
|
||||
throw JSONValidationError("Unknown game: " + game);
|
||||
}
|
||||
|
||||
if (set_managed_info) {
|
||||
m_managed_version_id = Json::ensureString(obj, "versionId", {}, "Managed ID");
|
||||
if (set_internal_data) {
|
||||
if (m_managed_version_id.isEmpty())
|
||||
m_managed_version_id = Json::ensureString(obj, "versionId", {}, "Managed ID");
|
||||
m_managed_name = Json::ensureString(obj, "name", {}, "Managed Name");
|
||||
}
|
||||
|
||||
@ -369,19 +367,21 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector<
|
||||
files.push_back(file);
|
||||
}
|
||||
|
||||
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
|
||||
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
|
||||
QString name = it.key();
|
||||
if (name == "minecraft") {
|
||||
minecraftVersion = Json::requireString(*it, "Minecraft version");
|
||||
} else if (name == "fabric-loader") {
|
||||
fabricVersion = Json::requireString(*it, "Fabric Loader version");
|
||||
} else if (name == "quilt-loader") {
|
||||
quiltVersion = Json::requireString(*it, "Quilt Loader version");
|
||||
} else if (name == "forge") {
|
||||
forgeVersion = Json::requireString(*it, "Forge version");
|
||||
} else {
|
||||
throw JSONValidationError("Unknown dependency type: " + name);
|
||||
if (set_internal_data) {
|
||||
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
|
||||
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
|
||||
QString name = it.key();
|
||||
if (name == "minecraft") {
|
||||
m_minecraft_version = Json::requireString(*it, "Minecraft version");
|
||||
} else if (name == "fabric-loader") {
|
||||
m_fabric_version = Json::requireString(*it, "Fabric Loader version");
|
||||
} else if (name == "quilt-loader") {
|
||||
m_quilt_version = Json::requireString(*it, "Quilt Loader version");
|
||||
} else if (name == "forge") {
|
||||
m_forge_version = Json::requireString(*it, "Forge version");
|
||||
} else {
|
||||
throw JSONValidationError("Unknown dependency type: " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -395,13 +395,3 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector<
|
||||
|
||||
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
|
||||
|
||||
public:
|
||||
ModrinthCreationTask(QString staging_path, SettingsObjectPtr global_settings, QWidget* parent, QString source_url = {})
|
||||
: InstanceCreationTask(), m_parent(parent), m_source_url(std::move(source_url))
|
||||
ModrinthCreationTask(QString staging_path,
|
||||
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);
|
||||
setParentSettings(global_settings);
|
||||
|
||||
m_original_instance_id = std::move(original_instance_id);
|
||||
}
|
||||
|
||||
bool abort() override;
|
||||
@ -27,15 +37,13 @@ class ModrinthCreationTask final : public InstanceCreationTask {
|
||||
bool createInstance() override;
|
||||
|
||||
private:
|
||||
bool parseManifest(const QString&, std::vector<Modrinth::File>&, bool set_managed_info = true, bool show_optional_dialog = true);
|
||||
QString getManagedPackID() const;
|
||||
bool parseManifest(const QString&, std::vector<Modrinth::File>&, bool set_internal_data = true, bool show_optional_dialog = true);
|
||||
|
||||
private:
|
||||
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_source_url;
|
||||
|
||||
std::vector<Modrinth::File> m_files;
|
||||
NetJob::Ptr m_files_job;
|
||||
|
@ -128,6 +128,7 @@ auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion
|
||||
|
||||
file.name = Json::requireString(obj, "name");
|
||||
file.version = Json::requireString(obj, "version_number");
|
||||
file.changelog = Json::ensureString(obj, "changelog");
|
||||
|
||||
file.id = Json::requireString(obj, "id");
|
||||
file.project_id = Json::requireString(obj, "project_id");
|
||||
|
@ -80,6 +80,7 @@ struct ModpackExtra {
|
||||
struct ModpackVersion {
|
||||
QString name;
|
||||
QString version;
|
||||
QString changelog;
|
||||
|
||||
QString id;
|
||||
QString project_id;
|
||||
|
@ -47,7 +47,7 @@
|
||||
auto MetaEntry::getFullPath() -> QString
|
||||
{
|
||||
// FIXME: make local?
|
||||
return FS::PathCombine(basePath, relativePath);
|
||||
return FS::PathCombine(m_basePath, m_relativePath);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
selected_base.entry_list.remove(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
|
||||
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);
|
||||
input.open(QIODevice::ReadOnly);
|
||||
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);
|
||||
return staleEntry(base, resource_path);
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
@ -130,23 +130,23 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex
|
||||
}
|
||||
|
||||
// entry passed all the checks we cared about.
|
||||
entry->basePath = getBasePath(base);
|
||||
entry->m_basePath = getBasePath(base);
|
||||
return entry;
|
||||
}
|
||||
|
||||
auto HttpMetaCache::updateEntry(MetaEntryPtr stale_entry) -> bool
|
||||
{
|
||||
if (!m_entries.contains(stale_entry->baseId)) {
|
||||
qCritical() << "Cannot add entry with unknown base: " << stale_entry->baseId.toLocal8Bit();
|
||||
if (!m_entries.contains(stale_entry->m_baseId)) {
|
||||
qCritical() << "Cannot add entry with unknown base: " << stale_entry->m_baseId.toLocal8Bit();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stale_entry->stale) {
|
||||
if (stale_entry->m_stale) {
|
||||
qCritical() << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit();
|
||||
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();
|
||||
|
||||
return true;
|
||||
@ -157,7 +157,7 @@ auto HttpMetaCache::evictEntry(MetaEntryPtr entry) -> bool
|
||||
if (!entry)
|
||||
return false;
|
||||
|
||||
entry->stale = true;
|
||||
entry->m_stale = true;
|
||||
SaveEventually();
|
||||
return true;
|
||||
}
|
||||
@ -169,7 +169,7 @@ void HttpMetaCache::evictAll()
|
||||
qDebug() << "Evicting base" << base;
|
||||
for (MetaEntryPtr entry : map.entry_list) {
|
||||
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 foo = new MetaEntry();
|
||||
foo->baseId = base;
|
||||
foo->basePath = getBasePath(base);
|
||||
foo->relativePath = resource_path;
|
||||
foo->stale = true;
|
||||
foo->m_baseId = base;
|
||||
foo->m_basePath = getBasePath(base);
|
||||
foo->m_relativePath = resource_path;
|
||||
foo->m_stale = true;
|
||||
|
||||
return MetaEntryPtr(foo);
|
||||
}
|
||||
@ -235,23 +235,23 @@ void HttpMetaCache::Load()
|
||||
auto& entrymap = m_entries[base];
|
||||
|
||||
auto foo = new MetaEntry();
|
||||
foo->baseId = base;
|
||||
foo->relativePath = Json::ensureString(element_obj, "path");
|
||||
foo->md5sum = Json::ensureString(element_obj, "md5sum");
|
||||
foo->etag = Json::ensureString(element_obj, "etag");
|
||||
foo->local_changed_timestamp = Json::ensureDouble(element_obj, "last_changed_timestamp");
|
||||
foo->remote_changed_timestamp = Json::ensureString(element_obj, "remote_changed_timestamp");
|
||||
foo->m_baseId = base;
|
||||
foo->m_relativePath = Json::ensureString(element_obj, "path");
|
||||
foo->m_md5sum = Json::ensureString(element_obj, "md5sum");
|
||||
foo->m_etag = Json::ensureString(element_obj, "etag");
|
||||
foo->m_local_changed_timestamp = Json::ensureDouble(element_obj, "last_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));
|
||||
if (!foo->isEternal()) {
|
||||
foo->current_age = Json::ensureDouble(element_obj, "current_age");
|
||||
foo->max_age = Json::ensureDouble(element_obj, "max_age");
|
||||
foo->m_current_age = Json::ensureDouble(element_obj, "current_age");
|
||||
foo->m_max_age = Json::ensureDouble(element_obj, "max_age");
|
||||
}
|
||||
|
||||
// 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 entry : group.entry_list) {
|
||||
// do not save stale entries. they are dead.
|
||||
if (entry->stale) {
|
||||
if (entry->m_stale) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QJsonObject entryObj;
|
||||
Json::writeString(entryObj, "base", entry->baseId);
|
||||
Json::writeString(entryObj, "path", entry->relativePath);
|
||||
Json::writeString(entryObj, "md5sum", entry->md5sum);
|
||||
Json::writeString(entryObj, "etag", entry->etag);
|
||||
entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->local_changed_timestamp)));
|
||||
if (!entry->remote_changed_timestamp.isEmpty())
|
||||
entryObj.insert("remote_changed_timestamp", QJsonValue(entry->remote_changed_timestamp));
|
||||
Json::writeString(entryObj, "base", entry->m_baseId);
|
||||
Json::writeString(entryObj, "path", entry->m_relativePath);
|
||||
Json::writeString(entryObj, "md5sum", entry->m_md5sum);
|
||||
Json::writeString(entryObj, "etag", entry->m_etag);
|
||||
entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->m_local_changed_timestamp)));
|
||||
if (!entry->m_remote_changed_timestamp.isEmpty())
|
||||
entryObj.insert("remote_changed_timestamp", QJsonValue(entry->m_remote_changed_timestamp));
|
||||
if (entry->isEternal()) {
|
||||
entryObj.insert("eternal", true);
|
||||
} else {
|
||||
entryObj.insert("current_age", QJsonValue(double(entry->current_age)));
|
||||
entryObj.insert("max_age", QJsonValue(double(entry->max_age)));
|
||||
entryObj.insert("current_age", QJsonValue(double(entry->m_current_age)));
|
||||
entryObj.insert("max_age", QJsonValue(double(entry->m_max_age)));
|
||||
}
|
||||
entriesArr.append(entryObj);
|
||||
}
|
||||
|
@ -49,47 +49,47 @@ class MetaEntry {
|
||||
MetaEntry() = default;
|
||||
|
||||
public:
|
||||
auto isStale() -> bool { return stale; }
|
||||
void setStale(bool stale) { this->stale = stale; }
|
||||
auto isStale() -> bool { return m_stale; }
|
||||
void setStale(bool stale) { m_stale = stale; }
|
||||
|
||||
auto getFullPath() -> QString;
|
||||
|
||||
auto getRemoteChangedTimestamp() -> QString { return remote_changed_timestamp; }
|
||||
void setRemoteChangedTimestamp(QString remote_changed_timestamp) { this->remote_changed_timestamp = remote_changed_timestamp; }
|
||||
void setLocalChangedTimestamp(qint64 timestamp) { local_changed_timestamp = timestamp; }
|
||||
auto getRemoteChangedTimestamp() -> QString { return m_remote_changed_timestamp; }
|
||||
void setRemoteChangedTimestamp(QString remote_changed_timestamp) { m_remote_changed_timestamp = remote_changed_timestamp; }
|
||||
void setLocalChangedTimestamp(qint64 timestamp) { m_local_changed_timestamp = timestamp; }
|
||||
|
||||
auto getETag() -> QString { return etag; }
|
||||
void setETag(QString etag) { this->etag = etag; }
|
||||
auto getETag() -> QString { return m_etag; }
|
||||
void setETag(QString etag) { m_etag = etag; }
|
||||
|
||||
auto getMD5Sum() -> QString { return md5sum; }
|
||||
void setMD5Sum(QString md5sum) { this->md5sum = md5sum; }
|
||||
auto getMD5Sum() -> QString { return m_md5sum; }
|
||||
void setMD5Sum(QString md5sum) { m_md5sum = md5sum; }
|
||||
|
||||
/* Whether the entry expires after some time (false) or not (true). */
|
||||
void makeEternal(bool eternal) { is_eternal = eternal; }
|
||||
[[nodiscard]] bool isEternal() const { return is_eternal; }
|
||||
void makeEternal(bool eternal) { m_is_eternal = eternal; }
|
||||
[[nodiscard]] bool isEternal() const { return m_is_eternal; }
|
||||
|
||||
auto getCurrentAge() -> qint64 { return current_age; }
|
||||
void setCurrentAge(qint64 age) { current_age = age; }
|
||||
auto getCurrentAge() -> qint64 { return m_current_age; }
|
||||
void setCurrentAge(qint64 age) { m_current_age = age; }
|
||||
|
||||
auto getMaximumAge() -> qint64 { return max_age; }
|
||||
void setMaximumAge(qint64 age) { max_age = age; }
|
||||
auto getMaximumAge() -> qint64 { return m_max_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:
|
||||
QString baseId;
|
||||
QString basePath;
|
||||
QString relativePath;
|
||||
QString md5sum;
|
||||
QString etag;
|
||||
QString m_baseId;
|
||||
QString m_basePath;
|
||||
QString m_relativePath;
|
||||
QString m_md5sum;
|
||||
QString m_etag;
|
||||
|
||||
qint64 local_changed_timestamp = 0;
|
||||
QString remote_changed_timestamp; // QString for now, RFC 2822 encoded time
|
||||
qint64 current_age = 0;
|
||||
qint64 max_age = 0;
|
||||
bool is_eternal = false;
|
||||
qint64 m_local_changed_timestamp = 0;
|
||||
QString m_remote_changed_timestamp; // QString for now, RFC 2822 encoded time
|
||||
qint64 m_current_age = 0;
|
||||
qint64 m_max_age = 0;
|
||||
bool m_is_eternal = false;
|
||||
|
||||
bool stale = true;
|
||||
bool m_stale = true;
|
||||
};
|
||||
|
||||
using MetaEntryPtr = std::shared_ptr<MetaEntry>;
|
||||
|
@ -123,7 +123,7 @@ auto NetJob::getFailedFiles() -> QList<QString>
|
||||
|
||||
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)")
|
||||
.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/rename.svg</file>
|
||||
<file>scalable/launch.svg</file>
|
||||
<file>scalable/shortcut.svg</file>
|
||||
</qresource>
|
||||
</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 |