From 5932f36285ebf3ec7d852db1b80630589224eccc Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 30 Jul 2022 19:38:56 -0300 Subject: [PATCH 01/58] refactor: use std::filesystem for file copy Signed-off-by: flow --- launcher/FileSystem.cpp | 74 ++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 38 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 8eeb2885..e4db6d35 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -37,6 +37,7 @@ #include #include +#include #include #include #include @@ -58,6 +59,8 @@ #include #endif +#include + namespace FS { void ensureExists(const QDir& dir) @@ -128,6 +131,8 @@ bool ensureFolderPathExists(QString foldernamepath) bool copy::operator()(const QString& offset) { + using copy_opts = std::filesystem::copy_options; + // NOTE always deep copy on windows. the alternatives are too messy. #if defined Q_OS_WIN32 m_followSymlinks = true; @@ -136,47 +141,40 @@ bool copy::operator()(const QString& offset) auto src = PathCombine(m_src.absolutePath(), offset); auto dst = PathCombine(m_dst.absolutePath(), offset); - QFileInfo currentSrc(src); - if (!currentSrc.exists()) - return false; + std::error_code err; - if (!m_followSymlinks && currentSrc.isSymLink()) { - qDebug() << "creating symlink" << src << " - " << dst; - if (!ensureFilePathExists(dst)) { - qWarning() << "Cannot create path!"; - return false; + std::filesystem::copy_options opt = copy_opts::none; + + // The default behavior is to follow symlinks + if (!m_followSymlinks) + opt |= copy_opts::copy_symlinks; + + + // We can't use copy_opts::recursive because we need to take into account the + // blacklisted paths, so we iterate over the source directory, and if there's no blacklist + // match, we copy the file. + QDir src_dir(src); + QDirIterator source_it(src, QDir::Filter::Files, QDirIterator::Subdirectories); + + while (source_it.hasNext()) { + auto src_path = source_it.next(); + auto relative_path = src_dir.relativeFilePath(src_path); + + if (m_blacklist && m_blacklist->matches(relative_path)) + continue; + + auto dst_path = PathCombine(dst, relative_path); + ensureFilePathExists(dst_path); + + std::filesystem::copy(src_path.toStdString(), dst_path.toStdString(), opt, err); + if (err) { + qWarning() << "Failed to copy files:" << QString::fromStdString(err.message()); + qDebug() << "Source file:" << src_path; + qDebug() << "Destination file:" << dst_path; } - return QFile::link(currentSrc.symLinkTarget(), dst); - } else if (currentSrc.isFile()) { - qDebug() << "copying file" << src << " - " << dst; - if (!ensureFilePathExists(dst)) { - qWarning() << "Cannot create path!"; - return false; - } - return QFile::copy(src, dst); - } else if (currentSrc.isDir()) { - qDebug() << "recursing" << offset; - if (!ensureFolderPathExists(dst)) { - qWarning() << "Cannot create path!"; - return false; - } - QDir currentDir(src); - for (auto& f : currentDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System)) { - auto inner_offset = PathCombine(offset, f); - // ignore and skip stuff that matches the blacklist. - if (m_blacklist && m_blacklist->matches(inner_offset)) { - continue; - } - if (!operator()(inner_offset)) { - qWarning() << "Failed to copy" << inner_offset; - return false; - } - } - } else { - qCritical() << "Copy ERROR: Unknown filesystem object:" << src; - return false; } - return true; + + return err.value() == 0; } bool deletePath(QString path) From be3fae65113023d77d2df59f9fd765a4c83f07f2 Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 30 Jul 2022 20:40:25 -0300 Subject: [PATCH 02/58] refactor: use std::filesystem for path deletion Signed-off-by: flow --- launcher/FileSystem.cpp | 48 ++++++----------------------------------- 1 file changed, 7 insertions(+), 41 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index e4db6d35..c1baf20a 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -179,49 +179,15 @@ bool copy::operator()(const QString& offset) bool deletePath(QString path) { - bool OK = true; - QFileInfo finfo(path); - if (finfo.isFile()) { - return QFile::remove(path); + std::error_code err; + + std::filesystem::remove_all(path.toStdString(), err); + + if (err) { + qWarning() << "Failed to remove files:" << QString::fromStdString(err.message()); } - QDir dir(path); - - if (!dir.exists()) { - return OK; - } - auto allEntries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst); - - for (auto& info : allEntries) { -#if defined Q_OS_WIN32 - QString nativePath = QDir::toNativeSeparators(info.absoluteFilePath()); - auto wString = nativePath.toStdWString(); - DWORD dwAttrs = GetFileAttributesW(wString.c_str()); - // Windows: check for junctions, reparse points and other nasty things of that sort - if (dwAttrs & FILE_ATTRIBUTE_REPARSE_POINT) { - if (info.isFile()) { - OK &= QFile::remove(info.absoluteFilePath()); - } else if (info.isDir()) { - OK &= dir.rmdir(info.absoluteFilePath()); - } - } -#else - // We do not trust Qt with reparse points, but do trust it with unix symlinks. - if (info.isSymLink()) { - OK &= QFile::remove(info.absoluteFilePath()); - } -#endif - else if (info.isDir()) { - OK &= deletePath(info.absoluteFilePath()); - } else if (info.isFile()) { - OK &= QFile::remove(info.absoluteFilePath()); - } else { - OK = false; - qCritical() << "Delete ERROR: Unknown filesystem object:" << info.absoluteFilePath(); - } - } - OK &= dir.rmdir(dir.absolutePath()); - return OK; + return err.value() == 0; } bool trash(QString path, QString *pathInTrash = nullptr) From 1cf949226e786e827de2d477516c5a14ecfbd6bd Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 7 Aug 2022 08:50:39 -0300 Subject: [PATCH 03/58] refactor: use std::filesystem for overrides Signed-off-by: flow --- launcher/FileSystem.cpp | 41 +++++++++-------------------------------- 1 file changed, 9 insertions(+), 32 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index c1baf20a..fefa6dd1 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -403,47 +403,24 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na #endif } -QStringList listFolderPaths(QDir root) -{ - auto createAbsPath = [](QFileInfo const& entry) { return FS::PathCombine(entry.path(), entry.fileName()); }; - - QStringList entries; - - root.refresh(); - for (auto entry : root.entryInfoList(QDir::Filter::Files)) { - entries.append(createAbsPath(entry)); - } - - for (auto entry : root.entryInfoList(QDir::Filter::AllDirs | QDir::Filter::NoDotAndDotDot)) { - entries.append(listFolderPaths(createAbsPath(entry))); - } - - return entries; -} - bool overrideFolder(QString overwritten_path, QString override_path) { + using copy_opts = std::filesystem::copy_options; + if (!FS::ensureFolderPathExists(overwritten_path)) return false; - QStringList paths_to_override; - QDir root_override (override_path); - for (auto file : listFolderPaths(root_override)) { - QString destination = file; - destination.replace(override_path, overwritten_path); - ensureFilePathExists(destination); + std::error_code err; + std::filesystem::copy_options opt = copy_opts::recursive | copy_opts::overwrite_existing; - qDebug() << QString("Applying override %1 in %2").arg(file, destination); + std::filesystem::copy(override_path.toStdString(), overwritten_path.toStdString(), opt, err); - if (QFile::exists(destination)) - QFile::remove(destination); - if (!QFile::rename(file, destination)) { - qCritical() << QString("Failed to apply override from %1 to %2").arg(file, destination); - return false; - } + if (err) { + qCritical() << QString("Failed to apply override from %1 to %2").arg(override_path, overwritten_path); + qCritical() << "Reason:" << QString::fromStdString(err.message()); } - return true; + return err.value() == 0; } } From 277fa21f5f7dcdec1bde11f345136f464ee8d37a Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 7 Aug 2022 07:18:37 -0300 Subject: [PATCH 04/58] refactor: remove Win32 'crap' in FileSystem We should use std::filesystem symlink and hardlink functions instead. Signed-off-by: flow --- launcher/FileSystem.cpp | 44 ----------------------------------------- 1 file changed, 44 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index fefa6dd1..8966413a 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -300,50 +300,6 @@ bool checkProblemticPathJava(QDir folder) return pathfoldername.contains("!", Qt::CaseInsensitive); } -// Win32 crap -#ifdef Q_OS_WIN - -bool called_coinit = false; - -HRESULT CreateLink(LPCCH linkPath, LPCWSTR targetPath, LPCWSTR args) -{ - HRESULT hres; - - if (!called_coinit) { - hres = CoInitialize(NULL); - called_coinit = true; - - if (!SUCCEEDED(hres)) { - qWarning("Failed to initialize COM. Error 0x%08lX", hres); - return hres; - } - } - - IShellLink* link; - hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&link); - - if (SUCCEEDED(hres)) { - IPersistFile* persistFile; - - link->SetPath(targetPath); - link->SetArguments(args); - - hres = link->QueryInterface(IID_IPersistFile, (LPVOID*)&persistFile); - if (SUCCEEDED(hres)) { - WCHAR wstr[MAX_PATH]; - - MultiByteToWideChar(CP_ACP, 0, linkPath, -1, wstr, MAX_PATH); - - hres = persistFile->Save(wstr, TRUE); - persistFile->Release(); - } - link->Release(); - } - return hres; -} - -#endif - QString getDesktopDir() { return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); From c496ad1237198cf9c5a97b3b02c2897b5ac1990a Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 7 Aug 2022 07:19:29 -0300 Subject: [PATCH 05/58] chore: make DirNameFromString add normal duplicate identifier Wrap the number in parenthesis to be similar to other software. Signed-off-by: flow --- launcher/FileSystem.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 8966413a..2409696b 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -280,8 +280,7 @@ QString DirNameFromString(QString string, QString inDir) if (num == 0) { dirName = baseName; } else { - dirName = baseName + QString::number(num); - ; + dirName = baseName + "(" + QString::number(num) + ")"; } // If it's over 9000 From ee0fb2d0e00bba8c000a277d5f7199ae46a095df Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 7 Aug 2022 14:30:38 -0300 Subject: [PATCH 06/58] fix: use std::wstring for Windows filenames Signed-off-by: flow --- launcher/FileSystem.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 2409696b..5d179641 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -61,6 +61,22 @@ #include +#if defined Q_OS_WIN32 + +std::wstring toStdString(QString s) +{ + return s.toStdWString(); +} + +#else + +std::string toStdString(QString s) +{ + return s.toStdString(); +} + +#endif + namespace FS { void ensureExists(const QDir& dir) @@ -166,7 +182,7 @@ bool copy::operator()(const QString& offset) auto dst_path = PathCombine(dst, relative_path); ensureFilePathExists(dst_path); - std::filesystem::copy(src_path.toStdString(), dst_path.toStdString(), opt, err); + std::filesystem::copy(toStdString(src_path), toStdString(dst_path), opt, err); if (err) { qWarning() << "Failed to copy files:" << QString::fromStdString(err.message()); qDebug() << "Source file:" << src_path; @@ -181,7 +197,7 @@ bool deletePath(QString path) { std::error_code err; - std::filesystem::remove_all(path.toStdString(), err); + std::filesystem::remove_all(toStdString(path), err); if (err) { qWarning() << "Failed to remove files:" << QString::fromStdString(err.message()); @@ -368,7 +384,7 @@ bool overrideFolder(QString overwritten_path, QString override_path) std::error_code err; std::filesystem::copy_options opt = copy_opts::recursive | copy_opts::overwrite_existing; - std::filesystem::copy(override_path.toStdString(), overwritten_path.toStdString(), opt, err); + std::filesystem::copy(toStdString(override_path), toStdString(overwritten_path), opt, err); if (err) { qCritical() << QString("Failed to apply override from %1 to %2").arg(override_path, overwritten_path); From 931d6c280a877cefddcc93660aba0c1c0c014366 Mon Sep 17 00:00:00 2001 From: flow Date: Mon, 12 Sep 2022 13:12:55 -0300 Subject: [PATCH 07/58] chore(tests): add test for copy operation with blacklist I almost :skull: because no tests used this x.x Signed-off-by: flow --- tests/FileSystem_test.cpp | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp index 6df13e80..4efb90ac 100644 --- a/tests/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -4,6 +4,8 @@ #include +#include + class FileSystemTest : public QObject { Q_OBJECT @@ -111,6 +113,40 @@ slots: f(); } + void test_copy_with_blacklist() + { + QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder"); + auto f = [&folder]() + { + QTemporaryDir tempDir; + tempDir.setAutoRemove(true); + qDebug() << "From:" << folder << "To:" << tempDir.path(); + + QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder")); + qDebug() << tempDir.path(); + qDebug() << target_dir.path(); + FS::copy c(folder, target_dir.path()); + c.blacklist(new RegexpMatcher("[.]?mcmeta")); + c(); + + for(auto entry: target_dir.entryList()) + { + qDebug() << entry; + } + QVERIFY(!target_dir.entryList().contains("pack.mcmeta")); + QVERIFY(target_dir.entryList().contains("assets")); + }; + + // first try variant without trailing / + QVERIFY(!folder.endsWith('/')); + f(); + + // then variant with trailing / + folder.append('/'); + QVERIFY(folder.endsWith('/')); + f(); + } + void test_getDesktop() { QCOMPARE(FS::getDesktopDir(), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); From 8c41ff68f7b09957c8b2f63c7dbdc1045a7fc756 Mon Sep 17 00:00:00 2001 From: flow Date: Mon, 12 Sep 2022 18:41:13 -0300 Subject: [PATCH 08/58] chore(actions)!: bump macOS required version to 10.15 This is needed for std::filesystem support in macOS's libc. Signed-off-by: flow --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 434c5775..ae8947ab 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,7 +39,7 @@ jobs: qt_ver: 6 - os: macos-12 - macosx_deployment_target: 10.14 + macosx_deployment_target: 10.15 qt_ver: 6 qt_host: mac qt_version: '6.3.0' From 684b8f24f3fb99659f474a8cd1319c6aaeba1655 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Thu, 15 Sep 2022 09:29:48 +0200 Subject: [PATCH 09/58] fix: allow starting rd- versions Using `Collections.emptyList()` doesn't allow us to later append stuff into that list. Use an empty `ArrayList` instead. Signed-off-by: Sefa Eyeoglu --- libraries/launcher/org/polymc/impl/OneSixLauncher.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/launcher/org/polymc/impl/OneSixLauncher.java b/libraries/launcher/org/polymc/impl/OneSixLauncher.java index 362ff8d6..4ab46678 100644 --- a/libraries/launcher/org/polymc/impl/OneSixLauncher.java +++ b/libraries/launcher/org/polymc/impl/OneSixLauncher.java @@ -24,8 +24,8 @@ import java.applet.Applet; import java.io.File; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.Collections; import java.util.List; +import java.util.ArrayList; import java.util.logging.Level; import java.util.logging.Logger; @@ -58,10 +58,10 @@ public final class OneSixLauncher implements Launcher { public OneSixLauncher(Parameters params) { classLoader = ClassLoader.getSystemClassLoader(); - mcParams = params.allSafe("param", Collections.emptyList()); + mcParams = params.allSafe("param", new ArrayList()); mainClass = params.firstSafe("mainClass", "net.minecraft.client.Minecraft"); appletClass = params.firstSafe("appletClass", "net.minecraft.client.MinecraftApplet"); - traits = params.allSafe("traits", Collections.emptyList()); + traits = params.allSafe("traits", new ArrayList()); userName = params.first("userName"); sessionId = params.first("sessionId"); From 29dcb9d2747c61b20fe9f870574bc9c4c958e82a Mon Sep 17 00:00:00 2001 From: jopejoe1 Date: Mon, 11 Jul 2022 20:46:11 +0200 Subject: [PATCH 10/58] Added Launch Demo button. Signed-off-by: jopejoe1 --- launcher/Application.cpp | 5 +- launcher/Application.h | 1 + launcher/LaunchController.cpp | 9 +++- launcher/LaunchController.h | 5 ++ launcher/ui/InstanceWindow.cpp | 18 ++++++- launcher/ui/InstanceWindow.h | 2 + launcher/ui/MainWindow.cpp | 55 ++++++++++++++++++++-- launcher/ui/MainWindow.h | 2 + launcher/ui/pages/instance/ServersPage.cpp | 2 +- 9 files changed, 90 insertions(+), 9 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index c4179b49..0d1db11c 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -1041,7 +1041,7 @@ void Application::performMainStartupAction() qDebug() << " Launching with account" << m_profileToUse; } - launch(inst, true, nullptr, serverToJoin, accountToUse); + launch(inst, true, false, nullptr, serverToJoin, accountToUse); return; } } @@ -1145,6 +1145,7 @@ void Application::messageReceived(const QByteArray& message) launch( instance, true, + false, nullptr, serverObject, accountObject @@ -1245,6 +1246,7 @@ bool Application::openJsonEditor(const QString &filename) bool Application::launch( InstancePtr instance, bool online, + bool demo, BaseProfilerFactory *profiler, MinecraftServerTargetPtr serverToJoin, MinecraftAccountPtr accountToUse @@ -1268,6 +1270,7 @@ bool Application::launch( controller.reset(new LaunchController()); controller->setInstance(instance); controller->setOnline(online); + controller->setDemo(demo); controller->setProfiler(profiler); controller->setServerToJoin(serverToJoin); controller->setAccountToUse(accountToUse); diff --git a/launcher/Application.h b/launcher/Application.h index 41fd4c47..34ad8c15 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -213,6 +213,7 @@ public slots: bool launch( InstancePtr instance, bool online = true, + bool demo = false, BaseProfilerFactory *profiler = nullptr, MinecraftServerTargetPtr serverToJoin = nullptr, MinecraftAccountPtr accountToUse = nullptr diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 11f9b2bb..b4a58d01 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -167,6 +167,7 @@ void LaunchController::login() { tries++; m_session = std::make_shared(); m_session->wants_online = m_online; + m_session->demo = m_demo; m_accountToUse->fillSession(m_session); // Launch immediately in true offline mode @@ -184,12 +185,18 @@ void LaunchController::login() { if(!m_session->wants_online) { // we ask the user for a player name bool ok = false; + + QString message = tr("Choose your offline mode player name."); + if(m_session->demo) { + message = tr("Choose your demo mode player name."); + } + QString lastOfflinePlayerName = APPLICATION->settings()->get("LastOfflinePlayerName").toString(); QString usedname = lastOfflinePlayerName.isEmpty() ? m_session->player_name : lastOfflinePlayerName; QString name = QInputDialog::getText( m_parentWidget, tr("Player name"), - tr("Choose your offline mode player name."), + message, QLineEdit::Normal, usedname, &ok diff --git a/launcher/LaunchController.h b/launcher/LaunchController.h index 2171ad5e..af6c98d1 100644 --- a/launcher/LaunchController.h +++ b/launcher/LaunchController.h @@ -63,6 +63,10 @@ public: m_online = online; } + void setDemo(bool demo) { + m_demo = demo; + } + void setProfiler(BaseProfilerFactory *profiler) { m_profiler = profiler; } @@ -101,6 +105,7 @@ private slots: private: BaseProfilerFactory *m_profiler = nullptr; bool m_online = true; + bool m_demo = false; InstancePtr m_instance; QWidget * m_parentWidget = nullptr; InstanceWindow *m_console = nullptr; diff --git a/launcher/ui/InstanceWindow.cpp b/launcher/ui/InstanceWindow.cpp index 0ad8c594..5bf42bb5 100644 --- a/launcher/ui/InstanceWindow.cpp +++ b/launcher/ui/InstanceWindow.cpp @@ -95,8 +95,14 @@ InstanceWindow::InstanceWindow(InstancePtr instance, QWidget *parent) m_launchOfflineButton = new QPushButton(); horizontalLayout->addWidget(m_launchOfflineButton); m_launchOfflineButton->setText(tr("Launch Offline")); + + m_launchDemoButton = new QPushButton(); + horizontalLayout->addWidget(m_launchDemoButton); + m_launchDemoButton->setText(tr("Launch Demo")); + updateLaunchButtons(); connect(m_launchOfflineButton, SIGNAL(clicked(bool)), SLOT(on_btnLaunchMinecraftOffline_clicked())); + connect(m_launchDemoButton, SIGNAL(clicked(bool)), SLOT(on_btnLaunchMinecraftDemo_clicked())); m_closeButton = new QPushButton(); m_closeButton->setText(tr("Close")); @@ -143,6 +149,7 @@ void InstanceWindow::updateLaunchButtons() if(m_instance->isRunning()) { m_launchOfflineButton->setEnabled(false); + m_launchDemoButton->setEnabled(false); m_killButton->setText(tr("Kill")); m_killButton->setObjectName("killButton"); m_killButton->setToolTip(tr("Kill the running instance")); @@ -150,6 +157,7 @@ void InstanceWindow::updateLaunchButtons() else if(!m_instance->canLaunch()) { m_launchOfflineButton->setEnabled(false); + m_launchDemoButton->setEnabled(false); m_killButton->setText(tr("Launch")); m_killButton->setObjectName("launchButton"); m_killButton->setToolTip(tr("Launch the instance")); @@ -158,6 +166,7 @@ void InstanceWindow::updateLaunchButtons() else { m_launchOfflineButton->setEnabled(true); + m_launchDemoButton->setEnabled(true); m_killButton->setText(tr("Launch")); m_killButton->setObjectName("launchButton"); m_killButton->setToolTip(tr("Launch the instance")); @@ -169,7 +178,12 @@ void InstanceWindow::updateLaunchButtons() void InstanceWindow::on_btnLaunchMinecraftOffline_clicked() { - APPLICATION->launch(m_instance, false, nullptr); + APPLICATION->launch(m_instance, false, false, nullptr); +} + +void InstanceWindow::on_btnLaunchMinecraftDemo_clicked() +{ + APPLICATION->launch(m_instance, false, true, nullptr); } void InstanceWindow::instanceLaunchTaskChanged(shared_qobject_ptr proc) @@ -223,7 +237,7 @@ void InstanceWindow::on_btnKillMinecraft_clicked() } else { - APPLICATION->launch(m_instance, true, nullptr); + APPLICATION->launch(m_instance, true, false, nullptr); } } diff --git a/launcher/ui/InstanceWindow.h b/launcher/ui/InstanceWindow.h index aec07868..554c4c74 100644 --- a/launcher/ui/InstanceWindow.h +++ b/launcher/ui/InstanceWindow.h @@ -74,6 +74,7 @@ slots: void on_closeButton_clicked(); void on_btnKillMinecraft_clicked(); void on_btnLaunchMinecraftOffline_clicked(); + void on_btnLaunchMinecraftDemo_clicked(); void instanceLaunchTaskChanged(shared_qobject_ptr proc); void runningStateChanged(bool running); @@ -93,4 +94,5 @@ private: QPushButton *m_closeButton = nullptr; QPushButton *m_killButton = nullptr; QPushButton *m_launchOfflineButton = nullptr; + QPushButton *m_launchDemoButton = nullptr; }; diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 299401f5..2d8ed24c 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -240,6 +240,7 @@ public: TranslatedAction actionCAT; TranslatedAction actionCopyInstance; TranslatedAction actionLaunchInstanceOffline; + TranslatedAction actionLaunchInstanceDemo; TranslatedAction actionScreenshots; TranslatedAction actionExportInstance; QVector all_actions; @@ -499,6 +500,7 @@ public: fileMenu->addAction(actionAddInstance); fileMenu->addAction(actionLaunchInstance); fileMenu->addAction(actionLaunchInstanceOffline); + fileMenu->addAction(actionLaunchInstanceDemo); fileMenu->addAction(actionKillInstance); fileMenu->addAction(actionCloseWindow); fileMenu->addSeparator(); @@ -669,6 +671,12 @@ public: actionLaunchInstanceOffline.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Launch the selected instance in offline mode.")); all_actions.append(&actionLaunchInstanceOffline); + actionLaunchInstanceDemo = TranslatedAction(MainWindow); + actionLaunchInstanceDemo->setObjectName(QStringLiteral("actionLaunchInstanceDemo")); + actionLaunchInstanceDemo.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Launch &Demo")); + actionLaunchInstanceDemo.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Launch the selected instance in demo mode.")); + all_actions.append(&actionLaunchInstanceDemo); + actionKillInstance = TranslatedAction(MainWindow); actionKillInstance->setObjectName(QStringLiteral("actionKillInstance")); actionKillInstance->setDisabled(true); @@ -785,6 +793,7 @@ public: instanceToolBar->addAction(actionLaunchInstance); instanceToolBar->addAction(actionLaunchInstanceOffline); + instanceToolBar->addAction(actionLaunchInstanceDemo); instanceToolBar->addAction(actionKillInstance); instanceToolBar->addSeparator(); @@ -1190,16 +1199,20 @@ void MainWindow::updateToolsMenu() { QToolButton *launchButton = dynamic_cast(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstance)); QToolButton *launchOfflineButton = dynamic_cast(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstanceOffline)); + QToolButton *launchDemoButton = dynamic_cast(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstanceDemo)); bool currentInstanceRunning = m_selectedInstance && m_selectedInstance->isRunning(); ui->actionLaunchInstance->setDisabled(!m_selectedInstance || currentInstanceRunning); ui->actionLaunchInstanceOffline->setDisabled(!m_selectedInstance || currentInstanceRunning); + ui->actionLaunchInstanceDemo->setDisabled(!m_selectedInstance || currentInstanceRunning); QMenu *launchMenu = ui->actionLaunchInstance->menu(); QMenu *launchOfflineMenu = ui->actionLaunchInstanceOffline->menu(); + QMenu *launchDemoMenu = ui->actionLaunchInstanceDemo->menu(); launchButton->setPopupMode(QToolButton::MenuButtonPopup); launchOfflineButton->setPopupMode(QToolButton::MenuButtonPopup); + launchDemoButton->setPopupMode(QToolButton::MenuButtonPopup); if (launchMenu) { launchMenu->clear(); @@ -1215,66 +1228,90 @@ void MainWindow::updateToolsMenu() { launchOfflineMenu = new QMenu(this); } + if (launchDemoMenu) { + launchDemoMenu->clear(); + } + else + { + launchDemoMenu = new QMenu(this); + } QAction *normalLaunch = launchMenu->addAction(tr("Launch")); normalLaunch->setShortcut(QKeySequence::Open); QAction *normalLaunchOffline = launchOfflineMenu->addAction(tr("Launch Offline")); normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O"))); + QAction *normalLaunchDemo = launchDemoMenu->addAction(tr("Launch Demo")); if (m_selectedInstance) { normalLaunch->setEnabled(m_selectedInstance->canLaunch()); normalLaunchOffline->setEnabled(m_selectedInstance->canLaunch()); + normalLaunchDemo->setEnabled(m_selectedInstance->canLaunch()); connect(normalLaunch, &QAction::triggered, [this]() { - APPLICATION->launch(m_selectedInstance, true); + APPLICATION->launch(m_selectedInstance, true, false); }); connect(normalLaunchOffline, &QAction::triggered, [this]() { - APPLICATION->launch(m_selectedInstance, false); + APPLICATION->launch(m_selectedInstance, false, false); + }); + connect(normalLaunchDemo, &QAction::triggered, [this]() { + APPLICATION->launch(m_selectedInstance, false, true); }); } else { normalLaunch->setDisabled(true); normalLaunchOffline->setDisabled(true); + normalLaunchDemo->setDisabled(true); } QString profilersTitle = tr("Profilers"); launchMenu->addSeparator()->setText(profilersTitle); launchOfflineMenu->addSeparator()->setText(profilersTitle); + launchDemoMenu->addSeparator()->setText(profilersTitle); for (auto profiler : APPLICATION->profilers().values()) { QAction *profilerAction = launchMenu->addAction(profiler->name()); QAction *profilerOfflineAction = launchOfflineMenu->addAction(profiler->name()); + QAction *profilerDemoAction = launchDemoMenu->addAction(profiler->name()); QString error; if (!profiler->check(&error)) { profilerAction->setDisabled(true); profilerOfflineAction->setDisabled(true); + profilerDemoAction->setDisabled(true); QString profilerToolTip = tr("Profiler not setup correctly. Go into settings, \"External Tools\"."); profilerAction->setToolTip(profilerToolTip); profilerOfflineAction->setToolTip(profilerToolTip); + profilerDemoAction->setToolTip(profilerToolTip); } else if (m_selectedInstance) { profilerAction->setEnabled(m_selectedInstance->canLaunch()); profilerOfflineAction->setEnabled(m_selectedInstance->canLaunch()); + profilerDemoAction->setEnabled(m_selectedInstance->canLaunch()); connect(profilerAction, &QAction::triggered, [this, profiler]() { - APPLICATION->launch(m_selectedInstance, true, profiler.get()); + APPLICATION->launch(m_selectedInstance, true, false, profiler.get()); }); connect(profilerOfflineAction, &QAction::triggered, [this, profiler]() { - APPLICATION->launch(m_selectedInstance, false, profiler.get()); + APPLICATION->launch(m_selectedInstance, false, false, profiler.get()); + }); + connect(profilerDemoAction, &QAction::triggered, [this, profiler]() + { + APPLICATION->launch(m_selectedInstance, false, true, profiler.get()); }); } else { profilerAction->setDisabled(true); profilerOfflineAction->setDisabled(true); + profilerDemoAction->setDisabled(true); } } ui->actionLaunchInstance->setMenu(launchMenu); ui->actionLaunchInstanceOffline->setMenu(launchOfflineMenu); + ui->actionLaunchInstanceDemo->setMenu(launchDemoMenu); } void MainWindow::repopulateAccountsMenu() @@ -2096,6 +2133,14 @@ void MainWindow::on_actionLaunchInstanceOffline_triggered() } } +void MainWindow::on_actionLaunchInstanceDemo_triggered() +{ + if (m_selectedInstance) + { + APPLICATION->launch(m_selectedInstance, false, true); + } +} + void MainWindow::on_actionKillInstance_triggered() { if(m_selectedInstance && m_selectedInstance->isRunning()) @@ -2139,6 +2184,7 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex & ui->setInstanceActionsEnabled(true); ui->actionLaunchInstance->setEnabled(m_selectedInstance->canLaunch()); ui->actionLaunchInstanceOffline->setEnabled(m_selectedInstance->canLaunch()); + ui->actionLaunchInstanceDemo->setEnabled(m_selectedInstance->canLaunch()); ui->actionKillInstance->setEnabled(m_selectedInstance->isRunning()); ui->actionExportInstance->setEnabled(m_selectedInstance->canExport()); ui->renameButton->setText(m_selectedInstance->name()); @@ -2158,6 +2204,7 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex & ui->setInstanceActionsEnabled(false); ui->actionLaunchInstance->setEnabled(false); ui->actionLaunchInstanceOffline->setEnabled(false); + ui->actionLaunchInstanceDemo->setEnabled(false); ui->actionKillInstance->setEnabled(false); APPLICATION->settings()->set("SelectedInstance", QString()); selectionBad(); diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index dde3d02c..8b41bf94 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -140,6 +140,8 @@ private slots: void on_actionLaunchInstanceOffline_triggered(); + void on_actionLaunchInstanceDemo_triggered(); + void on_actionKillInstance_triggered(); void on_actionDeleteInstance_triggered(); diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index e5cce96c..5e8bd7cc 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -811,7 +811,7 @@ void ServersPage::on_actionMove_Down_triggered() void ServersPage::on_actionJoin_triggered() { const auto &address = m_model->at(currentServer)->m_address; - APPLICATION->launch(m_inst, true, nullptr, std::make_shared(MinecraftServerTarget::parse(address))); + APPLICATION->launch(m_inst, true, false, nullptr, std::make_shared(MinecraftServerTarget::parse(address))); } #include "ServersPage.moc" From 5765a1fdf149e350f3d1869d38d27223980de08c Mon Sep 17 00:00:00 2001 From: flow Date: Wed, 13 Jul 2022 15:45:41 -0300 Subject: [PATCH 11/58] fix: allow demo for older versions We were not propagating the '--demo' flag in the legacy launcher, unconditionally setting the 'demo' parameter to false. Signed-off-by: flow --- libraries/launcher/org/polymc/applet/LegacyFrame.java | 5 +++-- libraries/launcher/org/polymc/impl/OneSixLauncher.java | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/libraries/launcher/org/polymc/applet/LegacyFrame.java b/libraries/launcher/org/polymc/applet/LegacyFrame.java index 2cdd17d7..7ae56e60 100644 --- a/libraries/launcher/org/polymc/applet/LegacyFrame.java +++ b/libraries/launcher/org/polymc/applet/LegacyFrame.java @@ -63,7 +63,8 @@ public final class LegacyFrame extends Frame { int winSizeH, boolean maximize, String serverAddress, - String serverPort + String serverPort, + boolean isDemo ) { // Implements support for launching in to multiplayer on classic servers using a mpticket // file generated by an external program and stored in the instance's root folder. @@ -106,7 +107,7 @@ public final class LegacyFrame extends Frame { appletWrap.setParameter("sessionid", session); appletWrap.setParameter("stand-alone", "true"); // Show the quit button. appletWrap.setParameter("haspaid", "true"); // Some old versions need this for world saves to work. - appletWrap.setParameter("demo", "false"); + appletWrap.setParameter("demo", isDemo ? "true" : "false"); appletWrap.setParameter("fullscreen", "false"); add(appletWrap); diff --git a/libraries/launcher/org/polymc/impl/OneSixLauncher.java b/libraries/launcher/org/polymc/impl/OneSixLauncher.java index 362ff8d6..28c3aaa6 100644 --- a/libraries/launcher/org/polymc/impl/OneSixLauncher.java +++ b/libraries/launcher/org/polymc/impl/OneSixLauncher.java @@ -137,7 +137,8 @@ public final class OneSixLauncher implements Launcher { winSizeH, maximize, serverAddress, - serverPort + serverPort, + mcParams.contains("--demo") ); return; From 777be6a48debe82f1cd1863dde2e09487e2dfafa Mon Sep 17 00:00:00 2001 From: jopejoe1 Date: Wed, 13 Jul 2022 22:53:36 +0200 Subject: [PATCH 12/58] Add 'Ctrl+Alt+O' Shortcut to launch demo instance. Signed-off-by: jopejoe1 --- launcher/ui/MainWindow.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 2d8ed24c..473f0c03 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1241,6 +1241,8 @@ void MainWindow::updateToolsMenu() QAction *normalLaunchOffline = launchOfflineMenu->addAction(tr("Launch Offline")); normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O"))); QAction *normalLaunchDemo = launchDemoMenu->addAction(tr("Launch Demo")); + normalLaunchDemo->setShortcut(QKeySequence(tr("Ctrl+Alt+O"))); + if (m_selectedInstance) { normalLaunch->setEnabled(m_selectedInstance->canLaunch()); From 11216d200c0184e419507e9b607fd78e62838966 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 15 Sep 2022 18:24:21 -0300 Subject: [PATCH 13/58] change: move demo action to "Play offline" menu Signed-off-by: flow --- launcher/ui/MainWindow.cpp | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 473f0c03..9b195d26 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -793,7 +793,6 @@ public: instanceToolBar->addAction(actionLaunchInstance); instanceToolBar->addAction(actionLaunchInstanceOffline); - instanceToolBar->addAction(actionLaunchInstanceDemo); instanceToolBar->addAction(actionKillInstance); instanceToolBar->addSeparator(); @@ -1199,7 +1198,6 @@ void MainWindow::updateToolsMenu() { QToolButton *launchButton = dynamic_cast(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstance)); QToolButton *launchOfflineButton = dynamic_cast(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstanceOffline)); - QToolButton *launchDemoButton = dynamic_cast(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstanceDemo)); bool currentInstanceRunning = m_selectedInstance && m_selectedInstance->isRunning(); @@ -1209,10 +1207,8 @@ void MainWindow::updateToolsMenu() QMenu *launchMenu = ui->actionLaunchInstance->menu(); QMenu *launchOfflineMenu = ui->actionLaunchInstanceOffline->menu(); - QMenu *launchDemoMenu = ui->actionLaunchInstanceDemo->menu(); launchButton->setPopupMode(QToolButton::MenuButtonPopup); launchOfflineButton->setPopupMode(QToolButton::MenuButtonPopup); - launchDemoButton->setPopupMode(QToolButton::MenuButtonPopup); if (launchMenu) { launchMenu->clear(); @@ -1228,21 +1224,13 @@ void MainWindow::updateToolsMenu() { launchOfflineMenu = new QMenu(this); } - if (launchDemoMenu) { - launchDemoMenu->clear(); - } - else - { - launchDemoMenu = new QMenu(this); - } QAction *normalLaunch = launchMenu->addAction(tr("Launch")); normalLaunch->setShortcut(QKeySequence::Open); QAction *normalLaunchOffline = launchOfflineMenu->addAction(tr("Launch Offline")); normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O"))); - QAction *normalLaunchDemo = launchDemoMenu->addAction(tr("Launch Demo")); + QAction *normalLaunchDemo = launchOfflineMenu->addAction(tr("Launch Demo")); normalLaunchDemo->setShortcut(QKeySequence(tr("Ctrl+Alt+O"))); - if (m_selectedInstance) { normalLaunch->setEnabled(m_selectedInstance->canLaunch()); @@ -1268,28 +1256,23 @@ void MainWindow::updateToolsMenu() QString profilersTitle = tr("Profilers"); launchMenu->addSeparator()->setText(profilersTitle); launchOfflineMenu->addSeparator()->setText(profilersTitle); - launchDemoMenu->addSeparator()->setText(profilersTitle); for (auto profiler : APPLICATION->profilers().values()) { QAction *profilerAction = launchMenu->addAction(profiler->name()); QAction *profilerOfflineAction = launchOfflineMenu->addAction(profiler->name()); - QAction *profilerDemoAction = launchDemoMenu->addAction(profiler->name()); QString error; if (!profiler->check(&error)) { profilerAction->setDisabled(true); profilerOfflineAction->setDisabled(true); - profilerDemoAction->setDisabled(true); QString profilerToolTip = tr("Profiler not setup correctly. Go into settings, \"External Tools\"."); profilerAction->setToolTip(profilerToolTip); profilerOfflineAction->setToolTip(profilerToolTip); - profilerDemoAction->setToolTip(profilerToolTip); } else if (m_selectedInstance) { profilerAction->setEnabled(m_selectedInstance->canLaunch()); profilerOfflineAction->setEnabled(m_selectedInstance->canLaunch()); - profilerDemoAction->setEnabled(m_selectedInstance->canLaunch()); connect(profilerAction, &QAction::triggered, [this, profiler]() { @@ -1299,21 +1282,15 @@ void MainWindow::updateToolsMenu() { APPLICATION->launch(m_selectedInstance, false, false, profiler.get()); }); - connect(profilerDemoAction, &QAction::triggered, [this, profiler]() - { - APPLICATION->launch(m_selectedInstance, false, true, profiler.get()); - }); } else { profilerAction->setDisabled(true); profilerOfflineAction->setDisabled(true); - profilerDemoAction->setDisabled(true); } } ui->actionLaunchInstance->setMenu(launchMenu); ui->actionLaunchInstanceOffline->setMenu(launchOfflineMenu); - ui->actionLaunchInstanceDemo->setMenu(launchDemoMenu); } void MainWindow::repopulateAccountsMenu() From 1b2a7de4e2d643f49c36dc424c9244a98ce93f7e Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 15 Sep 2022 18:32:14 -0300 Subject: [PATCH 14/58] fix: show 'demo' instead of 'offline' in log when in demo mode Signed-off-by: flow --- launcher/LaunchController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index b4a58d01..830fcf82 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -376,7 +376,7 @@ void LaunchController::launchInstance() } m_launcher->prependStep(new TextPrint(m_launcher.get(), resolved_servers, MessageLevel::Launcher)); } else { - online_mode = "offline"; + online_mode = m_demo ? "demo" : "offline"; } m_launcher->prependStep(new TextPrint(m_launcher.get(), "Launched instance in " + online_mode + " mode\n", MessageLevel::Launcher)); From 81e326571bb31dc4a92f1dbe32cb3ec50ace71f8 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 15 Sep 2022 19:23:58 -0300 Subject: [PATCH 15/58] fix: enable demo launch only on supported instances e.g. >= 1.3.1 instances Signed-off-by: flow --- launcher/minecraft/MinecraftInstance.cpp | 8 ++++++++ launcher/minecraft/MinecraftInstance.h | 2 ++ launcher/ui/InstanceWindow.cpp | 8 +++++++- launcher/ui/MainWindow.cpp | 14 ++++++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 9478b1b8..ab904fa2 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -245,6 +245,14 @@ QString MinecraftInstance::getLocalLibraryPath() const return libraries_dir.absolutePath(); } +bool MinecraftInstance::supportsDemo() const +{ + Version instance_ver { getPackProfile()->getComponentVersion("net.minecraft") }; + // Demo mode was introduced in 1.3.1: https://minecraft.fandom.com/wiki/Demo_mode#History + // FIXME: Due to Version constraints atm, this can't handle well non-release versions + return instance_ver >= Version("1.3.1"); +} + QString MinecraftInstance::jarModsDir() const { QDir jarmods_dir(FS::PathCombine(instanceRoot(), "jarmods/")); diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index d62ac655..fe39674a 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -69,6 +69,8 @@ public: // where the instance-local libraries should be QString getLocalLibraryPath() const; + /** Returns whether the instance, with its version, has support for demo mode. */ + [[nodiscard]] bool supportsDemo() const; ////// Profile management ////// std::shared_ptr getPackProfile() const; diff --git a/launcher/ui/InstanceWindow.cpp b/launcher/ui/InstanceWindow.cpp index 5bf42bb5..09ce0d67 100644 --- a/launcher/ui/InstanceWindow.cpp +++ b/launcher/ui/InstanceWindow.cpp @@ -166,7 +166,13 @@ void InstanceWindow::updateLaunchButtons() else { m_launchOfflineButton->setEnabled(true); - m_launchDemoButton->setEnabled(true); + + // Disable demo-mode if not available. + auto instance = dynamic_cast(m_instance.get()); + if (instance) { + m_launchDemoButton->setEnabled(instance->supportsDemo()); + } + m_killButton->setText(tr("Launch")); m_killButton->setObjectName("launchButton"); m_killButton->setToolTip(tr("Launch the instance")); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 9b195d26..58b1ae80 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1253,6 +1253,13 @@ void MainWindow::updateToolsMenu() normalLaunchOffline->setDisabled(true); normalLaunchDemo->setDisabled(true); } + + // Disable demo-mode if not available. + auto instance = dynamic_cast(m_selectedInstance.get()); + if (instance) { + normalLaunchDemo->setEnabled(instance->supportsDemo()); + } + QString profilersTitle = tr("Profilers"); launchMenu->addSeparator()->setText(profilersTitle); launchOfflineMenu->addSeparator()->setText(profilersTitle); @@ -2164,6 +2171,13 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex & ui->actionLaunchInstance->setEnabled(m_selectedInstance->canLaunch()); ui->actionLaunchInstanceOffline->setEnabled(m_selectedInstance->canLaunch()); ui->actionLaunchInstanceDemo->setEnabled(m_selectedInstance->canLaunch()); + + // Disable demo-mode if not available. + auto instance = dynamic_cast(m_selectedInstance.get()); + if (instance) { + ui->actionLaunchInstanceDemo->setEnabled(instance->supportsDemo()); + } + ui->actionKillInstance->setEnabled(m_selectedInstance->isRunning()); ui->actionExportInstance->setEnabled(m_selectedInstance->canExport()); ui->renameButton->setText(m_selectedInstance->name()); From 9e35230467267691ce745429de8d7e0b18095084 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 16 Sep 2022 19:22:37 -0300 Subject: [PATCH 16/58] fix: memory leak when getting mods from the mods folder friendly reminder to always delete your news. Signed-off-by: flow --- launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index 3a857740..afe892f4 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -57,6 +57,8 @@ void ModFolderLoadTask::executeTask() if (mod->enabled()) { if (m_result->mods.contains(mod->internal_id())) { m_result->mods[mod->internal_id()]->setStatus(ModStatus::Installed); + // Delete the object we just created, since a valid one is already in the mods list. + delete mod; } else { m_result->mods[mod->internal_id()] = mod; From 10493bd44ab59171ac4f2e3ab7b600bcff8e4af6 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 16 Sep 2022 19:25:53 -0300 Subject: [PATCH 17/58] fix: move newly allocated resources to the main thread This avoids them getting deleted when the worker thread exits, due to thread affinity on the created thread. Signed-off-by: flow --- .../minecraft/mod/tasks/BasicFolderLoadTask.h | 12 ++++++++++-- .../minecraft/mod/tasks/ModFolderLoadTask.cpp | 16 +++++++++++++++- launcher/minecraft/mod/tasks/ModFolderLoadTask.h | 3 +++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h index be0e752d..2fce2942 100644 --- a/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -23,14 +24,14 @@ class BasicFolderLoadTask : public Task { [[nodiscard]] ResultPtr result() const { return m_result; } public: - BasicFolderLoadTask(QDir dir) : Task(nullptr, false), m_dir(dir), m_result(new Result) + BasicFolderLoadTask(QDir dir) : Task(nullptr, false), m_dir(dir), m_result(new Result), m_thread_to_spawn_into(thread()) { m_create_func = [](QFileInfo const& entry) -> Resource* { return new Resource(entry); }; } BasicFolderLoadTask(QDir dir, std::function create_function) - : Task(nullptr, false), m_dir(dir), m_result(new Result), m_create_func(std::move(create_function)) + : Task(nullptr, false), m_dir(dir), m_result(new Result), m_create_func(std::move(create_function)), m_thread_to_spawn_into(thread()) {} [[nodiscard]] bool canAbort() const override { return true; } @@ -42,9 +43,13 @@ class BasicFolderLoadTask : public Task { void executeTask() override { + if (thread() != m_thread_to_spawn_into) + connect(this, &Task::finished, this->thread(), &QThread::quit); + m_dir.refresh(); for (auto entry : m_dir.entryInfoList()) { auto resource = m_create_func(entry); + resource->moveToThread(m_thread_to_spawn_into); m_result->resources.insert(resource->internal_id(), resource); } @@ -61,4 +66,7 @@ private: std::atomic m_aborted = false; std::function m_create_func; + + /** This is the thread in which we should put new mod objects */ + QThread* m_thread_to_spawn_into; }; diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index afe892f4..40a6ba18 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -38,12 +38,23 @@ #include "minecraft/mod/MetadataHandler.h" +#include + ModFolderLoadTask::ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan) - : Task(nullptr, false), m_mods_dir(mods_dir), m_index_dir(index_dir), m_is_indexed(is_indexed), m_clean_orphan(clean_orphan), m_result(new Result()) + : Task(nullptr, false) + , m_mods_dir(mods_dir) + , m_index_dir(index_dir) + , m_is_indexed(is_indexed) + , m_clean_orphan(clean_orphan) + , m_result(new Result()) + , m_thread_to_spawn_into(thread()) {} void ModFolderLoadTask::executeTask() { + if (thread() != m_thread_to_spawn_into) + connect(this, &Task::finished, this->thread(), &QThread::quit); + if (m_is_indexed) { // Read metadata first getFromMetadata(); @@ -98,6 +109,9 @@ void ModFolderLoadTask::executeTask() } } + for (auto mod : m_result->mods) + mod->moveToThread(m_thread_to_spawn_into); + if (m_aborted) emit finished(); else diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h index 0f18b8b9..af5f58a5 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h @@ -79,4 +79,7 @@ private: ResultPtr m_result; std::atomic m_aborted = false; + + /** This is the thread in which we should put new mod objects */ + QThread* m_thread_to_spawn_into; }; From c9eb584ac80956730dd56068945f6791e29716b3 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 16 Sep 2022 20:00:36 -0300 Subject: [PATCH 18/58] fix: prevent deletes by shared pointer accidental creation This fixes the launcher crashing when opening the game :iea: Signed-off-by: flow --- launcher/minecraft/MinecraftInstance.cpp | 2 +- launcher/minecraft/mod/ResourceFolderModel.cpp | 2 +- launcher/minecraft/mod/ResourceFolderModel.h | 6 +++--- launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 9478b1b8..47f53948 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -706,7 +706,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr { out << QString("%1:").arg(label); auto modList = model.allMods(); - std::sort(modList.begin(), modList.end(), [](Mod::Ptr a, Mod::Ptr b) { + std::sort(modList.begin(), modList.end(), [](auto a, auto b) { auto aName = a->fileinfo().completeBaseName(); auto bName = b->fileinfo().completeBaseName(); return aName.localeAwareCompare(bName) < 0; diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 45d1db59..95bd5648 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -251,7 +251,7 @@ bool ResourceFolderModel::update() return true; } -void ResourceFolderModel::resolveResource(Resource::Ptr res) +void ResourceFolderModel::resolveResource(Resource* res) { if (!res->shouldResolve()) { return; diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index 5652c156..7edbe030 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -68,7 +68,7 @@ class ResourceFolderModel : public QAbstractListModel { virtual bool update(); /** Creates a new parse task, if needed, for 'res' and start it.*/ - virtual void resolveResource(Resource::Ptr res); + virtual void resolveResource(Resource* res); [[nodiscard]] size_t size() const { return m_resources.size(); }; [[nodiscard]] bool empty() const { return size() == 0; } @@ -265,7 +265,7 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet } m_resources[row].reset(new_resource); - resolveResource(m_resources.at(row)); + resolveResource(m_resources[row].get()); emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); } } @@ -313,7 +313,7 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet for (auto& added : added_set) { auto res = new_resources[added]; m_resources.append(res); - resolveResource(res); + resolveResource(m_resources.last().get()); } endInsertRows(); diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index 40a6ba18..78ef4386 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -99,7 +99,7 @@ void ModFolderLoadTask::executeTask() // Remove orphan metadata to prevent issues // See https://github.com/PolyMC/PolyMC/issues/996 if (m_clean_orphan) { - QMutableMapIterator iter(m_result->mods); + QMutableMapIterator iter(m_result->mods); while (iter.hasNext()) { auto mod = iter.next().value(); if (mod->status() == ModStatus::NotInstalled) { From 0873b8d3046db38a8dbdde70a3d7f294c14dcf86 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 16 Sep 2022 20:02:21 -0300 Subject: [PATCH 19/58] fix: prevent container detaching in ResourceFolderModel and use const accessors whenever we can! Signed-off-by: flow --- launcher/minecraft/mod/ModFolderModel.cpp | 2 +- launcher/minecraft/mod/ResourceFolderModel.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 13fed1c9..9aca686f 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -234,7 +234,7 @@ auto ModFolderModel::allMods() -> QList { QList mods; - for (auto& res : m_resources) { + for (auto& res : qAsConst(m_resources)) { mods.append(static_cast(res.get())); } diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index 7edbe030..25095a45 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -247,7 +247,7 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet auto row = row_it.value(); auto& new_resource = new_resources[kept]; - auto const& current_resource = m_resources[row]; + auto const& current_resource = m_resources.at(row); if (new_resource->dateTimeChanged() == current_resource->dateTimeChanged()) { // no significant change, ignore... @@ -265,7 +265,7 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet } m_resources[row].reset(new_resource); - resolveResource(m_resources[row].get()); + resolveResource(m_resources.at(row).get()); emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); } } @@ -324,7 +324,7 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet { m_resources_index.clear(); int idx = 0; - for (auto const& mod : m_resources) { + for (auto const& mod : qAsConst(m_resources)) { m_resources_index[mod->internal_id()] = idx; idx++; } From 07dcefabcbe3436ae6de09bc8c99120ab3f0a745 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 4 Sep 2022 14:45:09 +0200 Subject: [PATCH 20/58] feat: add texture pack parsing Signed-off-by: Sefa Eyeoglu --- launcher/CMakeLists.txt | 4 + launcher/minecraft/mod/TexturePack.cpp | 45 +++++ launcher/minecraft/mod/TexturePack.h | 53 ++++++ .../minecraft/mod/TexturePackFolderModel.cpp | 78 +++++---- .../minecraft/mod/TexturePackFolderModel.h | 2 + .../minecraft/mod/TexturePackParse_test.cpp | 70 ++++++++ .../mod/tasks/LocalTexturePackParseTask.cpp | 154 ++++++++++++++++++ .../mod/tasks/LocalTexturePackParseTask.h | 56 +++++++ .../another_test_texturefolder/pack.txt | Bin 0 -> 16 bytes .../mod/testdata/test_texture_pack_idk.zip | Bin 0 -> 184 bytes .../assets/minecraft/textures/blah.txt | Bin 0 -> 2 bytes .../mod/testdata/test_texturefolder/pack.txt | Bin 0 -> 24 bytes launcher/ui/pages/instance/TexturePackPage.h | 12 ++ launcher/ui/widgets/InfoFrame.cpp | 47 +++--- launcher/ui/widgets/InfoFrame.h | 4 + tests/CMakeLists.txt | 3 + 16 files changed, 476 insertions(+), 52 deletions(-) create mode 100644 launcher/minecraft/mod/TexturePack.cpp create mode 100644 launcher/minecraft/mod/TexturePack.h create mode 100644 launcher/minecraft/mod/TexturePackParse_test.cpp create mode 100644 launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp create mode 100644 launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h create mode 100644 launcher/minecraft/mod/testdata/another_test_texturefolder/pack.txt create mode 100644 launcher/minecraft/mod/testdata/test_texture_pack_idk.zip create mode 100644 launcher/minecraft/mod/testdata/test_texturefolder/assets/minecraft/textures/blah.txt create mode 100644 launcher/minecraft/mod/testdata/test_texturefolder/pack.txt diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index e44b98eb..848d2e51 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -320,6 +320,8 @@ set(MINECRAFT_SOURCES minecraft/mod/ResourcePack.cpp minecraft/mod/ResourcePackFolderModel.h minecraft/mod/ResourcePackFolderModel.cpp + minecraft/mod/TexturePack.h + minecraft/mod/TexturePack.cpp minecraft/mod/TexturePackFolderModel.h minecraft/mod/TexturePackFolderModel.cpp minecraft/mod/ShaderPackFolderModel.h @@ -332,6 +334,8 @@ set(MINECRAFT_SOURCES minecraft/mod/tasks/LocalModUpdateTask.cpp minecraft/mod/tasks/LocalResourcePackParseTask.h minecraft/mod/tasks/LocalResourcePackParseTask.cpp + minecraft/mod/tasks/LocalTexturePackParseTask.h + minecraft/mod/tasks/LocalTexturePackParseTask.cpp # Assets minecraft/AssetsUtils.h diff --git a/launcher/minecraft/mod/TexturePack.cpp b/launcher/minecraft/mod/TexturePack.cpp new file mode 100644 index 00000000..32f69fc7 --- /dev/null +++ b/launcher/minecraft/mod/TexturePack.cpp @@ -0,0 +1,45 @@ +#include "TexturePack.h" + +#include +#include +#include + +#include "minecraft/mod/tasks/LocalTexturePackParseTask.h" + +void TexturePack::setDescription(QString new_description) +{ + QMutexLocker locker(&m_data_lock); + + m_description = new_description; +} + +void TexturePack::setImage(QImage new_image) +{ + QMutexLocker locker(&m_data_lock); + + Q_ASSERT(!new_image.isNull()); + + if (m_pack_image_cache_key.key.isValid()) + QPixmapCache::remove(m_pack_image_cache_key.key); + + m_pack_image_cache_key.key = QPixmapCache::insert(QPixmap::fromImage(new_image)); + m_pack_image_cache_key.was_ever_used = true; +} + +QPixmap TexturePack::image(QSize size) +{ + QPixmap cached_image; + if (QPixmapCache::find(m_pack_image_cache_key.key, &cached_image)) { + if (size.isNull()) + return cached_image; + return cached_image.scaled(size); + } + + // No valid image we can get + if (!m_pack_image_cache_key.was_ever_used) + return {}; + + // Imaged got evicted from the cache. Re-process it and retry. + TexturePackUtils::process(*this); + return image(size); +} diff --git a/launcher/minecraft/mod/TexturePack.h b/launcher/minecraft/mod/TexturePack.h new file mode 100644 index 00000000..cd5e4b67 --- /dev/null +++ b/launcher/minecraft/mod/TexturePack.h @@ -0,0 +1,53 @@ +#pragma once + +#include "Resource.h" + +#include +#include +#include +#include + +class Version; + +/* TODO: + * + * Store localized descriptions + * */ + +class TexturePack : public Resource { + Q_OBJECT + public: + using Ptr = shared_qobject_ptr; + + TexturePack(QObject* parent = nullptr) : Resource(parent) {} + TexturePack(QFileInfo file_info) : Resource(file_info) {} + + /** Gets the description of the resource pack. */ + [[nodiscard]] QString description() const { return m_description; } + + /** Gets the image of the resource pack, converted to a QPixmap for drawing, and scaled to size. */ + [[nodiscard]] QPixmap image(QSize size); + + /** Thread-safe. */ + void setDescription(QString new_description); + + /** Thread-safe. */ + void setImage(QImage new_image); + + protected: + mutable QMutex m_data_lock; + + /** The texture pack's description, as defined in the pack.txt file. + */ + QString m_description; + + /** The texture pack's image file cache key, for access in the QPixmapCache global instance. + * + * The 'was_ever_used' state simply identifies whether the key was never inserted on the cache (true), + * so as to tell whether a cache entry is inexistent or if it was just evicted from the cache. + */ + struct { + QPixmapCache::Key key; + bool was_ever_used = false; + } m_pack_image_cache_key; +}; diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index 2c7c945b..561f6202 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -1,38 +1,52 @@ // SPDX-License-Identifier: GPL-3.0-only /* -* PolyMC - 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. -*/ + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * 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 "TexturePackFolderModel.h" +#include "minecraft/mod/tasks/BasicFolderLoadTask.h" +#include "minecraft/mod/tasks/LocalTexturePackParseTask.h" + TexturePackFolderModel::TexturePackFolderModel(const QString &dir) : ResourceFolderModel(QDir(dir)) {} + +Task* TexturePackFolderModel::createUpdateTask() +{ + return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return new TexturePack(entry); }); +} + +Task* TexturePackFolderModel::createParseTask(Resource& resource) +{ + return new LocalTexturePackParseTask(m_next_resolution_ticket, static_cast(resource)); +} diff --git a/launcher/minecraft/mod/TexturePackFolderModel.h b/launcher/minecraft/mod/TexturePackFolderModel.h index 69e98661..f9a95466 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.h +++ b/launcher/minecraft/mod/TexturePackFolderModel.h @@ -8,4 +8,6 @@ class TexturePackFolderModel : public ResourceFolderModel public: explicit TexturePackFolderModel(const QString &dir); + [[nodiscard]] Task* createUpdateTask() override; + [[nodiscard]] Task* createParseTask(Resource&) override; }; diff --git a/launcher/minecraft/mod/TexturePackParse_test.cpp b/launcher/minecraft/mod/TexturePackParse_test.cpp new file mode 100644 index 00000000..207c2c87 --- /dev/null +++ b/launcher/minecraft/mod/TexturePackParse_test.cpp @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * 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 "FileSystem.h" + +#include "TexturePack.h" +#include "tasks/LocalTexturePackParseTask.h" + +class TexturePackParseTest : public QObject { + Q_OBJECT + + private slots: + void test_parseZIP() + { + QString source = QFINDTESTDATA("testdata"); + + QString zip_rp = FS::PathCombine(source, "test_texture_pack_idk.zip"); + TexturePack pack { QFileInfo(zip_rp) }; + + TexturePackUtils::processZIP(pack); + + QVERIFY(pack.description() == "joe biden, wake up"); + } + + void test_parseFolder() + { + QString source = QFINDTESTDATA("testdata"); + + QString folder_rp = FS::PathCombine(source, "test_texturefolder"); + TexturePack pack { QFileInfo(folder_rp) }; + + TexturePackUtils::processFolder(pack); + + QVERIFY(pack.description() == "Some texture pack surely"); + } + + void test_parseFolder2() + { + QString source = QFINDTESTDATA("testdata"); + + QString folder_rp = FS::PathCombine(source, "another_test_texturefolder"); + TexturePack pack { QFileInfo(folder_rp) }; + + TexturePackUtils::process(pack); + + QVERIFY(pack.description() == "quieres\nfor real"); + } +}; + +QTEST_GUILESS_MAIN(TexturePackParseTest) + +#include "TexturePackParse_test.moc" diff --git a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp new file mode 100644 index 00000000..2d5a557e --- /dev/null +++ b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * 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 "LocalTexturePackParseTask.h" + +#include "FileSystem.h" + +#include +#include + +#include + +namespace TexturePackUtils { + +bool process(TexturePack& pack) +{ + switch (pack.type()) { + case ResourceType::FOLDER: + TexturePackUtils::processFolder(pack); + return true; + case ResourceType::ZIPFILE: + TexturePackUtils::processZIP(pack); + return true; + default: + qWarning() << "Invalid type for resource pack parse task!"; + return false; + } +} + +void processFolder(TexturePack& pack) +{ + Q_ASSERT(pack.type() == ResourceType::FOLDER); + + QFileInfo mcmeta_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.txt")); + if (mcmeta_file_info.isFile()) { + QFile mcmeta_file(mcmeta_file_info.filePath()); + if (!mcmeta_file.open(QIODevice::ReadOnly)) + return; + + auto data = mcmeta_file.readAll(); + + TexturePackUtils::processPackTXT(pack, std::move(data)); + + mcmeta_file.close(); + } + + QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png")); + if (image_file_info.isFile()) { + QFile mcmeta_file(image_file_info.filePath()); + if (!mcmeta_file.open(QIODevice::ReadOnly)) + return; + + auto data = mcmeta_file.readAll(); + + TexturePackUtils::processPackPNG(pack, std::move(data)); + + mcmeta_file.close(); + } +} + +void processZIP(TexturePack& pack) +{ + Q_ASSERT(pack.type() == ResourceType::ZIPFILE); + + QuaZip zip(pack.fileinfo().filePath()); + if (!zip.open(QuaZip::mdUnzip)) + return; + + QuaZipFile file(&zip); + + if (zip.setCurrentFile("pack.txt")) { + if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "Failed to open file in zip."; + zip.close(); + return; + } + + auto data = file.readAll(); + + TexturePackUtils::processPackTXT(pack, std::move(data)); + + file.close(); + } + + if (zip.setCurrentFile("pack.png")) { + if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "Failed to open file in zip."; + zip.close(); + return; + } + + auto data = file.readAll(); + + TexturePackUtils::processPackPNG(pack, std::move(data)); + + file.close(); + } + + zip.close(); +} + +void processPackTXT(TexturePack& pack, QByteArray&& raw_data) +{ + pack.setDescription(QString(raw_data)); +} + +void processPackPNG(TexturePack& pack, QByteArray&& raw_data) +{ + auto img = QImage::fromData(raw_data); + if (!img.isNull()) { + pack.setImage(img); + } else { + qWarning() << "Failed to parse pack.png."; + } +} +} // namespace TexturePackUtils + +LocalTexturePackParseTask::LocalTexturePackParseTask(int token, TexturePack& rp) + : Task(nullptr, false), m_token(token), m_resource_pack(rp) +{} + +bool LocalTexturePackParseTask::abort() +{ + m_aborted = true; + return true; +} + +void LocalTexturePackParseTask::executeTask() +{ + Q_ASSERT(m_resource_pack.valid()); + + if (!TexturePackUtils::process(m_resource_pack)) + return; + + if (m_aborted) + emitAborted(); + else + emitSucceeded(); +} diff --git a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h new file mode 100644 index 00000000..239e1197 --- /dev/null +++ b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * 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 "minecraft/mod/TexturePack.h" + +#include "tasks/Task.h" + +namespace TexturePackUtils { +bool process(TexturePack& pack); + +void processZIP(TexturePack& pack); +void processFolder(TexturePack& pack); + +void processPackTXT(TexturePack& pack, QByteArray&& raw_data); +void processPackPNG(TexturePack& pack, QByteArray&& raw_data); +} // namespace TexturePackUtils + +class LocalTexturePackParseTask : public Task { + Q_OBJECT + public: + LocalTexturePackParseTask(int token, TexturePack& rp); + + [[nodiscard]] bool canAbort() const override { return true; } + bool abort() override; + + void executeTask() override; + + [[nodiscard]] int token() const { return m_token; } + + private: + int m_token; + + TexturePack& m_resource_pack; + + bool m_aborted = false; +}; diff --git a/launcher/minecraft/mod/testdata/another_test_texturefolder/pack.txt b/launcher/minecraft/mod/testdata/another_test_texturefolder/pack.txt new file mode 100644 index 0000000000000000000000000000000000000000..bbc0d271af4cb5e90e82f4ea825a47f02fd27705 GIT binary patch literal 16 XcmXRc%}gyyE#^wgFH$Htraits().contains("texturepacks"); } + + public slots: + bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override + { + auto sourceCurrent = m_filterModel->mapToSource(current); + int row = sourceCurrent.row(); + auto& rp = static_cast(m_model->at(row)); + ui->frame->updateWithTexturePack(rp); + + return true; + } }; diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp index 9e0553f8..bb895c9b 100644 --- a/launcher/ui/widgets/InfoFrame.cpp +++ b/launcher/ui/widgets/InfoFrame.cpp @@ -105,10 +105,7 @@ static const QMap s_value_to_color = { {'f', "#FFFFFF"} }; -void InfoFrame::updateWithResourcePack(ResourcePack& resource_pack) -{ - setName(resource_pack.name()); - +QString InfoFrame::renderColorCodes(QString input) { // We have to manually set the colors for use. // // A color is set using §x, with x = a hex number from 0 to f. @@ -119,39 +116,49 @@ void InfoFrame::updateWithResourcePack(ResourcePack& resource_pack) // TODO: Make the same logic for font formatting too. // TODO: Wrap links inside tags - auto description = resource_pack.description(); - - QString description_parsed(""); + QString html(""); bool in_div = false; - auto desc_it = description.constBegin(); - while (desc_it != description.constEnd()) { - if (*desc_it == u'§') { + auto it = input.constBegin(); + while (it != input.constEnd()) { + if (*it == u'§') { if (in_div) - description_parsed += ""; + html += ""; - auto const& num = *(++desc_it); - description_parsed += QString("").arg(s_value_to_color.constFind(num).value()); + auto const& num = *(++it); + html += QString("").arg(s_value_to_color.constFind(num).value()); in_div = true; - desc_it++; + it++; } - description_parsed += *desc_it; - desc_it++; + html += *it; + it++; } if (in_div) - description_parsed += ""; - description_parsed += ""; + html += ""; + html += ""; - description_parsed.replace("\n", "
"); + html.replace("\n", "
"); + return html; +} - setDescription(description_parsed); +void InfoFrame::updateWithResourcePack(ResourcePack& resource_pack) +{ + setName(resource_pack.name()); + setDescription(renderColorCodes(resource_pack.description())); setImage(resource_pack.image({64, 64})); } +void InfoFrame::updateWithTexturePack(TexturePack& texture_pack) +{ + setName(texture_pack.name()); + setDescription(texture_pack.description()); + setImage(texture_pack.image({64, 64})); +} + void InfoFrame::clear() { setName(); diff --git a/launcher/ui/widgets/InfoFrame.h b/launcher/ui/widgets/InfoFrame.h index 70d15b1e..84523e28 100644 --- a/launcher/ui/widgets/InfoFrame.h +++ b/launcher/ui/widgets/InfoFrame.h @@ -19,6 +19,7 @@ #include "minecraft/mod/Mod.h" #include "minecraft/mod/ResourcePack.h" +#include "minecraft/mod/TexturePack.h" namespace Ui { @@ -41,6 +42,9 @@ class InfoFrame : public QFrame { void updateWithMod(Mod const& m); void updateWithResource(Resource const& resource); void updateWithResourcePack(ResourcePack& rp); + void updateWithTexturePack(TexturePack& tp); + + static QString renderColorCodes(QString input); public slots: void descriptionEllipsisHandler(QString link); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1265d7a5..9c86c221 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -24,6 +24,9 @@ ecm_add_test(ResourceFolderModel_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_V ecm_add_test(ResourcePackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME ResourcePackParse) +ecm_add_test(minecraft/mod/TexturePackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test + TEST_NAME TexturePackParse) + ecm_add_test(ParseUtils_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME ParseUtils) From aad6f74db6a08ca23a9b4ee7f261a051d230bef7 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 4 Sep 2022 15:05:11 +0200 Subject: [PATCH 21/58] fix: tests Signed-off-by: Sefa Eyeoglu --- tests/CMakeLists.txt | 2 +- tests/ResourceFolderModel_test.cpp | 1 - .../mod => tests}/TexturePackParse_test.cpp | 10 +++++----- .../another_test_texturefolder/pack.txt | Bin .../TexturePackParse}/test_texture_pack_idk.zip | Bin .../assets/minecraft/textures/blah.txt | Bin .../TexturePackParse}/test_texturefolder/pack.txt | Bin 7 files changed, 6 insertions(+), 7 deletions(-) rename {launcher/minecraft/mod => tests}/TexturePackParse_test.cpp (85%) rename {launcher/minecraft/mod/testdata => tests/testdata/TexturePackParse}/another_test_texturefolder/pack.txt (100%) rename {launcher/minecraft/mod/testdata => tests/testdata/TexturePackParse}/test_texture_pack_idk.zip (100%) rename {launcher/minecraft/mod/testdata => tests/testdata/TexturePackParse}/test_texturefolder/assets/minecraft/textures/blah.txt (100%) rename {launcher/minecraft/mod/testdata => tests/testdata/TexturePackParse}/test_texturefolder/pack.txt (100%) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9c86c221..630f1200 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -24,7 +24,7 @@ ecm_add_test(ResourceFolderModel_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_V ecm_add_test(ResourcePackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME ResourcePackParse) -ecm_add_test(minecraft/mod/TexturePackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test +ecm_add_test(TexturePackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME TexturePackParse) ecm_add_test(ParseUtils_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test diff --git a/tests/ResourceFolderModel_test.cpp b/tests/ResourceFolderModel_test.cpp index 3f0f3ba1..e38b8e93 100644 --- a/tests/ResourceFolderModel_test.cpp +++ b/tests/ResourceFolderModel_test.cpp @@ -146,7 +146,6 @@ slots: for (auto mod : model.allMods()) qDebug() << mod->name(); - // FIXME: It considers every file in the directory as a mod, but we should probably filter that out somehow. QCOMPARE(model.size(), 4); model.stopWatching(); diff --git a/launcher/minecraft/mod/TexturePackParse_test.cpp b/tests/TexturePackParse_test.cpp similarity index 85% rename from launcher/minecraft/mod/TexturePackParse_test.cpp rename to tests/TexturePackParse_test.cpp index 207c2c87..3e9eaf23 100644 --- a/launcher/minecraft/mod/TexturePackParse_test.cpp +++ b/tests/TexturePackParse_test.cpp @@ -21,8 +21,8 @@ #include "FileSystem.h" -#include "TexturePack.h" -#include "tasks/LocalTexturePackParseTask.h" +#include "minecraft/mod/TexturePack.h" +#include "minecraft/mod/tasks/LocalTexturePackParseTask.h" class TexturePackParseTest : public QObject { Q_OBJECT @@ -30,7 +30,7 @@ class TexturePackParseTest : public QObject { private slots: void test_parseZIP() { - QString source = QFINDTESTDATA("testdata"); + QString source = QFINDTESTDATA("testdata/TexturePackParse"); QString zip_rp = FS::PathCombine(source, "test_texture_pack_idk.zip"); TexturePack pack { QFileInfo(zip_rp) }; @@ -42,7 +42,7 @@ class TexturePackParseTest : public QObject { void test_parseFolder() { - QString source = QFINDTESTDATA("testdata"); + QString source = QFINDTESTDATA("testdata/TexturePackParse"); QString folder_rp = FS::PathCombine(source, "test_texturefolder"); TexturePack pack { QFileInfo(folder_rp) }; @@ -54,7 +54,7 @@ class TexturePackParseTest : public QObject { void test_parseFolder2() { - QString source = QFINDTESTDATA("testdata"); + QString source = QFINDTESTDATA("testdata/TexturePackParse"); QString folder_rp = FS::PathCombine(source, "another_test_texturefolder"); TexturePack pack { QFileInfo(folder_rp) }; diff --git a/launcher/minecraft/mod/testdata/another_test_texturefolder/pack.txt b/tests/testdata/TexturePackParse/another_test_texturefolder/pack.txt similarity index 100% rename from launcher/minecraft/mod/testdata/another_test_texturefolder/pack.txt rename to tests/testdata/TexturePackParse/another_test_texturefolder/pack.txt diff --git a/launcher/minecraft/mod/testdata/test_texture_pack_idk.zip b/tests/testdata/TexturePackParse/test_texture_pack_idk.zip similarity index 100% rename from launcher/minecraft/mod/testdata/test_texture_pack_idk.zip rename to tests/testdata/TexturePackParse/test_texture_pack_idk.zip diff --git a/launcher/minecraft/mod/testdata/test_texturefolder/assets/minecraft/textures/blah.txt b/tests/testdata/TexturePackParse/test_texturefolder/assets/minecraft/textures/blah.txt similarity index 100% rename from launcher/minecraft/mod/testdata/test_texturefolder/assets/minecraft/textures/blah.txt rename to tests/testdata/TexturePackParse/test_texturefolder/assets/minecraft/textures/blah.txt diff --git a/launcher/minecraft/mod/testdata/test_texturefolder/pack.txt b/tests/testdata/TexturePackParse/test_texturefolder/pack.txt similarity index 100% rename from launcher/minecraft/mod/testdata/test_texturefolder/pack.txt rename to tests/testdata/TexturePackParse/test_texturefolder/pack.txt From 23fc453fae7091d6ef8176b3ba265e9242ebd540 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Thu, 8 Sep 2022 09:43:55 +0200 Subject: [PATCH 22/58] fix: comments and naming of texture pack stuff Signed-off-by: Sefa Eyeoglu --- launcher/minecraft/mod/TexturePack.cpp | 19 ++++++++++ launcher/minecraft/mod/TexturePack.h | 28 +++++++++++---- .../minecraft/mod/TexturePackFolderModel.h | 36 +++++++++++++++++++ .../mod/tasks/LocalTexturePackParseTask.cpp | 7 ++-- .../mod/tasks/LocalTexturePackParseTask.h | 3 +- tests/TexturePackParse_test.cpp | 1 + 6 files changed, 83 insertions(+), 11 deletions(-) diff --git a/launcher/minecraft/mod/TexturePack.cpp b/launcher/minecraft/mod/TexturePack.cpp index 32f69fc7..796eb69d 100644 --- a/launcher/minecraft/mod/TexturePack.cpp +++ b/launcher/minecraft/mod/TexturePack.cpp @@ -1,3 +1,22 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * 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 . + */ + #include "TexturePack.h" #include diff --git a/launcher/minecraft/mod/TexturePack.h b/launcher/minecraft/mod/TexturePack.h index cd5e4b67..6aa5e18e 100644 --- a/launcher/minecraft/mod/TexturePack.h +++ b/launcher/minecraft/mod/TexturePack.h @@ -1,3 +1,22 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * 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 . + */ + #pragma once #include "Resource.h" @@ -9,11 +28,6 @@ class Version; -/* TODO: - * - * Store localized descriptions - * */ - class TexturePack : public Resource { Q_OBJECT public: @@ -22,10 +36,10 @@ class TexturePack : public Resource { TexturePack(QObject* parent = nullptr) : Resource(parent) {} TexturePack(QFileInfo file_info) : Resource(file_info) {} - /** Gets the description of the resource pack. */ + /** Gets the description of the texture pack. */ [[nodiscard]] QString description() const { return m_description; } - /** Gets the image of the resource pack, converted to a QPixmap for drawing, and scaled to size. */ + /** Gets the image of the texture pack, converted to a QPixmap for drawing, and scaled to size. */ [[nodiscard]] QPixmap image(QSize size); /** Thread-safe. */ diff --git a/launcher/minecraft/mod/TexturePackFolderModel.h b/launcher/minecraft/mod/TexturePackFolderModel.h index f9a95466..261f83b4 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.h +++ b/launcher/minecraft/mod/TexturePackFolderModel.h @@ -1,3 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * 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. + */ + #pragma once #include "ResourceFolderModel.h" diff --git a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp index 2d5a557e..bf1e308f 100644 --- a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 flowln + * 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 @@ -131,7 +132,7 @@ void processPackPNG(TexturePack& pack, QByteArray&& raw_data) } // namespace TexturePackUtils LocalTexturePackParseTask::LocalTexturePackParseTask(int token, TexturePack& rp) - : Task(nullptr, false), m_token(token), m_resource_pack(rp) + : Task(nullptr, false), m_token(token), m_texture_pack(rp) {} bool LocalTexturePackParseTask::abort() @@ -142,9 +143,9 @@ bool LocalTexturePackParseTask::abort() void LocalTexturePackParseTask::executeTask() { - Q_ASSERT(m_resource_pack.valid()); + Q_ASSERT(m_texture_pack.valid()); - if (!TexturePackUtils::process(m_resource_pack)) + if (!TexturePackUtils::process(m_texture_pack)) return; if (m_aborted) diff --git a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h index 239e1197..cb0e404a 100644 --- a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 flowln + * 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 @@ -50,7 +51,7 @@ class LocalTexturePackParseTask : public Task { private: int m_token; - TexturePack& m_resource_pack; + TexturePack& m_texture_pack; bool m_aborted = false; }; diff --git a/tests/TexturePackParse_test.cpp b/tests/TexturePackParse_test.cpp index 3e9eaf23..0771f79f 100644 --- a/tests/TexturePackParse_test.cpp +++ b/tests/TexturePackParse_test.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 flowln + * 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 From ebbcc9f6da37d0a28f171dfef7d025354056b0e4 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Thu, 8 Sep 2022 11:54:04 +0200 Subject: [PATCH 23/58] fix: actually render color codes for texture packs Signed-off-by: Sefa Eyeoglu --- launcher/ui/widgets/InfoFrame.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp index bb895c9b..f78dbe16 100644 --- a/launcher/ui/widgets/InfoFrame.cpp +++ b/launcher/ui/widgets/InfoFrame.cpp @@ -155,7 +155,7 @@ void InfoFrame::updateWithResourcePack(ResourcePack& resource_pack) void InfoFrame::updateWithTexturePack(TexturePack& texture_pack) { setName(texture_pack.name()); - setDescription(texture_pack.description()); + setDescription(renderColorCodes(texture_pack.description())); setImage(texture_pack.image({64, 64})); } From a24d589845aded0a485ddced900768efaca5328b Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Tue, 20 Sep 2022 10:36:34 +0200 Subject: [PATCH 24/58] fix: ensure all resource folders exist Signed-off-by: Sefa Eyeoglu --- launcher/minecraft/mod/ModFolderModel.cpp | 1 - launcher/minecraft/mod/ResourceFolderModel.cpp | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 9aca686f..66e80f4a 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -51,7 +51,6 @@ ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed) : ResourceFolderModel(QDir(dir)), m_is_indexed(is_indexed) { - FS::ensureFolderPathExists(m_dir.absolutePath()); m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::VERSION, SortType::DATE }; } diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 95bd5648..b2356309 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -14,6 +14,8 @@ ResourceFolderModel::ResourceFolderModel(QDir dir, QObject* parent) : QAbstractListModel(parent), m_dir(dir), m_watcher(this) { + 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); From ec9ddc4f225c89ea3ccbf24c9a3be36848aa3172 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 31 Jul 2022 19:48:38 -0300 Subject: [PATCH 25/58] chore: add helper function for copying managed pack data between insts. Signed-off-by: flow --- launcher/BaseInstance.cpp | 10 ++++++++++ launcher/BaseInstance.h | 1 + 2 files changed, 11 insertions(+) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index e6d4d8e3..3b261678 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -154,6 +154,16 @@ void BaseInstance::setManagedPack(const QString& type, const QString& id, const settings()->set("ManagedPackVersionName", version); } +void BaseInstance::copyManagedPack(BaseInstance& other) +{ + settings()->set("ManagedPack", other.isManagedPack()); + settings()->set("ManagedPackType", other.getManagedPackType()); + settings()->set("ManagedPackID", other.getManagedPackID()); + settings()->set("ManagedPackName", other.getManagedPackName()); + settings()->set("ManagedPackVersionID", other.getManagedPackVersionID()); + settings()->set("ManagedPackVersionName", other.getManagedPackVersionName()); +} + int BaseInstance::getConsoleMaxLines() const { auto lineSetting = m_settings->getSetting("ConsoleMaxLines"); diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 3af104e9..653d1378 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -147,6 +147,7 @@ public: QString getManagedPackVersionID(); QString getManagedPackVersionName(); void setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version); + void copyManagedPack(BaseInstance& other); /// guess log level from a line of game log virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) From 941d75824af8d8e3deb1dfc0597c9493911f0cf0 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 7 Jul 2022 20:31:24 -0300 Subject: [PATCH 26/58] refactor: add instance creation abstraction and move vanilla This is so that 1. Code is more cleanly separated, and 2. Allows to more easily add instance updating :) Signed-off-by: flow --- launcher/CMakeLists.txt | 2 + launcher/InstanceCreationTask.cpp | 42 ++++------------- launcher/InstanceCreationTask.h | 47 ++++++++++++------- .../minecraft/VanillaInstanceCreationTask.cpp | 32 +++++++++++++ .../minecraft/VanillaInstanceCreationTask.h | 20 ++++++++ launcher/ui/pages/modplatform/VanillaPage.cpp | 10 ++-- 6 files changed, 99 insertions(+), 54 deletions(-) create mode 100644 launcher/minecraft/VanillaInstanceCreationTask.cpp create mode 100644 launcher/minecraft/VanillaInstanceCreationTask.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 848d2e51..c51cd6bd 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -297,6 +297,8 @@ set(MINECRAFT_SOURCES minecraft/Library.cpp minecraft/Library.h minecraft/MojangDownloadInfo.h + minecraft/VanillaInstanceCreationTask.cpp + minecraft/VanillaInstanceCreationTask.h minecraft/VersionFile.cpp minecraft/VersionFile.h minecraft/VersionFilterData.h diff --git a/launcher/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp index e01bf306..c8c91997 100644 --- a/launcher/InstanceCreationTask.cpp +++ b/launcher/InstanceCreationTask.cpp @@ -1,40 +1,18 @@ #include "InstanceCreationTask.h" -#include "settings/INISettingsObject.h" -#include "FileSystem.h" -//FIXME: remove this -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" +#include -InstanceCreationTask::InstanceCreationTask(BaseVersionPtr version) -{ - m_version = version; - m_usingLoader = false; -} - -InstanceCreationTask::InstanceCreationTask(BaseVersionPtr version, QString loader, BaseVersionPtr loaderVersion) -{ - m_version = version; - m_usingLoader = true; - m_loader = loader; - m_loaderVersion = loaderVersion; -} +InstanceCreationTask::InstanceCreationTask() {} void InstanceCreationTask::executeTask() { - setStatus(tr("Creating instance from version %1").arg(m_version->name())); - { - auto instanceSettings = std::make_shared(FS::PathCombine(m_stagingPath, "instance.cfg")); - instanceSettings->suspendSave(); - MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath); - auto components = inst.getPackProfile(); - components->buildingFromScratch(); - components->setComponentVersion("net.minecraft", m_version->descriptor(), true); - if(m_usingLoader) - components->setComponentVersion(m_loader, m_loaderVersion->descriptor()); - inst.setName(m_instName); - inst.setIconKey(m_instIcon); - instanceSettings->resumeSave(); + if (updateInstance() || createInstance()) { + emitSucceeded(); + return; } - emitSucceeded(); + + qWarning() << "Instance creation failed!"; + if (!m_error_message.isEmpty()) + qWarning() << "Reason: " << m_error_message; + emitFailed(tr("Error while creating new instance.")); } diff --git a/launcher/InstanceCreationTask.h b/launcher/InstanceCreationTask.h index 23367c3f..af854713 100644 --- a/launcher/InstanceCreationTask.h +++ b/launcher/InstanceCreationTask.h @@ -1,26 +1,39 @@ #pragma once -#include "tasks/Task.h" -#include "net/NetJob.h" -#include -#include "settings/SettingsObject.h" #include "BaseVersion.h" #include "InstanceTask.h" -class InstanceCreationTask : public InstanceTask -{ +class InstanceCreationTask : public InstanceTask { Q_OBJECT -public: - explicit InstanceCreationTask(BaseVersionPtr version); - explicit InstanceCreationTask(BaseVersionPtr version, QString loader, BaseVersionPtr loaderVersion); + public: + InstanceCreationTask(); + virtual ~InstanceCreationTask() = default; -protected: - //! Entry point for tasks. - virtual void executeTask() override; + protected: + void executeTask() final override; -private: /* data */ - BaseVersionPtr m_version; - bool m_usingLoader; - QString m_loader; - BaseVersionPtr m_loaderVersion; + /** + * Tries to update an already existing instance. + * + * This can be implemented by subclasses to provide a way of updating an already existing + * instance, according to that implementation's concept of 'identity' (i.e. instances that + * are updates / downgrades of one another). + * + * If this returns true, createInstance() will not run, so you should do all update steps in here. + * Otherwise, createInstance() is run as normal. + */ + virtual bool updateInstance() { return false; }; + + /** + * Creates a new instance. + * + * Returns whether the instance creation was successful (true) or not (false). + */ + virtual bool createInstance() { return false; }; + + protected: + void setError(QString message) { m_error_message = message; }; + + private: + QString m_error_message; }; diff --git a/launcher/minecraft/VanillaInstanceCreationTask.cpp b/launcher/minecraft/VanillaInstanceCreationTask.cpp new file mode 100644 index 00000000..2d1593b6 --- /dev/null +++ b/launcher/minecraft/VanillaInstanceCreationTask.cpp @@ -0,0 +1,32 @@ +#include "VanillaInstanceCreationTask.h" + +#include "FileSystem.h" +#include "settings/INISettingsObject.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" + +VanillaCreationTask::VanillaCreationTask(BaseVersionPtr version, QString loader, BaseVersionPtr loader_version) + : InstanceCreationTask(), m_version(version), m_using_loader(true), m_loader(loader), m_loader_version(loader_version) +{} + +bool VanillaCreationTask::createInstance() +{ + setStatus(tr("Creating instance from version %1").arg(m_version->name())); + + auto instance_settings = std::make_shared(FS::PathCombine(m_stagingPath, "instance.cfg")); + instance_settings->suspendSave(); + { + MinecraftInstance inst(m_globalSettings, instance_settings, m_stagingPath); + auto components = inst.getPackProfile(); + components->buildingFromScratch(); + components->setComponentVersion("net.minecraft", m_version->descriptor(), true); + if(m_using_loader) + components->setComponentVersion(m_loader, m_loader_version->descriptor()); + + inst.setName(m_instName); + inst.setIconKey(m_instIcon); + } + instance_settings->resumeSave(); + + return true; +} diff --git a/launcher/minecraft/VanillaInstanceCreationTask.h b/launcher/minecraft/VanillaInstanceCreationTask.h new file mode 100644 index 00000000..540ecb70 --- /dev/null +++ b/launcher/minecraft/VanillaInstanceCreationTask.h @@ -0,0 +1,20 @@ +#pragma once + +#include "InstanceCreationTask.h" + +class VanillaCreationTask final : public InstanceCreationTask { + Q_OBJECT + public: + VanillaCreationTask(BaseVersionPtr version) : InstanceCreationTask(), m_version(version) {} + VanillaCreationTask(BaseVersionPtr version, QString loader, BaseVersionPtr loader_version); + + bool createInstance() override; + + private: + // Version to update to / create of the instance. + BaseVersionPtr m_version; + + bool m_using_loader = false; + QString m_loader; + BaseVersionPtr m_loader_version; +}; diff --git a/launcher/ui/pages/modplatform/VanillaPage.cpp b/launcher/ui/pages/modplatform/VanillaPage.cpp index a026947f..99190f31 100644 --- a/launcher/ui/pages/modplatform/VanillaPage.cpp +++ b/launcher/ui/pages/modplatform/VanillaPage.cpp @@ -39,12 +39,12 @@ #include #include "Application.h" +#include "Filter.h" +#include "Version.h" #include "meta/Index.h" #include "meta/VersionList.h" +#include "minecraft/VanillaInstanceCreationTask.h" #include "ui/dialogs/NewInstanceDialog.h" -#include "Filter.h" -#include "InstanceCreationTask.h" -#include "Version.h" VanillaPage::VanillaPage(NewInstanceDialog *dialog, QWidget *parent) : QWidget(parent), dialog(dialog), ui(new Ui::VanillaPage) @@ -217,11 +217,11 @@ void VanillaPage::suggestCurrent() // There isn't a selected version if the version list is empty if(ui->loaderVersionList->selectedVersion() == nullptr) - dialog->setSuggestedPack(m_selectedVersion->descriptor(), new InstanceCreationTask(m_selectedVersion)); + dialog->setSuggestedPack(m_selectedVersion->descriptor(), new VanillaCreationTask(m_selectedVersion)); else { dialog->setSuggestedPack(m_selectedVersion->descriptor(), - new InstanceCreationTask(m_selectedVersion, m_selectedLoader, + new VanillaCreationTask(m_selectedVersion, m_selectedLoader, m_selectedLoaderVersion)); } dialog->setSuggestedIcon("default"); From 4441b373385f9b7f77deed2a27751337951f38f6 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 7 Jul 2022 21:10:41 -0300 Subject: [PATCH 27/58] refactor: move modrinth modpack import to separate file Signed-off-by: flow --- launcher/CMakeLists.txt | 2 + launcher/InstanceImportTask.cpp | 204 ++-------------- .../modrinth/ModrinthInstanceCreationTask.cpp | 225 ++++++++++++++++++ .../modrinth/ModrinthInstanceCreationTask.h | 39 +++ 4 files changed, 281 insertions(+), 189 deletions(-) create mode 100644 launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp create mode 100644 launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index c51cd6bd..68439ee8 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -495,6 +495,8 @@ set(MODRINTH_SOURCES modplatform/modrinth/ModrinthPackManifest.h modplatform/modrinth/ModrinthCheckUpdate.cpp modplatform/modrinth/ModrinthCheckUpdate.h + modplatform/modrinth/ModrinthInstanceCreationTask.cpp + modplatform/modrinth/ModrinthInstanceCreationTask.h ) set(MODPACKSCH_SOURCES diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index de0afc96..b19b5fa6 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -52,8 +52,8 @@ #include "minecraft/PackProfile.h" #include "modplatform/flame/FileResolvingTask.h" #include "modplatform/flame/PackManifest.h" -#include "modplatform/modrinth/ModrinthPackManifest.h" #include "modplatform/technic/TechnicPackProcessor.h" +#include "modplatform/modrinth/ModrinthInstanceCreationTask.h" #include "Application.h" #include "icons/IconList.h" @@ -256,6 +256,7 @@ void InstanceImportTask::extractFinished() void InstanceImportTask::extractAborted() { emitFailed(tr("Instance import has been aborted.")); + emit aborted(); return; } @@ -584,198 +585,23 @@ void InstanceImportTask::processMultiMC() emitSucceeded(); } -// https://docs.modrinth.com/docs/modpacks/format_definition/ void InstanceImportTask::processModrinth() { - std::vector files; - QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion; - try { - QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json"); - auto doc = Json::requireDocument(indexPath); - auto obj = Json::requireObject(doc, "modrinth.index.json"); - int formatVersion = Json::requireInteger(obj, "formatVersion", "modrinth.index.json"); - if (formatVersion == 1) { - auto game = Json::requireString(obj, "game", "modrinth.index.json"); - if (game != "minecraft") { - throw JSONValidationError("Unknown game: " + game); - } + auto* inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, m_sourceUrl.toString()); - auto jsonFiles = Json::requireIsArrayOf(obj, "files", "modrinth.index.json"); - bool had_optional = false; - for (auto modInfo : jsonFiles) { - Modrinth::File file; - file.path = Json::requireString(modInfo, "path"); - - auto env = Json::ensureObject(modInfo, "env"); - // 'env' field is optional - if (!env.isEmpty()) { - QString support = Json::ensureString(env, "client", "unsupported"); - if (support == "unsupported") { - continue; - } else if (support == "optional") { - // TODO: Make a review dialog for choosing which ones the user wants! - if (!had_optional) { - had_optional = true; - auto info = CustomMessageBox::selectable( - m_parent, tr("Optional mod detected!"), - tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"), - QMessageBox::Information); - info->exec(); - } - - if (file.path.endsWith(".jar")) - file.path += ".disabled"; - } - } - - QJsonObject hashes = Json::requireObject(modInfo, "hashes"); - QString hash; - QCryptographicHash::Algorithm hashAlgorithm; - hash = Json::ensureString(hashes, "sha1"); - hashAlgorithm = QCryptographicHash::Sha1; - if (hash.isEmpty()) { - hash = Json::ensureString(hashes, "sha512"); - hashAlgorithm = QCryptographicHash::Sha512; - if (hash.isEmpty()) { - hash = Json::ensureString(hashes, "sha256"); - hashAlgorithm = QCryptographicHash::Sha256; - if (hash.isEmpty()) { - throw JSONValidationError("No hash found for: " + file.path); - } - } - } - file.hash = QByteArray::fromHex(hash.toLatin1()); - file.hashAlgorithm = hashAlgorithm; - - // Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode - // (as Modrinth seems to incorrectly handle spaces) - - auto download_arr = Json::ensureArray(modInfo, "downloads"); - for(auto download : download_arr) { - qWarning() << download.toString(); - bool is_last = download.toString() == download_arr.last().toString(); - - auto download_url = QUrl(download.toString()); - - if (!download_url.isValid()) { - qDebug() << QString("Download URL (%1) for %2 is not a correctly formatted URL") - .arg(download_url.toString(), file.path); - if(is_last && file.downloads.isEmpty()) - throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path)); - } - else { - file.downloads.push_back(download_url); - } - } - - files.push_back(file); - } - - auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json"); - for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) { - QString name = it.key(); - if (name == "minecraft") { - minecraftVersion = Json::requireString(*it, "Minecraft version"); - } - else if (name == "fabric-loader") { - fabricVersion = Json::requireString(*it, "Fabric Loader version"); - } - else if (name == "quilt-loader") { - quiltVersion = Json::requireString(*it, "Quilt Loader version"); - } - else if (name == "forge") { - forgeVersion = Json::requireString(*it, "Forge version"); - } - else { - throw JSONValidationError("Unknown dependency type: " + name); - } - } - } else { - throw JSONValidationError(QStringLiteral("Unknown format version: %s").arg(formatVersion)); - } - QFile::remove(indexPath); - } catch (const JSONValidationError& e) { - emitFailed(tr("Could not understand pack index:\n") + e.cause()); - return; - } + inst_creation_task->setName(m_instName); + inst_creation_task->setIcon(m_instIcon); + inst_creation_task->setGroup(m_instGroup); - auto mcPath = FS::PathCombine(m_stagingPath, ".minecraft"); + connect(inst_creation_task, &Task::succeeded, this, &InstanceImportTask::emitSucceeded); + connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed); + connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress); + connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus); + connect(inst_creation_task, &Task::finished, this, [inst_creation_task]{ inst_creation_task->deleteLater(); }); - auto override_path = FS::PathCombine(m_stagingPath, "overrides"); - if (QFile::exists(override_path)) { - if (!QFile::rename(override_path, mcPath)) { - emitFailed(tr("Could not rename the overrides folder:\n") + "overrides"); - return; - } - } - - // Do client overrides - auto client_override_path = FS::PathCombine(m_stagingPath, "client-overrides"); - if (QFile::exists(client_override_path)) { - if (!FS::overrideFolder(mcPath, client_override_path)) { - emitFailed(tr("Could not rename the client overrides folder:\n") + "client overrides"); - return; - } - } - - QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); - auto instanceSettings = std::make_shared(configPath); - MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); - auto components = instance.getPackProfile(); - components->buildingFromScratch(); - components->setComponentVersion("net.minecraft", minecraftVersion, true); - if (!fabricVersion.isEmpty()) - components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion); - if (!quiltVersion.isEmpty()) - components->setComponentVersion("org.quiltmc.quilt-loader", quiltVersion); - if (!forgeVersion.isEmpty()) - components->setComponentVersion("net.minecraftforge", forgeVersion); - if (m_instIcon != "default") - { - instance.setIconKey(m_instIcon); - } - else - { - instance.setIconKey("modrinth"); - } - instance.setName(m_instName); - instance.saveNow(); - - m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network()); - for (auto file : files) - { - auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path); - qDebug() << "Will try to download" << file.downloads.front() << "to" << path; - auto dl = Net::Download::makeFile(file.downloads.dequeue(), path); - dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); - m_filesNetJob->addNetAction(dl); - - if (file.downloads.size() > 0) { - // FIXME: This really needs to be put into a ConcurrentTask of - // MultipleOptionsTask's , once those exist :) - connect(dl.get(), &NetAction::failed, [this, &file, path, dl]{ - auto dl = Net::Download::makeFile(file.downloads.dequeue(), path); - dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); - m_filesNetJob->addNetAction(dl); - dl->succeeded(); - }); - } - } - connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() - { - m_filesNetJob.reset(); - emitSucceeded(); - } - ); - connect(m_filesNetJob.get(), &NetJob::failed, [&](const QString &reason) - { - m_filesNetJob.reset(); - emitFailed(reason); + connect(this, &Task::aborted, inst_creation_task, [inst_creation_task] { + inst_creation_task->abort(); }); - connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) - { - setProgress(current, total); - }); - setStatus(tr("Downloading mods...")); - m_filesNetJob->start(); + + inst_creation_task->start(); } diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp new file mode 100644 index 00000000..7eb6cc8f --- /dev/null +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -0,0 +1,225 @@ +#include "ModrinthInstanceCreationTask.h" + +#include "Application.h" +#include "FileSystem.h" +#include "Json.h" + +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" + +#include "net/NetJob.h" +#include "net/ChecksumValidator.h" + +#include "settings/INISettingsObject.h" + +#include "ui/dialogs/CustomMessageBox.h" + +bool ModrinthCreationTask::createInstance() +{ + QEventLoop loop; + + if (m_files.empty() && !parseManifest()) + return false; + + auto mcPath = FS::PathCombine(m_stagingPath, ".minecraft"); + + auto override_path = FS::PathCombine(m_stagingPath, "overrides"); + if (QFile::exists(override_path)) { + if (!QFile::rename(override_path, mcPath)) { + setError(tr("Could not rename the overrides folder:\n") + "overrides"); + return false; + } + } + + // Do client overrides + auto client_override_path = FS::PathCombine(m_stagingPath, "client-overrides"); + if (QFile::exists(client_override_path)) { + if (!FS::overrideFolder(mcPath, client_override_path)) { + setError(tr("Could not rename the client overrides folder:\n") + "client overrides"); + return false; + } + } + + QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); + auto instanceSettings = std::make_shared(configPath); + MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); + auto components = instance.getPackProfile(); + components->buildingFromScratch(); + components->setComponentVersion("net.minecraft", minecraftVersion, true); + + if (!fabricVersion.isEmpty()) + components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion); + if (!quiltVersion.isEmpty()) + components->setComponentVersion("org.quiltmc.quilt-loader", quiltVersion); + if (!forgeVersion.isEmpty()) + components->setComponentVersion("net.minecraftforge", forgeVersion); + if (m_instIcon != "default") { + instance.setIconKey(m_instIcon); + } else { + instance.setIconKey("modrinth"); + } + instance.setName(m_instName); + instance.setManagedPack("modrinth", getManagedPackID(), m_managed_name, m_managed_id, {}); + instance.saveNow(); + + m_files_job = new NetJob(tr("Mod download"), APPLICATION->network()); + + for (auto file : m_files) { + auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path); + qDebug() << "Will try to download" << file.downloads.front() << "to" << path; + auto dl = Net::Download::makeFile(file.downloads.dequeue(), path); + dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); + m_files_job->addNetAction(dl); + + if (file.downloads.size() > 0) { + // FIXME: This really needs to be put into a ConcurrentTask of + // MultipleOptionsTask's , once those exist :) + connect(dl.get(), &NetAction::failed, [this, &file, path, dl] { + auto dl = Net::Download::makeFile(file.downloads.dequeue(), path); + dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); + m_files_job->addNetAction(dl); + dl->succeeded(); + }); + } + } + + bool ended_well = false; + + connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { ended_well = true; }); + connect(m_files_job.get(), &NetJob::failed, [&](const QString& reason) { + ended_well = false; + setError(reason); + }); + connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit); + connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) { setProgress(current, total); }); + + setStatus(tr("Downloading mods...")); + m_files_job->start(); + + loop.exec(); + + return ended_well; +} + +bool ModrinthCreationTask::parseManifest() +{ + try { + QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json"); + auto doc = Json::requireDocument(indexPath); + auto obj = Json::requireObject(doc, "modrinth.index.json"); + int formatVersion = Json::requireInteger(obj, "formatVersion", "modrinth.index.json"); + if (formatVersion == 1) { + auto game = Json::requireString(obj, "game", "modrinth.index.json"); + if (game != "minecraft") { + throw JSONValidationError("Unknown game: " + game); + } + + m_managed_version_id = Json::ensureString(obj, "versionId", "Managed ID"); + m_managed_name = Json::ensureString(obj, "name", "Managed Name"); + + auto jsonFiles = Json::requireIsArrayOf(obj, "files", "modrinth.index.json"); + bool had_optional = false; + for (auto modInfo : jsonFiles) { + Modrinth::File file; + file.path = Json::requireString(modInfo, "path"); + + auto env = Json::ensureObject(modInfo, "env"); + // 'env' field is optional + if (!env.isEmpty()) { + QString support = Json::ensureString(env, "client", "unsupported"); + if (support == "unsupported") { + continue; + } else if (support == "optional") { + // TODO: Make a review dialog for choosing which ones the user wants! + if (!had_optional) { + had_optional = true; + auto info = CustomMessageBox::selectable( + m_parent, tr("Optional mod detected!"), + tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"), + QMessageBox::Information); + info->exec(); + } + + if (file.path.endsWith(".jar")) + file.path += ".disabled"; + } + } + + QJsonObject hashes = Json::requireObject(modInfo, "hashes"); + QString hash; + QCryptographicHash::Algorithm hashAlgorithm; + hash = Json::ensureString(hashes, "sha1"); + hashAlgorithm = QCryptographicHash::Sha1; + if (hash.isEmpty()) { + hash = Json::ensureString(hashes, "sha512"); + hashAlgorithm = QCryptographicHash::Sha512; + if (hash.isEmpty()) { + hash = Json::ensureString(hashes, "sha256"); + hashAlgorithm = QCryptographicHash::Sha256; + if (hash.isEmpty()) { + throw JSONValidationError("No hash found for: " + file.path); + } + } + } + file.hash = QByteArray::fromHex(hash.toLatin1()); + file.hashAlgorithm = hashAlgorithm; + + // Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode + // (as Modrinth seems to incorrectly handle spaces) + + auto download_arr = Json::ensureArray(modInfo, "downloads"); + for (auto download : download_arr) { + qWarning() << download.toString(); + bool is_last = download.toString() == download_arr.last().toString(); + + auto download_url = QUrl(download.toString()); + + if (!download_url.isValid()) { + qDebug() + << QString("Download URL (%1) for %2 is not a correctly formatted URL").arg(download_url.toString(), file.path); + if (is_last && file.downloads.isEmpty()) + throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path)); + } else { + file.downloads.push_back(download_url); + } + } + + m_files.push_back(file); + } + + auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json"); + for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) { + QString name = it.key(); + if (name == "minecraft") { + minecraftVersion = Json::requireString(*it, "Minecraft version"); + } else if (name == "fabric-loader") { + fabricVersion = Json::requireString(*it, "Fabric Loader version"); + } else if (name == "quilt-loader") { + quiltVersion = Json::requireString(*it, "Quilt Loader version"); + } else if (name == "forge") { + forgeVersion = Json::requireString(*it, "Forge version"); + } else { + throw JSONValidationError("Unknown dependency type: " + name); + } + } + } else { + throw JSONValidationError(QStringLiteral("Unknown format version: %s").arg(formatVersion)); + } + QFile::remove(indexPath); + } catch (const JSONValidationError& e) { + setError(tr("Could not understand pack index:\n") + e.cause()); + return false; + } + + return true; +} + +QString ModrinthCreationTask::getManagedPackID() const +{ + if (!m_source_url.isEmpty()) { + QRegularExpression regex(R"(data\/(.*)\/versions)"); + return regex.match(m_source_url).captured(0); + } + + return {}; +} diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h new file mode 100644 index 00000000..61f7dd5c --- /dev/null +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h @@ -0,0 +1,39 @@ +#pragma once + +#include "InstanceCreationTask.h" + +#include "modplatform/modrinth/ModrinthPackManifest.h" + +#include "net/NetJob.h" + +class ModrinthCreationTask final : public InstanceCreationTask { + Q_OBJECT + + public: + ModrinthCreationTask(QString staging_path, SettingsObjectPtr global_settings, QWidget* parent, QString source_url = {}) + : InstanceCreationTask(), m_parent(parent) + { + setStagingPath(staging_path); + setParentSettings(global_settings); + } + + bool abort() override; + bool canAbort() const override { return true; } + + bool updateInstance() override; + bool createInstance() override; + + private: + bool parseManifest(); + QString getManagedPackID() const; + + private: + QWidget* m_parent = nullptr; + + QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion; + QString m_managed_id, m_managed_version_id, m_managed_name; + QString m_source_url; + + std::vector m_files; + NetJob::Ptr m_files_job; +}; From 208ed73e59c46ee7966f463558c07805a9b541e6 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 8 Jul 2022 13:00:44 -0300 Subject: [PATCH 28/58] feat: add early modrinth pack updating Still some FIXMEs and TODOs to consider, but the general thing is here! Signed-off-by: flow --- launcher/InstanceImportTask.cpp | 11 +- launcher/InstanceList.cpp | 66 ++++++++-- launcher/InstanceList.h | 7 +- launcher/InstanceTask.h | 7 ++ .../modrinth/ModrinthInstanceCreationTask.cpp | 118 ++++++++++++++++-- .../modrinth/ModrinthInstanceCreationTask.h | 2 +- 6 files changed, 185 insertions(+), 26 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index b19b5fa6..4bdf9cd2 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -593,15 +593,16 @@ void InstanceImportTask::processModrinth() inst_creation_task->setIcon(m_instIcon); inst_creation_task->setGroup(m_instGroup); - connect(inst_creation_task, &Task::succeeded, this, &InstanceImportTask::emitSucceeded); + connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] { + setOverride(inst_creation_task->shouldOverride()); + emitSucceeded(); + }); connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed); connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress); connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus); - connect(inst_creation_task, &Task::finished, this, [inst_creation_task]{ inst_creation_task->deleteLater(); }); + connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater); - connect(this, &Task::aborted, inst_creation_task, [inst_creation_task] { - inst_creation_task->abort(); - }); + connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort); inst_creation_task->start(); } diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 4447a17c..698aa24e 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -535,7 +535,20 @@ InstancePtr InstanceList::getInstanceById(QString instId) const return InstancePtr(); } -QModelIndex InstanceList::getInstanceIndexById(const QString& id) const +InstancePtr InstanceList::getInstanceByManagedName(QString managed_name) const +{ + if (managed_name.isEmpty()) + return {}; + + for (auto instance : m_instances) { + if (instance->getManagedPackName() == managed_name) + return instance; + } + + return {}; +} + +QModelIndex InstanceList::getInstanceIndexById(const QString &id) const { return index(getInstIndex(getInstanceById(id).get())); } @@ -764,9 +777,8 @@ class InstanceStaging : public Task { Q_OBJECT const unsigned minBackoff = 1; const unsigned maxBackoff = 16; - public: - InstanceStaging(InstanceList* parent, Task* child, const QString& stagingPath, const QString& instanceName, const QString& groupName) + InstanceStaging(InstanceList* parent, InstanceTask* child, const QString& stagingPath, const QString& instanceName, const QString& groupName) : backoff(minBackoff, maxBackoff) { m_parent = parent; @@ -808,7 +820,8 @@ class InstanceStaging : public Task { void childSucceded() { unsigned sleepTime = backoff(); - if (m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName)) { + if (m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName, m_child->shouldOverride())) + { emitSucceeded(); return; } @@ -834,8 +847,8 @@ class InstanceStaging : public Task { */ ExponentialSeries backoff; QString m_stagingPath; - InstanceList* m_parent; - unique_qobject_ptr m_child; + InstanceList * m_parent; + unique_qobject_ptr m_child; QString m_instanceName; QString m_groupName; QTimer m_backoffTimer; @@ -866,23 +879,52 @@ QString InstanceList::getStagedInstancePath() return path; } -bool InstanceList::commitStagedInstance(const QString& path, const QString& instanceName, const QString& groupName) +bool InstanceList::commitStagedInstance(const QString& path, const QString& instanceName, const QString& groupName, bool should_override) { QDir dir; - QString instID = FS::DirNameFromString(instanceName, m_instDir); + QString instID; + InstancePtr inst; + + QString raw_inst_name = instanceName.section(' ', 0, -2); + if (should_override) { + // This is to avoid problems when the instance folder gets manually renamed + if ((inst = getInstanceByManagedName(raw_inst_name))) { + instID = QFileInfo(inst->instanceRoot()).fileName(); + } else { + instID = FS::RemoveInvalidFilenameChars(raw_inst_name, '-'); + } + } else { + instID = FS::DirNameFromString(raw_inst_name, m_instDir); + } + { WatchLock lock(m_watcher, m_instDir); QString destination = FS::PathCombine(m_instDir, instID); - if (!dir.rename(path, destination)) { - qWarning() << "Failed to move" << path << "to" << destination; - return false; + + if (should_override) { + if (!FS::overrideFolder(destination, path)) { + qWarning() << "Failed to override" << path << "to" << destination; + return false; + } + + if (!inst) + inst = getInstanceById(instID); + if (inst) + inst->setName(instanceName); + } else { + if (!dir.rename(path, destination)) { + qWarning() << "Failed to move" << path << "to" << destination; + return false; + } } m_instanceGroupIndex[instID] = groupName; - instanceSet.insert(instID); m_groupNameCache.insert(groupName); + instanceSet.insert(instID); + emit instancesChanged(); emit instanceSelectRequest(instID); } + saveGroupList(); return true; } diff --git a/launcher/InstanceList.h b/launcher/InstanceList.h index 62282f04..6b4dcfa4 100644 --- a/launcher/InstanceList.h +++ b/launcher/InstanceList.h @@ -101,7 +101,10 @@ public: InstListError loadList(); void saveNow(); + /* O(n) */ InstancePtr getInstanceById(QString id) const; + /* O(n) */ + InstancePtr getInstanceByManagedName(QString managed_name) const; QModelIndex getInstanceIndexById(const QString &id) const; QStringList getGroups(); bool isGroupCollapsed(const QString &groupName); @@ -127,8 +130,10 @@ public: /** * Commit the staging area given by @keyPath to the provider - used when creation succeeds. * Used by instance manipulation tasks. + * should_override is used when another similar instance already exists, and we want to override it + * - for instance, when updating it. */ - bool commitStagedInstance(const QString & keyPath, const QString& instanceName, const QString & groupName); + bool commitStagedInstance(const QString & keyPath, const QString& instanceName, const QString & groupName, bool should_override); /** * Destroy a previously created staging area given by @keyPath - used when creation fails. diff --git a/launcher/InstanceTask.h b/launcher/InstanceTask.h index 82e23f11..02810a52 100644 --- a/launcher/InstanceTask.h +++ b/launcher/InstanceTask.h @@ -43,10 +43,17 @@ public: return m_instGroup; } + bool shouldOverride() const { return m_override_existing; } + +protected: + void setOverride(bool override) { m_override_existing = override; } + protected: /* data */ SettingsObjectPtr m_globalSettings; QString m_instName; QString m_instIcon; QString m_instGroup; QString m_stagingPath; + + bool m_override_existing = false; }; diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 7eb6cc8f..efb8c99b 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -2,25 +2,128 @@ #include "Application.h" #include "FileSystem.h" +#include "InstanceList.h" #include "Json.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" -#include "net/NetJob.h" +#include "modplatform/ModIndex.h" + #include "net/ChecksumValidator.h" #include "settings/INISettingsObject.h" #include "ui/dialogs/CustomMessageBox.h" +#include + +bool ModrinthCreationTask::abort() +{ + if (m_files_job) + return m_files_job->abort(); + return true; +} + +bool ModrinthCreationTask::updateInstance() +{ + auto instance_list = APPLICATION->instances(); + + // FIXME: How to handle situations when there's more than one install already for a given modpack? + // Based on the way we create the instance name (name + " " + version). Is there a better way? + auto inst = instance_list->getInstanceByManagedName(m_instName.section(' ', 0, -2)); + + if (!inst) { + inst = instance_list->getInstanceById(m_instName); + + if (!inst) + return false; + } + + QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json"); + if (!parseManifest(index_path, m_files)) + return false; + + auto version_id = inst->getManagedPackVersionID(); + auto version_str = !version_id.isEmpty() ? tr(" (version %1)").arg(version_id) : ""; + + auto info = CustomMessageBox::selectable(m_parent, tr("Similar modpack was found!"), + tr("One or more of your instances are from this same modpack%1. Do you want to create a " + "separate instance, or update the existing one?") + .arg(version_str), + QMessageBox::Information, QMessageBox::Ok | QMessageBox::Abort); + info->setButtonText(QMessageBox::Ok, tr("Update existing instance")); + info->setButtonText(QMessageBox::Abort, tr("Create new instance")); + + if (info->exec() && info->clickedButton() == info->button(QMessageBox::Abort)) + return false; + + // Remove repeated files, we don't need to download them! + QDir old_inst_dir(inst->instanceRoot()); + + QString old_index_path(FS::PathCombine(old_inst_dir.absolutePath(), "mrpack", "modrinth.index.json")); + QFileInfo old_index_file(old_index_path); + if (old_index_file.exists()) { + std::vector old_files; + parseManifest(old_index_path, old_files); + + // Let's remove all duplicated, identical resources! + auto files_iterator = m_files.begin(); +begin: + while (files_iterator != m_files.end()) { + auto const& file = *files_iterator; + + auto old_files_iterator = old_files.begin(); + while (old_files_iterator != old_files.end()) { + auto const& old_file = *old_files_iterator; + + if (old_file.hash == file.hash) { + qDebug() << "Removed file at" << file.path << "from list of downloads"; + files_iterator = m_files.erase(files_iterator); + old_files_iterator = old_files.erase(old_files_iterator); + goto begin; // Sorry :c + } + + old_files_iterator++; + } + + files_iterator++; + } + + // Some files were removed from the old version, and some will be downloaded in an updated version, + // so we're fine removing them! + if (!old_files.empty()) { + QDir old_minecraft_dir(inst->gameRoot()); + for (auto const& file : old_files) { + qWarning() << "Removing" << file.path; + old_minecraft_dir.remove(file.path); + } + } + } + + // TODO: Currently 'overrides' will always override the stuff on update. How do we preserve unchanged overrides? + + setOverride(true); + qDebug() << "Will override instance!"; + + // We let it go through the createInstance() stage, just with a couple modifications for updating + return false; +} + +// https://docs.modrinth.com/docs/modpacks/format_definition/ bool ModrinthCreationTask::createInstance() { QEventLoop loop; - if (m_files.empty() && !parseManifest()) + QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json"); + if (m_files.empty() && !parseManifest(index_path, m_files)) return false; + // Keep index file in case we need it some other time (like when changing versions) + QString new_index_place(FS::PathCombine(m_stagingPath, "mrpack", "modrinth.index.json")); + FS::ensureFilePathExists(new_index_place); + QFile::rename(index_path, new_index_place); + auto mcPath = FS::PathCombine(m_stagingPath, ".minecraft"); auto override_path = FS::PathCombine(m_stagingPath, "overrides"); @@ -43,6 +146,7 @@ bool ModrinthCreationTask::createInstance() QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); auto instanceSettings = std::make_shared(configPath); MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); + auto components = instance.getPackProfile(); components->buildingFromScratch(); components->setComponentVersion("net.minecraft", minecraftVersion, true); @@ -53,6 +157,7 @@ bool ModrinthCreationTask::createInstance() components->setComponentVersion("org.quiltmc.quilt-loader", quiltVersion); if (!forgeVersion.isEmpty()) components->setComponentVersion("net.minecraftforge", forgeVersion); + if (m_instIcon != "default") { instance.setIconKey(m_instIcon); } else { @@ -101,11 +206,10 @@ bool ModrinthCreationTask::createInstance() return ended_well; } -bool ModrinthCreationTask::parseManifest() +bool ModrinthCreationTask::parseManifest(QString index_path, std::vector& files) { try { - QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json"); - auto doc = Json::requireDocument(indexPath); + auto doc = Json::requireDocument(index_path); auto obj = Json::requireObject(doc, "modrinth.index.json"); int formatVersion = Json::requireInteger(obj, "formatVersion", "modrinth.index.json"); if (formatVersion == 1) { @@ -184,7 +288,7 @@ bool ModrinthCreationTask::parseManifest() } } - m_files.push_back(file); + files.push_back(file); } auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json"); @@ -205,7 +309,7 @@ bool ModrinthCreationTask::parseManifest() } else { throw JSONValidationError(QStringLiteral("Unknown format version: %s").arg(formatVersion)); } - QFile::remove(indexPath); + } catch (const JSONValidationError& e) { setError(tr("Could not understand pack index:\n") + e.cause()); return false; diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h index 61f7dd5c..4e804e58 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h @@ -24,7 +24,7 @@ class ModrinthCreationTask final : public InstanceCreationTask { bool createInstance() override; private: - bool parseManifest(); + bool parseManifest(QString, std::vector&); QString getManagedPackID() const; private: From 242fb156a281ef188c9fd75969c5d70ba6f8c140 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 21 Jul 2022 16:40:28 -0300 Subject: [PATCH 29/58] feat: add 'getFiles' by fileIds route in Flame API Signed-off-by: flow --- launcher/modplatform/flame/FlameAPI.cpp | 23 +++++++++++++++++++++++ launcher/modplatform/flame/FlameAPI.h | 1 + 2 files changed, 24 insertions(+) diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index 9c74918b..f8f50dc6 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -183,3 +183,26 @@ auto FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const -> return netJob; } + +auto FlameAPI::getFiles(QStringList fileIds, QByteArray* response) const -> NetJob* +{ + auto* netJob = new NetJob(QString("Flame::GetFiles"), APPLICATION->network()); + + QJsonObject body_obj; + QJsonArray files_arr; + for (auto& fileId : fileIds) { + files_arr.append(fileId); + } + + body_obj["fileIds"] = files_arr; + + QJsonDocument body(body_obj); + auto body_raw = body.toJson(); + + netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods/files"), response, body_raw)); + + QObject::connect(netJob, &NetJob::finished, [response, netJob] { delete response; netJob->deleteLater(); }); + QObject::connect(netJob, &NetJob::failed, [body_raw] { qDebug() << body_raw; }); + + return netJob; +} diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 4eac0664..5e6166b9 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -12,6 +12,7 @@ class FlameAPI : public NetworkModAPI { auto getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion; auto getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* override; + auto getFiles(QStringList fileIds, QByteArray* response) const -> NetJob*; private: inline auto getSortFieldInt(QString sortString) const -> int From 2246c3359bcd07d8bfab949d79a1e428a948f1b7 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 21 Jul 2022 16:41:44 -0300 Subject: [PATCH 30/58] refactor: add `throw_on_blocked` arg to Flame file parse Signed-off-by: flow --- launcher/modplatform/flame/PackManifest.cpp | 4 ++-- launcher/modplatform/flame/PackManifest.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/modplatform/flame/PackManifest.cpp b/launcher/modplatform/flame/PackManifest.cpp index 12a4b990..81395fcd 100644 --- a/launcher/modplatform/flame/PackManifest.cpp +++ b/launcher/modplatform/flame/PackManifest.cpp @@ -61,7 +61,7 @@ void Flame::loadManifest(Flame::Manifest& m, const QString& filepath) loadManifestV1(m, obj); } -bool Flame::File::parseFromObject(const QJsonObject& obj) +bool Flame::File::parseFromObject(const QJsonObject& obj, bool throw_on_blocked) { fileName = Json::requireString(obj, "fileName"); // This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience @@ -91,7 +91,7 @@ bool Flame::File::parseFromObject(const QJsonObject& obj) // may throw, if the project is blocked QString rawUrl = Json::ensureString(obj, "downloadUrl"); url = QUrl(rawUrl, QUrl::TolerantMode); - if (!url.isValid()) { + if (!url.isValid() && throw_on_blocked) { throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl)); } diff --git a/launcher/modplatform/flame/PackManifest.h b/launcher/modplatform/flame/PackManifest.h index 677db1c3..a69e1321 100644 --- a/launcher/modplatform/flame/PackManifest.h +++ b/launcher/modplatform/flame/PackManifest.h @@ -46,7 +46,7 @@ namespace Flame struct File { // NOTE: throws JSONValidationError - bool parseFromObject(const QJsonObject& object); + bool parseFromObject(const QJsonObject& object, bool throw_on_blocked = true); int projectId = 0; int fileId = 0; From 72d2ca234e80fe65bb6a7d5fe106b01d9dc6f096 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 8 Jul 2022 18:44:43 -0300 Subject: [PATCH 31/58] refactor: move flame modpack import to separate file Signed-off-by: flow --- launcher/CMakeLists.txt | 2 + launcher/InstanceCreationTask.h | 2 + launcher/InstanceImportTask.cpp | 298 +----------------- .../flame/FlameInstanceCreationTask.cpp | 284 +++++++++++++++++ .../flame/FlameInstanceCreationTask.h | 32 ++ 5 files changed, 337 insertions(+), 281 deletions(-) create mode 100644 launcher/modplatform/flame/FlameInstanceCreationTask.cpp create mode 100644 launcher/modplatform/flame/FlameInstanceCreationTask.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 68439ee8..7bd92fac 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -486,6 +486,8 @@ set(FLAME_SOURCES modplatform/flame/FileResolvingTask.cpp modplatform/flame/FlameCheckUpdate.cpp modplatform/flame/FlameCheckUpdate.h + modplatform/flame/FlameInstanceCreationTask.h + modplatform/flame/FlameInstanceCreationTask.cpp ) set(MODRINTH_SOURCES diff --git a/launcher/InstanceCreationTask.h b/launcher/InstanceCreationTask.h index af854713..68c5de59 100644 --- a/launcher/InstanceCreationTask.h +++ b/launcher/InstanceCreationTask.h @@ -31,6 +31,8 @@ class InstanceCreationTask : public InstanceTask { */ virtual bool createInstance() { return false; }; + QString getError() const { return m_error_message; } + protected: void setError(QString message) { m_error_message = message; }; diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 4bdf9cd2..72c2496f 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -50,10 +50,9 @@ #include "Json.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" -#include "modplatform/flame/FileResolvingTask.h" -#include "modplatform/flame/PackManifest.h" #include "modplatform/technic/TechnicPackProcessor.h" #include "modplatform/modrinth/ModrinthInstanceCreationTask.h" +#include "modplatform/flame/FlameInstanceCreationTask.h" #include "Application.h" #include "icons/IconList.h" @@ -262,287 +261,24 @@ void InstanceImportTask::extractAborted() void InstanceImportTask::processFlame() { - const static QMap forgemap = { - {"1.2.5", "3.4.9.171"}, - {"1.4.2", "6.0.1.355"}, - {"1.4.7", "6.6.2.534"}, - {"1.5.2", "7.8.1.737"} - }; - Flame::Manifest pack; - try - { - QString configPath = FS::PathCombine(m_stagingPath, "manifest.json"); - Flame::loadManifest(pack, configPath); - QFile::remove(configPath); - } - catch (const JSONValidationError &e) - { - emitFailed(tr("Could not understand pack manifest:\n") + e.cause()); - return; - } - if(!pack.overrides.isEmpty()) - { - QString overridePath = FS::PathCombine(m_stagingPath, pack.overrides); - if (QFile::exists(overridePath)) - { - QString mcPath = FS::PathCombine(m_stagingPath, "minecraft"); - if (!QFile::rename(overridePath, mcPath)) - { - emitFailed(tr("Could not rename the overrides folder:\n") + pack.overrides); - return; - } - } - else - { - logWarning(tr("The specified overrides folder (%1) is missing. Maybe the modpack was already used before?").arg(pack.overrides)); - } - } + auto* inst_creation_task = new FlameCreationTask(m_stagingPath, m_globalSettings, m_parent); - QString forgeVersion; - QString fabricVersion; - // TODO: is Quilt relevant here? - for(auto &loader: pack.minecraft.modLoaders) - { - auto id = loader.id; - if(id.startsWith("forge-")) - { - id.remove("forge-"); - forgeVersion = id; - continue; - } - if(id.startsWith("fabric-")) - { - id.remove("fabric-"); - fabricVersion = id; - continue; - } - logWarning(tr("Unknown mod loader in manifest: %1").arg(id)); - } - - QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); - auto instanceSettings = std::make_shared(configPath); - MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); - auto mcVersion = pack.minecraft.version; - // Hack to correct some 'special sauce'... - if(mcVersion.endsWith('.')) - { - mcVersion.remove(QRegularExpression("[.]+$")); - logWarning(tr("Mysterious trailing dots removed from Minecraft version while importing pack.")); - } - auto components = instance.getPackProfile(); - components->buildingFromScratch(); - components->setComponentVersion("net.minecraft", mcVersion, true); - if(!forgeVersion.isEmpty()) - { - // FIXME: dirty, nasty, hack. Proper solution requires dependency resolution and knowledge of the metadata. - if(forgeVersion == "recommended") - { - if(forgemap.contains(mcVersion)) - { - forgeVersion = forgemap[mcVersion]; - } - else - { - logWarning(tr("Could not map recommended Forge version for Minecraft %1").arg(mcVersion)); - } - } - components->setComponentVersion("net.minecraftforge", forgeVersion); - } - if(!fabricVersion.isEmpty()) - { - components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion); - } - if (m_instIcon != "default") - { - instance.setIconKey(m_instIcon); - } - else - { - if(pack.name.contains("Direwolf20")) - { - instance.setIconKey("steve"); - } - else if(pack.name.contains("FTB") || pack.name.contains("Feed The Beast")) - { - instance.setIconKey("ftb_logo"); - } - else - { - // default to something other than the MultiMC default to distinguish these - instance.setIconKey("flame"); - } - } - QString jarmodsPath = FS::PathCombine(m_stagingPath, "minecraft", "jarmods"); - QFileInfo jarmodsInfo(jarmodsPath); - if(jarmodsInfo.isDir()) - { - // install all the jar mods - qDebug() << "Found jarmods:"; - QDir jarmodsDir(jarmodsPath); - QStringList jarMods; - for (auto info: jarmodsDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) - { - qDebug() << info.fileName(); - jarMods.push_back(info.absoluteFilePath()); - } - auto profile = instance.getPackProfile(); - profile->installJarMods(jarMods); - // nuke the original files - FS::deletePath(jarmodsPath); - } - instance.setName(m_instName); - m_modIdResolver = new Flame::FileResolvingTask(APPLICATION->network(), pack); - connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, [&]() - { - auto results = m_modIdResolver->getResults(); - //first check for blocked mods - QString text; - QList urls; - auto anyBlocked = false; - for(const auto& result: results.files.values()) { - if (!result.resolved || result.url.isEmpty()) { - text += QString("%1:
%2
").arg(result.fileName, result.websiteUrl); - urls.append(QUrl(result.websiteUrl)); - anyBlocked = true; - } - } - if(anyBlocked) { - qWarning() << "Blocked mods found, displaying mod list"; - - auto message_dialog = new BlockedModsDialog(m_parent, - tr("Blocked mods found"), - tr("The following mods were blocked on third party launchers.
" - "You will need to manually download them and add them to the modpack"), - text, - urls); - message_dialog->setModal(true); - - if (message_dialog->exec()) { - m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network()); - for (const auto &result: m_modIdResolver->getResults().files) { - QString filename = result.fileName; - if (!result.required) { - filename += ".disabled"; - } - - auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename); - auto path = FS::PathCombine(m_stagingPath, relpath); - - switch (result.type) { - case Flame::File::Type::Folder: { - logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); - // fall-through intentional, we treat these as plain old mods and dump them wherever. - } - case Flame::File::Type::SingleFile: - case Flame::File::Type::Mod: { - if (!result.url.isEmpty()) { - qDebug() << "Will download" << result.url << "to" << path; - auto dl = Net::Download::makeFile(result.url, path); - m_filesNetJob->addNetAction(dl); - } - break; - } - case Flame::File::Type::Modpack: - logWarning( - tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg( - relpath)); - break; - case Flame::File::Type::Cmod2: - case Flame::File::Type::Ctoc: - case Flame::File::Type::Unknown: - logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath)); - break; - } - } - m_modIdResolver.reset(); - connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() { - m_filesNetJob.reset(); - emitSucceeded(); - } - ); - connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) { - m_filesNetJob.reset(); - emitFailed(reason); - }); - connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) { - setProgress(current, total); - }); - setStatus(tr("Downloading mods...")); - m_filesNetJob->start(); - } else { - m_modIdResolver.reset(); - emitFailed("Canceled"); - } - } else { - //TODO extract to function ? - m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network()); - for (const auto &result: m_modIdResolver->getResults().files) { - QString filename = result.fileName; - if (!result.required) { - filename += ".disabled"; - } - - auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename); - auto path = FS::PathCombine(m_stagingPath, relpath); - - switch (result.type) { - case Flame::File::Type::Folder: { - logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); - // fall-through intentional, we treat these as plain old mods and dump them wherever. - } - case Flame::File::Type::SingleFile: - case Flame::File::Type::Mod: { - if (!result.url.isEmpty()) { - qDebug() << "Will download" << result.url << "to" << path; - auto dl = Net::Download::makeFile(result.url, path); - m_filesNetJob->addNetAction(dl); - } - break; - } - case Flame::File::Type::Modpack: - logWarning( - tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg( - relpath)); - break; - case Flame::File::Type::Cmod2: - case Flame::File::Type::Ctoc: - case Flame::File::Type::Unknown: - logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath)); - break; - } - } - m_modIdResolver.reset(); - connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() { - m_filesNetJob.reset(); - emitSucceeded(); - } - ); - connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) { - m_filesNetJob.reset(); - emitFailed(reason); - }); - connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) { - setProgress(current, total); - }); - setStatus(tr("Downloading mods...")); - m_filesNetJob->start(); - } - } - ); - connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) - { - m_modIdResolver.reset(); - emitFailed(tr("Unable to resolve mod IDs:\n") + reason); + inst_creation_task->setName(m_instName); + inst_creation_task->setIcon(m_instIcon); + inst_creation_task->setGroup(m_instGroup); + + connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] { + setOverride(inst_creation_task->shouldOverride()); + emitSucceeded(); }); - connect(m_modIdResolver.get(), &Flame::FileResolvingTask::progress, [&](qint64 current, qint64 total) - { - setProgress(current, total); - }); - connect(m_modIdResolver.get(), &Flame::FileResolvingTask::status, [&](QString status) - { - setStatus(status); - }); - m_modIdResolver->start(); + connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed); + connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress); + connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus); + connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater); + + connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort); + + inst_creation_task->start(); } void InstanceImportTask::processTechnic() diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp new file mode 100644 index 00000000..431c8b3d --- /dev/null +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -0,0 +1,284 @@ +#include "FlameInstanceCreationTask.h" + +#include "modplatform/flame/PackManifest.h" + +#include "Application.h" +#include "FileSystem.h" +#include "Json.h" + +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" + +#include "settings/INISettingsObject.h" + +#include "ui/dialogs/BlockedModsDialog.h" + +bool FlameCreationTask::abort() +{ + if (m_files_job) + m_files_job->abort(); + if (m_mod_id_resolver) + m_mod_id_resolver->abort(); + + return true; +} + +const static QMap forgemap = { { "1.2.5", "3.4.9.171" }, + { "1.4.2", "6.0.1.355" }, + { "1.4.7", "6.6.2.534" }, + { "1.5.2", "7.8.1.737" } }; + +bool FlameCreationTask::createInstance() +{ + QEventLoop loop; + + Flame::Manifest pack; + try { + QString configPath = FS::PathCombine(m_stagingPath, "manifest.json"); + Flame::loadManifest(pack, configPath); + QFile::remove(configPath); + } catch (const JSONValidationError& e) { + setError(tr("Could not understand pack manifest:\n") + e.cause()); + return false; + } + + if (!pack.overrides.isEmpty()) { + QString overridePath = FS::PathCombine(m_stagingPath, pack.overrides); + if (QFile::exists(overridePath)) { + QString mcPath = FS::PathCombine(m_stagingPath, "minecraft"); + if (!QFile::rename(overridePath, mcPath)) { + setError(tr("Could not rename the overrides folder:\n") + pack.overrides); + return false; + } + } else { + logWarning( + tr("The specified overrides folder (%1) is missing. Maybe the modpack was already used before?").arg(pack.overrides)); + } + } + + QString forgeVersion; + QString fabricVersion; + // TODO: is Quilt relevant here? + for (auto& loader : pack.minecraft.modLoaders) { + auto id = loader.id; + if (id.startsWith("forge-")) { + id.remove("forge-"); + forgeVersion = id; + continue; + } + if (id.startsWith("fabric-")) { + id.remove("fabric-"); + fabricVersion = id; + continue; + } + logWarning(tr("Unknown mod loader in manifest: %1").arg(id)); + } + + QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); + auto instanceSettings = std::make_shared(configPath); + MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); + auto mcVersion = pack.minecraft.version; + + // Hack to correct some 'special sauce'... + if (mcVersion.endsWith('.')) { + mcVersion.remove(QRegularExpression("[.]+$")); + logWarning(tr("Mysterious trailing dots removed from Minecraft version while importing pack.")); + } + + auto components = instance.getPackProfile(); + components->buildingFromScratch(); + components->setComponentVersion("net.minecraft", mcVersion, true); + if (!forgeVersion.isEmpty()) { + // FIXME: dirty, nasty, hack. Proper solution requires dependency resolution and knowledge of the metadata. + if (forgeVersion == "recommended") { + if (forgemap.contains(mcVersion)) { + forgeVersion = forgemap[mcVersion]; + } else { + logWarning(tr("Could not map recommended Forge version for Minecraft %1").arg(mcVersion)); + } + } + components->setComponentVersion("net.minecraftforge", forgeVersion); + } + if (!fabricVersion.isEmpty()) + components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion); + + if (m_instIcon != "default") { + instance.setIconKey(m_instIcon); + } else { + if (pack.name.contains("Direwolf20")) { + instance.setIconKey("steve"); + } else if (pack.name.contains("FTB") || pack.name.contains("Feed The Beast")) { + instance.setIconKey("ftb_logo"); + } else { + instance.setIconKey("flame"); + } + } + + QString jarmodsPath = FS::PathCombine(m_stagingPath, "minecraft", "jarmods"); + QFileInfo jarmodsInfo(jarmodsPath); + if (jarmodsInfo.isDir()) { + // install all the jar mods + qDebug() << "Found jarmods:"; + QDir jarmodsDir(jarmodsPath); + QStringList jarMods; + for (auto info : jarmodsDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) { + qDebug() << info.fileName(); + jarMods.push_back(info.absoluteFilePath()); + } + auto profile = instance.getPackProfile(); + profile->installJarMods(jarMods); + // nuke the original files + FS::deletePath(jarmodsPath); + } + + instance.setName(m_instName); + + m_mod_id_resolver = new Flame::FileResolvingTask(APPLICATION->network(), pack); + connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::succeeded, this, [this, &loop]{ + idResolverSucceeded(loop); + }); + connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) { + m_mod_id_resolver.reset(); + setError(tr("Unable to resolve mod IDs:\n") + reason); + }); + connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::progress, this, &FlameCreationTask::setProgress); + connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::status, this, &FlameCreationTask::setStatus); + + m_mod_id_resolver->start(); + + loop.exec(); + + return getError().isEmpty(); +} + +void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) +{ + auto results = m_mod_id_resolver->getResults(); + // first check for blocked mods + QString text; + QList urls; + auto anyBlocked = false; + for (const auto& result : results.files.values()) { + if (!result.resolved || result.url.isEmpty()) { + text += QString("%1: %2
").arg(result.fileName, result.websiteUrl); + urls.append(QUrl(result.websiteUrl)); + anyBlocked = true; + } + } + if (anyBlocked) { + qWarning() << "Blocked mods found, displaying mod list"; + + auto message_dialog = new BlockedModsDialog(m_parent, tr("Blocked mods found"), + tr("The following mods were blocked on third party launchers.
" + "You will need to manually download them and add them to the modpack"), + text, + urls); + message_dialog->setModal(true); + + if (message_dialog->exec()) { + m_files_job = new NetJob(tr("Mod download"), APPLICATION->network()); + for (const auto& result : m_mod_id_resolver->getResults().files) { + QString filename = result.fileName; + if (!result.required) { + filename += ".disabled"; + } + + auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename); + auto path = FS::PathCombine(m_stagingPath, relpath); + + switch (result.type) { + case Flame::File::Type::Folder: { + logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); + // fall-through intentional, we treat these as plain old mods and dump them wherever. + } + case Flame::File::Type::SingleFile: + case Flame::File::Type::Mod: { + if (!result.url.isEmpty()) { + qDebug() << "Will download" << result.url << "to" << path; + auto dl = Net::Download::makeFile(result.url, path); + m_files_job->addNetAction(dl); + } + break; + } + case Flame::File::Type::Modpack: + logWarning(tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(relpath)); + break; + case Flame::File::Type::Cmod2: + case Flame::File::Type::Ctoc: + case Flame::File::Type::Unknown: + logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath)); + break; + } + } + + m_mod_id_resolver.reset(); + connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { + m_files_job.reset(); + emitSucceeded(); + }); + connect(m_files_job.get(), &NetJob::failed, [&](QString reason) { + m_files_job.reset(); + setError(reason); + }); + connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) { setProgress(current, total); }); + connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit); + + setStatus(tr("Downloading mods...")); + m_files_job->start(); + } else { + m_mod_id_resolver.reset(); + setError("Canceled"); + } + } else { + // TODO extract to function ? + m_files_job = new NetJob(tr("Mod download"), APPLICATION->network()); + for (const auto& result : m_mod_id_resolver->getResults().files) { + QString filename = result.fileName; + if (!result.required) { + filename += ".disabled"; + } + + auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename); + auto path = FS::PathCombine(m_stagingPath, relpath); + + switch (result.type) { + case Flame::File::Type::Folder: { + logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); + // fall-through intentional, we treat these as plain old mods and dump them wherever. + } + case Flame::File::Type::SingleFile: + case Flame::File::Type::Mod: { + if (!result.url.isEmpty()) { + qDebug() << "Will download" << result.url << "to" << path; + auto dl = Net::Download::makeFile(result.url, path); + m_files_job->addNetAction(dl); + } + break; + } + case Flame::File::Type::Modpack: + logWarning(tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(relpath)); + break; + case Flame::File::Type::Cmod2: + case Flame::File::Type::Ctoc: + case Flame::File::Type::Unknown: + logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath)); + break; + } + } + + m_mod_id_resolver.reset(); + connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { + m_files_job.reset(); + emitSucceeded(); + }); + connect(m_files_job.get(), &NetJob::failed, [&](QString reason) { + m_files_job.reset(); + setError(reason); + }); + connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) { setProgress(current, total); }); + connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit); + + setStatus(tr("Downloading mods...")); + m_files_job->start(); + } +} diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.h b/launcher/modplatform/flame/FlameInstanceCreationTask.h new file mode 100644 index 00000000..efb099d9 --- /dev/null +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.h @@ -0,0 +1,32 @@ +#pragma once + +#include "InstanceCreationTask.h" + +#include "modplatform/flame/FileResolvingTask.h" + +#include "net/NetJob.h" + +class FlameCreationTask final : public InstanceCreationTask { + Q_OBJECT + + public: + FlameCreationTask(QString staging_path, SettingsObjectPtr global_settings, QWidget* parent) + : InstanceCreationTask(), m_parent(parent) + { + setStagingPath(staging_path); + setParentSettings(global_settings); + } + + bool abort() override; + + bool createInstance() override; + + private slots: + void idResolverSucceeded(QEventLoop&); + + private: + QWidget* m_parent = nullptr; + + shared_qobject_ptr m_mod_id_resolver; + NetJob::Ptr m_files_job; +}; From ebd46705d503a8627e759327e93c5a8432d4e47f Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 21 Jul 2022 16:43:08 -0300 Subject: [PATCH 32/58] refactor: move creation of CF file download task to a separate function Signed-off-by: flow --- .../flame/FlameInstanceCreationTask.cpp | 155 +++++++----------- .../flame/FlameInstanceCreationTask.h | 1 + 2 files changed, 57 insertions(+), 99 deletions(-) diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 431c8b3d..6ec1060c 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -154,6 +154,7 @@ bool FlameCreationTask::createInstance() void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) { auto results = m_mod_id_resolver->getResults(); + // first check for blocked mods QString text; QList urls; @@ -176,109 +177,65 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) message_dialog->setModal(true); if (message_dialog->exec()) { - m_files_job = new NetJob(tr("Mod download"), APPLICATION->network()); - for (const auto& result : m_mod_id_resolver->getResults().files) { - QString filename = result.fileName; - if (!result.required) { - filename += ".disabled"; - } - - auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename); - auto path = FS::PathCombine(m_stagingPath, relpath); - - switch (result.type) { - case Flame::File::Type::Folder: { - logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); - // fall-through intentional, we treat these as plain old mods and dump them wherever. - } - case Flame::File::Type::SingleFile: - case Flame::File::Type::Mod: { - if (!result.url.isEmpty()) { - qDebug() << "Will download" << result.url << "to" << path; - auto dl = Net::Download::makeFile(result.url, path); - m_files_job->addNetAction(dl); - } - break; - } - case Flame::File::Type::Modpack: - logWarning(tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(relpath)); - break; - case Flame::File::Type::Cmod2: - case Flame::File::Type::Ctoc: - case Flame::File::Type::Unknown: - logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath)); - break; - } - } - - m_mod_id_resolver.reset(); - connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { - m_files_job.reset(); - emitSucceeded(); - }); - connect(m_files_job.get(), &NetJob::failed, [&](QString reason) { - m_files_job.reset(); - setError(reason); - }); - connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) { setProgress(current, total); }); - connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit); - - setStatus(tr("Downloading mods...")); - m_files_job->start(); + setupDownloadJob(loop); } else { m_mod_id_resolver.reset(); setError("Canceled"); } } else { - // TODO extract to function ? - m_files_job = new NetJob(tr("Mod download"), APPLICATION->network()); - for (const auto& result : m_mod_id_resolver->getResults().files) { - QString filename = result.fileName; - if (!result.required) { - filename += ".disabled"; - } - - auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename); - auto path = FS::PathCombine(m_stagingPath, relpath); - - switch (result.type) { - case Flame::File::Type::Folder: { - logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); - // fall-through intentional, we treat these as plain old mods and dump them wherever. - } - case Flame::File::Type::SingleFile: - case Flame::File::Type::Mod: { - if (!result.url.isEmpty()) { - qDebug() << "Will download" << result.url << "to" << path; - auto dl = Net::Download::makeFile(result.url, path); - m_files_job->addNetAction(dl); - } - break; - } - case Flame::File::Type::Modpack: - logWarning(tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(relpath)); - break; - case Flame::File::Type::Cmod2: - case Flame::File::Type::Ctoc: - case Flame::File::Type::Unknown: - logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath)); - break; - } - } - - m_mod_id_resolver.reset(); - connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { - m_files_job.reset(); - emitSucceeded(); - }); - connect(m_files_job.get(), &NetJob::failed, [&](QString reason) { - m_files_job.reset(); - setError(reason); - }); - connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) { setProgress(current, total); }); - connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit); - - setStatus(tr("Downloading mods...")); - m_files_job->start(); + setupDownloadJob(loop); } } + +void FlameCreationTask::setupDownloadJob(QEventLoop& loop) +{ + m_files_job = new NetJob(tr("Mod download"), APPLICATION->network()); + for (const auto& result : m_mod_id_resolver->getResults().files) { + QString filename = result.fileName; + if (!result.required) { + filename += ".disabled"; + } + + auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename); + auto path = FS::PathCombine(m_stagingPath, relpath); + + switch (result.type) { + case Flame::File::Type::Folder: { + logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); + // fall-through intentional, we treat these as plain old mods and dump them wherever. + } + case Flame::File::Type::SingleFile: + case Flame::File::Type::Mod: { + if (!result.url.isEmpty()) { + qDebug() << "Will download" << result.url << "to" << path; + auto dl = Net::Download::makeFile(result.url, path); + m_files_job->addNetAction(dl); + } + break; + } + case Flame::File::Type::Modpack: + logWarning(tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(relpath)); + break; + case Flame::File::Type::Cmod2: + case Flame::File::Type::Ctoc: + case Flame::File::Type::Unknown: + logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath)); + break; + } + } + + m_mod_id_resolver.reset(); + connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { + m_files_job.reset(); + emitSucceeded(); + }); + connect(m_files_job.get(), &NetJob::failed, [&](QString reason) { + m_files_job.reset(); + setError(reason); + }); + connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) { setProgress(current, total); }); + connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit); + + setStatus(tr("Downloading mods...")); + m_files_job->start(); +} diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.h b/launcher/modplatform/flame/FlameInstanceCreationTask.h index efb099d9..be11f805 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.h +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.h @@ -23,6 +23,7 @@ class FlameCreationTask final : public InstanceCreationTask { private slots: void idResolverSucceeded(QEventLoop&); + void setupDownloadJob(QEventLoop&); private: QWidget* m_parent = nullptr; From 795d6f35eed689677186a26ba54ce9e77613859d Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 14 Jul 2022 16:41:49 -0300 Subject: [PATCH 33/58] feat: add curseforge modpack updating Signed-off-by: flow --- .../flame/FlameInstanceCreationTask.cpp | 201 ++++++++++++++++-- .../flame/FlameInstanceCreationTask.h | 7 +- launcher/modplatform/flame/PackManifest.cpp | 24 ++- launcher/modplatform/flame/PackManifest.h | 10 +- 4 files changed, 206 insertions(+), 36 deletions(-) diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 6ec1060c..f0f02bc8 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -1,5 +1,6 @@ #include "FlameInstanceCreationTask.h" +#include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/PackManifest.h" #include "Application.h" @@ -11,10 +12,23 @@ #include "settings/INISettingsObject.h" +#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/BlockedModsDialog.h" +// NOTE: Because of CF's ToS, I don't know if it counts as caching data, so it'll be disabled for now +#define DO_DIFF_UPDATE 0 + +const static QMap forgemap = { { "1.2.5", "3.4.9.171" }, + { "1.4.2", "6.0.1.355" }, + { "1.4.7", "6.6.2.534" }, + { "1.5.2", "7.8.1.737" } }; + +static const FlameAPI api; + bool FlameCreationTask::abort() { + if (m_process_update_file_info_job) + m_process_update_file_info_job->abort(); if (m_files_job) m_files_job->abort(); if (m_mod_id_resolver) @@ -23,43 +37,186 @@ bool FlameCreationTask::abort() return true; } -const static QMap forgemap = { { "1.2.5", "3.4.9.171" }, - { "1.4.2", "6.0.1.355" }, - { "1.4.7", "6.6.2.534" }, - { "1.5.2", "7.8.1.737" } }; - -bool FlameCreationTask::createInstance() +bool FlameCreationTask::updateInstance() { - QEventLoop loop; + auto instance_list = APPLICATION->instances(); + + // FIXME: How to handle situations when there's more than one install already for a given modpack? + auto inst = instance_list->getInstanceByManagedName(originalName()); + + if (!inst) { + inst = instance_list->getInstanceById(originalName()); + + if (!inst) + return false; + } + + QString index_path(FS::PathCombine(m_stagingPath, "manifest.json")); - Flame::Manifest pack; try { - QString configPath = FS::PathCombine(m_stagingPath, "manifest.json"); - Flame::loadManifest(pack, configPath); - QFile::remove(configPath); + Flame::loadManifest(m_pack, index_path); } catch (const JSONValidationError& e) { setError(tr("Could not understand pack manifest:\n") + e.cause()); return false; } - if (!pack.overrides.isEmpty()) { - QString overridePath = FS::PathCombine(m_stagingPath, pack.overrides); + auto version_id = inst->getManagedPackVersionName(); + auto version_str = !version_id.isEmpty() ? tr(" (version %1)").arg(version_id) : ""; + + auto info = CustomMessageBox::selectable(m_parent, tr("Similar modpack was found!"), + tr("One or more of your instances are from this same modpack%1. Do you want to create a " + "separate instance, or update the existing one?") + .arg(version_str), + QMessageBox::Information, QMessageBox::Ok | QMessageBox::Abort); + info->setButtonText(QMessageBox::Ok, tr("Update existing instance")); + info->setButtonText(QMessageBox::Abort, tr("Create new instance")); + + if (info->exec() && info->clickedButton() == info->button(QMessageBox::Abort)) + return false; + + QDir old_inst_dir(inst->instanceRoot()); + + QString old_index_path(FS::PathCombine(old_inst_dir.absolutePath(), "flame", "manifest.json")); + QFileInfo old_index_file(old_index_path); + if (old_index_file.exists()) { + Flame::Manifest old_pack; + Flame::loadManifest(old_pack, old_index_path); + + auto& old_files = old_pack.files; + +#if DO_DIFF_UPDATE + // Remove repeated files, we don't need to download them! + auto& files = m_pack.files; + + // Let's remove all duplicated, identical resources! + auto files_iterator = files.begin(); + while (files_iterator != files.end()) { + auto const& file = files_iterator; + + auto old_file = old_files.find(file.key()); + if (old_file != old_files.end()) { + // We found a match, but is it a different version? + if (old_file->fileId == file->fileId) { + qDebug() << "Removed file at" << file->targetFolder << "with id" << file->fileId << "from list of downloads"; + + old_files.remove(file.key()); + files_iterator = files.erase(files_iterator); + } + } + + files_iterator++; + } +#endif + // Remove remaining old files (we need to do an API request to know which ids are which files...) + QStringList fileIds; + + for (auto& file : old_files) { + fileIds.append(QString::number(file.fileId)); + } + + auto* raw_response = new QByteArray; + auto job = api.getFiles(fileIds, raw_response); + + QEventLoop loop; + + connect(job, &NetJob::succeeded, this, [raw_response, fileIds, old_inst_dir, &old_files] { + // Parse the API response + QJsonParseError parse_error{}; + auto doc = QJsonDocument::fromJson(*raw_response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from Flame files task at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *raw_response; + return; + } + + try { + QJsonArray entries; + if (fileIds.size() == 1) + entries = { Json::requireObject(Json::requireObject(doc), "data") }; + else + entries = Json::requireArray(Json::requireObject(doc), "data"); + + for (auto entry : entries) { + auto entry_obj = Json::requireObject(entry); + + Flame::File file; + // We don't care about blocked mods, we just need local data to delete the file + file.parseFromObject(entry_obj, false); + + auto id = Json::requireInteger(entry_obj, "id"); + old_files.insert(id, file); + } + } catch (Json::JsonException& e) { + qCritical() << e.cause() << e.what(); + } + + // Delete the files + for (auto& file : old_files) { + if (file.fileName.isEmpty() || file.targetFolder.isEmpty()) + continue; + + qDebug() << "Removing" << file.fileName << "at" << file.targetFolder; + QString path(FS::PathCombine(old_inst_dir.absolutePath(), "minecraft", file.targetFolder, file.fileName)); + if (!QFile::remove(path)) + qDebug() << "Failed to remove file at" << path; + } + }); + connect(job, &NetJob::finished, &loop, &QEventLoop::quit); + + m_process_update_file_info_job = job; + job->start(); + + loop.exec(); + + m_process_update_file_info_job = nullptr; + } + // TODO: Currently 'overrides' will always override the stuff on update. How do we preserve unchanged overrides? + + setOverride(true); + qDebug() << "Will override instance!"; + + // We let it go through the createInstance() stage, just with a couple modifications for updating + return false; +} + +bool FlameCreationTask::createInstance() +{ + QEventLoop loop; + + try { + QString index_path(FS::PathCombine(m_stagingPath, "manifest.json")); + if (!m_pack.is_loaded) + Flame::loadManifest(m_pack, index_path); + + // Keep index file in case we need it some other time (like when changing versions) + QString new_index_place(FS::PathCombine(m_stagingPath, "flame", "manifest.json")); + FS::ensureFilePathExists(new_index_place); + QFile::rename(index_path, new_index_place); + + } catch (const JSONValidationError& e) { + setError(tr("Could not understand pack manifest:\n") + e.cause()); + return false; + } + + if (!m_pack.overrides.isEmpty()) { + QString overridePath = FS::PathCombine(m_stagingPath, m_pack.overrides); if (QFile::exists(overridePath)) { QString mcPath = FS::PathCombine(m_stagingPath, "minecraft"); if (!QFile::rename(overridePath, mcPath)) { - setError(tr("Could not rename the overrides folder:\n") + pack.overrides); + setError(tr("Could not rename the overrides folder:\n") + m_pack.overrides); return false; } } else { logWarning( - tr("The specified overrides folder (%1) is missing. Maybe the modpack was already used before?").arg(pack.overrides)); + tr("The specified overrides folder (%1) is missing. Maybe the modpack was already used before?").arg(m_pack.overrides)); } } QString forgeVersion; QString fabricVersion; // TODO: is Quilt relevant here? - for (auto& loader : pack.minecraft.modLoaders) { + for (auto& loader : m_pack.minecraft.modLoaders) { auto id = loader.id; if (id.startsWith("forge-")) { id.remove("forge-"); @@ -77,7 +234,7 @@ bool FlameCreationTask::createInstance() QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); auto instanceSettings = std::make_shared(configPath); MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); - auto mcVersion = pack.minecraft.version; + auto mcVersion = m_pack.minecraft.version; // Hack to correct some 'special sauce'... if (mcVersion.endsWith('.')) { @@ -105,9 +262,9 @@ bool FlameCreationTask::createInstance() if (m_instIcon != "default") { instance.setIconKey(m_instIcon); } else { - if (pack.name.contains("Direwolf20")) { + if (m_pack.name.contains("Direwolf20")) { instance.setIconKey("steve"); - } else if (pack.name.contains("FTB") || pack.name.contains("Feed The Beast")) { + } else if (m_pack.name.contains("FTB") || m_pack.name.contains("Feed The Beast")) { instance.setIconKey("ftb_logo"); } else { instance.setIconKey("flame"); @@ -133,10 +290,8 @@ bool FlameCreationTask::createInstance() instance.setName(m_instName); - m_mod_id_resolver = new Flame::FileResolvingTask(APPLICATION->network(), pack); - connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::succeeded, this, [this, &loop]{ - idResolverSucceeded(loop); - }); + m_mod_id_resolver = new Flame::FileResolvingTask(APPLICATION->network(), m_pack); + connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::succeeded, this, [this, &loop] { idResolverSucceeded(loop); }); connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) { m_mod_id_resolver.reset(); setError(tr("Unable to resolve mod IDs:\n") + reason); diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.h b/launcher/modplatform/flame/FlameInstanceCreationTask.h index be11f805..99822d93 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.h +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.h @@ -19,6 +19,7 @@ class FlameCreationTask final : public InstanceCreationTask { bool abort() override; + bool updateInstance() override; bool createInstance() override; private slots: @@ -29,5 +30,9 @@ class FlameCreationTask final : public InstanceCreationTask { QWidget* m_parent = nullptr; shared_qobject_ptr m_mod_id_resolver; - NetJob::Ptr m_files_job; + Flame::Manifest m_pack; + + // Handle to allow aborting + NetJob* m_process_update_file_info_job = nullptr; + NetJob::Ptr m_files_job = nullptr; }; diff --git a/launcher/modplatform/flame/PackManifest.cpp b/launcher/modplatform/flame/PackManifest.cpp index 81395fcd..22008297 100644 --- a/launcher/modplatform/flame/PackManifest.cpp +++ b/launcher/modplatform/flame/PackManifest.cpp @@ -29,21 +29,29 @@ static void loadMinecraftV1(Flame::Minecraft& m, QJsonObject& minecraft) } } -static void loadManifestV1(Flame::Manifest& m, QJsonObject& manifest) +static void loadManifestV1(Flame::Manifest& pack, QJsonObject& manifest) { auto mc = Json::requireObject(manifest, "minecraft"); - loadMinecraftV1(m.minecraft, mc); - m.name = Json::ensureString(manifest, QString("name"), "Unnamed"); - m.version = Json::ensureString(manifest, QString("version"), QString()); - m.author = Json::ensureString(manifest, QString("author"), "Anonymous"); + + loadMinecraftV1(pack.minecraft, mc); + + pack.name = Json::ensureString(manifest, QString("name"), "Unnamed"); + pack.version = Json::ensureString(manifest, QString("version"), QString()); + pack.author = Json::ensureString(manifest, QString("author"), "Anonymous"); + auto arr = Json::ensureArray(manifest, "files", QJsonArray()); - for (QJsonValueRef item : arr) { + for (auto item : arr) { auto obj = Json::requireObject(item); + Flame::File file; loadFileV1(file, obj); - m.files.insert(file.fileId,file); + + pack.files.insert(file.fileId,file); } - m.overrides = Json::ensureString(manifest, "overrides", "overrides"); + + pack.overrides = Json::ensureString(manifest, "overrides", "overrides"); + + pack.is_loaded = true; } void Flame::loadManifest(Flame::Manifest& m, const QString& filepath) diff --git a/launcher/modplatform/flame/PackManifest.h b/launcher/modplatform/flame/PackManifest.h index a69e1321..0b7461d8 100644 --- a/launcher/modplatform/flame/PackManifest.h +++ b/launcher/modplatform/flame/PackManifest.h @@ -35,11 +35,11 @@ #pragma once -#include -#include -#include -#include #include +#include +#include +#include +#include namespace Flame { @@ -97,6 +97,8 @@ struct Manifest //File id -> File QMap files; QString overrides; + + bool is_loaded = false; }; void loadManifest(Flame::Manifest & m, const QString &filepath); From eed73c90785ec977ee975d403270f9138aa6960c Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 8 Jul 2022 19:44:44 -0300 Subject: [PATCH 34/58] refactor: clean up InstanceImportTask a bit Also removes a divide by two in the download progress (why was it there???) Signed-off-by: flow --- launcher/InstanceImportTask.cpp | 44 ++++++++++++++------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 72c2496f..da57ddeb 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -35,34 +35,26 @@ */ #include "InstanceImportTask.h" -#include + #include "Application.h" -#include "BaseInstance.h" #include "FileSystem.h" #include "MMCZip.h" #include "NullInstance.h" + #include "icons/IconList.h" #include "icons/IconUtils.h" -#include "settings/INISettingsObject.h" -// FIXME: this does not belong here, it's Minecraft/Flame specific -#include -#include "Json.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" #include "modplatform/technic/TechnicPackProcessor.h" #include "modplatform/modrinth/ModrinthInstanceCreationTask.h" #include "modplatform/flame/FlameInstanceCreationTask.h" -#include "Application.h" -#include "icons/IconList.h" -#include "net/ChecksumValidator.h" - -#include "ui/dialogs/CustomMessageBox.h" -#include "ui/dialogs/BlockedModsDialog.h" +#include "settings/INISettingsObject.h" +#include #include +#include + InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent) { m_sourceUrl = sourceUrl; @@ -80,26 +72,26 @@ bool InstanceImportTask::abort() void InstanceImportTask::executeTask() { - if (m_sourceUrl.isLocalFile()) - { + if (m_sourceUrl.isLocalFile()) { m_archivePath = m_sourceUrl.toLocalFile(); processZipPack(); - } - else - { + } else { setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString())); m_downloadRequired = true; - const QString path = m_sourceUrl.host() + '/' + m_sourceUrl.path(); + const QString path(m_sourceUrl.host() + '/' + m_sourceUrl.path()); + auto entry = APPLICATION->metacache()->resolveEntry("general", path); entry->setStale(true); + m_archivePath = entry->getFullPath(); + m_filesNetJob = new NetJob(tr("Modpack download"), APPLICATION->network()); m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry)); - m_archivePath = entry->getFullPath(); - auto job = m_filesNetJob.get(); - connect(job, &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded); - connect(job, &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged); - connect(job, &NetJob::failed, this, &InstanceImportTask::downloadFailed); + + connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded); + connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged); + connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed); + m_filesNetJob->start(); } } @@ -118,7 +110,7 @@ void InstanceImportTask::downloadFailed(QString reason) void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total) { - setProgress(current / 2, total); + setProgress(current, total); } void InstanceImportTask::processZipPack() From 6131346e2f80c5ce4377fcc608be4f3929f43f91 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 14 Jul 2022 16:13:23 -0300 Subject: [PATCH 35/58] refactor: change the way instance names are handled While working on pack updating, instance naming always gets in the way, since we need both way of respecting the user's name choice, and a standarized way of getting the original pack name / version. This tries to circunvent such problems by abstracting away the naming schema into it's own struct, holding both the original name / version, and the user-defined name, so that everyone can be happy and world peace can be achieved! (at least that's what i'd hope :c). Signed-off-by: flow --- launcher/InstanceCopyTask.cpp | 2 +- launcher/InstanceImportTask.cpp | 8 +-- launcher/InstanceList.cpp | 32 ++++----- launcher/InstanceList.h | 6 +- launcher/InstanceTask.cpp | 29 +++++++- launcher/InstanceTask.h | 70 +++++++++---------- .../minecraft/VanillaInstanceCreationTask.cpp | 4 +- .../atlauncher/ATLPackInstallTask.cpp | 2 +- .../flame/FlameInstanceCreationTask.cpp | 4 +- .../legacy_ftb/PackInstallTask.cpp | 2 +- .../modpacksch/FTBPackInstallTask.cpp | 2 +- .../modrinth/ModrinthInstanceCreationTask.cpp | 10 ++- .../technic/SingleZipPackInstallTask.cpp | 2 +- .../technic/SolderPackInstallTask.cpp | 2 +- launcher/ui/dialogs/NewInstanceDialog.cpp | 31 ++++++-- launcher/ui/dialogs/NewInstanceDialog.h | 6 +- .../pages/modplatform/atlauncher/AtlPage.cpp | 2 +- launcher/ui/pages/modplatform/ftb/FtbPage.cpp | 2 +- .../ui/pages/modplatform/legacy_ftb/Page.cpp | 2 +- .../modplatform/modrinth/ModrinthPage.cpp | 2 +- .../pages/modplatform/technic/TechnicPage.cpp | 4 +- 21 files changed, 131 insertions(+), 93 deletions(-) diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index c2bfe839..b1e33884 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -44,7 +44,7 @@ void InstanceCopyTask::copyFinished() auto instanceSettings = std::make_shared(FS::PathCombine(m_stagingPath, "instance.cfg")); InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, m_stagingPath)); - inst->setName(m_instName); + inst->setName(name()); inst->setIconKey(m_instIcon); if(!m_keepPlaytime) { inst->resetTimePlayed(); diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index da57ddeb..09e65064 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -255,7 +255,7 @@ void InstanceImportTask::processFlame() { auto* inst_creation_task = new FlameCreationTask(m_stagingPath, m_globalSettings, m_parent); - inst_creation_task->setName(m_instName); + inst_creation_task->setName(*this); inst_creation_task->setIcon(m_instIcon); inst_creation_task->setGroup(m_instGroup); @@ -278,7 +278,7 @@ void InstanceImportTask::processTechnic() shared_qobject_ptr packProcessor = new Technic::TechnicPackProcessor(); connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &InstanceImportTask::emitSucceeded); connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &InstanceImportTask::emitFailed); - packProcessor->run(m_globalSettings, m_instName, m_instIcon, m_stagingPath); + packProcessor->run(m_globalSettings, name(), m_instIcon, m_stagingPath); } void InstanceImportTask::processMultiMC() @@ -292,7 +292,7 @@ void InstanceImportTask::processMultiMC() instance.resetTimePlayed(); // set a new nice name - instance.setName(m_instName); + instance.setName(name()); // if the icon was specified by user, use that. otherwise pull icon from the pack if (m_instIcon != "default") { @@ -317,7 +317,7 @@ void InstanceImportTask::processModrinth() { auto* inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, m_sourceUrl.toString()); - inst_creation_task->setName(m_instName); + inst_creation_task->setName(*this); inst_creation_task->setIcon(m_instIcon); inst_creation_task->setGroup(m_instGroup); diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 698aa24e..0e59150e 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -778,19 +778,14 @@ class InstanceStaging : public Task { const unsigned minBackoff = 1; const unsigned maxBackoff = 16; public: - InstanceStaging(InstanceList* parent, InstanceTask* child, const QString& stagingPath, const QString& instanceName, const QString& groupName) - : backoff(minBackoff, maxBackoff) + InstanceStaging(InstanceList* parent, InstanceTask* child, QString stagingPath, InstanceName const& instanceName, QString groupName) + : m_parent(parent), backoff(minBackoff, maxBackoff), m_stagingPath(std::move(stagingPath)), m_instance_name(std::move(instanceName)), m_groupName(std::move(groupName)) { - m_parent = parent; m_child.reset(child); connect(child, &Task::succeeded, this, &InstanceStaging::childSucceded); connect(child, &Task::failed, this, &InstanceStaging::childFailed); connect(child, &Task::status, this, &InstanceStaging::setStatus); connect(child, &Task::progress, this, &InstanceStaging::setProgress); - m_instanceName = instanceName; - m_groupName = groupName; - m_stagingPath = stagingPath; - m_backoffTimer.setSingleShot(true); connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded); } @@ -820,7 +815,7 @@ class InstanceStaging : public Task { void childSucceded() { unsigned sleepTime = backoff(); - if (m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName, m_child->shouldOverride())) + if (m_parent->commitStagedInstance(m_stagingPath, m_instance_name, m_groupName, m_child->shouldOverride())) { emitSucceeded(); return; @@ -830,7 +825,7 @@ class InstanceStaging : public Task { emitFailed(tr("Failed to commit instance, even after multiple retries. It is being blocked by something.")); return; } - qDebug() << "Failed to commit instance" << m_instanceName << "Initiating backoff:" << sleepTime; + qDebug() << "Failed to commit instance" << m_instance_name.name() << "Initiating backoff:" << sleepTime; m_backoffTimer.start(sleepTime * 500); } void childFailed(const QString& reason) @@ -840,6 +835,7 @@ class InstanceStaging : public Task { } private: + InstanceList * m_parent; /* * WHY: the whole reason why this uses an exponential backoff retry scheme is antivirus on Windows. * Basically, it starts messing things up while the launcher is extracting/creating instances @@ -847,9 +843,8 @@ class InstanceStaging : public Task { */ ExponentialSeries backoff; QString m_stagingPath; - InstanceList * m_parent; unique_qobject_ptr m_child; - QString m_instanceName; + InstanceName m_instance_name; QString m_groupName; QTimer m_backoffTimer; }; @@ -859,7 +854,7 @@ Task* InstanceList::wrapInstanceTask(InstanceTask* task) auto stagingPath = getStagedInstancePath(); task->setStagingPath(stagingPath); task->setParentSettings(m_globalSettings); - return new InstanceStaging(this, task, stagingPath, task->name(), task->group()); + return new InstanceStaging(this, task, stagingPath, *task, task->group()); } QString InstanceList::getStagedInstancePath() @@ -879,22 +874,23 @@ QString InstanceList::getStagedInstancePath() return path; } -bool InstanceList::commitStagedInstance(const QString& path, const QString& instanceName, const QString& groupName, bool should_override) +bool InstanceList::commitStagedInstance(QString path, InstanceName const& instanceName, QString groupName, bool should_override) { QDir dir; QString instID; InstancePtr inst; - QString raw_inst_name = instanceName.section(' ', 0, -2); if (should_override) { // This is to avoid problems when the instance folder gets manually renamed - if ((inst = getInstanceByManagedName(raw_inst_name))) { + if ((inst = getInstanceByManagedName(instanceName.originalName()))) { + instID = QFileInfo(inst->instanceRoot()).fileName(); + } else if ((inst = getInstanceByManagedName(instanceName.modifiedName()))) { instID = QFileInfo(inst->instanceRoot()).fileName(); } else { - instID = FS::RemoveInvalidFilenameChars(raw_inst_name, '-'); + instID = FS::RemoveInvalidFilenameChars(instanceName.modifiedName(), '-'); } } else { - instID = FS::DirNameFromString(raw_inst_name, m_instDir); + instID = FS::DirNameFromString(instanceName.modifiedName(), m_instDir); } { @@ -910,7 +906,7 @@ bool InstanceList::commitStagedInstance(const QString& path, const QString& inst if (!inst) inst = getInstanceById(instID); if (inst) - inst->setName(instanceName); + inst->setName(instanceName.name()); } else { if (!dir.rename(path, destination)) { qWarning() << "Failed to move" << path << "to" << destination; diff --git a/launcher/InstanceList.h b/launcher/InstanceList.h index 6b4dcfa4..df11b55a 100644 --- a/launcher/InstanceList.h +++ b/launcher/InstanceList.h @@ -24,10 +24,10 @@ #include "BaseInstance.h" -#include "QObjectPtr.h" - class QFileSystemWatcher; class InstanceTask; +struct InstanceName; + using InstanceId = QString; using GroupId = QString; using InstanceLocator = std::pair; @@ -133,7 +133,7 @@ public: * should_override is used when another similar instance already exists, and we want to override it * - for instance, when updating it. */ - bool commitStagedInstance(const QString & keyPath, const QString& instanceName, const QString & groupName, bool should_override); + bool commitStagedInstance(QString keyPath, const InstanceName& instanceName, QString groupName, bool should_override); /** * Destroy a previously created staging area given by @keyPath - used when creation fails. diff --git a/launcher/InstanceTask.cpp b/launcher/InstanceTask.cpp index dd132877..43a0b947 100644 --- a/launcher/InstanceTask.cpp +++ b/launcher/InstanceTask.cpp @@ -1,9 +1,34 @@ #include "InstanceTask.h" -InstanceTask::InstanceTask() +QString InstanceName::name() const { + if (!m_modified_name.isEmpty()) + return modifiedName(); + return QString("%1 %2").arg(m_original_name, m_original_version); } -InstanceTask::~InstanceTask() +QString InstanceName::originalName() const { + return m_original_name; } + +QString InstanceName::modifiedName() const +{ + if (!m_modified_name.isEmpty()) + return m_modified_name; + return m_original_name; +} + +QString InstanceName::version() const +{ + return m_original_version; +} + +void InstanceName::setName(InstanceName& other) +{ + m_original_name = other.m_original_name; + m_original_version = other.m_original_version; + m_modified_name = other.m_modified_name; +} + +InstanceTask::InstanceTask() : Task(), InstanceName() {} diff --git a/launcher/InstanceTask.h b/launcher/InstanceTask.h index 02810a52..5d67a2f0 100644 --- a/launcher/InstanceTask.h +++ b/launcher/InstanceTask.h @@ -1,56 +1,50 @@ #pragma once -#include "tasks/Task.h" #include "settings/SettingsObject.h" +#include "tasks/Task.h" -class InstanceTask : public Task -{ +struct InstanceName { + public: + InstanceName() = default; + InstanceName(QString name, QString version) : m_original_name(std::move(name)), m_original_version(std::move(version)) {} + + [[nodiscard]] QString modifiedName() const; + [[nodiscard]] QString originalName() const; + [[nodiscard]] QString name() const; + [[nodiscard]] QString version() const; + + void setName(QString name) { m_modified_name = name; } + void setName(InstanceName& other); + + protected: + QString m_original_name; + QString m_original_version; + + QString m_modified_name; +}; + +class InstanceTask : public Task, public InstanceName { Q_OBJECT -public: - explicit InstanceTask(); - virtual ~InstanceTask(); + public: + InstanceTask(); + ~InstanceTask() override = default; - void setParentSettings(SettingsObjectPtr settings) - { - m_globalSettings = settings; - } + void setParentSettings(SettingsObjectPtr settings) { m_globalSettings = settings; } - void setStagingPath(const QString &stagingPath) - { - m_stagingPath = stagingPath; - } + void setStagingPath(const QString& stagingPath) { m_stagingPath = stagingPath; } - void setName(const QString &name) - { - m_instName = name; - } - QString name() const - { - return m_instName; - } + void setIcon(const QString& icon) { m_instIcon = icon; } - void setIcon(const QString &icon) - { - m_instIcon = icon; - } - - void setGroup(const QString &group) - { - m_instGroup = group; - } - QString group() const - { - return m_instGroup; - } + void setGroup(const QString& group) { m_instGroup = group; } + QString group() const { return m_instGroup; } bool shouldOverride() const { return m_override_existing; } -protected: + protected: void setOverride(bool override) { m_override_existing = override; } -protected: /* data */ + protected: /* data */ SettingsObjectPtr m_globalSettings; - QString m_instName; QString m_instIcon; QString m_instGroup; QString m_stagingPath; diff --git a/launcher/minecraft/VanillaInstanceCreationTask.cpp b/launcher/minecraft/VanillaInstanceCreationTask.cpp index 2d1593b6..fb11379c 100644 --- a/launcher/minecraft/VanillaInstanceCreationTask.cpp +++ b/launcher/minecraft/VanillaInstanceCreationTask.cpp @@ -1,9 +1,9 @@ #include "VanillaInstanceCreationTask.h" #include "FileSystem.h" -#include "settings/INISettingsObject.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" +#include "settings/INISettingsObject.h" VanillaCreationTask::VanillaCreationTask(BaseVersionPtr version, QString loader, BaseVersionPtr loader_version) : InstanceCreationTask(), m_version(version), m_using_loader(true), m_loader(loader), m_loader_version(loader_version) @@ -23,7 +23,7 @@ bool VanillaCreationTask::createInstance() if(m_using_loader) components->setComponentVersion(m_loader, m_loader_version->descriptor()); - inst.setName(m_instName); + inst.setName(name()); inst.setIconKey(m_instIcon); } instance_settings->resumeSave(); diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 70a35395..13cab256 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -1005,7 +1005,7 @@ void PackInstallTask::install() components->saveNow(); - instance.setName(m_instName); + instance.setName(name()); instance.setIconKey(m_instIcon); instance.setManagedPack("atlauncher", m_pack_safe_name, m_pack_name, m_version_name, m_version_name); instanceSettings->resumeSave(); diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index f0f02bc8..202caa28 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -5,6 +5,7 @@ #include "Application.h" #include "FileSystem.h" +#include "InstanceList.h" #include "Json.h" #include "minecraft/MinecraftInstance.h" @@ -288,7 +289,8 @@ bool FlameCreationTask::createInstance() FS::deletePath(jarmodsPath); } - instance.setName(m_instName); + instance.setManagedPack("flame", {}, m_pack.name, {}, m_pack.version); + instance.setName(name()); m_mod_id_resolver = new Flame::FileResolvingTask(APPLICATION->network(), m_pack); connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::succeeded, this, [this, &loop] { idResolverSucceeded(loop); }); diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp index 83e14969..e2e61a7c 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp @@ -228,7 +228,7 @@ void PackInstallTask::install() progress(4, 4); - instance.setName(m_instName); + instance.setName(name()); if(m_instIcon == "default") { m_instIcon = "ftb_logo"; diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp index 3c15667c..f17a311a 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp @@ -335,7 +335,7 @@ void PackInstallTask::install() components->saveNow(); - instance.setName(m_instName); + instance.setName(name()); instance.setIconKey(m_instIcon); instance.setManagedPack("modpacksch", QString::number(m_pack.id), m_pack.name, QString::number(m_version.id), m_version.name); instanceSettings->resumeSave(); diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index efb8c99b..06ac29c3 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -8,8 +8,6 @@ #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" -#include "modplatform/ModIndex.h" - #include "net/ChecksumValidator.h" #include "settings/INISettingsObject.h" @@ -30,11 +28,10 @@ bool ModrinthCreationTask::updateInstance() auto instance_list = APPLICATION->instances(); // FIXME: How to handle situations when there's more than one install already for a given modpack? - // Based on the way we create the instance name (name + " " + version). Is there a better way? - auto inst = instance_list->getInstanceByManagedName(m_instName.section(' ', 0, -2)); + auto inst = instance_list->getInstanceByManagedName(originalName()); if (!inst) { - inst = instance_list->getInstanceById(m_instName); + inst = instance_list->getInstanceById(originalName()); if (!inst) return false; @@ -163,8 +160,9 @@ bool ModrinthCreationTask::createInstance() } else { instance.setIconKey("modrinth"); } - instance.setName(m_instName); + instance.setManagedPack("modrinth", getManagedPackID(), m_managed_name, m_managed_id, {}); + instance.setName(name()); instance.saveNow(); m_files_job = new NetJob(tr("Mod download"), APPLICATION->network()); diff --git a/launcher/modplatform/technic/SingleZipPackInstallTask.cpp b/launcher/modplatform/technic/SingleZipPackInstallTask.cpp index 9093b245..6438d9ef 100644 --- a/launcher/modplatform/technic/SingleZipPackInstallTask.cpp +++ b/launcher/modplatform/technic/SingleZipPackInstallTask.cpp @@ -133,7 +133,7 @@ void Technic::SingleZipPackInstallTask::extractFinished() shared_qobject_ptr packProcessor = new Technic::TechnicPackProcessor(); connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &Technic::SingleZipPackInstallTask::emitSucceeded); connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &Technic::SingleZipPackInstallTask::emitFailed); - packProcessor->run(m_globalSettings, m_instName, m_instIcon, m_stagingPath, m_minecraftVersion); + packProcessor->run(m_globalSettings, name(), m_instIcon, m_stagingPath, m_minecraftVersion); } void Technic::SingleZipPackInstallTask::extractAborted() diff --git a/launcher/modplatform/technic/SolderPackInstallTask.cpp b/launcher/modplatform/technic/SolderPackInstallTask.cpp index 89dbf4ca..65b6ca51 100644 --- a/launcher/modplatform/technic/SolderPackInstallTask.cpp +++ b/launcher/modplatform/technic/SolderPackInstallTask.cpp @@ -214,7 +214,7 @@ void Technic::SolderPackInstallTask::extractFinished() shared_qobject_ptr packProcessor = new Technic::TechnicPackProcessor(); connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &Technic::SolderPackInstallTask::emitSucceeded); connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &Technic::SolderPackInstallTask::emitFailed); - packProcessor->run(m_globalSettings, m_instName, m_instIcon, m_stagingPath, m_minecraftVersion, true); + packProcessor->run(m_globalSettings, name(), m_instIcon, m_stagingPath, m_minecraftVersion, true); } void Technic::SolderPackInstallTask::extractAborted() diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp index 675f8b15..04fecbde 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.cpp +++ b/launcher/ui/dialogs/NewInstanceDialog.cpp @@ -177,13 +177,30 @@ NewInstanceDialog::~NewInstanceDialog() delete ui; } -void NewInstanceDialog::setSuggestedPack(const QString& name, InstanceTask* task) +void NewInstanceDialog::setSuggestedPack(QString name, InstanceTask* task) { creationTask.reset(task); - ui->instNameTextBox->setPlaceholderText(name); - if(!task) - { + ui->instNameTextBox->setPlaceholderText(name); + importVersion.clear(); + + if (!task) { + ui->iconButton->setIcon(APPLICATION->icons()->getIcon("default")); + importIcon = false; + } + + auto allowOK = task && !instName().isEmpty(); + m_buttons->button(QDialogButtonBox::Ok)->setEnabled(allowOK); +} + +void NewInstanceDialog::setSuggestedPack(QString name, QString version, InstanceTask* task) +{ + creationTask.reset(task); + + ui->instNameTextBox->setPlaceholderText(name); + importVersion = version; + + if (!task) { ui->iconButton->setIcon(APPLICATION->icons()->getIcon("default")); importIcon = false; } @@ -214,7 +231,11 @@ InstanceTask * NewInstanceDialog::extractTask() { InstanceTask * extracted = creationTask.get(); creationTask.release(); - extracted->setName(instName()); + + InstanceName inst_name(ui->instNameTextBox->placeholderText().trimmed(), importVersion); + inst_name.setName(ui->instNameTextBox->text().trimmed()); + extracted->setName(inst_name); + extracted->setGroup(instGroup()); extracted->setIcon(iconKey()); return extracted; diff --git a/launcher/ui/dialogs/NewInstanceDialog.h b/launcher/ui/dialogs/NewInstanceDialog.h index a3c8cd1c..185ab9e6 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.h +++ b/launcher/ui/dialogs/NewInstanceDialog.h @@ -37,7 +37,6 @@ #include -#include "BaseVersion.h" #include "ui/pages/BasePageProvider.h" #include "InstanceTask.h" @@ -61,7 +60,8 @@ public: void updateDialogState(); - void setSuggestedPack(const QString & name = QString(), InstanceTask * task = nullptr); + void setSuggestedPack(QString name = QString(), InstanceTask * task = nullptr); + void setSuggestedPack(QString name, QString version, InstanceTask * task = nullptr); void setSuggestedIconFromFile(const QString &path, const QString &name); void setSuggestedIcon(const QString &key); @@ -95,5 +95,7 @@ private: QString importIconPath; QString importIconName; + QString importVersion; + void importIconNow(); }; diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp index 7901b90b..87544445 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp @@ -117,7 +117,7 @@ void AtlPage::suggestCurrent() } auto uiSupport = new AtlUserInteractionSupportImpl(this); - dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ATLauncher::PackInstallTask(uiSupport, selected.name, selectedVersion)); + dialog->setSuggestedPack(selected.name, selectedVersion, new ATLauncher::PackInstallTask(uiSupport, selected.name, selectedVersion)); auto editedLogoName = selected.safeName; auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(selected.safeName.toLower()); diff --git a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp b/launcher/ui/pages/modplatform/ftb/FtbPage.cpp index 504d7f7b..8975d74e 100644 --- a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp +++ b/launcher/ui/pages/modplatform/ftb/FtbPage.cpp @@ -127,7 +127,7 @@ void FtbPage::suggestCurrent() return; } - dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ModpacksCH::PackInstallTask(selected, selectedVersion, this)); + dialog->setSuggestedPack(selected.name, selectedVersion, new ModpacksCH::PackInstallTask(selected, selectedVersion, this)); for(auto art : selected.art) { if(art.type == "square") { QString editedLogoName; diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp index 6ffbd312..0208b36b 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp @@ -176,7 +176,7 @@ void Page::suggestCurrent() return; } - dialog->setSuggestedPack(selected.name + " " + selectedVersion, new PackInstallTask(APPLICATION->network(), selected, selectedVersion)); + dialog->setSuggestedPack(selected.name, selectedVersion, new PackInstallTask(APPLICATION->network(), selected, selectedVersion)); QString editedLogoName; if(selected.logo.toLower().startsWith("ftb")) { diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index df29c0c3..16fec82c 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -294,7 +294,7 @@ void ModrinthPage::suggestCurrent() for (auto& ver : current.versions) { if (ver.id == selectedVersion) { - dialog->setSuggestedPack(current.name + " " + ver.version, new InstanceImportTask(ver.download_url, this)); + dialog->setSuggestedPack(current.name, ver.version, new InstanceImportTask(ver.download_url, this)); auto iconName = current.iconName; m_model->getLogo(iconName, current.iconUrl.toString(), [this, iconName](QString logo) { dialog->setSuggestedIconFromFile(logo, iconName); }); diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp index b8c1e00a..b15af244 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp @@ -271,11 +271,11 @@ void TechnicPage::selectVersion() { if (!current.isSolder) { - dialog->setSuggestedPack(current.name + " " + selectedVersion, new Technic::SingleZipPackInstallTask(current.url, current.minecraftVersion)); + dialog->setSuggestedPack(current.name, selectedVersion, new Technic::SingleZipPackInstallTask(current.url, current.minecraftVersion)); } else { - dialog->setSuggestedPack(current.name + " " + selectedVersion, new Technic::SolderPackInstallTask(APPLICATION->network(), current.url, current.slug, selectedVersion, current.minecraftVersion)); + dialog->setSuggestedPack(current.name, selectedVersion, new Technic::SolderPackInstallTask(APPLICATION->network(), current.url, current.slug, selectedVersion, current.minecraftVersion)); } } From 7024acac06885f9d9e24125afef04231aebdd113 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 28 Jul 2022 22:34:24 -0300 Subject: [PATCH 36/58] feat: add override helper functions These help us keep track of relevant metadata information about overrides, so that we know what they are when we update a pack. Signed-off-by: flow --- launcher/CMakeLists.txt | 2 + .../modplatform/helpers/OverrideUtils.cpp | 59 +++++++++++++++++++ launcher/modplatform/helpers/OverrideUtils.h | 20 +++++++ 3 files changed, 81 insertions(+) create mode 100644 launcher/modplatform/helpers/OverrideUtils.cpp create mode 100644 launcher/modplatform/helpers/OverrideUtils.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 7bd92fac..2ff700ad 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -461,6 +461,8 @@ set(API_SOURCES modplatform/helpers/NetworkModAPI.cpp modplatform/helpers/HashUtils.h modplatform/helpers/HashUtils.cpp + modplatform/helpers/OverrideUtils.h + modplatform/helpers/OverrideUtils.cpp ) set(FTB_SOURCES diff --git a/launcher/modplatform/helpers/OverrideUtils.cpp b/launcher/modplatform/helpers/OverrideUtils.cpp new file mode 100644 index 00000000..3496a286 --- /dev/null +++ b/launcher/modplatform/helpers/OverrideUtils.cpp @@ -0,0 +1,59 @@ +#include "OverrideUtils.h" + +#include + +#include "FileSystem.h" + +namespace Override { + +void createOverrides(QString name, QString parent_folder, QString override_path) +{ + QString file_path(FS::PathCombine(parent_folder, name + ".txt")); + if (QFile::exists(file_path)) + QFile::remove(file_path); + + FS::ensureFilePathExists(file_path); + + QFile file(file_path); + file.open(QFile::WriteOnly); + + QDirIterator override_iterator(override_path, QDirIterator::Subdirectories); + while (override_iterator.hasNext()) { + auto override_file_path = override_iterator.next(); + QFileInfo info(override_file_path); + if (info.isFile()) { + // Absolute path with temp directory -> relative path + override_file_path = override_file_path.split(name).last().remove(0, 1); + + file.write(override_file_path.toUtf8()); + file.write("\n"); + } + } + + file.close(); +} + +QStringList readOverrides(QString name, QString parent_folder) +{ + QString file_path(FS::PathCombine(parent_folder, name + ".txt")); + + QFile file(file_path); + if (!file.exists()) + return {}; + + QStringList previous_overrides; + + file.open(QFile::ReadOnly); + + QString entry; + do { + entry = file.readLine(); + previous_overrides.append(entry.trimmed()); + } while (!entry.isEmpty()); + + file.close(); + + return previous_overrides; +} + +} // namespace Override diff --git a/launcher/modplatform/helpers/OverrideUtils.h b/launcher/modplatform/helpers/OverrideUtils.h new file mode 100644 index 00000000..73f059d6 --- /dev/null +++ b/launcher/modplatform/helpers/OverrideUtils.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace Override { + +/** This creates a file in `parent_folder` that holds information about which + * overrides are in `override_path`. + * + * If there's already an existing such file, it will be ovewritten. + */ +void createOverrides(QString name, QString parent_folder, QString override_path); + +/** This reads an existing overrides archive, returning a list of overrides. + * + * If there's no such file in `parent_folder`, it will return an empty list. + */ +QStringList readOverrides(QString name, QString parent_folder); + +} // namespace Override From 3a9d58e31c70159f574b5cc24a104f81c9d981ed Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 28 Jul 2022 22:35:40 -0300 Subject: [PATCH 37/58] feat: add override handling in modrinth pack update Signed-off-by: flow --- .../modrinth/ModrinthInstanceCreationTask.cpp | 42 +++++++++++++++---- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 06ac29c3..212b3447 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -8,6 +8,8 @@ #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" +#include "modplatform/helpers/OverrideUtils.h" + #include "net/ChecksumValidator.h" #include "settings/INISettingsObject.h" @@ -58,7 +60,9 @@ bool ModrinthCreationTask::updateInstance() // Remove repeated files, we don't need to download them! QDir old_inst_dir(inst->instanceRoot()); - QString old_index_path(FS::PathCombine(old_inst_dir.absolutePath(), "mrpack", "modrinth.index.json")); + QString old_index_folder(FS::PathCombine(old_inst_dir.absolutePath(), "mrpack")); + + QString old_index_path(FS::PathCombine(old_index_folder, "modrinth.index.json")); QFileInfo old_index_file(old_index_path); if (old_index_file.exists()) { std::vector old_files; @@ -66,7 +70,7 @@ bool ModrinthCreationTask::updateInstance() // Let's remove all duplicated, identical resources! auto files_iterator = m_files.begin(); -begin: + begin: while (files_iterator != m_files.end()) { auto const& file = *files_iterator; @@ -78,7 +82,7 @@ begin: qDebug() << "Removed file at" << file.path << "from list of downloads"; files_iterator = m_files.erase(files_iterator); old_files_iterator = old_files.erase(old_files_iterator); - goto begin; // Sorry :c + goto begin; // Sorry :c } old_files_iterator++; @@ -87,18 +91,32 @@ begin: files_iterator++; } + QDir old_minecraft_dir(inst->gameRoot()); // Some files were removed from the old version, and some will be downloaded in an updated version, // so we're fine removing them! if (!old_files.empty()) { - QDir old_minecraft_dir(inst->gameRoot()); for (auto const& file : old_files) { - qWarning() << "Removing" << file.path; + qDebug() << "Removing" << file.path; old_minecraft_dir.remove(file.path); } } + + // We will remove all the previous overrides, to prevent duplicate files! + // TODO: Currently 'overrides' will always override the stuff on update. How do we preserve unchanged overrides? + // FIXME: We may want to do something about disabled mods. + auto old_overrides = Override::readOverrides("overrides", old_index_folder); + for (auto entry : old_overrides) { + qDebug() << "Removing" << entry; + old_minecraft_dir.remove(entry); + } + + auto old_client_overrides = Override::readOverrides("client-overrides", old_index_folder); + for (auto entry : old_overrides) { + qDebug() << "Removing" << entry; + old_minecraft_dir.remove(entry); + } } - // TODO: Currently 'overrides' will always override the stuff on update. How do we preserve unchanged overrides? setOverride(true); qDebug() << "Will override instance!"; @@ -112,12 +130,14 @@ bool ModrinthCreationTask::createInstance() { QEventLoop loop; + QString parent_folder(FS::PathCombine(m_stagingPath, "mrpack")); + QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json"); if (m_files.empty() && !parseManifest(index_path, m_files)) return false; // Keep index file in case we need it some other time (like when changing versions) - QString new_index_place(FS::PathCombine(m_stagingPath, "mrpack", "modrinth.index.json")); + QString new_index_place(FS::PathCombine(parent_folder, "modrinth.index.json")); FS::ensureFilePathExists(new_index_place); QFile::rename(index_path, new_index_place); @@ -125,6 +145,10 @@ bool ModrinthCreationTask::createInstance() auto override_path = FS::PathCombine(m_stagingPath, "overrides"); if (QFile::exists(override_path)) { + // Create a list of overrides in "overrides.txt" inside mrpack/ + Override::createOverrides("overrides", parent_folder, override_path); + + // Apply the overrides if (!QFile::rename(override_path, mcPath)) { setError(tr("Could not rename the overrides folder:\n") + "overrides"); return false; @@ -134,6 +158,10 @@ bool ModrinthCreationTask::createInstance() // Do client overrides auto client_override_path = FS::PathCombine(m_stagingPath, "client-overrides"); if (QFile::exists(client_override_path)) { + // Create a list of overrides in "client-overrides.txt" inside mrpack/ + Override::createOverrides("client-overrides", parent_folder, client_override_path); + + // Apply the overrides if (!FS::overrideFolder(mcPath, client_override_path)) { setError(tr("Could not rename the client overrides folder:\n") + "client overrides"); return false; From be769d07f1498a212599234a3ad14c6b9431d206 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 31 Jul 2022 19:50:09 -0300 Subject: [PATCH 38/58] fix: correctly set all managed pack fields in Modrinth pack Signed-off-by: flow --- .../modrinth/ModrinthInstanceCreationTask.cpp | 28 +++++++++++++------ .../modrinth/ModrinthInstanceCreationTask.h | 10 +++++-- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 212b3447..b5140f34 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -5,7 +5,6 @@ #include "InstanceList.h" #include "Json.h" -#include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" #include "modplatform/helpers/OverrideUtils.h" @@ -43,8 +42,8 @@ bool ModrinthCreationTask::updateInstance() if (!parseManifest(index_path, m_files)) return false; - auto version_id = inst->getManagedPackVersionID(); - auto version_str = !version_id.isEmpty() ? tr(" (version %1)").arg(version_id) : ""; + auto version_name = inst->getManagedPackVersionName(); + auto version_str = !version_name.isEmpty() ? tr(" (version %1)").arg(version_name) : ""; auto info = CustomMessageBox::selectable(m_parent, tr("Similar modpack was found!"), tr("One or more of your instances are from this same modpack%1. Do you want to create a " @@ -66,7 +65,7 @@ bool ModrinthCreationTask::updateInstance() QFileInfo old_index_file(old_index_path); if (old_index_file.exists()) { std::vector old_files; - parseManifest(old_index_path, old_files); + parseManifest(old_index_path, old_files, false); // Let's remove all duplicated, identical resources! auto files_iterator = m_files.begin(); @@ -121,6 +120,8 @@ bool ModrinthCreationTask::updateInstance() setOverride(true); qDebug() << "Will override instance!"; + m_instance = inst; + // We let it go through the createInstance() stage, just with a couple modifications for updating return false; } @@ -189,7 +190,7 @@ bool ModrinthCreationTask::createInstance() instance.setIconKey("modrinth"); } - instance.setManagedPack("modrinth", getManagedPackID(), m_managed_name, m_managed_id, {}); + instance.setManagedPack("modrinth", getManagedPackID(), m_managed_name, m_managed_version_id, version()); instance.setName(name()); instance.saveNow(); @@ -229,10 +230,17 @@ bool ModrinthCreationTask::createInstance() loop.exec(); + if (m_instance) { + auto inst = m_instance.value(); + + inst->copyManagedPack(instance); + inst->setName(instance.name()); + } + return ended_well; } -bool ModrinthCreationTask::parseManifest(QString index_path, std::vector& files) +bool ModrinthCreationTask::parseManifest(QString index_path, std::vector& files, bool set_managed_info) { try { auto doc = Json::requireDocument(index_path); @@ -244,8 +252,10 @@ bool ModrinthCreationTask::parseManifest(QString index_path, std::vector(obj, "files", "modrinth.index.json"); bool had_optional = false; @@ -348,7 +358,7 @@ QString ModrinthCreationTask::getManagedPackID() const { if (!m_source_url.isEmpty()) { QRegularExpression regex(R"(data\/(.*)\/versions)"); - return regex.match(m_source_url).captured(0); + return regex.match(m_source_url).captured(1); } return {}; diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h index 4e804e58..bcf80682 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h @@ -2,6 +2,10 @@ #include "InstanceCreationTask.h" +#include + +#include "minecraft/MinecraftInstance.h" + #include "modplatform/modrinth/ModrinthPackManifest.h" #include "net/NetJob.h" @@ -11,7 +15,7 @@ class ModrinthCreationTask final : public InstanceCreationTask { public: ModrinthCreationTask(QString staging_path, SettingsObjectPtr global_settings, QWidget* parent, QString source_url = {}) - : InstanceCreationTask(), m_parent(parent) + : InstanceCreationTask(), m_parent(parent), m_source_url(std::move(source_url)) { setStagingPath(staging_path); setParentSettings(global_settings); @@ -24,7 +28,7 @@ class ModrinthCreationTask final : public InstanceCreationTask { bool createInstance() override; private: - bool parseManifest(QString, std::vector&); + bool parseManifest(QString, std::vector&, bool set_managed_info = true); QString getManagedPackID() const; private: @@ -36,4 +40,6 @@ class ModrinthCreationTask final : public InstanceCreationTask { std::vector m_files; NetJob::Ptr m_files_job; + + std::optional m_instance; }; From 8c0816c1669c6acedd798b4d0b49c7a567cdcf1a Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 31 Jul 2022 16:45:01 -0300 Subject: [PATCH 39/58] feat: add override awareness to CF modpack updating Signed-off-by: flow --- .../flame/FlameInstanceCreationTask.cpp | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 202caa28..4d70e223 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -11,14 +11,13 @@ #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" +#include "modplatform/helpers/OverrideUtils.h" + #include "settings/INISettingsObject.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/BlockedModsDialog.h" -// NOTE: Because of CF's ToS, I don't know if it counts as caching data, so it'll be disabled for now -#define DO_DIFF_UPDATE 0 - const static QMap forgemap = { { "1.2.5", "3.4.9.171" }, { "1.4.2", "6.0.1.355" }, { "1.4.7", "6.6.2.534" }, @@ -77,7 +76,9 @@ bool FlameCreationTask::updateInstance() QDir old_inst_dir(inst->instanceRoot()); - QString old_index_path(FS::PathCombine(old_inst_dir.absolutePath(), "flame", "manifest.json")); + QString old_index_folder(FS::PathCombine(old_inst_dir.absolutePath(), "flame")); + QString old_index_path(FS::PathCombine(old_index_folder, "manifest.json")); + QFileInfo old_index_file(old_index_path); if (old_index_file.exists()) { Flame::Manifest old_pack; @@ -85,11 +86,9 @@ bool FlameCreationTask::updateInstance() auto& old_files = old_pack.files; -#if DO_DIFF_UPDATE - // Remove repeated files, we don't need to download them! auto& files = m_pack.files; - // Let's remove all duplicated, identical resources! + // Remove repeated files, we don't need to download them! auto files_iterator = files.begin(); while (files_iterator != files.end()) { auto const& file = files_iterator; @@ -107,7 +106,18 @@ bool FlameCreationTask::updateInstance() files_iterator++; } -#endif + + QString old_minecraft_dir(inst->gameRoot()); + + // We will remove all the previous overrides, to prevent duplicate files! + // TODO: Currently 'overrides' will always override the stuff on update. How do we preserve unchanged overrides? + // FIXME: We may want to do something about disabled mods. + auto old_overrides = Override::readOverrides("overrides", old_index_folder); + for (auto entry : old_overrides) { + qDebug() << "Removing" << entry; + old_minecraft_dir.remove(entry); + } + // Remove remaining old files (we need to do an API request to know which ids are which files...) QStringList fileIds; @@ -120,7 +130,7 @@ bool FlameCreationTask::updateInstance() QEventLoop loop; - connect(job, &NetJob::succeeded, this, [raw_response, fileIds, old_inst_dir, &old_files] { + connect(job, &NetJob::succeeded, this, [raw_response, fileIds, old_inst_dir, &old_files, old_minecraft_dir] { // Parse the API response QJsonParseError parse_error{}; auto doc = QJsonDocument::fromJson(*raw_response, &parse_error); @@ -158,7 +168,7 @@ bool FlameCreationTask::updateInstance() continue; qDebug() << "Removing" << file.fileName << "at" << file.targetFolder; - QString path(FS::PathCombine(old_inst_dir.absolutePath(), "minecraft", file.targetFolder, file.fileName)); + QString path(FS::PathCombine(old_minecraft_dir, file.targetFolder, file.fileName)); if (!QFile::remove(path)) qDebug() << "Failed to remove file at" << path; } @@ -172,7 +182,6 @@ bool FlameCreationTask::updateInstance() m_process_update_file_info_job = nullptr; } - // TODO: Currently 'overrides' will always override the stuff on update. How do we preserve unchanged overrides? setOverride(true); qDebug() << "Will override instance!"; @@ -185,13 +194,15 @@ bool FlameCreationTask::createInstance() { QEventLoop loop; + QString parent_folder(FS::PathCombine(m_stagingPath, "flame")); + try { QString index_path(FS::PathCombine(m_stagingPath, "manifest.json")); if (!m_pack.is_loaded) Flame::loadManifest(m_pack, index_path); // Keep index file in case we need it some other time (like when changing versions) - QString new_index_place(FS::PathCombine(m_stagingPath, "flame", "manifest.json")); + QString new_index_place(FS::PathCombine(parent_folder, "manifest.json")); FS::ensureFilePathExists(new_index_place); QFile::rename(index_path, new_index_place); @@ -203,6 +214,9 @@ bool FlameCreationTask::createInstance() if (!m_pack.overrides.isEmpty()) { QString overridePath = FS::PathCombine(m_stagingPath, m_pack.overrides); if (QFile::exists(overridePath)) { + // Create a list of overrides in "overrides.txt" inside flame/ + Override::createOverrides("overrides", parent_folder, overridePath); + QString mcPath = FS::PathCombine(m_stagingPath, "minecraft"); if (!QFile::rename(overridePath, mcPath)) { setError(tr("Could not rename the overrides folder:\n") + m_pack.overrides); @@ -338,6 +352,7 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) } else { m_mod_id_resolver.reset(); setError("Canceled"); + loop.quit(); } } else { setupDownloadJob(loop); From 4b0ceea8941826134c949b1c2fb80e05c174e5ec Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 31 Jul 2022 19:56:14 -0300 Subject: [PATCH 40/58] fix: correctly set managed pack fields in CF pack Signed-off-by: flow --- launcher/modplatform/flame/FlameInstanceCreationTask.cpp | 9 +++++++++ launcher/modplatform/flame/FlameInstanceCreationTask.h | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 4d70e223..76ac11af 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -186,6 +186,8 @@ bool FlameCreationTask::updateInstance() setOverride(true); qDebug() << "Will override instance!"; + m_instance = inst; + // We let it go through the createInstance() stage, just with a couple modifications for updating return false; } @@ -319,6 +321,13 @@ bool FlameCreationTask::createInstance() loop.exec(); + if (m_instance) { + auto inst = m_instance.value(); + + inst->copyManagedPack(instance); + inst->setName(instance.name()); + } + return getError().isEmpty(); } diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.h b/launcher/modplatform/flame/FlameInstanceCreationTask.h index 99822d93..ccb5f827 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.h +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.h @@ -2,6 +2,10 @@ #include "InstanceCreationTask.h" +#include + +#include "minecraft/MinecraftInstance.h" + #include "modplatform/flame/FileResolvingTask.h" #include "net/NetJob.h" @@ -35,4 +39,6 @@ class FlameCreationTask final : public InstanceCreationTask { // Handle to allow aborting NetJob* m_process_update_file_info_job = nullptr; NetJob::Ptr m_files_job = nullptr; + + std::optional m_instance; }; From 654157096985d0cec0d3b7bb53f39a5c0157206e Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 28 Jul 2022 15:58:04 -0300 Subject: [PATCH 41/58] fix: simplify abort handling and add missing emits Signed-off-by: flow --- launcher/InstanceImportTask.cpp | 11 ++++++++--- launcher/InstanceImportTask.h | 1 + launcher/InstanceList.cpp | 17 ++++++++++------- .../atlauncher/ATLPackInstallTask.cpp | 18 ++++++++++++++++++ .../atlauncher/ATLPackInstallTask.h | 1 + .../modplatform/legacy_ftb/PackFetchTask.cpp | 14 ++++++++++++++ .../modplatform/legacy_ftb/PackFetchTask.h | 2 ++ .../modplatform/legacy_ftb/PackInstallTask.cpp | 6 ++++++ .../modplatform/legacy_ftb/PackInstallTask.h | 1 + .../modpacksch/FTBPackInstallTask.cpp | 3 +-- .../technic/SolderPackInstallTask.cpp | 8 ++++++++ .../technic/SolderPackInstallTask.h | 1 + launcher/ui/MainWindow.cpp | 4 ++++ launcher/ui/dialogs/ProgressDialog.cpp | 5 ++--- .../ui/pages/modplatform/legacy_ftb/Page.cpp | 8 +++++++- .../ui/pages/modplatform/legacy_ftb/Page.h | 1 + 16 files changed, 85 insertions(+), 16 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 09e65064..56aabb2d 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -91,6 +91,7 @@ void InstanceImportTask::executeTask() connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded); connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged); connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed); + connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted); m_filesNetJob->start(); } @@ -113,6 +114,12 @@ void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total) setProgress(current, total); } +void InstanceImportTask::downloadAborted() +{ + emitAborted(); + m_filesNetJob.reset(); +} + void InstanceImportTask::processZipPack() { setStatus(tr("Extracting modpack")); @@ -246,9 +253,7 @@ void InstanceImportTask::extractFinished() void InstanceImportTask::extractAborted() { - emitFailed(tr("Instance import has been aborted.")); - emit aborted(); - return; + emitAborted(); } void InstanceImportTask::processFlame() diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h index 48ba2161..acb1f9d6 100644 --- a/launcher/InstanceImportTask.h +++ b/launcher/InstanceImportTask.h @@ -80,6 +80,7 @@ private slots: void downloadSucceeded(); void downloadFailed(QString reason); void downloadProgressChanged(qint64 current, qint64 total); + void downloadAborted(); void extractFinished(); void extractAborted(); diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 0e59150e..5f604f9a 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -784,6 +784,7 @@ class InstanceStaging : public Task { m_child.reset(child); connect(child, &Task::succeeded, this, &InstanceStaging::childSucceded); connect(child, &Task::failed, this, &InstanceStaging::childFailed); + connect(child, &Task::aborted, this, &InstanceStaging::childAborted); connect(child, &Task::status, this, &InstanceStaging::setStatus); connect(child, &Task::progress, this, &InstanceStaging::setProgress); connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded); @@ -794,17 +795,14 @@ class InstanceStaging : public Task { // FIXME/TODO: add ability to abort during instance commit retries bool abort() override { - if (m_child && m_child->canAbort()) { + if (m_child && m_child->canAbort()) return m_child->abort(); - } + return false; } bool canAbort() const override { - if (m_child && m_child->canAbort()) { - return true; - } - return false; + return (m_child && m_child->canAbort()); } protected: @@ -834,7 +832,12 @@ class InstanceStaging : public Task { emitFailed(reason); } - private: + void childAborted() + { + emitAborted(); + } + +private: InstanceList * m_parent; /* * WHY: the whole reason why this uses an exponential backoff retry scheme is antivirus on Windows. diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 13cab256..a553eafd 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -90,6 +90,7 @@ void PackInstallTask::executeTask() QObject::connect(netJob, &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded); QObject::connect(netJob, &NetJob::failed, this, &PackInstallTask::onDownloadFailed); + QObject::connect(netJob, &NetJob::aborted, this, &PackInstallTask::onDownloadAborted); } void PackInstallTask::onDownloadSucceeded() @@ -169,6 +170,12 @@ void PackInstallTask::onDownloadFailed(QString reason) emitFailed(reason); } +void PackInstallTask::onDownloadAborted() +{ + jobPtr.reset(); + emitAborted(); +} + void PackInstallTask::deleteExistingFiles() { setStatus(tr("Deleting existing files...")); @@ -675,6 +682,11 @@ void PackInstallTask::installConfigs() abortable = true; setProgress(current, total); }); + connect(jobPtr.get(), &NetJob::aborted, [&]{ + abortable = false; + jobPtr.reset(); + emitAborted(); + }); jobPtr->start(); } @@ -831,6 +843,12 @@ void PackInstallTask::downloadMods() abortable = true; setProgress(current, total); }); + connect(jobPtr.get(), &NetJob::aborted, [&] + { + abortable = false; + jobPtr.reset(); + emitAborted(); + }); jobPtr->start(); } diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.h b/launcher/modplatform/atlauncher/ATLPackInstallTask.h index a7124d59..ed4436f0 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.h +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.h @@ -93,6 +93,7 @@ protected: private slots: void onDownloadSucceeded(); void onDownloadFailed(QString reason); + void onDownloadAborted(); void onModsDownloaded(); void onModsExtracted(); diff --git a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp index 4da6a866..36aa60c7 100644 --- a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp @@ -59,6 +59,7 @@ void PackFetchTask::fetch() QObject::connect(jobPtr.get(), &NetJob::succeeded, this, &PackFetchTask::fileDownloadFinished); QObject::connect(jobPtr.get(), &NetJob::failed, this, &PackFetchTask::fileDownloadFailed); + QObject::connect(jobPtr.get(), &NetJob::aborted, this, &PackFetchTask::fileDownloadAborted); jobPtr->start(); } @@ -98,6 +99,14 @@ void PackFetchTask::fetchPrivate(const QStringList & toFetch) delete data; }); + QObject::connect(job, &NetJob::aborted, this, [this, job, data]{ + emit aborted(); + job->deleteLater(); + + data->clear(); + delete data; + }); + job->start(); } } @@ -204,4 +213,9 @@ void PackFetchTask::fileDownloadFailed(QString reason) emit failed(reason); } +void PackFetchTask::fileDownloadAborted() +{ + emit aborted(); +} + } diff --git a/launcher/modplatform/legacy_ftb/PackFetchTask.h b/launcher/modplatform/legacy_ftb/PackFetchTask.h index f1667e90..8f3c4f3b 100644 --- a/launcher/modplatform/legacy_ftb/PackFetchTask.h +++ b/launcher/modplatform/legacy_ftb/PackFetchTask.h @@ -33,10 +33,12 @@ private: protected slots: void fileDownloadFinished(); void fileDownloadFailed(QString reason); + void fileDownloadAborted(); signals: void finished(ModpackList publicPacks, ModpackList thirdPartyPacks); void failed(QString reason); + void aborted(); void privateFileDownloadFinished(Modpack modpack); void privateFileDownloadFailed(QString reason, QString packCode); diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp index e2e61a7c..209ad884 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp @@ -86,6 +86,7 @@ void PackInstallTask::downloadPack() connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded); connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed); connect(netJobContainer.get(), &NetJob::progress, this, &PackInstallTask::onDownloadProgress); + connect(netJobContainer.get(), &NetJob::aborted, this, &PackInstallTask::onDownloadAborted); netJobContainer->start(); progress(1, 4); @@ -110,6 +111,11 @@ void PackInstallTask::onDownloadProgress(qint64 current, qint64 total) setStatus(tr("Downloading zip for %1 (%2%)").arg(m_pack.name).arg(current / 10)); } +void PackInstallTask::onDownloadAborted() +{ + emitAborted(); +} + void PackInstallTask::unzip() { progress(2, 4); diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.h b/launcher/modplatform/legacy_ftb/PackInstallTask.h index da4c0da5..da791e06 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.h +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.h @@ -38,6 +38,7 @@ private slots: void onDownloadSucceeded(); void onDownloadFailed(QString reason); void onDownloadProgress(qint64 current, qint64 total); + void onDownloadAborted(); void onUnzipFinished(); void onUnzipCanceled(); diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp index f17a311a..97ce1dc6 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp @@ -65,9 +65,8 @@ bool PackInstallTask::abort() if (m_mod_id_resolver_task) aborted &= m_mod_id_resolver_task->abort(); - // FIXME: This should be 'emitAborted()', but InstanceStaging doesn't connect to the abort signal yet... if (aborted) - emitFailed(tr("Aborted")); + emitAborted(); return aborted; } diff --git a/launcher/modplatform/technic/SolderPackInstallTask.cpp b/launcher/modplatform/technic/SolderPackInstallTask.cpp index 65b6ca51..19731b38 100644 --- a/launcher/modplatform/technic/SolderPackInstallTask.cpp +++ b/launcher/modplatform/technic/SolderPackInstallTask.cpp @@ -77,6 +77,7 @@ void Technic::SolderPackInstallTask::executeTask() auto job = m_filesNetJob.get(); connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::fileListSucceeded); connect(job, &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed); + connect(job, &NetJob::aborted, this, &Technic::SolderPackInstallTask::downloadAborted); m_filesNetJob->start(); } @@ -127,6 +128,7 @@ void Technic::SolderPackInstallTask::fileListSucceeded() connect(m_filesNetJob.get(), &NetJob::succeeded, this, &Technic::SolderPackInstallTask::downloadSucceeded); connect(m_filesNetJob.get(), &NetJob::progress, this, &Technic::SolderPackInstallTask::downloadProgressChanged); connect(m_filesNetJob.get(), &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed); + connect(m_filesNetJob.get(), &NetJob::aborted, this, &Technic::SolderPackInstallTask::downloadAborted); m_filesNetJob->start(); } @@ -171,6 +173,12 @@ void Technic::SolderPackInstallTask::downloadProgressChanged(qint64 current, qin setProgress(current / 2, total); } +void Technic::SolderPackInstallTask::downloadAborted() +{ + emitAborted(); + m_filesNetJob.reset(); +} + void Technic::SolderPackInstallTask::extractFinished() { if (!m_extractFuture.result()) diff --git a/launcher/modplatform/technic/SolderPackInstallTask.h b/launcher/modplatform/technic/SolderPackInstallTask.h index 117a7bd6..aa14ce88 100644 --- a/launcher/modplatform/technic/SolderPackInstallTask.h +++ b/launcher/modplatform/technic/SolderPackInstallTask.h @@ -61,6 +61,7 @@ namespace Technic void downloadSucceeded(); void downloadFailed(QString reason); void downloadProgressChanged(qint64 current, qint64 total); + void downloadAborted(); void extractFinished(); void extractAborted(); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 58b1ae80..5729b44d 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1656,6 +1656,10 @@ void MainWindow::runModalTask(Task *task) CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); } }); + connect(task, &Task::aborted, [this] + { + CustomMessageBox::selectable(this, tr("Task aborted"), tr("The task has been aborted by the user."), QMessageBox::Information)->show(); + }); ProgressDialog loadDialog(this); loadDialog.setSkipButton(true, tr("Abort")); loadDialog.execWithTask(task); diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index 3c7f53d3..6f9cc8e0 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -43,8 +43,7 @@ void ProgressDialog::setSkipButton(bool present, QString label) void ProgressDialog::on_skipButton_clicked(bool checked) { Q_UNUSED(checked); - if (task->abort()) - QDialog::reject(); + task->abort(); } ProgressDialog::~ProgressDialog() @@ -81,7 +80,7 @@ int ProgressDialog::execWithTask(Task* task) connect(task, &Task::stepStatus, this, &ProgressDialog::changeStatus); connect(task, &Task::progress, this, &ProgressDialog::changeProgress); - connect(task, &Task::aborted, [this] { onTaskFailed(tr("Aborted by user")); }); + connect(task, &Task::aborted, [this] { QDialog::reject(); }); m_is_multi_step = task->isMultiStep(); if (!m_is_multi_step) { diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp index 0208b36b..98ab8799 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp @@ -146,6 +146,7 @@ void Page::openedImpl() { connect(ftbFetchTask.get(), &PackFetchTask::finished, this, &Page::ftbPackDataDownloadSuccessfully); connect(ftbFetchTask.get(), &PackFetchTask::failed, this, &Page::ftbPackDataDownloadFailed); + connect(ftbFetchTask.get(), &PackFetchTask::aborted, this, &Page::ftbPackDataDownloadAborted); connect(ftbFetchTask.get(), &PackFetchTask::privateFileDownloadFinished, this, &Page::ftbPrivatePackDataDownloadSuccessfully); connect(ftbFetchTask.get(), &PackFetchTask::privateFileDownloadFailed, this, &Page::ftbPrivatePackDataDownloadFailed); @@ -220,7 +221,12 @@ void Page::ftbPackDataDownloadSuccessfully(ModpackList publicPacks, ModpackList void Page::ftbPackDataDownloadFailed(QString reason) { - //TODO: Display the error + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); +} + +void Page::ftbPackDataDownloadAborted() +{ + CustomMessageBox::selectable(this, tr("Task aborted"), tr("The task has been aborted by the user."), QMessageBox::Information)->show(); } void Page::ftbPrivatePackDataDownloadSuccessfully(Modpack pack) diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.h b/launcher/ui/pages/modplatform/legacy_ftb/Page.h index 52db7d91..1de8b40a 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.h +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.h @@ -95,6 +95,7 @@ private: private slots: void ftbPackDataDownloadSuccessfully(ModpackList publicPacks, ModpackList thirdPartyPacks); void ftbPackDataDownloadFailed(QString reason); + void ftbPackDataDownloadAborted(); void ftbPrivatePackDataDownloadSuccessfully(Modpack pack); void ftbPrivatePackDataDownloadFailed(QString reason, QString packCode); From 6a50fa35ec0dcb8b91ffc0ce70d4d8b9ee5d9fb8 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 31 Jul 2022 17:56:51 -0300 Subject: [PATCH 42/58] feat: add canAbort() status change in Task By now, it's a recurring pattern of wanting to restrict aborting in certain situations. This avoids further code duplication, and adds a signal that external users can hook up to to respond to such change. Signed-off-by: flow --- launcher/tasks/Task.h | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index 2baf0188..f2872643 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -68,7 +68,7 @@ class Task : public QObject, public QRunnable { virtual QStringList warnings() const; - virtual bool canAbort() const { return false; } + virtual bool canAbort() const { return m_can_abort; } auto getState() const -> State { return m_state; } @@ -96,6 +96,10 @@ class Task : public QObject, public QRunnable { void status(QString status); void stepStatus(QString status); + /** Emitted when the canAbort() status has changed. + */ + void abortStatusChanged(bool can_abort); + public slots: // QRunnable's interface void run() override { start(); } @@ -103,6 +107,8 @@ class Task : public QObject, public QRunnable { virtual void start(); virtual bool abort() { if(canAbort()) emitAborted(); return canAbort(); }; + void setAbortStatus(bool can_abort) { m_can_abort = can_abort; emit abortStatusChanged(can_abort); } + protected: virtual void executeTask() = 0; @@ -125,4 +131,8 @@ class Task : public QObject, public QRunnable { // TODO: Nuke in favor of QLoggingCategory bool m_show_debug = true; + + private: + // Change using setAbortStatus + bool m_can_abort = false; }; From 87002fb8f84b104a221de07ba99ba0eb5cc6bbb6 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 31 Jul 2022 18:21:59 -0300 Subject: [PATCH 43/58] fix: hook up setAbortStatus in instance import tasks Signed-off-by: flow --- launcher/InstanceCreationTask.cpp | 15 +++++++++++++-- launcher/InstanceImportTask.cpp | 9 ++++++++- launcher/InstanceImportTask.h | 1 - launcher/InstanceList.cpp | 1 + .../flame/FlameInstanceCreationTask.cpp | 5 ++++- .../modrinth/ModrinthInstanceCreationTask.cpp | 5 ++++- .../modrinth/ModrinthInstanceCreationTask.h | 1 - 7 files changed, 30 insertions(+), 7 deletions(-) diff --git a/launcher/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp index c8c91997..d7663c2d 100644 --- a/launcher/InstanceCreationTask.cpp +++ b/launcher/InstanceCreationTask.cpp @@ -2,11 +2,22 @@ #include -InstanceCreationTask::InstanceCreationTask() {} +InstanceCreationTask::InstanceCreationTask() = default; void InstanceCreationTask::executeTask() { - if (updateInstance() || createInstance()) { + if (updateInstance()) { + emitSucceeded(); + return; + } + + // If this is set, it means we're updating an instance. Since the previous step likely + // removed some old files, we'd better not let the user abort the next task, since it'd + // put the instance in an invalid state. + // TODO: Figure out an unexpensive way of making such file removal a recoverable transaction. + setAbortStatus(!shouldOverride()); + + if (createInstance()) { emitSucceeded(); return; } diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 56aabb2d..4819a6ff 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -63,15 +63,20 @@ InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent) bool InstanceImportTask::abort() { + if (!canAbort()) + return false; + if (m_filesNetJob) m_filesNetJob->abort(); m_extractFuture.cancel(); - return false; + return Task::abort(); } void InstanceImportTask::executeTask() { + setAbortStatus(true); + if (m_sourceUrl.isLocalFile()) { m_archivePath = m_sourceUrl.toLocalFile(); processZipPack(); @@ -274,6 +279,7 @@ void InstanceImportTask::processFlame() connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater); connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort); + connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortStatus); inst_creation_task->start(); } @@ -336,6 +342,7 @@ void InstanceImportTask::processModrinth() connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater); connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort); + connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortStatus); inst_creation_task->start(); } diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h index acb1f9d6..ef70c819 100644 --- a/launcher/InstanceImportTask.h +++ b/launcher/InstanceImportTask.h @@ -58,7 +58,6 @@ class InstanceImportTask : public InstanceTask public: explicit InstanceImportTask(const QUrl sourceUrl, QWidget* parent = nullptr); - bool canAbort() const override { return true; } bool abort() override; const QVector &getBlockedFiles() const { diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 5f604f9a..bf25d2d0 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -785,6 +785,7 @@ class InstanceStaging : public Task { connect(child, &Task::succeeded, this, &InstanceStaging::childSucceded); connect(child, &Task::failed, this, &InstanceStaging::childFailed); connect(child, &Task::aborted, this, &InstanceStaging::childAborted); + connect(child, &Task::abortStatusChanged, this, &InstanceStaging::setAbortStatus); connect(child, &Task::status, this, &InstanceStaging::setStatus); connect(child, &Task::progress, this, &InstanceStaging::setProgress); connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded); diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 76ac11af..c8b2e297 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -27,6 +27,9 @@ static const FlameAPI api; bool FlameCreationTask::abort() { + if (!canAbort()) + return false; + if (m_process_update_file_info_job) m_process_update_file_info_job->abort(); if (m_files_job) @@ -34,7 +37,7 @@ bool FlameCreationTask::abort() if (m_mod_id_resolver) m_mod_id_resolver->abort(); - return true; + return Task::abort(); } bool FlameCreationTask::updateInstance() diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index b5140f34..3234d92b 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -19,9 +19,12 @@ bool ModrinthCreationTask::abort() { + if (!canAbort()) + return false; + if (m_files_job) return m_files_job->abort(); - return true; + return Task::abort(); } bool ModrinthCreationTask::updateInstance() diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h index bcf80682..e87e4fb9 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h @@ -22,7 +22,6 @@ class ModrinthCreationTask final : public InstanceCreationTask { } bool abort() override; - bool canAbort() const override { return true; } bool updateInstance() override; bool createInstance() override; From 68facd6b93d1a08a7c2417aa0d0ec614b0feecbe Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 31 Jul 2022 18:28:56 -0300 Subject: [PATCH 44/58] fix(ui): hook up abort status signal in ProgressDialog Now we have a visual indication on when tasks are abortable! Signed-off-by: flow --- launcher/ui/dialogs/ProgressDialog.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index 6f9cc8e0..258a32e4 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -81,6 +81,7 @@ int ProgressDialog::execWithTask(Task* task) connect(task, &Task::progress, this, &ProgressDialog::changeProgress); connect(task, &Task::aborted, [this] { QDialog::reject(); }); + connect(task, &Task::abortStatusChanged, ui->skipButton, &QPushButton::setEnabled); m_is_multi_step = task->isMultiStep(); if (!m_is_multi_step) { From eda6cf11ef53c11ed2691399ec1adbc83ca0a4d6 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 31 Jul 2022 20:29:12 -0300 Subject: [PATCH 45/58] feat(ui): improve info dialog before updating an instance Adds a 'Cancel' option, and add a note about doing a backup before updating. Signed-off-by: flow --- launcher/InstanceCreationTask.cpp | 6 ++++ launcher/InstanceCreationTask.h | 3 ++ launcher/InstanceImportTask.cpp | 2 ++ .../flame/FlameInstanceCreationTask.cpp | 21 +++++++---- .../modrinth/ModrinthInstanceCreationTask.cpp | 36 ++++++++++++------- .../modrinth/ModrinthInstanceCreationTask.h | 2 +- 6 files changed, 50 insertions(+), 20 deletions(-) diff --git a/launcher/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp index d7663c2d..3908bb9a 100644 --- a/launcher/InstanceCreationTask.cpp +++ b/launcher/InstanceCreationTask.cpp @@ -11,6 +11,12 @@ void InstanceCreationTask::executeTask() return; } + // When the user aborted in the update stage. + if (m_abort) { + emitAborted(); + return; + } + // If this is set, it means we're updating an instance. Since the previous step likely // removed some old files, we'd better not let the user abort the next task, since it'd // put the instance in an invalid state. diff --git a/launcher/InstanceCreationTask.h b/launcher/InstanceCreationTask.h index 68c5de59..9b51c448 100644 --- a/launcher/InstanceCreationTask.h +++ b/launcher/InstanceCreationTask.h @@ -36,6 +36,9 @@ class InstanceCreationTask : public InstanceTask { protected: void setError(QString message) { m_error_message = message; }; + protected: + bool m_abort = false; + private: QString m_error_message; }; diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 4819a6ff..e35913da 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -279,6 +279,7 @@ void InstanceImportTask::processFlame() connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater); connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort); + connect(inst_creation_task, &Task::aborted, this, &Task::abort); connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortStatus); inst_creation_task->start(); @@ -342,6 +343,7 @@ void InstanceImportTask::processModrinth() connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater); connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort); + connect(inst_creation_task, &Task::aborted, this, &Task::abort); connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortStatus); inst_creation_task->start(); diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index c8b2e297..22ee0998 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -66,17 +66,26 @@ bool FlameCreationTask::updateInstance() auto version_id = inst->getManagedPackVersionName(); auto version_str = !version_id.isEmpty() ? tr(" (version %1)").arg(version_id) : ""; - auto info = CustomMessageBox::selectable(m_parent, tr("Similar modpack was found!"), - tr("One or more of your instances are from this same modpack%1. Do you want to create a " - "separate instance, or update the existing one?") - .arg(version_str), - QMessageBox::Information, QMessageBox::Ok | QMessageBox::Abort); + auto info = CustomMessageBox::selectable( + m_parent, tr("Similar modpack was found!"), + tr("One or more of your instances are from this same modpack%1. Do you want to create a " + "separate instance, or update the existing one?\n\nNOTE: Make sure you made a backup of your important instance data before " + "updating, as worlds can be corrupted and some configuration may be lost (due to pack overrides).") + .arg(version_str), QMessageBox::Information, QMessageBox::Ok | QMessageBox::Reset | QMessageBox::Abort); info->setButtonText(QMessageBox::Ok, tr("Update existing instance")); info->setButtonText(QMessageBox::Abort, tr("Create new instance")); + info->setButtonText(QMessageBox::Reset, tr("Cancel")); - if (info->exec() && info->clickedButton() == info->button(QMessageBox::Abort)) + info->exec(); + + if (info->clickedButton() == info->button(QMessageBox::Abort)) return false; + if (info->clickedButton() == info->button(QMessageBox::Reset)) { + m_abort = true; + return false; + } + QDir old_inst_dir(inst->instanceRoot()); QString old_index_folder(FS::PathCombine(old_inst_dir.absolutePath(), "flame")); diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 3234d92b..449d7387 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -42,23 +42,33 @@ bool ModrinthCreationTask::updateInstance() } QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json"); - if (!parseManifest(index_path, m_files)) + if (!parseManifest(index_path, m_files, true, false)) return false; auto version_name = inst->getManagedPackVersionName(); auto version_str = !version_name.isEmpty() ? tr(" (version %1)").arg(version_name) : ""; - auto info = CustomMessageBox::selectable(m_parent, tr("Similar modpack was found!"), - tr("One or more of your instances are from this same modpack%1. Do you want to create a " - "separate instance, or update the existing one?") - .arg(version_str), - QMessageBox::Information, QMessageBox::Ok | QMessageBox::Abort); - info->setButtonText(QMessageBox::Ok, tr("Update existing instance")); - info->setButtonText(QMessageBox::Abort, tr("Create new instance")); + auto info = CustomMessageBox::selectable( + m_parent, tr("Similar modpack was found!"), + tr("One or more of your instances are from this same modpack%1. Do you want to create a " + "separate instance, or update the existing one?\n\nNOTE: Make sure you made a backup of your important instance data before " + "updating, as worlds can be corrupted and some configuration may be lost (due to pack overrides).") + .arg(version_str), + QMessageBox::Information, QMessageBox::Ok | QMessageBox::Reset | QMessageBox::Abort); + info->setButtonText(QMessageBox::Ok, tr("Create new instance")); + info->setButtonText(QMessageBox::Abort, tr("Update existing instance")); + info->setButtonText(QMessageBox::Reset, tr("Cancel")); - if (info->exec() && info->clickedButton() == info->button(QMessageBox::Abort)) + info->exec(); + + if (info->clickedButton() == info->button(QMessageBox::Ok)) return false; + if (info->clickedButton() == info->button(QMessageBox::Reset)) { + m_abort = true; + return false; + } + // Remove repeated files, we don't need to download them! QDir old_inst_dir(inst->instanceRoot()); @@ -68,7 +78,7 @@ bool ModrinthCreationTask::updateInstance() QFileInfo old_index_file(old_index_path); if (old_index_file.exists()) { std::vector old_files; - parseManifest(old_index_path, old_files, false); + parseManifest(old_index_path, old_files, false, false); // Let's remove all duplicated, identical resources! auto files_iterator = m_files.begin(); @@ -137,7 +147,7 @@ bool ModrinthCreationTask::createInstance() QString parent_folder(FS::PathCombine(m_stagingPath, "mrpack")); QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json"); - if (m_files.empty() && !parseManifest(index_path, m_files)) + if (m_files.empty() && !parseManifest(index_path, m_files, true, true)) return false; // Keep index file in case we need it some other time (like when changing versions) @@ -243,7 +253,7 @@ bool ModrinthCreationTask::createInstance() return ended_well; } -bool ModrinthCreationTask::parseManifest(QString index_path, std::vector& files, bool set_managed_info) +bool ModrinthCreationTask::parseManifest(QString index_path, std::vector& files, bool set_managed_info, bool show_optional_dialog) { try { auto doc = Json::requireDocument(index_path); @@ -274,7 +284,7 @@ bool ModrinthCreationTask::parseManifest(QString index_path, std::vector&, bool set_managed_info = true); + bool parseManifest(QString, std::vector&, bool set_managed_info = true, bool show_optional_dialog = true); QString getManagedPackID() const; private: From 2dd372600c5e744067fda51f8ffae16fb1caa16c Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 5 Aug 2022 21:25:21 -0300 Subject: [PATCH 46/58] fix: some abort-related issues Signed-off-by: flow --- launcher/InstanceCreationTask.cpp | 2 ++ launcher/InstanceList.cpp | 8 +++++--- launcher/modplatform/flame/FlameInstanceCreationTask.cpp | 1 + .../modplatform/modrinth/ModrinthInstanceCreationTask.cpp | 3 ++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/launcher/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp index 3908bb9a..a82bdfe8 100644 --- a/launcher/InstanceCreationTask.cpp +++ b/launcher/InstanceCreationTask.cpp @@ -6,6 +6,8 @@ InstanceCreationTask::InstanceCreationTask() = default; void InstanceCreationTask::executeTask() { + setAbortStatus(true); + if (updateInstance()) { emitSucceeded(); return; diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index bf25d2d0..a4b8d8aa 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -796,10 +796,12 @@ class InstanceStaging : public Task { // FIXME/TODO: add ability to abort during instance commit retries bool abort() override { - if (m_child && m_child->canAbort()) - return m_child->abort(); + if (!canAbort()) + return false; - return false; + m_child->abort(); + + return Task::abort(); } bool canAbort() const override { diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 22ee0998..5acb6300 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -30,6 +30,7 @@ bool FlameCreationTask::abort() if (!canAbort()) return false; + m_abort = true; if (m_process_update_file_info_job) m_process_update_file_info_job->abort(); if (m_files_job) diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 449d7387..a0c67876 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -22,8 +22,9 @@ bool ModrinthCreationTask::abort() if (!canAbort()) return false; + m_abort = true; if (m_files_job) - return m_files_job->abort(); + m_files_job->abort(); return Task::abort(); } From d2fdbec41dd664fb6cfc66ec6c79ee9f78eb8c7b Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 5 Aug 2022 21:26:02 -0300 Subject: [PATCH 47/58] fix: move file deletion to the end of the instance update This makes it harder for problems in the updating process to affect the current instance. Network issues, for instance, will no longer put the instance in an invalid state. Still, a possible improvement to this would be passing that logic to InstanceStaging instead, to be handled with the instance commiting directly. However, as it is now, the code would become very spaguetti-y, and given that the override operation in the commiting could also put the instance into an invalid state, it seems to me that, in order to fully error-proof this, we would need to do a copy operation on the whole instance, in order to modify the copy, and only in the end override everything an once with a rename. That also has the possibility of corrupting the instance if done without super care, however, so I think we may need to instead create an automatic backup system, with an undo command of sorts, or something like that. This doesn't seem very trivial though, so it'll probably need to wait until another PR. In the meantime, the user is advised to always backup their instances before doing this kind of action, as always. What a long commit message o.O Signed-off-by: flow --- launcher/InstanceCreationTask.cpp | 41 ++++++++++++++----- launcher/InstanceCreationTask.h | 2 + .../flame/FlameInstanceCreationTask.cpp | 25 ++++++----- .../modrinth/ModrinthInstanceCreationTask.cpp | 22 ++++++---- 4 files changed, 61 insertions(+), 29 deletions(-) diff --git a/launcher/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp index a82bdfe8..1b1a8c15 100644 --- a/launcher/InstanceCreationTask.cpp +++ b/launcher/InstanceCreationTask.cpp @@ -1,6 +1,7 @@ #include "InstanceCreationTask.h" #include +#include InstanceCreationTask::InstanceCreationTask() = default; @@ -19,19 +20,37 @@ void InstanceCreationTask::executeTask() return; } - // If this is set, it means we're updating an instance. Since the previous step likely - // removed some old files, we'd better not let the user abort the next task, since it'd - // put the instance in an invalid state. - // TODO: Figure out an unexpensive way of making such file removal a recoverable transaction. - setAbortStatus(!shouldOverride()); + if (!createInstance()) { + if (m_abort) + return; - if (createInstance()) { - emitSucceeded(); + qWarning() << "Instance creation failed!"; + if (!m_error_message.isEmpty()) + qWarning() << "Reason: " << m_error_message; + emitFailed(tr("Error while creating new instance.")); return; } - qWarning() << "Instance creation failed!"; - if (!m_error_message.isEmpty()) - qWarning() << "Reason: " << m_error_message; - emitFailed(tr("Error while creating new instance.")); + // If this is set, it means we're updating an instance. So, we now need to remove the + // files scheduled to, and we'd better not let the user abort in the middle of it, since it'd + // put the instance in an invalid state. + if (shouldOverride()) { + setAbortStatus(false); + setStatus(tr("Removing old conflicting files...")); + qDebug() << "Removing old files"; + + for (auto path : m_files_to_remove) { + if (!QFile::exists(path)) + continue; + qDebug() << "Removing" << path; + if (!QFile::remove(path)) { + qCritical() << "Couldn't remove the old conflicting files."; + emitFailed(tr("Failed to remove old conflicting files.")); + return; + } + } + } + + emitSucceeded(); + return; } diff --git a/launcher/InstanceCreationTask.h b/launcher/InstanceCreationTask.h index 9b51c448..03ee1a7a 100644 --- a/launcher/InstanceCreationTask.h +++ b/launcher/InstanceCreationTask.h @@ -39,6 +39,8 @@ class InstanceCreationTask : public InstanceTask { protected: bool m_abort = false; + QStringList m_files_to_remove; + private: QString m_error_message; }; diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 5acb6300..e3521a38 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -120,15 +120,17 @@ bool FlameCreationTask::updateInstance() files_iterator++; } - QString old_minecraft_dir(inst->gameRoot()); + QDir old_minecraft_dir(inst->gameRoot()); // We will remove all the previous overrides, to prevent duplicate files! // TODO: Currently 'overrides' will always override the stuff on update. How do we preserve unchanged overrides? // FIXME: We may want to do something about disabled mods. auto old_overrides = Override::readOverrides("overrides", old_index_folder); for (auto entry : old_overrides) { - qDebug() << "Removing" << entry; - old_minecraft_dir.remove(entry); + if (entry.isEmpty()) + continue; + qDebug() << "Scheduling" << entry << "for removal"; + m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(entry)); } // Remove remaining old files (we need to do an API request to know which ids are which files...) @@ -143,7 +145,7 @@ bool FlameCreationTask::updateInstance() QEventLoop loop; - connect(job, &NetJob::succeeded, this, [raw_response, fileIds, old_inst_dir, &old_files, old_minecraft_dir] { + connect(job, &NetJob::succeeded, this, [this, raw_response, fileIds, old_inst_dir, &old_files, old_minecraft_dir] { // Parse the API response QJsonParseError parse_error{}; auto doc = QJsonDocument::fromJson(*raw_response, &parse_error); @@ -180,10 +182,9 @@ bool FlameCreationTask::updateInstance() if (file.fileName.isEmpty() || file.targetFolder.isEmpty()) continue; - qDebug() << "Removing" << file.fileName << "at" << file.targetFolder; - QString path(FS::PathCombine(old_minecraft_dir, file.targetFolder, file.fileName)); - if (!QFile::remove(path)) - qDebug() << "Failed to remove file at" << path; + QString relative_path(FS::PathCombine(file.targetFolder, file.fileName)); + qDebug() << "Scheduling" << relative_path << "for removal"; + m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(relative_path)); } }); connect(job, &NetJob::finished, &loop, &QEventLoop::quit); @@ -334,14 +335,17 @@ bool FlameCreationTask::createInstance() loop.exec(); - if (m_instance) { + bool did_succeed = getError().isEmpty(); + + if (m_instance && did_succeed) { + setAbortStatus(false); auto inst = m_instance.value(); inst->copyManagedPack(instance); inst->setName(instance.name()); } - return getError().isEmpty(); + return did_succeed; } void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) @@ -421,7 +425,6 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop) m_mod_id_resolver.reset(); connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { m_files_job.reset(); - emitSucceeded(); }); connect(m_files_job.get(), &NetJob::failed, [&](QString reason) { m_files_job.reset(); diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index a0c67876..7d19639c 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -105,12 +105,15 @@ bool ModrinthCreationTask::updateInstance() } QDir old_minecraft_dir(inst->gameRoot()); + // Some files were removed from the old version, and some will be downloaded in an updated version, // so we're fine removing them! if (!old_files.empty()) { for (auto const& file : old_files) { - qDebug() << "Removing" << file.path; - old_minecraft_dir.remove(file.path); + if (file.path.isEmpty()) + continue; + qDebug() << "Scheduling" << file.path << "for removal"; + m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(file.path)); } } @@ -119,14 +122,18 @@ bool ModrinthCreationTask::updateInstance() // FIXME: We may want to do something about disabled mods. auto old_overrides = Override::readOverrides("overrides", old_index_folder); for (auto entry : old_overrides) { - qDebug() << "Removing" << entry; - old_minecraft_dir.remove(entry); + if (entry.isEmpty()) + continue; + qDebug() << "Scheduling" << entry << "for removal"; + m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(entry)); } auto old_client_overrides = Override::readOverrides("client-overrides", old_index_folder); for (auto entry : old_overrides) { - qDebug() << "Removing" << entry; - old_minecraft_dir.remove(entry); + if (entry.isEmpty()) + continue; + qDebug() << "Scheduling" << entry << "for removal"; + m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(entry)); } } @@ -244,7 +251,8 @@ bool ModrinthCreationTask::createInstance() loop.exec(); - if (m_instance) { + if (m_instance && ended_well) { + setAbortStatus(false); auto inst = m_instance.value(); inst->copyManagedPack(instance); From 9eb35ea7c8b30c4bfa02e2ba218671902d92ff21 Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 20 Aug 2022 11:33:34 -0300 Subject: [PATCH 48/58] fix: don't load specific settings for managed pack info This avoids loading all settings for all instances when searching for one with a specific managed pack name. Signed-off-by: flow --- launcher/BaseInstance.cpp | 48 +++++++++++++++++++-------------------- launcher/BaseInstance.h | 12 +++++----- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 3b261678..2995df6f 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -114,54 +114,54 @@ QString BaseInstance::getPostExitCommand() return settings()->get("PostExitCommand").toString(); } -bool BaseInstance::isManagedPack() +bool BaseInstance::isManagedPack() const { - return settings()->get("ManagedPack").toBool(); + return m_settings->get("ManagedPack").toBool(); } -QString BaseInstance::getManagedPackType() +QString BaseInstance::getManagedPackType() const { - return settings()->get("ManagedPackType").toString(); + return m_settings->get("ManagedPackType").toString(); } -QString BaseInstance::getManagedPackID() +QString BaseInstance::getManagedPackID() const { - return settings()->get("ManagedPackID").toString(); + return m_settings->get("ManagedPackID").toString(); } -QString BaseInstance::getManagedPackName() +QString BaseInstance::getManagedPackName() const { - return settings()->get("ManagedPackName").toString(); + return m_settings->get("ManagedPackName").toString(); } -QString BaseInstance::getManagedPackVersionID() +QString BaseInstance::getManagedPackVersionID() const { - return settings()->get("ManagedPackVersionID").toString(); + return m_settings->get("ManagedPackVersionID").toString(); } -QString BaseInstance::getManagedPackVersionName() +QString BaseInstance::getManagedPackVersionName() const { - return settings()->get("ManagedPackVersionName").toString(); + return m_settings->get("ManagedPackVersionName").toString(); } void BaseInstance::setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version) { - settings()->set("ManagedPack", true); - settings()->set("ManagedPackType", type); - settings()->set("ManagedPackID", id); - settings()->set("ManagedPackName", name); - settings()->set("ManagedPackVersionID", versionId); - settings()->set("ManagedPackVersionName", version); + m_settings->set("ManagedPack", true); + m_settings->set("ManagedPackType", type); + m_settings->set("ManagedPackID", id); + m_settings->set("ManagedPackName", name); + m_settings->set("ManagedPackVersionID", versionId); + m_settings->set("ManagedPackVersionName", version); } void BaseInstance::copyManagedPack(BaseInstance& other) { - settings()->set("ManagedPack", other.isManagedPack()); - settings()->set("ManagedPackType", other.getManagedPackType()); - settings()->set("ManagedPackID", other.getManagedPackID()); - settings()->set("ManagedPackName", other.getManagedPackName()); - settings()->set("ManagedPackVersionID", other.getManagedPackVersionID()); - settings()->set("ManagedPackVersionName", other.getManagedPackVersionName()); + m_settings->set("ManagedPack", other.isManagedPack()); + m_settings->set("ManagedPackType", other.getManagedPackType()); + m_settings->set("ManagedPackID", other.getManagedPackID()); + m_settings->set("ManagedPackName", other.getManagedPackName()); + m_settings->set("ManagedPackVersionID", other.getManagedPackVersionID()); + m_settings->set("ManagedPackVersionName", other.getManagedPackVersionName()); } int BaseInstance::getConsoleMaxLines() const diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 653d1378..21cc3413 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -140,12 +140,12 @@ public: QString getPostExitCommand(); QString getWrapperCommand(); - bool isManagedPack(); - QString getManagedPackType(); - QString getManagedPackID(); - QString getManagedPackName(); - QString getManagedPackVersionID(); - QString getManagedPackVersionName(); + bool isManagedPack() const; + QString getManagedPackType() const; + QString getManagedPackID() const; + QString getManagedPackName() const; + QString getManagedPackVersionID() const; + QString getManagedPackVersionName() const; void setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version); void copyManagedPack(BaseInstance& other); From be8c6f218cfe3acc31335305bb40284f8f6cb582 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 21 Aug 2022 09:26:27 -0300 Subject: [PATCH 49/58] refactor: setAbortStatus -> setAbortable Signed-off-by: flow --- launcher/InstanceCreationTask.cpp | 4 ++-- launcher/InstanceImportTask.cpp | 6 +++--- launcher/InstanceList.cpp | 2 +- launcher/modplatform/flame/FlameInstanceCreationTask.cpp | 2 +- .../modplatform/modrinth/ModrinthInstanceCreationTask.cpp | 2 +- launcher/tasks/Task.h | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/launcher/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp index 1b1a8c15..3971effa 100644 --- a/launcher/InstanceCreationTask.cpp +++ b/launcher/InstanceCreationTask.cpp @@ -7,7 +7,7 @@ InstanceCreationTask::InstanceCreationTask() = default; void InstanceCreationTask::executeTask() { - setAbortStatus(true); + setAbortable(true); if (updateInstance()) { emitSucceeded(); @@ -35,7 +35,7 @@ void InstanceCreationTask::executeTask() // files scheduled to, and we'd better not let the user abort in the middle of it, since it'd // put the instance in an invalid state. if (shouldOverride()) { - setAbortStatus(false); + setAbortable(false); setStatus(tr("Removing old conflicting files...")); qDebug() << "Removing old files"; diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index e35913da..b490620d 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -75,7 +75,7 @@ bool InstanceImportTask::abort() void InstanceImportTask::executeTask() { - setAbortStatus(true); + setAbortable(true); if (m_sourceUrl.isLocalFile()) { m_archivePath = m_sourceUrl.toLocalFile(); @@ -280,7 +280,7 @@ void InstanceImportTask::processFlame() connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort); connect(inst_creation_task, &Task::aborted, this, &Task::abort); - connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortStatus); + connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortable); inst_creation_task->start(); } @@ -344,7 +344,7 @@ void InstanceImportTask::processModrinth() connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort); connect(inst_creation_task, &Task::aborted, this, &Task::abort); - connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortStatus); + connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortable); inst_creation_task->start(); } diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index a4b8d8aa..a414e0d5 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -785,7 +785,7 @@ class InstanceStaging : public Task { connect(child, &Task::succeeded, this, &InstanceStaging::childSucceded); connect(child, &Task::failed, this, &InstanceStaging::childFailed); connect(child, &Task::aborted, this, &InstanceStaging::childAborted); - connect(child, &Task::abortStatusChanged, this, &InstanceStaging::setAbortStatus); + connect(child, &Task::abortStatusChanged, this, &InstanceStaging::setAbortable); connect(child, &Task::status, this, &InstanceStaging::setStatus); connect(child, &Task::progress, this, &InstanceStaging::setProgress); connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded); diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index e3521a38..a0eda1b0 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -338,7 +338,7 @@ bool FlameCreationTask::createInstance() bool did_succeed = getError().isEmpty(); if (m_instance && did_succeed) { - setAbortStatus(false); + setAbortable(false); auto inst = m_instance.value(); inst->copyManagedPack(instance); diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 7d19639c..d4c37cb9 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -252,7 +252,7 @@ bool ModrinthCreationTask::createInstance() loop.exec(); if (m_instance && ended_well) { - setAbortStatus(false); + setAbortable(false); auto inst = m_instance.value(); inst->copyManagedPack(instance); diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index f2872643..3d607dca 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -107,7 +107,7 @@ class Task : public QObject, public QRunnable { virtual void start(); virtual bool abort() { if(canAbort()) emitAborted(); return canAbort(); }; - void setAbortStatus(bool can_abort) { m_can_abort = can_abort; emit abortStatusChanged(can_abort); } + void setAbortable(bool can_abort) { m_can_abort = can_abort; emit abortStatusChanged(can_abort); } protected: virtual void executeTask() = 0; From ddde885084a8eba61e691974edbc75186438ed55 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 21 Aug 2022 10:00:23 -0300 Subject: [PATCH 50/58] fix: show warning in case there's no old index when updating Signed-off-by: flow --- .../modplatform/flame/FlameInstanceCreationTask.cpp | 11 +++++++++++ .../modrinth/ModrinthInstanceCreationTask.cpp | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index a0eda1b0..1b282770 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -195,6 +195,17 @@ bool FlameCreationTask::updateInstance() loop.exec(); m_process_update_file_info_job = nullptr; + } else { + // We don't have an old index file, so we may duplicate stuff! + auto dialog = CustomMessageBox::selectable(m_parent, + tr("No index file."), + tr("We couldn't find a suitable index file for the older version. This may cause some of the files to be duplicated. Do you want to continue?"), + QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel); + + if (dialog->exec() == QDialog::DialogCode::Rejected) { + m_abort = true; + return false; + } } setOverride(true); diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index d4c37cb9..c1898dd9 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -135,6 +135,17 @@ bool ModrinthCreationTask::updateInstance() qDebug() << "Scheduling" << entry << "for removal"; m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(entry)); } + } else { + // We don't have an old index file, so we may duplicate stuff! + auto dialog = CustomMessageBox::selectable(m_parent, + tr("No index file."), + tr("We couldn't find a suitable index file for the older version. This may cause some of the files to be duplicated. Do you want to continue?"), + QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel); + + if (dialog->exec() == QDialog::DialogCode::Rejected) { + m_abort = true; + return false; + } } From 06019f01e3e7b87e752ddc15deb850e272a82d21 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 11 Sep 2022 13:16:25 -0300 Subject: [PATCH 51/58] feat: add dialog to ask whether to chaneg instance's name This prevents custom names from being lost when updating, by only changing the name if the old instance name constains the old version, so that we can update it if the user whishes to. Signed-off-by: flow --- launcher/InstanceList.cpp | 5 ---- launcher/InstanceTask.cpp | 24 ++++++++++++++++--- launcher/InstanceTask.h | 4 ++++ .../flame/FlameInstanceCreationTask.cpp | 12 ++++++++-- .../modrinth/ModrinthInstanceCreationTask.cpp | 10 +++++++- 5 files changed, 44 insertions(+), 11 deletions(-) diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index a414e0d5..47b0e75a 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -908,11 +908,6 @@ bool InstanceList::commitStagedInstance(QString path, InstanceName const& instan qWarning() << "Failed to override" << path << "to" << destination; return false; } - - if (!inst) - inst = getInstanceById(instID); - if (inst) - inst->setName(instanceName.name()); } else { if (!dir.rename(path, destination)) { qWarning() << "Failed to move" << path << "to" << destination; diff --git a/launcher/InstanceTask.cpp b/launcher/InstanceTask.cpp index 43a0b947..da280731 100644 --- a/launcher/InstanceTask.cpp +++ b/launcher/InstanceTask.cpp @@ -1,5 +1,23 @@ #include "InstanceTask.h" +#include "ui/dialogs/CustomMessageBox.h" + +InstanceNameChange askForChangingInstanceName(QWidget* parent, QString old_name, QString new_name) +{ + auto dialog = + CustomMessageBox::selectable(parent, QObject::tr("Change instance name"), + QObject::tr("The instance's name seems to include the old version. Would you like to update it?\n\n" + "Old name: %1\n" + "New name: %2") + .arg(old_name, new_name), + QMessageBox::Question, QMessageBox::No | QMessageBox::Yes); + auto result = dialog->exec(); + + if (result == QMessageBox::Yes) + return InstanceNameChange::ShouldChange; + return InstanceNameChange::ShouldKeep; +} + QString InstanceName::name() const { if (!m_modified_name.isEmpty()) @@ -26,9 +44,9 @@ QString InstanceName::version() const void InstanceName::setName(InstanceName& other) { - m_original_name = other.m_original_name; - m_original_version = other.m_original_version; - m_modified_name = other.m_modified_name; + m_original_name = other.m_original_name; + m_original_version = other.m_original_version; + m_modified_name = other.m_modified_name; } InstanceTask::InstanceTask() : Task(), InstanceName() {} diff --git a/launcher/InstanceTask.h b/launcher/InstanceTask.h index 5d67a2f0..0987b557 100644 --- a/launcher/InstanceTask.h +++ b/launcher/InstanceTask.h @@ -3,6 +3,10 @@ #include "settings/SettingsObject.h" #include "tasks/Task.h" +/* Helpers */ +enum class InstanceNameChange { ShouldChange, ShouldKeep }; +[[nodiscard]] InstanceNameChange askForChangingInstanceName(QWidget* parent, QString old_name, QString new_name); + struct InstanceName { public: InstanceName() = default; diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 1b282770..69a41e55 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -15,8 +15,8 @@ #include "settings/INISettingsObject.h" -#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/BlockedModsDialog.h" +#include "ui/dialogs/CustomMessageBox.h" const static QMap forgemap = { { "1.2.5", "3.4.9.171" }, { "1.4.2", "6.0.1.355" }, @@ -348,12 +348,20 @@ bool FlameCreationTask::createInstance() bool did_succeed = getError().isEmpty(); + // Update information of the already installed instance, if any. if (m_instance && did_succeed) { setAbortable(false); auto inst = m_instance.value(); + // Only change the name if it didn't use a custom name, so that the previous custom name + // is preserved, but if we're using the original one, we update the version string. + // NOTE: This needs to come before the copyManagedPack call! + if (inst->name().contains(inst->getManagedPackVersionName())) { + if (askForChangingInstanceName(m_parent, inst->name(), instance.name()) == InstanceNameChange::ShouldChange) + inst->setName(instance.name()); + } + inst->copyManagedPack(instance); - inst->setName(instance.name()); } return did_succeed; diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index c1898dd9..2cb6e786 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -262,12 +262,20 @@ bool ModrinthCreationTask::createInstance() loop.exec(); + // Update information of the already installed instance, if any. if (m_instance && ended_well) { setAbortable(false); auto inst = m_instance.value(); + // Only change the name if it didn't use a custom name, so that the previous custom name + // is preserved, but if we're using the original one, we update the version string. + // NOTE: This needs to come before the copyManagedPack call! + if (inst->name().contains(inst->getManagedPackVersionName())) { + if (askForChangingInstanceName(m_parent, inst->name(), instance.name()) == InstanceNameChange::ShouldChange) + inst->setName(instance.name()); + } + inst->copyManagedPack(instance); - inst->setName(instance.name()); } return ended_well; From 4f6d964217f6addd4f29969168a7d40ed4729dde Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 11 Sep 2022 13:24:58 -0300 Subject: [PATCH 52/58] fix: don't change groups when updating an instance Signed-off-by: flow --- launcher/InstanceList.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 47b0e75a..6b0e3a4b 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -913,9 +913,11 @@ bool InstanceList::commitStagedInstance(QString path, InstanceName const& instan qWarning() << "Failed to move" << path << "to" << destination; return false; } + + m_instanceGroupIndex[instID] = groupName; + m_groupNameCache.insert(groupName); } - m_instanceGroupIndex[instID] = groupName; - m_groupNameCache.insert(groupName); + instanceSet.insert(instID); emit instancesChanged(); From 60b38de69f43f2623b7c972e5072fc4ce5aeb3c4 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 24 Sep 2022 11:43:54 +0200 Subject: [PATCH 53/58] fix: fallback for languages without a native name Signed-off-by: Sefa Eyeoglu --- launcher/translations/TranslationsModel.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index 848b4d19..a1ebd0c1 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -86,6 +86,10 @@ struct Language else { result = locale.nativeLanguageName(); } + + if (result.isEmpty()) { + result = key; + } return result; } From b187231b0ea798cd8ebeaa200b4befe29a433a55 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 24 Sep 2022 11:48:14 +0200 Subject: [PATCH 54/58] fix: sort languages by their name instead of key Signed-off-by: Sefa Eyeoglu --- launcher/translations/TranslationsModel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index a1ebd0c1..2f57de3a 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -398,7 +398,7 @@ void TranslationsModel::reloadLocalFiles() return false; } } - return a.key < b.key; + return a.languageName().toLower() < b.languageName().toLower(); }); endInsertRows(); } From 8a4f1c66f83a339d33ab0ba0076d8c1141055067 Mon Sep 17 00:00:00 2001 From: ErogigGit Date: Sat, 24 Sep 2022 22:37:51 +0200 Subject: [PATCH 55/58] Allow double clicking to mark for dowload Signed-off-by: Erogig --- launcher/ui/pages/modplatform/ModPage.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 986caa77..4fce0242 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -60,6 +60,7 @@ ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api) connect(ui->searchButton, &QPushButton::clicked, this, &ModPage::triggerSearch); connect(ui->modFilterButton, &QPushButton::clicked, this, &ModPage::filterMods); + connect(ui->packView, &QListView::doubleClicked, this, &ModPage::onModSelected); m_search_timer.setTimerType(Qt::TimerType::CoarseTimer); m_search_timer.setSingleShot(true); From 3d4feeec8d9e0f29de81c4e827724e4dd8b28077 Mon Sep 17 00:00:00 2001 From: ErogigGit Date: Sat, 24 Sep 2022 23:31:30 +0200 Subject: [PATCH 56/58] DCO Remediation Commit for ErogigGit I, ErogigGit , hereby add my Signed-off-by to this commit: 8a4f1c66f83a339d33ab0ba0076d8c1141055067 Signed-off-by: ErogigGit From 9ff364b0d3c12f9138037cce585c03c850721b82 Mon Sep 17 00:00:00 2001 From: timoreo Date: Mon, 26 Sep 2022 11:50:31 +0200 Subject: [PATCH 57/58] huge nit: added const refs, everywhere Signed-off-by: timoreo --- launcher/InstanceList.cpp | 4 ++-- launcher/InstanceList.h | 4 ++-- launcher/InstanceTask.cpp | 2 +- launcher/InstanceTask.h | 2 +- launcher/minecraft/VanillaInstanceCreationTask.cpp | 4 +++- launcher/minecraft/VanillaInstanceCreationTask.h | 4 +++- launcher/modplatform/flame/FlameAPI.cpp | 2 +- launcher/modplatform/flame/FlameAPI.h | 2 +- .../modplatform/flame/FlameInstanceCreationTask.cpp | 4 ++-- launcher/modplatform/flame/FlameInstanceCreationTask.h | 2 +- launcher/modplatform/helpers/OverrideUtils.cpp | 4 ++-- launcher/modplatform/helpers/OverrideUtils.h | 4 ++-- .../modrinth/ModrinthInstanceCreationTask.cpp | 10 +++++----- .../modrinth/ModrinthInstanceCreationTask.h | 2 +- launcher/ui/dialogs/NewInstanceDialog.cpp | 7 ++++--- launcher/ui/dialogs/NewInstanceDialog.h | 4 ++-- 16 files changed, 33 insertions(+), 28 deletions(-) diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 6b0e3a4b..cebd70d7 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -535,7 +535,7 @@ InstancePtr InstanceList::getInstanceById(QString instId) const return InstancePtr(); } -InstancePtr InstanceList::getInstanceByManagedName(QString managed_name) const +InstancePtr InstanceList::getInstanceByManagedName(const QString& managed_name) const { if (managed_name.isEmpty()) return {}; @@ -880,7 +880,7 @@ QString InstanceList::getStagedInstancePath() return path; } -bool InstanceList::commitStagedInstance(QString path, InstanceName const& instanceName, QString groupName, bool should_override) +bool InstanceList::commitStagedInstance(const QString& path, InstanceName const& instanceName, const QString& groupName, bool should_override) { QDir dir; QString instID; diff --git a/launcher/InstanceList.h b/launcher/InstanceList.h index df11b55a..3673298f 100644 --- a/launcher/InstanceList.h +++ b/launcher/InstanceList.h @@ -104,7 +104,7 @@ public: /* O(n) */ InstancePtr getInstanceById(QString id) const; /* O(n) */ - InstancePtr getInstanceByManagedName(QString managed_name) const; + InstancePtr getInstanceByManagedName(const QString& managed_name) const; QModelIndex getInstanceIndexById(const QString &id) const; QStringList getGroups(); bool isGroupCollapsed(const QString &groupName); @@ -133,7 +133,7 @@ public: * should_override is used when another similar instance already exists, and we want to override it * - for instance, when updating it. */ - bool commitStagedInstance(QString keyPath, const InstanceName& instanceName, QString groupName, bool should_override); + bool commitStagedInstance(const QString& keyPath, const InstanceName& instanceName, const QString& groupName, bool should_override); /** * Destroy a previously created staging area given by @keyPath - used when creation fails. diff --git a/launcher/InstanceTask.cpp b/launcher/InstanceTask.cpp index da280731..55a44fd3 100644 --- a/launcher/InstanceTask.cpp +++ b/launcher/InstanceTask.cpp @@ -2,7 +2,7 @@ #include "ui/dialogs/CustomMessageBox.h" -InstanceNameChange askForChangingInstanceName(QWidget* parent, QString old_name, QString new_name) +InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& old_name, const QString& new_name) { auto dialog = CustomMessageBox::selectable(parent, QObject::tr("Change instance name"), diff --git a/launcher/InstanceTask.h b/launcher/InstanceTask.h index 0987b557..e35533fc 100644 --- a/launcher/InstanceTask.h +++ b/launcher/InstanceTask.h @@ -5,7 +5,7 @@ /* Helpers */ enum class InstanceNameChange { ShouldChange, ShouldKeep }; -[[nodiscard]] InstanceNameChange askForChangingInstanceName(QWidget* parent, QString old_name, QString new_name); +[[nodiscard]] InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& old_name, const QString& new_name); struct InstanceName { public: diff --git a/launcher/minecraft/VanillaInstanceCreationTask.cpp b/launcher/minecraft/VanillaInstanceCreationTask.cpp index fb11379c..c45daa9a 100644 --- a/launcher/minecraft/VanillaInstanceCreationTask.cpp +++ b/launcher/minecraft/VanillaInstanceCreationTask.cpp @@ -1,12 +1,14 @@ #include "VanillaInstanceCreationTask.h" +#include + #include "FileSystem.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" #include "settings/INISettingsObject.h" VanillaCreationTask::VanillaCreationTask(BaseVersionPtr version, QString loader, BaseVersionPtr loader_version) - : InstanceCreationTask(), m_version(version), m_using_loader(true), m_loader(loader), m_loader_version(loader_version) + : InstanceCreationTask(), m_version(std::move(version)), m_using_loader(true), m_loader(std::move(loader)), m_loader_version(std::move(loader_version)) {} bool VanillaCreationTask::createInstance() diff --git a/launcher/minecraft/VanillaInstanceCreationTask.h b/launcher/minecraft/VanillaInstanceCreationTask.h index 540ecb70..7a37bbd6 100644 --- a/launcher/minecraft/VanillaInstanceCreationTask.h +++ b/launcher/minecraft/VanillaInstanceCreationTask.h @@ -2,10 +2,12 @@ #include "InstanceCreationTask.h" +#include + class VanillaCreationTask final : public InstanceCreationTask { Q_OBJECT public: - VanillaCreationTask(BaseVersionPtr version) : InstanceCreationTask(), m_version(version) {} + VanillaCreationTask(BaseVersionPtr version) : InstanceCreationTask(), m_version(std::move(version)) {} VanillaCreationTask(BaseVersionPtr version, QString loader, BaseVersionPtr loader_version); bool createInstance() override; diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index f8f50dc6..4d71da21 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -184,7 +184,7 @@ auto FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const -> return netJob; } -auto FlameAPI::getFiles(QStringList fileIds, QByteArray* response) const -> NetJob* +auto FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) const -> NetJob* { auto* netJob = new NetJob(QString("Flame::GetFiles"), APPLICATION->network()); diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 5e6166b9..4c6ca64c 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -12,7 +12,7 @@ class FlameAPI : public NetworkModAPI { auto getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion; auto getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* override; - auto getFiles(QStringList fileIds, QByteArray* response) const -> NetJob*; + auto getFiles(const QStringList& fileIds, QByteArray* response) const -> NetJob*; private: inline auto getSortFieldInt(QString sortString) const -> int diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 69a41e55..48ac02e0 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -126,7 +126,7 @@ bool FlameCreationTask::updateInstance() // TODO: Currently 'overrides' will always override the stuff on update. How do we preserve unchanged overrides? // FIXME: We may want to do something about disabled mods. auto old_overrides = Override::readOverrides("overrides", old_index_folder); - for (auto entry : old_overrides) { + for (const auto& entry : old_overrides) { if (entry.isEmpty()) continue; qDebug() << "Scheduling" << entry << "for removal"; @@ -320,7 +320,7 @@ bool FlameCreationTask::createInstance() qDebug() << "Found jarmods:"; QDir jarmodsDir(jarmodsPath); QStringList jarMods; - for (auto info : jarmodsDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) { + for (const auto& info : jarmodsDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) { qDebug() << info.fileName(); jarMods.push_back(info.absoluteFilePath()); } diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.h b/launcher/modplatform/flame/FlameInstanceCreationTask.h index ccb5f827..ded0e2ce 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.h +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.h @@ -14,7 +14,7 @@ class FlameCreationTask final : public InstanceCreationTask { Q_OBJECT public: - FlameCreationTask(QString staging_path, SettingsObjectPtr global_settings, QWidget* parent) + FlameCreationTask(const QString& staging_path, SettingsObjectPtr global_settings, QWidget* parent) : InstanceCreationTask(), m_parent(parent) { setStagingPath(staging_path); diff --git a/launcher/modplatform/helpers/OverrideUtils.cpp b/launcher/modplatform/helpers/OverrideUtils.cpp index 3496a286..65b5f760 100644 --- a/launcher/modplatform/helpers/OverrideUtils.cpp +++ b/launcher/modplatform/helpers/OverrideUtils.cpp @@ -6,7 +6,7 @@ namespace Override { -void createOverrides(QString name, QString parent_folder, QString override_path) +void createOverrides(const QString& name, const QString& parent_folder, const QString& override_path) { QString file_path(FS::PathCombine(parent_folder, name + ".txt")); if (QFile::exists(file_path)) @@ -33,7 +33,7 @@ void createOverrides(QString name, QString parent_folder, QString override_path) file.close(); } -QStringList readOverrides(QString name, QString parent_folder) +QStringList readOverrides(const QString& name, const QString& parent_folder) { QString file_path(FS::PathCombine(parent_folder, name + ".txt")); diff --git a/launcher/modplatform/helpers/OverrideUtils.h b/launcher/modplatform/helpers/OverrideUtils.h index 73f059d6..536261a2 100644 --- a/launcher/modplatform/helpers/OverrideUtils.h +++ b/launcher/modplatform/helpers/OverrideUtils.h @@ -9,12 +9,12 @@ namespace Override { * * If there's already an existing such file, it will be ovewritten. */ -void createOverrides(QString name, QString parent_folder, QString override_path); +void createOverrides(const QString& name, const QString& parent_folder, const QString& override_path); /** This reads an existing overrides archive, returning a list of overrides. * * If there's no such file in `parent_folder`, it will return an empty list. */ -QStringList readOverrides(QString name, QString parent_folder); +QStringList readOverrides(const QString& name, const QString& parent_folder); } // namespace Override diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 2cb6e786..f9709551 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -121,7 +121,7 @@ bool ModrinthCreationTask::updateInstance() // TODO: Currently 'overrides' will always override the stuff on update. How do we preserve unchanged overrides? // FIXME: We may want to do something about disabled mods. auto old_overrides = Override::readOverrides("overrides", old_index_folder); - for (auto entry : old_overrides) { + for (const auto& entry : old_overrides) { if (entry.isEmpty()) continue; qDebug() << "Scheduling" << entry << "for removal"; @@ -129,7 +129,7 @@ bool ModrinthCreationTask::updateInstance() } auto old_client_overrides = Override::readOverrides("client-overrides", old_index_folder); - for (auto entry : old_overrides) { + for (const auto& entry : old_overrides) { if (entry.isEmpty()) continue; qDebug() << "Scheduling" << entry << "for removal"; @@ -235,7 +235,7 @@ bool ModrinthCreationTask::createInstance() dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); m_files_job->addNetAction(dl); - if (file.downloads.size() > 0) { + if (!file.downloads.empty()) { // FIXME: This really needs to be put into a ConcurrentTask of // MultipleOptionsTask's , once those exist :) connect(dl.get(), &NetAction::failed, [this, &file, path, dl] { @@ -281,7 +281,7 @@ bool ModrinthCreationTask::createInstance() return ended_well; } -bool ModrinthCreationTask::parseManifest(QString index_path, std::vector& files, bool set_managed_info, bool show_optional_dialog) +bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector& files, bool set_managed_info, bool show_optional_dialog) { try { auto doc = Json::requireDocument(index_path); @@ -300,7 +300,7 @@ bool ModrinthCreationTask::parseManifest(QString index_path, std::vector(obj, "files", "modrinth.index.json"); bool had_optional = false; - for (auto modInfo : jsonFiles) { + for (const auto& modInfo : jsonFiles) { Modrinth::File file; file.path = Json::requireString(modInfo, "path"); diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h index 01d0d1bf..e459aadf 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h @@ -27,7 +27,7 @@ class ModrinthCreationTask final : public InstanceCreationTask { bool createInstance() override; private: - bool parseManifest(QString, std::vector&, bool set_managed_info = true, bool show_optional_dialog = true); + bool parseManifest(const QString&, std::vector&, bool set_managed_info = true, bool show_optional_dialog = true); QString getManagedPackID() const; private: diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp index 04fecbde..d203795a 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.cpp +++ b/launcher/ui/dialogs/NewInstanceDialog.cpp @@ -51,6 +51,7 @@ #include #include #include +#include #include "ui/widgets/PageContainer.h" #include "ui/pages/modplatform/VanillaPage.h" @@ -177,7 +178,7 @@ NewInstanceDialog::~NewInstanceDialog() delete ui; } -void NewInstanceDialog::setSuggestedPack(QString name, InstanceTask* task) +void NewInstanceDialog::setSuggestedPack(const QString& name, InstanceTask* task) { creationTask.reset(task); @@ -193,12 +194,12 @@ void NewInstanceDialog::setSuggestedPack(QString name, InstanceTask* task) m_buttons->button(QDialogButtonBox::Ok)->setEnabled(allowOK); } -void NewInstanceDialog::setSuggestedPack(QString name, QString version, InstanceTask* task) +void NewInstanceDialog::setSuggestedPack(const QString& name, QString version, InstanceTask* task) { creationTask.reset(task); ui->instNameTextBox->setPlaceholderText(name); - importVersion = version; + importVersion = std::move(version); if (!task) { ui->iconButton->setIcon(APPLICATION->icons()->getIcon("default")); diff --git a/launcher/ui/dialogs/NewInstanceDialog.h b/launcher/ui/dialogs/NewInstanceDialog.h index 185ab9e6..961f512e 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.h +++ b/launcher/ui/dialogs/NewInstanceDialog.h @@ -60,8 +60,8 @@ public: void updateDialogState(); - void setSuggestedPack(QString name = QString(), InstanceTask * task = nullptr); - void setSuggestedPack(QString name, QString version, InstanceTask * task = nullptr); + void setSuggestedPack(const QString& name = QString(), InstanceTask * task = nullptr); + void setSuggestedPack(const QString& name, QString version, InstanceTask * task = nullptr); void setSuggestedIconFromFile(const QString &path, const QString &name); void setSuggestedIcon(const QString &key); From dd6f670dec7dfd1a9ad6f4595ad5447ac735c737 Mon Sep 17 00:00:00 2001 From: timoreo Date: Mon, 26 Sep 2022 11:50:55 +0200 Subject: [PATCH 58/58] fix: Fixed memory leak Signed-off-by: timoreo --- .../modrinth/ModrinthInstanceCreationTask.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index f9709551..ddeea224 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -238,11 +238,12 @@ bool ModrinthCreationTask::createInstance() if (!file.downloads.empty()) { // FIXME: This really needs to be put into a ConcurrentTask of // MultipleOptionsTask's , once those exist :) - connect(dl.get(), &NetAction::failed, [this, &file, path, dl] { - auto dl = Net::Download::makeFile(file.downloads.dequeue(), path); - dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); - m_files_job->addNetAction(dl); - dl->succeeded(); + auto param = dl.toWeakRef(); + connect(dl.get(), &NetAction::failed, [this, &file, path, param] { + auto ndl = Net::Download::makeFile(file.downloads.dequeue(), path); + ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); + m_files_job->addNetAction(ndl); + if (auto shared = param.lock()) shared->succeeded(); }); } }