Replace QuaZip (and other archiving operations) with libarchive (#3959)
This commit is contained in:
@@ -9,7 +9,7 @@ runs:
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
sudo apt-get -y install \
|
||||
dpkg-dev \
|
||||
dpkg-dev libarchive-dev \
|
||||
ninja-build extra-cmake-modules scdoc \
|
||||
libqrencode-dev \
|
||||
appstream libxcb-cursor-dev
|
||||
|
||||
@@ -81,7 +81,7 @@ runs:
|
||||
cmark:p
|
||||
qrencode:p
|
||||
tomlplusplus:p
|
||||
quazip-qt6:p
|
||||
libarchive:p
|
||||
|
||||
- name: List pacman packages (MinGW)
|
||||
if: ${{ inputs.msystem != '' }}
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,6 +1,3 @@
|
||||
[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
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
libraries/nbtplusplus
|
||||
libraries/quazip
|
||||
|
||||
@@ -327,14 +327,6 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 6)
|
||||
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()
|
||||
@@ -357,6 +349,14 @@ else()
|
||||
pkg_check_modules(libqrencode REQUIRED IMPORTED_TARGET libqrencode)
|
||||
endif()
|
||||
|
||||
# Find libarchive through CMake packages, mainly for vcpkg
|
||||
find_package(LibArchive QUIET)
|
||||
# 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()
|
||||
|
||||
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
||||
# Find toml++
|
||||
find_package(tomlplusplus 3.2.0 QUIET)
|
||||
@@ -505,14 +505,6 @@ if(FORCE_BUNDLED_ZLIB)
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
@@ -1320,6 +1330,11 @@ 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)
|
||||
target_link_libraries(Launcher_logic
|
||||
@@ -1340,7 +1355,6 @@ target_link_libraries(Launcher_logic
|
||||
${Launcher_QT_LIBS}
|
||||
)
|
||||
target_link_libraries(Launcher_logic
|
||||
QuaZip::QuaZip
|
||||
cmark::cmark
|
||||
LocalPeer
|
||||
Launcher_rainbow
|
||||
@@ -1407,7 +1421,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}
|
||||
systeminfo
|
||||
BuildConfig
|
||||
@@ -1417,6 +1430,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();
|
||||
|
||||
@@ -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;
|
||||
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,194 +177,121 @@ 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();
|
||||
file_name = FS::RemoveInvalidPathChars(file_name);
|
||||
if (!file_name.startsWith(subdir))
|
||||
continue;
|
||||
auto extPtr = ArchiveWriter::createDiskWriter();
|
||||
auto ext = extPtr.get();
|
||||
|
||||
auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(subdir.size()));
|
||||
auto original_name = relative_file_name;
|
||||
|
||||
// Fix subdirs/files ending with a / getting transformed into absolute paths
|
||||
if (relative_file_name.startsWith('/'))
|
||||
relative_file_name = relative_file_name.mid(1);
|
||||
|
||||
// Fix weird "folders with a single file get squashed" thing
|
||||
QString sub_path;
|
||||
if (relative_file_name.contains('/') && !relative_file_name.endsWith('/')) {
|
||||
sub_path = relative_file_name.section('/', 0, -2) + '/';
|
||||
FS::ensureFolderPathExists(FS::PathCombine(target, sub_path));
|
||||
|
||||
relative_file_name = relative_file_name.split('/').last();
|
||||
}
|
||||
|
||||
QString target_file_path;
|
||||
if (relative_file_name.isEmpty()) {
|
||||
target_file_path = target + '/';
|
||||
} else {
|
||||
target_file_path = FS::PathCombine(target_top_dir.toLocalFile(), sub_path, relative_file_name);
|
||||
if (relative_file_name.endsWith('/') && !target_file_path.endsWith('/'))
|
||||
target_file_path += '/';
|
||||
}
|
||||
|
||||
if (!target_top_dir.isParentOf(QUrl::fromLocalFile(target_file_path))) {
|
||||
qWarning() << "Extracting" << relative_file_name << "was cancelled, because it was effectively outside of the target path"
|
||||
<< target;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (!JlCompress::extractFile(zip, "", target_file_path)) {
|
||||
qWarning() << "Failed to extract file" << original_name << "to" << target_file_path;
|
||||
JlCompress::removeFile(extracted);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
extracted.append(target_file_path);
|
||||
auto fileInfo = QFileInfo(target_file_path);
|
||||
if (fileInfo.isFile()) {
|
||||
auto permissions = fileInfo.permissions();
|
||||
auto maxPermisions = QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser |
|
||||
QFileDevice::Permission::ReadGroup | QFileDevice::Permission::ReadOther;
|
||||
auto minPermisions = QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser;
|
||||
|
||||
auto newPermisions = (permissions & maxPermisions) | minPermisions;
|
||||
if (newPermisions != permissions) {
|
||||
if (!QFile::setPermissions(target_file_path, newPermisions)) {
|
||||
qWarning() << (QObject::tr("Could not fix permissions for %1").arg(target_file_path));
|
||||
}
|
||||
if (!zip->parse([&subdir, &target, &target_top_dir, ext, &extracted](ArchiveReader::File* f) {
|
||||
QString file_name = f->filename();
|
||||
file_name = FS::RemoveInvalidPathChars(file_name);
|
||||
if (!file_name.startsWith(subdir)) {
|
||||
f->skip();
|
||||
return true;
|
||||
}
|
||||
} else if (fileInfo.isDir()) {
|
||||
// Ensure the folder has the minimal required permissions
|
||||
QFile::Permissions minimalPermissions = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadGroup |
|
||||
QFile::ExeGroup | QFile::ReadOther | QFile::ExeOther;
|
||||
|
||||
QFile::Permissions currentPermissions = fileInfo.permissions();
|
||||
if ((currentPermissions & minimalPermissions) != minimalPermissions) {
|
||||
if (!QFile::setPermissions(target_file_path, minimalPermissions)) {
|
||||
qWarning() << (QObject::tr("Could not fix permissions for %1").arg(target_file_path));
|
||||
}
|
||||
auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(subdir.size()));
|
||||
auto original_name = relative_file_name;
|
||||
|
||||
// Fix subdirs/files ending with a / getting transformed into absolute paths
|
||||
if (relative_file_name.startsWith('/'))
|
||||
relative_file_name = relative_file_name.mid(1);
|
||||
|
||||
// Fix weird "folders with a single file get squashed" thing
|
||||
QString sub_path;
|
||||
if (relative_file_name.contains('/') && !relative_file_name.endsWith('/')) {
|
||||
sub_path = relative_file_name.section('/', 0, -2) + '/';
|
||||
FS::ensureFolderPathExists(FS::PathCombine(target, sub_path));
|
||||
|
||||
relative_file_name = relative_file_name.split('/').last();
|
||||
}
|
||||
}
|
||||
qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path;
|
||||
} while (zip->goToNextFile());
|
||||
QString target_file_path;
|
||||
if (relative_file_name.isEmpty()) {
|
||||
target_file_path = target + '/';
|
||||
} else {
|
||||
target_file_path = FS::PathCombine(target_top_dir.toLocalFile(), sub_path, relative_file_name);
|
||||
if (relative_file_name.endsWith('/') && !target_file_path.endsWith('/'))
|
||||
target_file_path += '/';
|
||||
}
|
||||
|
||||
if (!target_top_dir.isParentOf(QUrl::fromLocalFile(target_file_path))) {
|
||||
qWarning() << "Extracting" << relative_file_name << "was cancelled, because it was effectively outside of the target path"
|
||||
<< target;
|
||||
return false;
|
||||
}
|
||||
if (!f->writeFile(ext, target_file_path)) {
|
||||
qWarning() << "Failed to extract file" << original_name << "to" << target_file_path;
|
||||
return false;
|
||||
}
|
||||
|
||||
extracted.append(target_file_path);
|
||||
|
||||
qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path;
|
||||
return true;
|
||||
})) {
|
||||
qWarning() << "Failed to parse file" << zip->getZipName();
|
||||
FS::removeFiles(extracted);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return extracted;
|
||||
}
|
||||
|
||||
// ours
|
||||
bool extractRelFile(QuaZip* zip, const QString& file, const QString& target)
|
||||
{
|
||||
return JlCompress::extractFile(zip, file, target);
|
||||
}
|
||||
|
||||
// ours
|
||||
std::optional<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;
|
||||
// check if this is a minimum size empty zip file...
|
||||
QFileInfo fileInfo(fileCompressed);
|
||||
if (fileInfo.size() == 22) {
|
||||
return QStringList();
|
||||
}
|
||||
ArchiveReader zip(fileCompressed);
|
||||
return extractSubDir(&zip, "", dir);
|
||||
}
|
||||
|
||||
// ours
|
||||
std::optional<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;
|
||||
// check if this is a minimum size empty zip file...
|
||||
QFileInfo fileInfo(fileCompressed);
|
||||
if (fileInfo.size() == 22) {
|
||||
return QStringList();
|
||||
}
|
||||
ArchiveReader zip(fileCompressed);
|
||||
return extractSubDir(&zip, subdir, dir);
|
||||
}
|
||||
|
||||
// ours
|
||||
bool extractFile(QString fileCompressed, QString file, QString target)
|
||||
{
|
||||
QuaZip zip(fileCompressed);
|
||||
if (!zip.open(QuaZip::mdUnzip)) {
|
||||
// check if this is a minimum size empty zip file...
|
||||
QFileInfo fileInfo(fileCompressed);
|
||||
if (fileInfo.size() == 22) {
|
||||
return true;
|
||||
}
|
||||
qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError();
|
||||
// check if this is a minimum size empty zip file...
|
||||
QFileInfo fileInfo(fileCompressed);
|
||||
if (fileInfo.size() == 22) {
|
||||
return true;
|
||||
}
|
||||
ArchiveReader zip(fileCompressed);
|
||||
auto f = zip.goToFile(file);
|
||||
if (!f) {
|
||||
return false;
|
||||
}
|
||||
return extractRelFile(&zip, file, target);
|
||||
auto extPtr = ArchiveWriter::createDiskWriter();
|
||||
auto ext = extPtr.get();
|
||||
|
||||
return f->writeFile(ext, target);
|
||||
}
|
||||
|
||||
bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFileFunction excludeFilter)
|
||||
@@ -453,218 +329,4 @@ bool collectFileListRecursively(const QString& rootDir, const QString& subDir, Q
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
void ExportToZipTask::executeTask()
|
||||
{
|
||||
setStatus("Adding files...");
|
||||
setProgress(0, m_files.length());
|
||||
m_build_zip_future = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return exportZip(); });
|
||||
connect(&m_build_zip_watcher, &QFutureWatcher<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,68 +66,45 @@ void ArchiveDownloadTask::executeTask()
|
||||
void ArchiveDownloadTask::extractJava(QString input)
|
||||
{
|
||||
setStatus(tr("Extracting Java"));
|
||||
if (input.endsWith("tar")) {
|
||||
setStatus(tr("Extracting Java (Progress is not reported for tar archives)"));
|
||||
QFile in(input);
|
||||
if (!in.open(QFile::ReadOnly)) {
|
||||
emitFailed(tr("Unable to open supplied tar file."));
|
||||
return;
|
||||
}
|
||||
if (!Tar::extract(&in, QDir(m_final_path).absolutePath())) {
|
||||
emitFailed(tr("Unable to extract supplied tar file."));
|
||||
return;
|
||||
}
|
||||
emitSucceeded();
|
||||
return;
|
||||
} else if (input.endsWith("tar.gz") || input.endsWith("taz") || input.endsWith("tgz")) {
|
||||
setStatus(tr("Extracting Java (Progress is not reported for tar archives)"));
|
||||
if (!GZTar::extract(input, QDir(m_final_path).absolutePath())) {
|
||||
emitFailed(tr("Unable to extract supplied tar file."));
|
||||
return;
|
||||
}
|
||||
emitSucceeded();
|
||||
return;
|
||||
} else if (input.endsWith("zip")) {
|
||||
auto zip = std::make_shared<QuaZip>(input);
|
||||
if (!zip->open(QuaZip::mdUnzip)) {
|
||||
emitFailed(tr("Unable to open supplied zip file."));
|
||||
return;
|
||||
}
|
||||
auto files = zip->getFileNameList();
|
||||
if (files.isEmpty()) {
|
||||
emitFailed(tr("No files were found in the supplied zip file."));
|
||||
return;
|
||||
}
|
||||
m_task = makeShared<MMCZip::ExtractZipTask>(zip, m_final_path, files[0]);
|
||||
|
||||
auto progressStep = std::make_shared<TaskStepProgress>();
|
||||
connect(m_task.get(), &Task::finished, this, [this, progressStep] {
|
||||
progressStep->state = TaskStepState::Succeeded;
|
||||
stepProgress(*progressStep);
|
||||
});
|
||||
|
||||
connect(m_task.get(), &Task::succeeded, this, &ArchiveDownloadTask::emitSucceeded);
|
||||
connect(m_task.get(), &Task::aborted, this, &ArchiveDownloadTask::emitAborted);
|
||||
connect(m_task.get(), &Task::failed, this, [this, progressStep](QString reason) {
|
||||
progressStep->state = TaskStepState::Failed;
|
||||
stepProgress(*progressStep);
|
||||
emitFailed(reason);
|
||||
});
|
||||
connect(m_task.get(), &Task::stepProgress, this, &ArchiveDownloadTask::propagateStepProgress);
|
||||
|
||||
connect(m_task.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) {
|
||||
progressStep->update(current, total);
|
||||
stepProgress(*progressStep);
|
||||
});
|
||||
connect(m_task.get(), &Task::status, this, [this, progressStep](QString status) {
|
||||
progressStep->status = status;
|
||||
stepProgress(*progressStep);
|
||||
});
|
||||
m_task->start();
|
||||
MMCZip::ArchiveReader zip(input);
|
||||
if (!zip.collectFiles()) {
|
||||
emitFailed(tr("Unable to open supplied zip file."));
|
||||
return;
|
||||
}
|
||||
auto files = zip.getFiles();
|
||||
if (files.isEmpty()) {
|
||||
emitFailed(tr("No files were found in the supplied zip file."));
|
||||
return;
|
||||
}
|
||||
auto firstFolderParts = files[0].split('/', Qt::SkipEmptyParts);
|
||||
m_task = makeShared<MMCZip::ExtractZipTask>(input, m_final_path, firstFolderParts.value(0));
|
||||
|
||||
emitFailed(tr("Could not determine archive type!"));
|
||||
auto progressStep = std::make_shared<TaskStepProgress>();
|
||||
connect(m_task.get(), &Task::finished, this, [this, progressStep] {
|
||||
progressStep->state = TaskStepState::Succeeded;
|
||||
stepProgress(*progressStep);
|
||||
});
|
||||
|
||||
connect(m_task.get(), &Task::succeeded, this, &ArchiveDownloadTask::emitSucceeded);
|
||||
connect(m_task.get(), &Task::aborted, this, &ArchiveDownloadTask::emitAborted);
|
||||
connect(m_task.get(), &Task::failed, this, [this, progressStep](QString reason) {
|
||||
progressStep->state = TaskStepState::Failed;
|
||||
stepProgress(*progressStep);
|
||||
emitFailed(reason);
|
||||
});
|
||||
connect(m_task.get(), &Task::stepProgress, this, &ArchiveDownloadTask::propagateStepProgress);
|
||||
|
||||
connect(m_task.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) {
|
||||
progressStep->update(current, total);
|
||||
stepProgress(*progressStep);
|
||||
});
|
||||
connect(m_task.get(), &Task::status, this, [this, progressStep](QString status) {
|
||||
progressStep->status = status;
|
||||
stepProgress(*progressStep);
|
||||
});
|
||||
m_task->start();
|
||||
return;
|
||||
}
|
||||
|
||||
bool ArchiveDownloadTask::abort()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
auto location = MMCZip::findFolderOfFileInZip(&zip, "level.dat");
|
||||
m_isValid = !location.isEmpty();
|
||||
if (!m_isValid) {
|
||||
return;
|
||||
}
|
||||
m_containerOffsetPath = location;
|
||||
QuaZipFile zippedFile(&zip);
|
||||
// read the install profile
|
||||
m_isValid = zip.setCurrentFile(location + "level.dat");
|
||||
if (!m_isValid) {
|
||||
return;
|
||||
}
|
||||
m_isValid = zippedFile.open(QIODevice::ReadOnly);
|
||||
QuaZipFileInfo64 levelDatInfo;
|
||||
zippedFile.getFileInfo(&levelDatInfo);
|
||||
auto modTime = levelDatInfo.getNTFSmTime();
|
||||
if (!modTime.isValid()) {
|
||||
modTime = levelDatInfo.dateTime;
|
||||
}
|
||||
m_levelDatTime = modTime;
|
||||
if (!m_isValid) {
|
||||
return;
|
||||
}
|
||||
loadFromLevelDat(zippedFile.readAll());
|
||||
zippedFile.close();
|
||||
MMCZip::ArchiveReader r(file.absoluteFilePath());
|
||||
|
||||
m_isValid = false;
|
||||
r.parse([this](MMCZip::ArchiveReader::File* file, bool& stop) {
|
||||
const QString levelDat = "level.dat";
|
||||
auto filePath = file->filename();
|
||||
QFileInfo fi(filePath);
|
||||
if (fi.fileName().compare(levelDat, Qt::CaseInsensitive) == 0) {
|
||||
m_containerOffsetPath = filePath.chopped(levelDat.length());
|
||||
if (!m_containerOffsetPath.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
m_levelDatTime = file->dateTime();
|
||||
loadFromLevelDat(file->readAll());
|
||||
m_isValid = true;
|
||||
stop = true;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
bool World::install(const QString& to, const QString& name)
|
||||
@@ -284,10 +271,7 @@ bool World::install(const QString& to, const QString& name)
|
||||
}
|
||||
bool ok = false;
|
||||
if (m_containerFile.isFile()) {
|
||||
QuaZip zip(m_containerFile.absoluteFilePath());
|
||||
if (!zip.open(QuaZip::mdUnzip)) {
|
||||
return false;
|
||||
}
|
||||
MMCZip::ArchiveReader zip(m_containerFile.absoluteFilePath());
|
||||
ok = !MMCZip::extractSubDir(&zip, m_containerOffsetPath, finalPath);
|
||||
} else if (m_containerFile.isDir()) {
|
||||
QString from = m_containerFile.filePath();
|
||||
@@ -350,7 +334,7 @@ optional<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()
|
||||
|
||||
@@ -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,67 +103,62 @@ bool processZIP(DataPack* pack, ProcessingLevel level)
|
||||
{
|
||||
Q_ASSERT(pack->type() == ResourceType::ZIPFILE);
|
||||
|
||||
QuaZip zip(pack->fileinfo().filePath());
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
MMCZip::ArchiveReader zip(pack->fileinfo().filePath());
|
||||
|
||||
bool metaParsed = false;
|
||||
bool iconParsed = false;
|
||||
bool mcmeta_result = false;
|
||||
bool pack_png_result = false;
|
||||
if (!zip.parse(
|
||||
[&metaParsed, &iconParsed, &mcmeta_result, &pack_png_result, pack, level](MMCZip::ArchiveReader::File* f, bool& breakControl) {
|
||||
bool skip = true;
|
||||
if (!metaParsed && f->filename() == "pack.mcmeta") {
|
||||
metaParsed = true;
|
||||
skip = false;
|
||||
auto data = f->readAll();
|
||||
|
||||
mcmeta_result = DataPackUtils::processMCMeta(pack, std::move(data));
|
||||
|
||||
if (!mcmeta_result) {
|
||||
breakControl = true;
|
||||
return true; // mcmeta invalid
|
||||
}
|
||||
}
|
||||
if (!iconParsed && level != ProcessingLevel::BasicInfoOnly && f->filename() == "pack.png") {
|
||||
iconParsed = true;
|
||||
skip = false;
|
||||
auto data = f->readAll();
|
||||
|
||||
pack_png_result = DataPackUtils::processPackPNG(pack, std::move(data));
|
||||
if (!pack_png_result) {
|
||||
breakControl = true;
|
||||
return true; // pack.png invalid
|
||||
}
|
||||
}
|
||||
if (skip) {
|
||||
f->skip();
|
||||
}
|
||||
if (metaParsed && (level == ProcessingLevel::BasicInfoOnly || iconParsed)) {
|
||||
breakControl = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
})) {
|
||||
return false; // can't open zip file
|
||||
|
||||
QuaZipFile file(&zip);
|
||||
|
||||
auto mcmeta_invalid = [&pack]() {
|
||||
}
|
||||
if (!mcmeta_result) {
|
||||
qWarning() << "Data pack at" << pack->fileinfo().filePath() << "does not have a valid pack.mcmeta";
|
||||
return false; // the mcmeta is not optional
|
||||
};
|
||||
|
||||
if (zip.setCurrentFile("pack.mcmeta")) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Failed to open file in zip.";
|
||||
zip.close();
|
||||
return mcmeta_invalid();
|
||||
}
|
||||
|
||||
auto data = file.readAll();
|
||||
|
||||
bool mcmeta_result = DataPackUtils::processMCMeta(pack, std::move(data));
|
||||
|
||||
file.close();
|
||||
if (!mcmeta_result) {
|
||||
return mcmeta_invalid(); // mcmeta invalid
|
||||
}
|
||||
} else {
|
||||
return mcmeta_invalid(); // could not set pack.mcmeta as current file.
|
||||
}
|
||||
|
||||
if (level == ProcessingLevel::BasicInfoOnly) {
|
||||
zip.close();
|
||||
return true; // only need basic info already checked
|
||||
}
|
||||
|
||||
auto png_invalid = [&pack]() {
|
||||
if (!pack_png_result) {
|
||||
qWarning() << "Data pack at" << pack->fileinfo().filePath() << "does not have a valid pack.png";
|
||||
return true; // the png is optional
|
||||
};
|
||||
|
||||
if (zip.setCurrentFile("pack.png")) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Failed to open file in zip.";
|
||||
zip.close();
|
||||
return png_invalid();
|
||||
}
|
||||
|
||||
auto data = file.readAll();
|
||||
|
||||
bool pack_png_result = DataPackUtils::processPackPNG(pack, std::move(data));
|
||||
|
||||
file.close();
|
||||
zip.close();
|
||||
if (!pack_png_result) {
|
||||
return png_invalid(); // pack.png invalid
|
||||
}
|
||||
} else {
|
||||
zip.close();
|
||||
return png_invalid(); // could not set pack.mcmeta as current file.
|
||||
}
|
||||
zip.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -311,28 +303,17 @@ bool processPackPNG(const DataPack* pack)
|
||||
return false; // not processed correctly; https://github.com/PrismLauncher/PrismLauncher/issues/1740
|
||||
}
|
||||
case ResourceType::ZIPFILE: {
|
||||
QuaZip zip(pack->fileinfo().filePath());
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
return false; // can't open zip file
|
||||
MMCZip::ArchiveReader zip(pack->fileinfo().filePath());
|
||||
auto f = zip.goToFile("pack.png");
|
||||
if (!f) {
|
||||
return png_invalid();
|
||||
}
|
||||
auto data = f->readAll();
|
||||
|
||||
QuaZipFile file(&zip);
|
||||
if (zip.setCurrentFile("pack.png")) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Failed to open file in zip.";
|
||||
zip.close();
|
||||
return png_invalid();
|
||||
}
|
||||
bool pack_png_result = DataPackUtils::processPackPNG(pack, std::move(data));
|
||||
|
||||
auto data = file.readAll();
|
||||
|
||||
bool pack_png_result = DataPackUtils::processPackPNG(pack, std::move(data));
|
||||
|
||||
file.close();
|
||||
if (!pack_png_result) {
|
||||
return png_invalid(); // pack.png invalid
|
||||
}
|
||||
} else {
|
||||
return png_invalid(); // could not set pack.mcmeta as current file.
|
||||
if (!pack_png_result) {
|
||||
return png_invalid(); // pack.png invalid
|
||||
}
|
||||
return false; // not processed correctly; https://github.com/PrismLauncher/PrismLauncher/issues/1740
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
details = ReadMCModTOML(file.readAll());
|
||||
file.close();
|
||||
|
||||
// to replace ${file.jarVersion} with the actual version, as needed
|
||||
if (details.version == "${file.jarVersion}") {
|
||||
if (zip.setCurrentFile("META-INF/MANIFEST.MF")) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
zip.close();
|
||||
return false;
|
||||
if (filePath == "META-INF/mods.toml" || filePath == "META-INF/neoforge.mods.toml") {
|
||||
details = ReadMCModTOML(file->readAll());
|
||||
isValid = true;
|
||||
if (details.version == "${file.jarVersion}" && !manifestVersion.isEmpty()) {
|
||||
details.version = manifestVersion;
|
||||
}
|
||||
|
||||
stop = details.version != "${file.jarVersion}";
|
||||
baseForgePopulated = true;
|
||||
return true;
|
||||
}
|
||||
if (filePath == "META-INF/MANIFEST.MF") {
|
||||
// quick and dirty line-by-line parser
|
||||
auto manifestLines = QString(file.readAll()).split(s_newlineRegex);
|
||||
QString manifestVersion = "";
|
||||
auto manifestLines = QString(file->readAll()).split(s_newlineRegex);
|
||||
manifestVersion = "";
|
||||
for (auto& line : manifestLines) {
|
||||
if (line.startsWith("Implementation-Version: ", Qt::CaseInsensitive)) {
|
||||
manifestVersion = line.remove("Implementation-Version: ", Qt::CaseInsensitive);
|
||||
@@ -508,94 +508,64 @@ bool processZIP(Mod& mod, [[maybe_unused]] ProcessingLevel level)
|
||||
if (manifestVersion.contains("task ':jar' property 'archiveVersion'") || manifestVersion == "") {
|
||||
manifestVersion = "NONE";
|
||||
}
|
||||
|
||||
details.version = manifestVersion;
|
||||
|
||||
file.close();
|
||||
if (baseForgePopulated) {
|
||||
details.version = manifestVersion;
|
||||
stop = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (filePath == "mcmod.info") {
|
||||
details = ReadMCModInfo(file->readAll());
|
||||
isValid = true;
|
||||
stop = true;
|
||||
return true;
|
||||
}
|
||||
if (filePath == "quilt.mod.json") {
|
||||
details = ReadQuiltModInfo(file->readAll());
|
||||
isValid = true;
|
||||
stop = true;
|
||||
return true;
|
||||
}
|
||||
if (filePath == "fabric.mod.json") {
|
||||
details = ReadFabricModInfo(file->readAll());
|
||||
isValid = true;
|
||||
stop = true;
|
||||
return true;
|
||||
}
|
||||
if (filePath == "forgeversion.properties") {
|
||||
details = ReadForgeInfo(file->readAll());
|
||||
isValid = true;
|
||||
stop = true;
|
||||
return true;
|
||||
}
|
||||
if (filePath == "META-INF/nil/mappings.json") {
|
||||
// nilloader uses the filename of the metadata file for the modid, so we can't know the exact filename
|
||||
// thankfully, there is a good file to use as a canary so we don't look for nil meta all the time
|
||||
isNilMod = true;
|
||||
stop = !nilFilePath.isEmpty();
|
||||
file->skip();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
zip.close();
|
||||
mod.setDetails(details);
|
||||
|
||||
return true;
|
||||
} else if (zip.setCurrentFile("mcmod.info")) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
zip.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
details = ReadMCModInfo(file.readAll());
|
||||
file.close();
|
||||
zip.close();
|
||||
|
||||
mod.setDetails(details);
|
||||
return true;
|
||||
} else if (zip.setCurrentFile("quilt.mod.json")) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
zip.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
details = ReadQuiltModInfo(file.readAll());
|
||||
file.close();
|
||||
zip.close();
|
||||
|
||||
mod.setDetails(details);
|
||||
return true;
|
||||
} else if (zip.setCurrentFile("fabric.mod.json")) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
zip.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
details = ReadFabricModInfo(file.readAll());
|
||||
file.close();
|
||||
zip.close();
|
||||
|
||||
mod.setDetails(details);
|
||||
return true;
|
||||
} else if (zip.setCurrentFile("forgeversion.properties")) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
zip.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
details = ReadForgeInfo(file.readAll());
|
||||
file.close();
|
||||
zip.close();
|
||||
|
||||
mod.setDetails(details);
|
||||
return true;
|
||||
} else if (zip.setCurrentFile("META-INF/nil/mappings.json")) {
|
||||
// nilloader uses the filename of the metadata file for the modid, so we can't know the exact filename
|
||||
// thankfully, there is a good file to use as a canary so we don't look for nil meta all the time
|
||||
|
||||
QString foundNilMeta;
|
||||
for (auto& fname : zip.getFileNameList()) {
|
||||
// nilmods can shade nilloader to be able to run as a standalone agent - which includes nilloader's own meta file
|
||||
if (fname.endsWith(".nilmod.css") && fname != "nilloader.nilmod.css") {
|
||||
foundNilMeta = fname;
|
||||
break;
|
||||
if (filePath.endsWith(".nilmod.css") && filePath != "nilloader.nilmod.css") {
|
||||
nilData = file->readAll();
|
||||
nilFilePath = filePath;
|
||||
stop = isNilMod;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (zip.setCurrentFile(foundNilMeta)) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
zip.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
details = ReadNilModInfo(file.readAll(), foundNilMeta);
|
||||
file.close();
|
||||
zip.close();
|
||||
|
||||
mod.setDetails(details);
|
||||
file->skip();
|
||||
return true;
|
||||
}
|
||||
})) {
|
||||
return false;
|
||||
}
|
||||
if (isNilMod) {
|
||||
details = ReadNilModInfo(nilData, nilFilePath);
|
||||
isValid = true;
|
||||
}
|
||||
if (isValid) {
|
||||
mod.setDetails(details);
|
||||
return true;
|
||||
}
|
||||
|
||||
zip.close();
|
||||
return false; // no valid mod found in archive
|
||||
}
|
||||
|
||||
@@ -624,25 +594,14 @@ bool processLitemod(Mod& mod, [[maybe_unused]] ProcessingLevel level)
|
||||
{
|
||||
ModDetails details;
|
||||
|
||||
QuaZip zip(mod.fileinfo().filePath());
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
return false;
|
||||
MMCZip::ArchiveReader zip(mod.fileinfo().filePath());
|
||||
|
||||
QuaZipFile file(&zip);
|
||||
|
||||
if (zip.setCurrentFile("litemod.json")) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
zip.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
details = ReadLiteModInfo(file.readAll());
|
||||
file.close();
|
||||
if (auto file = zip.goToFile("litemod.json"); file) {
|
||||
details = ReadLiteModInfo(file->readAll());
|
||||
|
||||
mod.setDetails(details);
|
||||
return true;
|
||||
}
|
||||
zip.close();
|
||||
|
||||
return false; // no valid litemod.json found in archive
|
||||
}
|
||||
@@ -700,24 +659,13 @@ bool loadIconFile(const Mod& mod, QPixmap* pixmap)
|
||||
return png_invalid("file '" + icon_info.filePath() + "' does not exists or is not a file");
|
||||
}
|
||||
case ResourceType::ZIPFILE: {
|
||||
QuaZip zip(mod.fileinfo().filePath());
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
return png_invalid("failed to open '" + mod.fileinfo().filePath() + "' as a zip archive");
|
||||
|
||||
QuaZipFile file(&zip);
|
||||
|
||||
if (zip.setCurrentFile(mod.iconPath())) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Failed to open file in zip.";
|
||||
zip.close();
|
||||
return png_invalid("Failed to open '" + mod.iconPath() + "' in zip archive");
|
||||
}
|
||||
|
||||
auto data = file.readAll();
|
||||
MMCZip::ArchiveReader zip(mod.fileinfo().filePath());
|
||||
auto file = zip.goToFile(mod.iconPath());
|
||||
if (file) {
|
||||
auto data = file->readAll();
|
||||
|
||||
bool icon_result = ModUtils::processIconPNG(mod, std::move(data), pixmap);
|
||||
|
||||
file.close();
|
||||
if (!icon_result) {
|
||||
return png_invalid("invalid png image"); // icon png invalid
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
file->skip();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (zip.setCurrentFile("pack.png")) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Failed to open file in zip.";
|
||||
zip.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto data = file.readAll();
|
||||
|
||||
bool packPNG_result = TexturePackUtils::processPackPNG(pack, std::move(data));
|
||||
|
||||
file.close();
|
||||
zip.close();
|
||||
if (!packPNG_result) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
zip.close();
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
bool processPackTXT(TexturePack& pack, QByteArray&& raw_data)
|
||||
@@ -189,32 +158,19 @@ bool processPackPNG(const TexturePack& pack)
|
||||
return false;
|
||||
}
|
||||
case ResourceType::ZIPFILE: {
|
||||
QuaZip zip(pack.fileinfo().filePath());
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
return false; // can't open zip file
|
||||
MMCZip::ArchiveReader zip(pack.fileinfo().filePath());
|
||||
|
||||
QuaZipFile file(&zip);
|
||||
if (zip.setCurrentFile("pack.png")) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Failed to open file in zip.";
|
||||
zip.close();
|
||||
return png_invalid();
|
||||
}
|
||||
|
||||
auto data = file.readAll();
|
||||
auto file = zip.goToFile("pack.png");
|
||||
if (file) {
|
||||
auto data = file->readAll();
|
||||
|
||||
bool pack_png_result = TexturePackUtils::processPackPNG(pack, std::move(data));
|
||||
|
||||
file.close();
|
||||
if (!pack_png_result) {
|
||||
zip.close();
|
||||
return png_invalid(); // pack.png invalid
|
||||
}
|
||||
} else {
|
||||
zip.close();
|
||||
return png_invalid(); // could not set pack.mcmeta as current file.
|
||||
}
|
||||
return false;
|
||||
return png_invalid(); // could not set pack.mcmeta as current file.
|
||||
}
|
||||
default:
|
||||
qWarning() << "Invalid type for resource pack parse task!";
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
if (!relativePath.endsWith("/level.dat", Qt::CaseInsensitive))
|
||||
continue;
|
||||
|
||||
int slashIndex = relativePath.indexOf('/');
|
||||
if (slashIndex == -1)
|
||||
continue; // malformed: no slash between saves/ and level.dat
|
||||
|
||||
QString worldName = relativePath.left(slashIndex);
|
||||
QString remaining = relativePath.mid(slashIndex + 1);
|
||||
|
||||
// Check that there's nothing between worldName/ and level.dat
|
||||
if (remaining == "level.dat") {
|
||||
return std::make_tuple(true, worldName, saves);
|
||||
}
|
||||
zipDir.cd("..");
|
||||
}
|
||||
|
||||
return std::make_tuple(false, "", saves);
|
||||
}
|
||||
|
||||
@@ -128,19 +144,14 @@ bool processZIP(WorldSave& save, ProcessingLevel level)
|
||||
{
|
||||
Q_ASSERT(save.type() == ResourceType::ZIPFILE);
|
||||
|
||||
QuaZip zip(save.fileinfo().filePath());
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
return false; // can't open zip file
|
||||
|
||||
auto [found, save_dir_name, found_saves_dir] = contains_level_dat(zip);
|
||||
|
||||
if (save_dir_name.endsWith("/")) {
|
||||
save_dir_name.chop(1);
|
||||
}
|
||||
auto [found, save_dir_name, found_saves_dir] = contains_level_dat(save.fileinfo().filePath());
|
||||
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
if (save_dir_name.endsWith("/")) {
|
||||
save_dir_name.chop(1);
|
||||
}
|
||||
|
||||
save.setSaveDirName(save_dir_name);
|
||||
|
||||
@@ -151,14 +162,11 @@ bool processZIP(WorldSave& save, ProcessingLevel level)
|
||||
}
|
||||
|
||||
if (level == ProcessingLevel::BasicInfoOnly) {
|
||||
zip.close();
|
||||
return true; // only need basic info already checked
|
||||
}
|
||||
|
||||
// reserved for more intensive processing
|
||||
|
||||
zip.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(); });
|
||||
|
||||
@@ -107,11 +107,13 @@ See [github repo](https://github.com/nayuki/QR-Code-generator).
|
||||
|
||||
MIT
|
||||
|
||||
## quazip
|
||||
## libarchive
|
||||
|
||||
A zip manipulation library.
|
||||
Multi-format archive and compression library.
|
||||
|
||||
LGPL 2.1 with linking exception.
|
||||
See [github repo](https://github.com/libarchive/libarchive).
|
||||
|
||||
BSD 2-Clause license with some exception.
|
||||
|
||||
## rainbow
|
||||
|
||||
|
||||
Submodule libraries/quazip deleted from 3fd3b299b8
@@ -16,6 +16,7 @@
|
||||
zlib,
|
||||
msaClientID ? null,
|
||||
gamemodeSupport ? stdenv.hostPlatform.isLinux,
|
||||
libarchive,
|
||||
}:
|
||||
assert lib.assertMsg (
|
||||
gamemodeSupport -> stdenv.hostPlatform.isLinux
|
||||
@@ -76,8 +77,9 @@ stdenv.mkDerivation {
|
||||
cmark
|
||||
kdePackages.qtbase
|
||||
kdePackages.qtnetworkauth
|
||||
kdePackages.quazip
|
||||
kdePackages.qt5compat
|
||||
qrencode
|
||||
libarchive
|
||||
tomlplusplus
|
||||
zlib
|
||||
]
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user