1
Fork 0
mirror of https://github.com/redstrate/Astra.git synced 2025-04-21 04:07:46 +00:00

Adapt patcher to the new Kirigami interface, and perform hash checks

This commit is contained in:
Joshua Goins 2023-07-30 14:56:24 -04:00
parent dd1f22ab31
commit aa2a50b50b
6 changed files with 102 additions and 57 deletions

View file

@ -149,6 +149,8 @@ signals:
void showNewsListChanged(); void showNewsListChanged();
void loginError(QString message); void loginError(QString message);
void stageChanged(QString message); void stageChanged(QString message);
void stageIndeterminate();
void stageDeterminate(int min, int max, int value);
void newsChanged(); void newsChanged();
private: private:

View file

@ -4,14 +4,16 @@
#include <QString> #include <QString>
#include <physis.hpp> #include <physis.hpp>
class LauncherCore;
// General-purpose patcher routine. It opens a nice dialog box, handles downloading // General-purpose patcher routine. It opens a nice dialog box, handles downloading
// and processing patches. // and processing patches.
class Patcher : public QObject class Patcher : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
Patcher(QString baseDirectory, GameData *game_data, QObject *parent = nullptr); Patcher(LauncherCore &launcher, QString baseDirectory, GameData *game_data, QObject *parent = nullptr);
Patcher(QString baseDirectory, BootData *game_data, QObject *parent = nullptr); Patcher(LauncherCore &launcher, QString baseDirectory, BootData *game_data, QObject *parent = nullptr);
void processPatchList(QNetworkAccessManager &mgr, const QString &patchList); void processPatchList(QNetworkAccessManager &mgr, const QString &patchList);
@ -28,6 +30,9 @@ private:
struct QueuedPatch { struct QueuedPatch {
QString name, repository, version, path; QString name, repository, version, path;
QStringList hashes;
long hashBlockSize;
long length;
}; };
void processPatch(const QueuedPatch &patch); void processPatch(const QueuedPatch &patch);
@ -39,4 +44,6 @@ private:
GameData *game_data = nullptr; GameData *game_data = nullptr;
int remainingPatches = -1; int remainingPatches = -1;
LauncherCore &m_launcher;
}; };

View file

@ -1,5 +1,6 @@
#include "patcher.h" #include "patcher.h"
#include <KLocalizedString>
#include <QDir> #include <QDir>
#include <QFile> #include <QFile>
#include <QNetworkReply> #include <QNetworkReply>
@ -9,78 +10,63 @@
#include <physis.hpp> #include <physis.hpp>
#include <utility> #include <utility>
Patcher::Patcher(QString baseDirectory, BootData *boot_data, QObject *parent) #include "launchercore.h"
Patcher::Patcher(LauncherCore &launcher, QString baseDirectory, BootData *boot_data, QObject *parent)
: QObject(parent) : QObject(parent)
, baseDirectory(std::move(baseDirectory)) , baseDirectory(std::move(baseDirectory))
, boot_data(boot_data) , boot_data(boot_data)
, m_launcher(launcher)
{ {
/*dialog = new QProgressDialog(); Q_EMIT m_launcher.stageChanged(i18n("Checking the FINAL FANTASY XIV Updater/Launcher version."));
dialog->setLabelText("Checking the FINAL FANTASY XIV Updater/Launcher version.");
dialog->show();*/
} }
Patcher::Patcher(QString baseDirectory, GameData *game_data, QObject *parent) Patcher::Patcher(LauncherCore &launcher, QString baseDirectory, GameData *game_data, QObject *parent)
: QObject(parent) : QObject(parent)
, baseDirectory(std::move(baseDirectory)) , baseDirectory(std::move(baseDirectory))
, game_data(game_data) , game_data(game_data)
, m_launcher(launcher)
{ {
/*dialog = new QProgressDialog(); Q_EMIT m_launcher.stageChanged(i18n("Checking the FINAL FANTASY XIV Game version."));
dialog->setLabelText("Checking the FINAL FANTASY XIV Game version.");
dialog->show();*/
} }
void Patcher::processPatchList(QNetworkAccessManager &mgr, const QString &patchList) void Patcher::processPatchList(QNetworkAccessManager &mgr, const QString &patchList)
{ {
if (patchList.isEmpty()) { if (patchList.isEmpty()) {
// dialog->hide();
emit done(); emit done();
} else { } else {
if (isBoot()) { if (isBoot()) {
// dialog->setLabelText("Updating the FINAL FANTASY XIV Updater/Launcher version."); Q_EMIT m_launcher.stageIndeterminate();
Q_EMIT m_launcher.stageChanged(i18n("Checking the FINAL FANTASY XIV Update/Launcher version."));
} else { } else {
// dialog->setLabelText("Updating the FINAL FANTASY XIV Game version."); Q_EMIT m_launcher.stageIndeterminate();
Q_EMIT m_launcher.stageChanged(i18n("Checking the FINAL FANTASY XIV Game version."));
} }
const QStringList parts = patchList.split("\r\n"); const QStringList parts = patchList.split("\r\n");
remainingPatches = parts.size() - 7; remainingPatches = parts.size() - 7;
patchQueue.resize(remainingPatches);
int patchIndex = 0;
for (int i = 5; i < parts.size() - 2; i++) { for (int i = 5; i < parts.size() - 2; i++) {
const QStringList patchParts = parts[i].split("\t"); const QStringList patchParts = parts[i].split("\t");
const int length = patchParts[0].toInt(); const int length = patchParts[0].toInt();
Q_UNUSED(length) int ourIndex = patchIndex++;
QString name, url, version, repository; const QString version = patchParts[4];
const long hashBlockSize = patchParts.size() == 9 ? patchParts[6].toLong() : 0;
if (isBoot()) { const QString name = version;
name = patchParts[4]; const QStringList hashes = patchParts.size() == 9 ? (patchParts[7].split(',')) : QStringList();
url = patchParts[5]; const QString url = patchParts[patchParts.size() == 9 ? 8 : 5];
version = name;
} else {
url = patchParts[8];
version = patchParts[4];
name = url.split('/').last().remove(".patch");
}
qDebug() << "Parsed patch name: " << name; qDebug() << "Parsed patch name: " << name;
auto url_parts = url.split('/'); auto url_parts = url.split('/');
repository = url_parts[url_parts.size() - 3]; const QString 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; const QString patchesDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/patches/" + repository;
@ -92,13 +78,21 @@ void Patcher::processPatchList(QNetworkAccessManager &mgr, const QString &patchL
QNetworkRequest patchRequest(url); QNetworkRequest patchRequest(url);
auto patchReply = mgr.get(patchRequest); auto patchReply = mgr.get(patchRequest);
connect(patchReply, &QNetworkReply::downloadProgress, [=](int recieved, int total) { connect(patchReply, &QNetworkReply::downloadProgress, [this, repository, version, length](int recieved, int total) {
Q_UNUSED(recieved)
Q_UNUSED(total) Q_UNUSED(total)
// dialog->setValue(recieved);
if (isBoot()) {
Q_EMIT m_launcher.stageChanged(i18n("Updating the FINAL FANTASY XIV Updater/Launcher version.\nDownloading ffxivboot - %1", version));
} else {
Q_EMIT m_launcher.stageChanged(i18n("Updating the FINAL FANTASY XIV Game version.\nDownloading %1 - %2", repository, version));
}
Q_EMIT m_launcher.stageDeterminate(0, length, recieved);
}); });
connect(patchReply, &QNetworkReply::finished, [this, patchesDir, name, patchReply, repository, version] { connect(patchReply,
&QNetworkReply::finished,
[this, ourIndex, patchesDir, name, patchReply, repository, version, hashes, hashBlockSize, length] {
QFile file(patchesDir + "/" + name + ".patch"); QFile file(patchesDir + "/" + name + ".patch");
file.open(QIODevice::WriteOnly); file.open(QIODevice::WriteOnly);
file.write(patchReply->readAll()); file.write(patchReply->readAll());
@ -106,7 +100,7 @@ void Patcher::processPatchList(QNetworkAccessManager &mgr, const QString &patchL
auto patch_path = patchesDir + "/" + name + ".patch"; auto patch_path = patchesDir + "/" + name + ".patch";
patchQueue.push_back({name, repository, version, patch_path}); patchQueue[ourIndex] = {name, repository, version, patch_path, hashes, hashBlockSize, length};
remainingPatches--; remainingPatches--;
checkIfDone(); checkIfDone();
@ -114,7 +108,7 @@ void Patcher::processPatchList(QNetworkAccessManager &mgr, const QString &patchL
} else { } else {
qDebug() << "Found existing patch: " << name; qDebug() << "Found existing patch: " << name;
patchQueue.push_back({name, repository, version, patchesDir + "/" + name + ".patch"}); patchQueue[ourIndex] = {name, repository, version, patchesDir + "/" + name + ".patch", hashes, hashBlockSize, length};
remainingPatches--; remainingPatches--;
checkIfDone(); checkIfDone();
@ -126,20 +120,51 @@ void Patcher::processPatchList(QNetworkAccessManager &mgr, const QString &patchL
void Patcher::checkIfDone() void Patcher::checkIfDone()
{ {
if (remainingPatches <= 0) { if (remainingPatches <= 0) {
if (isBoot()) {
Q_EMIT m_launcher.stageChanged(i18n("Applying updates to the FINAL FANTASY XIV Updater/Launcher."));
} else {
Q_EMIT m_launcher.stageChanged(i18n("Applying updates to the FINAL FANTASY XIV Game."));
}
int i = 0;
for (const auto &patch : patchQueue) { for (const auto &patch : patchQueue) {
Q_EMIT m_launcher.stageDeterminate(0, patchQueue.size(), i++);
processPatch(patch); processPatch(patch);
} }
patchQueue.clear(); patchQueue.clear();
// dialog->hide();
emit done(); emit done();
} }
} }
void Patcher::processPatch(const QueuedPatch &patch) void Patcher::processPatch(const QueuedPatch &patch)
{ {
// Perform hash checking
if (!patch.hashes.isEmpty()) {
auto f = QFile(patch.path);
f.open(QIODevice::ReadOnly);
Q_ASSERT(patch.length == f.size());
const int parts = std::ceil(static_cast<double>(patch.length) / static_cast<double>(patch.hashBlockSize));
QByteArray block;
block.resize(patch.hashBlockSize);
for (int i = 0; i < parts; i++) {
const auto read = f.read(patch.hashBlockSize);
if (read.length() <= patch.hashBlockSize) {
block = read;
}
QCryptographicHash hash(QCryptographicHash::Sha1);
hash.addData(block);
Q_ASSERT(hash.result().toHex() == patch.hashes[i]);
}
}
if (isBoot()) { if (isBoot()) {
physis_bootdata_apply_patch(boot_data, patch.path.toStdString().c_str()); physis_bootdata_apply_patch(boot_data, patch.path.toStdString().c_str());
} else { } else {

View file

@ -22,7 +22,7 @@ void SquareBoot::bootCheck(const LoginInformation &info)
{ {
Q_EMIT window.stageChanged(i18n("Checking for launcher updates...")); Q_EMIT window.stageChanged(i18n("Checking for launcher updates..."));
patcher = new Patcher(info.profile->gamePath() + "/boot", info.profile->bootData, this); patcher = new Patcher(window, info.profile->gamePath() + "/boot", info.profile->bootData, this);
connect(patcher, &Patcher::done, [this, &info] { connect(patcher, &Patcher::done, [this, &info] {
info.profile->readGameVersion(); info.profile->readGameVersion();

View file

@ -171,7 +171,7 @@ void SquareLauncher::registerSession(const LoginInformation &info)
if (reply->rawHeaderList().contains("X-Patch-Unique-Id")) { if (reply->rawHeaderList().contains("X-Patch-Unique-Id")) {
QString body = reply->readAll(); QString body = reply->readAll();
patcher = new Patcher(info.profile->gamePath() + "/game", info.profile->gameData, this); patcher = new Patcher(window, info.profile->gamePath() + "/game", info.profile->gameData, this);
connect(patcher, &Patcher::done, [this, &info, reply] { connect(patcher, &Patcher::done, [this, &info, reply] {
info.profile->readGameVersion(); info.profile->readGameVersion();

View file

@ -37,6 +37,17 @@ Kirigami.Page {
placeholder.text = message placeholder.text = message
} }
function onStageIndeterminate() {
placeholder.determinate = false
}
function onStageDeterminate(min, max, value) {
placeholder.determinate = true
placeholder.progressBar.value = value
placeholder.progressBar.from = min
placeholder.progressBar.to = max
}
function onLoginError(message) { function onLoginError(message) {
errorDialog.subtitle = message errorDialog.subtitle = message
errorDialog.open() errorDialog.open()