diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 888e09e1..2f72e0d3 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -555,8 +555,7 @@ set(LOGIC_SOURCES ${META_SOURCES} ${ICONS_SOURCES} ${FTB_SOURCES} - ${FLAME_SOURCES} - ${MODPACKSCH_SOURCES} + ${FTBA_SOURCES} ${TECHNIC_SOURCES} ${ATLAUNCHER_SOURCES} ${MODRINTH_SOURCES} @@ -721,6 +720,13 @@ SET(LAUNCHER_SOURCES ui/pages/modplatform/legacy_ftb/ListModel.h ui/pages/modplatform/legacy_ftb/ListModel.cpp + ui/pages/modplatform/import_ftb/PackInstallTask.cpp + ui/pages/modplatform/import_ftb/PackInstallTask.h + ui/pages/modplatform/import_ftb/Model.cpp + ui/pages/modplatform/import_ftb/Model.h + ui/pages/modplatform/import_ftb/FTBAPage.cpp + ui/pages/modplatform/import_ftb/FTBAPage.h + ui/pages/modplatform/modrinth/ModrinthData.h ui/pages/modplatform/modrinth/ModrinthModel.cpp ui/pages/modplatform/modrinth/ModrinthModel.h @@ -848,6 +854,7 @@ qt5_wrap_ui(LAUNCHER_UI ui/pages/modplatform/atlauncher/AtlPage.ui ui/pages/modplatform/VanillaPage.ui ui/pages/modplatform/legacy_ftb/Page.ui + ui/pages/modplatform/import_ftb/FTBAPage.ui ui/pages/modplatform/ImportPage.ui ui/pages/modplatform/modrinth/ModrinthPage.ui ui/pages/modplatform/technic/TechnicPage.ui diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp index a9342f37..0b933fb5 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.cpp +++ b/launcher/ui/dialogs/NewInstanceDialog.cpp @@ -36,6 +36,7 @@ #include "ui/pages/modplatform/VanillaPage.h" #include "ui/pages/modplatform/atlauncher/AtlPage.h" #include "ui/pages/modplatform/legacy_ftb/Page.h" +#include "ui/pages/modplatform/import_ftb/FTBAPage.h" #include "ui/pages/modplatform/ImportPage.h" #include "ui/pages/modplatform/modrinth/ModrinthPage.h" #include "ui/pages/modplatform/technic/TechnicPage.h" @@ -128,9 +129,10 @@ QList NewInstanceDialog::getPages() { new VanillaPage(this), importPage, - new AtlPage(this), - new LegacyFTB::Page(this), new ModrinthPage(this), + new AtlPage(this), + new ImportFTB::FTBAPage(this), + new LegacyFTB::Page(this), technicPage }; } diff --git a/launcher/ui/pages/modplatform/import_ftb/FTBAPage.cpp b/launcher/ui/pages/modplatform/import_ftb/FTBAPage.cpp new file mode 100644 index 00000000..8a395382 --- /dev/null +++ b/launcher/ui/pages/modplatform/import_ftb/FTBAPage.cpp @@ -0,0 +1,122 @@ +/* + * Copyright 2022 Petr Mrázek + * + * This source is subject to the Microsoft Permissive License (MS-PL). + * Please see the COPYING.md file for more information. + */ + +#include "FTBAPage.h" +#include "ui_FTBAPage.h" + +#include "Application.h" + +#include "ui/dialogs/NewInstanceDialog.h" + +#include "PackInstallTask.h" +#include "Model.h" + +namespace ImportFTB { + +FTBAPage::FTBAPage(NewInstanceDialog* dialog, QWidget *parent) + : QWidget(parent), dialog(dialog), ui(new Ui::FTBAPage) +{ + ui->setupUi(this); + + packModel = new Model(this); + + ui->packList->setModel(packModel); + ui->packList->setSortingEnabled(true); + ui->packList->header()->hide(); + ui->packList->setIndentation(0); + ui->packList->setIconSize(QSize(42, 42)); + + connect(ui->packList->selectionModel(), &QItemSelectionModel::currentChanged, this, &FTBAPage::onPublicPackSelectionChanged); + + ui->packList->selectionModel()->reset(); + + currentList = ui->packList; + currentModpackInfo = ui->packDescription; + + currentList->selectionModel()->reset(); + QModelIndex idx = currentList->currentIndex(); + if(idx.isValid()) + { + auto pack = packModel->data(idx, Qt::UserRole).value(); + onPackSelectionChanged(&pack); + } + else + { + onPackSelectionChanged(); + } +} + +FTBAPage::~FTBAPage() +{ + delete ui; +} + +bool FTBAPage::shouldDisplay() const +{ + return true; +} + +void FTBAPage::openedImpl() +{ + if(!initialized) + { + packModel->reload(); + initialized = true; + } + suggestCurrent(); +} + +void FTBAPage::suggestCurrent() +{ + if(!isOpened) + { + return; + } + + if(!selected) + { + dialog->setSuggestedPack(); + return; + } + auto selectedPack = *selected; + + dialog->setSuggestedPack(selectedPack.name, new PackInstallTask(selectedPack)); + QString logoName = QString("ftb_%1").arg(selectedPack.id); + dialog->setSuggestedIconFromFile(selectedPack.iconPath, logoName); +} + +void FTBAPage::onPublicPackSelectionChanged(QModelIndex now, QModelIndex prev) +{ + if(!now.isValid()) + { + onPackSelectionChanged(); + return; + } + Modpack selectedPack = packModel->data(now, Qt::UserRole).value(); + onPackSelectionChanged(&selectedPack); +} + +void FTBAPage::onPackSelectionChanged(Modpack* pack) +{ + if(pack) + { + currentModpackInfo->setHtml("Pack by " + pack->authors.join(", ") + "" + "
Minecraft " + pack->mcVersion + "
" + "
" + pack->description); + selected = *pack; + } + else + { + currentModpackInfo->setHtml(""); + if(isOpened) + { + dialog->setSuggestedPack(); + } + return; + } + suggestCurrent(); +} + +} diff --git a/launcher/ui/pages/modplatform/import_ftb/FTBAPage.h b/launcher/ui/pages/modplatform/import_ftb/FTBAPage.h new file mode 100644 index 00000000..eea8c172 --- /dev/null +++ b/launcher/ui/pages/modplatform/import_ftb/FTBAPage.h @@ -0,0 +1,79 @@ +/* + * Copyright 2022 Petr Mrázek + * + * This source is subject to the Microsoft Permissive License (MS-PL). + * Please see the COPYING.md file for more information. + */ + +#pragma once + +#include +#include +#include + +#include "ui/pages/BasePage.h" +#include +#include "tasks/Task.h" +#include "QObjectPtr.h" + +#include "Model.h" + +class NewInstanceDialog; + +namespace ImportFTB { + +namespace Ui +{ +class FTBAPage; +} + +class Model; + +class FTBAPage : public QWidget, public BasePage +{ + Q_OBJECT + +public: + explicit FTBAPage(NewInstanceDialog * dialog, QWidget *parent = 0); + virtual ~FTBAPage(); + QString displayName() const override + { + return tr("FTBApp Import"); + } + QIcon icon() const override + { + return APPLICATION->getThemedIcon("ftb_logo"); + } + QString id() const override + { + return "import_ftb"; + } + QString helpPage() const override + { + return "FTB-app"; + } + bool shouldDisplay() const override; + void openedImpl() override; + +private: + void suggestCurrent(); + void onPackSelectionChanged(Modpack *pack = nullptr); + +private slots: + void onPublicPackSelectionChanged(QModelIndex first, QModelIndex second); + +private: + QTreeView* currentList = nullptr; + QTextBrowser* currentModpackInfo = nullptr; + + bool initialized = false; + nonstd::optional selected; + + Model* packModel = nullptr; + + NewInstanceDialog* dialog = nullptr; + + Ui::FTBAPage *ui = nullptr; +}; + +} diff --git a/launcher/ui/pages/modplatform/import_ftb/FTBAPage.ui b/launcher/ui/pages/modplatform/import_ftb/FTBAPage.ui new file mode 100644 index 00000000..cc7aeaff --- /dev/null +++ b/launcher/ui/pages/modplatform/import_ftb/FTBAPage.ui @@ -0,0 +1,40 @@ + + + + ImportFTB::FTBAPage + + + + 0 + 0 + 1461 + 1011 + + + + + + + + 16777215 + 16777215 + + + + true + + + + + + + + + + + diff --git a/launcher/ui/pages/modplatform/import_ftb/Model.cpp b/launcher/ui/pages/modplatform/import_ftb/Model.cpp new file mode 100644 index 00000000..ab6e7f99 --- /dev/null +++ b/launcher/ui/pages/modplatform/import_ftb/Model.cpp @@ -0,0 +1,216 @@ +/* + * Copyright 2022 Petr Mrázek + * + * This source is subject to the Microsoft Permissive License (MS-PL). + * Please see the COPYING.md file for more information. + */ + +#include "Model.h" +#include "Application.h" + +#include +#include + +#include +#include + +#include + +#include +#include +#include + +namespace ImportFTB { + +Model::Model(QObject *parent) : QAbstractListModel(parent) +{ +} + +int Model::rowCount(const QModelIndex &parent) const +{ + return modpacks.size(); +} + +int Model::columnCount(const QModelIndex &parent) const +{ + return 1; +} + +QVariant Model::data(const QModelIndex &index, int role) const +{ + int pos = index.row(); + if(pos >= modpacks.size() || pos < 0 || !index.isValid()) + { + return QVariant(); + } + + Modpack pack = modpacks.at(pos); + if(role == Qt::DisplayRole) + { + return pack.name; + } + else if(role == Qt::DecorationRole) + { + return pack.icon; + } + else if(role == Qt::UserRole) + { + QVariant v; + v.setValue(pack); + return v; + } + + return QVariant(); +} + +namespace { +QString getFTBAInstances() { + QByteArray data; + try + { + auto path = FS::PathCombine(QDir::homePath(), ".ftba/bin/settings.json"); + data = FS::read(path); + } + catch (const Exception &e) + { + qWarning() << "Could not read FTBA settings file"; + return QString(); + } + + try + { + auto document = Json::requireDocument(data); + auto object = Json::requireObject(document); + return Json::requireString(object, "instanceLocation"); + } + catch (Json::JsonException & e) + { + qCritical() << "Could not read FTBA settings file as JSON: " << e.cause(); + return QString(); + } +} + +/* +Reference from an FTBA file, as of 28.05.2022 +{ + "_private": false, + "uuid": "25850fc6-ec61-4bc6-8397-77e4497bf922", + "id": 95, + "art": "data:image/png;base64,...==", + "path": "/home/peterix/.ftba/instances/25850fc6-ec61-4bc6-8397-77e4497bf922", + "versionId": 2127, + "name": "FTB Presents Direwolf20 1.18", + "minMemory": 4096, + "recMemory": 6144, + "memory": 6144, + "version": "1.2.0", + "dir": "/home/peterix/.ftba/instances/25850fc6-ec61-4bc6-8397-77e4497bf922", + "authors": [ + "FTB Team" + ], + "description": "Direwolf's modded legacy started all the way back in 2011. With 5 modpacks from the FTB Team, dedicated to creating a simple yet unique form of kitchen-sink modpacks.\n\nThis new iteration of Direwolf's modpack is packed with hand-selected mods from Direwolf20 himself, the FTB Team have worked hard in forging a kitchen-sink styled modded Minecraft experience that just about anyone can play. \n\nFollow along with Direwolf on his YouTube series, learning with him as you venture out into new modded territory. Improve his designs as you build up more infrastructure and automate anything and everything. The world is yours, for you to do anything in.", + "mcVersion": "1.18.2", + "jvmArgs": "", + "embeddedJre": true, + "url": "", + "artUrl": "https://apps.modpacks.ch/modpacks/art/90/1024_1024.png", + "width": 1920, + "height": 1080, + "modLoader": "1.18.2-forge-40.1.20", + "isModified": false, + "isImport": false, + "cloudSaves": false, + "hasInstMods": false, + "installComplete": true, + "packType": 0, + "totalPlayTime": 0, + "lastPlayed": 1653168242 +} +*/ + +bool parseModpackJson(const QByteArray& data, Modpack & out) { + try + { + auto document = Json::requireDocument(data); + auto object = Json::requireObject(document); + bool isInstalled = Json::requireBoolean(object, "installComplete"); + if(!isInstalled) { + return false; + } + out.id = Json::requireInteger(object, "id"); + out.name = Json::requireString(object, "name"); + out.version = Json::requireString(object, "version"); + out.description = Json::requireString(object, "description"); + auto authorsArray = Json::requireArray(object, "authors"); + for(auto author: authorsArray) { + out.authors.append(Json::requireString(author)); + } + + out.mcVersion = Json::requireString(object, "mcVersion"); + out.modLoader = Json::requireString(object, "modLoader"); + out.hasInstMods = Json::requireBoolean(object, "hasInstMods"); + + out.minMemory = Json::requireInteger(object, "minMemory"); + out.recMemory = Json::requireInteger(object, "recMemory"); + out.memory = Json::requireInteger(object, "memory"); + return true; + } + catch (Json::JsonException & e) + { + qCritical() << "Could not read FTBA settings file as JSON: " << e.cause(); + return false; + } +} + +bool tryLoadInstance(const QString& location, Modpack & out) { + QFileInfo dirInfo(location); + + if(dirInfo.isSymLink()) + return false; + + QByteArray data; + auto jsonLocation = FS::PathCombine(location, "instance.json"); + try + { + data = FS::read(jsonLocation); + if(!parseModpackJson(data, out)) { + return false; + } + out.instanceDir = location; + out.iconPath = FS::PathCombine(location, "folder.jpg"); + out.icon = QIcon(out.iconPath); + return true; + } + catch (const Exception &e) + { + qWarning() << "Could not read FTBA instance JSON file: " << jsonLocation; + return false; + } + return false; +} + +} + +void Model::reload() { + beginResetModel(); + modpacks.clear(); + QString instancesLocation = getFTBAInstances(); + if(instancesLocation.isEmpty()) { + qDebug() << "FTB instances are not found..."; + } + else { + qDebug() << "Discovering FTB instances in" << instancesLocation; + QDirIterator iter(instancesLocation, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks); + while (iter.hasNext()) { + QString subDir = iter.next(); + ImportFTB::Modpack out; + if(!tryLoadInstance(subDir, out)) { + continue; + } + modpacks.append(out); + } + } + endResetModel(); +} + +} diff --git a/launcher/ui/pages/modplatform/import_ftb/Model.h b/launcher/ui/pages/modplatform/import_ftb/Model.h new file mode 100644 index 00000000..dad2d38b --- /dev/null +++ b/launcher/ui/pages/modplatform/import_ftb/Model.h @@ -0,0 +1,69 @@ +/* + * Copyright 2022 Petr Mrázek + * + * This source is subject to the Microsoft Permissive License (MS-PL). + * Please see the COPYING.md file for more information. + */ + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace ImportFTB { + +struct Modpack +{ + int id = 0; + int versionId = 0; + + QString name; + QString version; + QString description; + QStringList authors; + + QString mcVersion; + QString modLoader; + bool hasInstMods = false; + + int minMemory = 1024; + int recMemory = 2048; + int memory = 2048; + + QString instanceDir; + QString iconPath; + QIcon icon; + + QString getLogoName() { + return QString("ftb_%1").arg(id); + } +}; + +class Model : public QAbstractListModel +{ + Q_OBJECT +private: + QList modpacks; + +public: + Model(QObject *parent); + virtual ~Model() = default; + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + + void reload(); +}; + +} + +Q_DECLARE_METATYPE(ImportFTB::Modpack) diff --git a/launcher/ui/pages/modplatform/import_ftb/PackInstallTask.cpp b/launcher/ui/pages/modplatform/import_ftb/PackInstallTask.cpp new file mode 100644 index 00000000..72c47831 --- /dev/null +++ b/launcher/ui/pages/modplatform/import_ftb/PackInstallTask.cpp @@ -0,0 +1,96 @@ +/* + * Copyright 2022 Petr Mrázek + * + * This source is subject to the Microsoft Permissive License (MS-PL). + * Please see the COPYING.md file for more information. + */ + +#include "PackInstallTask.h" + +#include + +#include "MMCZip.h" +#include "BaseInstance.h" +#include "FileSystem.h" +#include "settings/INISettingsObject.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" +#include "minecraft/GradleSpecifier.h" + +#include "BuildConfig.h" +#include "Application.h" + +#include + +namespace ImportFTB { + +PackInstallTask::PackInstallTask(Modpack pack) { + m_pack = pack; +} + +void PackInstallTask::executeTask() { + FS::copy folderCopy(m_pack.instanceDir, FS::PathCombine(m_stagingPath, ".minecraft")); + folderCopy.followSymlinks(true).blacklist(new RegexpMatcher("(instance|version)[.]json")); + + m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), folderCopy); + connect(&m_copyFutureWatcher, &QFutureWatcher::finished, this, &PackInstallTask::copyFinished); + connect(&m_copyFutureWatcher, &QFutureWatcher::canceled, this, &PackInstallTask::copyAborted); + m_copyFutureWatcher.setFuture(m_copyFuture); + abortable = true; +} + +void PackInstallTask::copyFinished() { + abortable = false; + QString instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg"); + auto instanceSettings = std::make_shared(instanceConfigPath); + instanceSettings->suspendSave(); + instanceSettings->registerSetting("InstanceType", "Legacy"); + instanceSettings->set("InstanceType", "OneSix"); + + MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); + auto components = instance.getPackProfile(); + components->buildingFromScratch(); + components->setComponentVersion("net.minecraft", m_pack.mcVersion, true); + + auto modloader = m_pack.modLoader; + if(!modloader.isEmpty()) { + if(modloader.contains("-forge-")) { + auto forgeVersion = modloader.replace(m_pack.mcVersion, "").replace("-forge-", ""); + components->setComponentVersion("net.minecraftforge", forgeVersion, true); + } + else { + qWarning() << "Unknown modloader version: " << modloader; + } + } + + components->saveNow(); + + instance.setName(m_instName); + if(m_instIcon == "default") + { + m_instIcon = "ftb_logo"; + } + instance.setIconKey(m_instIcon); + instanceSettings->resumeSave(); + + emitSucceeded(); +} + +void PackInstallTask::copyAborted() { + emitAborted(); +} + + +bool ImportFTB::PackInstallTask::canAbort() const { + return abortable; +} + +bool ImportFTB::PackInstallTask::abort() { + if(abortable) { + m_copyFuture.cancel(); + return true; + } + return false; +} + +} diff --git a/launcher/ui/pages/modplatform/import_ftb/PackInstallTask.h b/launcher/ui/pages/modplatform/import_ftb/PackInstallTask.h new file mode 100644 index 00000000..04d37a72 --- /dev/null +++ b/launcher/ui/pages/modplatform/import_ftb/PackInstallTask.h @@ -0,0 +1,46 @@ +/* + * Copyright 2022 Petr Mrázek + * + * This source is subject to the Microsoft Permissive License (MS-PL). + * Please see the COPYING.md file for more information. + */ + +#pragma once + +#include "InstanceTask.h" +#include "Model.h" + +#include +#include + +namespace ImportFTB { + +class PackInstallTask : public InstanceTask +{ + Q_OBJECT + +public: + explicit PackInstallTask(Modpack pack); + virtual ~PackInstallTask() = default; + + bool canAbort() const override; + bool abort() override; + +protected: + virtual void executeTask() override; + +private: + void install(); + +private slots: + void copyFinished(); + void copyAborted(); + +private: + bool abortable = false; + Modpack m_pack; + QFuture m_copyFuture; + QFutureWatcher m_copyFutureWatcher; +}; + +}