Merge remote-tracking branch 'upstream/staging' into curseforge-url-handle

This commit is contained in:
Sefa Eyeoglu 2023-08-06 21:54:00 +02:00
commit 74fe2fb2a6
No known key found for this signature in database
GPG Key ID: E13DFD4B47127951
175 changed files with 5037 additions and 2815 deletions

8
.editorconfig Normal file
View 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
View 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}.

View File

@ -68,7 +68,7 @@ jobs:
qt_ver: 6
qt_host: windows
qt_arch: ''
qt_version: '6.5.1'
qt_version: '6.5.2'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
@ -80,7 +80,7 @@ jobs:
qt_ver: 6
qt_host: windows
qt_arch: 'win64_msvc2019_arm64'
qt_version: '6.5.1'
qt_version: '6.5.2'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
@ -90,7 +90,7 @@ jobs:
qt_ver: 6
qt_host: mac
qt_arch: ''
qt_version: '6.5.0'
qt_version: '6.5.2'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
@ -264,23 +264,23 @@ jobs:
- name: Configure CMake (macOS)
if: runner.os == 'macOS' && matrix.qt_ver == 6
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)
if: runner.os == 'macOS' && matrix.qt_ver == 5
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)
if: runner.os == 'Windows' && matrix.msystem != ''
shell: msys2 {0}
run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=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)
if: runner.os == 'Windows' && matrix.msystem == ''
run: |
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON
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)
if ("${{ env.CCACHE_VAR }}")
{
@ -295,7 +295,7 @@ jobs:
- name: Configure CMake (Linux)
if: runner.os == 'Linux'
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
@ -586,33 +586,3 @@ jobs:
with:
bundle: "Prism Launcher.flatpak"
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
View File

@ -19,3 +19,6 @@
[submodule "libraries/cmark"]
path = libraries/cmark
url = https://github.com/commonmark/cmark.git
[submodule "flatpak/shared-modules"]
path = flatpak/shared-modules
url = https://github.com/flathub/shared-modules.git

View File

@ -85,6 +85,38 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTOML_ENABLE_FLOAT16=0")
# set CXXFLAGS for build targets
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}")
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)
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")
# 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
set(Launcher_UPDATER_BASE "" CACHE STRING "Base URL for the updater.")
@ -286,6 +318,8 @@ add_subdirectory(program_info)
####################################### Install layout #######################################
set(Launcher_ENABLE_UPDATER NO)
if(NOT (UNIX AND APPLE))
# Install "portable.txt" if selected component is "portable"
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_LONG_VERSION_STRING "${Launcher_VERSION_NAME}")
set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns)
set(MACOSX_BUNDLE_COPYRIGHT "© 2022 ${Launcher_Copyright_Mac}")
set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=")
set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml")
set(MACOSX_BUNDLE_COPYRIGHT "© 2022-2023 ${Launcher_Copyright_Mac}")
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" 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_SHA256 "bf6ac1caa9f8d321d5784859c88da874f28412f37fb327bc21b7b14c5d61ef94" CACHE STRING "SHA256 checksum for Sparkle release archive")
@ -321,8 +355,12 @@ if(UNIX AND APPLE)
# directories to look for dependencies
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
set(INSTALL_BUNDLE "full")
set(INSTALL_BUNDLE "full" CACHE STRING "Use fixup_bundle to bundle dependencies")
# Add the icon
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(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
set(INSTALL_BUNDLE "nodeps")
set(INSTALL_BUNDLE "nodeps" CACHE STRING "Use fixup_bundle to bundle dependencies")
# Set RPATH
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_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)
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})
# install as bundle
set(INSTALL_BUNDLE "full")
set(INSTALL_BUNDLE "full" CACHE STRING "Use fixup_bundle to bundle dependencies")
else()
message(FATAL_ERROR "Platform not supported")
endif()

View File

@ -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:
```
```text
<commit message>
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:
```
```text
Developer's Certificate of Origin 1.1
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-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.

View File

@ -82,14 +82,16 @@ Thanks to the awesome people over at [MacStadium](https://www.macstadium.com/),
## 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>).
- 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.
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)
- [CurseForge 3rd Party API Terms and Conditions](https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions)

View File

@ -65,7 +65,7 @@ Config::Config()
MAC_SPARKLE_PUB_KEY = "@MACOSX_SPARKLE_UPDATE_PUBLIC_KEY@";
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;
}

View File

@ -68,7 +68,7 @@ class Config {
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;
/// A string containing the build timestamp

24
flake.lock generated
View File

@ -21,11 +21,11 @@
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1688254665,
"narHash": "sha256-8FHEgBrr7gYNiS/NzCxIO3m4hvtLRW9YY1nYo1ivm3o=",
"lastModified": 1688466019,
"narHash": "sha256-VeM2akYrBYMsb4W/MmBo1zmaMfgbL4cH3Pu8PGyIwJ0=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "267149c58a14d15f7f81b4d737308421de9d7152",
"rev": "8e8d955c22df93dbe24f19ea04f47a74adbdc5ec",
"type": "github"
},
"original": {
@ -76,11 +76,11 @@
"libnbtplusplus": {
"flake": false,
"locked": {
"lastModified": 1650031308,
"narHash": "sha256-TvVOjkUobYJD9itQYueELJX3wmecvEdCbJ0FinW2mL4=",
"lastModified": 1690036783,
"narHash": "sha256-A5kTgICnx+Qdq3Fir/bKTfdTt/T1NQP2SC+nhN1ENug=",
"owner": "PrismLauncher",
"repo": "libnbtplusplus",
"rev": "2203af7eeb48c45398139b583615134efd8d407f",
"rev": "a5e8fd52b8bf4ab5d5bcc042b2a247867589985f",
"type": "github"
},
"original": {
@ -91,11 +91,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1688221086,
"narHash": "sha256-cdW6qUL71cNWhHCpMPOJjlw0wzSRP0pVlRn2vqX/VVg=",
"lastModified": 1690630721,
"narHash": "sha256-Y04onHyBQT4Erfr2fc82dbJTfXGYrf4V0ysLUYnPOP8=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "cd99c2b3c9f160cd004318e0697f90bbd5960825",
"rev": "d2b52322f35597c62abf56de91b0236746b2a03d",
"type": "github"
},
"original": {
@ -138,11 +138,11 @@
]
},
"locked": {
"lastModified": 1688386108,
"narHash": "sha256-Vffto9QaVonzYAcPlAzd0soqWYpPpKk60dfNLSIXcFA=",
"lastModified": 1690628027,
"narHash": "sha256-OTSbA2hM6VmxyZ/4siYPANffMBzIsKu04GLjXcv8ST0=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "42587d3414d1747999a5f71e92a83cf6547b62da",
"rev": "1e2443dd3f669eb65433b2fc26a3065e05a7dc9c",
"type": "github"
},
"original": {

22
flatpak/libdecor.json Normal file
View 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"
]
}

View File

@ -5,13 +5,6 @@ sdk: org.kde.Sdk
sdk-extensions:
- org.freedesktop.Sdk.Extension.openjdk17
- org.freedesktop.Sdk.Extension.openjdk8
add-extensions:
com.valvesoftware.Steam.Utility.gamescope:
version: stable
add-ld-path: lib
no-autodownload: true
autodelete: false
directory: utils/gamescope
command: prismlauncher
finish-args:
@ -25,13 +18,25 @@ finish-args:
- --filesystem=xdg-run/app/com.discordapp.Discord:create
# Mod drag&drop
- --filesystem=xdg-download:ro
# FTBApp import
- --filesystem=~/.ftba:ro
cleanup:
- /lib/libGLU*
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
buildsystem: cmake-ninja
builddir: true
config-opts:
- -DLauncher_BUILD_PLATFORM=flatpak
- -DCMAKE_BUILD_TYPE=Debug
- -DCMAKE_BUILD_TYPE=RelWithDebInfo
- -DLauncher_QT_VERSION_MAJOR=5
build-options:
env:
@ -40,7 +45,7 @@ modules:
sources:
- type: dir
path: ../
builddir: true
- name: openjdk
buildsystem: simple
build-commands:
@ -49,14 +54,45 @@ modules:
- mv /app/jre /app/jdk/17
- /usr/lib/sdk/openjdk8/install.sh
- 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
buildsystem: autotools
sources:
- type: archive
url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.1.tar.xz
sha256: 7bc76daf9d72f8aff885efad04ce06b90488a1a169d118dea8a2b661832e8762
cleanup: [/share/man, /bin/xkeystone]
url: https://xorg.freedesktop.org/archive/individual/app/xrandr-1.5.2.tar.xz
sha256: c8bee4790d9058bacc4b6246456c58021db58a87ddda1a9d0139bf5f18f1f240
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
buildsystem: meson
config-opts:
@ -67,19 +103,56 @@ modules:
# post-install is running inside the build dir, we need it from the source though
- install -Dm755 ../data/gamemoderun -t /app/bin
sources:
- type: git
url: https://github.com/FeralInteractive/gamemode
tag: "1.7"
commit: 4dc99dff76218718763a6b07fc1900fa6d1dafd9
- type: archive
archive-type: tar-gzip
url: https://api.github.com/repos/FeralInteractive/gamemode/tarball/1.7
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
buildsystem: simple
build-commands:
- mkdir -p /app/utils/gamescope
- install -Dm755 prime-run /app/bin/prime-run
- mv /app/bin/prismlauncher /app/bin/prismrun
- install -Dm755 prismlauncher /app/bin/prismlauncher
sources:
- type: file
path: ../flatpak/prime-run
path: prime-run
- type: file
path: ../flatpak/prismlauncher
path: prismlauncher

View File

@ -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,

View File

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

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

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

View File

@ -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";
done
export PATH="${PATH}${PATH:+:}/app/utils/gamescope/bin:/usr/lib/extensions/vulkan/MangoHud/bin"
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}${LD_LIBRARY_PATH:+:}/usr/lib/extensions/vulkan/MangoHud/\$LIB/"
export PATH="${PATH}${PATH:+:}/usr/lib/extensions/vulkan/gamescope/bin:/usr/lib/extensions/vulkan/MangoHud/bin"
export VK_LAYER_PATH="/usr/lib/extensions/vulkan/share/vulkan/implicit_layer.d/"
exec /app/bin/prismrun "$@"

@ -0,0 +1 @@
Subproject commit 45094ca570be383d06df729b6972830ec63bd3df

6
garnix.yaml Normal file
View File

@ -0,0 +1,6 @@
builds:
exclude: []
include:
- "checks.x86_64-linux.*"
- "devShells.*.*"
- "packages.*.*"

View File

@ -6,9 +6,10 @@
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* 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 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
* it under the terms of the GNU General Public License as published by
@ -130,17 +131,12 @@
#include "MangoHud.h"
#endif
#ifdef Q_OS_MAC
#if defined(Q_OS_MAC) && defined(SPARKLE_ENABLED)
#include "updater/MacSparkleUpdater.h"
#endif
#if defined Q_OS_WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <stdio.h>
#include "WindowsConsole.h"
#endif
#define STRINGIFY(x) #x
@ -167,31 +163,15 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QSt
fflush(stderr);
}
}
} // namespace
Application::Application(int& argc, char** argv) : QApplication(argc, argv)
{
#if defined Q_OS_WIN32
// attach the parent console
if(AttachConsole(ATTACH_PARENT_PROCESS))
{
// 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);
// attach the parent console if stdout not already captured
if (AttachWindowsConsole()) {
consoleAttached = true;
}
#endif
@ -282,7 +262,16 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
}
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();
adjustedBy = "Persistent data path";
@ -434,7 +423,11 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
}
// seach root path
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);
#endif
qDebug() << "Testing" << 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() << "Version : " << BuildConfig.printableVersionString();
qDebug() << "Platform : " << BuildConfig.BUILD_PLATFORM;
qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT;
qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC;
if (adjustedBy.size())
@ -569,6 +563,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
// Language
m_settings->registerSetting("Language", QString());
m_settings->registerSetting("UseSystemLocale", false);
// Console
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("IgnoreJavaWizard", false);
// Mod loader settings
m_settings->registerSetting("DisableQuiltBeacon", false);
// Native library workarounds
m_settings->registerSetting("UseNativeOpenAL", 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("RecordGameTime", true);
// Minecraft launch method
m_settings->registerSetting("MCLaunchMethod", "LauncherPart");
// Minecraft mods
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());
// 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");
}
@ -767,7 +762,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
if(BuildConfig.UPDATER_ENABLED)
{
qDebug() << "Initializing updater";
#ifdef Q_OS_MAC
#if defined(Q_OS_MAC) && defined(SPARKLE_ENABLED)
m_updater.reset(new MacSparkleUpdater());
#endif
qDebug() << "<> Updater started.";
@ -919,12 +914,7 @@ bool Application::createSetupWizard()
}
return false;
}();
bool languageRequired = [&]()
{
if (settings()->get("Language").toString().isEmpty())
return true;
return false;
}();
bool languageRequired = settings()->get("Language").toString().isEmpty();
bool pasteInterventionRequired = settings()->get("PastebinURL") != "";
bool themeInterventionRequired = settings()->get("ApplicationTheme") == "";
bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired || themeInterventionRequired;
@ -1174,6 +1164,16 @@ QIcon Application::getThemedIcon(const QString& 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)
{
const QString file = QDir::current().absoluteFilePath(filename);
@ -1560,7 +1560,7 @@ QString Application::getJarPath(QString jarFile)
{
QStringList potentialPaths = {
#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
FS::PathCombine(m_rootPath, "jars"),
FS::PathCombine(applicationDirPath(), "jars"),

View File

@ -2,7 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* 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>
*
* This program is free software: you can redistribute it and/or modify
@ -48,6 +48,7 @@
#include <BaseInstance.h>
#include "minecraft/launch/MinecraftServerTarget.h"
#include "ui/themes/CatPack.h"
class LaunchController;
class LocalPeer;
@ -126,9 +127,11 @@ public:
void setApplicationTheme(const QString& name);
shared_qobject_ptr<ExternalUpdater> updater() {
return m_updater;
}
QList<CatPack*> getValidCatPacks();
QString getCatPack(QString catName = "");
shared_qobject_ptr<ExternalUpdater> updater() { return m_updater; }
void triggerUpdateCheck();

View File

@ -15,15 +15,14 @@
#pragma once
#include <memory>
#include <QString>
#include <QMetaType>
#include <QString>
#include <memory>
/*!
* An abstract base class for versions.
*/
class BaseVersion
{
class BaseVersion {
public:
using Ptr = std::shared_ptr<BaseVersion>;
virtual ~BaseVersion() {}
@ -45,14 +44,8 @@ public:
*/
virtual QString typeString() const = 0;
virtual bool operator<(BaseVersion &a)
{
return name() < a.name();
};
virtual bool operator>(BaseVersion &a)
{
return name() > a.name();
};
virtual bool operator<(BaseVersion& a) { return name() < a.name(); };
virtual bool operator>(BaseVersion& a) { return name() > a.name(); };
};
Q_DECLARE_METATYPE(BaseVersion::Ptr)

View File

@ -136,6 +136,15 @@ set(NET_SOURCES
net/Validator.h
net/Upload.cpp
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
@ -262,8 +271,6 @@ set(MINECRAFT_SOURCES
minecraft/launch/CreateGameFolders.h
minecraft/launch/ModMinecraftJar.cpp
minecraft/launch/ModMinecraftJar.h
minecraft/launch/DirectJavaLaunch.cpp
minecraft/launch/DirectJavaLaunch.h
minecraft/launch/ExtractNatives.cpp
minecraft/launch/ExtractNatives.h
minecraft/launch/LauncherPartLaunch.cpp
@ -487,6 +494,9 @@ set(API_SOURCES
modplatform/helpers/HashUtils.cpp
modplatform/helpers/OverrideUtils.h
modplatform/helpers/OverrideUtils.cpp
modplatform/helpers/ExportToModList.h
modplatform/helpers/ExportToModList.cpp
)
set(FTB_SOURCES
@ -498,6 +508,11 @@ set(FTB_SOURCES
modplatform/legacy_ftb/PrivatePackManager.cpp
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
@ -560,6 +575,9 @@ set(ATLAUNCHER_SOURCES
)
set(LINKEXE_SOURCES
WindowsConsole.cpp
WindowsConsole.h
filelink/FileLink.h
filelink/FileLink.cpp
FileSystem.h
@ -665,7 +683,7 @@ set(LOGIC_SOURCES
${ATLAUNCHER_SOURCES}
)
if(APPLE)
if(APPLE AND Launcher_ENABLE_UPDATER)
set (LOGIC_SOURCES ${LOGIC_SOURCES} ${MAC_UPDATE_SOURCES})
endif()
@ -758,6 +776,8 @@ SET(LAUNCHER_SOURCES
ui/themes/SystemTheme.h
ui/themes/ThemeManager.cpp
ui/themes/ThemeManager.h
ui/themes/CatPack.cpp
ui/themes/CatPack.h
# Processes
LaunchController.h
@ -867,6 +887,11 @@ SET(LAUNCHER_SOURCES
ui/pages/modplatform/legacy_ftb/ListModel.h
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.h
ui/pages/modplatform/flame/FlamePage.cpp
@ -911,6 +936,8 @@ SET(LAUNCHER_SOURCES
ui/dialogs/ExportInstanceDialog.h
ui/dialogs/ExportPackDialog.cpp
ui/dialogs/ExportPackDialog.h
ui/dialogs/ExportToModListDialog.cpp
ui/dialogs/ExportToModListDialog.h
ui/dialogs/IconPickerDialog.cpp
ui/dialogs/IconPickerDialog.h
ui/dialogs/ImportResourceDialog.cpp
@ -1011,6 +1038,14 @@ SET(LAUNCHER_SOURCES
ui/instanceview/VisualGroup.h
)
if(WIN32)
set(LAUNCHER_SOURCES
WindowsConsole.cpp
WindowsConsole.h
${LAUNCHER_SOURCES}
)
endif()
qt_wrap_ui(LAUNCHER_UI
ui/MainWindow.ui
ui/setupwizard/PasteWizardPage.ui
@ -1039,6 +1074,7 @@ qt_wrap_ui(LAUNCHER_UI
ui/pages/modplatform/ResourcePage.ui
ui/pages/modplatform/flame/FlamePage.ui
ui/pages/modplatform/legacy_ftb/Page.ui
ui/pages/modplatform/import_ftb/ImportFTBPage.ui
ui/pages/modplatform/ImportPage.ui
ui/pages/modplatform/modrinth/ModrinthPage.ui
ui/pages/modplatform/technic/TechnicPage.ui
@ -1058,6 +1094,7 @@ qt_wrap_ui(LAUNCHER_UI
ui/dialogs/SkinUploadDialog.ui
ui/dialogs/ExportInstanceDialog.ui
ui/dialogs/ExportPackDialog.ui
ui/dialogs/ExportToModListDialog.ui
ui/dialogs/IconPickerDialog.ui
ui/dialogs/ImportResourceDialog.ui
ui/dialogs/MSALoginDialog.ui
@ -1094,6 +1131,7 @@ endif()
# Add executable
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_link_libraries(Launcher_logic
systeminfo
@ -1133,18 +1171,24 @@ if(APPLE)
set(CMAKE_MACOSX_RPATH 1)
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(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")
add_compile_definitions(SPARKLE_ENABLED)
endif()
target_link_libraries(Launcher_logic
"-framework AppKit"
"-framework Carbon"
"-framework Foundation"
"-framework ApplicationServices"
)
if(Launcher_ENABLE_UPDATER)
target_link_libraries(Launcher_logic ${SPARKLE_FRAMEWORK})
endif()
endif()
target_link_libraries(Launcher_logic)
@ -1205,7 +1249,7 @@ if(WIN32)
)
endif()
if (UNIX AND APPLE)
if (UNIX AND APPLE AND Launcher_ENABLE_UPDATER)
# Add Sparkle updater
# It has to be copied here instead of just allowing fixup_bundle to install it, otherwise essential parts of
# the framework aren't installed

View File

@ -118,7 +118,7 @@ bool openDirectory(const QString &path, bool ensureExists)
return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath()));
};
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if(!isFlatpak())
if(!isSandbox())
{
return IndirectOpen(f);
}
@ -139,7 +139,7 @@ bool openFile(const QString &path)
return QDesktopServices::openUrl(QUrl::fromLocalFile(path));
};
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if(!isFlatpak())
if(!isSandbox())
{
return IndirectOpen(f);
}
@ -157,7 +157,7 @@ bool openFile(const QString &application, const QString &path, const QString &wo
qDebug() << "Opening file" << path << "using" << application;
#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
if(!isFlatpak())
if(!isSandbox())
{
return IndirectOpen([&]()
{
@ -177,7 +177,7 @@ bool run(const QString &application, const QStringList &args, const QString &wor
{
qDebug() << "Running" << application << "with args" << args.join(' ');
#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
return IndirectOpen([&]()
@ -202,7 +202,7 @@ bool openUrl(const QUrl &url)
return QDesktopServices::openUrl(url);
};
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if(!isFlatpak())
if(!isSandbox())
{
return IndirectOpen(f);
}
@ -224,4 +224,18 @@ bool isFlatpak()
#endif
}
bool isSnap()
{
#ifdef Q_OS_LINUX
return getenv("SNAP");
#else
return false;
#endif
}
bool isSandbox()
{
return isSnap() || isFlatpak();
}
}

View File

@ -34,5 +34,18 @@ namespace DesktopServices
*/
bool openUrl(const QUrl &url);
/**
* Determine whether the launcher is running in a Flatpak environment
*/
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();
}

View File

@ -54,6 +54,8 @@
#include "settings/INISettingsObject.h"
#include "tasks/Task.h"
#include "net/ApiDownload.h"
#include <QtConcurrentRun>
#include <algorithm>
@ -101,12 +103,12 @@ void InstanceImportTask::downloadFromUrl()
auto entry = APPLICATION->metacache()->resolveEntry("general", path);
entry->setStale(true);
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();
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
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::aborted, this, &InstanceImportTask::downloadAborted);
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::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::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::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::details, this, &InstanceImportTask::setDetails);
connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater);

View File

@ -799,7 +799,7 @@ class InstanceStaging : public Task {
connect(child, &Task::status, this, &InstanceStaging::setStatus);
connect(child, &Task::details, this, &InstanceStaging::setDetails);
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);
}

View File

@ -390,7 +390,10 @@ void LaunchController::launchInstance()
m_launcher->prependStep(makeShared<TextPrint>(m_launcher.get(), "Launched instance in " + online_mode + " mode\n", MessageLevel::Launcher));
// 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();
}

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 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
@ -33,56 +34,50 @@
* limitations under the License.
*/
#include "MMCZip.h"
#include <quazip/quazip.h>
#include <quazip/quazipdir.h>
#include <quazip/quazipfile.h>
#include "MMCZip.h"
#include "FileSystem.h"
#include <QCoreApplication>
#include <QDebug>
#include <QtConcurrentRun>
namespace MMCZip {
// 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());
modZip.open(QuaZip::mdUnzip);
QuaZipFile fileInsideMod(&modZip);
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();
if (filter && !filter(filename))
{
qDebug() << "Skipping file " << filename << " from "
<< from.fileName() << " - filtered";
if (filter && !filter(filename)) {
qDebug() << "Skipping file " << filename << " from " << from.fileName() << " - filtered";
continue;
}
if (contained.contains(filename))
{
qDebug() << "Skipping already contained file " << filename << " from "
<< from.fileName();
if (contained.contains(filename)) {
qDebug() << "Skipping already contained file " << filename << " from " << from.fileName();
continue;
}
contained.insert(filename);
if (!fileInsideMod.open(QIODevice::ReadOnly))
{
if (!fileInsideMod.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open " << filename << " from " << from.fileName();
return false;
}
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";
fileInsideMod.close();
return false;
}
if (!JlCompress::copyData(fileInsideMod, zipOutFile))
{
if (!JlCompress::copyData(fileInsideMod, zipOutFile)) {
zipOutFile.close();
fileInsideMod.close();
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;
}
bool MMCZip::compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files, bool followSymlinks)
bool compressDirFiles(QuaZip* zip, QString dir, QFileInfoList files, bool followSymlinks)
{
QDir directory(dir);
if (!directory.exists()) return false;
if (!directory.exists())
return false;
for (auto e : files) {
auto filePath = directory.relativeFilePath(e.absoluteFilePath());
@ -109,13 +105,14 @@ bool MMCZip::compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files, boo
srcPath = e.canonicalFilePath();
}
}
if( !JlCompress::compressFile(zip, srcPath, filePath)) return false;
if (!JlCompress::compressFile(zip, srcPath, filePath))
return false;
}
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);
QDir().mkpath(QFileInfo(fileCompressed).absolutePath());
@ -136,11 +133,10 @@ bool MMCZip::compressDirFiles(QString fileCompressed, QString dir, QFileInfoList
}
// 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);
if (!zipOut.open(QuaZip::mdCreate))
{
if (!zipOut.open(QuaZip::mdCreate)) {
QFile::remove(targetJarPath);
qCritical() << "Failed to open the minecraft.jar for modding";
return false;
@ -151,37 +147,29 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
// Modify the jar
// 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;
// do not merge disabled mods.
if (!mod->enabled())
continue;
if (mod->type() == ResourceType::ZIPFILE)
{
if (!mergeZipFiles(&zipOut, mod->fileinfo(), addedFiles))
{
if (mod->type() == ResourceType::ZIPFILE) {
if (!mergeZipFiles(&zipOut, mod->fileinfo(), addedFiles)) {
zipOut.close();
QFile::remove(targetJarPath);
qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar.";
return false;
}
}
else if (mod->type() == ResourceType::SINGLEFILE)
{
} else if (mod->type() == ResourceType::SINGLEFILE) {
// FIXME: buggy - does not work with addedFiles
auto filename = mod->fileinfo();
if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName()))
{
if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName())) {
zipOut.close();
QFile::remove(targetJarPath);
qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar.";
return false;
}
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
// FIXME: buggy - does not work with addedFiles
auto filename = mod->fileinfo();
@ -190,25 +178,21 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
dir.cdUp();
QString parent_dir = dir.absolutePath();
auto files = QFileInfoList();
MMCZip::collectFileListRecursively(what_to_zip, nullptr, &files, nullptr);
collectFileListRecursively(what_to_zip, nullptr, &files, nullptr);
for (auto e : files) {
if (addedFiles.contains(e.filePath()))
files.removeAll(e);
}
if (!MMCZip::compressDirFiles(&zipOut, parent_dir, files))
{
if (!compressDirFiles(&zipOut, parent_dir, files)) {
zipOut.close();
QFile::remove(targetJarPath);
qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar.";
return false;
}
qDebug() << "Adding folder " << filename.fileName() << " from "
<< filename.absoluteFilePath();
}
else
{
qDebug() << "Adding folder " << filename.fileName() << " from " << filename.absoluteFilePath();
} else {
// Make sure we do not continue launching when something is missing or undefined...
zipOut.close();
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();
QFile::remove(targetJarPath);
qCritical() << "Failed to insert minecraft.jar contents.";
@ -227,8 +210,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
// Recompress the jar
zipOut.close();
if (zipOut.getZipError() != 0)
{
if (zipOut.getZipError() != 0) {
QFile::remove(targetJarPath);
qCritical() << "Failed to finalize minecraft.jar!";
return false;
@ -237,7 +219,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
}
// 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);
for (auto&& fileName : rootDir.entryList(QDir::Files)) {
@ -261,27 +243,23 @@ QString MMCZip::findFolderOfFileInZip(QuaZip* zip, const QString& what, const QS
}
// 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);
for(auto fileName: rootDir.entryList(QDir::Files))
{
if(fileName == what)
{
for (auto fileName : rootDir.entryList(QDir::Files)) {
if (fileName == what) {
result.append(root);
return true;
}
}
for(auto fileName: rootDir.entryList(QDir::Dirs))
{
for (auto fileName : rootDir.entryList(QDir::Dirs)) {
findFilesInZip(zip, what, result, root + fileName);
}
return !result.isEmpty();
}
// 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);
@ -292,13 +270,10 @@ std::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & su
if (numEntries < 0) {
qWarning() << "Failed to enumerate files in archive";
return std::nullopt;
}
else if(numEntries == 0) {
} else if (numEntries == 0) {
qDebug() << "Extracting empty archives seems odd...";
return extracted;
}
else if (!zip->goToFirstFile())
{
} else if (!zip->goToFirstFile()) {
qWarning() << "Failed to seek to first file in zip";
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))) {
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;
}
@ -345,7 +321,8 @@ std::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & su
}
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;
} while (zip->goToNextFile());
@ -354,51 +331,50 @@ std::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & su
}
// 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);
}
// ours
std::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString dir)
std::optional<QStringList> extractDir(QString fileCompressed, QString dir)
{
QuaZip zip(fileCompressed);
if (!zip.open(QuaZip::mdUnzip))
{
if (!zip.open(QuaZip::mdUnzip)) {
// check if this is a minimum size empty zip file...
QFileInfo fileInfo(fileCompressed);
if (fileInfo.size() == 22) {
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 MMCZip::extractSubDir(&zip, "", dir);
return extractSubDir(&zip, "", dir);
}
// 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);
if (!zip.open(QuaZip::mdUnzip))
{
if (!zip.open(QuaZip::mdUnzip)) {
// check if this is a minimum size empty zip file...
QFileInfo fileInfo(fileCompressed);
if (fileInfo.size() == 22) {
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 MMCZip::extractSubDir(&zip, subdir, dir);
return extractSubDir(&zip, subdir, dir);
}
// ours
bool MMCZip::extractFile(QString fileCompressed, QString file, QString target)
bool extractFile(QString fileCompressed, QString file, QString target)
{
QuaZip zip(fileCompressed);
if (!zip.open(QuaZip::mdUnzip))
{
if (!zip.open(QuaZip::mdUnzip)) {
// check if this is a minimum size empty zip file...
QFileInfo fileInfo(fileCompressed);
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();
return false;
}
return MMCZip::extractRelFile(&zip, file, target);
return extractRelFile(&zip, file, target);
}
bool MMCZip::collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList *files,
MMCZip::FilterFunction excludeFilter) {
bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFunction excludeFilter)
{
QDir rootDirectory(rootDir);
if (!rootDirectory.exists()) return false;
if (!rootDirectory.exists())
return false;
QDir directory;
if (subDir == nullptr)
@ -421,7 +398,8 @@ bool MMCZip::collectFileListRecursively(const QString& rootDir, const QString& s
else
directory = QDir(subDir);
if (!directory.exists()) return false; // shouldn't ever happen
if (!directory.exists())
return false; // shouldn't ever happen
// recurse directories
QFileInfoList entries = directory.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden);
@ -439,7 +417,88 @@ bool MMCZip::collectFileListRecursively(const QString& rootDir, const QString& s
continue;
}
files->append(e); // we want the original paths for MMCZip::compressDirFiles
files->append(e); // we want the original paths for compressDirFiles
}
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

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 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
@ -35,24 +36,28 @@
#pragma once
#include <QString>
#include <QFileInfo>
#include <QSet>
#include "minecraft/mod/Mod.h"
#include <functional>
#include <quazip.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 "minecraft/mod/Mod.h"
#include "tasks/Task.h"
namespace MMCZip
{
namespace MMCZip {
using FilterFunction = std::function<bool(const QString&)>;
/**
* Merge two zip files, using a filter function
*/
bool mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained,
const FilterFunction filter = nullptr);
bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& contained, const FilterFunction filter = nullptr);
/**
* Compress directory, by providing a list of files to compress
@ -141,4 +146,47 @@ namespace MMCZip
* \return true for success or false for failure
*/
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

View File

@ -24,6 +24,8 @@
#include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/ResourceFolderModel.h"
#include "net/ApiDownload.h"
ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
ModPlatform::IndexedVersion version,
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::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);
addTask(m_filesNetJob);

132
launcher/WindowsConsole.cpp Normal file
View 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
View 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();

View File

@ -37,11 +37,7 @@
#include <sys.h>
#if defined Q_OS_WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <stdio.h>
#include <windows.h>
#include "WindowsConsole.h"
#endif
// 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
// attach the parent console
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
// 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);
if (AttachWindowsConsole()) {
consoleAttached = true;
}
#endif
@ -188,7 +170,7 @@ void FileLinkApp::runLink()
FS::LinkResult result = { src_path, dst_path, QString::fromStdString(os_err.message()), os_err.value() };
m_path_results.append(result);
} else {
FS::LinkResult result = { src_path, dst_path };
FS::LinkResult result = { src_path, dst_path, "", 0};
m_path_results.append(result);
}
}
@ -248,7 +230,7 @@ void FileLinkApp::readPathPairs()
in >> numLinks;
qDebug() << "numLinks" << numLinks;
for (int i = 0; i < numLinks; i++) {
for (unsigned int i = 0; i < numLinks; i++) {
FS::LinkPair pair;
in >> pair.src;
in >> pair.dst;
@ -271,7 +253,6 @@ FileLinkApp::~FileLinkApp()
fclose(stdout);
fclose(stdin);
fclose(stderr);
FreeConsole();
}
#endif
}

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 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
@ -35,13 +36,14 @@
#include "IconList.h"
#include <FileSystem.h>
#include <QMap>
#include <QEventLoop>
#include <QMimeData>
#include <QUrl>
#include <QFileSystemWatcher>
#include <QSet>
#include <QDebug>
#include <QEventLoop>
#include <QFileSystemWatcher>
#include <QMap>
#include <QMimeData>
#include <QSet>
#include <QUrl>
#include "icons/IconUtils.h"
#define MAX_SIZE 1024
@ -50,17 +52,14 @@ IconList::IconList(const QStringList &builtinPaths, QString path, QObject *paren
QSet<QString> builtinNames;
// add builtin icons
for(auto & builtinPath: builtinPaths)
{
for (auto& builtinPath : builtinPaths) {
QDir instance_icons(builtinPath);
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());
}
}
for(auto & builtinName : builtinNames)
{
for (auto& builtinName : builtinNames) {
addThemeIcon(builtinName);
}
@ -78,17 +77,14 @@ IconList::IconList(const QStringList &builtinPaths, QString path, QObject *paren
void IconList::sortIconList()
{
qDebug() << "Sorting icon list...";
std::sort(icons.begin(), icons.end(), [](const MMCIcon& a, const MMCIcon& b) {
return a.m_key.localeAwareCompare(b.m_key) < 0;
});
std::sort(icons.begin(), icons.end(), [](const MMCIcon& a, const MMCIcon& b) { return a.m_key.localeAwareCompare(b.m_key) < 0; });
reindex();
}
void IconList::directoryChanged(const QString& 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.refresh();
if (is_watching)
@ -100,8 +96,7 @@ void IconList::directoryChanged(const QString &path)
return;
m_dir.refresh();
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);
foo = m_dir.filePath(foo);
}
@ -111,8 +106,7 @@ void IconList::directoryChanged(const QString &path)
auto new_set = new_list.toSet();
#endif
QList<QString> current_list;
for (auto &it : icons)
{
for (auto& it : icons) {
if (!it.has(IconType::FileBased))
continue;
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;
to_add -= current_set;
for (auto remove : to_remove)
{
for (auto remove : to_remove) {
qDebug() << "Removing " << remove;
QFileInfo rmfile(remove);
QString key = rmfile.completeBaseName();
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
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
if (!IconUtils::isIconSuffix(suffix))
key = rmfile.fileName();
int idx = getIconIndex(key);
if (idx == -1)
continue;
icons[idx].remove(IconType::FileBased);
if (icons[idx].type() == IconType::ToBeDeleted)
{
if (icons[idx].type() == IconType::ToBeDeleted) {
beginRemoveRows(QModelIndex(), idx, idx);
icons.remove(idx);
reindex();
endRemoveRows();
}
else
{
} else {
dataChanged(index(idx), index(idx));
}
m_watcher->removePath(remove);
emit iconUpdated(key);
}
for (auto add : to_add)
{
for (auto add : to_add) {
qDebug() << "Adding " << add;
QFileInfo addfile(add);
@ -168,11 +157,10 @@ void IconList::directoryChanged(const QString &path)
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
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
if (!IconUtils::isIconSuffix(suffix))
key = addfile.fileName();
if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased))
{
if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased)) {
m_watcher->addPath(add);
emit iconUpdated(key);
}
@ -213,12 +201,9 @@ void IconList::startWatching()
auto abs_path = m_dir.absolutePath();
FS::ensureFolderPathExists(abs_path);
is_watching = m_watcher->addPath(abs_path);
if (is_watching)
{
if (is_watching) {
qDebug() << "Started watching " << abs_path;
}
else
{
} else {
qDebug() << "Failed to start watching " << abs_path;
}
}
@ -241,7 +226,11 @@ Qt::DropActions IconList::supportedDropActions() const
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)
return true;
@ -250,12 +239,10 @@ bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[mayb
return false;
// files dropped from outside?
if (data->hasUrls())
{
if (data->hasUrls()) {
auto urls = data->urls();
QStringList iconFiles;
for (auto url : urls)
{
for (auto url : urls) {
// only local files may be dropped...
if (!url.isLocalFile())
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 defaultFlags = QAbstractListModel::flags(index);
if (index.isValid())
return Qt::ItemIsDropEnabled | defaultFlags;
else
return Qt::ItemIsDropEnabled | defaultFlags;
}
@ -286,8 +270,7 @@ QVariant IconList::data(const QModelIndex &index, int role) const
if (row < 0 || row >= icons.size())
return QVariant();
switch (role)
{
switch (role) {
case Qt::DecorationRole:
return icons[row].icon();
case Qt::DisplayRole:
@ -307,19 +290,7 @@ int IconList::rowCount(const QModelIndex &parent) const
void IconList::installIcons(const QStringList& iconFiles)
{
for (QString file : iconFiles)
{
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;
}
installIcon(file, {});
}
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())
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);
}
bool IconList::iconFileExists(const QString& key) const
{
auto iconEntry = icon(key);
if(!iconEntry)
{
return false;
}
return iconEntry->has(IconType::FileBased);
return iconEntry && iconEntry->has(IconType::FileBased);
}
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)
{
if (!iconFileExists(key))
return false;
return QFile::remove(icon(key)->getFilePath());
return iconFileExists(key) && QFile::remove(icon(key)->getFilePath());
}
bool IconList::trashIcon(const QString& key)
{
if (!iconFileExists(key))
return false;
return FS::trash(icon(key)->getFilePath(), nullptr);
return iconFileExists(key) && FS::trash(icon(key)->getFilePath(), nullptr);
}
bool IconList::addThemeIcon(const QString& key)
{
auto iter = name_index.find(key);
if (iter != name_index.end())
{
if (iter != name_index.end()) {
auto& oldOne = icons[*iter];
oldOne.replace(Builtin, key);
dataChanged(index(*iter), index(*iter));
return true;
}
else
{
// add a new icon
beginInsertRows(QModelIndex(), icons.size(), icons.size());
{
@ -392,7 +352,6 @@ bool IconList::addThemeIcon(const QString& key)
endInsertRows();
return true;
}
}
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())
return false;
auto iter = name_index.find(key);
if (iter != name_index.end())
{
if (iter != name_index.end()) {
auto& oldOne = icons[*iter];
oldOne.replace(type, icon, path);
dataChanged(index(*iter), index(*iter));
return true;
}
else
{
// add a new icon
beginInsertRows(QModelIndex(), icons.size(), icons.size());
{
@ -423,7 +379,6 @@ bool IconList::addIcon(const QString &key, const QString &name, const QString &p
endInsertRows();
return true;
}
}
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);
}
void IconList::reindex()
{
name_index.clear();
int i = 0;
for (auto &iter : icons)
{
for (auto& iter : icons) {
name_index[iter.m_key] = i;
i++;
}
@ -472,5 +425,3 @@ QString IconList::getDirectory() const
{
return m_dir.absolutePath();
}
//#include "IconList.moc"

View File

@ -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");
* 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
* limitations under the License.
*/
#pragma once
#include <QMutex>
#include <QAbstractListModel>
#include <QFile>
#include <QDir>
#include <QFile>
#include <QMutex>
#include <QtGui/QIcon>
#include <memory>
@ -29,8 +48,7 @@
class QFileSystemWatcher;
class IconList : public QAbstractListModel
{
class IconList : public QAbstractListModel {
Q_OBJECT
public:
explicit IconList(const QStringList& builtinPaths, QString path, QObject* parent = 0);
@ -80,6 +98,7 @@ public slots:
protected slots:
void fileChanged(const QString& path);
void SettingChanged(const Setting& setting, QVariant value);
private:
shared_qobject_ptr<QFileSystemWatcher> m_watcher;
bool is_watching;

View File

@ -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 "FileSystem.h"
#include <QDirIterator>
#include <array>
#include "FileSystem.h"
namespace {
std::array<const char *, 6> validIconExtensions = {{
"svg",
"png",
"ico",
"gif",
"jpg",
"jpeg"
}};
static const QStringList validIconExtensions = { { "svg", "png", "ico", "gif", "jpg", "jpeg" } };
}
namespace IconUtils {
QString findBestIconIn(const QString &folder, const QString & iconKey) {
int best_found = validIconExtensions.size();
QString findBestIconIn(const QString& folder, const QString& iconKey)
{
QString best_filename;
QDirIterator it(folder, QDir::NoDotAndDotDot | QDir::Files, QDirIterator::NoIteratorFlags);
@ -27,36 +53,20 @@ QString findBestIconIn(const QString &folder, const QString & iconKey) {
it.next();
auto fileInfo = it.fileInfo();
if(fileInfo.completeBaseName() != iconKey)
continue;
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();
if (fileInfo.completeBaseName() == iconKey && isIconSuffix(fileInfo.suffix()))
return fileInfo.absoluteFilePath();
}
}
}
return FS::PathCombine(folder, best_filename);
return {};
}
QString getIconFilter() {
QString out;
QTextStream stream(&out);
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;
QString getIconFilter()
{
return "(*." + validIconExtensions.join(" *.") + ")";
}
bool isIconSuffix(QString suffix)
{
return validIconExtensions.contains(suffix);
}
} // namespace IconUtils

View File

@ -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
#include <QString>
@ -10,4 +45,5 @@ QString findBestIconIn(const QString &folder, const QString & iconKey);
// Get icon file type filter for file browser dialogs
QString getIconFilter();
}
bool isIconSuffix(QString suffix);
} // namespace IconUtils

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 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
@ -40,8 +41,7 @@
IconType operator--(IconType& t, int)
{
IconType temp = t;
switch (t)
{
switch (t) {
case IconType::Builtin:
t = IconType::ToBeDeleted;
break;
@ -52,8 +52,7 @@ IconType operator--(IconType &t, int)
t = IconType::Transient;
break;
default:
{
}
break;
}
return temp;
}
@ -90,10 +89,8 @@ void MMCIcon::remove(IconType rm_type)
{
m_images[rm_type].filename = QString();
m_images[rm_type].icon = QIcon();
for (auto iter = rm_type; iter != IconType::ToBeDeleted; iter--)
{
if (m_images[iter].present())
{
for (auto iter = rm_type; iter != IconType::ToBeDeleted; iter--) {
if (m_images[iter].present()) {
m_current_type = iter;
return;
}
@ -103,8 +100,7 @@ void MMCIcon::remove(IconType rm_type)
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_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)
{
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_images[new_type].icon = QIcon();
@ -131,7 +126,6 @@ QString MMCIcon::getFilePath() const
return m_images[m_current_type].filename;
}
bool MMCIcon::isBuiltIn() const
{
return m_current_type == IconType::Builtin;

View File

@ -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");
* 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
* limitations under the License.
*/
#pragma once
#include <QString>
#include <QDateTime>
#include <QIcon>
#include <QString>
enum IconType : unsigned
{
Builtin,
Transient,
FileBased,
ICONS_TOTAL,
ToBeDeleted
};
enum IconType : unsigned { Builtin, Transient, FileBased, ICONS_TOTAL, ToBeDeleted };
struct MMCImage
{
struct MMCImage {
QIcon icon;
QString key;
QString filename;
bool present() const
{
return !icon.isNull() || !key.isEmpty();
}
bool present() const { return !icon.isNull() || !key.isEmpty(); }
};
struct MMCIcon
{
struct MMCIcon {
QString m_key;
QString m_name;
MMCImage m_images[ICONS_TOTAL];

View File

@ -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 "BaseVersion.h"
#include "StringUtils.h"
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);
if (archCompare != 0)
return archCompare < 0;
if(id < rhs.id)
{
if (id < rhs.id) {
return true;
}
if(id > rhs.id)
{
if (id > rhs.id) {
return false;
}
return StringUtils::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0;
@ -27,3 +44,21 @@ bool JavaInstall::operator>(const JavaInstall &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);
}
}

View File

@ -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
#include "BaseVersion.h"
#include "JavaVersion.h"
struct JavaInstall : public BaseVersion
{
struct JavaInstall : public BaseVersion {
JavaInstall() {}
JavaInstall(QString id, QString arch, QString path)
: id(id), arch(arch), path(path)
{
}
virtual QString descriptor()
{
return id.toString();
}
JavaInstall(QString id, QString arch, QString path) : id(id), arch(arch), path(path) {}
virtual QString descriptor() { return id.toString(); }
virtual QString name()
{
return id.toString();
}
virtual QString name() { return id.toString(); }
virtual QString typeString() const
{
return arch;
}
virtual QString typeString() const { 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);

View File

@ -28,7 +28,7 @@ void Update::executeTask()
{
connect(m_updateTask.get(), &Task::finished, this, &Update::updateFinished);
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::details, this, &Update::setDetails);
emit progressReportingRequest();

View File

@ -15,7 +15,7 @@
#include "BaseEntity.h"
#include "net/Download.h"
#include "net/ApiDownload.h"
#include "net/HttpMetaCache.h"
#include "net/NetJob.h"
#include "Json.h"
@ -130,7 +130,7 @@ void Meta::BaseEntity::load(Net::Mode loadType)
auto url = this->url();
auto entry = APPLICATION->metacache()->resolveEntry("meta", localFilename());
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.
* If that fails, the file is not written to storage.

View File

@ -45,7 +45,7 @@
#include "AssetsUtils.h"
#include "FileSystem.h"
#include "net/Download.h"
#include "net/ApiDownload.h"
#include "net/ChecksumValidator.h"
#include "BuildConfig.h"
@ -311,7 +311,7 @@ NetAction::Ptr AssetObject::getDownloadAction()
QFileInfo objectFile(getLocalPath());
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())
{
auto rawHash = QByteArray::fromHex(hash.toLatin1());

View File

@ -36,7 +36,7 @@
#include "Library.h"
#include "MinecraftInstance.h"
#include <net/Download.h>
#include <net/ApiDownload.h>
#include <net/ChecksumValidator.h>
#include <FileSystem.h>
#include <BuildConfig.h>
@ -129,14 +129,14 @@ QList<NetAction::Ptr> Library::getDownloads(
if(sha1.size())
{
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));
qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url;
out.append(dl);
}
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;
}
return true;

View File

@ -4,6 +4,7 @@
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* 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
* it under the terms of the GNU General Public License as published by
@ -60,7 +61,6 @@
#include "launch/steps/QuitAfterGameStop.h"
#include "minecraft/launch/LauncherPartLaunch.h"
#include "minecraft/launch/DirectJavaLaunch.h"
#include "minecraft/launch/ModMinecraftJar.h"
#include "minecraft/launch/ClaimAccount.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("PermGen"), memorySetting);
// Minecraft launch method
auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false);
m_settings->registerOverride(global_settings->getSetting("MCLaunchMethod"), launchMethodOverride);
// Native library workarounds
auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false);
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("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");
}
@ -391,6 +391,12 @@ QStringList MinecraftInstance::extraArguments()
agent->library()->getApplicableFiles(runtimeContext(), jar, temp1, temp2, temp3, getLocalLibraryPath());
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;
}
@ -832,7 +838,7 @@ QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSess
{
addToFilter(sessionRef.session, tr("<SESSION ID>"));
}
if (sessionRef.access_token != "offline") {
if (sessionRef.access_token != "0") {
addToFilter(sessionRef.access_token, tr("<ACCESS TOKEN>"));
}
if(sessionRef.client_token.size()) {
@ -979,15 +985,6 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
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)
{
process->appendStep(makeShared<CreateGameFolders>(pptr));
@ -1061,24 +1058,12 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
{
// actually launch the game
auto method = launchMethod();
if(method == "LauncherPart")
{
auto step = makeShared<LauncherPartLaunch>(pptr);
step->setWorkingDirectory(gameRoot());
step->setAuthSession(session);
step->setServerToJoin(serverToJoin);
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
if(getPostExitCommand().size())
@ -1100,11 +1085,6 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
return m_launchProcess;
}
QString MinecraftInstance::launchMethod()
{
return settings()->get("MCLaunchMethod").toString();
}
JavaVersion MinecraftInstance::getJavaVersion()
{
return JavaVersion(settings()->get("JavaVersion").toString());

View File

@ -165,8 +165,6 @@ public:
protected:
QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session);
QStringList validLaunchMethods();
QString launchMethod();
protected: // data
std::shared_ptr<PackProfile> m_components;

View File

@ -22,7 +22,7 @@ void MinecraftLoadAndCheck::executeTask()
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::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);
}

View File

@ -100,7 +100,7 @@ void MinecraftUpdate::next()
disconnect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed);
disconnect(task.get(), &Task::aborted, this, &Task::abort);
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::details, this, &MinecraftUpdate::setDetails);
}
@ -120,7 +120,7 @@ void MinecraftUpdate::next()
connect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed);
connect(task.get(), &Task::aborted, this, &Task::abort);
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::details, this, &MinecraftUpdate::setDetails);
// if the task is already running, do not start it again

View File

@ -374,6 +374,10 @@ bool AccountData::resumeStateFromV3(QJsonObject data) {
}
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");
if(!entitlementFromJSONV3(data, minecraftEntitlement)) {
if(minecraftProfile.validity != Katabasis::Validity::None) {

View File

@ -26,6 +26,7 @@ bool AuthSession::MakeOffline(QString offline_playername)
return false;
}
session = "-";
access_token = "0";
player_name = offline_playername;
status = PlayableOffline;
return true;

View File

@ -37,6 +37,7 @@
#include "MinecraftAccount.h"
#include <QCryptographicHash>
#include <QUuid>
#include <QJsonObject>
#include <QJsonArray>
@ -93,14 +94,14 @@ MinecraftAccountPtr MinecraftAccount::createOffline(const QString &username)
{
auto account = makeShared<MinecraftAccount>();
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.issueInstant = QDateTime::currentDateTimeUtc();
account->data.yggdrasilToken.extra["userName"] = username;
account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
account->data.minecraftEntitlement.ownsMinecraft = 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.validity = Katabasis::Validity::Certain;
return account;
@ -334,3 +335,32 @@ void MinecraftAccount::incrementUses()
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);
}

View File

@ -98,6 +98,8 @@ public: /* construction */
static MinecraftAccountPtr loadFromJsonV2(const QJsonObject &json);
static MinecraftAccountPtr loadFromJsonV3(const QJsonObject &json);
static QUuid uuidFromUsername(QString username);
//! Saves a MinecraftAccount to a JSON object and returns it.
QJsonObject saveToJson() const;

View File

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

View File

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

View File

@ -1,9 +1,9 @@
#include "LocalModParseTask.h"
#include <qdcss.h>
#include <quazip/quazip.h>
#include <quazip/quazipfile.h>
#include <toml++/toml.h>
#include <qdcss.h>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
@ -369,12 +369,11 @@ ModDetails ReadQuiltModInfo(QByteArray contents)
details.icon_file = icon.toString();
}
}
}
return details;
}
ModDetails ReadForgeInfo(QString fileName)
ModDetails ReadForgeInfo(QByteArray contents)
{
ModDetails details;
// Read the data
@ -382,7 +381,7 @@ ModDetails ReadForgeInfo(QString fileName)
details.mod_id = "Forge";
details.homeurl = "http://www.minecraftforge.net/forum/";
INIFile ini;
if (!ini.loadFile(fileName))
if (!ini.loadFile(contents))
return details;
QString major = ini.get("forge.major.number", "0").toString();
@ -554,7 +553,7 @@ bool processZIP(Mod& mod, ProcessingLevel level)
return false;
}
details = ReadForgeInfo(file.getFileName());
details = ReadForgeInfo(file.readAll());
file.close();
zip.close();

View File

@ -44,7 +44,8 @@ static const QMap<PackedResourceType, QString> s_packed_type_names = {
namespace ResourceUtils {
PackedResourceType identify(QFileInfo file){
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";
return PackedResourceType::Mod;
} else if (ResourcePackUtils::validate(file)) {

View File

@ -7,6 +7,8 @@
#include "Application.h"
#include "net/ApiDownload.h"
AssetUpdateTask::AssetUpdateTask(MinecraftInstance * inst)
{
m_inst = inst;
@ -34,7 +36,7 @@ void AssetUpdateTask::executeTask()
entry->setStale(true);
auto hexSha1 = assets->sha1.toLatin1();
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());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
job->addNetAction(dl);
@ -45,7 +47,7 @@ void AssetUpdateTask::executeTask()
connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetIndexFailed);
connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
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";
downloadJob->start();
@ -84,7 +86,7 @@ void AssetUpdateTask::assetIndexFinished()
connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetsFailed);
connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
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();
return;
}

View File

@ -8,6 +8,8 @@
#include "BuildConfig.h"
#include "Application.h"
#include "net/ApiDownload.h"
FMLLibrariesTask::FMLLibrariesTask(MinecraftInstance * inst)
{
m_inst = inst;
@ -68,14 +70,14 @@ void FMLLibrariesTask::executeTask()
{
auto entry = metacache->resolveEntry("fmllibs", 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::failed, this, &FMLLibrariesTask::fmllibsFailed);
connect(dljob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
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->start();
}

View File

@ -70,7 +70,7 @@ void LibrariesTask::executeTask()
connect(downloadJob.get(), &NetJob::failed, this, &LibrariesTask::jarlibFailed);
connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
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();
}

View File

@ -145,7 +145,8 @@ void EnsureMetadataTask::executeTask()
connect(project_task.get(), &Task::finished, this, [=] {
invalidade_leftover();
project_task->deleteLater();
m_current_task = nullptr;
if (m_current_task)
m_current_task.reset();
});
m_current_task = project_task;
@ -154,7 +155,8 @@ void EnsureMetadataTask::executeTask()
connect(version_task.get(), &Task::finished, [=] {
version_task->deleteLater();
m_current_task = nullptr;
if (m_current_task)
m_current_task.reset();
});
if (m_mods.size() > 1)

View File

@ -53,6 +53,8 @@
#include "meta/Version.h"
#include "meta/VersionList.h"
#include "net/ApiDownload.h"
#include "BuildConfig.h"
#include "Application.h"
@ -84,7 +86,7 @@ void PackInstallTask::executeTask()
NetJob::Ptr netJob{ new NetJob("ATLauncher::VersionFetch", APPLICATION->network()) };
auto searchUrl =
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::failed, this, &PackInstallTask::onDownloadFailed);
@ -659,7 +661,7 @@ void PackInstallTask::installConfigs()
auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", path);
entry->setStale(true);
auto dl = Net::Download::makeCached(url, entry);
auto dl = Net::ApiDownload::makeCached(url, entry);
if (!m_version.configs.sha1.isEmpty()) {
auto rawSha1 = QByteArray::fromHex(m_version.configs.sha1.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
@ -684,7 +686,7 @@ void PackInstallTask::installConfigs()
abortable = true;
setProgress(current, total);
});
connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress);
connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propagateStepProgress);
connect(jobPtr.get(), &NetJob::aborted, [&]{
abortable = false;
jobPtr.reset();
@ -782,7 +784,7 @@ void PackInstallTask::downloadMods()
entry->setStale(true);
modsToExtract.insert(entry->getFullPath(), mod);
auto dl = Net::Download::makeCached(url, entry);
auto dl = Net::ApiDownload::makeCached(url, entry);
if (!mod.md5.isEmpty()) {
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
@ -794,7 +796,7 @@ void PackInstallTask::downloadMods()
entry->setStale(true);
modsToDecomp.insert(entry->getFullPath(), mod);
auto dl = Net::Download::makeCached(url, entry);
auto dl = Net::ApiDownload::makeCached(url, entry);
if (!mod.md5.isEmpty()) {
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
@ -808,7 +810,7 @@ void PackInstallTask::downloadMods()
auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", cacheName);
entry->setStale(true);
auto dl = Net::Download::makeCached(url, entry);
auto dl = Net::ApiDownload::makeCached(url, entry);
if (!mod.md5.isEmpty()) {
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
@ -852,7 +854,7 @@ void PackInstallTask::downloadMods()
abortable = true;
setProgress(current, total);
});
connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress);
connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propagateStepProgress);
connect(jobPtr.get(), &NetJob::aborted, [&]
{
abortable = false;

View File

@ -1,7 +1,9 @@
#include "FileResolvingTask.h"
#include "Json.h"
#include "net/ApiUpload.h"
#include "net/Upload.h"
#include "net/ApiDownload.h"
#include "modplatform/modrinth/ModrinthPackIndex.h"
@ -21,6 +23,10 @@ bool Flame::FileResolvingTask::abort()
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..."));
setProgress(0, 3);
m_dljob.reset(new NetJob("Mod id resolver", m_network));
@ -34,7 +40,7 @@ void Flame::FileResolvingTask::executeTask()
return l;
}));
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);
auto step_progress = std::make_shared<TaskStepProgress>();
@ -48,7 +54,7 @@ void Flame::FileResolvingTask::executeTask()
stepProgress(*step_progress);
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) {
qDebug() << "Resolve slug progress" << current << total;
step_progress->update(current, total);
@ -95,7 +101,7 @@ void Flame::FileResolvingTask::netJobFinished()
if (!hash.isEmpty()) {
auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash);
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; });
m_checkJob->addNetAction(dl);
@ -114,7 +120,7 @@ void Flame::FileResolvingTask::netJobFinished()
stepProgress(*step_progress);
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) {
qDebug() << "Resolve slug progress" << current << total;
step_progress->update(current, total);
@ -128,7 +134,8 @@ void Flame::FileResolvingTask::netJobFinished()
m_checkJob->start();
}
void Flame::FileResolvingTask::modrinthCheckFinished() {
void Flame::FileResolvingTask::modrinthCheckFinished()
{
setProgress(2, 3);
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
auto block = std::make_shared<QList<File*>>();
auto it = blockedProjects.keys();
std::copy_if(it.begin(), it.end(), std::back_inserter(*block), [](File *f) {
return !f->resolved;
});
std::copy_if(it.begin(), it.end(), std::back_inserter(*block), [](File* f) { return !f->resolved; });
// Display not found mods early
if (!block->empty()) {
// 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 output = std::make_shared<QByteArray>();
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;
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 json = QJsonDocument::fromJson(*output);
auto base = Json::requireString(Json::requireObject(Json::requireObject(Json::requireObject(json),"data"),"links"),
"websiteUrl");
auto base =
Json::requireString(Json::requireObject(Json::requireObject(Json::requireObject(json), "data"), "links"), "websiteUrl");
auto link = QString("%1/download/%2").arg(base, QString::number(mod->fileId));
mod->websiteUrl = link;
});
@ -192,7 +197,7 @@ void Flame::FileResolvingTask::modrinthCheckFinished() {
stepProgress(*step_progress);
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) {
qDebug() << "Resolve slug progress" << current << total;
step_progress->update(current, total);

View File

@ -8,7 +8,9 @@
#include "Application.h"
#include "BuildConfig.h"
#include "Json.h"
#include "net/ApiUpload.h"
#include "net/NetJob.h"
#include "net/ApiDownload.h"
#include "net/Upload.h"
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);
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;
}
@ -38,7 +40,7 @@ auto FlameAPI::getModFileChangelog(int modId, int fileId) -> QString
auto netJob = makeShared<NetJob>(QString("Flame::FileChangelog"), APPLICATION->network());
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")
.arg(QString::fromStdString(std::to_string(modId)), QString::fromStdString(std::to_string(fileId))),
response));
@ -74,7 +76,7 @@ auto FlameAPI::getModDescription(int modId) -> QString
auto netJob = makeShared<NetJob>(QString("Flame::ModDescription"), APPLICATION->network());
auto response = std::make_shared<QByteArray>();
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] {
QJsonParseError parse_error{};
@ -113,7 +115,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe
auto response = std::make_shared<QByteArray>();
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] {
QJsonParseError parse_error{};
@ -173,7 +175,7 @@ Task::Ptr FlameAPI::getProjects(QStringList addonIds, std::shared_ptr<QByteArray
QJsonDocument body(body_obj);
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; });
@ -195,7 +197,7 @@ Task::Ptr FlameAPI::getFiles(const QStringList& fileIds, std::shared_ptr<QByteAr
QJsonDocument body(body_obj);
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; });

View File

@ -13,6 +13,8 @@
#include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/ResourceFolderModel.h"
#include "net/ApiDownload.h"
static FlameAPI api;
bool FlameCheckUpdate::abort()
@ -33,7 +35,7 @@ ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info)
auto response = std::make_shared<QByteArray>();
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);
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 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);
QObject::connect(get_file_info_job, &NetJob::succeeded, [response, &ver]() {

View File

@ -57,14 +57,11 @@
#include <QDebug>
#include <QFileInfo>
#include "meta/Index.h"
#include "meta/VersionList.h"
#include "minecraft/World.h"
#include "minecraft/mod/tasks/LocalResourceParse.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" } };
#include "net/ApiDownload.h"
static const FlameAPI api;
@ -259,6 +256,56 @@ bool FlameCreationTask::updateInstance()
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()
{
QEventLoop loop;
@ -297,22 +344,29 @@ bool FlameCreationTask::createInstance()
}
}
QString forgeVersion;
QString fabricVersion;
// TODO: is Quilt relevant here?
QString loaderType;
QString loaderUid;
QString loaderVersion;
for (auto& loader : m_pack.minecraft.modLoaders) {
auto id = loader.id;
if (id.startsWith("forge-")) {
id.remove("forge-");
forgeVersion = id;
continue;
}
if (id.startsWith("fabric-")) {
loaderType = "forge";
loaderUid = "net.minecraftforge";
} else if (loaderType == "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;
}
logWarning(tr("Unknown mod loader in manifest: %1").arg(id));
loaderVersion = id;
}
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
@ -329,19 +383,12 @@ bool FlameCreationTask::createInstance()
auto components = instance.getPackProfile();
components->buildingFromScratch();
components->setComponentVersion("net.minecraft", mcVersion, true);
if (!forgeVersion.isEmpty()) {
// FIXME: dirty, nasty, hack. Proper solution requires dependency resolution and knowledge of the metadata.
if (forgeVersion == "recommended") {
if (forgemap.contains(mcVersion)) {
forgeVersion = forgemap[mcVersion];
} else {
logWarning(tr("Could not map recommended Forge version for Minecraft %1").arg(mcVersion));
if (!loaderType.isEmpty()) {
auto version = getVersionForLoader(loaderUid, loaderType, loaderVersion, mcVersion);
if (version.isEmpty())
return false;
components->setComponentVersion(loaderUid, version);
}
}
components->setComponentVersion("net.minecraftforge", forgeVersion);
}
if (!fabricVersion.isEmpty())
components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion);
if (m_instIcon != "default") {
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::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);
m_mod_id_resolver->start();
@ -477,7 +524,7 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
case Flame::File::Type::Mod: {
if (!result.url.isEmpty()) {
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);
}
break;
@ -506,7 +553,7 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
setDetails(tr("%1 out of %2 complete").arg(current).arg(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);
setStatus(tr("Downloading mods..."));
@ -545,7 +592,6 @@ void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods)
setAbortable(true);
}
void FlameCreationTask::validateZIPResouces()
{
qDebug() << "Validating whether resources stored as .zip are in the right place";
@ -563,6 +609,8 @@ void FlameCreationTask::validateZIPResouces()
if (FS::move(localPath, destPath)) {
return destPath;
}
} else {
qDebug() << "Target folder of" << fileName << "is correct at" << targetFolder;
}
return localPath;
};
@ -584,6 +632,9 @@ void FlameCreationTask::validateZIPResouces()
QString worldPath;
switch (type) {
case PackedResourceType::Mod:
validatePath(fileName, targetFolder, "mods");
break;
case PackedResourceType::ResourcePack:
validatePath(fileName, targetFolder, "resourcepacks");
break;
@ -593,9 +644,6 @@ void FlameCreationTask::validateZIPResouces()
case PackedResourceType::DataPack:
validatePath(fileName, targetFolder, "datapacks");
break;
case PackedResourceType::Mod :
validatePath(fileName, targetFolder, "mods");
break;
case PackedResourceType::ShaderPack:
// 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

View File

@ -57,10 +57,7 @@ class FlameCreationTask final : public InstanceCreationTask {
QString id,
QString version_id,
QString original_instance_id = {})
: InstanceCreationTask()
, m_parent(parent)
, m_managed_id(std::move(id))
, m_managed_version_id(std::move(version_id))
: InstanceCreationTask(), m_parent(parent), m_managed_id(std::move(id)), m_managed_version_id(std::move(version_id))
{
setStagingPath(staging_path);
setParentSettings(global_settings);
@ -78,6 +75,7 @@ class FlameCreationTask final : public InstanceCreationTask {
void setupDownloadJob(QEventLoop&);
void copyBlockedMods(QList<BlockedMod> const& blocked_mods);
void validateZIPResouces();
QString getVersionForLoader(QString uid, QString loaderType, QString version, QString mcVersion);
private:
QWidget* m_parent = nullptr;

View File

@ -26,6 +26,7 @@
#include <QMessageBox>
#include <QtConcurrentRun>
#include <algorithm>
#include <iterator>
#include <memory>
#include "Json.h"
#include "MMCZip.h"
@ -64,20 +65,11 @@ void FlamePackExportTask::executeTask()
bool FlamePackExportTask::abort()
{
if (task != nullptr) {
if (task) {
task->abort();
task = nullptr;
emitAborted();
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;
}
@ -166,7 +158,7 @@ void FlamePackExportTask::collectHashes()
stepProgress(*progressStep);
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) {
progressStep->update(current, total);
@ -336,89 +328,40 @@ void FlamePackExportTask::buildZip()
setStatus(tr("Adding files..."));
setProgress(4, 5);
buildZipFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]() {
QuaZip zip(output);
if (!zip.open(QuaZip::mdCreate)) {
QFile::remove(output);
return BuildZipResult(tr("Could not create file"));
}
auto zipTask = makeShared<MMCZip::ExportToZipTask>(output, gameRoot, files, "overrides/", true);
zipTask->addExtraFile("manifest.json", generateIndex());
zipTask->addExtraFile("modlist.html", generateHTML());
if (buildZipFuture.isCanceled())
return BuildZipResult();
QuaZipFile indexFile(&zip);
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());
QStringList exclude;
std::transform(resolvedFiles.keyBegin(), resolvedFiles.keyEnd(), std::back_insert_iterator(exclude),
[this](QString file) { return gameRoot.relativeFilePath(file); });
zipTask->setExcludeFiles(exclude);
auto progressStep = std::make_shared<TaskStepProgress>();
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"));
}
connect(zipTask.get(), &Task::finished, this, [this, progressStep] {
progressStep->state = TaskStepState::Succeeded;
stepProgress(*progressStep);
return BuildZipResult();
});
connect(&buildZipWatcher, &QFutureWatcher<BuildZipResult>::finished, this, &FlamePackExportTask::finish);
buildZipWatcher.setFuture(buildZipFuture);
}
void FlamePackExportTask::finish()
{
if (buildZipFuture.isCanceled())
emitAborted();
else {
const BuildZipResult result = buildZipFuture.result();
if (result.has_value())
emitFailed(result.value());
else
emitSucceeded();
}
connect(zipTask.get(), &Task::succeeded, this, &FlamePackExportTask::emitSucceeded);
connect(zipTask.get(), &Task::aborted, this, &FlamePackExportTask::emitAborted);
connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) {
progressStep->state = TaskStepState::Failed;
stepProgress(*progressStep);
emitFailed(reason);
});
connect(zipTask.get(), &Task::stepProgress, this, &FlamePackExportTask::propagateStepProgress);
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()
@ -471,3 +414,18 @@ QByteArray FlamePackExportTask::generateIndex()
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();
}

View File

@ -19,8 +19,6 @@
#pragma once
#include <QFuture>
#include <QFutureWatcher>
#include "BaseInstance.h"
#include "MMCZip.h"
#include "minecraft/MinecraftInstance.h"
@ -52,7 +50,6 @@ class FlamePackExportTask : public Task {
const QString output;
const MMCZip::FilterFunction filter;
typedef std::optional<QString> BuildZipResult;
struct ResolvedFile {
int addonId;
int version;
@ -76,15 +73,13 @@ class FlamePackExportTask : public Task {
QMap<QString, HashInfo> pendingHashes{};
QMap<QString, ResolvedFile> resolvedFiles{};
Task::Ptr task;
QFuture<BuildZipResult> buildZipFuture;
QFutureWatcher<BuildZipResult> buildZipWatcher;
void collectFiles();
void collectHashes();
void makeApiRequest();
void getProjectsInfo();
void buildZip();
void finish();
QByteArray generateIndex();
QByteArray generateHTML();
};

View File

@ -76,13 +76,8 @@ bool Flame::File::parseFromObject(const QJsonObject& obj, bool throw_on_blocked
// It is also optional
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";
}
// get the hash
hash = QString();
auto hashes = Json::ensureArray(obj, "hashes");

View 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

View 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

View File

@ -10,6 +10,8 @@
#include "modplatform/ModIndex.h"
#include "net/ApiDownload.h"
Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&& callbacks) const
{
auto search_url_optional = getSearchURL(args);
@ -23,7 +25,7 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&
auto response = std::make_shared<QByteArray>();
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] {
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 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] {
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());
netJob->addNetAction(Net::Download::makeByteArray(QUrl(project_url), response));
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(project_url), response));
return netJob;
}

View 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

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

View 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

View 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

View File

@ -40,6 +40,8 @@
#include "BuildConfig.h"
#include "Application.h"
#include "net/ApiDownload.h"
namespace LegacyFTB {
void PackFetchTask::fetch()
@ -51,7 +53,7 @@ void PackFetchTask::fetch()
QUrl publicPacksUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/modpacks.xml");
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");
qDebug() << "Downloading thirdparty version info from" << thirdPartyUrl.toString();
@ -71,7 +73,7 @@ void PackFetchTask::fetchPrivate(const QStringList& toFetch)
for (auto& packCode : toFetch) {
auto data = std::make_shared<QByteArray>();
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] {
ModpackList packs;

View File

@ -37,16 +37,18 @@
#include <QtConcurrent>
#include "MMCZip.h"
#include "BaseInstance.h"
#include "FileSystem.h"
#include "settings/INISettingsObject.h"
#include "MMCZip.h"
#include "minecraft/GradleSpecifier.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
#include "minecraft/GradleSpecifier.h"
#include "settings/INISettingsObject.h"
#include "BuildConfig.h"
#include "Application.h"
#include "BuildConfig.h"
#include "net/ApiDownload.h"
namespace LegacyFTB {
@ -65,6 +67,7 @@ void PackInstallTask::executeTask()
void PackInstallTask::downloadPack()
{
setStatus(tr("Downloading zip for %1").arg(m_pack.name));
setProgress(1, 4);
setAbortable(false);
archivePath = QString("%1/%2/%3").arg(m_pack.dir, m_version.replace(".", "_"), m_pack.file);
@ -76,13 +79,12 @@ void PackInstallTask::downloadPack()
} else {
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::failed, this, &PackInstallTask::onDownloadFailed);
connect(netJobContainer.get(), &NetJob::progress, this, &PackInstallTask::onDownloadProgress);
connect(netJobContainer.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress);
connect(netJobContainer.get(), &NetJob::aborted, this, &PackInstallTask::onDownloadAborted);
connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::unzip);
connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::emitFailed);
connect(netJobContainer.get(), &NetJob::stepProgress, this, &PackInstallTask::propagateStepProgress);
connect(netJobContainer.get(), &NetJob::aborted, this, &PackInstallTask::emitAborted);
netJobContainer->start();
@ -90,27 +92,6 @@ void PackInstallTask::downloadPack()
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()
{
setStatus(tr("Extracting modpack"));
@ -120,16 +101,17 @@ void PackInstallTask::unzip()
QDir extractDir(m_stagingPath);
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));
return;
}
#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
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
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onUnzipFinished);
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &PackInstallTask::onUnzipCanceled);
@ -151,11 +133,9 @@ void PackInstallTask::install()
setStatus(tr("Installing modpack"));
progress(3, 4);
QDir unzipMcDir(m_stagingPath + "/unzip/minecraft");
if(unzipMcDir.exists())
{
if (unzipMcDir.exists()) {
// 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!"));
return;
}
@ -175,8 +155,7 @@ void PackInstallTask::install()
// handle different versions
QFile packJson(m_stagingPath + "/.minecraft/pack.json");
QDir jarmodDir = QDir(m_stagingPath + "/unzip/instMods");
if(packJson.exists())
{
if (packJson.exists()) {
packJson.open(QIODevice::ReadOnly | QIODevice::Text);
QJsonDocument doc = QJsonDocument::fromJson(packJson.readAll());
packJson.close();
@ -184,11 +163,9 @@ void PackInstallTask::install()
// we only care about the libs
QJsonArray libs = doc.object().value("libraries").toArray();
foreach (const QJsonValue &value, libs)
{
foreach (const QJsonValue& value, libs) {
QString nameValue = value.toObject().value("name").toString();
if(!nameValue.startsWith("net.minecraftforge"))
{
if (!nameValue.startsWith("net.minecraftforge")) {
continue;
}
@ -199,16 +176,13 @@ void PackInstallTask::install()
fallback = false;
break;
}
}
if(jarmodDir.exists())
{
if (jarmodDir.exists()) {
qDebug() << "Found jarmods, installing...";
QStringList jarmods;
for (auto info: jarmodDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files))
{
for (auto info : jarmodDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) {
qDebug() << "Jarmod:" << info.fileName();
jarmods.push_back(info.absoluteFilePath());
}
@ -220,8 +194,7 @@ void PackInstallTask::install()
// just nuke unzip directory, it s not needed anymore
FS::deletePath(m_stagingPath + "/unzip");
if(fallback)
{
if (fallback) {
// TODO: Some fallback mechanism... or just keep failing!
emitFailed(tr("No installation method found!"));
return;
@ -232,8 +205,7 @@ void PackInstallTask::install()
progress(4, 4);
instance.setName(name());
if(m_instIcon == "default")
{
if (m_instIcon == "default") {
m_instIcon = "ftb_logo";
}
instance.setIconKey(m_instIcon);
@ -252,4 +224,4 @@ bool PackInstallTask::abort()
return InstanceTask::abort();
}
}
} // namespace LegacyFTB

View File

@ -1,12 +1,12 @@
#pragma once
#include "InstanceTask.h"
#include "net/NetJob.h"
#include <quazip/quazip.h>
#include <quazip/quazipdir.h>
#include "InstanceTask.h"
#include "PackHelpers.h"
#include "meta/Index.h"
#include "meta/Version.h"
#include "meta/VersionList.h"
#include "PackHelpers.h"
#include "net/NetJob.h"
#include "net/NetJob.h"
@ -14,8 +14,7 @@
namespace LegacyFTB {
class PackInstallTask : public InstanceTask
{
class PackInstallTask : public InstanceTask {
Q_OBJECT
public:
@ -35,10 +34,6 @@ private:
void install();
private slots:
void onDownloadSucceeded();
void onDownloadFailed(QString reason);
void onDownloadProgress(qint64 current, qint64 total);
void onDownloadAborted();
void onUnzipFinished();
void onUnzipCanceled();
@ -56,4 +51,4 @@ private: /* data */
QString m_version;
};
}
} // namespace LegacyFTB

View File

@ -6,6 +6,8 @@
#include "Application.h"
#include "Json.h"
#include "net/ApiDownload.h"
#include "net/ApiUpload.h"
#include "net/NetJob.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());
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));
return netJob;
@ -31,7 +33,7 @@ Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_f
QJsonDocument body(body_obj);
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;
}
@ -60,7 +62,7 @@ Task::Ptr ModrinthAPI::latestVersion(QString hash,
QJsonDocument body(body_obj);
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));
return netJob;
@ -93,7 +95,8 @@ Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes,
QJsonDocument body(body_obj);
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;
}
@ -103,7 +106,7 @@ Task::Ptr ModrinthAPI::getProjects(QStringList addonIds, std::shared_ptr<QByteAr
auto netJob = makeShared<NetJob>(QString("Modrinth::GetProjects"), APPLICATION->network());
auto searchUrl = getMultipleModInfoURL(addonIds);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));
return netJob;
}

View File

@ -12,6 +12,7 @@
#include "net/ChecksumValidator.h"
#include "net/NetJob.h"
#include "net/ApiDownload.h"
#include "settings/INISettingsObject.h"
#include "ui/dialogs/CustomMessageBox.h"
@ -238,7 +239,7 @@ bool ModrinthCreationTask::createInstance()
}
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));
m_files_job->addNetAction(dl);
@ -247,7 +248,7 @@ bool ModrinthCreationTask::createInstance()
// MultipleOptionsTask's , once those exist :)
auto param = dl.toWeakRef();
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));
m_files_job->addNetAction(ndl);
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));
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..."));
m_files_job->start();

View File

@ -55,20 +55,11 @@ void ModrinthPackExportTask::executeTask()
bool ModrinthPackExportTask::abort()
{
if (task != nullptr) {
if (task) {
task->abort();
task = nullptr;
emitAborted();
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;
}
@ -205,63 +196,36 @@ void ModrinthPackExportTask::buildZip()
{
setStatus(tr("Adding files..."));
buildZipFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]() {
QuaZip zip(output);
if (!zip.open(QuaZip::mdCreate)) {
QFile::remove(output);
return BuildZipResult(tr("Could not create file"));
}
auto zipTask = makeShared<MMCZip::ExportToZipTask>(output, gameRoot, files, "overrides/", true);
zipTask->addExtraFile("modrinth.index.json", generateIndex());
if (buildZipFuture.isCanceled())
return BuildZipResult();
zipTask->setExcludeFiles(resolvedFiles.keys());
QuaZipFile indexFile(&zip);
if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) {
QFile::remove(output);
return BuildZipResult(tr("Could not create index"));
}
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();
auto progressStep = std::make_shared<TaskStepProgress>();
connect(zipTask.get(), &Task::finished, this, [this, progressStep] {
progressStep->state = TaskStepState::Succeeded;
stepProgress(*progressStep);
});
connect(&buildZipWatcher, &QFutureWatcher<BuildZipResult>::finished, this, &ModrinthPackExportTask::finish);
buildZipWatcher.setFuture(buildZipFuture);
}
void ModrinthPackExportTask::finish()
{
if (buildZipFuture.isCanceled())
emitAborted();
else {
const BuildZipResult result = buildZipFuture.result();
if (result.has_value())
emitFailed(result.value());
else
emitSucceeded();
}
connect(zipTask.get(), &Task::succeeded, this, &ModrinthPackExportTask::emitSucceeded);
connect(zipTask.get(), &Task::aborted, this, &ModrinthPackExportTask::emitAborted);
connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) {
progressStep->state = TaskStepState::Failed;
stepProgress(*progressStep);
emitFailed(reason);
});
connect(zipTask.get(), &Task::stepProgress, this, &ModrinthPackExportTask::propagateStepProgress);
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()

View File

@ -56,22 +56,17 @@ class ModrinthPackExportTask : public Task {
const QString output;
const MMCZip::FilterFunction filter;
typedef std::optional<QString> BuildZipResult;
ModrinthAPI api;
QFileInfoList files;
QMap<QString, QString> pendingHashes;
QMap<QString, ResolvedFile> resolvedFiles;
Task::Ptr task;
QFuture<BuildZipResult> buildZipFuture;
QFutureWatcher<BuildZipResult> buildZipWatcher;
void collectFiles();
void collectHashes();
void makeApiRequest();
void parseApiResponse(const std::shared_ptr<QByteArray> response);
void buildZip();
void finish();
QByteArray generateIndex();
};

View File

@ -23,6 +23,8 @@
#include "Application.h"
#include "net/ApiDownload.h"
Technic::SingleZipPackInstallTask::SingleZipPackInstallTask(const QUrl &sourceUrl, const QString &minecraftVersion)
{
m_sourceUrl = sourceUrl;
@ -45,12 +47,12 @@ void Technic::SingleZipPackInstallTask::executeTask()
auto entry = APPLICATION->metacache()->resolveEntry("general", path);
entry->setStale(true);
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();
auto job = m_filesNetJob.get();
connect(job, &NetJob::succeeded, this, &Technic::SingleZipPackInstallTask::downloadSucceeded);
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);
m_filesNetJob->start();
}

View File

@ -43,6 +43,7 @@
#include "SolderPackManifest.h"
#include "TechnicPackProcessor.h"
#include "net/ChecksumValidator.h"
#include "net/ApiDownload.h"
Technic::SolderPackInstallTask::SolderPackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network,
const QUrl& solderUrl,
@ -71,7 +72,7 @@ void Technic::SolderPackInstallTask::executeTask()
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);
m_filesNetJob->addNetAction(Net::Download::makeByteArray(sourceUrl, m_response));
m_filesNetJob->addNetAction(Net::ApiDownload::makeByteArray(sourceUrl, m_response));
auto job = m_filesNetJob.get();
connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::fileListSucceeded);
@ -112,7 +113,7 @@ void Technic::SolderPackInstallTask::fileListSucceeded()
for (const auto& mod : build.mods) {
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()) {
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
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::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::aborted, this, &Technic::SolderPackInstallTask::downloadAborted);
m_filesNetJob->start();

View 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

View 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

View 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

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

View File

@ -42,8 +42,6 @@ namespace Net {
/*
* 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 {
public:

View File

@ -47,17 +47,11 @@
#include "ChecksumValidator.h"
#include "MetaCacheSink.h"
#include "Application.h"
#include "BuildConfig.h"
#include "net/Logging.h"
#include "net/NetAction.h"
#include "MMCTime.h"
#include "StringUtils.h"
namespace Net {
#if defined(LAUNCHER_APPLICATION)
auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr
{
auto dl = makeShared<Download>();
@ -69,6 +63,7 @@ auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Down
dl->m_sink.reset(cachedNode);
return dl;
}
#endif
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;
}
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
auto Net::Download::abort() -> bool
{
if (m_reply) {
m_reply->abort();
} else {
m_state = State::AbortedByUser;
}
return true;
}

View File

@ -38,57 +38,26 @@
#pragma once
#include <chrono>
#include "HttpMetaCache.h"
#include "NetAction.h"
#include "Sink.h"
#include "Validator.h"
#include "QObjectPtr.h"
#include "net/NetRequest.h"
namespace Net {
class Download : public NetAction {
class Download : public NetRequest {
Q_OBJECT
public:
using Ptr = shared_qobject_ptr<class Download>;
enum class Option { NoOptions = 0, AcceptLocalFiles = 1, MakeEternal = 2 };
Q_DECLARE_FLAGS(Options, Option)
public:
~Download() override = default;
explicit Download() : NetRequest() { logCat = taskDownloadLogC; }
#if defined(LAUNCHER_APPLICATION)
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 makeFile(QUrl url, QString path, Options options = Option::NoOptions) -> Download::Ptr;
public:
void addValidator(Validator* v);
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;
protected:
virtual QNetworkReply* getReply(QNetworkRequest&) override;
};
} // namespace Net
Q_DECLARE_OPERATORS_FOR_FLAGS(Net::Download::Options)

View 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

View File

@ -42,6 +42,8 @@
#include "QObjectPtr.h"
#include "tasks/Task.h"
#include "HeaderProxy.h"
class NetAction : public Task {
Q_OBJECT
protected:
@ -56,13 +58,17 @@ class NetAction : public Task {
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:
virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0;
virtual void downloadError(QNetworkReply::NetworkError error) = 0;
virtual void downloadFinished() = 0;
virtual void downloadReadyRead() = 0;
virtual void sslErrors(const QList<QSslError>& errors) {
virtual void sslErrors(const QList<QSslError>& errors)
{
int i = 1;
for (auto error : errors) {
qCritical() << "Network SSL Error #" << i << " : " << error.errorString();
@ -70,7 +76,6 @@ class NetAction : public Task {
qCritical() << "Certificate in question:\n" << cert.toText();
i++;
}
};
public slots:
@ -91,4 +96,5 @@ class NetAction : public Task {
/// source URL
QUrl m_url;
std::vector<std::shared_ptr<Net::HeaderProxy>> m_headerProxies;
};

331
launcher/net/NetRequest.cpp Normal file
View 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
View 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)

View 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