From 4f0ec908ecc0c14c1ffe8e9631d031c754e3540a Mon Sep 17 00:00:00 2001
From: leo78913 <leo3758@riseup.net>
Date: Tue, 9 May 2023 23:29:16 -0300
Subject: feat: add a close button to the main toolbar when running on
 gamescope

Signed-off-by: leo78913 <leo3758@riseup.net>
---
 launcher/ui/MainWindow.cpp | 6 ++++++
 1 file changed, 6 insertions(+)

(limited to 'launcher/ui/MainWindow.cpp')

diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 72b7db64..9b8db1ae 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -219,6 +219,12 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
         // disabled until we have an instance selected
         ui->instanceToolBar->setEnabled(false);
         setInstanceActionsEnabled(false);
+
+        // add a close button at the end of the main toolbar when running on gamescope / steam deck
+        if (qgetenv("XDG_CURRENT_DESKTOP") == "gamescope") {
+            ui->mainToolBar->addAction(ui->actionCloseWindow);
+        }
+
     }
 
     // add the toolbar toggles to the view menu
-- 
cgit 


From d33de2e4277dfcd090a36c96e09148ea6a5d2e55 Mon Sep 17 00:00:00 2001
From: Trial97 <alexandru.tripon97@gmail.com>
Date: Thu, 8 Jun 2023 00:54:32 +0300
Subject: Made cat scalable

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
---
 launcher/ui/MainWindow.cpp                | 17 ++---------------
 launcher/ui/instanceview/InstanceView.cpp | 23 ++++++++++++++++++++++-
 launcher/ui/instanceview/InstanceView.h   |  8 ++++----
 3 files changed, 28 insertions(+), 20 deletions(-)

(limited to 'launcher/ui/MainWindow.cpp')

diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 834f57dd..4b77dd1c 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -901,21 +901,8 @@ void MainWindow::onCatToggled(bool state)
 
 void MainWindow::setCatBackground(bool enabled)
 {
-    if (enabled) {
-        view->setStyleSheet(QString(R"(
-InstanceView
-{
-    background-image: url(:/backgrounds/%1);
-    background-attachment: fixed;
-    background-clip: padding;
-    background-position: bottom right;
-    background-repeat: none;
-    background-color:palette(base);
-})")
-                                .arg(ThemeManager::getCatImage()));
-    } else {
-        view->setStyleSheet(QString());
-    }
+    view->setCatVisible(enabled);
+    view->viewport()->repaint();
 }
 
 void MainWindow::runModalTask(Task *task)
diff --git a/launcher/ui/instanceview/InstanceView.cpp b/launcher/ui/instanceview/InstanceView.cpp
index fbeffe35..4c83e94a 100644
--- a/launcher/ui/instanceview/InstanceView.cpp
+++ b/launcher/ui/instanceview/InstanceView.cpp
@@ -48,6 +48,7 @@
 #include <QAccessible>
 
 #include "VisualGroup.h"
+#include "ui/themes/ThemeManager.h"
 #include <QDebug>
 
 #include <Application.h>
@@ -73,6 +74,7 @@ InstanceView::InstanceView(QWidget *parent)
     setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
     setAcceptDrops(true);
     setAutoScroll(true);
+    setCatVisible(APPLICATION->settings()->get("TheCat").toBool());
 }
 
 InstanceView::~InstanceView()
@@ -498,12 +500,31 @@ void InstanceView::mouseDoubleClickEvent(QMouseEvent *event)
     }
 }
 
-void InstanceView::paintEvent(QPaintEvent *event)
+void InstanceView::setCatVisible(bool visible)
+{
+    m_catVisible = visible;
+    m_catPixmap.load(QString(":/backgrounds/%1").arg(ThemeManager::getCatImage()));
+}
+
+void InstanceView::paintEvent(QPaintEvent* event)
 {
     executeDelayedItemsLayout();
 
     QPainter painter(this->viewport());
 
+    if (m_catVisible) {
+        int widWidth = this->viewport()->width();
+        int widHeight = this->viewport()->height();
+        if (m_catPixmap.width() < widWidth)
+            widWidth = m_catPixmap.width();
+        if (m_catPixmap.height() < widHeight)
+            widHeight = m_catPixmap.height();
+        auto pixmap = m_catPixmap.scaled(widWidth, widHeight, Qt::KeepAspectRatio);
+        QRect rectOfPixmap = pixmap.rect();
+        rectOfPixmap.moveBottomRight(this->viewport()->rect().bottomRight());
+        painter.drawPixmap(rectOfPixmap.topLeft(), pixmap);
+    }
+
 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
     QStyleOptionViewItem option;
     initViewItemOption(&option);
diff --git a/launcher/ui/instanceview/InstanceView.h b/launcher/ui/instanceview/InstanceView.h
index ac338274..a9bd0bd7 100644
--- a/launcher/ui/instanceview/InstanceView.h
+++ b/launcher/ui/instanceview/InstanceView.h
@@ -85,10 +85,8 @@ public:
 
     virtual QRegion visualRegionForSelection(const QItemSelection &selection) const override;
 
-    int spacing() const
-    {
-        return m_spacing;
-    };
+    int spacing() const { return m_spacing; };
+    void setCatVisible(bool visible);
 
 public slots:
     virtual void updateGeometries() override;
@@ -139,6 +137,8 @@ private:
     int m_currentItemsPerRow = -1;
     int m_currentCursorColumn= -1;
     mutable QCache<int, QRect> geometryCache;
+    bool m_catVisible = false;
+    QPixmap m_catPixmap;
 
     // point where the currently active mouse action started in geometry coordinates
     QPoint m_pressedPosition;
-- 
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/ui/MainWindow.cpp')

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 58321f34915bd22ab8a2c195b64af3006d962be9 Mon Sep 17 00:00:00 2001
From: Trial97 <alexandru.tripon97@gmail.com>
Date: Thu, 22 Jun 2023 20:03:44 +0300
Subject: Added curseforge export

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
---
 launcher/CMakeLists.txt                            |   2 +
 launcher/modplatform/flame/FlamePackExportTask.cpp | 223 +++++++++++++++++++++
 launcher/modplatform/flame/FlamePackExportTask.h   |  74 +++++++
 launcher/ui/MainWindow.cpp                         |   9 +
 launcher/ui/MainWindow.h                           |   1 +
 launcher/ui/MainWindow.ui                          |   8 +
 launcher/ui/dialogs/ExportMrPackDialog.cpp         |  30 ++-
 launcher/ui/dialogs/ExportMrPackDialog.h           |   6 +-
 8 files changed, 342 insertions(+), 11 deletions(-)
 create mode 100644 launcher/modplatform/flame/FlamePackExportTask.cpp
 create mode 100644 launcher/modplatform/flame/FlamePackExportTask.h

(limited to 'launcher/ui/MainWindow.cpp')

diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index c7efdad8..df6c9012 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -517,6 +517,8 @@ set(FLAME_SOURCES
     modplatform/flame/FlameCheckUpdate.h
     modplatform/flame/FlameInstanceCreationTask.h
     modplatform/flame/FlameInstanceCreationTask.cpp
+    modplatform/flame/FlamePackExportTask.h
+    modplatform/flame/FlamePackExportTask.cpp
 )
 
 set(MODRINTH_SOURCES
diff --git a/launcher/modplatform/flame/FlamePackExportTask.cpp b/launcher/modplatform/flame/FlamePackExportTask.cpp
new file mode 100644
index 00000000..1ae13f72
--- /dev/null
+++ b/launcher/modplatform/flame/FlamePackExportTask.cpp
@@ -0,0 +1,223 @@
+// 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 "FlamePackExportTask.h"
+#include <QJsonArray>
+#include <QJsonObject>
+
+#include <QCryptographicHash>
+#include <QFileInfo>
+#include <QMessageBox>
+#include <QtConcurrentRun>
+#include "Json.h"
+#include "MMCZip.h"
+#include "minecraft/PackProfile.h"
+#include "minecraft/mod/ModFolderModel.h"
+#include "modplatform/helpers/ExportModsToStringTask.h"
+
+const QStringList FlamePackExportTask::PREFIXES({ "mods/", "coremods/", "resourcepacks/", "texturepacks/", "shaderpacks/" });
+const QStringList FlamePackExportTask::FILE_EXTENSIONS({ "jar", "litemod", "zip" });
+const QString FlamePackExportTask::TEMPLATE = "<li><a href={url}>{name}({authors})</a></li>";
+
+FlamePackExportTask::FlamePackExportTask(const QString& name,
+                                         const QString& version,
+                                         const QVariant& projectID,
+                                         InstancePtr instance,
+                                         const QString& output,
+                                         MMCZip::FilterFunction filter)
+    : name(name)
+    , version(version)
+    , projectID(projectID)
+    , instance(instance)
+    , mcInstance(dynamic_cast<MinecraftInstance*>(instance.get()))
+    , gameRoot(instance->gameRoot())
+    , output(output)
+    , filter(filter)
+{}
+
+void FlamePackExportTask::executeTask()
+{
+    setStatus(tr("Searching for files..."));
+    setProgress(0, 0);
+    collectFiles();
+}
+
+bool FlamePackExportTask::abort()
+{
+    if (task != nullptr) {
+        task->abort();
+        task = nullptr;
+        emitAborted();
+        return true;
+    }
+
+    if (buildZipFuture.isRunning()) {
+        buildZipFuture.cancel();
+        // NOTE: Here we don't do `emitAborted()` because it will be done when `buildZipFuture` actually cancels, which may not occur
+        // immediately.
+        return true;
+    }
+
+    return false;
+}
+
+void FlamePackExportTask::collectFiles()
+{
+    setAbortable(false);
+    QCoreApplication::processEvents();
+
+    files.clear();
+    if (!MMCZip::collectFileListRecursively(instance->gameRoot(), nullptr, &files, filter)) {
+        emitFailed(tr("Could not search for files"));
+        return;
+    }
+
+    resolvedFiles.clear();
+
+    mcInstance->loaderModList()->update();
+    connect(mcInstance->loaderModList().get(), &ModFolderModel::updateFinished, this, [this]() {
+        mods = mcInstance->loaderModList()->allMods();
+        buildZip();
+    });
+}
+
+void FlamePackExportTask::buildZip()
+{
+    setStatus(tr("Adding files..."));
+
+    buildZipFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]() {
+        QuaZip zip(output);
+        if (!zip.open(QuaZip::mdCreate)) {
+            QFile::remove(output);
+            return BuildZipResult(tr("Could not create file"));
+        }
+
+        if (buildZipFuture.isCanceled())
+            return BuildZipResult();
+
+        QuaZipFile indexFile(&zip);
+        if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("manifest.json"))) {
+            QFile::remove(output);
+            return BuildZipResult(tr("Could not create index"));
+        }
+        indexFile.write(generateIndex());
+
+        QuaZipFile modlist(&zip);
+        if (!modlist.open(QIODevice::WriteOnly, QuaZipNewInfo("modlist.html"))) {
+            QFile::remove(output);
+            return BuildZipResult(tr("Could not create index"));
+        }
+        QString content = ExportToString::ExportModsToStringTask(mods, TEMPLATE);
+        content = "<ul>" + content + "</ul>";
+        modlist.write(content.toUtf8());
+
+        size_t progress = 0;
+        for (const QFileInfo& file : files) {
+            if (buildZipFuture.isCanceled()) {
+                QFile::remove(output);
+                return BuildZipResult();
+            }
+
+            setProgress(progress, files.length());
+            const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath());
+            if (!resolvedFiles.contains(relative) && !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) {
+                QFile::remove(output);
+                return BuildZipResult(tr("Could not read and compress %1").arg(relative));
+            }
+            progress++;
+        }
+
+        zip.close();
+
+        if (zip.getZipError() != 0) {
+            QFile::remove(output);
+            return BuildZipResult(tr("A zip error occurred"));
+        }
+
+        return BuildZipResult();
+    });
+    connect(&buildZipWatcher, &QFutureWatcher<BuildZipResult>::finished, this, &FlamePackExportTask::finish);
+    buildZipWatcher.setFuture(buildZipFuture);
+}
+
+void FlamePackExportTask::finish()
+{
+    if (buildZipFuture.isCanceled())
+        emitAborted();
+    else {
+        const BuildZipResult result = buildZipFuture.result();
+        if (result.has_value())
+            emitFailed(result.value());
+        else
+            emitSucceeded();
+    }
+}
+
+QByteArray FlamePackExportTask::generateIndex()
+{
+    QJsonObject obj;
+    obj["manifestType"] = "minecraftModpack";
+    obj["manifestVersion"] = 1;
+    obj["name"] = name;
+    obj["version"] = version;
+    obj["author"] = author;
+    obj["projectID"] = projectID.toInt();
+    obj["overrides"] = "overrides";
+    if (mcInstance) {
+        QJsonObject version;
+        auto profile = mcInstance->getPackProfile();
+        // collect all supported components
+        const ComponentPtr minecraft = profile->getComponent("net.minecraft");
+        const ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader");
+        const ComponentPtr fabric = profile->getComponent("net.fabricmc.fabric-loader");
+        const ComponentPtr forge = profile->getComponent("net.minecraftforge");
+
+        // convert all available components to mrpack dependencies
+        if (minecraft != nullptr)
+            version["version"] = minecraft->m_version;
+
+        QJsonObject loader;
+        if (quilt != nullptr)
+            loader["id"] = quilt->getName();
+        else if (fabric != nullptr)
+            loader["id"] = fabric->getName();
+        else if (forge != nullptr)
+            loader["id"] = forge->getName();
+        loader["primary"] = true;
+
+        version["modLoaders"] = QJsonArray({ loader });
+        obj["minecraft"] = version;
+    }
+
+    QJsonArray files;
+    QMapIterator<QString, ResolvedFile> iterator(resolvedFiles);
+    while (iterator.hasNext()) {
+        iterator.next();
+
+        const ResolvedFile& value = iterator.value();
+
+        QJsonObject file;
+        file["projectID"] = value.projectID.toInt();
+        file["fileID"] = value.fileID.toInt();
+        file["required"] = value.required;
+        files << file;
+    }
+    obj["files"] = files;
+
+    return QJsonDocument(obj).toJson(QJsonDocument::Compact);
+}
diff --git a/launcher/modplatform/flame/FlamePackExportTask.h b/launcher/modplatform/flame/FlamePackExportTask.h
new file mode 100644
index 00000000..83927099
--- /dev/null
+++ b/launcher/modplatform/flame/FlamePackExportTask.h
@@ -0,0 +1,74 @@
+// 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 <QFuture>
+#include <QFutureWatcher>
+#include "BaseInstance.h"
+#include "MMCZip.h"
+#include "minecraft/MinecraftInstance.h"
+#include "tasks/Task.h"
+
+class FlamePackExportTask : public Task {
+   public:
+    FlamePackExportTask(const QString& name,
+                        const QString& version,
+                        const QVariant& projectID,
+                        InstancePtr instance,
+                        const QString& output,
+                        MMCZip::FilterFunction filter);
+
+   protected:
+    void executeTask() override;
+    bool abort() override;
+
+   private:
+    struct ResolvedFile {
+        QVariant projectID, fileID;
+        bool required;
+    };
+
+    static const QStringList PREFIXES;
+    static const QStringList FILE_EXTENSIONS;
+    static const QString TEMPLATE;
+
+    // inputs
+    const QString name, version, author;
+    const QVariant projectID;
+    const InstancePtr instance;
+    MinecraftInstance* mcInstance;
+    const QDir gameRoot;
+    const QString output;
+    const MMCZip::FilterFunction filter;
+
+    typedef std::optional<QString> BuildZipResult;
+
+    QFileInfoList files;
+    QMap<QString, ResolvedFile> resolvedFiles;
+    Task::Ptr task;
+    QFuture<BuildZipResult> buildZipFuture;
+    QFutureWatcher<BuildZipResult> buildZipWatcher;
+    QList<Mod*> mods;
+
+    void collectFiles();
+    void buildZip();
+    void finish();
+
+    QByteArray generateIndex();
+};
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index e04011ca..0027d180 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -205,6 +205,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->actionExportInstanceFlamePack);
         ui->actionExportInstance->setMenu(exportInstanceMenu);
     }
 
@@ -1416,6 +1417,14 @@ void MainWindow::on_actionExportInstanceMrPack_triggered()
     }
 }
 
+void MainWindow::on_actionExportInstanceFlamePack_triggered()
+{
+    if (m_selectedInstance) {
+        ExportMrPackDialog dlg(m_selectedInstance, this, ModPlatform::ResourceProvider::FLAME);
+        dlg.exec();
+    }
+}
+
 void MainWindow::on_actionRenameInstance_triggered()
 {
     if (m_selectedInstance)
diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h
index 3bb20c4a..5f74b501 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_actionExportInstanceFlamePack_triggered();
 
     void on_actionRenameInstance_triggered();
 
diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui
index f67fb185..f7e93f48 100644
--- a/launcher/ui/MainWindow.ui
+++ b/launcher/ui/MainWindow.ui
@@ -479,6 +479,14 @@
     <string>Modrinth (mrpack)</string>
    </property>
   </action>
+  <action name="actionExportInstanceFlamePack">
+   <property name="icon">
+    <iconset theme="flame"/>
+   </property>
+   <property name="text">
+    <string>Curseforge (zip)</string>
+   </property>
+  </action>
   <action name="actionCreateInstanceShortcut">
    <property name="icon">
     <iconset theme="shortcut">
diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp
index 561b92e4..16ef526a 100644
--- a/launcher/ui/dialogs/ExportMrPackDialog.cpp
+++ b/launcher/ui/dialogs/ExportMrPackDialog.cpp
@@ -18,6 +18,8 @@
 
 #include "ExportMrPackDialog.h"
 #include "minecraft/mod/ModFolderModel.h"
+#include "modplatform/ModIndex.h"
+#include "modplatform/flame/FlamePackExportTask.h"
 #include "ui/dialogs/CustomMessageBox.h"
 #include "ui/dialogs/ProgressDialog.h"
 #include "ui_ExportMrPackDialog.h"
@@ -32,12 +34,15 @@
 #include "MMCZip.h"
 #include "modplatform/modrinth/ModrinthPackExportTask.h"
 
-ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent)
-    : QDialog(parent), instance(instance), ui(new Ui::ExportMrPackDialog)
+ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent, ModPlatform::ResourceProvider provider)
+    : QDialog(parent), instance(instance), ui(new Ui::ExportMrPackDialog), m_provider(provider)
 {
     ui->setupUi(this);
     ui->name->setText(instance->name());
-    ui->summary->setText(instance->notes().split(QRegularExpression("\\r?\\n"))[0]);
+    if (m_provider == ModPlatform::ResourceProvider::MODRINTH)
+        ui->summary->setText(instance->notes().split(QRegularExpression("\\r?\\n"))[0]);
+    else
+        ui->summaryLabel->setText("ProjectID");
 
     // ensure a valid pack is generated
     // the name and version fields mustn't be empty
@@ -97,20 +102,25 @@ void ExportMrPackDialog::done(int result)
 
         if (output.isEmpty())
             return;
-
-        ModrinthPackExportTask task(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output,
-                                    [this](const QString& path) { return proxy->blockedPaths().covers(path); });
-
-        connect(&task, &Task::failed,
+        Task* task;
+        if (m_provider == ModPlatform::ResourceProvider::MODRINTH)
+            task = new ModrinthPackExportTask(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output,
+                                              [this](const QString& path) { return proxy->blockedPaths().covers(path); });
+        else
+            task = new FlamePackExportTask(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output,
+                                           [this](const QString& path) { return proxy->blockedPaths().covers(path); });
+
+        connect(task, &Task::failed,
                 [this](const QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
-        connect(&task, &Task::aborted, [this] {
+        connect(task, &Task::aborted, [this] {
             CustomMessageBox::selectable(this, tr("Task aborted"), tr("The task has been aborted by the user."), QMessageBox::Information)
                 ->show();
         });
+        connect(task, &Task::finished, [task] { task->deleteLater(); });
 
         ProgressDialog progress(this);
         progress.setSkipButton(true, tr("Abort"));
-        if (progress.execWithTask(&task) != QDialog::Accepted)
+        if (progress.execWithTask(task) != QDialog::Accepted)
             return;
     }
 
diff --git a/launcher/ui/dialogs/ExportMrPackDialog.h b/launcher/ui/dialogs/ExportMrPackDialog.h
index 1c70c4ae..858a31bf 100644
--- a/launcher/ui/dialogs/ExportMrPackDialog.h
+++ b/launcher/ui/dialogs/ExportMrPackDialog.h
@@ -22,6 +22,7 @@
 #include "BaseInstance.h"
 #include "FastFileIconProvider.h"
 #include "FileIgnoreProxy.h"
+#include "modplatform/ModIndex.h"
 
 namespace Ui {
 class ExportMrPackDialog;
@@ -31,7 +32,9 @@ class ExportMrPackDialog : public QDialog {
     Q_OBJECT
 
    public:
-    explicit ExportMrPackDialog(InstancePtr instance, QWidget* parent = nullptr);
+    explicit ExportMrPackDialog(InstancePtr instance,
+                                QWidget* parent = nullptr,
+                                ModPlatform::ResourceProvider provider = ModPlatform::ResourceProvider::MODRINTH);
     ~ExportMrPackDialog();
 
     void done(int result) override;
@@ -42,4 +45,5 @@ class ExportMrPackDialog : public QDialog {
     Ui::ExportMrPackDialog* ui;
     FileIgnoreProxy* proxy;
     FastFileIconProvider icons;
+    const ModPlatform::ResourceProvider m_provider;
 };
-- 
cgit 


From cf94adb363c1ae791ebd6f0149899f63c78bfb1b Mon Sep 17 00:00:00 2001
From: Trial97 <alexandru.tripon97@gmail.com>
Date: Sat, 24 Jun 2023 01:05:49 +0300
Subject: Added some warnings

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
---
 launcher/minecraft/mod/Mod.cpp                     |   7 ++
 launcher/minecraft/mod/Mod.h                       |   1 +
 launcher/modplatform/ModIndex.cpp                  |   9 +-
 launcher/modplatform/ModIndex.h                    |   1 +
 launcher/modplatform/flame/FlamePackExportTask.cpp | 124 ++++++++++++++++++---
 launcher/modplatform/flame/FlamePackExportTask.h   |   7 ++
 .../modplatform/helpers/ExportModsToStringTask.cpp |  22 +---
 launcher/ui/MainWindow.cpp                         |  13 ++-
 launcher/ui/dialogs/ExportMrPackDialog.cpp         |   4 +-
 launcher/ui/dialogs/ExportMrPackDialog.ui          |   7 ++
 10 files changed, 158 insertions(+), 37 deletions(-)

(limited to 'launcher/ui/MainWindow.cpp')

diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp
index e613ddeb..e93ff8bc 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()->slug);
+}
+
 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..c68333c5 100644
--- a/launcher/modplatform/ModIndex.cpp
+++ b/launcher/modplatform/ModIndex.cpp
@@ -70,11 +70,18 @@ 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, QString slug)
+{
+    return ((provider == ModPlatform::ResourceProvider::FLAME) ? "https://www.curseforge.com/minecraft/mc-mods/"
+                                                               : "https://modrinth.com/mod/") +
+           slug.remove(".pw.toml");
+}
+
 }  // namespace ModPlatform
diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h
index 82da2ab2..7d8199b3 100644
--- a/launcher/modplatform/ModIndex.h
+++ b/launcher/modplatform/ModIndex.h
@@ -118,6 +118,7 @@ struct IndexedPack {
         return std::any_of(versions.constBegin(), versions.constEnd(), [](auto const& v) { return v.is_currently_selected; });
     }
 };
+QString getMetaURL(ResourceProvider provider, QString slug);
 
 }  // namespace ModPlatform
 
diff --git a/launcher/modplatform/flame/FlamePackExportTask.cpp b/launcher/modplatform/flame/FlamePackExportTask.cpp
index 880d4324..2f1201e1 100644
--- a/launcher/modplatform/flame/FlamePackExportTask.cpp
+++ b/launcher/modplatform/flame/FlamePackExportTask.cpp
@@ -24,13 +24,14 @@
 #include <QFileInfo>
 #include <QMessageBox>
 #include <QtConcurrentRun>
+#include <algorithm>
 #include <memory>
 #include "Json.h"
 #include "MMCZip.h"
 #include "minecraft/PackProfile.h"
 #include "minecraft/mod/ModFolderModel.h"
 #include "modplatform/ModIndex.h"
-#include "modplatform/helpers/ExportModsToStringTask.h"
+#include "modplatform/flame/FlameModIndex.h"
 #include "modplatform/helpers/HashUtils.h"
 #include "tasks/Task.h"
 
@@ -40,6 +41,7 @@ FlamePackExportTask::FlamePackExportTask(const QString& name,
                                          const QString& version,
                                          const QString& author,
                                          const QVariant& projectID,
+                                         const bool generateModList,
                                          InstancePtr instance,
                                          const QString& output,
                                          MMCZip::FilterFunction filter)
@@ -52,6 +54,7 @@ FlamePackExportTask::FlamePackExportTask(const QString& name,
     , gameRoot(instance->gameRoot())
     , output(output)
     , filter(filter)
+    , generateModList(generateModList)
 {}
 
 void FlamePackExportTask::executeTask()
@@ -116,7 +119,8 @@ void FlamePackExportTask::collectHashes()
         }
         if (mod->metadata() && mod->metadata()->provider == ModPlatform::ResourceProvider::FLAME) {
             resolvedFiles.insert(mod->fileinfo().absoluteFilePath(),
-                                 { mod->metadata()->project_id.toInt(), mod->metadata()->file_id.toInt(), mod->enabled() });
+                                 { mod->metadata()->project_id.toInt(), mod->metadata()->file_id.toInt(), mod->enabled(),
+                                   mod->metadata()->name, mod->metadata()->slug, mod->authors().join(", ") });
             setProgress(m_progress + 1, mods.count());
             continue;
         }
@@ -195,10 +199,10 @@ void FlamePackExportTask::makeApiRequest()
                 }
 
                 setStatus(tr("Parsing API response from CurseForge for '%1'...").arg((*mod)->name()));
-
-                resolvedFiles.insert(
-                    mod.value()->fileinfo().absoluteFilePath(),
-                    { Json::requireInteger(file_obj, "modId"), Json::requireInteger(file_obj, "modId"), mod.value()->enabled() });
+                if (Json::ensureBoolean(file_obj, "isAvailable", false))
+                    resolvedFiles.insert(
+                        mod.value()->fileinfo().absoluteFilePath(),
+                        { Json::requireInteger(file_obj, "modId"), Json::requireInteger(file_obj, "id"), mod.value()->enabled() });
             }
 
         } catch (Json::JsonException& e) {
@@ -206,13 +210,94 @@ void FlamePackExportTask::makeApiRequest()
             qDebug() << doc;
         }
         pendingHashes.clear();
-        buildZip();
     });
-
+    connect(task.get(), &Task::finished, this, &FlamePackExportTask::getProjectsInfo);
     connect(task.get(), &NetJob::failed, this, &FlamePackExportTask::emitFailed);
     task->start();
 }
 
+void FlamePackExportTask::getProjectsInfo()
+{
+    if (!generateModList) {
+        buildZip();
+        return;
+    }
+    setStatus(tr("Find project info from curseforge..."));
+    QList<QString> addonIds;
+    for (auto resolved : resolvedFiles) {
+        if (resolved.slug.isEmpty()) {
+            addonIds << QString::number(resolved.addonId);
+        }
+    }
+
+    auto response = std::make_shared<QByteArray>();
+    Task::Ptr proj_task;
+
+    if (addonIds.isEmpty()) {
+        buildZip();
+        return;
+    } else if (addonIds.size() == 1) {
+        proj_task = api.getProject(*addonIds.begin(), response);
+    } else {
+        proj_task = api.getProjects(addonIds, response);
+    }
+
+    connect(proj_task.get(), &Task::succeeded, this, [this, response, addonIds] {
+        QJsonParseError parse_error{};
+        auto doc = QJsonDocument::fromJson(*response, &parse_error);
+        if (parse_error.error != QJsonParseError::NoError) {
+            qWarning() << "Error while parsing JSON response from Modrinth projects task at " << parse_error.offset
+                       << " reason: " << parse_error.errorString();
+            qWarning() << *response;
+            return;
+        }
+
+        try {
+            QJsonArray entries;
+            if (addonIds.size() == 1)
+                entries = { Json::requireObject(Json::requireObject(doc), "data") };
+            else
+                entries = Json::requireArray(Json::requireObject(doc), "data");
+
+            size_t progress = 0;
+            for (auto entry : entries) {
+                setProgress(progress++, entries.count());
+                auto entry_obj = Json::requireObject(entry);
+
+                try {
+                    setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(Json::requireString(entry_obj, "name")));
+
+                    ModPlatform::IndexedPack pack;
+                    FlameMod::loadIndexedPack(pack, entry_obj);
+                    for (auto key : resolvedFiles.keys()) {
+                        auto val = resolvedFiles.value(key);
+                        if (val.addonId == pack.addonId) {
+                            val.name = pack.name;
+                            val.slug = pack.slug;
+                            QStringList authors;
+                            for (auto author : pack.authors)
+                                authors << author.name;
+
+                            val.authors = authors.join(", ");
+                            resolvedFiles[key] = val;
+                        }
+                    }
+
+                } catch (Json::JsonException& e) {
+                    qDebug() << e.cause();
+                    qDebug() << entries;
+                }
+            }
+        } catch (Json::JsonException& e) {
+            qDebug() << e.cause();
+            qDebug() << doc;
+        }
+        buildZip();
+    });
+    task.reset(proj_task);
+    task->start();
+}
+
 void FlamePackExportTask::buildZip()
 {
     setStatus(tr("Adding files..."));
@@ -234,14 +319,23 @@ void FlamePackExportTask::buildZip()
         }
         indexFile.write(generateIndex());
 
-        QuaZipFile modlist(&zip);
-        if (!modlist.open(QIODevice::WriteOnly, QuaZipNewInfo("modlist.html"))) {
-            QFile::remove(output);
-            return BuildZipResult(tr("Could not create index"));
+        if (generateModList) {
+            QuaZipFile modlist(&zip);
+            if (!modlist.open(QIODevice::WriteOnly, QuaZipNewInfo("modlist.html"))) {
+                QFile::remove(output);
+                return BuildZipResult(tr("Could not create index"));
+            }
+            QString content = "";
+            for (auto mod : resolvedFiles) {
+                content += QString(TEMPLATE)
+                               .replace("{name}", mod.name)
+                               .replace("{url}", ModPlatform::getMetaURL(ModPlatform::ResourceProvider::FLAME, mod.slug))
+                               .replace("{authors}", mod.authors) +
+                           "\n";
+            }
+            content = "<ul>" + content + "</ul>";
+            modlist.write(content.toUtf8());
         }
-        QString content = ExportToString::ExportModsToStringTask(mcInstance->loaderModList()->allMods(), TEMPLATE);
-        content = "<ul>" + content + "</ul>";
-        modlist.write(content.toUtf8());
 
         size_t progress = 0;
         for (const QFileInfo& file : files) {
diff --git a/launcher/modplatform/flame/FlamePackExportTask.h b/launcher/modplatform/flame/FlamePackExportTask.h
index f0069678..7f27e0d0 100644
--- a/launcher/modplatform/flame/FlamePackExportTask.h
+++ b/launcher/modplatform/flame/FlamePackExportTask.h
@@ -32,6 +32,7 @@ class FlamePackExportTask : public Task {
                         const QString& version,
                         const QString& author,
                         const QVariant& projectID,
+                        const bool generateModList,
                         InstancePtr instance,
                         const QString& output,
                         MMCZip::FilterFunction filter);
@@ -51,12 +52,17 @@ class FlamePackExportTask : public Task {
     const QDir gameRoot;
     const QString output;
     const MMCZip::FilterFunction filter;
+    const bool generateModList;
 
     typedef std::optional<QString> BuildZipResult;
     struct ResolvedFile {
         int addonId;
         int version;
         bool enabled;
+
+        QString name;
+        QString slug;
+        QString authors;
     };
 
     FlameAPI api;
@@ -71,6 +77,7 @@ class FlamePackExportTask : public Task {
     void collectFiles();
     void collectHashes();
     void makeApiRequest();
+    void getProjectsInfo();
     void buildZip();
     void finish();
 
diff --git a/launcher/modplatform/helpers/ExportModsToStringTask.cpp b/launcher/modplatform/helpers/ExportModsToStringTask.cpp
index e7be5ce1..03e1f4ba 100644
--- a/launcher/modplatform/helpers/ExportModsToStringTask.cpp
+++ b/launcher/modplatform/helpers/ExportModsToStringTask.cpp
@@ -19,6 +19,7 @@
 #include "modplatform/ModIndex.h"
 
 namespace ExportToString {
+
 QString ExportModsToStringTask(QList<Mod*> mods, Formats format, OptionalData extraData)
 {
     switch (format) {
@@ -28,12 +29,7 @@ QString ExportModsToStringTask(QList<Mod*> mods, Formats format, OptionalData ex
                 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");
-                    }
+                    auto url = mod->metaurl();
                     if (!url.isEmpty())
                         modName = QString("<a href=\"%1\">%2</a>").arg(url, modName);
                 }
@@ -57,12 +53,7 @@ QString ExportModsToStringTask(QList<Mod*> mods, Formats format, OptionalData ex
                 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");
-                    }
+                    auto url = mod->metaurl();
                     if (!url.isEmpty())
                         modName = QString("[%1](%2)").arg(modName, url);
                 }
@@ -93,12 +84,7 @@ QString ExportModsToStringTask(QList<Mod*> mods, QString lineTemplate)
         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 url = mod->metaurl();
         auto ver = mod->version();
         if (ver.isEmpty() && meta != nullptr)
             ver = meta->version().toString();
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 0027d180..eb09efbc 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -1420,8 +1420,17 @@ void MainWindow::on_actionExportInstanceMrPack_triggered()
 void MainWindow::on_actionExportInstanceFlamePack_triggered()
 {
     if (m_selectedInstance) {
-        ExportMrPackDialog dlg(m_selectedInstance, this, ModPlatform::ResourceProvider::FLAME);
-        dlg.exec();
+        auto instance = dynamic_cast<MinecraftInstance*>(m_selectedInstance.get());
+        if (instance) {
+            if (instance->getPackProfile()->getComponent("org.quiltmc.quilt-loader")) {
+                QMessageBox msgBox;
+                msgBox.setText(tr("Quilt is not yet supported by curseforge."));
+                msgBox.exec();
+                return;
+            }
+            ExportMrPackDialog dlg(m_selectedInstance, this, ModPlatform::ResourceProvider::FLAME);
+            dlg.exec();
+        }
     }
 }
 
diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp
index 3711bb8f..edd2148a 100644
--- a/launcher/ui/dialogs/ExportMrPackDialog.cpp
+++ b/launcher/ui/dialogs/ExportMrPackDialog.cpp
@@ -43,6 +43,7 @@ ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent, Mo
         ui->summary->setText(instance->notes().split(QRegularExpression("\\r?\\n"))[0]);
         ui->author->hide();
         ui->authorLabel->hide();
+        ui->gnerateModlist->hide();
     } else {
         setWindowTitle("Export CurseForge Pack");
         ui->version->setText("");
@@ -117,7 +118,8 @@ void ExportMrPackDialog::done(int result)
             task = new ModrinthPackExportTask(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output,
                                               [this](const QString& path) { return proxy->blockedPaths().covers(path); });
         else
-            task = new FlamePackExportTask(ui->name->text(), ui->version->text(), ui->author->text(), ui->summary->text(), instance, output,
+            task = new FlamePackExportTask(ui->name->text(), ui->version->text(), ui->author->text(), ui->summary->text(),
+                                           ui->gnerateModlist->isChecked(), instance, output,
                                            [this](const QString& path) { return proxy->blockedPaths().covers(path); });
 
         connect(task, &Task::failed,
diff --git a/launcher/ui/dialogs/ExportMrPackDialog.ui b/launcher/ui/dialogs/ExportMrPackDialog.ui
index 59ecb17c..1b137eb4 100644
--- a/launcher/ui/dialogs/ExportMrPackDialog.ui
+++ b/launcher/ui/dialogs/ExportMrPackDialog.ui
@@ -67,6 +67,13 @@
       <item row="4" column="1">
        <widget class="QLineEdit" name="author"/>
       </item>
+      <item row="5" column="2">
+       <widget class="QCheckBox" name="gnerateModlist">
+        <property name="text">
+         <string>Generate modlist</string>
+        </property>
+       </widget>
+      </item>
      </layout>
     </widget>
    </item>
-- 
cgit 


From 42bc04a0d2af26e97829e6b1afd87d550b9b44da Mon Sep 17 00:00:00 2001
From: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
Date: Sat, 24 Jun 2023 11:01:23 +0300
Subject: Update launcher/ui/MainWindow.cpp

Co-authored-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com>
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
---
 launcher/ui/MainWindow.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'launcher/ui/MainWindow.cpp')

diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index eb09efbc..89c78539 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -1424,7 +1424,7 @@ void MainWindow::on_actionExportInstanceFlamePack_triggered()
         if (instance) {
             if (instance->getPackProfile()->getComponent("org.quiltmc.quilt-loader")) {
                 QMessageBox msgBox;
-                msgBox.setText(tr("Quilt is not yet supported by curseforge."));
+                msgBox.setText(tr("Quilt is currently not supported by CurseForge modpacks."));
                 msgBox.exec();
                 return;
             }
-- 
cgit 


From 25579fbedcfac6b36c6b30ad2447d702b601e1d6 Mon Sep 17 00:00:00 2001
From: Trial97 <alexandru.tripon97@gmail.com>
Date: Sat, 24 Jun 2023 14:54:39 +0300
Subject: Renamed ExportMrPackDialog to ExportPackDialog

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
---
 launcher/CMakeLists.txt                    |   6 +-
 launcher/ui/MainWindow.cpp                 |   6 +-
 launcher/ui/dialogs/ExportMrPackDialog.cpp | 145 ----------------------------
 launcher/ui/dialogs/ExportMrPackDialog.h   |  49 ----------
 launcher/ui/dialogs/ExportMrPackDialog.ui  | 146 -----------------------------
 launcher/ui/dialogs/ExportPackDialog.cpp   | 145 ++++++++++++++++++++++++++++
 launcher/ui/dialogs/ExportPackDialog.h     |  49 ++++++++++
 launcher/ui/dialogs/ExportPackDialog.ui    | 146 +++++++++++++++++++++++++++++
 8 files changed, 346 insertions(+), 346 deletions(-)
 delete mode 100644 launcher/ui/dialogs/ExportMrPackDialog.cpp
 delete mode 100644 launcher/ui/dialogs/ExportMrPackDialog.h
 delete mode 100644 launcher/ui/dialogs/ExportMrPackDialog.ui
 create mode 100644 launcher/ui/dialogs/ExportPackDialog.cpp
 create mode 100644 launcher/ui/dialogs/ExportPackDialog.h
 create mode 100644 launcher/ui/dialogs/ExportPackDialog.ui

(limited to 'launcher/ui/MainWindow.cpp')

diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 8de616a6..0dc2b80a 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -908,8 +908,8 @@ SET(LAUNCHER_SOURCES
     ui/dialogs/EditAccountDialog.h
     ui/dialogs/ExportInstanceDialog.cpp
     ui/dialogs/ExportInstanceDialog.h
-    ui/dialogs/ExportMrPackDialog.cpp
-    ui/dialogs/ExportMrPackDialog.h
+    ui/dialogs/ExportPackDialog.cpp
+    ui/dialogs/ExportPackDialog.h
     ui/dialogs/IconPickerDialog.cpp
     ui/dialogs/IconPickerDialog.h
     ui/dialogs/ImportResourceDialog.cpp
@@ -1056,7 +1056,7 @@ qt_wrap_ui(LAUNCHER_UI
     ui/dialogs/ProfileSelectDialog.ui
     ui/dialogs/SkinUploadDialog.ui
     ui/dialogs/ExportInstanceDialog.ui
-    ui/dialogs/ExportMrPackDialog.ui
+    ui/dialogs/ExportPackDialog.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 89c78539..91809c7b 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -107,7 +107,7 @@
 #include "ui/dialogs/CopyInstanceDialog.h"
 #include "ui/dialogs/EditAccountDialog.h"
 #include "ui/dialogs/ExportInstanceDialog.h"
-#include "ui/dialogs/ExportMrPackDialog.h"
+#include "ui/dialogs/ExportPackDialog.h"
 #include "ui/dialogs/ImportResourceDialog.h"
 #include "ui/themes/ITheme.h"
 #include "ui/themes/ThemeManager.h"
@@ -1412,7 +1412,7 @@ void MainWindow::on_actionExportInstanceMrPack_triggered()
 {
     if (m_selectedInstance)
     {
-        ExportMrPackDialog dlg(m_selectedInstance, this);
+        ExportPackDialog dlg(m_selectedInstance, this);
         dlg.exec();
     }
 }
@@ -1428,7 +1428,7 @@ void MainWindow::on_actionExportInstanceFlamePack_triggered()
                 msgBox.exec();
                 return;
             }
-            ExportMrPackDialog dlg(m_selectedInstance, this, ModPlatform::ResourceProvider::FLAME);
+            ExportPackDialog dlg(m_selectedInstance, this, ModPlatform::ResourceProvider::FLAME);
             dlg.exec();
         }
     }
diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportMrPackDialog.cpp
deleted file mode 100644
index 94987c7e..00000000
--- a/launcher/ui/dialogs/ExportMrPackDialog.cpp
+++ /dev/null
@@ -1,145 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-only
-/*
- *  Prism Launcher - Minecraft Launcher
- *  Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
- *
- *  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 "ExportMrPackDialog.h"
-#include "minecraft/mod/ModFolderModel.h"
-#include "modplatform/ModIndex.h"
-#include "modplatform/flame/FlamePackExportTask.h"
-#include "ui/dialogs/CustomMessageBox.h"
-#include "ui/dialogs/ProgressDialog.h"
-#include "ui_ExportMrPackDialog.h"
-
-#include <QFileDialog>
-#include <QFileSystemModel>
-#include <QJsonDocument>
-#include <QMessageBox>
-#include <QPushButton>
-#include "FastFileIconProvider.h"
-#include "FileSystem.h"
-#include "MMCZip.h"
-#include "modplatform/modrinth/ModrinthPackExportTask.h"
-
-ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent, ModPlatform::ResourceProvider provider)
-    : QDialog(parent), instance(instance), ui(new Ui::ExportMrPackDialog), m_provider(provider)
-{
-    ui->setupUi(this);
-    ui->name->setText(instance->name());
-    if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
-        ui->summary->setText(instance->notes().split(QRegularExpression("\\r?\\n"))[0]);
-        ui->author->hide();
-        ui->authorLabel->hide();
-    } else {
-        setWindowTitle("Export CurseForge Pack");
-        ui->version->setText("");
-        ui->summaryLabel->setText("ProjectID");
-    }
-
-    // ensure a valid pack is generated
-    // the name and version fields mustn't be empty
-    connect(ui->name, &QLineEdit::textEdited, this, &ExportMrPackDialog::validate);
-    connect(ui->version, &QLineEdit::textEdited, this, &ExportMrPackDialog::validate);
-    // the instance name can technically be empty
-    validate();
-
-    QFileSystemModel* model = new QFileSystemModel(this);
-    model->setIconProvider(&icons);
-
-    // use the game root - everything outside cannot be exported
-    const QDir root(instance->gameRoot());
-    proxy = new FileIgnoreProxy(instance->gameRoot(), this);
-    proxy->setSourceModel(model);
-    proxy->setFilterRegularExpression("^(?!(\\.DS_Store)|([tT]humbs\\.db)).+$");
-
-    const QDir::Filters filter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Hidden);
-
-    for (const QString& file : root.entryList(filter)) {
-        if (!(file == "mods" || file == "coremods" || file == "datapacks" || file == "config" || file == "options.txt" ||
-              file == "servers.dat"))
-            proxy->blockedPaths().insert(file);
-    }
-
-    MinecraftInstance* mcInstance = dynamic_cast<MinecraftInstance*>(instance.get());
-    if (mcInstance) {
-        const QDir index = mcInstance->loaderModList()->indexDir();
-        if (index.exists())
-            proxy->blockedPaths().insert(root.relativeFilePath(index.absolutePath()));
-    }
-
-    ui->treeView->setModel(proxy);
-    ui->treeView->setRootIndex(proxy->mapFromSource(model->index(instance->gameRoot())));
-    ui->treeView->sortByColumn(0, Qt::AscendingOrder);
-
-    model->setFilter(filter);
-    model->setRootPath(instance->gameRoot());
-
-    QHeaderView* headerView = ui->treeView->header();
-    headerView->setSectionResizeMode(QHeaderView::ResizeToContents);
-    headerView->setSectionResizeMode(0, QHeaderView::Stretch);
-}
-
-ExportMrPackDialog::~ExportMrPackDialog()
-{
-    delete ui;
-}
-
-void ExportMrPackDialog::done(int result)
-{
-    if (result == Accepted) {
-        const QString filename = FS::RemoveInvalidFilenameChars(ui->name->text());
-        QString output;
-        if (m_provider == ModPlatform::ResourceProvider::MODRINTH)
-            output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(ui->name->text()),
-                                                  FS::PathCombine(QDir::homePath(), filename + ".mrpack"), "Modrinth pack (*.mrpack *.zip)",
-                                                  nullptr);
-        else
-            output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(ui->name->text()),
-                                                  FS::PathCombine(QDir::homePath(), filename + ".zip"), "CurseForge pack (*.zip)", nullptr);
-
-        if (output.isEmpty())
-            return;
-        Task* task;
-        if (m_provider == ModPlatform::ResourceProvider::MODRINTH)
-            task = new ModrinthPackExportTask(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output,
-                                              [this](const QString& path) { return proxy->blockedPaths().covers(path); });
-        else
-            task = new FlamePackExportTask(ui->name->text(), ui->version->text(), ui->author->text(), ui->summary->text(), instance, output,
-                                           [this](const QString& path) { return proxy->blockedPaths().covers(path); });
-
-        connect(task, &Task::failed,
-                [this](const QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
-        connect(task, &Task::aborted, [this] {
-            CustomMessageBox::selectable(this, tr("Task aborted"), tr("The task has been aborted by the user."), QMessageBox::Information)
-                ->show();
-        });
-        connect(task, &Task::finished, [task] { task->deleteLater(); });
-
-        ProgressDialog progress(this);
-        progress.setSkipButton(true, tr("Abort"));
-        if (progress.execWithTask(task) != QDialog::Accepted)
-            return;
-    }
-
-    QDialog::done(result);
-}
-
-void ExportMrPackDialog::validate()
-{
-    const bool invalid =
-        ui->name->text().isEmpty() || ((m_provider == ModPlatform::ResourceProvider::MODRINTH) && ui->version->text().isEmpty());
-    ui->buttonBox->button(QDialogButtonBox::Ok)->setDisabled(invalid);
-}
diff --git a/launcher/ui/dialogs/ExportMrPackDialog.h b/launcher/ui/dialogs/ExportMrPackDialog.h
deleted file mode 100644
index 858a31bf..00000000
--- a/launcher/ui/dialogs/ExportMrPackDialog.h
+++ /dev/null
@@ -1,49 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-only
-/*
- *  Prism Launcher - Minecraft Launcher
- *  Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
- *
- *  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 "BaseInstance.h"
-#include "FastFileIconProvider.h"
-#include "FileIgnoreProxy.h"
-#include "modplatform/ModIndex.h"
-
-namespace Ui {
-class ExportMrPackDialog;
-}
-
-class ExportMrPackDialog : public QDialog {
-    Q_OBJECT
-
-   public:
-    explicit ExportMrPackDialog(InstancePtr instance,
-                                QWidget* parent = nullptr,
-                                ModPlatform::ResourceProvider provider = ModPlatform::ResourceProvider::MODRINTH);
-    ~ExportMrPackDialog();
-
-    void done(int result) override;
-    void validate();
-
-   private:
-    const InstancePtr instance;
-    Ui::ExportMrPackDialog* ui;
-    FileIgnoreProxy* proxy;
-    FastFileIconProvider icons;
-    const ModPlatform::ResourceProvider m_provider;
-};
diff --git a/launcher/ui/dialogs/ExportMrPackDialog.ui b/launcher/ui/dialogs/ExportMrPackDialog.ui
deleted file mode 100644
index 59ecb17c..00000000
--- a/launcher/ui/dialogs/ExportMrPackDialog.ui
+++ /dev/null
@@ -1,146 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>ExportMrPackDialog</class>
- <widget class="QDialog" name="ExportMrPackDialog">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>650</width>
-    <height>413</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>
-    <widget class="QGroupBox" name="information">
-     <property name="title">
-      <string>Information</string>
-     </property>
-     <layout class="QGridLayout" name="gridLayout">
-      <item row="3" column="0">
-       <widget class="QLabel" name="summaryLabel">
-        <property name="text">
-         <string>Summary</string>
-        </property>
-       </widget>
-      </item>
-      <item row="3" column="1">
-       <widget class="QLineEdit" name="summary"/>
-      </item>
-      <item row="0" column="0">
-       <widget class="QLabel" name="nameLabel">
-        <property name="text">
-         <string>Name</string>
-        </property>
-       </widget>
-      </item>
-      <item row="1" column="0">
-       <widget class="QLabel" name="versionLabel">
-        <property name="text">
-         <string>Version</string>
-        </property>
-       </widget>
-      </item>
-      <item row="0" column="1">
-       <widget class="QLineEdit" name="name"/>
-      </item>
-      <item row="1" column="1">
-       <widget class="QLineEdit" name="version">
-        <property name="text">
-         <string>1.0.0</string>
-        </property>
-       </widget>
-      </item>
-      <item row="4" column="0">
-       <widget class="QLabel" name="authorLabel">
-        <property name="text">
-         <string>Author</string>
-        </property>
-       </widget>
-      </item>
-      <item row="4" column="1">
-       <widget class="QLineEdit" name="author"/>
-      </item>
-     </layout>
-    </widget>
-   </item>
-   <item>
-    <widget class="QLabel" name="filesLabel">
-     <property name="text">
-      <string>Files</string>
-     </property>
-    </widget>
-   </item>
-   <item>
-    <widget class="QTreeView" name="treeView">
-     <property name="alternatingRowColors">
-      <bool>true</bool>
-     </property>
-     <property name="selectionMode">
-      <enum>QAbstractItemView::ExtendedSelection</enum>
-     </property>
-     <property name="sortingEnabled">
-      <bool>true</bool>
-     </property>
-     <attribute name="headerStretchLastSection">
-      <bool>false</bool>
-     </attribute>
-    </widget>
-   </item>
-   <item>
-    <widget class="QDialogButtonBox" name="buttonBox">
-     <property name="standardButtons">
-      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
-     </property>
-    </widget>
-   </item>
-  </layout>
- </widget>
- <tabstops>
-  <tabstop>name</tabstop>
-  <tabstop>version</tabstop>
-  <tabstop>summary</tabstop>
-  <tabstop>treeView</tabstop>
- </tabstops>
- <resources/>
- <connections>
-  <connection>
-   <sender>buttonBox</sender>
-   <signal>accepted()</signal>
-   <receiver>ExportMrPackDialog</receiver>
-   <slot>accept()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>324</x>
-     <y>390</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>324</x>
-     <y>206</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>buttonBox</sender>
-   <signal>rejected()</signal>
-   <receiver>ExportMrPackDialog</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>
diff --git a/launcher/ui/dialogs/ExportPackDialog.cpp b/launcher/ui/dialogs/ExportPackDialog.cpp
new file mode 100644
index 00000000..8e921f89
--- /dev/null
+++ b/launcher/ui/dialogs/ExportPackDialog.cpp
@@ -0,0 +1,145 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ *  Prism Launcher - Minecraft Launcher
+ *  Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
+ *
+ *  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 "ExportPackDialog.h"
+#include "minecraft/mod/ModFolderModel.h"
+#include "modplatform/ModIndex.h"
+#include "modplatform/flame/FlamePackExportTask.h"
+#include "ui/dialogs/CustomMessageBox.h"
+#include "ui/dialogs/ProgressDialog.h"
+#include "ui_ExportMrPackDialog.h"
+
+#include <QFileDialog>
+#include <QFileSystemModel>
+#include <QJsonDocument>
+#include <QMessageBox>
+#include <QPushButton>
+#include "FastFileIconProvider.h"
+#include "FileSystem.h"
+#include "MMCZip.h"
+#include "modplatform/modrinth/ModrinthPackExportTask.h"
+
+ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPlatform::ResourceProvider provider)
+    : QDialog(parent), instance(instance), ui(new Ui::ExportPackDialog), m_provider(provider)
+{
+    ui->setupUi(this);
+    ui->name->setText(instance->name());
+    if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
+        ui->summary->setText(instance->notes().split(QRegularExpression("\\r?\\n"))[0]);
+        ui->author->hide();
+        ui->authorLabel->hide();
+    } else {
+        setWindowTitle("Export CurseForge Pack");
+        ui->version->setText("");
+        ui->summaryLabel->setText("ProjectID");
+    }
+
+    // ensure a valid pack is generated
+    // the name and version fields mustn't be empty
+    connect(ui->name, &QLineEdit::textEdited, this, &ExportPackDialog::validate);
+    connect(ui->version, &QLineEdit::textEdited, this, &ExportPackDialog::validate);
+    // the instance name can technically be empty
+    validate();
+
+    QFileSystemModel* model = new QFileSystemModel(this);
+    model->setIconProvider(&icons);
+
+    // use the game root - everything outside cannot be exported
+    const QDir root(instance->gameRoot());
+    proxy = new FileIgnoreProxy(instance->gameRoot(), this);
+    proxy->setSourceModel(model);
+    proxy->setFilterRegularExpression("^(?!(\\.DS_Store)|([tT]humbs\\.db)).+$");
+
+    const QDir::Filters filter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Hidden);
+
+    for (const QString& file : root.entryList(filter)) {
+        if (!(file == "mods" || file == "coremods" || file == "datapacks" || file == "config" || file == "options.txt" ||
+              file == "servers.dat"))
+            proxy->blockedPaths().insert(file);
+    }
+
+    MinecraftInstance* mcInstance = dynamic_cast<MinecraftInstance*>(instance.get());
+    if (mcInstance) {
+        const QDir index = mcInstance->loaderModList()->indexDir();
+        if (index.exists())
+            proxy->blockedPaths().insert(root.relativeFilePath(index.absolutePath()));
+    }
+
+    ui->treeView->setModel(proxy);
+    ui->treeView->setRootIndex(proxy->mapFromSource(model->index(instance->gameRoot())));
+    ui->treeView->sortByColumn(0, Qt::AscendingOrder);
+
+    model->setFilter(filter);
+    model->setRootPath(instance->gameRoot());
+
+    QHeaderView* headerView = ui->treeView->header();
+    headerView->setSectionResizeMode(QHeaderView::ResizeToContents);
+    headerView->setSectionResizeMode(0, QHeaderView::Stretch);
+}
+
+ExportPackDialog::~ExportPackDialog()
+{
+    delete ui;
+}
+
+void ExportPackDialog::done(int result)
+{
+    if (result == Accepted) {
+        const QString filename = FS::RemoveInvalidFilenameChars(ui->name->text());
+        QString output;
+        if (m_provider == ModPlatform::ResourceProvider::MODRINTH)
+            output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(ui->name->text()),
+                                                  FS::PathCombine(QDir::homePath(), filename + ".mrpack"), "Modrinth pack (*.mrpack *.zip)",
+                                                  nullptr);
+        else
+            output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(ui->name->text()),
+                                                  FS::PathCombine(QDir::homePath(), filename + ".zip"), "CurseForge pack (*.zip)", nullptr);
+
+        if (output.isEmpty())
+            return;
+        Task* task;
+        if (m_provider == ModPlatform::ResourceProvider::MODRINTH)
+            task = new ModrinthPackExportTask(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output,
+                                              [this](const QString& path) { return proxy->blockedPaths().covers(path); });
+        else
+            task = new FlamePackExportTask(ui->name->text(), ui->version->text(), ui->author->text(), ui->summary->text(), instance, output,
+                                           [this](const QString& path) { return proxy->blockedPaths().covers(path); });
+
+        connect(task, &Task::failed,
+                [this](const QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
+        connect(task, &Task::aborted, [this] {
+            CustomMessageBox::selectable(this, tr("Task aborted"), tr("The task has been aborted by the user."), QMessageBox::Information)
+                ->show();
+        });
+        connect(task, &Task::finished, [task] { task->deleteLater(); });
+
+        ProgressDialog progress(this);
+        progress.setSkipButton(true, tr("Abort"));
+        if (progress.execWithTask(task) != QDialog::Accepted)
+            return;
+    }
+
+    QDialog::done(result);
+}
+
+void ExportPackDialog::validate()
+{
+    const bool invalid =
+        ui->name->text().isEmpty() || ((m_provider == ModPlatform::ResourceProvider::MODRINTH) && ui->version->text().isEmpty());
+    ui->buttonBox->button(QDialogButtonBox::Ok)->setDisabled(invalid);
+}
diff --git a/launcher/ui/dialogs/ExportPackDialog.h b/launcher/ui/dialogs/ExportPackDialog.h
new file mode 100644
index 00000000..830c24d2
--- /dev/null
+++ b/launcher/ui/dialogs/ExportPackDialog.h
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ *  Prism Launcher - Minecraft Launcher
+ *  Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
+ *
+ *  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 "BaseInstance.h"
+#include "FastFileIconProvider.h"
+#include "FileIgnoreProxy.h"
+#include "modplatform/ModIndex.h"
+
+namespace Ui {
+class ExportPackDialog;
+}
+
+class ExportPackDialog : public QDialog {
+    Q_OBJECT
+
+   public:
+    explicit ExportPackDialog(InstancePtr instance,
+                              QWidget* parent = nullptr,
+                              ModPlatform::ResourceProvider provider = ModPlatform::ResourceProvider::MODRINTH);
+    ~ExportPackDialog();
+
+    void done(int result) override;
+    void validate();
+
+   private:
+    const InstancePtr instance;
+    Ui::ExportPackDialog* ui;
+    FileIgnoreProxy* proxy;
+    FastFileIconProvider icons;
+    const ModPlatform::ResourceProvider m_provider;
+};
diff --git a/launcher/ui/dialogs/ExportPackDialog.ui b/launcher/ui/dialogs/ExportPackDialog.ui
new file mode 100644
index 00000000..5762508a
--- /dev/null
+++ b/launcher/ui/dialogs/ExportPackDialog.ui
@@ -0,0 +1,146 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ExportPackDialog</class>
+ <widget class="QDialog" name="ExportPackDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>650</width>
+    <height>413</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>
+    <widget class="QGroupBox" name="information">
+     <property name="title">
+      <string>Information</string>
+     </property>
+     <layout class="QGridLayout" name="gridLayout">
+      <item row="3" column="0">
+       <widget class="QLabel" name="summaryLabel">
+        <property name="text">
+         <string>Summary</string>
+        </property>
+       </widget>
+      </item>
+      <item row="3" column="1">
+       <widget class="QLineEdit" name="summary"/>
+      </item>
+      <item row="0" column="0">
+       <widget class="QLabel" name="nameLabel">
+        <property name="text">
+         <string>Name</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="0">
+       <widget class="QLabel" name="versionLabel">
+        <property name="text">
+         <string>Version</string>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="1">
+       <widget class="QLineEdit" name="name"/>
+      </item>
+      <item row="1" column="1">
+       <widget class="QLineEdit" name="version">
+        <property name="text">
+         <string>1.0.0</string>
+        </property>
+       </widget>
+      </item>
+      <item row="4" column="0">
+       <widget class="QLabel" name="authorLabel">
+        <property name="text">
+         <string>Author</string>
+        </property>
+       </widget>
+      </item>
+      <item row="4" column="1">
+       <widget class="QLineEdit" name="author"/>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QLabel" name="filesLabel">
+     <property name="text">
+      <string>Files</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QTreeView" name="treeView">
+     <property name="alternatingRowColors">
+      <bool>true</bool>
+     </property>
+     <property name="selectionMode">
+      <enum>QAbstractItemView::ExtendedSelection</enum>
+     </property>
+     <property name="sortingEnabled">
+      <bool>true</bool>
+     </property>
+     <attribute name="headerStretchLastSection">
+      <bool>false</bool>
+     </attribute>
+    </widget>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <tabstops>
+  <tabstop>name</tabstop>
+  <tabstop>version</tabstop>
+  <tabstop>summary</tabstop>
+  <tabstop>treeView</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>ExportPackDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>324</x>
+     <y>390</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>324</x>
+     <y>206</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>ExportPackDialog</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 


From fd5b155ee7d796015c84c8b348f384bf21d8328d Mon Sep 17 00:00:00 2001
From: Trial97 <alexandru.tripon97@gmail.com>
Date: Sun, 25 Jun 2023 12:24:59 +0300
Subject: Added error message when exporting snapshots with curseforge

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
---
 launcher/modplatform/flame/FlamePackExportTask.cpp | 18 +++++++++++-------
 launcher/ui/MainWindow.cpp                         |  9 ++++++++-
 2 files changed, 19 insertions(+), 8 deletions(-)

(limited to 'launcher/ui/MainWindow.cpp')

diff --git a/launcher/modplatform/flame/FlamePackExportTask.cpp b/launcher/modplatform/flame/FlamePackExportTask.cpp
index e73f3de5..e5eeb098 100644
--- a/launcher/modplatform/flame/FlamePackExportTask.cpp
+++ b/launcher/modplatform/flame/FlamePackExportTask.cpp
@@ -409,16 +409,20 @@ QByteArray FlamePackExportTask::generateIndex()
         // convert all available components to mrpack dependencies
         if (minecraft != nullptr)
             version["version"] = minecraft->m_version;
-
-        QJsonObject loader;
+        QString id;
         if (quilt != nullptr)
-            loader["id"] = "quilt-" + quilt->getVersion();
+            id = "quilt-" + quilt->getVersion();
         else if (fabric != nullptr)
-            loader["id"] = "fabric-" + fabric->getVersion();
+            id = "fabric-" + fabric->getVersion();
         else if (forge != nullptr)
-            loader["id"] = "forge-" + forge->getVersion();
-        loader["primary"] = true;
-        version["modLoaders"] = QJsonArray({ loader });
+            id = "forge-" + forge->getVersion();
+        version["modLoaders"] = QJsonArray();
+        if (!id.isEmpty()) {
+            QJsonObject loader;
+            loader["id"] = id;
+            loader["primary"] = true;
+            version["modLoaders"] = QJsonArray({ loader });
+        }
         obj["minecraft"] = version;
     }
 
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 91809c7b..50eb9e64 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -1422,9 +1422,16 @@ void MainWindow::on_actionExportInstanceFlamePack_triggered()
     if (m_selectedInstance) {
         auto instance = dynamic_cast<MinecraftInstance*>(m_selectedInstance.get());
         if (instance) {
+            QString errorMsg;
             if (instance->getPackProfile()->getComponent("org.quiltmc.quilt-loader")) {
+                errorMsg = tr("Quilt is currently not supported by CurseForge modpacks.");
+            } else if (auto cmp = instance->getPackProfile()->getComponent("net.minecraft");
+                       cmp && cmp->getVersionFile() && cmp->getVersionFile()->type == "snapshot") {
+                errorMsg = tr("Snapshots are currently not supported by CurseForge modpacks.");
+            }
+            if (!errorMsg.isEmpty()) {
                 QMessageBox msgBox;
-                msgBox.setText(tr("Quilt is currently not supported by CurseForge modpacks."));
+                msgBox.setText(errorMsg);
                 msgBox.exec();
                 return;
             }
-- 
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/ui/MainWindow.cpp')

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 


From 953a2590e27954a3b1d163224bd343bb75a62b69 Mon Sep 17 00:00:00 2001
From: Leo <leo3758@riseup.net>
Date: Sun, 25 Jun 2023 10:11:58 -0300
Subject: Add fixme comment for no SSD detection

Co-authored-by: Sefa Eyeoglu <contact@scrumplex.net>
Signed-off-by: Leo <leo3758@riseup.net>
---
 launcher/ui/MainWindow.cpp | 1 +
 1 file changed, 1 insertion(+)

(limited to 'launcher/ui/MainWindow.cpp')

diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 9b8db1ae..7e2c4acf 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -221,6 +221,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
         setInstanceActionsEnabled(false);
 
         // add a close button at the end of the main toolbar when running on gamescope / steam deck
+        // FIXME: detect if we don't have server side decorations instead
         if (qgetenv("XDG_CURRENT_DESKTOP") == "gamescope") {
             ui->mainToolBar->addAction(ui->actionCloseWindow);
         }
-- 
cgit 


From 6e5716f097d89b4b7b4c48ae18e37474ef23b3e8 Mon Sep 17 00:00:00 2001
From: Trial97 <alexandru.tripon97@gmail.com>
Date: Tue, 27 Jun 2023 19:05:32 +0300
Subject: Fixed illegal characters in shortcuts name

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
---
 launcher/FileSystem.cpp    |  11 ++-
 launcher/ui/MainWindow.cpp | 203 +++++++++++++++++++--------------------------
 2 files changed, 95 insertions(+), 119 deletions(-)

(limited to 'launcher/ui/MainWindow.cpp')

diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index 835ad925..1ea9f755 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -372,7 +372,7 @@ void create_link::make_link_list(const QString& offset)
                 auto src_path = source_it.next();
                 auto relative_path = src_dir.relativeFilePath(src_path);
 
-                if (m_max_depth >= 0 && pathDepth(relative_path) > m_max_depth){
+                if (m_max_depth >= 0 && pathDepth(relative_path) > m_max_depth) {
                     relative_path = pathTruncate(relative_path, m_max_depth);
                     src_path = src_dir.filePath(relative_path);
                     if (linkedPaths.contains(src_path)) {
@@ -663,7 +663,7 @@ QString pathTruncate(const QString& path, int depth)
 
     QString trunc = QFileInfo(path).path();
 
-    if (pathDepth(trunc) > depth ) {
+    if (pathDepth(trunc) > depth) {
         return pathTruncate(trunc, depth);
     }
 
@@ -769,6 +769,9 @@ QString getDesktopDir()
 // Cross-platform Shortcut creation
 bool createShortcut(QString destination, QString target, QStringList args, QString name, QString icon)
 {
+    if (destination.isEmpty()) {
+        destination = PathCombine(getDesktopDir(), RemoveInvalidFilenameChars(name));
+    }
 #if defined(Q_OS_MACOS)
     destination += ".command";
 
@@ -791,6 +794,8 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
 
     return true;
 #elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
+    if (!destination.endsWith(".desktop"))  // in case of isFlatpak destination is already populated
+        destination += ".desktop";
     QFile f(destination);
     f.open(QIODevice::WriteOnly | QIODevice::Text);
     QTextStream stream(&f);
@@ -974,7 +979,7 @@ FilesystemType getFilesystemType(const QString& name)
 {
     for (auto iter = s_filesystem_type_names.constBegin(); iter != s_filesystem_type_names.constEnd(); ++iter) {
         auto fs_names = iter.value();
-        if(fs_names.contains(name.toUpper())) 
+        if (fs_names.contains(name.toUpper()))
             return iter.key();
     }
     return FilesystemType::UNKNOWN;
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index eeb78c53..7407483f 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -1510,140 +1510,111 @@ void MainWindow::on_actionKillInstance_triggered()
 
 void MainWindow::on_actionCreateInstanceShortcut_triggered()
 {
-    if (m_selectedInstance)
-    {
-        auto desktopPath = FS::getDesktopDir();
-        if (desktopPath.isEmpty()) {
-            // TODO come up with an alternative solution (open "save file" dialog)
-            QMessageBox::critical(this, tr("Create instance shortcut"), tr("Couldn't find desktop?!"));
-            return;
-        }
+    if (!m_selectedInstance)
+        return;
+    auto desktopPath = FS::getDesktopDir();
+    if (desktopPath.isEmpty()) {
+        // TODO come up with an alternative solution (open "save file" dialog)
+        QMessageBox::critical(this, tr("Create instance shortcut"), tr("Couldn't find desktop?!"));
+        return;
+    }
 
+    QString desktopFilePath;
+    QString appPath = QApplication::applicationFilePath();
+    QString iconPath;
+    QStringList args;
 #if defined(Q_OS_MACOS)
-        QString appPath = QApplication::applicationFilePath();
-        if (appPath.startsWith("/private/var/")) {
-            QMessageBox::critical(this, tr("Create instance shortcut"), tr("The launcher is in the folder it was extracted from, therefore it cannot create shortcuts."));
-            return;
-        }
-
-        if (FS::createShortcut(FS::PathCombine(desktopPath, m_selectedInstance->name()),
-                           appPath, { "--launch", m_selectedInstance->id() },
-                           m_selectedInstance->name(), "")) {
-            QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!"));
-        }
-        else
-        {
-            QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!"));
-        }
+    if (appPath.startsWith("/private/var/")) {
+        QMessageBox::critical(this, tr("Create instance shortcut"),
+                              tr("The launcher is in the folder it was extracted from, therefore it cannot create shortcuts."));
+        return;
+    }
 #elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
-        QString appPath = QApplication::applicationFilePath();
-        if (appPath.startsWith("/tmp/.mount_")) {
-            // AppImage!
-            appPath = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE"));
-            if (appPath.isEmpty())
-            {
-                QMessageBox::critical(this, tr("Create instance shortcut"), tr("Launcher is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)"));
-            }
-            else if (appPath.endsWith("/"))
-            {
-                appPath.chop(1);
-            }
+    if (appPath.startsWith("/tmp/.mount_")) {
+        // AppImage!
+        appPath = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE"));
+        if (appPath.isEmpty()) {
+            QMessageBox::critical(this, tr("Create instance shortcut"),
+                                  tr("Launcher is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)"));
+        } else if (appPath.endsWith("/")) {
+            appPath.chop(1);
         }
+    }
 
-        auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey());
-        if (icon == nullptr)
-        {
-            icon = APPLICATION->icons()->icon("grass");
-        }
+    auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey());
+    if (icon == nullptr) {
+        icon = APPLICATION->icons()->icon("grass");
+    }
 
-        QString iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.png");
+    iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.png");
 
-        QFile iconFile(iconPath);
-        if (!iconFile.open(QFile::WriteOnly))
-        {
-            QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
-            return;
-        }
-        bool success = icon->icon().pixmap(64, 64).save(&iconFile, "PNG");
-        iconFile.close();
+    QFile iconFile(iconPath);
+    if (!iconFile.open(QFile::WriteOnly)) {
+        QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
+        return;
+    }
+    bool success = icon->icon().pixmap(64, 64).save(&iconFile, "PNG");
+    iconFile.close();
 
-        if (!success)
-        {
-            iconFile.remove();
-            QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
-            return;
-        }
+    if (!success) {
+        iconFile.remove();
+        QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
+        return;
+    }
+
+    if (DesktopServices::isFlatpak()) {
+        desktopFilePath = FS::PathCombine(desktopPath, FS::RemoveInvalidFilenameChars(m_selectedInstance->name()) + ".desktop");
+        QFileDialog fileDialog;
+        // workaround to make sure the portal file dialog opens in the desktop directory
+        fileDialog.setDirectoryUrl(desktopPath);
+        desktopFilePath = fileDialog.getSaveFileName(this, tr("Create Shortcut"), desktopFilePath, tr("Desktop Entries (*.desktop)"));
+        if (desktopFilePath.isEmpty())
+            return;  // file dialog canceled by user
+        appPath = "flatpak";
+        QString flatpakAppId = BuildConfig.LAUNCHER_DESKTOPFILENAME;
+        flatpakAppId.remove(".desktop");
+        args.append({ "run", flatpakAppId });
+    }
 
-        QString desktopFilePath = FS::PathCombine(desktopPath, m_selectedInstance->name() + ".desktop");
-        QStringList args;
-        if (DesktopServices::isFlatpak()) {
-            QFileDialog fileDialog;
-            // workaround to make sure the portal file dialog opens in the desktop directory
-            fileDialog.setDirectoryUrl(desktopPath);
-            desktopFilePath = fileDialog.getSaveFileName(
-                            this, tr("Create Shortcut"), desktopFilePath,
-                            tr("Desktop Entries (*.desktop)"));
-            if (desktopFilePath.isEmpty())
-                return; // file dialog canceled by user
-            appPath = "flatpak";
-            QString flatpakAppId = BuildConfig.LAUNCHER_DESKTOPFILENAME;
-            flatpakAppId.remove(".desktop");
-            args.append({ "run", flatpakAppId });
-        }
-        args.append({ "--launch", m_selectedInstance->id() });
-        if (FS::createShortcut(desktopFilePath, appPath, args, m_selectedInstance->name(), iconPath)) {
-            QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!"));
-        }
-        else
-        {
-            iconFile.remove();
-            QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!"));
-        }
 #elif defined(Q_OS_WIN)
-        auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey());
-        if (icon == nullptr)
-        {
-            icon = APPLICATION->icons()->icon("grass");
-        }
+    auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey());
+    if (icon == nullptr) {
+        icon = APPLICATION->icons()->icon("grass");
+    }
 
-        QString iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.ico");
+    iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.ico");
 
-        // part of fix for weird bug involving the window icon being replaced
-        // dunno why it happens, but this 2-line fix seems to be enough, so w/e
-        auto appIcon = APPLICATION->getThemedIcon("logo");
+    // part of fix for weird bug involving the window icon being replaced
+    // dunno why it happens, but this 2-line fix seems to be enough, so w/e
+    auto appIcon = APPLICATION->getThemedIcon("logo");
 
-        QFile iconFile(iconPath);
-        if (!iconFile.open(QFile::WriteOnly))
-        {
-            QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
-            return;
-        }
-        bool success = icon->icon().pixmap(64, 64).save(&iconFile, "ICO");
-        iconFile.close();
+    QFile iconFile(iconPath);
+    if (!iconFile.open(QFile::WriteOnly)) {
+        QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
+        return;
+    }
+    bool success = icon->icon().pixmap(64, 64).save(&iconFile, "ICO");
+    iconFile.close();
 
-        // restore original window icon
-        QGuiApplication::setWindowIcon(appIcon);
+    // restore original window icon
+    QGuiApplication::setWindowIcon(appIcon);
 
-        if (!success)
-        {
-            iconFile.remove();
-            QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
-            return;
-        }
+    if (!success) {
+        iconFile.remove();
+        QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut."));
+        return;
+    }
 
-        if (FS::createShortcut(FS::PathCombine(desktopPath, m_selectedInstance->name()),
-                           QApplication::applicationFilePath(), { "--launch", m_selectedInstance->id() },
-                           m_selectedInstance->name(), iconPath)) {
-            QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!"));
-        }
-        else
-        {
-            iconFile.remove();
-            QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!"));
-        }
 #else
-        QMessageBox::critical(this, tr("Create instance shortcut"), tr("Not supported on your platform!"));
+    QMessageBox::critical(this, tr("Create instance shortcut"), tr("Not supported on your platform!"));
+    return;
 #endif
+    args.append({ "--launch", m_selectedInstance->id() });
+    if (FS::createShortcut(desktopFilePath, appPath, args, m_selectedInstance->name(), iconPath)) {
+        QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!"));
+    } else {
+        iconFile.remove();
+        QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!"));
     }
 }
 
-- 
cgit 


From 92847b977409f4f4b4daa1e34196596e40becc05 Mon Sep 17 00:00:00 2001
From: Trial97 <alexandru.tripon97@gmail.com>
Date: Tue, 27 Jun 2023 19:15:20 +0300
Subject: omit icon remove on macos

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
---
 launcher/ui/MainWindow.cpp | 2 ++
 1 file changed, 2 insertions(+)

(limited to 'launcher/ui/MainWindow.cpp')

diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 7407483f..496738e3 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -1613,7 +1613,9 @@ void MainWindow::on_actionCreateInstanceShortcut_triggered()
     if (FS::createShortcut(desktopFilePath, appPath, args, m_selectedInstance->name(), iconPath)) {
         QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!"));
     } else {
+#if not defined(Q_OS_MACOS)
         iconFile.remove();
+#endif
         QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!"));
     }
 }
-- 
cgit 


From 0008b22d8b352e3591ee7ba7c6d9313ed23cbd4a Mon Sep 17 00:00:00 2001
From: Trial97 <alexandru.tripon97@gmail.com>
Date: Wed, 28 Jun 2023 18:41:47 +0300
Subject: Renamed function

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
---
 launcher/ui/MainWindow.cpp                | 2 +-
 launcher/ui/instanceview/InstanceView.cpp | 9 ++++++---
 launcher/ui/instanceview/InstanceView.h   | 2 +-
 3 files changed, 8 insertions(+), 5 deletions(-)

(limited to 'launcher/ui/MainWindow.cpp')

diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 26fcb3a3..515abf07 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -927,7 +927,7 @@ void MainWindow::onCatToggled(bool state)
 
 void MainWindow::setCatBackground(bool enabled)
 {
-    view->setCatVisible(enabled);
+    view->setPaintCat(enabled);
     view->viewport()->repaint();
 }
 
diff --git a/launcher/ui/instanceview/InstanceView.cpp b/launcher/ui/instanceview/InstanceView.cpp
index 4c83e94a..1911dd59 100644
--- a/launcher/ui/instanceview/InstanceView.cpp
+++ b/launcher/ui/instanceview/InstanceView.cpp
@@ -74,7 +74,7 @@ InstanceView::InstanceView(QWidget *parent)
     setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
     setAcceptDrops(true);
     setAutoScroll(true);
-    setCatVisible(APPLICATION->settings()->get("TheCat").toBool());
+    setPaintCat(APPLICATION->settings()->get("TheCat").toBool());
 }
 
 InstanceView::~InstanceView()
@@ -500,10 +500,13 @@ void InstanceView::mouseDoubleClickEvent(QMouseEvent *event)
     }
 }
 
-void InstanceView::setCatVisible(bool visible)
+void InstanceView::setPaintCat(bool visible)
 {
     m_catVisible = visible;
-    m_catPixmap.load(QString(":/backgrounds/%1").arg(ThemeManager::getCatImage()));
+    if (visible)
+        m_catPixmap.load(QString(":/backgrounds/%1").arg(ThemeManager::getCatImage()));
+    else
+        m_catPixmap = QPixmap();
 }
 
 void InstanceView::paintEvent(QPaintEvent* event)
diff --git a/launcher/ui/instanceview/InstanceView.h b/launcher/ui/instanceview/InstanceView.h
index a9bd0bd7..36405675 100644
--- a/launcher/ui/instanceview/InstanceView.h
+++ b/launcher/ui/instanceview/InstanceView.h
@@ -86,7 +86,7 @@ public:
     virtual QRegion visualRegionForSelection(const QItemSelection &selection) const override;
 
     int spacing() const { return m_spacing; };
-    void setCatVisible(bool visible);
+    void setPaintCat(bool visible);
 
 public slots:
     virtual void updateGeometries() override;
-- 
cgit 


From a54bbae62282e8adce76df9626c58b6bb3e12967 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Sat, 8 Jul 2023 12:59:55 -0700
Subject: fix(instance edit): don't allow editing if no selected instance or
 instance doesn't support editing

Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
 launcher/ui/MainWindow.cpp | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

(limited to 'launcher/ui/MainWindow.cpp')

diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 515abf07..254f229d 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -1279,7 +1279,17 @@ void MainWindow::globalSettingsClosed()
 
 void MainWindow::on_actionEditInstance_triggered()
 {
-    APPLICATION->showInstanceWindow(m_selectedInstance);
+
+    if (!m_selectedInstance)
+        return;
+
+    if (m_selectedInstance->canEdit()) {
+        APPLICATION->showInstanceWindow(m_selectedInstance);
+    } else  {
+        CustomMessageBox::selectable(this, tr("Instance not editable"), 
+                                     tr("This instance is not editable. it may be broken, invalid, or too old. Check logs for details,"),
+                                     QMessageBox::Critical)->show();
+    }
 }
 
 void MainWindow::on_actionManageAccounts_triggered()
-- 
cgit 


From 99ba02afb6a7af1bc0800552338ab811b027eb9e Mon Sep 17 00:00:00 2001
From: Nathan <subtype.kitsch_0w@icloud.com>
Date: Mon, 10 Jul 2023 11:26:25 -0700
Subject: Shortcuts on macOS (#1081)

Co-authored-by: TheKodeToad <TheKodeToad@proton.me>
---
 launcher/FileSystem.cpp    | 60 ++++++++++++++++++++++++++++++++++++++++++++--
 launcher/ui/MainWindow.cpp | 44 +++++++++++++++++++++++++++++-----
 2 files changed, 96 insertions(+), 8 deletions(-)

(limited to 'launcher/ui/MainWindow.cpp')

diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index 812d45eb..4538702f 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -778,9 +778,43 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
         destination = PathCombine(getDesktopDir(), RemoveInvalidFilenameChars(name));
     }
 #if defined(Q_OS_MACOS)
-    destination += ".command";
+    // Create the Application
+    QDir applicationDirectory = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + "/" + BuildConfig.LAUNCHER_NAME + " Instances/";
 
-    QFile f(destination);
+    if (!applicationDirectory.mkpath(".")) {
+        qWarning() << "Couldn't create application directory";
+        return false;
+    }
+
+    QDir application = applicationDirectory.path() + "/" + name + ".app/";
+
+    if (application.exists()) {
+        qWarning() << "Application already exists!";
+        return false;
+    }
+
+    if (!application.mkpath(".")) {
+        qWarning() << "Couldn't create application";
+        return false;
+    }
+
+    QDir content = application.path() + "/Contents/";
+    QDir resources = content.path() + "/Resources/";
+    QDir binaryDir = content.path() + "/MacOS/";
+    QFile info = content.path() + "/Info.plist";
+
+    if (!(content.mkpath(".") && resources.mkpath(".") && binaryDir.mkpath("."))) {
+        qWarning() << "Couldn't create directories within application";
+        return false;
+    }
+    info.open(QIODevice::WriteOnly | QIODevice::Text);
+
+    QFile(icon).rename(resources.path() + "/Icon.icns");
+
+    // Create the Command file
+    QString exec = binaryDir.path() + "/Run.command";
+
+    QFile f(exec);
     f.open(QIODevice::WriteOnly | QIODevice::Text);
     QTextStream stream(&f);
 
@@ -797,6 +831,28 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
 
     f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | QFileDevice::ExeOther);
 
+    // Generate the Info.plist
+    QTextStream infoStream(&info);
+    infoStream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?> \n"
+                  "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" "
+                  "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"
+                  "<plist version=\"1.0\">\n"
+                  "<dict>\n"
+                  "    <key>CFBundleExecutable</key>\n"
+                  "    <string>Run.command</string>\n"  // The path to the executable
+                  "    <key>CFBundleIconFile</key>\n"
+                  "    <string>Icon.icns</string>\n"
+                  "    <key>CFBundleName</key>\n"
+                  "    <string>" << name << "</string>\n"  // Name of the application
+                  "    <key>CFBundlePackageType</key>\n"
+                  "    <string>APPL</string>\n"
+                  "    <key>CFBundleShortVersionString</key>\n"
+                  "    <string>1.0</string>\n"
+                  "    <key>CFBundleVersion</key>\n"
+                  "    <string>1.0</string>\n"
+                  "</dict>\n"
+                  "</plist>";
+
     return true;
 #elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
     if (!destination.endsWith(".desktop"))  // in case of isFlatpak destination is already populated
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 2f944472..fe364937 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -1536,11 +1536,39 @@ void MainWindow::on_actionCreateInstanceShortcut_triggered()
     QString iconPath;
     QStringList args;
 #if defined(Q_OS_MACOS)
-    if (appPath.startsWith("/private/var/")) {
-        QMessageBox::critical(this, tr("Create instance shortcut"),
-                              tr("The launcher is in the folder it was extracted from, therefore it cannot create shortcuts."));
-        return;
-    }
+        appPath = QApplication::applicationFilePath();
+        if (appPath.startsWith("/private/var/")) {
+            QMessageBox::critical(this, tr("Create instance shortcut"), 
+                                  tr("The launcher is in the folder it was extracted from, therefore it cannot create shortcuts."));
+            return;
+        }
+
+        auto pIcon = APPLICATION->icons()->icon(m_selectedInstance->iconKey());
+        if (pIcon == nullptr)
+        {
+            pIcon = APPLICATION->icons()->icon("grass");
+        }
+
+        iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "Icon.icns");
+
+        QFile iconFile(iconPath);
+        if (!iconFile.open(QFile::WriteOnly))
+        {
+            QMessageBox::critical(this, tr("Create instance Application"), tr("Failed to create icon for Application."));
+            return;
+        }
+
+        QIcon icon = pIcon->icon();
+
+        bool success = icon.pixmap(1024, 1024).save(iconPath, "ICNS");
+        iconFile.close();
+
+        if (!success)
+        {
+            iconFile.remove();
+            QMessageBox::critical(this, tr("Create instance Application"), tr("Failed to create icon for Application."));
+            return;
+        }
 #elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
     if (appPath.startsWith("/tmp/.mount_")) {
         // AppImage!
@@ -1623,7 +1651,11 @@ void MainWindow::on_actionCreateInstanceShortcut_triggered()
 #endif
     args.append({ "--launch", m_selectedInstance->id() });
     if (FS::createShortcut(desktopFilePath, appPath, args, m_selectedInstance->name(), iconPath)) {
-        QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!"));
+#if not defined(Q_OS_MACOS)
+            QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!"));
+#else
+        QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance!"));
+#endif
     } else {
 #if not defined(Q_OS_MACOS)
         iconFile.remove();
-- 
cgit 


From 1aa5fa03f94438ce53899e4660f12e794fd8bc35 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Mon, 10 Jul 2023 12:05:01 -0700
Subject: Update launcher/ui/MainWindow.cpp

Co-authored-by: TheKodeToad <TheKodeToad@proton.me>
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
 launcher/ui/MainWindow.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'launcher/ui/MainWindow.cpp')

diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 254f229d..5a8fcc78 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -1287,7 +1287,7 @@ void MainWindow::on_actionEditInstance_triggered()
         APPLICATION->showInstanceWindow(m_selectedInstance);
     } else  {
         CustomMessageBox::selectable(this, tr("Instance not editable"), 
-                                     tr("This instance is not editable. it may be broken, invalid, or too old. Check logs for details,"),
+                                     tr("This instance is not editable. It may be broken, invalid, or too old. Check logs for details."),
                                      QMessageBox::Critical)->show();
     }
 }
-- 
cgit