diff --git a/CMakeLists.txt b/CMakeLists.txt index f43eed7e3..1cd54635c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -346,14 +346,13 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 6) endif() if(NOT Launcher_FORCE_BUNDLED_LIBS) + find_package(PkgConfig REQUIRED) + pkg_check_modules(libarchive IMPORTED_TARGET libarchive) # Find toml++ find_package(tomlplusplus 3.2.0 QUIET) # Fallback to pkg-config (if available) if CMake files aren't found if(NOT tomlplusplus_FOUND) - find_package(PkgConfig) - if(PkgConfig_FOUND) - pkg_check_modules(tomlplusplus IMPORTED_TARGET tomlplusplus>=3.2.0) - endif() + pkg_check_modules(tomlplusplus IMPORTED_TARGET tomlplusplus>=3.2.0) endif() @@ -363,14 +362,6 @@ if(NOT Launcher_FORCE_BUNDLED_LIBS) # Find qrcodegencpp-cmake 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() include(ECMQtDeclareLoggingCategory) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 1c2042f8f..3a9009b2d 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -26,6 +26,8 @@ set(CORE_SOURCES NullInstance.h MMCZip.h MMCZip.cpp + archive/ArchiveReader.cpp + archive/ArchiveReader.h archive/ArchiveWriter.cpp archive/ArchiveWriter.h archive/ExportToZipTask.cpp @@ -1326,6 +1328,7 @@ target_link_libraries(Launcher_logic BuildConfig Qt${QT_VERSION_MAJOR}::Widgets qrcodegenerator + PkgConfig::libarchive ) if(TARGET PkgConfig::tomlplusplus) target_link_libraries(Launcher_logic PkgConfig::tomlplusplus) @@ -1333,12 +1336,6 @@ 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 gamemode diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 3a077e1e5..8d496fbb9 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -36,15 +36,18 @@ #include "MMCZip.h" #include +#include #include #include #include #include "FileSystem.h" +#include "archive/ArchiveReader.h" #include #include #include #include +#include #if defined(LAUNCHER_APPLICATION) #include @@ -57,39 +60,26 @@ using FilterFunction = std::function; #if defined(LAUNCHER_APPLICATION) bool mergeZipFiles(ArchiveWriter& into, QFileInfo from, QSet& contained, const FilterFunction& filter = nullptr) { - std::unique_ptr modZip(archive_read_new(), archive_read_free); - if (!modZip) { - qCritical() << "Failed to create archive"; - } - 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); + 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 (!into.addFile(modZip.get(), entry)) { + if (!into.addFile(f)) { qCritical() << "Failed to copy data of " << filename << " into the jar"; return false; } - } - - return true; + return true; + }); } bool compressDirFiles(ArchiveWriter& zip, QString dir, QFileInfoList files) @@ -195,178 +185,147 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList extractSubDir(QuaZip* zip, const QString& subdir, const QString& target) +std::optional extractSubDir(ArchiveReader* zip, const QString& subdir, const QString& target) { auto target_top_dir = QUrl::fromLocalFile(target); QStringList extracted; qDebug() << "Extracting subdir" << subdir << "from" << zip->getZipName() << "to" << target; - auto numEntries = zip->getEntriesCount(); - if (numEntries < 0) { + if (!zip->collectFiles()) { qWarning() << "Failed to enumerate files in archive"; return std::nullopt; - } else if (numEntries == 0) { + } + if (zip->getFiles().isEmpty()) { qDebug() << "Extracting empty archives seems odd..."; return extracted; - } else if (!zip->goToFirstFile()) { - qWarning() << "Failed to seek to first file in zip"; - return std::nullopt; } - do { - QString file_name = zip->getCurrentFileName(); - file_name = FS::RemoveInvalidPathChars(file_name); - if (!file_name.startsWith(subdir)) - continue; + int flags; - auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(subdir.size())); - auto original_name = relative_file_name; + /* Select which attributes we want to restore. */ + flags = ARCHIVE_EXTRACT_TIME; + flags |= ARCHIVE_EXTRACT_PERM; + flags |= ARCHIVE_EXTRACT_ACL; + flags |= ARCHIVE_EXTRACT_FFLAGS; - // Fix subdirs/files ending with a / getting transformed into absolute paths - if (relative_file_name.startsWith('/')) - relative_file_name = relative_file_name.mid(1); + std::unique_ptr 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); - // 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(); + JlCompress::removeFile(extracted); + return std::nullopt; + } return extracted; } -// ours -bool extractRelFile(QuaZip* zip, const QString& file, const QString& target) -{ - return JlCompress::extractFile(zip, file, target); -} - // ours std::optional extractDir(QString fileCompressed, QString dir) { - QuaZip zip(fileCompressed); - if (!zip.open(QuaZip::mdUnzip)) { - // check if this is a minimum size empty zip file... - QFileInfo fileInfo(fileCompressed); - if (fileInfo.size() == 22) { - return QStringList(); - } - qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError(); - ; - return std::nullopt; + // check if this is a minimum size empty zip file... + QFileInfo fileInfo(fileCompressed); + if (fileInfo.size() == 22) { + return QStringList(); } + ArchiveReader zip(fileCompressed); return extractSubDir(&zip, "", dir); } // ours std::optional extractDir(QString fileCompressed, QString subdir, QString dir) { - QuaZip zip(fileCompressed); - if (!zip.open(QuaZip::mdUnzip)) { - // check if this is a minimum size empty zip file... - QFileInfo fileInfo(fileCompressed); - if (fileInfo.size() == 22) { - return QStringList(); - } - qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError(); - ; - return std::nullopt; + // check if this is a minimum size empty zip file... + QFileInfo fileInfo(fileCompressed); + if (fileInfo.size() == 22) { + return QStringList(); } + ArchiveReader zip(fileCompressed); return extractSubDir(&zip, subdir, dir); } // ours bool extractFile(QString fileCompressed, QString file, QString target) { - QuaZip zip(fileCompressed); - if (!zip.open(QuaZip::mdUnzip)) { - // check if this is a minimum size empty zip file... - QFileInfo fileInfo(fileCompressed); - if (fileInfo.size() == 22) { - return true; - } - qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError(); + // check if this is a minimum size empty zip file... + QFileInfo fileInfo(fileCompressed); + if (fileInfo.size() == 22) { + return true; + } + ArchiveReader zip(fileCompressed); + auto f = zip.goToFile(file); + if (!f) { return false; } - return extractRelFile(&zip, file, target); + 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 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) diff --git a/launcher/MMCZip.h b/launcher/MMCZip.h index 452a50f4a..e18795ba4 100644 --- a/launcher/MMCZip.h +++ b/launcher/MMCZip.h @@ -48,6 +48,7 @@ #include #include #include +#include "archive/ArchiveReader.h" #if defined(LAUNCHER_APPLICATION) #include "minecraft/mod/Mod.h" @@ -63,21 +64,11 @@ using FilterFileFunction = std::function; */ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList& mods); #endif -/** - * Find a single file in archive by file name (not path) - * - * \param ignore_paths paths to skip when recursing the search - * - * \return the path prefix where the file is - */ -QString findFolderOfFileInZip(QuaZip* zip, const QString& what, const QStringList& ignore_paths = {}, const QString& root = QString("")); /** * Extract a subdirectory from an archive */ -std::optional extractSubDir(QuaZip* zip, const QString& subdir, const QString& target); - -bool extractRelFile(QuaZip* zip, const QString& file, const QString& target); +std::optional extractSubDir(ArchiveReader* zip, const QString& subdir, const QString& target); /** * Extract a whole archive. diff --git a/launcher/archive/ArchiveReader.cpp b/launcher/archive/ArchiveReader.cpp new file mode 100644 index 000000000..5bb8054d4 --- /dev/null +++ b/launcher/archive/ArchiveReader.cpp @@ -0,0 +1,170 @@ +#include "ArchiveReader.h" +#include +namespace MMCZip { +QStringList ArchiveReader::getFiles() +{ + return m_fileNames; +} + +bool ArchiveReader::collectFiles(bool onlyFiles) +{ + return parse([this, onlyFiles](File* f) { + if (!onlyFiles || f->isFile()) + m_fileNames << f->filename(); + return f->skip(); + }); +} + +QString ArchiveReader::File::filename() +{ + return QString::fromUtf8(archive_entry_pathname(m_entry)); +} + +QByteArray ArchiveReader::File::readAll(int* outStatus) +{ + QByteArray data; + const void* buff; + size_t size; + la_int64_t offset; + + int status; + while ((status = archive_read_data_block(m_archive.get(), &buff, &size, &offset)) == ARCHIVE_OK) { + data.append(static_cast(buff), static_cast(size)); + } + if (status != ARCHIVE_EOF && status != ARCHIVE_OK) { + qWarning() << "libarchive read error: " << archive_error_string(m_archive.get()); + } + if (outStatus) { + *outStatus = status; + } + 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 +{ + auto f = std::make_unique(); + auto a = f->m_archive.get(); + archive_read_support_format_all(a); + archive_read_support_filter_all(a); + auto fileName = m_archivePath.toUtf8(); + if (archive_read_open_filename(a, fileName.constData(), 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 doStuff) +{ + auto f = std::make_unique(); + auto a = f->m_archive.get(); + archive_read_support_format_all(a); + archive_read_support_filter_all(a); + auto fileName = m_archivePath.toUtf8(); + if (archive_read_open_filename(a, fileName.constData(), 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 diff --git a/launcher/archive/ArchiveReader.h b/launcher/archive/ArchiveReader.h new file mode 100644 index 000000000..9831c2cb7 --- /dev/null +++ b/launcher/archive/ArchiveReader.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace MMCZip { +class ArchiveReader { + public: + using ArchivePtr = std::unique_ptr; + ArchiveReader(QString fileName) : m_archivePath(fileName) {} + virtual ~ArchiveReader() = default; + + QStringList getFiles(); + QString getZipName(); + bool collectFiles(bool onlyFiles = true); + + 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 goToFile(QString filename); + bool parse(std::function); + + private: + QString m_archivePath; + + QStringList m_fileNames = {}; +}; +} // namespace MMCZip \ No newline at end of file diff --git a/launcher/archive/ArchiveWriter.cpp b/launcher/archive/ArchiveWriter.cpp index cfae8a8ff..a994f06aa 100644 --- a/launcher/archive/ArchiveWriter.cpp +++ b/launcher/archive/ArchiveWriter.cpp @@ -172,40 +172,8 @@ bool ArchiveWriter::addFile(const QString& fileDest, const QByteArray& data) return true; } -bool ArchiveWriter::addFile(archive* src, archive_entry* entry) +bool ArchiveWriter::addFile(ArchiveReader::File* f) { - if (!src) { - 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; + return f->writeFile(m_archive, "", true); } } // namespace MMCZip \ No newline at end of file diff --git a/launcher/archive/ArchiveWriter.h b/launcher/archive/ArchiveWriter.h index e8b5ca348..2e26e4b5e 100644 --- a/launcher/archive/ArchiveWriter.h +++ b/launcher/archive/ArchiveWriter.h @@ -5,20 +5,21 @@ #include #include +#include "archive/ArchiveReader.h" namespace MMCZip { class ArchiveWriter { public: ArchiveWriter(const QString& archiveName); - ~ArchiveWriter(); + virtual ~ArchiveWriter(); bool open(); bool close(); bool addFile(const QString& fileName, const QString& fileDest); bool addFile(const QString& fileDest, const QByteArray& data); - bool addFile(archive* src, archive_entry* entry = nullptr); + bool addFile(ArchiveReader::File* f); private: struct archive* m_archive = nullptr; diff --git a/launcher/archive/ExportToZipTask.h b/launcher/archive/ExportToZipTask.h index 12fbdcd2b..674053791 100644 --- a/launcher/archive/ExportToZipTask.h +++ b/launcher/archive/ExportToZipTask.h @@ -21,10 +21,6 @@ class ExportToZipTask : public Task { , m_follow_symlinks(followSymlinks) { 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(outputPath, QDir(dir), files, destinationPrefix, followSymlinks) {}; diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp index 8ae097bad..854bb2ee8 100644 --- a/launcher/minecraft/World.cpp +++ b/launcher/minecraft/World.cpp @@ -57,6 +57,7 @@ #include "FileSystem.h" #include "PSaveFile.h" +#include "archive/ArchiveReader.h" using std::nullopt; using std::optional; @@ -244,36 +245,37 @@ 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) { + MMCZip::ArchiveReader r(file.absoluteFilePath()); + + if (m_isValid = r.collectFiles(); !m_isValid) { return; } - auto location = MMCZip::findFolderOfFileInZip(&zip, "level.dat"); - m_isValid = !location.isEmpty(); - if (!m_isValid) { + + QString path; + 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; } - m_containerOffsetPath = location; - QuaZipFile zippedFile(&zip); // read the install profile - m_isValid = zip.setCurrentFile(location + "level.dat"); + m_levelDatTime = zippedFile->dateTime(); 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(); + loadFromLevelDat(zippedFile->readAll()); } 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; 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 +349,7 @@ optional read_string(nbt::value& parent, const char* name) return nullopt; } auto& tag_str = namedValue.as(); - return QString::fromStdString(tag_str.get()); + return QString::fromUtf8(tag_str.get()); } catch ([[maybe_unused]] const std::out_of_range& e) { // fallback for old world formats qWarning() << "String NBT tag" << name << "could not be found."; diff --git a/launcher/modplatform/technic/SingleZipPackInstallTask.cpp b/launcher/modplatform/technic/SingleZipPackInstallTask.cpp index cc9ced10b..09428c31d 100644 --- a/launcher/modplatform/technic/SingleZipPackInstallTask.cpp +++ b/launcher/modplatform/technic/SingleZipPackInstallTask.cpp @@ -66,11 +66,7 @@ void Technic::SingleZipPackInstallTask::downloadSucceeded() qDebug() << "Attempting to create instance from" << m_archivePath; // open the zip and find relevant files in it - m_packZip.reset(new QuaZip(m_archivePath)); - if (!m_packZip->open(QuaZip::mdUnzip)) { - emitFailed(tr("Unable to open supplied modpack zip file.")); - return; - } + m_packZip.reset(new MMCZip::ArchiveReader(m_archivePath)); m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), QString(""), extractDir.absolutePath()); connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, &Technic::SingleZipPackInstallTask::extractFinished); diff --git a/launcher/modplatform/technic/SingleZipPackInstallTask.h b/launcher/modplatform/technic/SingleZipPackInstallTask.h index d49d008b9..09fc7b658 100644 --- a/launcher/modplatform/technic/SingleZipPackInstallTask.h +++ b/launcher/modplatform/technic/SingleZipPackInstallTask.h @@ -16,6 +16,7 @@ #pragma once #include "InstanceTask.h" +#include "archive/ArchiveReader.h" #include "net/NetJob.h" #include @@ -54,7 +55,7 @@ class SingleZipPackInstallTask : public InstanceTask { QString m_minecraftVersion; QString m_archivePath; NetJob::Ptr m_filesNetJob; - std::unique_ptr m_packZip; + std::unique_ptr m_packZip; QFuture> m_extractFuture; QFutureWatcher> m_extractFutureWatcher; };