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

Parallelize and speed up the login process even more

Now the asset updating process is parallelized (especially asset file
downloading). There's less wasteful usage of the patcher and game
version reading when no patches need to be installed.
This commit is contained in:
Joshua Goins 2023-09-17 18:43:58 -04:00
parent 05bf6fff4f
commit 90a5ffc6c8
10 changed files with 287 additions and 352 deletions

View file

@ -6,6 +6,7 @@
#include <QJsonArray>
#include <QObject>
#include <QTemporaryDir>
#include <qcorotask.h>
#include "launchercore.h"
@ -19,17 +20,16 @@ class AssetUpdater : public QObject
public:
explicit AssetUpdater(Profile &profile, LauncherCore &launcher, QObject *parent = nullptr);
void update();
void beginInstall();
void checkIfCheckingIsDone();
void checkIfDalamudAssetsDone();
void checkIfFinished();
Q_SIGNALS:
void finishedUpdating();
QCoro::Task<> update();
private:
QCoro::Task<> checkRemoteDalamudAssetVersion();
QCoro::Task<> checkRemoteDalamudVersion();
QCoro::Task<> installDalamudAssets();
QCoro::Task<> installDalamud();
QCoro::Task<> installRuntime();
[[nodiscard]] QUrl dalamudVersionManifestUrl(Profile::DalamudChannel channel) const;
[[nodiscard]] QUrl dalamudLatestPackageUrl(Profile::DalamudChannel channel) const;
[[nodiscard]] QUrl dalamudAssetManifestUrl() const;
@ -48,14 +48,7 @@ private:
QDir dalamudAssetDir;
QDir dalamudRuntimeDir;
bool doneDownloadingDalamud = false;
bool doneDownloadingRuntimeCore = false;
bool doneDownloadingRuntimeDesktop = false;
bool needsRuntimeInstall = false;
bool needsDalamudInstall = false;
int remoteDalamudAssetVersion = -1;
QList<QString> dalamudAssetNeededFilenames;
QJsonArray remoteDalamudAssetArray;
Profile &m_profile;

View file

@ -179,6 +179,8 @@ signals:
void currentProfileChanged();
private:
QCoro::Task<> beginLogin(LoginInformation &info);
/*
* Begins the game executable, but calls to Dalamud if needed.
*/

View file

@ -21,7 +21,7 @@ public:
Patcher(LauncherCore &launcher, const QString &baseDirectory, GameData &gameData, QObject *parent = nullptr);
Patcher(LauncherCore &launcher, const QString &baseDirectory, BootData &bootData, QObject *parent = nullptr);
QCoro::Task<> patch(const QString &patchList);
QCoro::Task<bool> patch(const QString &patchList);
private:
void setupDirectories();

View file

@ -18,11 +18,11 @@ class SquareBoot : public QObject
public:
SquareBoot(LauncherCore &window, SquareLauncher &launcher, QObject *parent = nullptr);
QCoro::Task<> checkGateStatus(LoginInformation *info);
QCoro::Task<> bootCheck(const LoginInformation &info);
QCoro::Task<> checkGateStatus(const LoginInformation &info);
private:
QCoro::Task<> bootCheck(const LoginInformation &info);
Patcher *patcher = nullptr;
LauncherCore &window;

View file

@ -23,7 +23,7 @@ public:
QCoro::Task<> registerSession(const LoginInformation &info);
private:
QString getBootHash(const LoginInformation &info);
static QCoro::Task<QString> getBootHash(const LoginInformation &info);
Patcher *patcher = nullptr;

View file

@ -3,15 +3,19 @@
#include "assetupdater.h"
#include <KLocalizedString>
#include <QFile>
#include <QJsonDocument>
#include <QNetworkReply>
#include <QStandardPaths>
#include <qcorofuture.h>
#include <qcoronetworkreply.h>
#include <JlCompress.h>
#include <QtConcurrentRun>
const QString dotnetRuntimePackageURL = "https://dotnetcli.azureedge.net/dotnet/Runtime/%1/dotnet-runtime-%1-win-x64.zip";
const QString dotnetDesktopPackageURL = "https://dotnetcli.azureedge.net/dotnet/WindowsDesktop/%1/windowsdesktop-runtime-%1-win-x64.zip";
const QString dotnetRuntimePackageURL = QStringLiteral("https://dotnetcli.azureedge.net/dotnet/Runtime/%1/dotnet-runtime-%1-win-x64.zip");
const QString dotnetDesktopPackageURL = QStringLiteral("https://dotnetcli.azureedge.net/dotnet/WindowsDesktop/%1/windowsdesktop-runtime-%1-win-x64.zip");
AssetUpdater::AssetUpdater(Profile &profile, LauncherCore &launcher, QObject *parent)
: QObject(parent)
@ -20,85 +24,75 @@ AssetUpdater::AssetUpdater(Profile &profile, LauncherCore &launcher, QObject *pa
, m_profile(profile)
{
launcher.mgr->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
}
QCoro::Task<> AssetUpdater::update()
{
if (!m_profile.dalamudEnabled()) {
co_return;
}
qInfo() << "Starting asset update sequence...";
dataDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
dalamudDir = dataDir.absoluteFilePath("dalamud");
dalamudAssetDir = dalamudDir.absoluteFilePath("assets");
dalamudRuntimeDir = dalamudDir.absoluteFilePath("runtime");
dalamudDir = dataDir.absoluteFilePath(QStringLiteral("dalamud"));
dalamudAssetDir = dalamudDir.absoluteFilePath(QStringLiteral("assets"));
dalamudRuntimeDir = dalamudDir.absoluteFilePath(QStringLiteral("runtime"));
const auto createIfNeeded = [](const QDir &dir) {
if (!QDir().exists(dir.absolutePath()))
QDir().mkdir(dir.absolutePath());
QDir().mkpath(dir.absolutePath());
};
createIfNeeded(dataDir);
createIfNeeded(dalamudDir);
createIfNeeded(dalamudAssetDir);
createIfNeeded(dalamudRuntimeDir);
co_await checkRemoteDalamudAssetVersion();
co_await checkRemoteDalamudVersion();
}
void AssetUpdater::update()
QCoro::Task<> AssetUpdater::checkRemoteDalamudAssetVersion()
{
// non-dalamud users can bypass this process since it's not needed
if (!m_profile.dalamudEnabled()) {
Q_EMIT finishedUpdating();
return;
}
Q_EMIT launcher.stageChanged("Updating assets...");
// first, we want to collect all of the remote versions
qInfo() << "Starting update sequence...";
// dialog->setLabelText("Checking for updates...");
// dalamud assets
{
// we want to prevent logging in before we actually check the version
dalamudAssetNeededFilenames.clear();
remoteDalamudAssetVersion = -1;
dalamudAssetNeededFilenames.append("dummy");
// first we want to fetch the list of assets required
QNetworkRequest request(dalamudAssetManifestUrl());
const QNetworkRequest request(dalamudAssetManifestUrl());
auto reply = launcher.mgr->get(request);
connect(reply, &QNetworkReply::finished, [reply, this] {
Q_EMIT launcher.stageChanged("Checking for Dalamud asset updates...");
const auto reply = launcher.mgr->get(request);
co_await reply;
// TODO: handle asset failure
QJsonDocument doc = QJsonDocument::fromJson(reply->readAll());
const QJsonDocument doc = QJsonDocument::fromJson(reply->readAll());
qInfo() << "Dalamud asset remote version" << doc.object()["Version"].toInt();
remoteDalamudAssetVersion = doc.object()[QLatin1String("Version")].toInt();
remoteDalamudAssetArray = doc.object()[QLatin1String("Assets")].toArray();
qInfo() << "Dalamud asset remote version" << remoteDalamudAssetVersion;
qInfo() << "Dalamud asset local version" << m_profile.dalamudAssetVersion();
remoteDalamudAssetVersion = doc.object()["Version"].toInt();
// dalamud assets
if (remoteDalamudAssetVersion != m_profile.dalamudAssetVersion()) {
qInfo() << "Dalamud assets out of date.";
remoteDalamudAssetArray = doc.object()["Assets"].toArray();
checkIfCheckingIsDone();
});
co_await installDalamudAssets();
}
}
// dalamud injector / net runtime
// they're all updated in unison, so there's no reason to have multiple checks
QCoro::Task<> AssetUpdater::checkRemoteDalamudVersion()
{
QNetworkRequest request(dalamudVersionManifestUrl(m_profile.dalamudChannel()));
const QNetworkRequest request(dalamudVersionManifestUrl(m_profile.dalamudChannel()));
remoteDalamudVersion.clear();
remoteRuntimeVersion.clear();
auto reply = launcher.mgr->get(request);
connect(reply, &QNetworkReply::finished, [this, reply] {
Q_EMIT launcher.stageChanged("Checking for Dalamud updates...");
const auto reply = launcher.mgr->get(request);
co_await reply;
if (reply->error() != QNetworkReply::NetworkError::NoError) {
Q_EMIT launcher.loginError(QStringLiteral("Could not check for Dalamud updates.\n\n%1").arg(reply->errorString()));
return;
Q_EMIT launcher.loginError(i18n("Could not check for Dalamud updates.\n\n%1", reply->errorString()));
co_return;
}
QByteArray str = reply->readAll();
const QByteArray str = reply->readAll();
// for some god forsaken reason, the version string comes back as raw
// bytes, ex: \xFF\xFE{\x00\"\x00""A\x00s\x00s\x00""e\x00m\x00 so we
@ -110,226 +104,150 @@ void AssetUpdater::update()
reassmbled += t;
}
QJsonDocument doc = QJsonDocument::fromJson(reassmbled.toUtf8());
remoteDalamudVersion = doc["AssemblyVersion"].toString();
remoteRuntimeVersion = doc["RuntimeVersion"].toString();
const QJsonDocument doc = QJsonDocument::fromJson(reassmbled.toUtf8());
remoteDalamudVersion = doc[QLatin1String("AssemblyVersion")].toString();
remoteRuntimeVersion = doc[QLatin1String("RuntimeVersion")].toString();
qInfo() << "Latest Dalamud version reported: " << remoteDalamudVersion;
qInfo() << "Latest Dalamud version reported:" << remoteDalamudVersion << "local:" << m_profile.dalamudVersion();
qInfo() << "Latest NET runtime reported:" << remoteRuntimeVersion;
checkIfCheckingIsDone();
});
}
}
void AssetUpdater::beginInstall()
{
if (needsDalamudInstall) {
bool success = !JlCompress::extractDir(tempDir.path() + "/latest.zip", dalamudDir.absoluteFilePath(m_profile.dalamudChannelName())).empty();
if (!success) {
// TODO: handle failure here
qInfo() << "Failed to install Dalamud!";
} else {
needsDalamudInstall = false;
}
}
if (needsRuntimeInstall) {
bool success = !JlCompress::extractDir(tempDir.path() + "/dotnet-core.zip", dalamudRuntimeDir.absolutePath()).empty();
success |= !JlCompress::extractDir(tempDir.path() + "/dotnet-desktop.zip", dalamudRuntimeDir.absolutePath()).empty();
if (!success) {
qInfo() << "Failed to install dotnet!";
} else {
QFile file(dalamudRuntimeDir.absoluteFilePath("runtime.ver"));
file.open(QIODevice::WriteOnly | QIODevice::Text);
file.write(remoteRuntimeVersion.toUtf8());
file.close();
needsRuntimeInstall = false;
}
}
checkIfFinished();
}
void AssetUpdater::checkIfDalamudAssetsDone()
{
if (dalamudAssetNeededFilenames.empty()) {
qInfo() << "Finished downloading Dalamud assets.";
m_profile.setDalamudAssetVersion(remoteDalamudAssetVersion);
QFile file(dalamudAssetDir.absoluteFilePath("asset.ver"));
file.open(QIODevice::WriteOnly | QIODevice::Text);
file.write(QString::number(remoteDalamudAssetVersion).toUtf8());
file.close();
checkIfFinished();
}
}
void AssetUpdater::checkIfFinished()
{
if (doneDownloadingDalamud && doneDownloadingRuntimeCore && doneDownloadingRuntimeDesktop && dalamudAssetNeededFilenames.empty()) {
if (needsRuntimeInstall || needsDalamudInstall) {
beginInstall();
} else {
Q_EMIT finishedUpdating();
}
}
}
void AssetUpdater::checkIfCheckingIsDone()
{
if (remoteDalamudVersion.isEmpty() || remoteRuntimeVersion.isEmpty() || remoteDalamudAssetVersion == -1) {
return;
}
// now that we got all the information we need, let's check if anything is
// updateable
Q_EMIT launcher.stageChanged("Starting Dalamud update...");
// dalamud injector / net runtime
if (m_profile.runtimeVersion() != remoteRuntimeVersion) {
needsRuntimeInstall = true;
// core
{
QNetworkRequest request(dotnetRuntimePackageURL.arg(remoteRuntimeVersion));
auto reply = launcher.mgr->get(request);
connect(reply, &QNetworkReply::finished, [this, reply] {
qInfo() << "Dotnet-core finished downloading!";
Q_EMIT launcher.stageChanged("Updating Dotnet-core...");
QFile file(tempDir.path() + "/dotnet-core.zip");
file.open(QIODevice::WriteOnly);
file.write(reply->readAll());
file.close();
doneDownloadingRuntimeCore = true;
checkIfFinished();
});
}
// desktop
{
QNetworkRequest request(dotnetDesktopPackageURL.arg(remoteRuntimeVersion));
auto reply = launcher.mgr->get(request);
connect(reply, &QNetworkReply::finished, [this, reply] {
qInfo() << "Dotnet-desktop finished downloading!";
Q_EMIT launcher.stageChanged("Updating Dotnet-desktop...");
QFile file(tempDir.path() + "/dotnet-desktop.zip");
file.open(QIODevice::WriteOnly);
file.write(reply->readAll());
file.close();
doneDownloadingRuntimeDesktop = true;
checkIfFinished();
});
}
} else {
doneDownloadingRuntimeCore = true;
doneDownloadingRuntimeDesktop = true;
needsRuntimeInstall = false;
checkIfFinished();
}
if (remoteDalamudVersion != m_profile.dalamudVersion()) {
qInfo() << "Downloading Dalamud...";
needsDalamudInstall = true;
QNetworkRequest request(dalamudLatestPackageUrl(chosenChannel));
auto reply = launcher.mgr->get(request);
connect(reply, &QNetworkReply::finished, [this, reply] {
qInfo() << "Dalamud finished downloading!";
Q_EMIT launcher.stageChanged("Updating Dalamud...");
QFile file(tempDir.path() + "/latest.zip");
file.open(QIODevice::WriteOnly);
file.write(reply->readAll());
file.close();
doneDownloadingDalamud = true;
m_profile.setDalamudVersion(remoteDalamudVersion);
checkIfFinished();
});
} else {
qInfo() << "No need to update Dalamud.";
doneDownloadingDalamud = true;
needsDalamudInstall = false;
checkIfFinished();
co_await installDalamud();
}
// dalamud assets
if (remoteDalamudAssetVersion != m_profile.dalamudAssetVersion()) {
qInfo() << "Dalamud assets out of date.";
if (m_profile.runtimeVersion() != remoteRuntimeVersion) {
qInfo() << "Downloading Runtime...";
Q_EMIT launcher.stageChanged("Updating Dalamud assets...");
co_await installRuntime();
}
}
dalamudAssetNeededFilenames.clear();
for (auto assetObject : remoteDalamudAssetArray) {
QCoro::Task<> AssetUpdater::installDalamudAssets()
{
dalamudAssetNeededFilenames.append(assetObject.toObject()["FileName"].toString());
Q_EMIT launcher.stageChanged(i18n("Updating Dalamud assets..."));
QNetworkRequest assetRequest(assetObject.toObject()["Url"].toString());
auto assetReply = launcher.mgr->get(assetRequest);
QFutureSynchronizer<void> synchronizer;
connect(assetReply, &QNetworkReply::finished, [this, assetReply, assetObject = assetObject.toObject()] {
const QString fileName = assetObject["FileName"].toString();
const QString dirPath = fileName.left(fileName.lastIndexOf("/"));
for (const auto &assetObject : remoteDalamudAssetArray) {
const QNetworkRequest assetRequest(assetObject.toObject()[QLatin1String("Url")].toString());
const auto assetReply = launcher.mgr->get(assetRequest);
const auto future = QtFuture::connect(assetReply, &QNetworkReply::finished).then([this, assetReply, assetObject] {
const QString fileName = assetObject.toObject()[QLatin1String("FileName")].toString();
const QString dirPath = fileName.left(fileName.lastIndexOf(QLatin1Char('/')));
const QString path = dalamudAssetDir.absoluteFilePath(dirPath);
if (!QDir().exists(path))
QDir().mkpath(path);
QFile file(dalamudAssetDir.absoluteFilePath(assetObject["FileName"].toString()));
QFile file(dalamudAssetDir.absoluteFilePath(assetObject.toObject()[QLatin1String("FileName")].toString()));
file.open(QIODevice::WriteOnly);
file.write(assetReply->readAll());
file.close();
dalamudAssetNeededFilenames.removeOne(assetObject["FileName"].toString());
checkIfDalamudAssetsDone();
});
synchronizer.addFuture(future);
}
co_await QtConcurrent::run([&synchronizer] {
synchronizer.waitForFinished();
});
qInfo() << "Finished downloading Dalamud assets.";
m_profile.setDalamudAssetVersion(remoteDalamudAssetVersion);
QFile file(dalamudAssetDir.absoluteFilePath(QStringLiteral("asset.ver")));
file.open(QIODevice::WriteOnly | QIODevice::Text);
file.write(QString::number(remoteDalamudAssetVersion).toUtf8());
file.close();
}
QCoro::Task<> AssetUpdater::installDalamud()
{
Q_EMIT launcher.stageChanged(i18n("Updating Dalamud..."));
const QNetworkRequest request(dalamudLatestPackageUrl(chosenChannel));
const auto reply = launcher.mgr->get(request);
co_await reply;
qInfo() << "Dalamud finished downloading!";
QFile file(tempDir.path() + QStringLiteral("/latest.zip"));
file.open(QIODevice::WriteOnly);
file.write(reply->readAll());
file.close();
const bool success =
!JlCompress::extractDir(tempDir.path() + QLatin1String("/latest.zip"), dalamudDir.absoluteFilePath(m_profile.dalamudChannelName())).empty();
if (!success) {
// TODO: handle failure here
qInfo() << "Failed to install Dalamud!";
}
m_profile.setDalamudVersion(remoteDalamudVersion);
}
QCoro::Task<> AssetUpdater::installRuntime()
{
Q_EMIT launcher.stageChanged(i18n("Updating .NET Runtime..."));
// core
{
const QNetworkRequest request(dotnetRuntimePackageURL.arg(remoteRuntimeVersion));
const auto reply = launcher.mgr->get(request);
co_await reply;
qInfo() << "Dotnet-core finished downloading!";
QFile file(tempDir.path() + QStringLiteral("/dotnet-core.zip"));
file.open(QIODevice::WriteOnly);
file.write(reply->readAll());
file.close();
}
// desktop
{
const QNetworkRequest request(dotnetDesktopPackageURL.arg(remoteRuntimeVersion));
const auto reply = launcher.mgr->get(request);
co_await reply;
qInfo() << "Dotnet-desktop finished downloading!";
QFile file(tempDir.path() + QStringLiteral("/dotnet-desktop.zip"));
file.open(QIODevice::WriteOnly);
file.write(reply->readAll());
file.close();
}
bool success = !JlCompress::extractDir(tempDir.path() + QStringLiteral("/dotnet-core.zip"), dalamudRuntimeDir.absolutePath()).empty();
success |= !JlCompress::extractDir(tempDir.path() + QStringLiteral("/dotnet-desktop.zip"), dalamudRuntimeDir.absolutePath()).empty();
if (!success) {
qInfo() << "Failed to install dotnet!";
} else {
dalamudAssetNeededFilenames.clear();
qInfo() << "Dalamud assets up to date.";
checkIfFinished();
QFile file(dalamudRuntimeDir.absoluteFilePath(QStringLiteral("runtime.ver")));
file.open(QIODevice::WriteOnly | QIODevice::Text);
file.write(remoteRuntimeVersion.toUtf8());
file.close();
}
}
static const QMap<Profile::DalamudChannel, QString> channelToDistribPrefix = {{Profile::DalamudChannel::Stable, "/"},
{Profile::DalamudChannel::Staging, "stg/"},
{Profile::DalamudChannel::Net5, "net5/"}};
static const QMap<Profile::DalamudChannel, QString> channelToDistribPrefix = {{Profile::DalamudChannel::Stable, QStringLiteral("/")},
{Profile::DalamudChannel::Staging, QStringLiteral("stg/")},
{Profile::DalamudChannel::Net5, QStringLiteral("net5/")}};
QUrl AssetUpdater::dalamudVersionManifestUrl(const Profile::DalamudChannel channel) const
{
QUrl url;
url.setScheme("https");
url.setScheme(QStringLiteral("https"));
url.setHost(launcher.dalamudDistribServer());
url.setPath(QStringLiteral("/dalamud-distrib/%1version").arg(channelToDistribPrefix[channel]));
@ -339,7 +257,7 @@ QUrl AssetUpdater::dalamudVersionManifestUrl(const Profile::DalamudChannel chann
QUrl AssetUpdater::dalamudLatestPackageUrl(Profile::DalamudChannel channel) const
{
QUrl url;
url.setScheme("https");
url.setScheme(QStringLiteral("https"));
url.setHost(launcher.dalamudDistribServer());
url.setPath(QStringLiteral("/dalamud-distrib/%1latest.zip").arg(channelToDistribPrefix[channel]));
@ -349,7 +267,7 @@ QUrl AssetUpdater::dalamudLatestPackageUrl(Profile::DalamudChannel channel) cons
QUrl AssetUpdater::dalamudAssetManifestUrl() const
{
QUrl url;
url.setScheme("https");
url.setScheme(QStringLiteral("https"));
url.setHost(launcher.dalamudDistribServer());
url.setPath(QStringLiteral("/DalamudAssets/asset.json"));

View file

@ -71,6 +71,22 @@ void LauncherCore::launchGame(const Profile &profile, const LoginAuth &auth)
#endif
}
QCoro::Task<> LauncherCore::beginLogin(LoginInformation &info)
{
qDebug() << "Logging in, performing asset update check.";
auto assetUpdater = new AssetUpdater(*info.profile, *this, this);
co_await assetUpdater->update();
if (info.profile->account()->isSapphire()) {
m_sapphireLauncher->login(info.profile->account()->lobbyUrl(), info);
} else {
m_squareBoot->checkGateStatus(info);
}
assetUpdater->deleteLater();
}
void LauncherCore::beginGameExecutable(const Profile &profile, const LoginAuth &auth)
{
Q_EMIT stageChanged(i18n("Launching game..."));
@ -148,7 +164,7 @@ void LauncherCore::beginDalamudGame(const QString &gameExecutablePath, const Pro
#endif
dalamudProcess->setProcessEnvironment(env);
auto args = getGameArgs(profile, auth);
const auto args = getGameArgs(profile, auth);
launchExecutable(profile,
dalamudProcess,
@ -312,8 +328,6 @@ void LauncherCore::launchExecutable(const Profile &profile, QProcess *process, c
arguments.push_back(highestVersion.absoluteFilePath(QStringLiteral("proton")));
arguments.push_back(QStringLiteral("run"));
qInfo() << arguments << env.toStringList();
} else {
env.insert(QStringLiteral("WINEPREFIX"), profile.winePrefixPath());
@ -419,11 +433,7 @@ void LauncherCore::addRegistryKey(const Profile &settings, QString key, QString
void LauncherCore::login(Profile *profile, const QString &username, const QString &password, const QString &oneTimePassword)
{
qDebug() << "Logging in, performing asset update check.";
auto assetUpdater = new AssetUpdater(*profile, *this, this);
connect(assetUpdater, &AssetUpdater::finishedUpdating, this, [this, assetUpdater, profile, username, password, oneTimePassword] {
qDebug() << "Assets done updating!";
Q_ASSERT(profile != nullptr);
auto loginInformation = new LoginInformation(this);
loginInformation->profile = profile;
@ -435,15 +445,7 @@ void LauncherCore::login(Profile *profile, const QString &username, const QStrin
profile->account()->setPassword(password);
}
if (loginInformation->profile->account()->isSapphire()) {
m_sapphireLauncher->login(loginInformation->profile->account()->lobbyUrl(), *loginInformation);
} else {
m_squareBoot->checkGateStatus(loginInformation);
}
assetUpdater->deleteLater();
});
assetUpdater->update();
beginLogin(*loginInformation);
}
bool LauncherCore::autoLogin(Profile &profile)

View file

@ -37,10 +37,10 @@ Patcher::Patcher(LauncherCore &launcher, const QString &baseDirectory, GameData
Q_EMIT m_launcher.stageChanged(i18n("Checking %1 version.", getBaseString()));
}
QCoro::Task<> Patcher::patch(const QString &patchList)
QCoro::Task<bool> Patcher::patch(const QString &patchList)
{
if (patchList.isEmpty()) {
co_return;
co_return false;
}
Q_EMIT m_launcher.stageIndeterminate();
@ -114,6 +114,8 @@ QCoro::Task<> Patcher::patch(const QString &patchList)
processPatch(patch);
}
co_return true;
}
void Patcher::processPatch(const QueuedPatch &patch)

View file

@ -47,16 +47,21 @@ QCoro::Task<> SquareBoot::bootCheck(const LoginInformation &info)
const auto reply = window.mgr->get(request);
co_await reply;
const QString patchList = reply->readAll();
if (!patchList.isEmpty()) {
patcher = new Patcher(window, info.profile->gamePath() + QStringLiteral("/boot"), *info.profile->bootData(), this);
co_await patcher->patch(reply->readAll());
const bool hasPatched = co_await patcher->patch(reply->readAll());
if (hasPatched) {
// update game version information
info.profile->readGameVersion();
}
patcher->deleteLater();
}
launcher.login(info);
}
QCoro::Task<> SquareBoot::checkGateStatus(LoginInformation *info)
QCoro::Task<> SquareBoot::checkGateStatus(const LoginInformation &info)
{
Q_EMIT window.stageChanged(i18n("Checking gate..."));
qDebug() << "Checking gate...";
@ -70,7 +75,7 @@ QCoro::Task<> SquareBoot::checkGateStatus(LoginInformation *info)
QNetworkRequest request(url);
// TODO: really?
window.buildRequest(*info->profile, request);
window.buildRequest(*info.profile, request);
const auto reply = window.mgr->get(request);
co_await reply;
@ -79,7 +84,7 @@ QCoro::Task<> SquareBoot::checkGateStatus(LoginInformation *info)
const bool isGateOpen = !document.isEmpty() && document.object()[QLatin1String("status")].toInt() != 0;
if (isGateOpen) {
bootCheck(*info);
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,8 @@
#include <QNetworkReply>
#include <QRegularExpressionMatch>
#include <QUrlQuery>
#include <QtConcurrentMap>
#include <qcorofuture.h>
#include <qcoronetworkreply.h>
#include "account.h"
@ -99,6 +101,7 @@ QCoro::Task<> SquareLauncher::login(const LoginInformation &info)
if (storedResult == std::nullopt) {
co_return;
}
const auto [stored, referer] = *storedResult;
QUrlQuery postData;
@ -168,7 +171,7 @@ 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(), getBootHash(info));
QString report = QStringLiteral("%1=%2").arg(info.profile->bootVersion(), co_await getBootHash(info));
for (int i = 0; i < 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));
@ -177,8 +180,6 @@ QCoro::Task<> SquareLauncher::registerSession(const LoginInformation &info)
}
}
qInfo() << report;
const auto reply = window.mgr->post(request, report.toUtf8());
co_await reply;
@ -186,10 +187,15 @@ QCoro::Task<> SquareLauncher::registerSession(const LoginInformation &info)
if (reply->rawHeaderList().contains(QByteArrayLiteral("X-Patch-Unique-Id"))) {
const QString body = reply->readAll();
if (!body.isEmpty()) {
patcher = new Patcher(window, info.profile->gamePath() + QStringLiteral("/game"), *info.profile->gameData(), this);
co_await patcher->patch(body);
const bool hasPatched = co_await patcher->patch(body);
if (hasPatched) {
// re-read game version if it has updated
info.profile->readGameVersion();
}
patcher->deleteLater();
}
auth.SID = reply->rawHeader(QByteArrayLiteral("X-Patch-Unique-Id"));
@ -209,7 +215,7 @@ QCoro::Task<> SquareLauncher::registerSession(const LoginInformation &info)
}
}
QString SquareLauncher::getBootHash(const LoginInformation &info)
QCoro::Task<QString> SquareLauncher::getBootHash(const LoginInformation &info)
{
const QList<QString> fileList = {QStringLiteral("ffxivboot.exe"),
QStringLiteral("ffxivboot64.exe"),
@ -218,13 +224,20 @@ 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);
});
co_await hashFuture;
const QList<QString> hashes = hashFuture.results();
QString result;
for (int i = 0; i < fileList.count(); i++) {
result += fileList[i] + QStringLiteral("/") + getFileHash(info.profile->gamePath() + QStringLiteral("/boot/") + fileList[i]);
result += fileList[i] + QStringLiteral("/") + hashes[i];
if (i != fileList.length() - 1)
result += QStringLiteral(",");
}
return result;
co_return result;
}