mirror of
https://github.com/PrismLauncher/PrismLauncher.git
synced 2026-06-29 01:54:20 +03:00
Merge remote-tracking branch 'upstream/develop' into desysteminfo
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
This commit is contained in:
commit
283d49ba35
90 changed files with 1698 additions and 2299 deletions
|
|
@ -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
|
||||
45
.github/actions/package/linux/action.yml
vendored
45
.github/actions/package/linux/action.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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' }}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 != '' }}
|
||||
|
|
|
|||
2
.github/workflows/backport.yml
vendored
2
.github/workflows/backport.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
|
@ -139,7 +139,7 @@ jobs:
|
|||
##
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
|
|
|
|||
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
|
|
@ -60,7 +60,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: "true"
|
||||
|
||||
|
|
|
|||
2
.github/workflows/flatpak.yml
vendored
2
.github/workflows/flatpak.yml
vendored
|
|
@ -84,7 +84,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
|
|
|
|||
2
.github/workflows/nix.yml
vendored
2
.github/workflows/nix.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
|
@ -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"
|
||||
|
|
|
|||
4
.github/workflows/update-flake.yml
vendored
4
.github/workflows/update-flake.yml
vendored
|
|
@ -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"
|
||||
|
|
|
|||
15
.gitmodules
vendored
15
.gitmodules
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,2 +1 @@
|
|||
libraries/nbtplusplus
|
||||
libraries/quazip
|
||||
|
|
|
|||
124
CMakeLists.txt
124
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()
|
||||
find_package(ECM NO_MODULE REQUIRED)
|
||||
set(CMAKE_MODULE_PATH "${ECM_MODULE_PATH};${CMAKE_MODULE_PATH}")
|
||||
endif()
|
||||
|
||||
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,21 +329,23 @@ 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
|
||||
# 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 QUIET)
|
||||
if(PkgConfig_FOUND)
|
||||
pkg_check_modules(tomlplusplus IMPORTED_TARGET tomlplusplus>=3.2.0)
|
||||
endif()
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(tomlplusplus REQUIRED IMPORTED_TARGET tomlplusplus>=3.2.0)
|
||||
endif()
|
||||
|
||||
find_package(ZLIB REQUIRED)
|
||||
|
||||
# Find cmark
|
||||
find_package(cmark QUIET)
|
||||
endif()
|
||||
|
||||
include(ECMQtDeclareLoggingCategory)
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
24
COPYING.md
24
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
|
||||
|
|
|
|||
|
|
@ -1,100 +0,0 @@
|
|||
# SPDX-FileCopyrightText: 2014 Rohan Garg <rohan16garg@gmail.com>
|
||||
# SPDX-FileCopyrightText: 2014 Alex Merry <alex.merry@kde.org>
|
||||
# SPDX-FileCopyrightText: 2014-2016 Aleix Pol <aleixpol@kde.org>
|
||||
# SPDX-FileCopyrightText: 2017 Friedrich W. H. Kossebau <kossebau@kde.org>
|
||||
# SPDX-FileCopyrightText: 2022 Ahmad Samir <a.samir78@gmail.com>
|
||||
#
|
||||
# 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(<result_variable> <qt_variable> [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 ``<prefix>/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()
|
||||
|
|
@ -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 <vkrause@kde.org>
|
||||
#
|
||||
# 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()
|
||||
|
|
@ -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()
|
||||
|
||||
6
flake.lock
generated
6
flake.lock
generated
|
|
@ -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": {
|
||||
|
|
|
|||
14
flatpak/cmark.yml
Normal file
14
flatpak/cmark.yml
Normal file
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
6
flatpak/tomlplusplus.yml
Normal file
6
flatpak/tomlplusplus.yml
Normal file
|
|
@ -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
|
||||
|
|
@ -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
|
||||
// <root>/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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -291,6 +291,8 @@ bool move(const QString& source, const QString& dest);
|
|||
*/
|
||||
bool deletePath(QString path);
|
||||
|
||||
bool removeFiles(QStringList listFile);
|
||||
|
||||
/**
|
||||
* Trash a folder / file
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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 <QFileInfo>
|
||||
#include <QtConcurrentRun>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#include <quazip/quazipdir.h>
|
||||
|
||||
InstanceImportTask::InstanceImportTask(const QUrl& sourceUrl, QWidget* parent, QMap<QString, QString>&& 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<QuaZip>(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<MMCZip::ExtractZipTask>(packZip, extractDir, root);
|
||||
auto zipTask = makeShared<MMCZip::ExtractZipTask>(m_archivePath, extractDir, root);
|
||||
|
||||
auto progressStep = std::make_shared<TaskStepProgress>();
|
||||
connect(zipTask.get(), &Task::finished, this, [this, progressStep] {
|
||||
|
|
|
|||
|
|
@ -40,8 +40,6 @@
|
|||
#include <QUrl>
|
||||
#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();
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
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<AuthSession>();
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -36,10 +36,10 @@
|
|||
|
||||
#include "LoggedProcess.h"
|
||||
#include <QDebug>
|
||||
#include <QTextDecoder>
|
||||
#include <QStringDecoder>
|
||||
#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);
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <QProcess>
|
||||
#include <QTextDecoder>
|
||||
#include <QStringDecoder>
|
||||
#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;
|
||||
|
|
|
|||
|
|
@ -35,66 +35,46 @@
|
|||
*/
|
||||
|
||||
#include "MMCZip.h"
|
||||
#include <quazip/quazip.h>
|
||||
#include <quazip/quazipdir.h>
|
||||
#include <quazip/quazipfile.h>
|
||||
#include <archive.h>
|
||||
#include "FileSystem.h"
|
||||
#include "archive/ArchiveReader.h"
|
||||
#include "archive/ArchiveWriter.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QFileInfo>
|
||||
#include <QUrl>
|
||||
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
#include <QtConcurrentRun>
|
||||
#endif
|
||||
#include <memory>
|
||||
|
||||
namespace MMCZip {
|
||||
// ours
|
||||
bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& contained, const Filter& filter)
|
||||
using FilterFunction = std::function<bool(const QString&)>;
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
bool mergeZipFiles(ArchiveWriter& into, QFileInfo from, QSet<QString>& 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;
|
||||
});
|
||||
}
|
||||
|
||||
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<Mod*>& 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 QList<M
|
|||
if (!mod->enabled())
|
||||
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 QList<M
|
|||
} else if (mod->type() == ResourceType::SINGLEFILE) {
|
||||
// FIXME: buggy - does not work with addedFiles
|
||||
auto filename = mod->fileinfo();
|
||||
if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName())) {
|
||||
if (!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 QList<M
|
|||
files.removeAll(e);
|
||||
}
|
||||
|
||||
if (!compressDirFiles(&zipOut, parent_dir, files)) {
|
||||
if (!compressDirFiles(zipOut, parent_dir, files)) {
|
||||
zipOut.close();
|
||||
FS::deletePath(targetJarPath);
|
||||
qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar.";
|
||||
|
|
@ -209,7 +159,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<M
|
|||
}
|
||||
}
|
||||
|
||||
if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, [](const QString key) { return !key.contains("META-INF"); })) {
|
||||
if (!mergeZipFiles(zipOut, QFileInfo(sourceJarPath), addedFiles, [](const QString key) { return !key.contains("META-INF"); })) {
|
||||
zipOut.close();
|
||||
FS::deletePath(targetJarPath);
|
||||
qCritical() << "Failed to insert minecraft.jar contents.";
|
||||
|
|
@ -217,8 +167,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<M
|
|||
}
|
||||
|
||||
// Recompress the jar
|
||||
zipOut.close();
|
||||
if (zipOut.getZipError() != 0) {
|
||||
if (!zipOut.close()) {
|
||||
FS::deletePath(targetJarPath);
|
||||
qCritical() << "Failed to finalize minecraft.jar!";
|
||||
return false;
|
||||
|
|
@ -228,70 +177,32 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<M
|
|||
#endif
|
||||
|
||||
// ours
|
||||
QString findFolderOfFileInZip(QuaZip* zip, const QString& what, const QStringList& ignore_paths, const QString& root)
|
||||
{
|
||||
QuaZipDir rootDir(zip, root);
|
||||
for (auto&& fileName : rootDir.entryList(QDir::Files)) {
|
||||
if (fileName == what)
|
||||
return root;
|
||||
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
|
||||
// Recurse the search to non-ignored subfolders
|
||||
for (auto&& fileName : rootDir.entryList(QDir::Dirs)) {
|
||||
if (ignore_paths.contains(fileName))
|
||||
continue;
|
||||
|
||||
QString result = findFolderOfFileInZip(zip, what, ignore_paths, root + fileName);
|
||||
if (!result.isEmpty())
|
||||
return result;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// ours
|
||||
bool findFilesInZip(QuaZip* zip, const QString& what, QStringList& result, const QString& root)
|
||||
{
|
||||
QuaZipDir rootDir(zip, root);
|
||||
for (auto fileName : rootDir.entryList(QDir::Files)) {
|
||||
if (fileName == what) {
|
||||
result.append(root);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (auto fileName : rootDir.entryList(QDir::Dirs)) {
|
||||
findFilesInZip(zip, what, result, root + fileName);
|
||||
}
|
||||
return !result.isEmpty();
|
||||
}
|
||||
|
||||
// ours
|
||||
std::optional<QStringList> extractSubDir(QuaZip* zip, const QString& subdir, const QString& target)
|
||||
std::optional<QStringList> 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();
|
||||
auto extPtr = ArchiveWriter::createDiskWriter();
|
||||
auto ext = extPtr.get();
|
||||
|
||||
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))
|
||||
continue;
|
||||
if (!file_name.startsWith(subdir)) {
|
||||
f->skip();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(subdir.size()));
|
||||
auto original_name = relative_file_name;
|
||||
|
|
@ -308,7 +219,6 @@ std::optional<QStringList> extractSubDir(QuaZip* zip, const QString& subdir, con
|
|||
|
||||
relative_file_name = relative_file_name.split('/').last();
|
||||
}
|
||||
|
||||
QString target_file_path;
|
||||
if (relative_file_name.isEmpty()) {
|
||||
target_file_path = target + '/';
|
||||
|
|
@ -321,101 +231,67 @@ std::optional<QStringList> extractSubDir(QuaZip* zip, const QString& subdir, con
|
|||
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;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!JlCompress::extractFile(zip, "", target_file_path)) {
|
||||
if (!f->writeFile(ext, target_file_path)) {
|
||||
qWarning() << "Failed to extract file" << original_name << "to" << target_file_path;
|
||||
JlCompress::removeFile(extracted);
|
||||
return std::nullopt;
|
||||
return false;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
} 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path;
|
||||
} while (zip->goToNextFile());
|
||||
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<QStringList> 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;
|
||||
}
|
||||
ArchiveReader zip(fileCompressed);
|
||||
return extractSubDir(&zip, "", dir);
|
||||
}
|
||||
|
||||
// ours
|
||||
std::optional<QStringList> 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;
|
||||
}
|
||||
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();
|
||||
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<ZipResult>::finished, this, &ExportToZipTask::finish);
|
||||
m_build_zip_watcher.setFuture(m_build_zip_future);
|
||||
}
|
||||
|
||||
auto ExportToZipTask::exportZip() -> ZipResult
|
||||
{
|
||||
if (!m_dir.exists()) {
|
||||
return ZipResult(tr("Folder doesn't exist"));
|
||||
}
|
||||
if (!m_output.isOpen() && !m_output.open(QuaZip::mdCreate)) {
|
||||
return ZipResult(tr("Could not create file"));
|
||||
}
|
||||
|
||||
for (auto fileName : m_extra_files.keys()) {
|
||||
if (m_build_zip_future.isCanceled())
|
||||
return ZipResult();
|
||||
QuaZipFile indexFile(&m_output);
|
||||
if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo(fileName))) {
|
||||
return ZipResult(tr("Could not create:") + fileName);
|
||||
}
|
||||
indexFile.write(m_extra_files[fileName]);
|
||||
}
|
||||
|
||||
for (const QFileInfo& file : m_files) {
|
||||
if (m_build_zip_future.isCanceled())
|
||||
return ZipResult();
|
||||
|
||||
auto absolute = file.absoluteFilePath();
|
||||
auto relative = m_dir.relativeFilePath(absolute);
|
||||
setStatus("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<ZipResult>::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
|
||||
|
|
|
|||
|
|
@ -36,8 +36,6 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <quazip.h>
|
||||
#include <quazip/JlCompress.h>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QFuture>
|
||||
|
|
@ -46,72 +44,27 @@
|
|||
#include <QSet>
|
||||
#include <QString>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#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<bool(const QFileInfo&)>;
|
||||
|
||||
/**
|
||||
* Merge two zip files, using a filter function
|
||||
*/
|
||||
bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& 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<Mod*>& 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<QStringList> extractSubDir(QuaZip* zip, const QString& subdir, const QString& target);
|
||||
|
||||
bool extractRelFile(QuaZip* zip, const QString& file, const QString& target);
|
||||
std::optional<QStringList> 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<QString>;
|
||||
|
||||
protected:
|
||||
virtual void executeTask() override;
|
||||
bool abort() override;
|
||||
|
||||
ZipResult exportZip();
|
||||
void finish();
|
||||
|
||||
private:
|
||||
QString m_output_path;
|
||||
QuaZip m_output;
|
||||
QDir m_dir;
|
||||
QFileInfoList m_files;
|
||||
QString m_destination_prefix;
|
||||
bool m_follow_symlinks;
|
||||
QStringList m_exclude_files;
|
||||
QHash<QString, QByteArray> m_extra_files;
|
||||
|
||||
QFuture<ZipResult> m_build_zip_future;
|
||||
QFutureWatcher<ZipResult> m_build_zip_watcher;
|
||||
};
|
||||
|
||||
class ExtractZipTask : public Task {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ExtractZipTask(QString input, QDir outputDir, QString subdirectory = "")
|
||||
: ExtractZipTask(std::make_shared<QuaZip>(input), outputDir, subdirectory)
|
||||
{}
|
||||
ExtractZipTask(std::shared_ptr<QuaZip> input, QDir outputDir, QString subdirectory = "")
|
||||
: m_input(input), m_output_dir(outputDir), m_subdirectory(subdirectory)
|
||||
{}
|
||||
virtual ~ExtractZipTask() = default;
|
||||
|
||||
using ZipResult = std::optional<QString>;
|
||||
|
||||
protected:
|
||||
virtual void executeTask() override;
|
||||
bool abort() override;
|
||||
|
||||
ZipResult extractZip();
|
||||
void finish();
|
||||
|
||||
private:
|
||||
std::shared_ptr<QuaZip> m_input;
|
||||
QDir m_output_dir;
|
||||
QString m_subdirectory;
|
||||
|
||||
QFuture<ZipResult> m_zip_future;
|
||||
QFutureWatcher<ZipResult> m_zip_watcher;
|
||||
};
|
||||
#endif
|
||||
} // namespace MMCZip
|
||||
|
|
|
|||
|
|
@ -1,260 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include "Untar.h"
|
||||
#include <quagzipfile.h>
|
||||
#include <QByteArray>
|
||||
#include <QFileInfo>
|
||||
#include <QIODevice>
|
||||
#include <QString>
|
||||
#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);
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#pragma once
|
||||
#include <QIODevice>
|
||||
|
||||
// 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);
|
||||
}
|
||||
230
launcher/archive/ArchiveReader.cpp
Normal file
230
launcher/archive/ArchiveReader.cpp
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only AND LicenseRef-PublicDomain
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2025 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Additional note: Portions of this file are released into the public domain
|
||||
* under LicenseRef-PublicDomain.
|
||||
*/
|
||||
#include "ArchiveReader.h"
|
||||
#include <archive.h>
|
||||
#include <archive_entry.h>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
|
||||
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<const char*>(buff), static_cast<qsizetype>(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<File>
|
||||
{
|
||||
auto f = std::make_unique<File>();
|
||||
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<bool(File*, bool&)> doStuff)
|
||||
{
|
||||
auto f = std::make_unique<File>();
|
||||
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<bool(File*)> 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
|
||||
72
launcher/archive/ArchiveReader.h
Normal file
72
launcher/archive/ArchiveReader.h
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2025 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDateTime>
|
||||
#include <QStringList>
|
||||
#include <memory>
|
||||
|
||||
struct archive;
|
||||
struct archive_entry;
|
||||
namespace MMCZip {
|
||||
class ArchiveReader {
|
||||
public:
|
||||
using ArchivePtr = std::unique_ptr<struct archive, int (*)(struct archive*)>;
|
||||
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<File> goToFile(QString filename);
|
||||
bool parse(std::function<bool(File*)>);
|
||||
bool parse(std::function<bool(File*, bool&)>);
|
||||
|
||||
private:
|
||||
QString m_archivePath;
|
||||
size_t m_blockSize = 10240;
|
||||
|
||||
QStringList m_fileNames = {};
|
||||
};
|
||||
} // namespace MMCZip
|
||||
213
launcher/archive/ArchiveWriter.cpp
Normal file
213
launcher/archive/ArchiveWriter.cpp
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2025 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "ArchiveWriter.h"
|
||||
#include <archive.h>
|
||||
#include <archive_entry.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include <memory>
|
||||
|
||||
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<archive_entry, void (*)(archive_entry*)> 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<archive_entry, void (*)(archive_entry*)> 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<archive, void (*)(archive*)> 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<archive, void (*)(archive*)> 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
|
||||
46
launcher/archive/ArchiveWriter.h
Normal file
46
launcher/archive/ArchiveWriter.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2025 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QFileDevice>
|
||||
#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<archive, void (*)(archive*)> createDiskWriter();
|
||||
|
||||
private:
|
||||
struct archive* m_archive = nullptr;
|
||||
QString m_filename;
|
||||
QString m_format = "zip";
|
||||
};
|
||||
} // namespace MMCZip
|
||||
100
launcher/archive/ExportToZipTask.cpp
Normal file
100
launcher/archive/ExportToZipTask.cpp
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2025 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "ExportToZipTask.h"
|
||||
|
||||
#include <QtConcurrent>
|
||||
|
||||
#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<ZipResult>::finished, this, &ExportToZipTask::finish);
|
||||
m_build_zip_watcher.setFuture(m_build_zip_future);
|
||||
}
|
||||
|
||||
auto ExportToZipTask::exportZip() -> ZipResult
|
||||
{
|
||||
if (!m_dir.exists()) {
|
||||
return ZipResult(tr("Folder doesn't exist"));
|
||||
}
|
||||
if (!m_output.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
|
||||
72
launcher/archive/ExportToZipTask.h
Normal file
72
launcher/archive/ExportToZipTask.h
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2025 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfoList>
|
||||
#include <QFuture>
|
||||
#include <QFutureWatcher>
|
||||
|
||||
#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<QString>;
|
||||
|
||||
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<QString, QByteArray> m_extra_files;
|
||||
|
||||
QFuture<ZipResult> m_build_zip_future;
|
||||
QFutureWatcher<ZipResult> m_build_zip_watcher;
|
||||
};
|
||||
} // namespace MMCZip
|
||||
135
launcher/archive/ExtractZipTask.cpp
Normal file
135
launcher/archive/ExtractZipTask.cpp
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2025 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "ExtractZipTask.h"
|
||||
#include <QtConcurrent>
|
||||
#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<ZipResult>::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
|
||||
53
launcher/archive/ExtractZipTask.h
Normal file
53
launcher/archive/ExtractZipTask.h
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2025 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <QDir>
|
||||
#include <QFuture>
|
||||
#include <QFutureWatcher>
|
||||
#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<QString>;
|
||||
|
||||
protected:
|
||||
virtual void executeTask() override;
|
||||
bool abort() override;
|
||||
|
||||
ZipResult extractZip();
|
||||
void finish();
|
||||
|
||||
private:
|
||||
ArchiveReader m_input;
|
||||
QDir m_output_dir;
|
||||
QString m_subdirectory;
|
||||
|
||||
QFuture<ZipResult> m_zip_future;
|
||||
QFutureWatcher<ZipResult> m_zip_watcher;
|
||||
};
|
||||
} // namespace MMCZip
|
||||
|
|
@ -16,12 +16,11 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "java/download/ArchiveDownloadTask.h"
|
||||
#include <quazip.h>
|
||||
#include <memory>
|
||||
#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,39 +66,19 @@ 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<QuaZip>(input);
|
||||
if (!zip->open(QuaZip::mdUnzip)) {
|
||||
|
||||
MMCZip::ArchiveReader zip(input);
|
||||
if (!zip.collectFiles()) {
|
||||
emitFailed(tr("Unable to open supplied zip file."));
|
||||
return;
|
||||
}
|
||||
auto files = zip->getFileNameList();
|
||||
auto files = zip.getFiles();
|
||||
if (files.isEmpty()) {
|
||||
emitFailed(tr("No files were found in the supplied zip file."));
|
||||
return;
|
||||
}
|
||||
m_task = makeShared<MMCZip::ExtractZipTask>(zip, m_final_path, files[0]);
|
||||
auto firstFolderParts = files[0].split('/', Qt::SkipEmptyParts);
|
||||
m_task = makeShared<MMCZip::ExtractZipTask>(input, m_final_path, firstFolderParts.value(0));
|
||||
|
||||
auto progressStep = std::make_shared<TaskStepProgress>();
|
||||
connect(m_task.get(), &Task::finished, this, [this, progressStep] {
|
||||
|
|
@ -128,9 +107,6 @@ void ArchiveDownloadTask::extractJava(QString input)
|
|||
return;
|
||||
}
|
||||
|
||||
emitFailed(tr("Could not determine archive type!"));
|
||||
}
|
||||
|
||||
bool ArchiveDownloadTask::abort()
|
||||
{
|
||||
auto aborted = canAbort();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -43,9 +43,6 @@
|
|||
#include <FileSystem.h>
|
||||
#include <MMCZip.h>
|
||||
#include <io/stream_reader.h>
|
||||
#include <quazip/quazip.h>
|
||||
#include <quazip/quazipdir.h>
|
||||
#include <quazip/quazipfile.h>
|
||||
#include <tag_primitive.h>
|
||||
#include <tag_string.h>
|
||||
#include <sstream>
|
||||
|
|
@ -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;
|
||||
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;
|
||||
}
|
||||
auto location = MMCZip::findFolderOfFileInZip(&zip, "level.dat");
|
||||
m_isValid = !location.isEmpty();
|
||||
if (!m_isValid) {
|
||||
return;
|
||||
m_levelDatTime = file->dateTime();
|
||||
loadFromLevelDat(file->readAll());
|
||||
m_isValid = true;
|
||||
stop = true;
|
||||
}
|
||||
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();
|
||||
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<QString> read_string(nbt::value& parent, const char* name)
|
|||
return nullopt;
|
||||
}
|
||||
auto& tag_str = namedValue.as<nbt::tag_string>();
|
||||
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.";
|
||||
|
|
|
|||
|
|
@ -17,11 +17,10 @@
|
|||
#include <launch/LaunchTask.h>
|
||||
#include <minecraft/MinecraftInstance.h>
|
||||
|
||||
#include <quazip/quazip.h>
|
||||
#include <quazip/quazipdir.h>
|
||||
#include <QDir>
|
||||
#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()
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -44,8 +44,6 @@
|
|||
#include <QPixmap>
|
||||
#include <QPixmapCache>
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "ModDetails.h"
|
||||
#include "Resource.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -35,14 +35,10 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
|
||||
#include "minecraft/mod/MetadataHandler.h"
|
||||
|
||||
struct ModLicense {
|
||||
QString name = {};
|
||||
QString id = {};
|
||||
|
|
|
|||
|
|
@ -23,12 +23,9 @@
|
|||
|
||||
#include "FileSystem.h"
|
||||
#include "Json.h"
|
||||
#include "archive/ArchiveReader.h"
|
||||
#include "minecraft/mod/ResourcePack.h"
|
||||
|
||||
#include <quazip/quazip.h>
|
||||
#include <quazip/quazipdir.h>
|
||||
#include <quazip/quazipfile.h>
|
||||
|
||||
#include <QCryptographicHash>
|
||||
|
||||
namespace DataPackUtils {
|
||||
|
|
@ -106,68 +103,63 @@ 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,29 +303,18 @@ 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
|
||||
|
||||
QuaZipFile file(&zip);
|
||||
if (zip.setCurrentFile("pack.png")) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Failed to open file in zip.";
|
||||
zip.close();
|
||||
MMCZip::ArchiveReader zip(pack->fileinfo().filePath());
|
||||
auto f = zip.goToFile("pack.png");
|
||||
if (!f) {
|
||||
return png_invalid();
|
||||
}
|
||||
|
||||
auto data = file.readAll();
|
||||
auto data = f->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.
|
||||
}
|
||||
return false; // not processed correctly; https://github.com/PrismLauncher/PrismLauncher/issues/1740
|
||||
}
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
#include "LocalModParseTask.h"
|
||||
|
||||
#include <qdcss.h>
|
||||
#include <quazip/quazip.h>
|
||||
#include <quazip/quazipfile.h>
|
||||
#include <toml++/toml.h>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
|
|
@ -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();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
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";
|
||||
}
|
||||
|
||||
if (baseForgePopulated) {
|
||||
details.version = manifestVersion;
|
||||
|
||||
file.close();
|
||||
stop = 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);
|
||||
if (filePath == "mcmod.info") {
|
||||
details = ReadMCModInfo(file->readAll());
|
||||
isValid = true;
|
||||
stop = true;
|
||||
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);
|
||||
if (filePath == "quilt.mod.json") {
|
||||
details = ReadQuiltModInfo(file->readAll());
|
||||
isValid = true;
|
||||
stop = true;
|
||||
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);
|
||||
if (filePath == "fabric.mod.json") {
|
||||
details = ReadFabricModInfo(file->readAll());
|
||||
isValid = true;
|
||||
stop = true;
|
||||
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);
|
||||
if (filePath == "forgeversion.properties") {
|
||||
details = ReadForgeInfo(file->readAll());
|
||||
isValid = true;
|
||||
stop = true;
|
||||
return true;
|
||||
} else if (zip.setCurrentFile("META-INF/nil/mappings.json")) {
|
||||
}
|
||||
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
|
||||
|
||||
QString foundNilMeta;
|
||||
for (auto& fname : zip.getFileNameList()) {
|
||||
isNilMod = true;
|
||||
stop = !nilFilePath.isEmpty();
|
||||
file->skip();
|
||||
return true;
|
||||
}
|
||||
// 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();
|
||||
file->skip();
|
||||
return true;
|
||||
})) {
|
||||
return false;
|
||||
}
|
||||
|
||||
details = ReadNilModInfo(file.readAll(), foundNilMeta);
|
||||
file.close();
|
||||
zip.close();
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,7 @@
|
|||
#include "LocalShaderPackParseTask.h"
|
||||
|
||||
#include "FileSystem.h"
|
||||
|
||||
#include <quazip/quazip.h>
|
||||
#include <quazip/quazipdir.h>
|
||||
#include <quazip/quazipfile.h>
|
||||
#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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,9 +20,7 @@
|
|||
#include "LocalTexturePackParseTask.h"
|
||||
|
||||
#include "FileSystem.h"
|
||||
|
||||
#include <quazip/quazip.h>
|
||||
#include <quazip/quazipfile.h>
|
||||
#include "archive/ArchiveReader.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
|
||||
|
|
@ -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();
|
||||
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();
|
||||
|
||||
file->skip();
|
||||
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!";
|
||||
|
|
|
|||
|
|
@ -23,13 +23,11 @@
|
|||
#include "LocalWorldSaveParseTask.h"
|
||||
|
||||
#include "FileSystem.h"
|
||||
|
||||
#include <quazip/quazip.h>
|
||||
#include <quazip/quazipdir.h>
|
||||
#include <quazip/quazipfile.h>
|
||||
#include "archive/ArchiveReader.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <tuple>
|
||||
|
||||
namespace WorldSaveUtils {
|
||||
|
||||
|
|
@ -105,22 +103,40 @@ bool processFolder(WorldSave& save, ProcessingLevel level)
|
|||
/// QString <name of folder containing level.dat>,
|
||||
/// bool <saves folder found>
|
||||
/// )
|
||||
static std::tuple<bool, QString, bool> contains_level_dat(QuaZip& zip)
|
||||
static std::tuple<bool, QString, bool> 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());
|
||||
}
|
||||
zipDir.cd("..");
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,8 +39,6 @@
|
|||
#include <QtConcurrent>
|
||||
#include <algorithm>
|
||||
|
||||
#include <quazip/quazip.h>
|
||||
|
||||
#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<QString, QString>::of(MMCZip::extractDir), archivePath,
|
||||
extractDir.absolutePath() + "/minecraft");
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, [this]() { downloadMods(); });
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@
|
|||
#include <memory>
|
||||
#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 = "<li><a href=\"{url}\">{name}{authors}</a></li>\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<MMCZip::ExportToZipTask>(m_options.output, m_gameRoot, files, "overrides/", true, false);
|
||||
auto zipTask = makeShared<MMCZip::ExportToZipTask>(m_options.output, m_gameRoot, files, "overrides/", true);
|
||||
zipTask->addExtraFile("manifest.json", generateIndex());
|
||||
zipTask->addExtraFile("modlist.html", generateHTML());
|
||||
|
||||
|
|
|
|||
|
|
@ -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<ModPlatform::ModLoaderType>* 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
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ struct Modpack {
|
|||
using ModpackList = QList<Modpack>;
|
||||
|
||||
Modpack parseDirectory(QString path);
|
||||
|
||||
void legacyInstanceParsing(QString path, std::optional<ModPlatform::ModLoaderType>* loaderType, QString* loaderVersion);
|
||||
} // namespace FTBImportAPP
|
||||
|
||||
// We need it for the proxy model
|
||||
|
|
|
|||
|
|
@ -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<QString, QString>::of(MMCZip::extractDir), archivePath,
|
||||
extractDir.absolutePath() + "/unzip");
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onUnzipFinished);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
#pragma once
|
||||
#include <quazip/quazip.h>
|
||||
#include <quazip/quazipdir.h>
|
||||
#include "InstanceTask.h"
|
||||
#include "PackHelpers.h"
|
||||
#include "meta/Index.h"
|
||||
|
|
@ -39,7 +37,6 @@ class PackInstallTask : public InstanceTask {
|
|||
private: /* data */
|
||||
shared_qobject_ptr<QNetworkAccessManager> m_network;
|
||||
bool abortable = false;
|
||||
std::unique_ptr<QuaZip> m_packZip;
|
||||
QFuture<std::optional<QStringList>> m_extractFuture;
|
||||
QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;
|
||||
NetJob::Ptr netJobContainer;
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
#include <QtConcurrentRun>
|
||||
#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<MMCZip::ExportToZipTask>(output, gameRoot, files, "overrides/", true, true);
|
||||
auto zipTask = makeShared<MMCZip::ExportToZipTask>(output, gameRoot, files, "overrides/", true);
|
||||
zipTask->addExtraFile("modrinth.index.json", generateIndex());
|
||||
|
||||
zipTask->setExcludeFiles(resolvedFiles.keys());
|
||||
|
|
|
|||
|
|
@ -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 {};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<QStringList>::finished, this, &Technic::SingleZipPackInstallTask::extractFinished);
|
||||
|
|
|
|||
|
|
@ -16,10 +16,9 @@
|
|||
#pragma once
|
||||
|
||||
#include "InstanceTask.h"
|
||||
#include "archive/ArchiveReader.h"
|
||||
#include "net/NetJob.h"
|
||||
|
||||
#include <quazip/quazip.h>
|
||||
|
||||
#include <QFutureWatcher>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
|
|
@ -54,7 +53,7 @@ class SingleZipPackInstallTask : public InstanceTask {
|
|||
QString m_minecraftVersion;
|
||||
QString m_archivePath;
|
||||
NetJob::Ptr m_filesNetJob;
|
||||
std::unique_ptr<QuaZip> m_packZip;
|
||||
std::unique_ptr<MMCZip::ArchiveReader> m_packZip;
|
||||
QFuture<std::optional<QStringList>> m_extractFuture;
|
||||
QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -19,12 +19,10 @@
|
|||
#include <Json.h>
|
||||
#include <minecraft/MinecraftInstance.h>
|
||||
#include <minecraft/PackProfile.h>
|
||||
#include <quazip/quazip.h>
|
||||
#include <quazip/quazipdir.h>
|
||||
#include <quazip/quazipfile.h>
|
||||
#include <settings/INISettingsObject.h>
|
||||
|
||||
#include <memory>
|
||||
#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;
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@
|
|||
#include <QMessageBox>
|
||||
#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<MMCZip::ExportToZipTask>(output, m_instance->instanceRoot(), files, "", true, true);
|
||||
auto task = makeShared<MMCZip::ExportToZipTask>(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(); });
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@
|
|||
|
||||
#include "ModFolderPage.h"
|
||||
#include "ui/dialogs/ExportToModListDialog.h"
|
||||
#include "ui/dialogs/InstallLoaderDialog.h"
|
||||
#include "ui_ExternalResourcesPage.h"
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
|
|
@ -145,9 +146,10 @@ void ModFolderPage::downloadMods()
|
|||
|
||||
auto profile = static_cast<MinecraftInstance*>(m_instance)->getPackProfile();
|
||||
if (!profile->getModLoaders().has_value()) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!"));
|
||||
if (handleNoModLoader()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_downloadDialog = new ResourceDownload::ModDownloadDialog(this, m_model, m_instance);
|
||||
connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close);
|
||||
|
|
@ -201,9 +203,10 @@ void ModFolderPage::updateMods(bool includeDeps)
|
|||
|
||||
auto profile = static_cast<MinecraftInstance*>(m_instance)->getPackProfile();
|
||||
if (!profile->getModLoaders().has_value()) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!"));
|
||||
if (handleNoModLoader()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Mod updates are unavailable when metadata is disabled!"));
|
||||
return;
|
||||
|
|
@ -305,9 +308,10 @@ void ModFolderPage::changeModVersion()
|
|||
|
||||
auto profile = static_cast<MinecraftInstance*>(m_instance)->getPackProfile();
|
||||
if (!profile->getModLoaders().has_value()) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!"));
|
||||
if (handleNoModLoader()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Mod updates are unavailable when metadata is disabled!"));
|
||||
return;
|
||||
|
|
@ -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<MinecraftInstance*>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@
|
|||
class ModFolderPage : public ExternalResourcesPage {
|
||||
Q_OBJECT
|
||||
|
||||
inline bool handleNoModLoader();
|
||||
|
||||
public:
|
||||
explicit ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> model, QWidget* parent = nullptr);
|
||||
virtual ~ModFolderPage() = default;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 3460cd809b6dd311b58e92733ece2fc956224fd2
|
||||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 1f820dc98d0a520c175433bcbb0098327d82aac6
|
||||
|
|
@ -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})
|
||||
|
|
@ -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 <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 3fd3b299b875fbd2beac4894b8a870d80022cad7
|
||||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit c4369ae1d8955cae20c4ab40b9813ef4b60e48be
|
||||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 51b7f2abdade71cd9bb0e7a373ef2610ec6f9daf
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
BIN
program_info/org.prismlauncher.PrismLauncher_256.png
Executable file
BIN
program_info/org.prismlauncher.PrismLauncher_256.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"default-registry": {
|
||||
"kind": "git",
|
||||
"baseline": "1fddddc280dfed63956e15ef74f4321bc6a219c9",
|
||||
"baseline": "2d6a6cf3ac9a7cc93942c3d289a2f9c661a6f4a7",
|
||||
"repository": "https://github.com/microsoft/vcpkg"
|
||||
},
|
||||
"registries": [
|
||||
|
|
|
|||
15
vcpkg.json
15
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"
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue