diff options
Diffstat (limited to 'launcher')
27 files changed, 741 insertions, 213 deletions
diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 8bd434f0..11109857 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -409,69 +409,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) return; } -#if defined(Q_OS_MAC) - // move user data to new location if on macOS and it still exists in Contents/MacOS - QDir fi(applicationDirPath()); - QString originalData = fi.absolutePath(); - // if the config file exists in Contents/MacOS, then user data is still there and needs to moved - if (QFileInfo::exists(FS::PathCombine(originalData, BuildConfig.LAUNCHER_CONFIGFILE))) - { - if (!QFileInfo::exists(FS::PathCombine(originalData, "dontmovemacdata"))) - { - QMessageBox::StandardButton askMoveDialogue; - askMoveDialogue = QMessageBox::question( - nullptr, - BuildConfig.LAUNCHER_DISPLAYNAME, - "Would you like to move application data to a new data location? It will improve the launcher's performance, but if you switch to older versions it will look like instances have disappeared. If you select no, you can migrate later in settings. You should select yes unless you're commonly switching between different versions (eg. develop and stable).", - QMessageBox::Yes | QMessageBox::No, - QMessageBox::Yes - ); - if (askMoveDialogue == QMessageBox::Yes) - { - qDebug() << "On macOS and found config file in old location, moving user data..."; - QDir dir; - QStringList dataFiles { - "*.log", // Launcher log files: ${Launcher_Name}-@.log - "accounts.json", - "accounts", - "assets", - "cache", - "icons", - "instances", - "libraries", - "meta", - "metacache", - "mods", - BuildConfig.LAUNCHER_CONFIGFILE, - "themes", - "translations" - }; - QDirIterator files(originalData, dataFiles); - while (files.hasNext()) { - QString filePath(files.next()); - QString fileName(files.fileName()); - if (!dir.rename(filePath, FS::PathCombine(dataPath, fileName))) - { - qWarning() << "Failed to move " << fileName; - } - } - } - else - { - dataPath = originalData; - QDir::setCurrent(dataPath); - QFile file(originalData + "/dontmovemacdata"); - file.open(QIODevice::WriteOnly); - } - } - else - { - dataPath = originalData; - QDir::setCurrent(dataPath); - } - } -#endif - /* * Establish the mechanism for communication with an already running PolyMC that uses the same data path. * If there is one, tell it what the user actually wanted to do and exit. @@ -691,6 +628,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_settings->registerSetting("LastHostname", ""); m_settings->registerSetting("JvmArgs", ""); m_settings->registerSetting("IgnoreJavaCompatibility", false); + m_settings->registerSetting("IgnoreJavaWizard", false); // Native library workarounds m_settings->registerSetting("UseNativeOpenAL", false); @@ -936,6 +874,10 @@ bool Application::createSetupWizard() { bool javaRequired = [&]() { + bool ignoreJavaWizard = m_settings->get("IgnoreJavaWizard").toBool(); + if(ignoreJavaWizard) { + return false; + } QString currentHostName = QHostInfo::localHostName(); QString oldHostName = settings()->get("LastHostname").toString(); if (currentHostName != oldHostName) @@ -966,6 +908,7 @@ bool Application::createSetupWizard() { m_setupWizard->addPage(new LanguageWizardPage(m_setupWizard)); } + if (javaRequired) { m_setupWizard->addPage(new JavaWizardPage(m_setupWizard)); 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/resources/multimc/multimc.qrc b/launcher/resources/multimc/multimc.qrc index d31885b9..0fe673ff 100644 --- a/launcher/resources/multimc/multimc.qrc +++ b/launcher/resources/multimc/multimc.qrc @@ -313,5 +313,6 @@ <file>scalable/instances/fox.svg</file> <file>scalable/instances/bee.svg</file> + <file>scalable/instances/polymc.svg</file> </qresource> </RCC> diff --git a/launcher/resources/multimc/scalable/instances/polymc.svg b/launcher/resources/multimc/scalable/instances/polymc.svg new file mode 100644 index 00000000..c192d503 --- /dev/null +++ b/launcher/resources/multimc/scalable/instances/polymc.svg @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg width="64" height="64" version="1.1" viewBox="0 0 16.933 16.933" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <defs> + <linearGradient id="linearGradient84726" x1="4.4979" x2="12.435" y1="3.8011" y2="9.5681" gradientUnits="userSpaceOnUse"> + <stop stop-color="#88b858" offset="0"/> + <stop stop-color="#72b147" offset=".5"/> + <stop stop-color="#5a9a30" offset="1"/> + </linearGradient> + </defs> + <g> + <path d="m3.561 16.016s0-3.5642 4.9056-3.5642c4.9069 0 4.9056 3.5642 4.9056 3.5642z" fill="#765338"/> + <path d="m8.4667 12.452-4.9056 3.5642-3.0319-9.3311z" fill="#b7835a"/> + <path d="m8.4667 12.452 7.9375-5.7669-3.0319 9.3311z" fill="#5b422d"/> + <path d="m8.8308 12.716-0.36417 0.26458-0.36417-0.26458c0-0.26458 0.36417-0.26458 0.36417-0.26458s0.36417 0 0.36417 0.26458z" fill="#72b147"/> + <path d="m8.4667 12.452s-2e-7 -5.7669 7.9375-5.7669l-0.22507 0.69269-0.91853 1.1965-0.91853 0.13819-0.91853 1.1965-0.91853 0.13819-0.91853 1.1965-0.91853 0.13819-0.91853 1.1965-0.91853 0.13819z" fill="#5a9a30"/> + <path d="m8.1025 12.716-0.91853-0.13819-0.91853-1.1965-0.91853-0.13819-0.91853-1.1965-0.91853-0.13819-0.91853-1.1965-0.91853-0.13819-0.91853-1.1965-0.22507-0.69269c7.9375 1e-7 7.9375 5.7669 7.9375 5.7669z" fill="#88b858"/> + <path d="m0.52917 6.6846 7.9375 5.7669 7.9375-5.7669-7.9375-5.7669z" fill="url(#linearGradient84726)"/> + </g> + <path d="m0.75424 7.3773-0.22507-0.69269 7.9375 5.7669 7.9375-5.7669-0.22507 0.69269-7.7124 5.6034z" fill-opacity="0"/> +</svg> 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>&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>&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 &Mojang</string> </property> </action> <action name="actionRemove"> <property name="text"> - <string>Remove</string> + <string>Remo&ve</string> </property> </action> <action name="actionSetDefault"> <property name="text"> - <string>Set Default</string> + <string>&Set Default</string> </property> </action> <action name="actionNoDefault"> @@ -83,17 +83,17 @@ <bool>true</bool> </property> <property name="text"> - <string>No Default</string> + <string>&No Default</string> </property> </action> <action name="actionUploadSkin"> <property name="text"> - <string>Upload Skin</string> + <string>&Upload Skin</string> </property> </action> <action name="actionDeleteSkin"> <property name="text"> - <string>Delete Skin</string> + <string>&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>&Add Microsoft</string> </property> </action> <action name="actionAddOffline"> <property name="text"> - <string>Add Offline</string> + <string>Add &Offline</string> </property> </action> <action name="actionRefresh"> <property name="text"> - <string>Refresh</string> + <string>&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&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&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">&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>&Text Editor:</string> </property> </widget> </item> diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index f0616db1..b5e8de6c 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -97,6 +97,7 @@ void JavaPage::applySettings() s->set("JavaPath", ui->javaPathTextBox->text()); s->set("JvmArgs", ui->jvmArgsTextBox->text()); s->set("IgnoreJavaCompatibility", ui->skipCompatibilityCheckbox->isChecked()); + s->set("IgnoreJavaWizard", ui->skipJavaWizardCheckbox->isChecked()); JavaCommon::checkJVMArgs(s->get("JvmArgs").toString(), this->parentWidget()); } void JavaPage::loadSettings() @@ -121,6 +122,7 @@ void JavaPage::loadSettings() ui->javaPathTextBox->setText(s->get("JavaPath").toString()); ui->jvmArgsTextBox->setText(s->get("JvmArgs").toString()); ui->skipCompatibilityCheckbox->setChecked(s->get("IgnoreJavaCompatibility").toBool()); + ui->skipJavaWizardCheckbox->setChecked(s->get("IgnoreJavaWizard").toBool()); } void JavaPage::on_javaDetectBtn_clicked() diff --git a/launcher/ui/pages/global/JavaPage.ui b/launcher/ui/pages/global/JavaPage.ui index d27b200f..cfcf9094 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>&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&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">&PermGen:</string> </property> </widget> </item> @@ -150,39 +150,10 @@ </sizepolicy> </property> <property name="text"> - <string>Java path:</string> + <string>&Java path:</string> </property> </widget> </item> - <item row="0" column="1" colspan="2"> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QLineEdit" name="javaPathTextBox"/> - </item> - <item> - <widget class="QPushButton" name="javaBrowseBtn"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="maximumSize"> - <size> - <width>28</width> - <height>16777215</height> - </size> - </property> - <property name="text"> - <string notr="true">...</string> - </property> - </widget> - </item> - </layout> - </item> - <item row="2" column="1" colspan="2"> - <widget class="QLineEdit" name="jvmArgsTextBox"/> - </item> <item row="2" column="0"> <widget class="QLabel" name="labelJVMArgs"> <property name="sizePolicy"> @@ -192,25 +163,28 @@ </sizepolicy> </property> <property name="text"> - <string>JVM arguments:</string> + <string>J&VM arguments:</string> </property> </widget> </item> - <item row="3" column="1"> - <widget class="QPushButton" name="javaDetectBtn"> + <item row="4" column="1"> + <widget class="QCheckBox" name="skipCompatibilityCheckbox"> <property name="sizePolicy"> <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> + <property name="toolTip"> + <string>If enabled, the launcher will not check if an instance is compatible with the selected Java version.</string> + </property> <property name="text"> - <string>Auto-detect...</string> + <string>&Skip Java compatibility checks</string> </property> </widget> </item> - <item row="3" column="2"> - <widget class="QPushButton" name="javaTestBtn"> + <item row="3" column="1"> + <widget class="QPushButton" name="javaDetectBtn"> <property name="sizePolicy"> <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> <horstretch>0</horstretch> @@ -218,23 +192,59 @@ </sizepolicy> </property> <property name="text"> - <string>Test</string> + <string>&Auto-detect...</string> </property> </widget> </item> - <item row="4" column="1"> - <widget class="QCheckBox" name="skipCompatibilityCheckbox"> + <item row="0" column="1" colspan="2"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLineEdit" name="javaPathTextBox"/> + </item> + <item> + <widget class="QPushButton" name="javaBrowseBtn"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>28</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string notr="true">...</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="3" column="2"> + <widget class="QPushButton" name="javaTestBtn"> <property name="sizePolicy"> <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> + <property name="text"> + <string>&Test</string> + </property> + </widget> + </item> + <item row="2" column="1" colspan="2"> + <widget class="QLineEdit" name="jvmArgsTextBox"/> + </item> + <item row="5" column="1"> + <widget class="QCheckBox" name="skipJavaWizardCheckbox"> <property name="toolTip"> - <string>If enabled, the launcher will not check if an instance is compatible with the selected Java version.</string> + <string>If enabled, the launcher will not prompt you to choose a Java version if one isn't found.</string> </property> <property name="text"> - <string>Skip Java compatibility checks</string> + <string>Skip Java &Wizard</string> </property> </widget> </item> diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 097a2bfa..af2e2cd1 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -97,13 +97,6 @@ LauncherPage::LauncherPage(QWidget *parent) : QWidget(parent), ui(new Ui::Launch } connect(ui->fontSizeBox, SIGNAL(valueChanged(int)), SLOT(refreshFontPreview())); connect(ui->consoleFont, SIGNAL(currentFontChanged(QFont)), SLOT(refreshFontPreview())); - - //move mac data button - QFile file(QDir::current().absolutePath() + "/dontmovemacdata"); - if (!file.exists()) - { - ui->migrateDataFolderMacBtn->setVisible(false); - } } LauncherPage::~LauncherPage() @@ -190,13 +183,6 @@ void LauncherPage::on_modsDirBrowseBtn_clicked() ui->modsDirTextBox->setText(cooked_dir); } } -void LauncherPage::on_migrateDataFolderMacBtn_clicked() -{ - QFile file(QDir::current().absolutePath() + "/dontmovemacdata"); - file.remove(); - QProcess::startDetached(qApp->arguments()[0]); - qApp->quit(); -} void LauncherPage::refreshUpdateChannelList() { diff --git a/launcher/ui/pages/global/LauncherPage.h b/launcher/ui/pages/global/LauncherPage.h index 63cfe9c3..bbf5d2fe 100644 --- a/launcher/ui/pages/global/LauncherPage.h +++ b/launcher/ui/pages/global/LauncherPage.h @@ -88,7 +88,6 @@ slots: void on_instDirBrowseBtn_clicked(); void on_modsDirBrowseBtn_clicked(); void on_iconsDirBrowseBtn_clicked(); - void on_migrateDataFolderMacBtn_clicked(); /*! * Updates the list of update channels in the combo box. diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 4cc2a113..ae7eb73f 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -158,13 +158,6 @@ </widget> </item> <item> - <widget class="QPushButton" name="migrateDataFolderMacBtn"> - <property name="text"> - <string>Move the data to new location (will restart the launcher)</string> - </property> - </widget> - </item> - <item> <spacer name="verticalSpacer_2"> <property name="orientation"> <enum>Qt::Vertical</enum> @@ -196,7 +189,7 @@ <item> <widget class="QRadioButton" name="sortLastLaunchedBtn"> <property name="text"> - <string>By &last launched</string> + <string>&By last launched</string> </property> <attribute name="buttonGroup"> <string notr="true">sortingModeGroup</string> @@ -293,7 +286,7 @@ <item row="1" column="0"> <widget class="QLabel" name="label_4"> <property name="text"> - <string>Colors</string> + <string>&Colors</string> </property> <property name="buddy"> <cstring>themeComboBoxColors</cstring> @@ -334,7 +327,7 @@ <string>The menubar is more friendly for keyboard-driven interaction.</string> </property> <property name="text"> - <string>Replace toolbar with menubar</string> + <string>&Replace toolbar with menubar</string> </property> </widget> </item> @@ -370,21 +363,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 &running?</string> </property> </widget> </item> <item> <widget class="QCheckBox" name="autoCloseConsoleCheck"> <property name="text"> - <string>Automatically close console when the game quits?</string> + <string>&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 &crashes?</string> </property> </widget> </item> @@ -394,13 +387,13 @@ <item> <widget class="QGroupBox" name="groupBox_4"> <property name="title"> - <string>History limit</string> + <string>&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>&Stop logging when log overflows</string> </property> </widget> </item> @@ -441,7 +434,7 @@ </sizepolicy> </property> <property name="title"> - <string>Console font</string> + <string>Console &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 &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&ght:</string> + <string>Window &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&indow width:</string> + <string>Window &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 &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 &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 &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 &all instances</string> </property> </widget> </item> <item> <widget class="QCheckBox" name="recordGameTime"> <property name="text"> - <string>Record time spent playing instances</string> + <string>&Record time spent playing instances</string> </property> </widget> </item> @@ -176,7 +176,7 @@ <string><html><head/><body><p>The launcher will automatically reopen when the game crashes or exits.</p></body></html></string> </property> <property name="text"> - <string>Close the launcher after game window opens</string> + <string>&Close the launcher after game window opens</string> </property> </widget> </item> @@ -186,7 +186,7 @@ <string><html><head/><body><p>The launcher will automatically quit after the game exits or crashes.</p></body></html></string> </property> <property name="text"> - <string>Quit the launcher after game window closes</string> + <string>&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&KS5</string> + <string>&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&TTP</string> + <string>&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>&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>&Username:</string> </property> </widget> </item> <item row="1" column="0"> <widget class="QLabel" name="proxyPasswordLabel"> <property name="text"> - <string>Password:</string> + <string>&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&tom Commands</string> + <string>&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&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>&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>&Wrapper command:</string> </property> </widget> </item> |