1
Fork 0
mirror of https://github.com/redstrate/Astra.git synced 2025-04-20 19:57:45 +00:00

Use coroutines in square enix login process

This commit is contained in:
Joshua Goins 2023-09-16 18:52:12 -04:00
parent 6dc8041c71
commit b068d34001
4 changed files with 116 additions and 104 deletions

View file

@ -3,6 +3,8 @@
#pragma once #pragma once
#include <qcorotask.h>
#include "patcher.h" #include "patcher.h"
class SquareLauncher; class SquareLauncher;
@ -15,9 +17,9 @@ class SquareBoot : public QObject
public: public:
SquareBoot(LauncherCore &window, SquareLauncher &launcher, QObject *parent = nullptr); SquareBoot(LauncherCore &window, SquareLauncher &launcher, QObject *parent = nullptr);
Q_INVOKABLE void checkGateStatus(LoginInformation *info); QCoro::Task<> checkGateStatus(LoginInformation *info);
void bootCheck(const LoginInformation &info); QCoro::Task<> bootCheck(const LoginInformation &info);
private: private:
Patcher *patcher = nullptr; Patcher *patcher = nullptr;

View file

@ -3,6 +3,8 @@
#pragma once #pragma once
#include <qcorotask.h>
#include "launchercore.h" #include "launchercore.h"
#include "patcher.h" #include "patcher.h"
@ -12,18 +14,19 @@ class SquareLauncher : public QObject
public: public:
explicit SquareLauncher(LauncherCore &window, QObject *parent = nullptr); explicit SquareLauncher(LauncherCore &window, QObject *parent = nullptr);
void getStored(const LoginInformation &info); using StoredInfo = std::pair<QString, QUrl>;
QCoro::Task<std::optional<StoredInfo>> getStored(const LoginInformation &info);
void login(const LoginInformation &info, const QUrl &referer); QCoro::Task<> login(const LoginInformation &info);
void registerSession(const LoginInformation &info); QCoro::Task<> registerSession(const LoginInformation &info);
private: private:
QString getBootHash(const LoginInformation &info); QString getBootHash(const LoginInformation &info);
Patcher *patcher = nullptr; Patcher *patcher = nullptr;
QString stored, SID, username; QString SID, username;
LoginAuth auth; LoginAuth auth;
LauncherCore &window; LauncherCore &window;

View file

@ -10,6 +10,7 @@
#include <QStandardPaths> #include <QStandardPaths>
#include <QUrlQuery> #include <QUrlQuery>
#include <physis.hpp> #include <physis.hpp>
#include <qcoronetworkreply.h>
#include "account.h" #include "account.h"
#include "squarelauncher.h" #include "squarelauncher.h"
@ -21,7 +22,7 @@ SquareBoot::SquareBoot(LauncherCore &window, SquareLauncher &launcher, QObject *
{ {
} }
void SquareBoot::bootCheck(const LoginInformation &info) QCoro::Task<> SquareBoot::bootCheck(const LoginInformation &info)
{ {
Q_EMIT window.stageChanged(i18n("Checking for launcher updates...")); Q_EMIT window.stageChanged(i18n("Checking for launcher updates..."));
qDebug() << "Performing boot check..."; qDebug() << "Performing boot check...";
@ -30,7 +31,7 @@ void SquareBoot::bootCheck(const LoginInformation &info)
connect(patcher, &Patcher::done, [this, &info] { connect(patcher, &Patcher::done, [this, &info] {
info.profile->readGameVersion(); info.profile->readGameVersion();
launcher.getStored(info); launcher.login(info);
}); });
QUrlQuery query; QUrlQuery query;
@ -52,14 +53,14 @@ void SquareBoot::bootCheck(const LoginInformation &info)
request.setRawHeader("Host", QStringLiteral("patch-bootver.%1").arg(window.squareEnixServer()).toUtf8()); request.setRawHeader("Host", QStringLiteral("patch-bootver.%1").arg(window.squareEnixServer()).toUtf8());
auto reply = window.mgr->get(request); auto reply = window.mgr->get(request);
connect(reply, &QNetworkReply::finished, [this, reply] { co_await reply;
const QString response = reply->readAll();
patcher->processPatchList(*window.mgr, response); const QString response = reply->readAll();
});
patcher->processPatchList(*window.mgr, response);
} }
void SquareBoot::checkGateStatus(LoginInformation *info) QCoro::Task<> SquareBoot::checkGateStatus(LoginInformation *info)
{ {
Q_EMIT window.stageChanged(i18n("Checking gate...")); Q_EMIT window.stageChanged(i18n("Checking gate..."));
qDebug() << "Checking gate..."; qDebug() << "Checking gate...";
@ -76,15 +77,15 @@ void SquareBoot::checkGateStatus(LoginInformation *info)
window.buildRequest(*info->profile, request); window.buildRequest(*info->profile, request);
auto reply = window.mgr->get(request); auto reply = window.mgr->get(request);
connect(reply, &QNetworkReply::finished, [this, reply, info] { co_await reply;
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
const bool isGateOpen = !document.isEmpty() && document.object()["status"].toInt() != 0; const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
if (isGateOpen) { const bool isGateOpen = !document.isEmpty() && document.object()["status"].toInt() != 0;
bootCheck(*info);
} else { if (isGateOpen) {
Q_EMIT window.loginError(i18n("The login gate is closed, the game may be under maintenance.\n\n%1", reply->errorString())); bootCheck(*info);
} } else {
}); Q_EMIT window.loginError(i18n("The login gate is closed, the game may be under maintenance.\n\n%1", reply->errorString()));
}
} }

View file

@ -9,6 +9,7 @@
#include <QNetworkReply> #include <QNetworkReply>
#include <QRegularExpressionMatch> #include <QRegularExpressionMatch>
#include <QUrlQuery> #include <QUrlQuery>
#include <qcoronetworkreply.h>
#include "account.h" #include "account.h"
#include "launchercore.h" #include "launchercore.h"
@ -31,7 +32,7 @@ QString getFileHash(const QString &file)
return QString("%1/%2").arg(QString::number(f.size()), hash.result().toHex()); return QString("%1/%2").arg(QString::number(f.size()), hash.result().toHex());
} }
void SquareLauncher::getStored(const LoginInformation &info) QCoro::Task<std::optional<SquareLauncher::StoredInfo>> SquareLauncher::getStored(const LoginInformation &info)
{ {
Q_EMIT window.stageChanged(i18n("Logging in...")); Q_EMIT window.stageChanged(i18n("Logging in..."));
@ -62,39 +63,44 @@ void SquareLauncher::getStored(const LoginInformation &info)
auto request = QNetworkRequest(url); auto request = QNetworkRequest(url);
window.buildRequest(*info.profile, request); window.buildRequest(*info.profile, request);
QNetworkReply *reply = window.mgr->get(request); auto reply = window.mgr->get(request);
co_await reply;
connect(reply, &QNetworkReply::finished, [this, &info, reply, url] { const auto str = QString(reply->readAll());
auto str = QString(reply->readAll());
// fetches Steam username // fetches Steam username
if (info.profile->account()->license() == Account::GameLicense::WindowsSteam) { if (info.profile->account()->license() == Account::GameLicense::WindowsSteam) {
QRegularExpression re(R"lit(<input name=""sqexid"" type=""hidden"" value=""(?<sqexid>.*)""\/>)lit"); QRegularExpression re(R"lit(<input name=""sqexid"" type=""hidden"" value=""(?<sqexid>.*)""\/>)lit");
QRegularExpressionMatch match = re.match(str);
if (match.hasMatch()) {
username = match.captured(1);
} else {
Q_EMIT window.loginError(i18n("Could not get Steam username, have you attached your account?"));
}
} else {
username = info.username;
}
QRegularExpression re(R"lit(\t<\s*input .* name="_STORED_" value="(?<stored>.*)">)lit");
QRegularExpressionMatch match = re.match(str); QRegularExpressionMatch match = re.match(str);
if (match.hasMatch()) { if (match.hasMatch()) {
stored = match.captured(1); username = match.captured(1);
login(info, url);
} else { } else {
Q_EMIT window.loginError( Q_EMIT window.loginError(i18n("Could not get Steam username, have you attached your account?"));
i18n("Square Enix servers refused to confirm session information. The game may be under maintenance, try the official launcher."));
} }
}); } else {
username = info.username;
}
QRegularExpression re(R"lit(\t<\s*input .* name="_STORED_" value="(?<stored>.*)">)lit");
QRegularExpressionMatch match = re.match(str);
if (match.hasMatch()) {
co_return StoredInfo{match.captured(1), url};
} else {
Q_EMIT window.loginError(
i18n("Square Enix servers refused to confirm session information. The game may be under maintenance, try the official launcher."));
co_return {};
}
} }
void SquareLauncher::login(const LoginInformation &info, const QUrl &referer) QCoro::Task<> SquareLauncher::login(const LoginInformation &info)
{ {
const auto storedResult = co_await getStored(info);
if (storedResult == std::nullopt) {
co_return;
}
const auto [stored, referer] = *storedResult;
QUrlQuery postData; QUrlQuery postData;
postData.addQueryItem("_STORED_", stored); postData.addQueryItem("_STORED_", stored);
postData.addQueryItem("sqexid", info.username); postData.addQueryItem("sqexid", info.username);
@ -113,47 +119,47 @@ void SquareLauncher::login(const LoginInformation &info, const QUrl &referer)
request.setRawHeader("Cache-Control", "no-cache"); request.setRawHeader("Cache-Control", "no-cache");
auto reply = window.mgr->post(request, postData.toString(QUrl::FullyEncoded).toUtf8()); auto reply = window.mgr->post(request, postData.toString(QUrl::FullyEncoded).toUtf8());
connect(reply, &QNetworkReply::finished, [this, &info, reply] { co_await reply;
auto str = QString(reply->readAll());
QRegularExpression re(R"lit(window.external.user\("login=auth,ok,(?<launchParams>.*)\);)lit"); auto str = QString(reply->readAll());
QRegularExpressionMatch match = re.match(str);
if (match.hasMatch()) {
const auto parts = match.captured(1).split(',');
const bool terms = parts[3] == "1"; QRegularExpression re(R"lit(window.external.user\("login=auth,ok,(?<launchParams>.*)\);)lit");
const bool playable = parts[9] == "1"; QRegularExpressionMatch match = re.match(str);
if (match.hasMatch()) {
const auto parts = match.captured(1).split(',');
if (!playable) { const bool terms = parts[3] == "1";
Q_EMIT window.loginError(i18n("Your account is unplayable. Check that you have the correct license, and a valid subscription.")); const bool playable = parts[9] == "1";
return;
}
if (!terms) { if (!playable) {
Q_EMIT window.loginError(i18n("Your account is unplayable. You need to accept the terms of service from the official launcher first.")); Q_EMIT window.loginError(i18n("Your account is unplayable. Check that you have the correct license, and a valid subscription."));
return; co_return;
}
SID = parts[1];
auth.region = parts[5].toInt();
auth.maxExpansion = parts[13].toInt();
registerSession(info);
} else {
QRegularExpression re(R"lit(window.external.user\("login=auth,ng,err,(?<launchParams>.*)\);)lit");
QRegularExpressionMatch match = re.match(str);
const auto parts = match.captured(1).split(',');
// there's a stray quote at the end of the error string, so let's remove that
QString errorStr = match.captured(1).chopped(1);
Q_EMIT window.loginError(errorStr);
} }
});
if (!terms) {
Q_EMIT window.loginError(i18n("Your account is unplayable. You need to accept the terms of service from the official launcher first."));
co_return;
}
SID = parts[1];
auth.region = parts[5].toInt();
auth.maxExpansion = parts[13].toInt();
registerSession(info);
} else {
QRegularExpression re(R"lit(window.external.user\("login=auth,ng,err,(?<launchParams>.*)\);)lit");
QRegularExpressionMatch match = re.match(str);
const auto parts = match.captured(1).split(',');
// there's a stray quote at the end of the error string, so let's remove that
QString errorStr = match.captured(1).chopped(1);
Q_EMIT window.loginError(errorStr);
}
} }
void SquareLauncher::registerSession(const LoginInformation &info) QCoro::Task<> SquareLauncher::registerSession(const LoginInformation &info)
{ {
QUrl url; QUrl url;
url.setScheme("https"); url.setScheme("https");
@ -177,35 +183,35 @@ void SquareLauncher::registerSession(const LoginInformation &info)
} }
auto reply = window.mgr->post(request, report.toUtf8()); auto reply = window.mgr->post(request, report.toUtf8());
connect(reply, &QNetworkReply::finished, [this, &info, reply] { co_await reply;
if (reply->error() == QNetworkReply::NoError) {
if (reply->rawHeaderList().contains("X-Patch-Unique-Id")) {
QString body = reply->readAll();
patcher = new Patcher(window, info.profile->gamePath() + "/game", info.profile->gameData, this); if (reply->error() == QNetworkReply::NoError) {
connect(patcher, &Patcher::done, [this, &info, reply] { if (reply->rawHeaderList().contains("X-Patch-Unique-Id")) {
info.profile->readGameVersion(); QString body = reply->readAll();
auth.SID = reply->rawHeader("X-Patch-Unique-Id"); patcher = new Patcher(window, info.profile->gamePath() + "/game", info.profile->gameData, this);
connect(patcher, &Patcher::done, [this, &info, reply] {
info.profile->readGameVersion();
window.launchGame(*info.profile, auth); auth.SID = reply->rawHeader("X-Patch-Unique-Id");
});
patcher->processPatchList(*window.mgr, body); window.launchGame(*info.profile, auth);
} else { });
Q_EMIT window.loginError(i18n("Fatal error, request was successful but X-Patch-Unique-Id was not recieved."));
} patcher->processPatchList(*window.mgr, body);
} else { } else {
if (reply->error() == QNetworkReply::SslHandshakeFailedError) { Q_EMIT window.loginError(i18n("Fatal error, request was successful but X-Patch-Unique-Id was not recieved."));
Q_EMIT window.loginError(
i18n("SSL handshake error detected. If you are using OpenSUSE or Fedora, try running `update-crypto-policies --set LEGACY`."));
} else if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 405) {
Q_EMIT window.loginError(i18n("The game failed the anti-tamper check. Restore the game to the original state and try updating again."));
} else {
Q_EMIT window.loginError(i18n("Unknown error when registering the session."));
}
} }
}); } else {
if (reply->error() == QNetworkReply::SslHandshakeFailedError) {
Q_EMIT window.loginError(
i18n("SSL handshake error detected. If you are using OpenSUSE or Fedora, try running `update-crypto-policies --set LEGACY`."));
} else if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 405) {
Q_EMIT window.loginError(i18n("The game failed the anti-tamper check. Restore the game to the original state and try updating again."));
} else {
Q_EMIT window.loginError(i18n("Unknown error when registering the session."));
}
}
} }
QString SquareLauncher::getBootHash(const LoginInformation &info) QString SquareLauncher::getBootHash(const LoginInformation &info)