aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmake/MacOSXBundleInfo.plist.in11
-rw-r--r--launcher/Application.cpp59
-rw-r--r--launcher/Application.h4
-rw-r--r--launcher/InstanceImportTask.cpp38
-rw-r--r--launcher/InstanceImportTask.h1
-rw-r--r--launcher/modplatform/flame/FlameAPI.cpp11
-rw-r--r--launcher/modplatform/flame/FlameAPI.h1
-rw-r--r--launcher/ui/MainWindow.cpp101
-rw-r--r--launcher/ui/MainWindow.h2
-rw-r--r--launcher/ui/dialogs/NewInstanceDialog.cpp7
-rw-r--r--launcher/ui/dialogs/NewInstanceDialog.h43
-rw-r--r--launcher/ui/pages/modplatform/ImportPage.cpp92
-rw-r--r--launcher/ui/pages/modplatform/ImportPage.h3
-rw-r--r--launcher/ui/pages/modplatform/ImportPage.ui2
-rw-r--r--program_info/org.prismlauncher.PrismLauncher.desktop.in2
-rw-r--r--program_info/win_install.nsi.in4
16 files changed, 285 insertions, 96 deletions
diff --git a/cmake/MacOSXBundleInfo.plist.in b/cmake/MacOSXBundleInfo.plist.in
index 400e482f..d36ac3e8 100644
--- a/cmake/MacOSXBundleInfo.plist.in
+++ b/cmake/MacOSXBundleInfo.plist.in
@@ -67,5 +67,16 @@
<string>Alternate</string>
</dict>
</array>
+ <key>CFBundleURLTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleURLName</key>
+ <string>Curseforge</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>curseforge</string>
+ </array>
+ </dict>
+ </array>
</dict>
</plist>
diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index e89b7659..fc435369 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -196,9 +196,12 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
{{"s", "server"}, "Join the specified server on launch (only valid in combination with --launch)", "address"},
{{"a", "profile"}, "Use the account specified by its profile name (only valid in combination with --launch)", "profile"},
{"alive", "Write a small '" + liveCheckFile + "' file after the launcher starts"},
- {{"I", "import"}, "Import instance from specified zip (local path or URL)", "file"},
+ {{"I", "import"}, "Import instance or resource from specified local path or URL", "url"},
{"show", "Opens the window for the specified instance (by instance ID)", "show"}
});
+ // Has to be positional for some OS to handle that properly
+ parser.addPositionalArgument("URL", "Import the resource(s) at the given URL(s) (same as -I / --import)", "[URL...]");
+
parser.addHelpOption();
parser.addVersionOption();
@@ -211,16 +214,15 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_instanceIdToShowWindowOf = parser.value("show");
- for (auto zip_path : parser.values("import")){
- m_zipsToImport.append(QUrl::fromLocalFile(QFileInfo(zip_path).absoluteFilePath()));
+ for (auto url : parser.values("import")){
+ m_urlsToImport.append(normalizeImportUrl(url));
}
// treat unspecified positional arguments as import urls
- for (auto zip_path : parser.positionalArguments()) {
- m_zipsToImport.append(QUrl::fromLocalFile(QFileInfo(zip_path).absoluteFilePath()));
+ for (auto url : parser.positionalArguments()) {
+ m_urlsToImport.append(normalizeImportUrl(url));
}
-
// error if --launch is missing with --server or --profile
if((!m_serverToJoin.isEmpty() || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty())
{
@@ -332,12 +334,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
activate.command = "activate";
m_peerInstance->sendMessage(activate.serialize(), timeout);
- if(!m_zipsToImport.isEmpty())
- {
- for (auto zip_url : m_zipsToImport) {
+ if (!m_urlsToImport.isEmpty()) {
+ for (auto url : m_urlsToImport) {
ApplicationMessage import;
import.command = "import";
- import.args.insert("path", zip_url.toString());
+ import.args.insert("url", url.toString());
m_peerInstance->sendMessage(import.serialize(), timeout);
}
}
@@ -1023,10 +1024,10 @@ void Application::performMainStartupAction()
showMainWindow(false);
qDebug() << "<> Main window shown.";
}
- if(!m_zipsToImport.isEmpty())
+ if(!m_urlsToImport.isEmpty())
{
- qDebug() << "<> Importing from zip:" << m_zipsToImport;
- m_mainWindow->processURLs( m_zipsToImport );
+ qDebug() << "<> Importing from url:" << m_urlsToImport;
+ m_mainWindow->processURLs( m_urlsToImport );
}
}
@@ -1067,22 +1068,16 @@ void Application::messageReceived(const QByteArray& message)
auto & command = received.command;
- if(command == "activate")
- {
+ if (command == "activate") {
showMainWindow();
- }
- else if(command == "import")
- {
- QString path = received.args["path"];
- if(path.isEmpty())
- {
+ } else if (command == "import") {
+ QString url = received.args["url"];
+ if (url.isEmpty()) {
qWarning() << "Received" << command << "message without a zip path/URL.";
return;
}
- m_mainWindow->processURLs({ QUrl::fromLocalFile(QFileInfo(path).absoluteFilePath()) });
- }
- else if(command == "launch")
- {
+ m_mainWindow->processURLs({ normalizeImportUrl(url) });
+ } else if (command == "launch") {
QString id = received.args["id"];
QString server = received.args["server"];
QString profile = received.args["profile"];
@@ -1122,9 +1117,7 @@ void Application::messageReceived(const QByteArray& message)
serverObject,
accountObject
);
- }
- else
- {
+ } else {
qWarning() << "Received invalid message" << message;
}
}
@@ -1742,3 +1735,13 @@ void Application::triggerUpdateCheck()
qDebug() << "Updater not available.";
}
}
+
+QUrl Application::normalizeImportUrl(QString const& url)
+{
+ auto local_file = QFileInfo(url);
+ if (local_file.exists()) {
+ return QUrl::fromLocalFile(local_file.absoluteFilePath());
+ } else {
+ return QUrl::fromUserInput(url);
+ }
+}
diff --git a/launcher/Application.h b/launcher/Application.h
index c0a980b2..0296e6a8 100644
--- a/launcher/Application.h
+++ b/launcher/Application.h
@@ -214,6 +214,8 @@ public:
int suitableMaxMem();
+ QUrl normalizeImportUrl(QString const& url);
+
signals:
void updateAllowedChanged(bool status);
void globalSettingsAboutToOpen();
@@ -317,7 +319,7 @@ public:
QString m_serverToJoin;
QString m_profileToUse;
bool m_liveCheck = false;
- QList<QUrl> m_zipsToImport;
+ QList<QUrl> m_urlsToImport;
QString m_instanceIdToShowWindowOf;
std::unique_ptr<QFile> logFile;
};
diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp
index 98ed14b9..f067cc34 100644
--- a/launcher/InstanceImportTask.cpp
+++ b/launcher/InstanceImportTask.cpp
@@ -48,8 +48,11 @@
#include "modplatform/technic/TechnicPackProcessor.h"
#include "modplatform/modrinth/ModrinthInstanceCreationTask.h"
#include "modplatform/flame/FlameInstanceCreationTask.h"
+// FIXME : move this over to FlameInstanceCreationTask
+#include "Json.h"
#include "settings/INISettingsObject.h"
+#include "tasks/Task.h"
#include "net/ApiDownload.h"
@@ -90,25 +93,27 @@ void InstanceImportTask::executeTask()
setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString()));
m_downloadRequired = true;
- const QString path(m_sourceUrl.host() + '/' + m_sourceUrl.path());
-
- auto entry = APPLICATION->metacache()->resolveEntry("general", path);
- entry->setStale(true);
- m_archivePath = entry->getFullPath();
-
- m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network()));
- m_filesNetJob->addNetAction(Net::ApiDownload::makeCached(m_sourceUrl, entry));
-
- connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
- connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
- connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propagateStepProgress);
- connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed);
- connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted);
-
- m_filesNetJob->start();
+ downloadFromUrl();
}
}
+void InstanceImportTask::downloadFromUrl()
+{
+ const QString path = m_sourceUrl.host() + '/' + m_sourceUrl.path();
+ auto entry = APPLICATION->metacache()->resolveEntry("general", path);
+ entry->setStale(true);
+ m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network()));
+ m_filesNetJob->addNetAction(Net::ApiDownload::makeCached(m_sourceUrl, entry));
+ m_archivePath = entry->getFullPath();
+
+ connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
+ connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
+ connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propagateStepProgress);
+ connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed);
+ connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted);
+ m_filesNetJob->start();
+}
+
void InstanceImportTask::downloadSucceeded()
{
processZipPack();
@@ -398,3 +403,4 @@ void InstanceImportTask::processModrinth()
inst_creation_task->start();
}
+
diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h
index 7fda439f..9c1edc6d 100644
--- a/launcher/InstanceImportTask.h
+++ b/launcher/InstanceImportTask.h
@@ -106,4 +106,5 @@ private: /* data */
//FIXME: nuke
QWidget* m_parent;
+ void downloadFromUrl();
};
diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp
index 7231b466..2871f06a 100644
--- a/launcher/modplatform/flame/FlameAPI.cpp
+++ b/launcher/modplatform/flame/FlameAPI.cpp
@@ -204,6 +204,17 @@ Task::Ptr FlameAPI::getFiles(const QStringList& fileIds, std::shared_ptr<QByteAr
return netJob;
}
+Task::Ptr FlameAPI::getFile(const QString& addonId, const QString& fileId, std::shared_ptr<QByteArray>response) const
+{
+ auto netJob = makeShared<NetJob>(QString("Flame::GetFile"), APPLICATION->network());
+ netJob->addNetAction(
+ Net::Download::makeByteArray(QUrl(QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(addonId, fileId)), response));
+
+ QObject::connect(netJob.get(), &NetJob::failed, [addonId, fileId] { qDebug() << "Flame API file failure" << addonId << fileId; });
+
+ return netJob;
+}
+
// https://docs.curseforge.com/?python#tocS_ModsSearchSortField
static QList<ResourceAPI::SortingMethod> s_sorts = { { 1, "Featured", QObject::tr("Sort by Featured") },
{ 2, "Popularity", QObject::tr("Sort by Popularity") },
diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h
index 49bc316f..281c0a09 100644
--- a/launcher/modplatform/flame/FlameAPI.h
+++ b/launcher/modplatform/flame/FlameAPI.h
@@ -20,6 +20,7 @@ class FlameAPI : public NetworkResourceAPI {
Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const override;
Task::Ptr matchFingerprints(const QList<uint>& fingerprints, std::shared_ptr<QByteArray> response);
Task::Ptr getFiles(const QStringList& fileIds, std::shared_ptr<QByteArray> response) const;
+ Task::Ptr getFile(const QString& addonId, const QString& fileId, std::shared_ptr<QByteArray> response) const;
[[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override;
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index e342e833..dd383b23 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -118,11 +118,15 @@
#include "minecraft/mod/ShaderPackFolderModel.h"
#include "minecraft/mod/tasks/LocalResourceParse.h"
+#include "modplatform/flame/FlameAPI.h"
+
#include "KonamiCode.h"
#include "InstanceCopyTask.h"
#include "InstanceImportTask.h"
+#include "Json.h"
+
#include "MMCTime.h"
namespace {
@@ -929,7 +933,7 @@ void MainWindow::finalizeInstance(InstancePtr inst)
}
}
-void MainWindow::addInstance(QString url)
+void MainWindow::addInstance(const QString& url, const QMap<QString, QString>& extra_info)
{
QString groupName;
do {
@@ -949,7 +953,7 @@ void MainWindow::addInstance(QString url)
groupName = APPLICATION->settings()->get("LastUsedGroupForNewInstance").toString();
}
- NewInstanceDialog newInstDlg(groupName, url, this);
+ NewInstanceDialog newInstDlg(groupName, url, extra_info, this);
if (!newInstDlg.exec())
return;
@@ -976,18 +980,101 @@ void MainWindow::processURLs(QList<QUrl> urls)
if (url.scheme().isEmpty())
url.setScheme("file");
- if (!url.isLocalFile()) { // probably instance/modpack
- addInstance(url.toString());
- break;
+ QMap<QString, QString> extra_info;
+ QUrl local_url;
+ if (!url.isLocalFile()) { // download the remote resource and identify
+ QUrl dl_url;
+ if(url.scheme() == "curseforge") {
+ // need to find the download link for the modpack / resource
+ // format of url curseforge://install?addonId=IDHERE&fileId=IDHERE
+ QUrlQuery query(url);
+
+ auto addonId = query.allQueryItemValues("addonId")[0];
+ auto fileId = query.allQueryItemValues("fileId")[0];
+
+ extra_info.insert("pack_id", addonId);
+ extra_info.insert("pack_version_id", fileId);
+
+ auto array = std::make_shared<QByteArray>();
+
+ auto api = FlameAPI();
+ auto job = api.getFile(addonId, fileId, array);
+
+ QString resource_name;
+
+ connect(job.get(), &Task::failed, this,
+ [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
+ connect(job.get(), &Task::succeeded, this, [this, array, addonId, fileId, &dl_url, &resource_name] {
+ qDebug() << "Returned CFURL Json:\n" << array->toStdString().c_str();
+ auto doc = Json::requireDocument(*array);
+ auto data = Json::ensureObject(Json::ensureObject(doc.object()), "data");
+ // No way to find out if it's a mod or a modpack before here
+ // And also we need to check if it ends with .zip, instead of any better way
+ auto fileName = Json::ensureString(data, "fileName");
+
+ // Have to use ensureString then use QUrl to get proper url encoding
+ dl_url = QUrl(Json::ensureString(data, "downloadUrl", "", "downloadUrl"));
+ if (!dl_url.isValid()) {
+ CustomMessageBox::selectable(
+ this, tr("Error"),
+ tr("The modpack, mod, or resource %1 is blocked for third-parties! Please download it manually.").arg(fileName),
+ QMessageBox::Critical)
+ ->show();
+ return;
+ }
+
+ QFileInfo dl_file(dl_url.fileName());
+ resource_name = Json::ensureString(data, "displayName", dl_file.completeBaseName(), "displayName");
+ });
+
+ { // drop stack
+ ProgressDialog dlUrlDialod(this);
+ dlUrlDialod.setSkipButton(true, tr("Abort"));
+ dlUrlDialod.execWithTask(job.get());
+ }
+
+
+ } else {
+ dl_url = url;
+ }
+
+ if (!dl_url.isValid()) {
+ continue; // no valid url to download this resource
+ }
+
+ const QString path = dl_url.host() + '/' + dl_url.path();
+ auto entry = APPLICATION->metacache()->resolveEntry("general", path);
+ entry->setStale(true);
+ auto dl_job = unique_qobject_ptr<NetJob>(new NetJob(tr("Modpack download"), APPLICATION->network()));
+ dl_job->addNetAction(Net::Download::makeCached(dl_url, entry));
+ auto archivePath = entry->getFullPath();
+
+ bool dl_success = false;
+ connect(dl_job.get(), &Task::failed, this, [this](QString reason){CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
+ connect(dl_job.get(), &Task::succeeded, this, [&dl_success]{dl_success = true;});
+
+ { // drop stack
+ ProgressDialog dlUrlDialod(this);
+ dlUrlDialod.setSkipButton(true, tr("Abort"));
+ dlUrlDialod.execWithTask(dl_job.get());
+ }
+
+ if (!dl_success) {
+ continue; // no local file to identify
+ }
+ local_url = QUrl::fromLocalFile(archivePath);
+
+ } else {
+ local_url = url;
}
- auto localFileName = QDir::toNativeSeparators(url.toLocalFile());
+ auto localFileName = QDir::toNativeSeparators(local_url.toLocalFile());
QFileInfo localFileInfo(localFileName);
auto type = ResourceUtils::identify(localFileInfo);
if (ResourceUtils::ValidResourceTypes.count(type) == 0) { // probably instance/modpack
- addInstance(localFileName);
+ addInstance(localFileName, extra_info);
continue;
}
diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h
index 27c2756f..7fab3fe3 100644
--- a/launcher/ui/MainWindow.h
+++ b/launcher/ui/MainWindow.h
@@ -217,7 +217,7 @@ private slots:
private:
void retranslateUi();
- void addInstance(QString url = QString());
+ void addInstance(const QString& url = QString(), const QMap<QString, QString>& extra_info = {});
void activateInstance(InstancePtr instance);
void setCatBackground(bool enabled);
void updateInstanceToolIcon(QString new_icon);
diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp
index 2f1854d8..935f095e 100644
--- a/launcher/ui/dialogs/NewInstanceDialog.cpp
+++ b/launcher/ui/dialogs/NewInstanceDialog.cpp
@@ -62,8 +62,10 @@
#include "ui/pages/modplatform/modrinth/ModrinthPage.h"
#include "ui/pages/modplatform/technic/TechnicPage.h"
#include "ui/widgets/PageContainer.h"
-
-NewInstanceDialog::NewInstanceDialog(const QString& initialGroup, const QString& url, QWidget* parent)
+NewInstanceDialog::NewInstanceDialog(const QString& initialGroup,
+ const QString& url,
+ const QMap<QString, QString>& extra_info,
+ QWidget* parent)
: QDialog(parent), ui(new Ui::NewInstanceDialog)
{
ui->setupUi(this);
@@ -127,6 +129,7 @@ NewInstanceDialog::NewInstanceDialog(const QString& initialGroup, const QString&
QUrl actualUrl(url);
m_container->selectPage("import");
importPage->setUrl(url);
+ importPage->setExtraInfo(extra_info);
}
updateDialogState();
diff --git a/launcher/ui/dialogs/NewInstanceDialog.h b/launcher/ui/dialogs/NewInstanceDialog.h
index 961f512e..6a6ad89e 100644
--- a/launcher/ui/dialogs/NewInstanceDialog.h
+++ b/launcher/ui/dialogs/NewInstanceDialog.h
@@ -37,11 +37,10 @@
#include <QDialog>
-#include "ui/pages/BasePageProvider.h"
#include "InstanceTask.h"
+#include "ui/pages/BasePageProvider.h"
-namespace Ui
-{
+namespace Ui {
class NewInstanceDialog;
}
@@ -50,45 +49,47 @@ class QDialogButtonBox;
class ImportPage;
class FlamePage;
-class NewInstanceDialog : public QDialog, public BasePageProvider
-{
+class NewInstanceDialog : public QDialog, public BasePageProvider {
Q_OBJECT
-public:
- explicit NewInstanceDialog(const QString & initialGroup, const QString & url = QString(), QWidget *parent = 0);
+ public:
+ explicit NewInstanceDialog(const QString& initialGroup,
+ const QString& url = QString(),
+ const QMap<QString, QString>& extra_info = {},
+ QWidget* parent = 0);
~NewInstanceDialog();
void updateDialogState();
- void setSuggestedPack(const QString& name = QString(), InstanceTask * task = nullptr);
- void setSuggestedPack(const QString& name, QString version, InstanceTask * task = nullptr);
- void setSuggestedIconFromFile(const QString &path, const QString &name);
- void setSuggestedIcon(const QString &key);
+ void setSuggestedPack(const QString& name = QString(), InstanceTask* task = nullptr);
+ void setSuggestedPack(const QString& name, QString version, InstanceTask* task = nullptr);
+ void setSuggestedIconFromFile(const QString& path, const QString& name);
+ void setSuggestedIcon(const QString& key);
- InstanceTask * extractTask();
+ InstanceTask* extractTask();
QString dialogTitle() override;
- QList<BasePage *> getPages() override;
+ QList<BasePage*> getPages() override;
QString instName() const;
QString instGroup() const;
QString iconKey() const;
-public slots:
+ public slots:
void accept() override;
void reject() override;
-private slots:
+ private slots:
void on_iconButton_clicked();
- void on_instNameTextBox_textChanged(const QString &arg1);
+ void on_instNameTextBox_textChanged(const QString& arg1);
-private:
- Ui::NewInstanceDialog *ui = nullptr;
- PageContainer * m_container = nullptr;
- QDialogButtonBox * m_buttons = nullptr;
+ private:
+ Ui::NewInstanceDialog* ui = nullptr;
+ PageContainer* m_container = nullptr;
+ QDialogButtonBox* m_buttons = nullptr;
QString InstIconKey;
- ImportPage *importPage = nullptr;
+ ImportPage* importPage = nullptr;
std::unique_ptr<InstanceTask> creationTask;
bool importIcon = false;
diff --git a/launcher/ui/pages/modplatform/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp
index 30196aad..8250193a 100644
--- a/launcher/ui/pages/modplatform/ImportPage.cpp
+++ b/launcher/ui/pages/modplatform/ImportPage.cpp
@@ -35,12 +35,20 @@
*/
#include "ImportPage.h"
+
+#include "ui/dialogs/ProgressDialog.h"
#include "ui_ImportPage.h"
#include <QFileDialog>
#include <QValidator>
+#include <utility>
#include "ui/dialogs/NewInstanceDialog.h"
+#include "ui/dialogs/CustomMessageBox.h"
+
+#include "modplatform/flame/FlameAPI.h"
+
+#include "Json.h"
#include "InstanceImportTask.h"
@@ -98,16 +106,13 @@ void ImportPage::openedImpl()
void ImportPage::updateState()
{
- if(!isOpened)
- {
+ if (!isOpened) {
return;
}
- if(ui->modpackEdit->hasAcceptableInput())
- {
+ if (ui->modpackEdit->hasAcceptableInput()) {
QString input = ui->modpackEdit->text();
auto url = QUrl::fromUserInput(input);
- if(url.isLocalFile())
- {
+ if (url.isLocalFile()) {
// FIXME: actually do some validation of what's inside here... this is fake AF
QFileInfo fi(input);
@@ -116,28 +121,76 @@ void ImportPage::updateState()
// mrpack is a modrinth pack
bool isMRPack = fi.suffix() == "mrpack";
- if(fi.exists() && (isZip || isMRPack))
- {
+ if (fi.exists() && (isZip || isMRPack)) {
QFileInfo fi(url.fileName());
- dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url,this));
+ auto extra_info = QMap(m_extra_info);
+ qDebug() << "Pack Extra Info" << extra_info << m_extra_info;
+ dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this, std::move(extra_info)));
dialog->setSuggestedIcon("default");
}
- }
- else
- {
- if(input.endsWith("?client=y")) {
+ } else if (url.scheme() == "curseforge") {
+ // need to find the download link for the modpack
+ // format of url curseforge://install?addonId=IDHERE&fileId=IDHERE
+ QUrlQuery query(url);
+ auto addonId = query.allQueryItemValues("addonId")[0];
+ auto fileId = query.allQueryItemValues("fileId")[0];
+ auto array = std::make_shared<QByteArray>();
+
+ auto api = FlameAPI();
+ auto job = api.getFile(addonId, fileId, array);
+
+ connect(job.get(), &NetJob::failed, this,
+ [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
+ connect(job.get(), &NetJob::succeeded, this, [this, array, addonId, fileId] {
+ qDebug() << "Returned CFURL Json:\n" << array->toStdString().c_str();
+ auto doc = Json::requireDocument(*array);
+ auto data = Json::ensureObject(Json::ensureObject(doc.object()), "data");
+ // No way to find out if it's a mod or a modpack before here
+ // And also we need to check if it ends with .zip, instead of any better way
+ auto fileName = Json::ensureString(data, "fileName");
+ if (fileName.endsWith(".zip")) {
+ // Have to use ensureString then use QUrl to get proper url encoding
+ auto dl_url = QUrl(Json::ensureString(data, "downloadUrl", "", "downloadUrl"));
+ if (!dl_url.isValid()) {
+ CustomMessageBox::selectable(
+ this, tr("Error"),
+ tr("The modpack %1 is blocked for third-parties! Please download it manually.").arg(fileName),
+ QMessageBox::Critical)
+ ->show();
+ return;
+ }
+
+ QFileInfo dl_file(dl_url.fileName());
+ QString pack_name = Json::ensureString(data, "displayName", dl_file.completeBaseName(), "displayName");
+
+ QMap<QString, QString> extra_info;
+ extra_info.insert("pack_id", addonId);
+ extra_info.insert("pack_version_id", fileId);
+
+ dialog->setSuggestedPack(pack_name, new InstanceImportTask(dl_url, this, std::move(extra_info)));
+ dialog->setSuggestedIcon("default");
+
+ } else {
+ CustomMessageBox::selectable(this, tr("Error"), tr("This url isn't a valid modpack !"), QMessageBox::Critical)->show();
+ }
+ });
+ ProgressDialog dlUrlDialod(this);
+ dlUrlDialod.setSkipButton(true, tr("Abort"));
+ dlUrlDialod.execWithTask(job.get());
+ return;
+ } else {
+ if (input.endsWith("?client=y")) {
input.chop(9);
input.append("/file");
url = QUrl::fromUserInput(input);
}
// hook, line and sinker.
QFileInfo fi(url.fileName());
- dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url,this));
+ auto extra_info = QMap(m_extra_info);
+ dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this, std::move(extra_info)));
dialog->setSuggestedIcon("default");
}
- }
- else
- {
+ } else {
dialog->setSuggestedPack();
}
}
@@ -148,6 +201,11 @@ void ImportPage::setUrl(const QString& url)
updateState();
}
+void ImportPage::setExtraInfo(const QMap<QString, QString>& extra_info) {
+ m_extra_info = extra_info;
+ updateState();
+}
+
void ImportPage::on_modpackBtn_clicked()
{
auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString();
diff --git a/launcher/ui/pages/modplatform/ImportPage.h b/launcher/ui/pages/modplatform/ImportPage.h
index c2acb92d..54640752 100644
--- a/launcher/ui/pages/modplatform/ImportPage.h
+++ b/launcher/ui/pages/modplatform/ImportPage.h
@@ -76,7 +76,7 @@ public:
void setUrl(const QString & url);
void openedImpl() override;
-
+ void setExtraInfo(const QMap<QString, QString>& extra_info);
private slots:
void on_modpackBtn_clicked();
void updateState();
@@ -87,5 +87,6 @@ private:
private:
Ui::ImportPage *ui = nullptr;
NewInstanceDialog* dialog = nullptr;
+ QMap<QString, QString> m_extra_info = {};
};
diff --git a/launcher/ui/pages/modplatform/ImportPage.ui b/launcher/ui/pages/modplatform/ImportPage.ui
index 3583cf90..9a9736b8 100644
--- a/launcher/ui/pages/modplatform/ImportPage.ui
+++ b/launcher/ui/pages/modplatform/ImportPage.ui
@@ -40,7 +40,7 @@
<item>
<widget class="QLabel" name="label_5">
<property name="text">
- <string>- CurseForge modpacks (ZIP)</string>
+ <string>- CurseForge modpacks (ZIP / curseforge:// URL)</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
diff --git a/program_info/org.prismlauncher.PrismLauncher.desktop.in b/program_info/org.prismlauncher.PrismLauncher.desktop.in
index 20fabe9d..816c0059 100644
--- a/program_info/org.prismlauncher.PrismLauncher.desktop.in
+++ b/program_info/org.prismlauncher.PrismLauncher.desktop.in
@@ -10,4 +10,4 @@ Icon=org.prismlauncher.PrismLauncher
Categories=Game;ActionGame;AdventureGame;Simulation;
Keywords=game;minecraft;mc;
StartupWMClass=PrismLauncher
-MimeType=application/zip;application/x-modrinth-modpack+zip
+MimeType=application/zip;application/x-modrinth-modpack+zip;x-scheme-handler/curseforge;
diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in
index d3b5c256..cb7b0935 100644
--- a/program_info/win_install.nsi.in
+++ b/program_info/win_install.nsi.in
@@ -363,6 +363,10 @@ Section "@Launcher_DisplayName@"
; Write the installation path into the registry
WriteRegStr HKCU Software\@Launcher_CommonName@ "InstallDir" "$INSTDIR"
+ ; Write the URL Handler into registry for curseforge
+ WriteRegStr HKCU Software\Classes\curseforge "URL Protocol" ""
+ WriteRegStr HKCU Software\Classes\curseforge\shell\open\command "" '"$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "%1"'
+
; Write the uninstall keys for Windows
${GetParameters} $R0
${GetOptions} $R0 "/NoUninstaller" $R1