From 43cc04433d2bccd8c81bae56920bd464cbb27fe8 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 19 Jul 2023 11:58:27 +0300 Subject: [PATCH 001/103] feat: refactored Instance ImportTask Signed-off-by: Trial97 --- launcher/InstanceImportTask.cpp | 201 +++++++++---------- launcher/InstanceImportTask.h | 50 ++--- launcher/MMCZip.cpp | 105 ++++++++++ launcher/MMCZip.h | 25 +++ launcher/ui/pages/modplatform/ImportPage.cpp | 1 + 5 files changed, 245 insertions(+), 137 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 352848f02..36c5b8f94 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -45,14 +45,15 @@ #include "icons/IconList.h" #include "icons/IconUtils.h" -#include "modplatform/technic/TechnicPackProcessor.h" -#include "modplatform/modrinth/ModrinthInstanceCreationTask.h" #include "modplatform/flame/FlameInstanceCreationTask.h" +#include "modplatform/modrinth/ModrinthInstanceCreationTask.h" +#include "modplatform/technic/TechnicPackProcessor.h" #include "settings/INISettingsObject.h" #include #include +#include #include @@ -65,15 +66,8 @@ bool InstanceImportTask::abort() if (!canAbort()) return false; - if (m_filesNetJob) - m_filesNetJob->abort(); - if (m_extractFuture.isRunning()) { - // NOTE: The tasks created by QtConcurrent::run() can't actually get cancelled, - // but we can use this call to check the state when the extraction finishes. - m_extractFuture.cancel(); - m_extractFuture.waitForFinished(); - } - + if (task) + task->abort(); return Task::abort(); } @@ -86,7 +80,6 @@ void InstanceImportTask::executeTask() processZipPack(); } else { setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString())); - m_downloadRequired = true; const QString path(m_sourceUrl.host() + '/' + m_sourceUrl.path()); @@ -94,153 +87,153 @@ void InstanceImportTask::executeTask() entry->setStale(true); m_archivePath = entry->getFullPath(); - m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network())); - m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry)); + auto filesNetJob = makeShared(tr("Modpack download"), APPLICATION->network()); + filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry)); - connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded); - connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged); - connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propogateStepProgress); - connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed); - connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted); - - m_filesNetJob->start(); + connect(filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::processZipPack); + connect(filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::setProgress); + connect(filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propogateStepProgress); + connect(filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::emitFailed); + connect(filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::emitAborted); + task.reset(filesNetJob); + filesNetJob->start(); } } -void InstanceImportTask::downloadSucceeded() +QString InstanceImportTask::getRootFromZip(QuaZip* zip, const QString& root) { - processZipPack(); - m_filesNetJob.reset(); -} + if (!isRunning()) { + return {}; + } + QuaZipDir rootDir(zip, root); + for (auto&& fileName : rootDir.entryList(QDir::Files)) { + setDetails(fileName); + if (fileName == "instance.cfg") { + qDebug() << "MultiMC:" << true; + m_modpackType = ModpackType::MultiMC; + return root; + } + if (fileName == "manifest.json") { + qDebug() << "Flame:" << true; + m_modpackType = ModpackType::Flame; + return root; + } -void InstanceImportTask::downloadFailed(QString reason) -{ - emitFailed(reason); - m_filesNetJob.reset(); -} + QCoreApplication::processEvents(); + } -void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total) -{ - setProgress(current, total); -} + // Recurse the search to non-ignored subfolders + for (auto&& fileName : rootDir.entryList(QDir::Dirs)) { + if ("overrides/" == fileName) + continue; -void InstanceImportTask::downloadAborted() -{ - emitAborted(); - m_filesNetJob.reset(); + QString result = getRootFromZip(zip, root + fileName); + if (!result.isEmpty()) + return result; + } + + return {}; } void InstanceImportTask::processZipPack() { - setStatus(tr("Extracting modpack")); + setStatus(tr("Attempting to determine instance type")); QDir extractDir(m_stagingPath); 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)) - { + auto packZip = std::make_shared(m_archivePath); + if (!packZip->open(QuaZip::mdUnzip)) { emitFailed(tr("Unable to open supplied modpack zip file.")); return; } - QuaZipDir packZipDir(m_packZip.get()); + QuaZipDir packZipDir(packZip.get()); + qDebug() << "Attempting to determine instance type"; - // https://docs.modrinth.com/docs/modpacks/format_definition/#storage - bool modrinthFound = packZipDir.exists("/modrinth.index.json"); - bool technicFound = packZipDir.exists("/bin/modpack.jar") || packZipDir.exists("/bin/version.json"); QString root; // NOTE: Prioritize modpack platforms that aren't searched for recursively. // Especially Flame has a very common filename for its manifest, which may appear inside overrides for example - if(modrinthFound) - { + // https://docs.modrinth.com/docs/modpacks/format_definition/#storage + if (packZipDir.exists("/modrinth.index.json")) { // process as Modrinth pack - qDebug() << "Modrinth:" << modrinthFound; + qDebug() << "Modrinth:" << true; m_modpackType = ModpackType::Modrinth; - } - else if (technicFound) - { + } else if (packZipDir.exists("/bin/modpack.jar") || packZipDir.exists("/bin/version.json")) { // process as Technic pack - qDebug() << "Technic:" << technicFound; + qDebug() << "Technic:" << true; extractDir.mkpath(".minecraft"); extractDir.cd(".minecraft"); m_modpackType = ModpackType::Technic; + } else { + root = getRootFromZip(packZip.get()); + setDetails(""); } - else - { - QStringList paths_to_ignore { "overrides/" }; - - if (QString mmcRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg", paths_to_ignore); !mmcRoot.isNull()) { - // process as MultiMC instance/pack - qDebug() << "MultiMC:" << mmcRoot; - root = mmcRoot; - m_modpackType = ModpackType::MultiMC; - } else if (QString flameRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json", paths_to_ignore); !flameRoot.isNull()) { - // process as Flame pack - qDebug() << "Flame:" << flameRoot; - root = flameRoot; - m_modpackType = ModpackType::Flame; - } - } - if(m_modpackType == ModpackType::Unknown) - { + if (m_modpackType == ModpackType::Unknown) { emitFailed(tr("Archive does not contain a recognized modpack type.")); return; } + setStatus(tr("Extracting modpack")); // make sure we extract just the pack - m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), root, extractDir.absolutePath()); - connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, &InstanceImportTask::extractFinished); - m_extractFutureWatcher.setFuture(m_extractFuture); + auto zipTask = makeShared(packZip, extractDir, root); + + auto progressStep = std::make_shared(); + connect(zipTask.get(), &Task::finished, this, [this, progressStep] { + progressStep->state = TaskStepState::Succeeded; + stepProgress(*progressStep); + }); + + connect(zipTask.get(), &Task::succeeded, this, &InstanceImportTask::extractFinished); + connect(zipTask.get(), &Task::aborted, this, &InstanceImportTask::emitAborted); + connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) { + progressStep->state = TaskStepState::Failed; + stepProgress(*progressStep); + emitFailed(reason); + }); + connect(zipTask.get(), &Task::stepProgress, this, &InstanceImportTask::propogateStepProgress); + + connect(zipTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) { + progressStep->update(current, total); + stepProgress(*progressStep); + }); + connect(zipTask.get(), &Task::status, this, [this, progressStep](QString status) { + progressStep->status = status; + stepProgress(*progressStep); + }); + task.reset(zipTask); + zipTask->start(); } void InstanceImportTask::extractFinished() { - m_packZip.reset(); - - if (m_extractFuture.isCanceled()) - return; - if (!m_extractFuture.result().has_value()) { - emitFailed(tr("Failed to extract modpack")); - return; - } - QDir extractDir(m_stagingPath); qDebug() << "Fixing permissions for extracted pack files..."; QDirIterator it(extractDir, QDirIterator::Subdirectories); - while (it.hasNext()) - { + while (it.hasNext()) { auto filepath = it.next(); QFileInfo file(filepath); auto permissions = QFile::permissions(filepath); auto origPermissions = permissions; - if(file.isDir()) - { + if (file.isDir()) { // Folder +rwx for current user permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser; - } - else - { + } else { // File +rw for current user permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser; } - if(origPermissions != permissions) - { - if(!QFile::setPermissions(filepath, permissions)) - { + if (origPermissions != permissions) { + if (!QFile::setPermissions(filepath, permissions)) { logWarning(tr("Could not fix permissions for %1").arg(filepath)); - } - else - { + } else { qDebug() << "Fixed" << filepath; } } } - switch(m_modpackType) - { + switch (m_modpackType) { case ModpackType::MultiMC: processMultiMC(); return; @@ -276,7 +269,8 @@ void InstanceImportTask::processFlame() if (original_instance_id_it != m_extra_info.constEnd()) original_instance_id = original_instance_id_it.value(); - inst_creation_task = makeShared(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id); + inst_creation_task = + makeShared(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id); } else { // FIXME: Find a way to get IDs in directly imported ZIPs inst_creation_task = makeShared(m_stagingPath, m_globalSettings, m_parent, QString(), QString()); @@ -286,7 +280,7 @@ void InstanceImportTask::processFlame() inst_creation_task->setIcon(m_instIcon); inst_creation_task->setGroup(m_instGroup); inst_creation_task->setConfirmUpdate(shouldConfirmUpdate()); - + connect(inst_creation_task.get(), &Task::succeeded, this, [this, inst_creation_task] { setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID()); emitSucceeded(); @@ -362,7 +356,8 @@ void InstanceImportTask::processModrinth() if (original_instance_id_it != m_extra_info.constEnd()) original_instance_id = original_instance_id_it.value(); - inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id); + inst_creation_task = + new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id); } else { QString pack_id; if (!m_sourceUrl.isEmpty()) { @@ -378,7 +373,7 @@ void InstanceImportTask::processModrinth() inst_creation_task->setIcon(m_instIcon); inst_creation_task->setGroup(m_instGroup); inst_creation_task->setConfirmUpdate(shouldConfirmUpdate()); - + connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] { setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID()); emitSucceeded(); diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h index 7fda439fc..cf4025194 100644 --- a/launcher/InstanceImportTask.h +++ b/launcher/InstanceImportTask.h @@ -35,64 +35,46 @@ #pragma once -#include "InstanceTask.h" -#include "net/NetJob.h" -#include #include #include -#include "settings/SettingsObject.h" -#include "QObjectPtr.h" -#include "modplatform/flame/PackManifest.h" +#include +#include "InstanceTask.h" +#include #include class QuaZip; -namespace Flame -{ - class FileResolvingTask; +namespace Flame { +class FileResolvingTask; } -class InstanceImportTask : public InstanceTask -{ +class InstanceImportTask : public InstanceTask { Q_OBJECT -public: + public: explicit InstanceImportTask(const QUrl sourceUrl, QWidget* parent = nullptr, QMap&& extra_info = {}); bool abort() override; - const QVector &getBlockedFiles() const - { - return m_blockedMods; - } -protected: + protected: //! Entry point for tasks. virtual void executeTask() override; -private: - void processZipPack(); + private: void processMultiMC(); void processTechnic(); void processFlame(); void processModrinth(); + QString getRootFromZip(QuaZip* zip, const QString& root = ""); -private slots: - void downloadSucceeded(); - void downloadFailed(QString reason); - void downloadProgressChanged(qint64 current, qint64 total); - void downloadAborted(); + private slots: + void processZipPack(); void extractFinished(); -private: /* data */ - NetJob::Ptr m_filesNetJob; - shared_qobject_ptr m_modIdResolver; + private: /* data */ QUrl m_sourceUrl; QString m_archivePath; - bool m_downloadRequired = false; - std::unique_ptr m_packZip; - QFuture> m_extractFuture; - QFutureWatcher> m_extractFutureWatcher; - QVector m_blockedMods; - enum class ModpackType{ + Task::Ptr task; + enum class ModpackType { Unknown, MultiMC, Technic, @@ -104,6 +86,6 @@ private: /* data */ // the source URL / the resource it points to alone. QMap m_extra_info; - //FIXME: nuke + // FIXME: nuke QWidget* m_parent; }; diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index acd6bf7e4..13ad96e4e 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -501,4 +501,109 @@ bool ExportToZipTask::abort() return false; } +void ExtractZipTask::executeTask() +{ + m_zip_future = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return extractZip(); }); + connect(&m_zip_watcher, &QFutureWatcher::finished, this, &ExtractZipTask::finish); + m_zip_watcher.setFuture(m_zip_future); +} + +auto ExtractZipTask::extractZip() -> ZipResult +{ + auto target = m_output_dir.absolutePath(); + auto target_top_dir = QUrl::fromLocalFile(target); + + QStringList extracted; + + qDebug() << "Extracting subdir" << m_subdirectory << "from" << m_input->getZipName() << "to" << target; + auto numEntries = m_input->getEntriesCount(); + if (numEntries < 0) { + return ZipResult(tr("Failed to enumerate files in archive")); + } + if (numEntries == 0) { + logWarning(tr("Extracting empty archives seems odd...")); + return ZipResult(); + } + if (!m_input->goToFirstFile()) { + return ZipResult(tr("Failed to seek to first file in zip")); + } + + setStatus("Extracting files..."); + setProgress(0, numEntries); + do { + if (m_zip_future.isCanceled()) + return ZipResult(); + setProgress(m_progress + 1, m_progressTotal); + QString file_name = m_input->getCurrentFileName(); + if (!file_name.startsWith(m_subdirectory)) + continue; + + auto relative_file_name = QDir::fromNativeSeparators(file_name.remove(0, m_subdirectory.size())); + auto original_name = relative_file_name; + setStatus("Unziping: " + relative_file_name); + + // Fix subdirs/files ending with a / getting transformed into absolute paths + if (relative_file_name.startsWith('/')) + relative_file_name = relative_file_name.mid(1); + + // Fix weird "folders with a single file get squashed" thing + QString sub_path; + if (relative_file_name.contains('/') && !relative_file_name.endsWith('/')) { + sub_path = relative_file_name.section('/', 0, -2) + '/'; + FS::ensureFolderPathExists(FS::PathCombine(target, sub_path)); + + relative_file_name = relative_file_name.split('/').last(); + } + + QString target_file_path; + if (relative_file_name.isEmpty()) { + target_file_path = target + '/'; + } else { + target_file_path = FS::PathCombine(target_top_dir.toLocalFile(), sub_path, relative_file_name); + if (relative_file_name.endsWith('/') && !target_file_path.endsWith('/')) + target_file_path += '/'; + } + + if (!target_top_dir.isParentOf(QUrl::fromLocalFile(target_file_path))) { + return ZipResult(tr("Extracting %1 was cancelled, because it was effectively outside of the target path %2") + .arg(relative_file_name, target)); + } + + if (!JlCompress::extractFile(m_input.get(), "", target_file_path)) { + JlCompress::removeFile(extracted); + return ZipResult(tr("Failed to extract file %1 to %2").arg(original_name, target_file_path)); + } + + extracted.append(target_file_path); + 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 (m_input->goToNextFile()); + + return ZipResult(); +} + +void ExtractZipTask::finish() +{ + if (m_zip_future.isCanceled()) { + emitAborted(); + } else if (auto result = m_zip_future.result(); result.has_value()) { + emitFailed(result.value()); + } else { + emitSucceeded(); + } +} + +bool ExtractZipTask::abort() +{ + if (m_zip_future.isRunning()) { + m_zip_future.cancel(); + // NOTE: Here we don't do `emitAborted()` because it will be done when `m_build_zip_future` actually cancels, which may not occur + // immediately. + return true; + } + return false; +} + } // namespace MMCZip \ No newline at end of file diff --git a/launcher/MMCZip.h b/launcher/MMCZip.h index bc527ad1b..212be6f51 100644 --- a/launcher/MMCZip.h +++ b/launcher/MMCZip.h @@ -189,4 +189,29 @@ class ExportToZipTask : public Task { QFuture m_build_zip_future; QFutureWatcher m_build_zip_watcher; }; + +class ExtractZipTask : public Task { + public: + ExtractZipTask(std::shared_ptr input, QDir outputDir, QString subdirectory = "") + : m_input(input), m_output_dir(outputDir), m_subdirectory(subdirectory) + {} + virtual ~ExtractZipTask() = default; + + typedef std::optional ZipResult; + + protected: + virtual void executeTask() override; + bool abort() override; + + ZipResult extractZip(); + void finish(); + + private: + std::shared_ptr m_input; + QDir m_output_dir; + QString m_subdirectory; + + QFuture m_zip_future; + QFutureWatcher m_zip_watcher; +}; } // namespace MMCZip diff --git a/launcher/ui/pages/modplatform/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp index 30196aad6..a45f3a81c 100644 --- a/launcher/ui/pages/modplatform/ImportPage.cpp +++ b/launcher/ui/pages/modplatform/ImportPage.cpp @@ -38,6 +38,7 @@ #include "ui_ImportPage.h" #include +#include #include #include "ui/dialogs/NewInstanceDialog.h" From 907c2fd19c148230d58a3d02fd31ecd8bf7aae86 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 18 Aug 2023 09:16:17 +0300 Subject: [PATCH 002/103] added missing header Signed-off-by: Trial97 --- launcher/ui/pages/modplatform/ImportPage.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/ui/pages/modplatform/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp index 3cb161629..cc11d2c16 100644 --- a/launcher/ui/pages/modplatform/ImportPage.cpp +++ b/launcher/ui/pages/modplatform/ImportPage.cpp @@ -52,6 +52,7 @@ #include "Json.h" #include "InstanceImportTask.h" +#include "net/NetJob.h" class UrlValidator : public QValidator { public: From bf810053b720ac728affd45878147ce593b4f6fd Mon Sep 17 00:00:00 2001 From: Trial97 Date: Mon, 21 Aug 2023 17:21:11 +0300 Subject: [PATCH 003/103] updated instance copy Signed-off-by: Trial97 --- launcher/FileSystem.h | 1 + launcher/InstanceCopyTask.cpp | 83 +++++++++++++++++++++++------------ launcher/InstanceCopyTask.h | 1 + 3 files changed, 56 insertions(+), 29 deletions(-) diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index bfed576c1..946e55a2c 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -218,6 +218,7 @@ class create_link : public QObject { bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); } int totalLinked() { return m_linked; } + int totalToLink() { return static_cast(m_links_to_make.size()); } void runPrivileged() { runPrivileged(QString()); } void runPrivileged(const QString& offset); diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index 8abf30640..3a40bc06d 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -1,10 +1,12 @@ #include "InstanceCopyTask.h" #include #include +#include #include "FileSystem.h" #include "NullInstance.h" #include "pathmatcher/RegexpMatcher.h" #include "settings/INISettingsObject.h" +#include "tasks/Task.h" InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs) { @@ -38,38 +40,50 @@ void InstanceCopyTask::executeTask() { setStatus(tr("Copying instance %1").arg(m_origInstance->name())); - auto copySaves = [&]() { - QFileInfo mcDir(FS::PathCombine(m_stagingPath, "minecraft")); - QFileInfo dotMCDir(FS::PathCombine(m_stagingPath, ".minecraft")); - - QString staging_mc_dir; - if (mcDir.exists() && !dotMCDir.exists()) - staging_mc_dir = mcDir.filePath(); - else - staging_mc_dir = dotMCDir.filePath(); - - FS::copy savesCopy(FS::PathCombine(m_origInstance->gameRoot(), "saves"), FS::PathCombine(staging_mc_dir, "saves")); - savesCopy.followSymlinks(true); - - return savesCopy(); - }; - - m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this, copySaves] { + m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] { if (m_useClone) { FS::clone folderClone(m_origInstance->instanceRoot(), m_stagingPath); folderClone.matcher(m_matcher.get()); + folderClone(true); + setProgress(0, folderClone.totalCloned()); + connect(&folderClone, &FS::clone::fileCloned, + [this](QString src, QString dst) { setProgress(m_progress + 1, m_progressTotal); }); return folderClone(); - } else if (m_useLinks || m_useHardLinks) { + } + if (m_useLinks || m_useHardLinks) { + std::unique_ptr savesCopy; + if (m_copySaves) { + QFileInfo mcDir(FS::PathCombine(m_stagingPath, "minecraft")); + QFileInfo dotMCDir(FS::PathCombine(m_stagingPath, ".minecraft")); + + QString staging_mc_dir; + if (mcDir.exists() && !dotMCDir.exists()) + staging_mc_dir = mcDir.filePath(); + else + staging_mc_dir = dotMCDir.filePath(); + + savesCopy = std::make_unique(FS::PathCombine(m_origInstance->gameRoot(), "saves"), + FS::PathCombine(staging_mc_dir, "saves")); + savesCopy->followSymlinks(true); + (*savesCopy)(true); + setProgress(0, savesCopy->totalCopied()); + connect(savesCopy.get(), &FS::copy::fileCopied, [this](QString src) { setProgress(m_progress + 1, m_progressTotal); }); + } FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath); int depth = m_linkRecursively ? -1 : 0; // we need to at least link the top level instead of the instance folder folderLink.linkRecursively(true).setMaxDepth(depth).useHardLinks(m_useHardLinks).matcher(m_matcher.get()); + folderLink(true); + setProgress(0, m_progressTotal + folderLink.totalToLink()); + connect(&folderLink, &FS::create_link::fileLinked, + [this](QString src, QString dst) { setProgress(m_progress + 1, m_progressTotal); }); bool there_were_errors = false; if (!folderLink()) { #if defined Q_OS_WIN32 if (!m_useHardLinks) { + setProgress(0, m_progressTotal); qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks"; qDebug() << "attempting to run with privelage"; @@ -94,13 +108,11 @@ void InstanceCopyTask::executeTask() } } - if (m_copySaves) { - there_were_errors |= !copySaves(); + if (savesCopy) { + there_were_errors |= !(*savesCopy)(); } return got_priv_results && !there_were_errors; - } else { - qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str(); } #else qDebug() << "Link Failed!" << folderLink.getOSError().value() << folderLink.getOSError().message().c_str(); @@ -108,17 +120,19 @@ void InstanceCopyTask::executeTask() return false; } - if (m_copySaves) { - there_were_errors |= !copySaves(); + if (savesCopy) { + there_were_errors |= !(*savesCopy)(); } return !there_were_errors; - } else { - FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath); - folderCopy.followSymlinks(false).matcher(m_matcher.get()); - - return folderCopy(); } + FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath); + folderCopy.followSymlinks(false).matcher(m_matcher.get()); + + folderCopy(true); + setProgress(0, folderCopy.totalCopied()); + connect(&folderCopy, &FS::copy::fileCopied, [this](QString src) { setProgress(m_progress + 1, m_progressTotal); }); + return folderCopy(); }); connect(&m_copyFutureWatcher, &QFutureWatcher::finished, this, &InstanceCopyTask::copyFinished); connect(&m_copyFutureWatcher, &QFutureWatcher::canceled, this, &InstanceCopyTask::copyAborted); @@ -171,3 +185,14 @@ void InstanceCopyTask::copyAborted() emitFailed(tr("Instance folder copy has been aborted.")); return; } + +bool InstanceCopyTask::abort() +{ + if (m_copyFutureWatcher.isRunning()) { + m_copyFutureWatcher.cancel(); + // NOTE: Here we don't do `emitAborted()` because it will be done when `m_copyFutureWatcher` actually cancels, which may not occur + // immediately. + return true; + } + return false; +} \ No newline at end of file diff --git a/launcher/InstanceCopyTask.h b/launcher/InstanceCopyTask.h index 357c6df0b..0f7f1020d 100644 --- a/launcher/InstanceCopyTask.h +++ b/launcher/InstanceCopyTask.h @@ -19,6 +19,7 @@ class InstanceCopyTask : public InstanceTask { protected: //! Entry point for tasks. virtual void executeTask() override; + bool abort() override; void copyFinished(); void copyAborted(); From 609eaa67abd9e9844f5cd52f81c70826689cd90a Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 1 Sep 2023 21:23:51 +0300 Subject: [PATCH 004/103] refactored skin apis Signed-off-by: Trial97 --- launcher/CMakeLists.txt | 1 + launcher/minecraft/services/CapeChange.cpp | 89 +++++--------------- launcher/minecraft/services/CapeChange.h | 30 +++---- launcher/minecraft/services/SkinDelete.cpp | 59 ++++--------- launcher/minecraft/services/SkinDelete.h | 26 +++--- launcher/minecraft/services/SkinUpload.cpp | 84 +++++++----------- launcher/minecraft/services/SkinUpload.h | 31 +++---- launcher/net/Logging.cpp | 1 + launcher/net/Logging.h | 1 + launcher/net/NetRequest.cpp | 2 + launcher/net/StaticHeaderProxy.h | 39 +++++++++ launcher/ui/dialogs/SkinUploadDialog.cpp | 5 +- launcher/ui/pages/global/AccountListPage.cpp | 2 +- 13 files changed, 145 insertions(+), 225 deletions(-) create mode 100644 launcher/net/StaticHeaderProxy.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 18e0acab1..3b6218b7f 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -139,6 +139,7 @@ set(NET_SOURCES net/HeaderProxy.h net/RawHeaderProxy.h net/ApiHeaderProxy.h + net/StaticHeaderProxy.h net/ApiDownload.h net/ApiDownload.cpp net/ApiUpload.cpp diff --git a/launcher/minecraft/services/CapeChange.cpp b/launcher/minecraft/services/CapeChange.cpp index 2ba38a6af..5a7820b54 100644 --- a/launcher/minecraft/services/CapeChange.cpp +++ b/launcher/minecraft/services/CapeChange.cpp @@ -35,87 +35,38 @@ #include "CapeChange.h" -#include -#include +#include -#include "Application.h" +#include "net/ByteArraySink.h" +#include "net/StaticHeaderProxy.h" -CapeChange::CapeChange(QObject* parent, QString token, QString cape) : Task(parent), m_capeId(cape), m_token(token) {} - -void CapeChange::setCape([[maybe_unused]] QString& cape) +CapeChange::CapeChange(QString token, QString cape) : NetRequest(), m_capeId(cape), m_token(token) { - QNetworkRequest request(QUrl("https://api.minecraftservices.com/minecraft/profile/capes/active")); - auto requestString = QString("{\"capeId\":\"%1\"}").arg(m_capeId); - request.setRawHeader("Authorization", QString("Bearer %1").arg(m_token).toLocal8Bit()); - QNetworkReply* rep = APPLICATION->network()->put(request, requestString.toUtf8()); + logCat = taskMCServicesLogC; +}; - setStatus(tr("Equipping cape")); - - m_reply = shared_qobject_ptr(rep); - connect(rep, &QNetworkReply::uploadProgress, this, &CapeChange::setProgress); -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 - connect(rep, &QNetworkReply::errorOccurred, this, &CapeChange::downloadError); -#else - connect(rep, QOverload::of(&QNetworkReply::error), this, &CapeChange::downloadError); -#endif - connect(rep, &QNetworkReply::sslErrors, this, &CapeChange::sslErrors); - connect(rep, &QNetworkReply::finished, this, &CapeChange::downloadFinished); -} - -void CapeChange::clearCape() -{ - QNetworkRequest request(QUrl("https://api.minecraftservices.com/minecraft/profile/capes/active")); - auto requestString = QString("{\"capeId\":\"%1\"}").arg(m_capeId); - request.setRawHeader("Authorization", QString("Bearer %1").arg(m_token).toLocal8Bit()); - QNetworkReply* rep = APPLICATION->network()->deleteResource(request); - - setStatus(tr("Removing cape")); - - m_reply = shared_qobject_ptr(rep); - connect(rep, &QNetworkReply::uploadProgress, this, &CapeChange::setProgress); -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 - connect(rep, &QNetworkReply::errorOccurred, this, &CapeChange::downloadError); -#else - connect(rep, QOverload::of(&QNetworkReply::error), this, &CapeChange::downloadError); -#endif - connect(rep, &QNetworkReply::sslErrors, this, &CapeChange::sslErrors); - connect(rep, &QNetworkReply::finished, this, &CapeChange::downloadFinished); -} - -void CapeChange::executeTask() +QNetworkReply* CapeChange::getReply(QNetworkRequest& request) { if (m_capeId.isEmpty()) { - clearCape(); + setStatus(tr("Removing cape")); + return m_network->deleteResource(request); } else { - setCape(m_capeId); + setStatus(tr("Equipping cape")); + return m_network->post(request, QString("{\"capeId\":\"%1\"}").arg(m_capeId).toUtf8()); } } -void CapeChange::downloadError(QNetworkReply::NetworkError error) +void CapeChange::init() { - // error happened during download. - qCritical() << "Network error: " << error; - emitFailed(m_reply->errorString()); + addHeaderProxy(new Net::StaticHeaderProxy(QList{ + { "Authorization", QString("Bearer %1").arg(m_token).toLocal8Bit() }, + })); } -void CapeChange::sslErrors(const QList& errors) +CapeChange::Ptr CapeChange::make(QString token, QString capeId) { - int i = 1; - for (auto error : errors) { - qCritical() << "Cape change SSL Error #" << i << " : " << error.errorString(); - auto cert = error.certificate(); - qCritical() << "Certificate in question:\n" << cert.toText(); - i++; - } -} - -void CapeChange::downloadFinished() -{ - // if the download failed - if (m_reply->error() != QNetworkReply::NetworkError::NoError) { - emitFailed(QString("Network error: %1").arg(m_reply->errorString())); - m_reply.reset(); - return; - } - emitSucceeded(); + auto up = makeShared(token, capeId); + up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/capes/active"); + up->m_sink.reset(new Net::ByteArraySink(std::make_shared())); + return up; } diff --git a/launcher/minecraft/services/CapeChange.h b/launcher/minecraft/services/CapeChange.h index d0c893c44..74805ef43 100644 --- a/launcher/minecraft/services/CapeChange.h +++ b/launcher/minecraft/services/CapeChange.h @@ -1,31 +1,21 @@ #pragma once -#include -#include -#include -#include "QObjectPtr.h" -#include "tasks/Task.h" +#include "net/NetRequest.h" -class CapeChange : public Task { +class CapeChange : public Net::NetRequest { Q_OBJECT public: - CapeChange(QObject* parent, QString token, QString capeId); - virtual ~CapeChange() {} + using Ptr = shared_qobject_ptr; + CapeChange(QString token, QString capeId); + virtual ~CapeChange() = default; - private: - void setCape(QString& cape); - void clearCape(); + static CapeChange::Ptr make(QString token, QString capeId); + void init() override; + + protected: + virtual QNetworkReply* getReply(QNetworkRequest&) override; private: QString m_capeId; QString m_token; - shared_qobject_ptr m_reply; - - protected: - virtual void executeTask(); - - public slots: - void downloadError(QNetworkReply::NetworkError); - void sslErrors(const QList& errors); - void downloadFinished(); }; diff --git a/launcher/minecraft/services/SkinDelete.cpp b/launcher/minecraft/services/SkinDelete.cpp index 9e9020692..7944637f6 100644 --- a/launcher/minecraft/services/SkinDelete.cpp +++ b/launcher/minecraft/services/SkinDelete.cpp @@ -35,56 +35,31 @@ #include "SkinDelete.h" -#include -#include +#include "net/ByteArraySink.h" +#include "net/StaticHeaderProxy.h" -#include "Application.h" - -SkinDelete::SkinDelete(QObject* parent, QString token) : Task(parent), m_token(token) {} - -void SkinDelete::executeTask() +SkinDelete::SkinDelete(QString token) : NetRequest(), m_token(token) { - QNetworkRequest request(QUrl("https://api.minecraftservices.com/minecraft/profile/skins/active")); - request.setRawHeader("Authorization", QString("Bearer %1").arg(m_token).toLocal8Bit()); - QNetworkReply* rep = APPLICATION->network()->deleteResource(request); - m_reply = shared_qobject_ptr(rep); + logCat = taskMCServicesLogC; +}; +QNetworkReply* SkinDelete::getReply(QNetworkRequest& request) +{ setStatus(tr("Deleting skin")); - connect(rep, &QNetworkReply::uploadProgress, this, &SkinDelete::setProgress); -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 - connect(rep, &QNetworkReply::errorOccurred, this, &SkinDelete::downloadError); -#else - connect(rep, QOverload::of(&QNetworkReply::error), this, &SkinDelete::downloadError); -#endif - connect(rep, &QNetworkReply::sslErrors, this, &SkinDelete::sslErrors); - connect(rep, &QNetworkReply::finished, this, &SkinDelete::downloadFinished); + return m_network->deleteResource(request); } -void SkinDelete::downloadError(QNetworkReply::NetworkError error) +void SkinDelete::init() { - // error happened during download. - qCritical() << "Network error: " << error; - emitFailed(m_reply->errorString()); + addHeaderProxy(new Net::StaticHeaderProxy(QList{ + { "Authorization", QString("Bearer %1").arg(m_token).toLocal8Bit() }, + })); } -void SkinDelete::sslErrors(const QList& errors) +SkinDelete::Ptr SkinDelete::make(QString token) { - int i = 1; - for (auto error : errors) { - qCritical() << "Skin Delete SSL Error #" << i << " : " << error.errorString(); - auto cert = error.certificate(); - qCritical() << "Certificate in question:\n" << cert.toText(); - i++; - } -} - -void SkinDelete::downloadFinished() -{ - // if the download failed - if (m_reply->error() != QNetworkReply::NetworkError::NoError) { - emitFailed(QString("Network error: %1").arg(m_reply->errorString())); - m_reply.reset(); - return; - } - emitSucceeded(); + auto up = makeShared(token); + up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/skins/active"); + up->m_sink.reset(new Net::ByteArraySink(std::make_shared())); + return up; } diff --git a/launcher/minecraft/services/SkinDelete.h b/launcher/minecraft/services/SkinDelete.h index d5b2e63db..b0fb866cd 100644 --- a/launcher/minecraft/services/SkinDelete.h +++ b/launcher/minecraft/services/SkinDelete.h @@ -1,26 +1,20 @@ #pragma once -#include -#include -#include "tasks/Task.h" +#include "net/NetRequest.h" -typedef shared_qobject_ptr SkinDeletePtr; - -class SkinDelete : public Task { +class SkinDelete : public Net::NetRequest { Q_OBJECT public: - SkinDelete(QObject* parent, QString token); + using Ptr = shared_qobject_ptr; + SkinDelete(QString token); virtual ~SkinDelete() = default; + static SkinDelete::Ptr make(QString token); + void init() override; + + protected: + virtual QNetworkReply* getReply(QNetworkRequest&) override; + private: QString m_token; - shared_qobject_ptr m_reply; - - protected: - virtual void executeTask(); - - public slots: - void downloadError(QNetworkReply::NetworkError); - void sslErrors(const QList& errors); - void downloadFinished(); }; diff --git a/launcher/minecraft/services/SkinUpload.cpp b/launcher/minecraft/services/SkinUpload.cpp index 163b481b1..0400fa0f4 100644 --- a/launcher/minecraft/services/SkinUpload.cpp +++ b/launcher/minecraft/services/SkinUpload.cpp @@ -36,30 +36,17 @@ #include "SkinUpload.h" #include -#include -#include "Application.h" +#include "net/ByteArraySink.h" +#include "net/StaticHeaderProxy.h" -QByteArray getVariant(SkinUpload::Model model) +SkinUpload::SkinUpload(QString token, QByteArray skin, SkinUpload::Model model) : NetRequest(), m_model(model), m_skin(skin), m_token(token) { - switch (model) { - default: - qDebug() << "Unknown skin type!"; - case SkinUpload::STEVE: - return "CLASSIC"; - case SkinUpload::ALEX: - return "SLIM"; - } -} + logCat = taskMCServicesLogC; +}; -SkinUpload::SkinUpload(QObject* parent, QString token, QByteArray skin, SkinUpload::Model model) - : Task(parent), m_model(model), m_skin(skin), m_token(token) -{} - -void SkinUpload::executeTask() +QNetworkReply* SkinUpload::getReply(QNetworkRequest& request) { - QNetworkRequest request(QUrl("https://api.minecraftservices.com/minecraft/profile/skins")); - request.setRawHeader("Authorization", QString("Bearer %1").arg(m_token).toLocal8Bit()); QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); QHttpPart skin; @@ -69,50 +56,37 @@ void SkinUpload::executeTask() QHttpPart model; model.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"variant\"")); - model.setBody(getVariant(m_model)); + + switch (m_model) { + default: + qDebug() << "Unknown skin type!"; + emitFailed("Unknown skin type!"); + return nullptr; + case SkinUpload::STEVE: + model.setBody("CLASSIC"); + break; + case SkinUpload::ALEX: + model.setBody("SLIM"); + break; + } multiPart->append(skin); multiPart->append(model); - - QNetworkReply* rep = APPLICATION->network()->post(request, multiPart); - m_reply = shared_qobject_ptr(rep); - setStatus(tr("Uploading skin")); - connect(rep, &QNetworkReply::uploadProgress, this, &SkinUpload::setProgress); -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 - connect(rep, &QNetworkReply::errorOccurred, this, &SkinUpload::downloadError); -#else - connect(rep, QOverload::of(&QNetworkReply::error), this, &SkinUpload::downloadError); -#endif - connect(rep, &QNetworkReply::sslErrors, this, &SkinUpload::sslErrors); - connect(rep, &QNetworkReply::finished, this, &SkinUpload::downloadFinished); + return m_network->post(request, multiPart); } -void SkinUpload::downloadError(QNetworkReply::NetworkError error) +void SkinUpload::init() { - // error happened during download. - qCritical() << "Network error: " << error; - emitFailed(m_reply->errorString()); + addHeaderProxy(new Net::StaticHeaderProxy(QList{ + { "Authorization", QString("Bearer %1").arg(m_token).toLocal8Bit() }, + })); } -void SkinUpload::sslErrors(const QList& errors) +SkinUpload::Ptr SkinUpload::make(QString token, QByteArray skin, SkinUpload::Model model) { - int i = 1; - for (auto error : errors) { - qCritical() << "Skin Upload SSL Error #" << i << " : " << error.errorString(); - auto cert = error.certificate(); - qCritical() << "Certificate in question:\n" << cert.toText(); - i++; - } -} - -void SkinUpload::downloadFinished() -{ - // if the download failed - if (m_reply->error() != QNetworkReply::NetworkError::NoError) { - emitFailed(QString("Network error: %1").arg(m_reply->errorString())); - m_reply.reset(); - return; - } - emitSucceeded(); + auto up = makeShared(token, skin, model); + up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/skins"); + up->m_sink.reset(new Net::ByteArraySink(std::make_shared())); + return up; } diff --git a/launcher/minecraft/services/SkinUpload.h b/launcher/minecraft/services/SkinUpload.h index 5716aa996..2da836d52 100644 --- a/launcher/minecraft/services/SkinUpload.h +++ b/launcher/minecraft/services/SkinUpload.h @@ -1,34 +1,25 @@ #pragma once -#include -#include -#include -#include "tasks/Task.h" +#include "net/NetRequest.h" -typedef shared_qobject_ptr SkinUploadPtr; - -class SkinUpload : public Task { +class SkinUpload : public Net::NetRequest { Q_OBJECT public: + using Ptr = shared_qobject_ptr; enum Model { STEVE, ALEX }; // Note this class takes ownership of the file. - SkinUpload(QObject* parent, QString token, QByteArray skin, Model model = STEVE); - virtual ~SkinUpload() {} + SkinUpload(QString token, QByteArray skin, Model model = STEVE); + virtual ~SkinUpload() = default; + + static SkinUpload::Ptr make(QString token, QByteArray skin, Model model = STEVE); + void init() override; + + protected: + virtual QNetworkReply* getReply(QNetworkRequest&) override; private: Model m_model; QByteArray m_skin; QString m_token; - shared_qobject_ptr m_reply; - - protected: - virtual void executeTask(); - - public slots: - - void downloadError(QNetworkReply::NetworkError); - void sslErrors(const QList& errors); - - void downloadFinished(); }; diff --git a/launcher/net/Logging.cpp b/launcher/net/Logging.cpp index a9b9db7cf..45d2dcc20 100644 --- a/launcher/net/Logging.cpp +++ b/launcher/net/Logging.cpp @@ -22,5 +22,6 @@ Q_LOGGING_CATEGORY(taskNetLogC, "launcher.task.net") Q_LOGGING_CATEGORY(taskDownloadLogC, "launcher.task.net.download") Q_LOGGING_CATEGORY(taskUploadLogC, "launcher.task.net.upload") +Q_LOGGING_CATEGORY(taskMCServicesLogC, "launcher.task.minecraft.servicies") Q_LOGGING_CATEGORY(taskMetaCacheLogC, "launcher.task.net.metacache") Q_LOGGING_CATEGORY(taskHttpMetaCacheLogC, "launcher.task.net.metacache.http") diff --git a/launcher/net/Logging.h b/launcher/net/Logging.h index 4deed2b49..d3a11cdce 100644 --- a/launcher/net/Logging.h +++ b/launcher/net/Logging.h @@ -24,5 +24,6 @@ Q_DECLARE_LOGGING_CATEGORY(taskNetLogC) Q_DECLARE_LOGGING_CATEGORY(taskDownloadLogC) Q_DECLARE_LOGGING_CATEGORY(taskUploadLogC) +Q_DECLARE_LOGGING_CATEGORY(taskMCServicesLogC) Q_DECLARE_LOGGING_CATEGORY(taskMetaCacheLogC) Q_DECLARE_LOGGING_CATEGORY(taskHttpMetaCacheLogC) diff --git a/launcher/net/NetRequest.cpp b/launcher/net/NetRequest.cpp index ff59da18b..eef550e15 100644 --- a/launcher/net/NetRequest.cpp +++ b/launcher/net/NetRequest.cpp @@ -111,6 +111,8 @@ void NetRequest::executeTask() m_last_progress_bytes = 0; QNetworkReply* rep = getReply(request); + if (rep == nullptr) // it failed + return; m_reply.reset(rep); connect(rep, &QNetworkReply::downloadProgress, this, &NetRequest::downloadProgress); connect(rep, &QNetworkReply::finished, this, &NetRequest::downloadFinished); diff --git a/launcher/net/StaticHeaderProxy.h b/launcher/net/StaticHeaderProxy.h new file mode 100644 index 000000000..0e62d80ff --- /dev/null +++ b/launcher/net/StaticHeaderProxy.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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 . + * + */ + +#pragma once + +#include "net/HeaderProxy.h" + +namespace Net { + +class StaticHeaderProxy : public HeaderProxy { + public: + StaticHeaderProxy(QList hdrs = {}) : HeaderProxy(), m_hdrs(hdrs){}; + virtual ~StaticHeaderProxy() = default; + + public: + virtual QList headers(const QNetworkRequest&) const override { return m_hdrs; }; + void setHeaders(QList hdrs) { m_hdrs = hdrs; }; + + private: + QList m_hdrs; +}; + +} // namespace Net \ No newline at end of file diff --git a/launcher/ui/dialogs/SkinUploadDialog.cpp b/launcher/ui/dialogs/SkinUploadDialog.cpp index 5b3ebfa23..70f1e6760 100644 --- a/launcher/ui/dialogs/SkinUploadDialog.cpp +++ b/launcher/ui/dialogs/SkinUploadDialog.cpp @@ -35,6 +35,7 @@ #include #include +#include #include #include @@ -101,12 +102,12 @@ void SkinUploadDialog::on_buttonBox_accepted() } else if (ui->alexBtn->isChecked()) { model = SkinUpload::ALEX; } - skinUpload.addTask(shared_qobject_ptr(new SkinUpload(this, m_acct->accessToken(), FS::read(fileName), model))); + skinUpload.addTask(SkinUpload::make(m_acct->accessToken(), FS::read(fileName), model)); } auto selectedCape = ui->capeCombo->currentData().toString(); if (selectedCape != m_acct->accountData()->minecraftProfile.currentCape) { - skinUpload.addTask(shared_qobject_ptr(new CapeChange(this, m_acct->accessToken(), selectedCape))); + skinUpload.addTask(CapeChange::make(m_acct->accessToken(), selectedCape)); } if (prog.execWithTask(&skinUpload) != QDialog::Accepted) { CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Failed to upload skin!"), QMessageBox::Warning)->exec(); diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp index c95bfabdd..3dcf05e0a 100644 --- a/launcher/ui/pages/global/AccountListPage.cpp +++ b/launcher/ui/pages/global/AccountListPage.cpp @@ -268,7 +268,7 @@ void AccountListPage::on_actionDeleteSkin_triggered() QModelIndex selected = selection.first(); MinecraftAccountPtr account = selected.data(AccountList::PointerRole).value(); ProgressDialog prog(this); - auto deleteSkinTask = std::make_shared(this, account->accessToken()); + auto deleteSkinTask = SkinDelete::make(account->accessToken()); if (prog.execWithTask((Task*)deleteSkinTask.get()) != QDialog::Accepted) { CustomMessageBox::selectable(this, tr("Skin Delete"), tr("Failed to delete current skin!"), QMessageBox::Warning)->exec(); return; From 9ad029e0286ee18a6531d673c052a48a5f40d8d5 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 1 Sep 2023 23:10:33 +0300 Subject: [PATCH 005/103] added skins directory Signed-off-by: Trial97 --- launcher/Application.cpp | 1 + launcher/ui/MainWindow.cpp | 5 ++++ launcher/ui/MainWindow.h | 2 ++ launcher/ui/MainWindow.ui | 13 +++++++++++ launcher/ui/pages/global/LauncherPage.cpp | 13 +++++++++++ launcher/ui/pages/global/LauncherPage.h | 1 + launcher/ui/pages/global/LauncherPage.ui | 28 +++++++++++++++++++---- 7 files changed, 59 insertions(+), 4 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 66044d9ac..9cff11546 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -536,6 +536,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_settings->registerSetting("IconsDir", "icons"); m_settings->registerSetting("DownloadsDir", QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); m_settings->registerSetting("DownloadsDirWatchRecursive", false); + m_settings->registerSetting("SkinsDir", "skins"); // Editors m_settings->registerSetting("JsonEditor", QString()); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 5e55a5abb..a82932e08 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1186,6 +1186,11 @@ void MainWindow::on_actionViewCentralModsFolder_triggered() DesktopServices::openDirectory(APPLICATION->settings()->get("CentralModsDir").toString(), true); } +void MainWindow::on_actionViewSkinsFolder_triggered() +{ + DesktopServices::openDirectory(APPLICATION->settings()->get("SkinsDir").toString(), true); +} + void MainWindow::on_actionViewIconThemeFolder_triggered() { DesktopServices::openDirectory(APPLICATION->themeManager()->getIconThemesFolder().path()); diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 0b6144522..5d816b5d0 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -118,6 +118,8 @@ class MainWindow : public QMainWindow { void on_actionViewWidgetThemeFolder_triggered(); void on_actionViewCatPackFolder_triggered(); + void on_actionViewSkinsFolder_triggered(); + void on_actionViewSelectedInstFolder_triggered(); void refreshInstances(); diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui index 91b2c2703..266551588 100644 --- a/launcher/ui/MainWindow.ui +++ b/launcher/ui/MainWindow.ui @@ -190,6 +190,7 @@ + @@ -575,6 +576,18 @@ Open the central mods folder in a file browser. + + + + .. + + + View &Skins Folder + + + Open the skins folder in a file browser. + + Themes diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 7f22fdb50..5576a0339 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -173,6 +173,17 @@ void LauncherPage::on_downloadsDirBrowseBtn_clicked() } } +void LauncherPage::on_skinsDirBrowseBtn_clicked() +{ + QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Skins Folder"), ui->skinsDirTextBox->text()); + + // do not allow current dir - it's dirty. Do not allow dirs that don't exist + if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) { + QString cooked_dir = FS::NormalizePath(raw_dir); + ui->skinsDirTextBox->setText(cooked_dir); + } +} + void LauncherPage::on_metadataDisableBtn_clicked() { ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked()); @@ -205,6 +216,7 @@ void LauncherPage::applySettings() s->set("CentralModsDir", ui->modsDirTextBox->text()); s->set("IconsDir", ui->iconsDirTextBox->text()); s->set("DownloadsDir", ui->downloadsDirTextBox->text()); + s->set("SkinsDir", ui->skinsDirTextBox->text()); s->set("DownloadsDirWatchRecursive", ui->downloadsDirWatchRecursiveCheckBox->isChecked()); auto sortMode = (InstSortMode)ui->sortingModeGroup->checkedId(); @@ -259,6 +271,7 @@ void LauncherPage::loadSettings() ui->modsDirTextBox->setText(s->get("CentralModsDir").toString()); ui->iconsDirTextBox->setText(s->get("IconsDir").toString()); ui->downloadsDirTextBox->setText(s->get("DownloadsDir").toString()); + ui->skinsDirTextBox->setText(s->get("SkinsDir").toString()); ui->downloadsDirWatchRecursiveCheckBox->setChecked(s->get("DownloadsDirWatchRecursive").toBool()); QString sortMode = s->get("InstSortMode").toString(); diff --git a/launcher/ui/pages/global/LauncherPage.h b/launcher/ui/pages/global/LauncherPage.h index e733224d2..f9aefb171 100644 --- a/launcher/ui/pages/global/LauncherPage.h +++ b/launcher/ui/pages/global/LauncherPage.h @@ -74,6 +74,7 @@ class LauncherPage : public QWidget, public BasePage { void on_modsDirBrowseBtn_clicked(); void on_iconsDirBrowseBtn_clicked(); void on_downloadsDirBrowseBtn_clicked(); + void on_skinsDirBrowseBtn_clicked(); void on_metadataDisableBtn_clicked(); /*! diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index bc259a9b8..87c4b5ef9 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -67,7 +67,7 @@ Folders - + &Downloads: @@ -90,13 +90,16 @@ - + - + + + + Browse @@ -147,7 +150,24 @@ - + + + + Browse + + + + + + + &Skins: + + + skinsDirTextBox + + + + When enabled, in addition to the downloads folder, its sub folders will also be searched when looking for resources (e.g. when looking for blocked mods on CurseForge). From c86b8b0f70894e447c8a08e136b919987c3dffae Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 5 Sep 2023 00:18:36 +0300 Subject: [PATCH 006/103] added skin manage dialog Signed-off-by: Trial97 --- launcher/CMakeLists.txt | 28 +- launcher/SkinUtils.cpp | 52 --- launcher/SkinUtils.h | 22 - launcher/minecraft/services/CapeChange.h | 21 - launcher/minecraft/services/SkinDelete.h | 20 - launcher/minecraft/services/SkinUpload.h | 25 -- .../{services => skins}/CapeChange.cpp | 6 +- launcher/minecraft/skins/CapeChange.h | 39 ++ .../{services => skins}/SkinDelete.cpp | 3 +- launcher/minecraft/skins/SkinDelete.h | 38 ++ launcher/minecraft/skins/SkinList.cpp | 393 ++++++++++++++++++ launcher/minecraft/skins/SkinList.h | 80 ++++ launcher/minecraft/skins/SkinModel.cpp | 128 ++++++ launcher/minecraft/skins/SkinModel.h | 58 +++ .../{services => skins}/SkinUpload.cpp | 28 +- launcher/minecraft/skins/SkinUpload.h | 42 ++ launcher/net/Logging.cpp | 2 +- launcher/net/Logging.h | 2 +- launcher/net/NetJob.h | 4 +- launcher/net/NetRequest.cpp | 1 + launcher/net/NetRequest.h | 1 + launcher/net/StaticHeaderProxy.h | 2 +- launcher/ui/MainWindow.cpp | 1 - launcher/ui/dialogs/ProfileSelectDialog.cpp | 1 - launcher/ui/dialogs/SkinUploadDialog.cpp | 165 -------- launcher/ui/dialogs/SkinUploadDialog.h | 28 -- launcher/ui/dialogs/SkinUploadDialog.ui | 95 ----- .../ui/dialogs/skins/SkinManageDialog.cpp | 339 +++++++++++++++ launcher/ui/dialogs/skins/SkinManageDialog.h | 62 +++ launcher/ui/dialogs/skins/SkinManageDialog.ui | 193 +++++++++ launcher/ui/pages/global/AccountListPage.cpp | 34 +- launcher/ui/pages/global/AccountListPage.h | 3 +- launcher/ui/pages/global/AccountListPage.ui | 24 +- 33 files changed, 1425 insertions(+), 515 deletions(-) delete mode 100644 launcher/SkinUtils.cpp delete mode 100644 launcher/SkinUtils.h delete mode 100644 launcher/minecraft/services/CapeChange.h delete mode 100644 launcher/minecraft/services/SkinDelete.h delete mode 100644 launcher/minecraft/services/SkinUpload.h rename launcher/minecraft/{services => skins}/CapeChange.cpp (90%) create mode 100644 launcher/minecraft/skins/CapeChange.h rename launcher/minecraft/{services => skins}/SkinDelete.cpp (96%) create mode 100644 launcher/minecraft/skins/SkinDelete.h create mode 100644 launcher/minecraft/skins/SkinList.cpp create mode 100644 launcher/minecraft/skins/SkinList.h create mode 100644 launcher/minecraft/skins/SkinModel.cpp create mode 100644 launcher/minecraft/skins/SkinModel.h rename launcher/minecraft/{services => skins}/SkinUpload.cpp (79%) create mode 100644 launcher/minecraft/skins/SkinUpload.h delete mode 100644 launcher/ui/dialogs/SkinUploadDialog.cpp delete mode 100644 launcher/ui/dialogs/SkinUploadDialog.h delete mode 100644 launcher/ui/dialogs/SkinUploadDialog.ui create mode 100644 launcher/ui/dialogs/skins/SkinManageDialog.cpp create mode 100644 launcher/ui/dialogs/skins/SkinManageDialog.h create mode 100644 launcher/ui/dialogs/skins/SkinManageDialog.ui diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 3b6218b7f..6914b3385 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -377,13 +377,17 @@ set(MINECRAFT_SOURCES minecraft/AssetsUtils.h minecraft/AssetsUtils.cpp - # Minecraft services - minecraft/services/CapeChange.cpp - minecraft/services/CapeChange.h - minecraft/services/SkinUpload.cpp - minecraft/services/SkinUpload.h - minecraft/services/SkinDelete.cpp - minecraft/services/SkinDelete.h + # Minecraft skins + minecraft/skins/CapeChange.cpp + minecraft/skins/CapeChange.h + minecraft/skins/SkinUpload.cpp + minecraft/skins/SkinUpload.h + minecraft/skins/SkinDelete.cpp + minecraft/skins/SkinDelete.h + minecraft/skins/SkinModel.cpp + minecraft/skins/SkinModel.h + minecraft/skins/SkinList.cpp + minecraft/skins/SkinList.h minecraft/Agent.h) @@ -742,8 +746,6 @@ SET(LAUNCHER_SOURCES ui/InstanceWindow.cpp # FIXME: maybe find a better home for this. - SkinUtils.cpp - SkinUtils.h FileIgnoreProxy.cpp FileIgnoreProxy.h FastFileIconProvider.cpp @@ -965,8 +967,6 @@ SET(LAUNCHER_SOURCES ui/dialogs/ReviewMessageBox.h ui/dialogs/VersionSelectDialog.cpp ui/dialogs/VersionSelectDialog.h - ui/dialogs/SkinUploadDialog.cpp - ui/dialogs/SkinUploadDialog.h ui/dialogs/ResourceDownloadDialog.cpp ui/dialogs/ResourceDownloadDialog.h ui/dialogs/ScrollMessageBox.cpp @@ -980,6 +980,9 @@ SET(LAUNCHER_SOURCES ui/dialogs/InstallLoaderDialog.cpp ui/dialogs/InstallLoaderDialog.h + ui/dialogs/skins/SkinManageDialog.cpp + ui/dialogs/skins/SkinManageDialog.h + # GUI - widgets ui/widgets/Common.cpp ui/widgets/Common.h @@ -1096,7 +1099,6 @@ qt_wrap_ui(LAUNCHER_UI ui/dialogs/NewComponentDialog.ui ui/dialogs/NewsDialog.ui ui/dialogs/ProfileSelectDialog.ui - ui/dialogs/SkinUploadDialog.ui ui/dialogs/ExportInstanceDialog.ui ui/dialogs/ExportPackDialog.ui ui/dialogs/ExportToModListDialog.ui @@ -1111,6 +1113,8 @@ qt_wrap_ui(LAUNCHER_UI ui/dialogs/ScrollMessageBox.ui ui/dialogs/BlockedModsDialog.ui ui/dialogs/ChooseProviderDialog.ui + + ui/dialogs/skins/SkinManageDialog.ui ) qt_add_resources(LAUNCHER_RESOURCES diff --git a/launcher/SkinUtils.cpp b/launcher/SkinUtils.cpp deleted file mode 100644 index 989114ad5..000000000 --- a/launcher/SkinUtils.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "SkinUtils.h" -#include "Application.h" -#include "net/HttpMetaCache.h" - -#include -#include -#include -#include -#include - -namespace SkinUtils { -/* - * Given a username, return a pixmap of the cached skin (if it exists), QPixmap() otherwise - */ -QPixmap getFaceFromCache(QString username, int height, int width) -{ - QFile fskin(APPLICATION->metacache()->resolveEntry("skins", username + ".png")->getFullPath()); - - if (fskin.exists()) { - QPixmap skinTexture(fskin.fileName()); - if (!skinTexture.isNull()) { - QPixmap skin = QPixmap(8, 8); -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) - skin.fill(QColorConstants::Transparent); -#else - skin.fill(QColor(0, 0, 0, 0)); -#endif - QPainter painter(&skin); - painter.drawPixmap(0, 0, skinTexture.copy(8, 8, 8, 8)); - painter.drawPixmap(0, 0, skinTexture.copy(40, 8, 8, 8)); - return skin.scaled(height, width, Qt::KeepAspectRatio); - } - } - - return QPixmap(); -} -} // namespace SkinUtils diff --git a/launcher/SkinUtils.h b/launcher/SkinUtils.h deleted file mode 100644 index 11bc8bc6f..000000000 --- a/launcher/SkinUtils.h +++ /dev/null @@ -1,22 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -namespace SkinUtils { -QPixmap getFaceFromCache(QString id, int height = 64, int width = 64); -} diff --git a/launcher/minecraft/services/CapeChange.h b/launcher/minecraft/services/CapeChange.h deleted file mode 100644 index 74805ef43..000000000 --- a/launcher/minecraft/services/CapeChange.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include "net/NetRequest.h" - -class CapeChange : public Net::NetRequest { - Q_OBJECT - public: - using Ptr = shared_qobject_ptr; - CapeChange(QString token, QString capeId); - virtual ~CapeChange() = default; - - static CapeChange::Ptr make(QString token, QString capeId); - void init() override; - - protected: - virtual QNetworkReply* getReply(QNetworkRequest&) override; - - private: - QString m_capeId; - QString m_token; -}; diff --git a/launcher/minecraft/services/SkinDelete.h b/launcher/minecraft/services/SkinDelete.h deleted file mode 100644 index b0fb866cd..000000000 --- a/launcher/minecraft/services/SkinDelete.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "net/NetRequest.h" - -class SkinDelete : public Net::NetRequest { - Q_OBJECT - public: - using Ptr = shared_qobject_ptr; - SkinDelete(QString token); - virtual ~SkinDelete() = default; - - static SkinDelete::Ptr make(QString token); - void init() override; - - protected: - virtual QNetworkReply* getReply(QNetworkRequest&) override; - - private: - QString m_token; -}; diff --git a/launcher/minecraft/services/SkinUpload.h b/launcher/minecraft/services/SkinUpload.h deleted file mode 100644 index 2da836d52..000000000 --- a/launcher/minecraft/services/SkinUpload.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include "net/NetRequest.h" - -class SkinUpload : public Net::NetRequest { - Q_OBJECT - public: - using Ptr = shared_qobject_ptr; - enum Model { STEVE, ALEX }; - - // Note this class takes ownership of the file. - SkinUpload(QString token, QByteArray skin, Model model = STEVE); - virtual ~SkinUpload() = default; - - static SkinUpload::Ptr make(QString token, QByteArray skin, Model model = STEVE); - void init() override; - - protected: - virtual QNetworkReply* getReply(QNetworkRequest&) override; - - private: - Model m_model; - QByteArray m_skin; - QString m_token; -}; diff --git a/launcher/minecraft/services/CapeChange.cpp b/launcher/minecraft/skins/CapeChange.cpp similarity index 90% rename from launcher/minecraft/services/CapeChange.cpp rename to launcher/minecraft/skins/CapeChange.cpp index 5a7820b54..863e89844 100644 --- a/launcher/minecraft/services/CapeChange.cpp +++ b/launcher/minecraft/skins/CapeChange.cpp @@ -2,6 +2,7 @@ /* * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (c) 2023 Trial97 * * 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 @@ -42,7 +43,7 @@ CapeChange::CapeChange(QString token, QString cape) : NetRequest(), m_capeId(cape), m_token(token) { - logCat = taskMCServicesLogC; + logCat = taskMCSkinsLogC; }; QNetworkReply* CapeChange::getReply(QNetworkRequest& request) @@ -52,7 +53,7 @@ QNetworkReply* CapeChange::getReply(QNetworkRequest& request) return m_network->deleteResource(request); } else { setStatus(tr("Equipping cape")); - return m_network->post(request, QString("{\"capeId\":\"%1\"}").arg(m_capeId).toUtf8()); + return m_network->put(request, QString("{\"capeId\":\"%1\"}").arg(m_capeId).toUtf8()); } } @@ -67,6 +68,7 @@ CapeChange::Ptr CapeChange::make(QString token, QString capeId) { auto up = makeShared(token, capeId); up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/capes/active"); + up->setObjectName(QString("BYTES:") + up->m_url.toString()); up->m_sink.reset(new Net::ByteArraySink(std::make_shared())); return up; } diff --git a/launcher/minecraft/skins/CapeChange.h b/launcher/minecraft/skins/CapeChange.h new file mode 100644 index 000000000..bcafcde87 --- /dev/null +++ b/launcher/minecraft/skins/CapeChange.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * 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 . + */ + +#pragma once + +#include "net/NetRequest.h" + +class CapeChange : public Net::NetRequest { + Q_OBJECT + public: + using Ptr = shared_qobject_ptr; + CapeChange(QString token, QString capeId); + virtual ~CapeChange() = default; + + static CapeChange::Ptr make(QString token, QString capeId); + void init() override; + + protected: + virtual QNetworkReply* getReply(QNetworkRequest&) override; + + private: + QString m_capeId; + QString m_token; +}; diff --git a/launcher/minecraft/services/SkinDelete.cpp b/launcher/minecraft/skins/SkinDelete.cpp similarity index 96% rename from launcher/minecraft/services/SkinDelete.cpp rename to launcher/minecraft/skins/SkinDelete.cpp index 7944637f6..982cac1b7 100644 --- a/launcher/minecraft/services/SkinDelete.cpp +++ b/launcher/minecraft/skins/SkinDelete.cpp @@ -2,6 +2,7 @@ /* * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (c) 2023 Trial97 * * 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 @@ -40,7 +41,7 @@ SkinDelete::SkinDelete(QString token) : NetRequest(), m_token(token) { - logCat = taskMCServicesLogC; + logCat = taskMCSkinsLogC; }; QNetworkReply* SkinDelete::getReply(QNetworkRequest& request) diff --git a/launcher/minecraft/skins/SkinDelete.h b/launcher/minecraft/skins/SkinDelete.h new file mode 100644 index 000000000..5d02e0cc4 --- /dev/null +++ b/launcher/minecraft/skins/SkinDelete.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * 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 . + */ + +#pragma once + +#include "net/NetRequest.h" + +class SkinDelete : public Net::NetRequest { + Q_OBJECT + public: + using Ptr = shared_qobject_ptr; + SkinDelete(QString token); + virtual ~SkinDelete() = default; + + static SkinDelete::Ptr make(QString token); + void init() override; + + protected: + virtual QNetworkReply* getReply(QNetworkRequest&) override; + + private: + QString m_token; +}; diff --git a/launcher/minecraft/skins/SkinList.cpp b/launcher/minecraft/skins/SkinList.cpp new file mode 100644 index 000000000..be329564b --- /dev/null +++ b/launcher/minecraft/skins/SkinList.cpp @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * 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 . + */ + +#include "SkinList.h" + +#include +#include + +#include "FileSystem.h" +#include "Json.h" +#include "minecraft/skins/SkinModel.h" + +SkinList::SkinList(QObject* parent, QString path, MinecraftAccountPtr acct) : QAbstractListModel(parent), m_acct(acct) +{ + FS::ensureFolderPathExists(m_dir.absolutePath()); + m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); + m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); + m_watcher.reset(new QFileSystemWatcher(this)); + is_watching = false; + connect(m_watcher.get(), &QFileSystemWatcher::directoryChanged, this, &SkinList::directoryChanged); + connect(m_watcher.get(), &QFileSystemWatcher::fileChanged, this, &SkinList::fileChanged); + directoryChanged(path); +} + +void SkinList::startWatching() +{ + if (is_watching) { + return; + } + update(); + is_watching = m_watcher->addPath(m_dir.absolutePath()); + if (is_watching) { + qDebug() << "Started watching " << m_dir.absolutePath(); + } else { + qDebug() << "Failed to start watching " << m_dir.absolutePath(); + } +} + +void SkinList::stopWatching() +{ + save(); + if (!is_watching) { + return; + } + is_watching = !m_watcher->removePath(m_dir.absolutePath()); + if (!is_watching) { + qDebug() << "Stopped watching " << m_dir.absolutePath(); + } else { + qDebug() << "Failed to stop watching " << m_dir.absolutePath(); + } +} + +bool SkinList::update() +{ + QVector newSkins; + m_dir.refresh(); + + auto manifestInfo = QFileInfo(m_dir.absoluteFilePath("index.json")); + if (manifestInfo.exists()) { + try { + auto doc = Json::requireDocument(manifestInfo.absoluteFilePath(), "SkinList JSON file"); + const auto root = doc.object(); + auto skins = Json::ensureArray(root, "skins"); + for (auto jSkin : skins) { + SkinModel s(m_dir, Json::ensureObject(jSkin)); + if (s.isValid()) { + newSkins << s; + } + } + } catch (const Exception& e) { + qCritical() << "Couldn't load skins json:" << e.cause(); + } + } else { + newSkins = loadMinecraftSkins(); + } + + bool needsSave = false; + const auto& skin = m_acct->accountData()->minecraftProfile.skin; + if (!skin.url.isEmpty() && !skin.data.isEmpty()) { + QPixmap skinTexture; + SkinModel* nskin = nullptr; + for (auto i = 0; i < newSkins.size(); i++) { + if (newSkins[i].getURL() == skin.url) { + nskin = &newSkins[i]; + break; + } + } + if (!nskin) { + auto name = m_acct->profileName() + ".png"; + if (QFileInfo(m_dir.absoluteFilePath(name)).exists()) { + name = QUrl(skin.url).fileName() + ".png"; + } + auto path = m_dir.absoluteFilePath(name); + if (skinTexture.loadFromData(skin.data, "PNG") && skinTexture.save(path)) { + SkinModel s(path); + s.setModel(SkinModel::CLASSIC); // maybe better model detection + s.setCapeId(m_acct->accountData()->minecraftProfile.currentCape); + s.setURL(skin.url); + newSkins << s; + needsSave = true; + } + } else { + nskin->setCapeId(m_acct->accountData()->minecraftProfile.currentCape); + } + } + + auto folderContents = m_dir.entryInfoList(); + // if there are any untracked files... + for (QFileInfo entry : folderContents) { + if (!entry.isFile() && entry.suffix() != "png") + continue; + + SkinModel w(entry.absoluteFilePath()); + if (w.isValid()) { + auto add = true; + for (auto s : newSkins) { + if (s.name() == w.name()) { + add = false; + break; + } + } + if (add) { + newSkins.append(w); + needsSave = true; + } + } + } + std::sort(newSkins.begin(), newSkins.end(), + [](const SkinModel& a, const SkinModel& b) { return a.getPath().localeAwareCompare(b.getPath()) < 0; }); + beginResetModel(); + m_skin_list.swap(newSkins); + endResetModel(); + if (needsSave) + save(); + return true; +} + +void SkinList::directoryChanged(const QString& path) +{ + QDir new_dir(path); + if (!new_dir.exists()) + if (!FS::ensureFolderPathExists(new_dir.absolutePath())) + return; + if (m_dir.absolutePath() != new_dir.absolutePath()) { + m_dir.setPath(path); + m_dir.refresh(); + if (is_watching) + stopWatching(); + startWatching(); + } + update(); +} + +void SkinList::fileChanged(const QString& path) +{ + qDebug() << "Checking " << path; + QFileInfo checkfile(path); + if (!checkfile.exists()) + return; + + for (int i = 0; i < m_skin_list.count(); i++) { + if (m_skin_list[i].getPath() == checkfile.absoluteFilePath()) { + m_skin_list[i].refresh(); + dataChanged(index(i), index(i)); + break; + } + } +} + +QStringList SkinList::mimeTypes() const +{ + return { "text/uri-list" }; +} + +Qt::DropActions SkinList::supportedDropActions() const +{ + return Qt::CopyAction; +} + +bool SkinList::dropMimeData(const QMimeData* data, + Qt::DropAction action, + [[maybe_unused]] int row, + [[maybe_unused]] int column, + [[maybe_unused]] const QModelIndex& parent) +{ + if (action == Qt::IgnoreAction) + return true; + // check if the action is supported + if (!data || !(action & supportedDropActions())) + return false; + + // files dropped from outside? + if (data->hasUrls()) { + auto urls = data->urls(); + QStringList iconFiles; + for (auto url : urls) { + // only local files may be dropped... + if (!url.isLocalFile()) + continue; + iconFiles += url.toLocalFile(); + } + installSkins(iconFiles); + return true; + } + return false; +} + +Qt::ItemFlags SkinList::flags(const QModelIndex& index) const +{ + Qt::ItemFlags f = Qt::ItemIsDropEnabled | QAbstractListModel::flags(index); + if (index.isValid()) { + f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); + } + return f; +} + +QVariant SkinList::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + + if (row < 0 || row >= m_skin_list.size()) + return QVariant(); + auto skin = m_skin_list[row]; + switch (role) { + case Qt::DecorationRole: + return skin.getTexture(); + case Qt::DisplayRole: + return skin.name(); + case Qt::UserRole: + return skin.name(); + case Qt::EditRole: + return skin.name(); + default: + return QVariant(); + } +} + +int SkinList::rowCount(const QModelIndex& parent) const +{ + return parent.isValid() ? 0 : m_skin_list.size(); +} + +void SkinList::installSkins(const QStringList& iconFiles) +{ + for (QString file : iconFiles) + installSkin(file, {}); +} + +void SkinList::installSkin(const QString& file, const QString& name) +{ + QFileInfo fileinfo(file); + if (!fileinfo.isReadable() || !fileinfo.isFile()) + return; + + if (fileinfo.suffix() != "png" && !SkinModel(fileinfo.absoluteFilePath()).isValid()) + return; + + QString target = FS::PathCombine(m_dir.absolutePath(), name.isEmpty() ? fileinfo.fileName() : name); + QFile::copy(file, target); +} + +int SkinList::getSkinIndex(const QString& key) const +{ + for (int i = 0; i < m_skin_list.count(); i++) { + if (m_skin_list[i].name() == key) { + return i; + } + } + return -1; +} + +const SkinModel* SkinList::skin(const QString& key) const +{ + int idx = getSkinIndex(key); + if (idx == -1) + return nullptr; + return &m_skin_list[idx]; +} + +SkinModel* SkinList::skin(const QString& key) +{ + int idx = getSkinIndex(key); + if (idx == -1) + return nullptr; + return &m_skin_list[idx]; +} + +bool SkinList::deleteSkin(const QString& key, const bool trash) +{ + int idx = getSkinIndex(key); + if (idx != -1) { + auto s = m_skin_list[idx]; + if (trash) { + if (FS::trash(s.getPath(), nullptr)) { + m_skin_list.remove(idx); + return true; + } + } else if (QFile::remove(s.getPath())) { + m_skin_list.remove(idx); + return true; + } + } + return false; +} + +void SkinList::save() +{ + QJsonObject doc; + QJsonArray arr; + for (auto s : m_skin_list) { + arr << s.toJSON(); + } + doc["skins"] = arr; + Json::write(doc, m_dir.absoluteFilePath("index.json")); +} + +int SkinList::getSelectedAccountSkin() +{ + const auto& skin = m_acct->accountData()->minecraftProfile.skin; + for (int i = 0; i < m_skin_list.count(); i++) { + if (m_skin_list[i].getURL() == skin.url) { + return i; + } + } + return -1; +} + +bool SkinList::setData(const QModelIndex& idx, const QVariant& value, int role) +{ + if (!idx.isValid() || role != Qt::EditRole) { + return false; + } + + int row = idx.row(); + if (row < 0 || row >= m_skin_list.size()) + return false; + auto skin = m_skin_list[row]; + auto newName = value.toString(); + if (skin.name() != newName) { + skin.rename(newName); + save(); + } + return true; +} + +QVector SkinList::loadMinecraftSkins() +{ + QString partialPath; +#if defined(Q_OS_OSX) + partialPath = FS::PathCombine(QDir::homePath(), "Library/Application Support"); +#elif defined(Q_OS_WIN32) + partialPath = QProcessEnvironment::systemEnvironment().value("LOCALAPPDATA", ""); +#else + partialPath = QDir::homePath(); +#endif + QVector newSkins; + auto path = FS::PathCombine(partialPath, ".minecraft", "launcher_custom_skins.json"); + auto manifestInfo = QFileInfo(path); + if (!manifestInfo.exists()) + return {}; + try { + auto doc = Json::requireDocument(manifestInfo.absoluteFilePath(), "SkinList JSON file"); + const auto root = doc.object(); + auto skins = Json::ensureObject(root, "customSkins"); + for (auto key : skins.keys()) { + SkinModel s(m_dir, Json::ensureObject(skins, key)); + if (s.isValid()) { + newSkins << s; + } + } + } catch (const Exception& e) { + qCritical() << "Couldn't load minecraft skins json:" << e.cause(); + } + return newSkins; +} diff --git a/launcher/minecraft/skins/SkinList.h b/launcher/minecraft/skins/SkinList.h new file mode 100644 index 000000000..8d8266d79 --- /dev/null +++ b/launcher/minecraft/skins/SkinList.h @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * 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 . + */ + +#pragma once + +#include +#include +#include + +#include "QObjectPtr.h" +#include "SkinModel.h" +#include "minecraft/auth/MinecraftAccount.h" + +class SkinList : public QAbstractListModel { + Q_OBJECT + public: + explicit SkinList(QObject* parent, QString path, MinecraftAccountPtr acct); + virtual ~SkinList() { save(); }; + + int getSkinIndex(const QString& key) const; + + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex& idx, const QVariant& value, int role) override; + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; + + virtual QStringList mimeTypes() const override; + virtual Qt::DropActions supportedDropActions() const override; + virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) override; + virtual Qt::ItemFlags flags(const QModelIndex& index) const override; + + bool deleteSkin(const QString& key, const bool trash); + + void installSkins(const QStringList& iconFiles); + void installSkin(const QString& file, const QString& name); + + const SkinModel* skin(const QString& key) const; + SkinModel* skin(const QString& key); + + void startWatching(); + void stopWatching(); + + QString getDir() const { return m_dir.absolutePath(); } + void save(); + int getSelectedAccountSkin(); + + private: + // hide copy constructor + SkinList(const SkinList&) = delete; + // hide assign op + SkinList& operator=(const SkinList&) = delete; + + QVector loadMinecraftSkins(); + + protected slots: + void directoryChanged(const QString& path); + void fileChanged(const QString& path); + bool update(); + + private: + shared_qobject_ptr m_watcher; + bool is_watching; + QVector m_skin_list; + QDir m_dir; + MinecraftAccountPtr m_acct; +}; \ No newline at end of file diff --git a/launcher/minecraft/skins/SkinModel.cpp b/launcher/minecraft/skins/SkinModel.cpp new file mode 100644 index 000000000..3b467019c --- /dev/null +++ b/launcher/minecraft/skins/SkinModel.cpp @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * 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 . + */ + +#include "SkinModel.h" +#include +#include +#include +#include + +#include "FileSystem.h" +#include "Json.h" + +SkinModel::SkinModel(QString path) : m_path(path), m_texture(path), m_model(Model::CLASSIC) {} + +SkinModel::SkinModel(QDir skinDir, QJsonObject obj) + : m_cape_id(Json::ensureString(obj, "capeId")), m_model(Model::CLASSIC), m_url(Json::ensureString(obj, "url")) +{ + auto name = Json::ensureString(obj, "name"); + auto skinImage = Json::ensureString(obj, "skinImage"); + if (!skinImage.isEmpty()) { // minecraft skin model + skinImage = skinImage.mid(22); + m_texture.loadFromData(QByteArray::fromBase64(skinImage.toUtf8()), "PNG"); + auto textureId = Json::ensureString(obj, "textureId"); + if (name.isEmpty()) { + name = textureId; + } + if (Json::ensureBoolean(obj, "slim", false)) { + m_model = Model::SLIM; + } + } else { + if (auto model = Json::ensureString(obj, "model"); model == "SLIM") { + m_model = Model::SLIM; + } + } + m_path = skinDir.absoluteFilePath(name) + ".png"; + if (!QFileInfo(m_path).exists() && isValid()) { + m_texture.save(m_path, "PNG"); + } else { + m_texture = QPixmap(m_path); + } +} + +QString SkinModel::name() const +{ + return QFileInfo(m_path).baseName(); +} + +bool SkinModel::rename(QString newName) +{ + auto info = QFileInfo(m_path); + m_path = FS::PathCombine(info.absolutePath(), newName + ".png"); + return FS::move(info.absoluteFilePath(), m_path); +} + +QJsonObject SkinModel::toJSON() const +{ + QJsonObject obj; + obj["name"] = name(); + obj["capeId"] = m_cape_id; + obj["url"] = m_url; + obj["model"] = getModelString(); + return obj; +} + +QString SkinModel::getModelString() const +{ + switch (m_model) { + case CLASSIC: + return "CLASSIC"; + case SLIM: + return "SLIM"; + } + return {}; +} + +bool SkinModel::isValid() const +{ + return !m_texture.isNull() && (m_texture.size().height() == 32 || m_texture.size().height() == 64) && m_texture.size().width() == 64; +} + +QPixmap SkinModel::renderFrontBody() const +{ + auto isSlim = m_model == SLIM; + auto slimOffset = isSlim ? 1 : 0; + auto isOldSkin = m_texture.height() < 64; + + auto head = m_texture.copy(QRect(8, 8, 16, 16)); + auto torso = m_texture.copy(QRect(20, 20, 28, 32)); + auto rightArm = m_texture.copy(QRect(44, 20, 48 - slimOffset, 32)); + auto rightLeg = m_texture.copy(QRect(4, 20, 8, 32)); + QPixmap leftArm, leftLeg; + + if (isOldSkin) { + leftArm = rightArm.transformed(QTransform().scale(-1, 1)); + leftLeg = rightLeg.transformed(QTransform().scale(-1, 1)); + } else { + leftArm = m_texture.copy(QRect(36, 52, 40 - slimOffset, 64)); + leftLeg = m_texture.copy(QRect(20, 52, 24, 64)); + } + QPixmap output(16, 32); + output.fill(Qt::black); + QPainter p; + if (!p.begin(&output)) + return {}; + p.drawPixmap(QPoint(4, 0), head); + p.drawPixmap(QPoint(4, 8), torso); + p.drawPixmap(QPoint(12, 8), leftArm); + p.drawPixmap(QPoint(slimOffset, 8), rightArm); + p.drawPixmap(QPoint(8, 20), leftLeg); + p.drawPixmap(QPoint(4, 20), leftArm); + + return output.scaled(128, 128, Qt::KeepAspectRatioByExpanding, Qt::FastTransformation); +} \ No newline at end of file diff --git a/launcher/minecraft/skins/SkinModel.h b/launcher/minecraft/skins/SkinModel.h new file mode 100644 index 000000000..6d135c7f7 --- /dev/null +++ b/launcher/minecraft/skins/SkinModel.h @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * 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 . + */ + +#pragma once + +#include +#include +#include + +class SkinModel { + public: + enum Model { CLASSIC, SLIM }; + + SkinModel(QString path); + SkinModel(QDir skinDir, QJsonObject obj); + virtual ~SkinModel() = default; + + QString name() const; + QString getModelString() const; + bool isValid() const; + QString getPath() const { return m_path; } + QPixmap getTexture() const { return m_texture; } + QString getCapeId() const { return m_cape_id; } + Model getModel() const { return m_model; } + QString getURL() const { return m_url; } + + bool rename(QString newName); + void setCapeId(QString capeID) { m_cape_id = capeID; } + void setModel(Model model) { m_model = model; } + void setURL(QString url) { m_url = url; } + void refresh() { m_texture = QPixmap(m_path); } + + QJsonObject toJSON() const; + + QPixmap renderFrontBody() const; + + private: + QString m_path; + QPixmap m_texture; + QString m_cape_id; + Model m_model; + QString m_url; +}; \ No newline at end of file diff --git a/launcher/minecraft/services/SkinUpload.cpp b/launcher/minecraft/skins/SkinUpload.cpp similarity index 79% rename from launcher/minecraft/services/SkinUpload.cpp rename to launcher/minecraft/skins/SkinUpload.cpp index 0400fa0f4..4e56bd7e6 100644 --- a/launcher/minecraft/services/SkinUpload.cpp +++ b/launcher/minecraft/skins/SkinUpload.cpp @@ -2,6 +2,7 @@ /* * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (c) 2023 Trial97 * * 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 @@ -37,12 +38,13 @@ #include +#include "FileSystem.h" #include "net/ByteArraySink.h" #include "net/StaticHeaderProxy.h" -SkinUpload::SkinUpload(QString token, QByteArray skin, SkinUpload::Model model) : NetRequest(), m_model(model), m_skin(skin), m_token(token) +SkinUpload::SkinUpload(QString token, SkinModel* skin) : NetRequest(), m_skin(skin), m_token(token) { - logCat = taskMCServicesLogC; + logCat = taskUploadLogC; }; QNetworkReply* SkinUpload::getReply(QNetworkRequest& request) @@ -52,23 +54,12 @@ QNetworkReply* SkinUpload::getReply(QNetworkRequest& request) QHttpPart skin; skin.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/png")); skin.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"file\"; filename=\"skin.png\"")); - skin.setBody(m_skin); + + skin.setBody(FS::read(m_skin->getPath())); QHttpPart model; model.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"variant\"")); - - switch (m_model) { - default: - qDebug() << "Unknown skin type!"; - emitFailed("Unknown skin type!"); - return nullptr; - case SkinUpload::STEVE: - model.setBody("CLASSIC"); - break; - case SkinUpload::ALEX: - model.setBody("SLIM"); - break; - } + model.setBody(m_skin->getModelString().toUtf8()); multiPart->append(skin); multiPart->append(model); @@ -83,10 +74,11 @@ void SkinUpload::init() })); } -SkinUpload::Ptr SkinUpload::make(QString token, QByteArray skin, SkinUpload::Model model) +SkinUpload::Ptr SkinUpload::make(QString token, SkinModel* skin) { - auto up = makeShared(token, skin, model); + auto up = makeShared(token, skin); up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/skins"); + up->setObjectName(QString("BYTES:") + up->m_url.toString()); up->m_sink.reset(new Net::ByteArraySink(std::make_shared())); return up; } diff --git a/launcher/minecraft/skins/SkinUpload.h b/launcher/minecraft/skins/SkinUpload.h new file mode 100644 index 000000000..d070f301d --- /dev/null +++ b/launcher/minecraft/skins/SkinUpload.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * 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 . + */ + +#pragma once + +#include "minecraft/skins/SkinModel.h" +#include "net/NetRequest.h" + +class SkinUpload : public Net::NetRequest { + Q_OBJECT + public: + using Ptr = shared_qobject_ptr; + + // Note this class takes ownership of the file. + SkinUpload(QString token, SkinModel* skin); + virtual ~SkinUpload() = default; + + static SkinUpload::Ptr make(QString token, SkinModel* skin); + void init() override; + + protected: + virtual QNetworkReply* getReply(QNetworkRequest&) override; + + private: + SkinModel* m_skin; + QString m_token; +}; diff --git a/launcher/net/Logging.cpp b/launcher/net/Logging.cpp index 45d2dcc20..cd0c88d3c 100644 --- a/launcher/net/Logging.cpp +++ b/launcher/net/Logging.cpp @@ -22,6 +22,6 @@ Q_LOGGING_CATEGORY(taskNetLogC, "launcher.task.net") Q_LOGGING_CATEGORY(taskDownloadLogC, "launcher.task.net.download") Q_LOGGING_CATEGORY(taskUploadLogC, "launcher.task.net.upload") -Q_LOGGING_CATEGORY(taskMCServicesLogC, "launcher.task.minecraft.servicies") +Q_LOGGING_CATEGORY(taskMCSkinsLogC, "launcher.task.minecraft.skins") Q_LOGGING_CATEGORY(taskMetaCacheLogC, "launcher.task.net.metacache") Q_LOGGING_CATEGORY(taskHttpMetaCacheLogC, "launcher.task.net.metacache.http") diff --git a/launcher/net/Logging.h b/launcher/net/Logging.h index d3a11cdce..2536f31aa 100644 --- a/launcher/net/Logging.h +++ b/launcher/net/Logging.h @@ -24,6 +24,6 @@ Q_DECLARE_LOGGING_CATEGORY(taskNetLogC) Q_DECLARE_LOGGING_CATEGORY(taskDownloadLogC) Q_DECLARE_LOGGING_CATEGORY(taskUploadLogC) -Q_DECLARE_LOGGING_CATEGORY(taskMCServicesLogC) +Q_DECLARE_LOGGING_CATEGORY(taskMCSkinsLogC) Q_DECLARE_LOGGING_CATEGORY(taskMetaCacheLogC) Q_DECLARE_LOGGING_CATEGORY(taskHttpMetaCacheLogC) diff --git a/launcher/net/NetJob.h b/launcher/net/NetJob.h index cc63f4497..26791bc0d 100644 --- a/launcher/net/NetJob.h +++ b/launcher/net/NetJob.h @@ -52,8 +52,8 @@ class NetJob : public ConcurrentTask { public: using Ptr = shared_qobject_ptr; - explicit NetJob(QString job_name, shared_qobject_ptr network) - : ConcurrentTask(nullptr, job_name), m_network(network) + explicit NetJob(QString job_name, shared_qobject_ptr network, int max_concurrent = 6) + : ConcurrentTask(nullptr, job_name, max_concurrent), m_network(network) {} ~NetJob() override = default; diff --git a/launcher/net/NetRequest.cpp b/launcher/net/NetRequest.cpp index eef550e15..853873528 100644 --- a/launcher/net/NetRequest.cpp +++ b/launcher/net/NetRequest.cpp @@ -5,6 +5,7 @@ * Copyright (C) 2022 Sefa Eyeoglu * Copyright (C) 2023 TheKodeToad * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> + * Copyright (c) 2023 Trial97 * * 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 diff --git a/launcher/net/NetRequest.h b/launcher/net/NetRequest.h index ee47ab2a6..917495ed9 100644 --- a/launcher/net/NetRequest.h +++ b/launcher/net/NetRequest.h @@ -4,6 +4,7 @@ * Copyright (c) 2022 flowln * Copyright (C) 2022 Sefa Eyeoglu * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> + * Copyright (c) 2023 Trial97 * * 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 diff --git a/launcher/net/StaticHeaderProxy.h b/launcher/net/StaticHeaderProxy.h index 0e62d80ff..aabbc9c92 100644 --- a/launcher/net/StaticHeaderProxy.h +++ b/launcher/net/StaticHeaderProxy.h @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * Prism Launcher - Minecraft Launcher - * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> + * Copyright (c) 2023 Trial97 * * 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 diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index a82932e08..4858e7d46 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -77,7 +77,6 @@ #include #include #include -#include #include #include #include diff --git a/launcher/ui/dialogs/ProfileSelectDialog.cpp b/launcher/ui/dialogs/ProfileSelectDialog.cpp index a62238bdb..fe03e1b6b 100644 --- a/launcher/ui/dialogs/ProfileSelectDialog.cpp +++ b/launcher/ui/dialogs/ProfileSelectDialog.cpp @@ -20,7 +20,6 @@ #include #include "Application.h" -#include "SkinUtils.h" #include "ui/dialogs/ProgressDialog.h" diff --git a/launcher/ui/dialogs/SkinUploadDialog.cpp b/launcher/ui/dialogs/SkinUploadDialog.cpp deleted file mode 100644 index 70f1e6760..000000000 --- a/launcher/ui/dialogs/SkinUploadDialog.cpp +++ /dev/null @@ -1,165 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * - * 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 . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include "CustomMessageBox.h" -#include "ProgressDialog.h" -#include "SkinUploadDialog.h" -#include "ui_SkinUploadDialog.h" - -void SkinUploadDialog::on_buttonBox_rejected() -{ - close(); -} - -void SkinUploadDialog::on_buttonBox_accepted() -{ - QString fileName; - QString input = ui->skinPathTextBox->text(); - ProgressDialog prog(this); - SequentialTask skinUpload; - - if (!input.isEmpty()) { - QRegularExpression urlPrefixMatcher(QRegularExpression::anchoredPattern("^([a-z]+)://.+$")); - bool isLocalFile = false; - // it has an URL prefix -> it is an URL - if (urlPrefixMatcher.match(input).hasMatch()) { - QUrl fileURL = input; - if (fileURL.isValid()) { - // local? - if (fileURL.isLocalFile()) { - isLocalFile = true; - fileName = fileURL.toLocalFile(); - } else { - CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Using remote URLs for setting skins is not implemented yet."), - QMessageBox::Warning) - ->exec(); - close(); - return; - } - } else { - CustomMessageBox::selectable(this, tr("Skin Upload"), tr("You cannot use an invalid URL for uploading skins."), - QMessageBox::Warning) - ->exec(); - close(); - return; - } - } else { - // just assume it's a path then - isLocalFile = true; - fileName = ui->skinPathTextBox->text(); - } - if (isLocalFile && !QFile::exists(fileName)) { - CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Skin file does not exist!"), QMessageBox::Warning)->exec(); - close(); - return; - } - SkinUpload::Model model = SkinUpload::STEVE; - if (ui->steveBtn->isChecked()) { - model = SkinUpload::STEVE; - } else if (ui->alexBtn->isChecked()) { - model = SkinUpload::ALEX; - } - skinUpload.addTask(SkinUpload::make(m_acct->accessToken(), FS::read(fileName), model)); - } - - auto selectedCape = ui->capeCombo->currentData().toString(); - if (selectedCape != m_acct->accountData()->minecraftProfile.currentCape) { - skinUpload.addTask(CapeChange::make(m_acct->accessToken(), selectedCape)); - } - if (prog.execWithTask(&skinUpload) != QDialog::Accepted) { - CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Failed to upload skin!"), QMessageBox::Warning)->exec(); - close(); - return; - } - CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Success"), QMessageBox::Information)->exec(); - close(); -} - -void SkinUploadDialog::on_skinBrowseBtn_clicked() -{ - auto filter = QMimeDatabase().mimeTypeForName("image/png").filterString(); - QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), filter); - if (raw_path.isEmpty() || !QFileInfo::exists(raw_path)) { - return; - } - QString cooked_path = FS::NormalizePath(raw_path); - ui->skinPathTextBox->setText(cooked_path); -} - -SkinUploadDialog::SkinUploadDialog(MinecraftAccountPtr acct, QWidget* parent) : QDialog(parent), m_acct(acct), ui(new Ui::SkinUploadDialog) -{ - ui->setupUi(this); - - // FIXME: add a model for this, download/refresh the capes on demand - auto& accountData = *acct->accountData(); - int index = 0; - ui->capeCombo->addItem(tr("No Cape"), QVariant()); - auto currentCape = accountData.minecraftProfile.currentCape; - if (currentCape.isEmpty()) { - ui->capeCombo->setCurrentIndex(index); - } - - for (auto& cape : accountData.minecraftProfile.capes) { - index++; - if (cape.data.size()) { - QPixmap capeImage; - if (capeImage.loadFromData(cape.data, "PNG")) { - QPixmap preview = QPixmap(10, 16); - QPainter painter(&preview); - painter.drawPixmap(0, 0, capeImage.copy(1, 1, 10, 16)); - ui->capeCombo->addItem(capeImage, cape.alias, cape.id); - if (currentCape == cape.id) { - ui->capeCombo->setCurrentIndex(index); - } - continue; - } - } - ui->capeCombo->addItem(cape.alias, cape.id); - if (currentCape == cape.id) { - ui->capeCombo->setCurrentIndex(index); - } - } -} diff --git a/launcher/ui/dialogs/SkinUploadDialog.h b/launcher/ui/dialogs/SkinUploadDialog.h deleted file mode 100644 index 81d6140cc..000000000 --- a/launcher/ui/dialogs/SkinUploadDialog.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include -#include - -namespace Ui { -class SkinUploadDialog; -} - -class SkinUploadDialog : public QDialog { - Q_OBJECT - public: - explicit SkinUploadDialog(MinecraftAccountPtr acct, QWidget* parent = 0); - virtual ~SkinUploadDialog(){}; - - public slots: - void on_buttonBox_accepted(); - - void on_buttonBox_rejected(); - - void on_skinBrowseBtn_clicked(); - - protected: - MinecraftAccountPtr m_acct; - - private: - Ui::SkinUploadDialog* ui; -}; diff --git a/launcher/ui/dialogs/SkinUploadDialog.ui b/launcher/ui/dialogs/SkinUploadDialog.ui deleted file mode 100644 index c6df92df3..000000000 --- a/launcher/ui/dialogs/SkinUploadDialog.ui +++ /dev/null @@ -1,95 +0,0 @@ - - - SkinUploadDialog - - - - 0 - 0 - 394 - 360 - - - - Skin Upload - - - - - - Skin File - - - - - - Leave empty to keep current skin - - - - - - - - 0 - 0 - - - - Browse - - - - - - - - - - Player Model - - - - - - Steve Model - - - true - - - - - - - Alex Model - - - - - - - - - - Cape - - - - - - - - - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp new file mode 100644 index 000000000..1ba7e7055 --- /dev/null +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * 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 . + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "Application.h" +#include "DesktopServices.h" +#include "QObjectPtr.h" +#include "SkinManageDialog.h" + +#include "minecraft/auth/AccountTask.h" +#include "minecraft/skins/CapeChange.h" +#include "minecraft/skins/SkinDelete.h" +#include "minecraft/skins/SkinList.h" +#include "minecraft/skins/SkinModel.h" +#include "minecraft/skins/SkinUpload.h" + +#include "net/NetJob.h" +#include "tasks/Task.h" + +#include "ui/dialogs/CustomMessageBox.h" +#include "ui/dialogs/ProgressDialog.h" +#include "ui/instanceview/InstanceDelegate.h" +#include "ui_SkinManageDialog.h" + +SkinManageDialog::SkinManageDialog(QWidget* parent, MinecraftAccountPtr acct) + : QDialog(parent), m_acct(acct), ui(new Ui::SkinManageDialog), m_list(this, APPLICATION->settings()->get("SkinsDir").toString(), acct) +{ + ui->setupUi(this); + + setWindowModality(Qt::WindowModal); + + auto contentsWidget = ui->listView; + contentsWidget->setViewMode(QListView::IconMode); + contentsWidget->setFlow(QListView::LeftToRight); + contentsWidget->setIconSize(QSize(48, 48)); + contentsWidget->setMovement(QListView::Static); + contentsWidget->setResizeMode(QListView::Adjust); + contentsWidget->setSelectionMode(QAbstractItemView::SingleSelection); + contentsWidget->setSpacing(5); + contentsWidget->setWordWrap(false); + contentsWidget->setWrapping(true); + contentsWidget->setUniformItemSizes(true); + contentsWidget->setTextElideMode(Qt::ElideRight); + contentsWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + contentsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + contentsWidget->installEventFilter(this); + contentsWidget->setItemDelegate(new ListViewDelegate()); + + contentsWidget->setAcceptDrops(true); + contentsWidget->setDropIndicatorShown(true); + contentsWidget->viewport()->setAcceptDrops(true); + contentsWidget->setDragDropMode(QAbstractItemView::DropOnly); + contentsWidget->setDefaultDropAction(Qt::CopyAction); + + contentsWidget->installEventFilter(this); + contentsWidget->setModel(&m_list); + + connect(contentsWidget, SIGNAL(doubleClicked(QModelIndex)), SLOT(activated(QModelIndex))); + + connect(contentsWidget->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), + SLOT(selectionChanged(QItemSelection, QItemSelection))); + connect(ui->listView, &QListView::customContextMenuRequested, this, &SkinManageDialog::show_context_menu); + + setupCapes(); + + ui->listView->setCurrentIndex(m_list.index(m_list.getSelectedAccountSkin())); +} + +SkinManageDialog::~SkinManageDialog() +{ + delete ui; +} + +void SkinManageDialog::activated(QModelIndex index) +{ + m_selected_skin = index.data(Qt::UserRole).toString(); + accept(); +} + +void SkinManageDialog::selectionChanged(QItemSelection selected, QItemSelection deselected) +{ + if (selected.empty()) + return; + + QString key = selected.first().indexes().first().data(Qt::UserRole).toString(); + if (key.isEmpty()) + return; + m_selected_skin = key; + auto skin = m_list.skin(key); + if (!skin) + return; + ui->selectedModel->setPixmap(skin->getTexture().scaled(128, 128, Qt::KeepAspectRatio, Qt::FastTransformation)); + ui->capeCombo->setCurrentIndex(m_capes_idx.value(skin->getCapeId())); + ui->steveBtn->setChecked(skin->getModel() == SkinModel::CLASSIC); + ui->alexBtn->setChecked(skin->getModel() == SkinModel::SLIM); +} + +void SkinManageDialog::delayed_scroll(QModelIndex model_index) +{ + auto contentsWidget = ui->listView; + contentsWidget->scrollTo(model_index); +} + +void SkinManageDialog::on_openDirBtn_clicked() +{ + DesktopServices::openDirectory(m_list.getDir(), true); +} + +void SkinManageDialog::on_addBtn_clicked() +{ + auto filter = QMimeDatabase().mimeTypeForName("image/png").filterString(); + QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), filter); + if (raw_path.isEmpty() || !QFileInfo::exists(raw_path)) { + return; + } + if (!SkinModel(raw_path).isValid()) { + CustomMessageBox::selectable(this, tr("Selected file is not a valid skin"), + tr("Skin images must be 64x64 or 64x32 pixel PNG files."), QMessageBox::Critical) + ->show(); + return; + } + m_list.installSkin(raw_path, {}); +} + +QPixmap previewCape(QPixmap capeImage) +{ + QPixmap preview = QPixmap(10, 16); + QPainter painter(&preview); + painter.drawPixmap(0, 0, capeImage.copy(1, 1, 10, 16)); + return preview.scaled(80, 128, Qt::IgnoreAspectRatio, Qt::FastTransformation); +} + +void SkinManageDialog::setupCapes() +{ + // FIXME: add a model for this, download/refresh the capes on demand + auto& accountData = *m_acct->accountData(); + int index = 0; + ui->capeCombo->addItem(tr("No Cape"), QVariant()); + auto currentCape = accountData.minecraftProfile.currentCape; + if (currentCape.isEmpty()) { + ui->capeCombo->setCurrentIndex(index); + } + + auto capesDir = FS::PathCombine(m_list.getDir(), "capes"); + NetJob::Ptr job{ new NetJob(tr("Download capes"), APPLICATION->network()) }; + bool needsToDownload = false; + for (auto& cape : accountData.minecraftProfile.capes) { + auto path = FS::PathCombine(capesDir, cape.id + ".png"); + if (cape.data.size()) { + QPixmap capeImage; + if (capeImage.loadFromData(cape.data, "PNG") && capeImage.save(path)) { + m_capes[cape.id] = previewCape(capeImage); + continue; + } + } + if (QFileInfo(path).exists()) { + continue; + } + if (!cape.url.isEmpty()) { + needsToDownload = true; + job->addNetAction(Net::Download::makeFile(cape.url, path)); + } + } + if (needsToDownload) { + ProgressDialog dlg(this); + dlg.execWithTask(job.get()); + } + for (auto& cape : accountData.minecraftProfile.capes) { + index++; + QPixmap capeImage; + if (!m_capes.contains(cape.id)) { + auto path = FS::PathCombine(capesDir, cape.id + ".png"); + if (QFileInfo(path).exists() && capeImage.load(path)) { + capeImage = previewCape(capeImage); + m_capes[cape.id] = capeImage; + } + } + if (!capeImage.isNull()) { + ui->capeCombo->addItem(capeImage, cape.alias, cape.id); + } else { + ui->capeCombo->addItem(cape.alias, cape.id); + } + + m_capes_idx[cape.id] = index; + } +} + +void SkinManageDialog::on_capeCombo_currentIndexChanged(int index) +{ + auto id = ui->capeCombo->currentData(); + ui->capeImage->setPixmap(m_capes.value(id.toString(), {})); + if (auto skin = m_list.skin(m_selected_skin); skin) { + skin->setCapeId(id.toString()); + } +} + +void SkinManageDialog::on_steveBtn_toggled(bool checked) +{ + if (auto skin = m_list.skin(m_selected_skin); skin) { + skin->setModel(checked ? SkinModel::CLASSIC : SkinModel::SLIM); + } +} + +void SkinManageDialog::accept() +{ + auto skin = m_list.skin(m_selected_skin); + if (!skin) + reject(); + auto path = skin->getPath(); + + ProgressDialog prog(this); + NetJob::Ptr skinUpload{ new NetJob(tr("Change skin"), APPLICATION->network(), 1) }; + + if (!QFile::exists(path)) { + CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Skin file does not exist!"), QMessageBox::Warning)->exec(); + reject(); + return; + } + + skinUpload->addNetAction(SkinUpload::make(m_acct->accessToken(), skin)); + + auto selectedCape = skin->getCapeId(); + if (selectedCape != m_acct->accountData()->minecraftProfile.currentCape) { + skinUpload->addNetAction(CapeChange::make(m_acct->accessToken(), selectedCape)); + } + + skinUpload->addTask(m_acct->refresh().staticCast()); + if (prog.execWithTask(skinUpload.get()) != QDialog::Accepted) { + CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Failed to upload skin!"), QMessageBox::Warning)->exec(); + reject(); + return; + } + skin->setURL(m_acct->accountData()->minecraftProfile.skin.url); + QDialog::accept(); +} + +void SkinManageDialog::on_resetBtn_clicked() +{ + ProgressDialog prog(this); + NetJob::Ptr skinReset{ new NetJob(tr("Reset skin"), APPLICATION->network(), 1) }; + skinReset->addNetAction(SkinDelete::make(m_acct->accessToken())); + skinReset->addTask(m_acct->refresh().staticCast()); + if (prog.execWithTask(skinReset.get()) != QDialog::Accepted) { + CustomMessageBox::selectable(this, tr("Skin Delete"), tr("Failed to delete current skin!"), QMessageBox::Warning)->exec(); + reject(); + return; + } + QDialog::accept(); +} + +void SkinManageDialog::show_context_menu(const QPoint& pos) +{ + QMenu myMenu(tr("Context menu"), this); + myMenu.addAction(ui->action_Rename_Skin); + myMenu.addAction(ui->action_Delete_Skin); + + myMenu.exec(ui->listView->mapToGlobal(pos)); +} + +bool SkinManageDialog::eventFilter(QObject* obj, QEvent* ev) +{ + if (obj == ui->listView) { + if (ev->type() == QEvent::KeyPress) { + QKeyEvent* keyEvent = static_cast(ev); + switch (keyEvent->key()) { + case Qt::Key_Delete: + on_action_Delete_Skin_triggered(false); + return true; + case Qt::Key_F2: + on_action_Rename_Skin_triggered(false); + return true; + default: + break; + } + } + } + return QDialog::eventFilter(obj, ev); +} + +void SkinManageDialog::on_action_Rename_Skin_triggered(bool checked) +{ + if (!m_selected_skin.isEmpty()) { + ui->listView->edit(ui->listView->currentIndex()); + } +} + +void SkinManageDialog::on_action_Delete_Skin_triggered(bool checked) +{ + if (m_selected_skin.isEmpty()) + return; + + if (m_list.getSkinIndex(m_selected_skin) == m_list.getSelectedAccountSkin()) { + CustomMessageBox::selectable(this, tr("Delete error"), tr("Can not delete skin that is in use."), QMessageBox::Warning); + return; + } + + auto skin = m_list.skin(m_selected_skin); + if (!skin) + return; + + auto response = CustomMessageBox::selectable(this, tr("Confirm Deletion"), + tr("You are about to delete \"%1\".\n" + "Are you sure?") + .arg(skin->name()), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); + + if (response == QMessageBox::Yes) { + if (!m_list.deleteSkin(m_selected_skin, true)) { + m_list.deleteSkin(m_selected_skin, false); + } + } +} diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.h b/launcher/ui/dialogs/skins/SkinManageDialog.h new file mode 100644 index 000000000..8c55c3310 --- /dev/null +++ b/launcher/ui/dialogs/skins/SkinManageDialog.h @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * 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 . + */ + +#pragma once + +#include +#include +#include + +#include "minecraft/auth/MinecraftAccount.h" +#include "minecraft/skins/SkinList.h" + +namespace Ui { +class SkinManageDialog; +} + +class SkinManageDialog : public QDialog { + Q_OBJECT + public: + explicit SkinManageDialog(QWidget* parent, MinecraftAccountPtr acct); + virtual ~SkinManageDialog(); + + public slots: + void selectionChanged(QItemSelection, QItemSelection); + void activated(QModelIndex); + void delayed_scroll(QModelIndex); + void on_openDirBtn_clicked(); + void on_addBtn_clicked(); + void accept() override; + void on_capeCombo_currentIndexChanged(int index); + void on_steveBtn_toggled(bool checked); + void on_resetBtn_clicked(); + void show_context_menu(const QPoint& pos); + bool eventFilter(QObject* obj, QEvent* ev) override; + void on_action_Rename_Skin_triggered(bool checked); + void on_action_Delete_Skin_triggered(bool checked); + + private: + void setupCapes(); + + MinecraftAccountPtr m_acct; + Ui::SkinManageDialog* ui; + SkinList m_list; + QString m_selected_skin; + QHash m_capes; + QHash m_capes_idx; +}; diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.ui b/launcher/ui/dialogs/skins/SkinManageDialog.ui new file mode 100644 index 000000000..6ad826478 --- /dev/null +++ b/launcher/ui/dialogs/skins/SkinManageDialog.ui @@ -0,0 +1,193 @@ + + + SkinManageDialog + + + + 0 + 0 + 968 + 757 + + + + Skin Upload + + + + + + + + + + + + + true + + + + + + + + 0 + 0 + + + + Model + + + + + + Clasic + + + true + + + + + + + Slim + + + + + + + + + + Cape + + + + + + + + + + + + true + + + + + + + + + + + + Qt::CustomContextMenu + + + false + + + 0 + + + + + + + + + + + Open Folder + + + + + + + Import Skin + + + + + + + Reset Skin + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + &Delete Skin + + + Deletes selected skin + + + Del + + + + + &Rename Skin + + + Rename selected skin + + + F2 + + + + + + + buttonBox + rejected() + SkinManageDialog + reject() + + + 617 + 736 + + + 483 + 378 + + + + + buttonBox + accepted() + SkinManageDialog + accept() + + + 617 + 736 + + + 483 + 378 + + + + + diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp index 3dcf05e0a..25ccfd0d7 100644 --- a/launcher/ui/pages/global/AccountListPage.cpp +++ b/launcher/ui/pages/global/AccountListPage.cpp @@ -35,6 +35,7 @@ */ #include "AccountListPage.h" +#include "ui/dialogs/skins/SkinManageDialog.h" #include "ui_AccountListPage.h" #include @@ -42,23 +43,13 @@ #include -#include "net/NetJob.h" - #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/LoginDialog.h" #include "ui/dialogs/MSALoginDialog.h" #include "ui/dialogs/OfflineLoginDialog.h" -#include "ui/dialogs/ProgressDialog.h" -#include "ui/dialogs/SkinUploadDialog.h" - -#include "minecraft/auth/AccountTask.h" -#include "minecraft/services/SkinDelete.h" -#include "tasks/Task.h" #include "Application.h" -#include "BuildConfig.h" - AccountListPage::AccountListPage(QWidget* parent) : QMainWindow(parent), ui(new Ui::AccountListPage) { ui->setupUi(this); @@ -235,8 +226,7 @@ void AccountListPage::updateButtonStates() } ui->actionRemove->setEnabled(accountIsReady); ui->actionSetDefault->setEnabled(accountIsReady); - ui->actionUploadSkin->setEnabled(accountIsReady && accountIsOnline); - ui->actionDeleteSkin->setEnabled(accountIsReady && accountIsOnline); + ui->actionManageSkins->setEnabled(accountIsReady && accountIsOnline); ui->actionRefresh->setEnabled(accountIsReady && accountIsOnline); if (m_accounts->defaultAccount().get() == nullptr) { @@ -248,29 +238,13 @@ void AccountListPage::updateButtonStates() } } -void AccountListPage::on_actionUploadSkin_triggered() +void AccountListPage::on_actionManageSkins_triggered() { QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); if (selection.size() > 0) { QModelIndex selected = selection.first(); MinecraftAccountPtr account = selected.data(AccountList::PointerRole).value(); - SkinUploadDialog dialog(account, this); + SkinManageDialog dialog(this, account); dialog.exec(); } } - -void AccountListPage::on_actionDeleteSkin_triggered() -{ - QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); - if (selection.size() <= 0) - return; - - QModelIndex selected = selection.first(); - MinecraftAccountPtr account = selected.data(AccountList::PointerRole).value(); - ProgressDialog prog(this); - auto deleteSkinTask = SkinDelete::make(account->accessToken()); - if (prog.execWithTask((Task*)deleteSkinTask.get()) != QDialog::Accepted) { - CustomMessageBox::selectable(this, tr("Skin Delete"), tr("Failed to delete current skin!"), QMessageBox::Warning)->exec(); - return; - } -} diff --git a/launcher/ui/pages/global/AccountListPage.h b/launcher/ui/pages/global/AccountListPage.h index add0f4aa0..64702cff7 100644 --- a/launcher/ui/pages/global/AccountListPage.h +++ b/launcher/ui/pages/global/AccountListPage.h @@ -77,8 +77,7 @@ class AccountListPage : public QMainWindow, public BasePage { void on_actionRefresh_triggered(); void on_actionSetDefault_triggered(); void on_actionNoDefault_triggered(); - void on_actionUploadSkin_triggered(); - void on_actionDeleteSkin_triggered(); + void on_actionManageSkins_triggered(); void listChanged(); diff --git a/launcher/ui/pages/global/AccountListPage.ui b/launcher/ui/pages/global/AccountListPage.ui index 469955b51..0e73f8c52 100644 --- a/launcher/ui/pages/global/AccountListPage.ui +++ b/launcher/ui/pages/global/AccountListPage.ui @@ -60,19 +60,13 @@ - - + Add &Mojang - - - Remo&ve - - &Set Default @@ -86,17 +80,12 @@ &No Default - + - &Upload Skin - - - - - &Delete Skin + &Manage Skins - Delete the currently active skin and go back to the default one + Manage Skins @@ -117,6 +106,11 @@ Refresh the account tokens + + + Remo&ve + + From 8bad255a9191cd76808a73942da366c981643d35 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 5 Sep 2023 20:13:16 +0300 Subject: [PATCH 007/103] added more import options Signed-off-by: Trial97 --- launcher/minecraft/skins/CapeChange.cpp | 2 +- launcher/minecraft/skins/SkinDelete.cpp | 2 +- launcher/minecraft/skins/SkinList.cpp | 74 ++++---- launcher/minecraft/skins/SkinList.h | 6 +- launcher/minecraft/skins/SkinModel.cpp | 58 +------ launcher/minecraft/skins/SkinModel.h | 3 +- launcher/minecraft/skins/SkinUpload.cpp | 4 +- launcher/net/NetAction.h | 1 + .../ui/dialogs/skins/SkinManageDialog.cpp | 158 ++++++++++++++++-- launcher/ui/dialogs/skins/SkinManageDialog.h | 4 +- launcher/ui/dialogs/skins/SkinManageDialog.ui | 43 ++++- 11 files changed, 229 insertions(+), 126 deletions(-) diff --git a/launcher/minecraft/skins/CapeChange.cpp b/launcher/minecraft/skins/CapeChange.cpp index 863e89844..4db28e245 100644 --- a/launcher/minecraft/skins/CapeChange.cpp +++ b/launcher/minecraft/skins/CapeChange.cpp @@ -44,7 +44,7 @@ CapeChange::CapeChange(QString token, QString cape) : NetRequest(), m_capeId(cape), m_token(token) { logCat = taskMCSkinsLogC; -}; +} QNetworkReply* CapeChange::getReply(QNetworkRequest& request) { diff --git a/launcher/minecraft/skins/SkinDelete.cpp b/launcher/minecraft/skins/SkinDelete.cpp index 982cac1b7..3c50cf313 100644 --- a/launcher/minecraft/skins/SkinDelete.cpp +++ b/launcher/minecraft/skins/SkinDelete.cpp @@ -42,7 +42,7 @@ SkinDelete::SkinDelete(QString token) : NetRequest(), m_token(token) { logCat = taskMCSkinsLogC; -}; +} QNetworkReply* SkinDelete::getReply(QNetworkRequest& request) { diff --git a/launcher/minecraft/skins/SkinList.cpp b/launcher/minecraft/skins/SkinList.cpp index be329564b..15d0b0a8e 100644 --- a/launcher/minecraft/skins/SkinList.cpp +++ b/launcher/minecraft/skins/SkinList.cpp @@ -85,8 +85,6 @@ bool SkinList::update() } catch (const Exception& e) { qCritical() << "Couldn't load skins json:" << e.cause(); } - } else { - newSkins = loadMinecraftSkins(); } bool needsSave = false; @@ -108,7 +106,7 @@ bool SkinList::update() auto path = m_dir.absoluteFilePath(name); if (skinTexture.loadFromData(skin.data, "PNG") && skinTexture.save(path)) { SkinModel s(path); - s.setModel(SkinModel::CLASSIC); // maybe better model detection + s.setModel(skin.variant == "slim" ? SkinModel::SLIM : SkinModel::CLASSIC); s.setCapeId(m_acct->accountData()->minecraftProfile.currentCape); s.setURL(skin.url); newSkins << s; @@ -116,6 +114,7 @@ bool SkinList::update() } } else { nskin->setCapeId(m_acct->accountData()->minecraftProfile.currentCape); + nskin->setModel(skin.variant == "slim" ? SkinModel::SLIM : SkinModel::CLASSIC); } } @@ -207,14 +206,14 @@ bool SkinList::dropMimeData(const QMimeData* data, // files dropped from outside? if (data->hasUrls()) { auto urls = data->urls(); - QStringList iconFiles; + QStringList skinFiles; for (auto url : urls) { // only local files may be dropped... if (!url.isLocalFile()) continue; - iconFiles += url.toLocalFile(); + skinFiles << url.toLocalFile(); } - installSkins(iconFiles); + installSkins(skinFiles); return true; } return false; @@ -261,20 +260,26 @@ int SkinList::rowCount(const QModelIndex& parent) const void SkinList::installSkins(const QStringList& iconFiles) { for (QString file : iconFiles) - installSkin(file, {}); + installSkin(file); } -void SkinList::installSkin(const QString& file, const QString& name) +QString SkinList::installSkin(const QString& file, const QString& name) { + if (file.isEmpty()) + return tr("Path is empty."); QFileInfo fileinfo(file); - if (!fileinfo.isReadable() || !fileinfo.isFile()) - return; - + if (!fileinfo.exists()) + return tr("File doesn't exist."); + if (!fileinfo.isFile()) + return tr("Not a file."); + if (!fileinfo.isReadable()) + return tr("File is not readable."); if (fileinfo.suffix() != "png" && !SkinModel(fileinfo.absoluteFilePath()).isValid()) - return; + return tr("Skin images must be 64x64 or 64x32 pixel PNG files."); QString target = FS::PathCombine(m_dir.absolutePath(), name.isEmpty() ? fileinfo.fileName() : name); - QFile::copy(file, target); + + return QFile::copy(file, target) ? "" : tr("Unable to copy file"); } int SkinList::getSkinIndex(const QString& key) const @@ -311,10 +316,12 @@ bool SkinList::deleteSkin(const QString& key, const bool trash) if (trash) { if (FS::trash(s.getPath(), nullptr)) { m_skin_list.remove(idx); + save(); return true; } } else if (QFile::remove(s.getPath())) { m_skin_list.remove(idx); + save(); return true; } } @@ -361,33 +368,22 @@ bool SkinList::setData(const QModelIndex& idx, const QVariant& value, int role) return true; } -QVector SkinList::loadMinecraftSkins() +void SkinList::updateSkin(SkinModel s) { - QString partialPath; -#if defined(Q_OS_OSX) - partialPath = FS::PathCombine(QDir::homePath(), "Library/Application Support"); -#elif defined(Q_OS_WIN32) - partialPath = QProcessEnvironment::systemEnvironment().value("LOCALAPPDATA", ""); -#else - partialPath = QDir::homePath(); -#endif - QVector newSkins; - auto path = FS::PathCombine(partialPath, ".minecraft", "launcher_custom_skins.json"); - auto manifestInfo = QFileInfo(path); - if (!manifestInfo.exists()) - return {}; - try { - auto doc = Json::requireDocument(manifestInfo.absoluteFilePath(), "SkinList JSON file"); - const auto root = doc.object(); - auto skins = Json::ensureObject(root, "customSkins"); - for (auto key : skins.keys()) { - SkinModel s(m_dir, Json::ensureObject(skins, key)); - if (s.isValid()) { - newSkins << s; - } + auto done = false; + for (auto i = 0; i < m_skin_list.size(); i++) { + if (m_skin_list[i].getPath() == s.getPath()) { + m_skin_list[i].setCapeId(s.getCapeId()); + m_skin_list[i].setModel(s.getModel()); + m_skin_list[i].setURL(s.getURL()); + done = true; + break; } - } catch (const Exception& e) { - qCritical() << "Couldn't load minecraft skins json:" << e.cause(); } - return newSkins; + if (!done) { + beginInsertRows(QModelIndex(), m_skin_list.count(), m_skin_list.count() + 1); + m_skin_list.append(s); + endInsertRows(); + } + save(); } diff --git a/launcher/minecraft/skins/SkinList.h b/launcher/minecraft/skins/SkinList.h index 8d8266d79..b6981e1b4 100644 --- a/launcher/minecraft/skins/SkinList.h +++ b/launcher/minecraft/skins/SkinList.h @@ -46,7 +46,7 @@ class SkinList : public QAbstractListModel { bool deleteSkin(const QString& key, const bool trash); void installSkins(const QStringList& iconFiles); - void installSkin(const QString& file, const QString& name); + QString installSkin(const QString& file, const QString& name = {}); const SkinModel* skin(const QString& key) const; SkinModel* skin(const QString& key); @@ -58,14 +58,14 @@ class SkinList : public QAbstractListModel { void save(); int getSelectedAccountSkin(); + void updateSkin(SkinModel s); + private: // hide copy constructor SkinList(const SkinList&) = delete; // hide assign op SkinList& operator=(const SkinList&) = delete; - QVector loadMinecraftSkins(); - protected slots: void directoryChanged(const QString& path); void fileChanged(const QString& path); diff --git a/launcher/minecraft/skins/SkinModel.cpp b/launcher/minecraft/skins/SkinModel.cpp index 3b467019c..d53b9e762 100644 --- a/launcher/minecraft/skins/SkinModel.cpp +++ b/launcher/minecraft/skins/SkinModel.cpp @@ -31,28 +31,12 @@ SkinModel::SkinModel(QDir skinDir, QJsonObject obj) : m_cape_id(Json::ensureString(obj, "capeId")), m_model(Model::CLASSIC), m_url(Json::ensureString(obj, "url")) { auto name = Json::ensureString(obj, "name"); - auto skinImage = Json::ensureString(obj, "skinImage"); - if (!skinImage.isEmpty()) { // minecraft skin model - skinImage = skinImage.mid(22); - m_texture.loadFromData(QByteArray::fromBase64(skinImage.toUtf8()), "PNG"); - auto textureId = Json::ensureString(obj, "textureId"); - if (name.isEmpty()) { - name = textureId; - } - if (Json::ensureBoolean(obj, "slim", false)) { - m_model = Model::SLIM; - } - } else { - if (auto model = Json::ensureString(obj, "model"); model == "SLIM") { - m_model = Model::SLIM; - } + + if (auto model = Json::ensureString(obj, "model"); model == "SLIM") { + m_model = Model::SLIM; } m_path = skinDir.absoluteFilePath(name) + ".png"; - if (!QFileInfo(m_path).exists() && isValid()) { - m_texture.save(m_path, "PNG"); - } else { - m_texture = QPixmap(m_path); - } + m_texture = QPixmap(m_path); } QString SkinModel::name() const @@ -92,37 +76,3 @@ bool SkinModel::isValid() const { return !m_texture.isNull() && (m_texture.size().height() == 32 || m_texture.size().height() == 64) && m_texture.size().width() == 64; } - -QPixmap SkinModel::renderFrontBody() const -{ - auto isSlim = m_model == SLIM; - auto slimOffset = isSlim ? 1 : 0; - auto isOldSkin = m_texture.height() < 64; - - auto head = m_texture.copy(QRect(8, 8, 16, 16)); - auto torso = m_texture.copy(QRect(20, 20, 28, 32)); - auto rightArm = m_texture.copy(QRect(44, 20, 48 - slimOffset, 32)); - auto rightLeg = m_texture.copy(QRect(4, 20, 8, 32)); - QPixmap leftArm, leftLeg; - - if (isOldSkin) { - leftArm = rightArm.transformed(QTransform().scale(-1, 1)); - leftLeg = rightLeg.transformed(QTransform().scale(-1, 1)); - } else { - leftArm = m_texture.copy(QRect(36, 52, 40 - slimOffset, 64)); - leftLeg = m_texture.copy(QRect(20, 52, 24, 64)); - } - QPixmap output(16, 32); - output.fill(Qt::black); - QPainter p; - if (!p.begin(&output)) - return {}; - p.drawPixmap(QPoint(4, 0), head); - p.drawPixmap(QPoint(4, 8), torso); - p.drawPixmap(QPoint(12, 8), leftArm); - p.drawPixmap(QPoint(slimOffset, 8), rightArm); - p.drawPixmap(QPoint(8, 20), leftLeg); - p.drawPixmap(QPoint(4, 20), leftArm); - - return output.scaled(128, 128, Qt::KeepAspectRatioByExpanding, Qt::FastTransformation); -} \ No newline at end of file diff --git a/launcher/minecraft/skins/SkinModel.h b/launcher/minecraft/skins/SkinModel.h index 6d135c7f7..46e9d6cf1 100644 --- a/launcher/minecraft/skins/SkinModel.h +++ b/launcher/minecraft/skins/SkinModel.h @@ -26,6 +26,7 @@ class SkinModel { public: enum Model { CLASSIC, SLIM }; + SkinModel() = default; SkinModel(QString path); SkinModel(QDir skinDir, QJsonObject obj); virtual ~SkinModel() = default; @@ -47,8 +48,6 @@ class SkinModel { QJsonObject toJSON() const; - QPixmap renderFrontBody() const; - private: QString m_path; QPixmap m_texture; diff --git a/launcher/minecraft/skins/SkinUpload.cpp b/launcher/minecraft/skins/SkinUpload.cpp index 4e56bd7e6..4496f3f1c 100644 --- a/launcher/minecraft/skins/SkinUpload.cpp +++ b/launcher/minecraft/skins/SkinUpload.cpp @@ -44,8 +44,8 @@ SkinUpload::SkinUpload(QString token, SkinModel* skin) : NetRequest(), m_skin(skin), m_token(token) { - logCat = taskUploadLogC; -}; + logCat = taskMCSkinsLogC; +} QNetworkReply* SkinUpload::getReply(QNetworkRequest& request) { diff --git a/launcher/net/NetAction.h b/launcher/net/NetAction.h index b66b91941..6440d38b9 100644 --- a/launcher/net/NetAction.h +++ b/launcher/net/NetAction.h @@ -55,6 +55,7 @@ class NetAction : public Task { virtual ~NetAction() = default; QUrl url() { return m_url; } + void setUrl(QUrl url) { m_url = url; } void setNetwork(shared_qobject_ptr network) { m_network = network; } diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index 1ba7e7055..0afb6cbc4 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -16,37 +16,41 @@ * along with this program. If not, see . */ -#include -#include -#include -#include +#include "SkinManageDialog.h" +#include "ui_SkinManageDialog.h" #include #include #include +#include +#include #include #include #include +#include +#include +#include #include "Application.h" #include "DesktopServices.h" +#include "Json.h" #include "QObjectPtr.h" -#include "SkinManageDialog.h" #include "minecraft/auth/AccountTask.h" +#include "minecraft/auth/Parsers.h" #include "minecraft/skins/CapeChange.h" #include "minecraft/skins/SkinDelete.h" #include "minecraft/skins/SkinList.h" #include "minecraft/skins/SkinModel.h" #include "minecraft/skins/SkinUpload.h" +#include "net/Download.h" #include "net/NetJob.h" #include "tasks/Task.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/ProgressDialog.h" #include "ui/instanceview/InstanceDelegate.h" -#include "ui_SkinManageDialog.h" SkinManageDialog::SkinManageDialog(QWidget* parent, MinecraftAccountPtr acct) : QDialog(parent), m_acct(acct), ui(new Ui::SkinManageDialog), m_list(this, APPLICATION->settings()->get("SkinsDir").toString(), acct) @@ -132,20 +136,15 @@ void SkinManageDialog::on_openDirBtn_clicked() DesktopServices::openDirectory(m_list.getDir(), true); } -void SkinManageDialog::on_addBtn_clicked() +void SkinManageDialog::on_fileBtn_clicked() { auto filter = QMimeDatabase().mimeTypeForName("image/png").filterString(); QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), filter); - if (raw_path.isEmpty() || !QFileInfo::exists(raw_path)) { + auto message = m_list.installSkin(raw_path, {}); + if (!message.isEmpty()) { + CustomMessageBox::selectable(this, tr("Selected file is not a valid skin"), message, QMessageBox::Critical)->show(); return; } - if (!SkinModel(raw_path).isValid()) { - CustomMessageBox::selectable(this, tr("Selected file is not a valid skin"), - tr("Skin images must be 64x64 or 64x32 pixel PNG files."), QMessageBox::Critical) - ->show(); - return; - } - m_list.installSkin(raw_path, {}); } QPixmap previewCape(QPixmap capeImage) @@ -337,3 +336,132 @@ void SkinManageDialog::on_action_Delete_Skin_triggered(bool checked) } } } + +void SkinManageDialog::on_urlBtn_clicked() +{ + auto url = QUrl(ui->urlLine->text()); + if (!url.isValid()) { + CustomMessageBox::selectable(this, tr("Invalid url"), tr("Invalid url"), QMessageBox::Critical)->show(); + return; + } + ui->urlLine->setText(""); + + NetJob::Ptr job{ new NetJob(tr("Download skin"), APPLICATION->network()) }; + + auto path = FS::PathCombine(m_list.getDir(), url.fileName()); + job->addNetAction(Net::Download::makeFile(url, path)); + ProgressDialog dlg(this); + dlg.execWithTask(job.get()); + SkinModel s(path); + if (!s.isValid()) { + CustomMessageBox::selectable(this, tr("URL is not a valid skin"), tr("Skin images must be 64x64 or 64x32 pixel PNG files."), + QMessageBox::Critical) + ->show(); + QFile::remove(path); + return; + } + if (QFileInfo(path).suffix().isEmpty()) { + QFile::rename(path, path + ".png"); + } +} + +class WaitTask : public Task { + public: + WaitTask() : m_loop(), m_done(false){}; + virtual ~WaitTask() = default; + + public slots: + void quit() + { + m_done = true; + m_loop.quit(); + } + + protected: + virtual void executeTask() + { + if (!m_done) + m_loop.exec(); + emitSucceeded(); + }; + + private: + QEventLoop m_loop; + bool m_done; +}; + +void SkinManageDialog::on_userBtn_clicked() +{ + auto user = ui->urlLine->text(); + if (user.isEmpty()) { + return; + } + ui->urlLine->setText(""); + MinecraftProfile mcProfile; + auto path = FS::PathCombine(m_list.getDir(), user + ".png"); + + NetJob::Ptr job{ new NetJob(tr("Download user skin"), APPLICATION->network(), 1) }; + + auto uuidOut = std::make_shared(); + auto profileOut = std::make_shared(); + + auto uuidLoop = makeShared(); + auto profileLoop = makeShared(); + + auto getUUID = Net::Download::makeByteArray("https://api.mojang.com/users/profiles/minecraft/" + user, uuidOut); + auto getProfile = Net::Download::makeByteArray(QUrl(), profileOut); + auto downloadSkin = Net::Download::makeFile(QUrl(), path); + + connect(getUUID.get(), &Task::aborted, uuidLoop.get(), &WaitTask::quit); + connect(getUUID.get(), &Task::failed, uuidLoop.get(), &WaitTask::quit); + connect(getProfile.get(), &Task::aborted, profileLoop.get(), &WaitTask::quit); + connect(getProfile.get(), &Task::failed, profileLoop.get(), &WaitTask::quit); + + connect(getUUID.get(), &Task::succeeded, this, [uuidLoop, uuidOut, job, getProfile] { + try { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*uuidOut, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from Minecraft skin service at " << parse_error.offset + << " reason: " << parse_error.errorString(); + uuidLoop->quit(); + return; + } + const auto root = doc.object(); + auto id = Json::ensureString(root, "id"); + if (!id.isEmpty()) { + getProfile->setUrl("https://sessionserver.mojang.com/session/minecraft/profile/" + id); + } else { + job->abort(); + } + } catch (const Exception& e) { + qCritical() << "Couldn't load skin json:" << e.cause(); + } + uuidLoop->quit(); + }); + + connect(getProfile.get(), &Task::succeeded, this, [profileLoop, profileOut, job, getProfile, &mcProfile, downloadSkin] { + if (Parsers::parseMinecraftProfileMojang(*profileOut, mcProfile)) { + downloadSkin->setUrl(mcProfile.skin.url); + } else { + job->abort(); + } + profileLoop->quit(); + }); + + job->addNetAction(getUUID); + job->addTask(uuidLoop); + job->addNetAction(getProfile); + job->addTask(profileLoop); + job->addNetAction(downloadSkin); + ProgressDialog dlg(this); + dlg.execWithTask(job.get()); + + SkinModel s(path); + s.setModel(mcProfile.skin.variant == "slim" ? SkinModel::SLIM : SkinModel::CLASSIC); + s.setURL(mcProfile.skin.url); + if (m_capes.contains(mcProfile.currentCape)) { + s.setCapeId(mcProfile.currentCape); + } + m_list.updateSkin(s); +} \ No newline at end of file diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.h b/launcher/ui/dialogs/skins/SkinManageDialog.h index 8c55c3310..ce8fc9348 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.h +++ b/launcher/ui/dialogs/skins/SkinManageDialog.h @@ -40,7 +40,9 @@ class SkinManageDialog : public QDialog { void activated(QModelIndex); void delayed_scroll(QModelIndex); void on_openDirBtn_clicked(); - void on_addBtn_clicked(); + void on_fileBtn_clicked(); + void on_urlBtn_clicked(); + void on_userBtn_clicked(); void accept() override; void on_capeCombo_currentIndexChanged(int index); void on_steveBtn_toggled(bool checked); diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.ui b/launcher/ui/dialogs/skins/SkinManageDialog.ui index 6ad826478..c2ce9143c 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.ui +++ b/launcher/ui/dialogs/skins/SkinManageDialog.ui @@ -100,7 +100,7 @@ - + @@ -108,13 +108,6 @@ - - - - Import Skin - - - @@ -122,8 +115,42 @@ + + + + + + + + + + + Import URL + + + + + + + Import user + + + + + + + Import File + + + + + + 0 + 0 + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok From 8c8e4329d72afb1b5141725127c883b52513761b Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 5 Sep 2023 23:45:32 +0300 Subject: [PATCH 008/103] fix codeql Signed-off-by: Trial97 --- launcher/minecraft/skins/SkinList.cpp | 12 ++++++------ launcher/minecraft/skins/SkinList.h | 2 +- launcher/ui/dialogs/skins/SkinManageDialog.cpp | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/launcher/minecraft/skins/SkinList.cpp b/launcher/minecraft/skins/SkinList.cpp index 15d0b0a8e..b3a593454 100644 --- a/launcher/minecraft/skins/SkinList.cpp +++ b/launcher/minecraft/skins/SkinList.cpp @@ -368,21 +368,21 @@ bool SkinList::setData(const QModelIndex& idx, const QVariant& value, int role) return true; } -void SkinList::updateSkin(SkinModel s) +void SkinList::updateSkin(SkinModel* s) { auto done = false; for (auto i = 0; i < m_skin_list.size(); i++) { - if (m_skin_list[i].getPath() == s.getPath()) { - m_skin_list[i].setCapeId(s.getCapeId()); - m_skin_list[i].setModel(s.getModel()); - m_skin_list[i].setURL(s.getURL()); + if (m_skin_list[i].getPath() == s->getPath()) { + m_skin_list[i].setCapeId(s->getCapeId()); + m_skin_list[i].setModel(s->getModel()); + m_skin_list[i].setURL(s->getURL()); done = true; break; } } if (!done) { beginInsertRows(QModelIndex(), m_skin_list.count(), m_skin_list.count() + 1); - m_skin_list.append(s); + m_skin_list.append(*s); endInsertRows(); } save(); diff --git a/launcher/minecraft/skins/SkinList.h b/launcher/minecraft/skins/SkinList.h index b6981e1b4..66af6a17b 100644 --- a/launcher/minecraft/skins/SkinList.h +++ b/launcher/minecraft/skins/SkinList.h @@ -58,7 +58,7 @@ class SkinList : public QAbstractListModel { void save(); int getSelectedAccountSkin(); - void updateSkin(SkinModel s); + void updateSkin(SkinModel* s); private: // hide copy constructor diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index 0afb6cbc4..4ef91a2bf 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -463,5 +463,5 @@ void SkinManageDialog::on_userBtn_clicked() if (m_capes.contains(mcProfile.currentCape)) { s.setCapeId(mcProfile.currentCape); } - m_list.updateSkin(s); + m_list.updateSkin(&s); } \ No newline at end of file From 6ec1cf6e4933013e04acb9086f3fcf4a844d181a Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 8 Sep 2023 19:50:46 +0300 Subject: [PATCH 009/103] made skin upload more generic Signed-off-by: Trial97 --- launcher/minecraft/skins/SkinUpload.cpp | 10 +++++----- launcher/minecraft/skins/SkinUpload.h | 8 ++++---- launcher/ui/dialogs/skins/SkinManageDialog.cpp | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/launcher/minecraft/skins/SkinUpload.cpp b/launcher/minecraft/skins/SkinUpload.cpp index 4496f3f1c..4a88faedf 100644 --- a/launcher/minecraft/skins/SkinUpload.cpp +++ b/launcher/minecraft/skins/SkinUpload.cpp @@ -42,7 +42,7 @@ #include "net/ByteArraySink.h" #include "net/StaticHeaderProxy.h" -SkinUpload::SkinUpload(QString token, SkinModel* skin) : NetRequest(), m_skin(skin), m_token(token) +SkinUpload::SkinUpload(QString token, QString path, QString variant) : NetRequest(), m_token(token), m_path(path), m_variant(variant) { logCat = taskMCSkinsLogC; } @@ -55,11 +55,11 @@ QNetworkReply* SkinUpload::getReply(QNetworkRequest& request) skin.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/png")); skin.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"file\"; filename=\"skin.png\"")); - skin.setBody(FS::read(m_skin->getPath())); + skin.setBody(FS::read(m_path)); QHttpPart model; model.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"variant\"")); - model.setBody(m_skin->getModelString().toUtf8()); + model.setBody(m_variant.toUtf8()); multiPart->append(skin); multiPart->append(model); @@ -74,9 +74,9 @@ void SkinUpload::init() })); } -SkinUpload::Ptr SkinUpload::make(QString token, SkinModel* skin) +SkinUpload::Ptr SkinUpload::make(QString token, QString path, QString variant) { - auto up = makeShared(token, skin); + auto up = makeShared(token, path, variant); up->m_url = QUrl("https://api.minecraftservices.com/minecraft/profile/skins"); up->setObjectName(QString("BYTES:") + up->m_url.toString()); up->m_sink.reset(new Net::ByteArraySink(std::make_shared())); diff --git a/launcher/minecraft/skins/SkinUpload.h b/launcher/minecraft/skins/SkinUpload.h index d070f301d..f24cef5a2 100644 --- a/launcher/minecraft/skins/SkinUpload.h +++ b/launcher/minecraft/skins/SkinUpload.h @@ -18,7 +18,6 @@ #pragma once -#include "minecraft/skins/SkinModel.h" #include "net/NetRequest.h" class SkinUpload : public Net::NetRequest { @@ -27,16 +26,17 @@ class SkinUpload : public Net::NetRequest { using Ptr = shared_qobject_ptr; // Note this class takes ownership of the file. - SkinUpload(QString token, SkinModel* skin); + SkinUpload(QString token, QString path, QString variant); virtual ~SkinUpload() = default; - static SkinUpload::Ptr make(QString token, SkinModel* skin); + static SkinUpload::Ptr make(QString token, QString path, QString variant); void init() override; protected: virtual QNetworkReply* getReply(QNetworkRequest&) override; private: - SkinModel* m_skin; QString m_token; + QString m_path; + QString m_variant; }; diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index 4ef91a2bf..24197baeb 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -242,7 +242,7 @@ void SkinManageDialog::accept() return; } - skinUpload->addNetAction(SkinUpload::make(m_acct->accessToken(), skin)); + skinUpload->addNetAction(SkinUpload::make(m_acct->accessToken(), skin->getPath(), skin->getModelString())); auto selectedCape = skin->getCapeId(); if (selectedCape != m_acct->accountData()->minecraftProfile.currentCape) { From d2e662ddbb518b147de76ca3f613e046d2912db5 Mon Sep 17 00:00:00 2001 From: cullvox Date: Sat, 9 Sep 2023 12:35:34 -0400 Subject: [PATCH 010/103] added support for components in resource pack descriptions. Signed-off-by: Caden Miller --- .../mod/tasks/LocalResourcePackParseTask.cpp | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index 73cbf891c..f78d3b7b3 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -186,7 +186,35 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) auto pack_obj = Json::requireObject(json_doc.object(), "pack", {}); pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0)); - pack.setDescription(Json::ensureString(pack_obj, "description", "")); + + // description could either be string, or array of dictionaries + auto desc_val = pack_obj.value("description"); + + if (desc_val.isString()) { + pack.setDescription(Json::ensureString(pack_obj, "description", "")); + } else if (desc_val.isArray()) { + + // rebuild the description from the dictionaries without colors + QString build_desc; + auto arr = desc_val.toArray(); + + for(const QJsonValue& dict : arr) { + // must be an object, for a dictionary + if (!dict.isObject()) throw Json::JsonException("Invalid value description."); + + auto obj = dict.toObject(); + auto val = obj.value(obj.keys()[0]); + + // value must be a string type + if (!val.isString()) throw Json::JsonException("Invalid value description type."); + + build_desc.append(val.toString()); + }; + + pack.setDescription(build_desc); + } else { + throw Json::JsonException("Invalid description type."); + } } catch (Json::JsonException& e) { qWarning() << "JsonException: " << e.what() << e.cause(); return false; From 093d09efe36dbe1b18e4ae0ccb48150ef2f9f3ab Mon Sep 17 00:00:00 2001 From: cullvox Date: Sat, 9 Sep 2023 19:03:33 -0400 Subject: [PATCH 011/103] fix style, and use qWarning instead of throw. Signed-off-by: cullvox --- .../mod/tasks/LocalResourcePackParseTask.cpp | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index f78d3b7b3..22f5fa163 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -189,9 +189,13 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) // description could either be string, or array of dictionaries auto desc_val = pack_obj.value("description"); + if (desc_val.isUndefined()) { + qWarning() << "No resource pack description found."; + return false; + } if (desc_val.isString()) { - pack.setDescription(Json::ensureString(pack_obj, "description", "")); + pack.setDescription(desc_val.toString()); } else if (desc_val.isArray()) { // rebuild the description from the dictionaries without colors @@ -200,20 +204,27 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) for(const QJsonValue& dict : arr) { // must be an object, for a dictionary - if (!dict.isObject()) throw Json::JsonException("Invalid value description."); + if (!dict.isObject()) + { + qWarning() << "Invalid component object."; + continue; + } auto obj = dict.toObject(); auto val = obj.value(obj.keys()[0]); - // value must be a string type - if (!val.isString()) throw Json::JsonException("Invalid value description type."); + if (!val.isString()) + { + qWarning() << "Invalid text description type in components."; + continue; + } build_desc.append(val.toString()); }; pack.setDescription(build_desc); } else { - throw Json::JsonException("Invalid description type."); + qWarning() << "Invalid description type."; } } catch (Json::JsonException& e) { qWarning() << "JsonException: " << e.what() << e.cause(); From 04aa0155bf181dcd17236ef5baa3b91a4b9df449 Mon Sep 17 00:00:00 2001 From: cullvox Date: Sat, 9 Sep 2023 19:45:30 -0400 Subject: [PATCH 012/103] DCO Remediation Commit for cullvox I, cullvox , hereby add my Signed-off-by to this commit: d2e662ddbb518b147de76ca3f613e046d2912db5 Signed-off-by: cullvox --- launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index 22f5fa163..7a2fd7a86 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -221,7 +221,6 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) build_desc.append(val.toString()); }; - pack.setDescription(build_desc); } else { qWarning() << "Invalid description type."; From 05f4214cc5d3a9005c0d259ca185303792aa2fa7 Mon Sep 17 00:00:00 2001 From: cullvox Date: Sat, 9 Sep 2023 19:50:13 -0400 Subject: [PATCH 013/103] fix clang-format failing --- .../minecraft/mod/tasks/LocalResourcePackParseTask.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index 7a2fd7a86..b696adea5 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -202,10 +202,9 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) QString build_desc; auto arr = desc_val.toArray(); - for(const QJsonValue& dict : arr) { + for (const QJsonValue& dict : arr) { // must be an object, for a dictionary - if (!dict.isObject()) - { + if (!dict.isObject()) { qWarning() << "Invalid component object."; continue; } @@ -213,8 +212,7 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) auto obj = dict.toObject(); auto val = obj.value(obj.keys()[0]); - if (!val.isString()) - { + if (!val.isString()) { qWarning() << "Invalid text description type in components."; continue; } From ef1dc2afaccc681fdfd53513ac134b36a5d943cc Mon Sep 17 00:00:00 2001 From: cullvox Date: Sat, 9 Sep 2023 19:50:59 -0400 Subject: [PATCH 014/103] DCO Remediation Commit for cullvox I, cullvox , hereby add my Signed-off-by to this commit: 05f4214cc5d3a9005c0d259ca185303792aa2fa7 Signed-off-by: cullvox --- launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index b696adea5..eaef76a6c 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -197,7 +197,6 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) if (desc_val.isString()) { pack.setDescription(desc_val.toString()); } else if (desc_val.isArray()) { - // rebuild the description from the dictionaries without colors QString build_desc; auto arr = desc_val.toArray(); From 1261908ef7465e000390c9c12133171f4e73302f Mon Sep 17 00:00:00 2001 From: cullvox Date: Sat, 9 Sep 2023 19:55:55 -0400 Subject: [PATCH 015/103] remove space for clang-format Signed-off-by: cullvox --- launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index eaef76a6c..1f535695b 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -194,7 +194,7 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) return false; } - if (desc_val.isString()) { + if (desc_val.isString()) { pack.setDescription(desc_val.toString()); } else if (desc_val.isArray()) { // rebuild the description from the dictionaries without colors From 7a7c3015f47cba94eb34deba6748dc8555d8e457 Mon Sep 17 00:00:00 2001 From: cullvox Date: Sat, 9 Sep 2023 20:25:49 -0400 Subject: [PATCH 016/103] fix not properly reading json text. The text now displays properly in the GUI of Prism. Signed-off-by: cullvox --- .../minecraft/mod/tasks/LocalResourcePackParseTask.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index 1f535695b..4c30c1204 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -209,14 +209,8 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) } auto obj = dict.toObject(); - auto val = obj.value(obj.keys()[0]); - if (!val.isString()) { - qWarning() << "Invalid text description type in components."; - continue; - } - - build_desc.append(val.toString()); + build_desc.append(Json::ensureString(obj, "text", {})); }; pack.setDescription(build_desc); } else { From 58bd6d929f20e43c38cb372e431d1e1240d736b6 Mon Sep 17 00:00:00 2001 From: cullvox Date: Sun, 10 Sep 2023 23:37:26 -0400 Subject: [PATCH 017/103] added formatting with colors. This is somewhat dirty implementation and I will clean it up soon. Using the HTML rich text features of Qt, made it much easier to understand what was needed. Signed-off-by: cullvox --- .../mod/tasks/LocalResourcePackParseTask.cpp | 170 +++++++++++++++--- 1 file changed, 143 insertions(+), 27 deletions(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index 4c30c1204..e6dde4ba0 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -178,9 +178,143 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level) return true; } -// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta -bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) +struct TextFormat { + QString color = "#000000"; + bool bold = false; + bool italic = false; + bool underlined = false; + bool strikethrough = false; + + TextFormat() = default; + ~TextFormat() = default; + + void clear() { + color = "#00000"; + bold = false; + italic = false; + underlined = false; + strikethrough = false; + } +}; + +QString textColorToHexColor(const QString& color) { + static const std::unordered_map str_to_hex = { + { "black", "#000000" }, + { "dark_blue", "#0000AA" }, + { "dark_green", "#00AA00" }, + { "dark_aqua", "#00AAAA" }, + { "dark_red", "#AA0000" }, + { "dark_purple", "#AA00AA" }, + { "gold", "#FFAA00" }, + { "gray", "#AAAAAA" }, + { "dark_gray", "#555555" }, + { "blue", "#5555FF" }, + { "green", "#55FF55" }, + { "aqua", "#55FFFF" }, + { "red", "#FF5555" }, + { "light_purple", "#FF55FF" }, + { "yellow", "#FFFF55" }, + { "white", "#FFFFFF" }, + }; + + auto it = str_to_hex.find(color); + return (it != str_to_hex.end()) ? it->second : QString("#000000"); // return black if color not found +} + +bool readFormat(const QJsonObject& obj, TextFormat& format) { + auto text = obj.value("text"); + auto color = obj.value("color"); + auto bold = obj.value("bold"); + auto italic = obj.value("italic"); + auto underlined = obj.value("underlined"); + auto strikethrough = obj.value("strikethrough"); + auto extra = obj.value("extra"); + + if (color.isString()) { + format.color = textColorToHexColor(color.toString()); + } + if (bold.isBool()) + format.bold = bold.toBool(); + if (italic.isBool()) + format.italic = italic.toBool(); + if (underlined.isBool()) + format.underlined = underlined.toBool(); + if (strikethrough.isBool()) + format.strikethrough = strikethrough.toBool(); + + return true; +} + +void appendBeginFormat(TextFormat& format, QString& toAppend) { + toAppend.append(""); + if (format.bold) + toAppend.append(""); + if (format.italic) + toAppend.append(""); + if (format.underlined) + toAppend.append(""); + if (format.strikethrough) + toAppend.append(""); +} + +void appendEndFormat(TextFormat& format, QString& toAppend) { + toAppend.append(""); + if (format.bold) + toAppend.append(""); + if (format.italic) + toAppend.append(""); + if (format.underlined) + toAppend.append(""); + if (format.strikethrough) + toAppend.append(""); +} + +bool processComponent(const QJsonValue& value, QString& result, TextFormat* parentFormat = nullptr); + +bool processComponentList(const QJsonArray& arr, QString& result, TextFormat* parentFormat = nullptr) { + + for (const QJsonValue& val : arr) { + processComponent(val, result, parentFormat); + } + + return true; +} + +bool processComponent(const QJsonValue& value, QString& result, TextFormat* parentFormat) { + if (value.isString()) { + result.append(value.toString()); + } else if (value.isObject()) { + auto obj = value.toObject(); + + TextFormat format{}; + if (parentFormat) + format = *parentFormat; + + if (!readFormat(obj, format)) + return false; + + appendBeginFormat(format, result); + + auto text = obj.value("text"); + if (text.isString()) + result.append(text.toString()); + + auto extra = obj.value("extra"); + if (extra.isArray()) + if (!processComponentList(extra.toArray(), result, &format)) + return false; + + appendEndFormat(format, result); + } + + return true; +} + +// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta +bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) { + + try { auto json_doc = QJsonDocument::fromJson(raw_data); auto pack_obj = Json::requireObject(json_doc.object(), "pack", {}); @@ -189,33 +323,15 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) // description could either be string, or array of dictionaries auto desc_val = pack_obj.value("description"); - if (desc_val.isUndefined()) { - qWarning() << "No resource pack description found."; - return false; - } - - if (desc_val.isString()) { + + if (desc_val.isString()) pack.setDescription(desc_val.toString()); - } else if (desc_val.isArray()) { - // rebuild the description from the dictionaries without colors - QString build_desc; - auto arr = desc_val.toArray(); - - for (const QJsonValue& dict : arr) { - // must be an object, for a dictionary - if (!dict.isObject()) { - qWarning() << "Invalid component object."; - continue; - } - - auto obj = dict.toObject(); - - build_desc.append(Json::ensureString(obj, "text", {})); - }; - pack.setDescription(build_desc); - } else { - qWarning() << "Invalid description type."; + else if (desc_val.isArray()) { + QString desc{}; + processComponentList(desc_val.toArray(), desc); + pack.setDescription(desc); } + } catch (Json::JsonException& e) { qWarning() << "JsonException: " << e.what() << e.cause(); return false; From fbe4043651bebaca399b9f4499a2097670e485b6 Mon Sep 17 00:00:00 2001 From: cullvox Date: Mon, 11 Sep 2023 01:34:53 -0400 Subject: [PATCH 018/103] fix formatting and add more proper errors. Signed-off-by: cullvox --- .../mod/tasks/LocalResourcePackParseTask.cpp | 129 +++++++++++------- 1 file changed, 80 insertions(+), 49 deletions(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index e6dde4ba0..ca1a5455c 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -184,45 +184,60 @@ struct TextFormat { bool italic = false; bool underlined = false; bool strikethrough = false; - - TextFormat() = default; - ~TextFormat() = default; - - void clear() { - color = "#00000"; - bold = false; - italic = false; - underlined = false; - strikethrough = false; - } }; -QString textColorToHexColor(const QString& color) +bool textColorToHexColor(const QString& text_color, QString& result) { - static const std::unordered_map str_to_hex = { - { "black", "#000000" }, - { "dark_blue", "#0000AA" }, - { "dark_green", "#00AA00" }, - { "dark_aqua", "#00AAAA" }, - { "dark_red", "#AA0000" }, - { "dark_purple", "#AA00AA" }, - { "gold", "#FFAA00" }, - { "gray", "#AAAAAA" }, - { "dark_gray", "#555555" }, - { "blue", "#5555FF" }, - { "green", "#55FF55" }, - { "aqua", "#55FFFF" }, - { "red", "#FF5555" }, - { "light_purple", "#FF55FF" }, - { "yellow", "#FFFF55" }, - { "white", "#FFFFFF" }, + static const QHash text_to_hex = { + { "black", "#000000" }, { "dark_blue", "#0000AA" }, { "dark_green", "#00AA00" }, { "dark_aqua", "#00AAAA" }, + { "dark_red", "#AA0000" }, { "dark_purple", "#AA00AA" }, { "gold", "#FFAA00" }, { "gray", "#AAAAAA" }, + { "dark_gray", "#555555" }, { "blue", "#5555FF" }, { "green", "#55FF55" }, { "aqua", "#55FFFF" }, + { "red", "#FF5555" }, { "light_purple", "#FF55FF" }, { "yellow", "#FFFF55" }, { "white", "#FFFFFF" }, }; - auto it = str_to_hex.find(color); - return (it != str_to_hex.end()) ? it->second : QString("#000000"); // return black if color not found + auto it = text_to_hex.find(text_color); + + if (it == text_to_hex.end()) { + qWarning() << "Invalid color string in component!"; + result = "#000000"; // return black if color not found + return false; + } + + result = it.value(); + return true; } -bool readFormat(const QJsonObject& obj, TextFormat& format) { +bool getBoolOrFromText(const QJsonValue& val, bool& result) +{ + if (val.isUndefined()) { + result = false; + return true; + } + + if (val.isBool()) { + result = val.toBool(); + return true; + } else if (val.isString()) { + auto bool_str = val.toString(); + + if (bool_str == "true") { + result = true; + return true; + } else if (bool_str == "false") { + result = false; + return true; + } else { + qWarning() << "Invalid bool value in component!"; + return false; + } + } else { + qWarning() << "Invalid type where bool expected!"; + return false; + } +} + +bool readFormat(const QJsonObject& obj, TextFormat& format) +{ auto text = obj.value("text"); auto color = obj.value("color"); auto bold = obj.value("bold"); @@ -232,21 +247,30 @@ bool readFormat(const QJsonObject& obj, TextFormat& format) { auto extra = obj.value("extra"); if (color.isString()) { - format.color = textColorToHexColor(color.toString()); - } - if (bold.isBool()) - format.bold = bold.toBool(); - if (italic.isBool()) - format.italic = italic.toBool(); - if (underlined.isBool()) - format.underlined = underlined.toBool(); - if (strikethrough.isBool()) - format.strikethrough = strikethrough.toBool(); + // colors can either be a hex code or one of a few text colors + auto col_str = color.toString(); - return true; + const QRegularExpression hex_expression("^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$"); + const auto hex_match = hex_expression.match(col_str); + + if (hex_match.hasMatch()) { + format.color = color.toString(); + } else { + if (!textColorToHexColor(color.toString(), format.color)) { + qWarning() << "Invalid color type in component!"; + return false; + } + } + } + + return getBoolOrFromText(bold, format.bold) && + getBoolOrFromText(italic, format.italic) && + getBoolOrFromText(underlined, format.underlined) && + getBoolOrFromText(strikethrough, format.strikethrough); } -void appendBeginFormat(TextFormat& format, QString& toAppend) { +void appendBeginFormat(TextFormat& format, QString& toAppend) +{ toAppend.append(""); if (format.bold) toAppend.append(""); @@ -275,7 +299,8 @@ bool processComponent(const QJsonValue& value, QString& result, TextFormat* pare bool processComponentList(const QJsonArray& arr, QString& result, TextFormat* parentFormat = nullptr) { for (const QJsonValue& val : arr) { - processComponent(val, result, parentFormat); + if (!processComponent(val, result, parentFormat)) + return false; } return true; @@ -306,6 +331,9 @@ bool processComponent(const QJsonValue& value, QString& result, TextFormat* pare return false; appendEndFormat(format, result); + } else { + qWarning() << "Invalid component type!"; + return false; } return true; @@ -324,12 +352,15 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) { // description could either be string, or array of dictionaries auto desc_val = pack_obj.value("description"); - if (desc_val.isString()) + if (desc_val.isString()) { pack.setDescription(desc_val.toString()); - else if (desc_val.isArray()) { - QString desc{}; - processComponentList(desc_val.toArray(), desc); + } else if (desc_val.isArray()) { + QString desc; + if (!processComponentList(desc_val.toArray(), desc)) + return false; pack.setDescription(desc); + } else { + return false; } } catch (Json::JsonException& e) { From b16085f66c9083a1ec6d0feb08b96ce9173baccd Mon Sep 17 00:00:00 2001 From: cullvox Date: Mon, 11 Sep 2023 02:05:05 -0400 Subject: [PATCH 019/103] attempt to fix clang-format and ubuntu build. Signed-off-by: cullvox --- .../mod/tasks/LocalResourcePackParseTask.cpp | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index ca1a5455c..c2aef06d5 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -26,6 +26,7 @@ #include #include +#include namespace ResourcePackUtils { @@ -236,7 +237,7 @@ bool getBoolOrFromText(const QJsonValue& val, bool& result) } } -bool readFormat(const QJsonObject& obj, TextFormat& format) +bool readFormat(const QJsonObject& obj, TextFormat& format) { auto text = obj.value("text"); auto color = obj.value("color"); @@ -247,7 +248,7 @@ bool readFormat(const QJsonObject& obj, TextFormat& format) auto extra = obj.value("extra"); if (color.isString()) { - // colors can either be a hex code or one of a few text colors + // colors can either be a hex code or one of a few text colors auto col_str = color.toString(); const QRegularExpression hex_expression("^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$"); @@ -263,13 +264,11 @@ bool readFormat(const QJsonObject& obj, TextFormat& format) } } - return getBoolOrFromText(bold, format.bold) && - getBoolOrFromText(italic, format.italic) && - getBoolOrFromText(underlined, format.underlined) && - getBoolOrFromText(strikethrough, format.strikethrough); + return getBoolOrFromText(bold, format.bold) && getBoolOrFromText(italic, format.italic) && + getBoolOrFromText(underlined, format.underlined) && getBoolOrFromText(strikethrough, format.strikethrough); } -void appendBeginFormat(TextFormat& format, QString& toAppend) +void appendBeginFormat(TextFormat& format, QString& toAppend) { toAppend.append(""); if (format.bold) @@ -282,7 +281,8 @@ void appendBeginFormat(TextFormat& format, QString& toAppend) toAppend.append(""); } -void appendEndFormat(TextFormat& format, QString& toAppend) { +void appendEndFormat(TextFormat& format, QString& toAppend) +{ toAppend.append(""); if (format.bold) toAppend.append(""); @@ -296,7 +296,8 @@ void appendEndFormat(TextFormat& format, QString& toAppend) { bool processComponent(const QJsonValue& value, QString& result, TextFormat* parentFormat = nullptr); -bool processComponentList(const QJsonArray& arr, QString& result, TextFormat* parentFormat = nullptr) { +bool processComponentList(const QJsonArray& arr, QString& result, TextFormat* parentFormat = nullptr) +{ for (const QJsonValue& val : arr) { if (!processComponent(val, result, parentFormat)) @@ -306,7 +307,8 @@ bool processComponentList(const QJsonArray& arr, QString& result, TextFormat* pa return true; } -bool processComponent(const QJsonValue& value, QString& result, TextFormat* parentFormat) { +bool processComponent(const QJsonValue& value, QString& result, TextFormat* parentFormat) +{ if (value.isString()) { result.append(value.toString()); } else if (value.isObject()) { @@ -340,9 +342,8 @@ bool processComponent(const QJsonValue& value, QString& result, TextFormat* pare } // https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta -bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) { - - +bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) +{ try { auto json_doc = QJsonDocument::fromJson(raw_data); auto pack_obj = Json::requireObject(json_doc.object(), "pack", {}); @@ -351,7 +352,7 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) { // description could either be string, or array of dictionaries auto desc_val = pack_obj.value("description"); - + if (desc_val.isString()) { pack.setDescription(desc_val.toString()); } else if (desc_val.isArray()) { From df88ccd4190ad2f605efe96b2b1503bc0ce0d5c8 Mon Sep 17 00:00:00 2001 From: cullvox Date: Mon, 11 Sep 2023 15:49:01 -0400 Subject: [PATCH 020/103] clean up and add review suggestions, links open Signed-off-by: cullvox --- .../mod/tasks/LocalResourcePackParseTask.cpp | 133 ++++++------------ .../mod/tasks/LocalResourcePackParseTask.h | 3 + 2 files changed, 43 insertions(+), 93 deletions(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index c2aef06d5..5b6f6fee0 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -185,87 +185,23 @@ struct TextFormat { bool italic = false; bool underlined = false; bool strikethrough = false; + bool is_linked = false; + QString link_url = ""; }; -bool textColorToHexColor(const QString& text_color, QString& result) -{ - static const QHash text_to_hex = { - { "black", "#000000" }, { "dark_blue", "#0000AA" }, { "dark_green", "#00AA00" }, { "dark_aqua", "#00AAAA" }, - { "dark_red", "#AA0000" }, { "dark_purple", "#AA00AA" }, { "gold", "#FFAA00" }, { "gray", "#AAAAAA" }, - { "dark_gray", "#555555" }, { "blue", "#5555FF" }, { "green", "#55FF55" }, { "aqua", "#55FFFF" }, - { "red", "#FF5555" }, { "light_purple", "#FF55FF" }, { "yellow", "#FFFF55" }, { "white", "#FFFFFF" }, - }; - - auto it = text_to_hex.find(text_color); - - if (it == text_to_hex.end()) { - qWarning() << "Invalid color string in component!"; - result = "#000000"; // return black if color not found - return false; - } - - result = it.value(); - return true; -} - -bool getBoolOrFromText(const QJsonValue& val, bool& result) -{ - if (val.isUndefined()) { - result = false; - return true; - } - - if (val.isBool()) { - result = val.toBool(); - return true; - } else if (val.isString()) { - auto bool_str = val.toString(); - - if (bool_str == "true") { - result = true; - return true; - } else if (bool_str == "false") { - result = false; - return true; - } else { - qWarning() << "Invalid bool value in component!"; - return false; - } - } else { - qWarning() << "Invalid type where bool expected!"; - return false; - } -} - bool readFormat(const QJsonObject& obj, TextFormat& format) { - auto text = obj.value("text"); - auto color = obj.value("color"); - auto bold = obj.value("bold"); - auto italic = obj.value("italic"); - auto underlined = obj.value("underlined"); - auto strikethrough = obj.value("strikethrough"); - auto extra = obj.value("extra"); + format.color = Json::ensureString(obj, "color"); + format.bold = Json::ensureBoolean(obj, "bold", false); + format.italic = Json::ensureBoolean(obj, "italic", false); + format.underlined = Json::ensureBoolean(obj, "underlined", false); + format.strikethrough = Json::ensureBoolean(obj, "strikethrough", false); - if (color.isString()) { - // colors can either be a hex code or one of a few text colors - auto col_str = color.toString(); + auto click_event = Json::ensureObject(obj, "clickEvent"); + format.is_linked = Json::ensureBoolean(click_event, "open_url", false); + format.link_url = Json::ensureString(click_event, "value"); - const QRegularExpression hex_expression("^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$"); - const auto hex_match = hex_expression.match(col_str); - - if (hex_match.hasMatch()) { - format.color = color.toString(); - } else { - if (!textColorToHexColor(color.toString(), format.color)) { - qWarning() << "Invalid color type in component!"; - return false; - } - } - } - - return getBoolOrFromText(bold, format.bold) && getBoolOrFromText(italic, format.italic) && - getBoolOrFromText(underlined, format.underlined) && getBoolOrFromText(strikethrough, format.strikethrough); + return true; } void appendBeginFormat(TextFormat& format, QString& toAppend) @@ -279,35 +215,36 @@ void appendBeginFormat(TextFormat& format, QString& toAppend) toAppend.append(""); if (format.strikethrough) toAppend.append(""); + if (format.is_linked) + toAppend.append(""); } void appendEndFormat(TextFormat& format, QString& toAppend) { - toAppend.append(""); - if (format.bold) - toAppend.append(""); + if (format.is_linked) + toAppend.append(""); + if (format.strikethrough) + toAppend.append(""); if (format.italic) toAppend.append(""); if (format.underlined) toAppend.append(""); - if (format.strikethrough) - toAppend.append(""); + if (format.bold) + toAppend.append(""); + toAppend.append(""); } -bool processComponent(const QJsonValue& value, QString& result, TextFormat* parentFormat = nullptr); - -bool processComponentList(const QJsonArray& arr, QString& result, TextFormat* parentFormat = nullptr) +bool processComponentList(const QJsonArray& arr, QString& result) { - for (const QJsonValue& val : arr) { - if (!processComponent(val, result, parentFormat)) + if (!processComponent(val, result)) return false; } return true; } -bool processComponent(const QJsonValue& value, QString& result, TextFormat* parentFormat) +bool processComponent(const QJsonValue& value, QString& result) { if (value.isString()) { result.append(value.toString()); @@ -315,8 +252,6 @@ bool processComponent(const QJsonValue& value, QString& result, TextFormat* pare auto obj = value.toObject(); TextFormat format{}; - if (parentFormat) - format = *parentFormat; if (!readFormat(obj, format)) return false; @@ -329,7 +264,7 @@ bool processComponent(const QJsonValue& value, QString& result, TextFormat* pare auto extra = obj.value("extra"); if (extra.isArray()) - if (!processComponentList(extra.toArray(), result, &format)) + if (!processComponentList(extra.toArray(), result)) return false; appendEndFormat(format, result); @@ -342,6 +277,7 @@ bool processComponent(const QJsonValue& value, QString& result, TextFormat* pare } // https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta +// https://minecraft.fandom.com/wiki/Raw_JSON_text_format#Plain_Text bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) { try { @@ -350,20 +286,31 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0)); - // description could either be string, or array of dictionaries + // description could be many things according to minecraft auto desc_val = pack_obj.value("description"); + QString desc; if (desc_val.isString()) { - pack.setDescription(desc_val.toString()); + desc = desc_val.toString(); } else if (desc_val.isArray()) { - QString desc; if (!processComponentList(desc_val.toArray(), desc)) return false; - pack.setDescription(desc); + } else if (desc_val.isObject()) { + if (!processComponent(desc_val, desc)) + return false; + } else if (desc_val.isBool()) { + desc = desc_val.toBool() ? "true" : "false"; + } else if (desc_val.isDouble()) { + desc = QString::number(desc_val.toDouble()); } else { + qWarning() << "Invalid description type!"; return false; } + qInfo() << desc; + + pack.setDescription(desc); + } catch (Json::JsonException& e) { qWarning() << "JsonException: " << e.what() << e.cause(); return false; diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h index 5199bf3f0..ed3317643 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h @@ -34,6 +34,9 @@ bool process(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); + +bool processComponent(const QJsonValue& value, QString& result); +bool processComponentList(const QJsonArray& value, QString& result); bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data); bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data); From a4e65305139dc5cdc7ad246b3203175a21262050 Mon Sep 17 00:00:00 2001 From: cullvox Date: Tue, 12 Sep 2023 21:34:42 -0400 Subject: [PATCH 021/103] added tests, fixed issues with overriding/format In the documentation it states that child values can override the parent values. Originally this code did not support that but now it does. Also added in testing inspired by the previous tests. Signed-off-by: cullvox --- .../mod/tasks/LocalResourcePackParseTask.cpp | 217 ++++++++++-------- .../mod/tasks/LocalResourcePackParseTask.h | 4 +- tests/CMakeLists.txt | 3 + tests/MetaComponentParse_test.cpp | 108 +++++++++ .../MetaComponentParse/component_basic.json | 4 + .../component_with_extra.json | 18 ++ .../component_with_format.json | 13 ++ .../component_with_link.json | 12 + .../component_with_mixed.json | 40 ++++ 9 files changed, 321 insertions(+), 98 deletions(-) create mode 100644 tests/MetaComponentParse_test.cpp create mode 100644 tests/testdata/MetaComponentParse/component_basic.json create mode 100644 tests/testdata/MetaComponentParse/component_with_extra.json create mode 100644 tests/testdata/MetaComponentParse/component_with_format.json create mode 100644 tests/testdata/MetaComponentParse/component_with_link.json create mode 100644 tests/testdata/MetaComponentParse/component_with_mixed.json diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index 5b6f6fee0..31cfdca04 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -179,96 +179,138 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level) return true; } -struct TextFormat { - QString color = "#000000"; - bool bold = false; - bool italic = false; - bool underlined = false; - bool strikethrough = false; - bool is_linked = false; - QString link_url = ""; -}; +struct TextFormatter { + // left is value, right is if the value was explicitly written + QPair color = { "#000000", false }; + QPair bold = { false, false }; + QPair italic = { false, false }; + QPair underlined = { false, false }; + QPair strikethrough = { false, false }; + QPair is_linked = { false, false }; + QPair link_url = { "", false }; -bool readFormat(const QJsonObject& obj, TextFormat& format) -{ - format.color = Json::ensureString(obj, "color"); - format.bold = Json::ensureBoolean(obj, "bold", false); - format.italic = Json::ensureBoolean(obj, "italic", false); - format.underlined = Json::ensureBoolean(obj, "underlined", false); - format.strikethrough = Json::ensureBoolean(obj, "strikethrough", false); + void setColor(const QString& new_color, bool written) { color = { new_color, written}; } + void setBold(bool new_bold, bool written) { bold = { new_bold, written}; } + void setItalic(bool new_italic, bool written) { italic = { new_italic, written}; } + void setUnderlined(bool new_underlined, bool written) { underlined = { new_underlined, written}; } + void setStrikethrough(bool new_strikethrough, bool written) { strikethrough = { new_strikethrough, written}; } + void setIsLinked(bool new_is_linked, bool written) { is_linked = { new_is_linked, written}; } + void setLinkURL(const QString& new_url, bool written) { link_url = { new_url, written}; } - auto click_event = Json::ensureObject(obj, "clickEvent"); - format.is_linked = Json::ensureBoolean(click_event, "open_url", false); - format.link_url = Json::ensureString(click_event, "value"); - - return true; -} - -void appendBeginFormat(TextFormat& format, QString& toAppend) -{ - toAppend.append(""); - if (format.bold) - toAppend.append(""); - if (format.italic) - toAppend.append(""); - if (format.underlined) - toAppend.append(""); - if (format.strikethrough) - toAppend.append(""); - if (format.is_linked) - toAppend.append(""); -} - -void appendEndFormat(TextFormat& format, QString& toAppend) -{ - if (format.is_linked) - toAppend.append(""); - if (format.strikethrough) - toAppend.append(""); - if (format.italic) - toAppend.append(""); - if (format.underlined) - toAppend.append(""); - if (format.bold) - toAppend.append(""); - toAppend.append(""); -} - -bool processComponentList(const QJsonArray& arr, QString& result) -{ - for (const QJsonValue& val : arr) { - if (!processComponent(val, result)) - return false; + void overrideFrom(const TextFormatter& child) + { + if (child.color.second) + color.first = child.color.first; + if (child.bold.second) + bold.first = child.bold.first; + if (child.italic.second) + italic.first = child.italic.first; + if (child.underlined.second) + underlined.first = child.underlined.first; + if (child.strikethrough.second) + strikethrough.first = child.strikethrough.first; + if (child.is_linked.second) + is_linked.first = child.is_linked.first; + if (child.link_url.second) + link_url.first = child.link_url.first; } - return true; -} + QString format(QString text) + { + if (text.isEmpty()) + return QString(); -bool processComponent(const QJsonValue& value, QString& result) + QString result; + + if (color.first != "#000000") + result.append(""); + if (bold.first) + result.append(""); + if (italic.first) + result.append(""); + if (underlined.first) + result.append(""); + if (strikethrough.first) + result.append(""); + if (is_linked.first) + result.append(""); + + result.append(text); + + if (is_linked.first) + result.append(""); + if (strikethrough.first) + result.append(""); + if (underlined.first) + result.append(""); + if (italic.first) + result.append(""); + if (bold.first) + result.append(""); + if (color.first != "#000000") + result.append(""); + + return result; + } + + bool readFormat(const QJsonObject& obj) + { + setColor(Json::ensureString(obj, "color", "#000000"), obj.contains("color")); + setBold(Json::ensureBoolean(obj, "bold", false), obj.contains("bold")); + setItalic(Json::ensureBoolean(obj, "italic", false), obj.contains("italic")); + setUnderlined(Json::ensureBoolean(obj, "underlined", false), obj.contains("underlined")); + setStrikethrough(Json::ensureBoolean(obj, "strikethrough", false), obj.contains("strikethrough")); + + auto click_event = Json::ensureObject(obj, "clickEvent"); + setIsLinked(Json::ensureBoolean(click_event, "open_url", false), click_event.contains("open_url")); + setLinkURL(Json::ensureString(click_event, "value"), click_event.contains("value")); + + return true; + } +}; + +bool processComponent(const QJsonValue& value, QString& result, const TextFormatter* parentFormat) { + TextFormatter formatter; + + if (parentFormat) + formatter = *parentFormat; + if (value.isString()) { - result.append(value.toString()); - } else if (value.isObject()) { + result.append(formatter.format(value.toString())); + } else if (value.isBool()) { + result.append(formatter.format(value.toBool() ? "true" : "false")); + } else if (value.isDouble()) { + result.append(formatter.format(QString::number(value.toDouble()))); + } else if (value.isObject()) { auto obj = value.toObject(); - TextFormat format{}; - - if (!readFormat(obj, format)) + if (not formatter.readFormat(obj)) return false; - - appendBeginFormat(format, result); - - auto text = obj.value("text"); - if (text.isString()) - result.append(text.toString()); + // override the parent format with our new one + TextFormatter mixed; + if (parentFormat) + mixed = *parentFormat; + + mixed.overrideFrom(formatter); + + result.append(mixed.format(Json::ensureString(obj, "text"))); + + // process any 'extra' children with this format auto extra = obj.value("extra"); - if (extra.isArray()) - if (!processComponentList(extra.toArray(), result)) - return false; + if (not extra.isUndefined()) + return processComponent(extra, result, &mixed); - appendEndFormat(format, result); - } else { + } else if (value.isArray()) { + auto array = value.toArray(); + + for (const QJsonValue& current : array) { + if (not processComponent(current, result, parentFormat)) { + return false; + } + } + } else { qWarning() << "Invalid component type!"; return false; } @@ -286,29 +328,12 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0)); - // description could be many things according to minecraft auto desc_val = pack_obj.value("description"); - - QString desc; - if (desc_val.isString()) { - desc = desc_val.toString(); - } else if (desc_val.isArray()) { - if (!processComponentList(desc_val.toArray(), desc)) - return false; - } else if (desc_val.isObject()) { - if (!processComponent(desc_val, desc)) - return false; - } else if (desc_val.isBool()) { - desc = desc_val.toBool() ? "true" : "false"; - } else if (desc_val.isDouble()) { - desc = QString::number(desc_val.toDouble()); - } else { - qWarning() << "Invalid description type!"; + QString desc{}; + if (not processComponent(desc_val, desc)) return false; - } qInfo() << desc; - pack.setDescription(desc); } catch (Json::JsonException& e) { diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h index ed3317643..7e893979a 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h @@ -35,8 +35,8 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Ful bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); -bool processComponent(const QJsonValue& value, QString& result); -bool processComponentList(const QJsonArray& value, QString& result); +struct TextFormatter; +bool processComponent(const QJsonValue& value, QString& result, const TextFormatter* parentFormat = nullptr); bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data); bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a26a49fec..8690c078e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -56,3 +56,6 @@ ecm_add_test(Index_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}: ecm_add_test(Version_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME Version) + +ecm_add_test(MetaComponentParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test + TEST_NAME MetaComponentParse) \ No newline at end of file diff --git a/tests/MetaComponentParse_test.cpp b/tests/MetaComponentParse_test.cpp new file mode 100644 index 000000000..77b49b478 --- /dev/null +++ b/tests/MetaComponentParse_test.cpp @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include + +#include + +class MetaComponentParseTest : public QObject { + Q_OBJECT + + void doTest(QString name) + { + QString source = QFINDTESTDATA("testdata/MetaComponentParse"); + + QString comp_rp = FS::PathCombine(source, name); + + QFile file; + file.setFileName(comp_rp); + QVERIFY(file.open(QIODevice::ReadOnly | QIODevice::Text)); + QString data = file.readAll(); + file.close(); + + QJsonDocument doc = QJsonDocument::fromJson(data.toUtf8()); + QJsonObject obj = doc.object(); + + QJsonValue description_json = obj.value("description"); + QJsonValue expected_json = obj.value("expected_output"); + + QVERIFY(description_json.isUndefined() == false); + QVERIFY(expected_json.isString() == true); + + QString expected = expected_json.toString(); + + QString processed; + bool valid = ResourcePackUtils::processComponent(description_json, processed); + + QVERIFY(processed == expected); + QVERIFY(valid == true); + } + +private slots: + void test_parseComponentBasic() + { + doTest("component_basic.json"); + } + + void test_parseComponentWithFormat() + { + doTest("component_with_format.json"); + } + + void test_parseComponentWithExtra() + { + doTest("component_with_extra.json"); + } + + void test_parseComponentWithLink() + { + doTest("component_with_link.json"); + } + + void test_parseComponentWithMixed() + { + doTest("component_with_mixed.json"); + } +}; + +QTEST_GUILESS_MAIN(MetaComponentParseTest) + +#include "MetaComponentParse_test.moc" diff --git a/tests/testdata/MetaComponentParse/component_basic.json b/tests/testdata/MetaComponentParse/component_basic.json new file mode 100644 index 000000000..5cfdeeeab --- /dev/null +++ b/tests/testdata/MetaComponentParse/component_basic.json @@ -0,0 +1,4 @@ +{ + "description": [{"text": "Hello, Component!"}], + "expected_output": "Hello, Component!" +} diff --git a/tests/testdata/MetaComponentParse/component_with_extra.json b/tests/testdata/MetaComponentParse/component_with_extra.json new file mode 100644 index 000000000..49397556d --- /dev/null +++ b/tests/testdata/MetaComponentParse/component_with_extra.json @@ -0,0 +1,18 @@ +{ + "description": [ + { + "text": "Hello, ", + "color": "red", + "bold": true, + "italic": true, + "extra": [ + { + "extra": "Component!", + "bold": false, + "italic": false + } + ] + } + ], + "expected_output": "Hello, Component!" +} \ No newline at end of file diff --git a/tests/testdata/MetaComponentParse/component_with_format.json b/tests/testdata/MetaComponentParse/component_with_format.json new file mode 100644 index 000000000..000de20cb --- /dev/null +++ b/tests/testdata/MetaComponentParse/component_with_format.json @@ -0,0 +1,13 @@ +{ + "description": [ + { + "text": "Hello, Component!", + "color": "blue", + "bold": true, + "italic": true, + "underlined": true, + "strikethrough": true + } + ], + "expected_output": "Hello, Component!" +} \ No newline at end of file diff --git a/tests/testdata/MetaComponentParse/component_with_link.json b/tests/testdata/MetaComponentParse/component_with_link.json new file mode 100644 index 000000000..e190cc5e8 --- /dev/null +++ b/tests/testdata/MetaComponentParse/component_with_link.json @@ -0,0 +1,12 @@ +{ + "description": [ + { + "text": "Hello, Component!", + "clickEvent": { + "open_url": true, + "value": "https://google.com" + } + } + ], + "expected_output": "Hello, Component!" +} diff --git a/tests/testdata/MetaComponentParse/component_with_mixed.json b/tests/testdata/MetaComponentParse/component_with_mixed.json new file mode 100644 index 000000000..7e65169af --- /dev/null +++ b/tests/testdata/MetaComponentParse/component_with_mixed.json @@ -0,0 +1,40 @@ +{ + "description": [ + { + "text": "The quick ", + "color": "blue", + "italic": true + }, + { + "text": "brown fox ", + "color": "#873600", + "bold": true, + "underlined": true, + "extra": { + "text": "jumped over ", + "color": "blue", + "bold": false, + "underlined": false, + "italic": true, + "strikethrough": true + } + }, + { + "text": "the lazy dog's back. ", + "color": "green", + "bold": true, + "italic": true, + "underlined": true, + "strikethrough": true, + "extra": [ + { + "text": "1234567890 ", + "color": "black", + "strikethrough": false, + "extra": "How vexingly quick daft zebras jump!" + } + ] + } + ], + "expected_output": "The quick brown fox jumped over the lazy dog's back. 1234567890 How vexingly quick daft zebras jump!" +} From ddf0c28b1b0b6617b0308405b4d5eefd20ff48b2 Mon Sep 17 00:00:00 2001 From: cullvox Date: Tue, 12 Sep 2023 21:45:29 -0400 Subject: [PATCH 022/103] clang-format fixes --- .../mod/tasks/LocalResourcePackParseTask.cpp | 22 ++++++------ .../mod/tasks/LocalResourcePackParseTask.h | 1 - tests/MetaComponentParse_test.cpp | 35 +++++-------------- 3 files changed, 19 insertions(+), 39 deletions(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index 31cfdca04..8e209a416 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -189,18 +189,18 @@ struct TextFormatter { QPair is_linked = { false, false }; QPair link_url = { "", false }; - void setColor(const QString& new_color, bool written) { color = { new_color, written}; } - void setBold(bool new_bold, bool written) { bold = { new_bold, written}; } - void setItalic(bool new_italic, bool written) { italic = { new_italic, written}; } - void setUnderlined(bool new_underlined, bool written) { underlined = { new_underlined, written}; } - void setStrikethrough(bool new_strikethrough, bool written) { strikethrough = { new_strikethrough, written}; } - void setIsLinked(bool new_is_linked, bool written) { is_linked = { new_is_linked, written}; } - void setLinkURL(const QString& new_url, bool written) { link_url = { new_url, written}; } + void setColor(const QString& new_color, bool written) { color = { new_color, written }; } + void setBold(bool new_bold, bool written) { bold = { new_bold, written }; } + void setItalic(bool new_italic, bool written) { italic = { new_italic, written }; } + void setUnderlined(bool new_underlined, bool written) { underlined = { new_underlined, written }; } + void setStrikethrough(bool new_strikethrough, bool written) { strikethrough = { new_strikethrough, written }; } + void setIsLinked(bool new_is_linked, bool written) { is_linked = { new_is_linked, written }; } + void setLinkURL(const QString& new_url, bool written) { link_url = { new_url, written }; } void overrideFrom(const TextFormatter& child) { if (child.color.second) - color.first = child.color.first; + color.first = child.color.first; if (child.bold.second) bold.first = child.bold.first; if (child.italic.second) @@ -282,7 +282,7 @@ bool processComponent(const QJsonValue& value, QString& result, const TextFormat result.append(formatter.format(value.toBool() ? "true" : "false")); } else if (value.isDouble()) { result.append(formatter.format(QString::number(value.toDouble()))); - } else if (value.isObject()) { + } else if (value.isObject()) { auto obj = value.toObject(); if (not formatter.readFormat(obj)) @@ -294,7 +294,7 @@ bool processComponent(const QJsonValue& value, QString& result, const TextFormat mixed = *parentFormat; mixed.overrideFrom(formatter); - + result.append(mixed.format(Json::ensureString(obj, "text"))); // process any 'extra' children with this format @@ -310,7 +310,7 @@ bool processComponent(const QJsonValue& value, QString& result, const TextFormat return false; } } - } else { + } else { qWarning() << "Invalid component type!"; return false; } diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h index 7e893979a..945499a91 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h @@ -34,7 +34,6 @@ bool process(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); - struct TextFormatter; bool processComponent(const QJsonValue& value, QString& result, const TextFormatter* parentFormat = nullptr); bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data); diff --git a/tests/MetaComponentParse_test.cpp b/tests/MetaComponentParse_test.cpp index 77b49b478..1b1e2ce3e 100644 --- a/tests/MetaComponentParse_test.cpp +++ b/tests/MetaComponentParse_test.cpp @@ -33,11 +33,11 @@ * limitations under the License. */ -#include -#include #include #include #include +#include +#include #include @@ -76,31 +76,12 @@ class MetaComponentParseTest : public QObject { QVERIFY(valid == true); } -private slots: - void test_parseComponentBasic() - { - doTest("component_basic.json"); - } - - void test_parseComponentWithFormat() - { - doTest("component_with_format.json"); - } - - void test_parseComponentWithExtra() - { - doTest("component_with_extra.json"); - } - - void test_parseComponentWithLink() - { - doTest("component_with_link.json"); - } - - void test_parseComponentWithMixed() - { - doTest("component_with_mixed.json"); - } + private slots: + void test_parseComponentBasic() { doTest("component_basic.json"); } + void test_parseComponentWithFormat() { doTest("component_with_format.json"); } + void test_parseComponentWithExtra() { doTest("component_with_extra.json"); } + void test_parseComponentWithLink() { doTest("component_with_link.json"); } + void test_parseComponentWithMixed() { doTest("component_with_mixed.json"); } }; QTEST_GUILESS_MAIN(MetaComponentParseTest) From e1dda6c005dab379a5b93a2a9279205895b71060 Mon Sep 17 00:00:00 2001 From: cullvox Date: Tue, 12 Sep 2023 21:50:33 -0400 Subject: [PATCH 023/103] DCO Remediation Commit for cullvox I, cullvox , hereby add my Signed-off-by to this commit: ddf0c28b1b0b6617b0308405b4d5eefd20ff48b2 Signed-off-by: cullvox --- launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index 8e209a416..5e53244ff 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -333,7 +333,6 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) if (not processComponent(desc_val, desc)) return false; - qInfo() << desc; pack.setDescription(desc); } catch (Json::JsonException& e) { From ee7016fa542e3ca92bdd691bd2e168d9d43a02c2 Mon Sep 17 00:00:00 2001 From: cullvox Date: Wed, 13 Sep 2023 14:28:56 -0400 Subject: [PATCH 024/103] use clang-format Signed-off-by: cullvox Hello, Component!" + ], + "expected_output": "Hello, Component!" } \ No newline at end of file diff --git a/tests/testdata/MetaComponentParse/component_with_format.json b/tests/testdata/MetaComponentParse/component_with_format.json index 000de20cb..00dfc7daf 100644 --- a/tests/testdata/MetaComponentParse/component_with_format.json +++ b/tests/testdata/MetaComponentParse/component_with_format.json @@ -1,13 +1,13 @@ { - "description": [ - { - "text": "Hello, Component!", - "color": "blue", - "bold": true, - "italic": true, - "underlined": true, - "strikethrough": true - } - ], - "expected_output": "Hello, Component!" + "description": [ + { + "text": "Hello, Component!", + "color": "blue", + "bold": true, + "italic": true, + "underlined": true, + "strikethrough": true + } + ], + "expected_output": "Hello, Component!" } \ No newline at end of file diff --git a/tests/testdata/MetaComponentParse/component_with_link.json b/tests/testdata/MetaComponentParse/component_with_link.json index e190cc5e8..b1e34c7d6 100644 --- a/tests/testdata/MetaComponentParse/component_with_link.json +++ b/tests/testdata/MetaComponentParse/component_with_link.json @@ -1,12 +1,12 @@ { - "description": [ - { - "text": "Hello, Component!", - "clickEvent": { - "open_url": true, - "value": "https://google.com" - } - } - ], - "expected_output": "Hello, Component!" + "description": [ + { + "text": "Hello, Component!", + "clickEvent": { + "open_url": true, + "value": "https://google.com" + } + } + ], + "expected_output": "Hello, Component!" } diff --git a/tests/testdata/MetaComponentParse/component_with_mixed.json b/tests/testdata/MetaComponentParse/component_with_mixed.json index 7e65169af..7c8c5b032 100644 --- a/tests/testdata/MetaComponentParse/component_with_mixed.json +++ b/tests/testdata/MetaComponentParse/component_with_mixed.json @@ -1,40 +1,41 @@ { - "description": [ - { - "text": "The quick ", - "color": "blue", - "italic": true - }, - { - "text": "brown fox ", - "color": "#873600", - "bold": true, - "underlined": true, - "extra": { - "text": "jumped over ", - "color": "blue", - "bold": false, - "underlined": false, - "italic": true, - "strikethrough": true - } - }, - { - "text": "the lazy dog's back. ", - "color": "green", - "bold": true, - "italic": true, - "underlined": true, - "strikethrough": true, - "extra": [ + "description": [ { - "text": "1234567890 ", - "color": "black", - "strikethrough": false, - "extra": "How vexingly quick daft zebras jump!" + "text": "The quick ", + "color": "blue", + "italic": true + }, + { + "text": "brown fox ", + "color": "#873600", + "bold": true, + "underlined": true, + "extra": { + "text": "jumped over ", + "color": "blue", + "bold": false, + "underlined": false, + "italic": true, + "strikethrough": true + } + }, + { + "text": "the lazy dog's back. ", + "color": "green", + "bold": true, + "italic": true, + "underlined": true, + "strikethrough": true, + "extra": [ + { + "text": "1234567890 ", + "color": "black", + "strikethrough": false, + "extra": "How vexingly quick daft zebras jump!" + } + ] } - ] - } - ], - "expected_output": "The quick brown fox jumped over the lazy dog's back. 1234567890 How vexingly quick daft zebras jump!" + ], + "expected_output": + "The quick brown fox jumped over the lazy dog's back. 1234567890 How vexingly quick daft zebras jump!" } From e96d0b6cafb0a0c17e7f5baef1bfbafe4a7fcb07 Mon Sep 17 00:00:00 2001 From: cullvox Date: Wed, 13 Sep 2023 14:31:04 -0400 Subject: [PATCH 025/103] DCO Remediation Commit for cullvox I, cullvox , hereby add my Signed-off-by to this commit: ee7016fa542e3ca92bdd691bd2e168d9d43a02c2 Signed-off-by: cullvox --- launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index 5e53244ff..fd28aa0b7 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -272,7 +272,6 @@ struct TextFormatter { bool processComponent(const QJsonValue& value, QString& result, const TextFormatter* parentFormat) { TextFormatter formatter; - if (parentFormat) formatter = *parentFormat; From c81689d39322d5ea5c7ed0073d73e4d8d2446b33 Mon Sep 17 00:00:00 2001 From: cullvox Date: Fri, 15 Sep 2023 20:41:21 -0400 Subject: [PATCH 026/103] fixes html elide issue with InfoFrame --- launcher/ui/widgets/InfoFrame.cpp | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp index 1f03f9eaf..0162b4000 100644 --- a/launcher/ui/widgets/InfoFrame.cpp +++ b/launcher/ui/widgets/InfoFrame.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include "InfoFrame.h" #include "ui_InfoFrame.h" @@ -274,12 +275,31 @@ void InfoFrame::setDescription(QString text) } QString labeltext; labeltext.reserve(300); - if (finaltext.length() > 290) { + + + // elide rich text by getting characters without formatting + const int maxCharacterElide = 290; + QTextDocument doc; + doc.setHtml(text); + + if (doc.characterCount() > maxCharacterElide) { + ui->descriptionLabel->setOpenExternalLinks(false); - ui->descriptionLabel->setTextFormat(Qt::TextFormat::RichText); + ui->descriptionLabel->setTextFormat(Qt::TextFormat::RichText); // This allows injecting HTML here. m_description = text; - // This allows injecting HTML here. - labeltext.append("" + finaltext.left(287) + "..."); + + const QString elidedPostfix = "..."; + + // move the cursor to the character elide, doesn't see html + QTextCursor cursor(&doc); + cursor.movePosition(QTextCursor::End); + cursor.setPosition(maxCharacterElide, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + + // insert the post fix at the cursor + cursor.insertHtml(elidedPostfix); + + labeltext.append(doc.toHtml()); QObject::connect(ui->descriptionLabel, &QLabel::linkActivated, this, &InfoFrame::descriptionEllipsisHandler); } else { ui->descriptionLabel->setTextFormat(Qt::TextFormat::AutoText); From 4053229544ca2d80cd569f4d67365f81ed64353d Mon Sep 17 00:00:00 2001 From: cullvox Date: Fri, 15 Sep 2023 20:55:34 -0400 Subject: [PATCH 027/103] DCO Remediation Commit for cullvox I, cullvox , hereby add my Signed-off-by to this commit: c81689d39322d5ea5c7ed0073d73e4d8d2446b33 Signed-off-by: cullvox --- launcher/ui/widgets/InfoFrame.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp index 0162b4000..6423e88d6 100644 --- a/launcher/ui/widgets/InfoFrame.cpp +++ b/launcher/ui/widgets/InfoFrame.cpp @@ -36,8 +36,9 @@ #include #include -#include #include +#include +#include #include "InfoFrame.h" #include "ui_InfoFrame.h" @@ -276,16 +277,14 @@ void InfoFrame::setDescription(QString text) QString labeltext; labeltext.reserve(300); - // elide rich text by getting characters without formatting const int maxCharacterElide = 290; QTextDocument doc; doc.setHtml(text); if (doc.characterCount() > maxCharacterElide) { - ui->descriptionLabel->setOpenExternalLinks(false); - ui->descriptionLabel->setTextFormat(Qt::TextFormat::RichText); // This allows injecting HTML here. + ui->descriptionLabel->setTextFormat(Qt::TextFormat::RichText); // This allows injecting HTML here. m_description = text; const QString elidedPostfix = "..."; @@ -298,7 +297,7 @@ void InfoFrame::setDescription(QString text) // insert the post fix at the cursor cursor.insertHtml(elidedPostfix); - + labeltext.append(doc.toHtml()); QObject::connect(ui->descriptionLabel, &QLabel::linkActivated, this, &InfoFrame::descriptionEllipsisHandler); } else { From 01e98a6ce810ed1e8169d2252dca5cec39f8d335 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 16 Sep 2023 10:20:24 +0300 Subject: [PATCH 028/103] simplify the raw json parsing Signed-off-by: Trial97 Fixed Tests Signed-off-by: Trial97 --- .../mod/tasks/LocalResourcePackParseTask.cpp | 214 +++++++----------- .../mod/tasks/LocalResourcePackParseTask.h | 3 +- launcher/ui/widgets/InfoFrame.cpp | 6 +- tests/MetaComponentParse_test.cpp | 10 +- .../component_with_extra.json | 7 +- .../component_with_format.json | 2 +- .../component_with_link.json | 2 +- .../component_with_mixed.json | 24 +- 8 files changed, 104 insertions(+), 164 deletions(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index fd28aa0b7..d9d26b9c2 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -179,142 +179,85 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level) return true; } -struct TextFormatter { - // left is value, right is if the value was explicitly written - QPair color = { "#000000", false }; - QPair bold = { false, false }; - QPair italic = { false, false }; - QPair underlined = { false, false }; - QPair strikethrough = { false, false }; - QPair is_linked = { false, false }; - QPair link_url = { "", false }; - - void setColor(const QString& new_color, bool written) { color = { new_color, written }; } - void setBold(bool new_bold, bool written) { bold = { new_bold, written }; } - void setItalic(bool new_italic, bool written) { italic = { new_italic, written }; } - void setUnderlined(bool new_underlined, bool written) { underlined = { new_underlined, written }; } - void setStrikethrough(bool new_strikethrough, bool written) { strikethrough = { new_strikethrough, written }; } - void setIsLinked(bool new_is_linked, bool written) { is_linked = { new_is_linked, written }; } - void setLinkURL(const QString& new_url, bool written) { link_url = { new_url, written }; } - - void overrideFrom(const TextFormatter& child) - { - if (child.color.second) - color.first = child.color.first; - if (child.bold.second) - bold.first = child.bold.first; - if (child.italic.second) - italic.first = child.italic.first; - if (child.underlined.second) - underlined.first = child.underlined.first; - if (child.strikethrough.second) - strikethrough.first = child.strikethrough.first; - if (child.is_linked.second) - is_linked.first = child.is_linked.first; - if (child.link_url.second) - link_url.first = child.link_url.first; - } - - QString format(QString text) - { - if (text.isEmpty()) - return QString(); - - QString result; - - if (color.first != "#000000") - result.append(""); - if (bold.first) - result.append(""); - if (italic.first) - result.append(""); - if (underlined.first) - result.append(""); - if (strikethrough.first) - result.append(""); - if (is_linked.first) - result.append(""); - - result.append(text); - - if (is_linked.first) - result.append(""); - if (strikethrough.first) - result.append(""); - if (underlined.first) - result.append(""); - if (italic.first) - result.append(""); - if (bold.first) - result.append(""); - if (color.first != "#000000") - result.append(""); - - return result; - } - - bool readFormat(const QJsonObject& obj) - { - setColor(Json::ensureString(obj, "color", "#000000"), obj.contains("color")); - setBold(Json::ensureBoolean(obj, "bold", false), obj.contains("bold")); - setItalic(Json::ensureBoolean(obj, "italic", false), obj.contains("italic")); - setUnderlined(Json::ensureBoolean(obj, "underlined", false), obj.contains("underlined")); - setStrikethrough(Json::ensureBoolean(obj, "strikethrough", false), obj.contains("strikethrough")); - - auto click_event = Json::ensureObject(obj, "clickEvent"); - setIsLinked(Json::ensureBoolean(click_event, "open_url", false), click_event.contains("open_url")); - setLinkURL(Json::ensureString(click_event, "value"), click_event.contains("value")); - - return true; - } -}; - -bool processComponent(const QJsonValue& value, QString& result, const TextFormatter* parentFormat) +QString buildStyle(const QJsonObject& obj) { - TextFormatter formatter; - if (parentFormat) - formatter = *parentFormat; - - if (value.isString()) { - result.append(formatter.format(value.toString())); - } else if (value.isBool()) { - result.append(formatter.format(value.toBool() ? "true" : "false")); - } else if (value.isDouble()) { - result.append(formatter.format(QString::number(value.toDouble()))); - } else if (value.isObject()) { - auto obj = value.toObject(); - - if (not formatter.readFormat(obj)) - return false; - - // override the parent format with our new one - TextFormatter mixed; - if (parentFormat) - mixed = *parentFormat; - - mixed.overrideFrom(formatter); - - result.append(mixed.format(Json::ensureString(obj, "text"))); - - // process any 'extra' children with this format - auto extra = obj.value("extra"); - if (not extra.isUndefined()) - return processComponent(extra, result, &mixed); - - } else if (value.isArray()) { - auto array = value.toArray(); - - for (const QJsonValue& current : array) { - if (not processComponent(current, result, parentFormat)) { - return false; - } + QStringList styles; + if (auto color = Json::ensureString(obj, "color"); !color.isEmpty()) { + styles << QString("color: %1;").arg(color); + } + if (obj.contains("bold")) { + QString weight = "normal"; + if (Json::ensureBoolean(obj, "bold", false)) { + weight = "bold"; } - } else { - qWarning() << "Invalid component type!"; - return false; + styles << QString("font-weight: %1;").arg(weight); + } + if (obj.contains("italic")) { + QString style = "normal"; + if (Json::ensureBoolean(obj, "italic", false)) { + style = "italic"; + } + styles << QString("font-style: %1;").arg(style); } - return true; + return styles.isEmpty() ? "" : QString("style=\"%1\"").arg(styles.join(" ")); +} + +QString processComponent(const QJsonArray& value, bool strikethrough, bool underline) +{ + QString result; + for (auto current : value) + result += processComponent(current, strikethrough, underline); + return result; +} + +QString processComponent(const QJsonObject& obj, bool strikethrough, bool underline) +{ + underline = Json::ensureBoolean(obj, "underlined", underline); + strikethrough = Json::ensureBoolean(obj, "strikethrough", strikethrough); + + QString result = Json::ensureString(obj, "text"); + if (underline) { + result = QString("%1").arg(result); + } + if (strikethrough) { + result = QString("%1").arg(result); + } + // the extra needs to be a array + result += processComponent(Json::ensureArray(obj, "extra"), strikethrough, underline); + if (auto style = buildStyle(obj); !style.isEmpty()) { + result = QString("%2").arg(style, result); + } + if (obj.contains("clickEvent")) { + auto click_event = Json::ensureObject(obj, "clickEvent"); + auto action = Json::ensureString(click_event, "action"); + auto value = Json::ensureString(click_event, "value"); + if (action == "open_url" && !value.isEmpty()) { + result = QString("%2").arg(value, result); + } + } + return result; +} + +QString processComponent(const QJsonValue& value, bool strikethrough, bool underline) +{ + if (value.isString()) { + return value.toString(); + } + if (value.isBool()) { + return value.toBool() ? "true" : "false"; + } + if (value.isDouble()) { + return QString::number(value.toDouble()); + } + if (value.isArray()) { + return processComponent(value.toArray(), strikethrough, underline); + } + if (value.isObject()) { + return processComponent(value.toObject(), strikethrough, underline); + } + qWarning() << "Invalid component type!"; + return {}; } // https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta @@ -327,12 +270,7 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data) pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0)); - auto desc_val = pack_obj.value("description"); - QString desc{}; - if (not processComponent(desc_val, desc)) - return false; - - pack.setDescription(desc); + pack.setDescription(processComponent(pack_obj.value("description"))); } catch (Json::JsonException& e) { qWarning() << "JsonException: " << e.what() << e.cause(); diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h index 945499a91..97bf7b2ba 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h @@ -34,8 +34,7 @@ bool process(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); -struct TextFormatter; -bool processComponent(const QJsonValue& value, QString& result, const TextFormatter* parentFormat = nullptr); +QString processComponent(const QJsonValue& value, bool strikethrough = false, bool underline = false); bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data); bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data); diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp index 6423e88d6..2cf3e7a93 100644 --- a/launcher/ui/widgets/InfoFrame.cpp +++ b/launcher/ui/widgets/InfoFrame.cpp @@ -287,8 +287,6 @@ void InfoFrame::setDescription(QString text) ui->descriptionLabel->setTextFormat(Qt::TextFormat::RichText); // This allows injecting HTML here. m_description = text; - const QString elidedPostfix = "..."; - // move the cursor to the character elide, doesn't see html QTextCursor cursor(&doc); cursor.movePosition(QTextCursor::End); @@ -296,7 +294,7 @@ void InfoFrame::setDescription(QString text) cursor.removeSelectedText(); // insert the post fix at the cursor - cursor.insertHtml(elidedPostfix); + cursor.insertHtml("..."); labeltext.append(doc.toHtml()); QObject::connect(ui->descriptionLabel, &QLabel::linkActivated, this, &InfoFrame::descriptionEllipsisHandler); @@ -335,7 +333,7 @@ void InfoFrame::setLicense(QString text) if (finaltext.length() > 290) { ui->licenseLabel->setOpenExternalLinks(false); ui->licenseLabel->setTextFormat(Qt::TextFormat::RichText); - m_description = text; + m_license = text; // This allows injecting HTML here. labeltext.append("" + finaltext.left(287) + "..."); QObject::connect(ui->licenseLabel, &QLabel::linkActivated, this, &InfoFrame::licenseEllipsisHandler); diff --git a/tests/MetaComponentParse_test.cpp b/tests/MetaComponentParse_test.cpp index 1b1e2ce3e..9979a9fa6 100644 --- a/tests/MetaComponentParse_test.cpp +++ b/tests/MetaComponentParse_test.cpp @@ -64,16 +64,14 @@ class MetaComponentParseTest : public QObject { QJsonValue description_json = obj.value("description"); QJsonValue expected_json = obj.value("expected_output"); - QVERIFY(description_json.isUndefined() == false); - QVERIFY(expected_json.isString() == true); + QVERIFY(!description_json.isUndefined()); + QVERIFY(expected_json.isString()); QString expected = expected_json.toString(); - QString processed; - bool valid = ResourcePackUtils::processComponent(description_json, processed); + QString processed = ResourcePackUtils::processComponent(description_json); - QVERIFY(processed == expected); - QVERIFY(valid == true); + QCOMPARE(processed, expected); } private slots: diff --git a/tests/testdata/MetaComponentParse/component_with_extra.json b/tests/testdata/MetaComponentParse/component_with_extra.json index e26b2abff..887becdbe 100644 --- a/tests/testdata/MetaComponentParse/component_with_extra.json +++ b/tests/testdata/MetaComponentParse/component_with_extra.json @@ -7,12 +7,15 @@ "italic": true, "extra": [ { - "extra": "Component!", + "extra": [ + "Component!" + ], "bold": false, "italic": false } ] } ], - "expected_output": "Hello, Component!" + "expected_output": + "Hello, Component!" } \ No newline at end of file diff --git a/tests/testdata/MetaComponentParse/component_with_format.json b/tests/testdata/MetaComponentParse/component_with_format.json index 00dfc7daf..1078886a6 100644 --- a/tests/testdata/MetaComponentParse/component_with_format.json +++ b/tests/testdata/MetaComponentParse/component_with_format.json @@ -9,5 +9,5 @@ "strikethrough": true } ], - "expected_output": "Hello, Component!" + "expected_output": "Hello, Component!" } \ No newline at end of file diff --git a/tests/testdata/MetaComponentParse/component_with_link.json b/tests/testdata/MetaComponentParse/component_with_link.json index b1e34c7d6..188c004cd 100644 --- a/tests/testdata/MetaComponentParse/component_with_link.json +++ b/tests/testdata/MetaComponentParse/component_with_link.json @@ -3,7 +3,7 @@ { "text": "Hello, Component!", "clickEvent": { - "open_url": true, + "action": "open_url", "value": "https://google.com" } } diff --git a/tests/testdata/MetaComponentParse/component_with_mixed.json b/tests/testdata/MetaComponentParse/component_with_mixed.json index 7c8c5b032..661fc1a3e 100644 --- a/tests/testdata/MetaComponentParse/component_with_mixed.json +++ b/tests/testdata/MetaComponentParse/component_with_mixed.json @@ -10,14 +10,16 @@ "color": "#873600", "bold": true, "underlined": true, - "extra": { - "text": "jumped over ", - "color": "blue", - "bold": false, - "underlined": false, - "italic": true, - "strikethrough": true - } + "extra": [ + { + "text": "jumped over ", + "color": "blue", + "bold": false, + "underlined": false, + "italic": true, + "strikethrough": true + } + ] }, { "text": "the lazy dog's back. ", @@ -31,11 +33,13 @@ "text": "1234567890 ", "color": "black", "strikethrough": false, - "extra": "How vexingly quick daft zebras jump!" + "extra": [ + "How vexingly quick daft zebras jump!" + ] } ] } ], "expected_output": - "The quick brown fox jumped over the lazy dog's back. 1234567890 How vexingly quick daft zebras jump!" + "The quick brown fox jumped over the lazy dog's back. 1234567890 How vexingly quick daft zebras jump!" } From 0e41ceffc4a2b8842e44a8bd1dc027944ce27471 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 16 Sep 2023 19:18:58 +0300 Subject: [PATCH 029/103] removed missed header Signed-off-by: Trial97 --- launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index d9d26b9c2..c5d899123 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -26,7 +26,6 @@ #include #include -#include namespace ResourcePackUtils { From 6a19f2dae82ecda7790e8fcf6cd8cf16f3bcdaf0 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 24 Oct 2023 11:19:19 +0300 Subject: [PATCH 030/103] fixed icon import Signed-off-by: Trial97 --- launcher/InstanceImportTask.cpp | 4 +++- launcher/icons/IconUtils.cpp | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 713787902..21ad834b4 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -334,13 +334,15 @@ void InstanceImportTask::processMultiMC() m_instIcon = instance.iconKey(); auto importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), m_instIcon); + if (importIconPath.isNull() || !QFile::exists(importIconPath)) + importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), "icon.png"); if (!importIconPath.isNull() && QFile::exists(importIconPath)) { // import icon auto iconList = APPLICATION->icons(); if (iconList->iconFileExists(m_instIcon)) { iconList->deleteIcon(m_instIcon); } - iconList->installIcons({ importIconPath }); + iconList->installIcon(importIconPath, m_instIcon); } } emitSucceeded(); diff --git a/launcher/icons/IconUtils.cpp b/launcher/icons/IconUtils.cpp index 99c38f47a..6825dd6da 100644 --- a/launcher/icons/IconUtils.cpp +++ b/launcher/icons/IconUtils.cpp @@ -52,8 +52,7 @@ QString findBestIconIn(const QString& folder, const QString& iconKey) while (it.hasNext()) { it.next(); auto fileInfo = it.fileInfo(); - - if (fileInfo.completeBaseName() == iconKey && isIconSuffix(fileInfo.suffix())) + if ((fileInfo.completeBaseName() == iconKey || fileInfo.fileName() == iconKey) && isIconSuffix(fileInfo.suffix())) return fileInfo.absoluteFilePath(); } return {}; From 263dc5af67b21719a690c3577c3e9d028fc1106a Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 25 Oct 2023 19:56:26 +0300 Subject: [PATCH 031/103] Fixed remane and delete of selected skin Signed-off-by: Trial97 --- launcher/minecraft/skins/SkinList.cpp | 2 +- launcher/ui/dialogs/skins/SkinManageDialog.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/launcher/minecraft/skins/SkinList.cpp b/launcher/minecraft/skins/SkinList.cpp index b3a593454..1b046a781 100644 --- a/launcher/minecraft/skins/SkinList.cpp +++ b/launcher/minecraft/skins/SkinList.cpp @@ -359,7 +359,7 @@ bool SkinList::setData(const QModelIndex& idx, const QVariant& value, int role) int row = idx.row(); if (row < 0 || row >= m_skin_list.size()) return false; - auto skin = m_skin_list[row]; + auto& skin = m_skin_list[row]; auto newName = value.toString(); if (skin.name() != newName) { skin.rename(newName); diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index 24197baeb..5419d3eed 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -229,8 +229,10 @@ void SkinManageDialog::on_steveBtn_toggled(bool checked) void SkinManageDialog::accept() { auto skin = m_list.skin(m_selected_skin); - if (!skin) + if (!skin) { reject(); + return; + } auto path = skin->getPath(); ProgressDialog prog(this); @@ -315,7 +317,7 @@ void SkinManageDialog::on_action_Delete_Skin_triggered(bool checked) return; if (m_list.getSkinIndex(m_selected_skin) == m_list.getSelectedAccountSkin()) { - CustomMessageBox::selectable(this, tr("Delete error"), tr("Can not delete skin that is in use."), QMessageBox::Warning); + CustomMessageBox::selectable(this, tr("Delete error"), tr("Can not delete skin that is in use."), QMessageBox::Warning)->exec(); return; } From 44cdf3f6979969c2de7953b58fd08836049fe5d5 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 26 Oct 2023 13:33:00 +0300 Subject: [PATCH 032/103] Fixed skin variant Signed-off-by: Trial97 --- launcher/minecraft/auth/Parsers.cpp | 2 +- launcher/minecraft/skins/SkinList.cpp | 4 ++-- launcher/ui/dialogs/skins/SkinManageDialog.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/launcher/minecraft/auth/Parsers.cpp b/launcher/minecraft/auth/Parsers.cpp index f6179a93e..a2b002c78 100644 --- a/launcher/minecraft/auth/Parsers.cpp +++ b/launcher/minecraft/auth/Parsers.cpp @@ -347,7 +347,7 @@ bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output) Skin skinOut; // fill in default skin info ourselves, as this endpoint doesn't provide it bool steve = isDefaultModelSteve(output.id); - skinOut.variant = steve ? "classic" : "slim"; + skinOut.variant = steve ? "CLASSIC" : "SLIM"; skinOut.url = steve ? SKIN_URL_STEVE : SKIN_URL_ALEX; // sadly we can't figure this out, but I don't think it really matters... skinOut.id = "00000000-0000-0000-0000-000000000000"; diff --git a/launcher/minecraft/skins/SkinList.cpp b/launcher/minecraft/skins/SkinList.cpp index 1b046a781..0505e4ced 100644 --- a/launcher/minecraft/skins/SkinList.cpp +++ b/launcher/minecraft/skins/SkinList.cpp @@ -106,7 +106,7 @@ bool SkinList::update() auto path = m_dir.absoluteFilePath(name); if (skinTexture.loadFromData(skin.data, "PNG") && skinTexture.save(path)) { SkinModel s(path); - s.setModel(skin.variant == "slim" ? SkinModel::SLIM : SkinModel::CLASSIC); + s.setModel(skin.variant == "SLIM" ? SkinModel::SLIM : SkinModel::CLASSIC); s.setCapeId(m_acct->accountData()->minecraftProfile.currentCape); s.setURL(skin.url); newSkins << s; @@ -114,7 +114,7 @@ bool SkinList::update() } } else { nskin->setCapeId(m_acct->accountData()->minecraftProfile.currentCape); - nskin->setModel(skin.variant == "slim" ? SkinModel::SLIM : SkinModel::CLASSIC); + nskin->setModel(skin.variant == "SLIM" ? SkinModel::SLIM : SkinModel::CLASSIC); } } diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index 5419d3eed..5c69be0ba 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -460,7 +460,7 @@ void SkinManageDialog::on_userBtn_clicked() dlg.execWithTask(job.get()); SkinModel s(path); - s.setModel(mcProfile.skin.variant == "slim" ? SkinModel::SLIM : SkinModel::CLASSIC); + s.setModel(mcProfile.skin.variant == "SLIM" ? SkinModel::SLIM : SkinModel::CLASSIC); s.setURL(mcProfile.skin.url); if (m_capes.contains(mcProfile.currentCape)) { s.setCapeId(mcProfile.currentCape); From 8b8ea2d27057dd9cebe20586f2ab5fc8426d1c99 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 26 Oct 2023 13:37:25 +0300 Subject: [PATCH 033/103] ensured that the variant is allways uppercase Signed-off-by: Trial97 --- launcher/minecraft/skins/SkinList.cpp | 4 ++-- launcher/ui/dialogs/skins/SkinManageDialog.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/minecraft/skins/SkinList.cpp b/launcher/minecraft/skins/SkinList.cpp index 0505e4ced..fd883ad52 100644 --- a/launcher/minecraft/skins/SkinList.cpp +++ b/launcher/minecraft/skins/SkinList.cpp @@ -106,7 +106,7 @@ bool SkinList::update() auto path = m_dir.absoluteFilePath(name); if (skinTexture.loadFromData(skin.data, "PNG") && skinTexture.save(path)) { SkinModel s(path); - s.setModel(skin.variant == "SLIM" ? SkinModel::SLIM : SkinModel::CLASSIC); + s.setModel(skin.variant.toUpper() == "SLIM" ? SkinModel::SLIM : SkinModel::CLASSIC); s.setCapeId(m_acct->accountData()->minecraftProfile.currentCape); s.setURL(skin.url); newSkins << s; @@ -114,7 +114,7 @@ bool SkinList::update() } } else { nskin->setCapeId(m_acct->accountData()->minecraftProfile.currentCape); - nskin->setModel(skin.variant == "SLIM" ? SkinModel::SLIM : SkinModel::CLASSIC); + nskin->setModel(skin.variant.toUpper() == "SLIM" ? SkinModel::SLIM : SkinModel::CLASSIC); } } diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index 5c69be0ba..9320101c2 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -460,7 +460,7 @@ void SkinManageDialog::on_userBtn_clicked() dlg.execWithTask(job.get()); SkinModel s(path); - s.setModel(mcProfile.skin.variant == "SLIM" ? SkinModel::SLIM : SkinModel::CLASSIC); + s.setModel(mcProfile.skin.variant.toUpper() == "SLIM" ? SkinModel::SLIM : SkinModel::CLASSIC); s.setURL(mcProfile.skin.url); if (m_capes.contains(mcProfile.currentCape)) { s.setCapeId(mcProfile.currentCape); From 321bbf1fa8bbdf37a5b81aa8c69512e1b04028eb Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 26 Oct 2023 19:06:38 +0300 Subject: [PATCH 034/103] fixed asan stuff Signed-off-by: Trial97 --- launcher/minecraft/skins/SkinUpload.cpp | 2 +- launcher/ui/dialogs/skins/SkinManageDialog.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/minecraft/skins/SkinUpload.cpp b/launcher/minecraft/skins/SkinUpload.cpp index 4a88faedf..dc1bc0bf9 100644 --- a/launcher/minecraft/skins/SkinUpload.cpp +++ b/launcher/minecraft/skins/SkinUpload.cpp @@ -49,7 +49,7 @@ SkinUpload::SkinUpload(QString token, QString path, QString variant) : NetReques QNetworkReply* SkinUpload::getReply(QNetworkRequest& request) { - QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); + QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType, this); QHttpPart skin; skin.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/png")); diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index 9320101c2..8028a719c 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -74,7 +74,7 @@ SkinManageDialog::SkinManageDialog(QWidget* parent, MinecraftAccountPtr acct) contentsWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); contentsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); contentsWidget->installEventFilter(this); - contentsWidget->setItemDelegate(new ListViewDelegate()); + contentsWidget->setItemDelegate(new ListViewDelegate(this)); contentsWidget->setAcceptDrops(true); contentsWidget->setDropIndicatorShown(true); From 3cbc63bb9f5265a55294e546f3812b52d4ef7346 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 2 Nov 2023 12:18:24 +0200 Subject: [PATCH 035/103] added size column Signed-off-by: Trial97 --- launcher/minecraft/mod/Mod.cpp | 4 ++- launcher/minecraft/mod/ModFolderModel.cpp | 24 +++++++++++----- launcher/minecraft/mod/ModFolderModel.h | 2 +- launcher/minecraft/mod/Resource.cpp | 7 +++++ launcher/minecraft/mod/Resource.h | 2 +- .../minecraft/mod/ResourceFolderModel.cpp | 28 +++++++++++-------- launcher/minecraft/mod/ResourceFolderModel.h | 12 ++++---- .../minecraft/mod/ResourcePackFolderModel.cpp | 19 +++++++++---- .../minecraft/mod/ResourcePackFolderModel.h | 2 +- .../minecraft/mod/TexturePackFolderModel.cpp | 16 +++++++---- .../minecraft/mod/TexturePackFolderModel.h | 2 +- 11 files changed, 78 insertions(+), 40 deletions(-) diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 310946379..374e60d56 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -45,6 +45,7 @@ #include "MetadataHandler.h" #include "Version.h" #include "minecraft/mod/ModDetails.h" +#include "minecraft/mod/Resource.h" #include "minecraft/mod/tasks/LocalModParseTask.h" static ModPlatform::ProviderCapabilities ProviderCaps; @@ -87,7 +88,8 @@ std::pair Mod::compare(const Resource& other, SortType type) const default: case SortType::ENABLED: case SortType::NAME: - case SortType::DATE: { + case SortType::DATE: + case SortType::SIZE: { auto res = Resource::compare(other, type); if (res.first != 0) return res; diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index a5f1489dd..5c6cdb00e 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -52,6 +52,8 @@ #include "Application.h" #include "Json.h" +#include "StringUtils.h" +#include "minecraft/mod/Resource.h" #include "minecraft/mod/tasks/LocalModParseTask.h" #include "minecraft/mod/tasks/LocalModUpdateTask.h" #include "minecraft/mod/tasks/ModFolderLoadTask.h" @@ -62,12 +64,15 @@ ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir) : ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed) { - m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider" }); - m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider") }); - m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION, SortType::DATE, SortType::PROVIDER }; + m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Size" }); + m_column_names_translated = + QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider"), tr("Size") }); + m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION, + SortType::DATE, SortType::PROVIDER, SortType::SIZE }; m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, - QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents }; - m_columnsHideable = { false, true, false, true, true, true }; + QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, + QHeaderView::ResizeToContents }; + m_columnsHideable = { false, true, false, true, true, true, true }; } QVariant ModFolderModel::data(const QModelIndex& index, int role) const @@ -105,12 +110,14 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const return provider.value(); } + case SizeColumn: + return StringUtils::humanReadableFileSize(m_resources[row]->fileinfo().size(), true); default: return QVariant(); } case Qt::ToolTipRole: - if (column == NAME_COLUMN) { + if (column == NameColumn) { if (at(row)->isSymLinkUnder(instDirPath())) { return m_resources[row]->internal_id() + tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." @@ -124,7 +131,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const } return m_resources[row]->internal_id(); case Qt::DecorationRole: { - if (column == NAME_COLUMN && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink())) + if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink())) return APPLICATION->getThemedIcon("status-yellow"); if (column == ImageColumn) { return at(row)->icon({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding); @@ -154,6 +161,7 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio case DateColumn: case ProviderColumn: case ImageColumn: + case SizeColumn: return columnNames().at(section); default: return QVariant(); @@ -171,6 +179,8 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio return tr("The date and time this mod was last changed (or added)."); case ProviderColumn: return tr("Where the mod was downloaded from."); + case SizeColumn: + return tr("The size of the resource."); default: return QVariant(); } diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index 61d840f9b..e830556ab 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -61,7 +61,7 @@ class QFileSystemWatcher; class ModFolderModel : public ResourceFolderModel { Q_OBJECT public: - enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, VersionColumn, DateColumn, ProviderColumn, NUM_COLUMNS }; + enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, VersionColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS }; enum ModStatusAction { Disable, Enable, Toggle }; ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed = false, bool create_dir = true); diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index da806f0f4..79e52a881 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -89,6 +89,13 @@ std::pair Resource::compare(const Resource& other, SortType type) con if (dateTimeChanged() < other.dateTimeChanged()) return { -1, type == SortType::DATE }; break; + case SortType::SIZE: { + if (fileinfo().size() > other.fileinfo().size()) + return { 1, type == SortType::SIZE }; + if (fileinfo().size() < other.fileinfo().size()) + return { -1, type == SortType::SIZE }; + break; + } } return { 0, false }; diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index c1ed49461..d94e4b368 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -15,7 +15,7 @@ enum class ResourceType { LITEMOD, //!< The resource is a litemod }; -enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER }; +enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER, SIZE }; enum class EnableAction { ENABLE, DISABLE, TOGGLE }; diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 0503b660b..16ff01227 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -15,6 +15,7 @@ #include "FileSystem.h" #include "QVariantUtils.h" +#include "StringUtils.h" #include "minecraft/mod/tasks/BasicFolderLoadTask.h" #include "settings/Setting.h" @@ -410,15 +411,17 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const switch (role) { case Qt::DisplayRole: switch (column) { - case NAME_COLUMN: + case NameColumn: return m_resources[row]->name(); - case DATE_COLUMN: + case DateColumn: return m_resources[row]->dateTimeChanged(); + case SizeColumn: + return StringUtils::humanReadableFileSize(m_resources[row]->fileinfo().size(), true); default: return {}; } case Qt::ToolTipRole: - if (column == NAME_COLUMN) { + if (column == NameColumn) { if (at(row).isSymLinkUnder(instDirPath())) { return m_resources[row]->internal_id() + tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." @@ -434,14 +437,14 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const return m_resources[row]->internal_id(); case Qt::DecorationRole: { - if (column == NAME_COLUMN && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink())) + if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink())) return APPLICATION->getThemedIcon("status-yellow"); return {}; } case Qt::CheckStateRole: switch (column) { - case ACTIVE_COLUMN: + case ActiveColumn: return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked; default: return {}; @@ -480,24 +483,27 @@ QVariant ResourceFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien switch (role) { case Qt::DisplayRole: switch (section) { - case ACTIVE_COLUMN: - case NAME_COLUMN: - case DATE_COLUMN: + case ActiveColumn: + case NameColumn: + case DateColumn: + case SizeColumn: return columnNames().at(section); default: return {}; } case Qt::ToolTipRole: { switch (section) { - case ACTIVE_COLUMN: + case ActiveColumn: //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. return tr("Is the resource enabled?"); - case NAME_COLUMN: + case NameColumn: //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. return tr("The name of the resource."); - case DATE_COLUMN: + case DateColumn: //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. return tr("The date and time this resource was last changed (or added)."); + case SizeColumn: + return tr("The size of the resource."); default: return {}; } diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index 60b8879c0..c282b8b80 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -96,7 +96,7 @@ class ResourceFolderModel : public QAbstractListModel { /* Qt behavior */ /* Basic columns */ - enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, NUM_COLUMNS }; + enum Columns { ActiveColumn = 0, NameColumn, DateColumn, SizeColumn, NUM_COLUMNS }; QStringList columnNames(bool translated = true) const { return translated ? m_column_names_translated : m_column_names; } [[nodiscard]] int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast(size()); } @@ -198,12 +198,12 @@ class ResourceFolderModel : public QAbstractListModel { protected: // Represents the relationship between a column's index (represented by the list index), and it's sorting key. // As such, the order in with they appear is very important! - QList m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE }; - QStringList m_column_names = { "Enable", "Name", "Last Modified" }; - QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified") }; + QList m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE, SortType::SIZE }; + QStringList m_column_names = { "Enable", "Name", "Last Modified", "Size" }; + QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("Size") }; QList m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Stretch, - QHeaderView::ResizeToContents }; - QList m_columnsHideable = { false, false, true }; + QHeaderView::ResizeToContents, QHeaderView::ResizeToContents }; + QList m_columnsHideable = { false, false, true, true }; QDir m_dir; BaseInstance* m_instance; diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index f27431576..d94751629 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -42,19 +42,21 @@ #include #include "Application.h" +#include "StringUtils.h" #include "Version.h" +#include "minecraft/mod/Resource.h" #include "minecraft/mod/tasks/BasicFolderLoadTask.h" #include "minecraft/mod/tasks/LocalResourcePackParseTask.h" ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) { - m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified" }); - m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified") }); - m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE }; - m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents, - QHeaderView::ResizeToContents }; - m_columnsHideable = { false, true, false, true, true }; + m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Size" }); + m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Size") }); + m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE, SortType::SIZE }; + m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, + QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents }; + m_columnsHideable = { false, true, false, true, true, true }; } QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const @@ -85,6 +87,8 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const } case DateColumn: return m_resources[row]->dateTimeChanged(); + case SizeColumn: + return StringUtils::humanReadableFileSize(m_resources[row]->fileinfo().size(), true); default: return {}; @@ -139,6 +143,7 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O case PackFormatColumn: case DateColumn: case ImageColumn: + case SizeColumn: return columnNames().at(section); default: return {}; @@ -155,6 +160,8 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O return tr("The resource pack format ID, as well as the Minecraft versions it was designed for."); case DateColumn: return tr("The date and time this resource pack was last changed (or added)."); + case SizeColumn: + return tr("The size of the resource."); default: return {}; } diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.h b/launcher/minecraft/mod/ResourcePackFolderModel.h index 29c2c5995..755b9c4c6 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.h +++ b/launcher/minecraft/mod/ResourcePackFolderModel.h @@ -7,7 +7,7 @@ class ResourcePackFolderModel : public ResourceFolderModel { Q_OBJECT public: - enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, NUM_COLUMNS }; + enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, SizeColumn, NUM_COLUMNS }; explicit ResourcePackFolderModel(const QString& dir, BaseInstance* instance); diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index 5c5f2b7c1..ef241351f 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -37,6 +37,7 @@ #include "Application.h" +#include "StringUtils.h" #include "TexturePackFolderModel.h" #include "minecraft/mod/tasks/BasicFolderLoadTask.h" @@ -44,12 +45,12 @@ TexturePackFolderModel::TexturePackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) { - m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified" }); - m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified") }); - m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE }; - m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, + m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified", "Size" }); + m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified"), tr("Size") }); + m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE, SortType::SIZE }; + m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents }; - m_columnsHideable = { false, true, false, true }; + m_columnsHideable = { false, true, false, true, true }; } Task* TexturePackFolderModel::createUpdateTask() @@ -77,6 +78,8 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const return m_resources[row]->name(); case DateColumn: return m_resources[row]->dateTimeChanged(); + case SizeColumn: + return StringUtils::humanReadableFileSize(m_resources[row]->fileinfo().size(), true); default: return {}; } @@ -123,6 +126,7 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or case NameColumn: case DateColumn: case ImageColumn: + case SizeColumn: return columnNames().at(section); default: return {}; @@ -138,6 +142,8 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or case DateColumn: //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. return tr("The date and time this resource was last changed (or added)."); + case SizeColumn: + return tr("The size of the resource."); default: return {}; } diff --git a/launcher/minecraft/mod/TexturePackFolderModel.h b/launcher/minecraft/mod/TexturePackFolderModel.h index b975d8641..de90f879f 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.h +++ b/launcher/minecraft/mod/TexturePackFolderModel.h @@ -44,7 +44,7 @@ class TexturePackFolderModel : public ResourceFolderModel { Q_OBJECT public: - enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, DateColumn, NUM_COLUMNS }; + enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, DateColumn, SizeColumn, NUM_COLUMNS }; explicit TexturePackFolderModel(const QString& dir, std::shared_ptr instance); From cbb453a0edf5bde883627247b8284f02d18c4493 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 3 Nov 2023 22:55:10 +0200 Subject: [PATCH 036/103] minimize the permisions for extracted files Signed-off-by: Trial97 --- launcher/MMCZip.cpp | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 6172fe911..c3acd8eb6 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -42,6 +42,7 @@ #include #include +#include #include #if defined(LAUNCHER_APPLICATION) @@ -327,9 +328,20 @@ std::optional extractSubDir(QuaZip* zip, const QString& subdir, con } extracted.append(target_file_path); - QFile::setPermissions(target_file_path, - QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser); + 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, permissions)) { + qWarning() << (QObject::tr("Could not fix permissions for %1").arg(target_file_path)); + } + } + } qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path; } while (zip->goToNextFile()); @@ -582,8 +594,20 @@ auto ExtractZipTask::extractZip() -> ZipResult } extracted.append(target_file_path); - QFile::setPermissions(target_file_path, - QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser); + 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, permissions)) { + logWarning(tr("Could not fix permissions for %1").arg(target_file_path)); + } + } + } qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path; } while (m_input->goToNextFile()); From 2b6d8b8a2d7aff89b7677a22980254115293dbd5 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 3 Nov 2023 23:02:17 +0200 Subject: [PATCH 037/103] updated tooltip messages Signed-off-by: Trial97 --- launcher/minecraft/mod/ModFolderModel.cpp | 2 +- launcher/minecraft/mod/ResourcePackFolderModel.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 5c6cdb00e..5ba1795fb 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -180,7 +180,7 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio case ProviderColumn: return tr("Where the mod was downloaded from."); case SizeColumn: - return tr("The size of the resource."); + return tr("The size of the mod."); default: return QVariant(); } diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index d94751629..e9c3d3043 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -161,7 +161,7 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O case DateColumn: return tr("The date and time this resource pack was last changed (or added)."); case SizeColumn: - return tr("The size of the resource."); + return tr("The size of the resource pack."); default: return {}; } From f828ea8929f9ba23f5f2d6a852c9634eebdf7786 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 4 Nov 2023 11:49:05 +0200 Subject: [PATCH 038/103] updated the texture pack tooltips Signed-off-by: Trial97 --- launcher/minecraft/mod/TexturePackFolderModel.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index ef241351f..e3f369bb8 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -135,15 +135,15 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or switch (section) { case ActiveColumn: //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. - return tr("Is the resource enabled?"); + return tr("Is the texture pack enabled?"); case NameColumn: //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. - return tr("The name of the resource."); + return tr("The name of the texture pack."); case DateColumn: //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. - return tr("The date and time this resource was last changed (or added)."); + return tr("The date and time this texture pack was last changed (or added)."); case SizeColumn: - return tr("The size of the resource."); + return tr("The size of the texture pack."); default: return {}; } From 5afe6600eea7d569c6f47dcd98226ca10c40aa62 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 4 Nov 2023 16:49:35 +0200 Subject: [PATCH 039/103] use fs::move instead of qt rename Signed-off-by: Trial97 --- launcher/FileSystem.cpp | 3 +-- launcher/InstanceList.cpp | 3 +-- launcher/modplatform/flame/FlameInstanceCreationTask.cpp | 4 ++-- launcher/modplatform/legacy_ftb/PackInstallTask.cpp | 2 +- .../modplatform/modrinth/ModrinthInstanceCreationTask.cpp | 4 ++-- 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index c7d5f85fa..794804985 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -652,8 +652,7 @@ bool move(const QString& source, const QString& dest) if (err) { qWarning() << "Failed to move file:" << QString::fromStdString(err.message()); - qDebug() << "Source file:" << source; - qDebug() << "Destination file:" << dest; + qDebug() << "Source file:" << source << ";Destination file:" << dest; } return err.value() == 0; diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 2b8f34293..8f0cbba79 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -953,7 +953,6 @@ bool InstanceList::commitStagedInstance(const QString& path, if (groupName.isEmpty() && !groupName.isNull()) groupName = QString(); - QDir dir; QString instID; InstancePtr inst; @@ -977,7 +976,7 @@ bool InstanceList::commitStagedInstance(const QString& path, return false; } } else { - if (!dir.rename(path, destination)) { + if (!FS::move(path, destination)) { qWarning() << "Failed to move" << path << "to" << destination; return false; } diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 2a26ce944..7e107f2d2 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -321,7 +321,7 @@ bool FlameCreationTask::createInstance() // Keep index file in case we need it some other time (like when changing versions) QString new_index_place(FS::PathCombine(parent_folder, "manifest.json")); FS::ensureFilePathExists(new_index_place); - QFile::rename(index_path, new_index_place); + FS::move(index_path, new_index_place); } catch (const JSONValidationError& e) { setError(tr("Could not understand pack manifest:\n") + e.cause()); @@ -335,7 +335,7 @@ bool FlameCreationTask::createInstance() Override::createOverrides("overrides", parent_folder, overridePath); QString mcPath = FS::PathCombine(m_stagingPath, "minecraft"); - if (!QFile::rename(overridePath, mcPath)) { + if (!FS::move(overridePath, mcPath)) { setError(tr("Could not rename the overrides folder:\n") + m_pack.overrides); return false; } diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp index 091296751..867773f58 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp @@ -137,7 +137,7 @@ void PackInstallTask::install() QDir unzipMcDir(m_stagingPath + "/unzip/minecraft"); if (unzipMcDir.exists()) { // ok, found minecraft dir, move contents to instance dir - if (!QDir().rename(m_stagingPath + "/unzip/minecraft", m_stagingPath + "/.minecraft")) { + if (!FS::move(m_stagingPath + "/unzip/minecraft", m_stagingPath + "/.minecraft")) { emitFailed(tr("Failed to move unzipped Minecraft!")); return; } diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index e732ad39c..f01c52d21 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -171,7 +171,7 @@ bool ModrinthCreationTask::createInstance() // Keep index file in case we need it some other time (like when changing versions) QString new_index_place(FS::PathCombine(parent_folder, "modrinth.index.json")); FS::ensureFilePathExists(new_index_place); - QFile::rename(index_path, new_index_place); + FS::move(index_path, new_index_place); auto mcPath = FS::PathCombine(m_stagingPath, ".minecraft"); @@ -181,7 +181,7 @@ bool ModrinthCreationTask::createInstance() Override::createOverrides("overrides", parent_folder, override_path); // Apply the overrides - if (!QFile::rename(override_path, mcPath)) { + if (!FS::move(override_path, mcPath)) { setError(tr("Could not rename the overrides folder:\n") + "overrides"); return false; } From 1d67fc66466566955719d4ab599d8b5482182f46 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 5 Nov 2023 16:47:33 +0200 Subject: [PATCH 040/103] added special case for windows Signed-off-by: Trial97 --- launcher/FileSystem.cpp | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 794804985..5bb10b069 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -643,6 +643,19 @@ void ExternalLinkFileProcess::runLinkFile() qDebug() << "Process exited"; } +bool moveByCopy(const QString& source, const QString& dest) +{ + if (!copy(source, dest)()) { // copy + qDebug() << "Copy of" << source << "to" << dest << "failed!"; + return false; + } + if (!deletePath(source)) { // remove original + qDebug() << "Deletion of" << source << "failed!"; + return false; + }; + return true; +} + bool move(const QString& source, const QString& dest) { std::error_code err; @@ -650,12 +663,14 @@ bool move(const QString& source, const QString& dest) ensureFilePathExists(dest); fs::rename(StringUtils::toStdString(source), StringUtils::toStdString(dest), err); - if (err) { - qWarning() << "Failed to move file:" << QString::fromStdString(err.message()); - qDebug() << "Source file:" << source << ";Destination file:" << dest; + if (err.value() != 0) { + if (moveByCopy(source, dest)) + return true; + qDebug() << "Move of" << source << "to" << dest << "failed!"; + qWarning() << "Failed to move file:" << QString::fromStdString(err.message()) << QString::number(err.value()); + return false; } - - return err.value() == 0; + return true; } bool deletePath(QString path) From b4bfc03e8b25d8eeea250560c1fc5e1c055677b2 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 9 Nov 2023 21:48:46 +0200 Subject: [PATCH 041/103] Added button to refresh themes and catpacks Signed-off-by: Trial97 --- launcher/ui/themes/ThemeManager.cpp | 10 +++++++++ launcher/ui/themes/ThemeManager.h | 2 ++ .../ui/widgets/ThemeCustomizationWidget.cpp | 21 +++++++++++++++++++ .../ui/widgets/ThemeCustomizationWidget.h | 1 + .../ui/widgets/ThemeCustomizationWidget.ui | 7 +++++++ 5 files changed, 41 insertions(+) diff --git a/launcher/ui/themes/ThemeManager.cpp b/launcher/ui/themes/ThemeManager.cpp index 0bcac100c..cb90bc826 100644 --- a/launcher/ui/themes/ThemeManager.cpp +++ b/launcher/ui/themes/ThemeManager.cpp @@ -313,3 +313,13 @@ void ThemeManager::initializeCatPacks() } } } + +void ThemeManager::refresh() +{ + m_themes.clear(); + m_icons.clear(); + m_catPacks.clear(); + + initializeThemes(); + initializeCatPacks(); +}; \ No newline at end of file diff --git a/launcher/ui/themes/ThemeManager.h b/launcher/ui/themes/ThemeManager.h index b5c66677b..8ed587806 100644 --- a/launcher/ui/themes/ThemeManager.h +++ b/launcher/ui/themes/ThemeManager.h @@ -55,6 +55,8 @@ class ThemeManager { QString getCatPack(QString catName = ""); QList getValidCatPacks(); + void refresh(); + private: std::map> m_themes; std::map m_icons; diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.cpp b/launcher/ui/widgets/ThemeCustomizationWidget.cpp index 0de97441f..a0e682bb9 100644 --- a/launcher/ui/widgets/ThemeCustomizationWidget.cpp +++ b/launcher/ui/widgets/ThemeCustomizationWidget.cpp @@ -39,6 +39,8 @@ ThemeCustomizationWidget::ThemeCustomizationWidget(QWidget* parent) : QWidget(pa [] { DesktopServices::openDirectory(APPLICATION->themeManager()->getApplicationThemesFolder().path()); }); connect(ui->catPackFolder, &QPushButton::clicked, this, [] { DesktopServices::openDirectory(APPLICATION->themeManager()->getCatPacksFolder().path()); }); + + connect(ui->refreshButton, &QPushButton::clicked, this, &ThemeCustomizationWidget::refresh); } ThemeCustomizationWidget::~ThemeCustomizationWidget() @@ -169,3 +171,22 @@ void ThemeCustomizationWidget::retranslate() { ui->retranslateUi(this); } + +void ThemeCustomizationWidget::refresh() +{ + applySettings(); + disconnect(ui->iconsComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &ThemeCustomizationWidget::applyIconTheme); + disconnect(ui->widgetStyleComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, + &ThemeCustomizationWidget::applyWidgetTheme); + disconnect(ui->backgroundCatComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, + &ThemeCustomizationWidget::applyCatTheme); + APPLICATION->themeManager()->refresh(); + ui->iconsComboBox->clear(); + ui->widgetStyleComboBox->clear(); + ui->backgroundCatComboBox->clear(); + loadSettings(); + connect(ui->iconsComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &ThemeCustomizationWidget::applyIconTheme); + connect(ui->widgetStyleComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, + &ThemeCustomizationWidget::applyWidgetTheme); + connect(ui->backgroundCatComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &ThemeCustomizationWidget::applyCatTheme); +}; \ No newline at end of file diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.h b/launcher/ui/widgets/ThemeCustomizationWidget.h index cef5fb6c6..6977b8495 100644 --- a/launcher/ui/widgets/ThemeCustomizationWidget.h +++ b/launcher/ui/widgets/ThemeCustomizationWidget.h @@ -44,6 +44,7 @@ class ThemeCustomizationWidget : public QWidget { void applyIconTheme(int index); void applyWidgetTheme(int index); void applyCatTheme(int index); + void refresh(); signals: int currentIconThemeChanged(int index); diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.ui b/launcher/ui/widgets/ThemeCustomizationWidget.ui index 4503181c2..322f9d6a7 100644 --- a/launcher/ui/widgets/ThemeCustomizationWidget.ui +++ b/launcher/ui/widgets/ThemeCustomizationWidget.ui @@ -167,6 +167,13 @@ + + + + Refresh + + + From 6804e2ba59a45f8ec504e392829bacb51a30c86d Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 14 Nov 2023 18:40:45 +0200 Subject: [PATCH 042/103] moved export to list to the mods page Signed-off-by: Trial97 --- launcher/ui/MainWindow.cpp | 10 ---- launcher/ui/MainWindow.h | 1 - launcher/ui/MainWindow.ui | 9 ---- launcher/ui/dialogs/ExportToModListDialog.cpp | 48 ++++++++----------- launcher/ui/dialogs/ExportToModListDialog.h | 10 ++-- launcher/ui/pages/instance/ModFolderPage.cpp | 18 ++++++- launcher/ui/pages/instance/ModFolderPage.h | 1 + 7 files changed, 42 insertions(+), 55 deletions(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 1da982dad..3ae80ebb6 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -96,7 +96,6 @@ #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/ExportInstanceDialog.h" #include "ui/dialogs/ExportPackDialog.h" -#include "ui/dialogs/ExportToModListDialog.h" #include "ui/dialogs/IconPickerDialog.h" #include "ui/dialogs/ImportResourceDialog.h" #include "ui/dialogs/NewInstanceDialog.h" @@ -208,7 +207,6 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi exportInstanceMenu->addAction(ui->actionExportInstanceZip); exportInstanceMenu->addAction(ui->actionExportInstanceMrPack); exportInstanceMenu->addAction(ui->actionExportInstanceFlamePack); - exportInstanceMenu->addAction(ui->actionExportInstanceToModList); ui->actionExportInstance->setMenu(exportInstanceMenu); } @@ -1390,14 +1388,6 @@ void MainWindow::on_actionExportInstanceMrPack_triggered() } } -void MainWindow::on_actionExportInstanceToModList_triggered() -{ - if (m_selectedInstance) { - ExportToModListDialog dlg(m_selectedInstance, this); - dlg.exec(); - } -} - void MainWindow::on_actionExportInstanceFlamePack_triggered() { if (m_selectedInstance) { diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 0b7287404..7935ddf56 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -156,7 +156,6 @@ class MainWindow : public QMainWindow { void on_actionExportInstanceZip_triggered(); void on_actionExportInstanceMrPack_triggered(); void on_actionExportInstanceFlamePack_triggered(); - void on_actionExportInstanceToModList_triggered(); void on_actionRenameInstance_triggered(); diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui index 91b2c2703..ee90eeba1 100644 --- a/launcher/ui/MainWindow.ui +++ b/launcher/ui/MainWindow.ui @@ -479,15 +479,6 @@ CurseForge (zip) - - - - .. - - - Mod List - - diff --git a/launcher/ui/dialogs/ExportToModListDialog.cpp b/launcher/ui/dialogs/ExportToModListDialog.cpp index a343f555a..f767727d7 100644 --- a/launcher/ui/dialogs/ExportToModListDialog.cpp +++ b/launcher/ui/dialogs/ExportToModListDialog.cpp @@ -22,8 +22,6 @@ #include #include "FileSystem.h" #include "Markdown.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/mod/ModFolderModel.h" #include "modplatform/helpers/ExportToModList.h" #include "ui_ExportToModListDialog.h" @@ -41,21 +39,12 @@ const QHash ExportToModListDialog::exampleLin { ExportToModList::CSV, "{name},{url},{version},\"{authors}\"" }, }; -ExportToModListDialog::ExportToModListDialog(InstancePtr instance, QWidget* parent) - : QDialog(parent), m_template_changed(false), name(instance->name()), ui(new Ui::ExportToModListDialog) +ExportToModListDialog::ExportToModListDialog(QString name, QList mods, QWidget* parent) + : QDialog(parent), m_mods(mods), m_template_changed(false), m_name(name), ui(new Ui::ExportToModListDialog) { ui->setupUi(this); enableCustom(false); - MinecraftInstance* mcInstance = dynamic_cast(instance.get()); - if (mcInstance) { - mcInstance->loaderModList()->update(); - connect(mcInstance->loaderModList().get(), &ModFolderModel::updateFinished, this, [this, mcInstance]() { - m_allMods = mcInstance->loaderModList()->allMods(); - triggerImp(); - }); - } - connect(ui->formatComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &ExportToModListDialog::formatChanged); connect(ui->authorsCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger); connect(ui->versionCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger); @@ -64,7 +53,7 @@ ExportToModListDialog::ExportToModListDialog(InstancePtr instance, QWidget* pare connect(ui->versionButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Version); }); connect(ui->urlButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Url); }); connect(ui->templateText, &QTextEdit::textChanged, this, [this] { - if (ui->templateText->toPlainText() != exampleLines[format]) + if (ui->templateText->toPlainText() != exampleLines[m_format]) ui->formatComboBox->setCurrentIndex(5); else triggerImp(); @@ -73,6 +62,7 @@ ExportToModListDialog::ExportToModListDialog(InstancePtr instance, QWidget* pare this->ui->finalText->selectAll(); this->ui->finalText->copy(); }); + triggerImp(); } ExportToModListDialog::~ExportToModListDialog() @@ -86,38 +76,38 @@ void ExportToModListDialog::formatChanged(int index) case 0: { enableCustom(false); ui->resultText->show(); - format = ExportToModList::HTML; + m_format = ExportToModList::HTML; break; } case 1: { enableCustom(false); ui->resultText->show(); - format = ExportToModList::MARKDOWN; + m_format = ExportToModList::MARKDOWN; break; } case 2: { enableCustom(false); ui->resultText->hide(); - format = ExportToModList::PLAINTXT; + m_format = ExportToModList::PLAINTXT; break; } case 3: { enableCustom(false); ui->resultText->hide(); - format = ExportToModList::JSON; + m_format = ExportToModList::JSON; break; } case 4: { enableCustom(false); ui->resultText->hide(); - format = ExportToModList::CSV; + m_format = ExportToModList::CSV; break; } case 5: { m_template_changed = true; enableCustom(true); ui->resultText->hide(); - format = ExportToModList::CUSTOM; + m_format = ExportToModList::CUSTOM; break; } } @@ -126,8 +116,8 @@ void ExportToModListDialog::formatChanged(int index) void ExportToModListDialog::triggerImp() { - if (format == ExportToModList::CUSTOM) { - ui->finalText->setPlainText(ExportToModList::exportToModList(m_allMods, ui->templateText->toPlainText())); + if (m_format == ExportToModList::CUSTOM) { + ui->finalText->setPlainText(ExportToModList::exportToModList(m_mods, ui->templateText->toPlainText())); return; } auto opt = 0; @@ -137,9 +127,9 @@ void ExportToModListDialog::triggerImp() opt |= ExportToModList::Version; if (ui->urlCheckBox->isChecked()) opt |= ExportToModList::Url; - auto txt = ExportToModList::exportToModList(m_allMods, format, static_cast(opt)); + auto txt = ExportToModList::exportToModList(m_mods, m_format, static_cast(opt)); ui->finalText->setPlainText(txt); - switch (format) { + switch (m_format) { case ExportToModList::CUSTOM: return; case ExportToModList::HTML: @@ -155,7 +145,7 @@ void ExportToModListDialog::triggerImp() case ExportToModList::CSV: break; } - auto exampleLine = exampleLines[format]; + auto exampleLine = exampleLines[m_format]; if (!m_template_changed && ui->templateText->toPlainText() != exampleLine) ui->templateText->setPlainText(exampleLine); } @@ -163,9 +153,9 @@ void ExportToModListDialog::triggerImp() void ExportToModListDialog::done(int result) { if (result == Accepted) { - const QString filename = FS::RemoveInvalidFilenameChars(name); + const QString filename = FS::RemoveInvalidFilenameChars(m_name); const QString output = - QFileDialog::getSaveFileName(this, tr("Export %1").arg(name), FS::PathCombine(QDir::homePath(), filename + extension()), + QFileDialog::getSaveFileName(this, tr("Export %1").arg(m_name), FS::PathCombine(QDir::homePath(), filename + extension()), "File (*.txt *.html *.md *.json *.csv)", nullptr); if (output.isEmpty()) @@ -178,7 +168,7 @@ void ExportToModListDialog::done(int result) QString ExportToModListDialog::extension() { - switch (format) { + switch (m_format) { case ExportToModList::HTML: return ".html"; case ExportToModList::MARKDOWN: @@ -197,7 +187,7 @@ QString ExportToModListDialog::extension() void ExportToModListDialog::addExtra(ExportToModList::OptionalData option) { - if (format != ExportToModList::CUSTOM) + if (m_format != ExportToModList::CUSTOM) return; switch (option) { case ExportToModList::Authors: diff --git a/launcher/ui/dialogs/ExportToModListDialog.h b/launcher/ui/dialogs/ExportToModListDialog.h index 9886ae5a0..4ebe203f7 100644 --- a/launcher/ui/dialogs/ExportToModListDialog.h +++ b/launcher/ui/dialogs/ExportToModListDialog.h @@ -20,7 +20,6 @@ #include #include -#include "BaseInstance.h" #include "minecraft/mod/Mod.h" #include "modplatform/helpers/ExportToModList.h" @@ -32,7 +31,7 @@ class ExportToModListDialog : public QDialog { Q_OBJECT public: - explicit ExportToModListDialog(InstancePtr instance, QWidget* parent = nullptr); + explicit ExportToModListDialog(QString name, QList mods, QWidget* parent = nullptr); ~ExportToModListDialog(); void done(int result) override; @@ -46,10 +45,11 @@ class ExportToModListDialog : public QDialog { private: QString extension(); void enableCustom(bool enabled); - QList m_allMods; + + QList m_mods; bool m_template_changed; - QString name; - ExportToModList::Formats format = ExportToModList::Formats::HTML; + QString m_name; + ExportToModList::Formats m_format = ExportToModList::Formats::HTML; Ui::ExportToModListDialog* ui; static const QHash exampleLines; }; diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index a38e608f2..31fb49d0c 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -37,6 +37,7 @@ */ #include "ModFolderPage.h" +#include "ui/dialogs/ExportToModListDialog.h" #include "ui_ExternalResourcesPage.h" #include @@ -111,6 +112,10 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr connect(actionRemoveItemMetadata, &QAction::triggered, this, &ModFolderPage::deleteModMetadata); actionRemoveItemMetadata->setEnabled(false); + auto actionExportMetadata = updateMenu->addAction(tr("Export metadata")); + actionExportMetadata->setToolTip(tr("Export mod's metadata to text")); + connect(actionExportMetadata, &QAction::triggered, this, &ModFolderPage::exportModMetadata); + ui->actionUpdateItem->setMenu(updateMenu); ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected mods (all mods if none are selected)")); @@ -124,7 +129,7 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr auto check_allow_update = [this] { return ui->treeView->selectionModel()->hasSelection() || !m_model->empty(); }; connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, - [this, check_allow_update, actionRemoveItemMetadata] { + [this, check_allow_update, actionRemoveItemMetadata, actionExportMetadata] { ui->actionUpdateItem->setEnabled(check_allow_update()); auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); @@ -360,3 +365,14 @@ void ModFolderPage::deleteModMetadata() m_model->deleteModsMetadata(selection); } + +void ModFolderPage::exportModMetadata() +{ + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); + auto selectedMods = m_model->selectedMods(selection); + if (selectedMods.length() == 0) + selectedMods = m_model->allMods(); + + ExportToModListDialog dlg(m_instance->name(), selectedMods, this); + dlg.exec(); +} diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index 4672350c6..928f5ca7e 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -62,6 +62,7 @@ class ModFolderPage : public ExternalResourcesPage { private slots: void removeItems(const QItemSelection& selection) override; void deleteModMetadata(); + void exportModMetadata(); void installMods(); void updateMods(bool includeDeps = false); From bedb4da8699f6fc8e23d8e9b5644f6dd92251b7b Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 14 Nov 2023 19:37:23 +0200 Subject: [PATCH 043/103] Added filename to the modlist export Signed-off-by: Trial97 --- .../modplatform/helpers/ExportToModList.cpp | 32 ++++++++++++++++--- .../modplatform/helpers/ExportToModList.h | 6 +--- launcher/ui/dialogs/ExportToModListDialog.cpp | 13 ++++++-- launcher/ui/dialogs/ExportToModListDialog.ui | 14 ++++++++ 4 files changed, 53 insertions(+), 12 deletions(-) diff --git a/launcher/modplatform/helpers/ExportToModList.cpp b/launcher/modplatform/helpers/ExportToModList.cpp index 1f01c4a89..86d051c75 100644 --- a/launcher/modplatform/helpers/ExportToModList.cpp +++ b/launcher/modplatform/helpers/ExportToModList.cpp @@ -16,6 +16,7 @@ * along with this program. If not, see . */ #include "ExportToModList.h" +#include #include #include #include @@ -42,17 +43,28 @@ QString toHTML(QList mods, OptionalData extraData) } if (extraData & Authors && !mod->authors().isEmpty()) line += " by " + mod->authors().join(", ").toHtmlEscaped(); + if (extraData & FileName) + line += QString(" (%1)").arg(mod->fileinfo().fileName().toHtmlEscaped()); + lines.append(QString("
  • %1
  • ").arg(line)); } return QString("
      \n\t%1\n
    ").arg(lines.join("\n\t")); } +QString toMarkdownEscaped(QString src) +{ + for (auto ch : "\\`*_{}[]<>()#+-.!|") + src.replace(ch, QString("\\%1").arg(ch)); + return src; +} + QString toMarkdown(QList mods, OptionalData extraData) { QStringList lines; + for (auto mod : mods) { auto meta = mod->metadata(); - auto modName = mod->name(); + auto modName = toMarkdownEscaped(mod->name()); if (extraData & Url) { auto url = mod->metaurl(); if (!url.isEmpty()) @@ -60,14 +72,16 @@ QString toMarkdown(QList mods, OptionalData extraData) } auto line = modName; if (extraData & Version) { - auto ver = mod->version(); + auto ver = toMarkdownEscaped(mod->version()); if (ver.isEmpty() && meta != nullptr) - ver = meta->version().toString(); + ver = toMarkdownEscaped(meta->version().toString()); if (!ver.isEmpty()) line += QString(" [%1]").arg(ver); } if (extraData & Authors && !mod->authors().isEmpty()) - line += " by " + mod->authors().join(", "); + line += " by " + toMarkdownEscaped(mod->authors().join(", ")); + if (extraData & FileName) + line += QString(" (%1)").arg(toMarkdownEscaped(mod->fileinfo().fileName())); lines << "- " + line; } return lines.join("\n"); @@ -95,6 +109,8 @@ QString toPlainTXT(QList mods, OptionalData extraData) } if (extraData & Authors && !mod->authors().isEmpty()) line += " by " + mod->authors().join(", "); + if (extraData & FileName) + line += QString(" (%1)").arg(mod->fileinfo().fileName()); lines << line; } return lines.join("\n"); @@ -122,6 +138,8 @@ QString toJSON(QList mods, OptionalData extraData) } if (extraData & Authors && !mod->authors().isEmpty()) line["authors"] = QJsonArray::fromStringList(mod->authors()); + if (extraData & FileName) + line["filename"] = mod->fileinfo().fileName(); lines << line; } QJsonDocument doc; @@ -154,6 +172,8 @@ QString toCSV(QList mods, OptionalData extraData) authors = QString("\"%1\"").arg(mod->authors().join(",")); data << authors; } + if (extraData & FileName) + data << mod->fileinfo().fileName(); lines << data.join(","); } return lines.join("\n"); @@ -189,11 +209,13 @@ QString exportToModList(QList mods, QString lineTemplate) if (ver.isEmpty() && meta != nullptr) ver = meta->version().toString(); auto authors = mod->authors().join(", "); + auto filename = mod->fileinfo().fileName(); lines << QString(lineTemplate) .replace("{name}", modName) .replace("{url}", url) .replace("{version}", ver) - .replace("{authors}", authors); + .replace("{authors}", authors) + .replace("{filename}", filename); } return lines.join("\n"); } diff --git a/launcher/modplatform/helpers/ExportToModList.h b/launcher/modplatform/helpers/ExportToModList.h index 7ea4ba9c2..ab7797fe6 100644 --- a/launcher/modplatform/helpers/ExportToModList.h +++ b/launcher/modplatform/helpers/ExportToModList.h @@ -23,11 +23,7 @@ namespace ExportToModList { enum Formats { HTML, MARKDOWN, PLAINTXT, JSON, CSV, CUSTOM }; -enum OptionalData { - Authors = 1 << 0, - Url = 1 << 1, - Version = 1 << 2, -}; +enum OptionalData { Authors = 1 << 0, Url = 1 << 1, Version = 1 << 2, FileName = 1 << 3 }; QString exportToModList(QList mods, Formats format, OptionalData extraData); QString exportToModList(QList mods, QString lineTemplate); } // namespace ExportToModList diff --git a/launcher/ui/dialogs/ExportToModListDialog.cpp b/launcher/ui/dialogs/ExportToModListDialog.cpp index f767727d7..95916421d 100644 --- a/launcher/ui/dialogs/ExportToModListDialog.cpp +++ b/launcher/ui/dialogs/ExportToModListDialog.cpp @@ -49,14 +49,15 @@ ExportToModListDialog::ExportToModListDialog(QString name, QList mods, QWi connect(ui->authorsCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger); connect(ui->versionCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger); connect(ui->urlCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger); + connect(ui->filenameCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger); connect(ui->authorsButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Authors); }); connect(ui->versionButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Version); }); connect(ui->urlButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Url); }); + connect(ui->filenameButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::FileName); }); connect(ui->templateText, &QTextEdit::textChanged, this, [this] { if (ui->templateText->toPlainText() != exampleLines[m_format]) ui->formatComboBox->setCurrentIndex(5); - else - triggerImp(); + triggerImp(); }); connect(ui->copyButton, &QPushButton::clicked, this, [this](bool) { this->ui->finalText->selectAll(); @@ -127,6 +128,8 @@ void ExportToModListDialog::triggerImp() opt |= ExportToModList::Version; if (ui->urlCheckBox->isChecked()) opt |= ExportToModList::Url; + if (ui->filenameCheckBox->isChecked()) + opt |= ExportToModList::FileName; auto txt = ExportToModList::exportToModList(m_mods, m_format, static_cast(opt)); ui->finalText->setPlainText(txt); switch (m_format) { @@ -199,6 +202,9 @@ void ExportToModListDialog::addExtra(ExportToModList::OptionalData option) case ExportToModList::Version: ui->templateText->insertPlainText("{version}"); break; + case ExportToModList::FileName: + ui->templateText->insertPlainText("{filename}"); + break; } } void ExportToModListDialog::enableCustom(bool enabled) @@ -211,4 +217,7 @@ void ExportToModListDialog::enableCustom(bool enabled) ui->urlCheckBox->setHidden(enabled); ui->urlButton->setHidden(!enabled); + + ui->filenameCheckBox->setHidden(enabled); + ui->filenameButton->setHidden(!enabled); } diff --git a/launcher/ui/dialogs/ExportToModListDialog.ui b/launcher/ui/dialogs/ExportToModListDialog.ui index 4f8ab52b5..3afda2fa8 100644 --- a/launcher/ui/dialogs/ExportToModListDialog.ui +++ b/launcher/ui/dialogs/ExportToModListDialog.ui @@ -117,6 +117,13 @@
    + + + + Filename + + + @@ -138,6 +145,13 @@ + + + + Filename + + + From 4d93f4adb144079449c05edf939226a6bb215921 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 15 Nov 2023 12:10:47 +0200 Subject: [PATCH 044/103] Fixed folder size Signed-off-by: Trial97 --- launcher/minecraft/World.cpp | 2 +- launcher/minecraft/mod/ModFolderModel.cpp | 2 +- launcher/minecraft/mod/Resource.cpp | 20 +++++++++++++++++-- launcher/minecraft/mod/Resource.h | 2 ++ .../minecraft/mod/ResourceFolderModel.cpp | 2 +- .../minecraft/mod/ResourcePackFolderModel.cpp | 2 +- .../minecraft/mod/TexturePackFolderModel.cpp | 2 +- 7 files changed, 25 insertions(+), 7 deletions(-) diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp index 1a680ac56..1eba148a5 100644 --- a/launcher/minecraft/World.cpp +++ b/launcher/minecraft/World.cpp @@ -206,8 +206,8 @@ int64_t calculateWorldSize(const QFileInfo& file) QDirIterator it(file.absoluteFilePath(), QDir::Files, QDirIterator::Subdirectories); int64_t total = 0; while (it.hasNext()) { - total += it.fileInfo().size(); it.next(); + total += it.fileInfo().size(); } return total; } diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 5ba1795fb..8f79a4e00 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -111,7 +111,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const return provider.value(); } case SizeColumn: - return StringUtils::humanReadableFileSize(m_resources[row]->fileinfo().size(), true); + return StringUtils::humanReadableFileSize(m_resources[row]->size(), true); default: return QVariant(); } diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index 79e52a881..0115cf7ca 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -1,5 +1,6 @@ #include "Resource.h" +#include #include #include @@ -18,6 +19,20 @@ void Resource::setFile(QFileInfo file_info) parseFile(); } +qint64 calculateFileSize(const QFileInfo& file) +{ + if (file.isDir()) { + QDirIterator it(file.absoluteFilePath(), QDir::Files); + qint64 total = 0; + while (it.hasNext()) { + it.next(); + total += it.fileInfo().size(); + } + return total; + } + return file.size(); +} + void Resource::parseFile() { QString file_name{ m_file_info.fileName() }; @@ -26,6 +41,7 @@ void Resource::parseFile() m_internal_id = file_name; + m_size = calculateFileSize(m_file_info); if (m_file_info.isDir()) { m_type = ResourceType::FOLDER; m_name = file_name; @@ -90,9 +106,9 @@ std::pair Resource::compare(const Resource& other, SortType type) con return { -1, type == SortType::DATE }; break; case SortType::SIZE: { - if (fileinfo().size() > other.fileinfo().size()) + if (size() > other.size()) return { 1, type == SortType::SIZE }; - if (fileinfo().size() < other.fileinfo().size()) + if (size() < other.size()) return { -1, type == SortType::SIZE }; break; } diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index d94e4b368..61949f917 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -45,6 +45,7 @@ class Resource : public QObject { [[nodiscard]] auto internal_id() const -> QString { return m_internal_id; } [[nodiscard]] auto type() const -> ResourceType { return m_type; } [[nodiscard]] bool enabled() const { return m_enabled; } + [[nodiscard]] qint64 size() const { return m_size; } [[nodiscard]] virtual auto name() const -> QString { return m_name; } [[nodiscard]] virtual bool valid() const { return m_type != ResourceType::UNKNOWN; } @@ -117,4 +118,5 @@ class Resource : public QObject { bool m_is_resolving = false; bool m_is_resolved = false; int m_resolution_ticket = 0; + qint64 m_size = 0; }; diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 16ff01227..648bf84db 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -416,7 +416,7 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const case DateColumn: return m_resources[row]->dateTimeChanged(); case SizeColumn: - return StringUtils::humanReadableFileSize(m_resources[row]->fileinfo().size(), true); + return StringUtils::humanReadableFileSize(m_resources[row]->size(), true); default: return {}; } diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index e9c3d3043..cf2eb50df 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -88,7 +88,7 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const case DateColumn: return m_resources[row]->dateTimeChanged(); case SizeColumn: - return StringUtils::humanReadableFileSize(m_resources[row]->fileinfo().size(), true); + return StringUtils::humanReadableFileSize(m_resources[row]->size(), true); default: return {}; diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index e3f369bb8..63fb1f8b3 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -79,7 +79,7 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const case DateColumn: return m_resources[row]->dateTimeChanged(); case SizeColumn: - return StringUtils::humanReadableFileSize(m_resources[row]->fileinfo().size(), true); + return StringUtils::humanReadableFileSize(m_resources[row]->size(), true); default: return {}; } From 215465e833e6faa17053bbaaf24b20ed99655d01 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 15 Nov 2023 14:11:34 +0200 Subject: [PATCH 045/103] added subdirectories to iteration Signed-off-by: Trial97 --- launcher/minecraft/mod/Resource.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index 0115cf7ca..72652cefb 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -22,7 +22,7 @@ void Resource::setFile(QFileInfo file_info) qint64 calculateFileSize(const QFileInfo& file) { if (file.isDir()) { - QDirIterator it(file.absoluteFilePath(), QDir::Files); + QDirIterator it(file.absoluteFilePath(), QDir::Files, QDirIterator::Subdirectories); qint64 total = 0; while (it.hasNext()) { it.next(); From 26931475aeb0ed4f0e6e3d935095d9b1fa69b651 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 19 Nov 2023 15:58:30 +0200 Subject: [PATCH 046/103] made folder size reflect the number of elements Signed-off-by: Trial97 --- launcher/minecraft/mod/ModFolderModel.cpp | 2 +- launcher/minecraft/mod/Resource.cpp | 25 ++++++++++--------- launcher/minecraft/mod/Resource.h | 4 +-- .../minecraft/mod/ResourceFolderModel.cpp | 2 +- .../minecraft/mod/ResourcePackFolderModel.cpp | 2 +- .../minecraft/mod/TexturePackFolderModel.cpp | 2 +- 6 files changed, 19 insertions(+), 18 deletions(-) diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 369ad3936..4e98d1520 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -110,7 +110,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const return provider.value(); } case SizeColumn: - return StringUtils::humanReadableFileSize(m_resources[row]->size(), true); + return m_resources[row]->sizeStr(); default: return QVariant(); } diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index 72652cefb..d8727db0a 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -5,6 +5,7 @@ #include #include "FileSystem.h" +#include "StringUtils.h" Resource::Resource(QObject* parent) : QObject(parent) {} @@ -19,18 +20,18 @@ void Resource::setFile(QFileInfo file_info) parseFile(); } -qint64 calculateFileSize(const QFileInfo& file) +QString calculateFileSize(const QFileInfo& file) { if (file.isDir()) { - QDirIterator it(file.absoluteFilePath(), QDir::Files, QDirIterator::Subdirectories); - qint64 total = 0; - while (it.hasNext()) { - it.next(); - total += it.fileInfo().size(); - } - return total; + auto dir = QDir(file.absoluteFilePath()); + dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot); + auto count = dir.count(); + auto str = QObject::tr("item"); + if (count != 1) + str = QObject::tr("items"); + return QString("%1 %2").arg(QString::number(count), str); } - return file.size(); + return StringUtils::humanReadableFileSize(file.size(), true); } void Resource::parseFile() @@ -41,7 +42,7 @@ void Resource::parseFile() m_internal_id = file_name; - m_size = calculateFileSize(m_file_info); + m_size_str = calculateFileSize(m_file_info); if (m_file_info.isDir()) { m_type = ResourceType::FOLDER; m_name = file_name; @@ -106,9 +107,9 @@ std::pair Resource::compare(const Resource& other, SortType type) con return { -1, type == SortType::DATE }; break; case SortType::SIZE: { - if (size() > other.size()) + if (fileinfo().size() > other.fileinfo().size()) return { 1, type == SortType::SIZE }; - if (size() < other.size()) + if (fileinfo().size() < other.fileinfo().size()) return { -1, type == SortType::SIZE }; break; } diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index 61949f917..029afbd5b 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -45,7 +45,7 @@ class Resource : public QObject { [[nodiscard]] auto internal_id() const -> QString { return m_internal_id; } [[nodiscard]] auto type() const -> ResourceType { return m_type; } [[nodiscard]] bool enabled() const { return m_enabled; } - [[nodiscard]] qint64 size() const { return m_size; } + [[nodiscard]] QString sizeStr() const { return m_size_str; } [[nodiscard]] virtual auto name() const -> QString { return m_name; } [[nodiscard]] virtual bool valid() const { return m_type != ResourceType::UNKNOWN; } @@ -118,5 +118,5 @@ class Resource : public QObject { bool m_is_resolving = false; bool m_is_resolved = false; int m_resolution_ticket = 0; - qint64 m_size = 0; + QString m_size_str; }; diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 6a9c5b7dd..8e14f4e80 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -417,7 +417,7 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const case DateColumn: return m_resources[row]->dateTimeChanged(); case SizeColumn: - return StringUtils::humanReadableFileSize(m_resources[row]->size(), true); + return m_resources[row]->sizeStr(); default: return {}; } diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index 7a6138833..7ad7b3038 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -88,7 +88,7 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const case DateColumn: return m_resources[row]->dateTimeChanged(); case SizeColumn: - return StringUtils::humanReadableFileSize(m_resources[row]->size(), true); + return m_resources[row]->sizeStr(); default: return {}; diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index 38db9d8ff..a042c9113 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -79,7 +79,7 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const case DateColumn: return m_resources[row]->dateTimeChanged(); case SizeColumn: - return StringUtils::humanReadableFileSize(m_resources[row]->size(), true); + return m_resources[row]->sizeStr(); default: return {}; } From ac223a29ef61643a2313914d180801ce6dcae139 Mon Sep 17 00:00:00 2001 From: deadmeu Date: Sun, 23 Apr 2023 00:29:49 +1000 Subject: [PATCH 047/103] refactor: shorten desktop entry comment field Signed-off-by: deadmeu --- program_info/org.prismlauncher.PrismLauncher.desktop.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/program_info/org.prismlauncher.PrismLauncher.desktop.in b/program_info/org.prismlauncher.PrismLauncher.desktop.in index f08f2ba43..0995c7c2a 100644 --- a/program_info/org.prismlauncher.PrismLauncher.desktop.in +++ b/program_info/org.prismlauncher.PrismLauncher.desktop.in @@ -1,7 +1,7 @@ [Desktop Entry] Version=1.0 Name=Prism Launcher -Comment=A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once. +Comment=Discover, manage, and play Minecraft instances Type=Application Terminal=false Exec=@Launcher_APP_BINARY_NAME@ From 657416fe30c96a7bfb3978e8f84c5d0c7951ba27 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 3 Dec 2023 21:29:02 +0200 Subject: [PATCH 048/103] Removed header Signed-off-by: Trial97 --- launcher/modplatform/helpers/ExportToModList.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/modplatform/helpers/ExportToModList.cpp b/launcher/modplatform/helpers/ExportToModList.cpp index 86d051c75..aea16ab50 100644 --- a/launcher/modplatform/helpers/ExportToModList.cpp +++ b/launcher/modplatform/helpers/ExportToModList.cpp @@ -16,7 +16,6 @@ * along with this program. If not, see . */ #include "ExportToModList.h" -#include #include #include #include From 648aa18e6f84be7fbabc330de09beff9f170afa3 Mon Sep 17 00:00:00 2001 From: theMackabu Date: Tue, 12 Dec 2023 14:22:23 -0800 Subject: [PATCH 049/103] add: refresh on load Signed-off-by: theMackabu --- launcher/ui/widgets/ThemeCustomizationWidget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.cpp b/launcher/ui/widgets/ThemeCustomizationWidget.cpp index a0e682bb9..79932e20b 100644 --- a/launcher/ui/widgets/ThemeCustomizationWidget.cpp +++ b/launcher/ui/widgets/ThemeCustomizationWidget.cpp @@ -27,6 +27,7 @@ ThemeCustomizationWidget::ThemeCustomizationWidget(QWidget* parent) : QWidget(pa { ui->setupUi(this); loadSettings(); + ThemeCustomizationWidget::refresh(); connect(ui->iconsComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &ThemeCustomizationWidget::applyIconTheme); connect(ui->widgetStyleComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, From d10b07567797a9e05fcc9996aefb7f1ba99a13b6 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 13 Dec 2023 14:36:58 +0200 Subject: [PATCH 050/103] Sort mods before export Signed-off-by: Trial97 --- launcher/ui/pages/instance/ModFolderPage.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 8dc32a8ff..8746513be 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -129,7 +129,7 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr auto check_allow_update = [this] { return ui->treeView->selectionModel()->hasSelection() || !m_model->empty(); }; connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, - [this, check_allow_update, actionRemoveItemMetadata, actionExportMetadata] { + [this, check_allow_update, actionRemoveItemMetadata] { ui->actionUpdateItem->setEnabled(check_allow_update()); auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); @@ -385,6 +385,7 @@ void ModFolderPage::exportModMetadata() if (selectedMods.length() == 0) selectedMods = m_model->allMods(); + std::sort(selectedMods.begin(), selectedMods.end(), [](const Mod* a, const Mod* b) { return a->name() < b->name(); }); ExportToModListDialog dlg(m_instance->name(), selectedMods, this); dlg.exec(); } From 13d29ac6f466776f13b01f90ba805af0490a41ca Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 22 Dec 2023 23:35:48 +0200 Subject: [PATCH 051/103] Updated the size sort code Signed-off-by: Trial97 --- launcher/minecraft/mod/Resource.cpp | 13 +++++++------ launcher/minecraft/mod/Resource.h | 2 ++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index d8727db0a..2fe7e87d4 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "FileSystem.h" #include "StringUtils.h" @@ -20,7 +21,7 @@ void Resource::setFile(QFileInfo file_info) parseFile(); } -QString calculateFileSize(const QFileInfo& file) +std::tuple calculateFileSize(const QFileInfo& file) { if (file.isDir()) { auto dir = QDir(file.absoluteFilePath()); @@ -29,9 +30,9 @@ QString calculateFileSize(const QFileInfo& file) auto str = QObject::tr("item"); if (count != 1) str = QObject::tr("items"); - return QString("%1 %2").arg(QString::number(count), str); + return { QString("%1 %2").arg(QString::number(count), str), -count }; } - return StringUtils::humanReadableFileSize(file.size(), true); + return { StringUtils::humanReadableFileSize(file.size(), true), file.size() }; } void Resource::parseFile() @@ -42,7 +43,7 @@ void Resource::parseFile() m_internal_id = file_name; - m_size_str = calculateFileSize(m_file_info); + std::tie(m_size_str, m_size_info) = calculateFileSize(m_file_info); if (m_file_info.isDir()) { m_type = ResourceType::FOLDER; m_name = file_name; @@ -107,9 +108,9 @@ std::pair Resource::compare(const Resource& other, SortType type) con return { -1, type == SortType::DATE }; break; case SortType::SIZE: { - if (fileinfo().size() > other.fileinfo().size()) + if (sizeInfo() > other.sizeInfo()) return { 1, type == SortType::SIZE }; - if (fileinfo().size() < other.fileinfo().size()) + if (sizeInfo() < other.sizeInfo()) return { -1, type == SortType::SIZE }; break; } diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index 029afbd5b..74f4d006e 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -46,6 +46,7 @@ class Resource : public QObject { [[nodiscard]] auto type() const -> ResourceType { return m_type; } [[nodiscard]] bool enabled() const { return m_enabled; } [[nodiscard]] QString sizeStr() const { return m_size_str; } + [[nodiscard]] qint64 sizeInfo() const { return m_size_info; } [[nodiscard]] virtual auto name() const -> QString { return m_name; } [[nodiscard]] virtual bool valid() const { return m_type != ResourceType::UNKNOWN; } @@ -119,4 +120,5 @@ class Resource : public QObject { bool m_is_resolved = false; int m_resolution_ticket = 0; QString m_size_str; + qint64 m_size_info; }; From f77749e4486f3a19a75b74f933e6d9de9c0c0a44 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 21 Jan 2024 21:24:47 +0200 Subject: [PATCH 052/103] Resized Refresh theme button Signed-off-by: Trial97 --- .../ui/widgets/ThemeCustomizationWidget.ui | 283 ++++++++++-------- 1 file changed, 157 insertions(+), 126 deletions(-) diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.ui b/launcher/ui/widgets/ThemeCustomizationWidget.ui index 322f9d6a7..1faa45c4f 100644 --- a/launcher/ui/widgets/ThemeCustomizationWidget.ui +++ b/launcher/ui/widgets/ThemeCustomizationWidget.ui @@ -13,7 +13,7 @@ Form - + QLayout::SetMinimumSize @@ -29,150 +29,181 @@ 0 - - - - &Icons - - - iconsComboBox - - - - - - - - - - 0 - 0 - - - - Qt::StrongFocus - - - - - - - View icon themes folder. - + + + + - + &Icons - - - .. - - - true + + iconsComboBox - - - - - - &Widgets - - - widgetStyleComboBox - - - - - - - - - - 0 - 0 - - - - Qt::StrongFocus - - + + + + + + + 0 + 0 + + + + Qt::StrongFocus + + + + + + + View icon themes folder. + + + + + + + + + true + + + + - - - - View widget themes folder. - + + - + &Widgets - - - .. - - - true + + widgetStyleComboBox - - - - - - The cat appears in the background and is not shown by default. It is only made visible when pressing the Cat button in the Toolbar. - - - C&at - - - backgroundCatComboBox - - - - - - - - - - 0 - 0 - - - - Qt::StrongFocus - + + + + + + + 0 + 0 + + + + Qt::StrongFocus + + + + + + + View widget themes folder. + + + + + + + + + true + + + + + + + The cat appears in the background and is not shown by default. It is only made visible when pressing the Cat button in the Toolbar. + + C&at + + + backgroundCatComboBox + - - - - View cat packs folder. - - - - - - - .. - - - true - - + + + + + + + 0 + 0 + + + + Qt::StrongFocus + + + The cat appears in the background and is not shown by default. It is only made visible when pressing the Cat button in the Toolbar. + + + + + + + View cat packs folder. + + + + + + + + + true + + + + - - - - Refresh - - + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Refresh all + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + From 6b637a8797dad184d5ed73a93f00e9f86da22ee0 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Mon, 22 Jan 2024 22:32:42 +0200 Subject: [PATCH 053/103] Display minecraft version if not mentioned for modrinth packs Signed-off-by: Trial97 --- .../modplatform/modrinth/ModrinthPackManifest.cpp | 4 ++++ launcher/modplatform/modrinth/ModrinthPackManifest.h | 1 + .../ui/pages/modplatform/modrinth/ModrinthPage.cpp | 11 ++++++----- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp index 7846e966d..f360df43a 100644 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp @@ -131,6 +131,10 @@ auto loadIndexedVersion(QJsonObject& obj) -> ModpackVersion file.name = Json::requireString(obj, "name"); file.version = Json::requireString(obj, "version_number"); + auto gameVersions = Json::ensureArray(obj, "game_versions"); + if (!gameVersions.isEmpty()) { + file.gameVersion = Json::ensureString(gameVersions[0]); + } file.version_type = ModPlatform::IndexedVersionType(Json::requireString(obj, "version_type")); file.changelog = Json::ensureString(obj, "changelog"); diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.h b/launcher/modplatform/modrinth/ModrinthPackManifest.h index 1ffd31d83..2bd61c5d9 100644 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.h +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.h @@ -84,6 +84,7 @@ struct ModpackExtra { struct ModpackVersion { QString name; QString version; + QString gameVersion; ModPlatform::IndexedVersionType version_type; QString changelog; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index fffa21940..452416edd 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -222,11 +222,12 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI } for (auto version : current.versions) { auto release_type = version.version_type.isValid() ? QString(" [%1]").arg(version.version_type.toString()) : ""; - if (!version.name.contains(version.version)) - ui->versionSelectionBox->addItem(QString("%1 — %2%3").arg(version.name, version.version, release_type), - QVariant(version.id)); - else - ui->versionSelectionBox->addItem(QString("%1%2").arg(version.name, release_type), QVariant(version.id)); + auto mcVersion = !version.gameVersion.isEmpty() && !version.name.contains(version.gameVersion) + ? QString(" for %1").arg(version.gameVersion) + : ""; + auto versionStr = !version.name.contains(version.version) ? version.version : ""; + ui->versionSelectionBox->addItem(QString("%1%2 — %3%4").arg(version.name, mcVersion, versionStr, release_type), + QVariant(version.id)); } QVariant current_updated; From b54410b48cf10821fc3229d5d6989645e95c66f0 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 26 Jan 2024 19:10:39 +0200 Subject: [PATCH 054/103] Added gameVersion for curseforge mod packs Signed-off-by: Trial97 --- launcher/ui/pages/modplatform/flame/FlamePage.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index f1fd9b5d8..0074a9225 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -178,7 +178,11 @@ void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelInde for (auto version : current.versions) { auto release_type = version.version_type.isValid() ? QString(" [%1]").arg(version.version_type.toString()) : ""; - ui->versionSelectionBox->addItem(QString("%1%2").arg(version.version, release_type), QVariant(version.downloadUrl)); + auto mcVersion = !version.mcVersion.isEmpty() && !version.version.contains(version.mcVersion) + ? QString(" for %1").arg(version.mcVersion) + : ""; + ui->versionSelectionBox->addItem(QString("%1%2%3").arg(version.version, mcVersion, release_type), + QVariant(version.downloadUrl)); } QVariant current_updated; From 031a9f4738df8977d8273968c2b3def6cc425fa2 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 10 Feb 2024 11:03:51 +0200 Subject: [PATCH 055/103] Replaced QFile::remove with FS::deletePath Signed-off-by: Trial97 --- launcher/Application.cpp | 9 ++------ launcher/BaseInstaller.cpp | 3 ++- launcher/InstanceCreationTask.cpp | 3 ++- launcher/MMCZip.cpp | 22 +++++++++---------- launcher/icons/IconList.cpp | 2 +- launcher/meta/BaseEntity.cpp | 4 ++-- launcher/minecraft/Component.cpp | 2 +- launcher/minecraft/PackProfile.cpp | 2 +- .../minecraft/mod/ResourceFolderModel.cpp | 2 +- .../atlauncher/ATLPackInstallTask.cpp | 4 ++-- .../modplatform/helpers/OverrideUtils.cpp | 2 +- .../updater/prismupdater/PrismUpdater.cpp | 11 +++------- 12 files changed, 29 insertions(+), 37 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 42343ff8f..25ee8fa69 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -389,20 +389,15 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) { static const QString baseLogFile = BuildConfig.LAUNCHER_NAME + "-%0.log"; static const QString logBase = FS::PathCombine("logs", baseLogFile); - auto moveFile = [](const QString& oldName, const QString& newName) { - QFile::remove(newName); - QFile::copy(oldName, newName); - QFile::remove(oldName); - }; if (FS::ensureFolderPathExists("logs")) { // if this did not fail for (auto i = 0; i <= 4; i++) if (auto oldName = baseLogFile.arg(i); QFile::exists(oldName)) // do not pointlessly delete new files if the old ones are not there - moveFile(oldName, logBase.arg(i)); + FS::move(oldName, logBase.arg(i)); } for (auto i = 4; i > 0; i--) - moveFile(logBase.arg(i - 1), logBase.arg(i)); + FS::move(logBase.arg(i - 1), logBase.arg(i)); logFile = std::unique_ptr(new QFile(logBase.arg(0))); if (!logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { diff --git a/launcher/BaseInstaller.cpp b/launcher/BaseInstaller.cpp index 1ff86ed40..96a3b5ebe 100644 --- a/launcher/BaseInstaller.cpp +++ b/launcher/BaseInstaller.cpp @@ -16,6 +16,7 @@ #include #include "BaseInstaller.h" +#include "FileSystem.h" #include "minecraft/MinecraftInstance.h" BaseInstaller::BaseInstaller() {} @@ -42,7 +43,7 @@ bool BaseInstaller::add(MinecraftInstance* to) bool BaseInstaller::remove(MinecraftInstance* from) { - return QFile::remove(filename(from->instanceRoot())); + return FS::deletePath(filename(from->instanceRoot())); } QString BaseInstaller::filename(const QString& root) const diff --git a/launcher/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp index 73dc17891..9688fa112 100644 --- a/launcher/InstanceCreationTask.cpp +++ b/launcher/InstanceCreationTask.cpp @@ -2,6 +2,7 @@ #include #include +#include "FileSystem.h" InstanceCreationTask::InstanceCreationTask() = default; @@ -47,7 +48,7 @@ void InstanceCreationTask::executeTask() if (!QFile::exists(path)) continue; qDebug() << "Removing" << path; - if (!QFile::remove(path)) { + if (!FS::deletePath(path)) { qCritical() << "Couldn't remove the old conflicting files."; emitFailed(tr("Failed to remove old conflicting files.")); return; diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index b81106a59..b6bfac25d 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -122,7 +122,7 @@ bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, QuaZip zip(fileCompressed); QDir().mkpath(QFileInfo(fileCompressed).absolutePath()); if (!zip.open(QuaZip::mdCreate)) { - QFile::remove(fileCompressed); + FS::deletePath(fileCompressed); return false; } @@ -130,7 +130,7 @@ bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, zip.close(); if (zip.getZipError() != 0) { - QFile::remove(fileCompressed); + FS::deletePath(fileCompressed); return false; } @@ -143,7 +143,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QListtype() == ResourceType::ZIPFILE) { if (!mergeZipFiles(&zipOut, mod->fileinfo(), addedFiles)) { zipOut.close(); - QFile::remove(targetJarPath); + FS::deletePath(targetJarPath); qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar."; return false; } @@ -170,7 +170,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QListfileinfo(); if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName())) { zipOut.close(); - QFile::remove(targetJarPath); + FS::deletePath(targetJarPath); qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar."; return false; } @@ -193,7 +193,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QListfileinfo().fileName() << "to the jar."; return false; } @@ -201,7 +201,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QListfileinfo().fileName() << "to the jar."; return false; } @@ -209,7 +209,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList ZipResult void ExportToZipTask::finish() { if (m_build_zip_future.isCanceled()) { - QFile::remove(m_output_path); + FS::deletePath(m_output_path); emitAborted(); } else if (auto result = m_build_zip_future.result(); result.has_value()) { - QFile::remove(m_output_path); + FS::deletePath(m_output_path); emitFailed(result.value()); } else { emitSucceeded(); diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp index 5576b9745..e4157ea2d 100644 --- a/launcher/icons/IconList.cpp +++ b/launcher/icons/IconList.cpp @@ -322,7 +322,7 @@ const MMCIcon* IconList::icon(const QString& key) const bool IconList::deleteIcon(const QString& key) { - return iconFileExists(key) && QFile::remove(icon(key)->getFilePath()); + return iconFileExists(key) && FS::deletePath(icon(key)->getFilePath()); } bool IconList::trashIcon(const QString& key) diff --git a/launcher/meta/BaseEntity.cpp b/launcher/meta/BaseEntity.cpp index 5f9804e48..8a99e3303 100644 --- a/launcher/meta/BaseEntity.cpp +++ b/launcher/meta/BaseEntity.cpp @@ -15,6 +15,7 @@ #include "BaseEntity.h" +#include "FileSystem.h" #include "Json.h" #include "net/ApiDownload.h" #include "net/HttpMetaCache.h" @@ -83,8 +84,7 @@ bool Meta::BaseEntity::loadLocalFile() } catch (const Exception& e) { qDebug() << QString("Unable to parse file %1: %2").arg(fname, e.cause()); // just make sure it's gone and we never consider it again. - QFile::remove(fname); - return false; + return !FS::deletePath(fname); } } diff --git a/launcher/minecraft/Component.cpp b/launcher/minecraft/Component.cpp index 79ea7a06d..ad2e4023c 100644 --- a/launcher/minecraft/Component.cpp +++ b/launcher/minecraft/Component.cpp @@ -336,7 +336,7 @@ bool Component::revert() bool result = true; // just kill the file and reload if (QFile::exists(filename)) { - result = QFile::remove(filename); + result = FS::deletePath(filename); } if (result) { // file gone... diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index 180f8aa30..4b17cdf07 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -839,7 +839,7 @@ bool PackProfile::installCustomJar_internal(QString filepath) QFileInfo jarInfo(finalPath); if (jarInfo.exists()) { - if (!QFile::remove(finalPath)) { + if (!FS::deletePath(finalPath)) { return false; } } diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 9157f35f0..5bea720d0 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -111,7 +111,7 @@ bool ResourceFolderModel::installResource(QString original_path) case ResourceType::ZIPFILE: case ResourceType::LITEMOD: { if (QFile::exists(new_path) || QFile::exists(new_path + QString(".disabled"))) { - if (!QFile::remove(new_path)) { + if (!FS::deletePath(new_path)) { qCritical() << "Cleaning up new location (" << new_path << ") was unsuccessful!"; return false; } diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 8ae8145de..57660aa6d 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -282,7 +282,7 @@ void PackInstallTask::deleteExistingFiles() // Delete the files for (const auto& item : filesToDelete) { - QFile::remove(item); + FS::deletePath(item); } } @@ -987,7 +987,7 @@ bool PackInstallTask::extractMods(const QMap& toExtract, // the copy from the Configs.zip QFileInfo fileInfo(to); if (fileInfo.exists()) { - if (!QFile::remove(to)) { + if (!FS::deletePath(to)) { qWarning() << "Failed to delete" << to; return false; } diff --git a/launcher/modplatform/helpers/OverrideUtils.cpp b/launcher/modplatform/helpers/OverrideUtils.cpp index 65b5f7603..60983a5cf 100644 --- a/launcher/modplatform/helpers/OverrideUtils.cpp +++ b/launcher/modplatform/helpers/OverrideUtils.cpp @@ -10,7 +10,7 @@ void createOverrides(const QString& name, const QString& parent_folder, const QS { QString file_path(FS::PathCombine(parent_folder, name + ".txt")); if (QFile::exists(file_path)) - QFile::remove(file_path); + FS::deletePath(file_path); FS::ensureFilePathExists(file_path); diff --git a/launcher/updater/prismupdater/PrismUpdater.cpp b/launcher/updater/prismupdater/PrismUpdater.cpp index 5fe22bdd0..8948b3e82 100644 --- a/launcher/updater/prismupdater/PrismUpdater.cpp +++ b/launcher/updater/prismupdater/PrismUpdater.cpp @@ -352,15 +352,10 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar FS::ensureFolderPathExists(FS::PathCombine(m_dataPath, "logs")); static const QString baseLogFile = BuildConfig.LAUNCHER_NAME + "Updater" + (m_checkOnly ? "-CheckOnly" : "") + "-%0.log"; static const QString logBase = FS::PathCombine(m_dataPath, "logs", baseLogFile); - auto moveFile = [](const QString& oldName, const QString& newName) { - QFile::remove(newName); - QFile::copy(oldName, newName); - QFile::remove(oldName); - }; if (FS::ensureFolderPathExists("logs")) { // enough history to track both launches of the updater during a portable install - moveFile(logBase.arg(1), logBase.arg(2)); - moveFile(logBase.arg(0), logBase.arg(1)); + FS::move(logBase.arg(1), logBase.arg(2)); + FS::move(logBase.arg(0), logBase.arg(1)); } logFile = std::unique_ptr(new QFile(logBase.arg(0))); @@ -924,7 +919,7 @@ bool PrismUpdaterApp::callAppImageUpdate() void PrismUpdaterApp::clearUpdateLog() { - QFile::remove(m_updateLogPath); + FS::deletePath(m_updateLogPath); } void PrismUpdaterApp::logUpdate(const QString& msg) From 344398f2382b879689aef7d4d02785faf0d9bff8 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Mon, 26 Feb 2024 21:20:14 +0200 Subject: [PATCH 056/103] Updated ftb app import instance detection Signed-off-by: Trial97 --- .../modplatform/import_ftb/ImportFTBPage.cpp | 10 ++- .../modplatform/import_ftb/ListModel.cpp | 90 ++++++++++++------- .../pages/modplatform/import_ftb/ListModel.h | 16 ++-- 3 files changed, 76 insertions(+), 40 deletions(-) diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp index ac06f4cdd..c72a5a9da 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp +++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp @@ -21,6 +21,7 @@ #include "ui_ImportFTBPage.h" #include +#include #include #include "FileSystem.h" #include "ListModel.h" @@ -58,7 +59,14 @@ ImportFTBPage::ImportFTBPage(NewInstanceDialog* dialog, QWidget* parent) : QWidg connect(ui->searchEdit, &QLineEdit::textChanged, this, &ImportFTBPage::triggerSearch); connect(ui->browseButton, &QPushButton::clicked, this, [this] { - auto path = listModel->getPath(); + auto paths = listModel->getPosiblePaths(); + QString path; + for (auto p : paths) { + if (p != "" && QFileInfo::exists(p)) { + path = p; + break; + } + } QString dir = QFileDialog::getExistingDirectory(this, tr("Select FTBApp instances directory"), path, QFileDialog::ShowDirsOnly); if (!dir.isEmpty()) listModel->setPath(dir); diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp index e058937a6..ad03d571b 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp @@ -24,7 +24,9 @@ #include #include #include "Application.h" +#include "Exception.h" #include "FileSystem.h" +#include "Json.h" #include "StringUtils.h" #include "modplatform/import_ftb/PackHelpers.h" #include "ui/widgets/ProjectItem.h" @@ -41,27 +43,54 @@ QString getStaticPath() #else partialPath = QDir::homePath(); #endif - return FS::PathCombine(partialPath, ".ftba"); + return FS::PathCombine(partialPath, ".ftba", "instances"); } -static const QString FTB_APP_PATH = FS::PathCombine(getStaticPath(), "instances"); +QString getDynamicPath() +{ + auto settingsPath = FS::PathCombine(QDir::homePath(), ".ftba", "bin", "settings.json"); + if (!QFileInfo::exists(settingsPath)) { + qWarning() << "The ftb app setings doesn't exist."; + return {}; + } + try { + auto doc = Json::requireDocument(FS::read(settingsPath)); + return Json::requireString(Json::requireObject(doc), "instanceLocation"); + } catch (const Exception& e) { + qCritical() << "Could not read ftb settings file: " << e.cause(); + } + return {}; +} + +ListModel::ListModel(QObject* parent) : QAbstractListModel(parent), m_static_path(getStaticPath()), m_dynamic_path(getDynamicPath()) {} void ListModel::update() { beginResetModel(); - modpacks.clear(); + m_modpacks.clear(); - QString instancesPath = getPath(); - if (auto instancesInfo = QFileInfo(instancesPath); instancesInfo.exists() && instancesInfo.isDir()) { - QDirIterator directoryIterator(instancesPath, QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable | QDir::Hidden, - QDirIterator::FollowSymlinks); - while (directoryIterator.hasNext()) { - auto modpack = parseDirectory(directoryIterator.next()); - if (!modpack.path.isEmpty()) - modpacks.append(modpack); + auto paths = getPosiblePaths(); + paths.removeDuplicates(); + for (auto instancesPath : paths) { + if (auto instancesInfo = QFileInfo(instancesPath); instancesInfo.exists() && instancesInfo.isDir()) { + QDirIterator directoryIterator(instancesPath, QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable | QDir::Hidden, + QDirIterator::FollowSymlinks); + while (directoryIterator.hasNext()) { + auto currentPath = directoryIterator.next(); + bool wasAdded = false; + for (auto pack : m_modpacks) { + if (pack.path == currentPath) { + wasAdded = true; + break; + } + } + if (!wasAdded) { + auto modpack = parseDirectory(currentPath); + if (!modpack.path.isEmpty()) + m_modpacks.append(modpack); + } + } } - } else { - qDebug() << "Couldn't find ftb instances folder: " << instancesPath; } endResetModel(); @@ -70,11 +99,11 @@ void ListModel::update() QVariant ListModel::data(const QModelIndex& index, int role) const { int pos = index.row(); - if (pos >= modpacks.size() || pos < 0 || !index.isValid()) { + if (pos >= m_modpacks.size() || pos < 0 || !index.isValid()) { return QVariant(); } - auto pack = modpacks.at(pos); + auto pack = m_modpacks.at(pos); if (role == Qt::ToolTipRole) { } @@ -110,9 +139,9 @@ QVariant ListModel::data(const QModelIndex& index, int role) const FilterModel::FilterModel(QObject* parent) : QSortFilterProxyModel(parent) { - currentSorting = Sorting::ByGameVersion; - sortings.insert(tr("Sort by Name"), Sorting::ByName); - sortings.insert(tr("Sort by Game Version"), Sorting::ByGameVersion); + m_currentSorting = Sorting::ByGameVersion; + m_sortings.insert(tr("Sort by Name"), Sorting::ByName); + m_sortings.insert(tr("Sort by Game Version"), Sorting::ByGameVersion); } bool FilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) const @@ -120,12 +149,12 @@ bool FilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) co Modpack leftPack = sourceModel()->data(left, Qt::UserRole).value(); Modpack rightPack = sourceModel()->data(right, Qt::UserRole).value(); - if (currentSorting == Sorting::ByGameVersion) { + if (m_currentSorting == Sorting::ByGameVersion) { Version lv(leftPack.mcVersion); Version rv(rightPack.mcVersion); return lv < rv; - } else if (currentSorting == Sorting::ByName) { + } else if (m_currentSorting == Sorting::ByName) { return StringUtils::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0; } @@ -136,39 +165,39 @@ bool FilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) co bool FilterModel::filterAcceptsRow([[maybe_unused]] int sourceRow, [[maybe_unused]] const QModelIndex& sourceParent) const { - if (searchTerm.isEmpty()) { + if (m_searchTerm.isEmpty()) { return true; } QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); Modpack pack = sourceModel()->data(index, Qt::UserRole).value(); - return pack.name.contains(searchTerm, Qt::CaseInsensitive); + return pack.name.contains(m_searchTerm, Qt::CaseInsensitive); } void FilterModel::setSearchTerm(const QString term) { - searchTerm = term.trimmed(); + m_searchTerm = term.trimmed(); invalidate(); } const QMap FilterModel::getAvailableSortings() { - return sortings; + return m_sortings; } QString FilterModel::translateCurrentSorting() { - return sortings.key(currentSorting); + return m_sortings.key(m_currentSorting); } void FilterModel::setSorting(Sorting s) { - currentSorting = s; + m_currentSorting = s; invalidate(); } FilterModel::Sorting FilterModel::getCurrentSorting() { - return currentSorting; + return m_currentSorting; } void ListModel::setPath(QString path) { @@ -176,11 +205,8 @@ void ListModel::setPath(QString path) update(); } -QString ListModel::getPath() +QStringList ListModel::getPosiblePaths() { - auto path = APPLICATION->settings()->get("FTBAppInstancesPath").toString(); - if (path.isEmpty() || !QFileInfo(path).exists()) - path = FTB_APP_PATH; - return path; + return { APPLICATION->settings()->get("FTBAppInstancesPath").toString(), m_dynamic_path, m_static_path }; } } // namespace FTBImportAPP \ No newline at end of file diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.h b/launcher/ui/pages/modplatform/import_ftb/ListModel.h index ed33a88f3..393836b26 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ListModel.h +++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.h @@ -42,28 +42,30 @@ class FilterModel : public QSortFilterProxyModel { bool lessThan(const QModelIndex& left, const QModelIndex& right) const override; private: - QMap sortings; - Sorting currentSorting; - QString searchTerm; + QMap m_sortings; + Sorting m_currentSorting; + QString m_searchTerm; }; class ListModel : public QAbstractListModel { Q_OBJECT public: - ListModel(QObject* parent) : QAbstractListModel(parent) {} + ListModel(QObject* parent); virtual ~ListModel() = default; - int rowCount(const QModelIndex& parent) const { return modpacks.size(); } + int rowCount(const QModelIndex& parent) const { return m_modpacks.size(); } int columnCount(const QModelIndex& parent) const { return 1; } QVariant data(const QModelIndex& index, int role) const; void update(); - QString getPath(); + QStringList getPosiblePaths(); void setPath(QString path); private: - ModpackList modpacks; + ModpackList m_modpacks; + const QString m_static_path; + const QString m_dynamic_path; }; } // namespace FTBImportAPP \ No newline at end of file From 27e76d0dcb69d0f34fa64fca89964b231c194387 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 27 Feb 2024 17:11:19 +0200 Subject: [PATCH 057/103] Removed static path Signed-off-by: Trial97 --- .../modplatform/import_ftb/ImportFTBPage.cpp | 11 +-- .../modplatform/import_ftb/ListModel.cpp | 69 ++++++++++--------- .../pages/modplatform/import_ftb/ListModel.h | 5 +- 3 files changed, 40 insertions(+), 45 deletions(-) diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp index c72a5a9da..db59fe10a 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp +++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp @@ -59,15 +59,8 @@ ImportFTBPage::ImportFTBPage(NewInstanceDialog* dialog, QWidget* parent) : QWidg connect(ui->searchEdit, &QLineEdit::textChanged, this, &ImportFTBPage::triggerSearch); connect(ui->browseButton, &QPushButton::clicked, this, [this] { - auto paths = listModel->getPosiblePaths(); - QString path; - for (auto p : paths) { - if (p != "" && QFileInfo::exists(p)) { - path = p; - break; - } - } - QString dir = QFileDialog::getExistingDirectory(this, tr("Select FTBApp instances directory"), path, QFileDialog::ShowDirsOnly); + QString dir = QFileDialog::getExistingDirectory(this, tr("Select FTBApp instances directory"), listModel->getUserPath(), + QFileDialog::ShowDirsOnly); if (!dir.isEmpty()) listModel->setPath(dir); }); diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp index ad03d571b..52671ea8e 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp @@ -33,22 +33,18 @@ namespace FTBImportAPP { -QString getStaticPath() +QString getFTBRoot() { - QString partialPath; + QString partialPath = QDir::homePath(); #if defined(Q_OS_OSX) - partialPath = FS::PathCombine(QDir::homePath(), "Library/Application Support"); -#elif defined(Q_OS_WIN32) - partialPath = QProcessEnvironment::systemEnvironment().value("LOCALAPPDATA", ""); -#else - partialPath = QDir::homePath(); + partialPath = FS::PathCombine(partialPath, "Library/Application Support"); #endif - return FS::PathCombine(partialPath, ".ftba", "instances"); + return FS::PathCombine(partialPath, ".ftba"); } QString getDynamicPath() { - auto settingsPath = FS::PathCombine(QDir::homePath(), ".ftba", "bin", "settings.json"); + auto settingsPath = FS::PathCombine(getFTBRoot(), "bin", "settings.json"); if (!QFileInfo::exists(settingsPath)) { qWarning() << "The ftb app setings doesn't exist."; return {}; @@ -62,36 +58,40 @@ QString getDynamicPath() return {}; } -ListModel::ListModel(QObject* parent) : QAbstractListModel(parent), m_static_path(getStaticPath()), m_dynamic_path(getDynamicPath()) {} +ListModel::ListModel(QObject* parent) : QAbstractListModel(parent), m_instances_path(getDynamicPath()) {} void ListModel::update() { beginResetModel(); m_modpacks.clear(); - auto paths = getPosiblePaths(); - paths.removeDuplicates(); - for (auto instancesPath : paths) { - if (auto instancesInfo = QFileInfo(instancesPath); instancesInfo.exists() && instancesInfo.isDir()) { - QDirIterator directoryIterator(instancesPath, QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable | QDir::Hidden, - QDirIterator::FollowSymlinks); - while (directoryIterator.hasNext()) { - auto currentPath = directoryIterator.next(); - bool wasAdded = false; - for (auto pack : m_modpacks) { - if (pack.path == currentPath) { - wasAdded = true; - break; - } - } - if (!wasAdded) { - auto modpack = parseDirectory(currentPath); - if (!modpack.path.isEmpty()) - m_modpacks.append(modpack); - } + auto wasPathAdded = [this](QString path) { + for (auto pack : m_modpacks) { + if (pack.path == path) + return true; + } + return false; + }; + + auto scanPath = [this, wasPathAdded](QString path) { + if (path.isEmpty()) + return; + if (auto instancesInfo = QFileInfo(path); !instancesInfo.exists() || !instancesInfo.isDir()) + return; + QDirIterator directoryIterator(path, QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable | QDir::Hidden, + QDirIterator::FollowSymlinks); + while (directoryIterator.hasNext()) { + auto currentPath = directoryIterator.next(); + if (!wasPathAdded(currentPath)) { + auto modpack = parseDirectory(currentPath); + if (!modpack.path.isEmpty()) + m_modpacks.append(modpack); } } - } + }; + + scanPath(APPLICATION->settings()->get("FTBAppInstancesPath").toString()); + scanPath(m_instances_path); endResetModel(); } @@ -205,8 +205,11 @@ void ListModel::setPath(QString path) update(); } -QStringList ListModel::getPosiblePaths() +QString ListModel::getUserPath() { - return { APPLICATION->settings()->get("FTBAppInstancesPath").toString(), m_dynamic_path, m_static_path }; + auto path = APPLICATION->settings()->get("FTBAppInstancesPath").toString(); + if (path.isEmpty()) + path = m_instances_path; + return path; } } // namespace FTBImportAPP \ No newline at end of file diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.h b/launcher/ui/pages/modplatform/import_ftb/ListModel.h index 393836b26..a842ac8ff 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ListModel.h +++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.h @@ -60,12 +60,11 @@ class ListModel : public QAbstractListModel { void update(); - QStringList getPosiblePaths(); + QString getUserPath(); void setPath(QString path); private: ModpackList m_modpacks; - const QString m_static_path; - const QString m_dynamic_path; + const QString m_instances_path; }; } // namespace FTBImportAPP \ No newline at end of file From 1c3c9d24573d4f3cf25580129d3611f5082dcc29 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 7 Apr 2024 13:48:34 +0300 Subject: [PATCH 058/103] Added the new ftb settings path Signed-off-by: Trial97 --- launcher/ui/pages/modplatform/import_ftb/ListModel.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp index 52671ea8e..f3c737977 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp @@ -44,7 +44,9 @@ QString getFTBRoot() QString getDynamicPath() { - auto settingsPath = FS::PathCombine(getFTBRoot(), "bin", "settings.json"); + auto settingsPath = FS::PathCombine(getFTBRoot(), "storage", "settings.json"); + if (!QFileInfo::exists(settingsPath)) + settingsPath = FS::PathCombine(getFTBRoot(), "bin", "settings.json"); if (!QFileInfo::exists(settingsPath)) { qWarning() << "The ftb app setings doesn't exist."; return {}; From 89230c4f8a69a66b46fdd00544ccfaf444a13652 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 20 Apr 2024 18:01:25 +0300 Subject: [PATCH 059/103] Moved the export modlist out of the sub-menu Signed-off-by: Trial97 --- .../ui/pages/instance/ExternalResourcesPage.ui | 17 ++++++++++++++--- launcher/ui/pages/instance/ModFolderPage.cpp | 7 +++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.ui b/launcher/ui/pages/instance/ExternalResourcesPage.ui index ff08e12d2..9d6f61db0 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.ui +++ b/launcher/ui/pages/instance/ExternalResourcesPage.ui @@ -70,15 +70,15 @@ - - true - Actions Qt::ToolButtonTextOnly + + true + RightToolBarArea @@ -171,6 +171,17 @@ Try to check or update all selected resources (all resources if none are selected)
    + + + true + + + Export modlist + + + Export mod's metadata to text + + diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 8b9924470..f045ab6c0 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -112,10 +112,6 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr connect(actionRemoveItemMetadata, &QAction::triggered, this, &ModFolderPage::deleteModMetadata); actionRemoveItemMetadata->setEnabled(false); - auto actionExportMetadata = updateMenu->addAction(tr("Export metadata")); - actionExportMetadata->setToolTip(tr("Export mod's metadata to text")); - connect(actionExportMetadata, &QAction::triggered, this, &ModFolderPage::exportModMetadata); - ui->actionUpdateItem->setMenu(updateMenu); ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected mods (all mods if none are selected)")); @@ -126,6 +122,9 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr ui->actionsToolbar->addAction(ui->actionVisitItemPage); connect(ui->actionVisitItemPage, &QAction::triggered, this, &ModFolderPage::visitModPages); + ui->actionsToolbar->insertActionAfter(ui->actionVisitItemPage, ui->actionExportMetadata); + connect(ui->actionExportMetadata, &QAction::triggered, this, &ModFolderPage::exportModMetadata); + auto check_allow_update = [this] { return ui->treeView->selectionModel()->hasSelection() || !m_model->empty(); }; connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, From dedea2c05d8503fea0d6b7c0adea25e64c8d511c Mon Sep 17 00:00:00 2001 From: SabrePenguin Date: Thu, 25 Apr 2024 14:56:22 -0400 Subject: [PATCH 060/103] Added a naive implementation of a
    inserter Signed-off-by: SabrePenguin --- launcher/Markdown.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/launcher/Markdown.cpp b/launcher/Markdown.cpp index 426067bf6..8b1f11f33 100644 --- a/launcher/Markdown.cpp +++ b/launcher/Markdown.cpp @@ -24,7 +24,15 @@ QString markdownToHTML(const QString& markdown) char* buffer = cmark_markdown_to_html(markdownData.constData(), markdownData.length(), CMARK_OPT_NOBREAKS | CMARK_OPT_UNSAFE); QString htmlStr(buffer); - + int first_pos = htmlStr.indexOf(""); + int img_pos = 0; + while( first_pos != -1 ) + { + img_pos = htmlStr.indexOf(""); + first_pos = htmlStr.indexOf("", first_pos+5); + } free(buffer); return htmlStr; From 2cfe482298826bf0bdb948cca65f72930e142859 Mon Sep 17 00:00:00 2001 From: SabrePenguin Date: Thu, 25 Apr 2024 15:40:24 -0400 Subject: [PATCH 061/103] Formatting and fixed img_pos allowed as negative Signed-off-by: SabrePenguin --- launcher/Markdown.cpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/launcher/Markdown.cpp b/launcher/Markdown.cpp index 8b1f11f33..31e026dc7 100644 --- a/launcher/Markdown.cpp +++ b/launcher/Markdown.cpp @@ -24,16 +24,20 @@ QString markdownToHTML(const QString& markdown) char* buffer = cmark_markdown_to_html(markdownData.constData(), markdownData.length(), CMARK_OPT_NOBREAKS | CMARK_OPT_UNSAFE); QString htmlStr(buffer); - int first_pos = htmlStr.indexOf(""); - int img_pos = 0; - while( first_pos != -1 ) - { - img_pos = htmlStr.indexOf(""); - first_pos = htmlStr.indexOf("", first_pos+5); - } + free(buffer); + + // Insert a breakpoint between a and tag as this can cause visual bugs + int first_pos = htmlStr.indexOf(""); + int img_pos; + while (first_pos != -1) { + img_pos = htmlStr.indexOf(" -1) // 5 is the size of the tag + htmlStr.insert(img_pos, "
    "); + + first_pos = htmlStr.indexOf("", first_pos + 5); + } return htmlStr; } \ No newline at end of file From 6ecd2c7f314d0e5fe7c8ce18ab520e3a38e8d717 Mon Sep 17 00:00:00 2001 From: SabrePenguin <147069705+SabrePenguin@users.noreply.github.com> Date: Fri, 26 Apr 2024 16:51:23 -0400 Subject: [PATCH 062/103] Update launcher/Markdown.cpp Co-authored-by: Alexandru Ionut Tripon Signed-off-by: SabrePenguin <147069705+SabrePenguin@users.noreply.github.com> --- launcher/Markdown.cpp | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/launcher/Markdown.cpp b/launcher/Markdown.cpp index 31e026dc7..f9081057c 100644 --- a/launcher/Markdown.cpp +++ b/launcher/Markdown.cpp @@ -26,18 +26,23 @@ QString markdownToHTML(const QString& markdown) QString htmlStr(buffer); free(buffer); - - // Insert a breakpoint between a and tag as this can cause visual bugs - int first_pos = htmlStr.indexOf(""); - int img_pos; - while (first_pos != -1) { - img_pos = htmlStr.indexOf(" -1) // 5 is the size of the tag - htmlStr.insert(img_pos, "
    "); + int pos = htmlStr.indexOf(""); + int imgPos; + while (pos != -1) { + pos = pos + 5; // 5 is the size of the tag + imgPos = htmlStr.indexOf("", first_pos + 5); + auto textBetween = htmlStr.mid(pos, imgPos - pos).trimmed(); // trim all white spaces + + if (textBetween.isEmpty()) + htmlStr.insert(pos, "
    "); + + pos = htmlStr.indexOf("", pos); } + return htmlStr; } \ No newline at end of file From a988415028fafb1414420573b13d4e1113efe03e Mon Sep 17 00:00:00 2001 From: SabrePenguin <147069705+SabrePenguin@users.noreply.github.com> Date: Fri, 26 Apr 2024 16:59:51 -0400 Subject: [PATCH 063/103] Pleasing the format check Co-authored-by: Alexandru Ionut Tripon Signed-off-by: SabrePenguin <147069705+SabrePenguin@users.noreply.github.com> --- launcher/Markdown.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/Markdown.cpp b/launcher/Markdown.cpp index f9081057c..1e7fb9f35 100644 --- a/launcher/Markdown.cpp +++ b/launcher/Markdown.cpp @@ -43,6 +43,5 @@ QString markdownToHTML(const QString& markdown) pos = htmlStr.indexOf("", pos); } - return htmlStr; } \ No newline at end of file From 5348a90a15676ed178f790648d60f4d3c3c23cf1 Mon Sep 17 00:00:00 2001 From: SabrePenguin <147069705+SabrePenguin@users.noreply.github.com> Date: Fri, 26 Apr 2024 18:21:17 -0400 Subject: [PATCH 064/103] Fix img tag allowing img.... Co-authored-by: TheKodeToad Signed-off-by: SabrePenguin <147069705+SabrePenguin@users.noreply.github.com> --- launcher/Markdown.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/Markdown.cpp b/launcher/Markdown.cpp index 1e7fb9f35..4ee1c0d28 100644 --- a/launcher/Markdown.cpp +++ b/launcher/Markdown.cpp @@ -31,7 +31,7 @@ QString markdownToHTML(const QString& markdown) int imgPos; while (pos != -1) { pos = pos + 5; // 5 is the size of the tag - imgPos = htmlStr.indexOf(" Date: Tue, 30 Apr 2024 22:40:04 -0400 Subject: [PATCH 065/103] Moved html patch to StringUtils Signed-off-by: SabrePenguin --- launcher/Markdown.cpp | 18 +----------------- launcher/StringUtils.cpp | 20 ++++++++++++++++++++ launcher/StringUtils.h | 2 ++ 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/launcher/Markdown.cpp b/launcher/Markdown.cpp index 4ee1c0d28..a624791df 100644 --- a/launcher/Markdown.cpp +++ b/launcher/Markdown.cpp @@ -26,22 +26,6 @@ QString markdownToHTML(const QString& markdown) QString htmlStr(buffer); free(buffer); - - int pos = htmlStr.indexOf(""); - int imgPos; - while (pos != -1) { - pos = pos + 5; // 5 is the size of the tag - imgPos = htmlStr.indexOf(""); - - pos = htmlStr.indexOf("", pos); - } - + return htmlStr; } \ No newline at end of file diff --git a/launcher/StringUtils.cpp b/launcher/StringUtils.cpp index 72ccdfbff..db53f353e 100644 --- a/launcher/StringUtils.cpp +++ b/launcher/StringUtils.cpp @@ -212,3 +212,23 @@ QPair StringUtils::splitFirst(const QString& s, const QRegular right = s.mid(end); return qMakePair(left, right); } + +QString StringUtils::htmlListPatch(QString htmlStr) +{ + int pos = htmlStr.indexOf(""); + int imgPos; + while (pos != -1) { + pos = pos + 5; // 5 is the size of the tag + imgPos = htmlStr.indexOf(""); + + pos = htmlStr.indexOf("", pos); + } + return htmlStr; +} \ No newline at end of file diff --git a/launcher/StringUtils.h b/launcher/StringUtils.h index 9d2bdd85e..624ee41a3 100644 --- a/launcher/StringUtils.h +++ b/launcher/StringUtils.h @@ -85,4 +85,6 @@ QPair splitFirst(const QString& s, const QString& sep, Qt::Cas QPair splitFirst(const QString& s, QChar sep, Qt::CaseSensitivity cs = Qt::CaseSensitive); QPair splitFirst(const QString& s, const QRegularExpression& re); +QString htmlListPatch(QString htmlStr); + } // namespace StringUtils From e3d64608b947eeb774a4ff317c3984bcac8d83be Mon Sep 17 00:00:00 2001 From: SabrePenguin Date: Tue, 30 Apr 2024 23:28:50 -0400 Subject: [PATCH 066/103] Switched to a Regex expression Signed-off-by: SabrePenguin --- launcher/StringUtils.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/launcher/StringUtils.cpp b/launcher/StringUtils.cpp index db53f353e..125b8ca6f 100644 --- a/launcher/StringUtils.cpp +++ b/launcher/StringUtils.cpp @@ -215,11 +215,13 @@ QPair StringUtils::splitFirst(const QString& s, const QRegular QString StringUtils::htmlListPatch(QString htmlStr) { - int pos = htmlStr.indexOf(""); - int imgPos; + QRegularExpression match("|"); + int pos = htmlStr.indexOf(match); + int imgPos, dist; while (pos != -1) { - pos = pos + 5; // 5 is the size of the tag - imgPos = htmlStr.indexOf("", pos) - pos + 1; // Get the size of the tag. Add one for zeroeth index + pos = pos + dist; + imgPos = htmlStr.indexOf(""); - pos = htmlStr.indexOf("", pos); + pos = htmlStr.indexOf(match, pos); } return htmlStr; } \ No newline at end of file From b9c010ffb2bcf8c08a38014079064eeba0af2fd9 Mon Sep 17 00:00:00 2001 From: SabrePenguin Date: Tue, 30 Apr 2024 23:43:36 -0400 Subject: [PATCH 067/103] Added patch to all setHtml calls Signed-off-by: SabrePenguin --- launcher/ui/dialogs/AboutDialog.cpp | 5 +++-- launcher/ui/dialogs/ExportToModListDialog.cpp | 5 +++-- launcher/ui/dialogs/ModUpdateDialog.cpp | 3 ++- launcher/ui/dialogs/UpdateAvailableDialog.cpp | 3 ++- launcher/ui/pages/instance/ManagedPackPage.cpp | 8 +++++--- launcher/ui/pages/modplatform/ResourcePage.cpp | 5 +++-- launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp | 3 ++- launcher/ui/pages/modplatform/flame/FlamePage.cpp | 3 ++- launcher/ui/pages/modplatform/legacy_ftb/Page.cpp | 7 +++++-- launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp | 3 ++- launcher/ui/pages/modplatform/technic/TechnicPage.cpp | 3 ++- launcher/updater/prismupdater/UpdaterDialogs.cpp | 3 ++- 12 files changed, 33 insertions(+), 18 deletions(-) diff --git a/launcher/ui/dialogs/AboutDialog.cpp b/launcher/ui/dialogs/AboutDialog.cpp index 17b79ecaa..40c852c37 100644 --- a/launcher/ui/dialogs/AboutDialog.cpp +++ b/launcher/ui/dialogs/AboutDialog.cpp @@ -39,6 +39,7 @@ #include "BuildConfig.h" #include "Markdown.h" #include "ui_AboutDialog.h" +#include "StringUtils.h" #include #include @@ -139,10 +140,10 @@ AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDia setWindowTitle(tr("About %1").arg(launcherName)); QString chtml = getCreditsHtml(); - ui->creditsText->setHtml(chtml); + ui->creditsText->setHtml(StringUtils::htmlListPatch(chtml)); QString lhtml = getLicenseHtml(); - ui->licenseText->setHtml(lhtml); + ui->licenseText->setHtml(StringUtils::htmlListPatch(lhtml)); ui->urlLabel->setOpenExternalLinks(true); diff --git a/launcher/ui/dialogs/ExportToModListDialog.cpp b/launcher/ui/dialogs/ExportToModListDialog.cpp index a343f555a..ab85cf584 100644 --- a/launcher/ui/dialogs/ExportToModListDialog.cpp +++ b/launcher/ui/dialogs/ExportToModListDialog.cpp @@ -26,6 +26,7 @@ #include "minecraft/mod/ModFolderModel.h" #include "modplatform/helpers/ExportToModList.h" #include "ui_ExportToModListDialog.h" +#include "StringUtils.h" #include #include @@ -143,10 +144,10 @@ void ExportToModListDialog::triggerImp() case ExportToModList::CUSTOM: return; case ExportToModList::HTML: - ui->resultText->setHtml(txt); + ui->resultText->setHtml(StringUtils::htmlListPatch(txt)); break; case ExportToModList::MARKDOWN: - ui->resultText->setHtml(markdownToHTML(txt)); + ui->resultText->setHtml(StringUtils::htmlListPatch(markdownToHTML(txt))); break; case ExportToModList::PLAINTXT: break; diff --git a/launcher/ui/dialogs/ModUpdateDialog.cpp b/launcher/ui/dialogs/ModUpdateDialog.cpp index 54893d775..a08b36ec1 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.cpp +++ b/launcher/ui/dialogs/ModUpdateDialog.cpp @@ -7,6 +7,7 @@ #include "modplatform/ModIndex.h" #include "modplatform/flame/FlameAPI.h" #include "ui_ReviewMessageBox.h" +#include "StringUtils.h" #include "Markdown.h" @@ -473,7 +474,7 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStri break; } - changelog_area->setHtml(text); + changelog_area->setHtml(StringUtils::htmlListPatch(text)); changelog_area->setOpenExternalLinks(true); changelog_area->setLineWrapMode(QTextBrowser::LineWrapMode::WidgetWidth); changelog_area->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); diff --git a/launcher/ui/dialogs/UpdateAvailableDialog.cpp b/launcher/ui/dialogs/UpdateAvailableDialog.cpp index 5eebe87a3..797b763c2 100644 --- a/launcher/ui/dialogs/UpdateAvailableDialog.cpp +++ b/launcher/ui/dialogs/UpdateAvailableDialog.cpp @@ -26,6 +26,7 @@ #include "BuildConfig.h" #include "Markdown.h" #include "ui_UpdateAvailableDialog.h" +#include "StringUtils.h" UpdateAvailableDialog::UpdateAvailableDialog(const QString& currentVersion, const QString& availableVersion, @@ -43,7 +44,7 @@ UpdateAvailableDialog::UpdateAvailableDialog(const QString& currentVersion, ui->icon->setPixmap(APPLICATION->getThemedIcon("checkupdate").pixmap(64)); auto releaseNotesHtml = markdownToHTML(releaseNotes); - ui->releaseNotes->setHtml(releaseNotesHtml); + ui->releaseNotes->setHtml(StringUtils::htmlListPatch(releaseNotesHtml)); ui->releaseNotes->setOpenExternalLinks(true); connect(ui->skipButton, &QPushButton::clicked, this, [this]() { diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index 2210d0263..9c3fcc354 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -20,6 +20,7 @@ #include "InstanceTask.h" #include "Json.h" #include "Markdown.h" +#include "StringUtils.h" #include "modplatform/modrinth/ModrinthPackManifest.h" @@ -332,7 +333,7 @@ void ModrinthManagedPackPage::suggestVersion() } auto version = m_pack.versions.at(index); - ui->changelogTextBrowser->setHtml(markdownToHTML(version.changelog.toUtf8())); + ui->changelogTextBrowser->setHtml(StringUtils::htmlListPatch(markdownToHTML(version.changelog.toUtf8()))); ManagedPackPage::suggestVersion(); } @@ -420,7 +421,7 @@ void FlameManagedPackPage::parseManagedPack() "Don't worry though, it will ask you to update this instance instead, so you'll not lose this instance!" ""); - ui->changelogTextBrowser->setHtml(message); + ui->changelogTextBrowser->setHtml(StringUtils::htmlListPatch(message)); return; } @@ -502,7 +503,8 @@ void FlameManagedPackPage::suggestVersion() } auto version = m_pack.versions.at(index); - ui->changelogTextBrowser->setHtml(m_api.getModFileChangelog(m_inst->getManagedPackID().toInt(), version.fileId)); + ui->changelogTextBrowser->setHtml(StringUtils::htmlListPatch( + m_api.getModFileChangelog(m_inst->getManagedPackID().toInt(), version.fileId))); ManagedPackPage::suggestVersion(); } diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index ae48e5523..b9c706c6c 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -45,6 +45,7 @@ #include "Markdown.h" +#include "StringUtils.h" #include "ui/dialogs/ResourceDownloadDialog.h" #include "ui/pages/modplatform/ResourceModel.h" #include "ui/widgets/ProjectItem.h" @@ -234,8 +235,8 @@ void ResourcePage::updateUi() text += "
    "; - m_ui->packDescription->setHtml( - text + (current_pack->extraData.body.isEmpty() ? current_pack->description : markdownToHTML(current_pack->extraData.body))); + m_ui->packDescription->setHtml(StringUtils::htmlListPatch( + text + (current_pack->extraData.body.isEmpty() ? current_pack->description : markdownToHTML(current_pack->extraData.body)))); m_ui->packDescription->flush(); } diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp index e492830c6..d79b7621a 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp @@ -39,6 +39,7 @@ #include "ui_AtlPage.h" #include "BuildConfig.h" +#include "StringUtils.h" #include "AtlUserInteractionSupportImpl.h" #include "modplatform/atlauncher/ATLPackInstallTask.h" @@ -144,7 +145,7 @@ void AtlPage::onSelectionChanged(QModelIndex first, [[maybe_unused]] QModelIndex selected = filterModel->data(first, Qt::UserRole).value(); - ui->packDescription->setHtml(selected.description.replace("\n", "
    ")); + ui->packDescription->setHtml(StringUtils::htmlListPatch(selected.description.replace("\n", "
    "))); for (const auto& version : selected.versions) { ui->versionSelectionBox->addItem(version.version); diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index f1fd9b5d8..e851e47be 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -46,6 +46,7 @@ #include "modplatform/flame/FlameAPI.h" #include "ui/dialogs/NewInstanceDialog.h" #include "ui/widgets/ProjectItem.h" +#include "StringUtils.h" #include "net/ApiDownload.h" @@ -292,6 +293,6 @@ void FlamePage::updateUi() text += "
    "; text += api.getModDescription(current.addonId).toUtf8(); - ui->packDescription->setHtml(text + current.description); + ui->packDescription->setHtml(StringUtils::htmlListPatch(text + current.description)); ui->packDescription->flush(); } diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp index 0ecaf4625..683d2e81d 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp @@ -37,6 +37,7 @@ #include "Page.h" #include "ui/widgets/ProjectItem.h" #include "ui_Page.h" +#include "StringUtils.h" #include @@ -260,8 +261,10 @@ void Page::onPackSelectionChanged(Modpack* pack) { ui->versionSelectionBox->clear(); if (pack) { - currentModpackInfo->setHtml("Pack by " + pack->author + "" + "
    Minecraft " + pack->mcVersion + "
    " + "
    " + - pack->description + "
    • " + pack->mods.replace(";", "
    • ") + "
    "); + currentModpackInfo->setHtml(StringUtils::htmlListPatch( + "Pack by " + pack->author + "" + "
    Minecraft " + pack->mcVersion + + "
    " + "
    " + + pack->description + "
    • " + pack->mods.replace(";", "
    • ") + "
    ")); bool currentAdded = false; for (int i = 0; i < pack->oldVersions.size(); i++) { diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index da5fe1e7b..b44afda7e 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -44,6 +44,7 @@ #include "InstanceImportTask.h" #include "Json.h" #include "Markdown.h" +#include "StringUtils.h" #include "ui/widgets/ProjectItem.h" @@ -304,7 +305,7 @@ void ModrinthPage::updateUI() text += markdownToHTML(current.extra.body.toUtf8()); - ui->packDescription->setHtml(text + current.description); + ui->packDescription->setHtml(StringUtils::htmlListPatch(text + current.description)); ui->packDescription->flush(); } diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp index 6b1ec8cb5..72b7814c9 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp @@ -47,6 +47,7 @@ #include "TechnicModel.h" #include "modplatform/technic/SingleZipPackInstallTask.h" #include "modplatform/technic/SolderPackInstallTask.h" +#include "StringUtils.h" #include "Application.h" #include "modplatform/technic/SolderPackManifest.h" @@ -233,7 +234,7 @@ void TechnicPage::metadataLoaded() text += "

    "; - ui->packDescription->setHtml(text + current.description); + ui->packDescription->setHtml(StringUtils::htmlListPatch(text + current.description)); // Strip trailing forward-slashes from Solder URL's if (current.isSolder) { diff --git a/launcher/updater/prismupdater/UpdaterDialogs.cpp b/launcher/updater/prismupdater/UpdaterDialogs.cpp index 395b658db..06dc161b1 100644 --- a/launcher/updater/prismupdater/UpdaterDialogs.cpp +++ b/launcher/updater/prismupdater/UpdaterDialogs.cpp @@ -26,6 +26,7 @@ #include #include "Markdown.h" +#include "StringUtils.h" SelectReleaseDialog::SelectReleaseDialog(const Version& current_version, const QList& releases, QWidget* parent) : QDialog(parent), m_releases(releases), m_currentVersion(current_version), ui(new Ui::SelectReleaseDialog) @@ -96,7 +97,7 @@ void SelectReleaseDialog::selectionChanged(QTreeWidgetItem* current, QTreeWidget QString body = markdownToHTML(release.body.toUtf8()); m_selectedRelease = release; - ui->changelogTextBrowser->setHtml(body); + ui->changelogTextBrowser->setHtml(StringUtils::htmlListPatch(body)); } SelectReleaseAssetDialog::SelectReleaseAssetDialog(const QList& assets, QWidget* parent) From 51f4ede7977c3bb6d0d2f3866d3faf57f3b43dab Mon Sep 17 00:00:00 2001 From: SabrePenguin Date: Wed, 1 May 2024 00:11:53 -0400 Subject: [PATCH 068/103] Fixing CI format issues Signed-off-by: SabrePenguin --- launcher/ui/dialogs/AboutDialog.cpp | 2 +- launcher/ui/dialogs/ExportToModListDialog.cpp | 2 +- launcher/ui/dialogs/ModUpdateDialog.cpp | 2 +- launcher/ui/dialogs/UpdateAvailableDialog.cpp | 2 +- launcher/ui/pages/instance/ManagedPackPage.cpp | 4 ++-- launcher/ui/pages/modplatform/flame/FlamePage.cpp | 2 +- launcher/ui/pages/modplatform/legacy_ftb/Page.cpp | 9 ++++----- launcher/ui/pages/modplatform/technic/TechnicPage.cpp | 2 +- 8 files changed, 12 insertions(+), 13 deletions(-) diff --git a/launcher/ui/dialogs/AboutDialog.cpp b/launcher/ui/dialogs/AboutDialog.cpp index 40c852c37..b652ba991 100644 --- a/launcher/ui/dialogs/AboutDialog.cpp +++ b/launcher/ui/dialogs/AboutDialog.cpp @@ -38,8 +38,8 @@ #include "Application.h" #include "BuildConfig.h" #include "Markdown.h" -#include "ui_AboutDialog.h" #include "StringUtils.h" +#include "ui_AboutDialog.h" #include #include diff --git a/launcher/ui/dialogs/ExportToModListDialog.cpp b/launcher/ui/dialogs/ExportToModListDialog.cpp index ab85cf584..7debf0cd8 100644 --- a/launcher/ui/dialogs/ExportToModListDialog.cpp +++ b/launcher/ui/dialogs/ExportToModListDialog.cpp @@ -22,11 +22,11 @@ #include #include "FileSystem.h" #include "Markdown.h" +#include "StringUtils.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/mod/ModFolderModel.h" #include "modplatform/helpers/ExportToModList.h" #include "ui_ExportToModListDialog.h" -#include "StringUtils.h" #include #include diff --git a/launcher/ui/dialogs/ModUpdateDialog.cpp b/launcher/ui/dialogs/ModUpdateDialog.cpp index a08b36ec1..6649bee4e 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.cpp +++ b/launcher/ui/dialogs/ModUpdateDialog.cpp @@ -3,11 +3,11 @@ #include "CustomMessageBox.h" #include "ProgressDialog.h" #include "ScrollMessageBox.h" +#include "StringUtils.h" #include "minecraft/mod/tasks/GetModDependenciesTask.h" #include "modplatform/ModIndex.h" #include "modplatform/flame/FlameAPI.h" #include "ui_ReviewMessageBox.h" -#include "StringUtils.h" #include "Markdown.h" diff --git a/launcher/ui/dialogs/UpdateAvailableDialog.cpp b/launcher/ui/dialogs/UpdateAvailableDialog.cpp index 797b763c2..810a1f089 100644 --- a/launcher/ui/dialogs/UpdateAvailableDialog.cpp +++ b/launcher/ui/dialogs/UpdateAvailableDialog.cpp @@ -25,8 +25,8 @@ #include "Application.h" #include "BuildConfig.h" #include "Markdown.h" -#include "ui_UpdateAvailableDialog.h" #include "StringUtils.h" +#include "ui_UpdateAvailableDialog.h" UpdateAvailableDialog::UpdateAvailableDialog(const QString& currentVersion, const QString& availableVersion, diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index 9c3fcc354..a47403926 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -503,8 +503,8 @@ void FlameManagedPackPage::suggestVersion() } auto version = m_pack.versions.at(index); - ui->changelogTextBrowser->setHtml(StringUtils::htmlListPatch( - m_api.getModFileChangelog(m_inst->getManagedPackID().toInt(), version.fileId))); + ui->changelogTextBrowser->setHtml( + StringUtils::htmlListPatch(m_api.getModFileChangelog(m_inst->getManagedPackID().toInt(), version.fileId))); ManagedPackPage::suggestVersion(); } diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index e851e47be..d3473412a 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -43,10 +43,10 @@ #include "FlameModel.h" #include "InstanceImportTask.h" #include "Json.h" +#include "StringUtils.h" #include "modplatform/flame/FlameAPI.h" #include "ui/dialogs/NewInstanceDialog.h" #include "ui/widgets/ProjectItem.h" -#include "StringUtils.h" #include "net/ApiDownload.h" diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp index 683d2e81d..80345883c 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp @@ -35,9 +35,9 @@ */ #include "Page.h" +#include "StringUtils.h" #include "ui/widgets/ProjectItem.h" #include "ui_Page.h" -#include "StringUtils.h" #include @@ -261,10 +261,9 @@ void Page::onPackSelectionChanged(Modpack* pack) { ui->versionSelectionBox->clear(); if (pack) { - currentModpackInfo->setHtml(StringUtils::htmlListPatch( - "Pack by " + pack->author + "" + "
    Minecraft " + pack->mcVersion + - "
    " + "
    " + - pack->description + "
    • " + pack->mods.replace(";", "
    • ") + "
    ")); + currentModpackInfo->setHtml(StringUtils::htmlListPatch("Pack by " + pack->author + "" + "
    Minecraft " + pack->mcVersion + + "
    " + "
    " + + pack->description + "
    • " + pack->mods.replace(";", "
    • ") + "
    ")); bool currentAdded = false; for (int i = 0; i < pack->oldVersions.size(); i++) { diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp index 72b7814c9..391c10122 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp @@ -44,10 +44,10 @@ #include "BuildConfig.h" #include "Json.h" +#include "StringUtils.h" #include "TechnicModel.h" #include "modplatform/technic/SingleZipPackInstallTask.h" #include "modplatform/technic/SolderPackInstallTask.h" -#include "StringUtils.h" #include "Application.h" #include "modplatform/technic/SolderPackManifest.h" From 814f84efab9af2fa59156d3e6101ce7fd8cffcb9 Mon Sep 17 00:00:00 2001 From: SabrePenguin Date: Wed, 1 May 2024 00:21:32 -0400 Subject: [PATCH 069/103] Fixed more clang formatting Signed-off-by: SabrePenguin --- launcher/Markdown.cpp | 2 +- launcher/StringUtils.cpp | 2 +- launcher/ui/pages/modplatform/legacy_ftb/Page.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/launcher/Markdown.cpp b/launcher/Markdown.cpp index a624791df..426067bf6 100644 --- a/launcher/Markdown.cpp +++ b/launcher/Markdown.cpp @@ -26,6 +26,6 @@ QString markdownToHTML(const QString& markdown) QString htmlStr(buffer); free(buffer); - + return htmlStr; } \ No newline at end of file diff --git a/launcher/StringUtils.cpp b/launcher/StringUtils.cpp index 125b8ca6f..e05affde1 100644 --- a/launcher/StringUtils.cpp +++ b/launcher/StringUtils.cpp @@ -219,7 +219,7 @@ QString StringUtils::htmlListPatch(QString htmlStr) int pos = htmlStr.indexOf(match); int imgPos, dist; while (pos != -1) { - dist = htmlStr.indexOf(">", pos) - pos + 1; // Get the size of the tag. Add one for zeroeth index + dist = htmlStr.indexOf(">", pos) - pos + 1; // Get the size of the tag. Add one for zeroeth index pos = pos + dist; imgPos = htmlStr.indexOf("versionSelectionBox->clear(); if (pack) { currentModpackInfo->setHtml(StringUtils::htmlListPatch("Pack by " + pack->author + "" + "
    Minecraft " + pack->mcVersion + - "
    " + "
    " + - pack->description + "
    • " + pack->mods.replace(";", "
    • ") + "
    ")); + "
    " + "
    " + pack->description + "
    • " + + pack->mods.replace(";", "
    • ") + "
    ")); bool currentAdded = false; for (int i = 0; i < pack->oldVersions.size(); i++) { From ce873e4a0dd517fd68cb49bab037362c9616672b Mon Sep 17 00:00:00 2001 From: SabrePenguin <147069705+SabrePenguin@users.noreply.github.com> Date: Wed, 1 May 2024 12:49:34 -0400 Subject: [PATCH 070/103] Regex correction Co-authored-by: TheKodeToad Signed-off-by: SabrePenguin <147069705+SabrePenguin@users.noreply.github.com> --- launcher/StringUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/StringUtils.cpp b/launcher/StringUtils.cpp index e05affde1..82147c16a 100644 --- a/launcher/StringUtils.cpp +++ b/launcher/StringUtils.cpp @@ -215,7 +215,7 @@ QPair StringUtils::splitFirst(const QString& s, const QRegular QString StringUtils::htmlListPatch(QString htmlStr) { - QRegularExpression match("|"); + QRegularExpression match("<\\s/\\s*ul\\s*>"); int pos = htmlStr.indexOf(match); int imgPos, dist; while (pos != -1) { From 0c76e7ab20f24ecc0a804732aca6ccf3ebd1e0c3 Mon Sep 17 00:00:00 2001 From: SabrePenguin Date: Wed, 1 May 2024 13:00:55 -0400 Subject: [PATCH 071/103] Made Regex static and const, removed dist Signed-off-by: SabrePenguin --- launcher/StringUtils.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/launcher/StringUtils.cpp b/launcher/StringUtils.cpp index 82147c16a..edda9f247 100644 --- a/launcher/StringUtils.cpp +++ b/launcher/StringUtils.cpp @@ -213,14 +213,14 @@ QPair StringUtils::splitFirst(const QString& s, const QRegular return qMakePair(left, right); } +static const QRegularExpression ulMatcher("<\\s*/\\s*ul\\s*>"); + QString StringUtils::htmlListPatch(QString htmlStr) { - QRegularExpression match("<\\s/\\s*ul\\s*>"); - int pos = htmlStr.indexOf(match); - int imgPos, dist; + int pos = htmlStr.indexOf(ulMatcher); + int imgPos; while (pos != -1) { - dist = htmlStr.indexOf(">", pos) - pos + 1; // Get the size of the tag. Add one for zeroeth index - pos = pos + dist; + pos = htmlStr.indexOf(">", pos) + 1; // Get the size of the tag. Add one for zeroeth index imgPos = htmlStr.indexOf(""); - pos = htmlStr.indexOf(match, pos); + pos = htmlStr.indexOf(ulMatcher, pos); } return htmlStr; } \ No newline at end of file From d795ba25e745912c9772bd084eb98f5f039d041d Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 10 May 2024 20:55:26 +0300 Subject: [PATCH 072/103] Delete instaces tmp diectory on startup Signed-off-by: Trial97 --- launcher/Application.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index bb8751ccc..f696d9022 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -1209,6 +1209,12 @@ void Application::performMainStartupAction() qDebug() << "<> Updater started."; } + { // delete instances tmp dirctory + auto instDir = m_settings->get("InstanceDir").toString(); + const QString tempRoot = FS::PathCombine(instDir, ".tmp"); + FS::deletePath(tempRoot); + } + if (!m_urlsToImport.isEmpty()) { qDebug() << "<> Importing from url:" << m_urlsToImport; m_mainWindow->processURLs(m_urlsToImport); From 0f2736306eec092e7dd4e7eb05ab0c7911515927 Mon Sep 17 00:00:00 2001 From: Alexandru Ionut Tripon Date: Sat, 18 May 2024 10:36:15 +0300 Subject: [PATCH 073/103] Update launcher/ui/dialogs/skins/SkinManageDialog.ui Co-authored-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Signed-off-by: Alexandru Ionut Tripon --- launcher/ui/dialogs/skins/SkinManageDialog.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.ui b/launcher/ui/dialogs/skins/SkinManageDialog.ui index c2ce9143c..ed8b7e530 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.ui +++ b/launcher/ui/dialogs/skins/SkinManageDialog.ui @@ -43,7 +43,7 @@ - Clasic + Classic true From 2ac6efaa4acac61a6c3e45948188181b4fdb118b Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 18 May 2024 11:56:43 +0300 Subject: [PATCH 074/103] Fix reggresion Signed-off-by: Trial97 --- launcher/ui/MainWindow.cpp | 2 +- launcher/ui/dialogs/skins/SkinManageDialog.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 0307476e4..301df2af4 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1213,7 +1213,7 @@ void MainWindow::on_actionViewCentralModsFolder_triggered() void MainWindow::on_actionViewSkinsFolder_triggered() { - DesktopServices::openDirectory(APPLICATION->settings()->get("SkinsDir").toString(), true); + DesktopServices::openPath(APPLICATION->settings()->get("SkinsDir").toString(), true); } void MainWindow::on_actionViewIconThemeFolder_triggered() diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index 8028a719c..9e71b867c 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -133,7 +133,7 @@ void SkinManageDialog::delayed_scroll(QModelIndex model_index) void SkinManageDialog::on_openDirBtn_clicked() { - DesktopServices::openDirectory(m_list.getDir(), true); + DesktopServices::openPath(m_list.getDir(), true); } void SkinManageDialog::on_fileBtn_clicked() From 3ba84cb84402651635fb9161960897a587bac385 Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Sat, 18 May 2024 12:57:09 +0200 Subject: [PATCH 075/103] fix: update bundled flatpak Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- flatpak/org.prismlauncher.PrismLauncher.yml | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 999029bd2..2b530dad9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -611,7 +611,7 @@ jobs: flatpak: runs-on: ubuntu-latest container: - image: bilelmoussaoui/flatpak-github-actions:kde-5.15-23.08 + image: bilelmoussaoui/flatpak-github-actions:kde-6.7 options: --privileged steps: - name: Checkout diff --git a/flatpak/org.prismlauncher.PrismLauncher.yml b/flatpak/org.prismlauncher.PrismLauncher.yml index b4c6e8143..352992c77 100644 --- a/flatpak/org.prismlauncher.PrismLauncher.yml +++ b/flatpak/org.prismlauncher.PrismLauncher.yml @@ -1,6 +1,6 @@ id: org.prismlauncher.PrismLauncher runtime: org.kde.Platform -runtime-version: 5.15-23.08 +runtime-version: 6.7 sdk: org.kde.Sdk sdk-extensions: - org.freedesktop.Sdk.Extension.openjdk21 @@ -38,7 +38,6 @@ modules: config-opts: - -DLauncher_BUILD_PLATFORM=flatpak - -DCMAKE_BUILD_TYPE=RelWithDebInfo - - -DLauncher_QT_VERSION_MAJOR=5 build-options: env: JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17 From 83fdfe809134955e573a259b30232be355d66be7 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 18 May 2024 16:52:29 +0300 Subject: [PATCH 076/103] fix regression Signed-off-by: Trial97 --- launcher/ui/themes/ThemeManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/themes/ThemeManager.cpp b/launcher/ui/themes/ThemeManager.cpp index e5daf787e..1cb83ca26 100644 --- a/launcher/ui/themes/ThemeManager.cpp +++ b/launcher/ui/themes/ThemeManager.cpp @@ -318,7 +318,7 @@ void ThemeManager::refresh() { m_themes.clear(); m_icons.clear(); - m_catPacks.clear(); + m_cat_packs.clear(); initializeThemes(); initializeCatPacks(); From d21597cf89e22258ffb2f7c01b2b5d9be9a06adc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 19 May 2024 00:20:26 +0000 Subject: [PATCH 077/103] chore(nix): update lockfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'flake-parts': 'github:hercules-ci/flake-parts/e5d10a24b66c3ea8f150e47dfdb0416ab7c3390e?narHash=sha256-yzcRNDoyVP7%2BSCNX0wmuDju1NUCt8Dz9%2BlyUXEI0dbI%3D' (2024-05-02) → 'github:hercules-ci/flake-parts/8dc45382d5206bd292f9c2768b8058a8fd8311d9?narHash=sha256-/GJvTdTpuDjNn84j82cU6bXztE0MSkdnTWClUCRub78%3D' (2024-05-16) • Updated input 'nixpkgs': 'github:nixos/nixpkgs/e4e7a43a9db7e22613accfeb1005cca1b2b1ee0d?narHash=sha256-FCi3R1MeS5bVp0M0xTheveP6hhcCYfW/aghSTPebYL4%3D' (2024-05-11) → 'github:nixos/nixpkgs/02923630b89aa1ab36ef8e422501a6f4fd4b2016?narHash=sha256-OhysviwHQz4p2HZL4g7XGMLoUbWMjkMr/ogaR3VUYNA%3D' (2024-05-18) • Updated input 'pre-commit-hooks': 'github:cachix/pre-commit-hooks.nix/2849da033884f54822af194400f8dff435ada242?narHash=sha256-q//cgb52vv81uOuwz1LaXElp3XAe1TqrABXODAEF6Sk%3D' (2024-04-30) → 'github:cachix/pre-commit-hooks.nix/fa606cccd7b0ccebe2880051208e4a0f61bfc8c1?narHash=sha256-nacSOeXtUEM77Gn0G4bTdEOeFIrkCBXiyyFZtdGwuH0%3D' (2024-05-16) • Removed input 'pre-commit-hooks/flake-utils' • Removed input 'pre-commit-hooks/flake-utils/systems' --- flake.lock | 52 +++++++++------------------------------------------- 1 file changed, 9 insertions(+), 43 deletions(-) diff --git a/flake.lock b/flake.lock index 740d5c43e..26044deb2 100644 --- a/flake.lock +++ b/flake.lock @@ -23,11 +23,11 @@ ] }, "locked": { - "lastModified": 1714641030, - "narHash": "sha256-yzcRNDoyVP7+SCNX0wmuDju1NUCt8Dz9+lyUXEI0dbI=", + "lastModified": 1715865404, + "narHash": "sha256-/GJvTdTpuDjNn84j82cU6bXztE0MSkdnTWClUCRub78=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "e5d10a24b66c3ea8f150e47dfdb0416ab7c3390e", + "rev": "8dc45382d5206bd292f9c2768b8058a8fd8311d9", "type": "github" }, "original": { @@ -36,24 +36,6 @@ "type": "github" } }, - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, "gitignore": { "inputs": { "nixpkgs": [ @@ -93,11 +75,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1715413075, - "narHash": "sha256-FCi3R1MeS5bVp0M0xTheveP6hhcCYfW/aghSTPebYL4=", + "lastModified": 1716062047, + "narHash": "sha256-OhysviwHQz4p2HZL4g7XGMLoUbWMjkMr/ogaR3VUYNA=", "owner": "nixos", "repo": "nixpkgs", - "rev": "e4e7a43a9db7e22613accfeb1005cca1b2b1ee0d", + "rev": "02923630b89aa1ab36ef8e422501a6f4fd4b2016", "type": "github" }, "original": { @@ -112,7 +94,6 @@ "flake-compat": [ "flake-compat" ], - "flake-utils": "flake-utils", "gitignore": "gitignore", "nixpkgs": [ "nixpkgs" @@ -122,11 +103,11 @@ ] }, "locked": { - "lastModified": 1714478972, - "narHash": "sha256-q//cgb52vv81uOuwz1LaXElp3XAe1TqrABXODAEF6Sk=", + "lastModified": 1715870890, + "narHash": "sha256-nacSOeXtUEM77Gn0G4bTdEOeFIrkCBXiyyFZtdGwuH0=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "2849da033884f54822af194400f8dff435ada242", + "rev": "fa606cccd7b0ccebe2880051208e4a0f61bfc8c1", "type": "github" }, "original": { @@ -143,21 +124,6 @@ "nixpkgs": "nixpkgs", "pre-commit-hooks": "pre-commit-hooks" } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } } }, "root": "root", From 1b0e8260ac3b992a1e60e88d9a7217c8c8a66bfc Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 19 May 2024 15:36:39 +0300 Subject: [PATCH 078/103] fix zero launch time Signed-off-by: Trial97 --- launcher/minecraft/MinecraftInstance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 1de822b7f..d119104fe 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -1000,7 +1000,7 @@ QString MinecraftInstance::getStatusbarDescription() QString description; description.append(tr("Minecraft %1").arg(mcVersion)); if (m_settings->get("ShowGameTime").toBool()) { - if (lastTimePlayed() > 0) { + if (lastTimePlayed() > 0 && lastLaunch() > 0) { QDateTime lastLaunchTime = QDateTime::fromMSecsSinceEpoch(lastLaunch()); description.append( tr(", last played on %1 for %2") From 201c4d2cc69521967322c98e9756d49d655476ce Mon Sep 17 00:00:00 2001 From: Lukas Eschbacher Date: Mon, 20 May 2024 14:39:24 +0200 Subject: [PATCH 079/103] nix: replace mesa-demos with glxinfo Signed-off-by: Lukas Eschbacher --- nix/pkg/wrapper.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nix/pkg/wrapper.nix b/nix/pkg/wrapper.nix index 1bcff1f9b..8c9c1cabc 100644 --- a/nix/pkg/wrapper.nix +++ b/nix/pkg/wrapper.nix @@ -18,7 +18,7 @@ jdk21, gamemode, flite, - mesa-demos, + glxinfo, udev, libusb1, msaClientID ? null, @@ -81,7 +81,7 @@ in runtimePrograms = [ xorg.xrandr - mesa-demos # need glxinfo + glxinfo ] ++ additionalPrograms; in From 08918be11eaefd708634bf75ea6f688f0c3f3df9 Mon Sep 17 00:00:00 2001 From: Fourmisain <8464472+Fourmisain@users.noreply.github.com> Date: Mon, 20 May 2024 20:35:05 +0200 Subject: [PATCH 080/103] add detection for IBM Semeru Java runtime Signed-off-by: Fourmisain <8464472+Fourmisain@users.noreply.github.com> --- launcher/java/JavaUtils.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index e767eff89..ccc20f35c 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -283,6 +283,16 @@ QList JavaUtils::FindJavaPaths() QList ADOPTIUMJDK64s = this->FindJavaFromRegistryKey(KEY_WOW64_64KEY, "SOFTWARE\\Eclipse Adoptium\\JDK", "Path", "\\hotspot\\MSI"); + // IBM Semeru + QList SEMERUJRE32s = + this->FindJavaFromRegistryKey(KEY_WOW64_32KEY, "SOFTWARE\\Semeru\\JRE", "Path", "\\openj9\\MSI"); + QList SEMERUJRE64s = + this->FindJavaFromRegistryKey(KEY_WOW64_64KEY, "SOFTWARE\\Semeru\\JRE", "Path", "\\openj9\\MSI"); + QList SEMERUJDK32s = + this->FindJavaFromRegistryKey(KEY_WOW64_32KEY, "SOFTWARE\\Semeru\\JDK", "Path", "\\openj9\\MSI"); + QList SEMERUJDK64s = + this->FindJavaFromRegistryKey(KEY_WOW64_64KEY, "SOFTWARE\\Semeru\\JDK", "Path", "\\openj9\\MSI"); + // Microsoft QList MICROSOFTJDK64s = this->FindJavaFromRegistryKey(KEY_WOW64_64KEY, "SOFTWARE\\Microsoft\\JDK", "Path", "\\hotspot\\MSI"); @@ -300,6 +310,7 @@ QList JavaUtils::FindJavaPaths() java_candidates.append(NEWJRE64s); java_candidates.append(ADOPTOPENJRE64s); java_candidates.append(ADOPTIUMJRE64s); + java_candidates.append(SEMERUJRE64s); java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre8/bin/javaw.exe")); java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre7/bin/javaw.exe")); java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre6/bin/javaw.exe")); @@ -308,6 +319,7 @@ QList JavaUtils::FindJavaPaths() java_candidates.append(ADOPTOPENJDK64s); java_candidates.append(FOUNDATIONJDK64s); java_candidates.append(ADOPTIUMJDK64s); + java_candidates.append(SEMERUJDK64s); java_candidates.append(MICROSOFTJDK64s); java_candidates.append(ZULU64s); java_candidates.append(LIBERICA64s); @@ -316,6 +328,7 @@ QList JavaUtils::FindJavaPaths() java_candidates.append(NEWJRE32s); java_candidates.append(ADOPTOPENJRE32s); java_candidates.append(ADOPTIUMJRE32s); + java_candidates.append(SEMERUJRE32s); java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre8/bin/javaw.exe")); java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre7/bin/javaw.exe")); java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre6/bin/javaw.exe")); @@ -324,6 +337,7 @@ QList JavaUtils::FindJavaPaths() java_candidates.append(ADOPTOPENJDK32s); java_candidates.append(FOUNDATIONJDK32s); java_candidates.append(ADOPTIUMJDK32s); + java_candidates.append(SEMERUJDK32s); java_candidates.append(ZULU32s); java_candidates.append(LIBERICA32s); From af157c1613c697cd0cbff78abe28d6e958372775 Mon Sep 17 00:00:00 2001 From: Fourmisain <8464472+Fourmisain@users.noreply.github.com> Date: Mon, 20 May 2024 23:13:18 +0200 Subject: [PATCH 081/103] fix x86 detection on 64 bit windows Signed-off-by: Fourmisain <8464472+Fourmisain@users.noreply.github.com> --- launcher/java/JavaUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index ccc20f35c..f7c7c6e5f 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -207,7 +207,7 @@ QList JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString QString newKeyName = keyName + "\\" + newSubkeyName + subkeySuffix; HKEY newKey; - if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, newKeyName.toStdWString().c_str(), 0, KEY_READ | KEY_WOW64_64KEY, &newKey) == + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, newKeyName.toStdWString().c_str(), 0, KEY_READ | keyType, &newKey) == ERROR_SUCCESS) { // Read the JavaHome value to find where Java is installed. DWORD valueSz = 0; From dbe4adc034a578484a6c4b7ba8d21c7c4d0bbb56 Mon Sep 17 00:00:00 2001 From: Fourmisain <8464472+Fourmisain@users.noreply.github.com> Date: Tue, 21 May 2024 10:51:27 +0200 Subject: [PATCH 082/103] detect IBM Semeru Certified Edition on linux Signed-off-by: Fourmisain <8464472+Fourmisain@users.noreply.github.com> --- launcher/java/JavaUtils.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index f7c7c6e5f..2c9c9a579 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -424,6 +424,7 @@ QList JavaUtils::FindJavaPaths() // manually installed JDKs in /opt scanJavaDirs("/opt/jdk"); scanJavaDirs("/opt/jdks"); + scanJavaDirs("/opt/ibm"); // IBM Semeru Certified Edition // flatpak scanJavaDirs("/app/jdk"); From 23095b70a4cead08e8b0e38bdf62d4d14482c41d Mon Sep 17 00:00:00 2001 From: Fourmisain <8464472+Fourmisain@users.noreply.github.com> Date: Tue, 21 May 2024 13:40:20 +0200 Subject: [PATCH 083/103] please the pre-commit check Signed-off-by: Fourmisain <8464472+Fourmisain@users.noreply.github.com> --- launcher/java/JavaUtils.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index 2c9c9a579..3b32b54c7 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -284,14 +284,10 @@ QList JavaUtils::FindJavaPaths() this->FindJavaFromRegistryKey(KEY_WOW64_64KEY, "SOFTWARE\\Eclipse Adoptium\\JDK", "Path", "\\hotspot\\MSI"); // IBM Semeru - QList SEMERUJRE32s = - this->FindJavaFromRegistryKey(KEY_WOW64_32KEY, "SOFTWARE\\Semeru\\JRE", "Path", "\\openj9\\MSI"); - QList SEMERUJRE64s = - this->FindJavaFromRegistryKey(KEY_WOW64_64KEY, "SOFTWARE\\Semeru\\JRE", "Path", "\\openj9\\MSI"); - QList SEMERUJDK32s = - this->FindJavaFromRegistryKey(KEY_WOW64_32KEY, "SOFTWARE\\Semeru\\JDK", "Path", "\\openj9\\MSI"); - QList SEMERUJDK64s = - this->FindJavaFromRegistryKey(KEY_WOW64_64KEY, "SOFTWARE\\Semeru\\JDK", "Path", "\\openj9\\MSI"); + QList SEMERUJRE32s = this->FindJavaFromRegistryKey(KEY_WOW64_32KEY, "SOFTWARE\\Semeru\\JRE", "Path", "\\openj9\\MSI"); + QList SEMERUJRE64s = this->FindJavaFromRegistryKey(KEY_WOW64_64KEY, "SOFTWARE\\Semeru\\JRE", "Path", "\\openj9\\MSI"); + QList SEMERUJDK32s = this->FindJavaFromRegistryKey(KEY_WOW64_32KEY, "SOFTWARE\\Semeru\\JDK", "Path", "\\openj9\\MSI"); + QList SEMERUJDK64s = this->FindJavaFromRegistryKey(KEY_WOW64_64KEY, "SOFTWARE\\Semeru\\JDK", "Path", "\\openj9\\MSI"); // Microsoft QList MICROSOFTJDK64s = @@ -424,7 +420,7 @@ QList JavaUtils::FindJavaPaths() // manually installed JDKs in /opt scanJavaDirs("/opt/jdk"); scanJavaDirs("/opt/jdks"); - scanJavaDirs("/opt/ibm"); // IBM Semeru Certified Edition + scanJavaDirs("/opt/ibm"); // IBM Semeru Certified Edition // flatpak scanJavaDirs("/app/jdk"); From a8712f712692298d0f1d2ad57940117eabf9a67e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 26 May 2024 00:20:28 +0000 Subject: [PATCH 084/103] chore(nix): update lockfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:nixos/nixpkgs/02923630b89aa1ab36ef8e422501a6f4fd4b2016?narHash=sha256-OhysviwHQz4p2HZL4g7XGMLoUbWMjkMr/ogaR3VUYNA%3D' (2024-05-18) → 'github:nixos/nixpkgs/47e03a624662ce399e55c45a5f6da698fc72c797?narHash=sha256-9dUxZf8MOqJH3vjbhrz7LH4qTcnRsPSBU1Q50T7q/X8%3D' (2024-05-25) • Updated input 'pre-commit-hooks': 'github:cachix/pre-commit-hooks.nix/fa606cccd7b0ccebe2880051208e4a0f61bfc8c1?narHash=sha256-nacSOeXtUEM77Gn0G4bTdEOeFIrkCBXiyyFZtdGwuH0%3D' (2024-05-16) → 'github:cachix/pre-commit-hooks.nix/0e8fcc54b842ad8428c9e705cb5994eaf05c26a0?narHash=sha256-xrsYFST8ij4QWaV6HEokCUNIZLjjLP1bYC60K8XiBVA%3D' (2024-05-20) --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index 26044deb2..6f7f9a0c5 100644 --- a/flake.lock +++ b/flake.lock @@ -75,11 +75,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1716062047, - "narHash": "sha256-OhysviwHQz4p2HZL4g7XGMLoUbWMjkMr/ogaR3VUYNA=", + "lastModified": 1716619601, + "narHash": "sha256-9dUxZf8MOqJH3vjbhrz7LH4qTcnRsPSBU1Q50T7q/X8=", "owner": "nixos", "repo": "nixpkgs", - "rev": "02923630b89aa1ab36ef8e422501a6f4fd4b2016", + "rev": "47e03a624662ce399e55c45a5f6da698fc72c797", "type": "github" }, "original": { @@ -103,11 +103,11 @@ ] }, "locked": { - "lastModified": 1715870890, - "narHash": "sha256-nacSOeXtUEM77Gn0G4bTdEOeFIrkCBXiyyFZtdGwuH0=", + "lastModified": 1716213921, + "narHash": "sha256-xrsYFST8ij4QWaV6HEokCUNIZLjjLP1bYC60K8XiBVA=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "fa606cccd7b0ccebe2880051208e4a0f61bfc8c1", + "rev": "0e8fcc54b842ad8428c9e705cb5994eaf05c26a0", "type": "github" }, "original": { From a2b5ac73ff1464d9c2cb70412ac57d3c9811d5d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Celeste=20Pel=C3=A1ez?= Date: Sun, 26 May 2024 00:36:34 -0500 Subject: [PATCH 085/103] Added support for bcachefs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Celeste Peláez --- launcher/FileSystem.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 5496c3795..f8ad76270 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -378,6 +378,7 @@ enum class FilesystemType { HFSX, FUSEBLK, F2FS, + BCACHEFS, UNKNOWN }; @@ -406,6 +407,7 @@ static const QMap s_filesystem_type_names = { { Fil { FilesystemType::HFSX, { "HFSX" } }, { FilesystemType::FUSEBLK, { "FUSEBLK" } }, { FilesystemType::F2FS, { "F2FS" } }, + { FilesystemType::BCACHEFS, { "BCACHEFS" } }, { FilesystemType::UNKNOWN, { "UNKNOWN" } } }; /** @@ -458,7 +460,7 @@ QString nearestExistentAncestor(const QString& path); FilesystemInfo statFS(const QString& path); static const QList s_clone_filesystems = { FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS, - FilesystemType::XFS, FilesystemType::REFS }; + FilesystemType::XFS, FilesystemType::REFS, FilesystemType::BCACHEFS }; /** * @brief if the Filesystem is reflink/clone capable From f5f32e2c6e7644577e2f583a975d2f9b0df405a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Celeste=20Pel=C3=A1ez?= Date: Sun, 26 May 2024 01:40:31 -0500 Subject: [PATCH 086/103] Reformatted `FileSystem.h` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Celeste Peláez --- launcher/FileSystem.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index f8ad76270..ea4bb9674 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -460,7 +460,7 @@ QString nearestExistentAncestor(const QString& path); FilesystemInfo statFS(const QString& path); static const QList s_clone_filesystems = { FilesystemType::BTRFS, FilesystemType::APFS, FilesystemType::ZFS, - FilesystemType::XFS, FilesystemType::REFS, FilesystemType::BCACHEFS }; + FilesystemType::XFS, FilesystemType::REFS, FilesystemType::BCACHEFS }; /** * @brief if the Filesystem is reflink/clone capable From 4917287292eacc4bade9464de7877d28ca67c63c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 May 2024 14:35:10 +0000 Subject: [PATCH 087/103] chore(deps): update korthout/backport-action action to v3 --- .github/workflows/backport.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 60bd86eec..d91d9507a 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -25,7 +25,7 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} - name: Create backport PRs - uses: korthout/backport-action@v2.5.0 + uses: korthout/backport-action@v3.0.2 with: # Config README: https://github.com/korthout/backport-action#backport-action pull_description: |- From eaccdca02deb536a670a063902e666a2a341751f Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 30 May 2024 16:21:08 +0300 Subject: [PATCH 088/103] added error message for import skin from user Signed-off-by: Trial97 --- launcher/net/NetJob.cpp | 33 +++++++++++-------- launcher/net/NetJob.h | 2 ++ .../ui/dialogs/skins/SkinManageDialog.cpp | 13 ++++++-- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index a6b1207c0..a331a9769 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -148,21 +148,28 @@ void NetJob::updateState() void NetJob::emitFailed(QString reason) { #if defined(LAUNCHER_APPLICATION) - auto response = CustomMessageBox::selectable(nullptr, "Confirm retry", - "The tasks failed\n" - "Failed urls\n" + - getFailedFiles().join("\n\t") + - "\n" - "If this continues to happen please check the logs of the application" - "Do you want to retry?", - QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) - ->exec(); + if (m_ask_retry) { + auto response = CustomMessageBox::selectable(nullptr, "Confirm retry", + "The tasks failed.\n" + "Failed urls\n" + + getFailedFiles().join("\n\t") + + ".\n" + "If this continues to happen please check the logs of the application.\n" + "Do you want to retry?", + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); - if (response == QMessageBox::Yes) { - m_try = 0; - executeNextSubTask(); - return; + if (response == QMessageBox::Yes) { + m_try = 0; + executeNextSubTask(); + return; + } } #endif ConcurrentTask::emitFailed(reason); +} + +void NetJob::setAskRetry(bool askRetry) +{ + m_ask_retry = askRetry; } \ No newline at end of file diff --git a/launcher/net/NetJob.h b/launcher/net/NetJob.h index a50d7f91e..af09f03ba 100644 --- a/launcher/net/NetJob.h +++ b/launcher/net/NetJob.h @@ -62,6 +62,7 @@ class NetJob : public ConcurrentTask { auto getFailedActions() -> QList; auto getFailedFiles() -> QList; + void setAskRetry(bool askRetry); public slots: // Qt can't handle auto at the start for some reason? @@ -78,4 +79,5 @@ class NetJob : public ConcurrentTask { shared_qobject_ptr m_network; int m_try = 1; + bool m_ask_retry = true; }; diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index 0b60e4248..19a1f6d52 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -345,9 +345,9 @@ void SkinManageDialog::on_urlBtn_clicked() CustomMessageBox::selectable(this, tr("Invalid url"), tr("Invalid url"), QMessageBox::Critical)->show(); return; } - ui->urlLine->setText(""); NetJob::Ptr job{ new NetJob(tr("Download skin"), APPLICATION->network()) }; + job->setAskRetry(false); auto path = FS::PathCombine(m_list.getDir(), url.fileName()); job->addNetAction(Net::Download::makeFile(url, path)); @@ -361,6 +361,7 @@ void SkinManageDialog::on_urlBtn_clicked() QFile::remove(path); return; } + ui->urlLine->setText(""); if (QFileInfo(path).suffix().isEmpty()) { QFile::rename(path, path + ".png"); } @@ -397,11 +398,11 @@ void SkinManageDialog::on_userBtn_clicked() if (user.isEmpty()) { return; } - ui->urlLine->setText(""); MinecraftProfile mcProfile; auto path = FS::PathCombine(m_list.getDir(), user + ".png"); NetJob::Ptr job{ new NetJob(tr("Download user skin"), APPLICATION->network(), 1) }; + job->setAskRetry(false); auto uuidOut = std::make_shared(); auto profileOut = std::make_shared(); @@ -459,6 +460,14 @@ void SkinManageDialog::on_userBtn_clicked() dlg.execWithTask(job.get()); SkinModel s(path); + if (!s.isValid()) { + CustomMessageBox::selectable(this, tr("Usename not found"), tr("Unable to find the skin for '%1'.").arg(user), + QMessageBox::Critical) + ->show(); + QFile::remove(path); + return; + } + ui->urlLine->setText(""); s.setModel(mcProfile.skin.variant.toUpper() == "SLIM" ? SkinModel::SLIM : SkinModel::CLASSIC); s.setURL(mcProfile.skin.url); if (m_capes.contains(mcProfile.currentCape)) { From f7120a4f6ff64e8e28096a51f5d99dd2565471bc Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 8 May 2024 14:53:49 +0300 Subject: [PATCH 089/103] Made Custom New Instance scrollable Signed-off-by: Trial97 --- launcher/ui/pages/modplatform/CustomPage.ui | 464 ++++++++++---------- 1 file changed, 241 insertions(+), 223 deletions(-) diff --git a/launcher/ui/pages/modplatform/CustomPage.ui b/launcher/ui/pages/modplatform/CustomPage.ui index fda3e8a2e..ce558f2cf 100644 --- a/launcher/ui/pages/modplatform/CustomPage.ui +++ b/launcher/ui/pages/modplatform/CustomPage.ui @@ -33,231 +33,250 @@ - - - - - 0 - 0 - - - - Qt::Horizontal + + + + true + + + + 0 + 0 + 791 + 551 + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + + + + + + 0 + 0 + + + + + + + + + + Filter + + + Qt::AlignCenter + + + + + + + Releases + + + true + + + true + + + + + + + Snapshots + + + true + + + + + + + Betas + + + true + + + + + + + Alphas + + + true + + + + + + + Experiments + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Refresh + + + + + + + + + + + + + + 0 + 0 + + + + + + + + + + Mod Loader + + + Qt::AlignCenter + + + + + + + None + + + true + + + loaderBtnGroup + + + + + + + NeoForge + + + loaderBtnGroup + + + + + + + Forge + + + loaderBtnGroup + + + + + + + Fabric + + + loaderBtnGroup + + + + + + + Quilt + + + loaderBtnGroup + + + + + + + LiteLoader + + + loaderBtnGroup + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Refresh + + + + + + + + + - - - - - - - 0 - 0 - - - - - - - - - - Filter - - - Qt::AlignCenter - - - - - - - Releases - - - true - - - true - - - - - - - Snapshots - - - true - - - - - - - Betas - - - true - - - - - - - Alphas - - - true - - - - - - - Experiments - - - true - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Refresh - - - - - - - - - - - - - - 0 - 0 - - - - - - - - - - Mod Loader - - - Qt::AlignCenter - - - - - - - None - - - true - - - loaderBtnGroup - - - - - - - NeoForge - - - loaderBtnGroup - - - - - - - Forge - - - loaderBtnGroup - - - - - - - Fabric - - - loaderBtnGroup - - - - - - - Quilt - - - loaderBtnGroup - - - - - - - LiteLoader - - - loaderBtnGroup - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Refresh - - - - - - - @@ -273,7 +292,6 @@
    - tabWidget releaseFilter snapshotFilter betaFilter From c9c39b8203bbfaf2205847e80a979baaf6c007a7 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 31 May 2024 17:47:39 +0300 Subject: [PATCH 090/103] fix double borders Signed-off-by: Trial97 --- launcher/ui/pages/modplatform/CustomPage.cpp | 1 - launcher/ui/pages/modplatform/CustomPage.ui | 472 +++++++++---------- 2 files changed, 229 insertions(+), 244 deletions(-) diff --git a/launcher/ui/pages/modplatform/CustomPage.cpp b/launcher/ui/pages/modplatform/CustomPage.cpp index d2b73008d..ba22bd2e6 100644 --- a/launcher/ui/pages/modplatform/CustomPage.cpp +++ b/launcher/ui/pages/modplatform/CustomPage.cpp @@ -49,7 +49,6 @@ CustomPage::CustomPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), dialog(dialog), ui(new Ui::CustomPage) { ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); connect(ui->versionList, &VersionSelectWidget::selectedVersionChanged, this, &CustomPage::setSelectedVersion); filterChanged(); connect(ui->alphaFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged); diff --git a/launcher/ui/pages/modplatform/CustomPage.ui b/launcher/ui/pages/modplatform/CustomPage.ui index ce558f2cf..39d9aa6dc 100644 --- a/launcher/ui/pages/modplatform/CustomPage.ui +++ b/launcher/ui/pages/modplatform/CustomPage.ui @@ -24,259 +24,245 @@ 0 - - - 0 + + + true - - - - - - - - - true - - - - - 0 - 0 - 791 - 551 - - - - - - - - 0 - 0 - + + + + 0 + 0 + 813 + 605 + + + + + + + + + + 0 + 0 + + + + + + + + + + Filter - - Qt::Horizontal + + Qt::AlignCenter - - - - - - - 0 - 0 - - - - - - - - - - Filter - - - Qt::AlignCenter - - - - - - - Releases - - - true - - - true - - - - - - - Snapshots - - - true - - - - - - - Betas - - - true - - - - - - - Alphas - - - true - - - - - - - Experiments - - - true - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Refresh - - - - - - + + + + Releases + + + true + + + true + + - - - - - - - 0 - 0 - - - - - - - - - - Mod Loader - - - Qt::AlignCenter - - - - - - - None - - - true - - - loaderBtnGroup - - - - - - - NeoForge - - - loaderBtnGroup - - - - - - - Forge - - - loaderBtnGroup - - - - - - - Fabric - - - loaderBtnGroup - - - - - - - Quilt - - - loaderBtnGroup - - - - - - - LiteLoader - - - loaderBtnGroup - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Refresh - - - - - - + + + + Snapshots + + + true + + + + + + + Betas + + + true + + + + + + + Alphas + + + true + + + + + + + Experiments + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Refresh + + - + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + + + + + + + Mod Loader + + + Qt::AlignCenter + + + + + + + None + + + true + + + loaderBtnGroup + + + + + + + NeoForge + + + loaderBtnGroup + + + + + + + Forge + + + loaderBtnGroup + + + + + + + Fabric + + + loaderBtnGroup + + + + + + + Quilt + + + loaderBtnGroup + + + + + + + LiteLoader + + + loaderBtnGroup + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Refresh + + + + + + + From f3509181869a789cb413f6704e276f58e847dcb0 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 31 May 2024 19:03:40 +0300 Subject: [PATCH 091/103] force initial size for the Add Instance dialog Signed-off-by: Trial97 --- launcher/ui/dialogs/NewInstanceDialog.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp index 3524d43f8..0fb8e320d 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.cpp +++ b/launcher/ui/dialogs/NewInstanceDialog.cpp @@ -52,6 +52,7 @@ #include #include #include +#include #include #include @@ -63,6 +64,7 @@ #include "ui/pages/modplatform/modrinth/ModrinthPage.h" #include "ui/pages/modplatform/technic/TechnicPage.h" #include "ui/widgets/PageContainer.h" + NewInstanceDialog::NewInstanceDialog(const QString& initialGroup, const QString& url, const QMap& extra_info, @@ -127,7 +129,13 @@ NewInstanceDialog::NewInstanceDialog(const QString& initialGroup, updateDialogState(); - restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("NewInstanceGeometry").toByteArray())); + if (APPLICATION->settings()->get("NewInstanceGeometry").isValid()) { + restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("NewInstanceGeometry").toByteArray())); + } else { + auto screen = parent->screen(); + auto geometry = screen->availableSize(); + resize(width(), qMin(geometry.height() - 50, 710)); + } } void NewInstanceDialog::reject() From 83883cadf9dd4e71db257821e0c46ef6421021eb Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 31 May 2024 19:30:10 +0300 Subject: [PATCH 092/103] fix qt5 build Signed-off-by: Trial97 --- launcher/ui/dialogs/NewInstanceDialog.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp index 0fb8e320d..1601708f9 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.cpp +++ b/launcher/ui/dialogs/NewInstanceDialog.cpp @@ -132,7 +132,11 @@ NewInstanceDialog::NewInstanceDialog(const QString& initialGroup, if (APPLICATION->settings()->get("NewInstanceGeometry").isValid()) { restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("NewInstanceGeometry").toByteArray())); } else { +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) auto screen = parent->screen(); +#else + auto screen = QGuiApplication::primaryScreen(); +#endif auto geometry = screen->availableSize(); resize(width(), qMin(geometry.height() - 50, 710)); } From bbff31bcc069f4ee6718a551f1cb087db54f7e56 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 2 Jun 2024 00:20:40 +0000 Subject: [PATCH 093/103] chore(nix): update lockfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'flake-parts': 'github:hercules-ci/flake-parts/8dc45382d5206bd292f9c2768b8058a8fd8311d9?narHash=sha256-/GJvTdTpuDjNn84j82cU6bXztE0MSkdnTWClUCRub78%3D' (2024-05-16) → 'github:hercules-ci/flake-parts/2a55567fcf15b1b1c7ed712a2c6fadaec7412ea8?narHash=sha256-iKzJcpdXih14qYVcZ9QC9XuZYnPc6T8YImb6dX166kw%3D' (2024-06-01) • Updated input 'nixpkgs': 'github:nixos/nixpkgs/47e03a624662ce399e55c45a5f6da698fc72c797?narHash=sha256-9dUxZf8MOqJH3vjbhrz7LH4qTcnRsPSBU1Q50T7q/X8%3D' (2024-05-25) → 'github:nixos/nixpkgs/6132b0f6e344ce2fe34fc051b72fb46e34f668e0?narHash=sha256-7R2ZvOnvd9h8fDd65p0JnB7wXfUvreox3xFdYWd1BnY%3D' (2024-05-30) --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index 6f7f9a0c5..e7bcc1635 100644 --- a/flake.lock +++ b/flake.lock @@ -23,11 +23,11 @@ ] }, "locked": { - "lastModified": 1715865404, - "narHash": "sha256-/GJvTdTpuDjNn84j82cU6bXztE0MSkdnTWClUCRub78=", + "lastModified": 1717285511, + "narHash": "sha256-iKzJcpdXih14qYVcZ9QC9XuZYnPc6T8YImb6dX166kw=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "8dc45382d5206bd292f9c2768b8058a8fd8311d9", + "rev": "2a55567fcf15b1b1c7ed712a2c6fadaec7412ea8", "type": "github" }, "original": { @@ -75,11 +75,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1716619601, - "narHash": "sha256-9dUxZf8MOqJH3vjbhrz7LH4qTcnRsPSBU1Q50T7q/X8=", + "lastModified": 1717112898, + "narHash": "sha256-7R2ZvOnvd9h8fDd65p0JnB7wXfUvreox3xFdYWd1BnY=", "owner": "nixos", "repo": "nixpkgs", - "rev": "47e03a624662ce399e55c45a5f6da698fc72c797", + "rev": "6132b0f6e344ce2fe34fc051b72fb46e34f668e0", "type": "github" }, "original": { From b7008d2880091db64587362a3bb72fb51541be5f Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 6 Jun 2024 00:09:08 +0300 Subject: [PATCH 094/103] replace auth server ping Signed-off-by: Trial97 --- buildconfig/BuildConfig.h | 1 - launcher/LaunchController.cpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h index 77b6eef54..bda80ac72 100644 --- a/buildconfig/BuildConfig.h +++ b/buildconfig/BuildConfig.h @@ -163,7 +163,6 @@ class Config { QString RESOURCE_BASE = "https://resources.download.minecraft.net/"; QString LIBRARY_BASE = "https://libraries.minecraft.net/"; - QString AUTH_BASE = "https://authserver.mojang.com/"; QString IMGUR_BASE_URL = "https://api.imgur.com/3/"; QString FMLLIBS_BASE_URL = "https://files.prismlauncher.org/fmllibs/"; // FIXME: move into CMakeLists QString TRANSLATIONS_BASE_URL = "https://i18n.prismlauncher.org/"; // FIXME: move into CMakeLists diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index cf8d0a794..e37b2f6e3 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -315,7 +315,7 @@ void LaunchController::launchInstance() online_mode = "online"; // Prepend Server Status - QStringList servers = { "authserver.mojang.com", "session.minecraft.net", "textures.minecraft.net", "api.mojang.com" }; + QStringList servers = { "login.microsoftonline.com", "session.minecraft.net", "textures.minecraft.net", "api.mojang.com" }; QString resolved_servers = ""; QHostInfo host_info; From 448ded2387025b11d122d51b0abd907bec0452aa Mon Sep 17 00:00:00 2001 From: coolguy1842 Date: Thu, 6 Jun 2024 16:18:47 +1000 Subject: [PATCH 095/103] simple fix for resizing too small causing an index out of range crash Signed-off-by: coolguy1842 --- launcher/ui/instanceview/VisualGroup.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/launcher/ui/instanceview/VisualGroup.cpp b/launcher/ui/instanceview/VisualGroup.cpp index 7bff727fe..e3bf628dc 100644 --- a/launcher/ui/instanceview/VisualGroup.cpp +++ b/launcher/ui/instanceview/VisualGroup.cpp @@ -66,6 +66,9 @@ void VisualGroup::update() rows[currentRow].height = maxRowHeight; rows[currentRow].top = offsetFromTop; currentRow++; + if(currentRow >= rows.size()) { + currentRow = rows.size() - 1; + } offsetFromTop += maxRowHeight + 5; positionInRow = 0; maxRowHeight = 0; From 71b2c4b1fd0a45e2325d09ca93b0f441ee1b7a50 Mon Sep 17 00:00:00 2001 From: coolguy1842 Date: Thu, 6 Jun 2024 16:32:41 +1000 Subject: [PATCH 096/103] fix formatting Signed-off-by: coolguy1842 --- launcher/ui/instanceview/VisualGroup.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/instanceview/VisualGroup.cpp b/launcher/ui/instanceview/VisualGroup.cpp index e3bf628dc..83103c502 100644 --- a/launcher/ui/instanceview/VisualGroup.cpp +++ b/launcher/ui/instanceview/VisualGroup.cpp @@ -66,7 +66,7 @@ void VisualGroup::update() rows[currentRow].height = maxRowHeight; rows[currentRow].top = offsetFromTop; currentRow++; - if(currentRow >= rows.size()) { + if (currentRow >= rows.size()) { currentRow = rows.size() - 1; } offsetFromTop += maxRowHeight + 5; From b096ee6a0c8b3fb4e60b26d9460e4ff5ae684d4f Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 6 Jun 2024 14:12:55 +0300 Subject: [PATCH 098/103] fix file permisions Signed-off-by: Trial97 --- launcher/MMCZip.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 0b9dd16f5..a32c1f5d3 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -342,7 +342,7 @@ std::optional extractSubDir(QuaZip* zip, const QString& subdir, con auto newPermisions = (permissions & maxPermisions) | minPermisions; if (newPermisions != permissions) { - if (!QFile::setPermissions(target_file_path, permissions)) { + if (!QFile::setPermissions(target_file_path, newPermisions)) { qWarning() << (QObject::tr("Could not fix permissions for %1").arg(target_file_path)); } } @@ -608,7 +608,7 @@ auto ExtractZipTask::extractZip() -> ZipResult auto newPermisions = (permissions & maxPermisions) | minPermisions; if (newPermisions != permissions) { - if (!QFile::setPermissions(target_file_path, permissions)) { + if (!QFile::setPermissions(target_file_path, newPermisions)) { logWarning(tr("Could not fix permissions for %1").arg(target_file_path)); } } From 24cf671370e3d3710cdc37773bbae193ae8925b3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 9 Jun 2024 00:22:00 +0000 Subject: [PATCH 099/103] chore(nix): update lockfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:nixos/nixpkgs/6132b0f6e344ce2fe34fc051b72fb46e34f668e0?narHash=sha256-7R2ZvOnvd9h8fDd65p0JnB7wXfUvreox3xFdYWd1BnY%3D' (2024-05-30) → 'github:nixos/nixpkgs/d226935fd75012939397c83f6c385e4d6d832288?narHash=sha256-HV97wqUQv9wvptiHCb3Y0/YH0lJ60uZ8FYfEOIzYEqI%3D' (2024-06-07) • Updated input 'pre-commit-hooks': 'github:cachix/pre-commit-hooks.nix/0e8fcc54b842ad8428c9e705cb5994eaf05c26a0?narHash=sha256-xrsYFST8ij4QWaV6HEokCUNIZLjjLP1bYC60K8XiBVA%3D' (2024-05-20) → 'github:cachix/pre-commit-hooks.nix/cc4d466cb1254af050ff7bdf47f6d404a7c646d1?narHash=sha256-7XfBuLULizXjXfBYy/VV%2BSpYMHreNRHk9nKMsm1bgb4%3D' (2024-06-06) --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index e7bcc1635..810bedea9 100644 --- a/flake.lock +++ b/flake.lock @@ -75,11 +75,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1717112898, - "narHash": "sha256-7R2ZvOnvd9h8fDd65p0JnB7wXfUvreox3xFdYWd1BnY=", + "lastModified": 1717774105, + "narHash": "sha256-HV97wqUQv9wvptiHCb3Y0/YH0lJ60uZ8FYfEOIzYEqI=", "owner": "nixos", "repo": "nixpkgs", - "rev": "6132b0f6e344ce2fe34fc051b72fb46e34f668e0", + "rev": "d226935fd75012939397c83f6c385e4d6d832288", "type": "github" }, "original": { @@ -103,11 +103,11 @@ ] }, "locked": { - "lastModified": 1716213921, - "narHash": "sha256-xrsYFST8ij4QWaV6HEokCUNIZLjjLP1bYC60K8XiBVA=", + "lastModified": 1717664902, + "narHash": "sha256-7XfBuLULizXjXfBYy/VV+SpYMHreNRHk9nKMsm1bgb4=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "0e8fcc54b842ad8428c9e705cb5994eaf05c26a0", + "rev": "cc4d466cb1254af050ff7bdf47f6d404a7c646d1", "type": "github" }, "original": { From 04b2ac2efa7ccd4a83b88e6305744c39683c72b9 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 9 Jun 2024 20:48:49 +0300 Subject: [PATCH 100/103] update fail url skin messagebox Signed-off-by: Trial97 --- launcher/ui/dialogs/skins/SkinManageDialog.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index 19a1f6d52..0ec11c59d 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -355,7 +355,9 @@ void SkinManageDialog::on_urlBtn_clicked() dlg.execWithTask(job.get()); SkinModel s(path); if (!s.isValid()) { - CustomMessageBox::selectable(this, tr("URL is not a valid skin"), tr("Skin images must be 64x64 or 64x32 pixel PNG files."), + CustomMessageBox::selectable(this, tr("URL is not a valid skin"), + QFileInfo::exists(path) ? tr("Skin images must be 64x64 or 64x32 pixel PNG files.") + : tr("Unable to download the skin: '%1'.").arg(ui->urlLine->text()), QMessageBox::Critical) ->show(); QFile::remove(path); From 1e0db46728d9532d2803801d425454ebf2b4cc71 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 9 Jun 2024 21:29:56 +0300 Subject: [PATCH 101/103] add a more verbose error message in case user skin import fails Signed-off-by: Trial97 --- .../ui/dialogs/skins/SkinManageDialog.cpp | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index 0ec11c59d..eb22102ee 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -416,18 +416,33 @@ void SkinManageDialog::on_userBtn_clicked() auto getProfile = Net::Download::makeByteArray(QUrl(), profileOut); auto downloadSkin = Net::Download::makeFile(QUrl(), path); + QString failReason; + connect(getUUID.get(), &Task::aborted, uuidLoop.get(), &WaitTask::quit); + connect(getUUID.get(), &Task::failed, this, [&failReason](QString reason) { + qCritical() << "Couldn't get user UUID:" << reason; + failReason = tr("failed to get user UUID"); + }); connect(getUUID.get(), &Task::failed, uuidLoop.get(), &WaitTask::quit); connect(getProfile.get(), &Task::aborted, profileLoop.get(), &WaitTask::quit); connect(getProfile.get(), &Task::failed, profileLoop.get(), &WaitTask::quit); + connect(getProfile.get(), &Task::failed, this, [&failReason](QString reason) { + qCritical() << "Couldn't get user profile:" << reason; + failReason = tr("failed to get user profile"); + }); + connect(downloadSkin.get(), &Task::failed, this, [&failReason](QString reason) { + qCritical() << "Couldn't download skin:" << reason; + failReason = tr("failed to download skin"); + }); - connect(getUUID.get(), &Task::succeeded, this, [uuidLoop, uuidOut, job, getProfile] { + connect(getUUID.get(), &Task::succeeded, this, [uuidLoop, uuidOut, job, getProfile, &failReason] { try { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*uuidOut, &parse_error); if (parse_error.error != QJsonParseError::NoError) { qWarning() << "Error while parsing JSON response from Minecraft skin service at " << parse_error.offset << " reason: " << parse_error.errorString(); + failReason = tr("failed to parse get user UUID response"); uuidLoop->quit(); return; } @@ -436,18 +451,21 @@ void SkinManageDialog::on_userBtn_clicked() if (!id.isEmpty()) { getProfile->setUrl("https://sessionserver.mojang.com/session/minecraft/profile/" + id); } else { + failReason = tr("user id is empty"); job->abort(); } } catch (const Exception& e) { qCritical() << "Couldn't load skin json:" << e.cause(); + failReason = tr("failed to parse get user UUID response"); } uuidLoop->quit(); }); - connect(getProfile.get(), &Task::succeeded, this, [profileLoop, profileOut, job, getProfile, &mcProfile, downloadSkin] { + connect(getProfile.get(), &Task::succeeded, this, [profileLoop, profileOut, job, getProfile, &mcProfile, downloadSkin, &failReason] { if (Parsers::parseMinecraftProfileMojang(*profileOut, mcProfile)) { downloadSkin->setUrl(mcProfile.skin.url); } else { + failReason = tr("failed to parse get user profile response"); job->abort(); } profileLoop->quit(); @@ -463,8 +481,11 @@ void SkinManageDialog::on_userBtn_clicked() SkinModel s(path); if (!s.isValid()) { - CustomMessageBox::selectable(this, tr("Usename not found"), tr("Unable to find the skin for '%1'.").arg(user), - QMessageBox::Critical) + if (failReason.isEmpty()) { + failReason = tr("the skin is invalid"); + } + CustomMessageBox::selectable(this, tr("Usename not found"), + tr("Unable to find the skin for '%1'\n because: %2.").arg(user, failReason), QMessageBox::Critical) ->show(); QFile::remove(path); return; From 242ddbb7e11d2f9681199d1023ce3f1ea9a0b841 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 9 Jun 2024 23:53:40 +0300 Subject: [PATCH 102/103] fix size column sorting Signed-off-by: Trial97 --- launcher/minecraft/mod/DataPack.cpp | 17 ++++------ launcher/minecraft/mod/DataPack.h | 2 +- launcher/minecraft/mod/Mod.cpp | 24 +++++--------- launcher/minecraft/mod/Mod.h | 2 +- launcher/minecraft/mod/Resource.cpp | 31 +++++++++++-------- launcher/minecraft/mod/Resource.h | 7 +++-- .../minecraft/mod/ResourceFolderModel.cpp | 8 ++--- launcher/minecraft/mod/ResourcePack.cpp | 17 ++++------ launcher/minecraft/mod/ResourcePack.h | 2 +- 9 files changed, 48 insertions(+), 62 deletions(-) diff --git a/launcher/minecraft/mod/DataPack.cpp b/launcher/minecraft/mod/DataPack.cpp index fc2d3f68b..4ef13d10d 100644 --- a/launcher/minecraft/mod/DataPack.cpp +++ b/launcher/minecraft/mod/DataPack.cpp @@ -65,29 +65,24 @@ std::pair DataPack::compatibleVersions() const return s_pack_format_versions.constFind(m_pack_format).value(); } -std::pair DataPack::compare(const Resource& other, SortType type) const +int DataPack::compare(const Resource& other, SortType type, Qt::SortOrder order) const { auto const& cast_other = static_cast(other); - switch (type) { - default: { - auto res = Resource::compare(other, type); - if (res.first != 0) - return res; - break; - } + default: + return Resource::compare(other, type, order); case SortType::PACK_FORMAT: { auto this_ver = packFormat(); auto other_ver = cast_other.packFormat(); if (this_ver > other_ver) - return { 1, type == SortType::PACK_FORMAT }; + return 1; if (this_ver < other_ver) - return { -1, type == SortType::PACK_FORMAT }; + return -1; break; } } - return { 0, false }; + return 0; } bool DataPack::applyFilter(QRegularExpression filter) const diff --git a/launcher/minecraft/mod/DataPack.h b/launcher/minecraft/mod/DataPack.h index b3787b238..3bc4888da 100644 --- a/launcher/minecraft/mod/DataPack.h +++ b/launcher/minecraft/mod/DataPack.h @@ -56,7 +56,7 @@ class DataPack : public Resource { bool valid() const override; - [[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair override; + [[nodiscard]] int compare(Resource const& other, SortType type, Qt::SortOrder order = Qt::SortOrder::AscendingOrder) const override; [[nodiscard]] bool applyFilter(QRegularExpression filter) const override; protected: diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 8653cf822..af033c051 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -78,41 +78,33 @@ void Mod::setDetails(const ModDetails& details) m_local_details = details; } -std::pair Mod::compare(const Resource& other, SortType type) const +int Mod::compare(const Resource& other, SortType type, Qt::SortOrder order) const { auto cast_other = dynamic_cast(&other); if (!cast_other) - return Resource::compare(other, type); + return Resource::compare(other, type, order); switch (type) { default: case SortType::ENABLED: case SortType::NAME: case SortType::DATE: - case SortType::SIZE: { - auto res = Resource::compare(other, type); - if (res.first != 0) - return res; - break; - } + case SortType::SIZE: + return Resource::compare(other, type, order); case SortType::VERSION: { auto this_ver = Version(version()); auto other_ver = Version(cast_other->version()); if (this_ver > other_ver) - return { 1, type == SortType::VERSION }; + return 1; if (this_ver < other_ver) - return { -1, type == SortType::VERSION }; + return -1; break; } case SortType::PROVIDER: { - auto compare_result = - QString::compare(provider().value_or("Unknown"), cast_other->provider().value_or("Unknown"), Qt::CaseInsensitive); - if (compare_result != 0) - return { compare_result, type == SortType::PROVIDER }; - break; + return QString::compare(provider().value_or("Unknown"), cast_other->provider().value_or("Unknown"), Qt::CaseInsensitive); } } - return { 0, false }; + return 0; } bool Mod::applyFilter(QRegularExpression filter) const diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index e97ee9d3b..ac7fd1be9 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -88,7 +88,7 @@ class Mod : public Resource { bool valid() const override; - [[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair override; + [[nodiscard]] int compare(Resource const& other, SortType type, Qt::SortOrder order = Qt::SortOrder::AscendingOrder) const override; [[nodiscard]] bool applyFilter(QRegularExpression filter) const override; // Delete all the files of this mod diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index 2fe7e87d4..68594d741 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -30,7 +30,7 @@ std::tuple calculateFileSize(const QFileInfo& file) auto str = QObject::tr("item"); if (count != 1) str = QObject::tr("items"); - return { QString("%1 %2").arg(QString::number(count), str), -count }; + return { QString("%1 %2").arg(QString::number(count), str), count }; } return { StringUtils::humanReadableFileSize(file.size(), true), file.size() }; } @@ -79,15 +79,15 @@ static void removeThePrefix(QString& string) string = string.trimmed(); } -std::pair Resource::compare(const Resource& other, SortType type) const +int Resource::compare(const Resource& other, SortType type, Qt::SortOrder order) const { switch (type) { default: case SortType::ENABLED: if (enabled() && !other.enabled()) - return { 1, type == SortType::ENABLED }; + return 1; if (!enabled() && other.enabled()) - return { -1, type == SortType::ENABLED }; + return -1; break; case SortType::NAME: { QString this_name{ name() }; @@ -96,27 +96,32 @@ std::pair Resource::compare(const Resource& other, SortType type) con removeThePrefix(this_name); removeThePrefix(other_name); - auto compare_result = QString::compare(this_name, other_name, Qt::CaseInsensitive); - if (compare_result != 0) - return { compare_result, type == SortType::NAME }; - break; + return QString::compare(this_name, other_name, Qt::CaseInsensitive); } case SortType::DATE: if (dateTimeChanged() > other.dateTimeChanged()) - return { 1, type == SortType::DATE }; + return 1; if (dateTimeChanged() < other.dateTimeChanged()) - return { -1, type == SortType::DATE }; + return -1; break; case SortType::SIZE: { + auto order_op = order == Qt::SortOrder::AscendingOrder ? 1 : -1; + if (this->type() != other.type()) { + if (this->type() == ResourceType::FOLDER) + return -1 * order_op; + if (other.type() == ResourceType::FOLDER) + return 1 * order_op; + } + if (sizeInfo() > other.sizeInfo()) - return { 1, type == SortType::SIZE }; + return 1; if (sizeInfo() < other.sizeInfo()) - return { -1, type == SortType::SIZE }; + return -1; break; } } - return { 0, false }; + return 0; } bool Resource::applyFilter(QRegularExpression filter) const diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index 74f4d006e..19f0e352b 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -55,10 +55,11 @@ class Resource : public QObject { * > 0: 'this' comes after 'other' * = 0: 'this' is equal to 'other' * < 0: 'this' comes before 'other' - * - * The second argument in the pair is true if the sorting type that decided which one is greater was 'type'. + * the order is used to force items to be allways at top and not used for sorting */ - [[nodiscard]] virtual auto compare(Resource const& other, SortType type = SortType::NAME) const -> std::pair; + [[nodiscard]] virtual int compare(Resource const& other, + SortType type = SortType::NAME, + Qt::SortOrder order = Qt::SortOrder::AscendingOrder) const; /** Returns whether the given filter should filter out 'this' (false), * or if such filter includes the Resource (true). diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 37d92fa26..dde9a67b1 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -615,13 +615,11 @@ SortType ResourceFolderModel::columnToSortKey(size_t column) const auto const& resource_left = model->at(source_left.row()); auto const& resource_right = model->at(source_right.row()); - auto compare_result = resource_left.compare(resource_right, column_sort_key); - if (compare_result.first == 0) + auto compare_result = resource_left.compare(resource_right, column_sort_key, sortOrder()); + if (compare_result == 0) return QSortFilterProxyModel::lessThan(source_left, source_right); - if (compare_result.second || sortOrder() != Qt::DescendingOrder) - return (compare_result.first < 0); - return (compare_result.first > 0); + return compare_result < 0; } QString ResourceFolderModel::instDirPath() const diff --git a/launcher/minecraft/mod/ResourcePack.cpp b/launcher/minecraft/mod/ResourcePack.cpp index 074534405..ac4e1adaa 100644 --- a/launcher/minecraft/mod/ResourcePack.cpp +++ b/launcher/minecraft/mod/ResourcePack.cpp @@ -94,29 +94,24 @@ std::pair ResourcePack::compatibleVersions() const return s_pack_format_versions.constFind(m_pack_format).value(); } -std::pair ResourcePack::compare(const Resource& other, SortType type) const +int ResourcePack::compare(const Resource& other, SortType type, Qt::SortOrder order) const { auto const& cast_other = static_cast(other); - switch (type) { - default: { - auto res = Resource::compare(other, type); - if (res.first != 0) - return res; - break; - } + default: + return Resource::compare(other, type, order); case SortType::PACK_FORMAT: { auto this_ver = packFormat(); auto other_ver = cast_other.packFormat(); if (this_ver > other_ver) - return { 1, type == SortType::PACK_FORMAT }; + return 1; if (this_ver < other_ver) - return { -1, type == SortType::PACK_FORMAT }; + return -1; break; } } - return { 0, false }; + return 0; } bool ResourcePack::applyFilter(QRegularExpression filter) const diff --git a/launcher/minecraft/mod/ResourcePack.h b/launcher/minecraft/mod/ResourcePack.h index c06f3793d..b14623d6b 100644 --- a/launcher/minecraft/mod/ResourcePack.h +++ b/launcher/minecraft/mod/ResourcePack.h @@ -44,7 +44,7 @@ class ResourcePack : public Resource { bool valid() const override; - [[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair override; + [[nodiscard]] int compare(Resource const& other, SortType type, Qt::SortOrder order = Qt::SortOrder::AscendingOrder) const override; [[nodiscard]] bool applyFilter(QRegularExpression filter) const override; protected: From 29d32f0d9ae30562b20afef6af52ed883dc17a48 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Mon, 10 Jun 2024 00:18:00 +0300 Subject: [PATCH 103/103] Update size order for folders Signed-off-by: Trial97 --- launcher/minecraft/mod/DataPack.cpp | 4 ++-- launcher/minecraft/mod/DataPack.h | 2 +- launcher/minecraft/mod/Mod.cpp | 6 +++--- launcher/minecraft/mod/Mod.h | 2 +- launcher/minecraft/mod/Resource.cpp | 7 +++---- launcher/minecraft/mod/Resource.h | 5 +---- launcher/minecraft/mod/ResourceFolderModel.cpp | 2 +- launcher/minecraft/mod/ResourcePack.cpp | 4 ++-- launcher/minecraft/mod/ResourcePack.h | 2 +- 9 files changed, 15 insertions(+), 19 deletions(-) diff --git a/launcher/minecraft/mod/DataPack.cpp b/launcher/minecraft/mod/DataPack.cpp index 4ef13d10d..ee4d4f1a6 100644 --- a/launcher/minecraft/mod/DataPack.cpp +++ b/launcher/minecraft/mod/DataPack.cpp @@ -65,12 +65,12 @@ std::pair DataPack::compatibleVersions() const return s_pack_format_versions.constFind(m_pack_format).value(); } -int DataPack::compare(const Resource& other, SortType type, Qt::SortOrder order) const +int DataPack::compare(const Resource& other, SortType type) const { auto const& cast_other = static_cast(other); switch (type) { default: - return Resource::compare(other, type, order); + return Resource::compare(other, type); case SortType::PACK_FORMAT: { auto this_ver = packFormat(); auto other_ver = cast_other.packFormat(); diff --git a/launcher/minecraft/mod/DataPack.h b/launcher/minecraft/mod/DataPack.h index 3bc4888da..4855b0203 100644 --- a/launcher/minecraft/mod/DataPack.h +++ b/launcher/minecraft/mod/DataPack.h @@ -56,7 +56,7 @@ class DataPack : public Resource { bool valid() const override; - [[nodiscard]] int compare(Resource const& other, SortType type, Qt::SortOrder order = Qt::SortOrder::AscendingOrder) const override; + [[nodiscard]] int compare(Resource const& other, SortType type) const override; [[nodiscard]] bool applyFilter(QRegularExpression filter) const override; protected: diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index af033c051..e92079055 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -78,11 +78,11 @@ void Mod::setDetails(const ModDetails& details) m_local_details = details; } -int Mod::compare(const Resource& other, SortType type, Qt::SortOrder order) const +int Mod::compare(const Resource& other, SortType type) const { auto cast_other = dynamic_cast(&other); if (!cast_other) - return Resource::compare(other, type, order); + return Resource::compare(other, type); switch (type) { default: @@ -90,7 +90,7 @@ int Mod::compare(const Resource& other, SortType type, Qt::SortOrder order) cons case SortType::NAME: case SortType::DATE: case SortType::SIZE: - return Resource::compare(other, type, order); + return Resource::compare(other, type); case SortType::VERSION: { auto this_ver = Version(version()); auto other_ver = Version(cast_other->version()); diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index ac7fd1be9..a0c68688d 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -88,7 +88,7 @@ class Mod : public Resource { bool valid() const override; - [[nodiscard]] int compare(Resource const& other, SortType type, Qt::SortOrder order = Qt::SortOrder::AscendingOrder) const override; + [[nodiscard]] int compare(Resource const& other, SortType type) const override; [[nodiscard]] bool applyFilter(QRegularExpression filter) const override; // Delete all the files of this mod diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index 68594d741..863b0165f 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -79,7 +79,7 @@ static void removeThePrefix(QString& string) string = string.trimmed(); } -int Resource::compare(const Resource& other, SortType type, Qt::SortOrder order) const +int Resource::compare(const Resource& other, SortType type) const { switch (type) { default: @@ -105,12 +105,11 @@ int Resource::compare(const Resource& other, SortType type, Qt::SortOrder order) return -1; break; case SortType::SIZE: { - auto order_op = order == Qt::SortOrder::AscendingOrder ? 1 : -1; if (this->type() != other.type()) { if (this->type() == ResourceType::FOLDER) - return -1 * order_op; + return -1; if (other.type() == ResourceType::FOLDER) - return 1 * order_op; + return 1; } if (sizeInfo() > other.sizeInfo()) diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index 19f0e352b..222f07afa 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -55,11 +55,8 @@ class Resource : public QObject { * > 0: 'this' comes after 'other' * = 0: 'this' is equal to 'other' * < 0: 'this' comes before 'other' - * the order is used to force items to be allways at top and not used for sorting */ - [[nodiscard]] virtual int compare(Resource const& other, - SortType type = SortType::NAME, - Qt::SortOrder order = Qt::SortOrder::AscendingOrder) const; + [[nodiscard]] virtual int compare(Resource const& other, SortType type = SortType::NAME) const; /** Returns whether the given filter should filter out 'this' (false), * or if such filter includes the Resource (true). diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index dde9a67b1..254342ee1 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -615,7 +615,7 @@ SortType ResourceFolderModel::columnToSortKey(size_t column) const auto const& resource_left = model->at(source_left.row()); auto const& resource_right = model->at(source_right.row()); - auto compare_result = resource_left.compare(resource_right, column_sort_key, sortOrder()); + auto compare_result = resource_left.compare(resource_right, column_sort_key); if (compare_result == 0) return QSortFilterProxyModel::lessThan(source_left, source_right); diff --git a/launcher/minecraft/mod/ResourcePack.cpp b/launcher/minecraft/mod/ResourcePack.cpp index ac4e1adaa..5b26503f5 100644 --- a/launcher/minecraft/mod/ResourcePack.cpp +++ b/launcher/minecraft/mod/ResourcePack.cpp @@ -94,12 +94,12 @@ std::pair ResourcePack::compatibleVersions() const return s_pack_format_versions.constFind(m_pack_format).value(); } -int ResourcePack::compare(const Resource& other, SortType type, Qt::SortOrder order) const +int ResourcePack::compare(const Resource& other, SortType type) const { auto const& cast_other = static_cast(other); switch (type) { default: - return Resource::compare(other, type, order); + return Resource::compare(other, type); case SortType::PACK_FORMAT: { auto this_ver = packFormat(); auto other_ver = cast_other.packFormat(); diff --git a/launcher/minecraft/mod/ResourcePack.h b/launcher/minecraft/mod/ResourcePack.h index b14623d6b..2254fc5c4 100644 --- a/launcher/minecraft/mod/ResourcePack.h +++ b/launcher/minecraft/mod/ResourcePack.h @@ -44,7 +44,7 @@ class ResourcePack : public Resource { bool valid() const override; - [[nodiscard]] int compare(Resource const& other, SortType type, Qt::SortOrder order = Qt::SortOrder::AscendingOrder) const override; + [[nodiscard]] int compare(Resource const& other, SortType type) const override; [[nodiscard]] bool applyFilter(QRegularExpression filter) const override; protected: