From d939d26280fbc5f55453e5b78f9503929b78e3f2 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Sun, 13 Mar 2022 20:44:57 -0400 Subject: [PATCH] Overhauled asset updating process (again) Now the code is much cleaner, should be less error prone. And best of all, the process is now user-visible in a nice progress box. --- include/assetupdater.h | 11 +- src/assetupdater.cpp | 455 +++++++++++++++++++++-------------------- 2 files changed, 246 insertions(+), 220 deletions(-) diff --git a/include/assetupdater.h b/include/assetupdater.h index 72ed88c..9a4f59c 100644 --- a/include/assetupdater.h +++ b/include/assetupdater.h @@ -2,6 +2,8 @@ #include #include +#include +#include class LauncherCore; class QNetworkReply; @@ -13,9 +15,9 @@ public: AssetUpdater(LauncherCore& launcher); void update(const ProfileSettings& profile); - void finishDownload(QNetworkReply* reply); void beginInstall(); + void checkIfCheckingIsDone(); void checkIfDalamudAssetsDone(); void checkIfFinished(); @@ -25,6 +27,8 @@ signals: private: LauncherCore& launcher; + QProgressDialog* dialog; + const ProfileSettings* currentSettings = nullptr; QString remoteDalamudVersion; @@ -40,6 +44,9 @@ private: bool needsDalamudInstall = false; bool needsNativeInstall = false; - int remoteDalamudAssetVersion; + int remoteDalamudAssetVersion = -1; QList dalamudAssetNeededFilenames; + QJsonArray remoteDalamudAssetArray; + + QString dataDir; }; diff --git a/src/assetupdater.cpp b/src/assetupdater.cpp index fac81a8..b591467 100644 --- a/src/assetupdater.cpp +++ b/src/assetupdater.cpp @@ -11,24 +11,32 @@ #include "launchercore.h" -const QString baseGoatcorpDomain = "https://goatcorp.github.io"; +const QString baseGoatDomain = "https://goatcorp.github.io"; -const QString dalamudRemotePath = baseGoatcorpDomain + "/dalamud-distrib"; -const QString dalamudVersion = "/latest"; -const QString dalamudVersionPath = dalamudRemotePath + "/version"; +const QString baseDalamudDistribution = baseGoatDomain + "/dalamud-distrib"; +const QString dalamudLatestPackageURL = baseDalamudDistribution + "/latest.zip"; +const QString dalamudVersionManifestURL = baseDalamudDistribution + "/version"; -const QString dalamudAssetRemotePath = baseGoatcorpDomain + "/DalamudAssets"; -const QString dalamudAssetManifestPath = dalamudAssetRemotePath + "/asset.json"; +const QString baseDalamudAssetDistribution = baseGoatDomain + "/DalamudAssets"; +const QString dalamudAssetManifestURL = baseDalamudAssetDistribution + "/asset.json"; -const QString nativeLauncherRemotePath = +const QString baseNativeLauncherDistribution = "https://github.com/redstrate/nativelauncher/releases/download/"; -const QString nativeLauncherVersion = "v1.0.0"; +const QString nativeLauncherLatestPackageURL = + baseNativeLauncherDistribution + "/latest/NativeLauncher.exe"; + +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"; AssetUpdater::AssetUpdater(LauncherCore& launcher) : launcher(launcher) { - connect(launcher.mgr, &QNetworkAccessManager::finished, this, - &AssetUpdater::finishDownload); - launcher.mgr->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy); + + dialog = new QProgressDialog("Updating assets...", "Cancel", 0, 0); + + dataDir = + QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); } void AssetUpdater::update(const ProfileSettings& profile) { @@ -38,71 +46,23 @@ void AssetUpdater::update(const ProfileSettings& profile) { return; } - const QString dataDir = - QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + // first, we want to collect all of the remote versions qInfo() << "Starting update sequence..."; + dialog->setLabelText("Checking for updates..."); - bool isDalamudUpdated = false; - if (remoteDalamudVersion.isEmpty()) { - QNetworkRequest request(dalamudVersionPath); - - auto reply = launcher.mgr->get(request); - reply->setObjectName("DalamudVersionCheck"); - currentSettings = &profile; // TODO: this is dirty, should change - - return; - } else { - if (launcher.dalamudVersion != remoteDalamudVersion) { - isDalamudUpdated = false; - } else { - qInfo() << "No need to update Dalamud."; - isDalamudUpdated = true; - } - - if(launcher.runtimeVersion != remoteRuntimeVersion) { - doneDownloadingRuntimeCore = false; - doneDownloadingRuntimeDesktop = false; - needsRuntimeInstall = true; - - // core - { - QNetworkRequest request( - QString("https://dotnetcli.azureedge.net/dotnet/Runtime/%1/dotnet-runtime-%1-win-x64.zip") - .arg(remoteRuntimeVersion)); - - auto reply = launcher.mgr->get(request); - reply->setObjectName("Dotnet-core"); - } - - // desktop - { - QNetworkRequest request( - QString("https://dotnetcli.azureedge.net/dotnet/WindowsDesktop/%1/windowsdesktop-runtime-%1-win-x64.zip") - .arg(remoteRuntimeVersion)); - - auto reply = launcher.mgr->get(request); - reply->setObjectName("Dotnet-desktop"); - } - } else { - qInfo() << "No need to update Runtime."; - doneDownloadingRuntimeCore = true; - doneDownloadingRuntimeDesktop = true; - } - } - - { - qInfo() << "Checking Dalamud assets..."; - + // dalamud assets + { // we want to prevent logging in before we actually check the version dalamudAssetNeededFilenames.append("dummy"); // first we want to fetch the list of assets required - QNetworkRequest request(dalamudAssetManifestPath); + QNetworkRequest request(dalamudAssetManifestURL); auto reply = launcher.mgr->get(request); - connect(reply, &QNetworkReply::finished, [reply, this, profile] { + dialog->setLabelText("Checking for Dalamud asset updates..."); + // lol, they actually provide invalid json. let's fix it if it's borked QString badJson = reply->readAll(); @@ -130,166 +90,45 @@ void AssetUpdater::update(const ProfileSettings& profile) { remoteDalamudAssetVersion = doc.object()["Version"].toInt(); - if(remoteDalamudAssetVersion != launcher.dalamudAssetVersion) { - qInfo() << "Dalamud assets out of date."; + remoteDalamudAssetArray = doc.object()["Assets"].toArray(); - dalamudAssetNeededFilenames.clear(); - - for(auto assetObject : doc.object()["Assets"].toArray()) { - { - qInfo() << "Starting download for " << assetObject.toObject()["FileName"]; - - 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 dataDir = - QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/DalamudAssets/"; - - if (!QDir().exists(dataDir)) - QDir().mkdir(dataDir); - - const QString fileName = assetObject["FileName"].toString(); - const QList dirPath = fileName.left(fileName.lastIndexOf("/")).split('/'); - - qInfo() << "Needed directories: " << dirPath; - - QString build = dataDir; - for(auto dir : dirPath) { - if (!QDir().exists(build + dir)) - QDir().mkdir(build + dir); - - build += dir + "/"; - } - - QFile file(dataDir + 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(); - } + checkIfCheckingIsDone(); }); } - if(remoteDalamudVersion != launcher.dalamudVersion) { - qInfo() << "Downloading Dalamud..."; - doneDownloadingDalamud = false; - needsDalamudInstall = true; - - QNetworkRequest request(dalamudRemotePath + dalamudVersion + - ".zip"); + // dalamud injector / net runtime / nativelauncher + // they're all updated in unison, so there's no reason to have multiple checks + { + QNetworkRequest request(dalamudVersionManifestURL); auto reply = launcher.mgr->get(request); - reply->setObjectName("Dalamud"); - } + connect(reply, &QNetworkReply::finished, [this, profile, reply] { + dialog->setLabelText("Checking for Dalamud updates..."); - const bool hasNative = QFile::exists(dataDir + "/NativeLauncher.exe"); - if (!hasNative) { - // download nativelauncher release (needed to launch the game with fixed - // ACLs) - { - qInfo() << "Downloading NativeLauncher..."; - doneDownloadingNativelauncher = false; - needsNativeInstall = true; + 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 = str.indexOf('{'); i < str.size(); i++) { + char t = str[i]; + if (QChar(t).isPrint()) + reassmbled += t; + } - QNetworkRequest request(nativeLauncherRemotePath + - nativeLauncherVersion + - "/NativeLauncher.exe"); + QJsonDocument doc = QJsonDocument::fromJson(reassmbled.toUtf8()); + remoteDalamudVersion = doc["AssemblyVersion"].toString(); + remoteRuntimeVersion = doc["RuntimeVersion"].toString(); - auto reply = launcher.mgr->get(request); - reply->setObjectName("NativeLauncher"); - } - } -} + qInfo() << "Latest Dalamud version reported: " << remoteDalamudVersion; + qInfo() << "Latest NET runtime reported: " << remoteRuntimeVersion; -void AssetUpdater::finishDownload(QNetworkReply* reply) { - if (reply->objectName() == "Dalamud") { - qInfo() << "Dalamud finished downloading!"; - - QFile file(tempDir.path() + "/latest.zip"); - file.open(QIODevice::WriteOnly); - file.write(reply->readAll()); - file.close(); - - doneDownloadingDalamud = true; - - launcher.dalamudVersion = remoteDalamudVersion; - - checkIfFinished(); - } else if (reply->objectName() == "NativeLauncher") { - qInfo() << "NativeLauncher finished downloading!"; - - QFile file(tempDir.path() + "/NativeLauncher.exe"); - file.open(QIODevice::WriteOnly); - file.write(reply->readAll()); - file.close(); - - doneDownloadingNativelauncher = true; - - checkIfFinished(); - } else if (reply->objectName() == "DalamudVersionCheck") { - 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 = 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; - - update(*currentSettings); - currentSettings = nullptr; - } else if(reply->objectName() == "Dotnet-core") { - qInfo() << "Dotnet-core finished downloading!"; - - QFile file(tempDir.path() + "/dotnet-core.zip"); - file.open(QIODevice::WriteOnly); - file.write(reply->readAll()); - file.close(); - - doneDownloadingRuntimeCore = true; - - checkIfFinished(); - } else if(reply->objectName() == "Dotnet-desktop") { - qInfo() << "Dotnet-desktop finished downloading!"; - - QFile file(tempDir.path() + "/dotnet-desktop.zip"); - file.open(QIODevice::WriteOnly); - file.write(reply->readAll()); - file.close(); - - doneDownloadingRuntimeDesktop = true; - - checkIfFinished(); + checkIfCheckingIsDone(); + }); } } void AssetUpdater::beginInstall() { - const QString dataDir = - QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - if(needsDalamudInstall) { bool success = !JlCompress::extractDir(tempDir.path() + "/latest.zip", dataDir + "/Dalamud") @@ -339,10 +178,7 @@ void AssetUpdater::checkIfDalamudAssetsDone() { launcher.dalamudAssetVersion = remoteDalamudAssetVersion; - const QString dataDir = - QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/DalamudAssets/"; - - QFile file(dataDir + "asset.ver"); + QFile file(dataDir + "/DalamudAssets/" + "asset.ver"); file.open(QIODevice::WriteOnly | QIODevice::Text); file.write(QString::number(remoteDalamudAssetVersion).toUtf8()); file.close(); @@ -357,9 +193,192 @@ void AssetUpdater::checkIfFinished() { doneDownloadingRuntimeCore && doneDownloadingRuntimeDesktop && dalamudAssetNeededFilenames.empty()) { - if(needsRuntimeInstall || needsNativeInstall || needsDalamudInstall) + if (needsRuntimeInstall || needsNativeInstall || needsDalamudInstall) { beginInstall(); - else + } else { + dialog->setLabelText("Finished!"); + dialog->close(); + 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 + + dialog->setLabelText("Starting update..."); + + // dalamud injector / net runtime + if(launcher.runtimeVersion != remoteRuntimeVersion) { + doneDownloadingRuntimeCore = false; + doneDownloadingRuntimeDesktop = false; + 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!"; + + dialog->setLabelText("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!"; + + dialog->setLabelText("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 != launcher.dalamudVersion) { + qInfo() << "Downloading Dalamud..."; + doneDownloadingDalamud = false; + needsDalamudInstall = true; + + QNetworkRequest request(dalamudLatestPackageURL); + + auto reply = launcher.mgr->get(request); + connect(reply, &QNetworkReply::finished, [this, reply] { + qInfo() << "Dalamud finished downloading!"; + + dialog->setLabelText("Updating Dalamud..."); + + QFile file(tempDir.path() + "/latest.zip"); + file.open(QIODevice::WriteOnly); + file.write(reply->readAll()); + file.close(); + + doneDownloadingDalamud = true; + + launcher.dalamudVersion = remoteDalamudVersion; + + checkIfFinished(); + }); + } else { + qInfo() << "No need to update Dalamud."; + + doneDownloadingDalamud = true; + needsDalamudInstall = false; + + checkIfFinished(); + } + + // dalamud assets + if(remoteDalamudAssetVersion != launcher.dalamudAssetVersion) { + qInfo() << "Dalamud assets out of date."; + + dialog->setLabelText("Updating Dalamud assets..."); + + dalamudAssetNeededFilenames.clear(); + + for(auto assetObject : remoteDalamudAssetArray) { + { + qInfo() << "Starting download for " << assetObject.toObject()["FileName"]; + + 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()] { + if (!QDir().exists(dataDir + "/DalamudAssets/")) + QDir().mkdir(dataDir + "/DalamudAssets/"); + + const QString fileName = assetObject["FileName"].toString(); + const QList dirPath = fileName.left(fileName.lastIndexOf("/")).split('/'); + + qInfo() << "Needed directories: " << dirPath; + + QString build = dataDir + "/DalamudAssets/"; + for(auto dir : dirPath) { + if (!QDir().exists(build + dir)) + QDir().mkdir(build + dir); + + build += dir + "/"; + } + + QFile file(dataDir + "/DalamudAssets/" + 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(); + } + + // nativelauncher + const bool hasNative = QFile::exists(dataDir + "/NativeLauncher.exe"); + if (!hasNative) { + // download nativelauncher release (needed to launch the game with fixed + // ACLs) + + qInfo() << "Downloading NativeLauncher..."; + doneDownloadingNativelauncher = false; + needsNativeInstall = true; + + QNetworkRequest request(nativeLauncherLatestPackageURL); + + auto reply = launcher.mgr->get(request); + connect(reply, &QNetworkReply::finished, [this, reply] { + qInfo() << "NativeLauncher finished downloading!"; + + dialog->setLabelText("Updating Nativelauncher..."); + + QFile file(tempDir.path() + "/NativeLauncher.exe"); + file.open(QIODevice::WriteOnly); + file.write(reply->readAll()); + file.close(); + + doneDownloadingNativelauncher = true; + + checkIfFinished(); + }); } }