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

View file

@ -179,6 +179,8 @@ signals:
void currentProfileChanged(); void currentProfileChanged();
private: private:
QCoro::Task<> beginLogin(LoginInformation &info);
/* /*
* Begins the game executable, but calls to Dalamud if needed. * 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, GameData &gameData, QObject *parent = nullptr);
Patcher(LauncherCore &launcher, const QString &baseDirectory, BootData &bootData, 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: private:
void setupDirectories(); void setupDirectories();

View file

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

View file

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

View file

@ -3,15 +3,19 @@
#include "assetupdater.h" #include "assetupdater.h"
#include <KLocalizedString>
#include <QFile> #include <QFile>
#include <QJsonDocument> #include <QJsonDocument>
#include <QNetworkReply> #include <QNetworkReply>
#include <QStandardPaths> #include <QStandardPaths>
#include <qcorofuture.h>
#include <qcoronetworkreply.h>
#include <JlCompress.h> #include <JlCompress.h>
#include <QtConcurrentRun>
const QString dotnetRuntimePackageURL = "https://dotnetcli.azureedge.net/dotnet/Runtime/%1/dotnet-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 = "https://dotnetcli.azureedge.net/dotnet/WindowsDesktop/%1/windowsdesktop-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) AssetUpdater::AssetUpdater(Profile &profile, LauncherCore &launcher, QObject *parent)
: QObject(parent) : QObject(parent)
@ -20,316 +24,230 @@ AssetUpdater::AssetUpdater(Profile &profile, LauncherCore &launcher, QObject *pa
, m_profile(profile) , m_profile(profile)
{ {
launcher.mgr->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy); 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); dataDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
dalamudDir = dataDir.absoluteFilePath("dalamud"); dalamudDir = dataDir.absoluteFilePath(QStringLiteral("dalamud"));
dalamudAssetDir = dalamudDir.absoluteFilePath("assets"); dalamudAssetDir = dalamudDir.absoluteFilePath(QStringLiteral("assets"));
dalamudRuntimeDir = dalamudDir.absoluteFilePath("runtime"); dalamudRuntimeDir = dalamudDir.absoluteFilePath(QStringLiteral("runtime"));
const auto createIfNeeded = [](const QDir &dir) { const auto createIfNeeded = [](const QDir &dir) {
if (!QDir().exists(dir.absolutePath())) if (!QDir().exists(dir.absolutePath()))
QDir().mkdir(dir.absolutePath()); QDir().mkpath(dir.absolutePath());
}; };
createIfNeeded(dataDir);
createIfNeeded(dalamudDir); createIfNeeded(dalamudDir);
createIfNeeded(dalamudAssetDir); createIfNeeded(dalamudAssetDir);
createIfNeeded(dalamudRuntimeDir); 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 // first we want to fetch the list of assets required
if (!m_profile.dalamudEnabled()) { const QNetworkRequest request(dalamudAssetManifestUrl());
Q_EMIT finishedUpdating();
return;
}
Q_EMIT launcher.stageChanged("Updating assets..."); const auto reply = launcher.mgr->get(request);
co_await reply;
// first, we want to collect all of the remote versions // TODO: handle asset failure
const QJsonDocument doc = QJsonDocument::fromJson(reply->readAll());
qInfo() << "Starting update sequence..."; remoteDalamudAssetVersion = doc.object()[QLatin1String("Version")].toInt();
// dialog->setLabelText("Checking for updates..."); remoteDalamudAssetArray = doc.object()[QLatin1String("Assets")].toArray();
// dalamud assets qInfo() << "Dalamud asset remote version" << remoteDalamudAssetVersion;
{ qInfo() << "Dalamud asset local version" << m_profile.dalamudAssetVersion();
// 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());
auto reply = launcher.mgr->get(request);
connect(reply, &QNetworkReply::finished, [reply, this] {
Q_EMIT launcher.stageChanged("Checking for Dalamud asset updates...");
// TODO: handle asset failure
QJsonDocument doc = QJsonDocument::fromJson(reply->readAll());
qInfo() << "Dalamud asset remote version" << doc.object()["Version"].toInt();
qInfo() << "Dalamud asset local version" << m_profile.dalamudAssetVersion();
remoteDalamudAssetVersion = doc.object()["Version"].toInt();
remoteDalamudAssetArray = doc.object()["Assets"].toArray();
checkIfCheckingIsDone();
});
}
// dalamud injector / net runtime
// they're all updated in unison, so there's no reason to have multiple checks
{
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...");
if (reply->error() != QNetworkReply::NetworkError::NoError) {
Q_EMIT launcher.loginError(QStringLiteral("Could not check for Dalamud updates.\n\n%1").arg(reply->errorString()));
return;
}
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
// start at the first character of the json '{' and work our way up.
QString reassmbled;
for (int i = static_cast<int>(str.indexOf('{')); i < str.size(); i++) {
char t = str[i];
if (QChar(t).isPrint())
reassmbled += t;
}
QJsonDocument doc = QJsonDocument::fromJson(reassmbled.toUtf8());
remoteDalamudVersion = doc["AssemblyVersion"].toString();
remoteRuntimeVersion = doc["RuntimeVersion"].toString();
qInfo() << "Latest Dalamud version reported: " << remoteDalamudVersion;
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();
}
// dalamud assets // dalamud assets
if (remoteDalamudAssetVersion != m_profile.dalamudAssetVersion()) { if (remoteDalamudAssetVersion != m_profile.dalamudAssetVersion()) {
qInfo() << "Dalamud assets out of date."; qInfo() << "Dalamud assets out of date.";
Q_EMIT launcher.stageChanged("Updating Dalamud assets..."); co_await installDalamudAssets();
dalamudAssetNeededFilenames.clear();
for (auto assetObject : remoteDalamudAssetArray) {
{
dalamudAssetNeededFilenames.append(assetObject.toObject()["FileName"].toString());
QNetworkRequest assetRequest(assetObject.toObject()["Url"].toString());
auto assetReply = launcher.mgr->get(assetRequest);
connect(assetReply, &QNetworkReply::finished, [this, assetReply, assetObject = assetObject.toObject()] {
const QString fileName = assetObject["FileName"].toString();
const QString dirPath = fileName.left(fileName.lastIndexOf("/"));
const QString path = dalamudAssetDir.absoluteFilePath(dirPath);
if (!QDir().exists(path))
QDir().mkpath(path);
QFile file(dalamudAssetDir.absoluteFilePath(assetObject["FileName"].toString()));
file.open(QIODevice::WriteOnly);
file.write(assetReply->readAll());
file.close();
dalamudAssetNeededFilenames.removeOne(assetObject["FileName"].toString());
checkIfDalamudAssetsDone();
});
}
}
} else {
dalamudAssetNeededFilenames.clear();
qInfo() << "Dalamud assets up to date.";
checkIfFinished();
} }
} }
static const QMap<Profile::DalamudChannel, QString> channelToDistribPrefix = {{Profile::DalamudChannel::Stable, "/"}, QCoro::Task<> AssetUpdater::checkRemoteDalamudVersion()
{Profile::DalamudChannel::Staging, "stg/"}, {
{Profile::DalamudChannel::Net5, "net5/"}}; const QNetworkRequest request(dalamudVersionManifestUrl(m_profile.dalamudChannel()));
remoteDalamudVersion.clear();
remoteRuntimeVersion.clear();
const auto reply = launcher.mgr->get(request);
co_await reply;
if (reply->error() != QNetworkReply::NetworkError::NoError) {
Q_EMIT launcher.loginError(i18n("Could not check for Dalamud updates.\n\n%1", reply->errorString()));
co_return;
}
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
// start at the first character of the json '{' and work our way up.
QString reassmbled;
for (int i = static_cast<int>(str.indexOf('{')); i < str.size(); i++) {
char t = str[i];
if (QChar(t).isPrint())
reassmbled += t;
}
const QJsonDocument doc = QJsonDocument::fromJson(reassmbled.toUtf8());
remoteDalamudVersion = doc[QLatin1String("AssemblyVersion")].toString();
remoteRuntimeVersion = doc[QLatin1String("RuntimeVersion")].toString();
qInfo() << "Latest Dalamud version reported:" << remoteDalamudVersion << "local:" << m_profile.dalamudVersion();
qInfo() << "Latest NET runtime reported:" << remoteRuntimeVersion;
if (remoteDalamudVersion != m_profile.dalamudVersion()) {
qInfo() << "Downloading Dalamud...";
co_await installDalamud();
}
if (m_profile.runtimeVersion() != remoteRuntimeVersion) {
qInfo() << "Downloading Runtime...";
co_await installRuntime();
}
}
QCoro::Task<> AssetUpdater::installDalamudAssets()
{
Q_EMIT launcher.stageChanged(i18n("Updating Dalamud assets..."));
QFutureSynchronizer<void> synchronizer;
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.toObject()[QLatin1String("FileName")].toString()));
file.open(QIODevice::WriteOnly);
file.write(assetReply->readAll());
file.close();
});
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 {
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, QStringLiteral("/")},
{Profile::DalamudChannel::Staging, QStringLiteral("stg/")},
{Profile::DalamudChannel::Net5, QStringLiteral("net5/")}};
QUrl AssetUpdater::dalamudVersionManifestUrl(const Profile::DalamudChannel channel) const QUrl AssetUpdater::dalamudVersionManifestUrl(const Profile::DalamudChannel channel) const
{ {
QUrl url; QUrl url;
url.setScheme("https"); url.setScheme(QStringLiteral("https"));
url.setHost(launcher.dalamudDistribServer()); url.setHost(launcher.dalamudDistribServer());
url.setPath(QStringLiteral("/dalamud-distrib/%1version").arg(channelToDistribPrefix[channel])); 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 AssetUpdater::dalamudLatestPackageUrl(Profile::DalamudChannel channel) const
{ {
QUrl url; QUrl url;
url.setScheme("https"); url.setScheme(QStringLiteral("https"));
url.setHost(launcher.dalamudDistribServer()); url.setHost(launcher.dalamudDistribServer());
url.setPath(QStringLiteral("/dalamud-distrib/%1latest.zip").arg(channelToDistribPrefix[channel])); 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 AssetUpdater::dalamudAssetManifestUrl() const
{ {
QUrl url; QUrl url;
url.setScheme("https"); url.setScheme(QStringLiteral("https"));
url.setHost(launcher.dalamudDistribServer()); url.setHost(launcher.dalamudDistribServer());
url.setPath(QStringLiteral("/DalamudAssets/asset.json")); url.setPath(QStringLiteral("/DalamudAssets/asset.json"));

View file

@ -71,6 +71,22 @@ void LauncherCore::launchGame(const Profile &profile, const LoginAuth &auth)
#endif #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) void LauncherCore::beginGameExecutable(const Profile &profile, const LoginAuth &auth)
{ {
Q_EMIT stageChanged(i18n("Launching game...")); Q_EMIT stageChanged(i18n("Launching game..."));
@ -148,7 +164,7 @@ void LauncherCore::beginDalamudGame(const QString &gameExecutablePath, const Pro
#endif #endif
dalamudProcess->setProcessEnvironment(env); dalamudProcess->setProcessEnvironment(env);
auto args = getGameArgs(profile, auth); const auto args = getGameArgs(profile, auth);
launchExecutable(profile, launchExecutable(profile,
dalamudProcess, dalamudProcess,
@ -312,8 +328,6 @@ void LauncherCore::launchExecutable(const Profile &profile, QProcess *process, c
arguments.push_back(highestVersion.absoluteFilePath(QStringLiteral("proton"))); arguments.push_back(highestVersion.absoluteFilePath(QStringLiteral("proton")));
arguments.push_back(QStringLiteral("run")); arguments.push_back(QStringLiteral("run"));
qInfo() << arguments << env.toStringList();
} else { } else {
env.insert(QStringLiteral("WINEPREFIX"), profile.winePrefixPath()); env.insert(QStringLiteral("WINEPREFIX"), profile.winePrefixPath());
@ -419,31 +433,19 @@ void LauncherCore::addRegistryKey(const Profile &settings, QString key, QString
void LauncherCore::login(Profile *profile, const QString &username, const QString &password, const QString &oneTimePassword) void LauncherCore::login(Profile *profile, const QString &username, const QString &password, const QString &oneTimePassword)
{ {
qDebug() << "Logging in, performing asset update check."; Q_ASSERT(profile != nullptr);
auto assetUpdater = new AssetUpdater(*profile, *this, this); auto loginInformation = new LoginInformation(this);
connect(assetUpdater, &AssetUpdater::finishedUpdating, this, [this, assetUpdater, profile, username, password, oneTimePassword] { loginInformation->profile = profile;
qDebug() << "Assets done updating!"; loginInformation->username = username;
loginInformation->password = password;
loginInformation->oneTimePassword = oneTimePassword;
auto loginInformation = new LoginInformation(this); if (profile->account()->rememberPassword()) {
loginInformation->profile = profile; profile->account()->setPassword(password);
loginInformation->username = username; }
loginInformation->password = password;
loginInformation->oneTimePassword = oneTimePassword;
if (profile->account()->rememberPassword()) { beginLogin(*loginInformation);
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();
} }
bool LauncherCore::autoLogin(Profile &profile) bool LauncherCore::autoLogin(Profile &profile)
@ -737,4 +739,4 @@ void LauncherCore::setCurrentProfile(Profile *profile)
m_currentProfileIndex = newIndex; m_currentProfileIndex = newIndex;
Q_EMIT currentProfileChanged(); Q_EMIT currentProfileChanged();
} }
} }

View file

@ -37,10 +37,10 @@ Patcher::Patcher(LauncherCore &launcher, const QString &baseDirectory, GameData
Q_EMIT m_launcher.stageChanged(i18n("Checking %1 version.", getBaseString())); 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()) { if (patchList.isEmpty()) {
co_return; co_return false;
} }
Q_EMIT m_launcher.stageIndeterminate(); Q_EMIT m_launcher.stageIndeterminate();
@ -114,6 +114,8 @@ QCoro::Task<> Patcher::patch(const QString &patchList)
processPatch(patch); processPatch(patch);
} }
co_return true;
} }
void Patcher::processPatch(const QueuedPatch &patch) 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); const auto reply = window.mgr->get(request);
co_await reply; co_await reply;
patcher = new Patcher(window, info.profile->gamePath() + QStringLiteral("/boot"), *info.profile->bootData(), this); const QString patchList = reply->readAll();
co_await patcher->patch(reply->readAll()); if (!patchList.isEmpty()) {
patcher = new Patcher(window, info.profile->gamePath() + QStringLiteral("/boot"), *info.profile->bootData(), this);
// update game version information const bool hasPatched = co_await patcher->patch(reply->readAll());
info.profile->readGameVersion(); if (hasPatched) {
// update game version information
info.profile->readGameVersion();
}
patcher->deleteLater();
}
launcher.login(info); launcher.login(info);
} }
QCoro::Task<> SquareBoot::checkGateStatus(LoginInformation *info) QCoro::Task<> SquareBoot::checkGateStatus(const LoginInformation &info)
{ {
Q_EMIT window.stageChanged(i18n("Checking gate...")); Q_EMIT window.stageChanged(i18n("Checking gate..."));
qDebug() << "Checking gate..."; qDebug() << "Checking gate...";
@ -70,7 +75,7 @@ QCoro::Task<> SquareBoot::checkGateStatus(LoginInformation *info)
QNetworkRequest request(url); QNetworkRequest request(url);
// TODO: really? // TODO: really?
window.buildRequest(*info->profile, request); window.buildRequest(*info.profile, request);
const auto reply = window.mgr->get(request); const auto reply = window.mgr->get(request);
co_await reply; co_await reply;
@ -79,7 +84,7 @@ QCoro::Task<> SquareBoot::checkGateStatus(LoginInformation *info)
const bool isGateOpen = !document.isEmpty() && document.object()[QLatin1String("status")].toInt() != 0; const bool isGateOpen = !document.isEmpty() && document.object()[QLatin1String("status")].toInt() != 0;
if (isGateOpen) { if (isGateOpen) {
bootCheck(*info); bootCheck(info);
} else { } else {
Q_EMIT window.loginError(i18n("The login gate is closed, the game may be under maintenance.\n\n%1", reply->errorString())); 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 <QNetworkReply>
#include <QRegularExpressionMatch> #include <QRegularExpressionMatch>
#include <QUrlQuery> #include <QUrlQuery>
#include <QtConcurrentMap>
#include <qcorofuture.h>
#include <qcoronetworkreply.h> #include <qcoronetworkreply.h>
#include "account.h" #include "account.h"
@ -99,6 +101,7 @@ QCoro::Task<> SquareLauncher::login(const LoginInformation &info)
if (storedResult == std::nullopt) { if (storedResult == std::nullopt) {
co_return; co_return;
} }
const auto [stored, referer] = *storedResult; const auto [stored, referer] = *storedResult;
QUrlQuery postData; QUrlQuery postData;
@ -168,7 +171,7 @@ QCoro::Task<> SquareLauncher::registerSession(const LoginInformation &info)
request.setRawHeader(QByteArrayLiteral("User-Agent"), QByteArrayLiteral("FFXIV PATCH CLIENT")); request.setRawHeader(QByteArrayLiteral("User-Agent"), QByteArrayLiteral("FFXIV PATCH CLIENT"));
request.setHeader(QNetworkRequest::ContentTypeHeader, QByteArrayLiteral("application/x-www-form-urlencoded")); 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++) { for (int i = 0; i < auth.maxExpansion; i++) {
if (i < static_cast<int>(info.profile->numInstalledExpansions())) { if (i < static_cast<int>(info.profile->numInstalledExpansions())) {
report += QStringLiteral("\nex%1\t%2").arg(QString::number(i + 1), info.profile->expansionVersion(i)); 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()); const auto reply = window.mgr->post(request, report.toUtf8());
co_await reply; co_await reply;
@ -186,10 +187,15 @@ QCoro::Task<> SquareLauncher::registerSession(const LoginInformation &info)
if (reply->rawHeaderList().contains(QByteArrayLiteral("X-Patch-Unique-Id"))) { if (reply->rawHeaderList().contains(QByteArrayLiteral("X-Patch-Unique-Id"))) {
const QString body = reply->readAll(); const QString body = reply->readAll();
patcher = new Patcher(window, info.profile->gamePath() + QStringLiteral("/game"), *info.profile->gameData(), this); if (!body.isEmpty()) {
co_await patcher->patch(body); patcher = new Patcher(window, info.profile->gamePath() + QStringLiteral("/game"), *info.profile->gameData(), this);
const bool hasPatched = co_await patcher->patch(body);
info.profile->readGameVersion(); if (hasPatched) {
// re-read game version if it has updated
info.profile->readGameVersion();
}
patcher->deleteLater();
}
auth.SID = reply->rawHeader(QByteArrayLiteral("X-Patch-Unique-Id")); 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"), const QList<QString> fileList = {QStringLiteral("ffxivboot.exe"),
QStringLiteral("ffxivboot64.exe"), QStringLiteral("ffxivboot64.exe"),
@ -218,13 +224,20 @@ QString SquareLauncher::getBootHash(const LoginInformation &info)
QStringLiteral("ffxivupdater.exe"), QStringLiteral("ffxivupdater.exe"),
QStringLiteral("ffxivupdater64.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; QString result;
for (int i = 0; i < fileList.count(); i++) { 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) if (i != fileList.length() - 1)
result += QStringLiteral(","); result += QStringLiteral(",");
} }
return result; co_return result;
} }