aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/FUNDING.yml1
-rw-r--r--README.md7
-rw-r--r--buildconfig/BuildConfig.h2
-rw-r--r--launcher/CMakeLists.txt4
-rw-r--r--launcher/minecraft/auth/Parsers.cpp175
-rw-r--r--launcher/minecraft/auth/Parsers.h1
-rw-r--r--launcher/minecraft/auth/Yggdrasil.cpp22
-rw-r--r--launcher/minecraft/auth/flows/Mojang.cpp6
-rw-r--r--launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp94
-rw-r--r--launcher/minecraft/auth/steps/MinecraftProfileStepMojang.h22
-rw-r--r--launcher/modplatform/atlauncher/ATLShareCode.cpp60
-rw-r--r--launcher/modplatform/atlauncher/ATLShareCode.h47
-rw-r--r--launcher/ui/MainWindow.cpp8
-rw-r--r--launcher/ui/pages/global/APIPage.ui4
-rw-r--r--launcher/ui/pages/global/AccountListPage.ui18
-rw-r--r--launcher/ui/pages/global/ExternalToolsPage.ui8
-rw-r--r--launcher/ui/pages/global/JavaPage.ui16
-rw-r--r--launcher/ui/pages/global/LauncherPage.ui18
-rw-r--r--launcher/ui/pages/global/MinecraftPage.ui20
-rw-r--r--launcher/ui/pages/global/ProxyPage.ui10
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp150
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h50
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui16
-rw-r--r--launcher/ui/widgets/CustomCommands.ui8
-rw-r--r--libraries/iconfix/CMakeLists.txt2
25 files changed, 673 insertions, 96 deletions
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 00000000..247675fc
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+open_collective: polymc
diff --git a/README.md b/README.md
index 4c73c47e..c493293d 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,8 @@
PolyMC is a custom launcher for Minecraft that focuses on predictability, long term stability and simplicity.
-This is a **fork** of the MultiMC Launcher and not endorsed by MultiMC. The PolyMC community felt that the maintainer was not acting in the spirit of Free Software so this fork was made.
+This is a **fork** of the MultiMC Launcher and not endorsed by MultiMC.
+If you want to read about why this fork was created, check out [our FAQ page](https://polymc.org/wiki/overview/faq/).
<br>
# Installation
@@ -81,8 +82,8 @@ To modify download information or change packaging information send a pull reque
Do whatever you want, we don't care. Just follow the license. If you have any questions about this feel free to ask in an issue.
-All launcher code is available under the GPL-3 license.
+All launcher code is available under the GPL-3.0-only license.
-[Source for the website](https://github.com/PolyMC/polymc.github.io) is hosted under the AGPL-3 License.
+[Source for the website](https://github.com/PolyMC/polymc.github.io) is hosted under the AGPL-3.0-or-later License.
The logo and related assets are under the CC BY-SA 4.0 license.
diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h
index 6304387c..c1d34708 100644
--- a/buildconfig/BuildConfig.h
+++ b/buildconfig/BuildConfig.h
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
@@ -139,6 +140,7 @@ public:
QString LEGACY_FTB_CDN_BASE_URL = "https://dist.creeper.host/FTB2/";
QString ATL_DOWNLOAD_SERVER_URL = "https://download.nodecdn.net/containers/atl/";
+ QString ATL_API_BASE_URL = "https://api.atlauncher.com/v1/";
QString TECHNIC_API_BASE_URL = "https://api.technicpack.net/";
/**
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 6ed86726..b79f03c8 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -235,6 +235,8 @@ set(MINECRAFT_SOURCES
minecraft/auth/steps/MigrationEligibilityStep.h
minecraft/auth/steps/MinecraftProfileStep.cpp
minecraft/auth/steps/MinecraftProfileStep.h
+ minecraft/auth/steps/MinecraftProfileStepMojang.cpp
+ minecraft/auth/steps/MinecraftProfileStepMojang.h
minecraft/auth/steps/MSAStep.cpp
minecraft/auth/steps/MSAStep.h
minecraft/auth/steps/XboxAuthorizationStep.cpp
@@ -557,6 +559,8 @@ set(ATLAUNCHER_SOURCES
modplatform/atlauncher/ATLPackInstallTask.h
modplatform/atlauncher/ATLPackManifest.cpp
modplatform/atlauncher/ATLPackManifest.h
+ modplatform/atlauncher/ATLShareCode.cpp
+ modplatform/atlauncher/ATLShareCode.h
)
add_unit_test(Index
diff --git a/launcher/minecraft/auth/Parsers.cpp b/launcher/minecraft/auth/Parsers.cpp
index 2dd36562..47473899 100644
--- a/launcher/minecraft/auth/Parsers.cpp
+++ b/launcher/minecraft/auth/Parsers.cpp
@@ -1,4 +1,5 @@
#include "Parsers.h"
+#include "Json.h"
#include <QJsonDocument>
#include <QJsonArray>
@@ -212,6 +213,180 @@ bool parseMinecraftProfile(QByteArray & data, MinecraftProfile &output) {
return true;
}
+namespace {
+ // these skin URLs are for the MHF_Steve and MHF_Alex accounts (made by a Mojang employee)
+ // they are needed because the session server doesn't return skin urls for default skins
+ static const QString SKIN_URL_STEVE = "http://textures.minecraft.net/texture/1a4af718455d4aab528e7a61f86fa25e6a369d1768dcb13f7df319a713eb810b";
+ static const QString SKIN_URL_ALEX = "http://textures.minecraft.net/texture/83cee5ca6afcdb171285aa00e8049c297b2dbeba0efb8ff970a5677a1b644032";
+
+ bool isDefaultModelSteve(QString uuid) {
+ // need to calculate *Java* hashCode of UUID
+ // if number is even, skin/model is steve, otherwise it is alex
+
+ // just in case dashes are in the id
+ uuid.remove('-');
+
+ if (uuid.size() != 32) {
+ return true;
+ }
+
+ // qulonglong is guaranteed to be 64 bits
+ // we need to use unsigned numbers to guarantee truncation below
+ qulonglong most = uuid.left(16).toULongLong(nullptr, 16);
+ qulonglong least = uuid.right(16).toULongLong(nullptr, 16);
+ qulonglong xored = most ^ least;
+ return ((static_cast<quint32>(xored >> 32)) ^ static_cast<quint32>(xored)) % 2 == 0;
+ }
+}
+
+/**
+Uses session server for skin/cape lookup instead of profile,
+because locked Mojang accounts cannot access profile endpoint
+(https://api.minecraftservices.com/minecraft/profile/)
+
+ref: https://wiki.vg/Mojang_API#UUID_to_Profile_and_Skin.2FCape
+
+{
+ "id": "<profile identifier>",
+ "name": "<player name>",
+ "properties": [
+ {
+ "name": "textures",
+ "value": "<base64 string>"
+ }
+ ]
+}
+
+decoded base64 "value":
+{
+ "timestamp": <java time in ms>,
+ "profileId": "<profile uuid>",
+ "profileName": "<player name>",
+ "textures": {
+ "SKIN": {
+ "url": "<player skin URL>"
+ },
+ "CAPE": {
+ "url": "<player cape URL>"
+ }
+ }
+}
+*/
+
+bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) {
+ qDebug() << "Parsing Minecraft profile...";
+#ifndef NDEBUG
+ qDebug() << data;
+#endif
+
+ QJsonParseError jsonError;
+ QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
+ if(jsonError.error) {
+ qWarning() << "Failed to parse response as JSON: " << jsonError.errorString();
+ return false;
+ }
+
+ auto obj = Json::requireObject(doc, "mojang minecraft profile");
+ if(!getString(obj.value("id"), output.id)) {
+ qWarning() << "Minecraft profile id is not a string";
+ return false;
+ }
+
+ if(!getString(obj.value("name"), output.name)) {
+ qWarning() << "Minecraft profile name is not a string";
+ return false;
+ }
+
+ auto propsArray = obj.value("properties").toArray();
+ QByteArray texturePayload;
+ for( auto p : propsArray) {
+ auto pObj = p.toObject();
+ auto name = pObj.value("name");
+ if (!name.isString() || name.toString() != "textures") {
+ continue;
+ }
+
+ auto value = pObj.value("value");
+ if (value.isString()) {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
+ texturePayload = QByteArray::fromBase64(value.toString().toUtf8(), QByteArray::AbortOnBase64DecodingErrors);
+#else
+ texturePayload = QByteArray::fromBase64(value.toString().toUtf8());
+#endif
+ }
+
+ if (!texturePayload.isEmpty()) {
+ break;
+ }
+ }
+
+ if (texturePayload.isNull()) {
+ qWarning() << "No texture payload data";
+ return false;
+ }
+
+ doc = QJsonDocument::fromJson(texturePayload, &jsonError);
+ if(jsonError.error) {
+ qWarning() << "Failed to parse response as JSON: " << jsonError.errorString();
+ return false;
+ }
+
+ obj = Json::requireObject(doc, "session texture payload");
+ auto textures = obj.value("textures");
+ if (!textures.isObject()) {
+ qWarning() << "No textures array in response";
+ return false;
+ }
+
+ Skin skinOut;
+ // fill in default skin info ourselves, as this endpoint doesn't provide it
+ bool steve = isDefaultModelSteve(output.id);
+ skinOut.variant = steve ? "classic" : "slim";
+ skinOut.url = steve ? SKIN_URL_STEVE : SKIN_URL_ALEX;
+ // sadly we can't figure this out, but I don't think it really matters...
+ skinOut.id = "00000000-0000-0000-0000-000000000000";
+ Cape capeOut;
+ auto tObj = textures.toObject();
+ for (auto idx = tObj.constBegin(); idx != tObj.constEnd(); ++idx) {
+ if (idx->isObject()) {
+ if (idx.key() == "SKIN") {
+ auto skin = idx->toObject();
+ if (!getString(skin.value("url"), skinOut.url)) {
+ qWarning() << "Skin url is not a string";
+ return false;
+ }
+
+ auto maybeMeta = skin.find("metadata");
+ if (maybeMeta != skin.end() && maybeMeta->isObject()) {
+ auto meta = maybeMeta->toObject();
+ // might not be present
+ getString(meta.value("model"), skinOut.variant);
+ }
+ }
+ else if (idx.key() == "CAPE") {
+ auto cape = idx->toObject();
+ if (!getString(cape.value("url"), capeOut.url)) {
+ qWarning() << "Cape url is not a string";
+ return false;
+ }
+
+ // we don't know the cape ID as it is not returned from the session server
+ // so just fake it - changing capes is probably locked anyway :(
+ capeOut.alias = "cape";
+ }
+ }
+ }
+
+ output.skin = skinOut;
+ if (capeOut.alias == "cape") {
+ output.capes = QMap<QString, Cape>({{capeOut.alias, capeOut}});
+ output.currentCape = capeOut.alias;
+ }
+
+ output.validity = Katabasis::Validity::Certain;
+ return true;
+}
+
bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output) {
qDebug() << "Parsing Minecraft entitlements...";
#ifndef NDEBUG
diff --git a/launcher/minecraft/auth/Parsers.h b/launcher/minecraft/auth/Parsers.h
index dac7f69b..2666d890 100644
--- a/launcher/minecraft/auth/Parsers.h
+++ b/launcher/minecraft/auth/Parsers.h
@@ -14,6 +14,7 @@ namespace Parsers
bool parseMojangResponse(QByteArray &data, Katabasis::Token &output);
bool parseMinecraftProfile(QByteArray &data, MinecraftProfile &output);
+ bool parseMinecraftProfileMojang(QByteArray &data, MinecraftProfile &output);
bool parseMinecraftEntitlements(QByteArray &data, MinecraftEntitlement &output);
bool parseRolloutResponse(QByteArray &data, bool& result);
}
diff --git a/launcher/minecraft/auth/Yggdrasil.cpp b/launcher/minecraft/auth/Yggdrasil.cpp
index 7ac842a6..29978411 100644
--- a/launcher/minecraft/auth/Yggdrasil.cpp
+++ b/launcher/minecraft/auth/Yggdrasil.cpp
@@ -209,6 +209,28 @@ void Yggdrasil::processResponse(QJsonObject responseData) {
m_data->yggdrasilToken.validity = Katabasis::Validity::Certain;
m_data->yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc();
+ // Get UUID here since we need it for later
+ auto profile = responseData.value("selectedProfile");
+ if (!profile.isObject()) {
+ changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send a selected profile."));
+ return;
+ }
+
+ auto profileObj = profile.toObject();
+ for (auto i = profileObj.constBegin(); i != profileObj.constEnd(); ++i) {
+ if (i.key() == "name" && i.value().isString()) {
+ m_data->minecraftProfile.name = i->toString();
+ }
+ else if (i.key() == "id" && i.value().isString()) {
+ m_data->minecraftProfile.id = i->toString();
+ }
+ }
+
+ if (m_data->minecraftProfile.id.isEmpty()) {
+ changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send a UUID in selected profile."));
+ return;
+ }
+
// We've made it through the minefield of possible errors. Return true to indicate that
// we've succeeded.
qDebug() << "Finished reading authentication response.";
diff --git a/launcher/minecraft/auth/flows/Mojang.cpp b/launcher/minecraft/auth/flows/Mojang.cpp
index 4661dbe2..b86b0936 100644
--- a/launcher/minecraft/auth/flows/Mojang.cpp
+++ b/launcher/minecraft/auth/flows/Mojang.cpp
@@ -1,7 +1,7 @@
#include "Mojang.h"
#include "minecraft/auth/steps/YggdrasilStep.h"
-#include "minecraft/auth/steps/MinecraftProfileStep.h"
+#include "minecraft/auth/steps/MinecraftProfileStepMojang.h"
#include "minecraft/auth/steps/MigrationEligibilityStep.h"
#include "minecraft/auth/steps/GetSkinStep.h"
@@ -10,7 +10,7 @@ MojangRefresh::MojangRefresh(
QObject *parent
) : AuthFlow(data, parent) {
m_steps.append(new YggdrasilStep(m_data, QString()));
- m_steps.append(new MinecraftProfileStep(m_data));
+ m_steps.append(new MinecraftProfileStepMojang(m_data));
m_steps.append(new MigrationEligibilityStep(m_data));
m_steps.append(new GetSkinStep(m_data));
}
@@ -21,7 +21,7 @@ MojangLogin::MojangLogin(
QObject *parent
): AuthFlow(data, parent), m_password(password) {
m_steps.append(new YggdrasilStep(m_data, m_password));
- m_steps.append(new MinecraftProfileStep(m_data));
+ m_steps.append(new MinecraftProfileStepMojang(m_data));
m_steps.append(new MigrationEligibilityStep(m_data));
m_steps.append(new GetSkinStep(m_data));
}
diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp b/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp
new file mode 100644
index 00000000..d3035272
--- /dev/null
+++ b/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp
@@ -0,0 +1,94 @@
+#include "MinecraftProfileStepMojang.h"
+
+#include <QNetworkRequest>
+
+#include "minecraft/auth/AuthRequest.h"
+#include "minecraft/auth/Parsers.h"
+
+MinecraftProfileStepMojang::MinecraftProfileStepMojang(AccountData* data) : AuthStep(data) {
+
+}
+
+MinecraftProfileStepMojang::~MinecraftProfileStepMojang() noexcept = default;
+
+QString MinecraftProfileStepMojang::describe() {
+ return tr("Fetching the Minecraft profile.");
+}
+
+
+void MinecraftProfileStepMojang::perform() {
+ if (m_data->minecraftProfile.id.isEmpty()) {
+ emit finished(AccountTaskState::STATE_FAILED_HARD, tr("A UUID is required to get the profile."));
+ return;
+ }
+
+ // use session server instead of profile due to profile endpoint being locked for locked Mojang accounts
+ QUrl url = QUrl("https://sessionserver.mojang.com/session/minecraft/profile/" + m_data->minecraftProfile.id);
+ QNetworkRequest req = QNetworkRequest(url);
+ AuthRequest *request = new AuthRequest(this);
+ connect(request, &AuthRequest::finished, this, &MinecraftProfileStepMojang::onRequestDone);
+ request->get(req);
+}
+
+void MinecraftProfileStepMojang::rehydrate() {
+ // NOOP, for now. We only save bools and there's nothing to check.
+}
+
+void MinecraftProfileStepMojang::onRequestDone(
+ QNetworkReply::NetworkError error,
+ QByteArray data,
+ QList<QNetworkReply::RawHeaderPair> headers
+) {
+ auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
+ requestor->deleteLater();
+
+#ifndef NDEBUG
+ qDebug() << data;
+#endif
+ if (error == QNetworkReply::ContentNotFoundError) {
+ // NOTE: Succeed even if we do not have a profile. This is a valid account state.
+ if(m_data->type == AccountType::Mojang) {
+ m_data->minecraftEntitlement.canPlayMinecraft = false;
+ m_data->minecraftEntitlement.ownsMinecraft = false;
+ }
+ m_data->minecraftProfile = MinecraftProfile();
+ emit finished(
+ AccountTaskState::STATE_SUCCEEDED,
+ tr("Account has no Minecraft profile.")
+ );
+ return;
+ }
+ if (error != QNetworkReply::NoError) {
+ qWarning() << "Error getting profile:";
+ qWarning() << " HTTP Status: " << requestor->httpStatus_;
+ qWarning() << " Internal error no.: " << error;
+ qWarning() << " Error string: " << requestor->errorString_;
+
+ qWarning() << " Response:";
+ qWarning() << QString::fromUtf8(data);
+
+ emit finished(
+ AccountTaskState::STATE_FAILED_SOFT,
+ tr("Minecraft Java profile acquisition failed.")
+ );
+ return;
+ }
+ if(!Parsers::parseMinecraftProfileMojang(data, m_data->minecraftProfile)) {
+ m_data->minecraftProfile = MinecraftProfile();
+ emit finished(
+ AccountTaskState::STATE_FAILED_SOFT,
+ tr("Minecraft Java profile response could not be parsed")
+ );
+ return;
+ }
+
+ if(m_data->type == AccountType::Mojang) {
+ auto validProfile = m_data->minecraftProfile.validity == Katabasis::Validity::Certain;
+ m_data->minecraftEntitlement.canPlayMinecraft = validProfile;
+ m_data->minecraftEntitlement.ownsMinecraft = validProfile;
+ }
+ emit finished(
+ AccountTaskState::STATE_WORKING,
+ tr("Minecraft Java profile acquisition succeeded.")
+ );
+}
diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.h b/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.h
new file mode 100644
index 00000000..e06b30ab
--- /dev/null
+++ b/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.h
@@ -0,0 +1,22 @@
+#pragma once
+#include <QObject>
+
+#include "QObjectPtr.h"
+#include "minecraft/auth/AuthStep.h"
+
+
+class MinecraftProfileStepMojang : public AuthStep {
+ Q_OBJECT
+
+public:
+ explicit MinecraftProfileStepMojang(AccountData *data);
+ virtual ~MinecraftProfileStepMojang() noexcept;
+
+ void perform() override;
+ void rehydrate() override;
+
+ QString describe() override;
+
+private slots:
+ void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
+};
diff --git a/launcher/modplatform/atlauncher/ATLShareCode.cpp b/launcher/modplatform/atlauncher/ATLShareCode.cpp
new file mode 100644
index 00000000..59030c87
--- /dev/null
+++ b/launcher/modplatform/atlauncher/ATLShareCode.cpp
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ *
+ * 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 "ATLShareCode.h"
+
+#include "Json.h"
+
+namespace ATLauncher {
+
+static void loadShareCodeMod(ShareCodeMod& m, QJsonObject& obj)
+{
+ m.selected = Json::requireBoolean(obj, "selected");
+ m.name = Json::requireString(obj, "name");
+}
+
+static void loadShareCode(ShareCode& c, QJsonObject& obj)
+{
+ c.pack = Json::requireString(obj, "pack");
+ c.version = Json::requireString(obj, "version");
+
+ auto mods = Json::requireObject(obj, "mods");
+ auto optional = Json::requireArray(mods, "optional");
+ for (const auto modRaw : optional) {
+ auto modObj = Json::requireObject(modRaw);
+ ShareCodeMod mod;
+ loadShareCodeMod(mod, modObj);
+ c.mods.append(mod);
+ }
+}
+
+void loadShareCodeResponse(ShareCodeResponse& r, QJsonObject& obj)
+{
+ r.error = Json::requireBoolean(obj, "error");
+ r.code = Json::requireInteger(obj, "code");
+
+ if (obj.contains("message") && !obj.value("message").isNull())
+ r.message = Json::requireString(obj, "message");
+
+ if (!r.error) {
+ auto dataRaw = Json::requireObject(obj, "data");
+ loadShareCode(r.data, dataRaw);
+ }
+}
+
+}
diff --git a/launcher/modplatform/atlauncher/ATLShareCode.h b/launcher/modplatform/atlauncher/ATLShareCode.h
new file mode 100644
index 00000000..88c30c98
--- /dev/null
+++ b/launcher/modplatform/atlauncher/ATLShareCode.h
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ *
+ * 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 <QString>
+#include <QVector>
+#include <QJsonObject>
+
+namespace ATLauncher {
+
+struct ShareCodeMod {
+ bool selected;
+ QString name;
+};
+
+struct ShareCode {
+ QString pack;
+ QString version;
+ QVector<ShareCodeMod> mods;
+};
+
+struct ShareCodeResponse {
+ bool error;
+ int code;
+ QString message;
+ ShareCode data;
+};
+
+void loadShareCodeResponse(ShareCodeResponse& r, QJsonObject& obj);
+
+}
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 7ac4d2d4..f34cf1ab 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -294,14 +294,14 @@ public:
actionViewInstanceFolder = TranslatedAction(MainWindow);
actionViewInstanceFolder->setObjectName(QStringLiteral("actionViewInstanceFolder"));
actionViewInstanceFolder->setIcon(APPLICATION->getThemedIcon("viewfolder"));
- actionViewInstanceFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "View Instance Folder"));
+ actionViewInstanceFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&View Instance Folder"));
actionViewInstanceFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the instance folder in a file browser."));
all_actions.append(&actionViewInstanceFolder);
actionViewCentralModsFolder = TranslatedAction(MainWindow);
actionViewCentralModsFolder->setObjectName(QStringLiteral("actionViewCentralModsFolder"));
actionViewCentralModsFolder->setIcon(APPLICATION->getThemedIcon("centralmods"));
- actionViewCentralModsFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "View Central Mods Folder"));
+ actionViewCentralModsFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "View &Central Mods Folder"));
actionViewCentralModsFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the central mods folder in a file browser."));
all_actions.append(&actionViewCentralModsFolder);
@@ -326,7 +326,7 @@ public:
actionSettings->setObjectName(QStringLiteral("actionSettings"));
actionSettings->setIcon(APPLICATION->getThemedIcon("settings"));
actionSettings->setMenuRole(QAction::PreferencesRole);
- actionSettings.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Settings..."));
+ actionSettings.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Setti&ngs..."));
actionSettings.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change settings."));
actionSettings->setShortcut(QKeySequence::Preferences);
all_actions.append(&actionSettings);
@@ -542,7 +542,7 @@ public:
actionOpenWiki = TranslatedAction(MainWindow);
actionOpenWiki->setObjectName(QStringLiteral("actionOpenWiki"));
- actionOpenWiki.setTextId(QT_TRANSLATE_NOOP("MainWindow", "%1 He&lp"));
+ actionOpenWiki.setTextId(QT_TRANSLATE_NOOP("MainWindow", "%1 &Help"));
actionOpenWiki.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the %1 wiki"));
connect(actionOpenWiki, &QAction::triggered, MainWindow, &MainWindow::on_actionOpenWiki_triggered);
all_actions.append(&actionOpenWiki);
diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui
index 7a9088d1..acde9aef 100644
--- a/launcher/ui/pages/global/APIPage.ui
+++ b/launcher/ui/pages/global/APIPage.ui
@@ -36,7 +36,7 @@
<item>
<widget class="QGroupBox" name="groupBox_paste">
<property name="title">
- <string>Pastebin URL</string>
+ <string>&amp;Pastebin URL</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
@@ -98,7 +98,7 @@
<item>
<widget class="QGroupBox" name="groupBox_msa">
<property name="title">
- <string>Microsoft Authentication</string>
+ <string>&amp;Microsoft Authentication</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
diff --git a/launcher/ui/pages/global/AccountListPage.ui b/launcher/ui/pages/global/AccountListPage.ui
index d21a92e2..469955b5 100644
--- a/launcher/ui/pages/global/AccountListPage.ui
+++ b/launcher/ui/pages/global/AccountListPage.ui
@@ -65,17 +65,17 @@
</widget>
<action name="actionAddMojang">
<property name="text">
- <string>Add Mojang</string>
+ <string>Add &amp;Mojang</string>
</property>
</action>
<action name="actionRemove">
<property name="text">
- <string>Remove</string>
+ <string>Remo&amp;ve</string>
</property>
</action>
<action name="actionSetDefault">
<property name="text">
- <string>Set Default</string>
+ <string>&amp;Set Default</string>
</property>
</action>
<action name="actionNoDefault">
@@ -83,17 +83,17 @@
<bool>true</bool>
</property>
<property name="text">
- <string>No Default</string>
+ <string>&amp;No Default</string>
</property>
</action>
<action name="actionUploadSkin">
<property name="text">
- <string>Upload Skin</string>
+ <string>&amp;Upload Skin</string>
</property>
</action>
<action name="actionDeleteSkin">
<property name="text">
- <string>Delete Skin</string>
+ <string>&amp;Delete Skin</string>
</property>
<property name="toolTip">
<string>Delete the currently active skin and go back to the default one</string>
@@ -101,17 +101,17 @@
</action>
<action name="actionAddMicrosoft">
<property name="text">
- <string>Add Microsoft</string>
+ <string>&amp;Add Microsoft</string>
</property>
</action>
<action name="actionAddOffline">
<property name="text">
- <string>Add Offline</string>
+ <string>Add &amp;Offline</string>
</property>
</action>
<action name="actionRefresh">
<property name="text">
- <string>Refresh</string>
+ <string>&amp;Refresh</string>
</property>
<property name="toolTip">
<string>Refresh the account tokens</string>
diff --git a/launcher/ui/pages/global/ExternalToolsPage.ui b/launcher/ui/pages/global/ExternalToolsPage.ui
index e79e9388..8609d469 100644
--- a/launcher/ui/pages/global/ExternalToolsPage.ui
+++ b/launcher/ui/pages/global/ExternalToolsPage.ui
@@ -36,7 +36,7 @@
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
- <string notr="true">JProfiler</string>
+ <string notr="true">J&amp;Profiler</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_10">
<item>
@@ -73,7 +73,7 @@
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
- <string notr="true">JVisualVM</string>
+ <string notr="true">J&amp;VisualVM</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_11">
<item>
@@ -110,7 +110,7 @@
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
- <string notr="true">MCEdit</string>
+ <string notr="true">&amp;MCEdit</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_12">
<item>
@@ -156,7 +156,7 @@
<item row="0" column="0">
<widget class="QLabel" name="labelJsonEditor">
<property name="text">
- <string>Text Editor:</string>
+ <string>&amp;Text Editor:</string>
</property>
</widget>
</item>
diff --git a/launcher/ui/pages/global/JavaPage.ui b/launcher/ui/pages/global/JavaPage.ui
index d27b200f..bb195770 100644
--- a/launcher/ui/pages/global/JavaPage.ui
+++ b/launcher/ui/pages/global/JavaPage.ui
@@ -70,14 +70,14 @@
<item row="0" column="0">
<widget class="QLabel" name="labelMinMem">
<property name="text">
- <string>Minimum memory allocation:</string>
+ <string>&amp;Minimum memory allocation:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelMaxMem">
<property name="text">
- <string>Maximum memory allocation:</string>
+ <string>Ma&amp;ximum memory allocation:</string>
</property>
</widget>
</item>
@@ -106,7 +106,7 @@
<item row="2" column="0">
<widget class="QLabel" name="labelPermGen">
<property name="text">
- <string notr="true">PermGen:</string>
+ <string notr="true">&amp;PermGen:</string>
</property>
</widget>
</item>
@@ -150,7 +150,7 @@
</sizepolicy>
</property>
<property name="text">
- <string>Java path:</string>
+ <string>&amp;Java path:</string>
</property>
</widget>
</item>
@@ -192,7 +192,7 @@
</sizepolicy>
</property>
<property name="text">
- <string>JVM arguments:</string>
+ <string>J&amp;VM arguments:</string>
</property>
</widget>
</item>
@@ -205,7 +205,7 @@
</sizepolicy>
</property>
<property name="text">
- <string>Auto-detect...</string>
+ <string>&amp;Auto-detect...</string>
</property>
</widget>
</item>
@@ -218,7 +218,7 @@
</sizepolicy>
</property>
<property name="text">
- <string>Test</string>
+ <string>&amp;Test</string>
</property>
</widget>
</item>
@@ -234,7 +234,7 @@
<string>If enabled, the launcher will not check if an instance is compatible with the selected Java version.</string>
</property>
<property name="text">
- <string>Skip Java compatibility checks</string>
+ <string>&amp;Skip Java compatibility checks</string>
</property>
</widget>
</item>
diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui
index 4cc2a113..086de17b 100644
--- a/launcher/ui/pages/global/LauncherPage.ui
+++ b/launcher/ui/pages/global/LauncherPage.ui
@@ -196,7 +196,7 @@
<item>
<widget class="QRadioButton" name="sortLastLaunchedBtn">
<property name="text">
- <string>By &amp;last launched</string>
+ <string>&amp;By last launched</string>
</property>
<attribute name="buttonGroup">
<string notr="true">sortingModeGroup</string>
@@ -293,7 +293,7 @@
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
- <string>Colors</string>
+ <string>&amp;Colors</string>
</property>
<property name="buddy">
<cstring>themeComboBoxColors</cstring>
@@ -334,7 +334,7 @@
<string>The menubar is more friendly for keyboard-driven interaction.</string>
</property>
<property name="text">
- <string>Replace toolbar with menubar</string>
+ <string>&amp;Replace toolbar with menubar</string>
</property>
</widget>
</item>
@@ -370,21 +370,21 @@
<item>
<widget class="QCheckBox" name="showConsoleCheck">
<property name="text">
- <string>Show console while the game is running?</string>
+ <string>Show console while the game is &amp;running?</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="autoCloseConsoleCheck">
<property name="text">
- <string>Automatically close console when the game quits?</string>
+ <string>&amp;Automatically close console when the game quits?</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="showConsoleErrorCheck">
<property name="text">
- <string>Show console when the game crashes?</string>
+ <string>Show console when the game &amp;crashes?</string>
</property>
</widget>
</item>
@@ -394,13 +394,13 @@
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
- <string>History limit</string>
+ <string>&amp;History limit</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="0">
<widget class="QCheckBox" name="checkStopLogging">
<property name="text">
- <string>Stop logging when log overflows</string>
+ <string>&amp;Stop logging when log overflows</string>
</property>
</widget>
</item>
@@ -441,7 +441,7 @@
</sizepolicy>
</property>
<property name="title">
- <string>Console font</string>
+ <string>Console &amp;font</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="0" colspan="2">
diff --git a/launcher/ui/pages/global/MinecraftPage.ui b/launcher/ui/pages/global/MinecraftPage.ui
index c18ab34b..353390bd 100644
--- a/launcher/ui/pages/global/MinecraftPage.ui
+++ b/launcher/ui/pages/global/MinecraftPage.ui
@@ -51,7 +51,7 @@
<item>
<widget class="QCheckBox" name="maximizedCheckBox">
<property name="text">
- <string>Start Minecraft maximized?</string>
+ <string>Start Minecraft &amp;maximized?</string>
</property>
</widget>
</item>
@@ -60,7 +60,7 @@
<item row="1" column="0">
<widget class="QLabel" name="labelWindowHeight">
<property name="text">
- <string>Window hei&amp;ght:</string>
+ <string>Window &amp;height:</string>
</property>
<property name="buddy">
<cstring>windowHeightSpinBox</cstring>
@@ -70,7 +70,7 @@
<item row="0" column="0">
<widget class="QLabel" name="labelWindowWidth">
<property name="text">
- <string>W&amp;indow width:</string>
+ <string>Window &amp;width:</string>
</property>
<property name="buddy">
<cstring>windowWidthSpinBox</cstring>
@@ -120,14 +120,14 @@
<item>
<widget class="QCheckBox" name="useNativeGLFWCheck">
<property name="text">
- <string>Use system installation of GLFW</string>
+ <string>Use system installation of &amp;GLFW</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="useNativeOpenALCheck">
<property name="text">
- <string>Use system installation of OpenAL</string>
+ <string>Use system installation of &amp;OpenAL</string>
</property>
</widget>
</item>
@@ -143,21 +143,21 @@
<item>
<widget class="QCheckBox" name="showGameTime">
<property name="text">
- <string>Show time spent playing instances</string>
+ <string>Show time spent &amp;playing instances</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="showGlobalGameTime">
<property name="text">
- <string>Show time spent playing across all instances</string>
+ <string>Show time spent playing across &amp;all instances</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="recordGameTime">
<property name="text">
- <string>Record time spent playing instances</string>
+ <string>&amp;Record time spent playing instances</string>
</property>
</widget>
</item>
@@ -176,7 +176,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The launcher will automatically reopen when the game crashes or exits.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
- <string>Close the launcher after game window opens</string>
+ <string>&amp;Close the launcher after game window opens</string>
</property>
</widget>
</item>
@@ -186,7 +186,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The launcher will automatically quit after the game exits or crashes.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
- <string>Quit the launcher after game window closes</string>
+ <string>&amp;Quit the launcher after game window closes</string>
</property>
</widget>
</item>
diff --git a/launcher/ui/pages/global/ProxyPage.ui b/launcher/ui/pages/global/ProxyPage.ui
index 347fa86c..5a2fc73d 100644
--- a/launcher/ui/pages/global/ProxyPage.ui
+++ b/launcher/ui/pages/global/ProxyPage.ui
@@ -81,7 +81,7 @@
<item>
<widget class="QRadioButton" name="proxySOCKS5Btn">
<property name="text">
- <string>SOC&amp;KS5</string>
+ <string>&amp;SOCKS5</string>
</property>
<attribute name="buttonGroup">
<string notr="true">proxyGroup</string>
@@ -91,7 +91,7 @@
<item>
<widget class="QRadioButton" name="proxyHTTPBtn">
<property name="text">
- <string>H&amp;TTP</string>
+ <string>&amp;HTTP</string>
</property>
<attribute name="buttonGroup">
<string notr="true">proxyGroup</string>
@@ -104,7 +104,7 @@
<item>
<widget class="QGroupBox" name="proxyAddrBox">
<property name="title">
- <string>Address and Port</string>
+ <string>&amp;Address and Port</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
@@ -145,14 +145,14 @@
<item row="0" column="0">
<widget class="QLabel" name="proxyUsernameLabel">
<property name="text">
- <string>Username:</string>
+ <string>&amp;Username:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="proxyPasswordLabel">
<property name="text">
- <string>Password:</string>
+ <string>&amp;Password:</string>
</property>
</widget>
</item>
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
index ac3869dc..26aa60af 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
@@ -1,30 +1,56 @@
+// SPDX-License-Identifier: GPL-3.0-only
/*
- * Copyright 2021 Jamie Mansfield <jmansfield@cadixdev.org>
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2021 Jamie Mansfield <jmansfield@cadixdev.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
#include "AtlOptionalModDialog.h"
#include "ui_AtlOptionalModDialog.h"
+#include <QInputDialog>
+#include <QMessageBox>
+#include "BuildConfig.h"
+#include "Json.h"
+#include "modplatform/atlauncher/ATLShareCode.h"
+#include "Application.h"
+
AtlOptionalModListModel::AtlOptionalModListModel(QWidget *parent, QVector<ATLauncher::VersionMod> mods)
: QAbstractListModel(parent), m_mods(mods) {
-
// fill mod index
for (int i = 0; i < m_mods.size(); i++) {
auto mod = m_mods.at(i);
m_index[mod.name] = i;
}
+
// set initial state
for (int i = 0; i < m_mods.size(); i++) {
auto mod = m_mods.at(i);
@@ -77,7 +103,7 @@ QVariant AtlOptionalModListModel::data(const QModelIndex &index, int role) const
}
}
- return QVariant();
+ return {};
}
bool AtlOptionalModListModel::setData(const QModelIndex &index, const QVariant &value, int role) {
@@ -104,7 +130,7 @@ QVariant AtlOptionalModListModel::headerData(int section, Qt::Orientation orient
}
}
- return QVariant();
+ return {};
}
Qt::ItemFlags AtlOptionalModListModel::flags(const QModelIndex &index) const {
@@ -115,6 +141,69 @@ Qt::ItemFlags AtlOptionalModListModel::flags(const QModelIndex &index) const {
return flags;
}
+void AtlOptionalModListModel::useShareCode(const QString& code) {
+ m_jobPtr.reset(new NetJob("Atl::Request", APPLICATION->network()));
+ auto url = QString(BuildConfig.ATL_API_BASE_URL + "share-codes/" + code);
+ m_jobPtr->addNetAction(Net::Download::makeByteArray(QUrl(url), &m_response));
+
+ connect(m_jobPtr.get(), &NetJob::succeeded,
+ this, &AtlOptionalModListModel::shareCodeSuccess);
+ connect(m_jobPtr.get(), &NetJob::failed,
+ this, &AtlOptionalModListModel::shareCodeFailure);
+
+ m_jobPtr->start();
+}
+
+void AtlOptionalModListModel::shareCodeSuccess() {
+ m_jobPtr.reset();
+
+ QJsonParseError parse_error {};
+ auto doc = QJsonDocument::fromJson(m_response, &parse_error);
+ if (parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from ATL at " << parse_error.offset << " reason: " << parse_error.errorString();
+ qWarning() << m_response;
+ return;
+ }
+ auto obj = doc.object();
+
+ ATLauncher::ShareCodeResponse response;
+ try {
+ ATLauncher::loadShareCodeResponse(response, obj);
+ }
+ catch (const JSONValidationError& e) {
+ qDebug() << QString::fromUtf8(m_response);
+ qWarning() << "Error while reading response from ATLauncher: " << e.cause();
+ return;
+ }
+
+ if (response.error) {
+ // fixme: plumb in an error message
+ qWarning() << "ATLauncher API Response Error" << response.message;
+ return;
+ }
+
+ // FIXME: verify pack and version, error if not matching.
+
+ // Clear the current selection
+ for (const auto& mod : m_mods) {
+ m_selection[mod.name] = false;
+ }
+
+ // Make the selections, as per the share code.
+ for (const auto& mod : response.data.mods) {
+ m_selection[mod.name] = mod.selected;
+ }
+
+ emit dataChanged(AtlOptionalModListModel::index(0, EnabledColumn),
+ AtlOptionalModListModel::index(m_mods.size() - 1, EnabledColumn));
+}
+
+void AtlOptionalModListModel::shareCodeFailure(const QString& reason) {
+ m_jobPtr.reset();
+
+ // fixme: plumb in an error message
+}
+
void AtlOptionalModListModel::selectRecommended() {
for (const auto& mod : m_mods) {
m_selection[mod.name] = mod.recommended;
@@ -212,14 +301,43 @@ AtlOptionalModDialog::AtlOptionalModDialog(QWidget *parent, QVector<ATLauncher::
ui->treeView->header()->setSectionResizeMode(
AtlOptionalModListModel::DescriptionColumn, QHeaderView::Stretch);
- connect(ui->selectRecommendedButton, &QPushButton::pressed,
+ connect(ui->shareCodeButton, &QPushButton::clicked,
+ this, &AtlOptionalModDialog::useShareCode);
+ connect(ui->selectRecommendedButton, &QPushButton::clicked,
listModel, &AtlOptionalModListModel::selectRecommended);
- connect(ui->clearAllButton, &QPushButton::pressed,
+ connect(ui->clearAllButton, &QPushButton::clicked,
listModel, &AtlOptionalModListModel::clearAll);
- connect(ui->installButton, &QPushButton::pressed,
+ connect(ui->installButton, &QPushButton::clicked,
this, &QDialog::close);
}
AtlOptionalModDialog::~AtlOptionalModDialog() {
delete ui;
}
+
+void AtlOptionalModDialog::useShareCode() {
+ bool ok;
+ auto shareCode = QInputDialog::getText(
+ this,
+ tr("Select a share code"),
+ tr("Share code:"),
+ QLineEdit::Normal,
+ "",
+ &ok
+ );
+
+ if (!ok) {
+ // If the user cancels the dialog, we don't need to show any error dialogs.
+ return;
+ }
+
+ if (shareCode.isEmpty()) {
+ QMessageBox box;
+ box.setIcon(QMessageBox::Warning);
+ box.setText(tr("No share code specified!"));
+ box.exec();
+ return;
+ }
+
+ listModel->useShareCode(shareCode);
+}
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h
index 9832014c..953b288e 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h
@@ -1,17 +1,36 @@
+// SPDX-License-Identifier: GPL-3.0-only
/*
- * Copyright 2021 Jamie Mansfield <jmansfield@cadixdev.org>
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2021 Jamie Mansfield <jmansfield@cadixdev.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
#pragma once
@@ -20,6 +39,7 @@
#include <QAbstractListModel>
#include "modplatform/atlauncher/ATLPackIndex.h"
+#include "net/NetJob.h"
namespace Ui {
class AtlOptionalModDialog;
@@ -49,7 +69,12 @@ public:
Qt::ItemFlags flags(const QModelIndex &index) const override;
+ void useShareCode(const QString& code);
+
public slots:
+ void shareCodeSuccess();
+ void shareCodeFailure(const QString& reason);
+
void selectRecommended();
void clearAll();
@@ -58,6 +83,9 @@ private:
void setMod(ATLauncher::VersionMod mod, int index, bool enable, bool shouldEmit = true);
private:
+ NetJob::Ptr m_jobPtr;
+ QByteArray m_response;
+
QVector<ATLauncher::VersionMod> m_mods;
QMap<QString, bool> m_selection;
QMap<QString, int> m_index;
@@ -75,6 +103,8 @@ public:
return listModel->getResult();
}
+ void useShareCode();
+
private:
Ui::AtlOptionalModDialog *ui;
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui
index 4c5c2ec5..d9496142 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui
@@ -24,23 +24,23 @@
</property>
</widget>
</item>
- <item row="1" column="1">
- <widget class="QPushButton" name="selectRecommendedButton">
- <property name="text">
- <string>Select Recommended</string>
- </property>
- </widget>
- </item>
<item row="1" column="0">
<widget class="QPushButton" name="shareCodeButton">
<property name="enabled">
- <bool>false</bool>
+ <bool>true</bool>
</property>
<property name="text">
<string>Use Share Code</string>
</property>
</widget>
</item>
+ <item row="1" column="1">
+ <widget class="QPushButton" name="selectRecommendedButton">
+ <property name="text">
+ <string>Select Recommended</string>
+ </property>
+ </widget>
+ </item>
<item row="1" column="2">
<widget class="QPushButton" name="clearAllButton">
<property name="text">
diff --git a/launcher/ui/widgets/CustomCommands.ui b/launcher/ui/widgets/CustomCommands.ui
index 650a9cc1..68e680a5 100644
--- a/launcher/ui/widgets/CustomCommands.ui
+++ b/launcher/ui/widgets/CustomCommands.ui
@@ -29,7 +29,7 @@
<bool>true</bool>
</property>
<property name="title">
- <string>Cus&amp;tom Commands</string>
+ <string>&amp;Custom Commands</string>
</property>
<property name="checkable">
<bool>true</bool>
@@ -41,7 +41,7 @@
<item row="2" column="0">
<widget class="QLabel" name="labelPostExitCmd">
<property name="text">
- <string>Post-exit command:</string>
+ <string>P&amp;ost-exit command:</string>
</property>
</widget>
</item>
@@ -51,7 +51,7 @@
<item row="0" column="0">
<widget class="QLabel" name="labelPreLaunchCmd">
<property name="text">
- <string>Pre-launch command:</string>
+ <string>&amp;Pre-launch command:</string>
</property>
</widget>
</item>
@@ -61,7 +61,7 @@
<item row="1" column="0">
<widget class="QLabel" name="labelWrapperCmd">
<property name="text">
- <string>Wrapper command:</string>
+ <string>&amp;Wrapper command:</string>
</property>
</widget>
</item>
diff --git a/libraries/iconfix/CMakeLists.txt b/libraries/iconfix/CMakeLists.txt
index 08441203..97a59129 100644
--- a/libraries/iconfix/CMakeLists.txt
+++ b/libraries/iconfix/CMakeLists.txt
@@ -12,7 +12,7 @@ internal/qiconloader.cpp
internal/qiconloader_p.h
)
-add_library(Launcher_iconfix ${ICONFIX_SOURCES})
+add_library(Launcher_iconfix STATIC ${ICONFIX_SOURCES})
target_include_directories(Launcher_iconfix PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} "${CMAKE_CURRENT_BINARY_DIR}" )
target_link_libraries(Launcher_iconfix Qt5::Core Qt5::Widgets)