1
Fork 0
mirror of https://github.com/redstrate/Astra.git synced 2025-04-20 11:47:46 +00:00
astra/launcher/src/launchercore.cpp

359 lines
11 KiB
C++
Raw Normal View History

// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "gameinstaller.h"
2023-09-17 08:31:24 -04:00
#include <KLocalizedString>
#include <QDir>
#include <QNetworkAccessManager>
#include <QStandardPaths>
#include <algorithm>
2023-09-16 18:37:42 -04:00
#include <qcoronetworkreply.h>
#include "account.h"
#include "assetupdater.h"
#include "astra_log.h"
#include "compatibilitytoolinstaller.h"
#include "gamerunner.h"
#include "launchercore.h"
#include "sapphirelogin.h"
#include "squareenixlogin.h"
#include "utility.h"
2023-10-11 14:49:24 -04:00
LauncherCore::LauncherCore()
: QObject()
{
2023-10-11 14:49:24 -04:00
m_settings = new LauncherSettings(this);
m_mgr = new QNetworkAccessManager(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);
m_runner = new GameRunner(*this, this);
m_profileManager->load();
m_accountManager->load();
// restore profile -> account connections
for (auto profile : m_profileManager->profiles()) {
if (auto account = m_accountManager->getByUuid(profile->accountUuid())) {
profile->setAccount(account);
}
}
// set default profile, if found
if (auto profile = m_profileManager->getProfileByUUID(m_settings->config()->currentProfile()); profile != nullptr) {
setCurrentProfile(profile);
}
m_loadingFinished = true;
Q_EMIT loadingFinished();
}
2023-10-11 14:49:24 -04:00
void LauncherCore::initializeSteam()
{
2023-10-11 14:49:24 -04:00
m_steamApi = new SteamAPI(this);
m_steamApi->setLauncherMode(true);
}
void LauncherCore::login(Profile *profile, const QString &username, const QString &password, const QString &oneTimePassword)
{
Q_ASSERT(profile != nullptr);
auto loginInformation = new LoginInformation(this);
loginInformation->profile = profile;
loginInformation->username = username;
loginInformation->password = password;
loginInformation->oneTimePassword = oneTimePassword;
if (profile->account()->rememberPassword()) {
profile->account()->setPassword(password);
}
beginLogin(*loginInformation);
}
2023-10-08 13:21:13 -04:00
bool LauncherCore::autoLogin(Profile *profile)
{
2023-10-08 17:52:44 -04:00
Q_ASSERT(profile != nullptr);
2023-10-06 18:09:50 -04:00
QString otp;
if (profile->account()->useOTP()) {
if (!profile->account()->rememberOTP()) {
Q_EMIT loginError(i18n("This account does not have an OTP secret set, but requires it for login."));
2023-10-08 13:21:13 -04:00
return false;
2023-10-06 18:09:50 -04:00
}
otp = profile->account()->getOTP();
if (otp.isEmpty()) {
Q_EMIT loginError(i18n("Failed to generate OTP, review the stored secret."));
2023-10-08 13:21:13 -04:00
return false;
2023-10-06 18:09:50 -04:00
}
}
login(profile, profile->account()->name(), profile->account()->getPassword(), otp);
2023-10-08 13:21:13 -04:00
return true;
}
GameInstaller *LauncherCore::createInstaller(Profile *profile)
{
Q_ASSERT(profile != nullptr);
2023-10-08 17:52:44 -04:00
return new GameInstaller(*this, *profile, this);
}
CompatibilityToolInstaller *LauncherCore::createCompatInstaller()
{
return new CompatibilityToolInstaller(*this, this);
}
2023-10-11 14:49:24 -04:00
void LauncherCore::clearAvatarCache()
{
2023-10-11 14:49:24 -04:00
const auto cacheLocation = QStandardPaths::standardLocations(QStandardPaths::CacheLocation)[0] + QStringLiteral("/avatars");
if (QDir(cacheLocation).exists()) {
QDir(cacheLocation).removeRecursively();
}
}
2023-10-11 14:49:24 -04:00
void LauncherCore::refreshNews()
{
2023-10-11 14:49:24 -04:00
fetchNews();
}
2023-10-11 14:49:24 -04:00
Profile *LauncherCore::currentProfile() const
{
2023-10-11 14:49:24 -04:00
return m_profileManager->getProfile(m_currentProfileIndex);
}
void LauncherCore::setCurrentProfile(Profile *profile)
{
Q_ASSERT(profile != nullptr);
const int newIndex = m_profileManager->getProfileIndex(profile->uuid());
if (newIndex != m_currentProfileIndex) {
m_currentProfileIndex = newIndex;
m_settings->config()->setCurrentProfile(profile->uuid());
m_settings->config()->save();
Q_EMIT currentProfileChanged();
}
}
2023-09-17 19:20:41 -04:00
[[nodiscard]] QString LauncherCore::autoLoginProfileName() const
{
return m_settings->config()->autoLoginProfile();
2023-09-17 19:20:41 -04:00
}
[[nodiscard]] Profile *LauncherCore::autoLoginProfile() const
{
if (m_settings->config()->autoLoginProfile().isEmpty()) {
2023-09-17 19:20:41 -04:00
return nullptr;
}
return m_profileManager->getProfileByUUID(m_settings->config()->autoLoginProfile());
2023-09-17 19:20:41 -04:00
}
void LauncherCore::setAutoLoginProfile(Profile *profile)
{
if (profile == nullptr) {
m_settings->config()->setAutoLoginProfile({});
m_settings->config()->save();
2023-09-17 19:20:41 -04:00
Q_EMIT autoLoginProfileChanged();
return;
}
auto uuid = profile->uuid();
if (uuid != m_settings->config()->autoLoginProfile()) {
m_settings->config()->setAutoLoginProfile(uuid);
m_settings->config()->save();
2023-09-17 19:20:41 -04:00
Q_EMIT autoLoginProfileChanged();
}
}
2023-10-11 14:49:24 -04:00
void LauncherCore::buildRequest(const Profile &settings, QNetworkRequest &request)
2023-09-16 18:37:42 -04:00
{
2023-10-11 14:49:24 -04:00
setSSL(request);
if (settings.account()->license() == Account::GameLicense::macOS) {
request.setHeader(QNetworkRequest::UserAgentHeader, QByteArrayLiteral("macSQEXAuthor/2.0.0(MacOSX; ja-jp)"));
} else {
request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("SQEXAuthor/2.0.0(Windows 6.2; ja-jp; %1)").arg(QString(QSysInfo::bootUniqueId())));
}
request.setRawHeader(QByteArrayLiteral("Accept"),
QByteArrayLiteral("image/gif, image/jpeg, image/pjpeg, application/x-ms-application, application/xaml+xml, "
"application/x-ms-xbap, */*"));
request.setRawHeader(QByteArrayLiteral("Accept-Encoding"), QByteArrayLiteral("gzip, deflate"));
request.setRawHeader(QByteArrayLiteral("Accept-Language"), QByteArrayLiteral("en-us"));
}
void LauncherCore::setSSL(QNetworkRequest &request)
{
QSslConfiguration config;
config.setProtocol(QSsl::AnyProtocol);
config.setPeerVerifyMode(QSslSocket::VerifyNone);
request.setSslConfiguration(config);
}
void LauncherCore::setupIgnoreSSL(QNetworkReply *reply)
{
Q_ASSERT(reply != nullptr);
if (m_settings->preferredProtocol() == QStringLiteral("http")) {
connect(reply, &QNetworkReply::sslErrors, this, [reply](const QList<QSslError> &errors) {
reply->ignoreSslErrors(errors);
});
}
}
bool LauncherCore::isLoadingFinished() const
{
return m_loadingFinished;
}
bool LauncherCore::isSteam() const
{
return m_steamApi != nullptr;
}
bool LauncherCore::isSteamDeck() const
{
if (m_steamApi != nullptr) {
return m_steamApi->isDeck();
} else {
return false;
}
}
bool LauncherCore::isPatching() const
{
return m_isPatching;
}
2023-10-11 14:49:24 -04:00
QNetworkAccessManager *LauncherCore::mgr()
{
return m_mgr;
}
LauncherSettings *LauncherCore::settings()
{
return m_settings;
}
ProfileManager *LauncherCore::profileManager()
{
return m_profileManager;
}
AccountManager *LauncherCore::accountManager()
{
return m_accountManager;
}
Headline *LauncherCore::headline() const
{
return m_headline;
}
QCoro::Task<> LauncherCore::beginLogin(LoginInformation &info)
{
info.profile->account()->updateConfig();
auto assetUpdater = new AssetUpdater(*info.profile, *this, this);
if (co_await assetUpdater->update()) {
std::optional<LoginAuth> auth;
if (info.profile->account()->isSapphire()) {
auth = co_await m_sapphireLogin->login(info.profile->account()->lobbyUrl(), info);
} else {
auth = co_await m_squareEnixLogin->login(&info);
}
if (auth != std::nullopt) {
Q_EMIT stageChanged(i18n("Launching game..."));
if (isSteam()) {
m_steamApi->setLauncherMode(false);
}
m_runner->beginGameExecutable(*info.profile, *auth);
}
}
assetUpdater->deleteLater();
2023-09-16 18:37:42 -04:00
}
QCoro::Task<> LauncherCore::fetchNews()
{
qInfo(ASTRA_LOG) << "Fetching news...";
QUrlQuery query;
2023-09-17 08:31:24 -04:00
query.addQueryItem(QStringLiteral("lang"), QStringLiteral("en-us"));
query.addQueryItem(QStringLiteral("media"), QStringLiteral("pcapp"));
QUrl url;
url.setScheme(m_settings->preferredProtocol());
url.setHost(QStringLiteral("frontier.%1").arg(m_settings->squareEnixServer()));
2023-09-17 08:31:24 -04:00
url.setPath(QStringLiteral("/news/headline.json"));
url.setQuery(query);
2023-09-17 08:31:24 -04:00
QNetworkRequest request(QStringLiteral("%1&%2").arg(url.toString(), QString::number(QDateTime::currentMSecsSinceEpoch())));
request.setRawHeader(QByteArrayLiteral("Accept"), QByteArrayLiteral("application/json, text/plain, */*"));
request.setRawHeader(QByteArrayLiteral("Origin"), QByteArrayLiteral("https://launcher.finalfantasyxiv.com"));
request.setRawHeader(QByteArrayLiteral("Referer"),
QStringLiteral("https://launcher.finalfantasyxiv.com/v600/index.html?rc_lang=%1&time=%2")
.arg("en-us", QDateTime::currentDateTimeUtc().toString("yyyy-MM-dd-HH"))
.toUtf8());
Utility::printRequest(QStringLiteral("GET"), request);
auto reply = mgr()->get(request);
2023-09-16 18:37:42 -04:00
co_await reply;
2023-09-16 18:37:42 -04:00
auto document = QJsonDocument::fromJson(reply->readAll());
2023-09-16 18:37:42 -04:00
auto headline = new Headline(this);
if (document.isEmpty()) {
headline->failedToLoad = true;
}
2023-09-16 18:37:42 -04:00
const auto parseNews = [](QJsonObject object) -> News {
News news;
2023-09-17 08:31:24 -04:00
news.date = QDateTime::fromString(object[QLatin1String("date")].toString(), Qt::DateFormat::ISODate);
news.id = object[QLatin1String("id")].toString();
news.tag = object[QLatin1String("tag")].toString();
news.title = object[QLatin1String("title")].toString();
2023-09-17 08:31:24 -04:00
if (object[QLatin1String("url")].toString().isEmpty()) {
news.url = QUrl(QStringLiteral("https://na.finalfantasyxiv.com/lodestone/news/detail/%1").arg(news.id));
2023-09-16 18:37:42 -04:00
} else {
2023-09-17 08:31:24 -04:00
news.url = QUrl(object[QLatin1String("url")].toString());
}
2023-09-16 18:37:42 -04:00
return news;
};
2023-09-17 08:31:24 -04:00
for (auto bannerObject : document.object()[QLatin1String("banner")].toArray()) {
2023-09-16 18:37:42 -04:00
auto banner = Banner();
2023-09-17 08:31:24 -04:00
banner.link = QUrl(bannerObject.toObject()[QLatin1String("link")].toString());
banner.bannerImage = QUrl(bannerObject.toObject()[QLatin1String("lsb_banner")].toString());
2023-09-16 18:37:42 -04:00
headline->banners.push_back(banner);
}
2023-09-17 08:31:24 -04:00
for (auto newsObject : document.object()[QLatin1String("news")].toArray()) {
2023-09-16 18:37:42 -04:00
auto news = parseNews(newsObject.toObject());
headline->news.push_back(news);
}
2023-09-17 08:31:24 -04:00
for (auto pinnedObject : document.object()[QLatin1String("pinned")].toArray()) {
2023-09-16 18:37:42 -04:00
auto pinned = parseNews(pinnedObject.toObject());
headline->pinned.push_back(pinned);
}
2023-09-17 08:31:24 -04:00
for (auto pinnedObject : document.object()[QLatin1String("topics")].toArray()) {
2023-09-16 18:37:42 -04:00
auto pinned = parseNews(pinnedObject.toObject());
headline->topics.push_back(pinned);
}
m_headline = headline;
Q_EMIT newsChanged();
}