This commit is contained in:
fn2006 2022-12-20 20:23:42 +00:00
commit 64b7562919
405 changed files with 24103 additions and 2294 deletions

View File

@ -7,10 +7,17 @@ on:
description: Type of build (Debug, Release, RelWithDebInfo, MinSizeRel) description: Type of build (Debug, Release, RelWithDebInfo, MinSizeRel)
type: string type: string
default: Debug default: Debug
is_qt_cached:
description: Enable Qt caching or not
type: string
default: true
secrets: secrets:
SPARKLE_ED25519_KEY: SPARKLE_ED25519_KEY:
description: Private key for signing Sparkle updates description: Private key for signing Sparkle updates
required: false required: false
CACHIX_AUTH_TOKEN:
description: Private token for authenticating against Cachix cache
required: false
jobs: jobs:
build: build:
@ -31,17 +38,11 @@ jobs:
qt_tools: '' qt_tools: ''
- os: windows-2022 - os: windows-2022
name: "Windows-Legacy" name: "Windows-MinGW-w64"
msystem: clang32
qt_ver: 5
- os: windows-2022
name: "Windows"
msystem: clang64 msystem: clang64
qt_ver: 6
- os: windows-2022 - os: windows-2022
name: "Windows-Legacy-MSVC" name: "Windows-MSVC-Legacy"
msystem: '' msystem: ''
architecture: 'win32' architecture: 'win32'
vcvars_arch: 'amd64_x86' vcvars_arch: 'amd64_x86'
@ -64,6 +65,18 @@ jobs:
qt_modules: 'qt5compat qtimageformats' qt_modules: 'qt5compat qtimageformats'
qt_tools: '' qt_tools: ''
- os: windows-2022
name: "Windows-MSVC-arm64"
msystem: ''
architecture: 'arm64'
vcvars_arch: 'amd64_arm64'
qt_ver: 6
qt_host: windows
qt_arch: 'win64_msvc2019_arm64'
qt_version: '6.4.0'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
- os: macos-12 - os: macos-12
name: macOS name: macOS
macosx_deployment_target: 10.15 macosx_deployment_target: 10.15
@ -102,14 +115,6 @@ jobs:
with: with:
submodules: 'true' submodules: 'true'
- name: Initialize CodeQL
if: runner.os == 'Linux' && matrix.qt_ver == 6
uses: github/codeql-action/init@v2
with:
config-file: ./.github/codeql/codeql-config.yml
queries: security-and-quality
languages: cpp, java
- name: 'Setup MSYS2' - name: 'Setup MSYS2'
if: runner.os == 'Windows' && matrix.msystem != '' if: runner.os == 'Windows' && matrix.msystem != ''
uses: msys2/setup-msys2@v2 uses: msys2/setup-msys2@v2
@ -124,12 +129,12 @@ jobs:
cmake:p cmake:p
extra-cmake-modules:p extra-cmake-modules:p
ninja:p ninja:p
qt${{ matrix.qt_ver }}-base:p qt6-base:p
qt${{ matrix.qt_ver }}-svg:p qt6-svg:p
qt${{ matrix.qt_ver }}-imageformats:p qt6-imageformats:p
quazip-qt${{ matrix.qt_ver }}:p quazip-qt6:p
ccache:p ccache:p
${{ matrix.qt_ver == 6 && 'qt6-5compat:p' || '' }} qt6-5compat:p
- name: Force newer ccache - name: Force newer ccache
if: runner.os == 'Windows' && matrix.msystem == '' && inputs.build_type == 'Debug' if: runner.os == 'Windows' && matrix.msystem == '' && inputs.build_type == 'Debug'
@ -140,7 +145,7 @@ jobs:
if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug' if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug'
uses: hendrikmuhs/ccache-action@v1.2.5 uses: hendrikmuhs/ccache-action@v1.2.5
with: with:
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }} key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
- name: Setup ccache (Windows MinGW-w64) - name: Setup ccache (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug' if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
@ -163,9 +168,9 @@ jobs:
uses: actions/cache@v3.0.11 uses: actions/cache@v3.0.11
with: with:
path: '${{ github.workspace }}\.ccache' path: '${{ github.workspace }}\.ccache'
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }} key: ${{ matrix.os }}-mingw-w64
restore-keys: | restore-keys: |
${{ matrix.os }}-qt${{ matrix.qt_ver }} ${{ matrix.os }}-mingw-w64
- name: Set short version - name: Set short version
shell: bash shell: bash
@ -190,7 +195,22 @@ jobs:
run: | run: |
sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5
- name: Install Qt (macOS, AppImage & Windows MSVC) - name: Install host Qt (Windows MSVC arm64)
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
uses: jurplel/install-qt-action@v3
with:
version: ${{ matrix.qt_version }}
host: 'windows'
target: 'desktop'
arch: ''
modules: ${{ matrix.qt_modules }}
tools: ${{ matrix.qt_tools }}
cache: ${{ inputs.is_qt_cached }}
cache-key-prefix: host-qt-arm64-windows
dir: ${{ github.workspace }}\HostQt
set-env: false
- name: Install Qt (macOS, Linux, Qt 6 & Windows MSVC)
if: runner.os == 'Linux' && matrix.qt_ver == 6 || runner.os == 'macOS' || (runner.os == 'Windows' && matrix.msystem == '') if: runner.os == 'Linux' && matrix.qt_ver == 6 || runner.os == 'macOS' || (runner.os == 'Windows' && matrix.msystem == '')
uses: jurplel/install-qt-action@v3 uses: jurplel/install-qt-action@v3
with: with:
@ -200,8 +220,14 @@ jobs:
arch: ${{ matrix.qt_arch }} arch: ${{ matrix.qt_arch }}
modules: ${{ matrix.qt_modules }} modules: ${{ matrix.qt_modules }}
tools: ${{ matrix.qt_tools }} tools: ${{ matrix.qt_tools }}
cache: true cache: ${{ inputs.is_qt_cached }}
cache-key-prefix: ${{ matrix.qt_host }}-${{ matrix.qt_version }}-"${{ matrix.qt_modules }}"-qt_cache
- name: Install MSVC (Windows MSVC)
if: runner.os == 'Windows' && matrix.msystem == ''
uses: ilammy/msvc-dev-cmd@v1
with:
vsversion: 2022
arch: ${{ matrix.vcvars_arch }}
- name: Prepare AppImage (Linux) - name: Prepare AppImage (Linux)
if: runner.os == 'Linux' && matrix.qt_ver != 5 if: runner.os == 'Linux' && matrix.qt_ver != 5
@ -212,6 +238,11 @@ jobs:
${{ github.workspace }}/.github/scripts/prepare_JREs.sh ${{ github.workspace }}/.github/scripts/prepare_JREs.sh
- name: Add QT_HOST_PATH var (Windows MSVC arm64)
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
run: |
echo "QT_HOST_PATH=${{ github.workspace }}\HostQt\Qt\${{ matrix.qt_version }}\msvc2019_64" >> $env:GITHUB_ENV
## ##
# CONFIGURE # CONFIGURE
## ##
@ -230,12 +261,12 @@ jobs:
if: runner.os == 'Windows' && matrix.msystem != '' if: runner.os == 'Windows' && matrix.msystem != ''
shell: msys2 {0} shell: msys2 {0}
run: | run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -G Ninja cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=6 -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -G Ninja
- name: Configure CMake (Windows MSVC) - name: Configure CMake (Windows MSVC)
if: runner.os == 'Windows' && matrix.msystem == '' if: runner.os == 'Windows' && matrix.msystem == ''
run: | run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON
# https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix) # https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix)
if ("${{ env.CCACHE_VAR }}") if ("${{ env.CCACHE_VAR }}")
{ {
@ -288,18 +319,10 @@ jobs:
ctest -E "^example64|example$" --test-dir build --output-on-failure ctest -E "^example64|example$" --test-dir build --output-on-failure
- name: Test (Windows MSVC) - name: Test (Windows MSVC)
if: runner.os == 'Windows' && matrix.msystem == '' if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture != 'arm64'
run: | run: |
ctest -E "^example64|example$" --test-dir build --output-on-failure -C ${{ inputs.build_type }} ctest -E "^example64|example$" --test-dir build --output-on-failure -C ${{ inputs.build_type }}
##
# CODE SCAN
##
- name: Perform CodeQL Analysis
if: runner.os == 'Linux' && matrix.qt_ver == 6
uses: github/codeql-action/analyze@v2
## ##
# PACKAGE BUILDS # PACKAGE BUILDS
## ##
@ -333,23 +356,12 @@ jobs:
EOF EOF
fi fi
- name: Add VC Enviroment Variables
if: runner.os == 'Windows' && matrix.msystem == ''
uses: ilammy/msvc-dev-cmd@v1
with:
arch: ${{ matrix.vcvars_arch }}
- name: Package (Windows MinGW-w64) - name: Package (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' if: runner.os == 'Windows' && matrix.msystem != ''
shell: msys2 {0} shell: msys2 {0}
run: | run: |
cmake --install ${{ env.BUILD_DIR }} cmake --install ${{ env.BUILD_DIR }}
cd ${{ env.INSTALL_DIR }}
if [ "${{ matrix.qt_ver }}" == "5" ]; then
cp /clang32/bin/libcrypto-1_1.dll /clang32/bin/libssl-1_1.dll ./
fi
- name: Package (Windows MSVC) - name: Package (Windows MSVC)
if: runner.os == 'Windows' && matrix.msystem == '' if: runner.os == 'Windows' && matrix.msystem == ''
run: | run: |

35
.github/workflows/codeql.yml vendored Normal file
View File

@ -0,0 +1,35 @@
name: "CodeQL Code Scanning"
on: [ push, pull_request, workflow_dispatch ]
jobs:
CodeQL:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
submodules: 'true'
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
config-file: ./.github/codeql/codeql-config.yml
queries: security-and-quality
languages: cpp, java
- name: Install Dependencies
run:
sudo apt-get -y update
sudo apt-get -y install ninja-build extra-cmake-modules scdoc qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5
- name: Configure and Build
run: |
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr -DLauncher_QT_VERSION_MAJOR=5 -G Ninja
cmake --build build
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

View File

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

View File

@ -12,6 +12,7 @@ jobs:
uses: ./.github/workflows/build.yml uses: ./.github/workflows/build.yml
with: with:
build_type: Release build_type: Release
is_qt_cached: false
secrets: secrets:
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }} SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
@ -45,15 +46,26 @@ jobs:
tar -czf PollyMC-${{ env.VERSION }}.tar.gz PollyMC-${{ env.VERSION }} tar -czf PollyMC-${{ env.VERSION }}.tar.gz PollyMC-${{ env.VERSION }}
for d in PollyMC-Windows-*; do for d in PollyMC-Windows-MSVC*; do
cd "${d}" || continue cd "${d}" || continue
MSVC="$(echo -n ${d} | grep -o MSVC || true)"
LEGACY="$(echo -n ${d} | grep -o Legacy || true)" LEGACY="$(echo -n ${d} | grep -o Legacy || true)"
ARM64="$(echo -n ${d} | grep -o arm64 || true)"
INST="$(echo -n ${d} | grep -o Setup || true)" INST="$(echo -n ${d} | grep -o Setup || true)"
PORT="$(echo -n ${d} | grep -o Portable || true)" PORT="$(echo -n ${d} | grep -o Portable || true)"
NAME="PollyMC-Windows" NAME="PollyMC-Windows-MSVC"
test -z "${MSVC}" && NAME="${NAME}-MinGW" || NAME="${NAME}-MSVC"
test -z "${LEGACY}" || NAME="${NAME}-Legacy" test -z "${LEGACY}" || NAME="${NAME}-Legacy"
test -z "${ARM64}" || NAME="${NAME}-arm64"
test -z "${PORT}" || NAME="${NAME}-Portable"
test -z "${INST}" || mv PollyMC-*.exe ../${NAME}-Setup-${{ env.VERSION }}.exe
test -n "${INST}" || zip -r -9 "../${NAME}-${{ env.VERSION }}.zip" *
cd ..
done
for d in PollyMC-Windows-MinGW-w64*; do
cd "${d}" || continue
INST="$(echo -n ${d} | grep -o Setup || true)"
PORT="$(echo -n ${d} | grep -o Portable || true)"
NAME="PollyMC-Windows-MinGW-w64"
test -z "${PORT}" || NAME="${NAME}-Portable" test -z "${PORT}" || NAME="${NAME}-Portable"
test -z "${INST}" || mv PollyMC-*.exe ../${NAME}-Setup-${{ env.VERSION }}.exe test -z "${INST}" || mv PollyMC-*.exe ../${NAME}-Setup-${{ env.VERSION }}.exe
test -n "${INST}" || zip -r -9 "../${NAME}-${{ env.VERSION }}.zip" * test -n "${INST}" || zip -r -9 "../${NAME}-${{ env.VERSION }}.zip" *
@ -76,18 +88,19 @@ jobs:
PollyMC-Linux-${{ env.VERSION }}-x86_64.AppImage PollyMC-Linux-${{ env.VERSION }}-x86_64.AppImage
PollyMC-Linux-Qt6-${{ env.VERSION }}.tar.gz PollyMC-Linux-Qt6-${{ env.VERSION }}.tar.gz
PollyMC-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz PollyMC-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
PollyMC-Windows-MinGW-Legacy-${{ env.VERSION }}.zip PollyMC-Windows-MinGW-w64-${{ env.VERSION }}.zip
PollyMC-Windows-MinGW-Legacy-Portable-${{ env.VERSION }}.zip PollyMC-Windows-MinGW-w64-Portable-${{ env.VERSION }}.zip
PollyMC-Windows-MinGW-Legacy-Setup-${{ env.VERSION }}.exe PollyMC-Windows-MinGW-w64-Setup-${{ env.VERSION }}.exe
PollyMC-Windows-MinGW-${{ env.VERSION }}.zip
PollyMC-Windows-MinGW-Portable-${{ env.VERSION }}.zip
PollyMC-Windows-MinGW-Setup-${{ env.VERSION }}.exe
PollyMC-Windows-MSVC-Legacy-${{ env.VERSION }}.zip PollyMC-Windows-MSVC-Legacy-${{ env.VERSION }}.zip
PollyMC-Windows-MSVC-Legacy-Portable-${{ env.VERSION }}.zip PollyMC-Windows-MSVC-Legacy-Portable-${{ env.VERSION }}.zip
PollyMC-Windows-MSVC-Legacy-Setup-${{ env.VERSION }}.exe PollyMC-Windows-MSVC-Legacy-Setup-${{ env.VERSION }}.exe
PollyMC-Windows-MSVC-arm64-${{ env.VERSION }}.zip
PollyMC-Windows-MSVC-arm64-Portable-${{ env.VERSION }}.zip
PollyMC-Windows-MSVC-arm64-Setup-${{ env.VERSION }}.exe
PollyMC-Windows-MSVC-${{ env.VERSION }}.zip PollyMC-Windows-MSVC-${{ env.VERSION }}.zip
PollyMC-Windows-MSVC-Portable-${{ env.VERSION }}.zip PollyMC-Windows-MSVC-Portable-${{ env.VERSION }}.zip
PollyMC-Windows-MSVC-Setup-${{ env.VERSION }}.exe PollyMC-Windows-MSVC-Setup-${{ env.VERSION }}.exe
PollyMC-macOS-${{ env.VERSION }}.tar.gz PollyMC-macOS-${{ env.VERSION }}.tar.gz
PollyMC-macOS-Legacy-${{ env.VERSION }}.tar.gz PollyMC-macOS-Legacy-${{ env.VERSION }}.tar.gz
PollyMC-${{ env.VERSION }}.tar.gz PollyMC-${{ env.VERSION }}.tar.gz

View File

@ -11,5 +11,5 @@ jobs:
with: with:
identifier: PrismLauncher.PrismLauncher identifier: PrismLauncher.PrismLauncher
version: ${{ github.event.release.tag_name }} version: ${{ github.event.release.tag_name }}
installers-regex: 'PrismLauncher-Windows-Setup-.+\.exe$' installers-regex: 'PrismLauncher-Windows-MSVC(:?-arm64|-Legacy)?-Setup-.+\.exe$'
token: ${{ secrets.WINGET_TOKEN }} token: ${{ secrets.WINGET_TOKEN }}

View File

@ -28,19 +28,28 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD 11)
include(GenerateExportHeader) include(GenerateExportHeader)
if(MSVC) if(MSVC)
# Use /W4 as /Wall includes unnesserey warnings such as added padding to structs
# /permissive- specify standards-conforming compiler behavior, also enabled by Qt6, default on with std:c++20
# /GS Adds buffer security checks, default on but incuded anyway to mirror gcc's fstack-protector flag # /GS Adds buffer security checks, default on but incuded anyway to mirror gcc's fstack-protector flag
set(CMAKE_CXX_FLAGS "/W4 /permissive- /GS ${CMAKE_CXX_FLAGS}") # /permissive- specify standards-conforming compiler behavior, also enabled by Qt6, default on with std:c++20
# Use /W4 as /Wall includes unnesserey warnings such as added padding to structs
set(CMAKE_CXX_FLAGS "/GS /permissive- /W4 ${CMAKE_CXX_FLAGS}")
# LINK accepts /SUBSYSTEM whics sets if we are a WINDOWS (gui) or a CONSOLE programs # LINK accepts /SUBSYSTEM whics sets if we are a WINDOWS (gui) or a CONSOLE programs
# This implicitly selects an entrypoint specific to the subsystem selected # This implicitly selects an entrypoint specific to the subsystem selected
# qtmain/QtEntryPointLib provides the correct entrypoint (wWinMain) for gui programs # qtmain/QtEntryPointLib provides the correct entrypoint (wWinMain) for gui programs
# Additinaly LINK autodetects we use a GUI so we can omit /SUBSYSTEM # Additinaly LINK autodetects we use a GUI so we can omit /SUBSYSTEM
# This allows tests to still use have console without using seperate linker flags # This allows tests to still use have console without using seperate linker flags
# /LTCG allows for linking wholy optimizated programs
# /MANIFEST:NO disables generating a manifest file, we instead provide our own # /MANIFEST:NO disables generating a manifest file, we instead provide our own
# /STACK sets the stack reserve size, ATL's pack list needs 3-4 MiB as of November 2022, provide 8 MiB # /STACK sets the stack reserve size, ATL's pack list needs 3-4 MiB as of November 2022, provide 8 MiB
set(CMAKE_EXE_LINKER_FLAGS "/MANIFEST:NO /STACK:8388608 ${CMAKE_EXE_LINKER_FLAGS}") set(CMAKE_EXE_LINKER_FLAGS "/LTCG /MANIFEST:NO /STACK:8388608 ${CMAKE_EXE_LINKER_FLAGS}")
# /GL enables whole program optimizations
# /Gw helps reduce binary size
# /Gy allows the compiler to package individual functions
# /guard:cf enables control flow guard
foreach(lang C CXX)
set("CMAKE_${lang}_FLAGS_RELEASE" "/GL /Gw /Gy /guard:cf")
endforeach()
# See https://github.com/ccache/ccache/issues/1040 # See https://github.com/ccache/ccache/issues/1040
# Note, CMake 3.25 replaces this with CMAKE_MSVC_DEBUG_INFORMATION_FORMAT # Note, CMake 3.25 replaces this with CMAKE_MSVC_DEBUG_INFORMATION_FORMAT
@ -70,6 +79,9 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_DISABLE_DEPRECATED_BEFORE=0x050C00") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_DISABLE_DEPRECATED_BEFORE=0x050C00")
# Fix aarch64 build for toml++
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTOML_ENABLE_FLOAT16=0")
# set CXXFLAGS for build targets # set CXXFLAGS for build targets
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}") set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}")
@ -126,7 +138,7 @@ set(Launcher_NEWS_OPEN_URL "https://prismlauncher.org/news" CACHE STRING "URL th
set(Launcher_HELP_URL "https://prismlauncher.org/wiki/help-pages/%1" CACHE STRING "URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help") set(Launcher_HELP_URL "https://prismlauncher.org/wiki/help-pages/%1" CACHE STRING "URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help")
######## Set version numbers ######## ######## Set version numbers ########
set(Launcher_VERSION_MAJOR 6) set(Launcher_VERSION_MAJOR 7)
set(Launcher_VERSION_MINOR 0) set(Launcher_VERSION_MINOR 0)
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}") set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}")
@ -217,7 +229,7 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 5)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUNICODE -D_UNICODE") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUNICODE -D_UNICODE")
elseif(Launcher_QT_VERSION_MAJOR EQUAL 6) elseif(Launcher_QT_VERSION_MAJOR EQUAL 6)
set(QT_VERSION_MAJOR 6) set(QT_VERSION_MAJOR 6)
find_package(Qt6 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml Core5Compat) find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat)
list(APPEND Launcher_QT_LIBS Qt6::Core5Compat) list(APPEND Launcher_QT_LIBS Qt6::Core5Compat)
if(NOT Launcher_FORCE_BUNDLED_LIBS) if(NOT Launcher_FORCE_BUNDLED_LIBS)
@ -231,12 +243,16 @@ else()
message(FATAL_ERROR "Qt version ${Launcher_QT_VERSION_MAJOR} is not supported") message(FATAL_ERROR "Qt version ${Launcher_QT_VERSION_MAJOR} is not supported")
endif() endif()
include(ECMQueryQt) if(Launcher_QT_VERSION_MAJOR EQUAL 5)
ecm_query_qt(QT_PLUGINS_DIR QT_INSTALL_PLUGINS) include(ECMQueryQt)
ecm_query_qt(QT_LIBS_DIR QT_INSTALL_LIBS) ecm_query_qt(QT_PLUGINS_DIR QT_INSTALL_PLUGINS)
ecm_query_qt(QT_LIBEXECS_DIR QT_INSTALL_LIBEXECS) ecm_query_qt(QT_LIBS_DIR QT_INSTALL_LIBS)
ecm_query_qt(QT_DATA_DIR QT_HOST_DATA) ecm_query_qt(QT_LIBEXECS_DIR QT_INSTALL_LIBEXECS)
set(QT_MKSPECS_DIR ${QT_DATA_DIR}/mkspecs) else()
set(QT_PLUGINS_DIR ${QT${QT_VERSION_MAJOR}_INSTALL_PREFIX}/${QT${QT_VERSION_MAJOR}_INSTALL_PLUGINS})
set(QT_LIBS_DIR ${QT${QT_VERSION_MAJOR}_INSTALL_PREFIX}/${QT${QT_VERSION_MAJOR}_INSTALL_LIBS})
set(QT_LIBEXECS_DIR ${QT${QT_VERSION_MAJOR}_INSTALL_PREFIX}/${QT${QT_VERSION_MAJOR}_INSTALL_LIBEXECS})
endif()
# NOTE: Qt 6 already sets this by default # NOTE: Qt 6 already sets this by default
if (Qt5_POSITION_INDEPENDENT_CODE) if (Qt5_POSITION_INDEPENDENT_CODE)
@ -364,11 +380,11 @@ if(NOT ZLIB_FOUND)
set(SKIP_INSTALL_ALL ON) set(SKIP_INSTALL_ALL ON)
add_subdirectory(libraries/zlib EXCLUDE_FROM_ALL) add_subdirectory(libraries/zlib EXCLUDE_FROM_ALL)
set(ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib" "${CMAKE_CURRENT_BINARY_DIR}/libraries/zlib") set(ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib" "${CMAKE_CURRENT_BINARY_DIR}/libraries/zlib" CACHE STRING "")
set_target_properties(zlibstatic PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${ZLIB_INCLUDE_DIR}") set_target_properties(zlibstatic PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${ZLIB_INCLUDE_DIR}")
add_library(ZLIB::ZLIB ALIAS zlibstatic) add_library(ZLIB::ZLIB ALIAS zlibstatic)
set(ZLIB_LIBRARY ZLIB::ZLIB) set(ZLIB_LIBRARY ZLIB::ZLIB CACHE STRING "zlib library name")
set(ZLIB_FOUND true)
find_package(ZLIB REQUIRED) find_package(ZLIB REQUIRED)
else() else()
message(STATUS "Using system zlib") message(STATUS "Using system zlib")

View File

@ -398,3 +398,45 @@
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
## Breeze icons
Copyright (C) 2014 Uri Herrera <uri_herrera@nitrux.in> and others
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.
## Oxygen Icons
The Oxygen Icon Theme
Copyright (C) 2007 Nuno Pinheiro <nuno@oxygen-icons.org>
Copyright (C) 2007 David Vignoni <david@icon-king.com>
Copyright (C) 2007 David Miller <miller@oxygen-icons.org>
Copyright (C) 2007 Johann Ollivier Lapeyre <johann@oxygen-icons.org>
Copyright (C) 2007 Kenneth Wimer <kwwii@bootsplash.org>
Copyright (C) 2007 Riccardo Iaconelli <riccardo@oxygen-icons.org>
and others
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.

View File

@ -13,3 +13,4 @@ Binaries can be found in releases.
Workaround for downloading from CurseForge and FTB not working [here](https://github.com/fn2006/PollyMC/wiki/CurseForge-Workaround). Workaround for downloading from CurseForge and FTB not working [here](https://github.com/fn2006/PollyMC/wiki/CurseForge-Workaround).
To build this yourself, follow the instructions on the Prism Launcher website but clone this repo instead. To build this yourself, follow the instructions on the Prism Launcher website but clone this repo instead.

View File

@ -161,6 +161,8 @@ class Config {
QString MODRINTH_STAGING_URL = "https://staging-api.modrinth.com/v2"; QString MODRINTH_STAGING_URL = "https://staging-api.modrinth.com/v2";
QString MODRINTH_PROD_URL = "https://api.modrinth.com/v2"; QString MODRINTH_PROD_URL = "https://api.modrinth.com/v2";
QString FLAME_BASE_URL = "https://api.curseforge.com/v1";
QString versionString() const; QString versionString() const;
/** /**
* \brief Converts the Version to a string. * \brief Converts the Version to a string.

31
flake.lock generated
View File

@ -3,11 +3,11 @@
"flake-compat": { "flake-compat": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1650374568, "lastModified": 1668681692,
"narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=", "narHash": "sha256-Ht91NGdewz8IQLtWZ9LCeNXMSXHUss+9COoqu6JLmXU=",
"owner": "edolstra", "owner": "edolstra",
"repo": "flake-compat", "repo": "flake-compat",
"rev": "b4a34015c698c7793d592d66adbab377907a2be8", "rev": "009399224d5e398d03b22badca40a37ac85412a1",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -34,11 +34,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1666057921, "lastModified": 1671417167,
"narHash": "sha256-VpQqtXdj6G7cH//SvoprjR7XT3KS7p+tCVebGK1N6tE=", "narHash": "sha256-JkHam6WQOwZN1t2C2sbp1TqMv3TVRjzrdoejqfefwrM=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "88eab1e431cabd0ed621428d8b40d425a07af39f", "rev": "bb31220cca6d044baa6dc2715b07497a2a7c4bc7",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -52,24 +52,7 @@
"inputs": { "inputs": {
"flake-compat": "flake-compat", "flake-compat": "flake-compat",
"libnbtplusplus": "libnbtplusplus", "libnbtplusplus": "libnbtplusplus",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs"
"tomlplusplus": "tomlplusplus"
}
},
"tomlplusplus": {
"flake": false,
"locked": {
"lastModified": 1666091090,
"narHash": "sha256-djpMCFPvkJcfynV8WnsYdtwLq+J7jpV1iM4C6TojiyM=",
"owner": "marzer",
"repo": "tomlplusplus",
"rev": "1e4a3833d013aee08f58c5b31c69f709afc69f73",
"type": "github"
},
"original": {
"owner": "marzer",
"repo": "tomlplusplus",
"type": "github"
} }
} }
}, },

View File

@ -5,10 +5,9 @@
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
flake-compat = { url = "github:edolstra/flake-compat"; flake = false; }; flake-compat = { url = "github:edolstra/flake-compat"; flake = false; };
libnbtplusplus = { url = "github:PrismLauncher/libnbtplusplus"; flake = false; }; libnbtplusplus = { url = "github:PrismLauncher/libnbtplusplus"; flake = false; };
tomlplusplus = { url = "github:marzer/tomlplusplus"; flake = false; };
}; };
outputs = { self, nixpkgs, libnbtplusplus, tomlplusplus, ... }: outputs = { self, nixpkgs, libnbtplusplus, ... }:
let let
# User-friendly version number. # User-friendly version number.
version = builtins.substring 0 8 self.lastModifiedDate; version = builtins.substring 0 8 self.lastModifiedDate;
@ -23,8 +22,8 @@
pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system}); pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system});
packagesFn = pkgs: rec { packagesFn = pkgs: rec {
prismlauncher = pkgs.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus tomlplusplus; }; prismlauncher-qt5 = pkgs.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus; };
prismlauncher-qt6 = pkgs.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus tomlplusplus; }; prismlauncher = pkgs.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus; };
}; };
in in
{ {

View File

@ -0,0 +1,83 @@
id: org.prismlauncher.PrismLauncher
runtime: org.kde.Platform
runtime-version: "5.15-22.08"
sdk: org.kde.Sdk
sdk-extensions:
- org.freedesktop.Sdk.Extension.openjdk17
- org.freedesktop.Sdk.Extension.openjdk8
add-extensions:
com.valvesoftware.Steam.Utility.gamescope:
version: stable
add-ld-path: lib
no-autodownload: true
autodelete: false
directory: utils/gamescope
command: prismlauncher
finish-args:
- --share=ipc
- --socket=x11
- --socket=wayland
- --device=all
- --share=network
- --socket=pulseaudio
# for Discord RPC mods
- --filesystem=xdg-run/app/com.discordapp.Discord:create
# Mod drag&drop
- --filesystem=xdg-download:ro
modules:
- name: prismlauncher
buildsystem: cmake-ninja
config-opts:
- -DLauncher_BUILD_PLATFORM=flatpak
- -DCMAKE_BUILD_TYPE=Debug
build-options:
env:
JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17
JAVA_COMPILER: /usr/lib/sdk/openjdk17/jvm/openjdk-17/bin/javac
sources:
- type: dir
path: ../
- name: openjdk
buildsystem: simple
build-commands:
- mkdir -p /app/jdk/
- /usr/lib/sdk/openjdk17/install.sh
- mv /app/jre /app/jdk/17
- /usr/lib/sdk/openjdk8/install.sh
- mv /app/jre /app/jdk/8
cleanup: [/jre]
- name: xrandr
buildsystem: autotools
sources:
- type: archive
url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.1.tar.xz
sha256: 7bc76daf9d72f8aff885efad04ce06b90488a1a169d118dea8a2b661832e8762
cleanup: [/share/man, /bin/xkeystone]
- name: gamemode
buildsystem: meson
config-opts:
- -Dwith-sd-bus-provider=no-daemon
- -Dwith-examples=false
post-install:
# gamemoderun is installed for users who want to use wrapper commands
# post-install is running inside the build dir, we need it from the source though
- install -Dm755 ../data/gamemoderun -t /app/bin
sources:
- type: git
url: https://github.com/FeralInteractive/gamemode
tag: "1.7"
commit: 4dc99dff76218718763a6b07fc1900fa6d1dafd9
- name: enhance
buildsystem: simple
build-commands:
- mkdir -p /app/utils/gamescope
- install -Dm755 prime-run /app/bin/prime-run
- mv /app/bin/prismlauncher /app/bin/prismrun
- install -Dm755 prismlauncher /app/bin/prismlauncher
sources:
- type: file
path: ../flatpak/prime-run
- type: file
path: ../flatpak/prismlauncher

4
flatpak/prime-run Normal file
View File

@ -0,0 +1,4 @@
#!/bin/sh
export __NV_PRIME_RENDER_OFFLOAD=1 __VK_LAYER_NV_optimus=NVIDIA_only __GLX_VENDOR_LIBRARY_NAME=nvidia
exec "$@"

11
flatpak/prismlauncher Normal file
View File

@ -0,0 +1,11 @@
#!/bin/bash
# discord RPC
for i in {0..9}; do
test -S "$XDG_RUNTIME_DIR"/discord-ipc-"$i" || ln -sf {app/com.discordapp.Discord,"$XDG_RUNTIME_DIR"}/discord-ipc-"$i";
done
export PATH="${PATH}${PATH:+:}/app/utils/gamescope/bin:/usr/lib/extensions/vulkan/MangoHud/bin"
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}${LD_LIBRARY_PATH:+:}/usr/lib/extensions/vulkan/MangoHud/\$LIB/"
exec /app/bin/prismrun "$@"

View File

@ -1,4 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-FileCopyrightText: 2022 Sefa Eyeoglu <contact@scrumplex.net>
//
// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0
/* /*
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
@ -38,10 +41,14 @@
#include "Application.h" #include "Application.h"
#include "BuildConfig.h" #include "BuildConfig.h"
#include "DataMigrationTask.h"
#include "net/PasteUpload.h" #include "net/PasteUpload.h"
#include "pathmatcher/MultiMatcher.h"
#include "pathmatcher/SimplePrefixMatcher.h"
#include "ui/MainWindow.h" #include "ui/MainWindow.h"
#include "ui/InstanceWindow.h" #include "ui/InstanceWindow.h"
#include "ui/dialogs/ProgressDialog.h"
#include "ui/instanceview/AccessibleInstanceView.h" #include "ui/instanceview/AccessibleInstanceView.h"
#include "ui/pages/BasePageProvider.h" #include "ui/pages/BasePageProvider.h"
@ -90,6 +97,7 @@
#include <QIcon> #include <QIcon>
#include "InstanceList.h" #include "InstanceList.h"
#include "MTPixmapCache.h"
#include <minecraft/auth/AccountList.h> #include <minecraft/auth/AccountList.h>
#include "icons/IconList.h" #include "icons/IconList.h"
@ -118,6 +126,7 @@
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
#include <dlfcn.h> #include <dlfcn.h>
#include "gamemode_client.h" #include "gamemode_client.h"
#include "MangoHud.h"
#endif #endif
@ -134,6 +143,8 @@
static const QLatin1String liveCheckFile("live.check"); static const QLatin1String liveCheckFile("live.check");
PixmapCache* PixmapCache::s_instance = nullptr;
namespace { namespace {
void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{ {
@ -226,7 +237,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
setOrganizationDomain(BuildConfig.LAUNCHER_DOMAIN); setOrganizationDomain(BuildConfig.LAUNCHER_DOMAIN);
setApplicationName(BuildConfig.LAUNCHER_NAME); setApplicationName(BuildConfig.LAUNCHER_NAME);
setApplicationDisplayName(QString("%1 %2").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString())); setApplicationDisplayName(QString("%1 %2").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString()));
setApplicationVersion(BuildConfig.printableVersionString()); setApplicationVersion(BuildConfig.printableVersionString() + "\n" + BuildConfig.GIT_COMMIT);
setDesktopFileName(BuildConfig.LAUNCHER_DESKTOPFILENAME); setDesktopFileName(BuildConfig.LAUNCHER_DESKTOPFILENAME);
startTime = QDateTime::currentDateTime(); startTime = QDateTime::currentDateTime();
@ -301,24 +312,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
dataPath = foo.absolutePath(); dataPath = foo.absolutePath();
adjustedBy = "Persistent data path"; adjustedBy = "Persistent data path";
/*
QDir polymcData(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation), "PolyMC"));
if (polymcData.exists()) {
dataPath = polymcData.absolutePath();
adjustedBy = "PolyMC data path";
}
*/
#ifdef Q_OS_LINUX
// TODO: this should be removed in a future version
// TODO: provide a migration path similar to macOS migration
QDir bar(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation), "polymc"));
if (bar.exists()) {
dataPath = bar.absolutePath();
adjustedBy = "Legacy data path";
}
#endif
#ifndef Q_OS_MACOS #ifndef Q_OS_MACOS
if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) { if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) {
dataPath = m_rootPath; dataPath = m_rootPath;
@ -441,6 +434,15 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
qDebug() << "<> Log initialized."; qDebug() << "<> Log initialized.";
} }
{
bool migrated = false;
if (!migrated)
migrated = handleDataMigration(dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../PolyMC"), "PolyMC", "polymc.cfg");
if (!migrated)
migrated = handleDataMigration(dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../multimc"), "MultiMC", "multimc.cfg");
}
{ {
qDebug() << BuildConfig.LAUNCHER_DISPLAYNAME << ", (c) 2013-2021 " << BuildConfig.LAUNCHER_COPYRIGHT; qDebug() << BuildConfig.LAUNCHER_DISPLAYNAME << ", (c) 2013-2021 " << BuildConfig.LAUNCHER_COPYRIGHT;
@ -695,6 +697,9 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_globalSettingsProvider->addPage<AccountListPage>(); m_globalSettingsProvider->addPage<AccountListPage>();
m_globalSettingsProvider->addPage<APIPage>(); m_globalSettingsProvider->addPage<APIPage>();
} }
PixmapCache::setInstance(new PixmapCache(this));
qDebug() << "<> Settings loaded."; qDebug() << "<> Settings loaded.";
} }
@ -918,13 +923,13 @@ bool Application::createSetupWizard()
return false; return false;
} }
bool Application::event(QEvent* event) { bool Application::event(QEvent* event)
{
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
if (event->type() == QEvent::ApplicationStateChange) { if (event->type() == QEvent::ApplicationStateChange) {
auto ev = static_cast<QApplicationStateChangeEvent*>(event); auto ev = static_cast<QApplicationStateChangeEvent*>(event);
if (m_prevAppState == Qt::ApplicationActive if (m_prevAppState == Qt::ApplicationActive && ev->applicationState() == Qt::ApplicationActive) {
&& ev->applicationState() == Qt::ApplicationActive) {
emit clickedOnDock(); emit clickedOnDock();
} }
m_prevAppState = ev->applicationState(); m_prevAppState = ev->applicationState();
@ -1522,17 +1527,8 @@ void Application::updateCapabilities()
if (gamemode_query_status() >= 0) if (gamemode_query_status() >= 0)
m_capabilities |= SupportsGameMode; m_capabilities |= SupportsGameMode;
{ if (!MangoHud::getLibraryString().isEmpty())
void *dummy = dlopen("libMangoHud_dlsym.so", RTLD_LAZY);
// try normal variant as well
if (dummy == NULL)
dummy = dlopen("libMangoHud.so", RTLD_LAZY);
if (dummy != NULL) {
dlclose(dummy);
m_capabilities |= SupportsMangoHud; m_capabilities |= SupportsMangoHud;
}
}
#endif #endif
} }
@ -1608,3 +1604,89 @@ int Application::suitableMaxMem()
return maxMemoryAlloc; return maxMemoryAlloc;
} }
bool Application::handleDataMigration(const QString& currentData,
const QString& oldData,
const QString& name,
const QString& configFile) const
{
QString nomigratePath = FS::PathCombine(currentData, name + "_nomigrate.txt");
QStringList configPaths = { FS::PathCombine(oldData, configFile), FS::PathCombine(oldData, BuildConfig.LAUNCHER_CONFIGFILE) };
QLocale locale;
// Is there a valid config at the old location?
bool configExists = false;
for (QString configPath : configPaths) {
configExists |= QFileInfo::exists(configPath);
}
if (!configExists || QFileInfo::exists(nomigratePath)) {
qDebug() << "<> No migration needed from" << name;
return false;
}
QString message;
bool currentExists = QFileInfo::exists(FS::PathCombine(currentData, BuildConfig.LAUNCHER_CONFIGFILE));
if (currentExists) {
message = tr("Old data from %1 was found, but you already have existing data for %2. Sadly you will need to migrate yourself. Do "
"you want to be reminded of the pending data migration next time you start %2?")
.arg(name, BuildConfig.LAUNCHER_DISPLAYNAME);
} else {
message = tr("It looks like you used %1 before. Do you want to migrate your data to the new location of %2?")
.arg(name, BuildConfig.LAUNCHER_DISPLAYNAME);
QFileInfo logInfo(FS::PathCombine(oldData, name + "-0.log"));
if (logInfo.exists()) {
QString lastModified = logInfo.lastModified().toString(locale.dateFormat());
message = tr("It looks like you used %1 on %2 before. Do you want to migrate your data to the new location of %3?")
.arg(name, lastModified, BuildConfig.LAUNCHER_DISPLAYNAME);
}
}
QMessageBox::StandardButton askMoveDialogue =
QMessageBox::question(nullptr, BuildConfig.LAUNCHER_DISPLAYNAME, message, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
auto setDoNotMigrate = [&nomigratePath] {
QFile file(nomigratePath);
file.open(QIODevice::WriteOnly);
};
// create no-migrate file if user doesn't want to migrate
if (askMoveDialogue != QMessageBox::Yes) {
qDebug() << "<> Migration declined for" << name;
setDoNotMigrate();
return currentExists; // cancel further migrations, if we already have a data directory
}
if (!currentExists) {
// Migrate!
auto matcher = std::make_shared<MultiMatcher>();
matcher->add(std::make_shared<SimplePrefixMatcher>(configFile));
matcher->add(std::make_shared<SimplePrefixMatcher>(
BuildConfig.LAUNCHER_CONFIGFILE)); // it's possible that we already used that directory before
matcher->add(std::make_shared<SimplePrefixMatcher>("accounts.json"));
matcher->add(std::make_shared<SimplePrefixMatcher>("accounts/"));
matcher->add(std::make_shared<SimplePrefixMatcher>("assets/"));
matcher->add(std::make_shared<SimplePrefixMatcher>("icons/"));
matcher->add(std::make_shared<SimplePrefixMatcher>("instances/"));
matcher->add(std::make_shared<SimplePrefixMatcher>("libraries/"));
matcher->add(std::make_shared<SimplePrefixMatcher>("mods/"));
matcher->add(std::make_shared<SimplePrefixMatcher>("themes/"));
ProgressDialog diag;
DataMigrationTask task(nullptr, oldData, currentData, matcher);
if (diag.execWithTask(&task)) {
qDebug() << "<> Migration succeeded";
setDoNotMigrate();
} else {
QString reason = task.failReason();
QMessageBox::critical(nullptr, BuildConfig.LAUNCHER_DISPLAYNAME, tr("Migration failed! Reason: %1").arg(reason));
}
} else {
qWarning() << "<> Migration was skipped, due to existing data";
}
return true;
}

View File

@ -231,6 +231,7 @@ private slots:
void setupWizardFinished(int status); void setupWizardFinished(int status);
private: private:
bool handleDataMigration(const QString & currentData, const QString & oldData, const QString & name, const QString & configFile) const;
bool createSetupWizard(); bool createSetupWizard();
void performMainStartupAction(); void performMainStartupAction();

View File

@ -47,8 +47,8 @@ void ApplicationMessage::parse(const QByteArray & input) {
args.clear(); args.clear();
auto parsedArgs = root.value("args").toObject(); auto parsedArgs = root.value("args").toObject();
for(auto iter = parsedArgs.begin(); iter != parsedArgs.end(); iter++) { for(auto iter = parsedArgs.constBegin(); iter != parsedArgs.constEnd(); iter++) {
args[iter.key()] = iter.value().toString(); args.insert(iter.key(), iter.value().toString());
} }
} }
@ -56,8 +56,8 @@ QByteArray ApplicationMessage::serialize() {
QJsonObject root; QJsonObject root;
root.insert("command", command); root.insert("command", command);
QJsonObject outArgs; QJsonObject outArgs;
for (auto iter = args.begin(); iter != args.end(); iter++) { for (auto iter = args.constBegin(); iter != args.constEnd(); iter++) {
outArgs[iter.key()] = iter.value(); outArgs.insert(iter.key(), iter.value());
} }
root.insert("args", outArgs); root.insert("args", outArgs);

View File

@ -1,12 +1,12 @@
#pragma once #pragma once
#include <QString> #include <QString>
#include <QMap> #include <QHash>
#include <QByteArray> #include <QByteArray>
struct ApplicationMessage { struct ApplicationMessage {
QString command; QString command;
QMap<QString, QString> args; QHash<QString, QString> args;
QByteArray serialize(); QByteArray serialize();
void parse(const QByteArray & input); void parse(const QByteArray & input);

View File

@ -151,7 +151,7 @@ public:
void copyManagedPack(BaseInstance& other); void copyManagedPack(BaseInstance& other);
/// guess log level from a line of game log /// guess log level from a line of game log
virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) virtual MessageLevel::Enum guessLevel([[maybe_unused]] const QString &line, MessageLevel::Enum level)
{ {
return level; return level;
}; };

View File

@ -95,12 +95,12 @@ BaseVersionList::RoleList BaseVersionList::providesRoles() const
int BaseVersionList::rowCount(const QModelIndex &parent) const int BaseVersionList::rowCount(const QModelIndex &parent) const
{ {
// Return count // Return count
return count(); return parent.isValid() ? 0 : count();
} }
int BaseVersionList::columnCount(const QModelIndex &parent) const int BaseVersionList::columnCount(const QModelIndex &parent) const
{ {
return 1; return parent.isValid() ? 0 : 1;
} }
QHash<int, QByteArray> BaseVersionList::roleNames() const QHash<int, QByteArray> BaseVersionList::roleNames() const

View File

@ -89,7 +89,18 @@ set(CORE_SOURCES
# Time # Time
MMCTime.h MMCTime.h
MMCTime.cpp MMCTime.cpp
MTPixmapCache.h
) )
if (UNIX AND NOT CYGWIN AND NOT APPLE)
set(CORE_SOURCES
${CORE_SOURCES}
# MangoHud
MangoHud.h
MangoHud.cpp
)
endif()
set(PATHMATCHER_SOURCES set(PATHMATCHER_SOURCES
# Path matchers # Path matchers
@ -97,6 +108,7 @@ set(PATHMATCHER_SOURCES
pathmatcher/IPathMatcher.h pathmatcher/IPathMatcher.h
pathmatcher/MultiMatcher.h pathmatcher/MultiMatcher.h
pathmatcher/RegexpMatcher.h pathmatcher/RegexpMatcher.h
pathmatcher/SimplePrefixMatcher.h
) )
set(NET_SOURCES set(NET_SOURCES
@ -583,6 +595,8 @@ SET(LAUNCHER_SOURCES
# Application base # Application base
Application.h Application.h
Application.cpp Application.cpp
DataMigrationTask.h
DataMigrationTask.cpp
UpdateController.cpp UpdateController.cpp
UpdateController.h UpdateController.h
ApplicationMessage.h ApplicationMessage.h
@ -606,6 +620,8 @@ SET(LAUNCHER_SOURCES
resources/pe_light/pe_light.qrc resources/pe_light/pe_light.qrc
resources/pe_colored/pe_colored.qrc resources/pe_colored/pe_colored.qrc
resources/pe_blue/pe_blue.qrc resources/pe_blue/pe_blue.qrc
resources/breeze_dark/breeze_dark.qrc
resources/breeze_light/breeze_light.qrc
resources/OSX/OSX.qrc resources/OSX/OSX.qrc
resources/iOS/iOS.qrc resources/iOS/iOS.qrc
resources/flat/flat.qrc resources/flat/flat.qrc
@ -683,6 +699,8 @@ SET(LAUNCHER_SOURCES
ui/pages/instance/GameOptionsPage.h ui/pages/instance/GameOptionsPage.h
ui/pages/instance/VersionPage.cpp ui/pages/instance/VersionPage.cpp
ui/pages/instance/VersionPage.h ui/pages/instance/VersionPage.h
ui/pages/instance/ManagedPackPage.cpp
ui/pages/instance/ManagedPackPage.h
ui/pages/instance/TexturePackPage.h ui/pages/instance/TexturePackPage.h
ui/pages/instance/ResourcePackPage.h ui/pages/instance/ResourcePackPage.h
ui/pages/instance/ShaderPackPage.h ui/pages/instance/ShaderPackPage.h
@ -801,6 +819,8 @@ SET(LAUNCHER_SOURCES
ui/dialogs/ExportInstanceDialog.h ui/dialogs/ExportInstanceDialog.h
ui/dialogs/IconPickerDialog.cpp ui/dialogs/IconPickerDialog.cpp
ui/dialogs/IconPickerDialog.h ui/dialogs/IconPickerDialog.h
ui/dialogs/ImportResourcePackDialog.cpp
ui/dialogs/ImportResourcePackDialog.h
ui/dialogs/LoginDialog.cpp ui/dialogs/LoginDialog.cpp
ui/dialogs/LoginDialog.h ui/dialogs/LoginDialog.h
ui/dialogs/MSALoginDialog.cpp ui/dialogs/MSALoginDialog.cpp
@ -922,6 +942,7 @@ qt_wrap_ui(LAUNCHER_UI
ui/pages/instance/OtherLogsPage.ui ui/pages/instance/OtherLogsPage.ui
ui/pages/instance/InstanceSettingsPage.ui ui/pages/instance/InstanceSettingsPage.ui
ui/pages/instance/VersionPage.ui ui/pages/instance/VersionPage.ui
ui/pages/instance/ManagedPackPage.ui
ui/pages/instance/WorldListPage.ui ui/pages/instance/WorldListPage.ui
ui/pages/instance/ScreenshotsPage.ui ui/pages/instance/ScreenshotsPage.ui
ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui
@ -949,6 +970,7 @@ qt_wrap_ui(LAUNCHER_UI
ui/dialogs/SkinUploadDialog.ui ui/dialogs/SkinUploadDialog.ui
ui/dialogs/ExportInstanceDialog.ui ui/dialogs/ExportInstanceDialog.ui
ui/dialogs/IconPickerDialog.ui ui/dialogs/IconPickerDialog.ui
ui/dialogs/ImportResourcePackDialog.ui
ui/dialogs/MSALoginDialog.ui ui/dialogs/MSALoginDialog.ui
ui/dialogs/OfflineLoginDialog.ui ui/dialogs/OfflineLoginDialog.ui
ui/dialogs/ElybyLoginDialog.ui ui/dialogs/ElybyLoginDialog.ui
@ -968,6 +990,8 @@ qt_add_resources(LAUNCHER_RESOURCES
resources/pe_light/pe_light.qrc resources/pe_light/pe_light.qrc
resources/pe_colored/pe_colored.qrc resources/pe_colored/pe_colored.qrc
resources/pe_blue/pe_blue.qrc resources/pe_blue/pe_blue.qrc
resources/breeze_dark/breeze_dark.qrc
resources/breeze_light/breeze_light.qrc
resources/OSX/OSX.qrc resources/OSX/OSX.qrc
resources/iOS/iOS.qrc resources/iOS/iOS.qrc
resources/flat/flat.qrc resources/flat/flat.qrc
@ -1153,6 +1177,8 @@ if(INSTALL_BUNDLE STREQUAL "full")
CONFIGURATIONS Debug RelWithDebInfo "" CONFIGURATIONS Debug RelWithDebInfo ""
DESTINATION ${PLUGIN_DEST_DIR} DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime COMPONENT Runtime
PATTERN "*qopensslbackend*" EXCLUDE
PATTERN "*qcertonlybackend*" EXCLUDE
) )
install( install(
DIRECTORY "${QT_PLUGINS_DIR}/tls" DIRECTORY "${QT_PLUGINS_DIR}/tls"
@ -1162,6 +1188,8 @@ if(INSTALL_BUNDLE STREQUAL "full")
REGEX "dd\\." EXCLUDE REGEX "dd\\." EXCLUDE
REGEX "_debug\\." EXCLUDE REGEX "_debug\\." EXCLUDE
REGEX "\\.dSYM" EXCLUDE REGEX "\\.dSYM" EXCLUDE
PATTERN "*qopensslbackend*" EXCLUDE
PATTERN "*qcertonlybackend*" EXCLUDE
) )
endif() endif()
configure_file( configure_file(

View File

@ -0,0 +1,96 @@
// SPDX-FileCopyrightText: 2022 Sefa Eyeoglu <contact@scrumplex.net>
//
// SPDX-License-Identifier: GPL-3.0-only
#include "DataMigrationTask.h"
#include "FileSystem.h"
#include <QDirIterator>
#include <QFileInfo>
#include <QMap>
#include <QtConcurrent>
DataMigrationTask::DataMigrationTask(QObject* parent,
const QString& sourcePath,
const QString& targetPath,
const IPathMatcher::Ptr pathMatcher)
: Task(parent), m_sourcePath(sourcePath), m_targetPath(targetPath), m_pathMatcher(pathMatcher), m_copy(sourcePath, targetPath)
{
m_copy.matcher(m_pathMatcher.get()).whitelist(true);
}
void DataMigrationTask::executeTask()
{
setStatus(tr("Scanning files..."));
// 1. Scan
// Check how many files we gotta copy
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&] {
return m_copy(true); // dry run to collect amount of files
});
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::dryRunFinished);
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &DataMigrationTask::dryRunAborted);
m_copyFutureWatcher.setFuture(m_copyFuture);
}
void DataMigrationTask::dryRunFinished()
{
disconnect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::dryRunFinished);
disconnect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &DataMigrationTask::dryRunAborted);
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
if (!m_copyFuture.isValid() || !m_copyFuture.result()) {
#else
if (!m_copyFuture.result()) {
#endif
emitFailed(tr("Failed to scan source path."));
return;
}
// 2. Copy
// Actually copy all files now.
m_toCopy = m_copy.totalCopied();
connect(&m_copy, &FS::copy::fileCopied, [&, this](const QString& relativeName) {
QString shortenedName = relativeName;
// shorten the filename to hopefully fit into one line
if (shortenedName.length() > 50)
shortenedName = relativeName.left(20) + "" + relativeName.right(29);
setProgress(m_copy.totalCopied(), m_toCopy);
setStatus(tr("Copying %1…").arg(shortenedName));
});
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&] {
return m_copy(false); // actually copy now
});
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::copyFinished);
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &DataMigrationTask::copyAborted);
m_copyFutureWatcher.setFuture(m_copyFuture);
}
void DataMigrationTask::dryRunAborted()
{
emitFailed(tr("Aborted"));
}
void DataMigrationTask::copyFinished()
{
disconnect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::copyFinished);
disconnect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &DataMigrationTask::copyAborted);
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
if (!m_copyFuture.isValid() || !m_copyFuture.result()) {
#else
if (!m_copyFuture.result()) {
#endif
emitFailed(tr("Some paths could not be copied!"));
return;
}
emitSucceeded();
}
void DataMigrationTask::copyAborted()
{
emitFailed(tr("Aborted"));
}

View File

@ -0,0 +1,42 @@
// SPDX-FileCopyrightText: 2022 Sefa Eyeoglu <contact@scrumplex.net>
//
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include "FileSystem.h"
#include "pathmatcher/IPathMatcher.h"
#include "tasks/Task.h"
#include <QFuture>
#include <QFutureWatcher>
/*
* Migrate existing data from other MMC-like launchers.
*/
class DataMigrationTask : public Task {
Q_OBJECT
public:
explicit DataMigrationTask(QObject* parent, const QString& sourcePath, const QString& targetPath, const IPathMatcher::Ptr pathmatcher);
~DataMigrationTask() override = default;
protected:
virtual void executeTask() override;
protected slots:
void dryRunFinished();
void dryRunAborted();
void copyFinished();
void copyAborted();
private:
const QString& m_sourcePath;
const QString& m_targetPath;
const IPathMatcher::Ptr m_pathMatcher;
FS::copy m_copy;
int m_toCopy = 0;
QFuture<bool> m_copyFuture;
QFutureWatcher<bool> m_copyFutureWatcher;
};

View File

@ -49,6 +49,7 @@
#include "StringUtils.h" #include "StringUtils.h"
#if defined Q_OS_WIN32 #if defined Q_OS_WIN32
#define WIN32_LEAN_AND_MEAN
#include <objbase.h> #include <objbase.h>
#include <objidl.h> #include <objidl.h>
#include <shlguid.h> #include <shlguid.h>
@ -152,9 +153,10 @@ bool ensureFolderPathExists(QString foldernamepath)
/// @brief Copies a directory and it's contents from src to dest /// @brief Copies a directory and it's contents from src to dest
/// @param offset subdirectory form src to copy to dest /// @param offset subdirectory form src to copy to dest
/// @return if there was an error during the filecopy /// @return if there was an error during the filecopy
bool copy::operator()(const QString& offset) bool copy::operator()(const QString& offset, bool dryRun)
{ {
using copy_opts = fs::copy_options; using copy_opts = fs::copy_options;
m_copied = 0; // reset counter
// NOTE always deep copy on windows. the alternatives are too messy. // NOTE always deep copy on windows. the alternatives are too messy.
#if defined Q_OS_WIN32 #if defined Q_OS_WIN32
@ -174,18 +176,21 @@ bool copy::operator()(const QString& offset)
// Function that'll do the actual copying // Function that'll do the actual copying
auto copy_file = [&](QString src_path, QString relative_dst_path) { auto copy_file = [&](QString src_path, QString relative_dst_path) {
if (m_blacklist && m_blacklist->matches(relative_dst_path)) if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
return; return;
auto dst_path = PathCombine(dst, relative_dst_path); auto dst_path = PathCombine(dst, relative_dst_path);
if (!dryRun) {
ensureFilePathExists(dst_path); ensureFilePathExists(dst_path);
fs::copy(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), opt, err); fs::copy(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), opt, err);
}
if (err) { if (err) {
qWarning() << "Failed to copy files:" << QString::fromStdString(err.message()); qWarning() << "Failed to copy files:" << QString::fromStdString(err.message());
qDebug() << "Source file:" << src_path; qDebug() << "Source file:" << src_path;
qDebug() << "Destination file:" << dst_path; qDebug() << "Destination file:" << dst_path;
} }
m_copied++;
emit fileCopied(relative_dst_path);
}; };
// We can't use copy_opts::recursive because we need to take into account the // We can't use copy_opts::recursive because we need to take into account the
@ -339,12 +344,35 @@ QString getDesktopDir()
} }
// Cross-platform Shortcut creation // Cross-platform Shortcut creation
bool createShortCut(QString location, QString dest, QStringList args, QString name, QString icon) bool createShortcut(QString destination, QString target, QStringList args, QString name, QString icon)
{ {
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) #if defined(Q_OS_MACOS)
location = PathCombine(location, name + ".desktop"); destination += ".command";
QFile f(location); QFile f(destination);
f.open(QIODevice::WriteOnly | QIODevice::Text);
QTextStream stream(&f);
QString argstring;
if (!args.empty())
argstring = " \"" + args.join("\" \"") + "\"";
stream << "#!/bin/bash"
<< "\n";
stream << "\""
<< target
<< "\" "
<< argstring
<< "\n";
stream.flush();
f.close();
f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | QFileDevice::ExeOther);
return true;
#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
QFile f(destination);
f.open(QIODevice::WriteOnly | QIODevice::Text); f.open(QIODevice::WriteOnly | QIODevice::Text);
QTextStream stream(&f); QTextStream stream(&f);
@ -356,10 +384,12 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na
<< "\n"; << "\n";
stream << "Type=Application" stream << "Type=Application"
<< "\n"; << "\n";
stream << "TryExec=" << dest.toLocal8Bit() << "\n"; stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n";
stream << "Exec=" << dest.toLocal8Bit() << argstring.toLocal8Bit() << "\n";
stream << "Name=" << name.toLocal8Bit() << "\n"; stream << "Name=" << name.toLocal8Bit() << "\n";
if (!icon.isEmpty())
{
stream << "Icon=" << icon.toLocal8Bit() << "\n"; stream << "Icon=" << icon.toLocal8Bit() << "\n";
}
stream.flush(); stream.flush();
f.close(); f.close();
@ -367,25 +397,132 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na
f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | QFileDevice::ExeOther); f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | QFileDevice::ExeOther);
return true; return true;
#elif defined Q_OS_WIN #elif defined(Q_OS_WIN)
// TODO: Fix QFileInfo targetInfo(target);
// QFile file(PathCombine(location, name + ".lnk"));
// WCHAR *file_w;
// WCHAR *dest_w;
// WCHAR *args_w;
// file.fileName().toWCharArray(file_w);
// dest.toWCharArray(dest_w);
// QString argStr; if (!targetInfo.exists())
// for (int i = 0; i < args.count(); i++) {
// { qWarning() << "Target file does not exist!";
// argStr.append(args[i]);
// argStr.append(" ");
// }
// argStr.toWCharArray(args_w);
// return SUCCEEDED(CreateLink(file_w, dest_w, args_w));
return false; return false;
}
target = targetInfo.absoluteFilePath();
if (target.length() >= MAX_PATH)
{
qWarning() << "Target file path is too long!";
return false;
}
if (!icon.isEmpty() && icon.length() >= MAX_PATH)
{
qWarning() << "Icon path is too long!";
return false;
}
destination += ".lnk";
if (destination.length() >= MAX_PATH)
{
qWarning() << "Destination path is too long!";
return false;
}
QString argStr;
int argCount = args.count();
for (int i = 0; i < argCount; i++)
{
if (args[i].contains(' '))
{
argStr.append('"').append(args[i]).append('"');
}
else
{
argStr.append(args[i]);
}
if (i < argCount - 1)
{
argStr.append(" ");
}
}
if (argStr.length() >= MAX_PATH)
{
qWarning() << "Arguments string is too long!";
return false;
}
HRESULT hres;
// ...yes, you need to initialize the entire COM stack just to make a shortcut
hres = CoInitialize(nullptr);
if (FAILED(hres))
{
qWarning() << "Failed to initialize COM!";
return false;
}
WCHAR wsz[MAX_PATH];
IShellLink* psl;
// create an IShellLink instance - this stores the shortcut's attributes
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
if (SUCCEEDED(hres))
{
wmemset(wsz, 0, MAX_PATH);
target.toWCharArray(wsz);
psl->SetPath(wsz);
wmemset(wsz, 0, MAX_PATH);
argStr.toWCharArray(wsz);
psl->SetArguments(wsz);
wmemset(wsz, 0, MAX_PATH);
targetInfo.absolutePath().toWCharArray(wsz);
psl->SetWorkingDirectory(wsz); // "Starts in" attribute
if (!icon.isEmpty())
{
wmemset(wsz, 0, MAX_PATH);
icon.toWCharArray(wsz);
psl->SetIconLocation(wsz, 0);
}
// query an IPersistFile interface from our IShellLink instance
// this is the interface that will actually let us save the shortcut to disk!
IPersistFile* ppf;
hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf);
if (SUCCEEDED(hres))
{
wmemset(wsz, 0, MAX_PATH);
destination.toWCharArray(wsz);
hres = ppf->Save(wsz, TRUE);
if (FAILED(hres))
{
qWarning() << "IPresistFile->Save() failed";
qWarning() << "hres = " << hres;
}
ppf->Release();
}
else
{
qWarning() << "Failed to query IPersistFile interface from IShellLink instance";
qWarning() << "hres = " << hres;
}
psl->Release();
}
else
{
qWarning() << "Failed to create IShellLink instance";
qWarning() << "hres = " << hres;
}
// go away COM, nobody likes you
CoUninitialize();
return SUCCEEDED(hres);
#else #else
qWarning("Desktop Shortcuts not supported on your platform!"); qWarning("Desktop Shortcuts not supported on your platform!");
return false; return false;

View File

@ -40,6 +40,7 @@
#include <QDir> #include <QDir>
#include <QFlags> #include <QFlags>
#include <QObject>
namespace FS { namespace FS {
@ -76,9 +77,10 @@ bool ensureFilePathExists(QString filenamepath);
bool ensureFolderPathExists(QString filenamepath); bool ensureFolderPathExists(QString filenamepath);
/// @brief Copies a directory and it's contents from src to dest /// @brief Copies a directory and it's contents from src to dest
class copy { class copy : public QObject {
Q_OBJECT
public: public:
copy(const QString& src, const QString& dst) copy(const QString& src, const QString& dst, QObject* parent = nullptr) : QObject(parent)
{ {
m_src.setPath(src); m_src.setPath(src);
m_dst.setPath(dst); m_dst.setPath(dst);
@ -88,21 +90,35 @@ class copy {
m_followSymlinks = follow; m_followSymlinks = follow;
return *this; return *this;
} }
copy& blacklist(const IPathMatcher* filter) copy& matcher(const IPathMatcher* filter)
{ {
m_blacklist = filter; m_matcher = filter;
return *this; return *this;
} }
bool operator()() { return operator()(QString()); } copy& whitelist(bool whitelist)
{
m_whitelist = whitelist;
return *this;
}
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
int totalCopied() { return m_copied; }
signals:
void fileCopied(const QString& relativeName);
// TODO: maybe add a "shouldCopy" signal in the future?
private: private:
bool operator()(const QString& offset); bool operator()(const QString& offset, bool dryRun = false);
private: private:
bool m_followSymlinks = true; bool m_followSymlinks = true;
const IPathMatcher* m_blacklist = nullptr; const IPathMatcher* m_matcher = nullptr;
bool m_whitelist = false;
QDir m_src; QDir m_src;
QDir m_dst; QDir m_dst;
int m_copied;
}; };
/** /**
@ -156,4 +172,9 @@ QString getDesktopDir();
// Overrides one folder with the contents of another, preserving items exclusive to the first folder // Overrides one folder with the contents of another, preserving items exclusive to the first folder
// Equivalent to doing QDir::rename, but allowing for overrides // Equivalent to doing QDir::rename, but allowing for overrides
bool overrideFolder(QString overwritten_path, QString override_path); bool overrideFolder(QString overwritten_path, QString override_path);
/**
* Creates a shortcut to the specified target file at the specified destination path.
*/
bool createShortcut(QString destination, QString target, QStringList args, QString name, QString icon);
} }

View File

@ -25,10 +25,12 @@ void InstanceCopyTask::executeTask()
{ {
setStatus(tr("Copying instance %1").arg(m_origInstance->name())); setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]{
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath); FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
folderCopy.followSymlinks(false).blacklist(m_matcher.get()); folderCopy.followSymlinks(false).matcher(m_matcher.get());
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), folderCopy); return folderCopy();
});
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &InstanceCopyTask::copyFinished); connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &InstanceCopyTask::copyFinished);
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &InstanceCopyTask::copyAborted); connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &InstanceCopyTask::copyAborted);
m_copyFutureWatcher.setFuture(m_copyFuture); m_copyFutureWatcher.setFuture(m_copyFuture);

View File

@ -25,9 +25,13 @@ void InstanceCreationTask::executeTask()
return; return;
qWarning() << "Instance creation failed!"; qWarning() << "Instance creation failed!";
if (!m_error_message.isEmpty()) if (!m_error_message.isEmpty()) {
qWarning() << "Reason: " << m_error_message; qWarning() << "Reason: " << m_error_message;
emitFailed(tr("Error while creating new instance:\n%1").arg(m_error_message));
} else {
emitFailed(tr("Error while creating new instance.")); emitFailed(tr("Error while creating new instance."));
}
return; return;
} }

View File

@ -55,11 +55,9 @@
#include <quazip/quazipdir.h> #include <quazip/quazipdir.h>
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent) InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent, QMap<QString, QString>&& extra_info)
{ : m_sourceUrl(sourceUrl), m_extra_info(extra_info), m_parent(parent)
m_sourceUrl = sourceUrl; {}
m_parent = parent;
}
bool InstanceImportTask::abort() bool InstanceImportTask::abort()
{ {
@ -164,18 +162,14 @@ void InstanceImportTask::processZipPack()
} }
else else
{ {
QString mmcRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg"); QStringList paths_to_ignore { "overrides/" };
QString flameRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json");
if (!mmcRoot.isNull()) if (QString mmcRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg", paths_to_ignore); !mmcRoot.isNull()) {
{
// process as MultiMC instance/pack // process as MultiMC instance/pack
qDebug() << "MultiMC:" << mmcRoot; qDebug() << "MultiMC:" << mmcRoot;
root = mmcRoot; root = mmcRoot;
m_modpackType = ModpackType::MultiMC; m_modpackType = ModpackType::MultiMC;
} } else if (QString flameRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json", paths_to_ignore); !flameRoot.isNull()) {
else if(!flameRoot.isNull())
{
// process as Flame pack // process as Flame pack
qDebug() << "Flame:" << flameRoot; qDebug() << "Flame:" << flameRoot;
root = flameRoot; root = flameRoot;
@ -263,14 +257,34 @@ void InstanceImportTask::extractAborted()
void InstanceImportTask::processFlame() void InstanceImportTask::processFlame()
{ {
auto* inst_creation_task = new FlameCreationTask(m_stagingPath, m_globalSettings, m_parent); FlameCreationTask* inst_creation_task = nullptr;
if (!m_extra_info.isEmpty()) {
auto pack_id_it = m_extra_info.constFind("pack_id");
Q_ASSERT(pack_id_it != m_extra_info.constEnd());
auto pack_id = pack_id_it.value();
auto pack_version_id_it = m_extra_info.constFind("pack_version_id");
Q_ASSERT(pack_version_id_it != m_extra_info.constEnd());
auto pack_version_id = pack_version_id_it.value();
QString original_instance_id;
auto original_instance_id_it = m_extra_info.constFind("original_instance_id");
if (original_instance_id_it != m_extra_info.constEnd())
original_instance_id = original_instance_id_it.value();
inst_creation_task = new FlameCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
} else {
// FIXME: Find a way to get IDs in directly imported ZIPs
inst_creation_task = new FlameCreationTask(m_stagingPath, m_globalSettings, m_parent, {}, {});
}
inst_creation_task->setName(*this); inst_creation_task->setName(*this);
inst_creation_task->setIcon(m_instIcon); inst_creation_task->setIcon(m_instIcon);
inst_creation_task->setGroup(m_instGroup); inst_creation_task->setGroup(m_instGroup);
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] { connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] {
setOverride(inst_creation_task->shouldOverride()); setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID());
emitSucceeded(); emitSucceeded();
}); });
connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed); connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed);
@ -327,14 +341,41 @@ void InstanceImportTask::processMultiMC()
void InstanceImportTask::processModrinth() void InstanceImportTask::processModrinth()
{ {
auto* inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, m_sourceUrl.toString()); ModrinthCreationTask* inst_creation_task = nullptr;
if (!m_extra_info.isEmpty()) {
auto pack_id_it = m_extra_info.constFind("pack_id");
Q_ASSERT(pack_id_it != m_extra_info.constEnd());
auto pack_id = pack_id_it.value();
QString pack_version_id;
auto pack_version_id_it = m_extra_info.constFind("pack_version_id");
if (pack_version_id_it != m_extra_info.constEnd())
pack_version_id = pack_version_id_it.value();
QString original_instance_id;
auto original_instance_id_it = m_extra_info.constFind("original_instance_id");
if (original_instance_id_it != m_extra_info.constEnd())
original_instance_id = original_instance_id_it.value();
inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
} else {
QString pack_id;
if (!m_sourceUrl.isEmpty()) {
QRegularExpression regex(R"(data\/(.*)\/versions)");
pack_id = regex.match(m_sourceUrl.toString()).captured(1);
}
// FIXME: Find a way to get the ID in directly imported ZIPs
inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id);
}
inst_creation_task->setName(*this); inst_creation_task->setName(*this);
inst_creation_task->setIcon(m_instIcon); inst_creation_task->setIcon(m_instIcon);
inst_creation_task->setGroup(m_instGroup); inst_creation_task->setGroup(m_instGroup);
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] { connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] {
setOverride(inst_creation_task->shouldOverride()); setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID());
emitSucceeded(); emitSucceeded();
}); });
connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed); connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed);

View File

@ -56,7 +56,7 @@ class InstanceImportTask : public InstanceTask
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit InstanceImportTask(const QUrl sourceUrl, QWidget* parent = nullptr); explicit InstanceImportTask(const QUrl sourceUrl, QWidget* parent = nullptr, QMap<QString, QString>&& extra_info = {});
bool abort() override; bool abort() override;
const QVector<Flame::File> &getBlockedFiles() const const QVector<Flame::File> &getBlockedFiles() const
@ -101,6 +101,10 @@ private: /* data */
Modrinth, Modrinth,
} m_modpackType = ModpackType::Unknown; } m_modpackType = ModpackType::Unknown;
// Extra info we might need, that's available before, but can't be derived from
// the source URL / the resource it points to alone.
QMap<QString, QString> m_extra_info;
//FIXME: nuke //FIXME: nuke
QWidget* m_parent; QWidget* m_parent;
}; };

View File

@ -816,7 +816,7 @@ class InstanceStaging : public Task {
void childSucceded() void childSucceded()
{ {
unsigned sleepTime = backoff(); unsigned sleepTime = backoff();
if (m_parent->commitStagedInstance(m_stagingPath, m_instance_name, m_groupName, m_child->shouldOverride())) if (m_parent->commitStagedInstance(m_stagingPath, m_instance_name, m_groupName, *m_child.get()))
{ {
emitSucceeded(); emitSucceeded();
return; return;
@ -880,25 +880,22 @@ QString InstanceList::getStagedInstancePath()
return path; return path;
} }
bool InstanceList::commitStagedInstance(const QString& path, InstanceName const& instanceName, const QString& groupName, bool should_override) bool InstanceList::commitStagedInstance(const QString& path, InstanceName const& instanceName, const QString& groupName, InstanceTask const& commiting)
{ {
QDir dir; QDir dir;
QString instID; QString instID;
InstancePtr inst; InstancePtr inst;
auto should_override = commiting.shouldOverride();
if (should_override) { if (should_override) {
// This is to avoid problems when the instance folder gets manually renamed instID = commiting.originalInstanceID();
if ((inst = getInstanceByManagedName(instanceName.originalName()))) {
instID = QFileInfo(inst->instanceRoot()).fileName();
} else if ((inst = getInstanceByManagedName(instanceName.modifiedName()))) {
instID = QFileInfo(inst->instanceRoot()).fileName();
} else {
instID = FS::RemoveInvalidFilenameChars(instanceName.modifiedName(), '-');
}
} else { } else {
instID = FS::DirNameFromString(instanceName.modifiedName(), m_instDir); instID = FS::DirNameFromString(instanceName.modifiedName(), m_instDir);
} }
Q_ASSERT(!instID.isEmpty());
{ {
WatchLock lock(m_watcher, m_instDir); WatchLock lock(m_watcher, m_instDir);
QString destination = FS::PathCombine(m_instDir, instID); QString destination = FS::PathCombine(m_instDir, instID);

View File

@ -133,7 +133,7 @@ public:
* should_override is used when another similar instance already exists, and we want to override it * should_override is used when another similar instance already exists, and we want to override it
* - for instance, when updating it. * - for instance, when updating it.
*/ */
bool commitStagedInstance(const QString& keyPath, const InstanceName& instanceName, const QString& groupName, bool should_override); bool commitStagedInstance(const QString& keyPath, const InstanceName& instanceName, const QString& groupName, const InstanceTask&);
/** /**
* Destroy a previously created staging area given by @keyPath - used when creation fails. * Destroy a previously created staging area given by @keyPath - used when creation fails.

View File

@ -5,6 +5,7 @@
#include "ui/pages/BasePageProvider.h" #include "ui/pages/BasePageProvider.h"
#include "ui/pages/instance/LogPage.h" #include "ui/pages/instance/LogPage.h"
#include "ui/pages/instance/VersionPage.h" #include "ui/pages/instance/VersionPage.h"
#include "ui/pages/instance/ManagedPackPage.h"
#include "ui/pages/instance/ModFolderPage.h" #include "ui/pages/instance/ModFolderPage.h"
#include "ui/pages/instance/ResourcePackPage.h" #include "ui/pages/instance/ResourcePackPage.h"
#include "ui/pages/instance/TexturePackPage.h" #include "ui/pages/instance/TexturePackPage.h"
@ -33,6 +34,7 @@ public:
values.append(new LogPage(inst)); values.append(new LogPage(inst));
std::shared_ptr<MinecraftInstance> onesix = std::dynamic_pointer_cast<MinecraftInstance>(inst); std::shared_ptr<MinecraftInstance> onesix = std::dynamic_pointer_cast<MinecraftInstance>(inst);
values.append(new VersionPage(onesix.get())); values.append(new VersionPage(onesix.get()));
values.append(ManagedPackPage::createPage(onesix.get()));
auto modsPage = new ModFolderPage(onesix.get(), onesix->loaderModList()); auto modsPage = new ModFolderPage(onesix.get(), onesix->loaderModList());
modsPage->setFilter("%1 (*.zip *.jar *.litemod)"); modsPage->setFilter("%1 (*.zip *.jar *.litemod)");
values.append(modsPage); values.append(modsPage);

View File

@ -18,6 +18,29 @@ InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& ol
return InstanceNameChange::ShouldKeep; return InstanceNameChange::ShouldKeep;
} }
ShouldUpdate askIfShouldUpdate(QWidget *parent, QString original_version_name)
{
auto info = CustomMessageBox::selectable(
parent, QObject::tr("Similar modpack was found!"),
QObject::tr("One or more of your instances are from this same modpack%1. Do you want to create a "
"separate instance, or update the existing one?\n\nNOTE: Make sure you made a backup of your important instance data before "
"updating, as worlds can be corrupted and some configuration may be lost (due to pack overrides).")
.arg(original_version_name),
QMessageBox::Information, QMessageBox::Ok | QMessageBox::Reset | QMessageBox::Abort);
info->setButtonText(QMessageBox::Ok, QObject::tr("Update existing instance"));
info->setButtonText(QMessageBox::Abort, QObject::tr("Create new instance"));
info->setButtonText(QMessageBox::Reset, QObject::tr("Cancel"));
info->exec();
if (info->clickedButton() == info->button(QMessageBox::Ok))
return ShouldUpdate::Update;
if (info->clickedButton() == info->button(QMessageBox::Abort))
return ShouldUpdate::SkipUpdating;
return ShouldUpdate::Cancel;
}
QString InstanceName::name() const QString InstanceName::name() const
{ {
if (!m_modified_name.isEmpty()) if (!m_modified_name.isEmpty())

View File

@ -6,6 +6,8 @@
/* Helpers */ /* Helpers */
enum class InstanceNameChange { ShouldChange, ShouldKeep }; enum class InstanceNameChange { ShouldChange, ShouldKeep };
[[nodiscard]] InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& old_name, const QString& new_name); [[nodiscard]] InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& old_name, const QString& new_name);
enum class ShouldUpdate { Update, SkipUpdating, Cancel };
[[nodiscard]] ShouldUpdate askIfShouldUpdate(QWidget* parent, QString original_version_name);
struct InstanceName { struct InstanceName {
public: public:
@ -42,10 +44,20 @@ class InstanceTask : public Task, public InstanceName {
void setGroup(const QString& group) { m_instGroup = group; } void setGroup(const QString& group) { m_instGroup = group; }
QString group() const { return m_instGroup; } QString group() const { return m_instGroup; }
[[nodiscard]] bool shouldConfirmUpdate() const { return m_confirm_update; }
void setConfirmUpdate(bool confirm) { m_confirm_update = confirm; }
bool shouldOverride() const { return m_override_existing; } bool shouldOverride() const { return m_override_existing; }
[[nodiscard]] QString originalInstanceID() const { return m_original_instance_id; };
protected: protected:
void setOverride(bool override) { m_override_existing = override; } void setOverride(bool override, QString instance_id_to_override = {})
{
m_override_existing = override;
if (!instance_id_to_override.isEmpty())
m_original_instance_id = instance_id_to_override;
}
protected: /* data */ protected: /* data */
SettingsObjectPtr m_globalSettings; SettingsObjectPtr m_globalSettings;
@ -54,4 +66,7 @@ class InstanceTask : public Task, public InstanceName {
QString m_stagingPath; QString m_stagingPath;
bool m_override_existing = false; bool m_override_existing = false;
bool m_confirm_update = true;
QString m_original_instance_id;
}; };

View File

@ -28,11 +28,11 @@ QString Time::prettifyDuration(int64_t duration) {
int days = (int) (duration / 24); int days = (int) (duration / 24);
if((hours == 0)&&(days == 0)) if((hours == 0)&&(days == 0))
{ {
return QObject::tr("%1m %2s").arg(minutes).arg(seconds); return QObject::tr("%1min %2s").arg(minutes).arg(seconds);
} }
if (days == 0) if (days == 0)
{ {
return QObject::tr("%1h %2m").arg(hours).arg(minutes); return QObject::tr("%1h %2min").arg(hours).arg(minutes);
} }
return QObject::tr("%1d %2h %3m").arg(days).arg(hours).arg(minutes); return QObject::tr("%1d %2h %3min").arg(days).arg(hours).arg(minutes);
} }

View File

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

View File

@ -80,9 +80,11 @@ namespace MMCZip
/** /**
* Find a single file in archive by file name (not path) * Find a single file in archive by file name (not path)
* *
* \param ignore_paths paths to skip when recursing the search
*
* \return the path prefix where the file is * \return the path prefix where the file is
*/ */
QString findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root = QString("")); QString findFolderOfFileInZip(QuaZip * zip, const QString & what, const QStringList& ignore_paths = {}, const QString &root = QString(""));
/** /**
* Find a multiple files of the same name in archive by file name * Find a multiple files of the same name in archive by file name

95
launcher/MTPixmapCache.h Normal file
View 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
View 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
View 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();
}

View File

@ -311,14 +311,14 @@ QModelIndex VersionProxyModel::index(int row, int column, const QModelIndex &par
int VersionProxyModel::columnCount(const QModelIndex &parent) const int VersionProxyModel::columnCount(const QModelIndex &parent) const
{ {
return m_columns.size(); return parent.isValid() ? 0 : m_columns.size();
} }
int VersionProxyModel::rowCount(const QModelIndex &parent) const int VersionProxyModel::rowCount(const QModelIndex &parent) const
{ {
if(sourceModel()) if(sourceModel())
{ {
return sourceModel()->rowCount(); return sourceModel()->rowCount(parent);
} }
return 0; return 0;
} }

View File

@ -242,7 +242,7 @@ Qt::DropActions IconList::supportedDropActions() const
return Qt::CopyAction; return Qt::CopyAction;
} }
bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[maybe_unused]] int row, [[maybe_unused]] int column, [[maybe_unused]] const QModelIndex &parent)
{ {
if (action == Qt::IgnoreAction) if (action == Qt::IgnoreAction)
return true; return true;
@ -302,7 +302,7 @@ QVariant IconList::data(const QModelIndex &index, int role) const
int IconList::rowCount(const QModelIndex &parent) const int IconList::rowCount(const QModelIndex &parent) const
{ {
return icons.size(); return parent.isValid() ? 0 : icons.size();
} }
void IconList::installIcons(const QStringList &iconFiles) void IconList::installIcons(const QStringList &iconFiles)
@ -354,15 +354,18 @@ const MMCIcon *IconList::icon(const QString &key) const
bool IconList::deleteIcon(const QString &key) bool IconList::deleteIcon(const QString &key)
{ {
int iconIdx = getIconIndex(key); if (!iconFileExists(key))
if (iconIdx == -1)
return false; return false;
auto &iconEntry = icons[iconIdx];
if (iconEntry.has(IconType::FileBased)) return QFile::remove(icon(key)->getFilePath());
{ }
return QFile::remove(iconEntry.m_images[IconType::FileBased].filename);
} bool IconList::trashIcon(const QString &key)
{
if (!iconFileExists(key))
return false; return false;
return FS::trash(icon(key)->getFilePath(), nullptr);
} }
bool IconList::addThemeIcon(const QString& key) bool IconList::addThemeIcon(const QString& key)

View File

@ -52,6 +52,7 @@ public:
bool addIcon(const QString &key, const QString &name, const QString &path, const IconType type); bool addIcon(const QString &key, const QString &name, const QString &path, const IconType type);
void saveIcon(const QString &key, const QString &path, const char * format) const; void saveIcon(const QString &key, const QString &path, const char * format) const;
bool deleteIcon(const QString &key); bool deleteIcon(const QString &key);
bool trashIcon(const QString &key);
bool iconFileExists(const QString &key) const; bool iconFileExists(const QString &key) const;
void installIcons(const QStringList &iconFiles); void installIcons(const QStringList &iconFiles);

View File

@ -81,6 +81,8 @@ int main(int argc, char *argv[])
Q_INIT_RESOURCE(pe_light); Q_INIT_RESOURCE(pe_light);
Q_INIT_RESOURCE(pe_blue); Q_INIT_RESOURCE(pe_blue);
Q_INIT_RESOURCE(pe_colored); Q_INIT_RESOURCE(pe_colored);
Q_INIT_RESOURCE(breeze_dark);
Q_INIT_RESOURCE(breeze_light);
Q_INIT_RESOURCE(OSX); Q_INIT_RESOURCE(OSX);
Q_INIT_RESOURCE(iOS); Q_INIT_RESOURCE(iOS);
Q_INIT_RESOURCE(flat); Q_INIT_RESOURCE(flat);

View File

@ -58,11 +58,11 @@ QVariant Index::data(const QModelIndex &index, int role) const
} }
int Index::rowCount(const QModelIndex &parent) const int Index::rowCount(const QModelIndex &parent) const
{ {
return m_lists.size(); return parent.isValid() ? 0 : m_lists.size();
} }
int Index::columnCount(const QModelIndex &parent) const int Index::columnCount(const QModelIndex &parent) const
{ {
return 1; return parent.isValid() ? 0 : 1;
} }
QVariant Index::headerData(int section, Qt::Orientation orientation, int role) const QVariant Index::headerData(int section, Qt::Orientation orientation, int role) const
{ {

View File

@ -60,11 +60,6 @@ struct Require
QString suggests; QString suggests;
}; };
inline Q_DECL_PURE_FUNCTION uint qHash(const Require &key, uint seed = 0) Q_DECL_NOTHROW
{
return qHash(key.uid, seed);
}
using RequireSet = std::set<Require>; using RequireSet = std::set<Require>;
void parseIndex(const QJsonObject &obj, Index *ptr); void parseIndex(const QJsonObject &obj, Index *ptr);

View File

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

View File

@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -87,6 +88,10 @@
#include "minecraft/gameoptions/GameOptions.h" #include "minecraft/gameoptions/GameOptions.h"
#include "minecraft/update/FoldersTask.h" #include "minecraft/update/FoldersTask.h"
#ifdef Q_OS_LINUX
#include "MangoHud.h"
#endif
#define IBUS "@im=ibus" #define IBUS "@im=ibus"
// all of this because keeping things compatible with deprecated old settings // all of this because keeping things compatible with deprecated old settings
@ -444,6 +449,17 @@ QStringList MinecraftInstance::javaArguments()
return args; return args;
} }
QString MinecraftInstance::getLauncher()
{
auto profile = m_components->getProfile();
// use legacy launcher if the traits are set
if (profile->getTraits().contains("legacyLaunch") || profile->getTraits().contains("alphaLaunch"))
return "legacy";
return "standard";
}
QMap<QString, QString> MinecraftInstance::getVariables() QMap<QString, QString> MinecraftInstance::getVariables()
{ {
QMap<QString, QString> out; QMap<QString, QString> out;
@ -478,9 +494,22 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
if (settings()->get("EnableMangoHud").toBool() && APPLICATION->capabilities() & Application::SupportsMangoHud) if (settings()->get("EnableMangoHud").toBool() && APPLICATION->capabilities() & Application::SupportsMangoHud)
{ {
auto preload = env.value("LD_PRELOAD", "") + ":libMangoHud_dlsym.so:libMangoHud.so";
env.insert("LD_PRELOAD", preload); auto preloadList = env.value("LD_PRELOAD").split(QLatin1String(":"));
auto libPaths = env.value("LD_LIBRARY_PATH").split(QLatin1String(":"));
auto mangoHudLibString = MangoHud::getLibraryString();
if (!mangoHudLibString.isEmpty())
{
QFileInfo mangoHudLib(mangoHudLibString);
// dlsym variant is only needed for OpenGL and not included in the vulkan layer
preloadList << "libMangoHud_dlsym.so" << mangoHudLib.fileName();
libPaths << mangoHudLib.absolutePath();
}
env.insert("LD_PRELOAD", preloadList.join(QLatin1String(":")));
env.insert("LD_LIBRARY_PATH", libPaths.join(QLatin1String(":")));
env.insert("MANGOHUD", "1"); env.insert("MANGOHUD", "1");
} }
@ -635,26 +664,13 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
launchScript += "sessionId " + session->session + "\n"; launchScript += "sessionId " + session->session + "\n";
} }
// libraries and class path.
{
QStringList jars, nativeJars;
profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot());
for(auto file: jars)
{
launchScript += "cp " + file + "\n";
}
for(auto file: nativeJars)
{
launchScript += "ext " + file + "\n";
}
launchScript += "natives " + getNativePath() + "\n";
}
for (auto trait : profile->getTraits()) for (auto trait : profile->getTraits())
{ {
launchScript += "traits " + trait + "\n"; launchScript += "traits " + trait + "\n";
} }
launchScript += "launcher onesix\n";
launchScript += "launcher " + getLauncher() + "\n";
// qDebug() << "Generated launch script:" << launchScript; // qDebug() << "Generated launch script:" << launchScript;
return launchScript; return launchScript;
} }
@ -790,6 +806,8 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
out << "Window size: " + QString::number(width) + " x " + QString::number(height); out << "Window size: " + QString::number(width) + " x " + QString::number(height);
} }
out << ""; out << "";
out << "Launcher: " + getLauncher();
out << "";
return out; return out;
} }
@ -1124,8 +1142,6 @@ std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList() c
if (!m_resource_pack_list) if (!m_resource_pack_list)
{ {
m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir())); m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir()));
m_resource_pack_list->enableInteraction(!isRunning());
connect(this, &BaseInstance::runningStatusChanged, m_resource_pack_list.get(), &ResourcePackFolderModel::disableInteraction);
} }
return m_resource_pack_list; return m_resource_pack_list;
} }
@ -1135,8 +1151,6 @@ std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList() con
if (!m_texture_pack_list) if (!m_texture_pack_list)
{ {
m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir())); m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir()));
m_texture_pack_list->disableInteraction(isRunning());
connect(this, &BaseInstance::runningStatusChanged, m_texture_pack_list.get(), &ModFolderModel::disableInteraction);
} }
return m_texture_pack_list; return m_texture_pack_list;
} }
@ -1146,8 +1160,6 @@ std::shared_ptr<ShaderPackFolderModel> MinecraftInstance::shaderPackList() const
if (!m_shader_pack_list) if (!m_shader_pack_list)
{ {
m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir())); m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir()));
m_shader_pack_list->disableInteraction(isRunning());
connect(this, &BaseInstance::runningStatusChanged, m_shader_pack_list.get(), &ModFolderModel::disableInteraction);
} }
return m_shader_pack_list; return m_shader_pack_list;
} }

View File

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

View File

@ -135,7 +135,7 @@ QJsonObject libDownloadInfoToJson(MojangLibraryDownloadInfo::Ptr libinfo)
{ {
out.insert("artifact", downloadInfoToJson(libinfo->artifact)); out.insert("artifact", downloadInfoToJson(libinfo->artifact));
} }
if(libinfo->classifiers.size()) if(!libinfo->classifiers.isEmpty())
{ {
QJsonObject classifiersOut; QJsonObject classifiersOut;
for(auto iter = libinfo->classifiers.begin(); iter != libinfo->classifiers.end(); iter++) for(auto iter = libinfo->classifiers.begin(); iter != libinfo->classifiers.end(); iter++)
@ -297,7 +297,7 @@ void MojangVersionFormat::writeVersionProperties(const VersionFile* in, QJsonObj
{ {
out.insert("assetIndex", assetIndexToJson(in->mojangAssetIndex)); out.insert("assetIndex", assetIndexToJson(in->mojangAssetIndex));
} }
if(in->mojangDownloads.size()) if(!in->mojangDownloads.isEmpty())
{ {
QJsonObject downloadsOut; QJsonObject downloadsOut;
for(auto iter = in->mojangDownloads.begin(); iter != in->mojangDownloads.end(); iter++) for(auto iter = in->mojangDownloads.begin(); iter != in->mojangDownloads.end(); iter++)
@ -306,6 +306,15 @@ void MojangVersionFormat::writeVersionProperties(const VersionFile* in, QJsonObj
} }
out.insert("downloads", downloadsOut); out.insert("downloads", downloadsOut);
} }
if(!in->compatibleJavaMajors.isEmpty())
{
QJsonArray compatibleJavaMajorsOut;
for(auto compatibleJavaMajor : in->compatibleJavaMajors)
{
compatibleJavaMajorsOut.append(compatibleJavaMajor);
}
out.insert("compatibleJavaMajors", compatibleJavaMajorsOut);
}
} }
QJsonDocument MojangVersionFormat::versionFileToJson(const VersionFilePtr &patch) QJsonDocument MojangVersionFormat::versionFileToJson(const VersionFilePtr &patch)
@ -396,7 +405,7 @@ QJsonObject MojangVersionFormat::libraryToJson(Library *library)
iter++; iter++;
} }
libRoot.insert("natives", nativeList); libRoot.insert("natives", nativeList);
if (library->m_extractExcludes.size()) if (!library->m_extractExcludes.isEmpty())
{ {
QJsonArray excludes; QJsonArray excludes;
QJsonObject extract; QJsonObject extract;
@ -408,7 +417,7 @@ QJsonObject MojangVersionFormat::libraryToJson(Library *library)
libRoot.insert("extract", extract); libRoot.insert("extract", extract);
} }
} }
if (library->m_rules.size()) if (!library->m_rules.isEmpty())
{ {
QJsonArray allRules; QJsonArray allRules;
for (auto &rule : library->m_rules) for (auto &rule : library->m_rules)

View File

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

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -47,7 +48,6 @@
#include "Exception.h" #include "Exception.h"
#include "minecraft/OneSixVersionFormat.h" #include "minecraft/OneSixVersionFormat.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "meta/Index.h"
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftInstance.h"
#include "Json.h" #include "Json.h"
@ -55,7 +55,6 @@
#include "PackProfile_p.h" #include "PackProfile_p.h"
#include "ComponentUpdateTask.h" #include "ComponentUpdateTask.h"
#include "Application.h"
#include "modplatform/ModAPI.h" #include "modplatform/ModAPI.h"
static const QMap<QString, ModAPI::ModLoaderType> modloaderMapping{ static const QMap<QString, ModAPI::ModLoaderType> modloaderMapping{
@ -613,7 +612,7 @@ QVariant PackProfile::data(const QModelIndex &index, int role) const
bool PackProfile::setData(const QModelIndex& index, const QVariant& value, int role) bool PackProfile::setData(const QModelIndex& index, const QVariant& value, int role)
{ {
if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index)) if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index.parent()))
{ {
return false; return false;
} }
@ -675,12 +674,12 @@ Qt::ItemFlags PackProfile::flags(const QModelIndex &index) const
int PackProfile::rowCount(const QModelIndex &parent) const int PackProfile::rowCount(const QModelIndex &parent) const
{ {
return d->components.size(); return parent.isValid() ? 0 : d->components.size();
} }
int PackProfile::columnCount(const QModelIndex &parent) const int PackProfile::columnCount(const QModelIndex &parent) const
{ {
return NUM_COLUMNS; return parent.isValid() ? 0 : NUM_COLUMNS;
} }
void PackProfile::move(const int index, const MoveDirection direction) void PackProfile::move(const int index, const MoveDirection direction)
@ -738,6 +737,11 @@ void PackProfile::installCustomJar(QString selectedFile)
installCustomJar_internal(selectedFile); installCustomJar_internal(selectedFile);
} }
void PackProfile::installAgents(QStringList selectedFiles)
{
installAgents_internal(selectedFiles);
}
bool PackProfile::installEmpty(const QString& uid, const QString& name) bool PackProfile::installEmpty(const QString& uid, const QString& name)
{ {
QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches"); QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches");
@ -832,18 +836,14 @@ bool PackProfile::installJarMods_internal(QStringList filepaths)
for(auto filepath:filepaths) for(auto filepath:filepaths)
{ {
QFileInfo sourceInfo(filepath); QFileInfo sourceInfo(filepath);
auto uuid = QUuid::createUuid(); QString id = QUuid::createUuid().toString(QUuid::WithoutBraces);
QString id = uuid.toString().remove('{').remove('}');
QString target_filename = id + ".jar"; QString target_filename = id + ".jar";
QString target_id = "org.multimc.jarmod." + id; QString target_id = "custom.jarmod." + id;
QString target_name = sourceInfo.completeBaseName() + " (jar mod)"; QString target_name = sourceInfo.completeBaseName() + " (jar mod)";
QString finalPath = FS::PathCombine(d->m_instance->jarModsDir(), target_filename); QString finalPath = FS::PathCombine(d->m_instance->jarModsDir(), target_filename);
QFileInfo targetInfo(finalPath); QFileInfo targetInfo(finalPath);
if(targetInfo.exists()) Q_ASSERT(!targetInfo.exists());
{
return false;
}
if (!QFile::copy(sourceInfo.absoluteFilePath(),QFileInfo(finalPath).absoluteFilePath())) if (!QFile::copy(sourceInfo.absoluteFilePath(),QFileInfo(finalPath).absoluteFilePath()))
{ {
@ -852,7 +852,7 @@ bool PackProfile::installJarMods_internal(QStringList filepaths)
auto f = std::make_shared<VersionFile>(); auto f = std::make_shared<VersionFile>();
auto jarMod = std::make_shared<Library>(); auto jarMod = std::make_shared<Library>();
jarMod->setRawName(GradleSpecifier("org.multimc.jarmods:" + id + ":1")); jarMod->setRawName(GradleSpecifier("custom.jarmods:" + id + ":1"));
jarMod->setFilename(target_filename); jarMod->setFilename(target_filename);
jarMod->setDisplayName(sourceInfo.completeBaseName()); jarMod->setDisplayName(sourceInfo.completeBaseName());
jarMod->setHint("local"); jarMod->setHint("local");
@ -892,7 +892,7 @@ bool PackProfile::installCustomJar_internal(QString filepath)
return false; return false;
} }
auto specifier = GradleSpecifier("org.multimc:customjar:1"); auto specifier = GradleSpecifier("custom:customjar:1");
QFileInfo sourceInfo(filepath); QFileInfo sourceInfo(filepath);
QString target_filename = specifier.getFileName(); QString target_filename = specifier.getFileName();
QString target_id = specifier.artifactId(); QString target_id = specifier.artifactId();
@ -939,6 +939,64 @@ bool PackProfile::installCustomJar_internal(QString filepath)
return true; return true;
} }
bool PackProfile::installAgents_internal(QStringList filepaths)
{
// FIXME code duplication
const QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches");
if (!FS::ensureFolderPathExists(patchDir))
return false;
const QString libDir = d->m_instance->getLocalLibraryPath();
if (!FS::ensureFolderPathExists(libDir))
return false;
for (const QString& source : filepaths) {
const QFileInfo sourceInfo(source);
const QString id = QUuid::createUuid().toString(QUuid::WithoutBraces);
const QString targetBaseName = id + ".jar";
const QString targetId = "custom.agent." + id;
const QString targetName = sourceInfo.completeBaseName() + " (agent)";
const QString target = FS::PathCombine(d->m_instance->getLocalLibraryPath(), targetBaseName);
const QFileInfo targetInfo(target);
Q_ASSERT(!targetInfo.exists());
if (!QFile::copy(source, target))
return false;
auto versionFile = std::make_shared<VersionFile>();
auto agent = std::make_shared<Library>();
agent->setRawName("custom.agents:" + id + ":1");
agent->setFilename(targetBaseName);
agent->setDisplayName(sourceInfo.completeBaseName());
agent->setHint("local");
versionFile->agents.append(std::make_shared<Agent>(agent, QString()));
versionFile->name = targetName;
versionFile->uid = targetId;
QFile patchFile(FS::PathCombine(patchDir, targetId + ".json"));
if (!patchFile.open(QFile::WriteOnly)) {
qCritical() << "Error opening" << patchFile.fileName() << "for reading:" << patchFile.errorString();
return false;
}
patchFile.write(OneSixVersionFormat::versionFileToJson(versionFile).toJson());
patchFile.close();
appendComponent(new Component(this, versionFile->uid, versionFile));
}
scheduleSave();
invalidateLaunchProfile();
return true;
}
std::shared_ptr<LaunchProfile> PackProfile::getProfile() const std::shared_ptr<LaunchProfile> PackProfile::getProfile() const
{ {
if(!d->m_profile) if(!d->m_profile)

View File

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

View File

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

View File

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

View File

@ -54,7 +54,7 @@ public:
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const virtual int rowCount(const QModelIndex &parent = QModelIndex()) const
{ {
return size(); return parent.isValid() ? 0 : static_cast<int>(size());
}; };
virtual QVariant headerData(int section, Qt::Orientation orientation, virtual QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const; int role = Qt::DisplayRole) const;

View File

@ -408,20 +408,20 @@ QVariant AccountList::headerData(int section, Qt::Orientation orientation, int r
} }
} }
int AccountList::rowCount(const QModelIndex &) const int AccountList::rowCount(const QModelIndex &parent) const
{ {
// Return count // Return count
return count(); return parent.isValid() ? 0 : count();
} }
int AccountList::columnCount(const QModelIndex &) const int AccountList::columnCount(const QModelIndex &parent) const
{ {
return NUM_COLUMNS; return parent.isValid() ? 0 : NUM_COLUMNS;
} }
Qt::ItemFlags AccountList::flags(const QModelIndex &index) const Qt::ItemFlags AccountList::flags(const QModelIndex &index) const
{ {
if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid()) if (index.row() < 0 || index.row() >= rowCount(index.parent()) || !index.isValid())
{ {
return Qt::NoItemFlags; return Qt::NoItemFlags;
} }

View File

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

View File

@ -20,6 +20,7 @@ ResourceFolderModel::ResourceFolderModel(QDir dir, QObject* parent) : QAbstractL
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &ResourceFolderModel::directoryChanged); connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &ResourceFolderModel::directoryChanged);
connect(&m_helper_thread_task, &ConcurrentTask::finished, this, [this]{ m_helper_thread_task.clear(); });
} }
ResourceFolderModel::~ResourceFolderModel() ResourceFolderModel::~ResourceFolderModel()
@ -275,7 +276,11 @@ void ResourceFolderModel::resolveResource(Resource* res)
connect( connect(
task, &Task::finished, this, [=] { m_active_parse_tasks.remove(ticket); }, Qt::ConnectionType::QueuedConnection); task, &Task::finished, this, [=] { m_active_parse_tasks.remove(ticket); }, Qt::ConnectionType::QueuedConnection);
QThreadPool::globalInstance()->start(task); m_helper_thread_task.addTask(task);
if (!m_helper_thread_task.isRunning()) {
QThreadPool::globalInstance()->start(&m_helper_thread_task);
}
} }
void ResourceFolderModel::onUpdateSucceeded() void ResourceFolderModel::onUpdateSucceeded()
@ -426,7 +431,7 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
bool ResourceFolderModel::setData(const QModelIndex& index, const QVariant& value, int role) bool ResourceFolderModel::setData(const QModelIndex& index, const QVariant& value, int role)
{ {
int row = index.row(); int row = index.row();
if (row < 0 || row >= rowCount(index) || !index.isValid()) if (row < 0 || row >= rowCount(index.parent()) || !index.isValid())
return false; return false;
if (role == Qt::CheckStateRole) if (role == Qt::CheckStateRole)

View File

@ -10,6 +10,7 @@
#include "Resource.h" #include "Resource.h"
#include "tasks/Task.h" #include "tasks/Task.h"
#include "tasks/ConcurrentTask.h"
class QSortFilterProxyModel; class QSortFilterProxyModel;
@ -90,8 +91,8 @@ class ResourceFolderModel : public QAbstractListModel {
/* Basic columns */ /* Basic columns */
enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, NUM_COLUMNS }; enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, NUM_COLUMNS };
[[nodiscard]] int rowCount(const QModelIndex& = {}) const override { return size(); } [[nodiscard]] int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast<int>(size()); }
[[nodiscard]] int columnCount(const QModelIndex& = {}) const override { return NUM_COLUMNS; }; [[nodiscard]] int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NUM_COLUMNS; };
[[nodiscard]] Qt::DropActions supportedDropActions() const override; [[nodiscard]] Qt::DropActions supportedDropActions() const override;
@ -176,7 +177,7 @@ class ResourceFolderModel : public QAbstractListModel {
* if the resource is complex and has more stuff to parse. * if the resource is complex and has more stuff to parse.
*/ */
virtual void onParseSucceeded(int ticket, QString resource_id); virtual void onParseSucceeded(int ticket, QString resource_id);
virtual void onParseFailed(int ticket, QString resource_id) {} virtual void onParseFailed(int ticket, QString resource_id) { Q_UNUSED(ticket); Q_UNUSED(resource_id); }
protected: protected:
// Represents the relationship between a column's index (represented by the list index), and it's sorting key. // Represents the relationship between a column's index (represented by the list index), and it's sorting key.
@ -197,6 +198,7 @@ class ResourceFolderModel : public QAbstractListModel {
// Represents the relationship between a resource's internal ID and it's row position on the model. // Represents the relationship between a resource's internal ID and it's row position on the model.
QMap<QString, int> m_resources_index; QMap<QString, int> m_resources_index;
ConcurrentTask m_helper_thread_task;
QMap<int, Task::Ptr> m_active_parse_tasks; QMap<int, Task::Ptr> m_active_parse_tasks;
std::atomic<int> m_next_resolution_ticket = 0; std::atomic<int> m_next_resolution_ticket = 0;
}; };

View File

@ -1,9 +1,11 @@
#include "ResourcePack.h" #include "ResourcePack.h"
#include <QCoreApplication>
#include <QDebug> #include <QDebug>
#include <QMap> #include <QMap>
#include <QRegularExpression> #include <QRegularExpression>
#include "MTPixmapCache.h"
#include "Version.h" #include "Version.h"
#include "minecraft/mod/tasks/LocalResourcePackParseTask.h" #include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
@ -15,7 +17,7 @@ static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
{ 3, { Version("1.11"), Version("1.12.2") } }, { 4, { Version("1.13"), Version("1.14.4") } }, { 3, { Version("1.11"), Version("1.12.2") } }, { 4, { Version("1.13"), Version("1.14.4") } },
{ 5, { Version("1.15"), Version("1.16.1") } }, { 6, { Version("1.16.2"), Version("1.16.5") } }, { 5, { Version("1.15"), Version("1.16.1") } }, { 6, { Version("1.16.2"), Version("1.16.5") } },
{ 7, { Version("1.17"), Version("1.17.1") } }, { 8, { Version("1.18"), Version("1.18.2") } }, { 7, { Version("1.17"), Version("1.17.1") } }, { 8, { Version("1.18"), Version("1.18.2") } },
{ 9, { Version("1.19"), Version("1.19.2") } }, { 9, { Version("1.19"), Version("1.19.2") } }, { 11, { Version("1.19.3"), Version("1.19.3") } },
}; };
void ResourcePack::setPackFormat(int new_format_id) void ResourcePack::setPackFormat(int new_format_id)
@ -43,16 +45,22 @@ void ResourcePack::setImage(QImage new_image)
Q_ASSERT(!new_image.isNull()); Q_ASSERT(!new_image.isNull());
if (m_pack_image_cache_key.key.isValid()) if (m_pack_image_cache_key.key.isValid())
QPixmapCache::remove(m_pack_image_cache_key.key); PixmapCache::instance().remove(m_pack_image_cache_key.key);
m_pack_image_cache_key.key = QPixmapCache::insert(QPixmap::fromImage(new_image)); m_pack_image_cache_key.key = PixmapCache::instance().insert(QPixmap::fromImage(new_image));
m_pack_image_cache_key.was_ever_used = true; m_pack_image_cache_key.was_ever_used = true;
// This can happen if the pixmap is too big to fit in the cache :c
if (!m_pack_image_cache_key.key.isValid()) {
qWarning() << "Could not insert a image cache entry! Ignoring it.";
m_pack_image_cache_key.was_ever_used = false;
}
} }
QPixmap ResourcePack::image(QSize size) QPixmap ResourcePack::image(QSize size)
{ {
QPixmap cached_image; QPixmap cached_image;
if (QPixmapCache::find(m_pack_image_cache_key.key, &cached_image)) { if (PixmapCache::instance().find(m_pack_image_cache_key.key, &cached_image)) {
if (size.isNull()) if (size.isNull())
return cached_image; return cached_image;
return cached_image.scaled(size); return cached_image.scaled(size);
@ -114,3 +122,8 @@ bool ResourcePack::applyFilter(QRegularExpression filter) const
return Resource::applyFilter(filter); return Resource::applyFilter(filter);
} }
bool ResourcePack::valid() const
{
return m_pack_format != 0;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -26,13 +26,19 @@
#include "tasks/Task.h" #include "tasks/Task.h"
namespace ResourcePackUtils { namespace ResourcePackUtils {
bool process(ResourcePack& pack);
void processZIP(ResourcePack& pack); enum class ProcessingLevel { Full, BasicInfoOnly };
void processFolder(ResourcePack& pack);
bool process(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
void processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
void processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
void processMCMeta(ResourcePack& pack, QByteArray&& raw_data); void processMCMeta(ResourcePack& pack, QByteArray&& raw_data);
void processPackPNG(ResourcePack& pack, QByteArray&& raw_data); void processPackPNG(ResourcePack& pack, QByteArray&& raw_data);
/** Checks whether a file is valid as a resource pack or not. */
bool validate(QFileInfo file);
} // namespace ResourcePackUtils } // namespace ResourcePackUtils
class LocalResourcePackParseTask : public Task { class LocalResourcePackParseTask : public Task {

View File

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

View File

@ -27,13 +27,19 @@
#include "tasks/Task.h" #include "tasks/Task.h"
namespace TexturePackUtils { namespace TexturePackUtils {
bool process(TexturePack& pack);
void processZIP(TexturePack& pack); enum class ProcessingLevel { Full, BasicInfoOnly };
void processFolder(TexturePack& pack);
bool process(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
void processZIP(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
void processFolder(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
void processPackTXT(TexturePack& pack, QByteArray&& raw_data); void processPackTXT(TexturePack& pack, QByteArray&& raw_data);
void processPackPNG(TexturePack& pack, QByteArray&& raw_data); void processPackPNG(TexturePack& pack, QByteArray&& raw_data);
/** Checks whether a file is valid as a texture pack or not. */
bool validate(QFileInfo file);
} // namespace TexturePackUtils } // namespace TexturePackUtils
class LocalTexturePackParseTask : public Task { class LocalTexturePackParseTask : public Task {

View File

@ -12,7 +12,7 @@ LibrariesTask::LibrariesTask(MinecraftInstance * inst)
void LibrariesTask::executeTask() void LibrariesTask::executeTask()
{ {
setStatus(tr("Getting the library files from Mojang...")); setStatus(tr("Downloading required library files..."));
qDebug() << m_inst->name() << ": downloading libraries"; qDebug() << m_inst->name() << ": downloading libraries";
MinecraftInstance *inst = (MinecraftInstance *)m_inst; MinecraftInstance *inst = (MinecraftInstance *)m_inst;

View File

@ -42,12 +42,25 @@ void Flame::FileResolvingTask::executeTask()
void Flame::FileResolvingTask::netJobFinished() void Flame::FileResolvingTask::netJobFinished()
{ {
setProgress(1, 3); setProgress(1, 3);
int index = 0;
// job to check modrinth for blocked projects // job to check modrinth for blocked projects
m_checkJob = new NetJob("Modrinth check", m_network); m_checkJob = new NetJob("Modrinth check", m_network);
blockedProjects = QMap<File *,QByteArray *>(); blockedProjects = QMap<File *,QByteArray *>();
auto doc = Json::requireDocument(*result);
auto array = Json::requireArray(doc.object()["data"]); QJsonDocument doc;
QJsonArray array;
try {
doc = Json::requireDocument(*result);
array = Json::requireArray(doc.object()["data"]);
} catch (Json::JsonException& e) {
qCritical() << "Non-JSON data returned from the CF API";
qCritical() << e.cause();
emitFailed(tr("Invalid data returned from the API."));
return;
}
for (QJsonValueRef file : array) { for (QJsonValueRef file : array) {
auto fileid = Json::requireInteger(Json::requireObject(file)["id"]); auto fileid = Json::requireInteger(Json::requireObject(file)["id"]);
auto& out = m_toProcess.files[fileid]; auto& out = m_toProcess.files[fileid];
@ -68,7 +81,6 @@ void Flame::FileResolvingTask::netJobFinished()
blockedProjects.insert(&out, output); blockedProjects.insert(&out, output);
} }
} }
index++;
} }
connect(m_checkJob.get(), &NetJob::finished, this, &Flame::FileResolvingTask::modrinthCheckFinished); connect(m_checkJob.get(), &NetJob::finished, this, &Flame::FileResolvingTask::modrinthCheckFinished);

View File

@ -1,3 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "FlameInstanceCreationTask.h" #include "FlameInstanceCreationTask.h"
#include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/FlameAPI.h"
@ -46,7 +81,12 @@ bool FlameCreationTask::updateInstance()
auto instance_list = APPLICATION->instances(); auto instance_list = APPLICATION->instances();
// FIXME: How to handle situations when there's more than one install already for a given modpack? // FIXME: How to handle situations when there's more than one install already for a given modpack?
auto inst = instance_list->getInstanceByManagedName(originalName()); InstancePtr inst;
if (auto original_id = originalInstanceID(); !original_id.isEmpty()) {
inst = instance_list->getInstanceById(original_id);
Q_ASSERT(inst);
} else {
inst = instance_list->getInstanceByManagedName(originalName());
if (!inst) { if (!inst) {
inst = instance_list->getInstanceById(originalName()); inst = instance_list->getInstanceById(originalName());
@ -54,6 +94,7 @@ bool FlameCreationTask::updateInstance()
if (!inst) if (!inst)
return false; return false;
} }
}
QString index_path(FS::PathCombine(m_stagingPath, "manifest.json")); QString index_path(FS::PathCombine(m_stagingPath, "manifest.json"));
@ -67,25 +108,15 @@ bool FlameCreationTask::updateInstance()
auto version_id = inst->getManagedPackVersionName(); auto version_id = inst->getManagedPackVersionName();
auto version_str = !version_id.isEmpty() ? tr(" (version %1)").arg(version_id) : ""; auto version_str = !version_id.isEmpty() ? tr(" (version %1)").arg(version_id) : "";
auto info = CustomMessageBox::selectable( if (shouldConfirmUpdate()) {
m_parent, tr("Similar modpack was found!"), auto should_update = askIfShouldUpdate(m_parent, version_str);
tr("One or more of your instances are from this same modpack%1. Do you want to create a " if (should_update == ShouldUpdate::SkipUpdating)
"separate instance, or update the existing one?\n\nNOTE: Make sure you made a backup of your important instance data before "
"updating, as worlds can be corrupted and some configuration may be lost (due to pack overrides).")
.arg(version_str), QMessageBox::Information, QMessageBox::Ok | QMessageBox::Reset | QMessageBox::Abort);
info->setButtonText(QMessageBox::Ok, tr("Update existing instance"));
info->setButtonText(QMessageBox::Abort, tr("Create new instance"));
info->setButtonText(QMessageBox::Reset, tr("Cancel"));
info->exec();
if (info->clickedButton() == info->button(QMessageBox::Abort))
return false; return false;
if (should_update == ShouldUpdate::Cancel) {
if (info->clickedButton() == info->button(QMessageBox::Reset)) {
m_abort = true; m_abort = true;
return false; return false;
} }
}
QDir old_inst_dir(inst->instanceRoot()); QDir old_inst_dir(inst->instanceRoot());
@ -197,9 +228,9 @@ bool FlameCreationTask::updateInstance()
m_process_update_file_info_job = nullptr; m_process_update_file_info_job = nullptr;
} else { } else {
// We don't have an old index file, so we may duplicate stuff! // We don't have an old index file, so we may duplicate stuff!
auto dialog = CustomMessageBox::selectable(m_parent, auto dialog = CustomMessageBox::selectable(m_parent, tr("No index file."),
tr("No index file."), tr("We couldn't find a suitable index file for the older version. This may cause some "
tr("We couldn't find a suitable index file for the older version. This may cause some of the files to be duplicated. Do you want to continue?"), "of the files to be duplicated. Do you want to continue?"),
QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel); QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel);
if (dialog->exec() == QDialog::DialogCode::Rejected) { if (dialog->exec() == QDialog::DialogCode::Rejected) {
@ -208,7 +239,7 @@ bool FlameCreationTask::updateInstance()
} }
} }
setOverride(true); setOverride(true, inst->id());
qDebug() << "Will override instance!"; qDebug() << "Will override instance!";
m_instance = inst; m_instance = inst;
@ -330,7 +361,9 @@ bool FlameCreationTask::createInstance()
FS::deletePath(jarmodsPath); FS::deletePath(jarmodsPath);
} }
instance.setManagedPack("flame", {}, m_pack.name, {}, m_pack.version); // Don't add managed info to packs without an ID (most likely imported from ZIP)
if (!m_managed_id.isEmpty())
instance.setManagedPack("flame", m_managed_id, m_pack.name, m_managed_version_id, m_pack.version);
instance.setName(name()); instance.setName(name());
m_mod_id_resolver = new Flame::FileResolvingTask(APPLICATION->network(), m_pack); m_mod_id_resolver = new Flame::FileResolvingTask(APPLICATION->network(), m_pack);
@ -338,6 +371,7 @@ bool FlameCreationTask::createInstance()
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) { connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) {
m_mod_id_resolver.reset(); m_mod_id_resolver.reset();
setError(tr("Unable to resolve mod IDs:\n") + reason); setError(tr("Unable to resolve mod IDs:\n") + reason);
loop.quit();
}); });
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::progress, this, &FlameCreationTask::setProgress); connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::progress, this, &FlameCreationTask::setProgress);
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::status, this, &FlameCreationTask::setStatus); connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::status, this, &FlameCreationTask::setStatus);
@ -353,14 +387,6 @@ bool FlameCreationTask::createInstance()
setAbortable(false); setAbortable(false);
auto inst = m_instance.value(); auto inst = m_instance.value();
// Only change the name if it didn't use a custom name, so that the previous custom name
// is preserved, but if we're using the original one, we update the version string.
// NOTE: This needs to come before the copyManagedPack call!
if (inst->name().contains(inst->getManagedPackVersionName())) {
if (askForChangingInstanceName(m_parent, inst->name(), instance.name()) == InstanceNameChange::ShouldChange)
inst->setName(instance.name());
}
inst->copyManagedPack(instance); inst->copyManagedPack(instance);
} }
@ -376,13 +402,13 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
auto anyBlocked = false; auto anyBlocked = false;
for (const auto& result : results.files.values()) { for (const auto& result : results.files.values()) {
if (!result.resolved || result.url.isEmpty()) { if (!result.resolved || result.url.isEmpty()) {
BlockedMod blocked_mod; BlockedMod blocked_mod;
blocked_mod.name = result.fileName; blocked_mod.name = result.fileName;
blocked_mod.websiteUrl = result.websiteUrl; blocked_mod.websiteUrl = result.websiteUrl;
blocked_mod.hash = result.hash; blocked_mod.hash = result.hash;
blocked_mod.matched = false; blocked_mod.matched = false;
blocked_mod.localPath = ""; blocked_mod.localPath = "";
blocked_mod.targetFolder = result.targetFolder;
blocked_mods.append(blocked_mod); blocked_mods.append(blocked_mod);
@ -392,13 +418,14 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
if (anyBlocked) { if (anyBlocked) {
qWarning() << "Blocked mods found, displaying mod list"; qWarning() << "Blocked mods found, displaying mod list";
auto message_dialog = new BlockedModsDialog(m_parent, tr("Blocked mods found"), BlockedModsDialog message_dialog(m_parent, tr("Blocked mods found"),
tr("The following mods were blocked on third party launchers.<br/>" tr("The following files are not available for download in third party launchers.<br/>"
"You will need to manually download them and add them to the modpack"), "You will need to manually download them and add them to the instance."),
blocked_mods); blocked_mods);
message_dialog->setModal(true);
if (message_dialog->exec()) { message_dialog.setModal(true);
if (message_dialog.exec()) {
qDebug() << "Post dialog blocked mods list: " << blocked_mods; qDebug() << "Post dialog blocked mods list: " << blocked_mods;
copyBlockedMods(blocked_mods); copyBlockedMods(blocked_mods);
setupDownloadJob(loop); setupDownloadJob(loop);
@ -427,7 +454,7 @@ void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods)
continue; continue;
} }
auto dest_path = FS::PathCombine(m_stagingPath, "minecraft", "mods", mod.name); auto dest_path = FS::PathCombine(m_stagingPath, "minecraft", mod.targetFolder, mod.name);
setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total))); setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total)));
@ -482,9 +509,7 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
} }
m_mod_id_resolver.reset(); m_mod_id_resolver.reset();
connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { m_files_job.reset(); });
m_files_job.reset();
});
connect(m_files_job.get(), &NetJob::failed, [&](QString reason) { connect(m_files_job.get(), &NetJob::failed, [&](QString reason) {
m_files_job.reset(); m_files_job.reset();
setError(reason); setError(reason);

View File

@ -1,3 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once #pragma once
#include "InstanceCreationTask.h" #include "InstanceCreationTask.h"
@ -16,11 +51,21 @@ class FlameCreationTask final : public InstanceCreationTask {
Q_OBJECT Q_OBJECT
public: public:
FlameCreationTask(const QString& staging_path, SettingsObjectPtr global_settings, QWidget* parent) FlameCreationTask(const QString& staging_path,
: InstanceCreationTask(), m_parent(parent) SettingsObjectPtr global_settings,
QWidget* parent,
QString id,
QString version_id,
QString original_instance_id = {})
: InstanceCreationTask()
, m_parent(parent)
, m_managed_id(std::move(id))
, m_managed_version_id(std::move(version_id))
{ {
setStagingPath(staging_path); setStagingPath(staging_path);
setParentSettings(global_settings); setParentSettings(global_settings);
m_original_instance_id = std::move(original_instance_id);
} }
bool abort() override; bool abort() override;
@ -43,5 +88,7 @@ class FlameCreationTask final : public InstanceCreationTask {
NetJob* m_process_update_file_info_job = nullptr; NetJob* m_process_update_file_info_job = nullptr;
NetJob::Ptr m_files_job = nullptr; NetJob::Ptr m_files_job = nullptr;
QString m_managed_id, m_managed_version_id;
std::optional<InstancePtr> m_instance; std::optional<InstancePtr> m_instance;
}; };

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 flowln <flowlnlnln@gmail.com> * Copyright (C) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
@ -189,13 +189,13 @@ void PackInstallTask::onResolveModsSucceeded()
// First check for blocked mods // First check for blocked mods
if (!results_file.resolved || results_file.url.isEmpty()) { if (!results_file.resolved || results_file.url.isEmpty()) {
BlockedMod blocked_mod; BlockedMod blocked_mod;
blocked_mod.name = local_file.name; blocked_mod.name = local_file.name;
blocked_mod.websiteUrl = results_file.websiteUrl; blocked_mod.websiteUrl = results_file.websiteUrl;
blocked_mod.hash = results_file.hash; blocked_mod.hash = results_file.hash;
blocked_mod.matched = false; blocked_mod.matched = false;
blocked_mod.localPath = ""; blocked_mod.localPath = "";
blocked_mod.targetFolder = results_file.targetFolder;
m_blocked_mods.append(blocked_mod); m_blocked_mods.append(blocked_mod);
@ -210,16 +210,17 @@ void PackInstallTask::onResolveModsSucceeded()
if (anyBlocked) { if (anyBlocked) {
qDebug() << "Blocked files found, displaying file list"; qDebug() << "Blocked files found, displaying file list";
auto message_dialog = new BlockedModsDialog(m_parent, tr("Blocked files found"), BlockedModsDialog message_dialog(m_parent, tr("Blocked files found"),
tr("The following files are not available for download in third party launchers.<br/>" tr("The following files are not available for download in third party launchers.<br/>"
"You will need to manually download them and add them to the instance."), "You will need to manually download them and add them to the instance."),
m_blocked_mods); m_blocked_mods);
if (message_dialog->exec() == QDialog::Accepted) { message_dialog.setModal(true);
if (message_dialog.exec() == QDialog::Accepted) {
qDebug() << "Post dialog blocked mods list: " << m_blocked_mods; qDebug() << "Post dialog blocked mods list: " << m_blocked_mods;
createInstance(); createInstance();
} } else {
else {
abort(); abort();
} }
@ -366,7 +367,7 @@ void PackInstallTask::copyBlockedMods()
continue; continue;
} }
auto dest_path = FS::PathCombine(m_stagingPath, ".minecraft", "mods", mod.name); auto dest_path = FS::PathCombine(m_stagingPath, ".minecraft", mod.targetFolder, mod.name);
setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total))); setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total)));

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 flowln <flowlnlnln@gmail.com> * Copyright (C) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *

View File

@ -33,7 +33,12 @@ bool ModrinthCreationTask::updateInstance()
auto instance_list = APPLICATION->instances(); auto instance_list = APPLICATION->instances();
// FIXME: How to handle situations when there's more than one install already for a given modpack? // FIXME: How to handle situations when there's more than one install already for a given modpack?
auto inst = instance_list->getInstanceByManagedName(originalName()); InstancePtr inst;
if (auto original_id = originalInstanceID(); !original_id.isEmpty()) {
inst = instance_list->getInstanceById(original_id);
Q_ASSERT(inst);
} else {
inst = instance_list->getInstanceByManagedName(originalName());
if (!inst) { if (!inst) {
inst = instance_list->getInstanceById(originalName()); inst = instance_list->getInstanceById(originalName());
@ -41,6 +46,7 @@ bool ModrinthCreationTask::updateInstance()
if (!inst) if (!inst)
return false; return false;
} }
}
QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json"); QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json");
if (!parseManifest(index_path, m_files, true, false)) if (!parseManifest(index_path, m_files, true, false))
@ -49,26 +55,15 @@ bool ModrinthCreationTask::updateInstance()
auto version_name = inst->getManagedPackVersionName(); auto version_name = inst->getManagedPackVersionName();
auto version_str = !version_name.isEmpty() ? tr(" (version %1)").arg(version_name) : ""; auto version_str = !version_name.isEmpty() ? tr(" (version %1)").arg(version_name) : "";
auto info = CustomMessageBox::selectable( if (shouldConfirmUpdate()) {
m_parent, tr("Similar modpack was found!"), auto should_update = askIfShouldUpdate(m_parent, version_str);
tr("One or more of your instances are from this same modpack%1. Do you want to create a " if (should_update == ShouldUpdate::SkipUpdating)
"separate instance, or update the existing one?\n\nNOTE: Make sure you made a backup of your important instance data before "
"updating, as worlds can be corrupted and some configuration may be lost (due to pack overrides).")
.arg(version_str),
QMessageBox::Information, QMessageBox::Ok | QMessageBox::Reset | QMessageBox::Abort);
info->setButtonText(QMessageBox::Ok, tr("Create new instance"));
info->setButtonText(QMessageBox::Abort, tr("Update existing instance"));
info->setButtonText(QMessageBox::Reset, tr("Cancel"));
info->exec();
if (info->clickedButton() == info->button(QMessageBox::Ok))
return false; return false;
if (should_update == ShouldUpdate::Cancel) {
if (info->clickedButton() == info->button(QMessageBox::Reset)) {
m_abort = true; m_abort = true;
return false; return false;
} }
}
// Remove repeated files, we don't need to download them! // Remove repeated files, we don't need to download them!
QDir old_inst_dir(inst->instanceRoot()); QDir old_inst_dir(inst->instanceRoot());
@ -149,7 +144,7 @@ bool ModrinthCreationTask::updateInstance()
} }
setOverride(true); setOverride(true, inst->id());
qDebug() << "Will override instance!"; qDebug() << "Will override instance!";
m_instance = inst; m_instance = inst;
@ -207,14 +202,14 @@ bool ModrinthCreationTask::createInstance()
auto components = instance.getPackProfile(); auto components = instance.getPackProfile();
components->buildingFromScratch(); components->buildingFromScratch();
components->setComponentVersion("net.minecraft", minecraftVersion, true); components->setComponentVersion("net.minecraft", m_minecraft_version, true);
if (!fabricVersion.isEmpty()) if (!m_fabric_version.isEmpty())
components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion); components->setComponentVersion("net.fabricmc.fabric-loader", m_fabric_version);
if (!quiltVersion.isEmpty()) if (!m_quilt_version.isEmpty())
components->setComponentVersion("org.quiltmc.quilt-loader", quiltVersion); components->setComponentVersion("org.quiltmc.quilt-loader", m_quilt_version);
if (!forgeVersion.isEmpty()) if (!m_forge_version.isEmpty())
components->setComponentVersion("net.minecraftforge", forgeVersion); components->setComponentVersion("net.minecraftforge", m_forge_version);
if (m_instIcon != "default") { if (m_instIcon != "default") {
instance.setIconKey(m_instIcon); instance.setIconKey(m_instIcon);
@ -222,7 +217,9 @@ bool ModrinthCreationTask::createInstance()
instance.setIconKey("modrinth"); instance.setIconKey("modrinth");
} }
instance.setManagedPack("modrinth", getManagedPackID(), m_managed_name, m_managed_version_id, version()); // Don't add managed info to packs without an ID (most likely imported from ZIP)
if (!m_managed_id.isEmpty())
instance.setManagedPack("modrinth", m_managed_id, m_managed_name, m_managed_version_id, version());
instance.setName(name()); instance.setName(name());
instance.saveNow(); instance.saveNow();
@ -282,7 +279,7 @@ bool ModrinthCreationTask::createInstance()
return ended_well; return ended_well;
} }
bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector<Modrinth::File>& files, bool set_managed_info, bool show_optional_dialog) bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector<Modrinth::File>& files, bool set_internal_data, bool show_optional_dialog)
{ {
try { try {
auto doc = Json::requireDocument(index_path); auto doc = Json::requireDocument(index_path);
@ -294,7 +291,8 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector<
throw JSONValidationError("Unknown game: " + game); throw JSONValidationError("Unknown game: " + game);
} }
if (set_managed_info) { if (set_internal_data) {
if (m_managed_version_id.isEmpty())
m_managed_version_id = Json::ensureString(obj, "versionId", {}, "Managed ID"); m_managed_version_id = Json::ensureString(obj, "versionId", {}, "Managed ID");
m_managed_name = Json::ensureString(obj, "name", {}, "Managed Name"); m_managed_name = Json::ensureString(obj, "name", {}, "Managed Name");
} }
@ -369,21 +367,23 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector<
files.push_back(file); files.push_back(file);
} }
if (set_internal_data) {
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json"); auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) { for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
QString name = it.key(); QString name = it.key();
if (name == "minecraft") { if (name == "minecraft") {
minecraftVersion = Json::requireString(*it, "Minecraft version"); m_minecraft_version = Json::requireString(*it, "Minecraft version");
} else if (name == "fabric-loader") { } else if (name == "fabric-loader") {
fabricVersion = Json::requireString(*it, "Fabric Loader version"); m_fabric_version = Json::requireString(*it, "Fabric Loader version");
} else if (name == "quilt-loader") { } else if (name == "quilt-loader") {
quiltVersion = Json::requireString(*it, "Quilt Loader version"); m_quilt_version = Json::requireString(*it, "Quilt Loader version");
} else if (name == "forge") { } else if (name == "forge") {
forgeVersion = Json::requireString(*it, "Forge version"); m_forge_version = Json::requireString(*it, "Forge version");
} else { } else {
throw JSONValidationError("Unknown dependency type: " + name); throw JSONValidationError("Unknown dependency type: " + name);
} }
} }
}
} else { } else {
throw JSONValidationError(QStringLiteral("Unknown format version: %s").arg(formatVersion)); throw JSONValidationError(QStringLiteral("Unknown format version: %s").arg(formatVersion));
} }
@ -395,13 +395,3 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector<
return true; return true;
} }
QString ModrinthCreationTask::getManagedPackID() const
{
if (!m_source_url.isEmpty()) {
QRegularExpression regex(R"(data\/(.*)\/versions)");
return regex.match(m_source_url).captured(1);
}
return {};
}

View File

@ -14,11 +14,21 @@ class ModrinthCreationTask final : public InstanceCreationTask {
Q_OBJECT Q_OBJECT
public: public:
ModrinthCreationTask(QString staging_path, SettingsObjectPtr global_settings, QWidget* parent, QString source_url = {}) ModrinthCreationTask(QString staging_path,
: InstanceCreationTask(), m_parent(parent), m_source_url(std::move(source_url)) SettingsObjectPtr global_settings,
QWidget* parent,
QString id,
QString version_id = {},
QString original_instance_id = {})
: InstanceCreationTask()
, m_parent(parent)
, m_managed_id(std::move(id))
, m_managed_version_id(std::move(version_id))
{ {
setStagingPath(staging_path); setStagingPath(staging_path);
setParentSettings(global_settings); setParentSettings(global_settings);
m_original_instance_id = std::move(original_instance_id);
} }
bool abort() override; bool abort() override;
@ -27,15 +37,13 @@ class ModrinthCreationTask final : public InstanceCreationTask {
bool createInstance() override; bool createInstance() override;
private: private:
bool parseManifest(const QString&, std::vector<Modrinth::File>&, bool set_managed_info = true, bool show_optional_dialog = true); bool parseManifest(const QString&, std::vector<Modrinth::File>&, bool set_internal_data = true, bool show_optional_dialog = true);
QString getManagedPackID() const;
private: private:
QWidget* m_parent = nullptr; QWidget* m_parent = nullptr;
QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion; QString m_minecraft_version, m_fabric_version, m_quilt_version, m_forge_version;
QString m_managed_id, m_managed_version_id, m_managed_name; QString m_managed_id, m_managed_version_id, m_managed_name;
QString m_source_url;
std::vector<Modrinth::File> m_files; std::vector<Modrinth::File> m_files;
NetJob::Ptr m_files_job; NetJob::Ptr m_files_job;

View File

@ -128,6 +128,7 @@ auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion
file.name = Json::requireString(obj, "name"); file.name = Json::requireString(obj, "name");
file.version = Json::requireString(obj, "version_number"); file.version = Json::requireString(obj, "version_number");
file.changelog = Json::ensureString(obj, "changelog");
file.id = Json::requireString(obj, "id"); file.id = Json::requireString(obj, "id");
file.project_id = Json::requireString(obj, "project_id"); file.project_id = Json::requireString(obj, "project_id");

View File

@ -80,6 +80,7 @@ struct ModpackExtra {
struct ModpackVersion { struct ModpackVersion {
QString name; QString name;
QString version; QString version;
QString changelog;
QString id; QString id;
QString project_id; QString project_id;

View File

@ -47,7 +47,7 @@
auto MetaEntry::getFullPath() -> QString auto MetaEntry::getFullPath() -> QString
{ {
// FIXME: make local? // FIXME: make local?
return FS::PathCombine(basePath, relativePath); return FS::PathCombine(m_basePath, m_relativePath);
} }
HttpMetaCache::HttpMetaCache(QString path) : QObject(), m_index_file(path) HttpMetaCache::HttpMetaCache(QString path) : QObject(), m_index_file(path)
@ -99,7 +99,7 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex
return staleEntry(base, resource_path); return staleEntry(base, resource_path);
} }
if (!expected_etag.isEmpty() && expected_etag != entry->etag) { if (!expected_etag.isEmpty() && expected_etag != entry->m_etag) {
// if the etag doesn't match expected, we disown the entry // if the etag doesn't match expected, we disown the entry
selected_base.entry_list.remove(resource_path); selected_base.entry_list.remove(resource_path);
return staleEntry(base, resource_path); return staleEntry(base, resource_path);
@ -107,17 +107,17 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex
// if the file changed, check md5sum // if the file changed, check md5sum
qint64 file_last_changed = finfo.lastModified().toUTC().toMSecsSinceEpoch(); qint64 file_last_changed = finfo.lastModified().toUTC().toMSecsSinceEpoch();
if (file_last_changed != entry->local_changed_timestamp) { if (file_last_changed != entry->m_local_changed_timestamp) {
QFile input(real_path); QFile input(real_path);
input.open(QIODevice::ReadOnly); input.open(QIODevice::ReadOnly);
QString md5sum = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Md5).toHex().constData(); QString md5sum = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Md5).toHex().constData();
if (entry->md5sum != md5sum) { if (entry->m_md5sum != md5sum) {
selected_base.entry_list.remove(resource_path); selected_base.entry_list.remove(resource_path);
return staleEntry(base, resource_path); return staleEntry(base, resource_path);
} }
// md5sums matched... keep entry and save the new state to file // md5sums matched... keep entry and save the new state to file
entry->local_changed_timestamp = file_last_changed; entry->m_local_changed_timestamp = file_last_changed;
SaveEventually(); SaveEventually();
} }
@ -130,23 +130,23 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex
} }
// entry passed all the checks we cared about. // entry passed all the checks we cared about.
entry->basePath = getBasePath(base); entry->m_basePath = getBasePath(base);
return entry; return entry;
} }
auto HttpMetaCache::updateEntry(MetaEntryPtr stale_entry) -> bool auto HttpMetaCache::updateEntry(MetaEntryPtr stale_entry) -> bool
{ {
if (!m_entries.contains(stale_entry->baseId)) { if (!m_entries.contains(stale_entry->m_baseId)) {
qCritical() << "Cannot add entry with unknown base: " << stale_entry->baseId.toLocal8Bit(); qCritical() << "Cannot add entry with unknown base: " << stale_entry->m_baseId.toLocal8Bit();
return false; return false;
} }
if (stale_entry->stale) { if (stale_entry->m_stale) {
qCritical() << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit(); qCritical() << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit();
return false; return false;
} }
m_entries[stale_entry->baseId].entry_list[stale_entry->relativePath] = stale_entry; m_entries[stale_entry->m_baseId].entry_list[stale_entry->m_relativePath] = stale_entry;
SaveEventually(); SaveEventually();
return true; return true;
@ -157,7 +157,7 @@ auto HttpMetaCache::evictEntry(MetaEntryPtr entry) -> bool
if (!entry) if (!entry)
return false; return false;
entry->stale = true; entry->m_stale = true;
SaveEventually(); SaveEventually();
return true; return true;
} }
@ -169,7 +169,7 @@ void HttpMetaCache::evictAll()
qDebug() << "Evicting base" << base; qDebug() << "Evicting base" << base;
for (MetaEntryPtr entry : map.entry_list) { for (MetaEntryPtr entry : map.entry_list) {
if (!evictEntry(entry)) if (!evictEntry(entry))
qWarning() << "Unexpected missing cache entry" << entry->basePath; qWarning() << "Unexpected missing cache entry" << entry->m_basePath;
} }
} }
} }
@ -177,10 +177,10 @@ void HttpMetaCache::evictAll()
auto HttpMetaCache::staleEntry(QString base, QString resource_path) -> MetaEntryPtr auto HttpMetaCache::staleEntry(QString base, QString resource_path) -> MetaEntryPtr
{ {
auto foo = new MetaEntry(); auto foo = new MetaEntry();
foo->baseId = base; foo->m_baseId = base;
foo->basePath = getBasePath(base); foo->m_basePath = getBasePath(base);
foo->relativePath = resource_path; foo->m_relativePath = resource_path;
foo->stale = true; foo->m_stale = true;
return MetaEntryPtr(foo); return MetaEntryPtr(foo);
} }
@ -235,23 +235,23 @@ void HttpMetaCache::Load()
auto& entrymap = m_entries[base]; auto& entrymap = m_entries[base];
auto foo = new MetaEntry(); auto foo = new MetaEntry();
foo->baseId = base; foo->m_baseId = base;
foo->relativePath = Json::ensureString(element_obj, "path"); foo->m_relativePath = Json::ensureString(element_obj, "path");
foo->md5sum = Json::ensureString(element_obj, "md5sum"); foo->m_md5sum = Json::ensureString(element_obj, "md5sum");
foo->etag = Json::ensureString(element_obj, "etag"); foo->m_etag = Json::ensureString(element_obj, "etag");
foo->local_changed_timestamp = Json::ensureDouble(element_obj, "last_changed_timestamp"); foo->m_local_changed_timestamp = Json::ensureDouble(element_obj, "last_changed_timestamp");
foo->remote_changed_timestamp = Json::ensureString(element_obj, "remote_changed_timestamp"); foo->m_remote_changed_timestamp = Json::ensureString(element_obj, "remote_changed_timestamp");
foo->makeEternal(Json::ensureBoolean(element_obj, (const QString)QStringLiteral("eternal"), false)); foo->makeEternal(Json::ensureBoolean(element_obj, (const QString)QStringLiteral("eternal"), false));
if (!foo->isEternal()) { if (!foo->isEternal()) {
foo->current_age = Json::ensureDouble(element_obj, "current_age"); foo->m_current_age = Json::ensureDouble(element_obj, "current_age");
foo->max_age = Json::ensureDouble(element_obj, "max_age"); foo->m_max_age = Json::ensureDouble(element_obj, "max_age");
} }
// presumed innocent until closer examination // presumed innocent until closer examination
foo->stale = false; foo->m_stale = false;
entrymap.entry_list[foo->relativePath] = MetaEntryPtr(foo); entrymap.entry_list[foo->m_relativePath] = MetaEntryPtr(foo);
} }
} }
@ -276,23 +276,23 @@ void HttpMetaCache::SaveNow()
for (auto group : m_entries) { for (auto group : m_entries) {
for (auto entry : group.entry_list) { for (auto entry : group.entry_list) {
// do not save stale entries. they are dead. // do not save stale entries. they are dead.
if (entry->stale) { if (entry->m_stale) {
continue; continue;
} }
QJsonObject entryObj; QJsonObject entryObj;
Json::writeString(entryObj, "base", entry->baseId); Json::writeString(entryObj, "base", entry->m_baseId);
Json::writeString(entryObj, "path", entry->relativePath); Json::writeString(entryObj, "path", entry->m_relativePath);
Json::writeString(entryObj, "md5sum", entry->md5sum); Json::writeString(entryObj, "md5sum", entry->m_md5sum);
Json::writeString(entryObj, "etag", entry->etag); Json::writeString(entryObj, "etag", entry->m_etag);
entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->local_changed_timestamp))); entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->m_local_changed_timestamp)));
if (!entry->remote_changed_timestamp.isEmpty()) if (!entry->m_remote_changed_timestamp.isEmpty())
entryObj.insert("remote_changed_timestamp", QJsonValue(entry->remote_changed_timestamp)); entryObj.insert("remote_changed_timestamp", QJsonValue(entry->m_remote_changed_timestamp));
if (entry->isEternal()) { if (entry->isEternal()) {
entryObj.insert("eternal", true); entryObj.insert("eternal", true);
} else { } else {
entryObj.insert("current_age", QJsonValue(double(entry->current_age))); entryObj.insert("current_age", QJsonValue(double(entry->m_current_age)));
entryObj.insert("max_age", QJsonValue(double(entry->max_age))); entryObj.insert("max_age", QJsonValue(double(entry->m_max_age)));
} }
entriesArr.append(entryObj); entriesArr.append(entryObj);
} }

View File

@ -49,47 +49,47 @@ class MetaEntry {
MetaEntry() = default; MetaEntry() = default;
public: public:
auto isStale() -> bool { return stale; } auto isStale() -> bool { return m_stale; }
void setStale(bool stale) { this->stale = stale; } void setStale(bool stale) { m_stale = stale; }
auto getFullPath() -> QString; auto getFullPath() -> QString;
auto getRemoteChangedTimestamp() -> QString { return remote_changed_timestamp; } auto getRemoteChangedTimestamp() -> QString { return m_remote_changed_timestamp; }
void setRemoteChangedTimestamp(QString remote_changed_timestamp) { this->remote_changed_timestamp = remote_changed_timestamp; } void setRemoteChangedTimestamp(QString remote_changed_timestamp) { m_remote_changed_timestamp = remote_changed_timestamp; }
void setLocalChangedTimestamp(qint64 timestamp) { local_changed_timestamp = timestamp; } void setLocalChangedTimestamp(qint64 timestamp) { m_local_changed_timestamp = timestamp; }
auto getETag() -> QString { return etag; } auto getETag() -> QString { return m_etag; }
void setETag(QString etag) { this->etag = etag; } void setETag(QString etag) { m_etag = etag; }
auto getMD5Sum() -> QString { return md5sum; } auto getMD5Sum() -> QString { return m_md5sum; }
void setMD5Sum(QString md5sum) { this->md5sum = md5sum; } void setMD5Sum(QString md5sum) { m_md5sum = md5sum; }
/* Whether the entry expires after some time (false) or not (true). */ /* Whether the entry expires after some time (false) or not (true). */
void makeEternal(bool eternal) { is_eternal = eternal; } void makeEternal(bool eternal) { m_is_eternal = eternal; }
[[nodiscard]] bool isEternal() const { return is_eternal; } [[nodiscard]] bool isEternal() const { return m_is_eternal; }
auto getCurrentAge() -> qint64 { return current_age; } auto getCurrentAge() -> qint64 { return m_current_age; }
void setCurrentAge(qint64 age) { current_age = age; } void setCurrentAge(qint64 age) { m_current_age = age; }
auto getMaximumAge() -> qint64 { return max_age; } auto getMaximumAge() -> qint64 { return m_max_age; }
void setMaximumAge(qint64 age) { max_age = age; } void setMaximumAge(qint64 age) { m_max_age = age; }
bool isExpired(qint64 offset) { return !is_eternal && (current_age >= max_age - offset); }; bool isExpired(qint64 offset) { return !m_is_eternal && (m_current_age >= m_max_age - offset); };
protected: protected:
QString baseId; QString m_baseId;
QString basePath; QString m_basePath;
QString relativePath; QString m_relativePath;
QString md5sum; QString m_md5sum;
QString etag; QString m_etag;
qint64 local_changed_timestamp = 0; qint64 m_local_changed_timestamp = 0;
QString remote_changed_timestamp; // QString for now, RFC 2822 encoded time QString m_remote_changed_timestamp; // QString for now, RFC 2822 encoded time
qint64 current_age = 0; qint64 m_current_age = 0;
qint64 max_age = 0; qint64 m_max_age = 0;
bool is_eternal = false; bool m_is_eternal = false;
bool stale = true; bool m_stale = true;
}; };
using MetaEntryPtr = std::shared_ptr<MetaEntry>; using MetaEntryPtr = std::shared_ptr<MetaEntry>;

View File

@ -123,7 +123,7 @@ auto NetJob::getFailedFiles() -> QList<QString>
void NetJob::updateState() void NetJob::updateState()
{ {
emit progress(m_done.count(), m_total_size); emit progress(m_done.count(), totalSize());
setStatus(tr("Executing %1 task(s) (%2 out of %3 are done)") setStatus(tr("Executing %1 task(s) (%2 out of %3 are done)")
.arg(QString::number(m_doing.count()), QString::number(m_done.count()), QString::number(m_total_size))); .arg(QString::number(m_doing.count()), QString::number(m_done.count()), QString::number(totalSize())));
} }

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

View File

@ -39,5 +39,6 @@
<file>scalable/export.svg</file> <file>scalable/export.svg</file>
<file>scalable/rename.svg</file> <file>scalable/rename.svg</file>
<file>scalable/launch.svg</file> <file>scalable/launch.svg</file>
<file>scalable/shortcut.svg</file>
</qresource> </qresource>
</RCC> </RCC>

View 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

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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

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