diff options
author | TheKodeToad <TheKodeToad@proton.me> | 2023-07-19 12:51:26 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-07-19 12:51:26 +0100 |
commit | f55120654aed5244040e1de59707b176bb816405 (patch) | |
tree | 5783871f6b2ff9c230dc071875fe047896bedd2a /launcher | |
parent | 44ca7c7d22f89d28bc9026700c8e0f8efe89b668 (diff) | |
parent | 593f45298614843c14acb11994320f90a474c750 (diff) | |
download | PrismLauncher-f55120654aed5244040e1de59707b176bb816405.tar.gz PrismLauncher-f55120654aed5244040e1de59707b176bb816405.tar.bz2 PrismLauncher-f55120654aed5244040e1de59707b176bb816405.zip |
Merge branch 'PrismLauncher:develop' into icon-indexing
Diffstat (limited to 'launcher')
-rw-r--r-- | launcher/BaseVersion.h | 19 | ||||
-rw-r--r-- | launcher/MMCZip.cpp | 253 | ||||
-rw-r--r-- | launcher/MMCZip.h | 254 | ||||
-rw-r--r-- | launcher/java/JavaInstall.cpp | 51 | ||||
-rw-r--r-- | launcher/java/JavaInstall.h | 57 | ||||
-rw-r--r-- | launcher/minecraft/MinecraftInstance.cpp | 7 | ||||
-rw-r--r-- | launcher/minecraft/auth/AuthSession.cpp | 1 | ||||
-rw-r--r-- | launcher/minecraft/auth/MinecraftAccount.cpp | 32 | ||||
-rw-r--r-- | launcher/minecraft/auth/MinecraftAccount.h | 2 | ||||
-rw-r--r-- | launcher/ui/dialogs/ExportInstanceDialog.cpp | 99 | ||||
-rw-r--r-- | launcher/ui/dialogs/ExportInstanceDialog.h | 25 |
11 files changed, 482 insertions, 318 deletions
diff --git a/launcher/BaseVersion.h b/launcher/BaseVersion.h index ca0e4502..c7cedbe1 100644 --- a/launcher/BaseVersion.h +++ b/launcher/BaseVersion.h @@ -15,16 +15,15 @@ #pragma once -#include <memory> -#include <QString> #include <QMetaType> +#include <QString> +#include <memory> /*! * An abstract base class for versions. */ -class BaseVersion -{ -public: +class BaseVersion { + public: using Ptr = std::shared_ptr<BaseVersion>; virtual ~BaseVersion() {} /*! @@ -45,14 +44,8 @@ public: */ virtual QString typeString() const = 0; - virtual bool operator<(BaseVersion &a) - { - return name() < a.name(); - }; - virtual bool operator>(BaseVersion &a) - { - return name() > a.name(); - }; + virtual bool operator<(BaseVersion& a) { return name() < a.name(); }; + virtual bool operator>(BaseVersion& a) { return name() > a.name(); }; }; Q_DECLARE_METATYPE(BaseVersion::Ptr) diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 1a336375..acd6bf7e 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -1,7 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * Copyright (c) 2023 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 @@ -33,56 +34,50 @@ * limitations under the License. */ +#include "MMCZip.h" #include <quazip/quazip.h> #include <quazip/quazipdir.h> #include <quazip/quazipfile.h> -#include "MMCZip.h" #include "FileSystem.h" #include <QCoreApplication> #include <QDebug> +#include <QtConcurrentRun> +namespace MMCZip { // ours -bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, const FilterFunction filter) +bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& contained, const FilterFunction filter) { QuaZip modZip(from.filePath()); modZip.open(QuaZip::mdUnzip); QuaZipFile fileInsideMod(&modZip); QuaZipFile zipOutFile(into); - for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile()) - { + for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile()) { QString filename = modZip.getCurrentFileName(); - if (filter && !filter(filename)) - { - qDebug() << "Skipping file " << filename << " from " - << from.fileName() << " - filtered"; + if (filter && !filter(filename)) { + qDebug() << "Skipping file " << filename << " from " << from.fileName() << " - filtered"; continue; } - if (contained.contains(filename)) - { - qDebug() << "Skipping already contained file " << filename << " from " - << from.fileName(); + if (contained.contains(filename)) { + qDebug() << "Skipping already contained file " << filename << " from " << from.fileName(); continue; } contained.insert(filename); - if (!fileInsideMod.open(QIODevice::ReadOnly)) - { + 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)) - { + if (!zipOutFile.open(QIODevice::WriteOnly, info_out)) { qCritical() << "Failed to open " << filename << " in the jar"; fileInsideMod.close(); return false; } - if (!JlCompress::copyData(fileInsideMod, zipOutFile)) - { + if (!JlCompress::copyData(fileInsideMod, zipOutFile)) { zipOutFile.close(); fileInsideMod.close(); qCritical() << "Failed to copy data of " << filename << " into the jar"; @@ -94,10 +89,11 @@ bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &containe return true; } -bool MMCZip::compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files, bool followSymlinks) +bool compressDirFiles(QuaZip* zip, QString dir, QFileInfoList files, bool followSymlinks) { QDir directory(dir); - if (!directory.exists()) return false; + if (!directory.exists()) + return false; for (auto e : files) { auto filePath = directory.relativeFilePath(e.absoluteFilePath()); @@ -109,17 +105,18 @@ bool MMCZip::compressDirFiles(QuaZip *zip, QString dir, QFileInfoList files, boo srcPath = e.canonicalFilePath(); } } - if( !JlCompress::compressFile(zip, srcPath, filePath)) return false; + if (!JlCompress::compressFile(zip, srcPath, filePath)) + return false; } return true; } -bool MMCZip::compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks) +bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks) { QuaZip zip(fileCompressed); QDir().mkpath(QFileInfo(fileCompressed).absolutePath()); - if(!zip.open(QuaZip::mdCreate)) { + if (!zip.open(QuaZip::mdCreate)) { QFile::remove(fileCompressed); return false; } @@ -127,7 +124,7 @@ bool MMCZip::compressDirFiles(QString fileCompressed, QString dir, QFileInfoList auto result = compressDirFiles(&zip, dir, files, followSymlinks); zip.close(); - if(zip.getZipError()!=0) { + if (zip.getZipError() != 0) { QFile::remove(fileCompressed); return false; } @@ -136,11 +133,10 @@ bool MMCZip::compressDirFiles(QString fileCompressed, QString dir, QFileInfoList } // ours -bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods) +bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods) { QuaZip zipOut(targetJarPath); - if (!zipOut.open(QuaZip::mdCreate)) - { + if (!zipOut.open(QuaZip::mdCreate)) { QFile::remove(targetJarPath); qCritical() << "Failed to open the minecraft.jar for modding"; return false; @@ -151,37 +147,29 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const // Modify the jar // This needs to be done in reverse-order to ensure we respect the loading order of components - for (auto i = mods.crbegin(); i != mods.crend(); i++) - { + for (auto i = mods.crbegin(); i != mods.crend(); i++) { const auto* mod = *i; // do not merge disabled mods. if (!mod->enabled()) continue; - if (mod->type() == ResourceType::ZIPFILE) - { - if (!mergeZipFiles(&zipOut, mod->fileinfo(), addedFiles)) - { + if (mod->type() == ResourceType::ZIPFILE) { + if (!mergeZipFiles(&zipOut, mod->fileinfo(), addedFiles)) { zipOut.close(); QFile::remove(targetJarPath); qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar."; return false; } - } - else if (mod->type() == ResourceType::SINGLEFILE) - { + } 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 (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName())) { zipOut.close(); QFile::remove(targetJarPath); qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar."; return false; } addedFiles.insert(filename.fileName()); - } - else if (mod->type() == ResourceType::FOLDER) - { + } else if (mod->type() == ResourceType::FOLDER) { // untested, but seems to be unused / not possible to reach // FIXME: buggy - does not work with addedFiles auto filename = mod->fileinfo(); @@ -190,25 +178,21 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const dir.cdUp(); QString parent_dir = dir.absolutePath(); auto files = QFileInfoList(); - MMCZip::collectFileListRecursively(what_to_zip, nullptr, &files, nullptr); + collectFileListRecursively(what_to_zip, nullptr, &files, nullptr); for (auto e : files) { if (addedFiles.contains(e.filePath())) files.removeAll(e); } - if (!MMCZip::compressDirFiles(&zipOut, parent_dir, files)) - { + if (!compressDirFiles(&zipOut, parent_dir, files)) { zipOut.close(); QFile::remove(targetJarPath); qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar."; return false; } - qDebug() << "Adding folder " << filename.fileName() << " from " - << filename.absoluteFilePath(); - } - else - { + qDebug() << "Adding folder " << filename.fileName() << " from " << filename.absoluteFilePath(); + } else { // Make sure we do not continue launching when something is missing or undefined... zipOut.close(); QFile::remove(targetJarPath); @@ -217,8 +201,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const } } - 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(); QFile::remove(targetJarPath); qCritical() << "Failed to insert minecraft.jar contents."; @@ -227,8 +210,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const // Recompress the jar zipOut.close(); - if (zipOut.getZipError() != 0) - { + if (zipOut.getZipError() != 0) { QFile::remove(targetJarPath); qCritical() << "Failed to finalize minecraft.jar!"; return false; @@ -237,7 +219,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const } // ours -QString MMCZip::findFolderOfFileInZip(QuaZip* zip, const QString& what, const QStringList& ignore_paths, const QString& root) +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)) { @@ -261,27 +243,23 @@ QString MMCZip::findFolderOfFileInZip(QuaZip* zip, const QString& what, const QS } // ours -bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root) +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) - { + for (auto fileName : rootDir.entryList(QDir::Files)) { + if (fileName == what) { result.append(root); return true; } } - for(auto fileName: rootDir.entryList(QDir::Dirs)) - { + for (auto fileName : rootDir.entryList(QDir::Dirs)) { findFilesInZip(zip, what, result, root + fileName); } return !result.isEmpty(); } - // ours -std::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target) +std::optional<QStringList> extractSubDir(QuaZip* zip, const QString& subdir, const QString& target) { auto target_top_dir = QUrl::fromLocalFile(target); @@ -289,16 +267,13 @@ std::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & su qDebug() << "Extracting subdir" << subdir << "from" << zip->getZipName() << "to" << target; auto numEntries = zip->getEntriesCount(); - if(numEntries < 0) { + if (numEntries < 0) { qWarning() << "Failed to enumerate files in archive"; return std::nullopt; - } - else if(numEntries == 0) { + } else if (numEntries == 0) { qDebug() << "Extracting empty archives seems odd..."; return extracted; - } - else if (!zip->goToFirstFile()) - { + } else if (!zip->goToFirstFile()) { qWarning() << "Failed to seek to first file in zip"; return std::nullopt; } @@ -334,7 +309,8 @@ std::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & su } 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; + qWarning() << "Extracting" << relative_file_name << "was cancelled, because it was effectively outside of the target path" + << target; return std::nullopt; } @@ -345,7 +321,8 @@ std::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & su } extracted.append(target_file_path); - QFile::setPermissions(target_file_path, QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser); + QFile::setPermissions(target_file_path, + QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser); qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path; } while (zip->goToNextFile()); @@ -354,66 +331,66 @@ std::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & su } // ours -bool MMCZip::extractRelFile(QuaZip *zip, const QString &file, const QString &target) +bool extractRelFile(QuaZip* zip, const QString& file, const QString& target) { return JlCompress::extractFile(zip, file, target); } // ours -std::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString dir) +std::optional<QStringList> extractDir(QString fileCompressed, QString dir) { QuaZip zip(fileCompressed); - if (!zip.open(QuaZip::mdUnzip)) - { + if (!zip.open(QuaZip::mdUnzip)) { // check if this is a minimum size empty zip file... QFileInfo fileInfo(fileCompressed); - if(fileInfo.size() == 22) { + if (fileInfo.size() == 22) { return QStringList(); } - qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();; + qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError(); + ; return std::nullopt; } - return MMCZip::extractSubDir(&zip, "", dir); + return extractSubDir(&zip, "", dir); } // ours -std::optional<QStringList> MMCZip::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)) - { + if (!zip.open(QuaZip::mdUnzip)) { // check if this is a minimum size empty zip file... QFileInfo fileInfo(fileCompressed); - if(fileInfo.size() == 22) { + if (fileInfo.size() == 22) { return QStringList(); } - qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();; + qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError(); + ; return std::nullopt; } - return MMCZip::extractSubDir(&zip, subdir, dir); + return extractSubDir(&zip, subdir, dir); } // ours -bool MMCZip::extractFile(QString fileCompressed, QString file, QString target) +bool extractFile(QString fileCompressed, QString file, QString target) { QuaZip zip(fileCompressed); - if (!zip.open(QuaZip::mdUnzip)) - { + if (!zip.open(QuaZip::mdUnzip)) { // check if this is a minimum size empty zip file... QFileInfo fileInfo(fileCompressed); - if(fileInfo.size() == 22) { + if (fileInfo.size() == 22) { return true; } qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError(); return false; } - return MMCZip::extractRelFile(&zip, file, target); + return extractRelFile(&zip, file, target); } -bool MMCZip::collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList *files, - MMCZip::FilterFunction excludeFilter) { +bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFunction excludeFilter) +{ QDir rootDirectory(rootDir); - if (!rootDirectory.exists()) return false; + if (!rootDirectory.exists()) + return false; QDir directory; if (subDir == nullptr) @@ -421,25 +398,107 @@ bool MMCZip::collectFileListRecursively(const QString& rootDir, const QString& s else directory = QDir(subDir); - if (!directory.exists()) return false; // shouldn't ever happen + if (!directory.exists()) + return false; // shouldn't ever happen // recurse directories QFileInfoList entries = directory.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden); - for (const auto& e: entries) { + for (const auto& e : entries) { if (!collectFileListRecursively(rootDir, e.filePath(), files, excludeFilter)) return false; } // collect files entries = directory.entryInfoList(QDir::Files); - for (const auto& e: entries) { + for (const auto& e : entries) { QString relativeFilePath = rootDirectory.relativeFilePath(e.absoluteFilePath()); if (excludeFilter && excludeFilter(relativeFilePath)) { qDebug() << "Skipping file " << relativeFilePath; continue; } - files->append(e); // we want the original paths for MMCZip::compressDirFiles + files->append(e); // we want the original paths for compressDirFiles } return true; } + +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("Compresing: " + 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()) { + QFile::remove(m_output_path); + emitAborted(); + } else if (auto result = m_build_zip_future.result(); result.has_value()) { + QFile::remove(m_output_path); + emitFailed(result.value()); + } else { + emitSucceeded(); + } +} + +bool ExportToZipTask::abort() +{ + if (m_build_zip_future.isRunning()) { + m_build_zip_future.cancel(); + // NOTE: Here we don't do `emitAborted()` because it will be done when `m_build_zip_future` actually cancels, which may not occur + // immediately. + return true; + } + return false; +} + +} // namespace MMCZip
\ No newline at end of file diff --git a/launcher/MMCZip.h b/launcher/MMCZip.h index 2a78f830..bc527ad1 100644 --- a/launcher/MMCZip.h +++ b/launcher/MMCZip.h @@ -1,7 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * Copyright (c) 2023 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 @@ -35,110 +36,157 @@ #pragma once -#include <QString> +#include <quazip.h> +#include <quazip/JlCompress.h> +#include <QDir> #include <QFileInfo> +#include <QFuture> +#include <QFutureWatcher> +#include <QHash> #include <QSet> -#include "minecraft/mod/Mod.h" +#include <QString> #include <functional> - -#include <quazip/JlCompress.h> +#include <memory> #include <optional> +#include "minecraft/mod/Mod.h" +#include "tasks/Task.h" -namespace MMCZip -{ - using FilterFunction = std::function<bool(const QString &)>; - - /** - * Merge two zip files, using a filter function - */ - bool mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, - const FilterFunction 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); - - /** - * take a source jar, add mods to it, resulting in target jar - */ - bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods); - - /** - * 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); - - /** - * Extract a whole archive. - * - * \param fileCompressed The name of the archive. - * \param dir The directory to extract to, the current directory if left empty. - * \return The list of the full paths of the files extracted, empty on failure. - */ - std::optional<QStringList> extractDir(QString fileCompressed, QString dir); - - /** - * Extract a subdirectory from an archive - * - * \param fileCompressed The name of the archive. - * \param subdir The directory within the archive to extract - * \param dir The directory to extract to, the current directory if left empty. - * \return The list of the full paths of the files extracted, empty on failure. - */ - std::optional<QStringList> extractDir(QString fileCompressed, QString subdir, QString dir); - - /** - * Extract a single file from an archive into a directory - * - * \param fileCompressed The name of the archive. - * \param file The file within the archive to extract - * \param dir The directory to extract to, the current directory if left empty. - * \return true for success or false for failure - */ - bool extractFile(QString fileCompressed, QString file, QString dir); - - /** - * Populate a QFileInfoList with a directory tree recursively, while allowing to excludeFilter what shouldn't be included. - * \param rootDir directory to start off - * \param subDir subdirectory, should be nullptr for first invocation - * \param files resulting list of QFileInfo - * \param excludeFilter function to excludeFilter which files shouldn't be included (returning true means to excude) - * \return true for success or false for failure - */ - bool collectFileListRecursively(const QString &rootDir, const QString &subDir, QFileInfoList *files, FilterFunction excludeFilter); -} +namespace MMCZip { +using FilterFunction = std::function<bool(const QString&)>; + +/** + * Merge two zip files, using a filter function + */ +bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& contained, const FilterFunction 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); + +/** + * take a source jar, add mods to it, resulting in target jar + */ +bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods); + +/** + * 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); + +/** + * Extract a whole archive. + * + * \param fileCompressed The name of the archive. + * \param dir The directory to extract to, the current directory if left empty. + * \return The list of the full paths of the files extracted, empty on failure. + */ +std::optional<QStringList> extractDir(QString fileCompressed, QString dir); + +/** + * Extract a subdirectory from an archive + * + * \param fileCompressed The name of the archive. + * \param subdir The directory within the archive to extract + * \param dir The directory to extract to, the current directory if left empty. + * \return The list of the full paths of the files extracted, empty on failure. + */ +std::optional<QStringList> extractDir(QString fileCompressed, QString subdir, QString dir); + +/** + * Extract a single file from an archive into a directory + * + * \param fileCompressed The name of the archive. + * \param file The file within the archive to extract + * \param dir The directory to extract to, the current directory if left empty. + * \return true for success or false for failure + */ +bool extractFile(QString fileCompressed, QString file, QString dir); + +/** + * Populate a QFileInfoList with a directory tree recursively, while allowing to excludeFilter what shouldn't be included. + * \param rootDir directory to start off + * \param subDir subdirectory, should be nullptr for first invocation + * \param files resulting list of QFileInfo + * \param excludeFilter function to excludeFilter which files shouldn't be included (returning true means to excude) + * \return true for success or false for failure + */ +bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFunction excludeFilter); + +class ExportToZipTask : public Task { + 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); } + + typedef std::optional<QString> ZipResult; + + 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; +}; +} // namespace MMCZip diff --git a/launcher/java/JavaInstall.cpp b/launcher/java/JavaInstall.cpp index d5932bcb..cfa47140 100644 --- a/launcher/java/JavaInstall.cpp +++ b/launcher/java/JavaInstall.cpp @@ -1,29 +1,64 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 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 "JavaInstall.h" +#include "BaseVersion.h" #include "StringUtils.h" -bool JavaInstall::operator<(const JavaInstall &rhs) +bool JavaInstall::operator<(const JavaInstall& rhs) { auto archCompare = StringUtils::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive); - if(archCompare != 0) + if (archCompare != 0) return archCompare < 0; - if(id < rhs.id) - { + if (id < rhs.id) { return true; } - if(id > rhs.id) - { + if (id > rhs.id) { return false; } return StringUtils::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0; } -bool JavaInstall::operator==(const JavaInstall &rhs) +bool JavaInstall::operator==(const JavaInstall& rhs) { return arch == rhs.arch && id == rhs.id && path == rhs.path; } -bool JavaInstall::operator>(const JavaInstall &rhs) +bool JavaInstall::operator>(const JavaInstall& rhs) { return (!operator<(rhs)) && (!operator==(rhs)); } + +bool JavaInstall::operator<(BaseVersion& a) +{ + try { + return operator<(dynamic_cast<JavaInstall&>(a)); + } catch (const std::bad_cast& e) { + return BaseVersion::operator<(a); + } +} + +bool JavaInstall::operator>(BaseVersion& a) +{ + try { + return operator>(dynamic_cast<JavaInstall&>(a)); + } catch (const std::bad_cast& e) { + return BaseVersion::operator>(a); + } +} diff --git a/launcher/java/JavaInstall.h b/launcher/java/JavaInstall.h index 64be40d1..30815b5a 100644 --- a/launcher/java/JavaInstall.h +++ b/launcher/java/JavaInstall.h @@ -1,33 +1,40 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 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 "BaseVersion.h" #include "JavaVersion.h" -struct JavaInstall : public BaseVersion -{ - JavaInstall(){} - JavaInstall(QString id, QString arch, QString path) - : id(id), arch(arch), path(path) - { - } - virtual QString descriptor() - { - return id.toString(); - } - - virtual QString name() - { - return id.toString(); - } - - virtual QString typeString() const - { - return arch; - } - - bool operator<(const JavaInstall & rhs); - bool operator==(const JavaInstall & rhs); - bool operator>(const JavaInstall & rhs); +struct JavaInstall : public BaseVersion { + JavaInstall() {} + JavaInstall(QString id, QString arch, QString path) : id(id), arch(arch), path(path) {} + virtual QString descriptor() { return id.toString(); } + + virtual QString name() { return id.toString(); } + + virtual QString typeString() const { return arch; } + + virtual bool operator<(BaseVersion& a) override; + virtual bool operator>(BaseVersion& a) override; + bool operator<(const JavaInstall& rhs); + bool operator==(const JavaInstall& rhs); + bool operator>(const JavaInstall& rhs); JavaVersion id; QString arch; diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 0833d591..342e634f 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -396,8 +396,11 @@ QStringList MinecraftInstance::extraArguments() agent->library()->getApplicableFiles(runtimeContext(), jar, temp1, temp2, temp3, getLocalLibraryPath()); list.append("-javaagent:"+jar[0]+(agent->argument().isEmpty() ? "" : "="+agent->argument())); } - if (version->getModLoaders().value() & ResourceAPI::Quilt && settings()->get("DisableQuiltBeacon").toBool()) { - list.append("-Dloader.disable_beacon=true"); + + { + const auto loaders = version->getModLoaders(); + if (loaders.has_value() && loaders.value() & ResourceAPI::Quilt && settings()->get("DisableQuiltBeacon").toBool()) + list.append("-Dloader.disable_beacon=true"); } return list; } diff --git a/launcher/minecraft/auth/AuthSession.cpp b/launcher/minecraft/auth/AuthSession.cpp index 6bea74a3..2c06beaa 100644 --- a/launcher/minecraft/auth/AuthSession.cpp +++ b/launcher/minecraft/auth/AuthSession.cpp @@ -26,6 +26,7 @@ bool AuthSession::MakeOffline(QString offline_playername) return false; } session = "-"; + access_token = "0"; player_name = offline_playername; status = PlayableOffline; return true; diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index 3b050ac0..d7b061e5 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -37,6 +37,7 @@ #include "MinecraftAccount.h" +#include <QCryptographicHash> #include <QUuid> #include <QJsonObject> #include <QJsonArray> @@ -100,7 +101,7 @@ MinecraftAccountPtr MinecraftAccount::createOffline(const QString &username) account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]")); account->data.minecraftEntitlement.ownsMinecraft = true; account->data.minecraftEntitlement.canPlayMinecraft = true; - account->data.minecraftProfile.id = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]")); + account->data.minecraftProfile.id = uuidFromUsername(username).toString().remove(QRegularExpression("[{}-]")); account->data.minecraftProfile.name = username; account->data.minecraftProfile.validity = Katabasis::Validity::Certain; return account; @@ -334,3 +335,32 @@ void MinecraftAccount::incrementUses() qWarning() << "Profile" << data.profileId() << "is now in use."; } } + +QUuid MinecraftAccount::uuidFromUsername(QString username) { + auto input = QString("OfflinePlayer:%1").arg(username).toUtf8(); + + // basically a reimplementation of Java's UUID#nameUUIDFromBytes + QByteArray digest = QCryptographicHash::hash(input, QCryptographicHash::Md5); + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + auto bOr = [](QByteArray& array, int index, char value) { + array[index] = array.at(index) | value; + }; + auto bAnd = [](QByteArray& array, int index, char value) { + array[index] = array.at(index) & value; + }; +#else + auto bOr = [](QByteArray& array, qsizetype index, char value) { + array[index] |= value; + }; + auto bAnd = [](QByteArray& array, qsizetype index, char value) { + array[index] &= value; + }; +#endif + bAnd(digest, 6, (char) 0x0f); // clear version + bOr(digest, 6, (char) 0x30); // set to version 3 + bAnd(digest, 8, (char) 0x3f); // clear variant + bOr(digest, 8, (char) 0x80); // set to IETF variant + + return QUuid::fromRfc4122(digest); +} diff --git a/launcher/minecraft/auth/MinecraftAccount.h b/launcher/minecraft/auth/MinecraftAccount.h index 0dcaeb53..67623a5a 100644 --- a/launcher/minecraft/auth/MinecraftAccount.h +++ b/launcher/minecraft/auth/MinecraftAccount.h @@ -98,6 +98,8 @@ public: /* construction */ static MinecraftAccountPtr loadFromJsonV2(const QJsonObject &json); static MinecraftAccountPtr loadFromJsonV3(const QJsonObject &json); + static QUuid uuidFromUsername(QString username); + //! Saves a MinecraftAccount to a JSON object and returns it. QJsonObject saveToJson() const; diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp index cc41c394..d1a69b93 100644 --- a/launcher/ui/dialogs/ExportInstanceDialog.cpp +++ b/launcher/ui/dialogs/ExportInstanceDialog.cpp @@ -3,6 +3,7 @@ * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> + * Copyright (c) 2023 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 @@ -41,6 +42,9 @@ #include <QFileSystemModel> #include <QMessageBox> #include "FileIgnoreProxy.h" +#include "QObjectPtr.h" +#include "ui/dialogs/CustomMessageBox.h" +#include "ui/dialogs/ProgressDialog.h" #include "ui_ExportInstanceDialog.h" #include <FileSystem.h> @@ -72,7 +76,7 @@ ExportInstanceDialog::ExportInstanceDialog(InstancePtr instance, QWidget* parent ui->treeView->setRootIndex(proxyModel->mapFromSource(model->index(root))); ui->treeView->sortByColumn(0, Qt::AscendingOrder); - connect(proxyModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(rowsInserted(QModelIndex,int,int))); + connect(proxyModel, SIGNAL(rowsInserted(QModelIndex, int, int)), SLOT(rowsInserted(QModelIndex, int, int))); model->setFilter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Hidden); model->setRootPath(root); @@ -92,32 +96,26 @@ void SaveIcon(InstancePtr m_instance) auto iconKey = m_instance->iconKey(); auto iconList = APPLICATION->icons(); auto mmcIcon = iconList->icon(iconKey); - if(!mmcIcon || mmcIcon->isBuiltIn()) { + if (!mmcIcon || mmcIcon->isBuiltIn()) { return; } auto path = mmcIcon->getFilePath(); - if(!path.isNull()) { - QFileInfo inInfo (path); - FS::copy(path, FS::PathCombine(m_instance->instanceRoot(), inInfo.fileName())) (); + if (!path.isNull()) { + QFileInfo inInfo(path); + FS::copy(path, FS::PathCombine(m_instance->instanceRoot(), inInfo.fileName()))(); return; } - auto & image = mmcIcon->m_images[mmcIcon->type()]; - auto & icon = image.icon; + auto& image = mmcIcon->m_images[mmcIcon->type()]; + auto& icon = image.icon; auto sizes = icon.availableSizes(); - if(sizes.size() == 0) - { + if (sizes.size() == 0) { return; } - auto areaOf = [](QSize size) - { - return size.width() * size.height(); - }; + auto areaOf = [](QSize size) { return size.width() * size.height(); }; QSize largest = sizes[0]; // find variant with largest area - for(auto size: sizes) - { - if(areaOf(largest) < areaOf(size)) - { + for (auto size : sizes) { + if (areaOf(largest) < areaOf(size)) { largest = size; } } @@ -125,16 +123,15 @@ void SaveIcon(InstancePtr m_instance) pixmap.save(FS::PathCombine(m_instance->instanceRoot(), iconKey + ".png")); } -bool ExportInstanceDialog::doExport() +void ExportInstanceDialog::doExport() { auto name = FS::RemoveInvalidFilenameChars(m_instance->name()); - const QString output = QFileDialog::getSaveFileName( - this, tr("Export %1").arg(m_instance->name()), - FS::PathCombine(QDir::homePath(), name + ".zip"), "Zip (*.zip)", nullptr); - if (output.isEmpty()) - { - return false; + const QString output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(m_instance->name()), + FS::PathCombine(QDir::homePath(), name + ".zip"), "Zip (*.zip)", nullptr); + if (output.isEmpty()) { + QDialog::done(QDialog::Rejected); + return; } SaveIcon(m_instance); @@ -143,46 +140,40 @@ bool ExportInstanceDialog::doExport() if (!MMCZip::collectFileListRecursively(m_instance->instanceRoot(), nullptr, &files, std::bind(&FileIgnoreProxy::filterFile, proxyModel, std::placeholders::_1))) { QMessageBox::warning(this, tr("Error"), tr("Unable to export instance")); - return false; + QDialog::done(QDialog::Rejected); + return; } - if (!MMCZip::compressDirFiles(output, m_instance->instanceRoot(), files, true)) - { - QMessageBox::warning(this, tr("Error"), tr("Unable to export instance")); - return false; - } - return 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(); }); + connect(task.get(), &Task::finished, this, [task] { task->deleteLater(); }); + + ProgressDialog progress(this); + progress.setSkipButton(true, tr("Abort")); + auto result = progress.execWithTask(task.get()); + QDialog::done(result); } void ExportInstanceDialog::done(int result) { savePackIgnore(); - if (result == QDialog::Accepted) - { - if (doExport()) - { - QDialog::done(QDialog::Accepted); - return; - } - else - { - return; - } + if (result == QDialog::Accepted) { + doExport(); + return; } QDialog::done(result); } void ExportInstanceDialog::rowsInserted(QModelIndex parent, int top, int bottom) { - //WARNING: possible off-by-one? - for(int i = top; i < bottom; i++) - { + // WARNING: possible off-by-one? + for (int i = top; i < bottom; i++) { auto node = proxyModel->index(i, 0, parent); - if(proxyModel->shouldExpand(node)) - { + if (proxyModel->shouldExpand(node)) { auto expNode = node.parent(); - if(!expNode.isValid()) - { + if (!expNode.isValid()) { continue; } ui->treeView->expand(node); @@ -199,8 +190,7 @@ void ExportInstanceDialog::loadPackIgnore() { auto filename = ignoreFileName(); QFile ignoreFile(filename); - if(!ignoreFile.open(QIODevice::ReadOnly)) - { + if (!ignoreFile.open(QIODevice::ReadOnly)) { return; } auto data = ignoreFile.readAll(); @@ -216,12 +206,9 @@ void ExportInstanceDialog::savePackIgnore() { auto data = proxyModel->blockedPaths().toStringList().join('\n').toUtf8(); auto filename = ignoreFileName(); - try - { + try { FS::write(filename, data); - } - catch (const Exception &e) - { + } catch (const Exception& e) { qWarning() << e.cause(); } } diff --git a/launcher/ui/dialogs/ExportInstanceDialog.h b/launcher/ui/dialogs/ExportInstanceDialog.h index 5e801875..02f38f63 100644 --- a/launcher/ui/dialogs/ExportInstanceDialog.h +++ b/launcher/ui/dialogs/ExportInstanceDialog.h @@ -2,6 +2,7 @@ /* * Prism Launcher - Minecraft Launcher * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> + * Copyright (c) 2023 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 @@ -38,39 +39,37 @@ #include <QDialog> #include <QModelIndex> #include <memory> -#include "FileIgnoreProxy.h" #include "FastFileIconProvider.h" +#include "FileIgnoreProxy.h" class BaseInstance; typedef std::shared_ptr<BaseInstance> InstancePtr; -namespace Ui -{ +namespace Ui { class ExportInstanceDialog; } -class ExportInstanceDialog : public QDialog -{ +class ExportInstanceDialog : public QDialog { Q_OBJECT -public: - explicit ExportInstanceDialog(InstancePtr instance, QWidget *parent = 0); + public: + explicit ExportInstanceDialog(InstancePtr instance, QWidget* parent = 0); ~ExportInstanceDialog(); virtual void done(int result); -private: - bool doExport(); + private: + void doExport(); void loadPackIgnore(); void savePackIgnore(); QString ignoreFileName(); -private: - Ui::ExportInstanceDialog *ui; + private: + Ui::ExportInstanceDialog* ui; InstancePtr m_instance; - FileIgnoreProxy * proxyModel; + FileIgnoreProxy* proxyModel; FastFileIconProvider icons; -private slots: + private slots: void rowsInserted(QModelIndex parent, int top, int bottom); }; |