diff options
-rw-r--r-- | cmake/MacOSXBundleInfo.plist.in | 11 | ||||
-rw-r--r-- | launcher/Application.cpp | 44 | ||||
-rw-r--r-- | launcher/Application.h | 4 | ||||
-rw-r--r-- | launcher/InstanceImportTask.cpp | 38 | ||||
-rw-r--r-- | launcher/InstanceImportTask.h | 1 | ||||
-rw-r--r-- | launcher/ui/pages/modplatform/ImportPage.cpp | 58 | ||||
-rw-r--r-- | launcher/ui/pages/modplatform/ImportPage.ui | 2 | ||||
-rw-r--r-- | program_info/org.prismlauncher.PrismLauncher.desktop.in | 2 | ||||
-rw-r--r-- | program_info/win_install.nsi.in | 4 |
9 files changed, 132 insertions, 32 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 1659eb44..5694b82e 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -216,9 +216,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("urls","import the resource at the given url(s) (same as -I / --import)","[urls...]"); + parser.addHelpOption(); parser.addVersionOption(); @@ -231,16 +234,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")){ + addImportUrl(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()) { + addImportUrl(url); } - // error if --launch is missing with --server or --profile if((!m_serverToJoin.isEmpty() || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty()) { @@ -343,12 +345,12 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) activate.command = "activate"; m_peerInstance->sendMessage(activate.serialize(), timeout); - if(!m_zipsToImport.isEmpty()) + if(!m_urlsToImport.isEmpty()) { - for (auto zip_url : m_zipsToImport) { + for (auto url : m_urlsToImport) { ApplicationMessage import; import.command = "import"; - import.args.insert("path", zip_url.toString()); + import.args.insert("path", url.toString()); m_peerInstance->sendMessage(import.serialize(), timeout); } } @@ -1025,10 +1027,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 ); } } @@ -1081,7 +1083,12 @@ void Application::messageReceived(const QByteArray& message) qWarning() << "Received" << command << "message without a zip path/URL."; return; } - m_mainWindow->processURLs({ QUrl::fromLocalFile(QFileInfo(path).absoluteFilePath()) }); + auto local_file = QFileInfo(path); + if (local_file.exists()) { + m_mainWindow->processURLs({ QUrl::fromLocalFile(local_file.absoluteFilePath()) }); + } else { + m_mainWindow->processURLs({ QUrl::fromUserInput(path) }); + } } else if(command == "launch") { @@ -1733,3 +1740,14 @@ void Application::triggerUpdateCheck() qDebug() << "Updater not available."; } } + +void Application::addImportUrl(QString const& url) +{ + auto local_file = QFileInfo(url); + if (local_file.exists()){ + m_urlsToImport.append(QUrl::fromLocalFile(local_file.absoluteFilePath())); + } else { + m_urlsToImport.append(QUrl::fromUserInput(url)); + } +} + diff --git a/launcher/Application.h b/launcher/Application.h index ced0af17..83bfa9ef 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -211,6 +211,8 @@ public: int suitableMaxMem(); + void addImportUrl(QString const& url); + signals: void updateAllowedChanged(bool status); void globalSettingsAboutToOpen(); @@ -314,7 +316,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 8a48873e..41b7898c 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -47,8 +47,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 <QtConcurrentRun> #include <algorithm> @@ -87,25 +90,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::Download::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::propogateStepProgress); - 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::Download::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::propogateStepProgress); + 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(); @@ -396,3 +401,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/ui/pages/modplatform/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp index 30196aad..78b7d1d5 100644 --- a/launcher/ui/pages/modplatform/ImportPage.cpp +++ b/launcher/ui/pages/modplatform/ImportPage.cpp @@ -35,12 +35,16 @@ */ #include "ImportPage.h" +#include "ui/dialogs/ProgressDialog.h" #include "ui_ImportPage.h" #include <QFileDialog> #include <QValidator> #include "ui/dialogs/NewInstanceDialog.h" +#include "ui/dialogs/CustomMessageBox.h" + +#include "Json.h" #include "InstanceImportTask.h" @@ -123,8 +127,62 @@ void ImportPage::updateState() dialog->setSuggestedIcon("default"); } } + 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 = new QByteArray(); + auto req = unique_qobject_ptr<NetJob>(new NetJob("Curseforge Meta", APPLICATION->network())); + req->addNetAction( + Net::Download::makeByteArray(QUrl(QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(addonId, fileId)), array)); + + connect(req.get(), &NetJob::finished, [array] { + delete array; + }); + connect(req.get(), &NetJob::failed, this, [this](QString reason){ + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); + }); + connect(req.get(), &NetJob::succeeded, this, [this, array, addonId, fileId] { + qDebug() << "Returned CFURL Json:\n" << array->toStdString().c_str(); + auto doc = Json::requireDocument(*array); + // 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(Json::ensureObject(Json::ensureObject(doc.object()), "data"), "fileName"); + if (fileName.endsWith(".zip")) { + // Have to use ensureString then use QUrl to get proper url encoding + auto dl_url = QUrl( + Json::ensureString(Json::ensureObject(Json::ensureObject(doc.object()), "data"), "downloadUrl", "", "downloadUrl")); + if (!dl_url.isValid()) { + CustomMessageBox::selectable(this, tr("Error"), tr("The modpack is blocked ! Please download it manually"), QMessageBox::Critical)->show(); + return; + } + + QFileInfo dl_file(dl_url.fileName()); + QString pack_name = Json::ensureString(Json::ensureObject(Json::ensureObject(doc.object()), "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(req.get()); + return; + } else { + + if(input.endsWith("?client=y")) { input.chop(9); input.append("/file"); 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 f08f2ba4..675e1f1f 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;launcher;mc;multimc;polymc; 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 1d902d5d..d2409fbb 100644 --- a/program_info/win_install.nsi.in +++ b/program_info/win_install.nsi.in @@ -295,6 +295,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 |