mirror of
https://github.com/PrismLauncher/PrismLauncher.git
synced 2025-08-03 19:37:45 -04:00
move some functions from MMCZip to use libarchive
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
parent
b2aee9e846
commit
270ff012ae
@ -346,15 +346,14 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 6)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(libarchive IMPORTED_TARGET libarchive)
|
||||||
# Find toml++
|
# Find toml++
|
||||||
find_package(tomlplusplus 3.2.0 QUIET)
|
find_package(tomlplusplus 3.2.0 QUIET)
|
||||||
# Fallback to pkg-config (if available) if CMake files aren't found
|
# Fallback to pkg-config (if available) if CMake files aren't found
|
||||||
if(NOT tomlplusplus_FOUND)
|
if(NOT tomlplusplus_FOUND)
|
||||||
find_package(PkgConfig)
|
|
||||||
if(PkgConfig_FOUND)
|
|
||||||
pkg_check_modules(tomlplusplus IMPORTED_TARGET tomlplusplus>=3.2.0)
|
pkg_check_modules(tomlplusplus IMPORTED_TARGET tomlplusplus>=3.2.0)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
|
||||||
|
|
||||||
|
|
||||||
# Find cmark
|
# Find cmark
|
||||||
@ -363,14 +362,6 @@ if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
|||||||
# Find qrcodegencpp-cmake
|
# Find qrcodegencpp-cmake
|
||||||
find_package(qrcodegencpp QUIET)
|
find_package(qrcodegencpp QUIET)
|
||||||
|
|
||||||
find_package(LibArchive 3.7.8 QUIET)
|
|
||||||
# Fallback to pkg-config (if available) if CMake files aren't found
|
|
||||||
if(NOT LibArchive_FOUND)
|
|
||||||
find_package(PkgConfig)
|
|
||||||
if(PkgConfig_FOUND)
|
|
||||||
pkg_check_modules(LibArchive IMPORTED_TARGET LibArchive>=3.7.8)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
include(ECMQtDeclareLoggingCategory)
|
include(ECMQtDeclareLoggingCategory)
|
||||||
|
@ -26,6 +26,8 @@ set(CORE_SOURCES
|
|||||||
NullInstance.h
|
NullInstance.h
|
||||||
MMCZip.h
|
MMCZip.h
|
||||||
MMCZip.cpp
|
MMCZip.cpp
|
||||||
|
archive/ArchiveReader.cpp
|
||||||
|
archive/ArchiveReader.h
|
||||||
archive/ArchiveWriter.cpp
|
archive/ArchiveWriter.cpp
|
||||||
archive/ArchiveWriter.h
|
archive/ArchiveWriter.h
|
||||||
archive/ExportToZipTask.cpp
|
archive/ExportToZipTask.cpp
|
||||||
@ -1326,6 +1328,7 @@ target_link_libraries(Launcher_logic
|
|||||||
BuildConfig
|
BuildConfig
|
||||||
Qt${QT_VERSION_MAJOR}::Widgets
|
Qt${QT_VERSION_MAJOR}::Widgets
|
||||||
qrcodegenerator
|
qrcodegenerator
|
||||||
|
PkgConfig::libarchive
|
||||||
)
|
)
|
||||||
if(TARGET PkgConfig::tomlplusplus)
|
if(TARGET PkgConfig::tomlplusplus)
|
||||||
target_link_libraries(Launcher_logic PkgConfig::tomlplusplus)
|
target_link_libraries(Launcher_logic PkgConfig::tomlplusplus)
|
||||||
@ -1333,12 +1336,6 @@ else()
|
|||||||
target_link_libraries(Launcher_logic tomlplusplus::tomlplusplus)
|
target_link_libraries(Launcher_logic tomlplusplus::tomlplusplus)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(TARGET PkgConfig::LibArchive)
|
|
||||||
target_link_libraries(Launcher_logic PkgConfig::LibArchive)
|
|
||||||
else()
|
|
||||||
target_link_libraries(Launcher_logic LibArchive::LibArchive)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (UNIX AND NOT CYGWIN AND NOT APPLE)
|
if (UNIX AND NOT CYGWIN AND NOT APPLE)
|
||||||
target_link_libraries(Launcher_logic
|
target_link_libraries(Launcher_logic
|
||||||
gamemode
|
gamemode
|
||||||
|
@ -36,15 +36,18 @@
|
|||||||
|
|
||||||
#include "MMCZip.h"
|
#include "MMCZip.h"
|
||||||
#include <archive.h>
|
#include <archive.h>
|
||||||
|
#include <qcontainerfwd.h>
|
||||||
#include <quazip/quazip.h>
|
#include <quazip/quazip.h>
|
||||||
#include <quazip/quazipdir.h>
|
#include <quazip/quazipdir.h>
|
||||||
#include <quazip/quazipfile.h>
|
#include <quazip/quazipfile.h>
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
|
#include "archive/ArchiveReader.h"
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#if defined(LAUNCHER_APPLICATION)
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
#include <QtConcurrentRun>
|
#include <QtConcurrentRun>
|
||||||
@ -57,39 +60,26 @@ using FilterFunction = std::function<bool(const QString&)>;
|
|||||||
#if defined(LAUNCHER_APPLICATION)
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
bool mergeZipFiles(ArchiveWriter& into, QFileInfo from, QSet<QString>& contained, const FilterFunction& filter = nullptr)
|
bool mergeZipFiles(ArchiveWriter& into, QFileInfo from, QSet<QString>& contained, const FilterFunction& filter = nullptr)
|
||||||
{
|
{
|
||||||
std::unique_ptr<struct archive, int (*)(struct archive*)> modZip(archive_read_new(), archive_read_free);
|
ArchiveReader r(from.absoluteFilePath());
|
||||||
if (!modZip) {
|
return r.parse([&into, &contained, &filter, from](ArchiveReader::File* f) {
|
||||||
qCritical() << "Failed to create archive";
|
auto filename = f->filename();
|
||||||
}
|
|
||||||
archive_read_support_format_all(modZip.get());
|
|
||||||
|
|
||||||
auto fromUtf8 = from.absoluteFilePath().toUtf8();
|
|
||||||
if (archive_read_open_filename(modZip.get(), fromUtf8.constData(), 10240) != ARCHIVE_OK) {
|
|
||||||
qCritical() << "Failed to open file:" << from << ": " << archive_error_string(modZip.get());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
archive_entry* entry;
|
|
||||||
|
|
||||||
while (archive_read_next_header(modZip.get(), &entry) == ARCHIVE_OK) {
|
|
||||||
auto name = archive_entry_pathname(entry);
|
|
||||||
auto filename = QString::fromStdString(name);
|
|
||||||
if (filter && !filter(filename)) {
|
if (filter && !filter(filename)) {
|
||||||
qDebug() << "Skipping file " << filename << " from " << from.fileName() << " - filtered";
|
qDebug() << "Skipping file " << filename << " from " << from.fileName() << " - filtered";
|
||||||
continue;
|
f->skip();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
if (contained.contains(filename)) {
|
if (contained.contains(filename)) {
|
||||||
qDebug() << "Skipping already contained file " << filename << " from " << from.fileName();
|
qDebug() << "Skipping already contained file " << filename << " from " << from.fileName();
|
||||||
continue;
|
f->skip();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
contained.insert(filename);
|
contained.insert(filename);
|
||||||
if (!into.addFile(modZip.get(), entry)) {
|
if (!into.addFile(f)) {
|
||||||
qCritical() << "Failed to copy data of " << filename << " into the jar";
|
qCritical() << "Failed to copy data of " << filename << " into the jar";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool compressDirFiles(ArchiveWriter& zip, QString dir, QFileInfoList files)
|
bool compressDirFiles(ArchiveWriter& zip, QString dir, QFileInfoList files)
|
||||||
@ -195,54 +185,45 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<M
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// ours
|
// ours
|
||||||
QString findFolderOfFileInZip(QuaZip* zip, const QString& what, const QStringList& ignore_paths, const QString& root)
|
std::optional<QStringList> extractSubDir(ArchiveReader* zip, const QString& subdir, const QString& target)
|
||||||
{
|
|
||||||
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
|
|
||||||
std::optional<QStringList> extractSubDir(QuaZip* zip, const QString& subdir, const QString& target)
|
|
||||||
{
|
{
|
||||||
auto target_top_dir = QUrl::fromLocalFile(target);
|
auto target_top_dir = QUrl::fromLocalFile(target);
|
||||||
|
|
||||||
QStringList extracted;
|
QStringList extracted;
|
||||||
|
|
||||||
qDebug() << "Extracting subdir" << subdir << "from" << zip->getZipName() << "to" << target;
|
qDebug() << "Extracting subdir" << subdir << "from" << zip->getZipName() << "to" << target;
|
||||||
auto numEntries = zip->getEntriesCount();
|
if (!zip->collectFiles()) {
|
||||||
if (numEntries < 0) {
|
|
||||||
qWarning() << "Failed to enumerate files in archive";
|
qWarning() << "Failed to enumerate files in archive";
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
} else if (numEntries == 0) {
|
}
|
||||||
|
if (zip->getFiles().isEmpty()) {
|
||||||
qDebug() << "Extracting empty archives seems odd...";
|
qDebug() << "Extracting empty archives seems odd...";
|
||||||
return extracted;
|
return extracted;
|
||||||
} else if (!zip->goToFirstFile()) {
|
|
||||||
qWarning() << "Failed to seek to first file in zip";
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
int flags;
|
||||||
QString file_name = zip->getCurrentFileName();
|
|
||||||
|
/* Select which attributes we want to restore. */
|
||||||
|
flags = ARCHIVE_EXTRACT_TIME;
|
||||||
|
flags |= ARCHIVE_EXTRACT_PERM;
|
||||||
|
flags |= ARCHIVE_EXTRACT_ACL;
|
||||||
|
flags |= ARCHIVE_EXTRACT_FFLAGS;
|
||||||
|
|
||||||
|
std::unique_ptr<archive, void (*)(archive*)> extPtr(archive_write_disk_new(), [](archive* a) {
|
||||||
|
archive_write_close(a);
|
||||||
|
archive_write_free(a);
|
||||||
|
});
|
||||||
|
auto ext = extPtr.get();
|
||||||
|
archive_write_disk_set_options(ext, flags);
|
||||||
|
archive_write_disk_set_standard_lookup(ext);
|
||||||
|
|
||||||
|
if (!zip->parse([&subdir, &target, &target_top_dir, ext, &extracted](ArchiveReader::File* f) {
|
||||||
|
QString file_name = f->filename();
|
||||||
file_name = FS::RemoveInvalidPathChars(file_name);
|
file_name = FS::RemoveInvalidPathChars(file_name);
|
||||||
if (!file_name.startsWith(subdir))
|
if (!file_name.startsWith(subdir)) {
|
||||||
continue;
|
f->skip();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(subdir.size()));
|
auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(subdir.size()));
|
||||||
auto original_name = relative_file_name;
|
auto original_name = relative_file_name;
|
||||||
@ -259,7 +240,6 @@ std::optional<QStringList> extractSubDir(QuaZip* zip, const QString& subdir, con
|
|||||||
|
|
||||||
relative_file_name = relative_file_name.split('/').last();
|
relative_file_name = relative_file_name.split('/').last();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString target_file_path;
|
QString target_file_path;
|
||||||
if (relative_file_name.isEmpty()) {
|
if (relative_file_name.isEmpty()) {
|
||||||
target_file_path = target + '/';
|
target_file_path = target + '/';
|
||||||
@ -272,101 +252,80 @@ std::optional<QStringList> extractSubDir(QuaZip* zip, const QString& subdir, con
|
|||||||
if (!target_top_dir.isParentOf(QUrl::fromLocalFile(target_file_path))) {
|
if (!target_top_dir.isParentOf(QUrl::fromLocalFile(target_file_path))) {
|
||||||
qWarning() << "Extracting" << relative_file_name << "was cancelled, because it was effectively outside of the target path"
|
qWarning() << "Extracting" << relative_file_name << "was cancelled, because it was effectively outside of the target path"
|
||||||
<< target;
|
<< target;
|
||||||
return std::nullopt;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!f->writeFile(ext, target_file_path)) {
|
||||||
if (!JlCompress::extractFile(zip, "", target_file_path)) {
|
|
||||||
qWarning() << "Failed to extract file" << original_name << "to" << target_file_path;
|
qWarning() << "Failed to extract file" << original_name << "to" << target_file_path;
|
||||||
JlCompress::removeFile(extracted);
|
return false;
|
||||||
return std::nullopt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extracted.append(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)) {
|
|
||||||
qWarning() << (QObject::tr("Could not fix permissions for %1").arg(target_file_path));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (fileInfo.isDir()) {
|
|
||||||
// Ensure the folder has the minimal required permissions
|
|
||||||
QFile::Permissions minimalPermissions = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadGroup |
|
|
||||||
QFile::ExeGroup | QFile::ReadOther | QFile::ExeOther;
|
|
||||||
|
|
||||||
QFile::Permissions currentPermissions = fileInfo.permissions();
|
|
||||||
if ((currentPermissions & minimalPermissions) != minimalPermissions) {
|
|
||||||
if (!QFile::setPermissions(target_file_path, minimalPermissions)) {
|
|
||||||
qWarning() << (QObject::tr("Could not fix permissions for %1").arg(target_file_path));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path;
|
qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path;
|
||||||
} while (zip->goToNextFile());
|
return true;
|
||||||
|
})) {
|
||||||
|
qWarning() << "Failed to parse file" << zip->getZipName();
|
||||||
|
JlCompress::removeFile(extracted);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
return extracted;
|
return extracted;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ours
|
|
||||||
bool extractRelFile(QuaZip* zip, const QString& file, const QString& target)
|
|
||||||
{
|
|
||||||
return JlCompress::extractFile(zip, file, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ours
|
// ours
|
||||||
std::optional<QStringList> extractDir(QString fileCompressed, QString dir)
|
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...
|
// check if this is a minimum size empty zip file...
|
||||||
QFileInfo fileInfo(fileCompressed);
|
QFileInfo fileInfo(fileCompressed);
|
||||||
if (fileInfo.size() == 22) {
|
if (fileInfo.size() == 22) {
|
||||||
return QStringList();
|
return QStringList();
|
||||||
}
|
}
|
||||||
qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError();
|
ArchiveReader zip(fileCompressed);
|
||||||
;
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
return extractSubDir(&zip, "", dir);
|
return extractSubDir(&zip, "", dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ours
|
// ours
|
||||||
std::optional<QStringList> extractDir(QString fileCompressed, QString subdir, QString dir)
|
std::optional<QStringList> extractDir(QString fileCompressed, QString subdir, QString dir)
|
||||||
{
|
{
|
||||||
QuaZip zip(fileCompressed);
|
|
||||||
if (!zip.open(QuaZip::mdUnzip)) {
|
|
||||||
// check if this is a minimum size empty zip file...
|
// check if this is a minimum size empty zip file...
|
||||||
QFileInfo fileInfo(fileCompressed);
|
QFileInfo fileInfo(fileCompressed);
|
||||||
if (fileInfo.size() == 22) {
|
if (fileInfo.size() == 22) {
|
||||||
return QStringList();
|
return QStringList();
|
||||||
}
|
}
|
||||||
qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError();
|
ArchiveReader zip(fileCompressed);
|
||||||
;
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
return extractSubDir(&zip, subdir, dir);
|
return extractSubDir(&zip, subdir, dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ours
|
// ours
|
||||||
bool extractFile(QString fileCompressed, QString file, QString target)
|
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...
|
// check if this is a minimum size empty zip file...
|
||||||
QFileInfo fileInfo(fileCompressed);
|
QFileInfo fileInfo(fileCompressed);
|
||||||
if (fileInfo.size() == 22) {
|
if (fileInfo.size() == 22) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError();
|
ArchiveReader zip(fileCompressed);
|
||||||
|
auto f = zip.goToFile(file);
|
||||||
|
if (!f) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return extractRelFile(&zip, file, target);
|
int flags;
|
||||||
|
|
||||||
|
/* Select which attributes we want to restore. */
|
||||||
|
flags = ARCHIVE_EXTRACT_TIME;
|
||||||
|
flags |= ARCHIVE_EXTRACT_PERM;
|
||||||
|
flags |= ARCHIVE_EXTRACT_ACL;
|
||||||
|
flags |= ARCHIVE_EXTRACT_FFLAGS;
|
||||||
|
|
||||||
|
std::unique_ptr<archive, void (*)(archive*)> extPtr(archive_write_disk_new(), [](archive* a) {
|
||||||
|
archive_write_close(a);
|
||||||
|
archive_write_free(a);
|
||||||
|
});
|
||||||
|
auto ext = extPtr.get();
|
||||||
|
archive_write_disk_set_options(ext, flags);
|
||||||
|
archive_write_disk_set_standard_lookup(ext);
|
||||||
|
|
||||||
|
return f->writeFile(ext, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFileFunction excludeFilter)
|
bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFileFunction excludeFilter)
|
||||||
|
@ -48,6 +48,7 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include "archive/ArchiveReader.h"
|
||||||
|
|
||||||
#if defined(LAUNCHER_APPLICATION)
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
#include "minecraft/mod/Mod.h"
|
#include "minecraft/mod/Mod.h"
|
||||||
@ -63,21 +64,11 @@ using FilterFileFunction = std::function<bool(const QFileInfo&)>;
|
|||||||
*/
|
*/
|
||||||
bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods);
|
bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods);
|
||||||
#endif
|
#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(""));
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract a subdirectory from an archive
|
* Extract a subdirectory from an archive
|
||||||
*/
|
*/
|
||||||
std::optional<QStringList> extractSubDir(QuaZip* zip, const QString& subdir, const QString& target);
|
std::optional<QStringList> extractSubDir(ArchiveReader* zip, const QString& subdir, const QString& target);
|
||||||
|
|
||||||
bool extractRelFile(QuaZip* zip, const QString& file, const QString& target);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract a whole archive.
|
* Extract a whole archive.
|
||||||
|
170
launcher/archive/ArchiveReader.cpp
Normal file
170
launcher/archive/ArchiveReader.cpp
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
#include "ArchiveReader.h"
|
||||||
|
#include <archive_entry.h>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
archive_read_close(m_archive.get());
|
||||||
|
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(), 10240) != 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 dinish entry:" << archive_error_string(out);
|
||||||
|
return (r > ARCHIVE_WARN);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ArchiveReader::parse(std::function<bool(File*)> 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(), 10240) != ARCHIVE_OK) {
|
||||||
|
qCritical() << "Failed to open archive file:" << m_archivePath << "-" << f->error();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (f->readNextHeader() == ARCHIVE_OK) {
|
||||||
|
if (!doStuff(f.get())) {
|
||||||
|
qCritical() << "Failed to parse file:" << f->filename() << "-" << f->error();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
archive_read_close(a);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
} // namespace MMCZip
|
53
launcher/archive/ArchiveReader.h
Normal file
53
launcher/archive/ArchiveReader.h
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <archive.h>
|
||||||
|
#include <archive_entry.h>
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
class File {
|
||||||
|
public:
|
||||||
|
File() : m_archive(ArchivePtr(archive_read_new(), archive_read_free)) {}
|
||||||
|
virtual ~File() {}
|
||||||
|
|
||||||
|
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*)>);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_archivePath;
|
||||||
|
|
||||||
|
QStringList m_fileNames = {};
|
||||||
|
};
|
||||||
|
} // namespace MMCZip
|
@ -172,40 +172,8 @@ bool ArchiveWriter::addFile(const QString& fileDest, const QByteArray& data)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ArchiveWriter::addFile(archive* src, archive_entry* entry)
|
bool ArchiveWriter::addFile(ArchiveReader::File* f)
|
||||||
{
|
{
|
||||||
if (!src) {
|
return f->writeFile(m_archive, "", true);
|
||||||
qCritical() << "Invalid source archive";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!entry) { // if is empty read next header
|
|
||||||
if (auto r = archive_read_next_header(src, &entry); r == ARCHIVE_EOF) {
|
|
||||||
return false;
|
|
||||||
} else if (r != ARCHIVE_OK) {
|
|
||||||
qCritical() << "Failed to read entry from source archive:" << archive_error_string(src);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (archive_write_header(m_archive, entry) != ARCHIVE_OK) {
|
|
||||||
qCritical() << "Failed to write header to entry:" << archive_entry_pathname(entry) << "-" << archive_error_string(m_archive);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const void* buff;
|
|
||||||
size_t size;
|
|
||||||
la_int64_t offset;
|
|
||||||
|
|
||||||
int status;
|
|
||||||
while ((status = archive_read_data_block(src, &buff, &size, &offset)) == ARCHIVE_OK) {
|
|
||||||
if (archive_write_data(m_archive, buff, size) < 0) {
|
|
||||||
qCritical() << "Failed writing data block:" << archive_error_string(m_archive);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (status != ARCHIVE_EOF) {
|
|
||||||
qCritical() << "Failed reading data block:" << archive_error_string(m_archive);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
} // namespace MMCZip
|
} // namespace MMCZip
|
@ -5,20 +5,21 @@
|
|||||||
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QFileDevice>
|
#include <QFileDevice>
|
||||||
|
#include "archive/ArchiveReader.h"
|
||||||
|
|
||||||
namespace MMCZip {
|
namespace MMCZip {
|
||||||
|
|
||||||
class ArchiveWriter {
|
class ArchiveWriter {
|
||||||
public:
|
public:
|
||||||
ArchiveWriter(const QString& archiveName);
|
ArchiveWriter(const QString& archiveName);
|
||||||
~ArchiveWriter();
|
virtual ~ArchiveWriter();
|
||||||
|
|
||||||
bool open();
|
bool open();
|
||||||
bool close();
|
bool close();
|
||||||
|
|
||||||
bool addFile(const QString& fileName, const QString& fileDest);
|
bool addFile(const QString& fileName, const QString& fileDest);
|
||||||
bool addFile(const QString& fileDest, const QByteArray& data);
|
bool addFile(const QString& fileDest, const QByteArray& data);
|
||||||
bool addFile(archive* src, archive_entry* entry = nullptr);
|
bool addFile(ArchiveReader::File* f);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct archive* m_archive = nullptr;
|
struct archive* m_archive = nullptr;
|
||||||
|
@ -21,10 +21,6 @@ class ExportToZipTask : public Task {
|
|||||||
, m_follow_symlinks(followSymlinks)
|
, m_follow_symlinks(followSymlinks)
|
||||||
{
|
{
|
||||||
setAbortable(true);
|
setAbortable(true);
|
||||||
// m_output.setUtf8Enabled(utf8Enabled); // ignore for now
|
|
||||||
// need to test:
|
|
||||||
// - https://github.com/PrismLauncher/PrismLauncher/pull/2225
|
|
||||||
// - https://github.com/PrismLauncher/PrismLauncher/pull/2353
|
|
||||||
};
|
};
|
||||||
ExportToZipTask(QString outputPath, QString dir, QFileInfoList files, QString destinationPrefix = "", bool followSymlinks = false)
|
ExportToZipTask(QString outputPath, QString dir, QFileInfoList files, QString destinationPrefix = "", bool followSymlinks = false)
|
||||||
: ExportToZipTask(outputPath, QDir(dir), files, destinationPrefix, followSymlinks) {};
|
: ExportToZipTask(outputPath, QDir(dir), files, destinationPrefix, followSymlinks) {};
|
||||||
|
@ -57,6 +57,7 @@
|
|||||||
|
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
#include "PSaveFile.h"
|
#include "PSaveFile.h"
|
||||||
|
#include "archive/ArchiveReader.h"
|
||||||
|
|
||||||
using std::nullopt;
|
using std::nullopt;
|
||||||
using std::optional;
|
using std::optional;
|
||||||
@ -244,36 +245,37 @@ void World::readFromFS(const QFileInfo& file)
|
|||||||
|
|
||||||
void World::readFromZip(const QFileInfo& file)
|
void World::readFromZip(const QFileInfo& file)
|
||||||
{
|
{
|
||||||
QuaZip zip(file.absoluteFilePath());
|
MMCZip::ArchiveReader r(file.absoluteFilePath());
|
||||||
m_isValid = zip.open(QuaZip::mdUnzip);
|
|
||||||
if (!m_isValid) {
|
if (m_isValid = r.collectFiles(); !m_isValid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto location = MMCZip::findFolderOfFileInZip(&zip, "level.dat");
|
|
||||||
m_isValid = !location.isEmpty();
|
QString path;
|
||||||
if (!m_isValid) {
|
const QString levelDat = "level.dat";
|
||||||
|
for (auto filePath : r.getFiles()) {
|
||||||
|
QFileInfo fi(filePath);
|
||||||
|
if (fi.fileName().compare(levelDat, Qt::CaseInsensitive) == 0) {
|
||||||
|
m_containerOffsetPath = filePath.chopped(levelDat.length());
|
||||||
|
path = filePath;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_isValid = !m_containerOffsetPath.isEmpty(); !m_isValid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto zippedFile = r.goToFile(path);
|
||||||
|
if (m_isValid = !!zippedFile; !m_isValid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_containerOffsetPath = location;
|
|
||||||
QuaZipFile zippedFile(&zip);
|
|
||||||
// read the install profile
|
// read the install profile
|
||||||
m_isValid = zip.setCurrentFile(location + "level.dat");
|
m_levelDatTime = zippedFile->dateTime();
|
||||||
if (!m_isValid) {
|
if (!m_isValid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_isValid = zippedFile.open(QIODevice::ReadOnly);
|
loadFromLevelDat(zippedFile->readAll());
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool World::install(const QString& to, const QString& name)
|
bool World::install(const QString& to, const QString& name)
|
||||||
@ -284,10 +286,7 @@ bool World::install(const QString& to, const QString& name)
|
|||||||
}
|
}
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
if (m_containerFile.isFile()) {
|
if (m_containerFile.isFile()) {
|
||||||
QuaZip zip(m_containerFile.absoluteFilePath());
|
MMCZip::ArchiveReader zip(m_containerFile.absoluteFilePath());
|
||||||
if (!zip.open(QuaZip::mdUnzip)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
ok = !MMCZip::extractSubDir(&zip, m_containerOffsetPath, finalPath);
|
ok = !MMCZip::extractSubDir(&zip, m_containerOffsetPath, finalPath);
|
||||||
} else if (m_containerFile.isDir()) {
|
} else if (m_containerFile.isDir()) {
|
||||||
QString from = m_containerFile.filePath();
|
QString from = m_containerFile.filePath();
|
||||||
@ -350,7 +349,7 @@ optional<QString> read_string(nbt::value& parent, const char* name)
|
|||||||
return nullopt;
|
return nullopt;
|
||||||
}
|
}
|
||||||
auto& tag_str = namedValue.as<nbt::tag_string>();
|
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) {
|
} catch ([[maybe_unused]] const std::out_of_range& e) {
|
||||||
// fallback for old world formats
|
// fallback for old world formats
|
||||||
qWarning() << "String NBT tag" << name << "could not be found.";
|
qWarning() << "String NBT tag" << name << "could not be found.";
|
||||||
|
@ -66,11 +66,7 @@ void Technic::SingleZipPackInstallTask::downloadSucceeded()
|
|||||||
qDebug() << "Attempting to create instance from" << m_archivePath;
|
qDebug() << "Attempting to create instance from" << m_archivePath;
|
||||||
|
|
||||||
// open the zip and find relevant files in it
|
// open the zip and find relevant files in it
|
||||||
m_packZip.reset(new QuaZip(m_archivePath));
|
m_packZip.reset(new MMCZip::ArchiveReader(m_archivePath));
|
||||||
if (!m_packZip->open(QuaZip::mdUnzip)) {
|
|
||||||
emitFailed(tr("Unable to open supplied modpack zip file."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_extractFuture =
|
m_extractFuture =
|
||||||
QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), QString(""), extractDir.absolutePath());
|
QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), QString(""), extractDir.absolutePath());
|
||||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &Technic::SingleZipPackInstallTask::extractFinished);
|
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &Technic::SingleZipPackInstallTask::extractFinished);
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "InstanceTask.h"
|
#include "InstanceTask.h"
|
||||||
|
#include "archive/ArchiveReader.h"
|
||||||
#include "net/NetJob.h"
|
#include "net/NetJob.h"
|
||||||
|
|
||||||
#include <quazip/quazip.h>
|
#include <quazip/quazip.h>
|
||||||
@ -54,7 +55,7 @@ class SingleZipPackInstallTask : public InstanceTask {
|
|||||||
QString m_minecraftVersion;
|
QString m_minecraftVersion;
|
||||||
QString m_archivePath;
|
QString m_archivePath;
|
||||||
NetJob::Ptr m_filesNetJob;
|
NetJob::Ptr m_filesNetJob;
|
||||||
std::unique_ptr<QuaZip> m_packZip;
|
std::unique_ptr<MMCZip::ArchiveReader> m_packZip;
|
||||||
QFuture<std::optional<QStringList>> m_extractFuture;
|
QFuture<std::optional<QStringList>> m_extractFuture;
|
||||||
QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;
|
QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user