From 9d2516a199ae4c33f773ab00ce59ecb2a6dfd0a5 Mon Sep 17 00:00:00 2001
From: Trial97 <alexandru.tripon97@gmail.com>
Date: Thu, 22 Jun 2023 16:00:45 +0300
Subject: Added ExportModsToStringTask

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
---
 launcher/CMakeLists.txt | 3 +++
 1 file changed, 3 insertions(+)

(limited to 'launcher/CMakeLists.txt')

diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index ce2771a4..c7efdad8 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -487,6 +487,9 @@ set(API_SOURCES
     modplatform/helpers/HashUtils.cpp
     modplatform/helpers/OverrideUtils.h
     modplatform/helpers/OverrideUtils.cpp
+
+    modplatform/helpers/ExportModsToStringTask.h
+    modplatform/helpers/ExportModsToStringTask.cpp
 )
 
 set(FTB_SOURCES
-- 
cgit 


From f7d502c68c530d66b385d530c838a9a6566828d0 Mon Sep 17 00:00:00 2001
From: Trial97 <alexandru.tripon97@gmail.com>
Date: Thu, 22 Jun 2023 16:05:47 +0300
Subject: Added ExportModsToStringDialog

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
---
 launcher/CMakeLists.txt                          |   3 +
 launcher/ui/MainWindow.cpp                       |  10 ++
 launcher/ui/MainWindow.h                         |   1 +
 launcher/ui/MainWindow.ui                        |   8 ++
 launcher/ui/dialogs/ExportModsToStringDialog.cpp | 119 ++++++++++++++++
 launcher/ui/dialogs/ExportModsToStringDialog.h   |  45 ++++++
 launcher/ui/dialogs/ExportModsToStringDialog.ui  | 171 +++++++++++++++++++++++
 7 files changed, 357 insertions(+)
 create mode 100644 launcher/ui/dialogs/ExportModsToStringDialog.cpp
 create mode 100644 launcher/ui/dialogs/ExportModsToStringDialog.h
 create mode 100644 launcher/ui/dialogs/ExportModsToStringDialog.ui

(limited to 'launcher/CMakeLists.txt')

diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index c7efdad8..5ef97f42 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -911,6 +911,8 @@ SET(LAUNCHER_SOURCES
     ui/dialogs/ExportInstanceDialog.h
     ui/dialogs/ExportMrPackDialog.cpp
     ui/dialogs/ExportMrPackDialog.h
+    ui/dialogs/ExportModsToStringDialog.cpp
+    ui/dialogs/ExportModsToStringDialog.h
     ui/dialogs/IconPickerDialog.cpp
     ui/dialogs/IconPickerDialog.h
     ui/dialogs/ImportResourceDialog.cpp
@@ -1058,6 +1060,7 @@ qt_wrap_ui(LAUNCHER_UI
     ui/dialogs/SkinUploadDialog.ui
     ui/dialogs/ExportInstanceDialog.ui
     ui/dialogs/ExportMrPackDialog.ui
+    ui/dialogs/ExportModsToStringDialog.ui
     ui/dialogs/IconPickerDialog.ui
     ui/dialogs/ImportResourceDialog.ui
     ui/dialogs/MSALoginDialog.ui
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index e04011ca..02ea30c3 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -43,6 +43,7 @@
 #include "FileSystem.h"
 
 #include "MainWindow.h"
+#include "ui/dialogs/ExportModsToStringDialog.h"
 #include "ui_MainWindow.h"
 
 #include <QVariant>
@@ -205,6 +206,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
         auto exportInstanceMenu = new QMenu(this);
         exportInstanceMenu->addAction(ui->actionExportInstanceZip);
         exportInstanceMenu->addAction(ui->actionExportInstanceMrPack);
+        exportInstanceMenu->addAction(ui->actionExportInstanceToString);
         ui->actionExportInstance->setMenu(exportInstanceMenu);
     }
 
@@ -1416,6 +1418,14 @@ void MainWindow::on_actionExportInstanceMrPack_triggered()
     }
 }
 
+void MainWindow::on_actionExportInstanceToString_triggered()
+{
+    if (m_selectedInstance) {
+        ExportModsToStringDialog dlg(m_selectedInstance, this);
+        dlg.exec();
+    }
+}
+
 void MainWindow::on_actionRenameInstance_triggered()
 {
     if (m_selectedInstance)
diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h
index 3bb20c4a..9b38810f 100644
--- a/launcher/ui/MainWindow.h
+++ b/launcher/ui/MainWindow.h
@@ -157,6 +157,7 @@ private slots:
     inline void on_actionExportInstance_triggered() { on_actionExportInstanceZip_triggered(); }
     void on_actionExportInstanceZip_triggered();
     void on_actionExportInstanceMrPack_triggered();
+    void on_actionExportInstanceToString_triggered();
 
     void on_actionRenameInstance_triggered();
 
diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui
index f67fb185..e6e52a91 100644
--- a/launcher/ui/MainWindow.ui
+++ b/launcher/ui/MainWindow.ui
@@ -479,6 +479,14 @@
     <string>Modrinth (mrpack)</string>
    </property>
   </action>
+  <action name="actionExportInstanceToString">
+   <property name="icon">
+    <iconset theme="new"/>
+   </property>
+   <property name="text">
+    <string>Text</string>
+   </property>
+  </action>
   <action name="actionCreateInstanceShortcut">
    <property name="icon">
     <iconset theme="shortcut">
diff --git a/launcher/ui/dialogs/ExportModsToStringDialog.cpp b/launcher/ui/dialogs/ExportModsToStringDialog.cpp
new file mode 100644
index 00000000..d08a4c70
--- /dev/null
+++ b/launcher/ui/dialogs/ExportModsToStringDialog.cpp
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ *  Prism Launcher - Minecraft Launcher
+ *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, version 3.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "ExportModsToStringDialog.h"
+#include <QCheckBox>
+#include <QComboBox>
+#include <QTextEdit>
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/mod/ModFolderModel.h"
+#include "modplatform/helpers/ExportModsToStringTask.h"
+#include "ui_ExportModsToStringDialog.h"
+
+#include <QFileDialog>
+#include <QFileSystemModel>
+#include <QJsonDocument>
+#include <QMessageBox>
+#include <QPushButton>
+
+ExportModsToStringDialog::ExportModsToStringDialog(InstancePtr instance, QWidget* parent)
+    : QDialog(parent), m_template_selected(false), ui(new Ui::ExportModsToStringDialog)
+{
+    ui->setupUi(this);
+    ui->templateGroup->setDisabled(true);
+
+    MinecraftInstance* mcInstance = dynamic_cast<MinecraftInstance*>(instance.get());
+    if (mcInstance) {
+        mcInstance->loaderModList()->update();
+        connect(mcInstance->loaderModList().get(), &ModFolderModel::updateFinished, this, [this, mcInstance]() {
+            m_allMods = mcInstance->loaderModList()->allMods();
+            trigger();
+        });
+    }
+
+    connect(ui->formatComboBox, &QComboBox::currentIndexChanged, this, &ExportModsToStringDialog::formatChanged);
+    connect(ui->authorsCheckBox, &QCheckBox::stateChanged, this, &ExportModsToStringDialog::trigger);
+    connect(ui->versionCheckBox, &QCheckBox::stateChanged, this, &ExportModsToStringDialog::trigger);
+    connect(ui->urlCheckBox, &QCheckBox::stateChanged, this, &ExportModsToStringDialog::trigger);
+    connect(ui->templateText, &QTextEdit::textChanged, this, &ExportModsToStringDialog::trigger);
+    connect(ui->copyButton, &QPushButton::clicked, this, [this]() {
+        this->ui->finalText->selectAll();
+        this->ui->finalText->copy();
+    });
+}
+
+ExportModsToStringDialog::~ExportModsToStringDialog()
+{
+    delete ui;
+}
+
+void ExportModsToStringDialog::formatChanged(int index)
+{
+    switch (index) {
+        case 0: {
+            ui->templateGroup->setDisabled(true);
+            ui->optionsGroup->setDisabled(false);
+            break;
+        }
+        case 1: {
+            ui->templateGroup->setDisabled(true);
+            ui->optionsGroup->setDisabled(false);
+            break;
+        }
+        case 2: {
+            ui->templateGroup->setDisabled(false);
+            ui->optionsGroup->setDisabled(true);
+            break;
+        }
+    }
+    trigger();
+}
+
+void ExportModsToStringDialog::trigger()
+{
+    ExportToString::Formats format;
+    switch (ui->formatComboBox->currentIndex()) {
+        case 2: {
+            m_template_selected = true;
+            ui->finalText->setPlainText(ExportToString::ExportModsToStringTask(m_allMods, ui->templateText->toPlainText()));
+            return;
+        }
+        case 0: {
+            format = ExportToString::HTML;
+            break;
+        }
+        case 1: {
+            format = ExportToString::MARKDOWN;
+            break;
+        }
+    }
+    auto opt = 0;
+    if (ui->authorsCheckBox->isChecked())
+        opt |= ExportToString::Authors;
+    if (ui->versionCheckBox->isChecked())
+        opt |= ExportToString::Version;
+    if (ui->urlCheckBox->isChecked())
+        opt |= ExportToString::Url;
+    ui->finalText->setPlainText(ExportToString::ExportModsToStringTask(m_allMods, format, static_cast<ExportToString::OptionalData>(opt)));
+    if (!m_template_selected) {
+        auto exampleLine = format == ExportToString::HTML ? "<ul><a href=\"{url}\">{name}</a>[{version}] by {authors}</ul>"
+                                                          : "[{name}]({url})[{version}] by {authors}";
+        if (ui->templateText->toPlainText() != exampleLine)
+            ui->templateText->setPlainText(exampleLine);
+    }
+}
diff --git a/launcher/ui/dialogs/ExportModsToStringDialog.h b/launcher/ui/dialogs/ExportModsToStringDialog.h
new file mode 100644
index 00000000..7fada4d5
--- /dev/null
+++ b/launcher/ui/dialogs/ExportModsToStringDialog.h
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ *  Prism Launcher - Minecraft Launcher
+ *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, version 3.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <QDialog>
+#include <QList>
+#include "BaseInstance.h"
+#include "minecraft/mod/Mod.h"
+
+namespace Ui {
+class ExportModsToStringDialog;
+}
+
+class ExportModsToStringDialog : public QDialog {
+    Q_OBJECT
+
+   public:
+    explicit ExportModsToStringDialog(InstancePtr instance, QWidget* parent = nullptr);
+    ~ExportModsToStringDialog();
+
+   protected slots:
+    void formatChanged(int index);
+    void trigger();
+
+   private:
+    QList<Mod*> m_allMods;
+    bool m_template_selected;
+    Ui::ExportModsToStringDialog* ui;
+};
diff --git a/launcher/ui/dialogs/ExportModsToStringDialog.ui b/launcher/ui/dialogs/ExportModsToStringDialog.ui
new file mode 100644
index 00000000..4451a278
--- /dev/null
+++ b/launcher/ui/dialogs/ExportModsToStringDialog.ui
@@ -0,0 +1,171 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ExportModsToStringDialog</class>
+ <widget class="QDialog" name="ExportModsToStringDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>650</width>
+    <height>446</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Export Modrinth Pack</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout_2">
+   <item>
+    <layout class="QVBoxLayout" name="verticalLayout" stretch="0,0">
+     <item>
+      <widget class="QGroupBox" name="groupBox_3">
+       <property name="title">
+        <string>Settings</string>
+       </property>
+       <layout class="QGridLayout" name="gridLayout">
+        <item row="0" column="0">
+         <widget class="QLabel" name="label">
+          <property name="frameShape">
+           <enum>QFrame::NoFrame</enum>
+          </property>
+          <property name="frameShadow">
+           <enum>QFrame::Plain</enum>
+          </property>
+          <property name="lineWidth">
+           <number>1</number>
+          </property>
+          <property name="text">
+           <string>Format</string>
+          </property>
+         </widget>
+        </item>
+        <item row="0" column="1">
+         <widget class="QComboBox" name="formatComboBox">
+          <item>
+           <property name="text">
+            <string>HTML</string>
+           </property>
+          </item>
+          <item>
+           <property name="text">
+            <string>Markdown</string>
+           </property>
+          </item>
+          <item>
+           <property name="text">
+            <string>Custom</string>
+           </property>
+          </item>
+         </widget>
+        </item>
+        <item row="1" column="0">
+         <widget class="QGroupBox" name="templateGroup">
+          <property name="title">
+           <string>Template</string>
+          </property>
+          <layout class="QVBoxLayout" name="verticalLayout_4">
+           <item>
+            <widget class="QTextEdit" name="templateText"/>
+           </item>
+          </layout>
+         </widget>
+        </item>
+        <item row="1" column="1">
+         <widget class="QGroupBox" name="optionsGroup">
+          <property name="title">
+           <string>Optional Info</string>
+          </property>
+          <layout class="QVBoxLayout" name="verticalLayout_5">
+           <item>
+            <widget class="QCheckBox" name="versionCheckBox">
+             <property name="text">
+              <string>Version</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QCheckBox" name="authorsCheckBox">
+             <property name="text">
+              <string>Authors</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QCheckBox" name="urlCheckBox">
+             <property name="text">
+              <string>URL</string>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </widget>
+        </item>
+       </layout>
+      </widget>
+     </item>
+     <item>
+      <widget class="QGroupBox" name="groupBox_4">
+       <property name="title">
+        <string>Result</string>
+       </property>
+       <layout class="QHBoxLayout" name="horizontalLayout">
+        <item>
+         <widget class="QPlainTextEdit" name="finalText">
+          <property name="minimumSize">
+           <size>
+            <width>0</width>
+            <height>143</height>
+           </size>
+          </property>
+          <property name="readOnly">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_2">
+     <item>
+      <widget class="QPushButton" name="copyButton">
+       <property name="text">
+        <string>Copy</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QDialogButtonBox" name="buttonBox">
+       <property name="standardButtons">
+        <set>QDialogButtonBox::Ok</set>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>ExportModsToStringDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>334</x>
+     <y>435</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>324</x>
+     <y>206</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
-- 
cgit 


From bf95cfb30eee52f23d0279284f70931b2c968dd3 Mon Sep 17 00:00:00 2001
From: Trial97 <alexandru.tripon97@gmail.com>
Date: Fri, 23 Jun 2023 01:37:28 +0300
Subject: Added CatPacks

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
---
 launcher/Application.cpp                         |  12 ++-
 launcher/Application.h                           |   9 +-
 launcher/CMakeLists.txt                          |   2 +
 launcher/ui/MainWindow.cpp                       |   4 +-
 launcher/ui/setupwizard/ThemeWizardPage.cpp      |   2 +-
 launcher/ui/themes/CatPack.cpp                   | 109 +++++++++++++++++++++++
 launcher/ui/themes/CatPack.h                     |  98 ++++++++++++++++++++
 launcher/ui/themes/ThemeManager.cpp              |  82 ++++++++++++++---
 launcher/ui/themes/ThemeManager.h                |  15 ++--
 launcher/ui/widgets/ThemeCustomizationWidget.cpp |  17 ++--
 launcher/ui/widgets/ThemeCustomizationWidget.h   |  34 +++----
 11 files changed, 331 insertions(+), 53 deletions(-)
 create mode 100644 launcher/ui/themes/CatPack.cpp
 create mode 100644 launcher/ui/themes/CatPack.h

(limited to 'launcher/CMakeLists.txt')

diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index 724e6e44..d8ac2168 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -1173,7 +1173,17 @@ QIcon Application::getThemedIcon(const QString& name)
     return QIcon::fromTheme(name);
 }
 
-bool Application::openJsonEditor(const QString &filename)
+QList<CatPack*> Application::getValidCatPacks()
+{
+    return m_themeManager->getValidCatPacks();
+}
+
+QString Application::getCatPack(QString catName)
+{
+    return m_themeManager->getCatPack(catName);
+}
+
+bool Application::openJsonEditor(const QString& filename)
 {
     const QString file = QDir::current().absoluteFilePath(filename);
     if (m_settings->get("JsonEditor").toString().isEmpty())
diff --git a/launcher/Application.h b/launcher/Application.h
index ced0af17..55b01cd4 100644
--- a/launcher/Application.h
+++ b/launcher/Application.h
@@ -48,6 +48,7 @@
 #include <BaseInstance.h>
 
 #include "minecraft/launch/MinecraftServerTarget.h"
+#include "ui/themes/CatPack.h"
 
 class LaunchController;
 class LocalPeer;
@@ -126,9 +127,11 @@ public:
 
     void setApplicationTheme(const QString& name);
 
-    shared_qobject_ptr<ExternalUpdater> updater() {
-        return m_updater;
-    }
+    QList<CatPack*> getValidCatPacks();
+
+    QString getCatPack(QString catName = "");
+
+    shared_qobject_ptr<ExternalUpdater> updater() { return m_updater; }
 
     void triggerUpdateCheck();
 
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index ce2771a4..4d0f7d06 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -755,6 +755,8 @@ SET(LAUNCHER_SOURCES
     ui/themes/SystemTheme.h
     ui/themes/ThemeManager.cpp
     ui/themes/ThemeManager.h
+    ui/themes/CatPack.cpp
+    ui/themes/CatPack.h
 
     # Processes
     LaunchController.h
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index e04011ca..8e1b5613 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -924,14 +924,14 @@ void MainWindow::setCatBackground(bool enabled)
         view->setStyleSheet(QString(R"(
 InstanceView
 {
-    background-image: url(:/backgrounds/%1);
+    background-image: url(%1);
     background-attachment: fixed;
     background-clip: padding;
     background-position: bottom right;
     background-repeat: none;
     background-color:palette(base);
 })")
-                                .arg(ThemeManager::getCatImage()));
+                                .arg(APPLICATION->getCatPack()));
     } else {
         view->setStyleSheet(QString());
     }
diff --git a/launcher/ui/setupwizard/ThemeWizardPage.cpp b/launcher/ui/setupwizard/ThemeWizardPage.cpp
index 42826aba..282a01ac 100644
--- a/launcher/ui/setupwizard/ThemeWizardPage.cpp
+++ b/launcher/ui/setupwizard/ThemeWizardPage.cpp
@@ -61,7 +61,7 @@ void ThemeWizardPage::updateIcons()
 void ThemeWizardPage::updateCat()
 {
     qDebug() << "Setting Cat";
-    ui->catImagePreviewButton->setIcon(QIcon(QString(R"(:/backgrounds/%1)").arg(ThemeManager::getCatImage())));
+    ui->catImagePreviewButton->setIcon(QIcon(QString(R"(%1)").arg(APPLICATION->getCatPack())));
 }
 
 void ThemeWizardPage::retranslate()
diff --git a/launcher/ui/themes/CatPack.cpp b/launcher/ui/themes/CatPack.cpp
new file mode 100644
index 00000000..e74b9709
--- /dev/null
+++ b/launcher/ui/themes/CatPack.cpp
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ *  Prism Launcher - Minecraft Launcher
+ *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, version 3.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 "ui/themes/CatPack.h"
+#include <qdatetime.h>
+#include <qjsonarray.h>
+#include <qjsonobject.h>
+#include <qobject.h>
+#include <QDateTime>
+#include <QDir>
+#include <QFileInfo>
+#include "FileSystem.h"
+#include "Json.h"
+#include "ui/themes/ThemeManager.h"
+
+QString BasicCatPack::path()
+{
+    const QDateTime now = QDateTime::currentDateTime();
+    const QDateTime birthday(QDate(now.date().year(), 11, 30), QTime(0, 0));
+    const QDateTime xmas(QDate(now.date().year(), 12, 25), QTime(0, 0));
+    const QDateTime halloween(QDate(now.date().year(), 10, 31), QTime(0, 0));
+
+    QString cat = QString(":/backgrounds/%1").arg(m_id);
+    if (std::abs(now.daysTo(xmas)) <= 4) {
+        cat += "-xmas";
+    } else if (std::abs(now.daysTo(halloween)) <= 4) {
+        cat += "-spooky";
+    } else if (std::abs(now.daysTo(birthday)) <= 12) {
+        cat += "-bday";
+    }
+    return cat;
+}
+
+JsonCatPack::JsonCatPack(QFileInfo& manifestInfo) : BasicCatPack(manifestInfo.dir().dirName())
+{
+    QString path = FS::PathCombine("catpacks", m_id);
+
+    if (!FS::ensureFolderPathExists(path)) {
+        themeWarningLog() << "couldn't create folder for catpack!";
+        return;
+    }
+
+    if (manifestInfo.exists() && manifestInfo.isFile()) {
+        try {
+            auto doc = Json::requireDocument(manifestInfo.absoluteFilePath(), "CatPack JSON file");
+            const auto root = doc.object();
+            m_name = Json::requireString(root, "name", "Catpack name");
+            m_id = Json::requireString(root, "id", "Catpack ID");
+            m_defaultPath = FS::PathCombine(path, Json::requireString(root, "default", "Deafult Cat"));
+            auto variants = Json::ensureArray(root, "variants", QJsonArray(), "Catpack Variants");
+            for (auto v : variants) {
+                auto variant = Json::ensureObject(v, QJsonObject(), "Cat variant");
+                m_variants << Variant{ FS::PathCombine(path, Json::requireString(variant, "path", "Variant path")),
+                                       date(Json::requireString(variant, "startTime", "Variant startTime")),
+                                       date(Json::requireString(variant, "endTime", "Variant endTime")) };
+            }
+
+        } catch (const Exception& e) {
+            themeWarningLog() << "Couldn't load catpack json: " << e.cause();
+            return;
+        }
+    } else {
+        themeDebugLog() << "No catpack json present.";
+    }
+}
+
+QString JsonCatPack::path()
+{
+    const QDateTime now = QDateTime::currentDateTime();
+    for (auto var : m_variants) {
+        QDateTime startDate(QDate(now.date().year(), var.startTime.mounth, var.startTime.day), QTime(0, 0));
+        QDateTime endDate(QDate(now.date().year(), var.endTime.mounth, var.endTime.day), QTime(0, 0));
+        if (startDate.daysTo(now) > 0 && now.daysTo(endDate) > 0)
+            return var.path;
+    }
+    return m_defaultPath;
+}
diff --git a/launcher/ui/themes/CatPack.h b/launcher/ui/themes/CatPack.h
new file mode 100644
index 00000000..d9010b8e
--- /dev/null
+++ b/launcher/ui/themes/CatPack.h
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ *  Prism Launcher - Minecraft Launcher
+ *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, version 3.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ * 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 <QDateTime>
+#include <QFileInfo>
+#include <QList>
+#include <QString>
+
+class CatPack {
+   public:
+    virtual ~CatPack() {}
+    virtual QString id() = 0;
+    virtual QString name() = 0;
+    virtual QString path() = 0;
+};
+
+class BasicCatPack : public CatPack {
+   public:
+    BasicCatPack(QString id, QString name) : m_id(id), m_name(name) {}
+    BasicCatPack(QString id) : BasicCatPack(id, id) {}
+    virtual QString id() { return m_id; };
+    virtual QString name() { return m_name; };
+    virtual QString path();
+
+   protected:
+    QString m_id;
+    QString m_name;
+};
+
+class FileCatPack : public BasicCatPack {
+   public:
+    FileCatPack(QString id, QFileInfo& fileInfo) : BasicCatPack(id), m_path(fileInfo.absoluteFilePath()) {}
+    FileCatPack(QFileInfo& fileInfo) : FileCatPack(fileInfo.baseName(), fileInfo) {}
+    virtual QString path() { return m_path; }
+
+   private:
+    QString m_path;
+};
+
+class JsonCatPack : public BasicCatPack {
+   public:
+    struct date {
+        date(QString d)
+        {
+            auto sp = d.split("-");
+            day = sp[0].toInt();
+            if (sp.length() >= 2)
+                mounth = sp[1].length();
+        }
+        int mounth;
+        int day;
+    };
+    struct Variant {
+        QString path;
+        date startTime;
+        date endTime;
+    };
+    JsonCatPack(QFileInfo& manifestInfo);
+    virtual QString path();
+
+   private:
+    QString m_defaultPath;
+    QList<Variant> m_variants;
+};
diff --git a/launcher/ui/themes/ThemeManager.cpp b/launcher/ui/themes/ThemeManager.cpp
index 94ac8a24..bfd0550a 100644
--- a/launcher/ui/themes/ThemeManager.cpp
+++ b/launcher/ui/themes/ThemeManager.cpp
@@ -22,6 +22,7 @@
 #include <QDirIterator>
 #include <QIcon>
 #include "ui/themes/BrightTheme.h"
+#include "ui/themes/CatPack.h"
 #include "ui/themes/CustomTheme.h"
 #include "ui/themes/DarkTheme.h"
 #include "ui/themes/SystemTheme.h"
@@ -32,6 +33,7 @@ ThemeManager::ThemeManager(MainWindow* mainWindow)
 {
     m_mainWindow = mainWindow;
     initializeThemes();
+    initializeCatPacks();
 }
 
 /// @brief Adds the Theme to the list of themes
@@ -111,6 +113,16 @@ QList<ITheme*> ThemeManager::getValidApplicationThemes()
     return ret;
 }
 
+QList<CatPack*> ThemeManager::getValidCatPacks()
+{
+    QList<CatPack*> ret;
+    ret.reserve(m_catPacks.size());
+    for (auto&& [id, theme] : m_catPacks) {
+        ret.append(theme.get());
+    }
+    return ret;
+}
+
 void ThemeManager::setIconTheme(const QString& name)
 {
     QIcon::setThemeName(name);
@@ -137,19 +149,63 @@ void ThemeManager::setApplicationTheme(const QString& name, bool initial)
     }
 }
 
-QString ThemeManager::getCatImage(QString catName)
+QString ThemeManager::getCatPack(QString catName)
+{
+    auto catIter = m_catPacks.find(!catName.isEmpty() ? catName : APPLICATION->settings()->get("BackgroundCat").toString());
+    if (catIter != m_catPacks.end()) {
+        auto& catPack = catIter->second;
+        themeDebugLog() << "applying catpack" << catPack->id();
+        return catPack->path();
+    } else {
+        themeWarningLog() << "Tried to get invalid catPack:" << catName;
+    }
+
+    return m_catPacks.begin()->second->path();
+}
+
+QString ThemeManager::addCatPack(std::unique_ptr<CatPack> catPack)
+{
+    QString id = catPack->id();
+    m_catPacks.emplace(id, std::move(catPack));
+    return id;
+}
+
+void ThemeManager::initializeCatPacks()
 {
-    QDateTime now = QDateTime::currentDateTime();
-    QDateTime birthday(QDate(now.date().year(), 11, 30), QTime(0, 0));
-    QDateTime xmas(QDate(now.date().year(), 12, 25), QTime(0, 0));
-    QDateTime halloween(QDate(now.date().year(), 10, 31), QTime(0, 0));
-    QString cat = !catName.isEmpty() ? catName : APPLICATION->settings()->get("BackgroundCat").toString();
-    if (std::abs(now.daysTo(xmas)) <= 4) {
-        cat += "-xmas";
-    } else if (std::abs(now.daysTo(halloween)) <= 4) {
-        cat += "-spooky";
-    } else if (std::abs(now.daysTo(birthday)) <= 12) {
-        cat += "-bday";
+    QList<std::pair<QString, QString>> defaultCats{ { "kitteh", QObject::tr("Background Cat (from MultiMC)") },
+                                                    { "rory", QObject::tr("Rory ID 11 (drawn by Ashtaka)") },
+                                                    { "rory-flat", QObject::tr("Rory ID 11 (flat edition, drawn by Ashtaka)") },
+                                                    { "teawie", QObject::tr("Teawie (drawn by SympathyTea)") } };
+    for (auto [id, name] : defaultCats) {
+        addCatPack(std::unique_ptr<CatPack>(new BasicCatPack(id, name)));
+    }
+    QDir catpacksDir("./catpacks/");
+    QString catpacksFolder = catpacksDir.absoluteFilePath("");
+    themeDebugLog() << "CatPacks Folder Path: " << catpacksFolder;
+
+    auto loadFiles = [this](QDir dir) {
+        // Load image files directly
+        QDirIterator ImageFileIterator(dir.absoluteFilePath(""), { "*.png", "*.gif", "*.jpg", "*.apng", "*.jxl", "*.avif" }, QDir::Files);
+        while (ImageFileIterator.hasNext()) {
+            QFile customCatFile(ImageFileIterator.next());
+            QFileInfo customCatFileInfo(customCatFile);
+            themeDebugLog() << "Loading QSS Theme from:" << customCatFileInfo.absoluteFilePath();
+            addCatPack(std::unique_ptr<CatPack>(new FileCatPack(customCatFileInfo)));
+        }
+    };
+
+    loadFiles(catpacksDir);
+
+    QDirIterator directoryIterator(catpacksFolder, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
+    while (directoryIterator.hasNext()) {
+        QDir dir(directoryIterator.next());
+        QFileInfo manifest(dir.absoluteFilePath("catpack.json"));
+        if (manifest.exists()) {
+            // Load background manifest
+            themeDebugLog() << "Loading background manifest from:" << manifest.absoluteFilePath();
+            addCatPack(std::unique_ptr<CatPack>(new JsonCatPack(manifest)));
+        } else {
+            loadFiles(dir);
+        }
     }
-    return cat;
 }
diff --git a/launcher/ui/themes/ThemeManager.h b/launcher/ui/themes/ThemeManager.h
index 87f36d9c..bc0d31cd 100644
--- a/launcher/ui/themes/ThemeManager.h
+++ b/launcher/ui/themes/ThemeManager.h
@@ -20,6 +20,7 @@
 #include <QString>
 
 #include "ui/MainWindow.h"
+#include "ui/themes/CatPack.h"
 #include "ui/themes/ITheme.h"
 
 inline auto themeDebugLog()
@@ -40,18 +41,20 @@ class ThemeManager {
     void applyCurrentlySelectedTheme(bool initial = false);
     void setApplicationTheme(const QString& name, bool initial = false);
 
-    /// <summary>
-    /// Returns the cat based on selected cat and with events (Birthday, XMas, etc.)
-    /// </summary>
-    /// <param name="catName">Optional, if you need a specific cat.</param>
-    /// <returns></returns>
-    static QString getCatImage(QString catName = "");
+    /// @brief Returns the background based on selected and with events (Birthday, XMas, etc.)
+    /// @param catName Optional, if you need a specific background.
+    /// @return
+    QString getCatPack(QString catName = "");
+    QList<CatPack*> getValidCatPacks();
 
    private:
     std::map<QString, std::unique_ptr<ITheme>> m_themes;
+    std::map<QString, std::unique_ptr<CatPack>> m_catPacks;
     MainWindow* m_mainWindow;
 
     void initializeThemes();
+    void initializeCatPacks();
     QString addTheme(std::unique_ptr<ITheme> theme);
     ITheme* getTheme(QString themeId);
+    QString addCatPack(std::unique_ptr<CatPack> catPack);
 };
diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.cpp b/launcher/ui/widgets/ThemeCustomizationWidget.cpp
index dcf13303..e2c5ce3d 100644
--- a/launcher/ui/widgets/ThemeCustomizationWidget.cpp
+++ b/launcher/ui/widgets/ThemeCustomizationWidget.cpp
@@ -95,9 +95,14 @@ void ThemeCustomizationWidget::applyWidgetTheme(int index) {
     emit currentWidgetThemeChanged(index);
 }
 
-void ThemeCustomizationWidget::applyCatTheme(int index) {
+void ThemeCustomizationWidget::applyCatTheme(int index)
+{
     auto settings = APPLICATION->settings();
-    settings->set("BackgroundCat", m_catOptions[index].first);
+    auto originalCat = settings->get("BackgroundCat").toString();
+    auto newCat = ui->backgroundCatComboBox->currentData().toString();
+    if (originalCat != newCat) {
+        settings->set("BackgroundCat", newCat);
+    }
 
     emit currentCatChanged(index);
 }
@@ -135,10 +140,10 @@ void ThemeCustomizationWidget::loadSettings()
     }
 
     auto cat = settings->get("BackgroundCat").toString();
-    for (auto& catFromList : m_catOptions) {
-        QIcon catIcon = QIcon(QString(":/backgrounds/%1").arg(ThemeManager::getCatImage(catFromList.first)));
-        ui->backgroundCatComboBox->addItem(catIcon, catFromList.second);
-        if (cat == catFromList.first) {
+    for (auto& catFromList : APPLICATION->getValidCatPacks()) {
+        QIcon catIcon = QIcon(QString("%1").arg(catFromList->path()));
+        ui->backgroundCatComboBox->addItem(catIcon, catFromList->name(), catFromList->id());
+        if (cat == catFromList->id()) {
             ui->backgroundCatComboBox->setCurrentIndex(ui->backgroundCatComboBox->count() - 1);
         }
     }
diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.h b/launcher/ui/widgets/ThemeCustomizationWidget.h
index d955a266..be204e57 100644
--- a/launcher/ui/widgets/ThemeCustomizationWidget.h
+++ b/launcher/ui/widgets/ThemeCustomizationWidget.h
@@ -53,25 +53,17 @@ class ThemeCustomizationWidget : public QWidget {
    private:
     Ui::ThemeCustomizationWidget* ui;
 
-    //TODO finish implementing
-    QList<std::pair<QString, QString>> m_iconThemeOptions{ 
-        { "pe_colored",     QObject::tr("Simple (Colored Icons)") }, 
-        { "pe_light",       QObject::tr("Simple (Light Icons)") },     
-        { "pe_dark",        QObject::tr("Simple (Dark Icons)") },
-        { "pe_blue",        QObject::tr("Simple (Blue Icons)") },    
-        { "breeze_light",   QObject::tr("Breeze Light") }, 
-        { "breeze_dark",    QObject::tr("Breeze Dark") },
-        { "OSX",            QObject::tr("OSX") },        
-        { "iOS",            QObject::tr("iOS") },          
-        { "flat",           QObject::tr("Flat") },
-        { "flat_white",     QObject::tr("Flat (White)") }, 
-        { "multimc",        QObject::tr("Legacy") },      
-        { "custom",         QObject::tr("Custom") } 
-    };
-    QList<std::pair<QString, QString>> m_catOptions{ 
-        { "kitteh",     QObject::tr("Background Cat (from MultiMC)") }, 
-        { "rory",       QObject::tr("Rory ID 11 (drawn by Ashtaka)") }, 
-        { "rory-flat",  QObject::tr("Rory ID 11 (flat edition, drawn by Ashtaka)") },
-        { "teawie",     QObject::tr("Teawie (drawn by SympathyTea)") }
-    };
+    // TODO finish implementing
+    QList<std::pair<QString, QString>> m_iconThemeOptions{ { "pe_colored", QObject::tr("Simple (Colored Icons)") },
+                                                           { "pe_light", QObject::tr("Simple (Light Icons)") },
+                                                           { "pe_dark", QObject::tr("Simple (Dark Icons)") },
+                                                           { "pe_blue", QObject::tr("Simple (Blue Icons)") },
+                                                           { "breeze_light", QObject::tr("Breeze Light") },
+                                                           { "breeze_dark", QObject::tr("Breeze Dark") },
+                                                           { "OSX", QObject::tr("OSX") },
+                                                           { "iOS", QObject::tr("iOS") },
+                                                           { "flat", QObject::tr("Flat") },
+                                                           { "flat_white", QObject::tr("Flat (White)") },
+                                                           { "multimc", QObject::tr("Legacy") },
+                                                           { "custom", QObject::tr("Custom") } };
 };
-- 
cgit 


From 84c63f4f017324b42c2470fb2e7a1ac5858fcaa0 Mon Sep 17 00:00:00 2001
From: Trial97 <alexandru.tripon97@gmail.com>
Date: Sun, 25 Jun 2023 14:11:41 +0300
Subject: Added plantxt export

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
---
 launcher/CMakeLists.txt                            |  10 +-
 launcher/minecraft/mod/Mod.cpp                     |   7 +
 launcher/minecraft/mod/Mod.h                       |   1 +
 launcher/modplatform/ModIndex.cpp                  |   8 +-
 launcher/modplatform/ModIndex.h                    |   1 +
 .../modplatform/helpers/ExportModsToStringTask.cpp | 114 ------------
 .../modplatform/helpers/ExportModsToStringTask.h   |  33 ----
 launcher/modplatform/helpers/ExportToModList.cpp   | 122 +++++++++++++
 launcher/modplatform/helpers/ExportToModList.h     |  33 ++++
 launcher/ui/MainWindow.cpp                         |   8 +-
 launcher/ui/MainWindow.h                           |   2 +-
 launcher/ui/MainWindow.ui                          |   4 +-
 launcher/ui/dialogs/ExportModsToStringDialog.cpp   | 122 -------------
 launcher/ui/dialogs/ExportModsToStringDialog.h     |  46 -----
 launcher/ui/dialogs/ExportModsToStringDialog.ui    | 171 ------------------
 launcher/ui/dialogs/ExportToModListDialog.cpp      | 171 ++++++++++++++++++
 launcher/ui/dialogs/ExportToModListDialog.h        |  52 ++++++
 launcher/ui/dialogs/ExportToModListDialog.ui       | 199 +++++++++++++++++++++
 18 files changed, 605 insertions(+), 499 deletions(-)
 delete mode 100644 launcher/modplatform/helpers/ExportModsToStringTask.cpp
 delete mode 100644 launcher/modplatform/helpers/ExportModsToStringTask.h
 create mode 100644 launcher/modplatform/helpers/ExportToModList.cpp
 create mode 100644 launcher/modplatform/helpers/ExportToModList.h
 delete mode 100644 launcher/ui/dialogs/ExportModsToStringDialog.cpp
 delete mode 100644 launcher/ui/dialogs/ExportModsToStringDialog.h
 delete mode 100644 launcher/ui/dialogs/ExportModsToStringDialog.ui
 create mode 100644 launcher/ui/dialogs/ExportToModListDialog.cpp
 create mode 100644 launcher/ui/dialogs/ExportToModListDialog.h
 create mode 100644 launcher/ui/dialogs/ExportToModListDialog.ui

(limited to 'launcher/CMakeLists.txt')

diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index aab7e90d..a4e82576 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -490,8 +490,8 @@ set(API_SOURCES
     modplatform/helpers/OverrideUtils.h
     modplatform/helpers/OverrideUtils.cpp
 
-    modplatform/helpers/ExportModsToStringTask.h
-    modplatform/helpers/ExportModsToStringTask.cpp
+    modplatform/helpers/ExportToModList.h
+    modplatform/helpers/ExportToModList.cpp
 )
 
 set(FTB_SOURCES
@@ -913,8 +913,8 @@ SET(LAUNCHER_SOURCES
     ui/dialogs/ExportInstanceDialog.h
     ui/dialogs/ExportMrPackDialog.cpp
     ui/dialogs/ExportMrPackDialog.h
-    ui/dialogs/ExportModsToStringDialog.cpp
-    ui/dialogs/ExportModsToStringDialog.h
+    ui/dialogs/ExportToModListDialog.cpp
+    ui/dialogs/ExportToModListDialog.h
     ui/dialogs/IconPickerDialog.cpp
     ui/dialogs/IconPickerDialog.h
     ui/dialogs/ImportResourceDialog.cpp
@@ -1062,7 +1062,7 @@ qt_wrap_ui(LAUNCHER_UI
     ui/dialogs/SkinUploadDialog.ui
     ui/dialogs/ExportInstanceDialog.ui
     ui/dialogs/ExportMrPackDialog.ui
-    ui/dialogs/ExportModsToStringDialog.ui
+    ui/dialogs/ExportToModListDialog.ui
     ui/dialogs/IconPickerDialog.ui
     ui/dialogs/ImportResourceDialog.ui
     ui/dialogs/MSALoginDialog.ui
diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp
index e613ddeb..d5b96bad 100644
--- a/launcher/minecraft/mod/Mod.cpp
+++ b/launcher/minecraft/mod/Mod.cpp
@@ -166,6 +166,13 @@ auto Mod::homeurl() const -> QString
     return details().homeurl;
 }
 
+auto Mod::metaurl() const -> QString
+{
+    if (metadata() == nullptr)
+        return homeurl();
+    return ModPlatform::getMetaURL(metadata()->provider, metadata()->project_id);
+}
+
 auto Mod::description() const -> QString
 {
     return details().description;
diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h
index d4e419f4..d6272f4d 100644
--- a/launcher/minecraft/mod/Mod.h
+++ b/launcher/minecraft/mod/Mod.h
@@ -70,6 +70,7 @@ public:
     auto provider()    const -> std::optional<QString>;
     auto licenses()     const -> const QList<ModLicense>&;
     auto issueTracker() const -> QString;
+    auto metaurl()     const -> QString;
 
     /** Get the intneral path to the mod's icon file*/
     QString iconPath() const { return m_local_details.icon_file; };
diff --git a/launcher/modplatform/ModIndex.cpp b/launcher/modplatform/ModIndex.cpp
index 6a507caf..a1c4d891 100644
--- a/launcher/modplatform/ModIndex.cpp
+++ b/launcher/modplatform/ModIndex.cpp
@@ -70,11 +70,17 @@ auto ProviderCapabilities::hash(ResourceProvider p, QIODevice* device, QString t
     }
 
     QCryptographicHash hash(algo);
-    if(!hash.addData(device))
+    if (!hash.addData(device))
         qCritical() << "Failed to read JAR to create hash!";
 
     Q_ASSERT(hash.result().length() == hash.hashLength(algo));
     return { hash.result().toHex() };
 }
 
+QString getMetaURL(ResourceProvider provider, QVariant projectID)
+{
+    return ((provider == ModPlatform::ResourceProvider::FLAME) ? "https://www.curseforge.com/projects/" : "https://modrinth.com/mod/") +
+           projectID.toString();
+}
+
 }  // namespace ModPlatform
diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h
index 3b0a03a1..3f51e700 100644
--- a/launcher/modplatform/ModIndex.h
+++ b/launcher/modplatform/ModIndex.h
@@ -144,6 +144,7 @@ inline auto getOverrideDeps() -> QList<OverrideDep>
              { "qvIfYCYJ", "P7dR8mSH", "API", ModPlatform::ResourceProvider::MODRINTH },
              { "lwVhp9o5", "Ha28R6CL", "KotlinLibraries", ModPlatform::ResourceProvider::MODRINTH } };
 };
+QString getMetaURL(ResourceProvider provider, QVariant projectID);
 
 }  // namespace ModPlatform
 
diff --git a/launcher/modplatform/helpers/ExportModsToStringTask.cpp b/launcher/modplatform/helpers/ExportModsToStringTask.cpp
deleted file mode 100644
index e7be5ce1..00000000
--- a/launcher/modplatform/helpers/ExportModsToStringTask.cpp
+++ /dev/null
@@ -1,114 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-only
-/*
- *  Prism Launcher - Minecraft Launcher
- *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, version 3.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-#include "ExportModsToStringTask.h"
-#include "modplatform/ModIndex.h"
-
-namespace ExportToString {
-QString ExportModsToStringTask(QList<Mod*> mods, Formats format, OptionalData extraData)
-{
-    switch (format) {
-        case HTML: {
-            QStringList lines;
-            for (auto mod : mods) {
-                auto meta = mod->metadata();
-                auto modName = mod->name();
-                if (extraData & Url) {
-                    auto url = mod->homeurl();
-                    if (meta != nullptr) {
-                        url = (meta->provider == ModPlatform::ResourceProvider::FLAME ? "https://www.curseforge.com/minecraft/mc-mods/"
-                                                                                      : "https://modrinth.com/mod/") +
-                              meta->slug.remove(".pw.toml");
-                    }
-                    if (!url.isEmpty())
-                        modName = QString("<a href=\"%1\">%2</a>").arg(url, modName);
-                }
-                auto line = modName;
-                if (extraData & Version) {
-                    auto ver = mod->version();
-                    if (ver.isEmpty() && meta != nullptr)
-                        ver = meta->version().toString();
-                    if (!ver.isEmpty())
-                        line += QString("[%1]").arg(ver);
-                }
-                if (extraData & Authors && !mod->authors().isEmpty())
-                    line += " by " + mod->authors().join(", ");
-                lines.append(QString("<ul>%1</ul>").arg(line));
-            }
-            return QString("<html><body>\n\t%1\n</body></html>").arg(lines.join("\n\t"));
-        }
-        case MARKDOWN: {
-            QStringList lines;
-            for (auto mod : mods) {
-                auto meta = mod->metadata();
-                auto modName = mod->name();
-                if (extraData & Url) {
-                    auto url = mod->homeurl();
-                    if (meta != nullptr) {
-                        url = (meta->provider == ModPlatform::ResourceProvider::FLAME ? "https://www.curseforge.com/minecraft/mc-mods/"
-                                                                                      : "https://modrinth.com/mod/") +
-                              meta->slug.remove(".pw.toml");
-                    }
-                    if (!url.isEmpty())
-                        modName = QString("[%1](%2)").arg(modName, url);
-                }
-                auto line = modName;
-                if (extraData & Version) {
-                    auto ver = mod->version();
-                    if (ver.isEmpty() && meta != nullptr)
-                        ver = meta->version().toString();
-                    if (!ver.isEmpty())
-                        line += QString("[%1]").arg(ver);
-                }
-                if (extraData & Authors && !mod->authors().isEmpty())
-                    line += " by " + mod->authors().join(", ");
-                lines << line;
-            }
-            return lines.join("\n");
-        }
-        default: {
-            return QString("unknown format:%1").arg(format);
-        }
-    }
-}
-
-QString ExportModsToStringTask(QList<Mod*> mods, QString lineTemplate)
-{
-    QStringList lines;
-    for (auto mod : mods) {
-        auto meta = mod->metadata();
-        auto modName = mod->name();
-
-        auto url = mod->homeurl();
-        if (meta != nullptr) {
-            url = (meta->provider == ModPlatform::ResourceProvider::FLAME ? "https://www.curseforge.com/minecraft/mc-mods/"
-                                                                          : "https://modrinth.com/mod/") +
-                  meta->slug.remove(".pw.toml");
-        }
-        auto ver = mod->version();
-        if (ver.isEmpty() && meta != nullptr)
-            ver = meta->version().toString();
-        auto authors = mod->authors().join(", ");
-        lines << QString(lineTemplate)
-                     .replace("{name}", modName)
-                     .replace("{url}", url)
-                     .replace("{version}", ver)
-                     .replace("{authors}", authors);
-    }
-    return lines.join("\n");
-}
-}  // namespace ExportToString
\ No newline at end of file
diff --git a/launcher/modplatform/helpers/ExportModsToStringTask.h b/launcher/modplatform/helpers/ExportModsToStringTask.h
deleted file mode 100644
index 756c69f7..00000000
--- a/launcher/modplatform/helpers/ExportModsToStringTask.h
+++ /dev/null
@@ -1,33 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-only
-/*
- *  Prism Launcher - Minecraft Launcher
- *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, version 3.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-#pragma once
-#include <qlist.h>
-#include <QString>
-#include "minecraft/mod/Mod.h"
-
-namespace ExportToString {
-
-enum Formats { HTML, MARKDOWN };
-enum OptionalData {
-    Authors = 1 << 0,
-    Url = 1 << 1,
-    Version = 1 << 2,
-};
-QString ExportModsToStringTask(QList<Mod*> mods, Formats format, OptionalData extraData);
-QString ExportModsToStringTask(QList<Mod*> mods, QString lineTemplate);
-}  // namespace ExportToString
diff --git a/launcher/modplatform/helpers/ExportToModList.cpp b/launcher/modplatform/helpers/ExportToModList.cpp
new file mode 100644
index 00000000..5e01367f
--- /dev/null
+++ b/launcher/modplatform/helpers/ExportToModList.cpp
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ *  Prism Launcher - Minecraft Launcher
+ *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, version 3.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+#include "ExportToModList.h"
+
+namespace ExportToModList {
+QString ExportToModList(QList<Mod*> mods, Formats format, OptionalData extraData)
+{
+    switch (format) {
+        case HTML: {
+            QStringList lines;
+            for (auto mod : mods) {
+                auto meta = mod->metadata();
+                auto modName = mod->name();
+                if (extraData & Url) {
+                    auto url = mod->metaurl();
+                    if (!url.isEmpty())
+                        modName = QString("<a href=\"%1\">%2</a>").arg(url, modName);
+                }
+                auto line = modName;
+                if (extraData & Version) {
+                    auto ver = mod->version();
+                    if (ver.isEmpty() && meta != nullptr)
+                        ver = meta->version().toString();
+                    if (!ver.isEmpty())
+                        line += QString("[%1]").arg(ver);
+                }
+                if (extraData & Authors && !mod->authors().isEmpty())
+                    line += " by " + mod->authors().join(", ");
+                lines.append(QString("<ul>%1</ul>").arg(line));
+            }
+            return QString("<html><body><li>\n\t%1\n</li></body></html>").arg(lines.join("\n\t"));
+        }
+        case MARKDOWN: {
+            QStringList lines;
+            for (auto mod : mods) {
+                auto meta = mod->metadata();
+                auto modName = mod->name();
+                if (extraData & Url) {
+                    auto url = mod->metaurl();
+                    if (!url.isEmpty())
+                        modName = QString("[%1](%2)").arg(modName, url);
+                }
+                auto line = modName;
+                if (extraData & Version) {
+                    auto ver = mod->version();
+                    if (ver.isEmpty() && meta != nullptr)
+                        ver = meta->version().toString();
+                    if (!ver.isEmpty())
+                        line += QString("[%1]").arg(ver);
+                }
+                if (extraData & Authors && !mod->authors().isEmpty())
+                    line += " by " + mod->authors().join(", ");
+                lines << "- " + line;
+            }
+            return lines.join("\n");
+        }
+        case PLAINTXT: {
+            QStringList lines;
+            for (auto mod : mods) {
+                auto meta = mod->metadata();
+                auto modName = mod->name();
+
+                auto line = "name: " + modName + ";";
+                if (extraData & Url) {
+                    auto url = mod->metaurl();
+                    if (!url.isEmpty())
+                        line += " url: " + url + ";";
+                }
+                if (extraData & Version) {
+                    auto ver = mod->version();
+                    if (ver.isEmpty() && meta != nullptr)
+                        ver = meta->version().toString();
+                    if (!ver.isEmpty())
+                        line += " version: " + QString("[%1]").arg(ver) + ";";
+                }
+                if (extraData & Authors && !mod->authors().isEmpty())
+                    line += " authors " + mod->authors().join(", ") + ";";
+                lines << line;
+            }
+            return lines.join("\n");
+        }
+        default: {
+            return QString("unknown format:%1").arg(format);
+        }
+    }
+}
+
+QString ExportToModList(QList<Mod*> mods, QString lineTemplate)
+{
+    QStringList lines;
+    for (auto mod : mods) {
+        auto meta = mod->metadata();
+        auto modName = mod->name();
+        auto url = mod->metaurl();
+        auto ver = mod->version();
+        if (ver.isEmpty() && meta != nullptr)
+            ver = meta->version().toString();
+        auto authors = mod->authors().join(", ");
+        lines << QString(lineTemplate)
+                     .replace("{name}", modName)
+                     .replace("{url}", url)
+                     .replace("{version}", ver)
+                     .replace("{authors}", authors);
+    }
+    return lines.join("\n");
+}
+}  // namespace ExportToModList
\ No newline at end of file
diff --git a/launcher/modplatform/helpers/ExportToModList.h b/launcher/modplatform/helpers/ExportToModList.h
new file mode 100644
index 00000000..9ff8d25a
--- /dev/null
+++ b/launcher/modplatform/helpers/ExportToModList.h
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ *  Prism Launcher - Minecraft Launcher
+ *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, version 3.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+#pragma once
+#include <qlist.h>
+#include <QString>
+#include "minecraft/mod/Mod.h"
+
+namespace ExportToModList {
+
+enum Formats { HTML, MARKDOWN, PLAINTXT, CUSTOM };
+enum OptionalData {
+    Authors = 1 << 0,
+    Url = 1 << 1,
+    Version = 1 << 2,
+};
+QString ExportToModList(QList<Mod*> mods, Formats format, OptionalData extraData);
+QString ExportToModList(QList<Mod*> mods, QString lineTemplate);
+}  // namespace ExportToModList
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 02ea30c3..37f4d4ed 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -43,7 +43,7 @@
 #include "FileSystem.h"
 
 #include "MainWindow.h"
-#include "ui/dialogs/ExportModsToStringDialog.h"
+#include "ui/dialogs/ExportToModListDialog.h"
 #include "ui_MainWindow.h"
 
 #include <QVariant>
@@ -206,7 +206,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
         auto exportInstanceMenu = new QMenu(this);
         exportInstanceMenu->addAction(ui->actionExportInstanceZip);
         exportInstanceMenu->addAction(ui->actionExportInstanceMrPack);
-        exportInstanceMenu->addAction(ui->actionExportInstanceToString);
+        exportInstanceMenu->addAction(ui->actionExportInstanceToModList);
         ui->actionExportInstance->setMenu(exportInstanceMenu);
     }
 
@@ -1418,10 +1418,10 @@ void MainWindow::on_actionExportInstanceMrPack_triggered()
     }
 }
 
-void MainWindow::on_actionExportInstanceToString_triggered()
+void MainWindow::on_actionExportInstanceToModList_triggered()
 {
     if (m_selectedInstance) {
-        ExportModsToStringDialog dlg(m_selectedInstance, this);
+        ExportToModListDialog dlg(m_selectedInstance, this);
         dlg.exec();
     }
 }
diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h
index 9b38810f..f62e39e1 100644
--- a/launcher/ui/MainWindow.h
+++ b/launcher/ui/MainWindow.h
@@ -157,7 +157,7 @@ private slots:
     inline void on_actionExportInstance_triggered() { on_actionExportInstanceZip_triggered(); }
     void on_actionExportInstanceZip_triggered();
     void on_actionExportInstanceMrPack_triggered();
-    void on_actionExportInstanceToString_triggered();
+    void on_actionExportInstanceToModList_triggered();
 
     void on_actionRenameInstance_triggered();
 
diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui
index ecc7e236..027742ba 100644
--- a/launcher/ui/MainWindow.ui
+++ b/launcher/ui/MainWindow.ui
@@ -479,12 +479,12 @@
     <string>Modrinth (mrpack)</string>
    </property>
   </action>
-  <action name="actionExportInstanceToString">
+  <action name="actionExportInstanceToModList">
    <property name="icon">
     <iconset theme="new"/>
    </property>
    <property name="text">
-    <string>Text</string>
+    <string>ModList (txt)</string>
    </property>
   </action>
   <action name="actionCreateInstanceShortcut">
diff --git a/launcher/ui/dialogs/ExportModsToStringDialog.cpp b/launcher/ui/dialogs/ExportModsToStringDialog.cpp
deleted file mode 100644
index 29d69918..00000000
--- a/launcher/ui/dialogs/ExportModsToStringDialog.cpp
+++ /dev/null
@@ -1,122 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-only
-/*
- *  Prism Launcher - Minecraft Launcher
- *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, version 3.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-#include "ExportModsToStringDialog.h"
-#include <QCheckBox>
-#include <QComboBox>
-#include <QTextEdit>
-#include "minecraft/MinecraftInstance.h"
-#include "minecraft/mod/ModFolderModel.h"
-#include "modplatform/helpers/ExportModsToStringTask.h"
-#include "ui_ExportModsToStringDialog.h"
-
-#include <QFileDialog>
-#include <QFileSystemModel>
-#include <QJsonDocument>
-#include <QMessageBox>
-#include <QPushButton>
-
-ExportModsToStringDialog::ExportModsToStringDialog(InstancePtr instance, QWidget* parent)
-    : QDialog(parent), m_template_selected(false), ui(new Ui::ExportModsToStringDialog)
-{
-    ui->setupUi(this);
-    ui->templateGroup->setDisabled(true);
-
-    MinecraftInstance* mcInstance = dynamic_cast<MinecraftInstance*>(instance.get());
-    if (mcInstance) {
-        mcInstance->loaderModList()->update();
-        connect(mcInstance->loaderModList().get(), &ModFolderModel::updateFinished, this, [this, mcInstance]() {
-            m_allMods = mcInstance->loaderModList()->allMods();
-            triggerImp();
-        });
-    }
-
-    connect(ui->formatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ExportModsToStringDialog::formatChanged);
-    connect(ui->authorsCheckBox, &QCheckBox::stateChanged, this, &ExportModsToStringDialog::trigger);
-    connect(ui->versionCheckBox, &QCheckBox::stateChanged, this, &ExportModsToStringDialog::trigger);
-    connect(ui->urlCheckBox, &QCheckBox::stateChanged, this, &ExportModsToStringDialog::trigger);
-    connect(ui->templateText, &QTextEdit::textChanged, this, &ExportModsToStringDialog::triggerImp);
-    connect(ui->copyButton, &QPushButton::clicked, this, [this](bool) {
-        this->ui->finalText->selectAll();
-        this->ui->finalText->copy();
-    });
-}
-
-ExportModsToStringDialog::~ExportModsToStringDialog()
-{
-    delete ui;
-}
-
-void ExportModsToStringDialog::formatChanged(int index)
-{
-    switch (index) {
-        case 0: {
-            ui->templateGroup->setDisabled(true);
-            ui->optionsGroup->setDisabled(false);
-            break;
-        }
-        case 1: {
-            ui->templateGroup->setDisabled(true);
-            ui->optionsGroup->setDisabled(false);
-            break;
-        }
-        case 2: {
-            ui->templateGroup->setDisabled(false);
-            ui->optionsGroup->setDisabled(true);
-            break;
-        }
-    }
-    triggerImp();
-}
-
-void ExportModsToStringDialog::triggerImp()
-{
-    auto format = ExportToString::HTML;
-    switch (ui->formatComboBox->currentIndex()) {
-        case 2: {
-            m_template_selected = true;
-            ui->finalText->setPlainText(ExportToString::ExportModsToStringTask(m_allMods, ui->templateText->toPlainText()));
-            return;
-        }
-        case 0: {
-            format = ExportToString::HTML;
-            break;
-        }
-        case 1: {
-            format = ExportToString::MARKDOWN;
-            break;
-        }
-        default: {
-            return;
-        }
-    }
-    auto opt = 0;
-    if (ui->authorsCheckBox->isChecked())
-        opt |= ExportToString::Authors;
-    if (ui->versionCheckBox->isChecked())
-        opt |= ExportToString::Version;
-    if (ui->urlCheckBox->isChecked())
-        opt |= ExportToString::Url;
-    ui->finalText->setPlainText(ExportToString::ExportModsToStringTask(m_allMods, format, static_cast<ExportToString::OptionalData>(opt)));
-    if (!m_template_selected) {
-        auto exampleLine = format == ExportToString::HTML ? "<ul><a href=\"{url}\">{name}</a>[{version}] by {authors}</ul>"
-                                                          : "[{name}]({url})[{version}] by {authors}";
-        if (ui->templateText->toPlainText() != exampleLine)
-            ui->templateText->setPlainText(exampleLine);
-    }
-}
diff --git a/launcher/ui/dialogs/ExportModsToStringDialog.h b/launcher/ui/dialogs/ExportModsToStringDialog.h
deleted file mode 100644
index d195d1ce..00000000
--- a/launcher/ui/dialogs/ExportModsToStringDialog.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-only
-/*
- *  Prism Launcher - Minecraft Launcher
- *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, version 3.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <QDialog>
-#include <QList>
-#include "BaseInstance.h"
-#include "minecraft/mod/Mod.h"
-
-namespace Ui {
-class ExportModsToStringDialog;
-}
-
-class ExportModsToStringDialog : public QDialog {
-    Q_OBJECT
-
-   public:
-    explicit ExportModsToStringDialog(InstancePtr instance, QWidget* parent = nullptr);
-    ~ExportModsToStringDialog();
-
-   protected slots:
-    void formatChanged(int index);
-    void triggerImp();
-    void trigger(int) { triggerImp(); };
-
-   private:
-    QList<Mod*> m_allMods;
-    bool m_template_selected;
-    Ui::ExportModsToStringDialog* ui;
-};
diff --git a/launcher/ui/dialogs/ExportModsToStringDialog.ui b/launcher/ui/dialogs/ExportModsToStringDialog.ui
deleted file mode 100644
index 4451a278..00000000
--- a/launcher/ui/dialogs/ExportModsToStringDialog.ui
+++ /dev/null
@@ -1,171 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>ExportModsToStringDialog</class>
- <widget class="QDialog" name="ExportModsToStringDialog">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>650</width>
-    <height>446</height>
-   </rect>
-  </property>
-  <property name="windowTitle">
-   <string>Export Modrinth Pack</string>
-  </property>
-  <property name="sizeGripEnabled">
-   <bool>true</bool>
-  </property>
-  <layout class="QVBoxLayout" name="verticalLayout_2">
-   <item>
-    <layout class="QVBoxLayout" name="verticalLayout" stretch="0,0">
-     <item>
-      <widget class="QGroupBox" name="groupBox_3">
-       <property name="title">
-        <string>Settings</string>
-       </property>
-       <layout class="QGridLayout" name="gridLayout">
-        <item row="0" column="0">
-         <widget class="QLabel" name="label">
-          <property name="frameShape">
-           <enum>QFrame::NoFrame</enum>
-          </property>
-          <property name="frameShadow">
-           <enum>QFrame::Plain</enum>
-          </property>
-          <property name="lineWidth">
-           <number>1</number>
-          </property>
-          <property name="text">
-           <string>Format</string>
-          </property>
-         </widget>
-        </item>
-        <item row="0" column="1">
-         <widget class="QComboBox" name="formatComboBox">
-          <item>
-           <property name="text">
-            <string>HTML</string>
-           </property>
-          </item>
-          <item>
-           <property name="text">
-            <string>Markdown</string>
-           </property>
-          </item>
-          <item>
-           <property name="text">
-            <string>Custom</string>
-           </property>
-          </item>
-         </widget>
-        </item>
-        <item row="1" column="0">
-         <widget class="QGroupBox" name="templateGroup">
-          <property name="title">
-           <string>Template</string>
-          </property>
-          <layout class="QVBoxLayout" name="verticalLayout_4">
-           <item>
-            <widget class="QTextEdit" name="templateText"/>
-           </item>
-          </layout>
-         </widget>
-        </item>
-        <item row="1" column="1">
-         <widget class="QGroupBox" name="optionsGroup">
-          <property name="title">
-           <string>Optional Info</string>
-          </property>
-          <layout class="QVBoxLayout" name="verticalLayout_5">
-           <item>
-            <widget class="QCheckBox" name="versionCheckBox">
-             <property name="text">
-              <string>Version</string>
-             </property>
-            </widget>
-           </item>
-           <item>
-            <widget class="QCheckBox" name="authorsCheckBox">
-             <property name="text">
-              <string>Authors</string>
-             </property>
-            </widget>
-           </item>
-           <item>
-            <widget class="QCheckBox" name="urlCheckBox">
-             <property name="text">
-              <string>URL</string>
-             </property>
-            </widget>
-           </item>
-          </layout>
-         </widget>
-        </item>
-       </layout>
-      </widget>
-     </item>
-     <item>
-      <widget class="QGroupBox" name="groupBox_4">
-       <property name="title">
-        <string>Result</string>
-       </property>
-       <layout class="QHBoxLayout" name="horizontalLayout">
-        <item>
-         <widget class="QPlainTextEdit" name="finalText">
-          <property name="minimumSize">
-           <size>
-            <width>0</width>
-            <height>143</height>
-           </size>
-          </property>
-          <property name="readOnly">
-           <bool>true</bool>
-          </property>
-         </widget>
-        </item>
-       </layout>
-      </widget>
-     </item>
-    </layout>
-   </item>
-   <item>
-    <layout class="QHBoxLayout" name="horizontalLayout_2">
-     <item>
-      <widget class="QPushButton" name="copyButton">
-       <property name="text">
-        <string>Copy</string>
-       </property>
-      </widget>
-     </item>
-     <item>
-      <widget class="QDialogButtonBox" name="buttonBox">
-       <property name="standardButtons">
-        <set>QDialogButtonBox::Ok</set>
-       </property>
-      </widget>
-     </item>
-    </layout>
-   </item>
-  </layout>
- </widget>
- <resources/>
- <connections>
-  <connection>
-   <sender>buttonBox</sender>
-   <signal>accepted()</signal>
-   <receiver>ExportModsToStringDialog</receiver>
-   <slot>accept()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>334</x>
-     <y>435</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>324</x>
-     <y>206</y>
-    </hint>
-   </hints>
-  </connection>
- </connections>
-</ui>
diff --git a/launcher/ui/dialogs/ExportToModListDialog.cpp b/launcher/ui/dialogs/ExportToModListDialog.cpp
new file mode 100644
index 00000000..0550725f
--- /dev/null
+++ b/launcher/ui/dialogs/ExportToModListDialog.cpp
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ *  Prism Launcher - Minecraft Launcher
+ *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, version 3.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "ExportToModListDialog.h"
+#include <QCheckBox>
+#include <QComboBox>
+#include <QTextEdit>
+#include "FileSystem.h"
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/mod/ModFolderModel.h"
+#include "modplatform/helpers/ExportToModList.h"
+#include "ui_ExportToModListDialog.h"
+
+#include <QFileDialog>
+#include <QFileSystemModel>
+#include <QJsonDocument>
+#include <QMessageBox>
+#include <QPushButton>
+
+ExportToModListDialog::ExportToModListDialog(InstancePtr instance, QWidget* parent)
+    : QDialog(parent), m_template_selected(false), name(instance->name()), ui(new Ui::ExportToModListDialog)
+{
+    ui->setupUi(this);
+    ui->templateGroup->setDisabled(true);
+
+    MinecraftInstance* mcInstance = dynamic_cast<MinecraftInstance*>(instance.get());
+    if (mcInstance) {
+        mcInstance->loaderModList()->update();
+        connect(mcInstance->loaderModList().get(), &ModFolderModel::updateFinished, this, [this, mcInstance]() {
+            m_allMods = mcInstance->loaderModList()->allMods();
+            triggerImp();
+        });
+    }
+
+    connect(ui->formatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ExportToModListDialog::formatChanged);
+    connect(ui->authorsCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger);
+    connect(ui->versionCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger);
+    connect(ui->urlCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger);
+    connect(ui->templateText, &QTextEdit::textChanged, this, &ExportToModListDialog::triggerImp);
+    connect(ui->copyButton, &QPushButton::clicked, this, [this](bool) {
+        this->ui->finalText->selectAll();
+        this->ui->finalText->copy();
+    });
+}
+
+ExportToModListDialog::~ExportToModListDialog()
+{
+    delete ui;
+}
+
+void ExportToModListDialog::formatChanged(int index)
+{
+    switch (index) {
+        case 0: {
+            ui->templateGroup->setDisabled(true);
+            ui->optionsGroup->setDisabled(false);
+            ui->resultText->show();
+            format = ExportToModList::HTML;
+            break;
+        }
+        case 1: {
+            ui->templateGroup->setDisabled(true);
+            ui->optionsGroup->setDisabled(false);
+            ui->resultText->show();
+            format = ExportToModList::MARKDOWN;
+            break;
+        }
+        case 2: {
+            ui->templateGroup->setDisabled(true);
+            ui->optionsGroup->setDisabled(false);
+            ui->resultText->hide();
+            format = ExportToModList::PLAINTXT;
+            break;
+        }
+        case 3: {
+            ui->templateGroup->setDisabled(false);
+            ui->optionsGroup->setDisabled(true);
+            ui->resultText->hide();
+            format = ExportToModList::CUSTOM;
+            break;
+        }
+    }
+    triggerImp();
+}
+
+void ExportToModListDialog::triggerImp()
+{
+    if (format == ExportToModList::CUSTOM) {
+        m_template_selected = true;
+        ui->finalText->setPlainText(ExportToModList::ExportToModList(m_allMods, ui->templateText->toPlainText()));
+        return;
+    }
+    auto opt = 0;
+    if (ui->authorsCheckBox->isChecked())
+        opt |= ExportToModList::Authors;
+    if (ui->versionCheckBox->isChecked())
+        opt |= ExportToModList::Version;
+    if (ui->urlCheckBox->isChecked())
+        opt |= ExportToModList::Url;
+    auto txt = ExportToModList::ExportToModList(m_allMods, format, static_cast<ExportToModList::OptionalData>(opt));
+    ui->finalText->setPlainText(txt);
+    QString exampleLine;
+    switch (format) {
+        case ExportToModList::HTML: {
+            exampleLine = "<ul><a href=\"{url}\">{name}</a>[{version}] by {authors}</ul>";
+            ui->resultText->setHtml(txt);
+            break;
+        }
+        case ExportToModList::MARKDOWN: {
+            exampleLine = "[{name}]({url})[{version}] by {authors}";
+            ui->resultText->setMarkdown(txt);
+            break;
+        }
+        case ExportToModList::PLAINTXT: {
+            exampleLine = "name: {name}; url: {url}; version: {version}; authors: {authors};";
+            break;
+        }
+        case ExportToModList::CUSTOM:
+            return;
+    }
+    if (!m_template_selected) {
+        if (ui->templateText->toPlainText() != exampleLine)
+            ui->templateText->setPlainText(exampleLine);
+    }
+}
+
+void ExportToModListDialog::done(int result)
+{
+    if (result == Accepted) {
+        const QString filename = FS::RemoveInvalidFilenameChars(name);
+        const QString output =
+            QFileDialog::getSaveFileName(this, tr("Export %1").arg(name), FS::PathCombine(QDir::homePath(), filename + extension()),
+                                         "File (*.txt *.html *.md)", nullptr);
+
+        if (output.isEmpty())
+            return;
+        FS::write(output, ui->finalText->toPlainText().toUtf8());
+    }
+
+    QDialog::done(result);
+}
+
+QString ExportToModListDialog::extension()
+{
+    switch (format) {
+        case ExportToModList::HTML:
+            return ".html";
+        case ExportToModList::MARKDOWN:
+            return ".md";
+        case ExportToModList::PLAINTXT:
+            return ".txt";
+        case ExportToModList::CUSTOM:
+            return ".txt";
+    }
+    return ".txt";
+}
diff --git a/launcher/ui/dialogs/ExportToModListDialog.h b/launcher/ui/dialogs/ExportToModListDialog.h
new file mode 100644
index 00000000..a7a6bcdc
--- /dev/null
+++ b/launcher/ui/dialogs/ExportToModListDialog.h
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ *  Prism Launcher - Minecraft Launcher
+ *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, version 3.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <QDialog>
+#include <QList>
+#include "BaseInstance.h"
+#include "minecraft/mod/Mod.h"
+#include "modplatform/helpers/ExportToModList.h"
+
+namespace Ui {
+class ExportToModListDialog;
+}
+
+class ExportToModListDialog : public QDialog {
+    Q_OBJECT
+
+   public:
+    explicit ExportToModListDialog(InstancePtr instance, QWidget* parent = nullptr);
+    ~ExportToModListDialog();
+
+    void done(int result) override;
+
+   protected slots:
+    void formatChanged(int index);
+    void triggerImp();
+    void trigger(int) { triggerImp(); };
+
+   private:
+    QString extension();
+    QList<Mod*> m_allMods;
+    bool m_template_selected;
+    QString name;
+    ExportToModList::Formats format = ExportToModList::Formats::HTML;
+    Ui::ExportToModListDialog* ui;
+};
diff --git a/launcher/ui/dialogs/ExportToModListDialog.ui b/launcher/ui/dialogs/ExportToModListDialog.ui
new file mode 100644
index 00000000..640b1766
--- /dev/null
+++ b/launcher/ui/dialogs/ExportToModListDialog.ui
@@ -0,0 +1,199 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ExportToModListDialog</class>
+ <widget class="QDialog" name="ExportToModListDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>650</width>
+    <height>446</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Export Pack to ModList</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout_2">
+   <item>
+    <layout class="QVBoxLayout" name="verticalLayout" stretch="0,0">
+     <item>
+      <widget class="QGroupBox" name="groupBox_3">
+       <property name="title">
+        <string>Settings</string>
+       </property>
+       <layout class="QGridLayout" name="gridLayout">
+        <item row="0" column="0">
+         <widget class="QLabel" name="label">
+          <property name="frameShape">
+           <enum>QFrame::NoFrame</enum>
+          </property>
+          <property name="frameShadow">
+           <enum>QFrame::Plain</enum>
+          </property>
+          <property name="lineWidth">
+           <number>1</number>
+          </property>
+          <property name="text">
+           <string>Format</string>
+          </property>
+         </widget>
+        </item>
+        <item row="0" column="1">
+         <widget class="QComboBox" name="formatComboBox">
+          <item>
+           <property name="text">
+            <string>HTML</string>
+           </property>
+          </item>
+          <item>
+           <property name="text">
+            <string>Markdown</string>
+           </property>
+          </item>
+          <item>
+           <property name="text">
+            <string>Plaintext</string>
+           </property>
+          </item>
+          <item>
+           <property name="text">
+            <string>Custom</string>
+           </property>
+          </item>
+         </widget>
+        </item>
+        <item row="1" column="0">
+         <widget class="QGroupBox" name="templateGroup">
+          <property name="title">
+           <string>Template</string>
+          </property>
+          <layout class="QVBoxLayout" name="verticalLayout_4">
+           <item>
+            <widget class="QTextEdit" name="templateText"/>
+           </item>
+          </layout>
+         </widget>
+        </item>
+        <item row="1" column="1">
+         <widget class="QGroupBox" name="optionsGroup">
+          <property name="title">
+           <string>Optional Info</string>
+          </property>
+          <layout class="QVBoxLayout" name="verticalLayout_5">
+           <item>
+            <widget class="QCheckBox" name="versionCheckBox">
+             <property name="text">
+              <string>Version</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QCheckBox" name="authorsCheckBox">
+             <property name="text">
+              <string>Authors</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QCheckBox" name="urlCheckBox">
+             <property name="text">
+              <string>URL</string>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </widget>
+        </item>
+       </layout>
+      </widget>
+     </item>
+     <item>
+      <widget class="QGroupBox" name="groupBox_4">
+       <property name="title">
+        <string>Result</string>
+       </property>
+       <layout class="QHBoxLayout" name="horizontalLayout">
+        <item>
+         <widget class="QPlainTextEdit" name="finalText">
+          <property name="minimumSize">
+           <size>
+            <width>0</width>
+            <height>143</height>
+           </size>
+          </property>
+          <property name="readOnly">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QTextBrowser" name="resultText">
+          <property name="openExternalLinks">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_2">
+     <item>
+      <widget class="QPushButton" name="copyButton">
+       <property name="text">
+        <string>Copy</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QDialogButtonBox" name="buttonBox">
+       <property name="standardButtons">
+        <set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>ExportToModListDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>334</x>
+     <y>435</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>324</x>
+     <y>206</y>
+    </hint>
+   </hints>
+  </connection>
+   <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>ExportToModListDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>324</x>
+     <y>390</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>324</x>
+     <y>206</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
-- 
cgit