aboutsummaryrefslogtreecommitdiff
path: root/launcher
diff options
context:
space:
mode:
Diffstat (limited to 'launcher')
-rw-r--r--launcher/Application.cpp21
-rw-r--r--launcher/Application.h10
-rw-r--r--launcher/BaseInstance.cpp6
-rw-r--r--launcher/FileSystem.cpp43
-rw-r--r--launcher/FileSystem.h4
-rw-r--r--launcher/InstanceImportTask.cpp116
-rw-r--r--launcher/minecraft/mod/Mod.cpp44
-rw-r--r--launcher/minecraft/mod/Mod.h8
-rw-r--r--launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp31
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackManifest.cpp16
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackManifest.h7
-rw-r--r--launcher/ui/MainWindow.cpp11
-rw-r--r--launcher/ui/MainWindow.h2
-rw-r--r--launcher/ui/pages/modplatform/ImportPage.cpp11
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicPage.ui6
15 files changed, 241 insertions, 95 deletions
diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index a4525d83..4e0393c0 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -875,6 +875,12 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_mcedit.reset(new MCEditTool(m_settings));
}
+#ifdef Q_OS_MACOS
+ connect(this, &Application::clickedOnDock, [this]() {
+ this->showMainWindow();
+ });
+#endif
+
connect(this, &Application::aboutToQuit, [this](){
if(m_instances)
{
@@ -958,6 +964,21 @@ bool Application::createSetupWizard()
return false;
}
+bool Application::event(QEvent* event) {
+#ifdef Q_OS_MACOS
+ if (event->type() == QEvent::ApplicationStateChange) {
+ auto ev = static_cast<QApplicationStateChangeEvent*>(event);
+
+ if (m_prevAppState == Qt::ApplicationActive
+ && ev->applicationState() == Qt::ApplicationActive) {
+ emit clickedOnDock();
+ }
+ m_prevAppState = ev->applicationState();
+ }
+#endif
+ return QApplication::event(event);
+}
+
void Application::setupWizardFinished(int status)
{
qDebug() << "Wizard result =" << status;
diff --git a/launcher/Application.h b/launcher/Application.h
index f440f433..e08e354a 100644
--- a/launcher/Application.h
+++ b/launcher/Application.h
@@ -94,6 +94,8 @@ public:
Application(int &argc, char **argv);
virtual ~Application();
+ bool event(QEvent* event) override;
+
std::shared_ptr<SettingsObject> settings() const {
return m_settings;
}
@@ -183,6 +185,10 @@ signals:
void globalSettingsAboutToOpen();
void globalSettingsClosed();
+#ifdef Q_OS_MACOS
+ void clickedOnDock();
+#endif
+
public slots:
bool launch(
InstancePtr instance,
@@ -240,6 +246,10 @@ private:
QString m_rootPath;
Status m_status = Application::StartingUp;
+#ifdef Q_OS_MACOS
+ Qt::ApplicationState m_prevAppState = Qt::ApplicationInactive;
+#endif
+
#if defined Q_OS_WIN32
// used on Windows to attach the standard IO streams
bool consoleAttached = false;
diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp
index 0240afa8..f02205e9 100644
--- a/launcher/BaseInstance.cpp
+++ b/launcher/BaseInstance.cpp
@@ -59,7 +59,11 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
m_settings->registerSetting("lastLaunchTime", 0);
m_settings->registerSetting("totalTimePlayed", 0);
m_settings->registerSetting("lastTimePlayed", 0);
- m_settings->registerSetting("InstanceType", "");
+
+ // NOTE: Sometimees InstanceType is already registered, as it was used to identify the type of
+ // a locally stored instance
+ if (!m_settings->getSetting("InstanceType"))
+ m_settings->registerSetting("InstanceType", "");
// Custom Commands
auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false);
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index 6de20de6..3837d75f 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -454,4 +454,47 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na
return false;
#endif
}
+
+QStringList listFolderPaths(QDir root)
+{
+ auto createAbsPath = [](QFileInfo const& entry) { return FS::PathCombine(entry.path(), entry.fileName()); };
+
+ QStringList entries;
+
+ root.refresh();
+ for (auto entry : root.entryInfoList(QDir::Filter::Files)) {
+ entries.append(createAbsPath(entry));
+ }
+
+ for (auto entry : root.entryInfoList(QDir::Filter::AllDirs | QDir::Filter::NoDotAndDotDot)) {
+ entries.append(listFolderPaths(createAbsPath(entry)));
+ }
+
+ return entries;
+}
+
+bool overrideFolder(QString overwritten_path, QString override_path)
+{
+ if (!FS::ensureFolderPathExists(overwritten_path))
+ return false;
+
+ QStringList paths_to_override;
+ QDir root_override (override_path);
+ for (auto file : listFolderPaths(root_override)) {
+ QString destination = file;
+ destination.replace(override_path, overwritten_path);
+
+ qDebug() << QString("Applying override %1 in %2").arg(file, destination);
+
+ if (QFile::exists(destination))
+ QFile::remove(destination);
+ if (!QFile::rename(file, destination)) {
+ qCritical() << QString("Failed to apply override from %1 to %2").arg(file, destination);
+ return false;
+ }
+ }
+
+ return true;
+}
+
}
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index 8f6e8b48..bc942ab3 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -124,4 +124,8 @@ QString getDesktopDir();
// call it *name* and assign it the icon *icon*
// return true if operation succeeded
bool createShortCut(QString location, QString dest, QStringList args, QString name, QString iconLocation);
+
+// Overrides one folder with the contents of another, preserving items exclusive to the first folder
+// Equivalent to doing QDir::rename, but allowing for overrides
+bool overrideFolder(QString overwritten_path, QString override_path);
}
diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp
index 09c2a333..d5684805 100644
--- a/launcher/InstanceImportTask.cpp
+++ b/launcher/InstanceImportTask.cpp
@@ -582,10 +582,10 @@ void InstanceImportTask::processMultiMC()
emitSucceeded();
}
+// https://docs.modrinth.com/docs/modpacks/format_definition/
void InstanceImportTask::processModrinth()
{
std::vector<Modrinth::File> files;
- std::vector<Modrinth::File> non_whitelisted_files;
QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion;
try {
QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json");
@@ -600,26 +600,30 @@ void InstanceImportTask::processModrinth()
auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json");
bool had_optional = false;
- for (auto& modInfo : jsonFiles) {
+ for (auto modInfo : jsonFiles) {
Modrinth::File file;
file.path = Json::requireString(modInfo, "path");
auto env = Json::ensureObject(modInfo, "env");
- QString support = Json::ensureString(env, "client", "unsupported");
- if (support == "unsupported") {
- continue;
- } else if (support == "optional") {
- // TODO: Make a review dialog for choosing which ones the user wants!
- if (!had_optional) {
- had_optional = true;
- auto info = CustomMessageBox::selectable(
- m_parent, tr("Optional mod detected!"),
- tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"), QMessageBox::Information);
- info->exec();
- }
+ // 'env' field is optional
+ if (!env.isEmpty()) {
+ QString support = Json::ensureString(env, "client", "unsupported");
+ if (support == "unsupported") {
+ continue;
+ } else if (support == "optional") {
+ // TODO: Make a review dialog for choosing which ones the user wants!
+ if (!had_optional) {
+ had_optional = true;
+ auto info = CustomMessageBox::selectable(
+ m_parent, tr("Optional mod detected!"),
+ tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"),
+ QMessageBox::Information);
+ info->exec();
+ }
- if (file.path.endsWith(".jar"))
- file.path += ".disabled";
+ if (file.path.endsWith(".jar"))
+ file.path += ".disabled";
+ }
}
QJsonObject hashes = Json::requireObject(modInfo, "hashes");
@@ -640,40 +644,31 @@ void InstanceImportTask::processModrinth()
}
file.hash = QByteArray::fromHex(hash.toLatin1());
file.hashAlgorithm = hashAlgorithm;
+
// Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode
// (as Modrinth seems to incorrectly handle spaces)
-
- file.download = Json::requireString(Json::ensureArray(modInfo, "downloads").first(), "Download URL for " + file.path);
-
- if (!file.download.isValid()) {
- qDebug() << QString("Download URL (%1) for %2 is not a correctly formatted URL").arg(file.download.toString(), file.path);
- throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path));
- }
- else if (!Modrinth::validateDownloadUrl(file.download)) {
- qDebug() << QString("Download URL (%1) for %2 is from a non-whitelisted by Modrinth domain").arg(file.download.toString(), file.path);
- non_whitelisted_files.push_back(file);
+
+ auto download_arr = Json::ensureArray(modInfo, "downloads");
+ for(auto download : download_arr) {
+ qWarning() << download.toString();
+ bool is_last = download.toString() == download_arr.last().toString();
+
+ auto download_url = QUrl(download.toString());
+
+ if (!download_url.isValid()) {
+ qDebug() << QString("Download URL (%1) for %2 is not a correctly formatted URL")
+ .arg(download_url.toString(), file.path);
+ if(is_last && file.downloads.isEmpty())
+ throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path));
+ }
+ else {
+ file.downloads.push_back(download_url);
+ }
}
files.push_back(file);
}
- if (!non_whitelisted_files.empty()) {
- QString text;
- for (const auto& file : non_whitelisted_files) {
- text += tr("Filepath: %1<br>URL: <a href='%2'>%2</a><br>").arg(file.path, file.download.toString());
- }
-
- auto message_dialog = new ScrollMessageBox(m_parent, tr("Non-whitelisted mods found"),
- tr("The following mods have URLs that are not whitelisted by Modrinth.\n"
- "Proceed with caution!"),
- text);
- message_dialog->setModal(true);
- if (message_dialog->exec() == QDialog::Rejected) {
- emitFailed("Aborted");
- return;
- }
- }
-
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
QString name = it.key();
@@ -701,16 +696,26 @@ void InstanceImportTask::processModrinth()
emitFailed(tr("Could not understand pack index:\n") + e.cause());
return;
}
+
+ auto mcPath = FS::PathCombine(m_stagingPath, ".minecraft");
- QString overridePath = FS::PathCombine(m_stagingPath, "overrides");
- if (QFile::exists(overridePath)) {
- QString mcPath = FS::PathCombine(m_stagingPath, ".minecraft");
- if (!QFile::rename(overridePath, mcPath)) {
+ auto override_path = FS::PathCombine(m_stagingPath, "overrides");
+ if (QFile::exists(override_path)) {
+ if (!QFile::rename(override_path, mcPath)) {
emitFailed(tr("Could not rename the overrides folder:\n") + "overrides");
return;
}
}
+ // Do client overrides
+ auto client_override_path = FS::PathCombine(m_stagingPath, "client-overrides");
+ if (QFile::exists(client_override_path)) {
+ if (!FS::overrideFolder(mcPath, client_override_path)) {
+ emitFailed(tr("Could not rename the client overrides folder:\n") + "client overrides");
+ return;
+ }
+ }
+
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
@@ -735,13 +740,24 @@ void InstanceImportTask::processModrinth()
instance.saveNow();
m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network());
- for (auto &file : files)
+ for (auto file : files)
{
auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path);
- qDebug() << "Will download" << file.download << "to" << path;
- auto dl = Net::Download::makeFile(file.download, path);
+ qDebug() << "Will try to download" << file.downloads.front() << "to" << path;
+ auto dl = Net::Download::makeFile(file.downloads.dequeue(), path);
dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
m_filesNetJob->addNetAction(dl);
+
+ if (file.downloads.size() > 0) {
+ // FIXME: This really needs to be put into a ConcurrentTask of
+ // MultipleOptionsTask's , once those exist :)
+ connect(dl.get(), &NetAction::failed, [this, &file, path, dl]{
+ auto dl = Net::Download::makeFile(file.downloads.dequeue(), path);
+ dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
+ m_filesNetJob->addNetAction(dl);
+ dl->succeeded();
+ });
+ }
}
connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]()
{
diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp
index 71a32d32..39c7efd8 100644
--- a/launcher/minecraft/mod/Mod.cpp
+++ b/launcher/minecraft/mod/Mod.cpp
@@ -58,8 +58,6 @@ Mod::Mod(const QFileInfo& file)
Mod::Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata)
: m_file(mods_dir.absoluteFilePath(metadata.filename))
- // It is weird, but name is not reliable for comparing with the JAR files name
- // FIXME: Maybe use hash when implemented?
, m_internal_id(metadata.filename)
, m_name(metadata.name)
{
@@ -131,7 +129,7 @@ auto Mod::enable(bool value) -> bool
return false;
} else {
path += ".disabled";
-
+
if (!file.rename(path))
return false;
}
@@ -145,16 +143,22 @@ auto Mod::enable(bool value) -> bool
void Mod::setStatus(ModStatus status)
{
- if(m_localDetails.get())
+ if (m_localDetails) {
m_localDetails->status = status;
+ } else {
+ m_temp_status = status;
+ }
}
void Mod::setMetadata(Metadata::ModStruct* metadata)
{
- if(status() == ModStatus::NoMetadata)
+ if (status() == ModStatus::NoMetadata)
setStatus(ModStatus::Installed);
- if(m_localDetails.get())
+ if (m_localDetails) {
m_localDetails->metadata.reset(metadata);
+ } else {
+ m_temp_metadata.reset(metadata);
+ }
}
auto Mod::destroy(QDir& index_dir) -> bool
@@ -205,20 +209,36 @@ auto Mod::authors() const -> QStringList
auto Mod::status() const -> ModStatus
{
+ if (!m_localDetails)
+ return m_temp_status;
return details().status;
}
+auto Mod::metadata() -> std::shared_ptr<Metadata::ModStruct>
+{
+ if (m_localDetails)
+ return m_localDetails->metadata;
+ return m_temp_metadata;
+}
+
+auto Mod::metadata() const -> const std::shared_ptr<Metadata::ModStruct>
+{
+ if (m_localDetails)
+ return m_localDetails->metadata;
+ return m_temp_metadata;
+}
+
void Mod::finishResolvingWithDetails(std::shared_ptr<ModDetails> details)
{
m_resolving = false;
m_resolved = true;
m_localDetails = details;
- if (status() != ModStatus::NoMetadata
- && m_temp_metadata.get()
- && m_temp_metadata->isValid() &&
- m_localDetails.get()) {
-
- m_localDetails->metadata.swap(m_temp_metadata);
+ if (m_localDetails && m_temp_metadata && m_temp_metadata->isValid()) {
+ m_localDetails->metadata = m_temp_metadata;
+ if (status() == ModStatus::NoMetadata)
+ setStatus(ModStatus::Installed);
}
+
+ setStatus(m_temp_status);
}
diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h
index 96d471b4..5f9c4684 100644
--- a/launcher/minecraft/mod/Mod.h
+++ b/launcher/minecraft/mod/Mod.h
@@ -73,8 +73,8 @@ public:
auto authors() const -> QStringList;
auto status() const -> ModStatus;
- auto metadata() const -> const std::shared_ptr<Metadata::ModStruct> { return details().metadata; };
- auto metadata() -> std::shared_ptr<Metadata::ModStruct> { return m_localDetails->metadata; };
+ auto metadata() -> std::shared_ptr<Metadata::ModStruct>;
+ auto metadata() const -> const std::shared_ptr<Metadata::ModStruct>;
void setStatus(ModStatus status);
void setMetadata(Metadata::ModStruct* metadata);
@@ -109,6 +109,10 @@ protected:
/* If the mod has metadata, this will be filled in the constructor, and passed to
* the ModDetails when calling finishResolvingWithDetails */
std::shared_ptr<Metadata::ModStruct> m_temp_metadata;
+
+ /* Set the mod status while it doesn't have local details just yet */
+ ModStatus m_temp_status = ModStatus::NotInstalled;
+
std::shared_ptr<ModDetails> m_localDetails;
bool m_enabled = true;
diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
index 62d856f6..80242fef 100644
--- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
+++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
@@ -53,12 +53,33 @@ void ModFolderLoadTask::run()
m_mods_dir.refresh();
for (auto entry : m_mods_dir.entryInfoList()) {
Mod mod(entry);
- if(m_result->mods.contains(mod.internal_id())){
- m_result->mods[mod.internal_id()].setStatus(ModStatus::Installed);
+
+ if (mod.enabled()) {
+ if (m_result->mods.contains(mod.internal_id())) {
+ m_result->mods[mod.internal_id()].setStatus(ModStatus::Installed);
+ }
+ else {
+ m_result->mods[mod.internal_id()] = mod;
+ m_result->mods[mod.internal_id()].setStatus(ModStatus::NoMetadata);
+ }
}
- else {
- m_result->mods[mod.internal_id()] = mod;
- m_result->mods[mod.internal_id()].setStatus(ModStatus::NoMetadata);
+ else {
+ QString chopped_id = mod.internal_id().chopped(9);
+ if (m_result->mods.contains(chopped_id)) {
+ m_result->mods[mod.internal_id()] = mod;
+
+ auto metadata = m_result->mods[chopped_id].metadata();
+ if (metadata) {
+ mod.setMetadata(new Metadata::ModStruct(*metadata));
+
+ m_result->mods[mod.internal_id()].setStatus(ModStatus::Installed);
+ m_result->mods.remove(chopped_id);
+ }
+ }
+ else {
+ m_result->mods[mod.internal_id()] = mod;
+ m_result->mods[mod.internal_id()].setStatus(ModStatus::NoMetadata);
+ }
}
}
diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp
index 33116231..cc12f62f 100644
--- a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp
+++ b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp
@@ -95,19 +95,6 @@ void loadIndexedVersions(Modpack& pack, QJsonDocument& doc)
pack.versionsLoaded = true;
}
-auto validateDownloadUrl(QUrl url) -> bool
-{
- static QSet<QString> domainWhitelist{
- "cdn.modrinth.com",
- "github.com",
- "raw.githubusercontent.com",
- "gitlab.com"
- };
-
- auto domain = url.host();
- return domainWhitelist.contains(domain);
-}
-
auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion
{
ModpackVersion file;
@@ -137,9 +124,6 @@ auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion
auto url = Json::requireString(parent, "url");
- if(!validateDownloadUrl(url))
- continue;
-
file.download_url = url;
if(is_primary)
break;
diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.h b/launcher/modplatform/modrinth/ModrinthPackManifest.h
index e5fc9a70..b2083f57 100644
--- a/launcher/modplatform/modrinth/ModrinthPackManifest.h
+++ b/launcher/modplatform/modrinth/ModrinthPackManifest.h
@@ -40,6 +40,7 @@
#include <QByteArray>
#include <QCryptographicHash>
+#include <QQueue>
#include <QString>
#include <QUrl>
#include <QVector>
@@ -48,14 +49,12 @@ class MinecraftInstance;
namespace Modrinth {
-struct File
-{
+struct File {
QString path;
QCryptographicHash::Algorithm hashAlgorithm;
QByteArray hash;
- // TODO: should this support multiple download URLs, like the JSON does?
- QUrl download;
+ QQueue<QUrl> downloads;
};
struct ModpackExtra {
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 7e152b96..0a5f2000 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -2101,6 +2101,9 @@ void MainWindow::instanceChanged(const QModelIndex &current, const QModelIndex &
selectionBad();
return;
}
+ if (m_selectedInstance) {
+ disconnect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance);
+ }
QString id = current.data(InstanceList::InstanceIDRole).toString();
m_selectedInstance = APPLICATION->instances()->getInstanceById(id);
if (m_selectedInstance)
@@ -2127,6 +2130,8 @@ void MainWindow::instanceChanged(const QModelIndex &current, const QModelIndex &
updateToolsMenu();
APPLICATION->settings()->set("SelectedInstance", m_selectedInstance->id());
+
+ connect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance);
}
else
{
@@ -2216,3 +2221,9 @@ void MainWindow::updateStatusCenter()
m_statusCenter->setText(tr("Total playtime: %1").arg(Time::prettifyDuration(timePlayed)));
}
}
+
+void MainWindow::refreshCurrentInstance(bool running)
+{
+ auto current = view->selectionModel()->currentIndex();
+ instanceChanged(current, current);
+}
diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h
index 2032acba..61a75c45 100644
--- a/launcher/ui/MainWindow.h
+++ b/launcher/ui/MainWindow.h
@@ -192,6 +192,8 @@ private slots:
void keyReleaseEvent(QKeyEvent *event) override;
#endif
+ void refreshCurrentInstance(bool running);
+
private:
void retranslateUi();
diff --git a/launcher/ui/pages/modplatform/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp
index b3ed1b73..0b8577b1 100644
--- a/launcher/ui/pages/modplatform/ImportPage.cpp
+++ b/launcher/ui/pages/modplatform/ImportPage.cpp
@@ -110,11 +110,13 @@ void ImportPage::updateState()
{
// FIXME: actually do some validation of what's inside here... this is fake AF
QFileInfo fi(input);
- // mrpack is a modrinth pack
// Allow non-latin people to use ZIP files!
- auto zip = QMimeDatabase().mimeTypeForUrl(url).suffixes().contains("zip");
- if(fi.exists() && (zip || fi.suffix() == "mrpack"))
+ bool isZip = QMimeDatabase().mimeTypeForUrl(url).suffixes().contains("zip");
+ // mrpack is a modrinth pack
+ bool isMRPack = fi.suffix() == "mrpack";
+
+ if(fi.exists() && (isZip || isMRPack))
{
QFileInfo fi(url.fileName());
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url,this));
@@ -149,7 +151,8 @@ void ImportPage::setUrl(const QString& url)
void ImportPage::on_modpackBtn_clicked()
{
auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString();
- filter += ";;" + tr("Modrinth pack (*.mrpack)");
+ //: Option for filtering for *.mrpack files when importing
+ filter += ";;" + tr("Modrinth pack") + " (*.mrpack)";
const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), filter);
if (url.isValid())
{
diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.ui b/launcher/ui/pages/modplatform/technic/TechnicPage.ui
index ca6a9b7e..15bf645f 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicPage.ui
+++ b/launcher/ui/pages/modplatform/technic/TechnicPage.ui
@@ -60,7 +60,11 @@
</widget>
</item>
<item row="0" column="1">
- <widget class="QTextBrowser" name="packDescription"/>
+ <widget class="QTextBrowser" name="packDescription">
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
</item>
</layout>
</item>