diff --git a/launcher/Application.cpp b/launcher/Application.cpp index d29b39f26..9aa613589 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -109,6 +109,8 @@ #include "icons/IconList.h" #include "net/HttpMetaCache.h" +#include "ui/GuiUtil.h" + #include "java/JavaInstallList.h" #include "updater/ExternalUpdater.h" diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 485a102ae..02dcb28f6 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -233,8 +233,6 @@ set(MINECRAFT_SOURCES minecraft/auth/MinecraftAccount.h minecraft/auth/Parsers.cpp minecraft/auth/Parsers.h - minecraft/auth/Yggdrasil.cpp - minecraft/auth/Yggdrasil.h minecraft/auth/AuthFlow.cpp minecraft/auth/AuthFlow.h @@ -257,6 +255,8 @@ set(MINECRAFT_SOURCES minecraft/auth/steps/XboxProfileStep.h minecraft/auth/steps/XboxUserStep.cpp minecraft/auth/steps/XboxUserStep.h + minecraft/auth/steps/YggdrasilMinecraftProfileStep.cpp + minecraft/auth/steps/YggdrasilMinecraftProfileStep.h minecraft/auth/steps/YggdrasilStep.cpp minecraft/auth/steps/YggdrasilStep.h @@ -1071,8 +1071,6 @@ SET(LAUNCHER_SOURCES ui/dialogs/IconPickerDialog.h ui/dialogs/ImportResourceDialog.cpp ui/dialogs/ImportResourceDialog.h - ui/dialogs/LoginDialog.cpp - ui/dialogs/LoginDialog.h ui/dialogs/AuthlibInjectorLoginDialog.cpp ui/dialogs/AuthlibInjectorLoginDialog.h ui/dialogs/MSALoginDialog.cpp diff --git a/launcher/CreateAuthlibInjectorAccount.cpp b/launcher/CreateAuthlibInjectorAccount.cpp index 9211b3d04..3d0c8e545 100644 --- a/launcher/CreateAuthlibInjectorAccount.cpp +++ b/launcher/CreateAuthlibInjectorAccount.cpp @@ -24,13 +24,16 @@ #include #include #include +#include "net/ByteArraySink.h" #include "Application.h" -#include "BuildConfig.h" CreateAuthlibInjectorAccount::CreateAuthlibInjectorAccount(QUrl url, MinecraftAccountPtr account, QString username) - : NetRequest(), m_url(url), m_account(account), m_username(username) -{} + : NetRequest(), m_account(account), m_username(username) +{ + m_url = url; + m_sink.reset(new Sink(*this)); +} QNetworkReply* CreateAuthlibInjectorAccount::getReply(QNetworkRequest& request) { @@ -43,26 +46,34 @@ CreateAuthlibInjectorAccount::Ptr CreateAuthlibInjectorAccount::make(QUrl url, M return CreateAuthlibInjectorAccount::Ptr(new CreateAuthlibInjectorAccount(url, account, username)); } -void CreateAuthlibInjectorAccount::downloadFinished() +auto CreateAuthlibInjectorAccount::Sink::init(QNetworkRequest& request) -> Task::State { - if (m_state != State::Failed) { - QVariant header = m_reply->rawHeader("X-Authlib-Injector-API-Location"); - if (header.isValid()) { - auto location = header.toString(); - m_url = m_url.resolved(location); - } else { - qDebug() << "X-Authlib-Injector-API-Location header not found!"; - } - m_account.reset(MinecraftAccount::createFromUsernameAuthlibInjector(m_username, m_url.toString())); - m_state = State::Succeeded; - emit succeeded(); - return; + return Task::State::Running; +} + +auto CreateAuthlibInjectorAccount::Sink::write(QByteArray& data) -> Task::State +{ + return Task::State::Running; +} + +auto CreateAuthlibInjectorAccount::Sink::abort() -> Task::State +{ + return Task::State::Failed; +} + +auto CreateAuthlibInjectorAccount::Sink::finalize(QNetworkReply& reply) -> Task::State +{ + QVariant header = reply.rawHeader("X-Authlib-Injector-API-Location"); + QUrl url = m_outer.m_url; + if (header.isValid()) { + auto location = header.toString(); + url = url.resolved(location); } else { - qDebug() << m_reply->readAll(); - m_reply.reset(); - emitFailed(); - return; + qDebug() << "X-Authlib-Injector-API-Location header not found!"; } + + m_outer.m_account.reset(MinecraftAccount::createFromUsernameAuthlibInjector(m_outer.m_username, url.toString())); + return Task::State::Succeeded; } MinecraftAccountPtr CreateAuthlibInjectorAccount::getAccount() diff --git a/launcher/CreateAuthlibInjectorAccount.h b/launcher/CreateAuthlibInjectorAccount.h index cb5baa12e..a02f69ccd 100644 --- a/launcher/CreateAuthlibInjectorAccount.h +++ b/launcher/CreateAuthlibInjectorAccount.h @@ -32,12 +32,26 @@ class CreateAuthlibInjectorAccount : public Net::NetRequest { MinecraftAccountPtr getAccount(); + class Sink : public Net::Sink { + public: + Sink(CreateAuthlibInjectorAccount& outer) : m_outer(outer) {} + virtual ~Sink() = default; + + public: + auto init(QNetworkRequest& request) -> Task::State override; + auto write(QByteArray& data) -> Task::State override; + auto abort() -> Task::State override; + auto finalize(QNetworkReply& reply) -> Task::State override; + auto hasLocalData() -> bool override { return false; } + + private: + CreateAuthlibInjectorAccount& m_outer; + }; + protected slots: virtual QNetworkReply* getReply(QNetworkRequest&) override; - void downloadFinished(); private: - QUrl m_url; MinecraftAccountPtr m_account; QString m_username; }; diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index e2e535c06..bd353edca 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -35,12 +35,12 @@ */ #include "LaunchController.h" +#include #include "Application.h" #include "launch/steps/PrintServers.h" #include "minecraft/auth/AccountData.h" #include "minecraft/auth/AccountList.h" #include "settings/MissingAuthlibInjectorBehavior.h" -#include "ui/pages/instance/VersionPage.h" #include "ui/InstanceWindow.h" #include "ui/MainWindow.h" diff --git a/launcher/main.cpp b/launcher/main.cpp index 35f545f52..dce75b5fb 100644 --- a/launcher/main.cpp +++ b/launcher/main.cpp @@ -72,7 +72,7 @@ int main(int argc, char* argv[]) Q_INIT_RESOURCE(multimc); Q_INIT_RESOURCE(backgrounds); Q_INIT_RESOURCE(documents); - Q_INIT_RESOURCE(prismlauncher); + Q_INIT_RESOURCE(fjordlauncher); Q_INIT_RESOURCE(pe_dark); Q_INIT_RESOURCE(pe_light); diff --git a/launcher/minecraft/auth/AccountTask.h b/launcher/minecraft/auth/AccountTask.h deleted file mode 100644 index 91ea1941a..000000000 --- a/launcher/minecraft/auth/AccountTask.h +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * - * 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 . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * 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 - -#include - -#include -#include -#include -#include - -#include "MinecraftAccount.h" - -class QNetworkReply; - -/** - * Enum for describing the state of the current task. - * Used by the getStateMessage function to determine what the status message should be. - */ -enum class AccountTaskState { - STATE_CREATED, - STATE_WORKING, - STATE_SUCCEEDED, - STATE_DISABLED, //!< MSA Client ID has changed. Tell user to reloginn - STATE_FAILED_SOFT, //!< soft failure. authentication went through partially - STATE_FAILED_HARD, //!< hard failure. main tokens are invalid - STATE_FAILED_GONE, //!< hard failure. main tokens are invalid, and the account no longer exists - STATE_OFFLINE //!< soft failure. authentication failed in the first step in a 'soft' way -}; - -class AccountTask : public Task { - Q_OBJECT - public: - explicit AccountTask(AccountData* data, QObject* parent = 0); - virtual ~AccountTask() {}; - - AccountTaskState m_taskState = AccountTaskState::STATE_CREATED; - - AccountTaskState taskState() { return m_taskState; } - - signals: - void showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn); - void hideVerificationUriAndCode(); - - protected: - /** - * Returns the state message for the given state. - * Used to set the status message for the task. - * Should be overridden by subclasses that want to change messages for a given state. - */ - virtual QString getStateMessage() const; - - protected slots: - // NOTE: true -> non-terminal state, false -> terminal state - bool changeState(AccountTaskState newState, QString reason = QString()); - - protected: - AccountData* m_data = nullptr; -}; diff --git a/launcher/minecraft/auth/AuthFlow.cpp b/launcher/minecraft/auth/AuthFlow.cpp index 38a0de1e6..ab3898971 100644 --- a/launcher/minecraft/auth/AuthFlow.cpp +++ b/launcher/minecraft/auth/AuthFlow.cpp @@ -13,6 +13,7 @@ #include "minecraft/auth/steps/XboxAuthorizationStep.h" #include "minecraft/auth/steps/XboxProfileStep.h" #include "minecraft/auth/steps/XboxUserStep.h" +#include "minecraft/auth/steps/YggdrasilMinecraftProfileStep.h" #include "minecraft/auth/steps/YggdrasilStep.h" #include "tasks/Task.h" @@ -20,7 +21,7 @@ #include -AuthFlow::AuthFlow(AccountData* data, Action action, QObject* parent) : Task(parent), m_data(data) +AuthFlow::AuthFlow(AccountData* data, Action action, QObject* parent, const std::optional password) : Task(parent), m_data(data) { if (data->type == AccountType::MSA) { if (action == Action::DeviceCode) { @@ -43,8 +44,8 @@ AuthFlow::AuthFlow(AccountData* data, Action action, QObject* parent) : Task(par m_steps.append(makeShared(m_data)); m_steps.append(makeShared(m_data)); } else if (data->type == AccountType::AuthlibInjector) { - m_steps.append(makeShared(m_data, QString())); - m_steps.append(makeShared(m_data)); + m_steps.append(makeShared(m_data, password)); + m_steps.append(makeShared(m_data)); m_steps.append(makeShared(m_data)); } changeState(AccountTaskState::STATE_CREATED); diff --git a/launcher/minecraft/auth/AuthFlow.h b/launcher/minecraft/auth/AuthFlow.h index 4d18ac845..e5db51d4c 100644 --- a/launcher/minecraft/auth/AuthFlow.h +++ b/launcher/minecraft/auth/AuthFlow.h @@ -17,7 +17,10 @@ class AuthFlow : public Task { public: enum class Action { Refresh, Login, DeviceCode }; - explicit AuthFlow(AccountData* data, Action action = Action::Refresh, QObject* parent = 0); + explicit AuthFlow(AccountData* data, + Action action = Action::Refresh, + QObject* parent = 0, + std::optional password = std::nullopt); virtual ~AuthFlow() = default; void executeTask() override; diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index 80c8a8b97..ea4331c2c 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -67,15 +67,6 @@ MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json) return nullptr; } -MinecraftAccountPtr MinecraftAccount::createFromUsername(const QString& username) -{ - auto account = makeShared(); - account->data.type = AccountType::Mojang; - account->data.yggdrasilToken.extra["userName"] = username; - account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]")); - return account; -} - MinecraftAccountPtr MinecraftAccount::createFromUsernameAuthlibInjector(const QString& username, const QString& authlibInjectorUrl) { auto account = makeShared(); @@ -143,11 +134,11 @@ QPixmap MinecraftAccount::getFace() const return skin.scaled(64, 64, Qt::KeepAspectRatio); } -shared_qobject_ptr MinecraftAccount::login(bool useDeviceCode) +shared_qobject_ptr MinecraftAccount::login(bool useDeviceCode, std::optional password) { Q_ASSERT(m_currentTask.get() == nullptr); - m_currentTask.reset(new AuthFlow(&data, useDeviceCode ? AuthFlow::Action::DeviceCode : AuthFlow::Action::Login, this)); + m_currentTask.reset(new AuthFlow(&data, useDeviceCode ? AuthFlow::Action::DeviceCode : AuthFlow::Action::Login, this, password)); connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded); connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed); connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); }); diff --git a/launcher/minecraft/auth/MinecraftAccount.h b/launcher/minecraft/auth/MinecraftAccount.h index 6876e6670..d1b574997 100644 --- a/launcher/minecraft/auth/MinecraftAccount.h +++ b/launcher/minecraft/auth/MinecraftAccount.h @@ -83,7 +83,6 @@ class MinecraftAccount : public QObject, public Usable { //! Default constructor explicit MinecraftAccount(QObject* parent = 0); - static MinecraftAccountPtr createFromUsername(const QString& username); static MinecraftAccountPtr createFromUsernameAuthlibInjector(const QString& username, const QString& authlibInjectorUrl); static MinecraftAccountPtr createBlankMSA(); @@ -98,7 +97,7 @@ class MinecraftAccount : public QObject, public Usable { QJsonObject saveToJson() const; public: /* manipulation */ - shared_qobject_ptr login(bool useDeviceCode = false); + shared_qobject_ptr login(bool useDeviceCode = false, std::optional password = std::nullopt); shared_qobject_ptr refresh(); diff --git a/launcher/minecraft/auth/Yggdrasil.cpp b/launcher/minecraft/auth/Yggdrasil.cpp deleted file mode 100644 index dc0e1855c..000000000 --- a/launcher/minecraft/auth/Yggdrasil.cpp +++ /dev/null @@ -1,354 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "Yggdrasil.h" -#include "AccountData.h" - -#include -#include -#include -#include -#include -#include - -#include - -#include "Application.h" -#include "BuildConfig.h" - -Yggdrasil::Yggdrasil(AccountData* data, QObject* parent) : AccountTask(data, parent) -{ - changeState(AccountTaskState::STATE_CREATED); -} - -void Yggdrasil::sendRequest(QUrl endpoint, QByteArray content) -{ - changeState(AccountTaskState::STATE_WORKING); - - QNetworkRequest netRequest(endpoint); - netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - m_netReply = APPLICATION->network()->post(netRequest, content); - connect(m_netReply, &QNetworkReply::finished, this, &Yggdrasil::processReply); - connect(m_netReply, &QNetworkReply::uploadProgress, this, &Yggdrasil::refreshTimers); - connect(m_netReply, &QNetworkReply::downloadProgress, this, &Yggdrasil::refreshTimers); - connect(m_netReply, &QNetworkReply::sslErrors, this, &Yggdrasil::sslErrors); - timeout_keeper.setSingleShot(true); - timeout_keeper.start(timeout_max); - counter.setSingleShot(false); - counter.start(time_step); - progress(0, timeout_max); - connect(&timeout_keeper, &QTimer::timeout, this, &Yggdrasil::abortByTimeout); - connect(&counter, &QTimer::timeout, this, &Yggdrasil::heartbeat); -} - -void Yggdrasil::executeTask() {} - -void Yggdrasil::refresh() -{ - start(); - /* - * { - * "clientToken": "client identifier" - * "accessToken": "current access token to be refreshed" - * "selectedProfile": // specifying this causes errors - * { - * "id": "profile ID" - * "name": "profile name" - * } - * "requestUser": true/false // request the user structure - * } - */ - QJsonObject req; - req.insert("clientToken", m_data->clientToken()); - req.insert("accessToken", m_data->accessToken()); - /* - { - auto currentProfile = m_account->currentProfile(); - QJsonObject profile; - profile.insert("id", currentProfile->id()); - profile.insert("name", currentProfile->name()); - req.insert("selectedProfile", profile); - } - */ - req.insert("requestUser", false); - QJsonDocument doc(req); - - QUrl reqUrl(m_data->authServerUrl() + "/refresh"); - QByteArray requestData = doc.toJson(); - - sendRequest(reqUrl, requestData); -} - -void Yggdrasil::login(QString password) -{ - start(); - /* - * { - * "agent": { // optional - * "name": "Minecraft", // So far this is the only encountered value - * "version": 1 // This number might be increased - * // by the vanilla client in the future - * }, - * "username": "mojang account name", // Can be an email address or player name for - * // unmigrated accounts - * "password": "mojang account password", - * "clientToken": "client identifier", // optional - * "requestUser": true/false // request the user structure - * } - */ - QJsonObject req; - - { - QJsonObject agent; - // C++ makes string literals void* for some stupid reason, so we have to tell it - // QString... Thanks Obama. - agent.insert("name", QString("Minecraft")); - agent.insert("version", 1); - req.insert("agent", agent); - } - - req.insert("username", m_data->userName()); - req.insert("password", password); - req.insert("requestUser", false); - - // If we already have a client token, give it to the server. - // Otherwise, let the server give us one. - - m_data->generateClientTokenIfMissing(); - req.insert("clientToken", m_data->clientToken()); - - QJsonDocument doc(req); - - QUrl reqUrl(m_data->authServerUrl() + "/authenticate"); - QNetworkRequest netRequest(reqUrl); - QByteArray requestData = doc.toJson(); - - sendRequest(reqUrl, requestData); -} - -void Yggdrasil::refreshTimers(qint64, qint64) -{ - timeout_keeper.stop(); - timeout_keeper.start(timeout_max); - progress(count = 0, timeout_max); -} - -void Yggdrasil::heartbeat() -{ - count += time_step; - progress(count, timeout_max); -} - -bool Yggdrasil::abort() -{ - progress(timeout_max, timeout_max); - // TODO: actually use this in a meaningful way - m_aborted = Yggdrasil::BY_USER; - m_netReply->abort(); - return true; -} - -void Yggdrasil::abortByTimeout() -{ - progress(timeout_max, timeout_max); - // TODO: actually use this in a meaningful way - m_aborted = Yggdrasil::BY_TIMEOUT; - m_netReply->abort(); -} - -void Yggdrasil::sslErrors(QList errors) -{ - int i = 1; - for (auto error : errors) { - qCritical() << "LOGIN SSL Error #" << i << " : " << error.errorString(); - auto cert = error.certificate(); - qCritical() << "Certificate in question:\n" << cert.toText(); - i++; - } -} - -void Yggdrasil::processResponse(QJsonObject responseData) -{ - // Read the response data. We need to get the client token, access token, and the selected - // profile. - qDebug() << "Processing authentication response."; - - // qDebug() << responseData; - // If we already have a client token, make sure the one the server gave us matches our - // existing one. - QString clientToken = responseData.value("clientToken").toString(""); - if (clientToken.isEmpty()) { - // Fail if the server gave us an empty client token - changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send a client token.")); - return; - } - if (m_data->clientToken().isEmpty()) { - m_data->setClientToken(clientToken); - } else if (clientToken != m_data->clientToken()) { - changeState(AccountTaskState::STATE_FAILED_HARD, - tr("Authentication server attempted to change the client token. This isn't supported.")); - return; - } - - // Now, we set the access token. - qDebug() << "Getting access token."; - QString accessToken = responseData.value("accessToken").toString(""); - if (accessToken.isEmpty()) { - // Fail if the server didn't give us an access token. - changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send an access token.")); - return; - } - // Set the access token. - m_data->yggdrasilToken.token = accessToken; - m_data->yggdrasilToken.validity = Katabasis::Validity::Certain; - m_data->yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc(); - - // Get UUID here since we need it for later - // FIXME: Here is a simple workaround for now,, which uses the first available profile when selectedProfile is not provided - auto profile = responseData.value("selectedProfile"); - if (!profile.isObject()) { - auto profiles = responseData.value("availableProfiles"); - if (!profiles.isArray()) { - changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send available profiles.")); - return; - } else { - if (profiles.toArray().isEmpty()) { - changeState(AccountTaskState::STATE_FAILED_HARD, tr("Account has no available profile.")); - return; - } else { - profile = profiles.toArray().first(); - } - } - } - - 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."; - changeState(AccountTaskState::STATE_SUCCEEDED); -} - -void Yggdrasil::processReply() -{ - changeState(AccountTaskState::STATE_WORKING); - - switch (m_netReply->error()) { - case QNetworkReply::NoError: - break; - case QNetworkReply::TimeoutError: - changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation timed out.")); - return; - case QNetworkReply::OperationCanceledError: - changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation cancelled.")); - return; - case QNetworkReply::SslHandshakeFailedError: - changeState(AccountTaskState::STATE_FAILED_SOFT, - tr("SSL Handshake failed.
There might be a few causes for it:
" - "
    " - "
  • You use Windows and need to update your root certificates, please install any outstanding updates.
  • " - "
  • Some device on your network is interfering with SSL traffic. In that case, " - "you have bigger worries than Minecraft not starting.
  • " - "
  • Possibly something else. Check the log file for details
  • " - "
")); - return; - // used for invalid credentials and similar errors. Fall through. - case QNetworkReply::ContentAccessDenied: - case QNetworkReply::ContentOperationNotPermittedError: - break; - case QNetworkReply::ContentGoneError: { - changeState(AccountTaskState::STATE_FAILED_GONE, - tr("The Mojang account no longer exists. It may have been migrated to a Microsoft account.")); - return; - } - default: - changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation failed due to a network error: %1 (%2)") - .arg(m_netReply->errorString()) - .arg(m_netReply->error())); - return; - } - - // Try to parse the response regardless of the response code. - // Sometimes the auth server will give more information and an error code. - QJsonParseError jsonError; - QByteArray replyData = m_netReply->readAll(); - QJsonDocument doc = QJsonDocument::fromJson(replyData, &jsonError); - // Check the response code. - int responseCode = m_netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - - if (responseCode == 200) { - // If the response code was 200, then there shouldn't be an error. Make sure - // anyways. - // Also, sometimes an empty reply indicates success. If there was no data received, - // pass an empty json object to the processResponse function. - if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0) { - processResponse(replyData.size() > 0 ? doc.object() : QJsonObject()); - return; - } else { - changeState(AccountTaskState::STATE_FAILED_SOFT, - tr("Failed to parse authentication server response JSON response: %1 at offset %2.") - .arg(jsonError.errorString()) - .arg(jsonError.offset)); - qCritical() << replyData; - } - return; - } - - // If the response code was not 200, then Yggdrasil may have given us information - // about the error. - // If we can parse the response, then get information from it. Otherwise just say - // there was an unknown error. - if (jsonError.error == QJsonParseError::NoError) { - // We were able to parse the server's response. Woo! - // Call processError. If a subclass has overridden it then they'll handle their - // stuff there. - qDebug() << "The request failed, but the server gave us an error message. Processing error."; - processError(doc.object()); - } else { - // The server didn't say anything regarding the error. Give the user an unknown - // error. - qDebug() << "The request failed and the server gave no error message. Unknown error."; - changeState( - AccountTaskState::STATE_FAILED_SOFT, - tr("An unknown error occurred when trying to communicate with the authentication server: %1").arg(m_netReply->errorString())); - } -} - -void Yggdrasil::processError(QJsonObject responseData) -{ - QJsonValue errorVal = responseData.value("error"); - QJsonValue errorMessageValue = responseData.value("errorMessage"); - QJsonValue causeVal = responseData.value("cause"); - - if (errorVal.isString() && errorMessageValue.isString()) { - m_error = std::shared_ptr(new Error{ errorVal.toString(""), errorMessageValue.toString(""), causeVal.toString("") }); - changeState(AccountTaskState::STATE_FAILED_HARD, m_error->m_errorMessageVerbose); - } else { - // Error is not in standard format. Don't set m_error and return unknown error. - changeState(AccountTaskState::STATE_FAILED_HARD, tr("An unknown Yggdrasil error occurred.")); - } -} diff --git a/launcher/minecraft/auth/Yggdrasil.h b/launcher/minecraft/auth/Yggdrasil.h deleted file mode 100644 index 560d7fb81..000000000 --- a/launcher/minecraft/auth/Yggdrasil.h +++ /dev/null @@ -1,92 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 - -#include "AccountTask.h" - -#include -#include -#include -#include - -#include "MinecraftAccount.h" - -class QNetworkAccessManager; -class QNetworkReply; - -/** - * A Yggdrasil task is a task that performs an operation on a given mojang account. - */ -class Yggdrasil : public AccountTask { - Q_OBJECT - public: - explicit Yggdrasil(AccountData* data, QObject* parent = 0); - virtual ~Yggdrasil() = default; - - void refresh(); - void login(QString password); - - struct Error { - QString m_errorMessageShort; - QString m_errorMessageVerbose; - QString m_cause; - }; - std::shared_ptr m_error; - - enum AbortedBy { BY_NOTHING, BY_USER, BY_TIMEOUT } m_aborted = BY_NOTHING; - - protected: - void executeTask() override; - - /** - * Processes the response received from the server. - * If an error occurred, this should emit a failed signal. - * If Yggdrasil gave an error response, it should call setError() first, and then return false. - * Otherwise, it should return true. - * Note: If the response from the server was blank, and the HTTP code was 200, this function is called with - * an empty QJsonObject. - */ - void processResponse(QJsonObject responseData); - - /** - * Processes an error response received from the server. - * The default implementation will read data from Yggdrasil's standard error response format and set it as this task's Error. - * \returns a QString error message that will be passed to emitFailed. - */ - virtual void processError(QJsonObject responseData); - - protected slots: - void processReply(); - void refreshTimers(qint64, qint64); - void heartbeat(); - void sslErrors(QList); - void abortByTimeout(); - - public slots: - virtual bool abort() override; - - private: - void sendRequest(QUrl endpoint, QByteArray content); - - protected: - QNetworkReply* m_netReply = nullptr; - QTimer timeout_keeper; - QTimer counter; - int count = 0; // num msec since time reset - - const int timeout_max = 30000; - const int time_step = 50; -}; diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp b/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp deleted file mode 100644 index dacb709f8..000000000 --- a/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include "MinecraftProfileStepMojang.h" - -#include - -#include "BuildConfig.h" -#include "Logging.h" -#include "minecraft/auth/AuthRequest.h" -#include "minecraft/auth/Parsers.h" -#include "net/NetUtils.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(m_data->sessionServerUrl() + "/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 headers) -{ - auto requestor = qobject_cast(QObject::sender()); - requestor->deleteLater(); - - qCDebug(authCredentials()) << data; - 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); - - if (Net::isApplicationError(error)) { - emit finished(AccountTaskState::STATE_FAILED_SOFT, - tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_)); - } else { - emit finished(AccountTaskState::STATE_OFFLINE, - tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_)); - } - 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 deleted file mode 100644 index 730ec3f68..000000000 --- a/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once -#include - -#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); -}; diff --git a/launcher/minecraft/auth/steps/YggdrasilMinecraftProfileStep.cpp b/launcher/minecraft/auth/steps/YggdrasilMinecraftProfileStep.cpp new file mode 100644 index 000000000..a2955a30e --- /dev/null +++ b/launcher/minecraft/auth/steps/YggdrasilMinecraftProfileStep.cpp @@ -0,0 +1,73 @@ +#include "YggdrasilMinecraftProfileStep.h" + +#include + +#include "Application.h" +#include "minecraft/auth/Parsers.h" +#include "net/NetUtils.h" +#include "net/RawHeaderProxy.h" + +YggdrasilMinecraftProfileStep::YggdrasilMinecraftProfileStep(AccountData* data) : AuthStep(data) {} + +QString YggdrasilMinecraftProfileStep::describe() +{ + return tr("Fetching the Minecraft profile."); +} + +void YggdrasilMinecraftProfileStep::perform() +{ + if (m_data->minecraftProfile.id.isEmpty()) { + emit finished(AccountTaskState::STATE_FAILED_HARD, tr("A UUID is required to get the profile.")); + return; + } + + QUrl url = QUrl(m_data->sessionServerUrl() + "/session/minecraft/profile/" + m_data->minecraftProfile.id); + auto headers = QList{ { "Content-Type", "application/json" }, { "Accept", "application/json" } }; + + m_response.reset(new QByteArray()); + m_request = Net::Download::makeByteArray(url, m_response); + m_request->addHeaderProxy(new Net::RawHeaderProxy(headers)); + + m_task.reset(new NetJob("MinecraftProfileStep", APPLICATION->network())); + m_task->setAskRetry(false); + m_task->addNetAction(m_request); + + connect(m_task.get(), &Task::finished, this, &YggdrasilMinecraftProfileStep::onRequestDone); + + m_task->start(); +} + +void YggdrasilMinecraftProfileStep::onRequestDone() +{ + if (m_request->error() == QNetworkReply::ContentNotFoundError) { + // NOTE: Succeed even if we do not have a profile. This is a valid account state. + m_data->minecraftProfile = MinecraftProfile(); + emit finished(AccountTaskState::STATE_WORKING, tr("Account has no Minecraft profile.")); + return; + } + if (m_request->error() != QNetworkReply::NoError) { + qWarning() << "Error getting profile:"; + qWarning() << " HTTP Status: " << m_request->replyStatusCode(); + qWarning() << " Internal error no.: " << m_request->error(); + qWarning() << " Error string: " << m_request->errorString(); + + qWarning() << " Response:"; + qWarning() << QString::fromUtf8(*m_response); + + if (Net::isApplicationError(m_request->error())) { + emit finished(AccountTaskState::STATE_FAILED_SOFT, + tr("Minecraft Java profile acquisition failed: %1").arg(m_request->errorString())); + } else { + emit finished(AccountTaskState::STATE_OFFLINE, + tr("Minecraft Java profile acquisition failed: %1").arg(m_request->errorString())); + } + return; + } + if (!Parsers::parseMinecraftProfileMojang(*m_response, m_data->minecraftProfile)) { + m_data->minecraftProfile = MinecraftProfile(); + emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile response could not be parsed")); + return; + } + + emit finished(AccountTaskState::STATE_WORKING, tr("Minecraft Java profile acquisition succeeded.")); +} diff --git a/launcher/minecraft/auth/steps/YggdrasilMinecraftProfileStep.h b/launcher/minecraft/auth/steps/YggdrasilMinecraftProfileStep.h new file mode 100644 index 000000000..c075ff9f5 --- /dev/null +++ b/launcher/minecraft/auth/steps/YggdrasilMinecraftProfileStep.h @@ -0,0 +1,26 @@ +#pragma once +#include + +#include "minecraft/auth/AuthStep.h" +#include "net/Download.h" +#include "net/NetJob.h" + +class YggdrasilMinecraftProfileStep : public AuthStep { + Q_OBJECT + + public: + explicit YggdrasilMinecraftProfileStep(AccountData* data); + virtual ~YggdrasilMinecraftProfileStep() noexcept = default; + + void perform() override; + + QString describe() override; + + private slots: + void onRequestDone(); + + private: + std::shared_ptr m_response; + Net::Download::Ptr m_request; + NetJob::Ptr m_task; +}; diff --git a/launcher/minecraft/auth/steps/YggdrasilStep.cpp b/launcher/minecraft/auth/steps/YggdrasilStep.cpp index ee6816ef7..2bd2b7496 100644 --- a/launcher/minecraft/auth/steps/YggdrasilStep.cpp +++ b/launcher/minecraft/auth/steps/YggdrasilStep.cpp @@ -1,16 +1,9 @@ #include "YggdrasilStep.h" +#include "Application.h" -#include "minecraft/auth/Parsers.h" -#include "minecraft/auth/Yggdrasil.h" +#include "net/RawHeaderProxy.h" -YggdrasilStep::YggdrasilStep(AccountData* data, QString password) : AuthStep(data), m_password(password) -{ - m_yggdrasil = new Yggdrasil(m_data, this); - - connect(m_yggdrasil, &Task::failed, this, &YggdrasilStep::onAuthFailed); - connect(m_yggdrasil, &Task::succeeded, this, &YggdrasilStep::onAuthSucceeded); - connect(m_yggdrasil, &Task::aborted, this, &YggdrasilStep::onAuthFailed); -} +YggdrasilStep::YggdrasilStep(AccountData* data, std::optional password) : AuthStep(data), m_password(password) {} QString YggdrasilStep::describe() { @@ -19,27 +12,195 @@ QString YggdrasilStep::describe() void YggdrasilStep::perform() { - if (m_password.size()) { - m_yggdrasil->login(m_password); + if (m_password) { + login(*m_password); } else { - m_yggdrasil->refresh(); + refresh(); } } -void YggdrasilStep::onAuthSucceeded() +void YggdrasilStep::login(QString password) { - emit finished(AccountTaskState::STATE_WORKING, tr("Logged in with Mojang")); -} + QUrl url(m_data->authServerUrl() + "/authenticate"); + auto headers = QList{ { "Content-Type", "application/json" }, { "Accept", "application/json" } }; -void YggdrasilStep::onAuthFailed() -{ - auto state = m_yggdrasil->taskState(); - QString errorMessage = tr("Mojang user authentication failed."); - - // NOTE: soft error in the first step means 'offline' - if (state == AccountTaskState::STATE_FAILED_SOFT) { - state = AccountTaskState::STATE_OFFLINE; - errorMessage = tr("Mojang user authentication ended with a network error."); + /* + * { + * "agent": { // optional + * "name": "Minecraft", // So far this is the only encountered value + * "version": 1 // This number might be increased + * // by the vanilla client in the future + * }, + * "username": "mojang account name", // Can be an email address or player name for + * // unmigrated accounts + * "password": "mojang account password", + * "clientToken": "client identifier", // optional + * "requestUser": true/false // request the user structure + * } + */ + QJsonObject req; + { + QJsonObject agent; + agent.insert("name", QString("Minecraft")); + agent.insert("version", 1); + req.insert("agent", agent); } - emit finished(state, errorMessage); + req.insert("username", m_data->userName()); + req.insert("password", password); + req.insert("requestUser", false); + // + // If we already have a client token, give it to the server. + // Otherwise, let the server give us one. + + m_data->generateClientTokenIfMissing(); + req.insert("clientToken", m_data->clientToken()); + + QJsonDocument doc(req); + QByteArray requestData = doc.toJson(); + + m_response.reset(new QByteArray()); + m_request = Net::Upload::makeByteArray(url, m_response, requestData); + m_request->addHeaderProxy(new Net::RawHeaderProxy(headers)); + + m_task.reset(new NetJob("YggdrasilStep", APPLICATION->network())); + m_task->setAskRetry(false); + m_task->addNetAction(m_request); + + connect(m_task.get(), &Task::finished, this, &YggdrasilStep::onRequestDone); + + m_task->start(); +} + +void YggdrasilStep::refresh() +{ + QUrl url(m_data->authServerUrl() + "/refresh"); + auto headers = QList{ { "Content-Type", "application/json" }, { "Accept", "application/json" } }; + + /* + * { + * "clientToken": "client identifier" + * "accessToken": "current access token to be refreshed" + * "selectedProfile": // specifying this causes errors + * { + * "id": "profile ID" + * "name": "profile name" + * } + * "requestUser": true/false // request the user structure + * } + */ + QJsonObject req; + req.insert("clientToken", m_data->clientToken()); + req.insert("accessToken", m_data->accessToken()); + req.insert("requestUser", false); + + QJsonDocument doc(req); + QByteArray requestData = doc.toJson(); + + m_response.reset(new QByteArray()); + m_request = Net::Upload::makeByteArray(url, m_response, requestData); + m_request->addHeaderProxy(new Net::RawHeaderProxy(headers)); + + m_task.reset(new NetJob("YggdrasilStep", APPLICATION->network())); + m_task->setAskRetry(false); + m_task->addNetAction(m_request); + + connect(m_task.get(), &Task::finished, this, &YggdrasilStep::onRequestDone); + + m_task->start(); +} + +void YggdrasilStep::onRequestDone() +{ + // TODO handle errors + + QJsonParseError jsonError; + QJsonDocument doc = QJsonDocument::fromJson(*m_response, &jsonError); + YggdrasilStep::processResponse(doc.object()); +} + +void YggdrasilStep::processResponse(QJsonObject responseData) +{ + // Read the response data. We need to get the client token, access token, and the selected + // profile. + qDebug() << "Processing authentication response."; + + // qDebug() << responseData; + // If we already have a client token, make sure the one the server gave us matches our + // existing one. + QString clientToken = responseData.value("clientToken").toString(""); + if (clientToken.isEmpty()) { + // Fail if the server gave us an empty client token + emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send a client token.")); + return; + } + if (m_data->clientToken().isEmpty()) { + m_data->setClientToken(clientToken); + } else if (clientToken != m_data->clientToken()) { + emit finished(AccountTaskState::STATE_FAILED_HARD, + tr("Authentication server attempted to change the client token. This isn't supported.")); + return; + } + + // Now, we set the access token. + qDebug() << "Getting access token."; + QString accessToken = responseData.value("accessToken").toString(""); + if (accessToken.isEmpty()) { + // Fail if the server didn't give us an access token. + emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send an access token.")); + return; + } + + // Set the access token. + m_data->yggdrasilToken.token = accessToken; + m_data->yggdrasilToken.validity = Validity::Certain; + m_data->yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc(); + + // Get UUID here since we need it for later + // FIXME: Here is a simple workaround for now,, which uses the first available profile when selectedProfile is not provided + auto profile = responseData.value("selectedProfile"); + if (!profile.isObject()) { + auto profiles = responseData.value("availableProfiles"); + if (!profiles.isArray()) { + emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send available profiles.")); + return; + } else { + if (profiles.toArray().isEmpty()) { + emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Account has no available profile.")); + return; + } else { + profile = profiles.toArray().first(); + } + } + } + + 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()) { + emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send a UUID in selected profile.")); + return; + } + + emit finished(AccountTaskState::STATE_WORKING, "Logged in"); +} + +void YggdrasilStep::processError(QJsonObject responseData) +{ + /*QJsonValue errorVal = responseData.value("error");*/ + /*QJsonValue errorMessageValue = responseData.value("errorMessage");*/ + /*QJsonValue causeVal = responseData.value("cause");*/ + /**/ + /*if (errorVal.isString() && errorMessageValue.isString()) {*/ + /* m_error = std::shared_ptr(new Error{ errorVal.toString(""), errorMessageValue.toString(""), causeVal.toString("") });*/ + /* changeState(AccountTaskState::STATE_FAILED_HARD, m_error->m_errorMessageVerbose);*/ + /*} else {*/ + /* // Error is not in standard format. Don't set m_error and return unknown error.*/ + /* changeState(AccountTaskState::STATE_FAILED_HARD, tr("An unknown Yggdrasil error occurred."));*/ + /*}*/ } diff --git a/launcher/minecraft/auth/steps/YggdrasilStep.h b/launcher/minecraft/auth/steps/YggdrasilStep.h index 8f5a4df10..0cc558d45 100644 --- a/launcher/minecraft/auth/steps/YggdrasilStep.h +++ b/launcher/minecraft/auth/steps/YggdrasilStep.h @@ -1,8 +1,9 @@ #pragma once #include -#include "QObjectPtr.h" #include "minecraft/auth/AuthStep.h" +#include "net/NetJob.h" +#include "net/Upload.h" class Yggdrasil; @@ -10,7 +11,7 @@ class YggdrasilStep : public AuthStep { Q_OBJECT public: - explicit YggdrasilStep(AccountData* data, QString password); + explicit YggdrasilStep(AccountData* data, std::optional password); virtual ~YggdrasilStep() noexcept = default; void perform() override; @@ -18,10 +19,17 @@ class YggdrasilStep : public AuthStep { QString describe() override; private slots: - void onAuthSucceeded(); - void onAuthFailed(); + void onRequestDone(); private: - Yggdrasil* m_yggdrasil = nullptr; - QString m_password; + void login(QString password); + void refresh(); + + void processResponse(QJsonObject responseData); + void processError(QJsonObject responseData); + + std::optional m_password; + std::shared_ptr m_response; + Net::Upload::Ptr m_request; + NetJob::Ptr m_task; }; diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp index de133ce7b..8482d1069 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp @@ -139,7 +139,6 @@ void PackInstallTask::resolveMods() m_file_id_map.clear(); Flame::Manifest manifest; - int index = 0; for (auto const& file : m_version.files) { if (!file.serverOnly && file.url.isEmpty()) { @@ -151,15 +150,12 @@ void PackInstallTask::resolveMods() Flame::File flame_file; flame_file.projectId = file.curseforge.project_id; flame_file.fileId = file.curseforge.file_id; - flame_file.hash = file.sha1; manifest.files.insert(flame_file.fileId, flame_file); m_file_id_map.append(flame_file.fileId); } else { m_file_id_map.append(-1); } - - index++; } m_mod_id_resolver_task.reset(new Flame::FileResolvingTask(APPLICATION->network(), manifest)); @@ -188,11 +184,11 @@ void PackInstallTask::onResolveModsSucceeded() VersionFile& local_file = m_version.files[index]; // First check for blocked mods - if (!results_file.resolved || results_file.url.isEmpty()) { + if (!results_file.version.downloadUrl.isEmpty()) { BlockedMod blocked_mod; blocked_mod.name = local_file.name; - blocked_mod.websiteUrl = results_file.websiteUrl; - blocked_mod.hash = results_file.hash; + blocked_mod.websiteUrl = results_file.pack.websiteUrl; + blocked_mod.hash = results_file.version.hash; blocked_mod.matched = false; blocked_mod.localPath = ""; blocked_mod.targetFolder = results_file.targetFolder; @@ -201,7 +197,7 @@ void PackInstallTask::onResolveModsSucceeded() anyBlocked = true; } else { - local_file.url = results_file.url.toString(); + local_file.url = results_file.version.downloadUrl; } } diff --git a/launcher/ui/dialogs/AuthlibInjectorLoginDialog.cpp b/launcher/ui/dialogs/AuthlibInjectorLoginDialog.cpp index 5f7ba4af8..bd2f76fba 100644 --- a/launcher/ui/dialogs/AuthlibInjectorLoginDialog.cpp +++ b/launcher/ui/dialogs/AuthlibInjectorLoginDialog.cpp @@ -21,14 +21,13 @@ #include "ui_AuthlibInjectorLoginDialog.h" #include "CreateAuthlibInjectorAccount.h" -#include "minecraft/auth/AccountTask.h" #include AuthlibInjectorLoginDialog::AuthlibInjectorLoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AuthlibInjectorLoginDialog) { ui->setupUi(this); - ui->progressBar->setVisible(false); + ui->loadingLabel->setVisible(false); ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); setAcceptDrops(true); @@ -104,10 +103,12 @@ void AuthlibInjectorLoginDialog::accept() return; setUserInputsEnabled(false); - ui->progressBar->setVisible(true); + ui->loadingLabel->setVisible(true); // Get the authlib-injector API root auto netJob = NetJob::Ptr(new NetJob("Get authlib-injector API root", APPLICATION->network())); + netJob->setAskRetry(false); + auto username = ui->userTextBox->text(); m_createAuthlibInjectorAccountTask = CreateAuthlibInjectorAccount::make(fixedAuthlibInjectorUrl, m_account, username); netJob->addNetAction(m_createAuthlibInjectorAccountTask); @@ -159,17 +160,16 @@ void AuthlibInjectorLoginDialog::onTaskFailed(const QString& reason) // Re-enable user-interaction setUserInputsEnabled(true); - ui->progressBar->setVisible(false); + ui->loadingLabel->setVisible(false); } void AuthlibInjectorLoginDialog::onUrlTaskSucceeded() { m_account = m_createAuthlibInjectorAccountTask->getAccount(); - m_loginTask = m_account->loginAuthlibInjector(ui->passTextBox->text()); + m_loginTask = m_account->login(false, ui->passTextBox->text()); connect(m_loginTask.get(), &Task::failed, this, &AuthlibInjectorLoginDialog::onTaskFailed); connect(m_loginTask.get(), &Task::succeeded, this, &AuthlibInjectorLoginDialog::onTaskSucceeded); connect(m_loginTask.get(), &Task::status, this, &AuthlibInjectorLoginDialog::onTaskStatus); - connect(m_loginTask.get(), &Task::progress, this, &AuthlibInjectorLoginDialog::onTaskProgress); m_loginTask->start(); } @@ -183,12 +183,6 @@ void AuthlibInjectorLoginDialog::onTaskStatus(const QString& status) ui->label->setText(status); } -void AuthlibInjectorLoginDialog::onTaskProgress(qint64 current, qint64 total) -{ - ui->progressBar->setMaximum(total); - ui->progressBar->setValue(current); -} - // Public interface MinecraftAccountPtr AuthlibInjectorLoginDialog::newAccount(QWidget* parent, QString msg) { diff --git a/launcher/ui/dialogs/AuthlibInjectorLoginDialog.h b/launcher/ui/dialogs/AuthlibInjectorLoginDialog.h index 8d9f1d710..34f4e479b 100644 --- a/launcher/ui/dialogs/AuthlibInjectorLoginDialog.h +++ b/launcher/ui/dialogs/AuthlibInjectorLoginDialog.h @@ -51,7 +51,6 @@ class AuthlibInjectorLoginDialog : public QDialog { void onUrlTaskSucceeded(); void onTaskSucceeded(); void onTaskStatus(const QString& status); - void onTaskProgress(qint64 current, qint64 total); void on_userTextBox_textEdited(const QString& newText); void on_passTextBox_textEdited(const QString& newText); diff --git a/launcher/ui/dialogs/AuthlibInjectorLoginDialog.ui b/launcher/ui/dialogs/AuthlibInjectorLoginDialog.ui index b6e157f37..271056c0e 100644 --- a/launcher/ui/dialogs/AuthlibInjectorLoginDialog.ui +++ b/launcher/ui/dialogs/AuthlibInjectorLoginDialog.ui @@ -60,14 +60,24 @@ - - - - 24 - - - false - + + + + + 16 + 75 + true + + + + Please wait... + + + Qt::AlignCenter + + + true + diff --git a/launcher/ui/dialogs/LoginDialog.cpp b/launcher/ui/dialogs/LoginDialog.cpp deleted file mode 100644 index 7296a13ef..000000000 --- a/launcher/ui/dialogs/LoginDialog.cpp +++ /dev/null @@ -1,115 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * 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 "LoginDialog.h" -#include "ui_LoginDialog.h" - -#include "minecraft/auth/AccountTask.h" - -#include - -LoginDialog::LoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::LoginDialog) -{ - ui->setupUi(this); - ui->progressBar->setVisible(false); - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); - - connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); -} - -LoginDialog::~LoginDialog() -{ - delete ui; -} - -// Stage 1: User interaction -void LoginDialog::accept() -{ - setUserInputsEnabled(false); - ui->progressBar->setVisible(true); - - // Setup the login task and start it - m_account = MinecraftAccount::createFromUsername(ui->userTextBox->text()); - m_loginTask = m_account->login(ui->passTextBox->text()); - connect(m_loginTask.get(), &Task::failed, this, &LoginDialog::onTaskFailed); - connect(m_loginTask.get(), &Task::succeeded, this, &LoginDialog::onTaskSucceeded); - connect(m_loginTask.get(), &Task::status, this, &LoginDialog::onTaskStatus); - connect(m_loginTask.get(), &Task::progress, this, &LoginDialog::onTaskProgress); - m_loginTask->start(); -} - -void LoginDialog::setUserInputsEnabled(bool enable) -{ - ui->userTextBox->setEnabled(enable); - ui->passTextBox->setEnabled(enable); - ui->buttonBox->setEnabled(enable); -} - -// Enable the OK button only when both textboxes contain something. -void LoginDialog::on_userTextBox_textEdited(const QString& newText) -{ - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!newText.isEmpty() && !ui->passTextBox->text().isEmpty()); -} -void LoginDialog::on_passTextBox_textEdited(const QString& newText) -{ - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!newText.isEmpty() && !ui->userTextBox->text().isEmpty()); -} - -void LoginDialog::onTaskFailed(const QString& reason) -{ - // Set message - auto lines = reason.split('\n'); - QString processed; - for (auto line : lines) { - if (line.size()) { - processed += "" + line + "
"; - } else { - processed += "
"; - } - } - ui->label->setText(processed); - - // Re-enable user-interaction - setUserInputsEnabled(true); - ui->progressBar->setVisible(false); -} - -void LoginDialog::onTaskSucceeded() -{ - QDialog::accept(); -} - -void LoginDialog::onTaskStatus(const QString& status) -{ - ui->label->setText(status); -} - -void LoginDialog::onTaskProgress(qint64 current, qint64 total) -{ - ui->progressBar->setMaximum(total); - ui->progressBar->setValue(current); -} - -// Public interface -MinecraftAccountPtr LoginDialog::newAccount(QWidget* parent, QString msg) -{ - LoginDialog dlg(parent); - dlg.ui->label->setText(msg); - if (dlg.exec() == QDialog::Accepted) { - return dlg.m_account; - } - return nullptr; -} diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp index d26e31223..3672934d7 100644 --- a/launcher/ui/pages/global/AccountListPage.cpp +++ b/launcher/ui/pages/global/AccountListPage.cpp @@ -133,19 +133,6 @@ void AccountListPage::listChanged() updateButtonStates(); } -void AccountListPage::on_actionAddMojang_triggered() -{ - MinecraftAccountPtr account = - LoginDialog::newAccount(this, tr("Please enter your Mojang account email and password to add your account.")); - - if (account) { - m_accounts->addAccount(account); - if (m_accounts->count() == 1) { - m_accounts->setDefaultAccount(account); - } - } -} - void AccountListPage::on_actionAddAuthlibInjector_triggered() { if (!m_accounts->anyAccountIsValid()) { diff --git a/launcher/ui/pages/global/AccountListPage.h b/launcher/ui/pages/global/AccountListPage.h index cc4bd81aa..a02da25bd 100644 --- a/launcher/ui/pages/global/AccountListPage.h +++ b/launcher/ui/pages/global/AccountListPage.h @@ -70,7 +70,6 @@ class AccountListPage : public QMainWindow, public BasePage { void retranslate() override; public slots: - void on_actionAddMojang_triggered(); void on_actionAddAuthlibInjector_triggered(); void on_actionAddMicrosoft_triggered(); void on_actionAddOffline_triggered(); diff --git a/launcher/ui/pages/global/AccountListPage.ui b/launcher/ui/pages/global/AccountListPage.ui index ea58042cd..a9284087e 100644 --- a/launcher/ui/pages/global/AccountListPage.ui +++ b/launcher/ui/pages/global/AccountListPage.ui @@ -53,7 +53,6 @@ false -