Merge remote-tracking branch 'upstream/staging' into curseforge-url-handle
This commit is contained in:
commit
74fe2fb2a6
8
.editorconfig
Normal file
8
.editorconfig
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# EditorConfig specs and documentation: https://EditorConfig.org
|
||||||
|
|
||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# C++ Code Style settings
|
||||||
|
[*.{c++,cc,cpp,cppm,cxx,h,h++,hh,hpp,hxx,inl,ipp,ixx,tlh,tli}]
|
||||||
|
cpp_generate_documentation_comments = doxygen_slash_star
|
32
.github/workflows/backport.yml
vendored
Normal file
32
.github/workflows/backport.yml
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
name: Backport
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types: [closed, labeled]
|
||||||
|
|
||||||
|
# WARNING:
|
||||||
|
# When extending this action, be aware that $GITHUB_TOKEN allows write access to
|
||||||
|
# the GitHub repository. This means that it should not evaluate user input in a
|
||||||
|
# way that allows code injection.
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
backport:
|
||||||
|
permissions:
|
||||||
|
contents: write # for korthout/backport-action to create branch
|
||||||
|
pull-requests: write # for korthout/backport-action to create PR to backport
|
||||||
|
name: Backport Pull Request
|
||||||
|
if: github.repository_owner == 'PrismLauncher' && github.event.pull_request.merged == true && (github.event_name != 'labeled' || startsWith('backport', github.event.label.name))
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
- name: Create backport PRs
|
||||||
|
uses: korthout/backport-action@v1.3.1
|
||||||
|
with:
|
||||||
|
# Config README: https://github.com/korthout/backport-action#backport-action
|
||||||
|
pull_description: |-
|
||||||
|
Bot-based backport to `${target_branch}`, triggered by a label in #${pull_number}.
|
||||||
|
|
46
.github/workflows/build.yml
vendored
46
.github/workflows/build.yml
vendored
@ -68,7 +68,7 @@ jobs:
|
|||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: windows
|
qt_host: windows
|
||||||
qt_arch: ''
|
qt_arch: ''
|
||||||
qt_version: '6.5.1'
|
qt_version: '6.5.2'
|
||||||
qt_modules: 'qt5compat qtimageformats'
|
qt_modules: 'qt5compat qtimageformats'
|
||||||
qt_tools: ''
|
qt_tools: ''
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ jobs:
|
|||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: windows
|
qt_host: windows
|
||||||
qt_arch: 'win64_msvc2019_arm64'
|
qt_arch: 'win64_msvc2019_arm64'
|
||||||
qt_version: '6.5.1'
|
qt_version: '6.5.2'
|
||||||
qt_modules: 'qt5compat qtimageformats'
|
qt_modules: 'qt5compat qtimageformats'
|
||||||
qt_tools: ''
|
qt_tools: ''
|
||||||
|
|
||||||
@ -90,7 +90,7 @@ jobs:
|
|||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: mac
|
qt_host: mac
|
||||||
qt_arch: ''
|
qt_arch: ''
|
||||||
qt_version: '6.5.0'
|
qt_version: '6.5.2'
|
||||||
qt_modules: 'qt5compat qtimageformats'
|
qt_modules: 'qt5compat qtimageformats'
|
||||||
qt_tools: ''
|
qt_tools: ''
|
||||||
|
|
||||||
@ -264,23 +264,23 @@ jobs:
|
|||||||
- name: Configure CMake (macOS)
|
- name: Configure CMake (macOS)
|
||||||
if: runner.os == 'macOS' && matrix.qt_ver == 6
|
if: runner.os == 'macOS' && matrix.qt_ver == 6
|
||||||
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_OSX_ARCHITECTURES="x86_64;arm64" -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=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -G Ninja
|
||||||
|
|
||||||
- name: Configure CMake (macOS-Legacy)
|
- name: Configure CMake (macOS-Legacy)
|
||||||
if: runner.os == 'macOS' && matrix.qt_ver == 5
|
if: runner.os == 'macOS' && matrix.qt_ver == 5
|
||||||
run: |
|
run: |
|
||||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DMACOSX_SPARKLE_UPDATE_PUBLIC_KEY="" -DMACOSX_SPARKLE_UPDATE_FEED_URL="" -G Ninja
|
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DMACOSX_SPARKLE_UPDATE_PUBLIC_KEY="" -DMACOSX_SPARKLE_UPDATE_FEED_URL="" -G Ninja
|
||||||
|
|
||||||
- name: Configure CMake (Windows MinGW-w64)
|
- name: Configure CMake (Windows MinGW-w64)
|
||||||
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=6 -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=official -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}} -DLauncher_FORCE_BUNDLED_LIBS=ON
|
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -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 }}")
|
||||||
{
|
{
|
||||||
@ -295,7 +295,7 @@ jobs:
|
|||||||
- name: Configure CMake (Linux)
|
- name: Configure CMake (Linux)
|
||||||
if: runner.os == 'Linux'
|
if: runner.os == 'Linux'
|
||||||
run: |
|
run: |
|
||||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=Linux -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -G Ninja
|
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -G Ninja
|
||||||
|
|
||||||
##
|
##
|
||||||
# BUILD
|
# BUILD
|
||||||
@ -586,33 +586,3 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
bundle: "Prism Launcher.flatpak"
|
bundle: "Prism Launcher.flatpak"
|
||||||
manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml
|
manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml
|
||||||
|
|
||||||
nix:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
package:
|
|
||||||
- prismlauncher
|
|
||||||
- prismlauncher-qt5
|
|
||||||
steps:
|
|
||||||
- name: Clone repository
|
|
||||||
if: inputs.build_type == 'Debug'
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
submodules: 'true'
|
|
||||||
- name: Install nix
|
|
||||||
if: inputs.build_type == 'Debug'
|
|
||||||
uses: cachix/install-nix-action@v22
|
|
||||||
with:
|
|
||||||
install_url: https://nixos.org/nix/install
|
|
||||||
extra_nix_config: |
|
|
||||||
auto-optimise-store = true
|
|
||||||
experimental-features = nix-command flakes
|
|
||||||
- uses: cachix/cachix-action@v12
|
|
||||||
if: inputs.build_type == 'Debug'
|
|
||||||
with:
|
|
||||||
name: prismlauncher
|
|
||||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
|
||||||
- name: Build
|
|
||||||
if: inputs.build_type == 'Debug'
|
|
||||||
run: nix build .#${{ matrix.package }} --print-build-logs
|
|
||||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -19,3 +19,6 @@
|
|||||||
[submodule "libraries/cmark"]
|
[submodule "libraries/cmark"]
|
||||||
path = libraries/cmark
|
path = libraries/cmark
|
||||||
url = https://github.com/commonmark/cmark.git
|
url = https://github.com/commonmark/cmark.git
|
||||||
|
[submodule "flatpak/shared-modules"]
|
||||||
|
path = flatpak/shared-modules
|
||||||
|
url = https://github.com/flathub/shared-modules.git
|
||||||
|
@ -85,6 +85,38 @@ 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}")
|
||||||
|
|
||||||
|
option(DEBUG_ADDRESS_SANITIZER "Enable Address Sanitizer in Debug builds" on)
|
||||||
|
|
||||||
|
# If this is a Debug build turn on address sanitiser
|
||||||
|
if (CMAKE_BUILD_TYPE STREQUAL "Debug" AND DEBUG_ADDRESS_SANITIZER)
|
||||||
|
message(STATUS "Address Sanitizer enabled for Debug builds, Turn it off with -DDEBUG_ADDRESS_SANITIZER=off")
|
||||||
|
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
|
||||||
|
if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
|
||||||
|
# using clang with clang-cl front end
|
||||||
|
message(STATUS "Address Sanitizer available on Clang MSVC frontend")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fsanitize=address /O1 /Oy-")
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /fsanitize=address /O1 /Oy-")
|
||||||
|
else()
|
||||||
|
# AppleClang and Clang
|
||||||
|
message(STATUS "Address Sanitizer available on Clang")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -O1 -fno-omit-frame-pointer")
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -O1 -fno-omit-frame-pointer")
|
||||||
|
endif()
|
||||||
|
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
# GCC
|
||||||
|
message(STATUS "Address Sanitizer available on GCC")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -O1 -fno-omit-frame-pointer")
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -O1 -fno-omit-frame-pointer")
|
||||||
|
link_libraries("asan")
|
||||||
|
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
|
||||||
|
message(STATUS "Address Sanitizer available on MSVC")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fsanitize=address /O1 /Oy-")
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /fsanitize=address /O1 /Oy-")
|
||||||
|
else()
|
||||||
|
message(STATUS "Address Sanitizer not available on compiler ${CMAKE_CXX_COMPILER_ID}")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
option(ENABLE_LTO "Enable Link Time Optimization" off)
|
option(ENABLE_LTO "Enable Link Time Optimization" off)
|
||||||
|
|
||||||
if(ENABLE_LTO)
|
if(ENABLE_LTO)
|
||||||
@ -146,7 +178,7 @@ set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.
|
|||||||
set(Launcher_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_MINOR},0,0")
|
set(Launcher_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_MINOR},0,0")
|
||||||
|
|
||||||
# Build platform.
|
# Build platform.
|
||||||
set(Launcher_BUILD_PLATFORM "" CACHE STRING "A short string identifying the platform that this build was built for. Only used to display in the about dialog.")
|
set(Launcher_BUILD_PLATFORM "unknown" CACHE STRING "A short string identifying the platform that this build was built for. Only used to display in the about dialog.")
|
||||||
|
|
||||||
# Channel list URL
|
# Channel list URL
|
||||||
set(Launcher_UPDATER_BASE "" CACHE STRING "Base URL for the updater.")
|
set(Launcher_UPDATER_BASE "" CACHE STRING "Base URL for the updater.")
|
||||||
@ -286,6 +318,8 @@ add_subdirectory(program_info)
|
|||||||
|
|
||||||
####################################### Install layout #######################################
|
####################################### Install layout #######################################
|
||||||
|
|
||||||
|
set(Launcher_ENABLE_UPDATER NO)
|
||||||
|
|
||||||
if(NOT (UNIX AND APPLE))
|
if(NOT (UNIX AND APPLE))
|
||||||
# Install "portable.txt" if selected component is "portable"
|
# Install "portable.txt" if selected component is "portable"
|
||||||
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_Portable_File}" DESTINATION "." COMPONENT portable EXCLUDE_FROM_ALL)
|
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_Portable_File}" DESTINATION "." COMPONENT portable EXCLUDE_FROM_ALL)
|
||||||
@ -310,9 +344,9 @@ if(UNIX AND APPLE)
|
|||||||
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_NAME}")
|
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_NAME}")
|
||||||
set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_NAME}")
|
set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_NAME}")
|
||||||
set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns)
|
set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns)
|
||||||
set(MACOSX_BUNDLE_COPYRIGHT "© 2022 ${Launcher_Copyright_Mac}")
|
set(MACOSX_BUNDLE_COPYRIGHT "© 2022-2023 ${Launcher_Copyright_Mac}")
|
||||||
set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=")
|
set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=" CACHE STRING "Public key for Sparkle update feed")
|
||||||
set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml")
|
set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml" CACHE STRING "URL for Sparkle update feed")
|
||||||
|
|
||||||
set(MACOSX_SPARKLE_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.1.0/Sparkle-2.1.0.tar.xz" CACHE STRING "URL to Sparkle release archive")
|
set(MACOSX_SPARKLE_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.1.0/Sparkle-2.1.0.tar.xz" CACHE STRING "URL to Sparkle release archive")
|
||||||
set(MACOSX_SPARKLE_SHA256 "bf6ac1caa9f8d321d5784859c88da874f28412f37fb327bc21b7b14c5d61ef94" CACHE STRING "SHA256 checksum for Sparkle release archive")
|
set(MACOSX_SPARKLE_SHA256 "bf6ac1caa9f8d321d5784859c88da874f28412f37fb327bc21b7b14c5d61ef94" CACHE STRING "SHA256 checksum for Sparkle release archive")
|
||||||
@ -321,8 +355,12 @@ if(UNIX AND APPLE)
|
|||||||
# directories to look for dependencies
|
# directories to look for dependencies
|
||||||
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} ${MACOSX_SPARKLE_DIR})
|
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} ${MACOSX_SPARKLE_DIR})
|
||||||
|
|
||||||
|
if(NOT MACOSX_SPARKLE_UPDATE_PUBLIC_KEY STREQUAL "" AND NOT MACOSX_SPARKLE_UPDATE_FEED_URL STREQUAL "")
|
||||||
|
set(Launcher_ENABLE_UPDATER YES)
|
||||||
|
endif()
|
||||||
|
|
||||||
# install as bundle
|
# install as bundle
|
||||||
set(INSTALL_BUNDLE "full")
|
set(INSTALL_BUNDLE "full" CACHE STRING "Use fixup_bundle to bundle dependencies")
|
||||||
|
|
||||||
# Add the icon
|
# Add the icon
|
||||||
install(FILES ${Launcher_Branding_ICNS} DESTINATION ${RESOURCES_DEST_DIR} RENAME ${Launcher_Name}.icns)
|
install(FILES ${Launcher_Branding_ICNS} DESTINATION ${RESOURCES_DEST_DIR} RENAME ${Launcher_Name}.icns)
|
||||||
@ -332,10 +370,10 @@ elseif(UNIX)
|
|||||||
|
|
||||||
set(BINARY_DEST_DIR "bin")
|
set(BINARY_DEST_DIR "bin")
|
||||||
set(LIBRARY_DEST_DIR "lib${LIB_SUFFIX}")
|
set(LIBRARY_DEST_DIR "lib${LIB_SUFFIX}")
|
||||||
set(JARS_DEST_DIR "share/${Launcher_APP_BINARY_NAME}")
|
set(JARS_DEST_DIR "share/${Launcher_Name}")
|
||||||
|
|
||||||
# install as bundle with no dependencies included
|
# install as bundle with no dependencies included
|
||||||
set(INSTALL_BUNDLE "nodeps")
|
set(INSTALL_BUNDLE "nodeps" CACHE STRING "Use fixup_bundle to bundle dependencies")
|
||||||
|
|
||||||
# Set RPATH
|
# Set RPATH
|
||||||
SET(Launcher_BINARY_RPATH "$ORIGIN/")
|
SET(Launcher_BINARY_RPATH "$ORIGIN/")
|
||||||
@ -345,7 +383,7 @@ elseif(UNIX)
|
|||||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION "${KDE_INSTALL_ICONDIR}/hicolor/scalable/apps")
|
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION "${KDE_INSTALL_ICONDIR}/hicolor/scalable/apps")
|
||||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_mrpack_MIMEInfo} DESTINATION ${KDE_INSTALL_MIMEDIR})
|
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_mrpack_MIMEInfo} DESTINATION ${KDE_INSTALL_MIMEDIR})
|
||||||
|
|
||||||
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/launcher/qtlogging.ini" DESTINATION "${KDE_INSTALL_DATADIR}/${Launcher_Name}")
|
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/launcher/qtlogging.ini" DESTINATION "share/${Launcher_Name}")
|
||||||
|
|
||||||
if(Launcher_ManPage)
|
if(Launcher_ManPage)
|
||||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_ManPage} DESTINATION "${KDE_INSTALL_MANDIR}/man6")
|
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_ManPage} DESTINATION "${KDE_INSTALL_MANDIR}/man6")
|
||||||
@ -369,7 +407,7 @@ elseif(WIN32)
|
|||||||
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
|
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
|
||||||
|
|
||||||
# install as bundle
|
# install as bundle
|
||||||
set(INSTALL_BUNDLE "full")
|
set(INSTALL_BUNDLE "full" CACHE STRING "Use fixup_bundle to bundle dependencies")
|
||||||
else()
|
else()
|
||||||
message(FATAL_ERROR "Platform not supported")
|
message(FATAL_ERROR "Platform not supported")
|
||||||
endif()
|
endif()
|
||||||
|
@ -19,7 +19,7 @@ In an effort to ensure that the code you contribute is actually compatible with
|
|||||||
|
|
||||||
This can be done by appending `-s` to your `git commit` call, or by manually appending the following text to your commit message:
|
This can be done by appending `-s` to your `git commit` call, or by manually appending the following text to your commit message:
|
||||||
|
|
||||||
```
|
```text
|
||||||
<commit message>
|
<commit message>
|
||||||
|
|
||||||
Signed-off-by: Author name <Author email>
|
Signed-off-by: Author name <Author email>
|
||||||
@ -27,7 +27,7 @@ Signed-off-by: Author name <Author email>
|
|||||||
|
|
||||||
By signing off your work, you agree to the terms below:
|
By signing off your work, you agree to the terms below:
|
||||||
|
|
||||||
```
|
```text
|
||||||
Developer's Certificate of Origin 1.1
|
Developer's Certificate of Origin 1.1
|
||||||
|
|
||||||
By making a contribution to this project, I certify that:
|
By making a contribution to this project, I certify that:
|
||||||
@ -61,3 +61,9 @@ As a bonus, you can also [cryptographically sign your commits][gh-signing-commit
|
|||||||
|
|
||||||
[gh-signing-commits]: https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits
|
[gh-signing-commits]: https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits
|
||||||
[gh-vigilant-mode]: https://docs.github.com/en/authentication/managing-commit-signature-verification/displaying-verification-statuses-for-all-of-your-commits
|
[gh-vigilant-mode]: https://docs.github.com/en/authentication/managing-commit-signature-verification/displaying-verification-statuses-for-all-of-your-commits
|
||||||
|
|
||||||
|
## Backporting to Release Branches
|
||||||
|
|
||||||
|
We use [automated backports](https://github.com/PrismLauncher/PrismLauncher/blob/develop/.github/workflows/backport.yml) to merge specific contributions from develop into `release` branches.
|
||||||
|
|
||||||
|
This is done when pull requests are merged and have labels such as `backport release-7.x` - which should be added along with the milestone for the release.
|
||||||
|
@ -82,14 +82,16 @@ Thanks to the awesome people over at [MacStadium](https://www.macstadium.com/),
|
|||||||
|
|
||||||
## Forking/Redistributing/Custom builds policy
|
## Forking/Redistributing/Custom builds policy
|
||||||
|
|
||||||
We don't care what you do with your fork/custom build as long as you follow the terms of the [license](LICENSE) (this is a legal responsibility), and if you made code changes rather than just packaging a custom build, please do the following as a basic courtesy:
|
You are free to fork, redistribute and provide custom builds as long as you follow the terms of the [license](LICENSE) (this is a legal responsibility), and if you made code changes rather than just packaging a custom build, please do the following as a basic courtesy:
|
||||||
|
|
||||||
- Make it clear that your fork is not Prism Launcher and is not endorsed by or affiliated with the Prism Launcher project (<https://prismlauncher.org>).
|
- Make it clear that your fork is not Prism Launcher and is not endorsed by or affiliated with the Prism Launcher project (<https://prismlauncher.org>).
|
||||||
- Go through [CMakeLists.txt](CMakeLists.txt) and change Prism Launcher's API keys to your own or set them to empty strings (`""`) to disable them (this way the program will still compile but the functionality requiring those keys will be disabled).
|
- Go through [CMakeLists.txt](CMakeLists.txt) and change Prism Launcher's API keys to your own or set them to empty strings (`""`) to disable them (this way the program will still compile but the functionality requiring those keys will be disabled).
|
||||||
|
|
||||||
If you have any questions or want any clarification on the above conditions please make an issue and ask us.
|
If you have any questions or want any clarification on the above conditions please make an issue and ask us.
|
||||||
|
|
||||||
Be aware that if you build this software without removing the provided API keys in [CMakeLists.txt](CMakeLists.txt) you are accepting the following terms and conditions:
|
If you are just building Prism Launcher for your distribution, please make sure to set the `Launcher_BUILD_PLATFORM` to a slug representing your distribution. Examples are `archlinux`, `fedora` and `nixpkgs`.
|
||||||
|
|
||||||
|
Note that if you build this software without removing the provided API keys in [CMakeLists.txt](CMakeLists.txt) you are accepting the following terms and conditions:
|
||||||
|
|
||||||
- [Microsoft Identity Platform Terms of Use](https://docs.microsoft.com/en-us/legal/microsoft-identity-platform/terms-of-use)
|
- [Microsoft Identity Platform Terms of Use](https://docs.microsoft.com/en-us/legal/microsoft-identity-platform/terms-of-use)
|
||||||
- [CurseForge 3rd Party API Terms and Conditions](https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions)
|
- [CurseForge 3rd Party API Terms and Conditions](https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions)
|
||||||
|
@ -65,7 +65,7 @@ Config::Config()
|
|||||||
MAC_SPARKLE_PUB_KEY = "@MACOSX_SPARKLE_UPDATE_PUBLIC_KEY@";
|
MAC_SPARKLE_PUB_KEY = "@MACOSX_SPARKLE_UPDATE_PUBLIC_KEY@";
|
||||||
MAC_SPARKLE_APPCAST_URL = "@MACOSX_SPARKLE_UPDATE_FEED_URL@";
|
MAC_SPARKLE_APPCAST_URL = "@MACOSX_SPARKLE_UPDATE_FEED_URL@";
|
||||||
|
|
||||||
if (BUILD_PLATFORM == "macOS" && !MAC_SPARKLE_PUB_KEY.isEmpty() && !MAC_SPARKLE_APPCAST_URL.isEmpty())
|
if (!MAC_SPARKLE_PUB_KEY.isEmpty() && !MAC_SPARKLE_APPCAST_URL.isEmpty())
|
||||||
{
|
{
|
||||||
UPDATER_ENABLED = true;
|
UPDATER_ENABLED = true;
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ class Config {
|
|||||||
|
|
||||||
bool UPDATER_ENABLED = false;
|
bool UPDATER_ENABLED = false;
|
||||||
|
|
||||||
/// A short string identifying this build's platform. For example, "lin64" or "win32".
|
/// A short string identifying this build's platform or distribution.
|
||||||
QString BUILD_PLATFORM;
|
QString BUILD_PLATFORM;
|
||||||
|
|
||||||
/// A string containing the build timestamp
|
/// A string containing the build timestamp
|
||||||
|
24
flake.lock
generated
24
flake.lock
generated
@ -21,11 +21,11 @@
|
|||||||
"nixpkgs-lib": "nixpkgs-lib"
|
"nixpkgs-lib": "nixpkgs-lib"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1688254665,
|
"lastModified": 1688466019,
|
||||||
"narHash": "sha256-8FHEgBrr7gYNiS/NzCxIO3m4hvtLRW9YY1nYo1ivm3o=",
|
"narHash": "sha256-VeM2akYrBYMsb4W/MmBo1zmaMfgbL4cH3Pu8PGyIwJ0=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "267149c58a14d15f7f81b4d737308421de9d7152",
|
"rev": "8e8d955c22df93dbe24f19ea04f47a74adbdc5ec",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -76,11 +76,11 @@
|
|||||||
"libnbtplusplus": {
|
"libnbtplusplus": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1650031308,
|
"lastModified": 1690036783,
|
||||||
"narHash": "sha256-TvVOjkUobYJD9itQYueELJX3wmecvEdCbJ0FinW2mL4=",
|
"narHash": "sha256-A5kTgICnx+Qdq3Fir/bKTfdTt/T1NQP2SC+nhN1ENug=",
|
||||||
"owner": "PrismLauncher",
|
"owner": "PrismLauncher",
|
||||||
"repo": "libnbtplusplus",
|
"repo": "libnbtplusplus",
|
||||||
"rev": "2203af7eeb48c45398139b583615134efd8d407f",
|
"rev": "a5e8fd52b8bf4ab5d5bcc042b2a247867589985f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -91,11 +91,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1688221086,
|
"lastModified": 1690630721,
|
||||||
"narHash": "sha256-cdW6qUL71cNWhHCpMPOJjlw0wzSRP0pVlRn2vqX/VVg=",
|
"narHash": "sha256-Y04onHyBQT4Erfr2fc82dbJTfXGYrf4V0ysLUYnPOP8=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "cd99c2b3c9f160cd004318e0697f90bbd5960825",
|
"rev": "d2b52322f35597c62abf56de91b0236746b2a03d",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -138,11 +138,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1688386108,
|
"lastModified": 1690628027,
|
||||||
"narHash": "sha256-Vffto9QaVonzYAcPlAzd0soqWYpPpKk60dfNLSIXcFA=",
|
"narHash": "sha256-OTSbA2hM6VmxyZ/4siYPANffMBzIsKu04GLjXcv8ST0=",
|
||||||
"owner": "cachix",
|
"owner": "cachix",
|
||||||
"repo": "pre-commit-hooks.nix",
|
"repo": "pre-commit-hooks.nix",
|
||||||
"rev": "42587d3414d1747999a5f71e92a83cf6547b62da",
|
"rev": "1e2443dd3f669eb65433b2fc26a3065e05a7dc9c",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
22
flatpak/libdecor.json
Normal file
22
flatpak/libdecor.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "libdecor",
|
||||||
|
"buildsystem": "meson",
|
||||||
|
"config-opts": [
|
||||||
|
"-Ddemo=false"
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://gitlab.freedesktop.org/libdecor/libdecor.git",
|
||||||
|
"commit": "73260393a97291c887e1074ab7f318e031be0ac6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patch",
|
||||||
|
"path": "patches/weird_libdecor.patch"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cleanup": [
|
||||||
|
"/include",
|
||||||
|
"/lib/pkgconfig"
|
||||||
|
]
|
||||||
|
}
|
@ -5,13 +5,6 @@ sdk: org.kde.Sdk
|
|||||||
sdk-extensions:
|
sdk-extensions:
|
||||||
- org.freedesktop.Sdk.Extension.openjdk17
|
- org.freedesktop.Sdk.Extension.openjdk17
|
||||||
- org.freedesktop.Sdk.Extension.openjdk8
|
- 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
|
command: prismlauncher
|
||||||
finish-args:
|
finish-args:
|
||||||
@ -25,13 +18,25 @@ finish-args:
|
|||||||
- --filesystem=xdg-run/app/com.discordapp.Discord:create
|
- --filesystem=xdg-run/app/com.discordapp.Discord:create
|
||||||
# Mod drag&drop
|
# Mod drag&drop
|
||||||
- --filesystem=xdg-download:ro
|
- --filesystem=xdg-download:ro
|
||||||
|
# FTBApp import
|
||||||
|
- --filesystem=~/.ftba:ro
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
- /lib/libGLU*
|
||||||
|
|
||||||
modules:
|
modules:
|
||||||
|
# Might be needed by some Controller mods (see https://github.com/isXander/Controlify/issues/31)
|
||||||
|
- shared-modules/libusb/libusb.json
|
||||||
|
|
||||||
|
# Needed for proper Wayland support
|
||||||
|
- libdecor.json
|
||||||
|
|
||||||
- name: prismlauncher
|
- name: prismlauncher
|
||||||
buildsystem: cmake-ninja
|
buildsystem: cmake-ninja
|
||||||
|
builddir: true
|
||||||
config-opts:
|
config-opts:
|
||||||
- -DLauncher_BUILD_PLATFORM=flatpak
|
- -DLauncher_BUILD_PLATFORM=flatpak
|
||||||
- -DCMAKE_BUILD_TYPE=Debug
|
- -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||||
- -DLauncher_QT_VERSION_MAJOR=5
|
- -DLauncher_QT_VERSION_MAJOR=5
|
||||||
build-options:
|
build-options:
|
||||||
env:
|
env:
|
||||||
@ -40,7 +45,7 @@ modules:
|
|||||||
sources:
|
sources:
|
||||||
- type: dir
|
- type: dir
|
||||||
path: ../
|
path: ../
|
||||||
builddir: true
|
|
||||||
- name: openjdk
|
- name: openjdk
|
||||||
buildsystem: simple
|
buildsystem: simple
|
||||||
build-commands:
|
build-commands:
|
||||||
@ -49,14 +54,45 @@ modules:
|
|||||||
- mv /app/jre /app/jdk/17
|
- mv /app/jre /app/jdk/17
|
||||||
- /usr/lib/sdk/openjdk8/install.sh
|
- /usr/lib/sdk/openjdk8/install.sh
|
||||||
- mv /app/jre /app/jdk/8
|
- mv /app/jre /app/jdk/8
|
||||||
cleanup: [/jre]
|
cleanup:
|
||||||
|
- /jre
|
||||||
|
|
||||||
|
- name: glfw
|
||||||
|
buildsystem: cmake-ninja
|
||||||
|
config-opts:
|
||||||
|
- -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||||
|
- -DBUILD_SHARED_LIBS:BOOL=ON
|
||||||
|
- -DGLFW_USE_WAYLAND=ON
|
||||||
|
sources:
|
||||||
|
- type: git
|
||||||
|
url: https://github.com/glfw/glfw.git
|
||||||
|
commit: 3fa2360720eeba1964df3c0ecf4b5df8648a8e52
|
||||||
|
- type: patch
|
||||||
|
path: patches/0003-Don-t-crash-on-calls-to-focus-or-icon.patch
|
||||||
|
- type: patch
|
||||||
|
path: patches/0005-Add-warning-about-being-an-unofficial-patch.patch
|
||||||
|
- type: patch
|
||||||
|
path: patches/0007-Platform-Prefer-Wayland-over-X11.patch
|
||||||
|
cleanup:
|
||||||
|
- /include
|
||||||
|
- /lib/cmake
|
||||||
|
- /lib/pkgconfig
|
||||||
|
|
||||||
- name: xrandr
|
- name: xrandr
|
||||||
buildsystem: autotools
|
buildsystem: autotools
|
||||||
sources:
|
sources:
|
||||||
- type: archive
|
- type: archive
|
||||||
url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.1.tar.xz
|
url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.2.tar.xz
|
||||||
sha256: 7bc76daf9d72f8aff885efad04ce06b90488a1a169d118dea8a2b661832e8762
|
sha256: c8bee4790d9058bacc4b6246456c58021db58a87ddda1a9d0139bf5f18f1f240
|
||||||
cleanup: [/share/man, /bin/xkeystone]
|
x-checker-data:
|
||||||
|
type: anitya
|
||||||
|
project-id: 14957
|
||||||
|
stable-only: true
|
||||||
|
url-template: https://xorg.freedesktop.org/archive/individual/app/xrandr-$version.tar.xz
|
||||||
|
cleanup:
|
||||||
|
- /share/man
|
||||||
|
- /bin/xkeystone
|
||||||
|
|
||||||
- name: gamemode
|
- name: gamemode
|
||||||
buildsystem: meson
|
buildsystem: meson
|
||||||
config-opts:
|
config-opts:
|
||||||
@ -67,19 +103,56 @@ modules:
|
|||||||
# post-install is running inside the build dir, we need it from the source though
|
# post-install is running inside the build dir, we need it from the source though
|
||||||
- install -Dm755 ../data/gamemoderun -t /app/bin
|
- install -Dm755 ../data/gamemoderun -t /app/bin
|
||||||
sources:
|
sources:
|
||||||
- type: git
|
- type: archive
|
||||||
url: https://github.com/FeralInteractive/gamemode
|
archive-type: tar-gzip
|
||||||
tag: "1.7"
|
url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.7
|
||||||
commit: 4dc99dff76218718763a6b07fc1900fa6d1dafd9
|
sha256: 57ce73ba605d1cf12f8d13725006a895182308d93eba0f69f285648449641803
|
||||||
|
x-checker-data:
|
||||||
|
type: json
|
||||||
|
url: https://api.github.com/repos/FeralInteractive/gamemode/releases/latest
|
||||||
|
version-query: .tag_name
|
||||||
|
url-query: .tarball_url
|
||||||
|
timestamp-query: .published_at
|
||||||
|
cleanup:
|
||||||
|
- /include
|
||||||
|
- /lib/pkgconfig
|
||||||
|
- /lib/libgamemodeauto.a
|
||||||
|
|
||||||
|
- name: glxinfo
|
||||||
|
buildsystem: meson
|
||||||
|
config-opts:
|
||||||
|
- --bindir=/app/mesa-demos
|
||||||
|
- -Degl=disabled
|
||||||
|
- -Dglut=disabled
|
||||||
|
- -Dosmesa=disabled
|
||||||
|
- -Dvulkan=disabled
|
||||||
|
- -Dwayland=disabled
|
||||||
|
post-install:
|
||||||
|
- mv -v /app/mesa-demos/glxinfo /app/bin
|
||||||
|
sources:
|
||||||
|
- type: archive
|
||||||
|
url: https://archive.mesa3d.org/demos/mesa-demos-9.0.0.tar.xz
|
||||||
|
sha256: 3046a3d26a7b051af7ebdd257a5f23bfeb160cad6ed952329cdff1e9f1ed496b
|
||||||
|
x-checker-data:
|
||||||
|
type: anitya
|
||||||
|
project-id: 16781
|
||||||
|
stable-only: true
|
||||||
|
url-template: https://archive.mesa3d.org/demos/mesa-demos-$version.tar.xz
|
||||||
|
cleanup:
|
||||||
|
- /include
|
||||||
|
- /mesa-demos
|
||||||
|
- /share
|
||||||
|
modules:
|
||||||
|
- shared-modules/glu/glu-9.json
|
||||||
|
|
||||||
- name: enhance
|
- name: enhance
|
||||||
buildsystem: simple
|
buildsystem: simple
|
||||||
build-commands:
|
build-commands:
|
||||||
- mkdir -p /app/utils/gamescope
|
|
||||||
- install -Dm755 prime-run /app/bin/prime-run
|
- install -Dm755 prime-run /app/bin/prime-run
|
||||||
- mv /app/bin/prismlauncher /app/bin/prismrun
|
- mv /app/bin/prismlauncher /app/bin/prismrun
|
||||||
- install -Dm755 prismlauncher /app/bin/prismlauncher
|
- install -Dm755 prismlauncher /app/bin/prismlauncher
|
||||||
sources:
|
sources:
|
||||||
- type: file
|
- type: file
|
||||||
path: ../flatpak/prime-run
|
path: prime-run
|
||||||
- type: file
|
- type: file
|
||||||
path: ../flatpak/prismlauncher
|
path: prismlauncher
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
diff --git a/src/wl_window.c b/src/wl_window.c
|
||||||
|
index 52d3b9eb..4ac4eb5d 100644
|
||||||
|
--- a/src/wl_window.c
|
||||||
|
+++ b/src/wl_window.c
|
||||||
|
@@ -2117,8 +2117,7 @@ void _glfwSetWindowTitleWayland(_GLFWwindow* window, const char* title)
|
||||||
|
void _glfwSetWindowIconWayland(_GLFWwindow* window,
|
||||||
|
int count, const GLFWimage* images)
|
||||||
|
{
|
||||||
|
- _glfwInputError(GLFW_FEATURE_UNAVAILABLE,
|
||||||
|
- "Wayland: The platform does not support setting the window icon");
|
||||||
|
+ fprintf(stderr, "!!! Ignoring Error: Wayland: The platform does not support setting the window icon\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void _glfwGetWindowPosWayland(_GLFWwindow* window, int* xpos, int* ypos)
|
||||||
|
@@ -2361,8 +2360,7 @@ void _glfwRequestWindowAttentionWayland(_GLFWwindow* window)
|
||||||
|
|
||||||
|
void _glfwFocusWindowWayland(_GLFWwindow* window)
|
||||||
|
{
|
||||||
|
- _glfwInputError(GLFW_FEATURE_UNAVAILABLE,
|
||||||
|
- "Wayland: The platform does not support setting the input focus");
|
||||||
|
+ fprintf(stderr, "!!! Ignoring Error: Wayland: The platform does not support setting the input focus\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void _glfwSetWindowMonitorWayland(_GLFWwindow* window,
|
@ -0,0 +1,17 @@
|
|||||||
|
diff --git a/src/init.c b/src/init.c
|
||||||
|
index 06dbb3f2..a7c6da86 100644
|
||||||
|
--- a/src/init.c
|
||||||
|
+++ b/src/init.c
|
||||||
|
@@ -449,6 +449,12 @@ GLFWAPI int glfwInit(void)
|
||||||
|
_glfw.initialized = GLFW_TRUE;
|
||||||
|
|
||||||
|
glfwDefaultWindowHints();
|
||||||
|
+
|
||||||
|
+ fprintf(stderr, "!!! Patched GLFW from https://github.com/Admicos/minecraft-wayland\n"
|
||||||
|
+ "!!! If any issues with the window, or some issues with rendering, occur, "
|
||||||
|
+ "first try with the built-in GLFW, and if that solves the issue, report there first.\n"
|
||||||
|
+ "!!! Use outside Minecraft is untested, and things might break.\n");
|
||||||
|
+
|
||||||
|
return GLFW_TRUE;
|
||||||
|
}
|
||||||
|
|
20
flatpak/patches/0007-Platform-Prefer-Wayland-over-X11.patch
Normal file
20
flatpak/patches/0007-Platform-Prefer-Wayland-over-X11.patch
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
diff --git a/src/platform.c b/src/platform.c
|
||||||
|
index c5966ae7..3e7442f9 100644
|
||||||
|
--- a/src/platform.c
|
||||||
|
+++ b/src/platform.c
|
||||||
|
@@ -49,12 +49,12 @@ static const struct
|
||||||
|
#if defined(_GLFW_COCOA)
|
||||||
|
{ GLFW_PLATFORM_COCOA, _glfwConnectCocoa },
|
||||||
|
#endif
|
||||||
|
-#if defined(_GLFW_X11)
|
||||||
|
- { GLFW_PLATFORM_X11, _glfwConnectX11 },
|
||||||
|
-#endif
|
||||||
|
#if defined(_GLFW_WAYLAND)
|
||||||
|
{ GLFW_PLATFORM_WAYLAND, _glfwConnectWayland },
|
||||||
|
#endif
|
||||||
|
+#if defined(_GLFW_X11)
|
||||||
|
+ { GLFW_PLATFORM_X11, _glfwConnectX11 },
|
||||||
|
+#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
GLFWbool _glfwSelectPlatform(int desiredID, _GLFWplatform* platform)
|
40
flatpak/patches/weird_libdecor.patch
Normal file
40
flatpak/patches/weird_libdecor.patch
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
diff --git a/src/libdecor.c b/src/libdecor.c
|
||||||
|
index a9c1106..1aa38b3 100644
|
||||||
|
--- a/src/libdecor.c
|
||||||
|
+++ b/src/libdecor.c
|
||||||
|
@@ -1391,22 +1391,32 @@ calculate_priority(const struct libdecor_plugin_description *plugin_description)
|
||||||
|
static bool
|
||||||
|
check_symbol_conflicts(const struct libdecor_plugin_description *plugin_description)
|
||||||
|
{
|
||||||
|
+ bool ret = true;
|
||||||
|
char * const *symbol;
|
||||||
|
+ void* main_prog = dlopen(NULL, RTLD_LAZY);
|
||||||
|
+ if (!main_prog) {
|
||||||
|
+ fprintf(stderr, "Plugin \"%s\" couldn't check conflicting symbols: \"%s\".\n",
|
||||||
|
+ plugin_description->description, dlerror());
|
||||||
|
+ return false;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
|
||||||
|
symbol = plugin_description->conflicting_symbols;
|
||||||
|
while (*symbol) {
|
||||||
|
dlerror();
|
||||||
|
- dlsym (RTLD_DEFAULT, *symbol);
|
||||||
|
+ dlsym (main_prog, *symbol);
|
||||||
|
if (!dlerror()) {
|
||||||
|
fprintf(stderr, "Plugin \"%s\" uses conflicting symbol \"%s\".\n",
|
||||||
|
plugin_description->description, *symbol);
|
||||||
|
- return false;
|
||||||
|
+ ret = false;
|
||||||
|
+ break;
|
||||||
|
}
|
||||||
|
|
||||||
|
symbol++;
|
||||||
|
}
|
||||||
|
|
||||||
|
- return true;
|
||||||
|
+ dlclose(main_prog);
|
||||||
|
+ return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct plugin_loader *
|
@ -5,7 +5,7 @@ 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";
|
test -S "$XDG_RUNTIME_DIR"/discord-ipc-"$i" || ln -sf {app/com.discordapp.Discord,"$XDG_RUNTIME_DIR"}/discord-ipc-"$i";
|
||||||
done
|
done
|
||||||
|
|
||||||
export PATH="${PATH}${PATH:+:}/app/utils/gamescope/bin:/usr/lib/extensions/vulkan/MangoHud/bin"
|
export PATH="${PATH}${PATH:+:}/usr/lib/extensions/vulkan/gamescope/bin:/usr/lib/extensions/vulkan/MangoHud/bin"
|
||||||
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}${LD_LIBRARY_PATH:+:}/usr/lib/extensions/vulkan/MangoHud/\$LIB/"
|
export VK_LAYER_PATH="/usr/lib/extensions/vulkan/share/vulkan/implicit_layer.d/"
|
||||||
|
|
||||||
exec /app/bin/prismrun "$@"
|
exec /app/bin/prismrun "$@"
|
||||||
|
1
flatpak/shared-modules
Submodule
1
flatpak/shared-modules
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 45094ca570be383d06df729b6972830ec63bd3df
|
6
garnix.yaml
Normal file
6
garnix.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
builds:
|
||||||
|
exclude: []
|
||||||
|
include:
|
||||||
|
- "checks.x86_64-linux.*"
|
||||||
|
- "devShells.*.*"
|
||||||
|
- "packages.*.*"
|
@ -6,9 +6,10 @@
|
|||||||
* Prism Launcher - Minecraft Launcher
|
* Prism Launcher - Minecraft Launcher
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
* Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
|
* Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
|
||||||
* Copyright (C) 2022 Tayou <tayou@gmx.net>
|
* Copyright (C) 2022 Tayou <git@tayou.org>
|
||||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||||
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||||
|
* Copyright (C) 2023 seth <getchoo at tuta dot io>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -130,17 +131,12 @@
|
|||||||
#include "MangoHud.h"
|
#include "MangoHud.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_MAC
|
#if defined(Q_OS_MAC) && defined(SPARKLE_ENABLED)
|
||||||
#include "updater/MacSparkleUpdater.h"
|
#include "updater/MacSparkleUpdater.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#if defined Q_OS_WIN32
|
#if defined Q_OS_WIN32
|
||||||
#ifndef WIN32_LEAN_AND_MEAN
|
#include "WindowsConsole.h"
|
||||||
#define WIN32_LEAN_AND_MEAN
|
|
||||||
#endif
|
|
||||||
#include <windows.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define STRINGIFY(x) #x
|
#define STRINGIFY(x) #x
|
||||||
@ -167,31 +163,15 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QSt
|
|||||||
fflush(stderr);
|
fflush(stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||||
{
|
{
|
||||||
#if defined Q_OS_WIN32
|
#if defined Q_OS_WIN32
|
||||||
// attach the parent console
|
// attach the parent console if stdout not already captured
|
||||||
if(AttachConsole(ATTACH_PARENT_PROCESS))
|
if (AttachWindowsConsole()) {
|
||||||
{
|
|
||||||
// if attach succeeds, reopen and sync all the i/o
|
|
||||||
if(freopen("CON", "w", stdout))
|
|
||||||
{
|
|
||||||
std::cout.sync_with_stdio();
|
|
||||||
}
|
|
||||||
if(freopen("CON", "w", stderr))
|
|
||||||
{
|
|
||||||
std::cerr.sync_with_stdio();
|
|
||||||
}
|
|
||||||
if(freopen("CON", "r", stdin))
|
|
||||||
{
|
|
||||||
std::cin.sync_with_stdio();
|
|
||||||
}
|
|
||||||
auto out = GetStdHandle (STD_OUTPUT_HANDLE);
|
|
||||||
DWORD written;
|
|
||||||
const char * endline = "\n";
|
|
||||||
WriteConsole(out, endline, strlen(endline), &written, NULL);
|
|
||||||
consoleAttached = true;
|
consoleAttached = true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -282,7 +262,16 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
QDir foo(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), ".."));
|
QDir foo;
|
||||||
|
if (DesktopServices::isSnap())
|
||||||
|
{
|
||||||
|
foo = QDir(getenv("SNAP_USER_COMMON"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foo = QDir(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), ".."));
|
||||||
|
}
|
||||||
|
|
||||||
dataPath = foo.absolutePath();
|
dataPath = foo.absolutePath();
|
||||||
adjustedBy = "Persistent data path";
|
adjustedBy = "Persistent data path";
|
||||||
|
|
||||||
@ -434,7 +423,11 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
}
|
}
|
||||||
// seach root path
|
// seach root path
|
||||||
if(!foundLoggingRules) {
|
if(!foundLoggingRules) {
|
||||||
|
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||||
|
logRulesPath = FS::PathCombine(m_rootPath, "share", BuildConfig.LAUNCHER_NAME, logRulesFile);
|
||||||
|
#else
|
||||||
logRulesPath = FS::PathCombine(m_rootPath, logRulesFile);
|
logRulesPath = FS::PathCombine(m_rootPath, logRulesFile);
|
||||||
|
#endif
|
||||||
qDebug() << "Testing" << logRulesPath << "...";
|
qDebug() << "Testing" << logRulesPath << "...";
|
||||||
foundLoggingRules = QFile::exists(logRulesPath);
|
foundLoggingRules = QFile::exists(logRulesPath);
|
||||||
}
|
}
|
||||||
@ -472,6 +465,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
|
|
||||||
qDebug() << BuildConfig.LAUNCHER_DISPLAYNAME << ", (c) 2013-2021 " << BuildConfig.LAUNCHER_COPYRIGHT;
|
qDebug() << BuildConfig.LAUNCHER_DISPLAYNAME << ", (c) 2013-2021 " << BuildConfig.LAUNCHER_COPYRIGHT;
|
||||||
qDebug() << "Version : " << BuildConfig.printableVersionString();
|
qDebug() << "Version : " << BuildConfig.printableVersionString();
|
||||||
|
qDebug() << "Platform : " << BuildConfig.BUILD_PLATFORM;
|
||||||
qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT;
|
qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT;
|
||||||
qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC;
|
qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC;
|
||||||
if (adjustedBy.size())
|
if (adjustedBy.size())
|
||||||
@ -569,6 +563,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
|
|
||||||
// Language
|
// Language
|
||||||
m_settings->registerSetting("Language", QString());
|
m_settings->registerSetting("Language", QString());
|
||||||
|
m_settings->registerSetting("UseSystemLocale", false);
|
||||||
|
|
||||||
// Console
|
// Console
|
||||||
m_settings->registerSetting("ShowConsole", false);
|
m_settings->registerSetting("ShowConsole", false);
|
||||||
@ -605,6 +600,9 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
m_settings->registerSetting("IgnoreJavaCompatibility", false);
|
m_settings->registerSetting("IgnoreJavaCompatibility", false);
|
||||||
m_settings->registerSetting("IgnoreJavaWizard", false);
|
m_settings->registerSetting("IgnoreJavaWizard", false);
|
||||||
|
|
||||||
|
// Mod loader settings
|
||||||
|
m_settings->registerSetting("DisableQuiltBeacon", false);
|
||||||
|
|
||||||
// Native library workarounds
|
// Native library workarounds
|
||||||
m_settings->registerSetting("UseNativeOpenAL", false);
|
m_settings->registerSetting("UseNativeOpenAL", false);
|
||||||
m_settings->registerSetting("UseNativeGLFW", false);
|
m_settings->registerSetting("UseNativeGLFW", false);
|
||||||
@ -619,9 +617,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
m_settings->registerSetting("ShowGlobalGameTime", true);
|
m_settings->registerSetting("ShowGlobalGameTime", true);
|
||||||
m_settings->registerSetting("RecordGameTime", true);
|
m_settings->registerSetting("RecordGameTime", true);
|
||||||
|
|
||||||
// Minecraft launch method
|
|
||||||
m_settings->registerSetting("MCLaunchMethod", "LauncherPart");
|
|
||||||
|
|
||||||
// Minecraft mods
|
// Minecraft mods
|
||||||
m_settings->registerSetting("ModMetadataDisabled", false);
|
m_settings->registerSetting("ModMetadataDisabled", false);
|
||||||
|
|
||||||
@ -695,7 +690,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
QUrl metaUrl(m_settings->get("MetaURLOverride").toString());
|
QUrl metaUrl(m_settings->get("MetaURLOverride").toString());
|
||||||
|
|
||||||
// get rid of invalid meta urls
|
// get rid of invalid meta urls
|
||||||
if (!metaUrl.isValid() || metaUrl.scheme() != "http" || metaUrl.scheme() != "https")
|
if (!metaUrl.isValid() || (metaUrl.scheme() != "http" && metaUrl.scheme() != "https"))
|
||||||
m_settings->reset("MetaURLOverride");
|
m_settings->reset("MetaURLOverride");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -767,7 +762,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
if(BuildConfig.UPDATER_ENABLED)
|
if(BuildConfig.UPDATER_ENABLED)
|
||||||
{
|
{
|
||||||
qDebug() << "Initializing updater";
|
qDebug() << "Initializing updater";
|
||||||
#ifdef Q_OS_MAC
|
#if defined(Q_OS_MAC) && defined(SPARKLE_ENABLED)
|
||||||
m_updater.reset(new MacSparkleUpdater());
|
m_updater.reset(new MacSparkleUpdater());
|
||||||
#endif
|
#endif
|
||||||
qDebug() << "<> Updater started.";
|
qDebug() << "<> Updater started.";
|
||||||
@ -919,12 +914,7 @@ bool Application::createSetupWizard()
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}();
|
}();
|
||||||
bool languageRequired = [&]()
|
bool languageRequired = settings()->get("Language").toString().isEmpty();
|
||||||
{
|
|
||||||
if (settings()->get("Language").toString().isEmpty())
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}();
|
|
||||||
bool pasteInterventionRequired = settings()->get("PastebinURL") != "";
|
bool pasteInterventionRequired = settings()->get("PastebinURL") != "";
|
||||||
bool themeInterventionRequired = settings()->get("ApplicationTheme") == "";
|
bool themeInterventionRequired = settings()->get("ApplicationTheme") == "";
|
||||||
bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired || themeInterventionRequired;
|
bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired || themeInterventionRequired;
|
||||||
@ -1174,6 +1164,16 @@ QIcon Application::getThemedIcon(const QString& name)
|
|||||||
return QIcon::fromTheme(name);
|
return QIcon::fromTheme(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QList<CatPack*> Application::getValidCatPacks()
|
||||||
|
{
|
||||||
|
return m_themeManager->getValidCatPacks();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Application::getCatPack(QString catName)
|
||||||
|
{
|
||||||
|
return m_themeManager->getCatPack(catName);
|
||||||
|
}
|
||||||
|
|
||||||
bool Application::openJsonEditor(const QString& filename)
|
bool Application::openJsonEditor(const QString& filename)
|
||||||
{
|
{
|
||||||
const QString file = QDir::current().absoluteFilePath(filename);
|
const QString file = QDir::current().absoluteFilePath(filename);
|
||||||
@ -1560,7 +1560,7 @@ QString Application::getJarPath(QString jarFile)
|
|||||||
{
|
{
|
||||||
QStringList potentialPaths = {
|
QStringList potentialPaths = {
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||||
FS::PathCombine(m_rootPath, "share/" + BuildConfig.LAUNCHER_APP_BINARY_NAME),
|
FS::PathCombine(m_rootPath, "share", BuildConfig.LAUNCHER_NAME),
|
||||||
#endif
|
#endif
|
||||||
FS::PathCombine(m_rootPath, "jars"),
|
FS::PathCombine(m_rootPath, "jars"),
|
||||||
FS::PathCombine(applicationDirPath(), "jars"),
|
FS::PathCombine(applicationDirPath(), "jars"),
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
/*
|
/*
|
||||||
* Prism Launcher - Minecraft Launcher
|
* Prism Launcher - Minecraft Launcher
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
* Copyright (C) 2022 Tayou <tayou@gmx.net>
|
* Copyright (C) 2022 Tayou <git@tayou.org>
|
||||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
* Copyright (C) 2023 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
|
||||||
@ -48,6 +48,7 @@
|
|||||||
#include <BaseInstance.h>
|
#include <BaseInstance.h>
|
||||||
|
|
||||||
#include "minecraft/launch/MinecraftServerTarget.h"
|
#include "minecraft/launch/MinecraftServerTarget.h"
|
||||||
|
#include "ui/themes/CatPack.h"
|
||||||
|
|
||||||
class LaunchController;
|
class LaunchController;
|
||||||
class LocalPeer;
|
class LocalPeer;
|
||||||
@ -126,9 +127,11 @@ public:
|
|||||||
|
|
||||||
void setApplicationTheme(const QString& name);
|
void setApplicationTheme(const QString& name);
|
||||||
|
|
||||||
shared_qobject_ptr<ExternalUpdater> updater() {
|
QList<CatPack*> getValidCatPacks();
|
||||||
return m_updater;
|
|
||||||
}
|
QString getCatPack(QString catName = "");
|
||||||
|
|
||||||
|
shared_qobject_ptr<ExternalUpdater> updater() { return m_updater; }
|
||||||
|
|
||||||
void triggerUpdateCheck();
|
void triggerUpdateCheck();
|
||||||
|
|
||||||
|
@ -15,15 +15,14 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <QString>
|
|
||||||
#include <QMetaType>
|
#include <QMetaType>
|
||||||
|
#include <QString>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* An abstract base class for versions.
|
* An abstract base class for versions.
|
||||||
*/
|
*/
|
||||||
class BaseVersion
|
class BaseVersion {
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
using Ptr = std::shared_ptr<BaseVersion>;
|
using Ptr = std::shared_ptr<BaseVersion>;
|
||||||
virtual ~BaseVersion() {}
|
virtual ~BaseVersion() {}
|
||||||
@ -45,14 +44,8 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual QString typeString() const = 0;
|
virtual QString typeString() const = 0;
|
||||||
|
|
||||||
virtual bool operator<(BaseVersion &a)
|
virtual bool operator<(BaseVersion& a) { return name() < a.name(); };
|
||||||
{
|
virtual bool operator>(BaseVersion& a) { return name() > a.name(); };
|
||||||
return name() < a.name();
|
|
||||||
};
|
|
||||||
virtual bool operator>(BaseVersion &a)
|
|
||||||
{
|
|
||||||
return name() > a.name();
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(BaseVersion::Ptr)
|
Q_DECLARE_METATYPE(BaseVersion::Ptr)
|
||||||
|
@ -136,6 +136,15 @@ set(NET_SOURCES
|
|||||||
net/Validator.h
|
net/Validator.h
|
||||||
net/Upload.cpp
|
net/Upload.cpp
|
||||||
net/Upload.h
|
net/Upload.h
|
||||||
|
net/HeaderProxy.h
|
||||||
|
net/RawHeaderProxy.h
|
||||||
|
net/ApiHeaderProxy.h
|
||||||
|
net/ApiDownload.h
|
||||||
|
net/ApiDownload.cpp
|
||||||
|
net/ApiUpload.cpp
|
||||||
|
net/ApiUpload.h
|
||||||
|
net/NetRequest.cpp
|
||||||
|
net/NetRequest.h
|
||||||
)
|
)
|
||||||
|
|
||||||
# Game launch logic
|
# Game launch logic
|
||||||
@ -262,8 +271,6 @@ set(MINECRAFT_SOURCES
|
|||||||
minecraft/launch/CreateGameFolders.h
|
minecraft/launch/CreateGameFolders.h
|
||||||
minecraft/launch/ModMinecraftJar.cpp
|
minecraft/launch/ModMinecraftJar.cpp
|
||||||
minecraft/launch/ModMinecraftJar.h
|
minecraft/launch/ModMinecraftJar.h
|
||||||
minecraft/launch/DirectJavaLaunch.cpp
|
|
||||||
minecraft/launch/DirectJavaLaunch.h
|
|
||||||
minecraft/launch/ExtractNatives.cpp
|
minecraft/launch/ExtractNatives.cpp
|
||||||
minecraft/launch/ExtractNatives.h
|
minecraft/launch/ExtractNatives.h
|
||||||
minecraft/launch/LauncherPartLaunch.cpp
|
minecraft/launch/LauncherPartLaunch.cpp
|
||||||
@ -487,6 +494,9 @@ set(API_SOURCES
|
|||||||
modplatform/helpers/HashUtils.cpp
|
modplatform/helpers/HashUtils.cpp
|
||||||
modplatform/helpers/OverrideUtils.h
|
modplatform/helpers/OverrideUtils.h
|
||||||
modplatform/helpers/OverrideUtils.cpp
|
modplatform/helpers/OverrideUtils.cpp
|
||||||
|
|
||||||
|
modplatform/helpers/ExportToModList.h
|
||||||
|
modplatform/helpers/ExportToModList.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(FTB_SOURCES
|
set(FTB_SOURCES
|
||||||
@ -498,6 +508,11 @@ set(FTB_SOURCES
|
|||||||
modplatform/legacy_ftb/PrivatePackManager.cpp
|
modplatform/legacy_ftb/PrivatePackManager.cpp
|
||||||
|
|
||||||
modplatform/legacy_ftb/PackHelpers.h
|
modplatform/legacy_ftb/PackHelpers.h
|
||||||
|
|
||||||
|
modplatform/import_ftb/PackInstallTask.h
|
||||||
|
modplatform/import_ftb/PackInstallTask.cpp
|
||||||
|
modplatform/import_ftb/PackHelpers.h
|
||||||
|
modplatform/import_ftb/PackHelpers.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(FLAME_SOURCES
|
set(FLAME_SOURCES
|
||||||
@ -560,6 +575,9 @@ set(ATLAUNCHER_SOURCES
|
|||||||
)
|
)
|
||||||
|
|
||||||
set(LINKEXE_SOURCES
|
set(LINKEXE_SOURCES
|
||||||
|
WindowsConsole.cpp
|
||||||
|
WindowsConsole.h
|
||||||
|
|
||||||
filelink/FileLink.h
|
filelink/FileLink.h
|
||||||
filelink/FileLink.cpp
|
filelink/FileLink.cpp
|
||||||
FileSystem.h
|
FileSystem.h
|
||||||
@ -665,7 +683,7 @@ set(LOGIC_SOURCES
|
|||||||
${ATLAUNCHER_SOURCES}
|
${ATLAUNCHER_SOURCES}
|
||||||
)
|
)
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE AND Launcher_ENABLE_UPDATER)
|
||||||
set (LOGIC_SOURCES ${LOGIC_SOURCES} ${MAC_UPDATE_SOURCES})
|
set (LOGIC_SOURCES ${LOGIC_SOURCES} ${MAC_UPDATE_SOURCES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@ -758,6 +776,8 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/themes/SystemTheme.h
|
ui/themes/SystemTheme.h
|
||||||
ui/themes/ThemeManager.cpp
|
ui/themes/ThemeManager.cpp
|
||||||
ui/themes/ThemeManager.h
|
ui/themes/ThemeManager.h
|
||||||
|
ui/themes/CatPack.cpp
|
||||||
|
ui/themes/CatPack.h
|
||||||
|
|
||||||
# Processes
|
# Processes
|
||||||
LaunchController.h
|
LaunchController.h
|
||||||
@ -867,6 +887,11 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/pages/modplatform/legacy_ftb/ListModel.h
|
ui/pages/modplatform/legacy_ftb/ListModel.h
|
||||||
ui/pages/modplatform/legacy_ftb/ListModel.cpp
|
ui/pages/modplatform/legacy_ftb/ListModel.cpp
|
||||||
|
|
||||||
|
ui/pages/modplatform/import_ftb/ImportFTBPage.cpp
|
||||||
|
ui/pages/modplatform/import_ftb/ImportFTBPage.h
|
||||||
|
ui/pages/modplatform/import_ftb/ListModel.h
|
||||||
|
ui/pages/modplatform/import_ftb/ListModel.cpp
|
||||||
|
|
||||||
ui/pages/modplatform/flame/FlameModel.cpp
|
ui/pages/modplatform/flame/FlameModel.cpp
|
||||||
ui/pages/modplatform/flame/FlameModel.h
|
ui/pages/modplatform/flame/FlameModel.h
|
||||||
ui/pages/modplatform/flame/FlamePage.cpp
|
ui/pages/modplatform/flame/FlamePage.cpp
|
||||||
@ -911,6 +936,8 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/dialogs/ExportInstanceDialog.h
|
ui/dialogs/ExportInstanceDialog.h
|
||||||
ui/dialogs/ExportPackDialog.cpp
|
ui/dialogs/ExportPackDialog.cpp
|
||||||
ui/dialogs/ExportPackDialog.h
|
ui/dialogs/ExportPackDialog.h
|
||||||
|
ui/dialogs/ExportToModListDialog.cpp
|
||||||
|
ui/dialogs/ExportToModListDialog.h
|
||||||
ui/dialogs/IconPickerDialog.cpp
|
ui/dialogs/IconPickerDialog.cpp
|
||||||
ui/dialogs/IconPickerDialog.h
|
ui/dialogs/IconPickerDialog.h
|
||||||
ui/dialogs/ImportResourceDialog.cpp
|
ui/dialogs/ImportResourceDialog.cpp
|
||||||
@ -1011,6 +1038,14 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/instanceview/VisualGroup.h
|
ui/instanceview/VisualGroup.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
set(LAUNCHER_SOURCES
|
||||||
|
WindowsConsole.cpp
|
||||||
|
WindowsConsole.h
|
||||||
|
${LAUNCHER_SOURCES}
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
qt_wrap_ui(LAUNCHER_UI
|
qt_wrap_ui(LAUNCHER_UI
|
||||||
ui/MainWindow.ui
|
ui/MainWindow.ui
|
||||||
ui/setupwizard/PasteWizardPage.ui
|
ui/setupwizard/PasteWizardPage.ui
|
||||||
@ -1039,6 +1074,7 @@ qt_wrap_ui(LAUNCHER_UI
|
|||||||
ui/pages/modplatform/ResourcePage.ui
|
ui/pages/modplatform/ResourcePage.ui
|
||||||
ui/pages/modplatform/flame/FlamePage.ui
|
ui/pages/modplatform/flame/FlamePage.ui
|
||||||
ui/pages/modplatform/legacy_ftb/Page.ui
|
ui/pages/modplatform/legacy_ftb/Page.ui
|
||||||
|
ui/pages/modplatform/import_ftb/ImportFTBPage.ui
|
||||||
ui/pages/modplatform/ImportPage.ui
|
ui/pages/modplatform/ImportPage.ui
|
||||||
ui/pages/modplatform/modrinth/ModrinthPage.ui
|
ui/pages/modplatform/modrinth/ModrinthPage.ui
|
||||||
ui/pages/modplatform/technic/TechnicPage.ui
|
ui/pages/modplatform/technic/TechnicPage.ui
|
||||||
@ -1058,6 +1094,7 @@ qt_wrap_ui(LAUNCHER_UI
|
|||||||
ui/dialogs/SkinUploadDialog.ui
|
ui/dialogs/SkinUploadDialog.ui
|
||||||
ui/dialogs/ExportInstanceDialog.ui
|
ui/dialogs/ExportInstanceDialog.ui
|
||||||
ui/dialogs/ExportPackDialog.ui
|
ui/dialogs/ExportPackDialog.ui
|
||||||
|
ui/dialogs/ExportToModListDialog.ui
|
||||||
ui/dialogs/IconPickerDialog.ui
|
ui/dialogs/IconPickerDialog.ui
|
||||||
ui/dialogs/ImportResourceDialog.ui
|
ui/dialogs/ImportResourceDialog.ui
|
||||||
ui/dialogs/MSALoginDialog.ui
|
ui/dialogs/MSALoginDialog.ui
|
||||||
@ -1094,6 +1131,7 @@ endif()
|
|||||||
|
|
||||||
# Add executable
|
# Add executable
|
||||||
add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES})
|
add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES})
|
||||||
|
target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION)
|
||||||
target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
target_link_libraries(Launcher_logic
|
target_link_libraries(Launcher_logic
|
||||||
systeminfo
|
systeminfo
|
||||||
@ -1133,18 +1171,24 @@ if(APPLE)
|
|||||||
set(CMAKE_MACOSX_RPATH 1)
|
set(CMAKE_MACOSX_RPATH 1)
|
||||||
set(CMAKE_INSTALL_RPATH "@loader_path/../Frameworks/")
|
set(CMAKE_INSTALL_RPATH "@loader_path/../Frameworks/")
|
||||||
|
|
||||||
|
if(Launcher_ENABLE_UPDATER)
|
||||||
file(DOWNLOAD ${MACOSX_SPARKLE_DOWNLOAD_URL} ${CMAKE_BINARY_DIR}/Sparkle.tar.xz EXPECTED_HASH SHA256=${MACOSX_SPARKLE_SHA256})
|
file(DOWNLOAD ${MACOSX_SPARKLE_DOWNLOAD_URL} ${CMAKE_BINARY_DIR}/Sparkle.tar.xz EXPECTED_HASH SHA256=${MACOSX_SPARKLE_SHA256})
|
||||||
file(ARCHIVE_EXTRACT INPUT ${CMAKE_BINARY_DIR}/Sparkle.tar.xz DESTINATION ${CMAKE_BINARY_DIR}/frameworks/Sparkle)
|
file(ARCHIVE_EXTRACT INPUT ${CMAKE_BINARY_DIR}/Sparkle.tar.xz DESTINATION ${CMAKE_BINARY_DIR}/frameworks/Sparkle)
|
||||||
|
|
||||||
find_library(SPARKLE_FRAMEWORK Sparkle "${CMAKE_BINARY_DIR}/frameworks/Sparkle")
|
find_library(SPARKLE_FRAMEWORK Sparkle "${CMAKE_BINARY_DIR}/frameworks/Sparkle")
|
||||||
|
add_compile_definitions(SPARKLE_ENABLED)
|
||||||
|
endif()
|
||||||
|
|
||||||
target_link_libraries(Launcher_logic
|
target_link_libraries(Launcher_logic
|
||||||
"-framework AppKit"
|
"-framework AppKit"
|
||||||
"-framework Carbon"
|
"-framework Carbon"
|
||||||
"-framework Foundation"
|
"-framework Foundation"
|
||||||
"-framework ApplicationServices"
|
"-framework ApplicationServices"
|
||||||
)
|
)
|
||||||
|
if(Launcher_ENABLE_UPDATER)
|
||||||
target_link_libraries(Launcher_logic ${SPARKLE_FRAMEWORK})
|
target_link_libraries(Launcher_logic ${SPARKLE_FRAMEWORK})
|
||||||
endif()
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
target_link_libraries(Launcher_logic)
|
target_link_libraries(Launcher_logic)
|
||||||
|
|
||||||
@ -1205,7 +1249,7 @@ if(WIN32)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (UNIX AND APPLE)
|
if (UNIX AND APPLE AND Launcher_ENABLE_UPDATER)
|
||||||
# Add Sparkle updater
|
# Add Sparkle updater
|
||||||
# It has to be copied here instead of just allowing fixup_bundle to install it, otherwise essential parts of
|
# It has to be copied here instead of just allowing fixup_bundle to install it, otherwise essential parts of
|
||||||
# the framework aren't installed
|
# the framework aren't installed
|
||||||
|
@ -118,7 +118,7 @@ bool openDirectory(const QString &path, bool ensureExists)
|
|||||||
return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath()));
|
return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath()));
|
||||||
};
|
};
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||||
if(!isFlatpak())
|
if(!isSandbox())
|
||||||
{
|
{
|
||||||
return IndirectOpen(f);
|
return IndirectOpen(f);
|
||||||
}
|
}
|
||||||
@ -139,7 +139,7 @@ bool openFile(const QString &path)
|
|||||||
return QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
return QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
||||||
};
|
};
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||||
if(!isFlatpak())
|
if(!isSandbox())
|
||||||
{
|
{
|
||||||
return IndirectOpen(f);
|
return IndirectOpen(f);
|
||||||
}
|
}
|
||||||
@ -157,7 +157,7 @@ bool openFile(const QString &application, const QString &path, const QString &wo
|
|||||||
qDebug() << "Opening file" << path << "using" << application;
|
qDebug() << "Opening file" << path << "using" << application;
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||||
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
|
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
|
||||||
if(!isFlatpak())
|
if(!isSandbox())
|
||||||
{
|
{
|
||||||
return IndirectOpen([&]()
|
return IndirectOpen([&]()
|
||||||
{
|
{
|
||||||
@ -177,7 +177,7 @@ bool run(const QString &application, const QStringList &args, const QString &wor
|
|||||||
{
|
{
|
||||||
qDebug() << "Running" << application << "with args" << args.join(' ');
|
qDebug() << "Running" << application << "with args" << args.join(' ');
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||||
if(!isFlatpak())
|
if(!isSandbox())
|
||||||
{
|
{
|
||||||
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
|
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
|
||||||
return IndirectOpen([&]()
|
return IndirectOpen([&]()
|
||||||
@ -202,7 +202,7 @@ bool openUrl(const QUrl &url)
|
|||||||
return QDesktopServices::openUrl(url);
|
return QDesktopServices::openUrl(url);
|
||||||
};
|
};
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||||
if(!isFlatpak())
|
if(!isSandbox())
|
||||||
{
|
{
|
||||||
return IndirectOpen(f);
|
return IndirectOpen(f);
|
||||||
}
|
}
|
||||||
@ -224,4 +224,18 @@ bool isFlatpak()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isSnap()
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_LINUX
|
||||||
|
return getenv("SNAP");
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isSandbox()
|
||||||
|
{
|
||||||
|
return isSnap() || isFlatpak();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -34,5 +34,18 @@ namespace DesktopServices
|
|||||||
*/
|
*/
|
||||||
bool openUrl(const QUrl &url);
|
bool openUrl(const QUrl &url);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the launcher is running in a Flatpak environment
|
||||||
|
*/
|
||||||
bool isFlatpak();
|
bool isFlatpak();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the launcher is running in a Snap environment
|
||||||
|
*/
|
||||||
|
bool isSnap();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the launcher is running in a sandboxed (Flatpak or Snap) environment
|
||||||
|
*/
|
||||||
|
bool isSandbox();
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,8 @@
|
|||||||
#include "settings/INISettingsObject.h"
|
#include "settings/INISettingsObject.h"
|
||||||
#include "tasks/Task.h"
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
|
#include "net/ApiDownload.h"
|
||||||
|
|
||||||
#include <QtConcurrentRun>
|
#include <QtConcurrentRun>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
@ -101,12 +103,12 @@ void InstanceImportTask::downloadFromUrl()
|
|||||||
auto entry = APPLICATION->metacache()->resolveEntry("general", path);
|
auto entry = APPLICATION->metacache()->resolveEntry("general", path);
|
||||||
entry->setStale(true);
|
entry->setStale(true);
|
||||||
m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network()));
|
m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network()));
|
||||||
m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry));
|
m_filesNetJob->addNetAction(Net::ApiDownload::makeCached(m_sourceUrl, entry));
|
||||||
m_archivePath = entry->getFullPath();
|
m_archivePath = entry->getFullPath();
|
||||||
|
|
||||||
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
|
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
|
||||||
connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
|
connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
|
||||||
connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propogateStepProgress);
|
connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propagateStepProgress);
|
||||||
connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed);
|
connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed);
|
||||||
connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted);
|
connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted);
|
||||||
m_filesNetJob->start();
|
m_filesNetJob->start();
|
||||||
@ -298,7 +300,7 @@ void InstanceImportTask::processFlame()
|
|||||||
});
|
});
|
||||||
connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed);
|
connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed);
|
||||||
connect(inst_creation_task.get(), &Task::progress, this, &InstanceImportTask::setProgress);
|
connect(inst_creation_task.get(), &Task::progress, this, &InstanceImportTask::setProgress);
|
||||||
connect(inst_creation_task.get(), &Task::stepProgress, this, &InstanceImportTask::propogateStepProgress);
|
connect(inst_creation_task.get(), &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress);
|
||||||
connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus);
|
connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus);
|
||||||
connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails);
|
connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails);
|
||||||
|
|
||||||
@ -390,7 +392,7 @@ void InstanceImportTask::processModrinth()
|
|||||||
});
|
});
|
||||||
connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed);
|
connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed);
|
||||||
connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress);
|
connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress);
|
||||||
connect(inst_creation_task, &Task::stepProgress, this, &InstanceImportTask::propogateStepProgress);
|
connect(inst_creation_task, &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress);
|
||||||
connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus);
|
connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus);
|
||||||
connect(inst_creation_task, &Task::details, this, &InstanceImportTask::setDetails);
|
connect(inst_creation_task, &Task::details, this, &InstanceImportTask::setDetails);
|
||||||
connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater);
|
connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater);
|
||||||
|
@ -799,7 +799,7 @@ class InstanceStaging : public Task {
|
|||||||
connect(child, &Task::status, this, &InstanceStaging::setStatus);
|
connect(child, &Task::status, this, &InstanceStaging::setStatus);
|
||||||
connect(child, &Task::details, this, &InstanceStaging::setDetails);
|
connect(child, &Task::details, this, &InstanceStaging::setDetails);
|
||||||
connect(child, &Task::progress, this, &InstanceStaging::setProgress);
|
connect(child, &Task::progress, this, &InstanceStaging::setProgress);
|
||||||
connect(child, &Task::stepProgress, this, &InstanceStaging::propogateStepProgress);
|
connect(child, &Task::stepProgress, this, &InstanceStaging::propagateStepProgress);
|
||||||
connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded);
|
connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -390,7 +390,10 @@ void LaunchController::launchInstance()
|
|||||||
m_launcher->prependStep(makeShared<TextPrint>(m_launcher.get(), "Launched instance in " + online_mode + " mode\n", MessageLevel::Launcher));
|
m_launcher->prependStep(makeShared<TextPrint>(m_launcher.get(), "Launched instance in " + online_mode + " mode\n", MessageLevel::Launcher));
|
||||||
|
|
||||||
// Prepend Version
|
// Prepend Version
|
||||||
m_launcher->prependStep(makeShared<TextPrint>(m_launcher.get(), BuildConfig.LAUNCHER_DISPLAYNAME + " version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::Launcher));
|
{
|
||||||
|
auto versionString = QString("%1 version: %2 (%3)").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString(), BuildConfig.BUILD_PLATFORM);
|
||||||
|
m_launcher->prependStep(makeShared<TextPrint>(m_launcher.get(), versionString + "\n\n", MessageLevel::Launcher));
|
||||||
|
}
|
||||||
m_launcher->start();
|
m_launcher->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -33,56 +34,50 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "MMCZip.h"
|
||||||
#include <quazip/quazip.h>
|
#include <quazip/quazip.h>
|
||||||
#include <quazip/quazipdir.h>
|
#include <quazip/quazipdir.h>
|
||||||
#include <quazip/quazipfile.h>
|
#include <quazip/quazipfile.h>
|
||||||
#include "MMCZip.h"
|
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QtConcurrentRun>
|
||||||
|
|
||||||
|
namespace MMCZip {
|
||||||
// ours
|
// ours
|
||||||
bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, const FilterFunction filter)
|
bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& contained, const FilterFunction filter)
|
||||||
{
|
{
|
||||||
QuaZip modZip(from.filePath());
|
QuaZip modZip(from.filePath());
|
||||||
modZip.open(QuaZip::mdUnzip);
|
modZip.open(QuaZip::mdUnzip);
|
||||||
|
|
||||||
QuaZipFile fileInsideMod(&modZip);
|
QuaZipFile fileInsideMod(&modZip);
|
||||||
QuaZipFile zipOutFile(into);
|
QuaZipFile zipOutFile(into);
|
||||||
for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile())
|
for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile()) {
|
||||||
{
|
|
||||||
QString filename = modZip.getCurrentFileName();
|
QString filename = modZip.getCurrentFileName();
|
||||||
if (filter && !filter(filename))
|
if (filter && !filter(filename)) {
|
||||||
{
|
qDebug() << "Skipping file " << filename << " from " << from.fileName() << " - filtered";
|
||||||
qDebug() << "Skipping file " << filename << " from "
|
|
||||||
<< from.fileName() << " - filtered";
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (contained.contains(filename))
|
if (contained.contains(filename)) {
|
||||||
{
|
qDebug() << "Skipping already contained file " << filename << " from " << from.fileName();
|
||||||
qDebug() << "Skipping already contained file " << filename << " from "
|
|
||||||
<< from.fileName();
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
contained.insert(filename);
|
contained.insert(filename);
|
||||||
|
|
||||||
if (!fileInsideMod.open(QIODevice::ReadOnly))
|
if (!fileInsideMod.open(QIODevice::ReadOnly)) {
|
||||||
{
|
|
||||||
qCritical() << "Failed to open " << filename << " from " << from.fileName();
|
qCritical() << "Failed to open " << filename << " from " << from.fileName();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QuaZipNewInfo info_out(fileInsideMod.getActualFileName());
|
QuaZipNewInfo info_out(fileInsideMod.getActualFileName());
|
||||||
|
|
||||||
if (!zipOutFile.open(QIODevice::WriteOnly, info_out))
|
if (!zipOutFile.open(QIODevice::WriteOnly, info_out)) {
|
||||||
{
|
|
||||||
qCritical() << "Failed to open " << filename << " in the jar";
|
qCritical() << "Failed to open " << filename << " in the jar";
|
||||||
fileInsideMod.close();
|
fileInsideMod.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!JlCompress::copyData(fileInsideMod, zipOutFile))
|
if (!JlCompress::copyData(fileInsideMod, zipOutFile)) {
|
||||||
{
|
|
||||||
zipOutFile.close();
|
zipOutFile.close();
|
||||||
fileInsideMod.close();
|
fileInsideMod.close();
|
||||||
qCritical() << "Failed to copy data of " << filename << " into the jar";
|
qCritical() << "Failed to copy data of " << filename << " into the jar";
|
||||||
@ -94,10 +89,11 @@ bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &containe
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MMCZip::compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files, bool followSymlinks)
|
bool compressDirFiles(QuaZip* zip, QString dir, QFileInfoList files, bool followSymlinks)
|
||||||
{
|
{
|
||||||
QDir directory(dir);
|
QDir directory(dir);
|
||||||
if (!directory.exists()) return false;
|
if (!directory.exists())
|
||||||
|
return false;
|
||||||
|
|
||||||
for (auto e : files) {
|
for (auto e : files) {
|
||||||
auto filePath = directory.relativeFilePath(e.absoluteFilePath());
|
auto filePath = directory.relativeFilePath(e.absoluteFilePath());
|
||||||
@ -109,13 +105,14 @@ bool MMCZip::compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files, boo
|
|||||||
srcPath = e.canonicalFilePath();
|
srcPath = e.canonicalFilePath();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if( !JlCompress::compressFile(zip, srcPath, filePath)) return false;
|
if (!JlCompress::compressFile(zip, srcPath, filePath))
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MMCZip::compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks)
|
bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks)
|
||||||
{
|
{
|
||||||
QuaZip zip(fileCompressed);
|
QuaZip zip(fileCompressed);
|
||||||
QDir().mkpath(QFileInfo(fileCompressed).absolutePath());
|
QDir().mkpath(QFileInfo(fileCompressed).absolutePath());
|
||||||
@ -136,11 +133,10 @@ bool MMCZip::compressDirFiles(QString fileCompressed, QString dir, QFileInfoList
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ours
|
// ours
|
||||||
bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods)
|
bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods)
|
||||||
{
|
{
|
||||||
QuaZip zipOut(targetJarPath);
|
QuaZip zipOut(targetJarPath);
|
||||||
if (!zipOut.open(QuaZip::mdCreate))
|
if (!zipOut.open(QuaZip::mdCreate)) {
|
||||||
{
|
|
||||||
QFile::remove(targetJarPath);
|
QFile::remove(targetJarPath);
|
||||||
qCritical() << "Failed to open the minecraft.jar for modding";
|
qCritical() << "Failed to open the minecraft.jar for modding";
|
||||||
return false;
|
return false;
|
||||||
@ -151,37 +147,29 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
|
|||||||
|
|
||||||
// Modify the jar
|
// Modify the jar
|
||||||
// This needs to be done in reverse-order to ensure we respect the loading order of components
|
// This needs to be done in reverse-order to ensure we respect the loading order of components
|
||||||
for (auto i = mods.crbegin(); i != mods.crend(); i++)
|
for (auto i = mods.crbegin(); i != mods.crend(); i++) {
|
||||||
{
|
|
||||||
const auto* mod = *i;
|
const auto* mod = *i;
|
||||||
// do not merge disabled mods.
|
// do not merge disabled mods.
|
||||||
if (!mod->enabled())
|
if (!mod->enabled())
|
||||||
continue;
|
continue;
|
||||||
if (mod->type() == ResourceType::ZIPFILE)
|
if (mod->type() == ResourceType::ZIPFILE) {
|
||||||
{
|
if (!mergeZipFiles(&zipOut, mod->fileinfo(), addedFiles)) {
|
||||||
if (!mergeZipFiles(&zipOut, mod->fileinfo(), addedFiles))
|
|
||||||
{
|
|
||||||
zipOut.close();
|
zipOut.close();
|
||||||
QFile::remove(targetJarPath);
|
QFile::remove(targetJarPath);
|
||||||
qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar.";
|
qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
} else if (mod->type() == ResourceType::SINGLEFILE) {
|
||||||
else if (mod->type() == ResourceType::SINGLEFILE)
|
|
||||||
{
|
|
||||||
// FIXME: buggy - does not work with addedFiles
|
// FIXME: buggy - does not work with addedFiles
|
||||||
auto filename = mod->fileinfo();
|
auto filename = mod->fileinfo();
|
||||||
if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName()))
|
if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName())) {
|
||||||
{
|
|
||||||
zipOut.close();
|
zipOut.close();
|
||||||
QFile::remove(targetJarPath);
|
QFile::remove(targetJarPath);
|
||||||
qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar.";
|
qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
addedFiles.insert(filename.fileName());
|
addedFiles.insert(filename.fileName());
|
||||||
}
|
} else if (mod->type() == ResourceType::FOLDER) {
|
||||||
else if (mod->type() == ResourceType::FOLDER)
|
|
||||||
{
|
|
||||||
// untested, but seems to be unused / not possible to reach
|
// untested, but seems to be unused / not possible to reach
|
||||||
// FIXME: buggy - does not work with addedFiles
|
// FIXME: buggy - does not work with addedFiles
|
||||||
auto filename = mod->fileinfo();
|
auto filename = mod->fileinfo();
|
||||||
@ -190,25 +178,21 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
|
|||||||
dir.cdUp();
|
dir.cdUp();
|
||||||
QString parent_dir = dir.absolutePath();
|
QString parent_dir = dir.absolutePath();
|
||||||
auto files = QFileInfoList();
|
auto files = QFileInfoList();
|
||||||
MMCZip::collectFileListRecursively(what_to_zip, nullptr, &files, nullptr);
|
collectFileListRecursively(what_to_zip, nullptr, &files, nullptr);
|
||||||
|
|
||||||
for (auto e : files) {
|
for (auto e : files) {
|
||||||
if (addedFiles.contains(e.filePath()))
|
if (addedFiles.contains(e.filePath()))
|
||||||
files.removeAll(e);
|
files.removeAll(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!MMCZip::compressDirFiles(&zipOut, parent_dir, files))
|
if (!compressDirFiles(&zipOut, parent_dir, files)) {
|
||||||
{
|
|
||||||
zipOut.close();
|
zipOut.close();
|
||||||
QFile::remove(targetJarPath);
|
QFile::remove(targetJarPath);
|
||||||
qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar.";
|
qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
qDebug() << "Adding folder " << filename.fileName() << " from "
|
qDebug() << "Adding folder " << filename.fileName() << " from " << filename.absoluteFilePath();
|
||||||
<< filename.absoluteFilePath();
|
} else {
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Make sure we do not continue launching when something is missing or undefined...
|
// Make sure we do not continue launching when something is missing or undefined...
|
||||||
zipOut.close();
|
zipOut.close();
|
||||||
QFile::remove(targetJarPath);
|
QFile::remove(targetJarPath);
|
||||||
@ -217,8 +201,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, [](const QString key){return !key.contains("META-INF");}))
|
if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, [](const QString key) { return !key.contains("META-INF"); })) {
|
||||||
{
|
|
||||||
zipOut.close();
|
zipOut.close();
|
||||||
QFile::remove(targetJarPath);
|
QFile::remove(targetJarPath);
|
||||||
qCritical() << "Failed to insert minecraft.jar contents.";
|
qCritical() << "Failed to insert minecraft.jar contents.";
|
||||||
@ -227,8 +210,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
|
|||||||
|
|
||||||
// Recompress the jar
|
// Recompress the jar
|
||||||
zipOut.close();
|
zipOut.close();
|
||||||
if (zipOut.getZipError() != 0)
|
if (zipOut.getZipError() != 0) {
|
||||||
{
|
|
||||||
QFile::remove(targetJarPath);
|
QFile::remove(targetJarPath);
|
||||||
qCritical() << "Failed to finalize minecraft.jar!";
|
qCritical() << "Failed to finalize minecraft.jar!";
|
||||||
return false;
|
return false;
|
||||||
@ -237,7 +219,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ours
|
// ours
|
||||||
QString MMCZip::findFolderOfFileInZip(QuaZip* zip, const QString& what, const QStringList& ignore_paths, const QString& root)
|
QString 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)) {
|
||||||
@ -261,27 +243,23 @@ QString MMCZip::findFolderOfFileInZip(QuaZip* zip, const QString& what, const QS
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ours
|
// ours
|
||||||
bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root)
|
bool findFilesInZip(QuaZip* zip, const QString& what, QStringList& result, 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)
|
|
||||||
{
|
|
||||||
result.append(root);
|
result.append(root);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(auto fileName: rootDir.entryList(QDir::Dirs))
|
for (auto fileName : rootDir.entryList(QDir::Dirs)) {
|
||||||
{
|
|
||||||
findFilesInZip(zip, what, result, root + fileName);
|
findFilesInZip(zip, what, result, root + fileName);
|
||||||
}
|
}
|
||||||
return !result.isEmpty();
|
return !result.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ours
|
// ours
|
||||||
std::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target)
|
std::optional<QStringList> extractSubDir(QuaZip* zip, const QString& subdir, const QString& target)
|
||||||
{
|
{
|
||||||
auto target_top_dir = QUrl::fromLocalFile(target);
|
auto target_top_dir = QUrl::fromLocalFile(target);
|
||||||
|
|
||||||
@ -292,13 +270,10 @@ std::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & su
|
|||||||
if (numEntries < 0) {
|
if (numEntries < 0) {
|
||||||
qWarning() << "Failed to enumerate files in archive";
|
qWarning() << "Failed to enumerate files in archive";
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
} else if (numEntries == 0) {
|
||||||
else if(numEntries == 0) {
|
|
||||||
qDebug() << "Extracting empty archives seems odd...";
|
qDebug() << "Extracting empty archives seems odd...";
|
||||||
return extracted;
|
return extracted;
|
||||||
}
|
} else if (!zip->goToFirstFile()) {
|
||||||
else if (!zip->goToFirstFile())
|
|
||||||
{
|
|
||||||
qWarning() << "Failed to seek to first file in zip";
|
qWarning() << "Failed to seek to first file in zip";
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
@ -334,7 +309,8 @@ std::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & su
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!target_top_dir.isParentOf(QUrl::fromLocalFile(target_file_path))) {
|
if (!target_top_dir.isParentOf(QUrl::fromLocalFile(target_file_path))) {
|
||||||
qWarning() << "Extracting" << relative_file_name << "was cancelled, because it was effectively outside of the target path" << target;
|
qWarning() << "Extracting" << relative_file_name << "was cancelled, because it was effectively outside of the target path"
|
||||||
|
<< target;
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -345,7 +321,8 @@ std::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & su
|
|||||||
}
|
}
|
||||||
|
|
||||||
extracted.append(target_file_path);
|
extracted.append(target_file_path);
|
||||||
QFile::setPermissions(target_file_path, QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser);
|
QFile::setPermissions(target_file_path,
|
||||||
|
QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser);
|
||||||
|
|
||||||
qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path;
|
qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path;
|
||||||
} while (zip->goToNextFile());
|
} while (zip->goToNextFile());
|
||||||
@ -354,51 +331,50 @@ std::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & su
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ours
|
// ours
|
||||||
bool MMCZip::extractRelFile(QuaZip *zip, const QString &file, const QString &target)
|
bool extractRelFile(QuaZip* zip, const QString& file, const QString& target)
|
||||||
{
|
{
|
||||||
return JlCompress::extractFile(zip, file, target);
|
return JlCompress::extractFile(zip, file, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ours
|
// ours
|
||||||
std::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString dir)
|
std::optional<QStringList> extractDir(QString fileCompressed, QString dir)
|
||||||
{
|
{
|
||||||
QuaZip zip(fileCompressed);
|
QuaZip zip(fileCompressed);
|
||||||
if (!zip.open(QuaZip::mdUnzip))
|
if (!zip.open(QuaZip::mdUnzip)) {
|
||||||
{
|
|
||||||
// check if this is a minimum size empty zip file...
|
// check if this is a minimum size empty zip file...
|
||||||
QFileInfo fileInfo(fileCompressed);
|
QFileInfo fileInfo(fileCompressed);
|
||||||
if (fileInfo.size() == 22) {
|
if (fileInfo.size() == 22) {
|
||||||
return QStringList();
|
return QStringList();
|
||||||
}
|
}
|
||||||
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();;
|
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();
|
||||||
|
;
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
return MMCZip::extractSubDir(&zip, "", dir);
|
return extractSubDir(&zip, "", dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ours
|
// ours
|
||||||
std::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString subdir, QString dir)
|
std::optional<QStringList> extractDir(QString fileCompressed, QString subdir, QString dir)
|
||||||
{
|
{
|
||||||
QuaZip zip(fileCompressed);
|
QuaZip zip(fileCompressed);
|
||||||
if (!zip.open(QuaZip::mdUnzip))
|
if (!zip.open(QuaZip::mdUnzip)) {
|
||||||
{
|
|
||||||
// check if this is a minimum size empty zip file...
|
// check if this is a minimum size empty zip file...
|
||||||
QFileInfo fileInfo(fileCompressed);
|
QFileInfo fileInfo(fileCompressed);
|
||||||
if (fileInfo.size() == 22) {
|
if (fileInfo.size() == 22) {
|
||||||
return QStringList();
|
return QStringList();
|
||||||
}
|
}
|
||||||
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();;
|
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();
|
||||||
|
;
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
return MMCZip::extractSubDir(&zip, subdir, dir);
|
return extractSubDir(&zip, subdir, dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ours
|
// ours
|
||||||
bool MMCZip::extractFile(QString fileCompressed, QString file, QString target)
|
bool extractFile(QString fileCompressed, QString file, QString target)
|
||||||
{
|
{
|
||||||
QuaZip zip(fileCompressed);
|
QuaZip zip(fileCompressed);
|
||||||
if (!zip.open(QuaZip::mdUnzip))
|
if (!zip.open(QuaZip::mdUnzip)) {
|
||||||
{
|
|
||||||
// check if this is a minimum size empty zip file...
|
// check if this is a minimum size empty zip file...
|
||||||
QFileInfo fileInfo(fileCompressed);
|
QFileInfo fileInfo(fileCompressed);
|
||||||
if (fileInfo.size() == 22) {
|
if (fileInfo.size() == 22) {
|
||||||
@ -407,13 +383,14 @@ bool MMCZip::extractFile(QString fileCompressed, QString file, QString target)
|
|||||||
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();
|
qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return MMCZip::extractRelFile(&zip, file, target);
|
return extractRelFile(&zip, file, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MMCZip::collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList *files,
|
bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFunction excludeFilter)
|
||||||
MMCZip::FilterFunction excludeFilter) {
|
{
|
||||||
QDir rootDirectory(rootDir);
|
QDir rootDirectory(rootDir);
|
||||||
if (!rootDirectory.exists()) return false;
|
if (!rootDirectory.exists())
|
||||||
|
return false;
|
||||||
|
|
||||||
QDir directory;
|
QDir directory;
|
||||||
if (subDir == nullptr)
|
if (subDir == nullptr)
|
||||||
@ -421,7 +398,8 @@ bool MMCZip::collectFileListRecursively(const QString& rootDir, const QString& s
|
|||||||
else
|
else
|
||||||
directory = QDir(subDir);
|
directory = QDir(subDir);
|
||||||
|
|
||||||
if (!directory.exists()) return false; // shouldn't ever happen
|
if (!directory.exists())
|
||||||
|
return false; // shouldn't ever happen
|
||||||
|
|
||||||
// recurse directories
|
// recurse directories
|
||||||
QFileInfoList entries = directory.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden);
|
QFileInfoList entries = directory.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden);
|
||||||
@ -439,7 +417,88 @@ bool MMCZip::collectFileListRecursively(const QString& rootDir, const QString& s
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
files->append(e); // we want the original paths for MMCZip::compressDirFiles
|
files->append(e); // we want the original paths for compressDirFiles
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ExportToZipTask::executeTask()
|
||||||
|
{
|
||||||
|
setStatus("Adding files...");
|
||||||
|
setProgress(0, m_files.length());
|
||||||
|
m_build_zip_future = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return exportZip(); });
|
||||||
|
connect(&m_build_zip_watcher, &QFutureWatcher<ZipResult>::finished, this, &ExportToZipTask::finish);
|
||||||
|
m_build_zip_watcher.setFuture(m_build_zip_future);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ExportToZipTask::exportZip() -> ZipResult
|
||||||
|
{
|
||||||
|
if (!m_dir.exists()) {
|
||||||
|
return ZipResult(tr("Folder doesn't exist"));
|
||||||
|
}
|
||||||
|
if (!m_output.isOpen() && !m_output.open(QuaZip::mdCreate)) {
|
||||||
|
return ZipResult(tr("Could not create file"));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto fileName : m_extra_files.keys()) {
|
||||||
|
if (m_build_zip_future.isCanceled())
|
||||||
|
return ZipResult();
|
||||||
|
QuaZipFile indexFile(&m_output);
|
||||||
|
if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo(fileName))) {
|
||||||
|
return ZipResult(tr("Could not create:") + fileName);
|
||||||
|
}
|
||||||
|
indexFile.write(m_extra_files[fileName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const QFileInfo& file : m_files) {
|
||||||
|
if (m_build_zip_future.isCanceled())
|
||||||
|
return ZipResult();
|
||||||
|
|
||||||
|
auto absolute = file.absoluteFilePath();
|
||||||
|
auto relative = m_dir.relativeFilePath(absolute);
|
||||||
|
setStatus("Compresing: " + relative);
|
||||||
|
setProgress(m_progress + 1, m_progressTotal);
|
||||||
|
if (m_follow_symlinks) {
|
||||||
|
if (file.isSymLink())
|
||||||
|
absolute = file.symLinkTarget();
|
||||||
|
else
|
||||||
|
absolute = file.canonicalFilePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_exclude_files.contains(relative) && !JlCompress::compressFile(&m_output, absolute, m_destination_prefix + relative)) {
|
||||||
|
return ZipResult(tr("Could not read and compress %1").arg(relative));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_output.close();
|
||||||
|
if (m_output.getZipError() != 0) {
|
||||||
|
return ZipResult(tr("A zip error occurred"));
|
||||||
|
}
|
||||||
|
return ZipResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExportToZipTask::finish()
|
||||||
|
{
|
||||||
|
if (m_build_zip_future.isCanceled()) {
|
||||||
|
QFile::remove(m_output_path);
|
||||||
|
emitAborted();
|
||||||
|
} else if (auto result = m_build_zip_future.result(); result.has_value()) {
|
||||||
|
QFile::remove(m_output_path);
|
||||||
|
emitFailed(result.value());
|
||||||
|
} else {
|
||||||
|
emitSucceeded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExportToZipTask::abort()
|
||||||
|
{
|
||||||
|
if (m_build_zip_future.isRunning()) {
|
||||||
|
m_build_zip_future.cancel();
|
||||||
|
// NOTE: Here we don't do `emitAborted()` because it will be done when `m_build_zip_future` actually cancels, which may not occur
|
||||||
|
// immediately.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace MMCZip
|
@ -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) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -35,24 +36,28 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QString>
|
#include <quazip.h>
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QSet>
|
|
||||||
#include "minecraft/mod/Mod.h"
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
#include <quazip/JlCompress.h>
|
#include <quazip/JlCompress.h>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QFuture>
|
||||||
|
#include <QFutureWatcher>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QSet>
|
||||||
|
#include <QString>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include "minecraft/mod/Mod.h"
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
namespace MMCZip
|
namespace MMCZip {
|
||||||
{
|
|
||||||
using FilterFunction = std::function<bool(const QString&)>;
|
using FilterFunction = std::function<bool(const QString&)>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merge two zip files, using a filter function
|
* Merge two zip files, using a filter function
|
||||||
*/
|
*/
|
||||||
bool mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained,
|
bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& contained, const FilterFunction filter = nullptr);
|
||||||
const FilterFunction filter = nullptr);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compress directory, by providing a list of files to compress
|
* Compress directory, by providing a list of files to compress
|
||||||
@ -141,4 +146,47 @@ namespace MMCZip
|
|||||||
* \return true for success or false for failure
|
* \return true for success or false for failure
|
||||||
*/
|
*/
|
||||||
bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFunction excludeFilter);
|
bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFunction excludeFilter);
|
||||||
}
|
|
||||||
|
class ExportToZipTask : public Task {
|
||||||
|
public:
|
||||||
|
ExportToZipTask(QString outputPath, QDir dir, QFileInfoList files, QString destinationPrefix = "", bool followSymlinks = false)
|
||||||
|
: m_output_path(outputPath)
|
||||||
|
, m_output(outputPath)
|
||||||
|
, m_dir(dir)
|
||||||
|
, m_files(files)
|
||||||
|
, m_destination_prefix(destinationPrefix)
|
||||||
|
, m_follow_symlinks(followSymlinks)
|
||||||
|
{
|
||||||
|
setAbortable(true);
|
||||||
|
};
|
||||||
|
ExportToZipTask(QString outputPath, QString dir, QFileInfoList files, QString destinationPrefix = "", bool followSymlinks = false)
|
||||||
|
: ExportToZipTask(outputPath, QDir(dir), files, destinationPrefix, followSymlinks){};
|
||||||
|
|
||||||
|
virtual ~ExportToZipTask() = default;
|
||||||
|
|
||||||
|
void setExcludeFiles(QStringList excludeFiles) { m_exclude_files = excludeFiles; }
|
||||||
|
void addExtraFile(QString fileName, QByteArray data) { m_extra_files.insert(fileName, data); }
|
||||||
|
|
||||||
|
typedef std::optional<QString> ZipResult;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void executeTask() override;
|
||||||
|
bool abort() override;
|
||||||
|
|
||||||
|
ZipResult exportZip();
|
||||||
|
void finish();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_output_path;
|
||||||
|
QuaZip m_output;
|
||||||
|
QDir m_dir;
|
||||||
|
QFileInfoList m_files;
|
||||||
|
QString m_destination_prefix;
|
||||||
|
bool m_follow_symlinks;
|
||||||
|
QStringList m_exclude_files;
|
||||||
|
QHash<QString, QByteArray> m_extra_files;
|
||||||
|
|
||||||
|
QFuture<ZipResult> m_build_zip_future;
|
||||||
|
QFutureWatcher<ZipResult> m_build_zip_watcher;
|
||||||
|
};
|
||||||
|
} // namespace MMCZip
|
||||||
|
@ -24,6 +24,8 @@
|
|||||||
#include "minecraft/mod/ModFolderModel.h"
|
#include "minecraft/mod/ModFolderModel.h"
|
||||||
#include "minecraft/mod/ResourceFolderModel.h"
|
#include "minecraft/mod/ResourceFolderModel.h"
|
||||||
|
|
||||||
|
#include "net/ApiDownload.h"
|
||||||
|
|
||||||
ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
|
ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
|
||||||
ModPlatform::IndexedVersion version,
|
ModPlatform::IndexedVersion version,
|
||||||
const std::shared_ptr<ResourceFolderModel> packs,
|
const std::shared_ptr<ResourceFolderModel> packs,
|
||||||
@ -51,10 +53,10 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_filesNetJob->addNetAction(Net::Download::makeFile(m_pack_version.downloadUrl, dir.absoluteFilePath(getFilename())));
|
m_filesNetJob->addNetAction(Net::ApiDownload::makeFile(m_pack_version.downloadUrl, dir.absoluteFilePath(getFilename())));
|
||||||
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ResourceDownloadTask::downloadSucceeded);
|
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ResourceDownloadTask::downloadSucceeded);
|
||||||
connect(m_filesNetJob.get(), &NetJob::progress, this, &ResourceDownloadTask::downloadProgressChanged);
|
connect(m_filesNetJob.get(), &NetJob::progress, this, &ResourceDownloadTask::downloadProgressChanged);
|
||||||
connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &ResourceDownloadTask::propogateStepProgress);
|
connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &ResourceDownloadTask::propagateStepProgress);
|
||||||
connect(m_filesNetJob.get(), &NetJob::failed, this, &ResourceDownloadTask::downloadFailed);
|
connect(m_filesNetJob.get(), &NetJob::failed, this, &ResourceDownloadTask::downloadFailed);
|
||||||
|
|
||||||
addTask(m_filesNetJob);
|
addTask(m_filesNetJob);
|
||||||
|
132
launcher/WindowsConsole.cpp
Normal file
132
launcher/WindowsConsole.cpp
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.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/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <io.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
void RedirectHandle(DWORD handle, FILE* stream, const char* mode ) {
|
||||||
|
|
||||||
|
HANDLE stdHandle = GetStdHandle(handle);
|
||||||
|
if (stdHandle != INVALID_HANDLE_VALUE) {
|
||||||
|
int fileDescriptor = _open_osfhandle((intptr_t)stdHandle, _O_TEXT);
|
||||||
|
if (fileDescriptor != -1) {
|
||||||
|
FILE* file = _fdopen(fileDescriptor, mode);
|
||||||
|
if (file != NULL) {
|
||||||
|
int dup2Result = _dup2(_fileno(file), _fileno(stream));
|
||||||
|
if (dup2Result == 0) {
|
||||||
|
setvbuf(stream, NULL, _IONBF, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// taken from https://stackoverflow.com/a/25927081
|
||||||
|
// getting a proper output to console with redirection support on windows is apparently hell
|
||||||
|
void BindCrtHandlesToStdHandles(bool bindStdIn, bool bindStdOut, bool bindStdErr)
|
||||||
|
{
|
||||||
|
// Re-initialize the C runtime "FILE" handles with clean handles bound to "nul". We do this because it has been
|
||||||
|
// observed that the file number of our standard handle file objects can be assigned internally to a value of -2
|
||||||
|
// when not bound to a valid target, which represents some kind of unknown internal invalid state. In this state our
|
||||||
|
// call to "_dup2" fails, as it specifically tests to ensure that the target file number isn't equal to this value
|
||||||
|
// before allowing the operation to continue. We can resolve this issue by first "re-opening" the target files to
|
||||||
|
// use the "nul" device, which will place them into a valid state, after which we can redirect them to our target
|
||||||
|
// using the "_dup2" function.
|
||||||
|
if (bindStdIn) {
|
||||||
|
FILE* dummyFile;
|
||||||
|
freopen_s(&dummyFile, "nul", "r", stdin);
|
||||||
|
}
|
||||||
|
if (bindStdOut) {
|
||||||
|
FILE* dummyFile;
|
||||||
|
freopen_s(&dummyFile, "nul", "w", stdout);
|
||||||
|
}
|
||||||
|
if (bindStdErr) {
|
||||||
|
FILE* dummyFile;
|
||||||
|
freopen_s(&dummyFile, "nul", "w", stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect unbuffered stdin from the current standard input handle
|
||||||
|
if (bindStdIn) {
|
||||||
|
RedirectHandle(STD_INPUT_HANDLE, stdin, "r");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect unbuffered stdout to the current standard output handle
|
||||||
|
if (bindStdOut) {
|
||||||
|
RedirectHandle(STD_OUTPUT_HANDLE, stdout, "w");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect unbuffered stderr to the current standard error handle
|
||||||
|
if (bindStdErr) {
|
||||||
|
RedirectHandle(STD_ERROR_HANDLE, stderr, "w");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the error state for each of the C++ standard stream objects. We need to do this, as attempts to access the
|
||||||
|
// standard streams before they refer to a valid target will cause the iostream objects to enter an error state. In
|
||||||
|
// versions of Visual Studio after 2005, this seems to always occur during startup regardless of whether anything
|
||||||
|
// has been read from or written to the targets or not.
|
||||||
|
if (bindStdIn) {
|
||||||
|
std::wcin.clear();
|
||||||
|
std::cin.clear();
|
||||||
|
}
|
||||||
|
if (bindStdOut) {
|
||||||
|
std::wcout.clear();
|
||||||
|
std::cout.clear();
|
||||||
|
}
|
||||||
|
if (bindStdErr) {
|
||||||
|
std::wcerr.clear();
|
||||||
|
std::cerr.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool AttachWindowsConsole() {
|
||||||
|
auto stdinType = GetFileType(GetStdHandle(STD_INPUT_HANDLE));
|
||||||
|
auto stdoutType = GetFileType(GetStdHandle(STD_OUTPUT_HANDLE));
|
||||||
|
auto stderrType = GetFileType(GetStdHandle(STD_ERROR_HANDLE));
|
||||||
|
|
||||||
|
bool bindStdIn = false;
|
||||||
|
bool bindStdOut = false;
|
||||||
|
bool bindStdErr = false;
|
||||||
|
|
||||||
|
if (stdinType == FILE_TYPE_CHAR || stdinType == FILE_TYPE_UNKNOWN) {
|
||||||
|
bindStdIn = true;
|
||||||
|
}
|
||||||
|
if (stdoutType == FILE_TYPE_CHAR || stdoutType == FILE_TYPE_UNKNOWN) {
|
||||||
|
bindStdOut = true;
|
||||||
|
}
|
||||||
|
if (stderrType == FILE_TYPE_CHAR || stderrType == FILE_TYPE_UNKNOWN) {
|
||||||
|
bindStdErr = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
|
||||||
|
BindCrtHandlesToStdHandles(bindStdIn, bindStdOut, bindStdErr);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
25
launcher/WindowsConsole.h
Normal file
25
launcher/WindowsConsole.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.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
|
||||||
|
|
||||||
|
void BindCrtHandlesToStdHandles(bool bindStdIn, bool bindStdOut, bool bindStdErr);
|
||||||
|
bool AttachWindowsConsole();
|
@ -37,11 +37,7 @@
|
|||||||
#include <sys.h>
|
#include <sys.h>
|
||||||
|
|
||||||
#if defined Q_OS_WIN32
|
#if defined Q_OS_WIN32
|
||||||
#ifndef WIN32_LEAN_AND_MEAN
|
#include "WindowsConsole.h"
|
||||||
#define WIN32_LEAN_AND_MEAN
|
|
||||||
#endif
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <windows.h>
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header
|
// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header
|
||||||
@ -67,21 +63,7 @@ FileLinkApp::FileLinkApp(int& argc, char** argv) : QCoreApplication(argc, argv),
|
|||||||
{
|
{
|
||||||
#if defined Q_OS_WIN32
|
#if defined Q_OS_WIN32
|
||||||
// attach the parent console
|
// attach the parent console
|
||||||
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
|
if (AttachWindowsConsole()) {
|
||||||
// if attach succeeds, reopen and sync all the i/o
|
|
||||||
if (freopen("CON", "w", stdout)) {
|
|
||||||
std::cout.sync_with_stdio();
|
|
||||||
}
|
|
||||||
if (freopen("CON", "w", stderr)) {
|
|
||||||
std::cerr.sync_with_stdio();
|
|
||||||
}
|
|
||||||
if (freopen("CON", "r", stdin)) {
|
|
||||||
std::cin.sync_with_stdio();
|
|
||||||
}
|
|
||||||
auto out = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
||||||
DWORD written;
|
|
||||||
const char* endline = "\n";
|
|
||||||
WriteConsole(out, endline, strlen(endline), &written, NULL);
|
|
||||||
consoleAttached = true;
|
consoleAttached = true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -188,7 +170,7 @@ void FileLinkApp::runLink()
|
|||||||
FS::LinkResult result = { src_path, dst_path, QString::fromStdString(os_err.message()), os_err.value() };
|
FS::LinkResult result = { src_path, dst_path, QString::fromStdString(os_err.message()), os_err.value() };
|
||||||
m_path_results.append(result);
|
m_path_results.append(result);
|
||||||
} else {
|
} else {
|
||||||
FS::LinkResult result = { src_path, dst_path };
|
FS::LinkResult result = { src_path, dst_path, "", 0};
|
||||||
m_path_results.append(result);
|
m_path_results.append(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -248,7 +230,7 @@ void FileLinkApp::readPathPairs()
|
|||||||
in >> numLinks;
|
in >> numLinks;
|
||||||
qDebug() << "numLinks" << numLinks;
|
qDebug() << "numLinks" << numLinks;
|
||||||
|
|
||||||
for (int i = 0; i < numLinks; i++) {
|
for (unsigned int i = 0; i < numLinks; i++) {
|
||||||
FS::LinkPair pair;
|
FS::LinkPair pair;
|
||||||
in >> pair.src;
|
in >> pair.src;
|
||||||
in >> pair.dst;
|
in >> pair.dst;
|
||||||
@ -271,7 +253,6 @@ FileLinkApp::~FileLinkApp()
|
|||||||
fclose(stdout);
|
fclose(stdout);
|
||||||
fclose(stdin);
|
fclose(stdin);
|
||||||
fclose(stderr);
|
fclose(stderr);
|
||||||
FreeConsole();
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -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) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -35,13 +36,14 @@
|
|||||||
|
|
||||||
#include "IconList.h"
|
#include "IconList.h"
|
||||||
#include <FileSystem.h>
|
#include <FileSystem.h>
|
||||||
#include <QMap>
|
|
||||||
#include <QEventLoop>
|
|
||||||
#include <QMimeData>
|
|
||||||
#include <QUrl>
|
|
||||||
#include <QFileSystemWatcher>
|
|
||||||
#include <QSet>
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QEventLoop>
|
||||||
|
#include <QFileSystemWatcher>
|
||||||
|
#include <QMap>
|
||||||
|
#include <QMimeData>
|
||||||
|
#include <QSet>
|
||||||
|
#include <QUrl>
|
||||||
|
#include "icons/IconUtils.h"
|
||||||
|
|
||||||
#define MAX_SIZE 1024
|
#define MAX_SIZE 1024
|
||||||
|
|
||||||
@ -50,17 +52,14 @@ IconList::IconList(const QStringList &builtinPaths, QString path, QObject *paren
|
|||||||
QSet<QString> builtinNames;
|
QSet<QString> builtinNames;
|
||||||
|
|
||||||
// add builtin icons
|
// add builtin icons
|
||||||
for(auto & builtinPath: builtinPaths)
|
for (auto& builtinPath : builtinPaths) {
|
||||||
{
|
|
||||||
QDir instance_icons(builtinPath);
|
QDir instance_icons(builtinPath);
|
||||||
auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name);
|
auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name);
|
||||||
for (auto file_info : file_info_list)
|
for (auto file_info : file_info_list) {
|
||||||
{
|
|
||||||
builtinNames.insert(file_info.completeBaseName());
|
builtinNames.insert(file_info.completeBaseName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(auto & builtinName : builtinNames)
|
for (auto& builtinName : builtinNames) {
|
||||||
{
|
|
||||||
addThemeIcon(builtinName);
|
addThemeIcon(builtinName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,17 +77,14 @@ IconList::IconList(const QStringList &builtinPaths, QString path, QObject *paren
|
|||||||
void IconList::sortIconList()
|
void IconList::sortIconList()
|
||||||
{
|
{
|
||||||
qDebug() << "Sorting icon list...";
|
qDebug() << "Sorting icon list...";
|
||||||
std::sort(icons.begin(), icons.end(), [](const MMCIcon& a, const MMCIcon& b) {
|
std::sort(icons.begin(), icons.end(), [](const MMCIcon& a, const MMCIcon& b) { return a.m_key.localeAwareCompare(b.m_key) < 0; });
|
||||||
return a.m_key.localeAwareCompare(b.m_key) < 0;
|
|
||||||
});
|
|
||||||
reindex();
|
reindex();
|
||||||
}
|
}
|
||||||
|
|
||||||
void IconList::directoryChanged(const QString& path)
|
void IconList::directoryChanged(const QString& path)
|
||||||
{
|
{
|
||||||
QDir new_dir(path);
|
QDir new_dir(path);
|
||||||
if(m_dir.absolutePath() != new_dir.absolutePath())
|
if (m_dir.absolutePath() != new_dir.absolutePath()) {
|
||||||
{
|
|
||||||
m_dir.setPath(path);
|
m_dir.setPath(path);
|
||||||
m_dir.refresh();
|
m_dir.refresh();
|
||||||
if (is_watching)
|
if (is_watching)
|
||||||
@ -100,8 +96,7 @@ void IconList::directoryChanged(const QString &path)
|
|||||||
return;
|
return;
|
||||||
m_dir.refresh();
|
m_dir.refresh();
|
||||||
auto new_list = m_dir.entryList(QDir::Files, QDir::Name);
|
auto new_list = m_dir.entryList(QDir::Files, QDir::Name);
|
||||||
for (auto it = new_list.begin(); it != new_list.end(); it++)
|
for (auto it = new_list.begin(); it != new_list.end(); it++) {
|
||||||
{
|
|
||||||
QString& foo = (*it);
|
QString& foo = (*it);
|
||||||
foo = m_dir.filePath(foo);
|
foo = m_dir.filePath(foo);
|
||||||
}
|
}
|
||||||
@ -111,8 +106,7 @@ void IconList::directoryChanged(const QString &path)
|
|||||||
auto new_set = new_list.toSet();
|
auto new_set = new_list.toSet();
|
||||||
#endif
|
#endif
|
||||||
QList<QString> current_list;
|
QList<QString> current_list;
|
||||||
for (auto &it : icons)
|
for (auto& it : icons) {
|
||||||
{
|
|
||||||
if (!it.has(IconType::FileBased))
|
if (!it.has(IconType::FileBased))
|
||||||
continue;
|
continue;
|
||||||
current_list.push_back(it.m_images[IconType::FileBased].filename);
|
current_list.push_back(it.m_images[IconType::FileBased].filename);
|
||||||
@ -129,38 +123,33 @@ void IconList::directoryChanged(const QString &path)
|
|||||||
QSet<QString> to_add = new_set;
|
QSet<QString> to_add = new_set;
|
||||||
to_add -= current_set;
|
to_add -= current_set;
|
||||||
|
|
||||||
for (auto remove : to_remove)
|
for (auto remove : to_remove) {
|
||||||
{
|
|
||||||
qDebug() << "Removing " << remove;
|
qDebug() << "Removing " << remove;
|
||||||
QFileInfo rmfile(remove);
|
QFileInfo rmfile(remove);
|
||||||
QString key = rmfile.completeBaseName();
|
QString key = rmfile.completeBaseName();
|
||||||
|
|
||||||
QString suffix = rmfile.suffix();
|
QString suffix = rmfile.suffix();
|
||||||
// The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well
|
// The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well
|
||||||
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
|
if (!IconUtils::isIconSuffix(suffix))
|
||||||
key = rmfile.fileName();
|
key = rmfile.fileName();
|
||||||
|
|
||||||
int idx = getIconIndex(key);
|
int idx = getIconIndex(key);
|
||||||
if (idx == -1)
|
if (idx == -1)
|
||||||
continue;
|
continue;
|
||||||
icons[idx].remove(IconType::FileBased);
|
icons[idx].remove(IconType::FileBased);
|
||||||
if (icons[idx].type() == IconType::ToBeDeleted)
|
if (icons[idx].type() == IconType::ToBeDeleted) {
|
||||||
{
|
|
||||||
beginRemoveRows(QModelIndex(), idx, idx);
|
beginRemoveRows(QModelIndex(), idx, idx);
|
||||||
icons.remove(idx);
|
icons.remove(idx);
|
||||||
reindex();
|
reindex();
|
||||||
endRemoveRows();
|
endRemoveRows();
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
dataChanged(index(idx), index(idx));
|
dataChanged(index(idx), index(idx));
|
||||||
}
|
}
|
||||||
m_watcher->removePath(remove);
|
m_watcher->removePath(remove);
|
||||||
emit iconUpdated(key);
|
emit iconUpdated(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto add : to_add)
|
for (auto add : to_add) {
|
||||||
{
|
|
||||||
qDebug() << "Adding " << add;
|
qDebug() << "Adding " << add;
|
||||||
|
|
||||||
QFileInfo addfile(add);
|
QFileInfo addfile(add);
|
||||||
@ -168,11 +157,10 @@ void IconList::directoryChanged(const QString &path)
|
|||||||
|
|
||||||
QString suffix = addfile.suffix();
|
QString suffix = addfile.suffix();
|
||||||
// The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well
|
// The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well
|
||||||
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
|
if (!IconUtils::isIconSuffix(suffix))
|
||||||
key = addfile.fileName();
|
key = addfile.fileName();
|
||||||
|
|
||||||
if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased))
|
if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased)) {
|
||||||
{
|
|
||||||
m_watcher->addPath(add);
|
m_watcher->addPath(add);
|
||||||
emit iconUpdated(key);
|
emit iconUpdated(key);
|
||||||
}
|
}
|
||||||
@ -213,12 +201,9 @@ void IconList::startWatching()
|
|||||||
auto abs_path = m_dir.absolutePath();
|
auto abs_path = m_dir.absolutePath();
|
||||||
FS::ensureFolderPathExists(abs_path);
|
FS::ensureFolderPathExists(abs_path);
|
||||||
is_watching = m_watcher->addPath(abs_path);
|
is_watching = m_watcher->addPath(abs_path);
|
||||||
if (is_watching)
|
if (is_watching) {
|
||||||
{
|
|
||||||
qDebug() << "Started watching " << abs_path;
|
qDebug() << "Started watching " << abs_path;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
qDebug() << "Failed to start watching " << abs_path;
|
qDebug() << "Failed to start watching " << abs_path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -241,7 +226,11 @@ Qt::DropActions IconList::supportedDropActions() const
|
|||||||
return Qt::CopyAction;
|
return Qt::CopyAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[maybe_unused]] int row, [[maybe_unused]] int column, [[maybe_unused]] 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;
|
||||||
@ -250,12 +239,10 @@ bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[mayb
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
// files dropped from outside?
|
// files dropped from outside?
|
||||||
if (data->hasUrls())
|
if (data->hasUrls()) {
|
||||||
{
|
|
||||||
auto urls = data->urls();
|
auto urls = data->urls();
|
||||||
QStringList iconFiles;
|
QStringList iconFiles;
|
||||||
for (auto url : urls)
|
for (auto url : urls) {
|
||||||
{
|
|
||||||
// only local files may be dropped...
|
// only local files may be dropped...
|
||||||
if (!url.isLocalFile())
|
if (!url.isLocalFile())
|
||||||
continue;
|
continue;
|
||||||
@ -270,9 +257,6 @@ bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[mayb
|
|||||||
Qt::ItemFlags IconList::flags(const QModelIndex& index) const
|
Qt::ItemFlags IconList::flags(const QModelIndex& index) const
|
||||||
{
|
{
|
||||||
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
|
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
|
||||||
if (index.isValid())
|
|
||||||
return Qt::ItemIsDropEnabled | defaultFlags;
|
|
||||||
else
|
|
||||||
return Qt::ItemIsDropEnabled | defaultFlags;
|
return Qt::ItemIsDropEnabled | defaultFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,8 +270,7 @@ QVariant IconList::data(const QModelIndex &index, int role) const
|
|||||||
if (row < 0 || row >= icons.size())
|
if (row < 0 || row >= icons.size())
|
||||||
return QVariant();
|
return QVariant();
|
||||||
|
|
||||||
switch (role)
|
switch (role) {
|
||||||
{
|
|
||||||
case Qt::DecorationRole:
|
case Qt::DecorationRole:
|
||||||
return icons[row].icon();
|
return icons[row].icon();
|
||||||
case Qt::DisplayRole:
|
case Qt::DisplayRole:
|
||||||
@ -307,19 +290,7 @@ int IconList::rowCount(const QModelIndex &parent) const
|
|||||||
void IconList::installIcons(const QStringList& iconFiles)
|
void IconList::installIcons(const QStringList& iconFiles)
|
||||||
{
|
{
|
||||||
for (QString file : iconFiles)
|
for (QString file : iconFiles)
|
||||||
{
|
installIcon(file, {});
|
||||||
QFileInfo fileinfo(file);
|
|
||||||
if (!fileinfo.isReadable() || !fileinfo.isFile())
|
|
||||||
continue;
|
|
||||||
QString target = FS::PathCombine(getDirectory(), fileinfo.fileName());
|
|
||||||
|
|
||||||
QString suffix = fileinfo.suffix();
|
|
||||||
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!QFile::copy(file, target))
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void IconList::installIcon(const QString& file, const QString& name)
|
void IconList::installIcon(const QString& file, const QString& name)
|
||||||
@ -328,19 +299,17 @@ void IconList::installIcon(const QString &file, const QString &name)
|
|||||||
if (!fileinfo.isReadable() || !fileinfo.isFile())
|
if (!fileinfo.isReadable() || !fileinfo.isFile())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QString target = FS::PathCombine(getDirectory(), name);
|
if (!IconUtils::isIconSuffix(fileinfo.suffix()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
QString target = FS::PathCombine(getDirectory(), name.isEmpty() ? fileinfo.fileName() : name);
|
||||||
QFile::copy(file, target);
|
QFile::copy(file, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IconList::iconFileExists(const QString& key) const
|
bool IconList::iconFileExists(const QString& key) const
|
||||||
{
|
{
|
||||||
auto iconEntry = icon(key);
|
auto iconEntry = icon(key);
|
||||||
if(!iconEntry)
|
return iconEntry && iconEntry->has(IconType::FileBased);
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return iconEntry->has(IconType::FileBased);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const MMCIcon* IconList::icon(const QString& key) const
|
const MMCIcon* IconList::icon(const QString& key) const
|
||||||
@ -353,32 +322,23 @@ const MMCIcon *IconList::icon(const QString &key) const
|
|||||||
|
|
||||||
bool IconList::deleteIcon(const QString& key)
|
bool IconList::deleteIcon(const QString& key)
|
||||||
{
|
{
|
||||||
if (!iconFileExists(key))
|
return iconFileExists(key) && QFile::remove(icon(key)->getFilePath());
|
||||||
return false;
|
|
||||||
|
|
||||||
return QFile::remove(icon(key)->getFilePath());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IconList::trashIcon(const QString& key)
|
bool IconList::trashIcon(const QString& key)
|
||||||
{
|
{
|
||||||
if (!iconFileExists(key))
|
return iconFileExists(key) && FS::trash(icon(key)->getFilePath(), nullptr);
|
||||||
return false;
|
|
||||||
|
|
||||||
return FS::trash(icon(key)->getFilePath(), nullptr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IconList::addThemeIcon(const QString& key)
|
bool IconList::addThemeIcon(const QString& key)
|
||||||
{
|
{
|
||||||
auto iter = name_index.find(key);
|
auto iter = name_index.find(key);
|
||||||
if (iter != name_index.end())
|
if (iter != name_index.end()) {
|
||||||
{
|
|
||||||
auto& oldOne = icons[*iter];
|
auto& oldOne = icons[*iter];
|
||||||
oldOne.replace(Builtin, key);
|
oldOne.replace(Builtin, key);
|
||||||
dataChanged(index(*iter), index(*iter));
|
dataChanged(index(*iter), index(*iter));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// add a new icon
|
// add a new icon
|
||||||
beginInsertRows(QModelIndex(), icons.size(), icons.size());
|
beginInsertRows(QModelIndex(), icons.size(), icons.size());
|
||||||
{
|
{
|
||||||
@ -392,7 +352,6 @@ bool IconList::addThemeIcon(const QString& key)
|
|||||||
endInsertRows();
|
endInsertRows();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
bool IconList::addIcon(const QString& key, const QString& name, const QString& path, const IconType type)
|
bool IconList::addIcon(const QString& key, const QString& name, const QString& path, const IconType type)
|
||||||
{
|
{
|
||||||
@ -401,15 +360,12 @@ bool IconList::addIcon(const QString &key, const QString &name, const QString &p
|
|||||||
if (icon.isNull())
|
if (icon.isNull())
|
||||||
return false;
|
return false;
|
||||||
auto iter = name_index.find(key);
|
auto iter = name_index.find(key);
|
||||||
if (iter != name_index.end())
|
if (iter != name_index.end()) {
|
||||||
{
|
|
||||||
auto& oldOne = icons[*iter];
|
auto& oldOne = icons[*iter];
|
||||||
oldOne.replace(type, icon, path);
|
oldOne.replace(type, icon, path);
|
||||||
dataChanged(index(*iter), index(*iter));
|
dataChanged(index(*iter), index(*iter));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// add a new icon
|
// add a new icon
|
||||||
beginInsertRows(QModelIndex(), icons.size(), icons.size());
|
beginInsertRows(QModelIndex(), icons.size(), icons.size());
|
||||||
{
|
{
|
||||||
@ -423,7 +379,6 @@ bool IconList::addIcon(const QString &key, const QString &name, const QString &p
|
|||||||
endInsertRows();
|
endInsertRows();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void IconList::saveIcon(const QString& key, const QString& path, const char* format) const
|
void IconList::saveIcon(const QString& key, const QString& path, const char* format) const
|
||||||
{
|
{
|
||||||
@ -432,13 +387,11 @@ void IconList::saveIcon(const QString &key, const QString &path, const char * fo
|
|||||||
pixmap.save(path, format);
|
pixmap.save(path, format);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void IconList::reindex()
|
void IconList::reindex()
|
||||||
{
|
{
|
||||||
name_index.clear();
|
name_index.clear();
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (auto &iter : icons)
|
for (auto& iter : icons) {
|
||||||
{
|
|
||||||
name_index[iter.m_key] = i;
|
name_index[iter.m_key] = i;
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
@ -472,5 +425,3 @@ QString IconList::getDirectory() const
|
|||||||
{
|
{
|
||||||
return m_dir.absolutePath();
|
return m_dir.absolutePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
//#include "IconList.moc"
|
|
||||||
|
@ -1,4 +1,24 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2023 Trial97 <alexandru.tripon97@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/>.
|
||||||
|
*
|
||||||
|
* 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -12,13 +32,12 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QMutex>
|
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QFile>
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QMutex>
|
||||||
#include <QtGui/QIcon>
|
#include <QtGui/QIcon>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
@ -29,8 +48,7 @@
|
|||||||
|
|
||||||
class QFileSystemWatcher;
|
class QFileSystemWatcher;
|
||||||
|
|
||||||
class IconList : public QAbstractListModel
|
class IconList : public QAbstractListModel {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit IconList(const QStringList& builtinPaths, QString path, QObject* parent = 0);
|
explicit IconList(const QStringList& builtinPaths, QString path, QObject* parent = 0);
|
||||||
@ -80,6 +98,7 @@ public slots:
|
|||||||
protected slots:
|
protected slots:
|
||||||
void fileChanged(const QString& path);
|
void fileChanged(const QString& path);
|
||||||
void SettingChanged(const Setting& setting, QVariant value);
|
void SettingChanged(const Setting& setting, QVariant value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
shared_qobject_ptr<QFileSystemWatcher> m_watcher;
|
shared_qobject_ptr<QFileSystemWatcher> m_watcher;
|
||||||
bool is_watching;
|
bool is_watching;
|
||||||
|
@ -1,25 +1,51 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2023 Trial97 <alexandru.tripon97@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/>.
|
||||||
|
*
|
||||||
|
* 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 "IconUtils.h"
|
#include "IconUtils.h"
|
||||||
|
|
||||||
#include "FileSystem.h"
|
|
||||||
#include <QDirIterator>
|
#include <QDirIterator>
|
||||||
|
#include "FileSystem.h"
|
||||||
#include <array>
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
std::array<const char *, 6> validIconExtensions = {{
|
static const QStringList validIconExtensions = { { "svg", "png", "ico", "gif", "jpg", "jpeg" } };
|
||||||
"svg",
|
|
||||||
"png",
|
|
||||||
"ico",
|
|
||||||
"gif",
|
|
||||||
"jpg",
|
|
||||||
"jpeg"
|
|
||||||
}};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace IconUtils {
|
namespace IconUtils {
|
||||||
|
|
||||||
QString findBestIconIn(const QString &folder, const QString & iconKey) {
|
QString findBestIconIn(const QString& folder, const QString& iconKey)
|
||||||
int best_found = validIconExtensions.size();
|
{
|
||||||
QString best_filename;
|
QString best_filename;
|
||||||
|
|
||||||
QDirIterator it(folder, QDir::NoDotAndDotDot | QDir::Files, QDirIterator::NoIteratorFlags);
|
QDirIterator it(folder, QDir::NoDotAndDotDot | QDir::Files, QDirIterator::NoIteratorFlags);
|
||||||
@ -27,36 +53,20 @@ QString findBestIconIn(const QString &folder, const QString & iconKey) {
|
|||||||
it.next();
|
it.next();
|
||||||
auto fileInfo = it.fileInfo();
|
auto fileInfo = it.fileInfo();
|
||||||
|
|
||||||
if(fileInfo.completeBaseName() != iconKey)
|
if (fileInfo.completeBaseName() == iconKey && isIconSuffix(fileInfo.suffix()))
|
||||||
continue;
|
return fileInfo.absoluteFilePath();
|
||||||
|
|
||||||
auto extension = fileInfo.suffix();
|
|
||||||
|
|
||||||
for(int i = 0; i < best_found; i++) {
|
|
||||||
if(extension == validIconExtensions[i]) {
|
|
||||||
best_found = i;
|
|
||||||
qDebug() << i << " : " << fileInfo.fileName();
|
|
||||||
best_filename = fileInfo.fileName();
|
|
||||||
}
|
}
|
||||||
}
|
return {};
|
||||||
}
|
|
||||||
return FS::PathCombine(folder, best_filename);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString getIconFilter() {
|
QString getIconFilter()
|
||||||
QString out;
|
{
|
||||||
QTextStream stream(&out);
|
return "(*." + validIconExtensions.join(" *.") + ")";
|
||||||
stream << '(';
|
|
||||||
for(size_t i = 0; i < validIconExtensions.size() - 1; i++) {
|
|
||||||
if(i > 0) {
|
|
||||||
stream << " ";
|
|
||||||
}
|
|
||||||
stream << "*." << validIconExtensions[i];
|
|
||||||
}
|
|
||||||
stream << " *." << validIconExtensions[validIconExtensions.size() - 1];
|
|
||||||
stream << ')';
|
|
||||||
return out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isIconSuffix(QString suffix)
|
||||||
|
{
|
||||||
|
return validIconExtensions.contains(suffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace IconUtils
|
||||||
|
@ -1,3 +1,38 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2023 Trial97 <alexandru.tripon97@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/>.
|
||||||
|
*
|
||||||
|
* 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 <QString>
|
#include <QString>
|
||||||
@ -10,4 +45,5 @@ QString findBestIconIn(const QString &folder, const QString & iconKey);
|
|||||||
// Get icon file type filter for file browser dialogs
|
// Get icon file type filter for file browser dialogs
|
||||||
QString getIconFilter();
|
QString getIconFilter();
|
||||||
|
|
||||||
}
|
bool isIconSuffix(QString suffix);
|
||||||
|
} // namespace IconUtils
|
||||||
|
@ -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) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -40,8 +41,7 @@
|
|||||||
IconType operator--(IconType& t, int)
|
IconType operator--(IconType& t, int)
|
||||||
{
|
{
|
||||||
IconType temp = t;
|
IconType temp = t;
|
||||||
switch (t)
|
switch (t) {
|
||||||
{
|
|
||||||
case IconType::Builtin:
|
case IconType::Builtin:
|
||||||
t = IconType::ToBeDeleted;
|
t = IconType::ToBeDeleted;
|
||||||
break;
|
break;
|
||||||
@ -52,8 +52,7 @@ IconType operator--(IconType &t, int)
|
|||||||
t = IconType::Transient;
|
t = IconType::Transient;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
{
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return temp;
|
return temp;
|
||||||
}
|
}
|
||||||
@ -90,10 +89,8 @@ void MMCIcon::remove(IconType rm_type)
|
|||||||
{
|
{
|
||||||
m_images[rm_type].filename = QString();
|
m_images[rm_type].filename = QString();
|
||||||
m_images[rm_type].icon = QIcon();
|
m_images[rm_type].icon = QIcon();
|
||||||
for (auto iter = rm_type; iter != IconType::ToBeDeleted; iter--)
|
for (auto iter = rm_type; iter != IconType::ToBeDeleted; iter--) {
|
||||||
{
|
if (m_images[iter].present()) {
|
||||||
if (m_images[iter].present())
|
|
||||||
{
|
|
||||||
m_current_type = iter;
|
m_current_type = iter;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -103,8 +100,7 @@ void MMCIcon::remove(IconType rm_type)
|
|||||||
|
|
||||||
void MMCIcon::replace(IconType new_type, QIcon icon, QString path)
|
void MMCIcon::replace(IconType new_type, QIcon icon, QString path)
|
||||||
{
|
{
|
||||||
if (new_type > m_current_type || m_current_type == IconType::ToBeDeleted)
|
if (new_type > m_current_type || m_current_type == IconType::ToBeDeleted) {
|
||||||
{
|
|
||||||
m_current_type = new_type;
|
m_current_type = new_type;
|
||||||
}
|
}
|
||||||
m_images[new_type].icon = icon;
|
m_images[new_type].icon = icon;
|
||||||
@ -114,8 +110,7 @@ void MMCIcon::replace(IconType new_type, QIcon icon, QString path)
|
|||||||
|
|
||||||
void MMCIcon::replace(IconType new_type, const QString& key)
|
void MMCIcon::replace(IconType new_type, const QString& key)
|
||||||
{
|
{
|
||||||
if (new_type > m_current_type || m_current_type == IconType::ToBeDeleted)
|
if (new_type > m_current_type || m_current_type == IconType::ToBeDeleted) {
|
||||||
{
|
|
||||||
m_current_type = new_type;
|
m_current_type = new_type;
|
||||||
}
|
}
|
||||||
m_images[new_type].icon = QIcon();
|
m_images[new_type].icon = QIcon();
|
||||||
@ -131,7 +126,6 @@ QString MMCIcon::getFilePath() const
|
|||||||
return m_images[m_current_type].filename;
|
return m_images[m_current_type].filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool MMCIcon::isBuiltIn() const
|
bool MMCIcon::isBuiltIn() const
|
||||||
{
|
{
|
||||||
return m_current_type == IconType::Builtin;
|
return m_current_type == IconType::Builtin;
|
||||||
|
@ -1,4 +1,24 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2023 Trial97 <alexandru.tripon97@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/>.
|
||||||
|
*
|
||||||
|
* 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -12,34 +32,21 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <QString>
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
enum IconType : unsigned
|
enum IconType : unsigned { Builtin, Transient, FileBased, ICONS_TOTAL, ToBeDeleted };
|
||||||
{
|
|
||||||
Builtin,
|
|
||||||
Transient,
|
|
||||||
FileBased,
|
|
||||||
ICONS_TOTAL,
|
|
||||||
ToBeDeleted
|
|
||||||
};
|
|
||||||
|
|
||||||
struct MMCImage
|
struct MMCImage {
|
||||||
{
|
|
||||||
QIcon icon;
|
QIcon icon;
|
||||||
QString key;
|
QString key;
|
||||||
QString filename;
|
QString filename;
|
||||||
bool present() const
|
bool present() const { return !icon.isNull() || !key.isEmpty(); }
|
||||||
{
|
|
||||||
return !icon.isNull() || !key.isEmpty();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MMCIcon
|
struct MMCIcon {
|
||||||
{
|
|
||||||
QString m_key;
|
QString m_key;
|
||||||
QString m_name;
|
QString m_name;
|
||||||
MMCImage m_images[ICONS_TOTAL];
|
MMCImage m_images[ICONS_TOTAL];
|
||||||
|
@ -1,5 +1,24 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2023 Trial97 <alexandru.tripon97@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 "JavaInstall.h"
|
#include "JavaInstall.h"
|
||||||
|
|
||||||
|
#include "BaseVersion.h"
|
||||||
#include "StringUtils.h"
|
#include "StringUtils.h"
|
||||||
|
|
||||||
bool JavaInstall::operator<(const JavaInstall& rhs)
|
bool JavaInstall::operator<(const JavaInstall& rhs)
|
||||||
@ -7,12 +26,10 @@ bool JavaInstall::operator<(const JavaInstall &rhs)
|
|||||||
auto archCompare = StringUtils::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive);
|
auto archCompare = StringUtils::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive);
|
||||||
if (archCompare != 0)
|
if (archCompare != 0)
|
||||||
return archCompare < 0;
|
return archCompare < 0;
|
||||||
if(id < rhs.id)
|
if (id < rhs.id) {
|
||||||
{
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if(id > rhs.id)
|
if (id > rhs.id) {
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return StringUtils::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0;
|
return StringUtils::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0;
|
||||||
@ -27,3 +44,21 @@ bool JavaInstall::operator>(const JavaInstall &rhs)
|
|||||||
{
|
{
|
||||||
return (!operator<(rhs)) && (!operator==(rhs));
|
return (!operator<(rhs)) && (!operator==(rhs));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool JavaInstall::operator<(BaseVersion& a)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return operator<(dynamic_cast<JavaInstall&>(a));
|
||||||
|
} catch (const std::bad_cast& e) {
|
||||||
|
return BaseVersion::operator<(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JavaInstall::operator>(BaseVersion& a)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return operator>(dynamic_cast<JavaInstall&>(a));
|
||||||
|
} catch (const std::bad_cast& e) {
|
||||||
|
return BaseVersion::operator>(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,30 +1,37 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2023 Trial97 <alexandru.tripon97@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
|
#pragma once
|
||||||
|
|
||||||
#include "BaseVersion.h"
|
#include "BaseVersion.h"
|
||||||
#include "JavaVersion.h"
|
#include "JavaVersion.h"
|
||||||
|
|
||||||
struct JavaInstall : public BaseVersion
|
struct JavaInstall : public BaseVersion {
|
||||||
{
|
|
||||||
JavaInstall() {}
|
JavaInstall() {}
|
||||||
JavaInstall(QString id, QString arch, QString path)
|
JavaInstall(QString id, QString arch, QString path) : id(id), arch(arch), path(path) {}
|
||||||
: id(id), arch(arch), path(path)
|
virtual QString descriptor() { return id.toString(); }
|
||||||
{
|
|
||||||
}
|
|
||||||
virtual QString descriptor()
|
|
||||||
{
|
|
||||||
return id.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual QString name()
|
virtual QString name() { return id.toString(); }
|
||||||
{
|
|
||||||
return id.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual QString typeString() const
|
virtual QString typeString() const { return arch; }
|
||||||
{
|
|
||||||
return arch;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
virtual bool operator<(BaseVersion& a) override;
|
||||||
|
virtual bool operator>(BaseVersion& a) override;
|
||||||
bool operator<(const JavaInstall& rhs);
|
bool operator<(const JavaInstall& rhs);
|
||||||
bool operator==(const JavaInstall& rhs);
|
bool operator==(const JavaInstall& rhs);
|
||||||
bool operator>(const JavaInstall& rhs);
|
bool operator>(const JavaInstall& rhs);
|
||||||
|
@ -28,7 +28,7 @@ void Update::executeTask()
|
|||||||
{
|
{
|
||||||
connect(m_updateTask.get(), &Task::finished, this, &Update::updateFinished);
|
connect(m_updateTask.get(), &Task::finished, this, &Update::updateFinished);
|
||||||
connect(m_updateTask.get(), &Task::progress, this, &Update::setProgress);
|
connect(m_updateTask.get(), &Task::progress, this, &Update::setProgress);
|
||||||
connect(m_updateTask.get(), &Task::stepProgress, this, &Update::propogateStepProgress);
|
connect(m_updateTask.get(), &Task::stepProgress, this, &Update::propagateStepProgress);
|
||||||
connect(m_updateTask.get(), &Task::status, this, &Update::setStatus);
|
connect(m_updateTask.get(), &Task::status, this, &Update::setStatus);
|
||||||
connect(m_updateTask.get(), &Task::details, this, &Update::setDetails);
|
connect(m_updateTask.get(), &Task::details, this, &Update::setDetails);
|
||||||
emit progressReportingRequest();
|
emit progressReportingRequest();
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
#include "BaseEntity.h"
|
#include "BaseEntity.h"
|
||||||
|
|
||||||
#include "net/Download.h"
|
#include "net/ApiDownload.h"
|
||||||
#include "net/HttpMetaCache.h"
|
#include "net/HttpMetaCache.h"
|
||||||
#include "net/NetJob.h"
|
#include "net/NetJob.h"
|
||||||
#include "Json.h"
|
#include "Json.h"
|
||||||
@ -130,7 +130,7 @@ void Meta::BaseEntity::load(Net::Mode loadType)
|
|||||||
auto url = this->url();
|
auto url = this->url();
|
||||||
auto entry = APPLICATION->metacache()->resolveEntry("meta", localFilename());
|
auto entry = APPLICATION->metacache()->resolveEntry("meta", localFilename());
|
||||||
entry->setStale(true);
|
entry->setStale(true);
|
||||||
auto dl = Net::Download::makeCached(url, entry);
|
auto dl = Net::ApiDownload::makeCached(url, entry);
|
||||||
/*
|
/*
|
||||||
* The validator parses the file and loads it into the object.
|
* The validator parses the file and loads it into the object.
|
||||||
* If that fails, the file is not written to storage.
|
* If that fails, the file is not written to storage.
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
|
|
||||||
#include "AssetsUtils.h"
|
#include "AssetsUtils.h"
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
#include "net/Download.h"
|
#include "net/ApiDownload.h"
|
||||||
#include "net/ChecksumValidator.h"
|
#include "net/ChecksumValidator.h"
|
||||||
#include "BuildConfig.h"
|
#include "BuildConfig.h"
|
||||||
|
|
||||||
@ -311,7 +311,7 @@ NetAction::Ptr AssetObject::getDownloadAction()
|
|||||||
QFileInfo objectFile(getLocalPath());
|
QFileInfo objectFile(getLocalPath());
|
||||||
if ((!objectFile.isFile()) || (objectFile.size() != size))
|
if ((!objectFile.isFile()) || (objectFile.size() != size))
|
||||||
{
|
{
|
||||||
auto objectDL = Net::Download::makeFile(getUrl(), objectFile.filePath());
|
auto objectDL = Net::ApiDownload::makeFile(getUrl(), objectFile.filePath());
|
||||||
if(hash.size())
|
if(hash.size())
|
||||||
{
|
{
|
||||||
auto rawHash = QByteArray::fromHex(hash.toLatin1());
|
auto rawHash = QByteArray::fromHex(hash.toLatin1());
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
#include "Library.h"
|
#include "Library.h"
|
||||||
#include "MinecraftInstance.h"
|
#include "MinecraftInstance.h"
|
||||||
|
|
||||||
#include <net/Download.h>
|
#include <net/ApiDownload.h>
|
||||||
#include <net/ChecksumValidator.h>
|
#include <net/ChecksumValidator.h>
|
||||||
#include <FileSystem.h>
|
#include <FileSystem.h>
|
||||||
#include <BuildConfig.h>
|
#include <BuildConfig.h>
|
||||||
@ -129,14 +129,14 @@ QList<NetAction::Ptr> Library::getDownloads(
|
|||||||
if(sha1.size())
|
if(sha1.size())
|
||||||
{
|
{
|
||||||
auto rawSha1 = QByteArray::fromHex(sha1.toLatin1());
|
auto rawSha1 = QByteArray::fromHex(sha1.toLatin1());
|
||||||
auto dl = Net::Download::makeCached(url, entry, options);
|
auto dl = Net::ApiDownload::makeCached(url, entry, options);
|
||||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
|
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
|
||||||
qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url;
|
qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url;
|
||||||
out.append(dl);
|
out.append(dl);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
out.append(Net::Download::makeCached(url, entry, options));
|
out.append(Net::ApiDownload::makeCached(url, entry, options));
|
||||||
qDebug() << "Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url;
|
qDebug() << "Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
* 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>
|
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||||
|
* Copyright (c) 2023 seth <getchoo at tuta dot io>
|
||||||
*
|
*
|
||||||
* 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
|
||||||
@ -60,7 +61,6 @@
|
|||||||
#include "launch/steps/QuitAfterGameStop.h"
|
#include "launch/steps/QuitAfterGameStop.h"
|
||||||
|
|
||||||
#include "minecraft/launch/LauncherPartLaunch.h"
|
#include "minecraft/launch/LauncherPartLaunch.h"
|
||||||
#include "minecraft/launch/DirectJavaLaunch.h"
|
|
||||||
#include "minecraft/launch/ModMinecraftJar.h"
|
#include "minecraft/launch/ModMinecraftJar.h"
|
||||||
#include "minecraft/launch/ClaimAccount.h"
|
#include "minecraft/launch/ClaimAccount.h"
|
||||||
#include "minecraft/launch/ReconstructAssets.h"
|
#include "minecraft/launch/ReconstructAssets.h"
|
||||||
@ -166,10 +166,6 @@ void MinecraftInstance::loadSpecificSettings()
|
|||||||
m_settings->registerOverride(global_settings->getSetting("MaxMemAlloc"), memorySetting);
|
m_settings->registerOverride(global_settings->getSetting("MaxMemAlloc"), memorySetting);
|
||||||
m_settings->registerOverride(global_settings->getSetting("PermGen"), memorySetting);
|
m_settings->registerOverride(global_settings->getSetting("PermGen"), memorySetting);
|
||||||
|
|
||||||
// Minecraft launch method
|
|
||||||
auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false);
|
|
||||||
m_settings->registerOverride(global_settings->getSetting("MCLaunchMethod"), launchMethodOverride);
|
|
||||||
|
|
||||||
// Native library workarounds
|
// Native library workarounds
|
||||||
auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false);
|
auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false);
|
||||||
m_settings->registerOverride(global_settings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride);
|
m_settings->registerOverride(global_settings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride);
|
||||||
@ -186,6 +182,10 @@ void MinecraftInstance::loadSpecificSettings()
|
|||||||
m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
|
m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
|
||||||
m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), miscellaneousOverride);
|
m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), miscellaneousOverride);
|
||||||
|
|
||||||
|
// Mod loader specific options
|
||||||
|
auto modLoaderSettings = m_settings->registerSetting("OverrideModLoaderSettings", false);
|
||||||
|
m_settings->registerOverride(global_settings->getSetting("DisableQuiltBeacon"), modLoaderSettings);
|
||||||
|
|
||||||
m_settings->set("InstanceType", "OneSix");
|
m_settings->set("InstanceType", "OneSix");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -391,6 +391,12 @@ QStringList MinecraftInstance::extraArguments()
|
|||||||
agent->library()->getApplicableFiles(runtimeContext(), jar, temp1, temp2, temp3, getLocalLibraryPath());
|
agent->library()->getApplicableFiles(runtimeContext(), jar, temp1, temp2, temp3, getLocalLibraryPath());
|
||||||
list.append("-javaagent:"+jar[0]+(agent->argument().isEmpty() ? "" : "="+agent->argument()));
|
list.append("-javaagent:"+jar[0]+(agent->argument().isEmpty() ? "" : "="+agent->argument()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto loaders = version->getModLoaders();
|
||||||
|
if (loaders.has_value() && loaders.value() & ResourceAPI::Quilt && settings()->get("DisableQuiltBeacon").toBool())
|
||||||
|
list.append("-Dloader.disable_beacon=true");
|
||||||
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -832,7 +838,7 @@ QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSess
|
|||||||
{
|
{
|
||||||
addToFilter(sessionRef.session, tr("<SESSION ID>"));
|
addToFilter(sessionRef.session, tr("<SESSION ID>"));
|
||||||
}
|
}
|
||||||
if (sessionRef.access_token != "offline") {
|
if (sessionRef.access_token != "0") {
|
||||||
addToFilter(sessionRef.access_token, tr("<ACCESS TOKEN>"));
|
addToFilter(sessionRef.access_token, tr("<ACCESS TOKEN>"));
|
||||||
}
|
}
|
||||||
if(sessionRef.client_token.size()) {
|
if(sessionRef.client_token.size()) {
|
||||||
@ -979,15 +985,6 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
|
|||||||
process->appendStep(makeShared<CheckJava>(pptr));
|
process->appendStep(makeShared<CheckJava>(pptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
// check launch method
|
|
||||||
QStringList validMethods = {"LauncherPart", "DirectJava"};
|
|
||||||
QString method = launchMethod();
|
|
||||||
if(!validMethods.contains(method))
|
|
||||||
{
|
|
||||||
process->appendStep(makeShared<TextPrint>(pptr, "Selected launch method \"" + method + "\" is not valid.\n", MessageLevel::Fatal));
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the .minecraft folder and server-resource-packs (workaround for Minecraft bug MCL-3732)
|
// create the .minecraft folder and server-resource-packs (workaround for Minecraft bug MCL-3732)
|
||||||
{
|
{
|
||||||
process->appendStep(makeShared<CreateGameFolders>(pptr));
|
process->appendStep(makeShared<CreateGameFolders>(pptr));
|
||||||
@ -1061,24 +1058,12 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
|
|||||||
|
|
||||||
{
|
{
|
||||||
// actually launch the game
|
// actually launch the game
|
||||||
auto method = launchMethod();
|
|
||||||
if(method == "LauncherPart")
|
|
||||||
{
|
|
||||||
auto step = makeShared<LauncherPartLaunch>(pptr);
|
auto step = makeShared<LauncherPartLaunch>(pptr);
|
||||||
step->setWorkingDirectory(gameRoot());
|
step->setWorkingDirectory(gameRoot());
|
||||||
step->setAuthSession(session);
|
step->setAuthSession(session);
|
||||||
step->setServerToJoin(serverToJoin);
|
step->setServerToJoin(serverToJoin);
|
||||||
process->appendStep(step);
|
process->appendStep(step);
|
||||||
}
|
}
|
||||||
else if (method == "DirectJava")
|
|
||||||
{
|
|
||||||
auto step = makeShared<DirectJavaLaunch>(pptr);
|
|
||||||
step->setWorkingDirectory(gameRoot());
|
|
||||||
step->setAuthSession(session);
|
|
||||||
step->setServerToJoin(serverToJoin);
|
|
||||||
process->appendStep(step);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// run post-exit command if that's needed
|
// run post-exit command if that's needed
|
||||||
if(getPostExitCommand().size())
|
if(getPostExitCommand().size())
|
||||||
@ -1100,11 +1085,6 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
|
|||||||
return m_launchProcess;
|
return m_launchProcess;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MinecraftInstance::launchMethod()
|
|
||||||
{
|
|
||||||
return settings()->get("MCLaunchMethod").toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
JavaVersion MinecraftInstance::getJavaVersion()
|
JavaVersion MinecraftInstance::getJavaVersion()
|
||||||
{
|
{
|
||||||
return JavaVersion(settings()->get("JavaVersion").toString());
|
return JavaVersion(settings()->get("JavaVersion").toString());
|
||||||
|
@ -165,8 +165,6 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session);
|
QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session);
|
||||||
QStringList validLaunchMethods();
|
|
||||||
QString launchMethod();
|
|
||||||
|
|
||||||
protected: // data
|
protected: // data
|
||||||
std::shared_ptr<PackProfile> m_components;
|
std::shared_ptr<PackProfile> m_components;
|
||||||
|
@ -22,7 +22,7 @@ void MinecraftLoadAndCheck::executeTask()
|
|||||||
connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::subtaskFailed);
|
connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::subtaskFailed);
|
||||||
connect(m_task.get(), &Task::aborted, this, [this]{ subtaskFailed(tr("Aborted")); });
|
connect(m_task.get(), &Task::aborted, this, [this]{ subtaskFailed(tr("Aborted")); });
|
||||||
connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::progress);
|
connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::progress);
|
||||||
connect(m_task.get(), &Task::stepProgress, this, &MinecraftLoadAndCheck::propogateStepProgress);
|
connect(m_task.get(), &Task::stepProgress, this, &MinecraftLoadAndCheck::propagateStepProgress);
|
||||||
connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus);
|
connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ void MinecraftUpdate::next()
|
|||||||
disconnect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed);
|
disconnect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed);
|
||||||
disconnect(task.get(), &Task::aborted, this, &Task::abort);
|
disconnect(task.get(), &Task::aborted, this, &Task::abort);
|
||||||
disconnect(task.get(), &Task::progress, this, &MinecraftUpdate::progress);
|
disconnect(task.get(), &Task::progress, this, &MinecraftUpdate::progress);
|
||||||
disconnect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propogateStepProgress);
|
disconnect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propagateStepProgress);
|
||||||
disconnect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus);
|
disconnect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus);
|
||||||
disconnect(task.get(), &Task::details, this, &MinecraftUpdate::setDetails);
|
disconnect(task.get(), &Task::details, this, &MinecraftUpdate::setDetails);
|
||||||
}
|
}
|
||||||
@ -120,7 +120,7 @@ void MinecraftUpdate::next()
|
|||||||
connect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed);
|
connect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed);
|
||||||
connect(task.get(), &Task::aborted, this, &Task::abort);
|
connect(task.get(), &Task::aborted, this, &Task::abort);
|
||||||
connect(task.get(), &Task::progress, this, &MinecraftUpdate::progress);
|
connect(task.get(), &Task::progress, this, &MinecraftUpdate::progress);
|
||||||
connect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propogateStepProgress);
|
connect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propagateStepProgress);
|
||||||
connect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus);
|
connect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus);
|
||||||
connect(task.get(), &Task::details, this, &MinecraftUpdate::setDetails);
|
connect(task.get(), &Task::details, this, &MinecraftUpdate::setDetails);
|
||||||
// if the task is already running, do not start it again
|
// if the task is already running, do not start it again
|
||||||
|
@ -374,6 +374,10 @@ bool AccountData::resumeStateFromV3(QJsonObject data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
yggdrasilToken = tokenFromJSONV3(data, "ygg");
|
yggdrasilToken = tokenFromJSONV3(data, "ygg");
|
||||||
|
// versions before 7.2 used "offline" as the offline token
|
||||||
|
if (yggdrasilToken.token == "offline")
|
||||||
|
yggdrasilToken.token = "0";
|
||||||
|
|
||||||
minecraftProfile = profileFromJSONV3(data, "profile");
|
minecraftProfile = profileFromJSONV3(data, "profile");
|
||||||
if(!entitlementFromJSONV3(data, minecraftEntitlement)) {
|
if(!entitlementFromJSONV3(data, minecraftEntitlement)) {
|
||||||
if(minecraftProfile.validity != Katabasis::Validity::None) {
|
if(minecraftProfile.validity != Katabasis::Validity::None) {
|
||||||
|
@ -26,6 +26,7 @@ bool AuthSession::MakeOffline(QString offline_playername)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
session = "-";
|
session = "-";
|
||||||
|
access_token = "0";
|
||||||
player_name = offline_playername;
|
player_name = offline_playername;
|
||||||
status = PlayableOffline;
|
status = PlayableOffline;
|
||||||
return true;
|
return true;
|
||||||
|
@ -37,6 +37,7 @@
|
|||||||
|
|
||||||
#include "MinecraftAccount.h"
|
#include "MinecraftAccount.h"
|
||||||
|
|
||||||
|
#include <QCryptographicHash>
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
@ -93,14 +94,14 @@ MinecraftAccountPtr MinecraftAccount::createOffline(const QString &username)
|
|||||||
{
|
{
|
||||||
auto account = makeShared<MinecraftAccount>();
|
auto account = makeShared<MinecraftAccount>();
|
||||||
account->data.type = AccountType::Offline;
|
account->data.type = AccountType::Offline;
|
||||||
account->data.yggdrasilToken.token = "offline";
|
account->data.yggdrasilToken.token = "0";
|
||||||
account->data.yggdrasilToken.validity = Katabasis::Validity::Certain;
|
account->data.yggdrasilToken.validity = Katabasis::Validity::Certain;
|
||||||
account->data.yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc();
|
account->data.yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc();
|
||||||
account->data.yggdrasilToken.extra["userName"] = username;
|
account->data.yggdrasilToken.extra["userName"] = username;
|
||||||
account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
|
account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
|
||||||
account->data.minecraftEntitlement.ownsMinecraft = true;
|
account->data.minecraftEntitlement.ownsMinecraft = true;
|
||||||
account->data.minecraftEntitlement.canPlayMinecraft = true;
|
account->data.minecraftEntitlement.canPlayMinecraft = true;
|
||||||
account->data.minecraftProfile.id = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
|
account->data.minecraftProfile.id = uuidFromUsername(username).toString().remove(QRegularExpression("[{}-]"));
|
||||||
account->data.minecraftProfile.name = username;
|
account->data.minecraftProfile.name = username;
|
||||||
account->data.minecraftProfile.validity = Katabasis::Validity::Certain;
|
account->data.minecraftProfile.validity = Katabasis::Validity::Certain;
|
||||||
return account;
|
return account;
|
||||||
@ -334,3 +335,32 @@ void MinecraftAccount::incrementUses()
|
|||||||
qWarning() << "Profile" << data.profileId() << "is now in use.";
|
qWarning() << "Profile" << data.profileId() << "is now in use.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QUuid MinecraftAccount::uuidFromUsername(QString username) {
|
||||||
|
auto input = QString("OfflinePlayer:%1").arg(username).toUtf8();
|
||||||
|
|
||||||
|
// basically a reimplementation of Java's UUID#nameUUIDFromBytes
|
||||||
|
QByteArray digest = QCryptographicHash::hash(input, QCryptographicHash::Md5);
|
||||||
|
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
auto bOr = [](QByteArray& array, int index, char value) {
|
||||||
|
array[index] = array.at(index) | value;
|
||||||
|
};
|
||||||
|
auto bAnd = [](QByteArray& array, int index, char value) {
|
||||||
|
array[index] = array.at(index) & value;
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
auto bOr = [](QByteArray& array, qsizetype index, char value) {
|
||||||
|
array[index] |= value;
|
||||||
|
};
|
||||||
|
auto bAnd = [](QByteArray& array, qsizetype index, char value) {
|
||||||
|
array[index] &= value;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
bAnd(digest, 6, (char) 0x0f); // clear version
|
||||||
|
bOr(digest, 6, (char) 0x30); // set to version 3
|
||||||
|
bAnd(digest, 8, (char) 0x3f); // clear variant
|
||||||
|
bOr(digest, 8, (char) 0x80); // set to IETF variant
|
||||||
|
|
||||||
|
return QUuid::fromRfc4122(digest);
|
||||||
|
}
|
||||||
|
@ -98,6 +98,8 @@ public: /* construction */
|
|||||||
static MinecraftAccountPtr loadFromJsonV2(const QJsonObject &json);
|
static MinecraftAccountPtr loadFromJsonV2(const QJsonObject &json);
|
||||||
static MinecraftAccountPtr loadFromJsonV3(const QJsonObject &json);
|
static MinecraftAccountPtr loadFromJsonV3(const QJsonObject &json);
|
||||||
|
|
||||||
|
static QUuid uuidFromUsername(QString username);
|
||||||
|
|
||||||
//! Saves a MinecraftAccount to a JSON object and returns it.
|
//! Saves a MinecraftAccount to a JSON object and returns it.
|
||||||
QJsonObject saveToJson() const;
|
QJsonObject saveToJson() const;
|
||||||
|
|
||||||
|
@ -1,166 +0,0 @@
|
|||||||
/* 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 "DirectJavaLaunch.h"
|
|
||||||
|
|
||||||
#include <QStandardPaths>
|
|
||||||
|
|
||||||
#include <launch/LaunchTask.h>
|
|
||||||
#include <minecraft/MinecraftInstance.h>
|
|
||||||
#include <FileSystem.h>
|
|
||||||
#include <Commandline.h>
|
|
||||||
|
|
||||||
#include "Application.h"
|
|
||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
|
||||||
#include "gamemode_client.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
DirectJavaLaunch::DirectJavaLaunch(LaunchTask *parent) : LaunchStep(parent)
|
|
||||||
{
|
|
||||||
connect(&m_process, &LoggedProcess::log, this, &DirectJavaLaunch::logLines);
|
|
||||||
connect(&m_process, &LoggedProcess::stateChanged, this, &DirectJavaLaunch::on_state);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DirectJavaLaunch::executeTask()
|
|
||||||
{
|
|
||||||
auto instance = m_parent->instance();
|
|
||||||
std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance);
|
|
||||||
QStringList args = minecraftInstance->javaArguments();
|
|
||||||
|
|
||||||
args.append("-Djava.library.path=" + minecraftInstance->getNativePath());
|
|
||||||
|
|
||||||
auto classPathEntries = minecraftInstance->getClassPath();
|
|
||||||
args.append("-cp");
|
|
||||||
QString classpath;
|
|
||||||
#ifdef Q_OS_WIN32
|
|
||||||
classpath = classPathEntries.join(';');
|
|
||||||
#else
|
|
||||||
classpath = classPathEntries.join(':');
|
|
||||||
#endif
|
|
||||||
args.append(classpath);
|
|
||||||
args.append(minecraftInstance->getMainClass());
|
|
||||||
|
|
||||||
QString allArgs = args.join(", ");
|
|
||||||
emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::Launcher);
|
|
||||||
|
|
||||||
auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString());
|
|
||||||
|
|
||||||
m_process.setProcessEnvironment(instance->createLaunchEnvironment());
|
|
||||||
|
|
||||||
// make detachable - this will keep the process running even if the object is destroyed
|
|
||||||
m_process.setDetachable(true);
|
|
||||||
|
|
||||||
auto mcArgs = minecraftInstance->processMinecraftArgs(m_session, m_serverToJoin);
|
|
||||||
args.append(mcArgs);
|
|
||||||
|
|
||||||
QString wrapperCommandStr = instance->getWrapperCommand().trimmed();
|
|
||||||
if(!wrapperCommandStr.isEmpty())
|
|
||||||
{
|
|
||||||
auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr);
|
|
||||||
auto wrapperCommand = wrapperArgs.takeFirst();
|
|
||||||
auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand);
|
|
||||||
if (realWrapperCommand.isEmpty())
|
|
||||||
{
|
|
||||||
const char *reason = QT_TR_NOOP("The wrapper command \"%1\" couldn't be found.");
|
|
||||||
emit logLine(QString(reason).arg(wrapperCommand), MessageLevel::Fatal);
|
|
||||||
emitFailed(tr(reason).arg(wrapperCommand));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
emit logLine("Wrapper command is:\n" + wrapperCommandStr + "\n\n", MessageLevel::Launcher);
|
|
||||||
args.prepend(javaPath);
|
|
||||||
m_process.start(wrapperCommand, wrapperArgs + args);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_process.start(javaPath, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
|
||||||
if (instance->settings()->get("EnableFeralGamemode").toBool() && APPLICATION->capabilities() & Application::SupportsGameMode)
|
|
||||||
{
|
|
||||||
auto pid = m_process.processId();
|
|
||||||
if (pid)
|
|
||||||
{
|
|
||||||
gamemode_request_start_for(pid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void DirectJavaLaunch::on_state(LoggedProcess::State state)
|
|
||||||
{
|
|
||||||
switch(state)
|
|
||||||
{
|
|
||||||
case LoggedProcess::FailedToStart:
|
|
||||||
{
|
|
||||||
//: Error message displayed if instance can't start
|
|
||||||
const char *reason = QT_TR_NOOP("Could not launch Minecraft!");
|
|
||||||
emit logLine(reason, MessageLevel::Fatal);
|
|
||||||
emitFailed(tr(reason));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case LoggedProcess::Aborted:
|
|
||||||
case LoggedProcess::Crashed:
|
|
||||||
{
|
|
||||||
m_parent->setPid(-1);
|
|
||||||
emitFailed(tr("Game crashed."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case LoggedProcess::Finished:
|
|
||||||
{
|
|
||||||
m_parent->setPid(-1);
|
|
||||||
// if the exit code wasn't 0, report this as a crash
|
|
||||||
auto exitCode = m_process.exitCode();
|
|
||||||
if(exitCode != 0)
|
|
||||||
{
|
|
||||||
emitFailed(tr("Game crashed."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//FIXME: make this work again
|
|
||||||
// m_postlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(exitCode));
|
|
||||||
// run post-exit
|
|
||||||
emitSucceeded();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case LoggedProcess::Running:
|
|
||||||
emit logLine(QString("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::Launcher);
|
|
||||||
m_parent->setPid(m_process.processId());
|
|
||||||
m_parent->instance()->setLastLaunch();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DirectJavaLaunch::setWorkingDirectory(const QString &wd)
|
|
||||||
{
|
|
||||||
m_process.setWorkingDirectory(wd);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DirectJavaLaunch::proceed()
|
|
||||||
{
|
|
||||||
// nil
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DirectJavaLaunch::abort()
|
|
||||||
{
|
|
||||||
auto state = m_process.state();
|
|
||||||
if (state == LoggedProcess::Running || state == LoggedProcess::Starting)
|
|
||||||
{
|
|
||||||
m_process.kill();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <launch/LaunchStep.h>
|
|
||||||
#include <LoggedProcess.h>
|
|
||||||
#include <minecraft/auth/AuthSession.h>
|
|
||||||
|
|
||||||
#include "MinecraftServerTarget.h"
|
|
||||||
|
|
||||||
class DirectJavaLaunch: public LaunchStep
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit DirectJavaLaunch(LaunchTask *parent);
|
|
||||||
virtual ~DirectJavaLaunch() {};
|
|
||||||
|
|
||||||
virtual void executeTask();
|
|
||||||
virtual bool abort();
|
|
||||||
virtual void proceed();
|
|
||||||
virtual bool canAbort() const
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
void setWorkingDirectory(const QString &wd);
|
|
||||||
void setAuthSession(AuthSessionPtr session)
|
|
||||||
{
|
|
||||||
m_session = session;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setServerToJoin(MinecraftServerTargetPtr serverToJoin)
|
|
||||||
{
|
|
||||||
m_serverToJoin = std::move(serverToJoin);
|
|
||||||
}
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void on_state(LoggedProcess::State state);
|
|
||||||
|
|
||||||
private:
|
|
||||||
LoggedProcess m_process;
|
|
||||||
QString m_command;
|
|
||||||
AuthSessionPtr m_session;
|
|
||||||
MinecraftServerTargetPtr m_serverToJoin;
|
|
||||||
};
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
|||||||
#include "LocalModParseTask.h"
|
#include "LocalModParseTask.h"
|
||||||
|
|
||||||
|
#include <qdcss.h>
|
||||||
#include <quazip/quazip.h>
|
#include <quazip/quazip.h>
|
||||||
#include <quazip/quazipfile.h>
|
#include <quazip/quazipfile.h>
|
||||||
#include <toml++/toml.h>
|
#include <toml++/toml.h>
|
||||||
#include <qdcss.h>
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
@ -369,12 +369,11 @@ ModDetails ReadQuiltModInfo(QByteArray contents)
|
|||||||
details.icon_file = icon.toString();
|
details.icon_file = icon.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return details;
|
return details;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModDetails ReadForgeInfo(QString fileName)
|
ModDetails ReadForgeInfo(QByteArray contents)
|
||||||
{
|
{
|
||||||
ModDetails details;
|
ModDetails details;
|
||||||
// Read the data
|
// Read the data
|
||||||
@ -382,7 +381,7 @@ ModDetails ReadForgeInfo(QString fileName)
|
|||||||
details.mod_id = "Forge";
|
details.mod_id = "Forge";
|
||||||
details.homeurl = "http://www.minecraftforge.net/forum/";
|
details.homeurl = "http://www.minecraftforge.net/forum/";
|
||||||
INIFile ini;
|
INIFile ini;
|
||||||
if (!ini.loadFile(fileName))
|
if (!ini.loadFile(contents))
|
||||||
return details;
|
return details;
|
||||||
|
|
||||||
QString major = ini.get("forge.major.number", "0").toString();
|
QString major = ini.get("forge.major.number", "0").toString();
|
||||||
@ -554,7 +553,7 @@ bool processZIP(Mod& mod, ProcessingLevel level)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
details = ReadForgeInfo(file.getFileName());
|
details = ReadForgeInfo(file.readAll());
|
||||||
file.close();
|
file.close();
|
||||||
zip.close();
|
zip.close();
|
||||||
|
|
||||||
|
@ -44,7 +44,8 @@ static const QMap<PackedResourceType, QString> s_packed_type_names = {
|
|||||||
namespace ResourceUtils {
|
namespace ResourceUtils {
|
||||||
PackedResourceType identify(QFileInfo file){
|
PackedResourceType identify(QFileInfo file){
|
||||||
if (file.exists() && file.isFile()) {
|
if (file.exists() && file.isFile()) {
|
||||||
if (ModUtils::validate(file)) { // Mods can also contain resource and data packs
|
if (ModUtils::validate(file)) {
|
||||||
|
// mods can contain resource and data packs so they must be tested first
|
||||||
qDebug() << file.fileName() << "is a mod";
|
qDebug() << file.fileName() << "is a mod";
|
||||||
return PackedResourceType::Mod;
|
return PackedResourceType::Mod;
|
||||||
} else if (ResourcePackUtils::validate(file)) {
|
} else if (ResourcePackUtils::validate(file)) {
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
|
||||||
|
#include "net/ApiDownload.h"
|
||||||
|
|
||||||
AssetUpdateTask::AssetUpdateTask(MinecraftInstance * inst)
|
AssetUpdateTask::AssetUpdateTask(MinecraftInstance * inst)
|
||||||
{
|
{
|
||||||
m_inst = inst;
|
m_inst = inst;
|
||||||
@ -34,7 +36,7 @@ void AssetUpdateTask::executeTask()
|
|||||||
entry->setStale(true);
|
entry->setStale(true);
|
||||||
auto hexSha1 = assets->sha1.toLatin1();
|
auto hexSha1 = assets->sha1.toLatin1();
|
||||||
qDebug() << "Asset index SHA1:" << hexSha1;
|
qDebug() << "Asset index SHA1:" << hexSha1;
|
||||||
auto dl = Net::Download::makeCached(indexUrl, entry);
|
auto dl = Net::ApiDownload::makeCached(indexUrl, entry);
|
||||||
auto rawSha1 = QByteArray::fromHex(assets->sha1.toLatin1());
|
auto rawSha1 = QByteArray::fromHex(assets->sha1.toLatin1());
|
||||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
|
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
|
||||||
job->addNetAction(dl);
|
job->addNetAction(dl);
|
||||||
@ -45,7 +47,7 @@ void AssetUpdateTask::executeTask()
|
|||||||
connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetIndexFailed);
|
connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetIndexFailed);
|
||||||
connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
|
connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
|
||||||
connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress);
|
connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress);
|
||||||
connect(downloadJob.get(), &NetJob::stepProgress, this, &AssetUpdateTask::propogateStepProgress);
|
connect(downloadJob.get(), &NetJob::stepProgress, this, &AssetUpdateTask::propagateStepProgress);
|
||||||
|
|
||||||
qDebug() << m_inst->name() << ": Starting asset index download";
|
qDebug() << m_inst->name() << ": Starting asset index download";
|
||||||
downloadJob->start();
|
downloadJob->start();
|
||||||
@ -84,7 +86,7 @@ void AssetUpdateTask::assetIndexFinished()
|
|||||||
connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetsFailed);
|
connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetsFailed);
|
||||||
connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
|
connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
|
||||||
connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress);
|
connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress);
|
||||||
connect(downloadJob.get(), &NetJob::stepProgress, this, &AssetUpdateTask::propogateStepProgress);
|
connect(downloadJob.get(), &NetJob::stepProgress, this, &AssetUpdateTask::propagateStepProgress);
|
||||||
downloadJob->start();
|
downloadJob->start();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
#include "BuildConfig.h"
|
#include "BuildConfig.h"
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
|
||||||
|
#include "net/ApiDownload.h"
|
||||||
|
|
||||||
FMLLibrariesTask::FMLLibrariesTask(MinecraftInstance * inst)
|
FMLLibrariesTask::FMLLibrariesTask(MinecraftInstance * inst)
|
||||||
{
|
{
|
||||||
m_inst = inst;
|
m_inst = inst;
|
||||||
@ -68,14 +70,14 @@ void FMLLibrariesTask::executeTask()
|
|||||||
{
|
{
|
||||||
auto entry = metacache->resolveEntry("fmllibs", lib.filename);
|
auto entry = metacache->resolveEntry("fmllibs", lib.filename);
|
||||||
QString urlString = BuildConfig.FMLLIBS_BASE_URL + lib.filename;
|
QString urlString = BuildConfig.FMLLIBS_BASE_URL + lib.filename;
|
||||||
dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry, options));
|
dljob->addNetAction(Net::ApiDownload::makeCached(QUrl(urlString), entry, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(dljob.get(), &NetJob::succeeded, this, &FMLLibrariesTask::fmllibsFinished);
|
connect(dljob.get(), &NetJob::succeeded, this, &FMLLibrariesTask::fmllibsFinished);
|
||||||
connect(dljob.get(), &NetJob::failed, this, &FMLLibrariesTask::fmllibsFailed);
|
connect(dljob.get(), &NetJob::failed, this, &FMLLibrariesTask::fmllibsFailed);
|
||||||
connect(dljob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
|
connect(dljob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
|
||||||
connect(dljob.get(), &NetJob::progress, this, &FMLLibrariesTask::progress);
|
connect(dljob.get(), &NetJob::progress, this, &FMLLibrariesTask::progress);
|
||||||
connect(dljob.get(), &NetJob::stepProgress, this, &FMLLibrariesTask::propogateStepProgress);
|
connect(dljob.get(), &NetJob::stepProgress, this, &FMLLibrariesTask::propagateStepProgress);
|
||||||
downloadJob.reset(dljob);
|
downloadJob.reset(dljob);
|
||||||
downloadJob->start();
|
downloadJob->start();
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ void LibrariesTask::executeTask()
|
|||||||
connect(downloadJob.get(), &NetJob::failed, this, &LibrariesTask::jarlibFailed);
|
connect(downloadJob.get(), &NetJob::failed, this, &LibrariesTask::jarlibFailed);
|
||||||
connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
|
connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
|
||||||
connect(downloadJob.get(), &NetJob::progress, this, &LibrariesTask::progress);
|
connect(downloadJob.get(), &NetJob::progress, this, &LibrariesTask::progress);
|
||||||
connect(downloadJob.get(), &NetJob::stepProgress, this, &LibrariesTask::propogateStepProgress);
|
connect(downloadJob.get(), &NetJob::stepProgress, this, &LibrariesTask::propagateStepProgress);
|
||||||
|
|
||||||
downloadJob->start();
|
downloadJob->start();
|
||||||
}
|
}
|
||||||
|
@ -145,7 +145,8 @@ void EnsureMetadataTask::executeTask()
|
|||||||
connect(project_task.get(), &Task::finished, this, [=] {
|
connect(project_task.get(), &Task::finished, this, [=] {
|
||||||
invalidade_leftover();
|
invalidade_leftover();
|
||||||
project_task->deleteLater();
|
project_task->deleteLater();
|
||||||
m_current_task = nullptr;
|
if (m_current_task)
|
||||||
|
m_current_task.reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
m_current_task = project_task;
|
m_current_task = project_task;
|
||||||
@ -154,7 +155,8 @@ void EnsureMetadataTask::executeTask()
|
|||||||
|
|
||||||
connect(version_task.get(), &Task::finished, [=] {
|
connect(version_task.get(), &Task::finished, [=] {
|
||||||
version_task->deleteLater();
|
version_task->deleteLater();
|
||||||
m_current_task = nullptr;
|
if (m_current_task)
|
||||||
|
m_current_task.reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (m_mods.size() > 1)
|
if (m_mods.size() > 1)
|
||||||
|
@ -53,6 +53,8 @@
|
|||||||
#include "meta/Version.h"
|
#include "meta/Version.h"
|
||||||
#include "meta/VersionList.h"
|
#include "meta/VersionList.h"
|
||||||
|
|
||||||
|
#include "net/ApiDownload.h"
|
||||||
|
|
||||||
#include "BuildConfig.h"
|
#include "BuildConfig.h"
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
|
||||||
@ -84,7 +86,7 @@ void PackInstallTask::executeTask()
|
|||||||
NetJob::Ptr netJob{ new NetJob("ATLauncher::VersionFetch", APPLICATION->network()) };
|
NetJob::Ptr netJob{ new NetJob("ATLauncher::VersionFetch", APPLICATION->network()) };
|
||||||
auto searchUrl =
|
auto searchUrl =
|
||||||
QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json").arg(m_pack_safe_name).arg(m_version_name);
|
QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json").arg(m_pack_safe_name).arg(m_version_name);
|
||||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
|
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));
|
||||||
|
|
||||||
QObject::connect(netJob.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
|
QObject::connect(netJob.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
|
||||||
QObject::connect(netJob.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
|
QObject::connect(netJob.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
|
||||||
@ -659,7 +661,7 @@ void PackInstallTask::installConfigs()
|
|||||||
auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", path);
|
auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", path);
|
||||||
entry->setStale(true);
|
entry->setStale(true);
|
||||||
|
|
||||||
auto dl = Net::Download::makeCached(url, entry);
|
auto dl = Net::ApiDownload::makeCached(url, entry);
|
||||||
if (!m_version.configs.sha1.isEmpty()) {
|
if (!m_version.configs.sha1.isEmpty()) {
|
||||||
auto rawSha1 = QByteArray::fromHex(m_version.configs.sha1.toLatin1());
|
auto rawSha1 = QByteArray::fromHex(m_version.configs.sha1.toLatin1());
|
||||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
|
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
|
||||||
@ -684,7 +686,7 @@ void PackInstallTask::installConfigs()
|
|||||||
abortable = true;
|
abortable = true;
|
||||||
setProgress(current, total);
|
setProgress(current, total);
|
||||||
});
|
});
|
||||||
connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress);
|
connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propagateStepProgress);
|
||||||
connect(jobPtr.get(), &NetJob::aborted, [&]{
|
connect(jobPtr.get(), &NetJob::aborted, [&]{
|
||||||
abortable = false;
|
abortable = false;
|
||||||
jobPtr.reset();
|
jobPtr.reset();
|
||||||
@ -782,7 +784,7 @@ void PackInstallTask::downloadMods()
|
|||||||
entry->setStale(true);
|
entry->setStale(true);
|
||||||
modsToExtract.insert(entry->getFullPath(), mod);
|
modsToExtract.insert(entry->getFullPath(), mod);
|
||||||
|
|
||||||
auto dl = Net::Download::makeCached(url, entry);
|
auto dl = Net::ApiDownload::makeCached(url, entry);
|
||||||
if (!mod.md5.isEmpty()) {
|
if (!mod.md5.isEmpty()) {
|
||||||
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
|
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
|
||||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
|
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
|
||||||
@ -794,7 +796,7 @@ void PackInstallTask::downloadMods()
|
|||||||
entry->setStale(true);
|
entry->setStale(true);
|
||||||
modsToDecomp.insert(entry->getFullPath(), mod);
|
modsToDecomp.insert(entry->getFullPath(), mod);
|
||||||
|
|
||||||
auto dl = Net::Download::makeCached(url, entry);
|
auto dl = Net::ApiDownload::makeCached(url, entry);
|
||||||
if (!mod.md5.isEmpty()) {
|
if (!mod.md5.isEmpty()) {
|
||||||
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
|
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
|
||||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
|
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
|
||||||
@ -808,7 +810,7 @@ void PackInstallTask::downloadMods()
|
|||||||
auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", cacheName);
|
auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", cacheName);
|
||||||
entry->setStale(true);
|
entry->setStale(true);
|
||||||
|
|
||||||
auto dl = Net::Download::makeCached(url, entry);
|
auto dl = Net::ApiDownload::makeCached(url, entry);
|
||||||
if (!mod.md5.isEmpty()) {
|
if (!mod.md5.isEmpty()) {
|
||||||
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
|
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
|
||||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
|
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
|
||||||
@ -852,7 +854,7 @@ void PackInstallTask::downloadMods()
|
|||||||
abortable = true;
|
abortable = true;
|
||||||
setProgress(current, total);
|
setProgress(current, total);
|
||||||
});
|
});
|
||||||
connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress);
|
connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propagateStepProgress);
|
||||||
connect(jobPtr.get(), &NetJob::aborted, [&]
|
connect(jobPtr.get(), &NetJob::aborted, [&]
|
||||||
{
|
{
|
||||||
abortable = false;
|
abortable = false;
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
#include "FileResolvingTask.h"
|
#include "FileResolvingTask.h"
|
||||||
|
|
||||||
#include "Json.h"
|
#include "Json.h"
|
||||||
|
#include "net/ApiUpload.h"
|
||||||
#include "net/Upload.h"
|
#include "net/Upload.h"
|
||||||
|
#include "net/ApiDownload.h"
|
||||||
|
|
||||||
#include "modplatform/modrinth/ModrinthPackIndex.h"
|
#include "modplatform/modrinth/ModrinthPackIndex.h"
|
||||||
|
|
||||||
@ -21,6 +23,10 @@ bool Flame::FileResolvingTask::abort()
|
|||||||
|
|
||||||
void Flame::FileResolvingTask::executeTask()
|
void Flame::FileResolvingTask::executeTask()
|
||||||
{
|
{
|
||||||
|
if (m_toProcess.files.isEmpty()) { // no file to resolve so leave it empty and emit success immediately
|
||||||
|
emitSucceeded();
|
||||||
|
return;
|
||||||
|
}
|
||||||
setStatus(tr("Resolving mod IDs..."));
|
setStatus(tr("Resolving mod IDs..."));
|
||||||
setProgress(0, 3);
|
setProgress(0, 3);
|
||||||
m_dljob.reset(new NetJob("Mod id resolver", m_network));
|
m_dljob.reset(new NetJob("Mod id resolver", m_network));
|
||||||
@ -34,7 +40,7 @@ void Flame::FileResolvingTask::executeTask()
|
|||||||
return l;
|
return l;
|
||||||
}));
|
}));
|
||||||
QByteArray data = Json::toText(object);
|
QByteArray data = Json::toText(object);
|
||||||
auto dl = Net::Upload::makeByteArray(QUrl("https://api.curseforge.com/v1/mods/files"), result, data);
|
auto dl = Net::ApiUpload::makeByteArray(QUrl("https://api.curseforge.com/v1/mods/files"), result, data);
|
||||||
m_dljob->addNetAction(dl);
|
m_dljob->addNetAction(dl);
|
||||||
|
|
||||||
auto step_progress = std::make_shared<TaskStepProgress>();
|
auto step_progress = std::make_shared<TaskStepProgress>();
|
||||||
@ -48,7 +54,7 @@ void Flame::FileResolvingTask::executeTask()
|
|||||||
stepProgress(*step_progress);
|
stepProgress(*step_progress);
|
||||||
emitFailed(reason);
|
emitFailed(reason);
|
||||||
});
|
});
|
||||||
connect(m_dljob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propogateStepProgress);
|
connect(m_dljob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propagateStepProgress);
|
||||||
connect(m_dljob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) {
|
connect(m_dljob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) {
|
||||||
qDebug() << "Resolve slug progress" << current << total;
|
qDebug() << "Resolve slug progress" << current << total;
|
||||||
step_progress->update(current, total);
|
step_progress->update(current, total);
|
||||||
@ -95,7 +101,7 @@ void Flame::FileResolvingTask::netJobFinished()
|
|||||||
if (!hash.isEmpty()) {
|
if (!hash.isEmpty()) {
|
||||||
auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash);
|
auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash);
|
||||||
auto output = std::make_shared<QByteArray>();
|
auto output = std::make_shared<QByteArray>();
|
||||||
auto dl = Net::Download::makeByteArray(QUrl(url), output);
|
auto dl = Net::ApiDownload::makeByteArray(QUrl(url), output);
|
||||||
QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() { out.resolved = true; });
|
QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() { out.resolved = true; });
|
||||||
|
|
||||||
m_checkJob->addNetAction(dl);
|
m_checkJob->addNetAction(dl);
|
||||||
@ -114,7 +120,7 @@ void Flame::FileResolvingTask::netJobFinished()
|
|||||||
stepProgress(*step_progress);
|
stepProgress(*step_progress);
|
||||||
emitFailed(reason);
|
emitFailed(reason);
|
||||||
});
|
});
|
||||||
connect(m_checkJob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propogateStepProgress);
|
connect(m_checkJob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propagateStepProgress);
|
||||||
connect(m_checkJob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) {
|
connect(m_checkJob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) {
|
||||||
qDebug() << "Resolve slug progress" << current << total;
|
qDebug() << "Resolve slug progress" << current << total;
|
||||||
step_progress->update(current, total);
|
step_progress->update(current, total);
|
||||||
@ -128,7 +134,8 @@ void Flame::FileResolvingTask::netJobFinished()
|
|||||||
m_checkJob->start();
|
m_checkJob->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Flame::FileResolvingTask::modrinthCheckFinished() {
|
void Flame::FileResolvingTask::modrinthCheckFinished()
|
||||||
|
{
|
||||||
setProgress(2, 3);
|
setProgress(2, 3);
|
||||||
qDebug() << "Finished with blocked mods : " << blockedProjects.size();
|
qDebug() << "Finished with blocked mods : " << blockedProjects.size();
|
||||||
|
|
||||||
@ -156,9 +163,7 @@ void Flame::FileResolvingTask::modrinthCheckFinished() {
|
|||||||
// copy to an output list and filter out projects found on modrinth
|
// copy to an output list and filter out projects found on modrinth
|
||||||
auto block = std::make_shared<QList<File*>>();
|
auto block = std::make_shared<QList<File*>>();
|
||||||
auto it = blockedProjects.keys();
|
auto it = blockedProjects.keys();
|
||||||
std::copy_if(it.begin(), it.end(), std::back_inserter(*block), [](File *f) {
|
std::copy_if(it.begin(), it.end(), std::back_inserter(*block), [](File* f) { return !f->resolved; });
|
||||||
return !f->resolved;
|
|
||||||
});
|
|
||||||
// Display not found mods early
|
// Display not found mods early
|
||||||
if (!block->empty()) {
|
if (!block->empty()) {
|
||||||
// blocked mods found, we need the slug for displaying.... we need another job :D !
|
// blocked mods found, we need the slug for displaying.... we need another job :D !
|
||||||
@ -168,13 +173,13 @@ void Flame::FileResolvingTask::modrinthCheckFinished() {
|
|||||||
auto projectId = mod->projectId;
|
auto projectId = mod->projectId;
|
||||||
auto output = std::make_shared<QByteArray>();
|
auto output = std::make_shared<QByteArray>();
|
||||||
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId);
|
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId);
|
||||||
auto dl = Net::Download::makeByteArray(url, output);
|
auto dl = Net::ApiDownload::makeByteArray(url, output);
|
||||||
qDebug() << "Fetching url slug for file:" << mod->fileName;
|
qDebug() << "Fetching url slug for file:" << mod->fileName;
|
||||||
QObject::connect(dl.get(), &Net::Download::succeeded, [block, index, output]() {
|
QObject::connect(dl.get(), &Net::Download::succeeded, [block, index, output]() {
|
||||||
auto mod = block->at(index); // use the shared_ptr so it is captured and only freed when we are done
|
auto mod = block->at(index); // use the shared_ptr so it is captured and only freed when we are done
|
||||||
auto json = QJsonDocument::fromJson(*output);
|
auto json = QJsonDocument::fromJson(*output);
|
||||||
auto base = Json::requireString(Json::requireObject(Json::requireObject(Json::requireObject(json),"data"),"links"),
|
auto base =
|
||||||
"websiteUrl");
|
Json::requireString(Json::requireObject(Json::requireObject(Json::requireObject(json), "data"), "links"), "websiteUrl");
|
||||||
auto link = QString("%1/download/%2").arg(base, QString::number(mod->fileId));
|
auto link = QString("%1/download/%2").arg(base, QString::number(mod->fileId));
|
||||||
mod->websiteUrl = link;
|
mod->websiteUrl = link;
|
||||||
});
|
});
|
||||||
@ -192,7 +197,7 @@ void Flame::FileResolvingTask::modrinthCheckFinished() {
|
|||||||
stepProgress(*step_progress);
|
stepProgress(*step_progress);
|
||||||
emitFailed(reason);
|
emitFailed(reason);
|
||||||
});
|
});
|
||||||
connect(m_slugJob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propogateStepProgress);
|
connect(m_slugJob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propagateStepProgress);
|
||||||
connect(m_slugJob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) {
|
connect(m_slugJob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) {
|
||||||
qDebug() << "Resolve slug progress" << current << total;
|
qDebug() << "Resolve slug progress" << current << total;
|
||||||
step_progress->update(current, total);
|
step_progress->update(current, total);
|
||||||
|
@ -8,7 +8,9 @@
|
|||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "BuildConfig.h"
|
#include "BuildConfig.h"
|
||||||
#include "Json.h"
|
#include "Json.h"
|
||||||
|
#include "net/ApiUpload.h"
|
||||||
#include "net/NetJob.h"
|
#include "net/NetJob.h"
|
||||||
|
#include "net/ApiDownload.h"
|
||||||
#include "net/Upload.h"
|
#include "net/Upload.h"
|
||||||
|
|
||||||
Task::Ptr FlameAPI::matchFingerprints(const QList<uint>& fingerprints, std::shared_ptr<QByteArray> response)
|
Task::Ptr FlameAPI::matchFingerprints(const QList<uint>& fingerprints, std::shared_ptr<QByteArray> response)
|
||||||
@ -26,7 +28,7 @@ Task::Ptr FlameAPI::matchFingerprints(const QList<uint>& fingerprints, std::shar
|
|||||||
QJsonDocument body(body_obj);
|
QJsonDocument body(body_obj);
|
||||||
auto body_raw = body.toJson();
|
auto body_raw = body.toJson();
|
||||||
|
|
||||||
netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/fingerprints"), response, body_raw));
|
netJob->addNetAction(Net::ApiUpload::makeByteArray(QString("https://api.curseforge.com/v1/fingerprints"), response, body_raw));
|
||||||
|
|
||||||
return netJob;
|
return netJob;
|
||||||
}
|
}
|
||||||
@ -38,7 +40,7 @@ auto FlameAPI::getModFileChangelog(int modId, int fileId) -> QString
|
|||||||
|
|
||||||
auto netJob = makeShared<NetJob>(QString("Flame::FileChangelog"), APPLICATION->network());
|
auto netJob = makeShared<NetJob>(QString("Flame::FileChangelog"), APPLICATION->network());
|
||||||
auto response = std::make_shared<QByteArray>();
|
auto response = std::make_shared<QByteArray>();
|
||||||
netJob->addNetAction(Net::Download::makeByteArray(
|
netJob->addNetAction(Net::ApiDownload::makeByteArray(
|
||||||
QString("https://api.curseforge.com/v1/mods/%1/files/%2/changelog")
|
QString("https://api.curseforge.com/v1/mods/%1/files/%2/changelog")
|
||||||
.arg(QString::fromStdString(std::to_string(modId)), QString::fromStdString(std::to_string(fileId))),
|
.arg(QString::fromStdString(std::to_string(modId)), QString::fromStdString(std::to_string(fileId))),
|
||||||
response));
|
response));
|
||||||
@ -74,7 +76,7 @@ auto FlameAPI::getModDescription(int modId) -> QString
|
|||||||
auto netJob = makeShared<NetJob>(QString("Flame::ModDescription"), APPLICATION->network());
|
auto netJob = makeShared<NetJob>(QString("Flame::ModDescription"), APPLICATION->network());
|
||||||
auto response = std::make_shared<QByteArray>();
|
auto response = std::make_shared<QByteArray>();
|
||||||
netJob->addNetAction(
|
netJob->addNetAction(
|
||||||
Net::Download::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/description").arg(QString::number(modId)), response));
|
Net::ApiDownload::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/description").arg(QString::number(modId)), response));
|
||||||
|
|
||||||
QObject::connect(netJob.get(), &NetJob::succeeded, [&netJob, response, &description] {
|
QObject::connect(netJob.get(), &NetJob::succeeded, [&netJob, response, &description] {
|
||||||
QJsonParseError parse_error{};
|
QJsonParseError parse_error{};
|
||||||
@ -113,7 +115,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe
|
|||||||
auto response = std::make_shared<QByteArray>();
|
auto response = std::make_shared<QByteArray>();
|
||||||
ModPlatform::IndexedVersion ver;
|
ModPlatform::IndexedVersion ver;
|
||||||
|
|
||||||
netJob->addNetAction(Net::Download::makeByteArray(versions_url, response));
|
netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response));
|
||||||
|
|
||||||
QObject::connect(netJob.get(), &NetJob::succeeded, [response, args, &ver] {
|
QObject::connect(netJob.get(), &NetJob::succeeded, [response, args, &ver] {
|
||||||
QJsonParseError parse_error{};
|
QJsonParseError parse_error{};
|
||||||
@ -173,7 +175,7 @@ Task::Ptr FlameAPI::getProjects(QStringList addonIds, std::shared_ptr<QByteArray
|
|||||||
QJsonDocument body(body_obj);
|
QJsonDocument body(body_obj);
|
||||||
auto body_raw = body.toJson();
|
auto body_raw = body.toJson();
|
||||||
|
|
||||||
netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods"), response, body_raw));
|
netJob->addNetAction(Net::ApiUpload::makeByteArray(QString("https://api.curseforge.com/v1/mods"), response, body_raw));
|
||||||
|
|
||||||
QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; });
|
QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; });
|
||||||
|
|
||||||
@ -195,7 +197,7 @@ Task::Ptr FlameAPI::getFiles(const QStringList& fileIds, std::shared_ptr<QByteAr
|
|||||||
QJsonDocument body(body_obj);
|
QJsonDocument body(body_obj);
|
||||||
auto body_raw = body.toJson();
|
auto body_raw = body.toJson();
|
||||||
|
|
||||||
netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods/files"), response, body_raw));
|
netJob->addNetAction(Net::ApiUpload::makeByteArray(QString("https://api.curseforge.com/v1/mods/files"), response, body_raw));
|
||||||
|
|
||||||
QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; });
|
QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; });
|
||||||
|
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
#include "minecraft/mod/ModFolderModel.h"
|
#include "minecraft/mod/ModFolderModel.h"
|
||||||
#include "minecraft/mod/ResourceFolderModel.h"
|
#include "minecraft/mod/ResourceFolderModel.h"
|
||||||
|
|
||||||
|
#include "net/ApiDownload.h"
|
||||||
|
|
||||||
static FlameAPI api;
|
static FlameAPI api;
|
||||||
|
|
||||||
bool FlameCheckUpdate::abort()
|
bool FlameCheckUpdate::abort()
|
||||||
@ -33,7 +35,7 @@ ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info)
|
|||||||
|
|
||||||
auto response = std::make_shared<QByteArray>();
|
auto response = std::make_shared<QByteArray>();
|
||||||
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(ver_info.addonId.toString());
|
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(ver_info.addonId.toString());
|
||||||
auto dl = Net::Download::makeByteArray(url, response);
|
auto dl = Net::ApiDownload::makeByteArray(url, response);
|
||||||
get_project_job->addNetAction(dl);
|
get_project_job->addNetAction(dl);
|
||||||
|
|
||||||
QObject::connect(get_project_job, &NetJob::succeeded, [response, &pack]() {
|
QObject::connect(get_project_job, &NetJob::succeeded, [response, &pack]() {
|
||||||
@ -77,7 +79,7 @@ ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId)
|
|||||||
|
|
||||||
auto response = std::make_shared<QByteArray>();
|
auto response = std::make_shared<QByteArray>();
|
||||||
auto url = QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(QString::number(addonId), QString::number(fileId));
|
auto url = QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(QString::number(addonId), QString::number(fileId));
|
||||||
auto dl = Net::Download::makeByteArray(url, response);
|
auto dl = Net::ApiDownload::makeByteArray(url, response);
|
||||||
get_file_info_job->addNetAction(dl);
|
get_file_info_job->addNetAction(dl);
|
||||||
|
|
||||||
QObject::connect(get_file_info_job, &NetJob::succeeded, [response, &ver]() {
|
QObject::connect(get_file_info_job, &NetJob::succeeded, [response, &ver]() {
|
||||||
|
@ -57,14 +57,11 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
|
||||||
|
#include "meta/Index.h"
|
||||||
|
#include "meta/VersionList.h"
|
||||||
#include "minecraft/World.h"
|
#include "minecraft/World.h"
|
||||||
#include "minecraft/mod/tasks/LocalResourceParse.h"
|
#include "minecraft/mod/tasks/LocalResourceParse.h"
|
||||||
|
#include "net/ApiDownload.h"
|
||||||
|
|
||||||
const static QMap<QString, QString> forgemap = { { "1.2.5", "3.4.9.171" },
|
|
||||||
{ "1.4.2", "6.0.1.355" },
|
|
||||||
{ "1.4.7", "6.6.2.534" },
|
|
||||||
{ "1.5.2", "7.8.1.737" } };
|
|
||||||
|
|
||||||
static const FlameAPI api;
|
static const FlameAPI api;
|
||||||
|
|
||||||
@ -259,6 +256,56 @@ bool FlameCreationTask::updateInstance()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString FlameCreationTask::getVersionForLoader(QString uid, QString loaderType, QString loaderVersion, QString mcVersion)
|
||||||
|
{
|
||||||
|
if (loaderVersion == "recommended") {
|
||||||
|
auto vlist = APPLICATION->metadataIndex()->get(uid);
|
||||||
|
if (!vlist) {
|
||||||
|
setError(tr("Failed to get local metadata index for %1").arg(uid));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!vlist->isLoaded()) {
|
||||||
|
QEventLoop loadVersionLoop;
|
||||||
|
auto task = vlist->getLoadTask();
|
||||||
|
connect(task.get(), &Task::finished, &loadVersionLoop, &QEventLoop::quit);
|
||||||
|
if (!task->isRunning())
|
||||||
|
task->start();
|
||||||
|
|
||||||
|
loadVersionLoop.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto version : vlist->versions()) {
|
||||||
|
// first recommended build we find, we use.
|
||||||
|
if (!version->isRecommended())
|
||||||
|
continue;
|
||||||
|
auto reqs = version->requiredSet();
|
||||||
|
|
||||||
|
// filter by minecraft version, if the loader depends on a certain version.
|
||||||
|
// not all mod loaders depend on a given Minecraft version, so we won't do this
|
||||||
|
// filtering for those loaders.
|
||||||
|
if (loaderType == "forge") {
|
||||||
|
auto iter = std::find_if(reqs.begin(), reqs.end(), [mcVersion](const Meta::Require& req) {
|
||||||
|
return req.uid == "net.minecraft" && req.equalsVersion == mcVersion;
|
||||||
|
});
|
||||||
|
if (iter == reqs.end())
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return version->descriptor();
|
||||||
|
}
|
||||||
|
|
||||||
|
setError(tr("Failed to find version for %1 loader").arg(loaderType));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loaderVersion.isEmpty()) {
|
||||||
|
emitFailed(tr("No loader version set for modpack!"));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return loaderVersion;
|
||||||
|
}
|
||||||
|
|
||||||
bool FlameCreationTask::createInstance()
|
bool FlameCreationTask::createInstance()
|
||||||
{
|
{
|
||||||
QEventLoop loop;
|
QEventLoop loop;
|
||||||
@ -297,22 +344,29 @@ bool FlameCreationTask::createInstance()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString forgeVersion;
|
QString loaderType;
|
||||||
QString fabricVersion;
|
QString loaderUid;
|
||||||
// TODO: is Quilt relevant here?
|
QString loaderVersion;
|
||||||
|
|
||||||
for (auto& loader : m_pack.minecraft.modLoaders) {
|
for (auto& loader : m_pack.minecraft.modLoaders) {
|
||||||
auto id = loader.id;
|
auto id = loader.id;
|
||||||
if (id.startsWith("forge-")) {
|
if (id.startsWith("forge-")) {
|
||||||
id.remove("forge-");
|
id.remove("forge-");
|
||||||
forgeVersion = id;
|
loaderType = "forge";
|
||||||
continue;
|
loaderUid = "net.minecraftforge";
|
||||||
}
|
} else if (loaderType == "fabric") {
|
||||||
if (id.startsWith("fabric-")) {
|
|
||||||
id.remove("fabric-");
|
id.remove("fabric-");
|
||||||
fabricVersion = id;
|
loaderType = "fabric";
|
||||||
|
loaderUid = "net.fabricmc.fabric-loader";
|
||||||
|
} else if (loaderType == "quilt") {
|
||||||
|
id.remove("quilt-");
|
||||||
|
loaderType = "quilt";
|
||||||
|
loaderUid = "org.quiltmc.quilt-loader";
|
||||||
|
} else {
|
||||||
|
logWarning(tr("Unknown mod loader in manifest: %1").arg(id));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
logWarning(tr("Unknown mod loader in manifest: %1").arg(id));
|
loaderVersion = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
|
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
|
||||||
@ -329,19 +383,12 @@ bool FlameCreationTask::createInstance()
|
|||||||
auto components = instance.getPackProfile();
|
auto components = instance.getPackProfile();
|
||||||
components->buildingFromScratch();
|
components->buildingFromScratch();
|
||||||
components->setComponentVersion("net.minecraft", mcVersion, true);
|
components->setComponentVersion("net.minecraft", mcVersion, true);
|
||||||
if (!forgeVersion.isEmpty()) {
|
if (!loaderType.isEmpty()) {
|
||||||
// FIXME: dirty, nasty, hack. Proper solution requires dependency resolution and knowledge of the metadata.
|
auto version = getVersionForLoader(loaderUid, loaderType, loaderVersion, mcVersion);
|
||||||
if (forgeVersion == "recommended") {
|
if (version.isEmpty())
|
||||||
if (forgemap.contains(mcVersion)) {
|
return false;
|
||||||
forgeVersion = forgemap[mcVersion];
|
components->setComponentVersion(loaderUid, version);
|
||||||
} else {
|
|
||||||
logWarning(tr("Could not map recommended Forge version for Minecraft %1").arg(mcVersion));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
components->setComponentVersion("net.minecraftforge", forgeVersion);
|
|
||||||
}
|
|
||||||
if (!fabricVersion.isEmpty())
|
|
||||||
components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion);
|
|
||||||
|
|
||||||
if (m_instIcon != "default") {
|
if (m_instIcon != "default") {
|
||||||
instance.setIconKey(m_instIcon);
|
instance.setIconKey(m_instIcon);
|
||||||
@ -386,7 +433,7 @@ bool FlameCreationTask::createInstance()
|
|||||||
});
|
});
|
||||||
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);
|
||||||
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::stepProgress, this, &FlameCreationTask::propogateStepProgress);
|
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::stepProgress, this, &FlameCreationTask::propagateStepProgress);
|
||||||
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::details, this, &FlameCreationTask::setDetails);
|
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::details, this, &FlameCreationTask::setDetails);
|
||||||
m_mod_id_resolver->start();
|
m_mod_id_resolver->start();
|
||||||
|
|
||||||
@ -477,7 +524,7 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
|
|||||||
case Flame::File::Type::Mod: {
|
case Flame::File::Type::Mod: {
|
||||||
if (!result.url.isEmpty()) {
|
if (!result.url.isEmpty()) {
|
||||||
qDebug() << "Will download" << result.url << "to" << path;
|
qDebug() << "Will download" << result.url << "to" << path;
|
||||||
auto dl = Net::Download::makeFile(result.url, path);
|
auto dl = Net::ApiDownload::makeFile(result.url, path);
|
||||||
m_files_job->addNetAction(dl);
|
m_files_job->addNetAction(dl);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -506,7 +553,7 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
|
|||||||
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
|
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
|
||||||
setProgress(current, total);
|
setProgress(current, total);
|
||||||
});
|
});
|
||||||
connect(m_files_job.get(), &NetJob::stepProgress, this, &FlameCreationTask::propogateStepProgress);
|
connect(m_files_job.get(), &NetJob::stepProgress, this, &FlameCreationTask::propagateStepProgress);
|
||||||
connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit);
|
connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit);
|
||||||
|
|
||||||
setStatus(tr("Downloading mods..."));
|
setStatus(tr("Downloading mods..."));
|
||||||
@ -545,7 +592,6 @@ void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods)
|
|||||||
setAbortable(true);
|
setAbortable(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void FlameCreationTask::validateZIPResouces()
|
void FlameCreationTask::validateZIPResouces()
|
||||||
{
|
{
|
||||||
qDebug() << "Validating whether resources stored as .zip are in the right place";
|
qDebug() << "Validating whether resources stored as .zip are in the right place";
|
||||||
@ -563,6 +609,8 @@ void FlameCreationTask::validateZIPResouces()
|
|||||||
if (FS::move(localPath, destPath)) {
|
if (FS::move(localPath, destPath)) {
|
||||||
return destPath;
|
return destPath;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
qDebug() << "Target folder of" << fileName << "is correct at" << targetFolder;
|
||||||
}
|
}
|
||||||
return localPath;
|
return localPath;
|
||||||
};
|
};
|
||||||
@ -584,6 +632,9 @@ void FlameCreationTask::validateZIPResouces()
|
|||||||
QString worldPath;
|
QString worldPath;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
case PackedResourceType::Mod:
|
||||||
|
validatePath(fileName, targetFolder, "mods");
|
||||||
|
break;
|
||||||
case PackedResourceType::ResourcePack:
|
case PackedResourceType::ResourcePack:
|
||||||
validatePath(fileName, targetFolder, "resourcepacks");
|
validatePath(fileName, targetFolder, "resourcepacks");
|
||||||
break;
|
break;
|
||||||
@ -593,9 +644,6 @@ void FlameCreationTask::validateZIPResouces()
|
|||||||
case PackedResourceType::DataPack:
|
case PackedResourceType::DataPack:
|
||||||
validatePath(fileName, targetFolder, "datapacks");
|
validatePath(fileName, targetFolder, "datapacks");
|
||||||
break;
|
break;
|
||||||
case PackedResourceType::Mod :
|
|
||||||
validatePath(fileName, targetFolder, "mods");
|
|
||||||
break;
|
|
||||||
case PackedResourceType::ShaderPack:
|
case PackedResourceType::ShaderPack:
|
||||||
// in theroy flame API can't do this but who knows, that *may* change ?
|
// in theroy flame API can't do this but who knows, that *may* change ?
|
||||||
// better to handle it if it *does* occure in the future
|
// better to handle it if it *does* occure in the future
|
||||||
|
@ -57,10 +57,7 @@ class FlameCreationTask final : public InstanceCreationTask {
|
|||||||
QString id,
|
QString id,
|
||||||
QString version_id,
|
QString version_id,
|
||||||
QString original_instance_id = {})
|
QString original_instance_id = {})
|
||||||
: InstanceCreationTask()
|
: InstanceCreationTask(), m_parent(parent), m_managed_id(std::move(id)), m_managed_version_id(std::move(version_id))
|
||||||
, 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);
|
||||||
@ -78,6 +75,7 @@ class FlameCreationTask final : public InstanceCreationTask {
|
|||||||
void setupDownloadJob(QEventLoop&);
|
void setupDownloadJob(QEventLoop&);
|
||||||
void copyBlockedMods(QList<BlockedMod> const& blocked_mods);
|
void copyBlockedMods(QList<BlockedMod> const& blocked_mods);
|
||||||
void validateZIPResouces();
|
void validateZIPResouces();
|
||||||
|
QString getVersionForLoader(QString uid, QString loaderType, QString version, QString mcVersion);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QWidget* m_parent = nullptr;
|
QWidget* m_parent = nullptr;
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QtConcurrentRun>
|
#include <QtConcurrentRun>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <iterator>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "Json.h"
|
#include "Json.h"
|
||||||
#include "MMCZip.h"
|
#include "MMCZip.h"
|
||||||
@ -64,20 +65,11 @@ void FlamePackExportTask::executeTask()
|
|||||||
|
|
||||||
bool FlamePackExportTask::abort()
|
bool FlamePackExportTask::abort()
|
||||||
{
|
{
|
||||||
if (task != nullptr) {
|
if (task) {
|
||||||
task->abort();
|
task->abort();
|
||||||
task = nullptr;
|
|
||||||
emitAborted();
|
emitAborted();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buildZipFuture.isRunning()) {
|
|
||||||
buildZipFuture.cancel();
|
|
||||||
// NOTE: Here we don't do `emitAborted()` because it will be done when `buildZipFuture` actually cancels, which may not occur
|
|
||||||
// immediately.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,7 +158,7 @@ void FlamePackExportTask::collectHashes()
|
|||||||
stepProgress(*progressStep);
|
stepProgress(*progressStep);
|
||||||
emitFailed(reason);
|
emitFailed(reason);
|
||||||
});
|
});
|
||||||
connect(hashingTask.get(), &Task::stepProgress, this, &FlamePackExportTask::propogateStepProgress);
|
connect(hashingTask.get(), &Task::stepProgress, this, &FlamePackExportTask::propagateStepProgress);
|
||||||
|
|
||||||
connect(hashingTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) {
|
connect(hashingTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) {
|
||||||
progressStep->update(current, total);
|
progressStep->update(current, total);
|
||||||
@ -336,89 +328,40 @@ void FlamePackExportTask::buildZip()
|
|||||||
setStatus(tr("Adding files..."));
|
setStatus(tr("Adding files..."));
|
||||||
setProgress(4, 5);
|
setProgress(4, 5);
|
||||||
|
|
||||||
buildZipFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]() {
|
auto zipTask = makeShared<MMCZip::ExportToZipTask>(output, gameRoot, files, "overrides/", true);
|
||||||
QuaZip zip(output);
|
zipTask->addExtraFile("manifest.json", generateIndex());
|
||||||
if (!zip.open(QuaZip::mdCreate)) {
|
zipTask->addExtraFile("modlist.html", generateHTML());
|
||||||
QFile::remove(output);
|
|
||||||
return BuildZipResult(tr("Could not create file"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buildZipFuture.isCanceled())
|
QStringList exclude;
|
||||||
return BuildZipResult();
|
std::transform(resolvedFiles.keyBegin(), resolvedFiles.keyEnd(), std::back_insert_iterator(exclude),
|
||||||
|
[this](QString file) { return gameRoot.relativeFilePath(file); });
|
||||||
QuaZipFile indexFile(&zip);
|
zipTask->setExcludeFiles(exclude);
|
||||||
if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("manifest.json"))) {
|
|
||||||
QFile::remove(output);
|
|
||||||
return BuildZipResult(tr("Could not create index"));
|
|
||||||
}
|
|
||||||
indexFile.write(generateIndex());
|
|
||||||
|
|
||||||
QuaZipFile modlist(&zip);
|
|
||||||
if (!modlist.open(QIODevice::WriteOnly, QuaZipNewInfo("modlist.html"))) {
|
|
||||||
QFile::remove(output);
|
|
||||||
return BuildZipResult(tr("Could not create index"));
|
|
||||||
}
|
|
||||||
QString content = "";
|
|
||||||
for (auto mod : resolvedFiles) {
|
|
||||||
if (mod.isMod) {
|
|
||||||
content += QString(TEMPLATE)
|
|
||||||
.replace("{name}", mod.name.toHtmlEscaped())
|
|
||||||
.replace("{url}", ModPlatform::getMetaURL(ModPlatform::ResourceProvider::FLAME, mod.addonId).toHtmlEscaped())
|
|
||||||
.replace("{authors}", !mod.authors.isEmpty() ? QString(" (by %1)").arg(mod.authors).toHtmlEscaped() : "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
content = "<ul>" + content + "</ul>";
|
|
||||||
modlist.write(content.toUtf8());
|
|
||||||
|
|
||||||
auto progressStep = std::make_shared<TaskStepProgress>();
|
auto progressStep = std::make_shared<TaskStepProgress>();
|
||||||
|
connect(zipTask.get(), &Task::finished, this, [this, progressStep] {
|
||||||
size_t progress = 0;
|
|
||||||
for (const QFileInfo& file : files) {
|
|
||||||
if (buildZipFuture.isCanceled()) {
|
|
||||||
QFile::remove(output);
|
|
||||||
progressStep->state = TaskStepState::Failed;
|
|
||||||
stepProgress(*progressStep);
|
|
||||||
return BuildZipResult();
|
|
||||||
}
|
|
||||||
progressStep->update(progress, files.length());
|
|
||||||
stepProgress(*progressStep);
|
|
||||||
|
|
||||||
const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath());
|
|
||||||
if (!resolvedFiles.contains(file.absoluteFilePath()) &&
|
|
||||||
!JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) {
|
|
||||||
QFile::remove(output);
|
|
||||||
return BuildZipResult(tr("Could not read and compress %1").arg(relative));
|
|
||||||
}
|
|
||||||
progress++;
|
|
||||||
}
|
|
||||||
|
|
||||||
zip.close();
|
|
||||||
|
|
||||||
if (zip.getZipError() != 0) {
|
|
||||||
QFile::remove(output);
|
|
||||||
progressStep->state = TaskStepState::Failed;
|
|
||||||
stepProgress(*progressStep);
|
|
||||||
return BuildZipResult(tr("A zip error occurred"));
|
|
||||||
}
|
|
||||||
progressStep->state = TaskStepState::Succeeded;
|
progressStep->state = TaskStepState::Succeeded;
|
||||||
stepProgress(*progressStep);
|
stepProgress(*progressStep);
|
||||||
return BuildZipResult();
|
|
||||||
});
|
});
|
||||||
connect(&buildZipWatcher, &QFutureWatcher<BuildZipResult>::finished, this, &FlamePackExportTask::finish);
|
|
||||||
buildZipWatcher.setFuture(buildZipFuture);
|
|
||||||
}
|
|
||||||
|
|
||||||
void FlamePackExportTask::finish()
|
connect(zipTask.get(), &Task::succeeded, this, &FlamePackExportTask::emitSucceeded);
|
||||||
{
|
connect(zipTask.get(), &Task::aborted, this, &FlamePackExportTask::emitAborted);
|
||||||
if (buildZipFuture.isCanceled())
|
connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) {
|
||||||
emitAborted();
|
progressStep->state = TaskStepState::Failed;
|
||||||
else {
|
stepProgress(*progressStep);
|
||||||
const BuildZipResult result = buildZipFuture.result();
|
emitFailed(reason);
|
||||||
if (result.has_value())
|
});
|
||||||
emitFailed(result.value());
|
connect(zipTask.get(), &Task::stepProgress, this, &FlamePackExportTask::propagateStepProgress);
|
||||||
else
|
|
||||||
emitSucceeded();
|
connect(zipTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) {
|
||||||
}
|
progressStep->update(current, total);
|
||||||
|
stepProgress(*progressStep);
|
||||||
|
});
|
||||||
|
connect(zipTask.get(), &Task::status, this, [this, progressStep](QString status) {
|
||||||
|
progressStep->status = status;
|
||||||
|
stepProgress(*progressStep);
|
||||||
|
});
|
||||||
|
task.reset(zipTask);
|
||||||
|
zipTask->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray FlamePackExportTask::generateIndex()
|
QByteArray FlamePackExportTask::generateIndex()
|
||||||
@ -471,3 +414,18 @@ QByteArray FlamePackExportTask::generateIndex()
|
|||||||
|
|
||||||
return QJsonDocument(obj).toJson(QJsonDocument::Compact);
|
return QJsonDocument(obj).toJson(QJsonDocument::Compact);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QByteArray FlamePackExportTask::generateHTML()
|
||||||
|
{
|
||||||
|
QString content = "";
|
||||||
|
for (auto mod : resolvedFiles) {
|
||||||
|
if (mod.isMod) {
|
||||||
|
content += QString(TEMPLATE)
|
||||||
|
.replace("{name}", mod.name.toHtmlEscaped())
|
||||||
|
.replace("{url}", ModPlatform::getMetaURL(ModPlatform::ResourceProvider::FLAME, mod.addonId).toHtmlEscaped())
|
||||||
|
.replace("{authors}", !mod.authors.isEmpty() ? QString(" (by %1)").arg(mod.authors).toHtmlEscaped() : "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
content = "<ul>" + content + "</ul>";
|
||||||
|
return content.toUtf8();
|
||||||
|
}
|
@ -19,8 +19,6 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QFuture>
|
|
||||||
#include <QFutureWatcher>
|
|
||||||
#include "BaseInstance.h"
|
#include "BaseInstance.h"
|
||||||
#include "MMCZip.h"
|
#include "MMCZip.h"
|
||||||
#include "minecraft/MinecraftInstance.h"
|
#include "minecraft/MinecraftInstance.h"
|
||||||
@ -52,7 +50,6 @@ class FlamePackExportTask : public Task {
|
|||||||
const QString output;
|
const QString output;
|
||||||
const MMCZip::FilterFunction filter;
|
const MMCZip::FilterFunction filter;
|
||||||
|
|
||||||
typedef std::optional<QString> BuildZipResult;
|
|
||||||
struct ResolvedFile {
|
struct ResolvedFile {
|
||||||
int addonId;
|
int addonId;
|
||||||
int version;
|
int version;
|
||||||
@ -76,15 +73,13 @@ class FlamePackExportTask : public Task {
|
|||||||
QMap<QString, HashInfo> pendingHashes{};
|
QMap<QString, HashInfo> pendingHashes{};
|
||||||
QMap<QString, ResolvedFile> resolvedFiles{};
|
QMap<QString, ResolvedFile> resolvedFiles{};
|
||||||
Task::Ptr task;
|
Task::Ptr task;
|
||||||
QFuture<BuildZipResult> buildZipFuture;
|
|
||||||
QFutureWatcher<BuildZipResult> buildZipWatcher;
|
|
||||||
|
|
||||||
void collectFiles();
|
void collectFiles();
|
||||||
void collectHashes();
|
void collectHashes();
|
||||||
void makeApiRequest();
|
void makeApiRequest();
|
||||||
void getProjectsInfo();
|
void getProjectsInfo();
|
||||||
void buildZip();
|
void buildZip();
|
||||||
void finish();
|
|
||||||
|
|
||||||
QByteArray generateIndex();
|
QByteArray generateIndex();
|
||||||
|
QByteArray generateHTML();
|
||||||
};
|
};
|
||||||
|
@ -76,13 +76,8 @@ bool Flame::File::parseFromObject(const QJsonObject& obj, bool throw_on_blocked
|
|||||||
// It is also optional
|
// It is also optional
|
||||||
type = File::Type::SingleFile;
|
type = File::Type::SingleFile;
|
||||||
|
|
||||||
if (fileName.endsWith(".zip")) {
|
|
||||||
// this is probably a resource pack
|
|
||||||
targetFolder = "resourcepacks";
|
|
||||||
} else {
|
|
||||||
// this is probably a mod, dunno what else could modpacks download
|
|
||||||
targetFolder = "mods";
|
targetFolder = "mods";
|
||||||
}
|
|
||||||
// get the hash
|
// get the hash
|
||||||
hash = QString();
|
hash = QString();
|
||||||
auto hashes = Json::ensureArray(obj, "hashes");
|
auto hashes = Json::ensureArray(obj, "hashes");
|
||||||
|
200
launcher/modplatform/helpers/ExportToModList.cpp
Normal file
200
launcher/modplatform/helpers/ExportToModList.cpp
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2023 Trial97 <alexandru.tripon97@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 "ExportToModList.h"
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
namespace ExportToModList {
|
||||||
|
QString toHTML(QList<Mod*> mods, OptionalData extraData)
|
||||||
|
{
|
||||||
|
QStringList lines;
|
||||||
|
for (auto mod : mods) {
|
||||||
|
auto meta = mod->metadata();
|
||||||
|
auto modName = mod->name().toHtmlEscaped();
|
||||||
|
if (extraData & Url) {
|
||||||
|
auto url = mod->metaurl().toHtmlEscaped();
|
||||||
|
if (!url.isEmpty())
|
||||||
|
modName = QString("<a href=\"%1\">%2</a>").arg(url, modName);
|
||||||
|
}
|
||||||
|
auto line = modName;
|
||||||
|
if (extraData & Version) {
|
||||||
|
auto ver = mod->version();
|
||||||
|
if (ver.isEmpty() && meta != nullptr)
|
||||||
|
ver = meta->version().toString();
|
||||||
|
if (!ver.isEmpty())
|
||||||
|
line += QString(" [%1]").arg(ver.toHtmlEscaped());
|
||||||
|
}
|
||||||
|
if (extraData & Authors && !mod->authors().isEmpty())
|
||||||
|
line += " by " + mod->authors().join(", ").toHtmlEscaped();
|
||||||
|
lines.append(QString("<li>%1</li>").arg(line));
|
||||||
|
}
|
||||||
|
return QString("<html><body><ul>\n\t%1\n</ul></body></html>").arg(lines.join("\n\t"));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString toMarkdown(QList<Mod*> mods, OptionalData extraData)
|
||||||
|
{
|
||||||
|
QStringList lines;
|
||||||
|
for (auto mod : mods) {
|
||||||
|
auto meta = mod->metadata();
|
||||||
|
auto modName = mod->name();
|
||||||
|
if (extraData & Url) {
|
||||||
|
auto url = mod->metaurl();
|
||||||
|
if (!url.isEmpty())
|
||||||
|
modName = QString("[%1](%2)").arg(modName, url);
|
||||||
|
}
|
||||||
|
auto line = modName;
|
||||||
|
if (extraData & Version) {
|
||||||
|
auto ver = mod->version();
|
||||||
|
if (ver.isEmpty() && meta != nullptr)
|
||||||
|
ver = meta->version().toString();
|
||||||
|
if (!ver.isEmpty())
|
||||||
|
line += QString(" [%1]").arg(ver);
|
||||||
|
}
|
||||||
|
if (extraData & Authors && !mod->authors().isEmpty())
|
||||||
|
line += " by " + mod->authors().join(", ");
|
||||||
|
lines << "- " + line;
|
||||||
|
}
|
||||||
|
return lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString toPlainTXT(QList<Mod*> mods, OptionalData extraData)
|
||||||
|
{
|
||||||
|
QStringList lines;
|
||||||
|
for (auto mod : mods) {
|
||||||
|
auto meta = mod->metadata();
|
||||||
|
auto modName = mod->name();
|
||||||
|
|
||||||
|
auto line = modName;
|
||||||
|
if (extraData & Url) {
|
||||||
|
auto url = mod->metaurl();
|
||||||
|
if (!url.isEmpty())
|
||||||
|
line += QString(" (%1)").arg(url);
|
||||||
|
}
|
||||||
|
if (extraData & Version) {
|
||||||
|
auto ver = mod->version();
|
||||||
|
if (ver.isEmpty() && meta != nullptr)
|
||||||
|
ver = meta->version().toString();
|
||||||
|
if (!ver.isEmpty())
|
||||||
|
line += QString(" [%1]").arg(ver);
|
||||||
|
}
|
||||||
|
if (extraData & Authors && !mod->authors().isEmpty())
|
||||||
|
line += " by " + mod->authors().join(", ");
|
||||||
|
lines << line;
|
||||||
|
}
|
||||||
|
return lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString toJSON(QList<Mod*> mods, OptionalData extraData)
|
||||||
|
{
|
||||||
|
QJsonArray lines;
|
||||||
|
for (auto mod : mods) {
|
||||||
|
auto meta = mod->metadata();
|
||||||
|
auto modName = mod->name();
|
||||||
|
QJsonObject line;
|
||||||
|
line["name"] = modName;
|
||||||
|
if (extraData & Url) {
|
||||||
|
auto url = mod->metaurl();
|
||||||
|
if (!url.isEmpty())
|
||||||
|
line["url"] = url;
|
||||||
|
}
|
||||||
|
if (extraData & Version) {
|
||||||
|
auto ver = mod->version();
|
||||||
|
if (ver.isEmpty() && meta != nullptr)
|
||||||
|
ver = meta->version().toString();
|
||||||
|
if (!ver.isEmpty())
|
||||||
|
line["version"] = ver;
|
||||||
|
}
|
||||||
|
if (extraData & Authors && !mod->authors().isEmpty())
|
||||||
|
line["authors"] = QJsonArray::fromStringList(mod->authors());
|
||||||
|
lines << line;
|
||||||
|
}
|
||||||
|
QJsonDocument doc;
|
||||||
|
doc.setArray(lines);
|
||||||
|
return doc.toJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString toCSV(QList<Mod*> mods, OptionalData extraData)
|
||||||
|
{
|
||||||
|
QStringList lines;
|
||||||
|
for (auto mod : mods) {
|
||||||
|
QStringList data;
|
||||||
|
auto meta = mod->metadata();
|
||||||
|
auto modName = mod->name();
|
||||||
|
|
||||||
|
data << modName;
|
||||||
|
if (extraData & Url)
|
||||||
|
data << mod->metaurl();
|
||||||
|
if (extraData & Version) {
|
||||||
|
auto ver = mod->version();
|
||||||
|
if (ver.isEmpty() && meta != nullptr)
|
||||||
|
ver = meta->version().toString();
|
||||||
|
data << ver;
|
||||||
|
}
|
||||||
|
if (extraData & Authors) {
|
||||||
|
QString authors;
|
||||||
|
if (mod->authors().length() == 1)
|
||||||
|
authors = mod->authors().back();
|
||||||
|
else if (mod->authors().length() > 1)
|
||||||
|
authors = QString("\"%1\"").arg(mod->authors().join(","));
|
||||||
|
data << authors;
|
||||||
|
}
|
||||||
|
lines << data.join(",");
|
||||||
|
}
|
||||||
|
return lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString exportToModList(QList<Mod*> mods, Formats format, OptionalData extraData)
|
||||||
|
{
|
||||||
|
switch (format) {
|
||||||
|
case HTML:
|
||||||
|
return toHTML(mods, extraData);
|
||||||
|
case MARKDOWN:
|
||||||
|
return toMarkdown(mods, extraData);
|
||||||
|
case PLAINTXT:
|
||||||
|
return toPlainTXT(mods, extraData);
|
||||||
|
case JSON:
|
||||||
|
return toJSON(mods, extraData);
|
||||||
|
case CSV:
|
||||||
|
return toCSV(mods, extraData);
|
||||||
|
default: {
|
||||||
|
return QString("unknown format:%1").arg(format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString exportToModList(QList<Mod*> mods, QString lineTemplate)
|
||||||
|
{
|
||||||
|
QStringList lines;
|
||||||
|
for (auto mod : mods) {
|
||||||
|
auto meta = mod->metadata();
|
||||||
|
auto modName = mod->name();
|
||||||
|
auto url = mod->metaurl();
|
||||||
|
auto ver = mod->version();
|
||||||
|
if (ver.isEmpty() && meta != nullptr)
|
||||||
|
ver = meta->version().toString();
|
||||||
|
auto authors = mod->authors().join(", ");
|
||||||
|
lines << QString(lineTemplate)
|
||||||
|
.replace("{name}", modName)
|
||||||
|
.replace("{url}", url)
|
||||||
|
.replace("{version}", ver)
|
||||||
|
.replace("{authors}", authors);
|
||||||
|
}
|
||||||
|
return lines.join("\n");
|
||||||
|
}
|
||||||
|
} // namespace ExportToModList
|
33
launcher/modplatform/helpers/ExportToModList.h
Normal file
33
launcher/modplatform/helpers/ExportToModList.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2023 Trial97 <alexandru.tripon97@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 <QList>
|
||||||
|
#include <QString>
|
||||||
|
#include "minecraft/mod/Mod.h"
|
||||||
|
|
||||||
|
namespace ExportToModList {
|
||||||
|
|
||||||
|
enum Formats { HTML, MARKDOWN, PLAINTXT, JSON, CSV, CUSTOM };
|
||||||
|
enum OptionalData {
|
||||||
|
Authors = 1 << 0,
|
||||||
|
Url = 1 << 1,
|
||||||
|
Version = 1 << 2,
|
||||||
|
};
|
||||||
|
QString exportToModList(QList<Mod*> mods, Formats format, OptionalData extraData);
|
||||||
|
QString exportToModList(QList<Mod*> mods, QString lineTemplate);
|
||||||
|
} // namespace ExportToModList
|
@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
#include "modplatform/ModIndex.h"
|
#include "modplatform/ModIndex.h"
|
||||||
|
|
||||||
|
#include "net/ApiDownload.h"
|
||||||
|
|
||||||
Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&& callbacks) const
|
Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&& callbacks) const
|
||||||
{
|
{
|
||||||
auto search_url_optional = getSearchURL(args);
|
auto search_url_optional = getSearchURL(args);
|
||||||
@ -23,7 +25,7 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&
|
|||||||
auto response = std::make_shared<QByteArray>();
|
auto response = std::make_shared<QByteArray>();
|
||||||
auto netJob = makeShared<NetJob>(QString("%1::Search").arg(debugName()), APPLICATION->network());
|
auto netJob = makeShared<NetJob>(QString("%1::Search").arg(debugName()), APPLICATION->network());
|
||||||
|
|
||||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(search_url), response));
|
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(search_url), response));
|
||||||
|
|
||||||
QObject::connect(netJob.get(), &NetJob::succeeded, [this, response, callbacks] {
|
QObject::connect(netJob.get(), &NetJob::succeeded, [this, response, callbacks] {
|
||||||
QJsonParseError parse_error{};
|
QJsonParseError parse_error{};
|
||||||
@ -85,7 +87,7 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi
|
|||||||
auto netJob = makeShared<NetJob>(QString("%1::Versions").arg(args.pack.name), APPLICATION->network());
|
auto netJob = makeShared<NetJob>(QString("%1::Versions").arg(args.pack.name), APPLICATION->network());
|
||||||
auto response = std::make_shared<QByteArray>();
|
auto response = std::make_shared<QByteArray>();
|
||||||
|
|
||||||
netJob->addNetAction(Net::Download::makeByteArray(versions_url, response));
|
netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response));
|
||||||
|
|
||||||
QObject::connect(netJob.get(), &NetJob::succeeded, [response, callbacks, args] {
|
QObject::connect(netJob.get(), &NetJob::succeeded, [response, callbacks, args] {
|
||||||
QJsonParseError parse_error{};
|
QJsonParseError parse_error{};
|
||||||
@ -113,7 +115,7 @@ Task::Ptr NetworkResourceAPI::getProject(QString addonId, std::shared_ptr<QByteA
|
|||||||
|
|
||||||
auto netJob = makeShared<NetJob>(QString("%1::GetProject").arg(addonId), APPLICATION->network());
|
auto netJob = makeShared<NetJob>(QString("%1::GetProject").arg(addonId), APPLICATION->network());
|
||||||
|
|
||||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(project_url), response));
|
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(project_url), response));
|
||||||
|
|
||||||
return netJob;
|
return netJob;
|
||||||
}
|
}
|
||||||
|
87
launcher/modplatform/import_ftb/PackHelpers.cpp
Normal file
87
launcher/modplatform/import_ftb/PackHelpers.cpp
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2023 Trial97 <alexandru.tripon97@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 "modplatform/import_ftb/PackHelpers.h"
|
||||||
|
|
||||||
|
#include <QIcon>
|
||||||
|
#include <QString>
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
|
#include "FileSystem.h"
|
||||||
|
#include "Json.h"
|
||||||
|
|
||||||
|
namespace FTBImportAPP {
|
||||||
|
|
||||||
|
Modpack parseDirectory(QString path)
|
||||||
|
{
|
||||||
|
Modpack modpack{ path };
|
||||||
|
auto instanceFile = QFileInfo(FS::PathCombine(path, "instance.json"));
|
||||||
|
if (!instanceFile.exists() || !instanceFile.isFile())
|
||||||
|
return {};
|
||||||
|
try {
|
||||||
|
auto doc = Json::requireDocument(instanceFile.absoluteFilePath(), "FTB_APP instance JSON file");
|
||||||
|
const auto root = doc.object();
|
||||||
|
modpack.uuid = Json::requireString(root, "uuid", "uuid");
|
||||||
|
modpack.id = Json::requireInteger(root, "id", "id");
|
||||||
|
modpack.versionId = Json::requireInteger(root, "versionId", "versionId");
|
||||||
|
modpack.name = Json::requireString(root, "name", "name");
|
||||||
|
modpack.version = Json::requireString(root, "version", "version");
|
||||||
|
modpack.mcVersion = Json::requireString(root, "mcVersion", "mcVersion");
|
||||||
|
modpack.jvmArgs = Json::ensureVariant(root, "jvmArgs", {}, "jvmArgs");
|
||||||
|
} catch (const Exception& e) {
|
||||||
|
qDebug() << "Couldn't load ftb instance json: " << e.cause();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto versionsFile = QFileInfo(FS::PathCombine(path, "version.json"));
|
||||||
|
if (!versionsFile.exists() || !versionsFile.isFile())
|
||||||
|
return {};
|
||||||
|
try {
|
||||||
|
auto doc = Json::requireDocument(versionsFile.absoluteFilePath(), "FTB_APP version JSON file");
|
||||||
|
const auto root = doc.object();
|
||||||
|
auto targets = Json::requireArray(root, "targets", "targets");
|
||||||
|
|
||||||
|
for (auto target : targets) {
|
||||||
|
auto obj = Json::requireObject(target, "target");
|
||||||
|
auto name = Json::requireString(obj, "name", "name");
|
||||||
|
auto version = Json::requireString(obj, "version", "version");
|
||||||
|
if (name == "forge") {
|
||||||
|
modpack.loaderType = ResourceAPI::Forge;
|
||||||
|
modpack.version = version;
|
||||||
|
break;
|
||||||
|
} else if (name == "fabric") {
|
||||||
|
modpack.loaderType = ResourceAPI::Fabric;
|
||||||
|
modpack.version = version;
|
||||||
|
break;
|
||||||
|
} else if (name == "quilt") {
|
||||||
|
modpack.loaderType = ResourceAPI::Quilt;
|
||||||
|
modpack.version = version;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (const Exception& e) {
|
||||||
|
qDebug() << "Couldn't load ftb version json: " << e.cause();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto iconFile = QFileInfo(FS::PathCombine(path, "folder.jpg"));
|
||||||
|
if (iconFile.exists() && iconFile.isFile()) {
|
||||||
|
modpack.icon = QIcon(iconFile.absoluteFilePath());
|
||||||
|
}
|
||||||
|
return modpack;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace FTBImportAPP
|
55
launcher/modplatform/import_ftb/PackHelpers.h
Normal file
55
launcher/modplatform/import_ftb/PackHelpers.h
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2023 Trial97 <alexandru.tripon97@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 <QIcon>
|
||||||
|
#include <QList>
|
||||||
|
#include <QMetaType>
|
||||||
|
#include <QString>
|
||||||
|
#include <QVariant>
|
||||||
|
#include "modplatform/ResourceAPI.h"
|
||||||
|
|
||||||
|
namespace FTBImportAPP {
|
||||||
|
|
||||||
|
struct Modpack {
|
||||||
|
QString path;
|
||||||
|
|
||||||
|
// json data
|
||||||
|
QString uuid;
|
||||||
|
int id;
|
||||||
|
int versionId;
|
||||||
|
QString name;
|
||||||
|
QString version;
|
||||||
|
QString mcVersion;
|
||||||
|
// not needed for instance creation
|
||||||
|
QVariant jvmArgs;
|
||||||
|
|
||||||
|
std::optional<ResourceAPI::ModLoaderType> loaderType;
|
||||||
|
QString loaderVersion;
|
||||||
|
|
||||||
|
QIcon icon;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef QList<Modpack> ModpackList;
|
||||||
|
|
||||||
|
Modpack parseDirectory(QString path);
|
||||||
|
|
||||||
|
} // namespace FTBImportAPP
|
||||||
|
|
||||||
|
// We need it for the proxy model
|
||||||
|
Q_DECLARE_METATYPE(FTBImportAPP::Modpack)
|
99
launcher/modplatform/import_ftb/PackInstallTask.cpp
Normal file
99
launcher/modplatform/import_ftb/PackInstallTask.cpp
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2023 Trial97 <alexandru.tripon97@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 "PackInstallTask.h"
|
||||||
|
|
||||||
|
#include <QtConcurrent>
|
||||||
|
|
||||||
|
#include "BaseInstance.h"
|
||||||
|
#include "FileSystem.h"
|
||||||
|
#include "minecraft/MinecraftInstance.h"
|
||||||
|
#include "minecraft/PackProfile.h"
|
||||||
|
#include "modplatform/ResourceAPI.h"
|
||||||
|
#include "modplatform/import_ftb/PackHelpers.h"
|
||||||
|
#include "settings/INISettingsObject.h"
|
||||||
|
|
||||||
|
namespace FTBImportAPP {
|
||||||
|
|
||||||
|
void PackInstallTask::executeTask()
|
||||||
|
{
|
||||||
|
setStatus(tr("Copying files..."));
|
||||||
|
setAbortable(false);
|
||||||
|
progress(1, 2);
|
||||||
|
|
||||||
|
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] {
|
||||||
|
FS::copy folderCopy(m_pack.path, FS::PathCombine(m_stagingPath, ".minecraft"));
|
||||||
|
folderCopy.followSymlinks(true);
|
||||||
|
return folderCopy();
|
||||||
|
});
|
||||||
|
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &PackInstallTask::copySettings);
|
||||||
|
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &PackInstallTask::emitAborted);
|
||||||
|
m_copyFutureWatcher.setFuture(m_copyFuture);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PackInstallTask::copySettings()
|
||||||
|
{
|
||||||
|
setStatus(tr("Copying settings..."));
|
||||||
|
progress(2, 2);
|
||||||
|
QString instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg");
|
||||||
|
auto instanceSettings = std::make_shared<INISettingsObject>(instanceConfigPath);
|
||||||
|
instanceSettings->suspendSave();
|
||||||
|
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
|
||||||
|
instance.settings()->set("InstanceType", "OneSix");
|
||||||
|
|
||||||
|
if (m_pack.jvmArgs.isValid() && !m_pack.jvmArgs.toString().isEmpty()) {
|
||||||
|
instance.settings()->set("OverrideJavaArgs", true);
|
||||||
|
instance.settings()->set("JvmArgs", m_pack.jvmArgs.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto components = instance.getPackProfile();
|
||||||
|
components->buildingFromScratch();
|
||||||
|
components->setComponentVersion("net.minecraft", m_pack.mcVersion, true);
|
||||||
|
|
||||||
|
auto modloader = m_pack.loaderType;
|
||||||
|
if (modloader.has_value())
|
||||||
|
switch (modloader.value()) {
|
||||||
|
case ResourceAPI::Forge: {
|
||||||
|
components->setComponentVersion("net.minecraftforge", m_pack.version, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ResourceAPI::Fabric: {
|
||||||
|
components->setComponentVersion("net.fabricmc.fabric-loader", m_pack.version, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ResourceAPI::Quilt: {
|
||||||
|
components->setComponentVersion("org.quiltmc.quilt-loader", m_pack.version, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ResourceAPI::Cauldron:
|
||||||
|
break;
|
||||||
|
case ResourceAPI::LiteLoader:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
components->saveNow();
|
||||||
|
|
||||||
|
instance.setName(name());
|
||||||
|
if (m_instIcon == "default")
|
||||||
|
m_instIcon = "ftb_logo";
|
||||||
|
instance.setIconKey(m_instIcon);
|
||||||
|
instanceSettings->resumeSave();
|
||||||
|
|
||||||
|
emitSucceeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace FTBImportAPP
|
49
launcher/modplatform/import_ftb/PackInstallTask.h
Normal file
49
launcher/modplatform/import_ftb/PackInstallTask.h
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2023 Trial97 <alexandru.tripon97@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 <QFuture>
|
||||||
|
#include <QFutureWatcher>
|
||||||
|
|
||||||
|
#include "InstanceTask.h"
|
||||||
|
#include "PackHelpers.h"
|
||||||
|
|
||||||
|
namespace FTBImportAPP {
|
||||||
|
|
||||||
|
class PackInstallTask : public InstanceTask {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit PackInstallTask(const Modpack& pack) : m_pack(pack) {}
|
||||||
|
virtual ~PackInstallTask() = default;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void executeTask() override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void copySettings();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QFuture<bool> m_copyFuture;
|
||||||
|
QFutureWatcher<bool> m_copyFutureWatcher;
|
||||||
|
|
||||||
|
const Modpack m_pack;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace FTBImportAPP
|
@ -40,6 +40,8 @@
|
|||||||
#include "BuildConfig.h"
|
#include "BuildConfig.h"
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
|
||||||
|
#include "net/ApiDownload.h"
|
||||||
|
|
||||||
namespace LegacyFTB {
|
namespace LegacyFTB {
|
||||||
|
|
||||||
void PackFetchTask::fetch()
|
void PackFetchTask::fetch()
|
||||||
@ -51,7 +53,7 @@ void PackFetchTask::fetch()
|
|||||||
|
|
||||||
QUrl publicPacksUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/modpacks.xml");
|
QUrl publicPacksUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/modpacks.xml");
|
||||||
qDebug() << "Downloading public version info from" << publicPacksUrl.toString();
|
qDebug() << "Downloading public version info from" << publicPacksUrl.toString();
|
||||||
jobPtr->addNetAction(Net::Download::makeByteArray(publicPacksUrl, publicModpacksXmlFileData));
|
jobPtr->addNetAction(Net::ApiDownload::makeByteArray(publicPacksUrl, publicModpacksXmlFileData));
|
||||||
|
|
||||||
QUrl thirdPartyUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/thirdparty.xml");
|
QUrl thirdPartyUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/thirdparty.xml");
|
||||||
qDebug() << "Downloading thirdparty version info from" << thirdPartyUrl.toString();
|
qDebug() << "Downloading thirdparty version info from" << thirdPartyUrl.toString();
|
||||||
@ -71,7 +73,7 @@ void PackFetchTask::fetchPrivate(const QStringList& toFetch)
|
|||||||
for (auto& packCode : toFetch) {
|
for (auto& packCode : toFetch) {
|
||||||
auto data = std::make_shared<QByteArray>();
|
auto data = std::make_shared<QByteArray>();
|
||||||
NetJob* job = new NetJob("Fetching private pack", m_network);
|
NetJob* job = new NetJob("Fetching private pack", m_network);
|
||||||
job->addNetAction(Net::Download::makeByteArray(privatePackBaseUrl.arg(packCode), data));
|
job->addNetAction(Net::ApiDownload::makeByteArray(privatePackBaseUrl.arg(packCode), data));
|
||||||
|
|
||||||
QObject::connect(job, &NetJob::succeeded, this, [this, job, data, packCode] {
|
QObject::connect(job, &NetJob::succeeded, this, [this, job, data, packCode] {
|
||||||
ModpackList packs;
|
ModpackList packs;
|
||||||
|
@ -37,16 +37,18 @@
|
|||||||
|
|
||||||
#include <QtConcurrent>
|
#include <QtConcurrent>
|
||||||
|
|
||||||
#include "MMCZip.h"
|
|
||||||
#include "BaseInstance.h"
|
#include "BaseInstance.h"
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
#include "settings/INISettingsObject.h"
|
#include "MMCZip.h"
|
||||||
|
#include "minecraft/GradleSpecifier.h"
|
||||||
#include "minecraft/MinecraftInstance.h"
|
#include "minecraft/MinecraftInstance.h"
|
||||||
#include "minecraft/PackProfile.h"
|
#include "minecraft/PackProfile.h"
|
||||||
#include "minecraft/GradleSpecifier.h"
|
#include "settings/INISettingsObject.h"
|
||||||
|
|
||||||
#include "BuildConfig.h"
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
#include "BuildConfig.h"
|
||||||
|
|
||||||
|
#include "net/ApiDownload.h"
|
||||||
|
|
||||||
namespace LegacyFTB {
|
namespace LegacyFTB {
|
||||||
|
|
||||||
@ -65,6 +67,7 @@ void PackInstallTask::executeTask()
|
|||||||
void PackInstallTask::downloadPack()
|
void PackInstallTask::downloadPack()
|
||||||
{
|
{
|
||||||
setStatus(tr("Downloading zip for %1").arg(m_pack.name));
|
setStatus(tr("Downloading zip for %1").arg(m_pack.name));
|
||||||
|
setProgress(1, 4);
|
||||||
setAbortable(false);
|
setAbortable(false);
|
||||||
|
|
||||||
archivePath = QString("%1/%2/%3").arg(m_pack.dir, m_version.replace(".", "_"), m_pack.file);
|
archivePath = QString("%1/%2/%3").arg(m_pack.dir, m_version.replace(".", "_"), m_pack.file);
|
||||||
@ -76,13 +79,12 @@ void PackInstallTask::downloadPack()
|
|||||||
} else {
|
} else {
|
||||||
url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "modpacks/%1").arg(archivePath);
|
url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "modpacks/%1").arg(archivePath);
|
||||||
}
|
}
|
||||||
netJobContainer->addNetAction(Net::Download::makeFile(url, archivePath));
|
netJobContainer->addNetAction(Net::ApiDownload::makeFile(url, archivePath));
|
||||||
|
|
||||||
connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
|
connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::unzip);
|
||||||
connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
|
connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::emitFailed);
|
||||||
connect(netJobContainer.get(), &NetJob::progress, this, &PackInstallTask::onDownloadProgress);
|
connect(netJobContainer.get(), &NetJob::stepProgress, this, &PackInstallTask::propagateStepProgress);
|
||||||
connect(netJobContainer.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress);
|
connect(netJobContainer.get(), &NetJob::aborted, this, &PackInstallTask::emitAborted);
|
||||||
connect(netJobContainer.get(), &NetJob::aborted, this, &PackInstallTask::onDownloadAborted);
|
|
||||||
|
|
||||||
netJobContainer->start();
|
netJobContainer->start();
|
||||||
|
|
||||||
@ -90,27 +92,6 @@ void PackInstallTask::downloadPack()
|
|||||||
progress(1, 4);
|
progress(1, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PackInstallTask::onDownloadSucceeded()
|
|
||||||
{
|
|
||||||
unzip();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PackInstallTask::onDownloadFailed(QString reason)
|
|
||||||
{
|
|
||||||
emitFailed(reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PackInstallTask::onDownloadProgress(qint64 current, qint64 total)
|
|
||||||
{
|
|
||||||
progress(current, total * 4);
|
|
||||||
setStatus(tr("Downloading zip for %1 (%2%)").arg(m_pack.name).arg(current / 10));
|
|
||||||
}
|
|
||||||
|
|
||||||
void PackInstallTask::onDownloadAborted()
|
|
||||||
{
|
|
||||||
emitAborted();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PackInstallTask::unzip()
|
void PackInstallTask::unzip()
|
||||||
{
|
{
|
||||||
setStatus(tr("Extracting modpack"));
|
setStatus(tr("Extracting modpack"));
|
||||||
@ -120,16 +101,17 @@ void PackInstallTask::unzip()
|
|||||||
QDir extractDir(m_stagingPath);
|
QDir extractDir(m_stagingPath);
|
||||||
|
|
||||||
m_packZip.reset(new QuaZip(archivePath));
|
m_packZip.reset(new QuaZip(archivePath));
|
||||||
if(!m_packZip->open(QuaZip::mdUnzip))
|
if (!m_packZip->open(QuaZip::mdUnzip)) {
|
||||||
{
|
|
||||||
emitFailed(tr("Failed to open modpack file %1!").arg(archivePath));
|
emitFailed(tr("Failed to open modpack file %1!").arg(archivePath));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), QOverload<QString, QString>::of(MMCZip::extractDir), archivePath, extractDir.absolutePath() + "/unzip");
|
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), QOverload<QString, QString>::of(MMCZip::extractDir), archivePath,
|
||||||
|
extractDir.absolutePath() + "/unzip");
|
||||||
#else
|
#else
|
||||||
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/unzip");
|
m_extractFuture =
|
||||||
|
QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/unzip");
|
||||||
#endif
|
#endif
|
||||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onUnzipFinished);
|
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onUnzipFinished);
|
||||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &PackInstallTask::onUnzipCanceled);
|
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &PackInstallTask::onUnzipCanceled);
|
||||||
@ -151,11 +133,9 @@ void PackInstallTask::install()
|
|||||||
setStatus(tr("Installing modpack"));
|
setStatus(tr("Installing modpack"));
|
||||||
progress(3, 4);
|
progress(3, 4);
|
||||||
QDir unzipMcDir(m_stagingPath + "/unzip/minecraft");
|
QDir unzipMcDir(m_stagingPath + "/unzip/minecraft");
|
||||||
if(unzipMcDir.exists())
|
if (unzipMcDir.exists()) {
|
||||||
{
|
|
||||||
// ok, found minecraft dir, move contents to instance dir
|
// ok, found minecraft dir, move contents to instance dir
|
||||||
if(!QDir().rename(m_stagingPath + "/unzip/minecraft", m_stagingPath + "/.minecraft"))
|
if (!QDir().rename(m_stagingPath + "/unzip/minecraft", m_stagingPath + "/.minecraft")) {
|
||||||
{
|
|
||||||
emitFailed(tr("Failed to move unzipped Minecraft!"));
|
emitFailed(tr("Failed to move unzipped Minecraft!"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -175,8 +155,7 @@ void PackInstallTask::install()
|
|||||||
// handle different versions
|
// handle different versions
|
||||||
QFile packJson(m_stagingPath + "/.minecraft/pack.json");
|
QFile packJson(m_stagingPath + "/.minecraft/pack.json");
|
||||||
QDir jarmodDir = QDir(m_stagingPath + "/unzip/instMods");
|
QDir jarmodDir = QDir(m_stagingPath + "/unzip/instMods");
|
||||||
if(packJson.exists())
|
if (packJson.exists()) {
|
||||||
{
|
|
||||||
packJson.open(QIODevice::ReadOnly | QIODevice::Text);
|
packJson.open(QIODevice::ReadOnly | QIODevice::Text);
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(packJson.readAll());
|
QJsonDocument doc = QJsonDocument::fromJson(packJson.readAll());
|
||||||
packJson.close();
|
packJson.close();
|
||||||
@ -184,11 +163,9 @@ void PackInstallTask::install()
|
|||||||
// we only care about the libs
|
// we only care about the libs
|
||||||
QJsonArray libs = doc.object().value("libraries").toArray();
|
QJsonArray libs = doc.object().value("libraries").toArray();
|
||||||
|
|
||||||
foreach (const QJsonValue &value, libs)
|
foreach (const QJsonValue& value, libs) {
|
||||||
{
|
|
||||||
QString nameValue = value.toObject().value("name").toString();
|
QString nameValue = value.toObject().value("name").toString();
|
||||||
if(!nameValue.startsWith("net.minecraftforge"))
|
if (!nameValue.startsWith("net.minecraftforge")) {
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,16 +176,13 @@ void PackInstallTask::install()
|
|||||||
fallback = false;
|
fallback = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(jarmodDir.exists())
|
if (jarmodDir.exists()) {
|
||||||
{
|
|
||||||
qDebug() << "Found jarmods, installing...";
|
qDebug() << "Found jarmods, installing...";
|
||||||
|
|
||||||
QStringList jarmods;
|
QStringList jarmods;
|
||||||
for (auto info: jarmodDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files))
|
for (auto info : jarmodDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) {
|
||||||
{
|
|
||||||
qDebug() << "Jarmod:" << info.fileName();
|
qDebug() << "Jarmod:" << info.fileName();
|
||||||
jarmods.push_back(info.absoluteFilePath());
|
jarmods.push_back(info.absoluteFilePath());
|
||||||
}
|
}
|
||||||
@ -220,8 +194,7 @@ void PackInstallTask::install()
|
|||||||
// just nuke unzip directory, it s not needed anymore
|
// just nuke unzip directory, it s not needed anymore
|
||||||
FS::deletePath(m_stagingPath + "/unzip");
|
FS::deletePath(m_stagingPath + "/unzip");
|
||||||
|
|
||||||
if(fallback)
|
if (fallback) {
|
||||||
{
|
|
||||||
// TODO: Some fallback mechanism... or just keep failing!
|
// TODO: Some fallback mechanism... or just keep failing!
|
||||||
emitFailed(tr("No installation method found!"));
|
emitFailed(tr("No installation method found!"));
|
||||||
return;
|
return;
|
||||||
@ -232,8 +205,7 @@ void PackInstallTask::install()
|
|||||||
progress(4, 4);
|
progress(4, 4);
|
||||||
|
|
||||||
instance.setName(name());
|
instance.setName(name());
|
||||||
if(m_instIcon == "default")
|
if (m_instIcon == "default") {
|
||||||
{
|
|
||||||
m_instIcon = "ftb_logo";
|
m_instIcon = "ftb_logo";
|
||||||
}
|
}
|
||||||
instance.setIconKey(m_instIcon);
|
instance.setIconKey(m_instIcon);
|
||||||
@ -252,4 +224,4 @@ bool PackInstallTask::abort()
|
|||||||
return InstanceTask::abort();
|
return InstanceTask::abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
} // namespace LegacyFTB
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "InstanceTask.h"
|
|
||||||
#include "net/NetJob.h"
|
|
||||||
#include <quazip/quazip.h>
|
#include <quazip/quazip.h>
|
||||||
#include <quazip/quazipdir.h>
|
#include <quazip/quazipdir.h>
|
||||||
|
#include "InstanceTask.h"
|
||||||
|
#include "PackHelpers.h"
|
||||||
#include "meta/Index.h"
|
#include "meta/Index.h"
|
||||||
#include "meta/Version.h"
|
#include "meta/Version.h"
|
||||||
#include "meta/VersionList.h"
|
#include "meta/VersionList.h"
|
||||||
#include "PackHelpers.h"
|
#include "net/NetJob.h"
|
||||||
|
|
||||||
#include "net/NetJob.h"
|
#include "net/NetJob.h"
|
||||||
|
|
||||||
@ -14,8 +14,7 @@
|
|||||||
|
|
||||||
namespace LegacyFTB {
|
namespace LegacyFTB {
|
||||||
|
|
||||||
class PackInstallTask : public InstanceTask
|
class PackInstallTask : public InstanceTask {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -35,10 +34,6 @@ private:
|
|||||||
void install();
|
void install();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onDownloadSucceeded();
|
|
||||||
void onDownloadFailed(QString reason);
|
|
||||||
void onDownloadProgress(qint64 current, qint64 total);
|
|
||||||
void onDownloadAborted();
|
|
||||||
|
|
||||||
void onUnzipFinished();
|
void onUnzipFinished();
|
||||||
void onUnzipCanceled();
|
void onUnzipCanceled();
|
||||||
@ -56,4 +51,4 @@ private: /* data */
|
|||||||
QString m_version;
|
QString m_version;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
} // namespace LegacyFTB
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "Json.h"
|
#include "Json.h"
|
||||||
|
#include "net/ApiDownload.h"
|
||||||
|
#include "net/ApiUpload.h"
|
||||||
#include "net/NetJob.h"
|
#include "net/NetJob.h"
|
||||||
#include "net/Upload.h"
|
#include "net/Upload.h"
|
||||||
|
|
||||||
@ -13,7 +15,7 @@ Task::Ptr ModrinthAPI::currentVersion(QString hash, QString hash_format, std::sh
|
|||||||
{
|
{
|
||||||
auto netJob = makeShared<NetJob>(QString("Modrinth::GetCurrentVersion"), APPLICATION->network());
|
auto netJob = makeShared<NetJob>(QString("Modrinth::GetCurrentVersion"), APPLICATION->network());
|
||||||
|
|
||||||
netJob->addNetAction(Net::Download::makeByteArray(
|
netJob->addNetAction(Net::ApiDownload::makeByteArray(
|
||||||
QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1?algorithm=%2").arg(hash, hash_format), response));
|
QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1?algorithm=%2").arg(hash, hash_format), response));
|
||||||
|
|
||||||
return netJob;
|
return netJob;
|
||||||
@ -31,7 +33,7 @@ Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_f
|
|||||||
QJsonDocument body(body_obj);
|
QJsonDocument body(body_obj);
|
||||||
auto body_raw = body.toJson();
|
auto body_raw = body.toJson();
|
||||||
|
|
||||||
netJob->addNetAction(Net::Upload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files"), response, body_raw));
|
netJob->addNetAction(Net::ApiUpload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files"), response, body_raw));
|
||||||
|
|
||||||
return netJob;
|
return netJob;
|
||||||
}
|
}
|
||||||
@ -60,7 +62,7 @@ Task::Ptr ModrinthAPI::latestVersion(QString hash,
|
|||||||
QJsonDocument body(body_obj);
|
QJsonDocument body(body_obj);
|
||||||
auto body_raw = body.toJson();
|
auto body_raw = body.toJson();
|
||||||
|
|
||||||
netJob->addNetAction(Net::Upload::makeByteArray(
|
netJob->addNetAction(Net::ApiUpload::makeByteArray(
|
||||||
QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1/update?algorithm=%2").arg(hash, hash_format), response, body_raw));
|
QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1/update?algorithm=%2").arg(hash, hash_format), response, body_raw));
|
||||||
|
|
||||||
return netJob;
|
return netJob;
|
||||||
@ -93,7 +95,8 @@ Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes,
|
|||||||
QJsonDocument body(body_obj);
|
QJsonDocument body(body_obj);
|
||||||
auto body_raw = body.toJson();
|
auto body_raw = body.toJson();
|
||||||
|
|
||||||
netJob->addNetAction(Net::Upload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files/update"), response, body_raw));
|
netJob->addNetAction(
|
||||||
|
Net::ApiUpload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files/update"), response, body_raw));
|
||||||
|
|
||||||
return netJob;
|
return netJob;
|
||||||
}
|
}
|
||||||
@ -103,7 +106,7 @@ Task::Ptr ModrinthAPI::getProjects(QStringList addonIds, std::shared_ptr<QByteAr
|
|||||||
auto netJob = makeShared<NetJob>(QString("Modrinth::GetProjects"), APPLICATION->network());
|
auto netJob = makeShared<NetJob>(QString("Modrinth::GetProjects"), APPLICATION->network());
|
||||||
auto searchUrl = getMultipleModInfoURL(addonIds);
|
auto searchUrl = getMultipleModInfoURL(addonIds);
|
||||||
|
|
||||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
|
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));
|
||||||
|
|
||||||
return netJob;
|
return netJob;
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#include "net/ChecksumValidator.h"
|
#include "net/ChecksumValidator.h"
|
||||||
|
|
||||||
#include "net/NetJob.h"
|
#include "net/NetJob.h"
|
||||||
|
#include "net/ApiDownload.h"
|
||||||
#include "settings/INISettingsObject.h"
|
#include "settings/INISettingsObject.h"
|
||||||
|
|
||||||
#include "ui/dialogs/CustomMessageBox.h"
|
#include "ui/dialogs/CustomMessageBox.h"
|
||||||
@ -238,7 +239,7 @@ bool ModrinthCreationTask::createInstance()
|
|||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "Will try to download" << file.downloads.front() << "to" << file_path;
|
qDebug() << "Will try to download" << file.downloads.front() << "to" << file_path;
|
||||||
auto dl = Net::Download::makeFile(file.downloads.dequeue(), file_path);
|
auto dl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path);
|
||||||
dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
|
dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
|
||||||
m_files_job->addNetAction(dl);
|
m_files_job->addNetAction(dl);
|
||||||
|
|
||||||
@ -247,7 +248,7 @@ bool ModrinthCreationTask::createInstance()
|
|||||||
// MultipleOptionsTask's , once those exist :)
|
// MultipleOptionsTask's , once those exist :)
|
||||||
auto param = dl.toWeakRef();
|
auto param = dl.toWeakRef();
|
||||||
connect(dl.get(), &NetAction::failed, [this, &file, file_path, param] {
|
connect(dl.get(), &NetAction::failed, [this, &file, file_path, param] {
|
||||||
auto ndl = Net::Download::makeFile(file.downloads.dequeue(), file_path);
|
auto ndl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path);
|
||||||
ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
|
ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
|
||||||
m_files_job->addNetAction(ndl);
|
m_files_job->addNetAction(ndl);
|
||||||
if (auto shared = param.lock()) shared->succeeded();
|
if (auto shared = param.lock()) shared->succeeded();
|
||||||
@ -267,7 +268,7 @@ bool ModrinthCreationTask::createInstance()
|
|||||||
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
|
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
|
||||||
setProgress(current, total);
|
setProgress(current, total);
|
||||||
});
|
});
|
||||||
connect(m_files_job.get(), &NetJob::stepProgress, this, &ModrinthCreationTask::propogateStepProgress);
|
connect(m_files_job.get(), &NetJob::stepProgress, this, &ModrinthCreationTask::propagateStepProgress);
|
||||||
|
|
||||||
setStatus(tr("Downloading mods..."));
|
setStatus(tr("Downloading mods..."));
|
||||||
m_files_job->start();
|
m_files_job->start();
|
||||||
|
@ -55,20 +55,11 @@ void ModrinthPackExportTask::executeTask()
|
|||||||
|
|
||||||
bool ModrinthPackExportTask::abort()
|
bool ModrinthPackExportTask::abort()
|
||||||
{
|
{
|
||||||
if (task != nullptr) {
|
if (task) {
|
||||||
task->abort();
|
task->abort();
|
||||||
task = nullptr;
|
|
||||||
emitAborted();
|
emitAborted();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buildZipFuture.isRunning()) {
|
|
||||||
buildZipFuture.cancel();
|
|
||||||
// NOTE: Here we don't do `emitAborted()` because it will be done when `buildZipFuture` actually cancels, which may not occur
|
|
||||||
// immediately.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,63 +196,36 @@ void ModrinthPackExportTask::buildZip()
|
|||||||
{
|
{
|
||||||
setStatus(tr("Adding files..."));
|
setStatus(tr("Adding files..."));
|
||||||
|
|
||||||
buildZipFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]() {
|
auto zipTask = makeShared<MMCZip::ExportToZipTask>(output, gameRoot, files, "overrides/", true);
|
||||||
QuaZip zip(output);
|
zipTask->addExtraFile("modrinth.index.json", generateIndex());
|
||||||
if (!zip.open(QuaZip::mdCreate)) {
|
|
||||||
QFile::remove(output);
|
|
||||||
return BuildZipResult(tr("Could not create file"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buildZipFuture.isCanceled())
|
zipTask->setExcludeFiles(resolvedFiles.keys());
|
||||||
return BuildZipResult();
|
|
||||||
|
|
||||||
QuaZipFile indexFile(&zip);
|
auto progressStep = std::make_shared<TaskStepProgress>();
|
||||||
if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) {
|
connect(zipTask.get(), &Task::finished, this, [this, progressStep] {
|
||||||
QFile::remove(output);
|
progressStep->state = TaskStepState::Succeeded;
|
||||||
return BuildZipResult(tr("Could not create index"));
|
stepProgress(*progressStep);
|
||||||
}
|
|
||||||
indexFile.write(generateIndex());
|
|
||||||
|
|
||||||
size_t progress = 0;
|
|
||||||
for (const QFileInfo& file : files) {
|
|
||||||
if (buildZipFuture.isCanceled()) {
|
|
||||||
QFile::remove(output);
|
|
||||||
return BuildZipResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
setProgress(progress, files.length());
|
|
||||||
const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath());
|
|
||||||
if (!resolvedFiles.contains(relative) && !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) {
|
|
||||||
QFile::remove(output);
|
|
||||||
return BuildZipResult(tr("Could not read and compress %1").arg(relative));
|
|
||||||
}
|
|
||||||
progress++;
|
|
||||||
}
|
|
||||||
|
|
||||||
zip.close();
|
|
||||||
|
|
||||||
if (zip.getZipError() != 0) {
|
|
||||||
QFile::remove(output);
|
|
||||||
return BuildZipResult(tr("A zip error occurred"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return BuildZipResult();
|
|
||||||
});
|
});
|
||||||
connect(&buildZipWatcher, &QFutureWatcher<BuildZipResult>::finished, this, &ModrinthPackExportTask::finish);
|
|
||||||
buildZipWatcher.setFuture(buildZipFuture);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModrinthPackExportTask::finish()
|
connect(zipTask.get(), &Task::succeeded, this, &ModrinthPackExportTask::emitSucceeded);
|
||||||
{
|
connect(zipTask.get(), &Task::aborted, this, &ModrinthPackExportTask::emitAborted);
|
||||||
if (buildZipFuture.isCanceled())
|
connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) {
|
||||||
emitAborted();
|
progressStep->state = TaskStepState::Failed;
|
||||||
else {
|
stepProgress(*progressStep);
|
||||||
const BuildZipResult result = buildZipFuture.result();
|
emitFailed(reason);
|
||||||
if (result.has_value())
|
});
|
||||||
emitFailed(result.value());
|
connect(zipTask.get(), &Task::stepProgress, this, &ModrinthPackExportTask::propagateStepProgress);
|
||||||
else
|
|
||||||
emitSucceeded();
|
connect(zipTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) {
|
||||||
}
|
progressStep->update(current, total);
|
||||||
|
stepProgress(*progressStep);
|
||||||
|
});
|
||||||
|
connect(zipTask.get(), &Task::status, this, [this, progressStep](QString status) {
|
||||||
|
progressStep->status = status;
|
||||||
|
stepProgress(*progressStep);
|
||||||
|
});
|
||||||
|
task.reset(zipTask);
|
||||||
|
zipTask->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray ModrinthPackExportTask::generateIndex()
|
QByteArray ModrinthPackExportTask::generateIndex()
|
||||||
|
@ -56,22 +56,17 @@ class ModrinthPackExportTask : public Task {
|
|||||||
const QString output;
|
const QString output;
|
||||||
const MMCZip::FilterFunction filter;
|
const MMCZip::FilterFunction filter;
|
||||||
|
|
||||||
typedef std::optional<QString> BuildZipResult;
|
|
||||||
|
|
||||||
ModrinthAPI api;
|
ModrinthAPI api;
|
||||||
QFileInfoList files;
|
QFileInfoList files;
|
||||||
QMap<QString, QString> pendingHashes;
|
QMap<QString, QString> pendingHashes;
|
||||||
QMap<QString, ResolvedFile> resolvedFiles;
|
QMap<QString, ResolvedFile> resolvedFiles;
|
||||||
Task::Ptr task;
|
Task::Ptr task;
|
||||||
QFuture<BuildZipResult> buildZipFuture;
|
|
||||||
QFutureWatcher<BuildZipResult> buildZipWatcher;
|
|
||||||
|
|
||||||
void collectFiles();
|
void collectFiles();
|
||||||
void collectHashes();
|
void collectHashes();
|
||||||
void makeApiRequest();
|
void makeApiRequest();
|
||||||
void parseApiResponse(const std::shared_ptr<QByteArray> response);
|
void parseApiResponse(const std::shared_ptr<QByteArray> response);
|
||||||
void buildZip();
|
void buildZip();
|
||||||
void finish();
|
|
||||||
|
|
||||||
QByteArray generateIndex();
|
QByteArray generateIndex();
|
||||||
};
|
};
|
||||||
|
@ -23,6 +23,8 @@
|
|||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
|
||||||
|
#include "net/ApiDownload.h"
|
||||||
|
|
||||||
Technic::SingleZipPackInstallTask::SingleZipPackInstallTask(const QUrl &sourceUrl, const QString &minecraftVersion)
|
Technic::SingleZipPackInstallTask::SingleZipPackInstallTask(const QUrl &sourceUrl, const QString &minecraftVersion)
|
||||||
{
|
{
|
||||||
m_sourceUrl = sourceUrl;
|
m_sourceUrl = sourceUrl;
|
||||||
@ -45,12 +47,12 @@ void Technic::SingleZipPackInstallTask::executeTask()
|
|||||||
auto entry = APPLICATION->metacache()->resolveEntry("general", path);
|
auto entry = APPLICATION->metacache()->resolveEntry("general", path);
|
||||||
entry->setStale(true);
|
entry->setStale(true);
|
||||||
m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network()));
|
m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network()));
|
||||||
m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry));
|
m_filesNetJob->addNetAction(Net::ApiDownload::makeCached(m_sourceUrl, entry));
|
||||||
m_archivePath = entry->getFullPath();
|
m_archivePath = entry->getFullPath();
|
||||||
auto job = m_filesNetJob.get();
|
auto job = m_filesNetJob.get();
|
||||||
connect(job, &NetJob::succeeded, this, &Technic::SingleZipPackInstallTask::downloadSucceeded);
|
connect(job, &NetJob::succeeded, this, &Technic::SingleZipPackInstallTask::downloadSucceeded);
|
||||||
connect(job, &NetJob::progress, this, &Technic::SingleZipPackInstallTask::downloadProgressChanged);
|
connect(job, &NetJob::progress, this, &Technic::SingleZipPackInstallTask::downloadProgressChanged);
|
||||||
connect(job, &NetJob::stepProgress, this, &Technic::SingleZipPackInstallTask::propogateStepProgress);
|
connect(job, &NetJob::stepProgress, this, &Technic::SingleZipPackInstallTask::propagateStepProgress);
|
||||||
connect(job, &NetJob::failed, this, &Technic::SingleZipPackInstallTask::downloadFailed);
|
connect(job, &NetJob::failed, this, &Technic::SingleZipPackInstallTask::downloadFailed);
|
||||||
m_filesNetJob->start();
|
m_filesNetJob->start();
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
#include "SolderPackManifest.h"
|
#include "SolderPackManifest.h"
|
||||||
#include "TechnicPackProcessor.h"
|
#include "TechnicPackProcessor.h"
|
||||||
#include "net/ChecksumValidator.h"
|
#include "net/ChecksumValidator.h"
|
||||||
|
#include "net/ApiDownload.h"
|
||||||
|
|
||||||
Technic::SolderPackInstallTask::SolderPackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network,
|
Technic::SolderPackInstallTask::SolderPackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network,
|
||||||
const QUrl& solderUrl,
|
const QUrl& solderUrl,
|
||||||
@ -71,7 +72,7 @@ void Technic::SolderPackInstallTask::executeTask()
|
|||||||
|
|
||||||
m_filesNetJob.reset(new NetJob(tr("Resolving modpack files"), m_network));
|
m_filesNetJob.reset(new NetJob(tr("Resolving modpack files"), m_network));
|
||||||
auto sourceUrl = QString("%1/modpack/%2/%3").arg(m_solderUrl.toString(), m_pack, m_version);
|
auto sourceUrl = QString("%1/modpack/%2/%3").arg(m_solderUrl.toString(), m_pack, m_version);
|
||||||
m_filesNetJob->addNetAction(Net::Download::makeByteArray(sourceUrl, m_response));
|
m_filesNetJob->addNetAction(Net::ApiDownload::makeByteArray(sourceUrl, m_response));
|
||||||
|
|
||||||
auto job = m_filesNetJob.get();
|
auto job = m_filesNetJob.get();
|
||||||
connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::fileListSucceeded);
|
connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::fileListSucceeded);
|
||||||
@ -112,7 +113,7 @@ void Technic::SolderPackInstallTask::fileListSucceeded()
|
|||||||
for (const auto& mod : build.mods) {
|
for (const auto& mod : build.mods) {
|
||||||
auto path = FS::PathCombine(m_outputDir.path(), QString("%1").arg(i));
|
auto path = FS::PathCombine(m_outputDir.path(), QString("%1").arg(i));
|
||||||
|
|
||||||
auto dl = Net::Download::makeFile(mod.url, path);
|
auto dl = Net::ApiDownload::makeFile(mod.url, path);
|
||||||
if (!mod.md5.isEmpty()) {
|
if (!mod.md5.isEmpty()) {
|
||||||
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
|
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
|
||||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
|
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
|
||||||
@ -126,7 +127,7 @@ void Technic::SolderPackInstallTask::fileListSucceeded()
|
|||||||
|
|
||||||
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &Technic::SolderPackInstallTask::downloadSucceeded);
|
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &Technic::SolderPackInstallTask::downloadSucceeded);
|
||||||
connect(m_filesNetJob.get(), &NetJob::progress, this, &Technic::SolderPackInstallTask::downloadProgressChanged);
|
connect(m_filesNetJob.get(), &NetJob::progress, this, &Technic::SolderPackInstallTask::downloadProgressChanged);
|
||||||
connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &Technic::SolderPackInstallTask::propogateStepProgress);
|
connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &Technic::SolderPackInstallTask::propagateStepProgress);
|
||||||
connect(m_filesNetJob.get(), &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed);
|
connect(m_filesNetJob.get(), &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed);
|
||||||
connect(m_filesNetJob.get(), &NetJob::aborted, this, &Technic::SolderPackInstallTask::downloadAborted);
|
connect(m_filesNetJob.get(), &NetJob::aborted, this, &Technic::SolderPackInstallTask::downloadAborted);
|
||||||
m_filesNetJob->start();
|
m_filesNetJob->start();
|
||||||
|
66
launcher/net/ApiDownload.cpp
Normal file
66
launcher/net/ApiDownload.cpp
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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 "net/ApiDownload.h"
|
||||||
|
#include "ByteArraySink.h"
|
||||||
|
#include "ChecksumValidator.h"
|
||||||
|
#include "MetaCacheSink.h"
|
||||||
|
#include "net/NetAction.h"
|
||||||
|
|
||||||
|
namespace Net {
|
||||||
|
|
||||||
|
auto ApiDownload::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr
|
||||||
|
{
|
||||||
|
auto dl = makeShared<ApiDownload>();
|
||||||
|
dl->m_url = url;
|
||||||
|
dl->setObjectName(QString("CACHE:") + url.toString());
|
||||||
|
dl->m_options = options;
|
||||||
|
auto md5Node = new ChecksumValidator(QCryptographicHash::Md5);
|
||||||
|
auto cachedNode = new MetaCacheSink(entry, md5Node, options.testFlag(Option::MakeEternal));
|
||||||
|
dl->m_sink.reset(cachedNode);
|
||||||
|
return dl;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ApiDownload::makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Options options) -> Download::Ptr
|
||||||
|
{
|
||||||
|
auto dl = makeShared<ApiDownload>();
|
||||||
|
dl->m_url = url;
|
||||||
|
dl->setObjectName(QString("BYTES:") + url.toString());
|
||||||
|
dl->m_options = options;
|
||||||
|
dl->m_sink.reset(new ByteArraySink(output));
|
||||||
|
return dl;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ApiDownload::makeFile(QUrl url, QString path, Options options) -> Download::Ptr
|
||||||
|
{
|
||||||
|
auto dl = makeShared<ApiDownload>();
|
||||||
|
dl->m_url = url;
|
||||||
|
dl->setObjectName(QString("FILE:") + url.toString());
|
||||||
|
dl->m_options = options;
|
||||||
|
dl->m_sink.reset(new FileSink(path));
|
||||||
|
return dl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiDownload::init()
|
||||||
|
{
|
||||||
|
qDebug() << "Setting up api download";
|
||||||
|
auto api_headers = new ApiHeaderProxy();
|
||||||
|
addHeaderProxy(api_headers);
|
||||||
|
}
|
||||||
|
} // namespace Net
|
38
launcher/net/ApiDownload.h
Normal file
38
launcher/net/ApiDownload.h
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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 "ApiHeaderProxy.h"
|
||||||
|
#include "Download.h"
|
||||||
|
|
||||||
|
namespace Net {
|
||||||
|
|
||||||
|
class ApiDownload : public Download {
|
||||||
|
public:
|
||||||
|
virtual ~ApiDownload() = default;
|
||||||
|
|
||||||
|
static auto makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions) -> Download::Ptr;
|
||||||
|
static auto makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Options options = Option::NoOptions) -> Download::Ptr;
|
||||||
|
static auto makeFile(QUrl url, QString path, Options options = Option::NoOptions) -> Download::Ptr;
|
||||||
|
|
||||||
|
void init() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Net
|
49
launcher/net/ApiHeaderProxy.h
Normal file
49
launcher/net/ApiHeaderProxy.h
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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 "Application.h"
|
||||||
|
#include "BuildConfig.h"
|
||||||
|
#include "net/HeaderProxy.h"
|
||||||
|
|
||||||
|
namespace Net {
|
||||||
|
|
||||||
|
class ApiHeaderProxy : public HeaderProxy {
|
||||||
|
public:
|
||||||
|
ApiHeaderProxy() : HeaderProxy() {}
|
||||||
|
virtual ~ApiHeaderProxy() = default;
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual QList<HeaderPair> headers(const QNetworkRequest& request) const override
|
||||||
|
{
|
||||||
|
QList<HeaderPair> hdrs;
|
||||||
|
if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host() == QUrl(BuildConfig.FLAME_BASE_URL).host()) {
|
||||||
|
hdrs.append({ "x-api-key", APPLICATION->getFlameAPIKey().toUtf8() });
|
||||||
|
} else if (request.url().host() == QUrl(BuildConfig.MODRINTH_PROD_URL).host() ||
|
||||||
|
request.url().host() == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) {
|
||||||
|
QString token = APPLICATION->getModrinthAPIToken();
|
||||||
|
if (!token.isNull())
|
||||||
|
hdrs.append({ "Authorization", token.toUtf8() });
|
||||||
|
}
|
||||||
|
return hdrs;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Net
|
43
launcher/net/ApiUpload.cpp
Normal file
43
launcher/net/ApiUpload.cpp
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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 "net/ApiUpload.h"
|
||||||
|
#include "ByteArraySink.h"
|
||||||
|
#include "ChecksumValidator.h"
|
||||||
|
#include "MetaCacheSink.h"
|
||||||
|
#include "net/NetAction.h"
|
||||||
|
|
||||||
|
namespace Net {
|
||||||
|
|
||||||
|
Upload::Ptr ApiUpload::makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, QByteArray m_post_data)
|
||||||
|
{
|
||||||
|
auto up = makeShared<ApiUpload>();
|
||||||
|
up->m_url = std::move(url);
|
||||||
|
up->m_sink.reset(new ByteArraySink(output));
|
||||||
|
up->m_post_data = std::move(m_post_data);
|
||||||
|
return up;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiUpload::init()
|
||||||
|
{
|
||||||
|
qDebug() << "Setting up api upload";
|
||||||
|
auto api_headers = new ApiHeaderProxy();
|
||||||
|
addHeaderProxy(api_headers);
|
||||||
|
}
|
||||||
|
} // namespace Net
|
36
launcher/net/ApiUpload.h
Normal file
36
launcher/net/ApiUpload.h
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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 "ApiHeaderProxy.h"
|
||||||
|
#include "Upload.h"
|
||||||
|
|
||||||
|
namespace Net {
|
||||||
|
|
||||||
|
class ApiUpload : public Upload {
|
||||||
|
public:
|
||||||
|
virtual ~ApiUpload() = default;
|
||||||
|
|
||||||
|
static Upload::Ptr makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, QByteArray m_post_data);
|
||||||
|
|
||||||
|
void init() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Net
|
@ -42,8 +42,6 @@ namespace Net {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Sink object for downloads that uses an external QByteArray it doesn't own as a target.
|
* Sink object for downloads that uses an external QByteArray it doesn't own as a target.
|
||||||
* FIXME: It is possible that the QByteArray is freed while we're doing some operation on it,
|
|
||||||
* causing a segmentation fault.
|
|
||||||
*/
|
*/
|
||||||
class ByteArraySink : public Sink {
|
class ByteArraySink : public Sink {
|
||||||
public:
|
public:
|
||||||
|
@ -47,17 +47,11 @@
|
|||||||
#include "ChecksumValidator.h"
|
#include "ChecksumValidator.h"
|
||||||
#include "MetaCacheSink.h"
|
#include "MetaCacheSink.h"
|
||||||
|
|
||||||
#include "Application.h"
|
|
||||||
#include "BuildConfig.h"
|
|
||||||
|
|
||||||
#include "net/Logging.h"
|
|
||||||
#include "net/NetAction.h"
|
#include "net/NetAction.h"
|
||||||
|
|
||||||
#include "MMCTime.h"
|
|
||||||
#include "StringUtils.h"
|
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
|
|
||||||
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr
|
auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr
|
||||||
{
|
{
|
||||||
auto dl = makeShared<Download>();
|
auto dl = makeShared<Download>();
|
||||||
@ -69,6 +63,7 @@ auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Down
|
|||||||
dl->m_sink.reset(cachedNode);
|
dl->m_sink.reset(cachedNode);
|
||||||
return dl;
|
return dl;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
auto Download::makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Options options) -> Download::Ptr
|
auto Download::makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Options options) -> Download::Ptr
|
||||||
{
|
{
|
||||||
@ -90,260 +85,8 @@ auto Download::makeFile(QUrl url, QString path, Options options) -> Download::Pt
|
|||||||
return dl;
|
return dl;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Download::addValidator(Validator* v)
|
QNetworkReply* Download::getReply(QNetworkRequest& request)
|
||||||
{
|
{
|
||||||
m_sink->addValidator(v);
|
return m_network->get(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Download::executeTask()
|
|
||||||
{
|
|
||||||
setStatus(tr("Downloading %1").arg(StringUtils::truncateUrlHumanFriendly(m_url, 80)));
|
|
||||||
|
|
||||||
if (getState() == Task::State::AbortedByUser) {
|
|
||||||
qCWarning(taskDownloadLogC) << getUid().toString() << "Attempt to start an aborted Download:" << m_url.toString();
|
|
||||||
emitAborted();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QNetworkRequest request(m_url);
|
|
||||||
m_state = m_sink->init(request);
|
|
||||||
switch (m_state) {
|
|
||||||
case State::Succeeded:
|
|
||||||
emit succeeded();
|
|
||||||
qCDebug(taskDownloadLogC) << getUid().toString() << "Download cache hit " << m_url.toString();
|
|
||||||
return;
|
|
||||||
case State::Running:
|
|
||||||
qCDebug(taskDownloadLogC) << getUid().toString() << "Downloading " << m_url.toString();
|
|
||||||
break;
|
|
||||||
case State::Inactive:
|
|
||||||
case State::Failed:
|
|
||||||
emitFailed();
|
|
||||||
return;
|
|
||||||
case State::AbortedByUser:
|
|
||||||
emitAborted();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8());
|
|
||||||
// TODO remove duplication
|
|
||||||
if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host() == QUrl(BuildConfig.FLAME_BASE_URL).host()) {
|
|
||||||
request.setRawHeader("x-api-key", APPLICATION->getFlameAPIKey().toUtf8());
|
|
||||||
} else if (request.url().host() == QUrl(BuildConfig.MODRINTH_PROD_URL).host() ||
|
|
||||||
request.url().host() == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) {
|
|
||||||
QString token = APPLICATION->getModrinthAPIToken();
|
|
||||||
if (!token.isNull())
|
|
||||||
request.setRawHeader("Authorization", token.toUtf8());
|
|
||||||
}
|
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
|
||||||
request.setTransferTimeout();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
m_last_progress_time = m_clock.now();
|
|
||||||
m_last_progress_bytes = 0;
|
|
||||||
|
|
||||||
QNetworkReply* rep = m_network->get(request);
|
|
||||||
m_reply.reset(rep);
|
|
||||||
connect(rep, &QNetworkReply::downloadProgress, this, &Download::downloadProgress);
|
|
||||||
connect(rep, &QNetworkReply::finished, this, &Download::downloadFinished);
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
|
||||||
connect(rep, &QNetworkReply::errorOccurred, this, &Download::downloadError);
|
|
||||||
#else
|
|
||||||
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &Download::downloadError);
|
|
||||||
#endif
|
|
||||||
connect(rep, &QNetworkReply::sslErrors, this, &Download::sslErrors);
|
|
||||||
connect(rep, &QNetworkReply::readyRead, this, &Download::downloadReadyRead);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
|
|
||||||
{
|
|
||||||
auto now = m_clock.now();
|
|
||||||
auto elapsed = now - m_last_progress_time;
|
|
||||||
|
|
||||||
// use milliseconds for speed precision
|
|
||||||
auto elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed);
|
|
||||||
auto bytes_received_since = bytesReceived - m_last_progress_bytes;
|
|
||||||
auto dl_speed_bps = (double)bytes_received_since / elapsed_ms.count() * 1000;
|
|
||||||
auto remaing_time_s = (bytesTotal - bytesReceived) / dl_speed_bps;
|
|
||||||
|
|
||||||
//: Current amount of bytes downloaded, out of the total amount of bytes in the download
|
|
||||||
QString dl_progress =
|
|
||||||
tr("%1 / %2").arg(StringUtils::humanReadableFileSize(bytesReceived)).arg(StringUtils::humanReadableFileSize(bytesTotal));
|
|
||||||
|
|
||||||
QString dl_speed_str;
|
|
||||||
if (elapsed_ms.count() > 0) {
|
|
||||||
auto str_eta = bytesTotal > 0 ? Time::humanReadableDuration(remaing_time_s) : tr("unknown");
|
|
||||||
//: Download speed, in bytes per second (remaining download time in parenthesis)
|
|
||||||
dl_speed_str =
|
|
||||||
tr("%1 /s (%2)").arg(StringUtils::humanReadableFileSize(dl_speed_bps)).arg(str_eta);
|
|
||||||
} else {
|
|
||||||
//: Download speed at 0 bytes per second
|
|
||||||
dl_speed_str = tr("0 B/s");
|
|
||||||
}
|
|
||||||
|
|
||||||
setDetails(dl_progress + "\n" + dl_speed_str);
|
|
||||||
|
|
||||||
setProgress(bytesReceived, bytesTotal);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Download::downloadError(QNetworkReply::NetworkError error)
|
|
||||||
{
|
|
||||||
if (error == QNetworkReply::OperationCanceledError) {
|
|
||||||
qCCritical(taskDownloadLogC) << getUid().toString() << "Aborted " << m_url.toString();
|
|
||||||
m_state = State::AbortedByUser;
|
|
||||||
} else {
|
|
||||||
if (m_options & Option::AcceptLocalFiles) {
|
|
||||||
if (m_sink->hasLocalData()) {
|
|
||||||
m_state = State::Succeeded;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// error happened during download.
|
|
||||||
qCCritical(taskDownloadLogC) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error;
|
|
||||||
m_state = State::Failed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Download::sslErrors(const QList<QSslError>& errors)
|
|
||||||
{
|
|
||||||
int i = 1;
|
|
||||||
for (auto error : errors) {
|
|
||||||
qCCritical(taskDownloadLogC) << getUid().toString() << "Download" << m_url.toString() << "SSL Error #" << i << " : "
|
|
||||||
<< error.errorString();
|
|
||||||
auto cert = error.certificate();
|
|
||||||
qCCritical(taskDownloadLogC) << getUid().toString() << "Certificate in question:\n" << cert.toText();
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Download::handleRedirect() -> bool
|
|
||||||
{
|
|
||||||
QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl();
|
|
||||||
if (!redirect.isValid()) {
|
|
||||||
if (!m_reply->hasRawHeader("Location")) {
|
|
||||||
// no redirect -> it's fine to continue
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// there is a Location header, but it's not correct. we need to apply some workarounds...
|
|
||||||
QByteArray redirectBA = m_reply->rawHeader("Location");
|
|
||||||
if (redirectBA.size() == 0) {
|
|
||||||
// empty, yet present redirect header? WTF?
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
QString redirectStr = QString::fromUtf8(redirectBA);
|
|
||||||
|
|
||||||
if (redirectStr.startsWith("//")) {
|
|
||||||
/*
|
|
||||||
* IF the URL begins with //, we need to insert the URL scheme.
|
|
||||||
* See: https://bugreports.qt.io/browse/QTBUG-41061
|
|
||||||
* See: http://tools.ietf.org/html/rfc3986#section-4.2
|
|
||||||
*/
|
|
||||||
redirectStr = m_reply->url().scheme() + ":" + redirectStr;
|
|
||||||
} else if (redirectStr.startsWith("/")) {
|
|
||||||
/*
|
|
||||||
* IF the URL begins with /, we need to process it as a relative URL
|
|
||||||
*/
|
|
||||||
auto url = m_reply->url();
|
|
||||||
url.setPath(redirectStr, QUrl::TolerantMode);
|
|
||||||
redirectStr = url.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues.
|
|
||||||
* FIXME: report Qt bug for this
|
|
||||||
*/
|
|
||||||
redirect = QUrl(redirectStr, QUrl::TolerantMode);
|
|
||||||
if (!redirect.isValid()) {
|
|
||||||
qCWarning(taskDownloadLogC) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr;
|
|
||||||
downloadError(QNetworkReply::ProtocolFailure);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
qCDebug(taskDownloadLogC) << getUid().toString() << "Fixed location header:" << redirect;
|
|
||||||
} else {
|
|
||||||
qCDebug(taskDownloadLogC) << getUid().toString() << "Location header:" << redirect;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_url = QUrl(redirect.toString());
|
|
||||||
qCDebug(taskDownloadLogC) << getUid().toString() << "Following redirect to " << m_url.toString();
|
|
||||||
startAction(m_network);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Download::downloadFinished()
|
|
||||||
{
|
|
||||||
// handle HTTP redirection first
|
|
||||||
if (handleRedirect()) {
|
|
||||||
qCDebug(taskDownloadLogC) << getUid().toString() << "Download redirected:" << m_url.toString();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the download failed before this point ...
|
|
||||||
if (m_state == State::Succeeded) // pretend to succeed so we continue processing :)
|
|
||||||
{
|
|
||||||
qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed but we are allowed to proceed:" << m_url.toString();
|
|
||||||
m_sink->abort();
|
|
||||||
m_reply.reset();
|
|
||||||
emit succeeded();
|
|
||||||
return;
|
|
||||||
} else if (m_state == State::Failed) {
|
|
||||||
qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed in previous step:" << m_url.toString();
|
|
||||||
m_sink->abort();
|
|
||||||
m_reply.reset();
|
|
||||||
emit failed("");
|
|
||||||
return;
|
|
||||||
} else if (m_state == State::AbortedByUser) {
|
|
||||||
qCDebug(taskDownloadLogC) << getUid().toString() << "Download aborted in previous step:" << m_url.toString();
|
|
||||||
m_sink->abort();
|
|
||||||
m_reply.reset();
|
|
||||||
emit aborted();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure we got all the remaining data, if any
|
|
||||||
auto data = m_reply->readAll();
|
|
||||||
if (data.size()) {
|
|
||||||
qCDebug(taskDownloadLogC) << getUid().toString() << "Writing extra" << data.size() << "bytes";
|
|
||||||
m_state = m_sink->write(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise, finalize the whole graph
|
|
||||||
m_state = m_sink->finalize(*m_reply.get());
|
|
||||||
if (m_state != State::Succeeded) {
|
|
||||||
qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed to finalize:" << m_url.toString();
|
|
||||||
m_sink->abort();
|
|
||||||
m_reply.reset();
|
|
||||||
emit failed("");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_reply.reset();
|
|
||||||
qCDebug(taskDownloadLogC) << getUid().toString() << "Download succeeded:" << m_url.toString();
|
|
||||||
emit succeeded();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Download::downloadReadyRead()
|
|
||||||
{
|
|
||||||
if (m_state == State::Running) {
|
|
||||||
auto data = m_reply->readAll();
|
|
||||||
m_state = m_sink->write(data);
|
|
||||||
if (m_state == State::Failed) {
|
|
||||||
qCCritical(taskDownloadLogC) << getUid().toString() << "Failed to process response chunk";
|
|
||||||
}
|
|
||||||
// qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes";
|
|
||||||
} else {
|
|
||||||
qCCritical(taskDownloadLogC) << getUid().toString() << "Cannot write download data! illegal status " << m_status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Net
|
} // namespace Net
|
||||||
|
|
||||||
auto Net::Download::abort() -> bool
|
|
||||||
{
|
|
||||||
if (m_reply) {
|
|
||||||
m_reply->abort();
|
|
||||||
} else {
|
|
||||||
m_state = State::AbortedByUser;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
@ -38,57 +38,26 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
#include "HttpMetaCache.h"
|
#include "HttpMetaCache.h"
|
||||||
#include "NetAction.h"
|
|
||||||
#include "Sink.h"
|
|
||||||
#include "Validator.h"
|
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
#include "QObjectPtr.h"
|
||||||
|
#include "net/NetRequest.h"
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
class Download : public NetAction {
|
class Download : public NetRequest {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using Ptr = shared_qobject_ptr<class Download>;
|
using Ptr = shared_qobject_ptr<class Download>;
|
||||||
enum class Option { NoOptions = 0, AcceptLocalFiles = 1, MakeEternal = 2 };
|
explicit Download() : NetRequest() { logCat = taskDownloadLogC; }
|
||||||
Q_DECLARE_FLAGS(Options, Option)
|
|
||||||
|
|
||||||
public:
|
|
||||||
~Download() override = default;
|
|
||||||
|
|
||||||
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
static auto makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions) -> Download::Ptr;
|
static auto makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions) -> Download::Ptr;
|
||||||
|
#endif
|
||||||
|
|
||||||
static auto makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Options options = Option::NoOptions) -> Download::Ptr;
|
static auto makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Options options = Option::NoOptions) -> Download::Ptr;
|
||||||
static auto makeFile(QUrl url, QString path, Options options = Option::NoOptions) -> Download::Ptr;
|
static auto makeFile(QUrl url, QString path, Options options = Option::NoOptions) -> Download::Ptr;
|
||||||
|
|
||||||
public:
|
protected:
|
||||||
void addValidator(Validator* v);
|
virtual QNetworkReply* getReply(QNetworkRequest&) override;
|
||||||
auto abort() -> bool override;
|
|
||||||
auto canAbort() const -> bool override { return true; };
|
|
||||||
|
|
||||||
private:
|
|
||||||
auto handleRedirect() -> bool;
|
|
||||||
|
|
||||||
protected slots:
|
|
||||||
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
|
|
||||||
void downloadError(QNetworkReply::NetworkError error) override;
|
|
||||||
void sslErrors(const QList<QSslError>& errors) override;
|
|
||||||
void downloadFinished() override;
|
|
||||||
void downloadReadyRead() override;
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void executeTask() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::unique_ptr<Sink> m_sink;
|
|
||||||
Options m_options;
|
|
||||||
|
|
||||||
std::chrono::steady_clock m_clock;
|
|
||||||
std::chrono::time_point<std::chrono::steady_clock> m_last_progress_time;
|
|
||||||
qint64 m_last_progress_bytes;
|
|
||||||
};
|
};
|
||||||
} // namespace Net
|
} // namespace Net
|
||||||
|
|
||||||
Q_DECLARE_OPERATORS_FOR_FLAGS(Net::Download::Options)
|
|
||||||
|
49
launcher/net/HeaderProxy.h
Normal file
49
launcher/net/HeaderProxy.h
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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 <QDebug>
|
||||||
|
#include <QNetworkRequest>
|
||||||
|
|
||||||
|
namespace Net {
|
||||||
|
|
||||||
|
struct HeaderPair {
|
||||||
|
QByteArray headerName;
|
||||||
|
QByteArray headerValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
class HeaderProxy {
|
||||||
|
public:
|
||||||
|
HeaderProxy(){};
|
||||||
|
virtual ~HeaderProxy(){};
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual QList<HeaderPair> headers(const QNetworkRequest& request) const = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void writeHeaders(QNetworkRequest& request)
|
||||||
|
{
|
||||||
|
for (auto header : headers(request)) {
|
||||||
|
request.setRawHeader(header.headerName, header.headerValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Net
|
@ -42,6 +42,8 @@
|
|||||||
#include "QObjectPtr.h"
|
#include "QObjectPtr.h"
|
||||||
#include "tasks/Task.h"
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
|
#include "HeaderProxy.h"
|
||||||
|
|
||||||
class NetAction : public Task {
|
class NetAction : public Task {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
protected:
|
protected:
|
||||||
@ -56,13 +58,17 @@ class NetAction : public Task {
|
|||||||
|
|
||||||
void setNetwork(shared_qobject_ptr<QNetworkAccessManager> network) { m_network = network; }
|
void setNetwork(shared_qobject_ptr<QNetworkAccessManager> network) { m_network = network; }
|
||||||
|
|
||||||
|
void addHeaderProxy(Net::HeaderProxy* proxy) { m_headerProxies.push_back(std::shared_ptr<Net::HeaderProxy>(proxy)); }
|
||||||
|
virtual void init() = 0;
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0;
|
virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0;
|
||||||
virtual void downloadError(QNetworkReply::NetworkError error) = 0;
|
virtual void downloadError(QNetworkReply::NetworkError error) = 0;
|
||||||
virtual void downloadFinished() = 0;
|
virtual void downloadFinished() = 0;
|
||||||
virtual void downloadReadyRead() = 0;
|
virtual void downloadReadyRead() = 0;
|
||||||
|
|
||||||
virtual void sslErrors(const QList<QSslError>& errors) {
|
virtual void sslErrors(const QList<QSslError>& errors)
|
||||||
|
{
|
||||||
int i = 1;
|
int i = 1;
|
||||||
for (auto error : errors) {
|
for (auto error : errors) {
|
||||||
qCritical() << "Network SSL Error #" << i << " : " << error.errorString();
|
qCritical() << "Network SSL Error #" << i << " : " << error.errorString();
|
||||||
@ -70,7 +76,6 @@ class NetAction : public Task {
|
|||||||
qCritical() << "Certificate in question:\n" << cert.toText();
|
qCritical() << "Certificate in question:\n" << cert.toText();
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
@ -91,4 +96,5 @@ class NetAction : public Task {
|
|||||||
|
|
||||||
/// source URL
|
/// source URL
|
||||||
QUrl m_url;
|
QUrl m_url;
|
||||||
|
std::vector<std::shared_ptr<Net::HeaderProxy>> m_headerProxies;
|
||||||
};
|
};
|
||||||
|
331
launcher/net/NetRequest.cpp
Normal file
331
launcher/net/NetRequest.cpp
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
|
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||||
|
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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/>.
|
||||||
|
*
|
||||||
|
* 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 "NetRequest.h"
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
|
#include "Application.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "net/NetAction.h"
|
||||||
|
|
||||||
|
#include "MMCTime.h"
|
||||||
|
#include "StringUtils.h"
|
||||||
|
|
||||||
|
namespace Net {
|
||||||
|
|
||||||
|
void NetRequest::addValidator(Validator* v)
|
||||||
|
{
|
||||||
|
m_sink->addValidator(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetRequest::executeTask()
|
||||||
|
{
|
||||||
|
init();
|
||||||
|
|
||||||
|
setStatus(tr("Requesting %1").arg(StringUtils::truncateUrlHumanFriendly(m_url, 80)));
|
||||||
|
|
||||||
|
if (getState() == Task::State::AbortedByUser) {
|
||||||
|
qCWarning(logCat) << getUid().toString() << "Attempt to start an aborted Request:" << m_url.toString();
|
||||||
|
emitAborted();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkRequest request(m_url);
|
||||||
|
m_state = m_sink->init(request);
|
||||||
|
switch (m_state) {
|
||||||
|
case State::Succeeded:
|
||||||
|
qCDebug(logCat) << getUid().toString() << "Request cache hit " << m_url.toString();
|
||||||
|
emit succeeded();
|
||||||
|
emit finished();
|
||||||
|
return;
|
||||||
|
case State::Running:
|
||||||
|
qCDebug(logCat) << getUid().toString() << "Runninng " << m_url.toString();
|
||||||
|
break;
|
||||||
|
case State::Inactive:
|
||||||
|
case State::Failed:
|
||||||
|
emitFailed();
|
||||||
|
return;
|
||||||
|
case State::AbortedByUser:
|
||||||
|
emitAborted();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
|
auto user_agent = APPLICATION->getUserAgent();
|
||||||
|
#else
|
||||||
|
auto user_agent = BuildConfig.USER_AGENT;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
request.setHeader(QNetworkRequest::UserAgentHeader, user_agent.toUtf8());
|
||||||
|
for (auto& header_proxy : m_headerProxies) {
|
||||||
|
header_proxy->writeHeaders(request);
|
||||||
|
}
|
||||||
|
// TODO remove duplication
|
||||||
|
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||||
|
request.setTransferTimeout();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
m_last_progress_time = m_clock.now();
|
||||||
|
m_last_progress_bytes = 0;
|
||||||
|
|
||||||
|
QNetworkReply* rep = getReply(request);
|
||||||
|
m_reply.reset(rep);
|
||||||
|
connect(rep, &QNetworkReply::downloadProgress, this, &NetRequest::downloadProgress);
|
||||||
|
connect(rep, &QNetworkReply::finished, this, &NetRequest::downloadFinished);
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||||
|
connect(rep, &QNetworkReply::errorOccurred, this, &NetRequest::downloadError);
|
||||||
|
#else
|
||||||
|
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &NetRequest::downloadError);
|
||||||
|
#endif
|
||||||
|
connect(rep, &QNetworkReply::sslErrors, this, &NetRequest::sslErrors);
|
||||||
|
connect(rep, &QNetworkReply::readyRead, this, &NetRequest::downloadReadyRead);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetRequest::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
|
||||||
|
{
|
||||||
|
auto now = m_clock.now();
|
||||||
|
auto elapsed = now - m_last_progress_time;
|
||||||
|
|
||||||
|
// use milliseconds for speed precision
|
||||||
|
auto elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed);
|
||||||
|
auto bytes_received_since = bytesReceived - m_last_progress_bytes;
|
||||||
|
auto dl_speed_bps = (double)bytes_received_since / elapsed_ms.count() * 1000;
|
||||||
|
auto remaining_time_s = (bytesTotal - bytesReceived) / dl_speed_bps;
|
||||||
|
|
||||||
|
//: Current amount of bytes downloaded, out of the total amount of bytes in the download
|
||||||
|
QString dl_progress =
|
||||||
|
tr("%1 / %2").arg(StringUtils::humanReadableFileSize(bytesReceived)).arg(StringUtils::humanReadableFileSize(bytesTotal));
|
||||||
|
|
||||||
|
QString dl_speed_str;
|
||||||
|
if (elapsed_ms.count() > 0) {
|
||||||
|
auto str_eta = bytesTotal > 0 ? Time::humanReadableDuration(remaining_time_s) : tr("unknown");
|
||||||
|
//: Download speed, in bytes per second (remaining download time in parenthesis)
|
||||||
|
dl_speed_str = tr("%1 /s (%2)").arg(StringUtils::humanReadableFileSize(dl_speed_bps)).arg(str_eta);
|
||||||
|
} else {
|
||||||
|
//: Download speed at 0 bytes per second
|
||||||
|
dl_speed_str = tr("0 B/s");
|
||||||
|
}
|
||||||
|
|
||||||
|
setDetails(dl_progress + "\n" + dl_speed_str);
|
||||||
|
|
||||||
|
setProgress(bytesReceived, bytesTotal);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetRequest::downloadError(QNetworkReply::NetworkError error)
|
||||||
|
{
|
||||||
|
if (error == QNetworkReply::OperationCanceledError) {
|
||||||
|
qCCritical(logCat) << getUid().toString() << "Aborted " << m_url.toString();
|
||||||
|
m_state = State::Failed;
|
||||||
|
} else {
|
||||||
|
if (m_options & Option::AcceptLocalFiles) {
|
||||||
|
if (m_sink->hasLocalData()) {
|
||||||
|
m_state = State::Succeeded;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// error happened during download.
|
||||||
|
qCCritical(logCat) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error;
|
||||||
|
m_state = State::Failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetRequest::sslErrors(const QList<QSslError>& errors)
|
||||||
|
{
|
||||||
|
int i = 1;
|
||||||
|
for (auto error : errors) {
|
||||||
|
qCCritical(logCat) << getUid().toString() << "Request" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString();
|
||||||
|
auto cert = error.certificate();
|
||||||
|
qCCritical(logCat) << getUid().toString() << "Certificate in question:\n" << cert.toText();
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto NetRequest::handleRedirect() -> bool
|
||||||
|
{
|
||||||
|
QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl();
|
||||||
|
if (!redirect.isValid()) {
|
||||||
|
if (!m_reply->hasRawHeader("Location")) {
|
||||||
|
// no redirect -> it's fine to continue
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// there is a Location header, but it's not correct. we need to apply some workarounds...
|
||||||
|
QByteArray redirectBA = m_reply->rawHeader("Location");
|
||||||
|
if (redirectBA.size() == 0) {
|
||||||
|
// empty, yet present redirect header? WTF?
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QString redirectStr = QString::fromUtf8(redirectBA);
|
||||||
|
|
||||||
|
if (redirectStr.startsWith("//")) {
|
||||||
|
/*
|
||||||
|
* IF the URL begins with //, we need to insert the URL scheme.
|
||||||
|
* See: https://bugreports.qt.io/browse/QTBUG-41061
|
||||||
|
* See: http://tools.ietf.org/html/rfc3986#section-4.2
|
||||||
|
*/
|
||||||
|
redirectStr = m_reply->url().scheme() + ":" + redirectStr;
|
||||||
|
} else if (redirectStr.startsWith("/")) {
|
||||||
|
/*
|
||||||
|
* IF the URL begins with /, we need to process it as a relative URL
|
||||||
|
*/
|
||||||
|
auto url = m_reply->url();
|
||||||
|
url.setPath(redirectStr, QUrl::TolerantMode);
|
||||||
|
redirectStr = url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues.
|
||||||
|
* FIXME: report Qt bug for this
|
||||||
|
*/
|
||||||
|
redirect = QUrl(redirectStr, QUrl::TolerantMode);
|
||||||
|
if (!redirect.isValid()) {
|
||||||
|
qCWarning(logCat) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr;
|
||||||
|
downloadError(QNetworkReply::ProtocolFailure);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
qCDebug(logCat) << getUid().toString() << "Fixed location header:" << redirect;
|
||||||
|
} else {
|
||||||
|
qCDebug(logCat) << getUid().toString() << "Location header:" << redirect;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_url = QUrl(redirect.toString());
|
||||||
|
qCDebug(logCat) << getUid().toString() << "Following redirect to " << m_url.toString();
|
||||||
|
startAction(m_network);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetRequest::downloadFinished()
|
||||||
|
{
|
||||||
|
// handle HTTP redirection first
|
||||||
|
if (handleRedirect()) {
|
||||||
|
qCDebug(logCat) << getUid().toString() << "Request redirected:" << m_url.toString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the download failed before this point ...
|
||||||
|
if (m_state == State::Succeeded) // pretend to succeed so we continue processing :)
|
||||||
|
{
|
||||||
|
qCDebug(logCat) << getUid().toString() << "Request failed but we are allowed to proceed:" << m_url.toString();
|
||||||
|
m_sink->abort();
|
||||||
|
m_reply.reset();
|
||||||
|
emit succeeded();
|
||||||
|
emit finished();
|
||||||
|
return;
|
||||||
|
} else if (m_state == State::Failed) {
|
||||||
|
qCDebug(logCat) << getUid().toString() << "Request failed in previous step:" << m_url.toString();
|
||||||
|
m_sink->abort();
|
||||||
|
m_reply.reset();
|
||||||
|
emit failed("");
|
||||||
|
emit finished();
|
||||||
|
return;
|
||||||
|
} else if (m_state == State::AbortedByUser) {
|
||||||
|
qCDebug(logCat) << getUid().toString() << "Request aborted in previous step:" << m_url.toString();
|
||||||
|
m_sink->abort();
|
||||||
|
m_reply.reset();
|
||||||
|
emit aborted();
|
||||||
|
emit finished();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure we got all the remaining data, if any
|
||||||
|
auto data = m_reply->readAll();
|
||||||
|
if (data.size()) {
|
||||||
|
qCDebug(logCat) << getUid().toString() << "Writing extra" << data.size() << "bytes";
|
||||||
|
m_state = m_sink->write(data);
|
||||||
|
if (m_state != State::Succeeded) {
|
||||||
|
qCDebug(logCat) << getUid().toString() << "Request failed to write:" << m_url.toString();
|
||||||
|
m_sink->abort();
|
||||||
|
emit failed("");
|
||||||
|
emit finished();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, finalize the whole graph
|
||||||
|
m_state = m_sink->finalize(*m_reply.get());
|
||||||
|
if (m_state != State::Succeeded) {
|
||||||
|
qCDebug(logCat) << getUid().toString() << "Request failed to finalize:" << m_url.toString();
|
||||||
|
m_sink->abort();
|
||||||
|
m_reply.reset();
|
||||||
|
emit failed("");
|
||||||
|
emit finished();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_reply.reset();
|
||||||
|
qCDebug(logCat) << getUid().toString() << "Request succeeded:" << m_url.toString();
|
||||||
|
emit succeeded();
|
||||||
|
emit finished();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetRequest::downloadReadyRead()
|
||||||
|
{
|
||||||
|
if (m_state == State::Running) {
|
||||||
|
auto data = m_reply->readAll();
|
||||||
|
m_state = m_sink->write(data);
|
||||||
|
if (m_state == State::Failed) {
|
||||||
|
qCCritical(logCat) << getUid().toString() << "Failed to process response chunk";
|
||||||
|
}
|
||||||
|
// qDebug() << "Request" << m_url.toString() << "gained" << data.size() << "bytes";
|
||||||
|
} else {
|
||||||
|
qCCritical(logCat) << getUid().toString() << "Cannot write download data! illegal status " << m_status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto NetRequest::abort() -> bool
|
||||||
|
{
|
||||||
|
m_state = State::AbortedByUser;
|
||||||
|
if (m_reply) {
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||||
|
disconnect(m_reply.get(), &QNetworkReply::errorOccurred, nullptr, nullptr);
|
||||||
|
#else
|
||||||
|
disconnect(m_reply.get(), QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), nullptr, nullptr);
|
||||||
|
#endif
|
||||||
|
m_reply->abort();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Net
|
99
launcher/net/NetRequest.h
Normal file
99
launcher/net/NetRequest.h
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
|
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qloggingcategory.h>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#include "NetAction.h"
|
||||||
|
#include "Sink.h"
|
||||||
|
#include "Validator.h"
|
||||||
|
|
||||||
|
#include "QObjectPtr.h"
|
||||||
|
#include "net/Logging.h"
|
||||||
|
|
||||||
|
namespace Net {
|
||||||
|
class NetRequest : public NetAction {
|
||||||
|
Q_OBJECT
|
||||||
|
protected:
|
||||||
|
explicit NetRequest() : NetAction(){};
|
||||||
|
|
||||||
|
public:
|
||||||
|
using Ptr = shared_qobject_ptr<class NetRequest>;
|
||||||
|
enum class Option { NoOptions = 0, AcceptLocalFiles = 1, MakeEternal = 2 };
|
||||||
|
Q_DECLARE_FLAGS(Options, Option)
|
||||||
|
|
||||||
|
public:
|
||||||
|
~NetRequest() override = default;
|
||||||
|
|
||||||
|
void init() override{};
|
||||||
|
|
||||||
|
public:
|
||||||
|
void addValidator(Validator* v);
|
||||||
|
auto abort() -> bool override;
|
||||||
|
auto canAbort() const -> bool override { return true; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
auto handleRedirect() -> bool;
|
||||||
|
virtual QNetworkReply* getReply(QNetworkRequest&) = 0;
|
||||||
|
|
||||||
|
protected slots:
|
||||||
|
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
|
||||||
|
void downloadError(QNetworkReply::NetworkError error) override;
|
||||||
|
void sslErrors(const QList<QSslError>& errors) override;
|
||||||
|
void downloadFinished() override;
|
||||||
|
void downloadReadyRead() override;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void executeTask() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::unique_ptr<Sink> m_sink;
|
||||||
|
Options m_options;
|
||||||
|
|
||||||
|
typedef const QLoggingCategory& (*logCatFunc)();
|
||||||
|
logCatFunc logCat = taskUploadLogC;
|
||||||
|
|
||||||
|
std::chrono::steady_clock m_clock;
|
||||||
|
std::chrono::time_point<std::chrono::steady_clock> m_last_progress_time;
|
||||||
|
qint64 m_last_progress_bytes;
|
||||||
|
};
|
||||||
|
} // namespace Net
|
||||||
|
|
||||||
|
Q_DECLARE_OPERATORS_FOR_FLAGS(Net::NetRequest::Options)
|
44
launcher/net/RawHeaderProxy.h
Normal file
44
launcher/net/RawHeaderProxy.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
|
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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 "net/HeaderProxy.h"
|
||||||
|
|
||||||
|
namespace Net {
|
||||||
|
|
||||||
|
class RawHeaderProxy : public HeaderProxy {
|
||||||
|
public:
|
||||||
|
RawHeaderProxy() : HeaderProxy() {}
|
||||||
|
virtual ~RawHeaderProxy() = default;
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual QList<HeaderPair> headers(const QNetworkRequest&) const override { return m_headers; };
|
||||||
|
|
||||||
|
void addHeader(const HeaderPair& header) { m_headers.append(header); }
|
||||||
|
void addHeader(const QByteArray& headerName, const QByteArray& headerValue) { m_headers.append({ headerName, headerValue }); }
|
||||||
|
void addHeaders(const QList<HeaderPair>& headers) { m_headers.append(headers); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
QList<HeaderPair> m_headers;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Net
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user