mirror of
https://github.com/redstrate/Astra.git
synced 2025-04-24 05:17:46 +00:00
Overhaul all the login classes, make the code way clearer
The old pre-async login system (especially SE's) was this weird mess and it wasn't clear what data was flowing where, etc. Now it's a flat function with comments and a bit better logging.
This commit is contained in:
parent
0bec42f836
commit
f99964b2bb
13 changed files with 268 additions and 261 deletions
|
@ -55,9 +55,8 @@ target_sources(astra PRIVATE
|
|||
include/processlogger.h
|
||||
include/profile.h
|
||||
include/profilemanager.h
|
||||
include/sapphirelauncher.h
|
||||
include/squareboot.h
|
||||
include/squarelauncher.h
|
||||
include/sapphirelogin.h
|
||||
include/squareenixlogin.h
|
||||
include/steamapi.h
|
||||
include/utility.h
|
||||
|
||||
|
@ -75,9 +74,8 @@ target_sources(astra PRIVATE
|
|||
src/processlogger.cpp
|
||||
src/profile.cpp
|
||||
src/profilemanager.cpp
|
||||
src/sapphirelauncher.cpp
|
||||
src/squareboot.cpp
|
||||
src/squarelauncher.cpp
|
||||
src/sapphirelogin.cpp
|
||||
src/squareenixlogin.cpp
|
||||
src/steamapi.cpp
|
||||
src/utility.cpp
|
||||
)
|
||||
|
|
|
@ -14,11 +14,10 @@
|
|||
#include "launchersettings.h"
|
||||
#include "profile.h"
|
||||
#include "profilemanager.h"
|
||||
#include "squareboot.h"
|
||||
#include "steamapi.h"
|
||||
|
||||
class SapphireLauncher;
|
||||
class SquareLauncher;
|
||||
class SapphireLogin;
|
||||
class SquareEnixLogin;
|
||||
class AssetUpdater;
|
||||
class GameInstaller;
|
||||
class CompatibilityToolInstaller;
|
||||
|
@ -62,7 +61,6 @@ class LauncherCore : public QObject
|
|||
Q_PROPERTY(bool isSteam READ isSteam CONSTANT)
|
||||
Q_PROPERTY(bool isSteamDeck READ isSteamDeck CONSTANT)
|
||||
Q_PROPERTY(LauncherSettings *settings READ settings CONSTANT)
|
||||
Q_PROPERTY(SquareBoot *squareBoot MEMBER m_squareBoot)
|
||||
Q_PROPERTY(ProfileManager *profileManager READ profileManager CONSTANT)
|
||||
Q_PROPERTY(AccountManager *accountManager READ accountManager CONSTANT)
|
||||
Q_PROPERTY(Headline *headline READ headline NOTIFY newsChanged)
|
||||
|
@ -181,9 +179,8 @@ private:
|
|||
ProfileManager *m_profileManager = nullptr;
|
||||
AccountManager *m_accountManager = nullptr;
|
||||
|
||||
SapphireLauncher *m_sapphireLauncher = nullptr;
|
||||
SquareBoot *m_squareBoot = nullptr;
|
||||
SquareLauncher *m_squareLauncher = nullptr;
|
||||
SapphireLogin *m_sapphireLogin = nullptr;
|
||||
SquareEnixLogin *m_squareEnixLogin = nullptr;
|
||||
|
||||
QNetworkAccessManager *m_mgr = nullptr;
|
||||
Headline *m_headline = nullptr;
|
||||
|
|
|
@ -4,12 +4,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QtLogging>
|
||||
#include <QtQml/qqmlregistration.h>
|
||||
|
||||
#include "squareboot.h"
|
||||
#include <physis.hpp>
|
||||
|
||||
class Account;
|
||||
class ProfileConfig;
|
||||
class LauncherCore;
|
||||
|
||||
class Profile : public QObject
|
||||
{
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "launchercore.h"
|
||||
|
||||
class SapphireLauncher : QObject
|
||||
{
|
||||
public:
|
||||
explicit SapphireLauncher(LauncherCore &window, QObject *parent = nullptr);
|
||||
|
||||
void login(const QString &lobbyUrl, const LoginInformation &info);
|
||||
void registerAccount(const QString &lobbyUrl, const LoginInformation &info);
|
||||
|
||||
private:
|
||||
LauncherCore &m_launcher;
|
||||
};
|
23
launcher/include/sapphirelogin.h
Normal file
23
launcher/include/sapphirelogin.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "launchercore.h"
|
||||
|
||||
class SapphireLogin : QObject
|
||||
{
|
||||
public:
|
||||
explicit SapphireLogin(LauncherCore &window, QObject *parent = nullptr);
|
||||
|
||||
/// Begins the login process for Sapphire servers
|
||||
/// \param info The required login information
|
||||
QCoro::Task<std::optional<LoginAuth>> login(const QString &lobbyUrl, const LoginInformation &info);
|
||||
|
||||
void registerAccount(const QString &lobbyUrl, const LoginInformation &info);
|
||||
|
||||
private:
|
||||
LauncherCore &m_launcher;
|
||||
};
|
|
@ -1,30 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <qcorotask.h>
|
||||
|
||||
#include "patcher.h"
|
||||
|
||||
class SquareLauncher;
|
||||
class LauncherCore;
|
||||
class LoginInformation;
|
||||
|
||||
class SquareBoot : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SquareBoot(LauncherCore &window, SquareLauncher &launcher, QObject *parent = nullptr);
|
||||
|
||||
QCoro::Task<> checkGateStatus(const LoginInformation &info);
|
||||
|
||||
private:
|
||||
QCoro::Task<> bootCheck(const LoginInformation &info);
|
||||
|
||||
Patcher *m_patcher = nullptr;
|
||||
|
||||
LauncherCore &m_launcher;
|
||||
SquareLauncher &m_squareLauncher;
|
||||
};
|
57
launcher/include/squareenixlogin.h
Normal file
57
launcher/include/squareenixlogin.h
Normal file
|
@ -0,0 +1,57 @@
|
|||
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <qcorotask.h>
|
||||
|
||||
#include "launchercore.h"
|
||||
#include "patcher.h"
|
||||
|
||||
class SquareEnixLogin : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SquareEnixLogin(LauncherCore &window, QObject *parent = nullptr);
|
||||
|
||||
/// Begins the login process for official Square Enix servers
|
||||
/// \param info The required login information
|
||||
/// \return Arguments used for logging into the game, if successful
|
||||
QCoro::Task<std::optional<LoginAuth>> login(LoginInformation *info);
|
||||
|
||||
private:
|
||||
/// Checks the gate status to see if the servers are closed for maintenance
|
||||
/// \return False if the gate is closed, true if open.
|
||||
QCoro::Task<bool> checkGateStatus();
|
||||
|
||||
/// Check for updates to the boot components. Even though we don't use these, it's checked by later login steps.
|
||||
QCoro::Task<> checkBootUpdates();
|
||||
|
||||
using StoredInfo = std::pair<QString, QUrl>;
|
||||
|
||||
/// Get the _STORED_ value used in the oauth step
|
||||
QCoro::Task<std::optional<StoredInfo>> getStoredValue();
|
||||
|
||||
/// Logs into the server
|
||||
/// \return Returns false if the oauth call failed for some reason
|
||||
QCoro::Task<bool> loginOauth();
|
||||
|
||||
/// Registers a new session with the login server and patches the game if necessary
|
||||
/// \return Returns false if the session registration failed for some reason
|
||||
QCoro::Task<bool> registerSession();
|
||||
|
||||
/// Returns the hashes of the boot components
|
||||
QCoro::Task<QString> getBootHash();
|
||||
|
||||
/// Gets the SHA1 hash of a file
|
||||
static QString getFileHash(const QString &file);
|
||||
|
||||
Patcher *m_patcher = nullptr;
|
||||
|
||||
QString m_SID, m_username;
|
||||
LoginAuth m_auth;
|
||||
LoginInformation *m_info = nullptr;
|
||||
|
||||
LauncherCore &m_launcher;
|
||||
};
|
|
@ -1,34 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <qcorotask.h>
|
||||
|
||||
#include "launchercore.h"
|
||||
#include "patcher.h"
|
||||
|
||||
class SquareLauncher : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SquareLauncher(LauncherCore &window, QObject *parent = nullptr);
|
||||
|
||||
QCoro::Task<> login(const LoginInformation &info);
|
||||
|
||||
private:
|
||||
using StoredInfo = std::pair<QString, QUrl>;
|
||||
QCoro::Task<std::optional<StoredInfo>> getStored(const LoginInformation &info);
|
||||
|
||||
QCoro::Task<> registerSession(const LoginInformation &info);
|
||||
|
||||
static QCoro::Task<QString> getBootHash(const LoginInformation &info);
|
||||
|
||||
Patcher *m_patcher = nullptr;
|
||||
|
||||
QString m_SID, m_username;
|
||||
LoginAuth m_auth;
|
||||
|
||||
LauncherCore &m_launcher;
|
||||
};
|
|
@ -24,8 +24,8 @@
|
|||
#include "encryptedarg.h"
|
||||
#include "launchercore.h"
|
||||
#include "processlogger.h"
|
||||
#include "sapphirelauncher.h"
|
||||
#include "squarelauncher.h"
|
||||
#include "sapphirelogin.h"
|
||||
#include "squareenixlogin.h"
|
||||
#include "utility.h"
|
||||
|
||||
void LauncherCore::setSSL(QNetworkRequest &request)
|
||||
|
@ -80,10 +80,15 @@ QCoro::Task<> LauncherCore::beginLogin(LoginInformation &info)
|
|||
|
||||
auto assetUpdater = new AssetUpdater(*info.profile, *this, this);
|
||||
if (co_await assetUpdater->update()) {
|
||||
std::optional<LoginAuth> auth;
|
||||
if (info.profile->account()->isSapphire()) {
|
||||
m_sapphireLauncher->login(info.profile->account()->lobbyUrl(), info);
|
||||
auth = co_await m_sapphireLogin->login(info.profile->account()->lobbyUrl(), info);
|
||||
} else {
|
||||
m_squareBoot->checkGateStatus(info);
|
||||
auth = co_await m_squareEnixLogin->login(&info);
|
||||
}
|
||||
|
||||
if (auth != std::nullopt) {
|
||||
launchGame(*info.profile, *auth);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -414,9 +419,8 @@ LauncherCore::LauncherCore()
|
|||
{
|
||||
m_settings = new LauncherSettings(this);
|
||||
m_mgr = new QNetworkAccessManager(this);
|
||||
m_sapphireLauncher = new SapphireLauncher(*this, this);
|
||||
m_squareLauncher = new SquareLauncher(*this, this);
|
||||
m_squareBoot = new SquareBoot(*this, *m_squareLauncher, this);
|
||||
m_sapphireLogin = new SapphireLogin(*this, this);
|
||||
m_squareEnixLogin = new SquareEnixLogin(*this, this);
|
||||
m_profileManager = new ProfileManager(*this, this);
|
||||
m_accountManager = new AccountManager(*this, this);
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#include "launchercore.h"
|
||||
#include "logger.h"
|
||||
#include "physis_logger.h"
|
||||
#include "sapphirelauncher.h"
|
||||
#include "sapphirelogin.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "sapphirelauncher.h"
|
||||
#include "sapphirelogin.h"
|
||||
#include "utility.h"
|
||||
|
||||
#include <KLocalizedString>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <qcoronetwork.h>
|
||||
|
||||
SapphireLauncher::SapphireLauncher(LauncherCore &window, QObject *parent)
|
||||
SapphireLogin::SapphireLogin(LauncherCore &window, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_launcher(window)
|
||||
{
|
||||
}
|
||||
|
||||
void SapphireLauncher::login(const QString &lobbyUrl, const LoginInformation &info)
|
||||
QCoro::Task<std::optional<LoginAuth>> SapphireLogin::login(const QString &lobbyUrl, const LoginInformation &info)
|
||||
{
|
||||
const QJsonObject data{{QStringLiteral("username"), info.username}, {QStringLiteral("pass"), info.password}};
|
||||
|
||||
|
@ -26,29 +27,29 @@ void SapphireLauncher::login(const QString &lobbyUrl, const LoginInformation &in
|
|||
Utility::printRequest(QStringLiteral("POST"), request);
|
||||
|
||||
const auto reply = m_launcher.mgr()->post(request, QJsonDocument(data).toJson(QJsonDocument::JsonFormat::Compact));
|
||||
co_await reply;
|
||||
|
||||
connect(reply, &QNetworkReply::finished, [this, reply, &info] {
|
||||
if (reply->error() != QNetworkReply::NetworkError::NoError) {
|
||||
Q_EMIT m_launcher.loginError(i18n("Could not contact lobby server.\n\n%1", reply->errorString()));
|
||||
return;
|
||||
}
|
||||
if (reply->error() != QNetworkReply::NetworkError::NoError) {
|
||||
Q_EMIT m_launcher.loginError(i18n("Could not contact lobby server.\n\n%1", reply->errorString()));
|
||||
co_return std::nullopt;
|
||||
}
|
||||
|
||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||
if (!document.isEmpty()) {
|
||||
LoginAuth auth;
|
||||
auth.SID = document[QLatin1String("sId")].toString();
|
||||
auth.lobbyhost = document[QLatin1String("lobbyHost")].toString();
|
||||
auth.frontierHost = document[QLatin1String("frontierHost")].toString();
|
||||
auth.region = 3;
|
||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||
if (!document.isEmpty()) {
|
||||
LoginAuth auth;
|
||||
auth.SID = document[QLatin1String("sId")].toString();
|
||||
auth.lobbyhost = document[QLatin1String("lobbyHost")].toString();
|
||||
auth.frontierHost = document[QLatin1String("frontierHost")].toString();
|
||||
auth.region = 3;
|
||||
|
||||
m_launcher.launchGame(*info.profile, auth);
|
||||
} else {
|
||||
Q_EMIT m_launcher.loginError(i18n("Invalid username or password."));
|
||||
}
|
||||
});
|
||||
co_return auth;
|
||||
} else {
|
||||
Q_EMIT m_launcher.loginError(i18n("Invalid username or password."));
|
||||
co_return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
void SapphireLauncher::registerAccount(const QString &lobbyUrl, const LoginInformation &info)
|
||||
void SapphireLogin::registerAccount(const QString &lobbyUrl, const LoginInformation &info)
|
||||
{
|
||||
const QJsonObject data{{QStringLiteral("username"), info.username}, {QStringLiteral("pass"), info.password}};
|
||||
const QUrl url(lobbyUrl + QStringLiteral("/sapphire-api/lobby/createAccount"));
|
|
@ -1,96 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "squareboot.h"
|
||||
|
||||
#include <KLocalizedString>
|
||||
#include <QFile>
|
||||
#include <QJsonDocument>
|
||||
#include <QNetworkReply>
|
||||
#include <QStandardPaths>
|
||||
#include <QUrlQuery>
|
||||
#include <physis.hpp>
|
||||
#include <qcoronetworkreply.h>
|
||||
|
||||
#include "account.h"
|
||||
#include "squarelauncher.h"
|
||||
#include "utility.h"
|
||||
|
||||
SquareBoot::SquareBoot(LauncherCore &window, SquareLauncher &launcher, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_launcher(window)
|
||||
, m_squareLauncher(launcher)
|
||||
{
|
||||
}
|
||||
|
||||
QCoro::Task<> SquareBoot::bootCheck(const LoginInformation &info)
|
||||
{
|
||||
Q_EMIT m_launcher.stageChanged(i18n("Checking for launcher updates..."));
|
||||
qDebug() << "Performing boot check...";
|
||||
|
||||
const QUrlQuery query{{QStringLiteral("time"), QDateTime::currentDateTimeUtc().toString(QStringLiteral("yyyy-MM-dd-HH-mm"))}};
|
||||
|
||||
QUrl url;
|
||||
url.setScheme(QStringLiteral("http"));
|
||||
url.setHost(QStringLiteral("patch-bootver.%1").arg(m_launcher.settings()->squareEnixServer()));
|
||||
url.setPath(QStringLiteral("/http/win32/ffxivneo_release_boot/%1").arg(info.profile->bootVersion()));
|
||||
url.setQuery(query);
|
||||
|
||||
auto request = QNetworkRequest(url);
|
||||
if (info.profile->account()->license() == Account::GameLicense::macOS) {
|
||||
request.setRawHeader(QByteArrayLiteral("User-Agent"), QByteArrayLiteral("FFXIV-MAC PATCH CLIENT"));
|
||||
} else {
|
||||
request.setRawHeader(QByteArrayLiteral("User-Agent"), QByteArrayLiteral("FFXIV PATCH CLIENT"));
|
||||
}
|
||||
|
||||
request.setRawHeader(QByteArrayLiteral("Host"), QStringLiteral("patch-bootver.%1").arg(m_launcher.settings()->squareEnixServer()).toUtf8());
|
||||
Utility::printRequest(QStringLiteral("GET"), request);
|
||||
|
||||
const auto reply = m_launcher.mgr()->get(request);
|
||||
co_await reply;
|
||||
|
||||
const QString patchList = reply->readAll();
|
||||
if (!patchList.isEmpty()) {
|
||||
m_patcher = new Patcher(m_launcher, info.profile->gamePath() + QStringLiteral("/boot"), *info.profile->bootData(), this);
|
||||
const bool hasPatched = co_await m_patcher->patch(PatchList(patchList));
|
||||
if (hasPatched) {
|
||||
// update game version information
|
||||
info.profile->readGameVersion();
|
||||
}
|
||||
m_patcher->deleteLater();
|
||||
}
|
||||
|
||||
m_squareLauncher.login(info);
|
||||
}
|
||||
|
||||
QCoro::Task<> SquareBoot::checkGateStatus(const LoginInformation &info)
|
||||
{
|
||||
Q_EMIT m_launcher.stageChanged(i18n("Checking gate..."));
|
||||
qDebug() << "Checking gate...";
|
||||
|
||||
QUrl url;
|
||||
url.setScheme(m_launcher.settings()->preferredProtocol());
|
||||
url.setHost(QStringLiteral("frontier.%1").arg(m_launcher.settings()->squareEnixServer()));
|
||||
url.setPath(QStringLiteral("/worldStatus/gate_status.json"));
|
||||
url.setQuery(QString::number(QDateTime::currentMSecsSinceEpoch()));
|
||||
|
||||
QNetworkRequest request(url);
|
||||
|
||||
// TODO: really?
|
||||
m_launcher.buildRequest(*info.profile, request);
|
||||
|
||||
Utility::printRequest(QStringLiteral("GET"), request);
|
||||
|
||||
const auto reply = m_launcher.mgr()->get(request);
|
||||
m_launcher.setupIgnoreSSL(reply);
|
||||
co_await reply;
|
||||
|
||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||
const bool isGateOpen = !document.isEmpty() && document.object()[QLatin1String("status")].toInt() != 0;
|
||||
|
||||
if (isGateOpen) {
|
||||
bootCheck(info);
|
||||
} else {
|
||||
Q_EMIT m_launcher.loginError(i18n("The login gate is closed, the game may be under maintenance.\n\n%1", reply->errorString()));
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "squarelauncher.h"
|
||||
#include "squareenixlogin.h"
|
||||
|
||||
#include <KLocalizedString>
|
||||
#include <QDesktopServices>
|
||||
|
@ -14,29 +14,117 @@
|
|||
#include <qcoronetworkreply.h>
|
||||
|
||||
#include "account.h"
|
||||
#include "astra_log.h"
|
||||
#include "launchercore.h"
|
||||
#include "utility.h"
|
||||
|
||||
SquareLauncher::SquareLauncher(LauncherCore &window, QObject *parent)
|
||||
SquareEnixLogin::SquareEnixLogin(LauncherCore &window, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_launcher(window)
|
||||
{
|
||||
}
|
||||
|
||||
QString getFileHash(const QString &file)
|
||||
QCoro::Task<std::optional<LoginAuth>> SquareEnixLogin::login(LoginInformation *info)
|
||||
{
|
||||
auto f = QFile(file);
|
||||
if (!f.open(QIODevice::ReadOnly))
|
||||
return {};
|
||||
Q_ASSERT(info != nullptr);
|
||||
m_info = info;
|
||||
|
||||
QCryptographicHash hash(QCryptographicHash::Sha1);
|
||||
hash.addData(&f);
|
||||
if (!co_await checkGateStatus()) {
|
||||
co_return std::nullopt;
|
||||
}
|
||||
|
||||
return QStringLiteral("%1/%2").arg(QString::number(f.size()), hash.result().toHex());
|
||||
co_await checkBootUpdates();
|
||||
|
||||
if (!co_await loginOauth()) {
|
||||
co_return std::nullopt;
|
||||
}
|
||||
|
||||
if (!co_await registerSession()) {
|
||||
co_return std::nullopt;
|
||||
}
|
||||
|
||||
co_return m_auth;
|
||||
}
|
||||
|
||||
QCoro::Task<std::optional<SquareLauncher::StoredInfo>> SquareLauncher::getStored(const LoginInformation &info)
|
||||
QCoro::Task<bool> SquareEnixLogin::checkGateStatus()
|
||||
{
|
||||
Q_EMIT m_launcher.stageChanged(i18n("Checking gate..."));
|
||||
qInfo(ASTRA_LOG) << "Checking if the gate is open...";
|
||||
|
||||
QUrl url;
|
||||
url.setScheme(m_launcher.settings()->preferredProtocol());
|
||||
url.setHost(QStringLiteral("frontier.%1").arg(m_launcher.settings()->squareEnixServer()));
|
||||
url.setPath(QStringLiteral("/worldStatus/gate_status.json"));
|
||||
url.setQuery(QString::number(QDateTime::currentMSecsSinceEpoch()));
|
||||
|
||||
QNetworkRequest request(url);
|
||||
|
||||
// TODO: really?
|
||||
m_launcher.buildRequest(*m_info->profile, request);
|
||||
|
||||
Utility::printRequest(QStringLiteral("GET"), request);
|
||||
|
||||
const auto reply = m_launcher.mgr()->get(request);
|
||||
m_launcher.setupIgnoreSSL(reply);
|
||||
co_await reply;
|
||||
|
||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||
const bool isGateOpen = !document.isEmpty() && document.object()[QLatin1String("status")].toInt() != 0;
|
||||
|
||||
if (isGateOpen) {
|
||||
qInfo(ASTRA_LOG) << "Gate is open!";
|
||||
co_return true;
|
||||
} else {
|
||||
qInfo(ASTRA_LOG) << "Gate is closed!";
|
||||
Q_EMIT m_launcher.loginError(i18n("The login gate is closed, the game may be under maintenance.\n\n%1", reply->errorString()));
|
||||
co_return false;
|
||||
}
|
||||
}
|
||||
|
||||
QCoro::Task<> SquareEnixLogin::checkBootUpdates()
|
||||
{
|
||||
Q_EMIT m_launcher.stageChanged(i18n("Checking for launcher updates..."));
|
||||
qInfo(ASTRA_LOG) << "Checking for updates to boot components...";
|
||||
|
||||
const QUrlQuery query{{QStringLiteral("time"), QDateTime::currentDateTimeUtc().toString(QStringLiteral("yyyy-MM-dd-HH-mm"))}};
|
||||
|
||||
QUrl url;
|
||||
url.setScheme(QStringLiteral("http"));
|
||||
url.setHost(QStringLiteral("patch-bootver.%1").arg(m_launcher.settings()->squareEnixServer()));
|
||||
url.setPath(QStringLiteral("/http/win32/ffxivneo_release_boot/%1").arg(m_info->profile->bootVersion()));
|
||||
url.setQuery(query);
|
||||
|
||||
auto request = QNetworkRequest(url);
|
||||
if (m_info->profile->account()->license() == Account::GameLicense::macOS) {
|
||||
request.setRawHeader(QByteArrayLiteral("User-Agent"), QByteArrayLiteral("FFXIV-MAC PATCH CLIENT"));
|
||||
} else {
|
||||
request.setRawHeader(QByteArrayLiteral("User-Agent"), QByteArrayLiteral("FFXIV PATCH CLIENT"));
|
||||
}
|
||||
|
||||
request.setRawHeader(QByteArrayLiteral("Host"), QStringLiteral("patch-bootver.%1").arg(m_launcher.settings()->squareEnixServer()).toUtf8());
|
||||
Utility::printRequest(QStringLiteral("GET"), request);
|
||||
|
||||
const auto reply = m_launcher.mgr()->get(request);
|
||||
co_await reply;
|
||||
|
||||
const QString patchList = reply->readAll();
|
||||
if (!patchList.isEmpty()) {
|
||||
m_patcher = new Patcher(m_launcher, m_info->profile->gamePath() + QStringLiteral("/boot"), *m_info->profile->bootData(), this);
|
||||
const bool hasPatched = co_await m_patcher->patch(PatchList(patchList));
|
||||
if (hasPatched) {
|
||||
// update game version information
|
||||
m_info->profile->readGameVersion();
|
||||
}
|
||||
m_patcher->deleteLater();
|
||||
}
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
QCoro::Task<std::optional<SquareEnixLogin::StoredInfo>> SquareEnixLogin::getStoredValue()
|
||||
{
|
||||
qInfo(ASTRA_LOG) << "Getting the STORED value...";
|
||||
|
||||
Q_EMIT m_launcher.stageChanged(i18n("Logging in..."));
|
||||
|
||||
QUrlQuery query;
|
||||
|
@ -44,12 +132,12 @@ QCoro::Task<std::optional<SquareLauncher::StoredInfo>> SquareLauncher::getStored
|
|||
query.addQueryItem(QStringLiteral("lng"), QStringLiteral("en"));
|
||||
// for some reason, we always use region 3. the actual region is acquired later
|
||||
query.addQueryItem(QStringLiteral("rgn"), QStringLiteral("3"));
|
||||
query.addQueryItem(QStringLiteral("isft"), info.profile->account()->isFreeTrial() ? QStringLiteral("1") : QStringLiteral("0"));
|
||||
query.addQueryItem(QStringLiteral("isft"), m_info->profile->account()->isFreeTrial() ? QStringLiteral("1") : QStringLiteral("0"));
|
||||
query.addQueryItem(QStringLiteral("cssmode"), QStringLiteral("1"));
|
||||
query.addQueryItem(QStringLiteral("isnew"), QStringLiteral("1"));
|
||||
query.addQueryItem(QStringLiteral("launchver"), QStringLiteral("3"));
|
||||
|
||||
if (info.profile->account()->license() == Account::GameLicense::WindowsSteam) {
|
||||
if (m_info->profile->account()->license() == Account::GameLicense::WindowsSteam) {
|
||||
query.addQueryItem(QStringLiteral("issteam"), QStringLiteral("1"));
|
||||
|
||||
// TODO: get steam ticket information from steam api
|
||||
|
@ -64,7 +152,7 @@ QCoro::Task<std::optional<SquareLauncher::StoredInfo>> SquareLauncher::getStored
|
|||
url.setQuery(query);
|
||||
|
||||
auto request = QNetworkRequest(url);
|
||||
m_launcher.buildRequest(*info.profile, request);
|
||||
m_launcher.buildRequest(*m_info->profile, request);
|
||||
|
||||
Utility::printRequest(QStringLiteral("GET"), request);
|
||||
|
||||
|
@ -74,7 +162,7 @@ QCoro::Task<std::optional<SquareLauncher::StoredInfo>> SquareLauncher::getStored
|
|||
const QString str = reply->readAll();
|
||||
|
||||
// fetches Steam username
|
||||
if (info.profile->account()->license() == Account::GameLicense::WindowsSteam) {
|
||||
if (m_info->profile->account()->license() == Account::GameLicense::WindowsSteam) {
|
||||
const QRegularExpression re(QStringLiteral(R"lit(<input name=""sqexid"" type=""hidden"" value=""(?<sqexid>.*)""\/>)lit"));
|
||||
const QRegularExpressionMatch match = re.match(str);
|
||||
|
||||
|
@ -84,7 +172,7 @@ QCoro::Task<std::optional<SquareLauncher::StoredInfo>> SquareLauncher::getStored
|
|||
Q_EMIT m_launcher.loginError(i18n("Could not get Steam username, have you attached your account?"));
|
||||
}
|
||||
} else {
|
||||
m_username = info.username;
|
||||
m_username = m_info->username;
|
||||
}
|
||||
|
||||
const QRegularExpression re(QStringLiteral(R"lit(\t<\s*input .* name="_STORED_" value="(?<stored>.*)">)lit"));
|
||||
|
@ -98,22 +186,22 @@ QCoro::Task<std::optional<SquareLauncher::StoredInfo>> SquareLauncher::getStored
|
|||
}
|
||||
}
|
||||
|
||||
QCoro::Task<> SquareLauncher::login(const LoginInformation &info)
|
||||
QCoro::Task<bool> SquareEnixLogin::loginOauth()
|
||||
{
|
||||
const auto storedResult = co_await getStored(info);
|
||||
const auto storedResult = co_await getStoredValue();
|
||||
if (storedResult == std::nullopt) {
|
||||
co_return;
|
||||
co_return false;
|
||||
}
|
||||
|
||||
const auto [stored, referer] = *storedResult;
|
||||
|
||||
qInfo() << "Performing oauth...";
|
||||
qInfo(ASTRA_LOG) << "Logging in...";
|
||||
|
||||
QUrlQuery postData;
|
||||
postData.addQueryItem(QStringLiteral("_STORED_"), stored);
|
||||
postData.addQueryItem(QStringLiteral("sqexid"), info.username);
|
||||
postData.addQueryItem(QStringLiteral("password"), info.password);
|
||||
postData.addQueryItem(QStringLiteral("otppw"), info.oneTimePassword);
|
||||
postData.addQueryItem(QStringLiteral("sqexid"), m_info->username);
|
||||
postData.addQueryItem(QStringLiteral("password"), m_info->password);
|
||||
postData.addQueryItem(QStringLiteral("otppw"), m_info->oneTimePassword);
|
||||
|
||||
QUrl url;
|
||||
url.setScheme(QStringLiteral("https"));
|
||||
|
@ -121,7 +209,7 @@ QCoro::Task<> SquareLauncher::login(const LoginInformation &info)
|
|||
url.setPath(QStringLiteral("/oauth/ffxivarr/login/login.send"));
|
||||
|
||||
QNetworkRequest request(url);
|
||||
m_launcher.buildRequest(*info.profile, request);
|
||||
m_launcher.buildRequest(*m_info->profile, request);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, QByteArrayLiteral("application/x-www-form-urlencoded"));
|
||||
request.setRawHeader(QByteArrayLiteral("Referer"), referer.toEncoded());
|
||||
request.setRawHeader(QByteArrayLiteral("Cache-Control"), QByteArrayLiteral("no-cache"));
|
||||
|
@ -144,34 +232,38 @@ QCoro::Task<> SquareLauncher::login(const LoginInformation &info)
|
|||
|
||||
if (!playable) {
|
||||
Q_EMIT m_launcher.loginError(i18n("Your account is unplayable. Check that you have the correct license, and a valid subscription."));
|
||||
co_return;
|
||||
co_return false;
|
||||
}
|
||||
|
||||
if (!terms) {
|
||||
Q_EMIT m_launcher.loginError(i18n("Your account is unplayable. You need to accept the terms of service from the official launcher first."));
|
||||
co_return;
|
||||
co_return false;
|
||||
}
|
||||
|
||||
m_SID = parts[1];
|
||||
m_auth.region = parts[5].toInt();
|
||||
m_auth.maxExpansion = parts[13].toInt();
|
||||
|
||||
registerSession(info);
|
||||
co_return true;
|
||||
} else {
|
||||
const QRegularExpression re(QStringLiteral(R"lit(window.external.user\("login=auth,ng,err,(?<launchParams>.*)\);)lit"));
|
||||
const QRegularExpressionMatch match = re.match(str);
|
||||
|
||||
// there's a stray quote at the end of the error string, so let's remove that
|
||||
Q_EMIT m_launcher.loginError(match.captured(1).chopped(1));
|
||||
|
||||
co_return false;
|
||||
}
|
||||
}
|
||||
|
||||
QCoro::Task<> SquareLauncher::registerSession(const LoginInformation &info)
|
||||
QCoro::Task<bool> SquareEnixLogin::registerSession()
|
||||
{
|
||||
qInfo(ASTRA_LOG) << "Registering the session...";
|
||||
|
||||
QUrl url;
|
||||
url.setScheme(QStringLiteral("https"));
|
||||
url.setHost(QStringLiteral("patch-gamever.%1").arg(m_launcher.settings()->squareEnixServer()));
|
||||
url.setPath(QStringLiteral("/http/win32/ffxivneo_release_game/%1/%2").arg(info.profile->baseGameVersion(), m_SID));
|
||||
url.setPath(QStringLiteral("/http/win32/ffxivneo_release_game/%1/%2").arg(m_info->profile->baseGameVersion(), m_SID));
|
||||
|
||||
auto request = QNetworkRequest(url);
|
||||
m_launcher.setSSL(request);
|
||||
|
@ -179,10 +271,10 @@ QCoro::Task<> SquareLauncher::registerSession(const LoginInformation &info)
|
|||
request.setRawHeader(QByteArrayLiteral("User-Agent"), QByteArrayLiteral("FFXIV PATCH CLIENT"));
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, QByteArrayLiteral("application/x-www-form-urlencoded"));
|
||||
|
||||
QString report = QStringLiteral("%1=%2").arg(info.profile->bootVersion(), co_await getBootHash(info));
|
||||
QString report = QStringLiteral("%1=%2").arg(m_info->profile->bootVersion(), co_await getBootHash());
|
||||
for (int i = 0; i < m_auth.maxExpansion; i++) {
|
||||
if (i < static_cast<int>(info.profile->numInstalledExpansions())) {
|
||||
report += QStringLiteral("\nex%1\t%2").arg(QString::number(i + 1), info.profile->expansionVersion(i));
|
||||
if (i < static_cast<int>(m_info->profile->numInstalledExpansions())) {
|
||||
report += QStringLiteral("\nex%1\t%2").arg(QString::number(i + 1), m_info->profile->expansionVersion(i));
|
||||
} else {
|
||||
report += QStringLiteral("\nex%1\t2012.01.01.0000.0000").arg(QString::number(i + 1));
|
||||
}
|
||||
|
@ -205,18 +297,18 @@ QCoro::Task<> SquareLauncher::registerSession(const LoginInformation &info)
|
|||
const QString body = reply->readAll();
|
||||
|
||||
if (!body.isEmpty()) {
|
||||
m_patcher = new Patcher(m_launcher, info.profile->gamePath() + QStringLiteral("/game"), *info.profile->gameData(), this);
|
||||
m_patcher = new Patcher(m_launcher, m_info->profile->gamePath() + QStringLiteral("/game"), *m_info->profile->gameData(), this);
|
||||
const bool hasPatched = co_await m_patcher->patch(PatchList(body));
|
||||
if (hasPatched) {
|
||||
// re-read game version if it has updated
|
||||
info.profile->readGameVersion();
|
||||
m_info->profile->readGameVersion();
|
||||
}
|
||||
m_patcher->deleteLater();
|
||||
}
|
||||
|
||||
m_auth.SID = patchUniqueId;
|
||||
|
||||
m_launcher.launchGame(*info.profile, m_auth);
|
||||
co_return true;
|
||||
} else {
|
||||
Q_EMIT m_launcher.loginError(i18n("Fatal error, request was successful but X-Patch-Unique-Id was not recieved."));
|
||||
}
|
||||
|
@ -232,9 +324,11 @@ QCoro::Task<> SquareLauncher::registerSession(const LoginInformation &info)
|
|||
Q_EMIT m_launcher.loginError(i18n("Unknown error when registering the session."));
|
||||
}
|
||||
}
|
||||
|
||||
co_return false;
|
||||
}
|
||||
|
||||
QCoro::Task<QString> SquareLauncher::getBootHash(const LoginInformation &info)
|
||||
QCoro::Task<QString> SquareEnixLogin::getBootHash()
|
||||
{
|
||||
const QList<QString> fileList = {QStringLiteral("ffxivboot.exe"),
|
||||
QStringLiteral("ffxivboot64.exe"),
|
||||
|
@ -243,8 +337,8 @@ QCoro::Task<QString> SquareLauncher::getBootHash(const LoginInformation &info)
|
|||
QStringLiteral("ffxivupdater.exe"),
|
||||
QStringLiteral("ffxivupdater64.exe")};
|
||||
|
||||
const auto hashFuture = QtConcurrent::mapped(fileList, [&info](const auto &filename) -> QString {
|
||||
return getFileHash(info.profile->gamePath() + QStringLiteral("/boot/") + filename);
|
||||
const auto hashFuture = QtConcurrent::mapped(fileList, [this](const auto &filename) -> QString {
|
||||
return getFileHash(m_info->profile->gamePath() + QStringLiteral("/boot/") + filename);
|
||||
});
|
||||
|
||||
co_await hashFuture;
|
||||
|
@ -260,3 +354,15 @@ QCoro::Task<QString> SquareLauncher::getBootHash(const LoginInformation &info)
|
|||
|
||||
co_return result;
|
||||
}
|
||||
|
||||
QString SquareEnixLogin::getFileHash(const QString &file)
|
||||
{
|
||||
auto f = QFile(file);
|
||||
if (!f.open(QIODevice::ReadOnly))
|
||||
return {};
|
||||
|
||||
QCryptographicHash hash(QCryptographicHash::Sha1);
|
||||
hash.addData(&f);
|
||||
|
||||
return QStringLiteral("%1/%2").arg(QString::number(f.size()), hash.result().toHex());
|
||||
}
|
Loading…
Add table
Reference in a new issue