diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 02dcb28f6..8ae461488 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -45,8 +45,8 @@ set(CORE_SOURCES
ResourceDownloadTask.h
ResourceDownloadTask.cpp
- CreateAuthlibInjectorAccount.cpp
- CreateAuthlibInjectorAccount.h
+ GetAuthlibInjectorApiLocation.cpp
+ GetAuthlibInjectorApiLocation.h
# Use tracking separate from memory management
Usable.h
diff --git a/launcher/CreateAuthlibInjectorAccount.cpp b/launcher/GetAuthlibInjectorApiLocation.cpp
similarity index 67%
rename from launcher/CreateAuthlibInjectorAccount.cpp
rename to launcher/GetAuthlibInjectorApiLocation.cpp
index 3d0c8e545..235e8c9e5 100644
--- a/launcher/CreateAuthlibInjectorAccount.cpp
+++ b/launcher/GetAuthlibInjectorApiLocation.cpp
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-#include "CreateAuthlibInjectorAccount.h"
+#include "GetAuthlibInjectorApiLocation.h"
#include
#include
@@ -28,40 +28,40 @@
#include "Application.h"
-CreateAuthlibInjectorAccount::CreateAuthlibInjectorAccount(QUrl url, MinecraftAccountPtr account, QString username)
+GetAuthlibInjectorApiLocation::GetAuthlibInjectorApiLocation(QUrl url, MinecraftAccountPtr account, QString username)
: NetRequest(), m_account(account), m_username(username)
{
m_url = url;
m_sink.reset(new Sink(*this));
}
-QNetworkReply* CreateAuthlibInjectorAccount::getReply(QNetworkRequest& request)
+QNetworkReply* GetAuthlibInjectorApiLocation::getReply(QNetworkRequest& request)
{
setStatus(tr("Getting authlib-injector server details"));
return m_network->get(request);
}
-CreateAuthlibInjectorAccount::Ptr CreateAuthlibInjectorAccount::make(QUrl url, MinecraftAccountPtr account, QString username)
+GetAuthlibInjectorApiLocation::Ptr GetAuthlibInjectorApiLocation::make(QUrl url, MinecraftAccountPtr account, QString username)
{
- return CreateAuthlibInjectorAccount::Ptr(new CreateAuthlibInjectorAccount(url, account, username));
+ return GetAuthlibInjectorApiLocation::Ptr(new GetAuthlibInjectorApiLocation(url, account, username));
}
-auto CreateAuthlibInjectorAccount::Sink::init(QNetworkRequest& request) -> Task::State
+auto GetAuthlibInjectorApiLocation::Sink::init(QNetworkRequest& request) -> Task::State
{
return Task::State::Running;
}
-auto CreateAuthlibInjectorAccount::Sink::write(QByteArray& data) -> Task::State
+auto GetAuthlibInjectorApiLocation::Sink::write(QByteArray& data) -> Task::State
{
return Task::State::Running;
}
-auto CreateAuthlibInjectorAccount::Sink::abort() -> Task::State
+auto GetAuthlibInjectorApiLocation::Sink::abort() -> Task::State
{
return Task::State::Failed;
}
-auto CreateAuthlibInjectorAccount::Sink::finalize(QNetworkReply& reply) -> Task::State
+auto GetAuthlibInjectorApiLocation::Sink::finalize(QNetworkReply& reply) -> Task::State
{
QVariant header = reply.rawHeader("X-Authlib-Injector-API-Location");
QUrl url = m_outer.m_url;
@@ -76,7 +76,7 @@ auto CreateAuthlibInjectorAccount::Sink::finalize(QNetworkReply& reply) -> Task:
return Task::State::Succeeded;
}
-MinecraftAccountPtr CreateAuthlibInjectorAccount::getAccount()
+MinecraftAccountPtr GetAuthlibInjectorApiLocation::getAccount()
{
return m_account;
}
diff --git a/launcher/CreateAuthlibInjectorAccount.h b/launcher/GetAuthlibInjectorApiLocation.h
similarity index 74%
rename from launcher/CreateAuthlibInjectorAccount.h
rename to launcher/GetAuthlibInjectorApiLocation.h
index a02f69ccd..f35e22f40 100644
--- a/launcher/CreateAuthlibInjectorAccount.h
+++ b/launcher/GetAuthlibInjectorApiLocation.h
@@ -21,20 +21,20 @@
#include "minecraft/auth/MinecraftAccount.h"
#include "net/NetRequest.h"
-class CreateAuthlibInjectorAccount : public Net::NetRequest {
+class GetAuthlibInjectorApiLocation : public Net::NetRequest {
Q_OBJECT
public:
- using Ptr = shared_qobject_ptr;
- CreateAuthlibInjectorAccount(QUrl url, MinecraftAccountPtr account, QString username);
- virtual ~CreateAuthlibInjectorAccount() = default;
+ using Ptr = shared_qobject_ptr;
+ GetAuthlibInjectorApiLocation(QUrl url, MinecraftAccountPtr account, QString username);
+ virtual ~GetAuthlibInjectorApiLocation() = default;
- static CreateAuthlibInjectorAccount::Ptr make(QUrl url, MinecraftAccountPtr account, QString username);
+ static GetAuthlibInjectorApiLocation::Ptr make(QUrl url, MinecraftAccountPtr account, QString username);
MinecraftAccountPtr getAccount();
class Sink : public Net::Sink {
public:
- Sink(CreateAuthlibInjectorAccount& outer) : m_outer(outer) {}
+ Sink(GetAuthlibInjectorApiLocation& outer) : m_outer(outer) {}
virtual ~Sink() = default;
public:
@@ -45,7 +45,7 @@ class CreateAuthlibInjectorAccount : public Net::NetRequest {
auto hasLocalData() -> bool override { return false; }
private:
- CreateAuthlibInjectorAccount& m_outer;
+ GetAuthlibInjectorApiLocation& m_outer;
};
protected slots:
diff --git a/launcher/minecraft/auth/AuthFlow.cpp b/launcher/minecraft/auth/AuthFlow.cpp
index ab3898971..c3a159197 100644
--- a/launcher/minecraft/auth/AuthFlow.cpp
+++ b/launcher/minecraft/auth/AuthFlow.cpp
@@ -131,7 +131,7 @@ bool AuthFlow::changeState(AccountTaskState newState, QString reason)
return false;
}
case AccountTaskState::STATE_FAILED_HARD: {
- setStatus(tr("Failed to authenticate. The session has expired."));
+ setStatus(tr("Failed to authenticate."));
m_data->errorString = reason;
m_data->accountState = AccountState::Expired;
emitFailed(reason);
diff --git a/launcher/minecraft/auth/steps/YggdrasilStep.cpp b/launcher/minecraft/auth/steps/YggdrasilStep.cpp
index 2bd2b7496..1d7392e76 100644
--- a/launcher/minecraft/auth/steps/YggdrasilStep.cpp
+++ b/launcher/minecraft/auth/steps/YggdrasilStep.cpp
@@ -7,7 +7,7 @@ YggdrasilStep::YggdrasilStep(AccountData* data, std::optional password)
QString YggdrasilStep::describe()
{
- return tr("Logging in with Mojang account.");
+ return tr("Logging in with Yggdrasil.");
}
void YggdrasilStep::perform()
@@ -64,6 +64,7 @@ void YggdrasilStep::login(QString password)
m_task.reset(new NetJob("YggdrasilStep", APPLICATION->network()));
m_task->setAskRetry(false);
+ m_task->setAutoRetryLimit(0);
m_task->addNetAction(m_request);
connect(m_task.get(), &Task::finished, this, &YggdrasilStep::onRequestDone);
@@ -102,6 +103,7 @@ void YggdrasilStep::refresh()
m_task.reset(new NetJob("YggdrasilStep", APPLICATION->network()));
m_task->setAskRetry(false);
+ m_task->setAutoRetryLimit(0);
m_task->addNetAction(m_request);
connect(m_task.get(), &Task::finished, this, &YggdrasilStep::onRequestDone);
@@ -111,10 +113,90 @@ void YggdrasilStep::refresh()
void YggdrasilStep::onRequestDone()
{
- // TODO handle errors
+ qDebug() << "Yggdrasil request done";
+ switch (m_request->error()) {
+ case QNetworkReply::NoError:
+ break;
+ case QNetworkReply::AuthenticationRequiredError:
+ // These cases will be handled as usual
+ break;
+ case QNetworkReply::TimeoutError:
+ emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation timed out."));
+ return;
+ case QNetworkReply::OperationCanceledError:
+ emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation cancelled."));
+ return;
+ case QNetworkReply::SslHandshakeFailedError:
+ emit finished(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: {
+ emit finished(AccountTaskState::STATE_FAILED_GONE,
+ tr("The Mojang account no longer exists. It may have been migrated to a Microsoft account."));
+ return;
+ }
+ default:
+ emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation failed due to a network error: %1 (%2)")
+ .arg(m_request->errorString())
+ .arg(m_request->error()));
+ return;
+ }
+ // Try to parse the response regardless of the response code.
+ // Sometimes the auth server will give more information and an error code.
+ // Check the response code.
QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(*m_response, &jsonError);
+ // Check the response code.
+ int responseCode = m_request->replyStatusCode();
+
+ 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 || m_response->size() == 0) {
+ processResponse(m_response->size() > 0 ? doc.object() : QJsonObject());
+ return;
+ } else {
+ emit finished(AccountTaskState::STATE_FAILED_SOFT,
+ tr("Failed to parse authentication server response JSON response: %1 at offset %2.")
+ .arg(jsonError.errorString())
+ .arg(jsonError.offset));
+ qCritical() << *m_response;
+ }
+ 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.";
+ emit finished(
+ AccountTaskState::STATE_FAILED_SOFT,
+ tr("An unknown error occurred when trying to communicate with the authentication server: %1").arg(m_request->errorString()));
+ }
+
YggdrasilStep::processResponse(doc.object());
}
@@ -122,9 +204,7 @@ 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("");
@@ -192,15 +272,15 @@ void YggdrasilStep::processResponse(QJsonObject responseData)
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."));*/
- /*}*/
+ 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("") });*/
+ emit finished(AccountTaskState::STATE_FAILED_HARD, errorMessageValue.toString(""));
+ } else {
+ // Error is not in standard format. Don't set m_error and return unknown error.
+ emit finished(AccountTaskState::STATE_FAILED_HARD, tr("An unknown Yggdrasil error occurred."));
+ }
}
diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp
index 3cd3958f7..38b73c6d9 100644
--- a/launcher/net/NetJob.cpp
+++ b/launcher/net/NetJob.cpp
@@ -66,8 +66,8 @@ auto NetJob::addNetAction(Net::NetRequest::Ptr action) -> bool
void NetJob::executeNextSubTask()
{
- // We're finished, check for failures and retry if we can (up to 3 times)
- if (isRunning() && m_queue.isEmpty() && m_doing.isEmpty() && !m_failed.isEmpty() && m_try < 3) {
+ // We're finished, check for failures and retry if we can (up to m_auto_retry_limit times)
+ if (isRunning() && m_queue.isEmpty() && m_doing.isEmpty() && !m_failed.isEmpty() && m_try < m_auto_retry_limit) {
m_try += 1;
while (!m_failed.isEmpty()) {
auto task = m_failed.take(*m_failed.keyBegin());
@@ -188,3 +188,8 @@ void NetJob::setAskRetry(bool askRetry)
{
m_ask_retry = askRetry;
}
+
+void NetJob::setAutoRetryLimit(int autoRetryLimit)
+{
+ m_auto_retry_limit = autoRetryLimit;
+}
diff --git a/launcher/net/NetJob.h b/launcher/net/NetJob.h
index 59213ba15..7366fe26a 100644
--- a/launcher/net/NetJob.h
+++ b/launcher/net/NetJob.h
@@ -63,6 +63,7 @@ class NetJob : public ConcurrentTask {
auto getFailedActions() -> QList;
auto getFailedFiles() -> QList;
void setAskRetry(bool askRetry);
+ void setAutoRetryLimit(int autoRetryLimit);
public slots:
// Qt can't handle auto at the start for some reason?
@@ -81,5 +82,6 @@ class NetJob : public ConcurrentTask {
int m_try = 1;
bool m_ask_retry = true;
+ int m_auto_retry_limit = 3;
int m_manual_try = 0;
};
diff --git a/launcher/ui/dialogs/AuthlibInjectorLoginDialog.cpp b/launcher/ui/dialogs/AuthlibInjectorLoginDialog.cpp
index bd2f76fba..b6d3b3f75 100644
--- a/launcher/ui/dialogs/AuthlibInjectorLoginDialog.cpp
+++ b/launcher/ui/dialogs/AuthlibInjectorLoginDialog.cpp
@@ -20,14 +20,17 @@
#include "ui/dialogs/CustomMessageBox.h"
#include "ui_AuthlibInjectorLoginDialog.h"
-#include "CreateAuthlibInjectorAccount.h"
+#include "Application.h"
+#include "GetAuthlibInjectorApiLocation.h"
#include
AuthlibInjectorLoginDialog::AuthlibInjectorLoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AuthlibInjectorLoginDialog)
{
ui->setupUi(this);
+ ui->userTextBox->setFocus();
ui->loadingLabel->setVisible(false);
+ ui->errorMessage->setVisible(false);
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
setAcceptDrops(true);
@@ -88,6 +91,7 @@ void AuthlibInjectorLoginDialog::dropEvent(QDropEvent* event)
// Stage 1: User interaction
void AuthlibInjectorLoginDialog::accept()
{
+ ui->errorMessage->setVisible(false);
auto fixedAuthlibInjectorUrl = AuthlibInjectorLoginDialog::fixUrl(ui->authlibInjectorTextBox->text());
auto response = CustomMessageBox::selectable(this, QObject::tr("Confirm account creation"),
@@ -108,15 +112,16 @@ void AuthlibInjectorLoginDialog::accept()
// Get the authlib-injector API root
auto netJob = NetJob::Ptr(new NetJob("Get authlib-injector API root", APPLICATION->network()));
netJob->setAskRetry(false);
+ netJob->setAutoRetryLimit(0);
auto username = ui->userTextBox->text();
- m_createAuthlibInjectorAccountTask = CreateAuthlibInjectorAccount::make(fixedAuthlibInjectorUrl, m_account, username);
- netJob->addNetAction(m_createAuthlibInjectorAccountTask);
+ m_apiLocationRequest = GetAuthlibInjectorApiLocation::make(fixedAuthlibInjectorUrl, m_account, username);
+ netJob->addNetAction(m_apiLocationRequest);
- m_createAuthlibInjectorAccountNetJob.reset(netJob);
- connect(netJob.get(), &NetJob::succeeded, this, &AuthlibInjectorLoginDialog::onUrlTaskSucceeded);
- connect(netJob.get(), &NetJob::failed, this, &AuthlibInjectorLoginDialog::onTaskFailed);
- m_createAuthlibInjectorAccountNetJob->start();
+ m_apiLocationTask.reset(netJob);
+ connect(netJob.get(), &NetJob::succeeded, this, &AuthlibInjectorLoginDialog::onApiLocationTaskSucceeded);
+ connect(netJob.get(), &NetJob::failed, this, &AuthlibInjectorLoginDialog::onApiLocationTaskFailed);
+ m_apiLocationTask->start();
}
void AuthlibInjectorLoginDialog::setUserInputsEnabled(bool enable)
@@ -144,6 +149,11 @@ void AuthlibInjectorLoginDialog::on_authlibInjectorTextBox_textEdited(const QStr
->setEnabled(!newText.isEmpty() && !ui->passTextBox->text().isEmpty() && !ui->authlibInjectorTextBox->text().isEmpty());
}
+void AuthlibInjectorLoginDialog::onApiLocationTaskFailed(const QString& reason)
+{
+ onTaskFailed(m_apiLocationRequest->errorString());
+}
+
void AuthlibInjectorLoginDialog::onTaskFailed(const QString& reason)
{
// Set message
@@ -156,16 +166,17 @@ void AuthlibInjectorLoginDialog::onTaskFailed(const QString& reason)
processed += "
";
}
}
- ui->label->setText(processed);
+ ui->errorMessage->setText(processed);
+ ui->errorMessage->setVisible(true);
// Re-enable user-interaction
setUserInputsEnabled(true);
ui->loadingLabel->setVisible(false);
}
-void AuthlibInjectorLoginDialog::onUrlTaskSucceeded()
+void AuthlibInjectorLoginDialog::onApiLocationTaskSucceeded()
{
- m_account = m_createAuthlibInjectorAccountTask->getAccount();
+ m_account = m_apiLocationRequest->getAccount();
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);
@@ -180,7 +191,7 @@ void AuthlibInjectorLoginDialog::onTaskSucceeded()
void AuthlibInjectorLoginDialog::onTaskStatus(const QString& status)
{
- ui->label->setText(status);
+ ui->errorMessage->setText(status);
}
// Public interface
diff --git a/launcher/ui/dialogs/AuthlibInjectorLoginDialog.h b/launcher/ui/dialogs/AuthlibInjectorLoginDialog.h
index 34f4e479b..55082a602 100644
--- a/launcher/ui/dialogs/AuthlibInjectorLoginDialog.h
+++ b/launcher/ui/dialogs/AuthlibInjectorLoginDialog.h
@@ -22,8 +22,7 @@
#include
#include
-#include "Application.h"
-#include "CreateAuthlibInjectorAccount.h"
+#include "GetAuthlibInjectorApiLocation.h"
#include "minecraft/auth/MinecraftAccount.h"
#include "tasks/Task.h"
@@ -47,8 +46,9 @@ class AuthlibInjectorLoginDialog : public QDialog {
protected slots:
void accept();
+ void onApiLocationTaskSucceeded();
+ void onApiLocationTaskFailed(const QString&);
void onTaskFailed(const QString& reason);
- void onUrlTaskSucceeded();
void onTaskSucceeded();
void onTaskStatus(const QString& status);
@@ -65,6 +65,6 @@ class AuthlibInjectorLoginDialog : public QDialog {
Ui::AuthlibInjectorLoginDialog* ui;
MinecraftAccountPtr m_account;
Task::Ptr m_loginTask;
- Task::Ptr m_createAuthlibInjectorAccountNetJob;
- CreateAuthlibInjectorAccount::Ptr m_createAuthlibInjectorAccountTask;
+ Task::Ptr m_apiLocationTask;
+ GetAuthlibInjectorApiLocation::Ptr m_apiLocationRequest;
};
diff --git a/launcher/ui/dialogs/AuthlibInjectorLoginDialog.ui b/launcher/ui/dialogs/AuthlibInjectorLoginDialog.ui
index 271056c0e..5965b8d3a 100644
--- a/launcher/ui/dialogs/AuthlibInjectorLoginDialog.ui
+++ b/launcher/ui/dialogs/AuthlibInjectorLoginDialog.ui
@@ -80,6 +80,22 @@
+ -
+
+
+ Error message label placeholder.
+
+
+ Qt::RichText
+
+
+ Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse
+
+
+ true
+
+
+
-