diff --git a/launcher/core/CMakeLists.txt b/launcher/core/CMakeLists.txt index e5b4020..d7fd47e 100644 --- a/launcher/core/CMakeLists.txt +++ b/launcher/core/CMakeLists.txt @@ -7,7 +7,8 @@ set(HEADERS include/launchercore.h include/sapphirelauncher.h include/squareboot.h - include/squarelauncher.h) + include/squarelauncher.h + include/patcher.h) set(SRC src/dxvkinstaller.cpp @@ -17,7 +18,8 @@ set(SRC src/launchercore.cpp src/sapphirelauncher.cpp src/squareboot.cpp - src/squarelauncher.cpp) + src/squarelauncher.cpp + src/patcher.cpp) if(ENABLE_WATCHDOG) set(HEADERS ${HEADERS} diff --git a/launcher/core/include/patcher.h b/launcher/core/include/patcher.h new file mode 100644 index 0000000..180e20b --- /dev/null +++ b/launcher/core/include/patcher.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include + +// General-purpose patcher routine. It opens a nice dialog box, handles downloading +// and processing patches. +class Patcher : public QObject { + Q_OBJECT +public: + // isBoot is used for telling the patcher that you're reading boot patches, which for some reason has a different patchlist format. + Patcher(bool isBoot, QString baseDirectory); + + void processPatchList(QNetworkAccessManager& mgr, QString patchList); + +signals: + void done(); + +private: + void checkIfDone(); + + struct QueuedPatch { + QString name, repository, version, path; + }; + + void processPatch(QueuedPatch patch); + + QVector patchQueue; + + bool isBoot = false; + QString baseDirectory; + + QProgressDialog* dialog = nullptr; + + int remainingPatches = -1; +}; \ No newline at end of file diff --git a/launcher/core/include/squareboot.h b/launcher/core/include/squareboot.h index 21db8ed..11feb0b 100644 --- a/launcher/core/include/squareboot.h +++ b/launcher/core/include/squareboot.h @@ -1,6 +1,6 @@ #pragma once -#include +#include "patcher.h" class SquareLauncher; class LauncherCore; @@ -16,7 +16,7 @@ public: void bootCheck(const LoginInformation& info); private: - QProgressDialog* dialog; + Patcher* patcher = nullptr; LauncherCore& window; SquareLauncher& launcher; diff --git a/launcher/core/include/squarelauncher.h b/launcher/core/include/squarelauncher.h index db43adb..fe690b1 100644 --- a/launcher/core/include/squarelauncher.h +++ b/launcher/core/include/squarelauncher.h @@ -1,6 +1,7 @@ #pragma once #include "launchercore.h" +#include "patcher.h" class SquareLauncher : public QObject { Q_OBJECT @@ -16,6 +17,8 @@ public: private: QString getBootHash(const LoginInformation& info); + Patcher* patcher = nullptr; + QString stored, SID, username; LoginAuth auth; diff --git a/launcher/core/src/patcher.cpp b/launcher/core/src/patcher.cpp new file mode 100644 index 0000000..245532c --- /dev/null +++ b/launcher/core/src/patcher.cpp @@ -0,0 +1,136 @@ +#include "patcher.h" +#include +#include +#include +#include +#include +#include + +Patcher::Patcher(bool isBoot, QString baseDirectory) : isBoot(isBoot), baseDirectory(baseDirectory) { + dialog = new QProgressDialog(); + + if(isBoot) { + dialog->setLabelText("Checking the FINAL FANTASY XIV Updater/Launcher version."); + } else { + dialog->setLabelText("Checking the FINAL FANTASY XIV Game version."); + } + + dialog->show(); +} + +void Patcher::processPatchList(QNetworkAccessManager& mgr, QString patchList) { + if(patchList.isEmpty()) { + dialog->hide(); + + emit done(); + } else { + if(isBoot) { + dialog->setLabelText("Updating the FINAL FANTASY XIV Updater/Launcher version."); + } else { + dialog->setLabelText("Updating the FINAL FANTASY XIV Game version."); + } + + const QStringList parts = patchList.split(QRegExp("\n|\r\n|\r")); + + remainingPatches = parts.size() - 7; + + for(int i = 5; i < parts.size() - 2; i++) { + const QStringList patchParts = parts[i].split("\t"); + + const int length = patchParts[0].toInt(); + + QString name, url, version, repository; + + if (isBoot) { + name = patchParts[4]; + url = patchParts[5]; + version = name; + } else { + url = patchParts[8]; + version = patchParts[4]; + name = url.split('/').last().remove(".patch"); + } + + auto url_parts = url.split('/'); + repository = url_parts[url_parts.size() - 3]; + + if(isBoot) { + dialog->setLabelText("Updating the FINAL FANTASY XIV Updater/Launcher version.\nDownloading ffxivboot - " + version); + } else { + dialog->setLabelText("Updating the FINAL FANTASY XIV Game version.\nDownloading " + repository + " - " + version); + } + + dialog->setMinimum(0); + dialog->setMaximum(length); + + const QString patchesDir = + QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/patches/" + repository; + + if(!QDir().exists(patchesDir)) + QDir().mkdir(patchesDir); + + if(!QFile::exists(patchesDir + "/" + name + ".patch")) { + QNetworkRequest patchRequest(url); + auto patchReply = mgr.get(patchRequest); + connect(patchReply, &QNetworkReply::downloadProgress, [=](int recieved, int total) { + dialog->setValue(recieved); + }); + + connect(patchReply, &QNetworkReply::finished, [=] { + QFile file(patchesDir + "/" + name + ".patch"); + file.open(QIODevice::WriteOnly); + file.write(patchReply->readAll()); + file.close(); + + auto patch_path = patchesDir + "/" + name + ".patch"; + + patchQueue.push_back({name, repository, version, patch_path}); + + remainingPatches--; + checkIfDone(); + }); + } else { + patchQueue.push_back({name, repository, version, patchesDir + "/" + name + ".patch"}); + + remainingPatches--; + checkIfDone(); + } + } + } +} + +void Patcher::checkIfDone() { + if(remainingPatches <= 0) { + for (auto patch : patchQueue) { + processPatch(patch); + } + + patchQueue.clear(); + + dialog->hide(); + + emit done(); + } +} + +void Patcher::processPatch(QueuedPatch patch) { + auto data_path = baseDirectory.toStdString(); + + physis_patch_process(data_path.c_str(), patch.path.toStdString().c_str()); + + QString verFilePath; + if(isBoot) { + verFilePath = baseDirectory + "/ffxivboot.ver"; + } else { + if(patch.repository == "game") { + verFilePath = baseDirectory + "/ffxivgame.ver"; + } else { + verFilePath = baseDirectory + "/sqpack/" + patch.repository + "/" + patch.repository + ".ver"; + } + } + + QFile verFile(verFilePath); + verFile.open(QIODevice::WriteOnly | QIODevice::Text); + verFile.write(patch.version.toUtf8()); + verFile.close(); +} diff --git a/launcher/core/src/squareboot.cpp b/launcher/core/src/squareboot.cpp index 068ecfd..ead50be 100644 --- a/launcher/core/src/squareboot.cpp +++ b/launcher/core/src/squareboot.cpp @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include #include @@ -17,9 +17,12 @@ SquareBoot::SquareBoot(LauncherCore& window, SquareLauncher& launcher) : window( } void SquareBoot::bootCheck(const LoginInformation& info) { - dialog = new QProgressDialog(); - dialog->setLabelText("Checking the FINAL FANTASY XIV Updater/Launcher version."); - dialog->show(); + patcher = new Patcher(true, info.settings->gamePath + "/boot"); + connect(patcher, &Patcher::done, [=, &info] { + window.readGameVersion(); + + launcher.getStored(info); + }); QUrlQuery query; query.addQueryItem("time", QDateTime::currentDateTimeUtc().toString("yyyy-MM-dd-HH-mm")); @@ -43,63 +46,7 @@ void SquareBoot::bootCheck(const LoginInformation& info) { connect(reply, &QNetworkReply::finished, [=, &info] { const QString response = reply->readAll(); - if(response.isEmpty()) { - dialog->hide(); - - launcher.getStored(info); - } else { - dialog->setLabelText("Updating the FINAL FANTASY XIV Updater/Launcher version."); - - // TODO: move this out into a dedicated function, we need to use this for regular game patches later on - // TODO: create a nice progress window like ffxivboot has - // TODO: improve flow when updating boot, maybe do at launch ala official launcher? - const QStringList parts = response.split(QRegExp("\n|\r\n|\r")); - - // patch list starts at line 5 - for(int i = 5; i < parts.size() - 2; i++) { - const QStringList patchParts = parts[i].split("\t"); - - const int length = patchParts[0].toInt(); - const int version = patchParts[4].toInt(); - const int hashType = patchParts[5].toInt(); - - QString name = patchParts[4]; - QString url = patchParts[5]; - - // TODO: show bytes recieved/total in the progress window, and speed - dialog->setLabelText("Updating the FINAL FANTASY XIV Updater/Launcher version.\nDownloading ffxivboot - " + name); - dialog->setMinimum(0); - dialog->setMaximum(length); - - QNetworkRequest patchRequest(url); - auto patchReply = window.mgr->get(patchRequest); - connect(patchReply, &QNetworkReply::downloadProgress, [=](int recieved, int total) { - dialog->setValue(recieved); - }); - - connect(patchReply, &QNetworkReply::finished, [=, &info] { - const QString dataDir = - QStandardPaths::writableLocation(QStandardPaths::TempLocation); - - QFile file(dataDir + "/" + name + ".patch"); - file.open(QIODevice::WriteOnly); - file.write(patchReply->readAll()); - file.close(); - - // TODO: we really a dedicated .ver writing/reading class - QFile verFile(info.settings->gamePath + "/boot/ffxivboot.ver"); - verFile.open(QIODevice::WriteOnly | QIODevice::Text); - verFile.write(name.toUtf8()); - verFile.close(); - - processPatch((dataDir + "/" + name + ".patch").toStdString(), (info.settings->gamePath + "/boot").toStdString()); - - info.settings->bootVersion = name; - - launcher.getStored(info); - }); - } - } + patcher->processPatchList(*window.mgr, response); }); } diff --git a/launcher/core/src/squarelauncher.cpp b/launcher/core/src/squarelauncher.cpp index 77e3f55..25bd944 100644 --- a/launcher/core/src/squarelauncher.cpp +++ b/launcher/core/src/squarelauncher.cpp @@ -183,17 +183,26 @@ void SquareLauncher::registerSession(const LoginInformation& info) { auto reply = window.mgr->post(request, report.toUtf8()); connect(reply, &QNetworkReply::finished, [=, &info] { if(reply->rawHeaderList().contains("X-Patch-Unique-Id")) { - auth.SID = reply->rawHeader("X-Patch-Unique-Id"); + QString body = reply->readAll(); + + patcher = new Patcher(false, info.settings->gamePath + "/game"); + connect(patcher, &Patcher::done, [=, &info] { + window.readGameVersion(); + + auth.SID = reply->rawHeader("X-Patch-Unique-Id"); #ifdef ENABLE_WATCHDOG - if(info.settings->enableWatchdog) { - window.watchdog->launchGame(*info.settings, auth); - } else { - window.launchGame(*info.settings, auth); - } + if(info.settings->enableWatchdog) { + window.watchdog->launchGame(*info.settings, auth); + } else { + window.launchGame(*info.settings, auth); + } #else - window.launchGame(*info.settings, auth); + window.launchGame(*info.settings, auth); #endif + }); + + patcher->processPatchList(*window.mgr, body); } else { auto messageBox = new QMessageBox(QMessageBox::Icon::Critical, "Failed to Login", "Failed the anti-tamper check. Please restore your game to the original state or update the game."); window.addUpdateButtons(*info.settings, *messageBox);