diff --git a/.editorconfig b/.editorconfig index 56166b207..52510c515 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,3 +6,6 @@ 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 + +[CMakeLists.txt] +ij_continuation_indent_size = 4 \ No newline at end of file diff --git a/.github/actions/package/linux/action.yml b/.github/actions/package/linux/action.yml index 74e1c2a14..2df591579 100644 --- a/.github/actions/package/linux/action.yml +++ b/.github/actions/package/linux/action.yml @@ -60,43 +60,24 @@ runs: run: | cmake --install ${{ env.BUILD_DIR }} --config ${{ inputs.build-type }} --prefix ${{ env.INSTALL_APPIMAGE_DIR }}/usr - mv ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.metainfo.xml ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.appdata.xml - export "NO_APPSTREAM=1" # we have to skip appstream checking because appstream on ubuntu 20.04 is outdated - - export OUTPUT="PrismLauncher-Linux-$APPIMAGE_ARCH.AppImage" - - chmod +x linuxdeploy-*.AppImage - - mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib - mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines - - cp -r ${{ runner.workspace }}/Qt/${{ inputs.qt-version }}/gcc_*64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines - - cp /usr/lib/"$DEB_HOST_MULTIARCH"/libcrypto.so.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ - cp /usr/lib/"$DEB_HOST_MULTIARCH"/libssl.so.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ - cp /usr/lib/"$DEB_HOST_MULTIARCH"/libOpenGL.so.0* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ - - LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib" - export LD_LIBRARY_PATH - - chmod +x AppImageUpdate-"$APPIMAGE_ARCH".AppImage - cp AppImageUpdate-"$APPIMAGE_ARCH".AppImage ${{ env.INSTALL_APPIMAGE_DIR }}/usr/bin - - export UPDATE_INFORMATION="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|PrismLauncher-Linux-$APPIMAGE_ARCH.AppImage.zsync" + cp ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.{metainfo,appdata}.xml if [ '${{ inputs.gpg-private-key-id }}' != '' ]; then - export SIGN=1 - export SIGN_KEY=${{ inputs.gpg-private-key-id }} - mkdir -p ~/.gnupg/ - echo "$GPG_PRIVATE_KEY" > ~/.gnupg/private.key - gpg --import ~/.gnupg/private.key + echo "$GPG_PRIVATE_KEY" > privkey.asc + gpg --import privkey.asc + gpg --export --armor 9C7A2C9B62603299 > pubkey.asc else echo ":warning: Skipped code signing for Linux AppImage, as gpg key was not present." >> $GITHUB_STEP_SUMMARY fi - ./linuxdeploy-"$APPIMAGE_ARCH".AppImage --appdir ${{ env.INSTALL_APPIMAGE_DIR }} --output appimage --plugin qt -i ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/icons/hicolor/scalable/apps/org.prismlauncher.PrismLauncher.svg - - mv "PrismLauncher-Linux-$APPIMAGE_ARCH.AppImage" "PrismLauncher-Linux-${{ env.VERSION }}-${{ inputs.build-type }}-$APPIMAGE_ARCH.AppImage" + appimagetool -s deploy "$INSTALL_APPIMAGE_DIR"/usr/share/applications/*.desktop + cp ~/bin/AppImageUpdate.AppImage "$INSTALL_APPIMAGE_DIR"/usr/bin/ + # FIXME(@getchoo): Validate AppStream information when https://github.com/probonopd/go-appimage/pull/379 is merged + mkappimage \ + --no-appstream \ + --updateinformation "gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|PrismLauncher-Linux-$APPIMAGE_ARCH.AppImage.zsync" \ + "$INSTALL_APPIMAGE_DIR" \ + "PrismLauncher-Linux-$VERSION-${{ inputs.build-type }}-$APPIMAGE_ARCH.AppImage" - name: Package portable tarball shell: bash @@ -128,4 +109,4 @@ runs: uses: actions/upload-artifact@v5 with: name: PrismLauncher-${{ runner.os }}-${{ inputs.version }}-${{ inputs.build-type }}-${{ env.APPIMAGE_ARCH }}.AppImage.zsync - path: PrismLauncher-${{ runner.os }}-${{ env.APPIMAGE_ARCH }}.AppImage.zsync + path: PrismLauncher-${{ runner.os }}-${{ inputs.version }}-${{ inputs.build-type }}-${{ env.APPIMAGE_ARCH }}.AppImage.zsync diff --git a/.github/actions/setup-dependencies/action.yml b/.github/actions/setup-dependencies/action.yml index 2d2889d5f..4c19474b7 100644 --- a/.github/actions/setup-dependencies/action.yml +++ b/.github/actions/setup-dependencies/action.yml @@ -56,7 +56,7 @@ runs: # TODO(@getchoo): Get this working on MSYS2! - name: Setup ccache if: ${{ (runner.os != 'Windows' || inputs.msystem == '') && inputs.build-type == 'Debug' }} - uses: hendrikmuhs/ccache-action@v1.2.19 + uses: hendrikmuhs/ccache-action@v1.2.20 with: variant: sccache create-symlink: ${{ runner.os != 'Windows' }} @@ -79,5 +79,5 @@ runs: aqtversion: "==3.1.*" version: ${{ inputs.qt-version }} arch: ${{ inputs.qt-architecture }} - modules: qt5compat qtimageformats qtnetworkauth + modules: qtimageformats qtnetworkauth cache: ${{ inputs.build-type == 'Debug' }} diff --git a/.github/actions/setup-dependencies/linux/action.yml b/.github/actions/setup-dependencies/linux/action.yml index 21ba30dc7..67a1a9826 100644 --- a/.github/actions/setup-dependencies/linux/action.yml +++ b/.github/actions/setup-dependencies/linux/action.yml @@ -10,26 +10,45 @@ runs: sudo apt-get -y update sudo apt-get -y install \ dpkg-dev \ - ninja-build extra-cmake-modules scdoc \ - libqrencode-dev \ - appstream libxcb-cursor-dev + ninja-build extra-cmake-modules pkg-config scdoc \ + cmark gamemode-dev libarchive-dev libcmark-dev libqrencode-dev zlib1g-dev \ + libxcb-cursor-dev + + # TODO(@getchoo): Install with the above when all targets use Ubuntu 24.04 + - name: Install tomlplusplus + if: ${{ runner.arch == 'ARM64' }} + shell: bash + run: | + sudo apt-get -y install libtomlplusplus-dev + + # FIXME(@getchoo): THIS IS HORRIBLE TO DO! + # Install tomlplusplus from Ubuntu 24.04, since it never got backported to 22.04 + # I've done too much to continue keeping this as a submodule.... + - name: Install tomlplusplus from 24.04 + if: ${{ runner.arch != 'ARM64' }} + shell: bash + run: | + deb_arch="$(dpkg-architecture -q DEB_HOST_ARCH)" + curl -Lo libtomlplusplus-dev.deb http://mirrors.kernel.org/ubuntu/pool/universe/t/tomlplusplus/libtomlplusplus-dev_3.4.0+ds-0.2build1_"$deb_arch".deb + curl -Lo libtomlplusplus3t64.deb http://mirrors.kernel.org/ubuntu/pool/universe/t/tomlplusplus/libtomlplusplus3t64_3.4.0+ds-0.2build1_"$deb_arch".deb + sudo dpkg -i libtomlplusplus3t64.deb + sudo dpkg -i libtomlplusplus-dev.deb + rm *.deb + sudo apt-get install -f - name: Setup AppImage tooling shell: bash + env: + GH_TOKEN: ${{ github.token }} run: | - declare -A appimage_deps - - deb_arch="$(dpkg-architecture -q DEB_HOST_ARCH)" - case "$deb_arch" in + # Determinate AppImage architecture to use + dpkg_arch="$(dpkg-architecture -q DEB_HOST_ARCH_CPU)" + case "$dpkg_arch" in "amd64") - appimage_deps["https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20250213-2/linuxdeploy-x86_64.AppImage"]="4648f278ab3ef31f819e67c30d50f462640e5365a77637d7e6f2ad9fd0b4522a linuxdeploy-x86_64.AppImage" - appimage_deps["https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/1-alpha-20250213-1/linuxdeploy-plugin-qt-x86_64.AppImage"]="15106be885c1c48a021198e7e1e9a48ce9d02a86dd0a1848f00bdbf3c1c92724 linuxdeploy-plugin-qt-x86_64.AppImage" - appimage_deps["https://github.com/AppImageCommunity/AppImageUpdate/releases/download/2.0.0-alpha-1-20241225/AppImageUpdate-x86_64.AppImage"]="f1747cf60058e99f1bb9099ee9787d16c10241313b7acec81810ea1b1e568c11 AppImageUpdate-x86_64.AppImage" + APPIMAGE_ARCH="x86_64" ;; "arm64") - appimage_deps["https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20250213-2/linuxdeploy-aarch64.AppImage"]="06706ac8189797dccd36bd384105892cb5e6e71f784f4df526cc958adc223cd6 linuxdeploy-aarch64.AppImage" - appimage_deps["https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/1-alpha-20250213-1/linuxdeploy-plugin-qt-aarch64.AppImage"]="bf1c24aff6d749b5cf423afad6f15abd4440f81dec1aab95706b25f6667cdcf1 linuxdeploy-plugin-qt-aarch64.AppImage" - appimage_deps["https://github.com/AppImageCommunity/AppImageUpdate/releases/download/2.0.0-alpha-1-20241225/AppImageUpdate-aarch64.AppImage"]="cf27f810dfe5eda41f130769e4a4b562b9d93665371c15ebeffb84ee06a41550 AppImageUpdate-aarch64.AppImage" + APPIMAGE_ARCH="aarch64" ;; *) echo "# 🚨 The Debian architecture \"$deb_arch\" is not recognized!" >> "$GITHUB_STEP_SUMMARY" @@ -37,9 +56,19 @@ runs: ;; esac - for url in "${!appimage_deps[@]}"; do - curl -LO "$url" - sha256sum -c - <<< "${appimage_deps[$url]}" - done + gh release download continuous \ + --repo probonopd/go-appimage \ + --pattern "appimagetool-*-$APPIMAGE_ARCH.AppImage" \ + --output ~/bin/appimagetool + gh release download continuous \ + --repo probonopd/go-appimage \ + --pattern "mkappimage-*-$APPIMAGE_ARCH.AppImage" \ + --output ~/bin/mkappimage + chmod +x ~/bin/appimagetool ~/bin/mkappimage + echo "$HOME/bin" >> "$GITHUB_PATH" - sudo apt -y install libopengl0 + gh release download \ + --repo AppImageCommunity/AppImageUpdate \ + --pattern "AppImageUpdate-$APPIMAGE_ARCH.AppImage" \ + --output ~/bin/AppImageUpdate.AppImage + chmod +x ~/bin/AppImageUpdate.AppImage diff --git a/.github/actions/setup-dependencies/windows/action.yml b/.github/actions/setup-dependencies/windows/action.yml index c6f23a9ca..ada060d66 100644 --- a/.github/actions/setup-dependencies/windows/action.yml +++ b/.github/actions/setup-dependencies/windows/action.yml @@ -76,12 +76,11 @@ runs: qt6-base:p qt6-svg:p qt6-imageformats:p - qt6-5compat:p qt6-networkauth:p cmark:p qrencode:p tomlplusplus:p - quazip-qt6:p + libarchive:p - name: List pacman packages (MinGW) if: ${{ inputs.msystem != '' }} diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 2b5633c76..42806c8de 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -21,7 +21,7 @@ jobs: 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@v5 + - uses: actions/checkout@v6 with: ref: ${{ github.event.pull_request.head.sha }} - name: Create backport PRs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 696311033..2723a2bd7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -139,7 +139,7 @@ jobs: ## - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: true diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3fdcc68ba..d70c4eb0f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -60,7 +60,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: "true" diff --git a/.github/workflows/flatpak.yml b/.github/workflows/flatpak.yml index c16917869..7a4a9b293 100644 --- a/.github/workflows/flatpak.yml +++ b/.github/workflows/flatpak.yml @@ -84,7 +84,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: true diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index d6cacc665..23bb287dd 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -92,7 +92,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Nix uses: cachix/install-nix-action@v31 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e4b9b56aa..9983fa6cb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: upload_url: ${{ steps.create_release.outputs.upload_url }} steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: "true" path: "PrismLauncher-source" diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml index 64d299ef7..9e2367661 100644 --- a/.github/workflows/update-flake.yml +++ b/.github/workflows/update-flake.yml @@ -16,10 +16,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31 - - uses: DeterminateSystems/update-flake-lock@v27 + - uses: DeterminateSystems/update-flake-lock@v28 with: commit-msg: "chore(nix): update lockfile" pr-title: "chore(nix): update lockfile" diff --git a/.gitmodules b/.gitmodules index 7ad40becb..f2d71ef29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,21 +1,6 @@ -[submodule "libraries/quazip"] - path = libraries/quazip - url = https://github.com/stachenov/quazip.git -[submodule "libraries/tomlplusplus"] - path = libraries/tomlplusplus - url = https://github.com/marzer/tomlplusplus.git [submodule "libraries/libnbtplusplus"] path = libraries/libnbtplusplus url = https://github.com/PrismLauncher/libnbtplusplus.git -[submodule "libraries/zlib"] - path = libraries/zlib - url = https://github.com/madler/zlib.git -[submodule "libraries/extra-cmake-modules"] - path = libraries/extra-cmake-modules - url = https://github.com/KDE/extra-cmake-modules -[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 diff --git a/.markdownlintignore b/.markdownlintignore index a8669d01d..96f627ad9 100644 --- a/.markdownlintignore +++ b/.markdownlintignore @@ -1,2 +1 @@ libraries/nbtplusplus -libraries/quazip diff --git a/CMakeLists.txt b/CMakeLists.txt index 6caa45bc7..51d1403b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,9 +104,6 @@ endif() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_WARN_DEPRECATED_UP_TO=0x060200") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_DISABLE_DEPRECATED_UP_TO=0x060000") -# Fix aarch64 build for toml++ -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTOML_ENABLE_FLOAT16=0") - # set CXXFLAGS for build targets set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}") @@ -174,20 +171,9 @@ endif() option(BUILD_TESTING "Build the testing tree." ON) -find_package(ECM QUIET NO_MODULE) -if(NOT ECM_FOUND) - if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/libraries/extra-cmake-modules/CMakeLists.txt") - message(STATUS "Using bundled ECM") - set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/libraries/extra-cmake-modules/modules;${CMAKE_MODULE_PATH}") - else() - message(FATAL_ERROR - " Could not find ECM\n \n" - " Either install ECM using the system package manager or clone submodules\n" - " Submodules can be cloned with 'git submodule update --init --recursive'") - endif() -else() - set(CMAKE_MODULE_PATH "${ECM_MODULE_PATH};${CMAKE_MODULE_PATH}") -endif() +find_package(ECM NO_MODULE REQUIRED) +set(CMAKE_MODULE_PATH "${ECM_MODULE_PATH};${CMAKE_MODULE_PATH}") + include(CTest) include(ECMAddTests) if(BUILD_TESTING) @@ -244,7 +230,6 @@ set(Launcher_DISCORD_URL "https://prismlauncher.org/discord" CACHE STRING "URL f set(Launcher_SUBREDDIT_URL "https://prismlauncher.org/reddit" CACHE STRING "URL for the subreddit.") # Builds -set(Launcher_FORCE_BUNDLED_LIBS OFF CACHE BOOL "Prevent using system libraries, if they are available as submodules") set(Launcher_QT_VERSION_MAJOR "6" CACHE STRING "Major Qt version to build against") # Java downloader @@ -309,32 +294,12 @@ set(Launcher_BUILD_TIMESTAMP "${TODAY}") ################################ 3rd Party Libs ################################ -# Successive configurations of cmake without cleaning the build dir will cause zlib fallback to fail due to cached values -# Record when fallback triggered and skip this find_package -if(NOT Launcher_FORCE_BUNDLED_LIBS AND NOT FORCE_BUNDLED_ZLIB) - find_package(ZLIB QUIET) -endif() -if(NOT ZLIB_FOUND) - set(FORCE_BUNDLED_ZLIB TRUE CACHE BOOL "") - mark_as_advanced(FORCE_BUNDLED_ZLIB) -endif() - # Find the required Qt parts -include(QtVersionlessBackport) if(Launcher_QT_VERSION_MAJOR EQUAL 6) set(QT_VERSION_MAJOR 6) - find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat NetworkAuth OpenGL) + find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml NetworkAuth OpenGL) find_package(Qt6 COMPONENTS DBus) list(APPEND Launcher_QT_DBUS Qt6::DBus) - list(APPEND Launcher_QT_LIBS Qt6::Core5Compat) - - if(NOT Launcher_FORCE_BUNDLED_LIBS) - find_package(QuaZip-Qt6 1.3 QUIET) - endif() - if (NOT QuaZip-Qt6_FOUND) - set(QUAZIP_QT_MAJOR_VERSION ${QT_VERSION_MAJOR} CACHE STRING "Qt version to use (4, 5 or 6), defaults to ${QT_VERSION_MAJOR}" FORCE) - set(FORCE_BUNDLED_QUAZIP 1) - endif() else() message(FATAL_ERROR "Qt version ${Launcher_QT_VERSION_MAJOR} is not supported") endif() @@ -345,6 +310,13 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 6) set(QT_LIBEXECS_DIR ${QT${QT_VERSION_MAJOR}_INSTALL_PREFIX}/${QT${QT_VERSION_MAJOR}_INSTALL_LIBEXECS}) endif() +find_package(cmark REQUIRED) + +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + find_package(PkgConfig REQUIRED) + pkg_check_modules(gamemode REQUIRED IMPORTED_TARGET gamemode) +endif() + # Find libqrencode ## NOTE(@getchoo): Never use pkg-config with MSVC since the vcpkg port makes our install bundle fail to find the dll if(MSVC) @@ -357,22 +329,24 @@ else() pkg_check_modules(libqrencode REQUIRED IMPORTED_TARGET libqrencode) endif() -if(NOT Launcher_FORCE_BUNDLED_LIBS) - # Find toml++ - find_package(tomlplusplus 3.2.0 QUIET) - # Fallback to pkg-config (if available) if CMake files aren't found - if(NOT tomlplusplus_FOUND) - find_package(PkgConfig QUIET) - if(PkgConfig_FOUND) - pkg_check_modules(tomlplusplus IMPORTED_TARGET tomlplusplus>=3.2.0) - endif() - endif() - - - # Find cmark - find_package(cmark QUIET) +# Find libarchive through CMake packages, mainly for vcpkg +find_package(LibArchive) +# CMake packages aren't available in most distributions of libarchive, so fallback to pkg-config +if(NOT LibArchive_FOUND) + find_package(PkgConfig REQUIRED) + pkg_check_modules(libarchive REQUIRED IMPORTED_TARGET libarchive) endif() +find_package(tomlplusplus 3.2.0 REQUIRED) +# fallback to pkgconfig, important especially as many distros package toml++ built with meson +if(NOT tomlplusplus_FOUND) + find_package(PkgConfig REQUIRED) + pkg_check_modules(tomlplusplus REQUIRED IMPORTED_TARGET tomlplusplus>=3.2.0) +endif() + +find_package(ZLIB REQUIRED) + + include(ECMQtDeclareLoggingCategory) ####################################### Program Info ####################################### @@ -438,6 +412,7 @@ elseif(UNIX) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_Desktop} DESTINATION ${KDE_INSTALL_APPDIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_MetaInfo} DESTINATION ${KDE_INSTALL_METAINFODIR}) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION "${KDE_INSTALL_ICONDIR}/hicolor/scalable/apps") + install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_PNG_256} DESTINATION "${KDE_INSTALL_ICONDIR}/hicolor/256x256/apps" RENAME "${Launcher_AppID}.png") install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_mrpack_MIMEInfo} DESTINATION ${KDE_INSTALL_MIMEDIR}) install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/launcher/qtlogging.ini" DESTINATION "share/${Launcher_Name}") @@ -478,60 +453,9 @@ add_subdirectory(libraries/libnbtplusplus) add_subdirectory(libraries/launcher) # java based launcher part for Minecraft add_subdirectory(libraries/javacheck) # java compatibility checker -if(FORCE_BUNDLED_ZLIB) - message(STATUS "Using bundled zlib") - set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) # Suppress cmake warnings and allow INTERPROCEDURAL_OPTIMIZATION for zlib - set(SKIP_INSTALL_ALL ON) - add_subdirectory(libraries/zlib EXCLUDE_FROM_ALL) - - # On OS where unistd.h exists, zlib's generated header defines `Z_HAVE_UNISTD_H`, while the included header does not. - # We cannot safely undo the rename on those systems, and they generally have packages for zlib anyway. - check_include_file(unistd.h NEED_GENERATED_ZCONF) - if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib/zconf.h.included" AND NOT NEED_GENERATED_ZCONF) - # zlib's cmake script renames a file, dirtying the submodule, see https://github.com/madler/zlib/issues/162 - message(STATUS "Undoing Rename") - message(STATUS " ${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib/zconf.h") - file(RENAME "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib/zconf.h.included" "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib/zconf.h") - endif() - - set(ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/libraries/zlib" "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib" CACHE STRING "" FORCE) - set_target_properties(zlibstatic PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${ZLIB_INCLUDE_DIR}") - add_library(ZLIB::ZLIB ALIAS zlibstatic) - set(ZLIB_LIBRARY ZLIB::ZLIB CACHE STRING "zlib library name") - - find_package(ZLIB REQUIRED) -else() - message(STATUS "Using system zlib") -endif() -if (FORCE_BUNDLED_QUAZIP) - message(STATUS "Using bundled QuaZip") - set(BUILD_SHARED_LIBS 0) # link statically to avoid conflicts. - set(QUAZIP_INSTALL 0) - add_subdirectory(libraries/quazip) # zip manipulation library -else() - message(STATUS "Using system QuaZip") -endif() add_subdirectory(libraries/rainbow) # Qt extension for colors add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions -if(NOT tomlplusplus_FOUND) - message(STATUS "Using bundled tomlplusplus") - add_subdirectory(libraries/tomlplusplus) # toml parser -else() - message(STATUS "Using system tomlplusplus") -endif() -if(NOT cmark_FOUND) - message(STATUS "Using bundled cmark") - set(ORIGINAL_BUILD_TESTING ${BUILD_TESTING}) - set(BUILD_TESTING 0) - set(BUILD_SHARED_LIBS 0) - add_subdirectory(libraries/cmark EXCLUDE_FROM_ALL) # Markdown parser - add_library(cmark::cmark ALIAS cmark) - set(BUILD_TESTING ${ORIGINAL_BUILD_TESTING}) -else() - message(STATUS "Using system cmark") -endif() -add_subdirectory(libraries/gamemode) add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API add_subdirectory(libraries/qdcss) # css parser diff --git a/COPYING.md b/COPYING.md index 8588c8951..fb33844f7 100644 --- a/COPYING.md +++ b/COPYING.md @@ -212,30 +212,6 @@ This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL -## Quazip - - Copyright (C) 2005-2021 Sergey A. Tachenov - - The QuaZip library is licensed under the GNU Lesser General Public - License V2.1 plus a static linking exception. - - The original ZIP/UNZIP package (MiniZip) is copyrighted by Gilles - Vollant and contributors, see quazip/(un)zip.h files for details. - Basically it's the zlib license. - - STATIC LINKING EXCEPTION - - The copyright holders give you permission to link this library with - independent modules to produce an executable, regardless of the license - terms of these independent modules, and to copy and distribute the - resulting executable under terms of your choice, provided that you also - meet, for each linked independent module, the terms and conditions of - the license of that module. An independent module is a module which is - not derived from or based on this library. If you modify this library, - you must extend this exception to your version of the library. - - See COPYING file for the full LGPL text. - ## launcher (`libraries/launcher`) PolyMC - Minecraft Launcher diff --git a/cmake/ECMQueryQt.cmake b/cmake/ECMQueryQt.cmake deleted file mode 100644 index 98eb50089..000000000 --- a/cmake/ECMQueryQt.cmake +++ /dev/null @@ -1,100 +0,0 @@ -# SPDX-FileCopyrightText: 2014 Rohan Garg -# SPDX-FileCopyrightText: 2014 Alex Merry -# SPDX-FileCopyrightText: 2014-2016 Aleix Pol -# SPDX-FileCopyrightText: 2017 Friedrich W. H. Kossebau -# SPDX-FileCopyrightText: 2022 Ahmad Samir -# -# SPDX-License-Identifier: BSD-3-Clause -#[=======================================================================[.rst: -ECMQueryQt ---------------- -This module can be used to query the installation paths used by Qt. - -For Qt5 this uses ``qmake``, and for Qt6 this used ``qtpaths`` (the latter has built-in -support to query the paths of a target platform when cross-compiling). - -This module defines the following function: -:: - - ecm_query_qt( [TRY]) - -Passing ``TRY`` will result in the method not making the build fail if the executable -used for querying has not been found, but instead simply print a warning message and -return an empty string. - -Example usage: - -.. code-block:: cmake - - include(ECMQueryQt) - ecm_query_qt(bin_dir QT_INSTALL_BINS) - -If the call succeeds ``${bin_dir}`` will be set to ``/path/to/bin/dir`` (e.g. -``/usr/lib64/qt/bin/``). - -Since: 5.93 -#]=======================================================================] - -include(${CMAKE_CURRENT_LIST_DIR}/QtVersionOption.cmake) -include(CheckLanguage) -check_language(CXX) -if (CMAKE_CXX_COMPILER) - # Enable the CXX language to let CMake look for config files in library dirs. - # See: https://gitlab.kitware.com/cmake/cmake/-/issues/23266 - enable_language(CXX) -endif() - -if (QT_MAJOR_VERSION STREQUAL "5") - # QUIET to accommodate the TRY option - find_package(Qt${QT_MAJOR_VERSION}Core QUIET) - if(TARGET Qt5::qmake) - get_target_property(_qmake_executable_default Qt5::qmake LOCATION) - - set(QUERY_EXECUTABLE ${_qmake_executable_default} - CACHE FILEPATH "Location of the Qt5 qmake executable") - set(_exec_name_text "Qt5 qmake") - set(_cli_option "-query") - endif() -elseif(QT_MAJOR_VERSION STREQUAL "6") - # QUIET to accommodate the TRY option - find_package(Qt6 COMPONENTS CoreTools QUIET CONFIG) - if (TARGET Qt6::qtpaths) - get_target_property(_qtpaths_executable Qt6::qtpaths LOCATION) - - set(QUERY_EXECUTABLE ${_qtpaths_executable} - CACHE FILEPATH "Location of the Qt6 qtpaths executable") - set(_exec_name_text "Qt6 qtpaths") - set(_cli_option "--query") - endif() -endif() - -function(ecm_query_qt result_variable qt_variable) - set(options TRY) - set(oneValueArgs) - set(multiValueArgs) - - cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - if(NOT QUERY_EXECUTABLE) - if(ARGS_TRY) - set(${result_variable} "" PARENT_SCOPE) - message(STATUS "No ${_exec_name_text} executable found. Can't check ${qt_variable}") - return() - else() - message(FATAL_ERROR "No ${_exec_name_text} executable found. Can't check ${qt_variable} as required") - endif() - endif() - execute_process( - COMMAND ${QUERY_EXECUTABLE} ${_cli_option} "${qt_variable}" - RESULT_VARIABLE return_code - OUTPUT_VARIABLE output - ) - if(return_code EQUAL 0) - string(STRIP "${output}" output) - file(TO_CMAKE_PATH "${output}" output_path) - set(${result_variable} "${output_path}" PARENT_SCOPE) - else() - message(WARNING "Failed call: ${_command} \"${qt_variable}\"") - message(FATAL_ERROR "${_exec_name_text} call failed: ${return_code}") - endif() -endfunction() diff --git a/cmake/QtVersionOption.cmake b/cmake/QtVersionOption.cmake deleted file mode 100644 index 1390f9db6..000000000 --- a/cmake/QtVersionOption.cmake +++ /dev/null @@ -1,38 +0,0 @@ -#.rst: -# QtVersionOption -# --------------- -# -# Adds a build option to select the major Qt version if necessary, -# that is, if the major Qt version has not yet been determined otherwise -# (e.g. by a corresponding find_package() call). -# -# This module is typically included by other modules requiring knowledge -# about the major Qt version. -# -# ``QT_MAJOR_VERSION`` is defined to either be "5" or "6". -# -# -# Since 5.82.0. - -#============================================================================= -# SPDX-FileCopyrightText: 2021 Volker Krause -# -# SPDX-License-Identifier: BSD-3-Clause - -if (DEFINED QT_MAJOR_VERSION) - return() -endif() - -if (TARGET Qt5::Core) - set(QT_MAJOR_VERSION 5) -elseif (TARGET Qt6::Core) - set(QT_MAJOR_VERSION 6) -else() - option(BUILD_WITH_QT6 "Build against Qt 6" OFF) - - if (BUILD_WITH_QT6) - set(QT_MAJOR_VERSION 6) - else() - set(QT_MAJOR_VERSION 5) - endif() -endif() diff --git a/cmake/QtVersionlessBackport.cmake b/cmake/QtVersionlessBackport.cmake deleted file mode 100644 index 46792db58..000000000 --- a/cmake/QtVersionlessBackport.cmake +++ /dev/null @@ -1,97 +0,0 @@ -#============================================================================= -# Copyright 2005-2011 Kitware, Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of Kitware, Inc. nor the names of its -# contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#============================================================================= - -# From Qt5CoreMacros.cmake - -function(qt_generate_moc) - if(QT_VERSION_MAJOR EQUAL 5) - qt5_generate_moc(${ARGV}) - elseif(QT_VERSION_MAJOR EQUAL 6) - qt6_generate_moc(${ARGV}) - endif() -endfunction() - -function(qt_wrap_cpp outfiles) - if(QT_VERSION_MAJOR EQUAL 5) - qt5_wrap_cpp("${outfiles}" ${ARGN}) - elseif(QT_VERSION_MAJOR EQUAL 6) - qt6_wrap_cpp("${outfiles}" ${ARGN}) - endif() - set("${outfiles}" "${${outfiles}}" PARENT_SCOPE) -endfunction() - -function(qt_add_binary_resources) - if(QT_VERSION_MAJOR EQUAL 5) - qt5_add_binary_resources(${ARGV}) - elseif(QT_VERSION_MAJOR EQUAL 6) - qt6_add_binary_resources(${ARGV}) - endif() -endfunction() - -function(qt_add_resources outfiles) - if(QT_VERSION_MAJOR EQUAL 5) - qt5_add_resources("${outfiles}" ${ARGN}) - elseif(QT_VERSION_MAJOR EQUAL 6) - qt6_add_resources("${outfiles}" ${ARGN}) - endif() - set("${outfiles}" "${${outfiles}}" PARENT_SCOPE) -endfunction() - -function(qt_add_big_resources outfiles) - if(QT_VERSION_MAJOR EQUAL 5) - qt5_add_big_resources(${outfiles} ${ARGN}) - elseif(QT_VERSION_MAJOR EQUAL 6) - qt6_add_big_resources(${outfiles} ${ARGN}) - endif() - set("${outfiles}" "${${outfiles}}" PARENT_SCOPE) -endfunction() - -function(qt_import_plugins) - if(QT_VERSION_MAJOR EQUAL 5) - qt5_import_plugins(${ARGV}) - elseif(QT_VERSION_MAJOR EQUAL 6) - qt6_import_plugins(${ARGV}) - endif() -endfunction() - - -# From Qt5WidgetsMacros.cmake - -function(qt_wrap_ui outfiles) - if(QT_VERSION_MAJOR EQUAL 5) - qt5_wrap_ui("${outfiles}" ${ARGN}) - elseif(QT_VERSION_MAJOR EQUAL 6) - qt6_wrap_ui("${outfiles}" ${ARGN}) - endif() - set("${outfiles}" "${${outfiles}}" PARENT_SCOPE) -endfunction() - diff --git a/flake.lock b/flake.lock index 6cdc11a85..5f4817048 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1762977756, - "narHash": "sha256-4PqRErxfe+2toFJFgcRKZ0UI9NSIOJa+7RXVtBhy4KE=", + "lastModified": 1764242076, + "narHash": "sha256-sKoIWfnijJ0+9e4wRvIgm/HgE27bzwQxcEmo2J/gNpI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "c5ae371f1a6a7fd27823bc500d9390b38c05fa55", + "rev": "2fad6eac6077f03fe109c4d4eb171cf96791faa4", "type": "github" }, "original": { diff --git a/flatpak/cmark.yml b/flatpak/cmark.yml new file mode 100644 index 000000000..d5078baab --- /dev/null +++ b/flatpak/cmark.yml @@ -0,0 +1,14 @@ +name: cmark +buildsystem: cmake-ninja +builddir: true +config-opts: + - -DCMAKE_TESTS=OFF +sources: + - type: archive + url: https://github.com/commonmark/cmark/archive/0.31.1.tar.gz + sha256: 3da93db5469c30588cfeb283d9d62edfc6ded9eb0edc10a4f5bbfb7d722ea802 + x-checker-data: + type: anitya + project-id: 9159 + stable-only: true + url-template: https://github.com/commonmark/cmark/archive/$version.tar.gz diff --git a/flatpak/org.prismlauncher.PrismLauncher.yml b/flatpak/org.prismlauncher.PrismLauncher.yml index 136aef91a..80ed8158d 100644 --- a/flatpak/org.prismlauncher.PrismLauncher.yml +++ b/flatpak/org.prismlauncher.PrismLauncher.yml @@ -27,6 +27,9 @@ finish-args: - --filesystem=/sys/kernel/mm/transparent_hugepage:ro modules: + - cmark.yml + - tomlplusplus.yml + # Might be needed by some Controller mods (see https://github.com/isXander/Controlify/issues/31) - shared-modules/libusb/libusb.json diff --git a/flatpak/tomlplusplus.yml b/flatpak/tomlplusplus.yml new file mode 100644 index 000000000..0afaf6678 --- /dev/null +++ b/flatpak/tomlplusplus.yml @@ -0,0 +1,6 @@ +name: tomlplusplus +buildsystem: cmake-ninja +sources: + - type: archive + url: https://github.com/marzer/tomlplusplus/archive/v3.4.0.tar.gz + sha256: 8517f65938a4faae9ccf8ebb36631a38c1cadfb5efa85d9a72e15b9e97d25155 diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 682cf5b1a..9352d569f 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -370,7 +370,25 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) } QString origcwdPath = QDir::currentPath(); +#if defined(Q_OS_LINUX) + const QString binFilePath = applicationFilePath(); + const bool isAppImage = binFilePath.startsWith("/tmp/.mount_"); + // Yes, this can technically trigger the logic below if someone makes an AppImage with an actual launcher exe named "ld-linux" + // Please don't :) + const bool executedFromLinker = QFileInfo(binFilePath).fileName().startsWith("ld-linux"); + + // NOTE(@getchoo): In order for `go-appimage` to generate self-contained AppImages, it executes apps from a bundled linker at + // /lib64 + // This is not the path to our actual binary, which we want + QString binPath; + if (isAppImage && executedFromLinker) { + binPath = FS::PathCombine(applicationDirPath(), "../usr/bin"); + } else { + binPath = applicationDirPath(); + } +#else QString binPath = applicationDirPath(); +#endif { // Root path is used for updates and portable data diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index c59c1a0bc..48794d7e2 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -26,8 +26,14 @@ set(CORE_SOURCES NullInstance.h MMCZip.h MMCZip.cpp - Untar.h - Untar.cpp + archive/ArchiveReader.cpp + archive/ArchiveReader.h + archive/ArchiveWriter.cpp + archive/ArchiveWriter.h + archive/ExportToZipTask.cpp + archive/ExportToZipTask.h + archive/ExtractZipTask.cpp + archive/ExtractZipTask.h StringUtils.h StringUtils.cpp QVariantUtils.h @@ -615,6 +621,10 @@ set(PRISMUPDATER_SOURCES # Zip MMCZip.h MMCZip.cpp + archive/ArchiveReader.cpp + archive/ArchiveReader.h + archive/ArchiveWriter.cpp + archive/ArchiveWriter.h # Time MMCTime.h @@ -1319,8 +1329,13 @@ if(TARGET PkgConfig::tomlplusplus) else() target_link_libraries(Launcher_logic tomlplusplus::tomlplusplus) endif() +if(TARGET PkgConfig::libarchive) + target_link_libraries(Launcher_logic PkgConfig::libarchive) +else() + target_link_libraries(Launcher_logic LibArchive::LibArchive) +endif() -if (UNIX AND NOT CYGWIN AND NOT APPLE) +if (CMAKE_SYSTEM_NAME STREQUAL "Linux") target_link_libraries(Launcher_logic gamemode ) @@ -1339,7 +1354,6 @@ target_link_libraries(Launcher_logic ${Launcher_QT_LIBS} ) target_link_libraries(Launcher_logic - QuaZip::QuaZip cmark::cmark LocalPeer Launcher_rainbow @@ -1406,7 +1420,6 @@ if(Launcher_BUILD_UPDATER) add_library(prism_updater_logic STATIC ${PRISMUPDATER_SOURCES} ${TASKS_SOURCES} ${PRISMUPDATER_UI}) target_include_directories(prism_updater_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(prism_updater_logic - QuaZip::QuaZip ${ZLIB_LIBRARIES} BuildConfig Qt${QT_VERSION_MAJOR}::Widgets @@ -1415,6 +1428,11 @@ if(Launcher_BUILD_UPDATER) ${Launcher_QT_LIBS} cmark::cmark ) + if(TARGET PkgConfig::libarchive) + target_link_libraries(prism_updater_logic PkgConfig::libarchive) + else() + target_link_libraries(prism_updater_logic LibArchive::LibArchive) + endif() add_executable("${Launcher_Name}_updater" WIN32 updater/prismupdater/updater_main.cpp) target_sources("${Launcher_Name}_updater" PRIVATE updater/prismupdater/updater.exe.manifest) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 5b44dbd48..30d0a9c4c 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -1702,4 +1702,14 @@ QString getUniqueResourceName(const QString& filePath) return newFileName; } +bool removeFiles(QStringList listFile) +{ + bool ret = true; + // For each file + for (int i = 0; i < listFile.count(); i++) { + // Remove + ret = ret && QFile::remove(listFile.at(i)); + } + return ret; +} } // namespace FS diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index b0d9ae2e8..f2676b147 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -291,6 +291,8 @@ bool move(const QString& source, const QString& dest); */ bool deletePath(QString path); +bool removeFiles(QStringList listFile); + /** * Trash a folder / file */ diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 77298e2ce..d3cc3bd02 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -38,10 +38,11 @@ #include "Application.h" #include "FileSystem.h" -#include "MMCZip.h" #include "NullInstance.h" #include "QObjectPtr.h" +#include "archive/ArchiveReader.h" +#include "archive/ExtractZipTask.h" #include "icons/IconList.h" #include "icons/IconUtils.h" @@ -54,12 +55,10 @@ #include "net/ApiDownload.h" +#include #include -#include #include -#include - InstanceImportTask::InstanceImportTask(const QUrl& sourceUrl, QWidget* parent, QMap&& extra_info) : m_sourceUrl(sourceUrl), m_extra_info(extra_info), m_parent(parent) {} @@ -109,38 +108,34 @@ void InstanceImportTask::downloadFromUrl() filesNetJob->start(); } -QString InstanceImportTask::getRootFromZip(QuaZip* zip, const QString& root) +QString InstanceImportTask::getRootFromZip(QStringList files) { if (!isRunning()) { return {}; } - QuaZipDir rootDir(zip, root); - for (auto&& fileName : rootDir.entryList(QDir::Files)) { + auto cleanPath = [](QString path) { + if (path == ".") + return QString(); + QString result = path; + if (result.startsWith("./")) + result = result.mid(2); + return result; + }; + for (auto&& fileName : files) { setDetails(fileName); - if (fileName == "instance.cfg") { + QFileInfo fileInfo(fileName); + if (fileInfo.fileName() == "instance.cfg") { qDebug() << "MultiMC:" << true; m_modpackType = ModpackType::MultiMC; - return root; + return cleanPath(fileInfo.path()); } - if (fileName == "manifest.json") { + if (fileInfo.fileName() == "manifest.json") { qDebug() << "Flame:" << true; m_modpackType = ModpackType::Flame; - return root; + return cleanPath(fileInfo.path()); } - QCoreApplication::processEvents(); } - - // Recurse the search to non-ignored subfolders - for (auto&& fileName : rootDir.entryList(QDir::Dirs)) { - if ("overrides/" == fileName) - continue; - - QString result = getRootFromZip(zip, root + fileName); - if (!result.isEmpty()) - return result; - } - return {}; } @@ -151,13 +146,12 @@ void InstanceImportTask::processZipPack() qDebug() << "Attempting to create instance from" << m_archivePath; // open the zip and find relevant files in it - auto packZip = std::make_shared(m_archivePath); - if (!packZip->open(QuaZip::mdUnzip)) { + MMCZip::ArchiveReader packZip(m_archivePath); + if (!packZip.collectFiles()) { emitFailed(tr("Unable to open supplied modpack zip file.")); return; } - QuaZipDir packZipDir(packZip.get()); qDebug() << "Attempting to determine instance type"; QString root; @@ -165,18 +159,18 @@ void InstanceImportTask::processZipPack() // NOTE: Prioritize modpack platforms that aren't searched for recursively. // Especially Flame has a very common filename for its manifest, which may appear inside overrides for example // https://docs.modrinth.com/docs/modpacks/format_definition/#storage - if (packZipDir.exists("/modrinth.index.json")) { + if (packZip.exists("/modrinth.index.json")) { // process as Modrinth pack qDebug() << "Modrinth:" << true; m_modpackType = ModpackType::Modrinth; - } else if (packZipDir.exists("/bin/modpack.jar") || packZipDir.exists("/bin/version.json")) { + } else if (packZip.exists("/bin/modpack.jar") || packZip.exists("/bin/version.json")) { // process as Technic pack qDebug() << "Technic:" << true; extractDir.mkpath("minecraft"); extractDir.cd("minecraft"); m_modpackType = ModpackType::Technic; } else { - root = getRootFromZip(packZip.get()); + root = getRootFromZip(packZip.getFiles()); setDetails(""); } if (m_modpackType == ModpackType::Unknown) { @@ -186,7 +180,7 @@ void InstanceImportTask::processZipPack() setStatus(tr("Extracting modpack")); // make sure we extract just the pack - auto zipTask = makeShared(packZip, extractDir, root); + auto zipTask = makeShared(m_archivePath, extractDir, root); auto progressStep = std::make_shared(); connect(zipTask.get(), &Task::finished, this, [this, progressStep] { diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h index 8884e0801..4c9e6feb5 100644 --- a/launcher/InstanceImportTask.h +++ b/launcher/InstanceImportTask.h @@ -40,8 +40,6 @@ #include #include "InstanceTask.h" -class QuaZip; - class InstanceImportTask : public InstanceTask { Q_OBJECT public: @@ -58,7 +56,7 @@ class InstanceImportTask : public InstanceTask { void processTechnic(); void processFlame(); void processModrinth(); - QString getRootFromZip(QuaZip* zip, const QString& root = ""); + QString getRootFromZip(QStringList files); private slots: void processZipPack(); diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 2273318ee..cbea045fc 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -148,8 +148,12 @@ bool LaunchController::askPlayDemo() return box.clickedButton() == demoButton; } -QString LaunchController::askOfflineName(QString playerName, bool demo, bool& ok) +QString LaunchController::askOfflineName(QString playerName, bool demo, bool* ok) { + if (ok != nullptr) { + *ok = false; + } + // we ask the user for a player name QString message = tr("Choose your offline mode player name."); if (demo) { @@ -166,9 +170,12 @@ QString LaunchController::askOfflineName(QString playerName, bool demo, bool& ok return {}; } - if (const QString name = dialog.getUsername(); !name.isEmpty()) { - usedname = name; - APPLICATION->settings()->set("LastOfflinePlayerName", usedname); + const QString name = dialog.getUsername(); + usedname = name; + APPLICATION->settings()->set("LastOfflinePlayerName", usedname); + + if (ok != nullptr) { + *ok = true; } return usedname; } @@ -185,7 +192,7 @@ void LaunchController::login() if (m_demo) { // we ask the user for a player name bool ok = false; - auto name = askOfflineName("Player", m_demo, ok); + auto name = askOfflineName("Player", m_demo, &ok); if (ok) { m_session = std::make_shared(); static const QRegularExpression s_removeChars("[{}-]"); @@ -264,7 +271,7 @@ void LaunchController::login() bool ok = false; QString name; if (m_offlineName.isEmpty()) { - name = askOfflineName(m_session->player_name, m_session->demo, ok); + name = askOfflineName(m_session->player_name, m_session->demo, &ok); if (!ok) { tryagain = false; break; diff --git a/launcher/LaunchController.h b/launcher/LaunchController.h index af57994f5..50b72eb18 100644 --- a/launcher/LaunchController.h +++ b/launcher/LaunchController.h @@ -77,7 +77,7 @@ class LaunchController : public Task { void launchInstance(); void decideAccount(); bool askPlayDemo(); - QString askOfflineName(QString playerName, bool demo, bool& ok); + QString askOfflineName(QString playerName, bool demo, bool* ok = nullptr); bool reauthenticateAccount(MinecraftAccountPtr account); private slots: diff --git a/launcher/LoggedProcess.cpp b/launcher/LoggedProcess.cpp index b1efc8bd3..bae45ad88 100644 --- a/launcher/LoggedProcess.cpp +++ b/launcher/LoggedProcess.cpp @@ -36,10 +36,10 @@ #include "LoggedProcess.h" #include -#include +#include #include "MessageLevel.h" -LoggedProcess::LoggedProcess(const QTextCodec* output_codec, QObject* parent) +LoggedProcess::LoggedProcess(const QStringConverter::Encoding output_codec, QObject* parent) : QProcess(parent), m_err_decoder(output_codec), m_out_decoder(output_codec) { // QProcess has a strange interface... let's map a lot of those into a few. @@ -57,9 +57,9 @@ LoggedProcess::~LoggedProcess() } } -QStringList LoggedProcess::reprocess(const QByteArray& data, QTextDecoder& decoder) +QStringList LoggedProcess::reprocess(const QByteArray& data, QStringDecoder& decoder) { - auto str = decoder.toUnicode(data); + QString str = decoder(data); if (!m_leftover_line.isEmpty()) { str.prepend(m_leftover_line); diff --git a/launcher/LoggedProcess.h b/launcher/LoggedProcess.h index 75ba15dfd..c01d464d1 100644 --- a/launcher/LoggedProcess.h +++ b/launcher/LoggedProcess.h @@ -36,7 +36,7 @@ #pragma once #include -#include +#include #include "MessageLevel.h" /* @@ -49,7 +49,7 @@ class LoggedProcess : public QProcess { enum State { NotRunning, Starting, FailedToStart, Running, Finished, Crashed, Aborted }; public: - explicit LoggedProcess(const QTextCodec* output_codec = QTextCodec::codecForLocale(), QObject* parent = 0); + explicit LoggedProcess(QStringConverter::Encoding outputEncoding = QStringConverter::System, QObject* parent = nullptr); virtual ~LoggedProcess(); State state() const; @@ -77,11 +77,11 @@ class LoggedProcess : public QProcess { private: void changeState(LoggedProcess::State state); - QStringList reprocess(const QByteArray& data, QTextDecoder& decoder); + QStringList reprocess(const QByteArray& data, QStringDecoder& decoder); private: - QTextDecoder m_err_decoder; - QTextDecoder m_out_decoder; + QStringDecoder m_err_decoder; + QStringDecoder m_out_decoder; QString m_leftover_line; bool m_killed = false; State m_state = NotRunning; diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index dfe397930..8e4e433ed 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -35,66 +35,46 @@ */ #include "MMCZip.h" -#include -#include -#include +#include #include "FileSystem.h" +#include "archive/ArchiveReader.h" +#include "archive/ArchiveWriter.h" #include #include #include #include - -#if defined(LAUNCHER_APPLICATION) -#include -#endif +#include namespace MMCZip { // ours -bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet& contained, const Filter& filter) +using FilterFunction = std::function; +#if defined(LAUNCHER_APPLICATION) +bool mergeZipFiles(ArchiveWriter& into, QFileInfo from, QSet& contained, const FilterFunction& filter = nullptr) { - QuaZip modZip(from.filePath()); - modZip.open(QuaZip::mdUnzip); - - QuaZipFile fileInsideMod(&modZip); - QuaZipFile zipOutFile(into); - for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile()) { - QString filename = modZip.getCurrentFileName(); + ArchiveReader r(from.absoluteFilePath()); + return r.parse([&into, &contained, &filter, from](ArchiveReader::File* f) { + auto filename = f->filename(); if (filter && !filter(filename)) { qDebug() << "Skipping file " << filename << " from " << from.fileName() << " - filtered"; - continue; + f->skip(); + return true; } if (contained.contains(filename)) { qDebug() << "Skipping already contained file " << filename << " from " << from.fileName(); - continue; + f->skip(); + return true; } contained.insert(filename); - - 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)) { - qCritical() << "Failed to open " << filename << " in the jar"; - fileInsideMod.close(); - return false; - } - if (!JlCompress::copyData(fileInsideMod, zipOutFile)) { - zipOutFile.close(); - fileInsideMod.close(); + if (!into.addFile(f)) { qCritical() << "Failed to copy data of " << filename << " into the jar"; return false; } - zipOutFile.close(); - fileInsideMod.close(); - } - return true; + return true; + }); } -bool compressDirFiles(QuaZip* zip, QString dir, QFileInfoList files, bool followSymlinks) +bool compressDirFiles(ArchiveWriter& zip, QString dir, QFileInfoList files) { QDir directory(dir); if (!directory.exists()) @@ -103,48 +83,18 @@ bool compressDirFiles(QuaZip* zip, QString dir, QFileInfoList files, bool follow for (auto e : files) { auto filePath = directory.relativeFilePath(e.absoluteFilePath()); auto srcPath = e.absoluteFilePath(); - if (followSymlinks) { - if (e.isSymLink()) { - srcPath = e.symLinkTarget(); - } else { - srcPath = e.canonicalFilePath(); - } - } - if (!JlCompress::compressFile(zip, srcPath, filePath)) + if (!zip.addFile(srcPath, filePath)) return false; } return true; } -bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks) -{ - QuaZip zip(fileCompressed); - zip.setUtf8Enabled(true); - QDir().mkpath(QFileInfo(fileCompressed).absolutePath()); - if (!zip.open(QuaZip::mdCreate)) { - FS::deletePath(fileCompressed); - return false; - } - - auto result = compressDirFiles(&zip, dir, files, followSymlinks); - - zip.close(); - if (zip.getZipError() != 0) { - FS::deletePath(fileCompressed); - return false; - } - - return result; -} - -#if defined(LAUNCHER_APPLICATION) // ours bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList& mods) { - QuaZip zipOut(targetJarPath); - zipOut.setUtf8Enabled(true); - if (!zipOut.open(QuaZip::mdCreate)) { + ArchiveWriter zipOut(targetJarPath); + if (!zipOut.open()) { FS::deletePath(targetJarPath); qCritical() << "Failed to open the minecraft.jar for modding"; return false; @@ -161,7 +111,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QListenabled()) continue; if (mod->type() == ResourceType::ZIPFILE) { - if (!mergeZipFiles(&zipOut, mod->fileinfo(), addedFiles)) { + if (!mergeZipFiles(zipOut, mod->fileinfo(), addedFiles)) { zipOut.close(); FS::deletePath(targetJarPath); qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar."; @@ -170,7 +120,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QListtype() == ResourceType::SINGLEFILE) { // FIXME: buggy - does not work with addedFiles auto filename = mod->fileinfo(); - if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName())) { + if (!zipOut.addFile(filename.absoluteFilePath(), filename.fileName())) { zipOut.close(); FS::deletePath(targetJarPath); qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar."; @@ -193,7 +143,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QListfileinfo().fileName() << "to the jar."; @@ -209,7 +159,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList extractSubDir(QuaZip* zip, const QString& subdir, const QString& target) +std::optional extractSubDir(ArchiveReader* zip, const QString& subdir, const QString& target) { auto target_top_dir = QUrl::fromLocalFile(target); QStringList extracted; qDebug() << "Extracting subdir" << subdir << "from" << zip->getZipName() << "to" << target; - auto numEntries = zip->getEntriesCount(); - if (numEntries < 0) { + if (!zip->collectFiles()) { qWarning() << "Failed to enumerate files in archive"; return std::nullopt; - } else if (numEntries == 0) { + } + if (zip->getFiles().isEmpty()) { qDebug() << "Extracting empty archives seems odd..."; return extracted; - } else if (!zip->goToFirstFile()) { - qWarning() << "Failed to seek to first file in zip"; - return std::nullopt; } - do { - QString file_name = zip->getCurrentFileName(); - file_name = FS::RemoveInvalidPathChars(file_name); - if (!file_name.startsWith(subdir)) - continue; + auto extPtr = ArchiveWriter::createDiskWriter(); + auto ext = extPtr.get(); - auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(subdir.size())); - auto original_name = relative_file_name; - - // Fix subdirs/files ending with a / getting transformed into absolute paths - if (relative_file_name.startsWith('/')) - relative_file_name = relative_file_name.mid(1); - - // Fix weird "folders with a single file get squashed" thing - QString sub_path; - if (relative_file_name.contains('/') && !relative_file_name.endsWith('/')) { - sub_path = relative_file_name.section('/', 0, -2) + '/'; - FS::ensureFolderPathExists(FS::PathCombine(target, sub_path)); - - relative_file_name = relative_file_name.split('/').last(); - } - - QString target_file_path; - if (relative_file_name.isEmpty()) { - target_file_path = target + '/'; - } else { - target_file_path = FS::PathCombine(target_top_dir.toLocalFile(), sub_path, relative_file_name); - if (relative_file_name.endsWith('/') && !target_file_path.endsWith('/')) - target_file_path += '/'; - } - - if (!target_top_dir.isParentOf(QUrl::fromLocalFile(target_file_path))) { - qWarning() << "Extracting" << relative_file_name << "was cancelled, because it was effectively outside of the target path" - << target; - return std::nullopt; - } - - if (!JlCompress::extractFile(zip, "", target_file_path)) { - qWarning() << "Failed to extract file" << original_name << "to" << target_file_path; - JlCompress::removeFile(extracted); - return std::nullopt; - } - - extracted.append(target_file_path); - auto fileInfo = QFileInfo(target_file_path); - if (fileInfo.isFile()) { - auto permissions = fileInfo.permissions(); - auto maxPermisions = QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser | - QFileDevice::Permission::ReadGroup | QFileDevice::Permission::ReadOther; - auto minPermisions = QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser; - - auto newPermisions = (permissions & maxPermisions) | minPermisions; - if (newPermisions != permissions) { - if (!QFile::setPermissions(target_file_path, newPermisions)) { - qWarning() << (QObject::tr("Could not fix permissions for %1").arg(target_file_path)); - } + if (!zip->parse([&subdir, &target, &target_top_dir, ext, &extracted](ArchiveReader::File* f) { + QString file_name = f->filename(); + file_name = FS::RemoveInvalidPathChars(file_name); + if (!file_name.startsWith(subdir)) { + f->skip(); + return true; } - } else if (fileInfo.isDir()) { - // Ensure the folder has the minimal required permissions - QFile::Permissions minimalPermissions = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadGroup | - QFile::ExeGroup | QFile::ReadOther | QFile::ExeOther; - QFile::Permissions currentPermissions = fileInfo.permissions(); - if ((currentPermissions & minimalPermissions) != minimalPermissions) { - if (!QFile::setPermissions(target_file_path, minimalPermissions)) { - qWarning() << (QObject::tr("Could not fix permissions for %1").arg(target_file_path)); - } + auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(subdir.size())); + auto original_name = relative_file_name; + + // Fix subdirs/files ending with a / getting transformed into absolute paths + if (relative_file_name.startsWith('/')) + relative_file_name = relative_file_name.mid(1); + + // Fix weird "folders with a single file get squashed" thing + QString sub_path; + if (relative_file_name.contains('/') && !relative_file_name.endsWith('/')) { + sub_path = relative_file_name.section('/', 0, -2) + '/'; + FS::ensureFolderPathExists(FS::PathCombine(target, sub_path)); + + relative_file_name = relative_file_name.split('/').last(); } - } - qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path; - } while (zip->goToNextFile()); + QString target_file_path; + if (relative_file_name.isEmpty()) { + target_file_path = target + '/'; + } else { + target_file_path = FS::PathCombine(target_top_dir.toLocalFile(), sub_path, relative_file_name); + if (relative_file_name.endsWith('/') && !target_file_path.endsWith('/')) + target_file_path += '/'; + } + + if (!target_top_dir.isParentOf(QUrl::fromLocalFile(target_file_path))) { + qWarning() << "Extracting" << relative_file_name << "was cancelled, because it was effectively outside of the target path" + << target; + return false; + } + if (!f->writeFile(ext, target_file_path)) { + qWarning() << "Failed to extract file" << original_name << "to" << target_file_path; + return false; + } + + extracted.append(target_file_path); + + qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path; + return true; + })) { + qWarning() << "Failed to parse file" << zip->getZipName(); + FS::removeFiles(extracted); + return std::nullopt; + } return extracted; } -// ours -bool extractRelFile(QuaZip* zip, const QString& file, const QString& target) -{ - return JlCompress::extractFile(zip, file, target); -} - // ours std::optional extractDir(QString fileCompressed, QString dir) { - QuaZip zip(fileCompressed); - 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 unpacking:" << fileCompressed << "Error:" << zip.getZipError(); - ; - return std::nullopt; + // check if this is a minimum size empty zip file... + QFileInfo fileInfo(fileCompressed); + if (fileInfo.size() == 22) { + return QStringList(); } + ArchiveReader zip(fileCompressed); return extractSubDir(&zip, "", dir); } // ours std::optional extractDir(QString fileCompressed, QString subdir, QString dir) { - QuaZip zip(fileCompressed); - 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 unpacking:" << fileCompressed << "Error:" << zip.getZipError(); - ; - return std::nullopt; + // check if this is a minimum size empty zip file... + QFileInfo fileInfo(fileCompressed); + if (fileInfo.size() == 22) { + return QStringList(); } + ArchiveReader zip(fileCompressed); return extractSubDir(&zip, subdir, dir); } // ours bool extractFile(QString fileCompressed, QString file, QString target) { - QuaZip zip(fileCompressed); - if (!zip.open(QuaZip::mdUnzip)) { - // check if this is a minimum size empty zip file... - QFileInfo fileInfo(fileCompressed); - if (fileInfo.size() == 22) { - return true; - } - qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError(); + // check if this is a minimum size empty zip file... + QFileInfo fileInfo(fileCompressed); + if (fileInfo.size() == 22) { + return true; + } + ArchiveReader zip(fileCompressed); + auto f = zip.goToFile(file); + if (!f) { return false; } - return extractRelFile(&zip, file, target); + auto extPtr = ArchiveWriter::createDiskWriter(); + auto ext = extPtr.get(); + + return f->writeFile(ext, target); } bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFileFunction excludeFilter) @@ -453,218 +329,4 @@ bool collectFileListRecursively(const QString& rootDir, const QString& subDir, Q } return true; } - -#if defined(LAUNCHER_APPLICATION) -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::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("Compressing: " + 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()) { - FS::deletePath(m_output_path); - emitAborted(); - } else if (auto result = m_build_zip_future.result(); result.has_value()) { - FS::deletePath(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; -} - -void ExtractZipTask::executeTask() -{ - if (!m_input->isOpen() && !m_input->open(QuaZip::mdUnzip)) { - emitFailed(tr("Unable to open supplied zip file.")); - return; - } - m_zip_future = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return extractZip(); }); - connect(&m_zip_watcher, &QFutureWatcher::finished, this, &ExtractZipTask::finish); - m_zip_watcher.setFuture(m_zip_future); -} - -auto ExtractZipTask::extractZip() -> ZipResult -{ - auto target = m_output_dir.absolutePath(); - auto target_top_dir = QUrl::fromLocalFile(target); - - QStringList extracted; - - qDebug() << "Extracting subdir" << m_subdirectory << "from" << m_input->getZipName() << "to" << target; - auto numEntries = m_input->getEntriesCount(); - if (numEntries < 0) { - return ZipResult(tr("Failed to enumerate files in archive")); - } - if (numEntries == 0) { - logWarning(tr("Extracting empty archives seems odd...")); - return ZipResult(); - } - if (!m_input->goToFirstFile()) { - return ZipResult(tr("Failed to seek to first file in zip")); - } - - setStatus("Extracting files..."); - setProgress(0, numEntries); - do { - if (m_zip_future.isCanceled()) - return ZipResult(); - setProgress(m_progress + 1, m_progressTotal); - QString file_name = m_input->getCurrentFileName(); - if (!file_name.startsWith(m_subdirectory)) - continue; - - auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(m_subdirectory.size())); - auto original_name = relative_file_name; - setStatus("Unpacking: " + relative_file_name); - - // Fix subdirs/files ending with a / getting transformed into absolute paths - if (relative_file_name.startsWith('/')) - relative_file_name = relative_file_name.mid(1); - - // Fix weird "folders with a single file get squashed" thing - QString sub_path; - if (relative_file_name.contains('/') && !relative_file_name.endsWith('/')) { - sub_path = relative_file_name.section('/', 0, -2) + '/'; - FS::ensureFolderPathExists(FS::PathCombine(target, sub_path)); - - relative_file_name = relative_file_name.split('/').last(); - } - - QString target_file_path; - if (relative_file_name.isEmpty()) { - target_file_path = target + '/'; - } else { - target_file_path = FS::PathCombine(target_top_dir.toLocalFile(), sub_path, relative_file_name); - if (relative_file_name.endsWith('/') && !target_file_path.endsWith('/')) - target_file_path += '/'; - } - - if (!target_top_dir.isParentOf(QUrl::fromLocalFile(target_file_path))) { - return ZipResult(tr("Extracting %1 was cancelled, because it was effectively outside of the target path %2") - .arg(relative_file_name, target)); - } - - if (!JlCompress::extractFile(m_input.get(), "", target_file_path)) { - JlCompress::removeFile(extracted); - return ZipResult(tr("Failed to extract file %1 to %2").arg(original_name, target_file_path)); - } - - extracted.append(target_file_path); - auto fileInfo = QFileInfo(target_file_path); - if (fileInfo.isFile()) { - auto permissions = fileInfo.permissions(); - auto maxPermisions = QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser | - QFileDevice::Permission::ReadGroup | QFileDevice::Permission::ReadOther; - auto minPermisions = QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser; - - auto newPermisions = (permissions & maxPermisions) | minPermisions; - if (newPermisions != permissions) { - if (!QFile::setPermissions(target_file_path, newPermisions)) { - logWarning(tr("Could not fix permissions for %1").arg(target_file_path)); - } - } - } else if (fileInfo.isDir()) { - // Ensure the folder has the minimal required permissions - QFile::Permissions minimalPermissions = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadGroup | - QFile::ExeGroup | QFile::ReadOther | QFile::ExeOther; - - QFile::Permissions currentPermissions = fileInfo.permissions(); - if ((currentPermissions & minimalPermissions) != minimalPermissions) { - if (!QFile::setPermissions(target_file_path, minimalPermissions)) { - logWarning(tr("Could not fix permissions for %1").arg(target_file_path)); - } - } - } - - qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path; - } while (m_input->goToNextFile()); - - return ZipResult(); -} - -void ExtractZipTask::finish() -{ - if (m_zip_future.isCanceled()) { - emitAborted(); - } else if (auto result = m_zip_future.result(); result.has_value()) { - emitFailed(result.value()); - } else { - emitSucceeded(); - } -} - -bool ExtractZipTask::abort() -{ - if (m_zip_future.isRunning()) { - m_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; -} - -#endif } // namespace MMCZip diff --git a/launcher/MMCZip.h b/launcher/MMCZip.h index e23d29d65..04fe90379 100644 --- a/launcher/MMCZip.h +++ b/launcher/MMCZip.h @@ -36,8 +36,6 @@ #pragma once -#include -#include #include #include #include @@ -46,72 +44,27 @@ #include #include #include -#include #include +#include "archive/ArchiveReader.h" #if defined(LAUNCHER_APPLICATION) #include "minecraft/mod/Mod.h" #endif -#include "Filter.h" -#include "tasks/Task.h" namespace MMCZip { using FilterFileFunction = std::function; -/** - * Merge two zip files, using a filter function - */ -bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet& contained, const Filter& filter = nullptr); - -/** - * Compress directory, by providing a list of files to compress - * \param zip target archive - * \param dir directory that will be compressed (to compress with relative paths) - * \param files list of files to compress - * \param followSymlinks should follow symlinks when compressing file data - * \return true for success or false for failure - */ -bool compressDirFiles(QuaZip* zip, QString dir, QFileInfoList files, bool followSymlinks = false); - -/** - * Compress directory, by providing a list of files to compress - * \param fileCompressed target archive file - * \param dir directory that will be compressed (to compress with relative paths) - * \param files list of files to compress - * \param followSymlinks should follow symlinks when compressing file data - * \return true for success or false for failure - */ -bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks = false); - #if defined(LAUNCHER_APPLICATION) /** * take a source jar, add mods to it, resulting in target jar */ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList& mods); #endif -/** - * Find a single file in archive by file name (not path) - * - * \param ignore_paths paths to skip when recursing the search - * - * \return the path prefix where the file is - */ -QString findFolderOfFileInZip(QuaZip* zip, const QString& what, const QStringList& ignore_paths = {}, const QString& root = QString("")); - -/** - * Find a multiple files of the same name in archive by file name - * If a file is found in a path, no deeper paths are searched - * - * \return true if anything was found - */ -bool findFilesInZip(QuaZip* zip, const QString& what, QStringList& result, const QString& root = QString()); /** * Extract a subdirectory from an archive */ -std::optional extractSubDir(QuaZip* zip, const QString& subdir, const QString& target); - -bool extractRelFile(QuaZip* zip, const QString& file, const QString& target); +std::optional extractSubDir(ArchiveReader* zip, const QString& subdir, const QString& target); /** * Extract a whole archive. @@ -151,90 +104,4 @@ bool extractFile(QString fileCompressed, QString file, QString dir); * \return true for success or false for failure */ bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFileFunction excludeFilter); - -#if defined(LAUNCHER_APPLICATION) -class ExportToZipTask : public Task { - Q_OBJECT - public: - ExportToZipTask(QString outputPath, - QDir dir, - QFileInfoList files, - QString destinationPrefix = "", - bool followSymlinks = false, - bool utf8Enabled = false) - : m_output_path(outputPath) - , m_output(outputPath) - , m_dir(dir) - , m_files(files) - , m_destination_prefix(destinationPrefix) - , m_follow_symlinks(followSymlinks) - { - setAbortable(true); - m_output.setUtf8Enabled(utf8Enabled); - }; - ExportToZipTask(QString outputPath, - QString dir, - QFileInfoList files, - QString destinationPrefix = "", - bool followSymlinks = false, - bool utf8Enabled = false) - : ExportToZipTask(outputPath, QDir(dir), files, destinationPrefix, followSymlinks, utf8Enabled) {}; - - virtual ~ExportToZipTask() = default; - - void setExcludeFiles(QStringList excludeFiles) { m_exclude_files = excludeFiles; } - void addExtraFile(QString fileName, QByteArray data) { m_extra_files.insert(fileName, data); } - - using ZipResult = std::optional; - - 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 m_extra_files; - - QFuture m_build_zip_future; - QFutureWatcher m_build_zip_watcher; -}; - -class ExtractZipTask : public Task { - Q_OBJECT - public: - ExtractZipTask(QString input, QDir outputDir, QString subdirectory = "") - : ExtractZipTask(std::make_shared(input), outputDir, subdirectory) - {} - ExtractZipTask(std::shared_ptr input, QDir outputDir, QString subdirectory = "") - : m_input(input), m_output_dir(outputDir), m_subdirectory(subdirectory) - {} - virtual ~ExtractZipTask() = default; - - using ZipResult = std::optional; - - protected: - virtual void executeTask() override; - bool abort() override; - - ZipResult extractZip(); - void finish(); - - private: - std::shared_ptr m_input; - QDir m_output_dir; - QString m_subdirectory; - - QFuture m_zip_future; - QFutureWatcher m_zip_watcher; -}; -#endif } // namespace MMCZip diff --git a/launcher/Untar.cpp b/launcher/Untar.cpp deleted file mode 100644 index f1963e7aa..000000000 --- a/launcher/Untar.cpp +++ /dev/null @@ -1,260 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (c) 2023-2024 Trial97 - * - * 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 . - * - * 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 "Untar.h" -#include -#include -#include -#include -#include -#include "FileSystem.h" - -// adaptation of the: -// - https://github.com/madler/zlib/blob/develop/contrib/untgz/untgz.c -// - https://en.wikipedia.org/wiki/Tar_(computing) -// - https://github.com/euroelessar/cutereader/blob/master/karchive/src/ktar.cpp - -#define BLOCKSIZE 512 -#define SHORTNAMESIZE 100 - -enum class TypeFlag : char { - Regular = '0', // regular file - ARegular = 0, // regular file - Link = '1', // link - Symlink = '2', // reserved - Character = '3', // character special - Block = '4', // block special - Directory = '5', // directory - FIFO = '6', // FIFO special - Contiguous = '7', // reserved - // Posix stuff - GlobalPosixHeader = 'g', - ExtendedPosixHeader = 'x', - // 'A'– 'Z' Vendor specific extensions(POSIX .1 - 1988) - // GNU - GNULongLink = 'K', /* long link name */ - GNULongName = 'L', /* long file name */ -}; - -// struct Header { /* byte offset */ -// char name[100]; /* 0 */ -// char mode[8]; /* 100 */ -// char uid[8]; /* 108 */ -// char gid[8]; /* 116 */ -// char size[12]; /* 124 */ -// char mtime[12]; /* 136 */ -// char chksum[8]; /* 148 */ -// TypeFlag typeflag; /* 156 */ -// char linkname[100]; /* 157 */ -// char magic[6]; /* 257 */ -// char version[2]; /* 263 */ -// char uname[32]; /* 265 */ -// char gname[32]; /* 297 */ -// char devmajor[8]; /* 329 */ -// char devminor[8]; /* 337 */ -// char prefix[155]; /* 345 */ -// /* 500 */ -// }; - -bool readLonglink(QIODevice* in, qint64 size, QByteArray& longlink) -{ - qint64 n = 0; - size--; // ignore trailing null - if (size < 0) { - qCritical() << "The filename size is negative"; - return false; - } - longlink.resize(size + (BLOCKSIZE - size % BLOCKSIZE)); // make the size divisible by BLOCKSIZE - for (qint64 offset = 0; offset < longlink.size(); offset += BLOCKSIZE) { - n = in->read(longlink.data() + offset, BLOCKSIZE); - if (n != BLOCKSIZE) { - qCritical() << "The expected blocksize was not respected for the name"; - return false; - } - } - longlink.truncate(qstrlen(longlink.constData())); - return true; -} - -int getOctal(char* buffer, int maxlenght, bool* ok) -{ - return QByteArray(buffer, qstrnlen(buffer, maxlenght)).toInt(ok, 8); -} - -QString decodeName(char* name) -{ - return QFile::decodeName(QByteArray(name, qstrnlen(name, 100))); -} -bool Tar::extract(QIODevice* in, QString dst) -{ - char buffer[BLOCKSIZE]; - QString name, symlink, firstFolderName; - bool doNotReset = false, ok; - while (true) { - auto n = in->read(buffer, BLOCKSIZE); - if (n != BLOCKSIZE) { // allways expect complete blocks - qCritical() << "The expected blocksize was not respected"; - return false; - } - if (buffer[0] == 0) { // end of archive - return true; - } - int mode = getOctal(buffer + 100, 8, &ok) | QFile::ReadUser | QFile::WriteUser; // hack to ensure write and read permisions - if (!ok) { - qCritical() << "The file mode can't be read"; - return false; - } - // there are names that are exactly 100 bytes long - // and neither longlink nor \0 terminated (bug:101472) - - if (name.isEmpty()) { - name = decodeName(buffer); - if (!firstFolderName.isEmpty() && name.startsWith(firstFolderName)) { - name = name.mid(firstFolderName.size()); - } - } - if (symlink.isEmpty()) - symlink = decodeName(buffer); - qint64 size = getOctal(buffer + 124, 12, &ok); - if (!ok) { - qCritical() << "The file size can't be read"; - return false; - } - switch (TypeFlag(buffer[156])) { - case TypeFlag::Regular: - /* fallthrough */ - case TypeFlag::ARegular: { - auto fileName = FS::PathCombine(dst, name); - if (!FS::ensureFilePathExists(fileName)) { - qCritical() << "Can't ensure the file path to exist: " << fileName; - return false; - } - QFile out(fileName); - if (!out.open(QFile::WriteOnly)) { - qCritical() << "Can't open file:" << fileName; - return false; - } - out.setPermissions(QFile::Permissions(mode)); - while (size > 0) { - QByteArray tmp(BLOCKSIZE, 0); - n = in->read(tmp.data(), BLOCKSIZE); - if (n != BLOCKSIZE) { - qCritical() << "The expected blocksize was not respected when reading file"; - return false; - } - tmp.truncate(qMin(qint64(BLOCKSIZE), size)); - out.write(tmp); - size -= BLOCKSIZE; - } - break; - } - case TypeFlag::Directory: { - if (firstFolderName.isEmpty()) { - firstFolderName = name; - break; - } - auto folderPath = FS::PathCombine(dst, name); - if (!FS::ensureFolderPathExists(folderPath)) { - qCritical() << "Can't ensure that folder exists: " << folderPath; - return false; - } - break; - } - case TypeFlag::GNULongLink: { - doNotReset = true; - QByteArray longlink; - if (readLonglink(in, size, longlink)) { - symlink = QFile::decodeName(longlink.constData()); - } else { - qCritical() << "Failed to read long link"; - return false; - } - break; - } - case TypeFlag::GNULongName: { - doNotReset = true; - QByteArray longlink; - if (readLonglink(in, size, longlink)) { - name = QFile::decodeName(longlink.constData()); - } else { - qCritical() << "Failed to read long name"; - return false; - } - break; - } - case TypeFlag::Link: - /* fallthrough */ - case TypeFlag::Symlink: { - auto fileName = FS::PathCombine(dst, name); - if (!FS::create_link(FS::PathCombine(QFileInfo(fileName).path(), symlink), fileName)()) { // do not use symlinks - qCritical() << "Can't create link for:" << fileName << " to:" << FS::PathCombine(QFileInfo(fileName).path(), symlink); - return false; - } - FS::ensureFilePathExists(fileName); - QFile::setPermissions(fileName, QFile::Permissions(mode)); - break; - } - case TypeFlag::Character: - /* fallthrough */ - case TypeFlag::Block: - /* fallthrough */ - case TypeFlag::FIFO: - /* fallthrough */ - case TypeFlag::Contiguous: - /* fallthrough */ - case TypeFlag::GlobalPosixHeader: - /* fallthrough */ - case TypeFlag::ExtendedPosixHeader: - /* fallthrough */ - default: - break; - } - if (!doNotReset) { - name.truncate(0); - symlink.truncate(0); - } - doNotReset = false; - } - return true; -} - -bool GZTar::extract(QString src, QString dst) -{ - QuaGzipFile a(src); - if (!a.open(QIODevice::ReadOnly)) { - qCritical() << "Can't open tar file:" << src; - return false; - } - return Tar::extract(&a, dst); -} \ No newline at end of file diff --git a/launcher/Untar.h b/launcher/Untar.h deleted file mode 100644 index 50e3a16e3..000000000 --- a/launcher/Untar.h +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (c) 2023-2024 Trial97 - * - * 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 . - * - * 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 - -// this is a hack used for the java downloader (feel free to remove it in favor of a library) -// both extract functions will extract the first folder inside dest(disregarding the prefix) -namespace Tar { -bool extract(QIODevice* in, QString dst); -} - -namespace GZTar { -bool extract(QString src, QString dst); -} \ No newline at end of file diff --git a/launcher/archive/ArchiveReader.cpp b/launcher/archive/ArchiveReader.cpp new file mode 100644 index 000000000..a866b49c5 --- /dev/null +++ b/launcher/archive/ArchiveReader.cpp @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-3.0-only AND LicenseRef-PublicDomain +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2025 Trial97 + * + * 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 . + * + * Additional note: Portions of this file are released into the public domain + * under LicenseRef-PublicDomain. + */ +#include "ArchiveReader.h" +#include +#include +#include +#include + +namespace MMCZip { +QStringList ArchiveReader::getFiles() +{ + return m_fileNames; +} + +bool ArchiveReader::collectFiles(bool onlyFiles) +{ + return parse([this, onlyFiles](File* f) { + if (!onlyFiles || f->isFile()) + m_fileNames << f->filename(); + return f->skip(); + }); +} + +QString ArchiveReader::File::filename() +{ + return QString::fromUtf8(archive_entry_pathname(m_entry)); +} + +QByteArray ArchiveReader::File::readAll(int* outStatus) +{ + QByteArray data; + const void* buff; + size_t size; + la_int64_t offset; + + int status; + while ((status = archive_read_data_block(m_archive.get(), &buff, &size, &offset)) == ARCHIVE_OK) { + data.append(static_cast(buff), static_cast(size)); + } + if (status != ARCHIVE_EOF && status != ARCHIVE_OK) { + qWarning() << "libarchive read error: " << archive_error_string(m_archive.get()); + } + if (outStatus) { + *outStatus = status; + } + return data; +} + +QDateTime ArchiveReader::File::dateTime() +{ + auto mtime = archive_entry_mtime(m_entry); + auto mtime_nsec = archive_entry_mtime_nsec(m_entry); + auto dt = QDateTime::fromSecsSinceEpoch(mtime); + return dt.addMSecs(mtime_nsec / 1e6); +} + +int ArchiveReader::File::readNextHeader() +{ + return archive_read_next_header(m_archive.get(), &m_entry); +} + +auto ArchiveReader::goToFile(QString filename) -> std::unique_ptr +{ + auto f = std::make_unique(); + auto a = f->m_archive.get(); + archive_read_support_format_all(a); + archive_read_support_filter_all(a); + auto fileName = m_archivePath.toUtf8(); + if (archive_read_open_filename(a, fileName.constData(), m_blockSize) != ARCHIVE_OK) { + qCritical() << "Failed to open archive file:" << m_archivePath << "-" << archive_error_string(a); + return nullptr; + } + + while (f->readNextHeader() == ARCHIVE_OK) { + if (f->filename() == filename) { + return f; + } + f->skip(); + } + + archive_read_close(a); + return nullptr; +} + +static int copy_data(struct archive* ar, struct archive* aw, bool notBlock = false) +{ + int r; + const void* buff; + size_t size; + la_int64_t offset; + + for (;;) { + r = archive_read_data_block(ar, &buff, &size, &offset); + if (r == ARCHIVE_EOF) + return (ARCHIVE_OK); + if (r < ARCHIVE_OK) { + qCritical() << "Failed reading data block:" << archive_error_string(ar); + return (r); + } + if (notBlock) { + r = archive_write_data(aw, buff, size); + } else { + r = archive_write_data_block(aw, buff, size, offset); + } + if (r < ARCHIVE_OK) { + qCritical() << "Failed writing data block:" << archive_error_string(aw); + return (r); + } + } +} + +bool ArchiveReader::File::writeFile(archive* out, QString targetFileName, bool notBlock) +{ + auto entry = m_entry; + if (!targetFileName.isEmpty()) { + entry = archive_entry_clone(m_entry); + auto nameUtf8 = targetFileName.toUtf8(); + archive_entry_set_pathname(entry, nameUtf8.constData()); + } + if (archive_write_header(out, entry) < ARCHIVE_OK) { + qCritical() << "Failed to write header to entry:" << filename() << "-" << archive_error_string(out); + return false; + } else if (archive_entry_size(m_entry) > 0) { + auto r = copy_data(m_archive.get(), out, notBlock); + if (r < ARCHIVE_OK) + qCritical() << "Failed reading data block:" << archive_error_string(out); + if (r < ARCHIVE_WARN) + return false; + } + auto r = archive_write_finish_entry(out); + if (r < ARCHIVE_OK) + qCritical() << "Failed to finish writing entry:" << archive_error_string(out); + return (r > ARCHIVE_WARN); +} + +bool ArchiveReader::parse(std::function doStuff) +{ + auto f = std::make_unique(); + auto a = f->m_archive.get(); + archive_read_support_format_all(a); + archive_read_support_filter_all(a); + auto fileName = m_archivePath.toUtf8(); + if (archive_read_open_filename(a, fileName.constData(), m_blockSize) != ARCHIVE_OK) { + qCritical() << "Failed to open archive file:" << m_archivePath << "-" << f->error(); + return false; + } + + bool breakControl = false; + while (f->readNextHeader() == ARCHIVE_OK) { + if (!doStuff(f.get(), breakControl)) { + qCritical() << "Failed to parse file:" << f->filename() << "-" << f->error(); + return false; + } + if (breakControl) { + break; + } + } + + archive_read_close(a); + return true; +} +bool ArchiveReader::parse(std::function doStuff) +{ + return parse([doStuff](File* f, bool&) { return doStuff(f); }); +} + +bool ArchiveReader::File::isFile() +{ + return (archive_entry_filetype(m_entry) & AE_IFMT) == AE_IFREG; +} +bool ArchiveReader::File::skip() +{ + return archive_read_data_skip(m_archive.get()) == ARCHIVE_OK; +} +const char* ArchiveReader::File::error() +{ + return archive_error_string(m_archive.get()); +} +QString ArchiveReader::getZipName() +{ + return m_archivePath; +} + +bool ArchiveReader::exists(const QString& filePath) const +{ + if (filePath == QLatin1String("/") || filePath.isEmpty()) + return true; + // Normalize input path (remove trailing slash, if any) + QString normalizedPath = QDir::cleanPath(filePath); + if (normalizedPath.startsWith('/')) + normalizedPath.remove(0, 1); + if (normalizedPath == QLatin1String(".")) + return true; + if (normalizedPath == QLatin1String("..")) + return false; // root only + + // Check for exact file match + if (m_fileNames.contains(normalizedPath, Qt::CaseInsensitive)) + return true; + + // Check for directory existence by seeing if any file starts with that path + QString dirPath = normalizedPath + QLatin1Char('/'); + for (const QString& f : m_fileNames) { + if (f.startsWith(dirPath, Qt::CaseInsensitive)) + return true; + } + + return false; +} + +ArchiveReader::File::File() : m_archive(ArchivePtr(archive_read_new(), archive_read_free)) {} +} // namespace MMCZip diff --git a/launcher/archive/ArchiveReader.h b/launcher/archive/ArchiveReader.h new file mode 100644 index 000000000..379006278 --- /dev/null +++ b/launcher/archive/ArchiveReader.h @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2025 Trial97 + * + * 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 . + */ +#pragma once + +#include +#include +#include +#include + +struct archive; +struct archive_entry; +namespace MMCZip { +class ArchiveReader { + public: + using ArchivePtr = std::unique_ptr; + ArchiveReader(QString fileName) : m_archivePath(fileName) {} + virtual ~ArchiveReader() = default; + + QStringList getFiles(); + QString getZipName(); + bool collectFiles(bool onlyFiles = true); + bool exists(const QString& filePath) const; + + class File { + public: + File(); + virtual ~File() = default; + + QString filename(); + bool isFile(); + QDateTime dateTime(); + const char* error(); + + QByteArray readAll(int* outStatus = nullptr); + bool skip(); + bool writeFile(archive* out, QString targetFileName = "", bool notBlock = false); + + private: + int readNextHeader(); + + private: + friend ArchiveReader; + ArchivePtr m_archive; + archive_entry* m_entry; + }; + + std::unique_ptr goToFile(QString filename); + bool parse(std::function); + bool parse(std::function); + + private: + QString m_archivePath; + size_t m_blockSize = 10240; + + QStringList m_fileNames = {}; +}; +} // namespace MMCZip \ No newline at end of file diff --git a/launcher/archive/ArchiveWriter.cpp b/launcher/archive/ArchiveWriter.cpp new file mode 100644 index 000000000..87cead69c --- /dev/null +++ b/launcher/archive/ArchiveWriter.cpp @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2025 Trial97 + * + * 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 . + */ +#include "ArchiveWriter.h" +#include +#include +#include + +#include +#include + +#include + +namespace MMCZip { + +ArchiveWriter::ArchiveWriter(const QString& archiveName) : m_filename(archiveName) {} + +ArchiveWriter::~ArchiveWriter() +{ + close(); +} + +bool ArchiveWriter::open() +{ + if (m_filename.isEmpty()) { + qCritical() << "Archive m_filename not set."; + return false; + } + + m_archive = archive_write_new(); + if (!m_archive) { + qCritical() << "Archive not initialized."; + return false; + } + + auto format = m_format.toUtf8(); + archive_write_set_format_by_name(m_archive, format.constData()); + + if (archive_write_set_options(m_archive, "hdrcharset=UTF-8") != ARCHIVE_OK) { + qCritical() << "Failed to open archive file:" << m_filename << "-" << archive_error_string(m_archive); + return false; + } + + auto archiveNameUtf8 = m_filename.toUtf8(); + if (archive_write_open_filename(m_archive, archiveNameUtf8.constData()) != ARCHIVE_OK) { + qCritical() << "Failed to open archive file:" << m_filename << "-" << archive_error_string(m_archive); + return false; + } + + return true; +} + +bool ArchiveWriter::close() +{ + bool success = true; + if (m_archive) { + if (archive_write_close(m_archive) != ARCHIVE_OK) { + qCritical() << "Failed to close archive" << m_filename << "-" << archive_error_string(m_archive); + success = false; + } + if (archive_write_free(m_archive) != ARCHIVE_OK) { + qCritical() << "Failed to free archive" << m_filename << "-" << archive_error_string(m_archive); + success = false; + } + m_archive = nullptr; + } + return success; +} + +bool ArchiveWriter::addFile(const QString& fileName, const QString& fileDest) +{ + QFileInfo fileInfo(fileName); + if (!fileInfo.exists()) { + qCritical() << "File does not exist:" << fileInfo.filePath(); + return false; + } + + std::unique_ptr entry_ptr(archive_entry_new(), archive_entry_free); + auto entry = entry_ptr.get(); + if (!entry) { + qCritical() << "Failed to create archive entry"; + return false; + } + + auto fileDestUtf8 = fileDest.toUtf8(); + archive_entry_set_pathname(entry, fileDestUtf8.constData()); + + QByteArray utf8 = fileInfo.absoluteFilePath().toUtf8(); + const char* cpath = utf8.constData(); + struct stat st; + if (stat(cpath, &st) != 0) { + qCritical() << "Failed to stat file:" << fileInfo.filePath(); + } + // This should handle the copying of most attributes + archive_entry_copy_stat(entry, &st); + + // However: + // "The [filetype] constants used by stat(2) may have different numeric values from the corresponding [libarchive constants]." + // - `archive_entry_stat(3)` + if (fileInfo.isSymLink()) { + archive_entry_set_filetype(entry, AE_IFLNK); + + // We also need to manually copy some attributes from the link itself, as `stat` above operates on its target + auto target = fileInfo.symLinkTarget().toUtf8(); + archive_entry_set_symlink(entry, target.constData()); + archive_entry_set_size(entry, 0); + archive_entry_set_perm(entry, fileInfo.permissions()); + } else if (fileInfo.isFile()) { + archive_entry_set_filetype(entry, AE_IFREG); + } else { + qCritical() << "Unsupported file type:" << fileInfo.filePath(); + return false; + } + + if (archive_write_header(m_archive, entry) != ARCHIVE_OK) { + qCritical() << "Failed to write header for: " << fileDest << "-" << archive_error_string(m_archive); + return false; + } + + if (fileInfo.isFile() && !fileInfo.isSymLink()) { + QFile file(fileInfo.absoluteFilePath()); + if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "Failed to open file: " << fileInfo.filePath(); + return false; + } + + constexpr qint64 chunkSize = 8192; + QByteArray buffer; + buffer.resize(chunkSize); + + while (!file.atEnd()) { + auto bytesRead = file.read(buffer.data(), chunkSize); + if (bytesRead < 0) { + qCritical() << "Read error in file: " << fileInfo.filePath(); + return false; + } + + if (archive_write_data(m_archive, buffer.constData(), bytesRead) < 0) { + qCritical() << "Write error in archive for: " << fileDest; + return false; + } + } + } + + return true; +} + +bool ArchiveWriter::addFile(const QString& fileDest, const QByteArray& data) +{ + std::unique_ptr entry_ptr(archive_entry_new(), archive_entry_free); + auto entry = entry_ptr.get(); + if (!entry) { + qCritical() << "Failed to create archive entry"; + return false; + } + + auto fileDestUtf8 = fileDest.toUtf8(); + archive_entry_set_pathname(entry, fileDestUtf8.constData()); + archive_entry_set_perm(entry, 0644); + + archive_entry_set_filetype(entry, AE_IFREG); + archive_entry_set_size(entry, data.size()); + + if (archive_write_header(m_archive, entry) != ARCHIVE_OK) { + qCritical() << "Failed to write header for: " << fileDest << "-" << archive_error_string(m_archive); + return false; + } + + if (archive_write_data(m_archive, data.constData(), data.size()) < 0) { + qCritical() << "Write error in archive for: " << fileDest << "-" << archive_error_string(m_archive); + return false; + } + return true; +} + +bool ArchiveWriter::addFile(ArchiveReader::File* f) +{ + return f->writeFile(m_archive, "", true); +} + +std::unique_ptr ArchiveWriter::createDiskWriter() +{ + int flags = ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS | + ARCHIVE_EXTRACT_SECURE_NODOTDOT | ARCHIVE_EXTRACT_SECURE_SYMLINKS; + + std::unique_ptr extPtr(archive_write_disk_new(), [](archive* a) { + if (a) { + archive_write_close(a); + archive_write_free(a); + } + }); + + archive* ext = extPtr.get(); + archive_write_disk_set_options(ext, flags); + archive_write_disk_set_standard_lookup(ext); + + return extPtr; +} +} // namespace MMCZip \ No newline at end of file diff --git a/launcher/archive/ArchiveWriter.h b/launcher/archive/ArchiveWriter.h new file mode 100644 index 000000000..807bb297c --- /dev/null +++ b/launcher/archive/ArchiveWriter.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2025 Trial97 + * + * 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 . + */ +#pragma once + +#include +#include +#include "archive/ArchiveReader.h" + +struct archive; +namespace MMCZip { + +class ArchiveWriter { + public: + ArchiveWriter(const QString& archiveName); + virtual ~ArchiveWriter(); + + bool open(); + bool close(); + + bool addFile(const QString& fileName, const QString& fileDest); + bool addFile(const QString& fileDest, const QByteArray& data); + bool addFile(ArchiveReader::File* f); + + static std::unique_ptr createDiskWriter(); + + private: + struct archive* m_archive = nullptr; + QString m_filename; + QString m_format = "zip"; +}; +} // namespace MMCZip \ No newline at end of file diff --git a/launcher/archive/ExportToZipTask.cpp b/launcher/archive/ExportToZipTask.cpp new file mode 100644 index 000000000..4886dc963 --- /dev/null +++ b/launcher/archive/ExportToZipTask.cpp @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2025 Trial97 + * + * 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 . + */ +#include "ExportToZipTask.h" + +#include + +#include "FileSystem.h" + +namespace MMCZip { +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::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.open()) { + return ZipResult(tr("Could not create file")); + } + + for (auto fileName : m_extra_files.keys()) { + if (m_build_zip_future.isCanceled()) + return ZipResult(); + if (!m_output.addFile(fileName, m_extra_files[fileName])) { + return ZipResult(tr("Could not add:") + 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("Compressing: " + 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) && !m_output.addFile(absolute, m_destination_prefix + relative)) { + return ZipResult(tr("Could not read and compress %1").arg(relative)); + } + } + + if (!m_output.close()) { + return ZipResult(tr("A zip error occurred")); + } + return ZipResult(); +} + +void ExportToZipTask::finish() +{ + if (m_build_zip_future.isCanceled()) { + FS::deletePath(m_output_path); + emitAborted(); + } else if (auto result = m_build_zip_future.result(); result.has_value()) { + FS::deletePath(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 \ No newline at end of file diff --git a/launcher/archive/ExportToZipTask.h b/launcher/archive/ExportToZipTask.h new file mode 100644 index 000000000..2e0a8273e --- /dev/null +++ b/launcher/archive/ExportToZipTask.h @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2025 Trial97 + * + * 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 . + */ +#pragma once + +#include +#include +#include +#include + +#include "archive/ArchiveWriter.h" +#include "tasks/Task.h" + +namespace MMCZip { +class ExportToZipTask : public Task { + Q_OBJECT + 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); } + + using ZipResult = std::optional; + + protected: + virtual void executeTask() override; + bool abort() override; + + ZipResult exportZip(); + void finish(); + + private: + QString m_output_path; + ArchiveWriter m_output; + QDir m_dir; + QFileInfoList m_files; + QString m_destination_prefix; + bool m_follow_symlinks; + QStringList m_exclude_files; + QHash m_extra_files; + + QFuture m_build_zip_future; + QFutureWatcher m_build_zip_watcher; +}; +} // namespace MMCZip \ No newline at end of file diff --git a/launcher/archive/ExtractZipTask.cpp b/launcher/archive/ExtractZipTask.cpp new file mode 100644 index 000000000..7dd002017 --- /dev/null +++ b/launcher/archive/ExtractZipTask.cpp @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2025 Trial97 + * + * 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 . + */ +#include "ExtractZipTask.h" +#include +#include "FileSystem.h" +#include "archive/ArchiveReader.h" +#include "archive/ArchiveWriter.h" + +namespace MMCZip { + +void ExtractZipTask::executeTask() +{ + m_zip_future = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return extractZip(); }); + connect(&m_zip_watcher, &QFutureWatcher::finished, this, &ExtractZipTask::finish); + m_zip_watcher.setFuture(m_zip_future); +} + +auto ExtractZipTask::extractZip() -> ZipResult +{ + auto target = m_output_dir.absolutePath(); + auto target_top_dir = QUrl::fromLocalFile(target); + + QStringList extracted; + + qDebug() << "Extracting subdir" << m_subdirectory << "from" << m_input.getZipName() << "to" << target; + if (!m_input.collectFiles()) { + return ZipResult(tr("Failed to enumerate files in archive")); + } + if (m_input.getFiles().isEmpty()) { + logWarning(tr("Extracting empty archives seems odd...")); + return ZipResult(); + } + + auto extPtr = ArchiveWriter::createDiskWriter(); + auto ext = extPtr.get(); + + setStatus("Extracting files..."); + setProgress(0, m_input.getFiles().count()); + ZipResult result; + if (!m_input.parse([this, &result, &target, &target_top_dir, ext, &extracted](ArchiveReader::File* f) { + if (m_zip_future.isCanceled()) + return false; + setProgress(m_progress + 1, m_progressTotal); + QString file_name = f->filename(); + if (!file_name.startsWith(m_subdirectory)) { + f->skip(); + return true; + } + + auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(m_subdirectory.size())); + auto original_name = relative_file_name; + setStatus("Unpacking: " + relative_file_name); + + // Fix subdirs/files ending with a / getting transformed into absolute paths + if (relative_file_name.startsWith('/')) + relative_file_name = relative_file_name.mid(1); + + // Fix weird "folders with a single file get squashed" thing + QString sub_path; + if (relative_file_name.contains('/') && !relative_file_name.endsWith('/')) { + sub_path = relative_file_name.section('/', 0, -2) + '/'; + FS::ensureFolderPathExists(FS::PathCombine(target, sub_path)); + + relative_file_name = relative_file_name.split('/').last(); + } + + QString target_file_path; + if (relative_file_name.isEmpty()) { + target_file_path = target + '/'; + } else { + target_file_path = FS::PathCombine(target_top_dir.toLocalFile(), sub_path, relative_file_name); + if (relative_file_name.endsWith('/') && !target_file_path.endsWith('/')) + target_file_path += '/'; + } + + if (!target_top_dir.isParentOf(QUrl::fromLocalFile(target_file_path))) { + result = ZipResult(tr("Extracting %1 was cancelled, because it was effectively outside of the target path %2") + .arg(relative_file_name, target)); + return false; + } + + if (!f->writeFile(ext, target_file_path)) { + result = ZipResult(tr("Failed to extract file %1 to %2").arg(original_name, target_file_path)); + return false; + } + extracted.append(target_file_path); + + qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path; + return true; + })) { + FS::removeFiles(extracted); + return result.has_value() || m_zip_future.isCanceled() ? result + : ZipResult(tr("Failed to parse file %1").arg(m_input.getZipName())); + } + return ZipResult(); +} + +void ExtractZipTask::finish() +{ + if (m_zip_future.isCanceled()) { + emitAborted(); + } else if (auto result = m_zip_future.result(); result.has_value()) { + emitFailed(result.value()); + } else { + emitSucceeded(); + } +} + +bool ExtractZipTask::abort() +{ + if (m_zip_future.isRunning()) { + m_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 \ No newline at end of file diff --git a/launcher/archive/ExtractZipTask.h b/launcher/archive/ExtractZipTask.h new file mode 100644 index 000000000..a9ad0a548 --- /dev/null +++ b/launcher/archive/ExtractZipTask.h @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2025 Trial97 + * + * 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 . + */ +#pragma once + +#include +#include +#include +#include "archive/ArchiveReader.h" +#include "tasks/Task.h" + +namespace MMCZip { + +class ExtractZipTask : public Task { + Q_OBJECT + public: + ExtractZipTask(QString input, QDir outputDir, QString subdirectory = "") + : m_input(input), m_output_dir(outputDir), m_subdirectory(subdirectory) + {} + virtual ~ExtractZipTask() = default; + + using ZipResult = std::optional; + + protected: + virtual void executeTask() override; + bool abort() override; + + ZipResult extractZip(); + void finish(); + + private: + ArchiveReader m_input; + QDir m_output_dir; + QString m_subdirectory; + + QFuture m_zip_future; + QFutureWatcher m_zip_watcher; +}; +} // namespace MMCZip \ No newline at end of file diff --git a/launcher/java/download/ArchiveDownloadTask.cpp b/launcher/java/download/ArchiveDownloadTask.cpp index bb31ca1e2..92357930b 100644 --- a/launcher/java/download/ArchiveDownloadTask.cpp +++ b/launcher/java/download/ArchiveDownloadTask.cpp @@ -16,12 +16,11 @@ * along with this program. If not, see . */ #include "java/download/ArchiveDownloadTask.h" -#include #include -#include "MMCZip.h" #include "Application.h" -#include "Untar.h" +#include "archive/ArchiveReader.h" +#include "archive/ExtractZipTask.h" #include "net/ChecksumValidator.h" #include "net/NetJob.h" #include "tasks/Task.h" @@ -67,68 +66,45 @@ void ArchiveDownloadTask::executeTask() void ArchiveDownloadTask::extractJava(QString input) { setStatus(tr("Extracting Java")); - if (input.endsWith("tar")) { - setStatus(tr("Extracting Java (Progress is not reported for tar archives)")); - QFile in(input); - if (!in.open(QFile::ReadOnly)) { - emitFailed(tr("Unable to open supplied tar file.")); - return; - } - if (!Tar::extract(&in, QDir(m_final_path).absolutePath())) { - emitFailed(tr("Unable to extract supplied tar file.")); - return; - } - emitSucceeded(); - return; - } else if (input.endsWith("tar.gz") || input.endsWith("taz") || input.endsWith("tgz")) { - setStatus(tr("Extracting Java (Progress is not reported for tar archives)")); - if (!GZTar::extract(input, QDir(m_final_path).absolutePath())) { - emitFailed(tr("Unable to extract supplied tar file.")); - return; - } - emitSucceeded(); - return; - } else if (input.endsWith("zip")) { - auto zip = std::make_shared(input); - if (!zip->open(QuaZip::mdUnzip)) { - emitFailed(tr("Unable to open supplied zip file.")); - return; - } - auto files = zip->getFileNameList(); - if (files.isEmpty()) { - emitFailed(tr("No files were found in the supplied zip file.")); - return; - } - m_task = makeShared(zip, m_final_path, files[0]); - auto progressStep = std::make_shared(); - connect(m_task.get(), &Task::finished, this, [this, progressStep] { - progressStep->state = TaskStepState::Succeeded; - stepProgress(*progressStep); - }); - - connect(m_task.get(), &Task::succeeded, this, &ArchiveDownloadTask::emitSucceeded); - connect(m_task.get(), &Task::aborted, this, &ArchiveDownloadTask::emitAborted); - connect(m_task.get(), &Task::failed, this, [this, progressStep](QString reason) { - progressStep->state = TaskStepState::Failed; - stepProgress(*progressStep); - emitFailed(reason); - }); - connect(m_task.get(), &Task::stepProgress, this, &ArchiveDownloadTask::propagateStepProgress); - - connect(m_task.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) { - progressStep->update(current, total); - stepProgress(*progressStep); - }); - connect(m_task.get(), &Task::status, this, [this, progressStep](QString status) { - progressStep->status = status; - stepProgress(*progressStep); - }); - m_task->start(); + MMCZip::ArchiveReader zip(input); + if (!zip.collectFiles()) { + emitFailed(tr("Unable to open supplied zip file.")); return; } + auto files = zip.getFiles(); + if (files.isEmpty()) { + emitFailed(tr("No files were found in the supplied zip file.")); + return; + } + auto firstFolderParts = files[0].split('/', Qt::SkipEmptyParts); + m_task = makeShared(input, m_final_path, firstFolderParts.value(0)); - emitFailed(tr("Could not determine archive type!")); + auto progressStep = std::make_shared(); + connect(m_task.get(), &Task::finished, this, [this, progressStep] { + progressStep->state = TaskStepState::Succeeded; + stepProgress(*progressStep); + }); + + connect(m_task.get(), &Task::succeeded, this, &ArchiveDownloadTask::emitSucceeded); + connect(m_task.get(), &Task::aborted, this, &ArchiveDownloadTask::emitAborted); + connect(m_task.get(), &Task::failed, this, [this, progressStep](QString reason) { + progressStep->state = TaskStepState::Failed; + stepProgress(*progressStep); + emitFailed(reason); + }); + connect(m_task.get(), &Task::stepProgress, this, &ArchiveDownloadTask::propagateStepProgress); + + connect(m_task.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) { + progressStep->update(current, total); + stepProgress(*progressStep); + }); + connect(m_task.get(), &Task::status, this, [this, progressStep](QString status) { + progressStep->status = status; + stepProgress(*progressStep); + }); + m_task->start(); + return; } bool ArchiveDownloadTask::abort() diff --git a/launcher/main.cpp b/launcher/main.cpp index 2bce655d2..46368e72e 100644 --- a/launcher/main.cpp +++ b/launcher/main.cpp @@ -37,6 +37,13 @@ int main(int argc, char* argv[]) { + // try to set the utf-8 locale for the libarchive + for (auto name : { ".UTF-8", "en_US.UTF-8", "C.UTF-8" }) { + if (std::setlocale(LC_CTYPE, name)) { + break; + } + } + // initialize Qt Application app(argc, argv); diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp index 8ae097bad..bdbe721e3 100644 --- a/launcher/minecraft/World.cpp +++ b/launcher/minecraft/World.cpp @@ -43,9 +43,6 @@ #include #include #include -#include -#include -#include #include #include #include @@ -57,6 +54,7 @@ #include "FileSystem.h" #include "PSaveFile.h" +#include "archive/ArchiveReader.h" using std::nullopt; using std::optional; @@ -244,36 +242,25 @@ void World::readFromFS(const QFileInfo& file) void World::readFromZip(const QFileInfo& file) { - QuaZip zip(file.absoluteFilePath()); - m_isValid = zip.open(QuaZip::mdUnzip); - if (!m_isValid) { - return; - } - auto location = MMCZip::findFolderOfFileInZip(&zip, "level.dat"); - m_isValid = !location.isEmpty(); - if (!m_isValid) { - return; - } - m_containerOffsetPath = location; - QuaZipFile zippedFile(&zip); - // read the install profile - m_isValid = zip.setCurrentFile(location + "level.dat"); - if (!m_isValid) { - return; - } - m_isValid = zippedFile.open(QIODevice::ReadOnly); - QuaZipFileInfo64 levelDatInfo; - zippedFile.getFileInfo(&levelDatInfo); - auto modTime = levelDatInfo.getNTFSmTime(); - if (!modTime.isValid()) { - modTime = levelDatInfo.dateTime; - } - m_levelDatTime = modTime; - if (!m_isValid) { - return; - } - loadFromLevelDat(zippedFile.readAll()); - zippedFile.close(); + MMCZip::ArchiveReader r(file.absoluteFilePath()); + + m_isValid = false; + r.parse([this](MMCZip::ArchiveReader::File* file, bool& stop) { + const QString levelDat = "level.dat"; + auto filePath = file->filename(); + QFileInfo fi(filePath); + if (fi.fileName().compare(levelDat, Qt::CaseInsensitive) == 0) { + m_containerOffsetPath = filePath.chopped(levelDat.length()); + if (!m_containerOffsetPath.isEmpty()) { + return false; + } + m_levelDatTime = file->dateTime(); + loadFromLevelDat(file->readAll()); + m_isValid = true; + stop = true; + } + return true; + }); } bool World::install(const QString& to, const QString& name) @@ -284,10 +271,7 @@ bool World::install(const QString& to, const QString& name) } bool ok = false; if (m_containerFile.isFile()) { - QuaZip zip(m_containerFile.absoluteFilePath()); - if (!zip.open(QuaZip::mdUnzip)) { - return false; - } + MMCZip::ArchiveReader zip(m_containerFile.absoluteFilePath()); ok = !MMCZip::extractSubDir(&zip, m_containerOffsetPath, finalPath); } else if (m_containerFile.isDir()) { QString from = m_containerFile.filePath(); @@ -350,7 +334,7 @@ optional read_string(nbt::value& parent, const char* name) return nullopt; } auto& tag_str = namedValue.as(); - return QString::fromStdString(tag_str.get()); + return QString::fromUtf8(tag_str.get()); } catch ([[maybe_unused]] const std::out_of_range& e) { // fallback for old world formats qWarning() << "String NBT tag" << name << "could not be found."; diff --git a/launcher/minecraft/launch/ExtractNatives.cpp b/launcher/minecraft/launch/ExtractNatives.cpp index afe091758..3174e0e2a 100644 --- a/launcher/minecraft/launch/ExtractNatives.cpp +++ b/launcher/minecraft/launch/ExtractNatives.cpp @@ -17,11 +17,10 @@ #include #include -#include -#include #include #include "FileSystem.h" -#include "MMCZip.h" +#include "archive/ArchiveReader.h" +#include "archive/ArchiveWriter.h" #ifdef major #undef major @@ -41,30 +40,21 @@ static QString replaceSuffix(QString target, const QString& suffix, const QStrin static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibHack) { - QuaZip zip(source); - if (!zip.open(QuaZip::mdUnzip)) { - return false; - } + MMCZip::ArchiveReader zip(source); QDir directory(targetFolder); - if (!zip.goToFirstFile()) { - return false; - } - do { - QString name = zip.getCurrentFileName(); + + auto extPtr = MMCZip::ArchiveWriter::createDiskWriter(); + auto ext = extPtr.get(); + + return zip.parse([applyJnilibHack, directory, ext](MMCZip::ArchiveReader::File* f) { + QString name = f->filename(); auto lowercase = name.toLower(); if (applyJnilibHack) { name = replaceSuffix(name, ".jnilib", ".dylib"); } QString absFilePath = directory.absoluteFilePath(name); - if (!JlCompress::extractFile(&zip, "", absFilePath)) { - return false; - } - } while (zip.goToNextFile()); - zip.close(); - if (zip.getZipError() != 0) { - return false; - } - return true; + return f->writeFile(ext, absFilePath); + }); } void ExtractNatives::executeTask() diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp index 388d55628..d824c904b 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.cpp +++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp @@ -50,7 +50,7 @@ LauncherPartLaunch::LauncherPartLaunch(LaunchTask* parent) : LaunchStep(parent) - , m_process(parent->instance()->getJavaVersion().defaultsToUtf8() ? QTextCodec::codecForName("UTF-8") : QTextCodec::codecForLocale()) + , m_process(parent->instance()->getJavaVersion().defaultsToUtf8() ? QStringConverter::Utf8 : QStringConverter::System) { if (parent->instance()->settings()->get("CloseAfterLaunch").toBool()) { static const QRegularExpression s_settingUser(".*Setting user.+", QRegularExpression::CaseInsensitiveOption); diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index eceb8c256..c548f5350 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -44,8 +44,6 @@ #include #include -#include - #include "ModDetails.h" #include "Resource.h" diff --git a/launcher/minecraft/mod/ModDetails.h b/launcher/minecraft/mod/ModDetails.h index 9195c0368..9b81f561f 100644 --- a/launcher/minecraft/mod/ModDetails.h +++ b/launcher/minecraft/mod/ModDetails.h @@ -35,14 +35,10 @@ #pragma once -#include - #include #include #include -#include "minecraft/mod/MetadataHandler.h" - struct ModLicense { QString name = {}; QString id = {}; diff --git a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp index 73e676dbd..9b4bb4a50 100644 --- a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp @@ -23,12 +23,9 @@ #include "FileSystem.h" #include "Json.h" +#include "archive/ArchiveReader.h" #include "minecraft/mod/ResourcePack.h" -#include -#include -#include - #include namespace DataPackUtils { @@ -106,67 +103,62 @@ bool processZIP(DataPack* pack, ProcessingLevel level) { Q_ASSERT(pack->type() == ResourceType::ZIPFILE); - QuaZip zip(pack->fileinfo().filePath()); - if (!zip.open(QuaZip::mdUnzip)) + MMCZip::ArchiveReader zip(pack->fileinfo().filePath()); + + bool metaParsed = false; + bool iconParsed = false; + bool mcmeta_result = false; + bool pack_png_result = false; + if (!zip.parse( + [&metaParsed, &iconParsed, &mcmeta_result, &pack_png_result, pack, level](MMCZip::ArchiveReader::File* f, bool& breakControl) { + bool skip = true; + if (!metaParsed && f->filename() == "pack.mcmeta") { + metaParsed = true; + skip = false; + auto data = f->readAll(); + + mcmeta_result = DataPackUtils::processMCMeta(pack, std::move(data)); + + if (!mcmeta_result) { + breakControl = true; + return true; // mcmeta invalid + } + } + if (!iconParsed && level != ProcessingLevel::BasicInfoOnly && f->filename() == "pack.png") { + iconParsed = true; + skip = false; + auto data = f->readAll(); + + pack_png_result = DataPackUtils::processPackPNG(pack, std::move(data)); + if (!pack_png_result) { + breakControl = true; + return true; // pack.png invalid + } + } + if (skip) { + f->skip(); + } + if (metaParsed && (level == ProcessingLevel::BasicInfoOnly || iconParsed)) { + breakControl = true; + } + + return true; + })) { return false; // can't open zip file - - QuaZipFile file(&zip); - - auto mcmeta_invalid = [&pack]() { + } + if (!mcmeta_result) { qWarning() << "Data pack at" << pack->fileinfo().filePath() << "does not have a valid pack.mcmeta"; return false; // the mcmeta is not optional - }; - - if (zip.setCurrentFile("pack.mcmeta")) { - if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open file in zip."; - zip.close(); - return mcmeta_invalid(); - } - - auto data = file.readAll(); - - bool mcmeta_result = DataPackUtils::processMCMeta(pack, std::move(data)); - - file.close(); - if (!mcmeta_result) { - return mcmeta_invalid(); // mcmeta invalid - } - } else { - return mcmeta_invalid(); // could not set pack.mcmeta as current file. } if (level == ProcessingLevel::BasicInfoOnly) { - zip.close(); return true; // only need basic info already checked } - auto png_invalid = [&pack]() { + if (!pack_png_result) { qWarning() << "Data pack at" << pack->fileinfo().filePath() << "does not have a valid pack.png"; return true; // the png is optional - }; - - if (zip.setCurrentFile("pack.png")) { - if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open file in zip."; - zip.close(); - return png_invalid(); - } - - auto data = file.readAll(); - - bool pack_png_result = DataPackUtils::processPackPNG(pack, std::move(data)); - - file.close(); - zip.close(); - if (!pack_png_result) { - return png_invalid(); // pack.png invalid - } - } else { - zip.close(); - return png_invalid(); // could not set pack.mcmeta as current file. } - zip.close(); return true; } @@ -311,28 +303,17 @@ bool processPackPNG(const DataPack* pack) return false; // not processed correctly; https://github.com/PrismLauncher/PrismLauncher/issues/1740 } case ResourceType::ZIPFILE: { - QuaZip zip(pack->fileinfo().filePath()); - if (!zip.open(QuaZip::mdUnzip)) - return false; // can't open zip file + MMCZip::ArchiveReader zip(pack->fileinfo().filePath()); + auto f = zip.goToFile("pack.png"); + if (!f) { + return png_invalid(); + } + auto data = f->readAll(); - QuaZipFile file(&zip); - if (zip.setCurrentFile("pack.png")) { - if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open file in zip."; - zip.close(); - return png_invalid(); - } + bool pack_png_result = DataPackUtils::processPackPNG(pack, std::move(data)); - auto data = file.readAll(); - - bool pack_png_result = DataPackUtils::processPackPNG(pack, std::move(data)); - - file.close(); - if (!pack_png_result) { - return png_invalid(); // pack.png invalid - } - } else { - return png_invalid(); // could not set pack.mcmeta as current file. + if (!pack_png_result) { + return png_invalid(); // pack.png invalid } return false; // not processed correctly; https://github.com/PrismLauncher/PrismLauncher/issues/1740 } diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index 38280f5af..59d3876b3 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -1,8 +1,6 @@ #include "LocalModParseTask.h" #include -#include -#include #include #include #include @@ -13,6 +11,7 @@ #include "FileSystem.h" #include "Json.h" +#include "archive/ArchiveReader.h" #include "minecraft/mod/ModDetails.h" #include "settings/INIFile.h" @@ -470,32 +469,33 @@ bool processZIP(Mod& mod, [[maybe_unused]] ProcessingLevel level) { ModDetails details; - QuaZip zip(mod.fileinfo().filePath()); - if (!zip.open(QuaZip::mdUnzip)) - return false; + MMCZip::ArchiveReader zip(mod.fileinfo().filePath()); - QuaZipFile file(&zip); + bool baseForgePopulated = false; + bool isNilMod = false; + bool isValid = false; + QString manifestVersion = {}; + QByteArray nilData = {}; + QString nilFilePath = {}; - if (zip.setCurrentFile("META-INF/mods.toml") || zip.setCurrentFile("META-INF/neoforge.mods.toml")) { - if (!file.open(QIODevice::ReadOnly)) { - zip.close(); - return false; - } + if (!zip.parse([&details, &baseForgePopulated, &manifestVersion, &isValid, &nilData, &isNilMod, &nilFilePath]( + MMCZip::ArchiveReader::File* file, bool& stop) { + auto filePath = file->filename(); - details = ReadMCModTOML(file.readAll()); - file.close(); - - // to replace ${file.jarVersion} with the actual version, as needed - if (details.version == "${file.jarVersion}") { - if (zip.setCurrentFile("META-INF/MANIFEST.MF")) { - if (!file.open(QIODevice::ReadOnly)) { - zip.close(); - return false; + if (filePath == "META-INF/mods.toml" || filePath == "META-INF/neoforge.mods.toml") { + details = ReadMCModTOML(file->readAll()); + isValid = true; + if (details.version == "${file.jarVersion}" && !manifestVersion.isEmpty()) { + details.version = manifestVersion; } - + stop = details.version != "${file.jarVersion}"; + baseForgePopulated = true; + return true; + } + if (filePath == "META-INF/MANIFEST.MF") { // quick and dirty line-by-line parser - auto manifestLines = QString(file.readAll()).split(s_newlineRegex); - QString manifestVersion = ""; + auto manifestLines = QString(file->readAll()).split(s_newlineRegex); + manifestVersion = ""; for (auto& line : manifestLines) { if (line.startsWith("Implementation-Version: ", Qt::CaseInsensitive)) { manifestVersion = line.remove("Implementation-Version: ", Qt::CaseInsensitive); @@ -508,94 +508,64 @@ bool processZIP(Mod& mod, [[maybe_unused]] ProcessingLevel level) if (manifestVersion.contains("task ':jar' property 'archiveVersion'") || manifestVersion == "") { manifestVersion = "NONE"; } - - details.version = manifestVersion; - - file.close(); + if (baseForgePopulated) { + details.version = manifestVersion; + stop = true; + } + return true; + } + if (filePath == "mcmod.info") { + details = ReadMCModInfo(file->readAll()); + isValid = true; + stop = true; + return true; + } + if (filePath == "quilt.mod.json") { + details = ReadQuiltModInfo(file->readAll()); + isValid = true; + stop = true; + return true; + } + if (filePath == "fabric.mod.json") { + details = ReadFabricModInfo(file->readAll()); + isValid = true; + stop = true; + return true; + } + if (filePath == "forgeversion.properties") { + details = ReadForgeInfo(file->readAll()); + isValid = true; + stop = true; + return true; + } + if (filePath == "META-INF/nil/mappings.json") { + // nilloader uses the filename of the metadata file for the modid, so we can't know the exact filename + // thankfully, there is a good file to use as a canary so we don't look for nil meta all the time + isNilMod = true; + stop = !nilFilePath.isEmpty(); + file->skip(); + return true; } - } - - zip.close(); - mod.setDetails(details); - - return true; - } else if (zip.setCurrentFile("mcmod.info")) { - if (!file.open(QIODevice::ReadOnly)) { - zip.close(); - return false; - } - - details = ReadMCModInfo(file.readAll()); - file.close(); - zip.close(); - - mod.setDetails(details); - return true; - } else if (zip.setCurrentFile("quilt.mod.json")) { - if (!file.open(QIODevice::ReadOnly)) { - zip.close(); - return false; - } - - details = ReadQuiltModInfo(file.readAll()); - file.close(); - zip.close(); - - mod.setDetails(details); - return true; - } else if (zip.setCurrentFile("fabric.mod.json")) { - if (!file.open(QIODevice::ReadOnly)) { - zip.close(); - return false; - } - - details = ReadFabricModInfo(file.readAll()); - file.close(); - zip.close(); - - mod.setDetails(details); - return true; - } else if (zip.setCurrentFile("forgeversion.properties")) { - if (!file.open(QIODevice::ReadOnly)) { - zip.close(); - return false; - } - - details = ReadForgeInfo(file.readAll()); - file.close(); - zip.close(); - - mod.setDetails(details); - return true; - } else if (zip.setCurrentFile("META-INF/nil/mappings.json")) { - // nilloader uses the filename of the metadata file for the modid, so we can't know the exact filename - // thankfully, there is a good file to use as a canary so we don't look for nil meta all the time - - QString foundNilMeta; - for (auto& fname : zip.getFileNameList()) { // nilmods can shade nilloader to be able to run as a standalone agent - which includes nilloader's own meta file - if (fname.endsWith(".nilmod.css") && fname != "nilloader.nilmod.css") { - foundNilMeta = fname; - break; + if (filePath.endsWith(".nilmod.css") && filePath != "nilloader.nilmod.css") { + nilData = file->readAll(); + nilFilePath = filePath; + stop = isNilMod; + return true; } - } - - if (zip.setCurrentFile(foundNilMeta)) { - if (!file.open(QIODevice::ReadOnly)) { - zip.close(); - return false; - } - - details = ReadNilModInfo(file.readAll(), foundNilMeta); - file.close(); - zip.close(); - - mod.setDetails(details); + file->skip(); return true; - } + })) { + return false; + } + if (isNilMod) { + details = ReadNilModInfo(nilData, nilFilePath); + isValid = true; + } + if (isValid) { + mod.setDetails(details); + return true; } - - zip.close(); return false; // no valid mod found in archive } @@ -624,25 +594,14 @@ bool processLitemod(Mod& mod, [[maybe_unused]] ProcessingLevel level) { ModDetails details; - QuaZip zip(mod.fileinfo().filePath()); - if (!zip.open(QuaZip::mdUnzip)) - return false; + MMCZip::ArchiveReader zip(mod.fileinfo().filePath()); - QuaZipFile file(&zip); - - if (zip.setCurrentFile("litemod.json")) { - if (!file.open(QIODevice::ReadOnly)) { - zip.close(); - return false; - } - - details = ReadLiteModInfo(file.readAll()); - file.close(); + if (auto file = zip.goToFile("litemod.json"); file) { + details = ReadLiteModInfo(file->readAll()); mod.setDetails(details); return true; } - zip.close(); return false; // no valid litemod.json found in archive } @@ -700,24 +659,13 @@ bool loadIconFile(const Mod& mod, QPixmap* pixmap) return png_invalid("file '" + icon_info.filePath() + "' does not exists or is not a file"); } case ResourceType::ZIPFILE: { - QuaZip zip(mod.fileinfo().filePath()); - if (!zip.open(QuaZip::mdUnzip)) - return png_invalid("failed to open '" + mod.fileinfo().filePath() + "' as a zip archive"); - - QuaZipFile file(&zip); - - if (zip.setCurrentFile(mod.iconPath())) { - if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open file in zip."; - zip.close(); - return png_invalid("Failed to open '" + mod.iconPath() + "' in zip archive"); - } - - auto data = file.readAll(); + MMCZip::ArchiveReader zip(mod.fileinfo().filePath()); + auto file = zip.goToFile(mod.iconPath()); + if (file) { + auto data = file->readAll(); bool icon_result = ModUtils::processIconPNG(mod, std::move(data), pixmap); - file.close(); if (!icon_result) { return png_invalid("invalid png image"); // icon png invalid } diff --git a/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.cpp index a6ecc5353..3443966d5 100644 --- a/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.cpp @@ -22,10 +22,7 @@ #include "LocalShaderPackParseTask.h" #include "FileSystem.h" - -#include -#include -#include +#include "archive/ArchiveReader.h" namespace ShaderPackUtils { @@ -63,25 +60,19 @@ bool processZIP(ShaderPack& pack, ProcessingLevel level) { Q_ASSERT(pack.type() == ResourceType::ZIPFILE); - QuaZip zip(pack.fileinfo().filePath()); - if (!zip.open(QuaZip::mdUnzip)) + MMCZip::ArchiveReader zip(pack.fileinfo().filePath()); + if (!zip.collectFiles()) return false; // can't open zip file - QuaZipFile file(&zip); - - QuaZipDir zipDir(&zip); - if (!zipDir.exists("/shaders")) { + if (!zip.exists("/shaders")) { return false; // assets dir does not exists at zip root } pack.setPackFormat(ShaderPackFormat::VALID); if (level == ProcessingLevel::BasicInfoOnly) { - zip.close(); return true; // only need basic info already checked } - zip.close(); - return true; } diff --git a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp index 18020808a..106d7c323 100644 --- a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp @@ -20,9 +20,7 @@ #include "LocalTexturePackParseTask.h" #include "FileSystem.h" - -#include -#include +#include "archive/ArchiveReader.h" #include @@ -91,55 +89,26 @@ bool processZIP(TexturePack& pack, ProcessingLevel level) { Q_ASSERT(pack.type() == ResourceType::ZIPFILE); - QuaZip zip(pack.fileinfo().filePath()); - if (!zip.open(QuaZip::mdUnzip)) - return false; + MMCZip::ArchiveReader zip(pack.fileinfo().filePath()); + bool packProcessed = false; + bool iconProcessed = false; - QuaZipFile file(&zip); - - if (zip.setCurrentFile("pack.txt")) { - if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open file in zip."; - zip.close(); - return false; + return zip.parse([&packProcessed, &iconProcessed, &pack, level](MMCZip::ArchiveReader::File* file, bool& stop) { + if (!packProcessed && file->filename() == "pack.txt") { + packProcessed = true; + auto data = file->readAll(); + stop = packProcessed && (iconProcessed || level == ProcessingLevel::BasicInfoOnly); + return TexturePackUtils::processPackTXT(pack, std::move(data)); } - - auto data = file.readAll(); - - bool packTXT_result = TexturePackUtils::processPackTXT(pack, std::move(data)); - - file.close(); - if (!packTXT_result) { - return false; + if (!iconProcessed && file->filename() == "pack.png") { + iconProcessed = true; + auto data = file->readAll(); + stop = packProcessed && iconProcessed; + return TexturePackUtils::processPackPNG(pack, std::move(data)); } - } - - if (level == ProcessingLevel::BasicInfoOnly) { - zip.close(); + file->skip(); return true; - } - - if (zip.setCurrentFile("pack.png")) { - if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open file in zip."; - zip.close(); - return false; - } - - auto data = file.readAll(); - - bool packPNG_result = TexturePackUtils::processPackPNG(pack, std::move(data)); - - file.close(); - zip.close(); - if (!packPNG_result) { - return false; - } - } - - zip.close(); - - return true; + }); } bool processPackTXT(TexturePack& pack, QByteArray&& raw_data) @@ -189,32 +158,19 @@ bool processPackPNG(const TexturePack& pack) return false; } case ResourceType::ZIPFILE: { - QuaZip zip(pack.fileinfo().filePath()); - if (!zip.open(QuaZip::mdUnzip)) - return false; // can't open zip file + MMCZip::ArchiveReader zip(pack.fileinfo().filePath()); - QuaZipFile file(&zip); - if (zip.setCurrentFile("pack.png")) { - if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open file in zip."; - zip.close(); - return png_invalid(); - } - - auto data = file.readAll(); + auto file = zip.goToFile("pack.png"); + if (file) { + auto data = file->readAll(); bool pack_png_result = TexturePackUtils::processPackPNG(pack, std::move(data)); - file.close(); if (!pack_png_result) { - zip.close(); return png_invalid(); // pack.png invalid } - } else { - zip.close(); - return png_invalid(); // could not set pack.mcmeta as current file. } - return false; + return png_invalid(); // could not set pack.mcmeta as current file. } default: qWarning() << "Invalid type for resource pack parse task!"; diff --git a/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp b/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp index d45f537fa..50f7bbfa1 100644 --- a/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp @@ -23,13 +23,11 @@ #include "LocalWorldSaveParseTask.h" #include "FileSystem.h" - -#include -#include -#include +#include "archive/ArchiveReader.h" #include #include +#include namespace WorldSaveUtils { @@ -105,22 +103,40 @@ bool processFolder(WorldSave& save, ProcessingLevel level) /// QString , /// bool /// ) -static std::tuple contains_level_dat(QuaZip& zip) +static std::tuple contains_level_dat(QString fileName) { + MMCZip::ArchiveReader zip(fileName); + if (!zip.collectFiles()) { + return std::make_tuple(false, "", false); + } bool saves = false; - QuaZipDir zipDir(&zip); - if (zipDir.exists("/saves")) { + if (zip.exists("/saves")) { saves = true; - zipDir.cd("/saves"); } - for (auto const& entry : zipDir.entryList()) { - zipDir.cd(entry); - if (zipDir.exists("level.dat")) { - return std::make_tuple(true, entry, saves); + for (auto file : zip.getFiles()) { + QString relativePath = file; + if (saves) { + if (!relativePath.startsWith("saves/", Qt::CaseInsensitive)) + continue; + relativePath = relativePath.mid(QString("saves/").length()); + } + if (!relativePath.endsWith("/level.dat", Qt::CaseInsensitive)) + continue; + + int slashIndex = relativePath.indexOf('/'); + if (slashIndex == -1) + continue; // malformed: no slash between saves/ and level.dat + + QString worldName = relativePath.left(slashIndex); + QString remaining = relativePath.mid(slashIndex + 1); + + // Check that there's nothing between worldName/ and level.dat + if (remaining == "level.dat") { + return std::make_tuple(true, worldName, saves); } - zipDir.cd(".."); } + return std::make_tuple(false, "", saves); } @@ -128,19 +144,14 @@ bool processZIP(WorldSave& save, ProcessingLevel level) { Q_ASSERT(save.type() == ResourceType::ZIPFILE); - QuaZip zip(save.fileinfo().filePath()); - if (!zip.open(QuaZip::mdUnzip)) - return false; // can't open zip file - - auto [found, save_dir_name, found_saves_dir] = contains_level_dat(zip); - - if (save_dir_name.endsWith("/")) { - save_dir_name.chop(1); - } + auto [found, save_dir_name, found_saves_dir] = contains_level_dat(save.fileinfo().filePath()); if (!found) { return false; } + if (save_dir_name.endsWith("/")) { + save_dir_name.chop(1); + } save.setSaveDirName(save_dir_name); @@ -151,14 +162,11 @@ bool processZIP(WorldSave& save, ProcessingLevel level) } if (level == ProcessingLevel::BasicInfoOnly) { - zip.close(); return true; // only need basic info already checked } // reserved for more intensive processing - zip.close(); - return true; } diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index ba3a25aa3..f107e4700 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -39,8 +39,6 @@ #include #include -#include - #include "FileSystem.h" #include "Json.h" #include "MMCZip.h" @@ -675,13 +673,6 @@ void PackInstallTask::extractConfigs() setStatus(tr("Extracting configs...")); QDir extractDir(m_stagingPath); - - QuaZip packZip(archivePath); - if (!packZip.open(QuaZip::mdUnzip)) { - emitFailed(tr("Failed to open pack configs %1!").arg(archivePath)); - return; - } - m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), QOverload::of(MMCZip::extractDir), archivePath, extractDir.absolutePath() + "/minecraft"); connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, [this]() { downloadMods(); }); diff --git a/launcher/modplatform/flame/FlamePackExportTask.cpp b/launcher/modplatform/flame/FlamePackExportTask.cpp index 98a61d0d1..d63eb709f 100644 --- a/launcher/modplatform/flame/FlamePackExportTask.cpp +++ b/launcher/modplatform/flame/FlamePackExportTask.cpp @@ -30,7 +30,6 @@ #include #include "Application.h" #include "Json.h" -#include "MMCZip.h" #include "minecraft/PackProfile.h" #include "minecraft/mod/ModFolderModel.h" #include "modplatform/ModIndex.h" @@ -38,6 +37,8 @@ #include "modplatform/helpers/HashUtils.h" #include "tasks/Task.h" +#include "archive/ExportToZipTask.h" + const QString FlamePackExportTask::TEMPLATE = "
  • {name}{authors}
  • \n"; const QStringList FlamePackExportTask::FILE_EXTENSIONS({ "jar", "zip" }); @@ -318,7 +319,7 @@ void FlamePackExportTask::buildZip() setStatus(tr("Adding files...")); setProgress(4, 5); - auto zipTask = makeShared(m_options.output, m_gameRoot, files, "overrides/", true, false); + auto zipTask = makeShared(m_options.output, m_gameRoot, files, "overrides/", true); zipTask->addExtraFile("manifest.json", generateIndex()); zipTask->addExtraFile("modlist.html", generateHTML()); diff --git a/launcher/modplatform/import_ftb/PackHelpers.cpp b/launcher/modplatform/import_ftb/PackHelpers.cpp index 78f7e4ba6..ec2229138 100644 --- a/launcher/modplatform/import_ftb/PackHelpers.cpp +++ b/launcher/modplatform/import_ftb/PackHelpers.cpp @@ -74,17 +74,49 @@ Modpack parseDirectory(QString path) modpack.mcVersion = Json::requireString(root, "mcVersion", "mcVersion"); modpack.jvmArgs = root["jvmArgs"].toVariant(); modpack.totalPlayTime = Json::requireInteger(root, "totalPlayTime", "totalPlayTime"); + + auto modLoader = Json::requireString(root, "modLoader", "modLoader"); + if (!modLoader.isEmpty()) { + const auto parts = modLoader.split('-', Qt::KeepEmptyParts); + if (parts.size() >= 2) { + const auto loader = parts.first().toLower(); + modpack.version = parts.at(1).trimmed(); + if (loader == "neoforge") { + modpack.loaderType = ModPlatform::NeoForge; + } else if (loader == "forge") { + modpack.loaderType = ModPlatform::Forge; + } else if (loader == "fabric") { + modpack.loaderType = ModPlatform::Fabric; + } else if (loader == "quilt") { + modpack.loaderType = ModPlatform::Quilt; + } + } + } else { + legacyInstanceParsing(path, &modpack.loaderType, &modpack.loaderVersion); + } } catch (const Exception& e) { qDebug() << "Couldn't load ftb instance json: " << e.cause(); return {}; } + + auto iconFile = QFileInfo(FS::PathCombine(path, "folder.jpg")); + if (iconFile.exists() && iconFile.isFile()) { + modpack.icon = QIcon(iconFile.absoluteFilePath()); + } else { // the logo is a file that the first bit denotes the image tipe followed by the actual image data + modpack.icon = loadFTBIcon(FS::PathCombine(path, ".ftbapp", "logo")); + } + return modpack; +} +void legacyInstanceParsing(QString path, std::optional* loaderType, QString* loaderVersion) +{ auto versionsFile = QFileInfo(FS::PathCombine(path, ".ftbapp", "version.json")); if (!versionsFile.exists() || !versionsFile.isFile()) { versionsFile = QFileInfo(FS::PathCombine(path, "version.json")); } if (!versionsFile.exists() || !versionsFile.isFile()) { - return {}; + qDebug() << "Couldn't find ftb version json"; + return; } try { auto doc = Json::requireDocument(versionsFile.absoluteFilePath(), "FTB_APP version JSON file"); @@ -96,34 +128,29 @@ Modpack parseDirectory(QString path) auto name = Json::requireString(obj, "name", "name"); auto version = Json::requireString(obj, "version", "version"); if (name == "neoforge") { - modpack.loaderType = ModPlatform::NeoForge; - modpack.version = version; + *loaderType = ModPlatform::NeoForge; + *loaderVersion = version; break; } else if (name == "forge") { - modpack.loaderType = ModPlatform::Forge; - modpack.version = version; + *loaderType = ModPlatform::Forge; + *loaderVersion = version; break; } else if (name == "fabric") { - modpack.loaderType = ModPlatform::Fabric; - modpack.version = version; + *loaderType = ModPlatform::Fabric; + *loaderVersion = version; break; } else if (name == "quilt") { - modpack.loaderType = ModPlatform::Quilt; - modpack.version = version; + *loaderType = ModPlatform::Quilt; + *loaderVersion = version; break; } } - } catch (const Exception& e) { + } + catch (const Exception& e) + { qDebug() << "Couldn't load ftb version json: " << e.cause(); - return {}; + return; } - auto iconFile = QFileInfo(FS::PathCombine(path, "folder.jpg")); - if (iconFile.exists() && iconFile.isFile()) { - modpack.icon = QIcon(iconFile.absoluteFilePath()); - } else { // the logo is a file that the first bit denotes the image tipe followed by the actual image data - modpack.icon = loadFTBIcon(FS::PathCombine(path, ".ftbapp", "logo")); - } - return modpack; } - } // namespace FTBImportAPP + diff --git a/launcher/modplatform/import_ftb/PackHelpers.h b/launcher/modplatform/import_ftb/PackHelpers.h index 449ed2546..f010313ff 100644 --- a/launcher/modplatform/import_ftb/PackHelpers.h +++ b/launcher/modplatform/import_ftb/PackHelpers.h @@ -49,7 +49,7 @@ struct Modpack { using ModpackList = QList; Modpack parseDirectory(QString path); - +void legacyInstanceParsing(QString path, std::optional* loaderType, QString* loaderVersion); } // namespace FTBImportAPP // We need it for the proxy model diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp index 034ee3eae..33c0c38b6 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp @@ -102,12 +102,6 @@ void PackInstallTask::unzip() QDir extractDir(m_stagingPath); - m_packZip.reset(new QuaZip(archivePath)); - if (!m_packZip->open(QuaZip::mdUnzip)) { - emitFailed(tr("Failed to open modpack file %1!").arg(archivePath)); - return; - } - m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), QOverload::of(MMCZip::extractDir), archivePath, extractDir.absolutePath() + "/unzip"); connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, &PackInstallTask::onUnzipFinished); diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.h b/launcher/modplatform/legacy_ftb/PackInstallTask.h index 3459ee902..6db6cb712 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.h +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.h @@ -1,6 +1,4 @@ #pragma once -#include -#include #include "InstanceTask.h" #include "PackHelpers.h" #include "meta/Index.h" @@ -39,7 +37,6 @@ class PackInstallTask : public InstanceTask { private: /* data */ shared_qobject_ptr m_network; bool abortable = false; - std::unique_ptr m_packZip; QFuture> m_extractFuture; QFutureWatcher> m_extractFutureWatcher; NetJob::Ptr netJobContainer; diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 9ee4101e6..c638e8db0 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -25,6 +25,7 @@ #include #include "Json.h" #include "MMCZip.h" +#include "archive/ExportToZipTask.h" #include "minecraft/PackProfile.h" #include "minecraft/mod/MetadataHandler.h" #include "minecraft/mod/ModFolderModel.h" @@ -200,7 +201,7 @@ void ModrinthPackExportTask::buildZip() { setStatus(tr("Adding files...")); - auto zipTask = makeShared(output, gameRoot, files, "overrides/", true, true); + auto zipTask = makeShared(output, gameRoot, files, "overrides/", true); zipTask->addExtraFile("modrinth.index.json", generateIndex()); zipTask->setExcludeFiles(resolvedFiles.keys()); diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index 0660d611c..072450c9a 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -71,7 +71,7 @@ auto stringEntry(toml::table table, QString entry_name) -> QString { auto node = table[StringUtils::toStdString(entry_name)]; if (!node) { - qWarning() << "Failed to read str property '" + entry_name + "' in mod metadata."; + qDebug() << "Failed to read str property '" + entry_name + "' in mod metadata."; return {}; } @@ -82,7 +82,7 @@ auto intEntry(toml::table table, QString entry_name) -> int { auto node = table[StringUtils::toStdString(entry_name)]; if (!node) { - qWarning() << "Failed to read int property '" + entry_name + "' in mod metadata."; + qDebug() << "Failed to read int property '" + entry_name + "' in mod metadata."; return {}; } diff --git a/launcher/modplatform/technic/SingleZipPackInstallTask.cpp b/launcher/modplatform/technic/SingleZipPackInstallTask.cpp index cc9ced10b..09428c31d 100644 --- a/launcher/modplatform/technic/SingleZipPackInstallTask.cpp +++ b/launcher/modplatform/technic/SingleZipPackInstallTask.cpp @@ -66,11 +66,7 @@ void Technic::SingleZipPackInstallTask::downloadSucceeded() qDebug() << "Attempting to create instance from" << m_archivePath; // open the zip and find relevant files in it - m_packZip.reset(new QuaZip(m_archivePath)); - if (!m_packZip->open(QuaZip::mdUnzip)) { - emitFailed(tr("Unable to open supplied modpack zip file.")); - return; - } + m_packZip.reset(new MMCZip::ArchiveReader(m_archivePath)); m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), QString(""), extractDir.absolutePath()); connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, &Technic::SingleZipPackInstallTask::extractFinished); diff --git a/launcher/modplatform/technic/SingleZipPackInstallTask.h b/launcher/modplatform/technic/SingleZipPackInstallTask.h index d49d008b9..9dd54458d 100644 --- a/launcher/modplatform/technic/SingleZipPackInstallTask.h +++ b/launcher/modplatform/technic/SingleZipPackInstallTask.h @@ -16,10 +16,9 @@ #pragma once #include "InstanceTask.h" +#include "archive/ArchiveReader.h" #include "net/NetJob.h" -#include - #include #include #include @@ -54,7 +53,7 @@ class SingleZipPackInstallTask : public InstanceTask { QString m_minecraftVersion; QString m_archivePath; NetJob::Ptr m_filesNetJob; - std::unique_ptr m_packZip; + std::unique_ptr m_packZip; QFuture> m_extractFuture; QFutureWatcher> m_extractFutureWatcher; }; diff --git a/launcher/modplatform/technic/TechnicPackProcessor.cpp b/launcher/modplatform/technic/TechnicPackProcessor.cpp index b762e8882..4c40ddf73 100644 --- a/launcher/modplatform/technic/TechnicPackProcessor.cpp +++ b/launcher/modplatform/technic/TechnicPackProcessor.cpp @@ -19,12 +19,10 @@ #include #include #include -#include -#include -#include #include #include +#include "archive/ArchiveReader.h" void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, const QString& instName, @@ -53,35 +51,30 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, QString versionJson = FS::PathCombine(minecraftPath, "bin", "version.json"); QString fmlMinecraftVersion; if (QFile::exists(modpackJar)) { - QuaZip zipFile(modpackJar); - if (!zipFile.open(QuaZip::mdUnzip)) { + MMCZip::ArchiveReader zipFile(modpackJar); + if (!zipFile.collectFiles()) { emit failed(tr("Unable to open \"bin/modpack.jar\" file!")); return; } - QuaZipDir zipFileRoot(&zipFile, "/"); - if (zipFileRoot.exists("/version.json")) { - if (zipFileRoot.exists("/fmlversion.properties")) { - zipFile.setCurrentFile("fmlversion.properties"); - QuaZipFile file(&zipFile); - if (!file.open(QIODevice::ReadOnly)) { + if (zipFile.exists("/version.json")) { + if (zipFile.exists("/fmlversion.properties")) { + auto file = zipFile.goToFile("fmlversion.properties"); + if (!file) { emit failed(tr("Unable to open \"fmlversion.properties\"!")); return; } - QByteArray fmlVersionData = file.readAll(); - file.close(); + QByteArray fmlVersionData = file->readAll(); INIFile iniFile; iniFile.loadFile(fmlVersionData); // If not present, this evaluates to a null string fmlMinecraftVersion = iniFile["fmlbuild.mcversion"].toString(); } - zipFile.setCurrentFile("version.json", QuaZip::csSensitive); - QuaZipFile file(&zipFile); - if (!file.open(QIODevice::ReadOnly)) { + auto file = zipFile.goToFile("version.json"); + if (!file) { emit failed(tr("Unable to open \"version.json\"!")); return; } - data = file.readAll(); - file.close(); + data = file->readAll(); } else { if (minecraftVersion.isEmpty()) { emit failed(tr("Could not find \"version.json\" inside \"bin/modpack.jar\", but Minecraft version is unknown")); @@ -93,16 +86,14 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, // Forge for 1.4.7 and for 1.5.2 require extra libraries. // Figure out the forge version and add it as a component // (the code still comes from the jar mod installed above) - if (zipFileRoot.exists("/forgeversion.properties")) { - zipFile.setCurrentFile("forgeversion.properties", QuaZip::csSensitive); - QuaZipFile file(&zipFile); - if (!file.open(QIODevice::ReadOnly)) { + if (zipFile.exists("/forgeversion.properties")) { + auto file = zipFile.goToFile("forgeversion.properties"); + if (!file) { // Really shouldn't happen, but error handling shall not be forgotten emit failed(tr("Unable to open \"forgeversion.properties\"")); return; } - QByteArray forgeVersionData = file.readAll(); - file.close(); + auto forgeVersionData = file->readAll(); INIFile iniFile; iniFile.loadFile(forgeVersionData); QString major, minor, revision, build; diff --git a/launcher/ui/dialogs/AboutDialog.cpp b/launcher/ui/dialogs/AboutDialog.cpp index 9dcda1832..ca5706d21 100644 --- a/launcher/ui/dialogs/AboutDialog.cpp +++ b/launcher/ui/dialogs/AboutDialog.cpp @@ -48,9 +48,12 @@ namespace { QString getCreditsHtml() { QFile dataFile(":/documents/credits.html"); - dataFile.open(QIODevice::ReadOnly); - + if (!dataFile.open(QIODevice::ReadOnly)) { + qWarning() << "Failed to open file '" << dataFile.fileName() << "' for reading!"; + return {}; + } QString fileContent = QString::fromUtf8(dataFile.readAll()); + dataFile.close(); return fileContent.arg(QObject::tr("%1 Developers").arg(BuildConfig.LAUNCHER_DISPLAYNAME), QObject::tr("MultiMC Developers"), QObject::tr("With special thanks to")); diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp index 8d98b0513..21c16f01a 100644 --- a/launcher/ui/dialogs/ExportInstanceDialog.cpp +++ b/launcher/ui/dialogs/ExportInstanceDialog.cpp @@ -43,6 +43,7 @@ #include #include "FileIgnoreProxy.h" #include "QObjectPtr.h" +#include "archive/ExportToZipTask.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/ProgressDialog.h" #include "ui_ExportInstanceDialog.h" @@ -150,7 +151,7 @@ void ExportInstanceDialog::doExport() return; } - auto task = makeShared(output, m_instance->instanceRoot(), files, "", true, true); + auto task = makeShared(output, m_instance->instanceRoot(), files, "", true); connect(task.get(), &Task::failed, this, [this, output](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); }); diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 198f336f9..7aee9e105 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -38,6 +38,7 @@ #include "ModFolderPage.h" #include "ui/dialogs/ExportToModListDialog.h" +#include "ui/dialogs/InstallLoaderDialog.h" #include "ui_ExternalResourcesPage.h" #include @@ -145,8 +146,9 @@ void ModFolderPage::downloadMods() auto profile = static_cast(m_instance)->getPackProfile(); if (!profile->getModLoaders().has_value()) { - QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!")); - return; + if (handleNoModLoader()) { + return; + } } m_downloadDialog = new ResourceDownload::ModDownloadDialog(this, m_model, m_instance); @@ -201,8 +203,9 @@ void ModFolderPage::updateMods(bool includeDeps) auto profile = static_cast(m_instance)->getPackProfile(); if (!profile->getModLoaders().has_value()) { - QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!")); - return; + if (handleNoModLoader()) { + return; + } } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Mod updates are unavailable when metadata is disabled!")); @@ -305,8 +308,9 @@ void ModFolderPage::changeModVersion() auto profile = static_cast(m_instance)->getPackProfile(); if (!profile->getModLoaders().has_value()) { - QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!")); - return; + if (handleNoModLoader()) { + return; + } } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Mod updates are unavailable when metadata is disabled!")); @@ -385,3 +389,34 @@ bool NilModFolderPage::shouldDisplay() const { return m_model->dir().exists(); } + +// Helper function so this doesn't need to be duplicated 3 times +inline bool ModFolderPage::handleNoModLoader() +{ + int resp = QMessageBox::question(this, this->tr("Missing Mod Loader"), + this->tr("You need to install a compatible mod loader before installing mods. Would you like to do so?"), + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + switch (resp) { + case QMessageBox::Yes: { + // Should be safe + auto profile = static_cast(this->m_instance)->getPackProfile(); + InstallLoaderDialog dialog(profile, QString(), this); + bool ret = dialog.exec(); + this->m_container->refreshContainer(); + + // returning negation of dialog.exec which'll be true if the install loader dialog got canceled/closed + // and false if the user went through and installed a loader + return !ret; + } + case QMessageBox::No: { + // Nothing happens the dialog is already closing + // returning true so the caller doesn't go and continue with opening it's dialog without a mod loader + return true; + } + default: { + // Unreachable + // returning true as a safety measure + return true; + } + } +} diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index b33992470..aadeecb20 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -45,6 +45,8 @@ class ModFolderPage : public ExternalResourcesPage { Q_OBJECT + inline bool handleNoModLoader(); + public: explicit ModFolderPage(BaseInstance* inst, std::shared_ptr model, QWidget* parent = nullptr); virtual ~ModFolderPage() = default; diff --git a/launcher/ui/widgets/LogView.cpp b/launcher/ui/widgets/LogView.cpp index df25a2434..73496a01a 100644 --- a/launcher/ui/widgets/LogView.cpp +++ b/launcher/ui/widgets/LogView.cpp @@ -180,5 +180,30 @@ void LogView::scrollToBottom() void LogView::findNext(const QString& what, bool reverse) { - find(what, reverse ? QTextDocument::FindFlag::FindBackward : QTextDocument::FindFlag(0)); + if (what.isEmpty()) + return; + + const QTextDocument::FindFlags flags(reverse ? QTextDocument::FindBackward : 0); + + if (find(what, flags)) + return; + + QTextCursor cursor = textCursor(); + + if (reverse) { + if (cursor.atEnd()) + return; + + cursor.movePosition(QTextCursor::End); + } else { + if (cursor.atStart()) + return; + + cursor.movePosition(QTextCursor::Start); + } + + cursor = document()->find(what, cursor, flags); + + if (!cursor.isNull()) + setTextCursor(cursor); } diff --git a/launcher/updater/prismupdater/PrismUpdater.cpp b/launcher/updater/prismupdater/PrismUpdater.cpp index 391c15eba..74b8a4875 100644 --- a/launcher/updater/prismupdater/PrismUpdater.cpp +++ b/launcher/updater/prismupdater/PrismUpdater.cpp @@ -809,7 +809,7 @@ bool PrismUpdaterApp::callAppImageUpdate() auto appimage_path = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE")); QProcess proc = QProcess(); qDebug() << "Calling: AppImageUpdate" << appimage_path; - proc.setProgram(FS::PathCombine(m_rootPath, "bin", "AppImageUpdate-x86_64.AppImage")); + proc.setProgram(FS::PathCombine(m_rootPath, "bin", "AppImageUpdate.AppImage")); proc.setArguments({ appimage_path }); auto result = proc.startDetached(); if (!result) diff --git a/libraries/LocalPeer/CMakeLists.txt b/libraries/LocalPeer/CMakeLists.txt index dd78647c0..539cea135 100644 --- a/libraries/LocalPeer/CMakeLists.txt +++ b/libraries/LocalPeer/CMakeLists.txt @@ -2,8 +2,7 @@ cmake_minimum_required(VERSION 3.15) project(LocalPeer) if(Launcher_QT_VERSION_MAJOR EQUAL 6) - find_package(Qt6 COMPONENTS Core Network Core5Compat REQUIRED) - list(APPEND LocalPeer_LIBS Qt${QT_VERSION_MAJOR}::Core5Compat) + find_package(Qt6 COMPONENTS Core Network REQUIRED) endif() set(SINGLE_SOURCES diff --git a/libraries/README.md b/libraries/README.md index 6fac77a97..e15d80eba 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -2,22 +2,6 @@ This folder has third-party or otherwise external libraries needed for other parts to work. -## gamemode - -A performance optimization daemon. - -See [github repo](https://github.com/FeralInteractive/gamemode). - -BSD-3-Clause licensed - -## cmark - -The C reference implementation of CommonMark, a standardized Markdown spec. - -See [github_repo](https://github.com/commonmark/cmark). - -BSD2 licensed. - ## javacheck Simple Java tool that prints the JVM details - version and platform bitness. @@ -99,20 +83,6 @@ Canonical implementation of the murmur2 hash, taken from [SMHasher](https://gith Public domain (the author disclaimed the copyright). -## QR-Code-generator - -A simple library for generating QR codes - -See [github repo](https://github.com/nayuki/QR-Code-generator). - -MIT - -## quazip - -A zip manipulation library. - -LGPL 2.1 with linking exception. - ## rainbow Color functions extracted from [KGuiAddons](https://inqlude.org/libraries/kguiaddons.html). Used for adaptive text coloring. diff --git a/libraries/cmark b/libraries/cmark deleted file mode 160000 index 3460cd809..000000000 --- a/libraries/cmark +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3460cd809b6dd311b58e92733ece2fc956224fd2 diff --git a/libraries/extra-cmake-modules b/libraries/extra-cmake-modules deleted file mode 160000 index 1f820dc98..000000000 --- a/libraries/extra-cmake-modules +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1f820dc98d0a520c175433bcbb0098327d82aac6 diff --git a/libraries/gamemode/CMakeLists.txt b/libraries/gamemode/CMakeLists.txt deleted file mode 100644 index 61195ac21..000000000 --- a/libraries/gamemode/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -cmake_minimum_required(VERSION 3.15) -project(gamemode - VERSION 1.6.1) - -add_library(gamemode) -target_include_directories(gamemode PUBLIC include) -target_link_libraries(gamemode PUBLIC ${CMAKE_DL_LIBS}) diff --git a/libraries/gamemode/include/gamemode_client.h b/libraries/gamemode/include/gamemode_client.h deleted file mode 100644 index b186cd489..000000000 --- a/libraries/gamemode/include/gamemode_client.h +++ /dev/null @@ -1,337 +0,0 @@ -/* - -Copyright (c) 2017-2019, Feral Interactive -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of Feral Interactive nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - - */ -#ifndef CLIENT_GAMEMODE_H -#define CLIENT_GAMEMODE_H -/* - * GameMode supports the following client functions - * Requests are refcounted in the daemon - * - * int gamemode_request_start() - Request gamemode starts - * 0 if the request was sent successfully - * -1 if the request failed - * - * int gamemode_request_end() - Request gamemode ends - * 0 if the request was sent successfully - * -1 if the request failed - * - * GAMEMODE_AUTO can be defined to make the above two functions apply during static init and - * destruction, as appropriate. In this configuration, errors will be printed to stderr - * - * int gamemode_query_status() - Query the current status of gamemode - * 0 if gamemode is inactive - * 1 if gamemode is active - * 2 if gamemode is active and this client is registered - * -1 if the query failed - * - * int gamemode_request_start_for(pid_t pid) - Request gamemode starts for another process - * 0 if the request was sent successfully - * -1 if the request failed - * -2 if the request was rejected - * - * int gamemode_request_end_for(pid_t pid) - Request gamemode ends for another process - * 0 if the request was sent successfully - * -1 if the request failed - * -2 if the request was rejected - * - * int gamemode_query_status_for(pid_t pid) - Query status of gamemode for another process - * 0 if gamemode is inactive - * 1 if gamemode is active - * 2 if gamemode is active and this client is registered - * -1 if the query failed - * - * const char* gamemode_error_string() - Get an error string - * returns a string describing any of the above errors - * - * Note: All the above requests can be blocking - dbus requests can and will block while the daemon - * handles the request. It is not recommended to make these calls in performance critical code - */ - -#include -#include - -#include -#include - -#include - -static char internal_gamemode_client_error_string[512] = { 0 }; - -/** - * Load libgamemode dynamically to dislodge us from most dependencies. - * This allows clients to link and/or use this regardless of runtime. - * See SDL2 for an example of the reasoning behind this in terms of - * dynamic versioning as well. - */ -static volatile int internal_libgamemode_loaded = 1; - -/* Typedefs for the functions to load */ -typedef int (*api_call_return_int)(void); -typedef const char* (*api_call_return_cstring)(void); -typedef int (*api_call_pid_return_int)(pid_t); - -/* Storage for functors */ -static api_call_return_int REAL_internal_gamemode_request_start = NULL; -static api_call_return_int REAL_internal_gamemode_request_end = NULL; -static api_call_return_int REAL_internal_gamemode_query_status = NULL; -static api_call_return_cstring REAL_internal_gamemode_error_string = NULL; -static api_call_pid_return_int REAL_internal_gamemode_request_start_for = NULL; -static api_call_pid_return_int REAL_internal_gamemode_request_end_for = NULL; -static api_call_pid_return_int REAL_internal_gamemode_query_status_for = NULL; - -/** - * Internal helper to perform the symbol binding safely. - * - * Returns 0 on success and -1 on failure - */ -__attribute__((always_inline)) static inline int internal_bind_libgamemode_symbol(void* handle, - const char* name, - void** out_func, - size_t func_size, - bool required) -{ - void* symbol_lookup = NULL; - char* dl_error = NULL; - - /* Safely look up the symbol */ - symbol_lookup = dlsym(handle, name); - dl_error = dlerror(); - if (required && (dl_error || !symbol_lookup)) { - snprintf(internal_gamemode_client_error_string, sizeof(internal_gamemode_client_error_string), "dlsym failed - %s", dl_error); - return -1; - } - - /* Have the symbol correctly, copy it to make it usable */ - memcpy(out_func, &symbol_lookup, func_size); - return 0; -} - -/** - * Loads libgamemode and needed functions - * - * Returns 0 on success and -1 on failure - */ -__attribute__((always_inline)) static inline int internal_load_libgamemode(void) -{ - /* We start at 1, 0 is a success and -1 is a fail */ - if (internal_libgamemode_loaded != 1) { - return internal_libgamemode_loaded; - } - - /* Anonymous struct type to define our bindings */ - struct binding { - const char* name; - void** functor; - size_t func_size; - bool required; - } bindings[] = { - { "real_gamemode_request_start", (void**)&REAL_internal_gamemode_request_start, sizeof(REAL_internal_gamemode_request_start), - true }, - { "real_gamemode_request_end", (void**)&REAL_internal_gamemode_request_end, sizeof(REAL_internal_gamemode_request_end), true }, - { "real_gamemode_query_status", (void**)&REAL_internal_gamemode_query_status, sizeof(REAL_internal_gamemode_query_status), false }, - { "real_gamemode_error_string", (void**)&REAL_internal_gamemode_error_string, sizeof(REAL_internal_gamemode_error_string), true }, - { "real_gamemode_request_start_for", (void**)&REAL_internal_gamemode_request_start_for, - sizeof(REAL_internal_gamemode_request_start_for), false }, - { "real_gamemode_request_end_for", (void**)&REAL_internal_gamemode_request_end_for, sizeof(REAL_internal_gamemode_request_end_for), - false }, - { "real_gamemode_query_status_for", (void**)&REAL_internal_gamemode_query_status_for, - sizeof(REAL_internal_gamemode_query_status_for), false }, - }; - - void* libgamemode = NULL; - - /* Try and load libgamemode */ - libgamemode = dlopen("libgamemode.so.0", RTLD_NOW); - if (!libgamemode) { - /* Attempt to load unversioned library for compatibility with older - * versions (as of writing, there are no ABI changes between the two - - * this may need to change if ever ABI-breaking changes are made) */ - libgamemode = dlopen("libgamemode.so", RTLD_NOW); - if (!libgamemode) { - snprintf(internal_gamemode_client_error_string, sizeof(internal_gamemode_client_error_string), "dlopen failed - %s", dlerror()); - internal_libgamemode_loaded = -1; - return -1; - } - } - - /* Attempt to bind all symbols */ - for (size_t i = 0; i < sizeof(bindings) / sizeof(bindings[0]); i++) { - struct binding* binder = &bindings[i]; - - if (internal_bind_libgamemode_symbol(libgamemode, binder->name, binder->functor, binder->func_size, binder->required)) { - internal_libgamemode_loaded = -1; - return -1; - }; - } - - /* Success */ - internal_libgamemode_loaded = 0; - return 0; -} - -/** - * Redirect to the real libgamemode - */ -__attribute__((always_inline)) static inline const char* gamemode_error_string(void) -{ - /* If we fail to load the system gamemode, or we have an error string already, return our error - * string instead of diverting to the system version */ - if (internal_load_libgamemode() < 0 || internal_gamemode_client_error_string[0] != '\0') { - return internal_gamemode_client_error_string; - } - - return REAL_internal_gamemode_error_string(); -} - -/** - * Redirect to the real libgamemode - * Allow automatically requesting game mode - * Also prints errors as they happen. - */ -#ifdef GAMEMODE_AUTO -__attribute__((constructor)) -#else -__attribute__((always_inline)) static inline -#endif -int gamemode_request_start(void) -{ - /* Need to load gamemode */ - if (internal_load_libgamemode() < 0) { -#ifdef GAMEMODE_AUTO - fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); -#endif - return -1; - } - - if (REAL_internal_gamemode_request_start() < 0) { -#ifdef GAMEMODE_AUTO - fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); -#endif - return -1; - } - - return 0; -} - -/* Redirect to the real libgamemode */ -#ifdef GAMEMODE_AUTO -__attribute__((destructor)) -#else -__attribute__((always_inline)) static inline -#endif -int gamemode_request_end(void) -{ - /* Need to load gamemode */ - if (internal_load_libgamemode() < 0) { -#ifdef GAMEMODE_AUTO - fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); -#endif - return -1; - } - - if (REAL_internal_gamemode_request_end() < 0) { -#ifdef GAMEMODE_AUTO - fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); -#endif - return -1; - } - - return 0; -} - -/* Redirect to the real libgamemode */ -__attribute__((always_inline)) static inline int gamemode_query_status(void) -{ - /* Need to load gamemode */ - if (internal_load_libgamemode() < 0) { - return -1; - } - - if (REAL_internal_gamemode_query_status == NULL) { - snprintf(internal_gamemode_client_error_string, sizeof(internal_gamemode_client_error_string), - "gamemode_query_status missing (older host?)"); - return -1; - } - - return REAL_internal_gamemode_query_status(); -} - -/* Redirect to the real libgamemode */ -__attribute__((always_inline)) static inline int gamemode_request_start_for(pid_t pid) -{ - /* Need to load gamemode */ - if (internal_load_libgamemode() < 0) { - return -1; - } - - if (REAL_internal_gamemode_request_start_for == NULL) { - snprintf(internal_gamemode_client_error_string, sizeof(internal_gamemode_client_error_string), - "gamemode_request_start_for missing (older host?)"); - return -1; - } - - return REAL_internal_gamemode_request_start_for(pid); -} - -/* Redirect to the real libgamemode */ -__attribute__((always_inline)) static inline int gamemode_request_end_for(pid_t pid) -{ - /* Need to load gamemode */ - if (internal_load_libgamemode() < 0) { - return -1; - } - - if (REAL_internal_gamemode_request_end_for == NULL) { - snprintf(internal_gamemode_client_error_string, sizeof(internal_gamemode_client_error_string), - "gamemode_request_end_for missing (older host?)"); - return -1; - } - - return REAL_internal_gamemode_request_end_for(pid); -} - -/* Redirect to the real libgamemode */ -__attribute__((always_inline)) static inline int gamemode_query_status_for(pid_t pid) -{ - /* Need to load gamemode */ - if (internal_load_libgamemode() < 0) { - return -1; - } - - if (REAL_internal_gamemode_query_status_for == NULL) { - snprintf(internal_gamemode_client_error_string, sizeof(internal_gamemode_client_error_string), - "gamemode_query_status_for missing (older host?)"); - return -1; - } - - return REAL_internal_gamemode_query_status_for(pid); -} - -#endif // CLIENT_GAMEMODE_H diff --git a/libraries/qdcss/CMakeLists.txt b/libraries/qdcss/CMakeLists.txt index 7e497feca..d1c1078cd 100644 --- a/libraries/qdcss/CMakeLists.txt +++ b/libraries/qdcss/CMakeLists.txt @@ -2,8 +2,7 @@ cmake_minimum_required(VERSION 3.15) project(qdcss) if(Launcher_QT_VERSION_MAJOR EQUAL 6) - find_package(Qt6 COMPONENTS Core Core5Compat REQUIRED) - list(APPEND qdcss_LIBS Qt${QT_VERSION_MAJOR}::Core5Compat) + find_package(Qt6 COMPONENTS Core REQUIRED) endif() set(QDCSS_SOURCES diff --git a/libraries/quazip b/libraries/quazip deleted file mode 160000 index 3fd3b299b..000000000 --- a/libraries/quazip +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3fd3b299b875fbd2beac4894b8a870d80022cad7 diff --git a/libraries/tomlplusplus b/libraries/tomlplusplus deleted file mode 160000 index c4369ae1d..000000000 --- a/libraries/tomlplusplus +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c4369ae1d8955cae20c4ab40b9813ef4b60e48be diff --git a/libraries/zlib b/libraries/zlib deleted file mode 160000 index 51b7f2abd..000000000 --- a/libraries/zlib +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 51b7f2abdade71cd9bb0e7a373ef2610ec6f9daf diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix index 2a00b5620..478eb7b3e 100644 --- a/nix/unwrapped.nix +++ b/nix/unwrapped.nix @@ -3,7 +3,6 @@ stdenv, cmake, cmark, - apple-sdk_11, extra-cmake-modules, gamemode, jdk17, @@ -16,11 +15,8 @@ tomlplusplus, zlib, msaClientID ? null, - gamemodeSupport ? stdenv.hostPlatform.isLinux, + libarchive, }: -assert lib.assertMsg ( - gamemodeSupport -> stdenv.hostPlatform.isLinux -) "gamemodeSupport is only available on Linux."; let date = @@ -77,13 +73,12 @@ stdenv.mkDerivation { cmark kdePackages.qtbase kdePackages.qtnetworkauth - kdePackages.quazip qrencode + libarchive tomlplusplus zlib ] - ++ lib.optionals stdenv.hostPlatform.isDarwin [ apple-sdk_11 ] - ++ lib.optional gamemodeSupport gamemode; + ++ lib.optional stdenv.hostPlatform.isLinux gamemode; cmakeFlags = [ # downstream branding diff --git a/nix/wrapper.nix b/nix/wrapper.nix index 01edd0d9a..4ca2d7b3b 100644 --- a/nix/wrapper.nix +++ b/nix/wrapper.nix @@ -51,7 +51,7 @@ assert lib.assertMsg ( ) "textToSpeechSupport only has an effect on Linux."; let - prismlauncher' = prismlauncher-unwrapped.override { inherit msaClientID gamemodeSupport; }; + prismlauncher' = prismlauncher-unwrapped.override { inherit msaClientID; }; in symlinkJoin { diff --git a/program_info/CMakeLists.txt b/program_info/CMakeLists.txt index db6920e20..23eb840b1 100644 --- a/program_info/CMakeLists.txt +++ b/program_info/CMakeLists.txt @@ -29,6 +29,7 @@ set(Launcher_SVGFileName "${Launcher_SVGFileName}" PARENT_SCOPE) set(Launcher_Desktop "program_info/${Launcher_AppID}.desktop" PARENT_SCOPE) set(Launcher_mrpack_MIMEInfo "program_info/modrinth-mrpack-mime.xml" PARENT_SCOPE) set(Launcher_MetaInfo "program_info/${Launcher_AppID}.metainfo.xml" PARENT_SCOPE) +set(Launcher_PNG_256 "program_info/${Launcher_AppID}_256.png" PARENT_SCOPE) set(Launcher_SVG "program_info/${Launcher_SVGFileName}" PARENT_SCOPE) set(Launcher_Branding_ICNS "program_info/prismlauncher.icns" PARENT_SCOPE) set(Launcher_Branding_ICO "program_info/prismlauncher.ico") diff --git a/program_info/org.prismlauncher.PrismLauncher_256.png b/program_info/org.prismlauncher.PrismLauncher_256.png new file mode 100755 index 000000000..6f164febe Binary files /dev/null and b/program_info/org.prismlauncher.PrismLauncher_256.png differ diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json index b1b0996e2..20811632c 100644 --- a/vcpkg-configuration.json +++ b/vcpkg-configuration.json @@ -1,7 +1,7 @@ { "default-registry": { "kind": "git", - "baseline": "1fddddc280dfed63956e15ef74f4321bc6a219c9", + "baseline": "2d6a6cf3ac9a7cc93942c3d289a2f9c661a6f4a7", "repository": "https://github.com/microsoft/vcpkg" }, "registries": [ diff --git a/vcpkg.json b/vcpkg.json index 0399cdf27..942e6d9e4 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,7 +1,5 @@ { "dependencies": [ - "bzip2", - "cmark", { "name": "ecm", "host": true @@ -14,6 +12,19 @@ "name": "pkgconf", "host": true }, + + "cmark", + { + "name": "libarchive", + "default-features": false, + "features": [ + "bzip2", + "lz4", + "lzma", + "lzo", + "zstd" + ] + }, "tomlplusplus", "zlib" ]