diff --git a/launcher/include/assetupdater.h b/launcher/include/assetupdater.h index 3e284ef..8c16012 100644 --- a/launcher/include/assetupdater.h +++ b/launcher/include/assetupdater.h @@ -6,6 +6,7 @@ #include #include #include +#include #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 dalamudAssetNeededFilenames; QJsonArray remoteDalamudAssetArray; Profile &m_profile; diff --git a/launcher/include/launchercore.h b/launcher/include/launchercore.h index 647ed85..df565ef 100755 --- a/launcher/include/launchercore.h +++ b/launcher/include/launchercore.h @@ -179,6 +179,8 @@ signals: void currentProfileChanged(); private: + QCoro::Task<> beginLogin(LoginInformation &info); + /* * Begins the game executable, but calls to Dalamud if needed. */ diff --git a/launcher/include/patcher.h b/launcher/include/patcher.h index 89e7b62..8002159 100644 --- a/launcher/include/patcher.h +++ b/launcher/include/patcher.h @@ -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 patch(const QString &patchList); private: void setupDirectories(); diff --git a/launcher/include/squareboot.h b/launcher/include/squareboot.h index 386e949..67f4cf4 100644 --- a/launcher/include/squareboot.h +++ b/launcher/include/squareboot.h @@ -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; diff --git a/launcher/include/squarelauncher.h b/launcher/include/squarelauncher.h index b79264c..89d7fac 100644 --- a/launcher/include/squarelauncher.h +++ b/launcher/include/squarelauncher.h @@ -23,7 +23,7 @@ public: QCoro::Task<> registerSession(const LoginInformation &info); private: - QString getBootHash(const LoginInformation &info); + static QCoro::Task getBootHash(const LoginInformation &info); Patcher *patcher = nullptr; diff --git a/launcher/src/assetupdater.cpp b/launcher/src/assetupdater.cpp index 566d76c..8045b87 100644 --- a/launcher/src/assetupdater.cpp +++ b/launcher/src/assetupdater.cpp @@ -3,15 +3,19 @@ #include "assetupdater.h" +#include #include #include #include #include +#include +#include #include +#include -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,316 +24,230 @@ 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; - } + // first we want to fetch the list of assets required + const QNetworkRequest request(dalamudAssetManifestUrl()); - 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..."; - // dialog->setLabelText("Checking for updates..."); + remoteDalamudAssetVersion = doc.object()[QLatin1String("Version")].toInt(); + remoteDalamudAssetArray = doc.object()[QLatin1String("Assets")].toArray(); - // 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()); - - 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(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(); - } + qInfo() << "Dalamud asset remote version" << remoteDalamudAssetVersion; + qInfo() << "Dalamud asset local version" << m_profile.dalamudAssetVersion(); // dalamud assets if (remoteDalamudAssetVersion != m_profile.dalamudAssetVersion()) { qInfo() << "Dalamud assets out of date."; - Q_EMIT launcher.stageChanged("Updating Dalamud assets..."); - - 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(); + co_await installDalamudAssets(); } } -static const QMap channelToDistribPrefix = {{Profile::DalamudChannel::Stable, "/"}, - {Profile::DalamudChannel::Staging, "stg/"}, - {Profile::DalamudChannel::Net5, "net5/"}}; +QCoro::Task<> AssetUpdater::checkRemoteDalamudVersion() +{ + 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(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 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 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")); diff --git a/launcher/src/launchercore.cpp b/launcher/src/launchercore.cpp index c3a2f1d..93020f7 100755 --- a/launcher/src/launchercore.cpp +++ b/launcher/src/launchercore.cpp @@ -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,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) { - qDebug() << "Logging in, performing asset update check."; + Q_ASSERT(profile != nullptr); - auto assetUpdater = new AssetUpdater(*profile, *this, this); - connect(assetUpdater, &AssetUpdater::finishedUpdating, this, [this, assetUpdater, profile, username, password, oneTimePassword] { - qDebug() << "Assets done updating!"; + auto loginInformation = new LoginInformation(this); + loginInformation->profile = profile; + loginInformation->username = username; + loginInformation->password = password; + loginInformation->oneTimePassword = oneTimePassword; - 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); + } - if (profile->account()->rememberPassword()) { - 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) @@ -737,4 +739,4 @@ void LauncherCore::setCurrentProfile(Profile *profile) m_currentProfileIndex = newIndex; Q_EMIT currentProfileChanged(); } -} \ No newline at end of file +} diff --git a/launcher/src/patcher.cpp b/launcher/src/patcher.cpp index 8dbcfa2..e2faa1d 100644 --- a/launcher/src/patcher.cpp +++ b/launcher/src/patcher.cpp @@ -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 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) diff --git a/launcher/src/squareboot.cpp b/launcher/src/squareboot.cpp index 1131abf..e4ca018 100644 --- a/launcher/src/squareboot.cpp +++ b/launcher/src/squareboot.cpp @@ -47,16 +47,21 @@ QCoro::Task<> SquareBoot::bootCheck(const LoginInformation &info) const auto reply = window.mgr->get(request); co_await reply; - patcher = new Patcher(window, info.profile->gamePath() + QStringLiteral("/boot"), *info.profile->bootData(), this); - co_await patcher->patch(reply->readAll()); - - // update game version information - info.profile->readGameVersion(); + const QString patchList = reply->readAll(); + if (!patchList.isEmpty()) { + patcher = new Patcher(window, info.profile->gamePath() + QStringLiteral("/boot"), *info.profile->bootData(), this); + 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())); } diff --git a/launcher/src/squarelauncher.cpp b/launcher/src/squarelauncher.cpp index 85551a1..b8c1102 100644 --- a/launcher/src/squarelauncher.cpp +++ b/launcher/src/squarelauncher.cpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include #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(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(); - patcher = new Patcher(window, info.profile->gamePath() + QStringLiteral("/game"), *info.profile->gameData(), this); - co_await patcher->patch(body); - - info.profile->readGameVersion(); + if (!body.isEmpty()) { + patcher = new Patcher(window, info.profile->gamePath() + QStringLiteral("/game"), *info.profile->gameData(), this); + 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 SquareLauncher::getBootHash(const LoginInformation &info) { const QList 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 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; } \ No newline at end of file