2023-08-05 22:14:05 -04:00
|
|
|
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
2022-07-20 18:00:42 -04:00
|
|
|
#include "patcher.h"
|
2023-07-30 08:49:34 -04:00
|
|
|
|
2023-07-30 14:56:24 -04:00
|
|
|
#include <KLocalizedString>
|
2022-08-15 11:14:37 -04:00
|
|
|
#include <QDir>
|
2022-07-20 18:00:42 -04:00
|
|
|
#include <QFile>
|
|
|
|
#include <QNetworkReply>
|
|
|
|
#include <QNetworkRequest>
|
|
|
|
#include <QStandardPaths>
|
2023-09-16 20:12:42 -04:00
|
|
|
#include <QtConcurrent>
|
2022-07-20 18:00:42 -04:00
|
|
|
#include <physis.hpp>
|
2023-09-16 20:12:42 -04:00
|
|
|
#include <qcorofuture.h>
|
2022-10-13 13:20:09 -04:00
|
|
|
#include <utility>
|
2022-07-20 18:00:42 -04:00
|
|
|
|
2023-07-30 14:56:24 -04:00
|
|
|
#include "launchercore.h"
|
|
|
|
|
|
|
|
Patcher::Patcher(LauncherCore &launcher, QString baseDirectory, BootData *boot_data, QObject *parent)
|
2023-07-30 08:49:34 -04:00
|
|
|
: QObject(parent)
|
|
|
|
, baseDirectory(std::move(baseDirectory))
|
|
|
|
, boot_data(boot_data)
|
2023-07-30 14:56:24 -04:00
|
|
|
, m_launcher(launcher)
|
2023-07-30 08:49:34 -04:00
|
|
|
{
|
2023-08-18 14:52:06 -04:00
|
|
|
setupDirectories();
|
|
|
|
|
2023-07-30 14:56:24 -04:00
|
|
|
Q_EMIT m_launcher.stageChanged(i18n("Checking the FINAL FANTASY XIV Updater/Launcher version."));
|
2022-08-09 22:44:10 -04:00
|
|
|
}
|
|
|
|
|
2023-07-30 14:56:24 -04:00
|
|
|
Patcher::Patcher(LauncherCore &launcher, QString baseDirectory, GameData *game_data, QObject *parent)
|
2023-07-30 08:49:34 -04:00
|
|
|
: QObject(parent)
|
|
|
|
, baseDirectory(std::move(baseDirectory))
|
|
|
|
, game_data(game_data)
|
2023-07-30 14:56:24 -04:00
|
|
|
, m_launcher(launcher)
|
2023-07-30 08:49:34 -04:00
|
|
|
{
|
2023-08-18 14:52:06 -04:00
|
|
|
setupDirectories();
|
|
|
|
|
2023-07-30 14:56:24 -04:00
|
|
|
Q_EMIT m_launcher.stageChanged(i18n("Checking the FINAL FANTASY XIV Game version."));
|
2022-07-20 18:00:42 -04:00
|
|
|
}
|
|
|
|
|
2023-09-16 20:12:42 -04:00
|
|
|
QCoro::Task<> Patcher::patch(QNetworkAccessManager &mgr, const QString &patchList)
|
2023-07-30 08:49:34 -04:00
|
|
|
{
|
2022-08-15 11:14:37 -04:00
|
|
|
if (patchList.isEmpty()) {
|
2023-09-16 20:12:42 -04:00
|
|
|
co_return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isBoot()) {
|
|
|
|
Q_EMIT m_launcher.stageIndeterminate();
|
|
|
|
Q_EMIT m_launcher.stageChanged(i18n("Checking the FINAL FANTASY XIV Update/Launcher version."));
|
2022-07-20 18:00:42 -04:00
|
|
|
} else {
|
2023-09-16 20:12:42 -04:00
|
|
|
Q_EMIT m_launcher.stageIndeterminate();
|
|
|
|
Q_EMIT m_launcher.stageChanged(i18n("Checking the FINAL FANTASY XIV Game version."));
|
|
|
|
}
|
2022-07-20 18:00:42 -04:00
|
|
|
|
2023-09-16 20:12:42 -04:00
|
|
|
const QStringList parts = patchList.split("\r\n");
|
2022-07-20 18:00:42 -04:00
|
|
|
|
2023-09-16 20:12:42 -04:00
|
|
|
remainingPatches = parts.size() - 7;
|
|
|
|
patchQueue.resize(remainingPatches);
|
2023-07-30 14:56:24 -04:00
|
|
|
|
2023-09-16 20:12:42 -04:00
|
|
|
QFutureSynchronizer<void> synchronizer;
|
2022-07-20 18:00:42 -04:00
|
|
|
|
2023-09-16 20:12:42 -04:00
|
|
|
int patchIndex = 0;
|
2022-07-20 18:00:42 -04:00
|
|
|
|
2023-09-16 20:12:42 -04:00
|
|
|
for (int i = 5; i < parts.size() - 2; i++) {
|
2023-09-16 20:14:37 -04:00
|
|
|
const QStringList patchParts = parts[i].split(QLatin1Char('\t'));
|
2022-07-20 18:00:42 -04:00
|
|
|
|
2023-09-16 20:12:42 -04:00
|
|
|
const int length = patchParts[0].toInt();
|
|
|
|
int ourIndex = patchIndex++;
|
2022-07-20 18:00:42 -04:00
|
|
|
|
2023-09-16 20:12:42 -04:00
|
|
|
const QString version = patchParts[4];
|
|
|
|
const long hashBlockSize = patchParts.size() == 9 ? patchParts[6].toLong() : 0;
|
2022-11-17 12:22:56 -05:00
|
|
|
|
2023-09-16 20:12:42 -04:00
|
|
|
const QString name = version;
|
2023-09-16 20:14:37 -04:00
|
|
|
const QStringList hashes = patchParts.size() == 9 ? (patchParts[7].split(QLatin1Char(','))) : QStringList();
|
2023-09-16 20:12:42 -04:00
|
|
|
const QString url = patchParts[patchParts.size() == 9 ? 8 : 5];
|
|
|
|
const QString filename = QStringLiteral("%1.patch").arg(name);
|
2022-07-20 18:00:42 -04:00
|
|
|
|
2023-09-16 20:14:37 -04:00
|
|
|
auto url_parts = url.split(QLatin1Char('/'));
|
2023-09-16 20:12:42 -04:00
|
|
|
const QString repository = url_parts[url_parts.size() - 3];
|
2022-07-20 18:00:42 -04:00
|
|
|
|
2023-09-16 20:12:42 -04:00
|
|
|
const QDir repositoryDir = patchesDir.absoluteFilePath(repository);
|
2022-07-20 18:00:42 -04:00
|
|
|
|
2023-09-16 20:12:42 -04:00
|
|
|
if (!QDir().exists(repositoryDir.absolutePath()))
|
|
|
|
QDir().mkpath(repositoryDir.absolutePath());
|
2022-11-17 12:22:56 -05:00
|
|
|
|
2023-09-16 20:12:42 -04:00
|
|
|
const QString patchPath = repositoryDir.absoluteFilePath(filename);
|
|
|
|
if (!QFile::exists(patchPath)) {
|
|
|
|
auto patchReply = mgr.get(QNetworkRequest(url));
|
2023-07-30 14:56:24 -04:00
|
|
|
|
2023-09-16 20:14:37 -04:00
|
|
|
connect(patchReply, &QNetworkReply::downloadProgress, [this, repository, version, length](int received, int total) {
|
2023-09-16 20:12:42 -04:00
|
|
|
Q_UNUSED(total)
|
2023-07-30 14:56:24 -04:00
|
|
|
|
2023-09-16 20:12:42 -04:00
|
|
|
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));
|
|
|
|
}
|
2022-07-20 18:00:42 -04:00
|
|
|
|
2023-09-16 20:14:37 -04:00
|
|
|
Q_EMIT m_launcher.stageDeterminate(0, length, received);
|
2023-09-16 20:12:42 -04:00
|
|
|
});
|
2022-07-20 18:00:42 -04:00
|
|
|
|
2023-09-16 20:12:42 -04:00
|
|
|
synchronizer.addFuture(QtFuture::connect(patchReply, &QNetworkReply::finished)
|
|
|
|
.then([this, patchPath, patchReply, ourIndex, name, repository, version, hashes, hashBlockSize, length] {
|
|
|
|
QFile file(patchPath);
|
|
|
|
file.open(QIODevice::WriteOnly);
|
|
|
|
file.write(patchReply->readAll());
|
|
|
|
file.close();
|
2022-07-20 18:00:42 -04:00
|
|
|
|
2023-09-16 20:12:42 -04:00
|
|
|
patchQueue[ourIndex] = {name, repository, version, patchPath, hashes, hashBlockSize, length};
|
|
|
|
}));
|
|
|
|
} else {
|
|
|
|
qDebug() << "Found existing patch: " << name;
|
2022-11-17 12:22:56 -05:00
|
|
|
|
2023-09-16 20:12:42 -04:00
|
|
|
patchQueue[ourIndex] = {name, repository, version, patchPath, hashes, hashBlockSize, length};
|
2022-07-20 18:00:42 -04:00
|
|
|
|
2023-09-16 20:12:42 -04:00
|
|
|
synchronizer.addFuture({});
|
2022-07-20 18:00:42 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-16 20:12:42 -04:00
|
|
|
co_await QtConcurrent::run([&synchronizer] {
|
|
|
|
synchronizer.waitForFinished();
|
|
|
|
});
|
2022-07-20 18:00:42 -04:00
|
|
|
|
2023-09-16 20:12:42 -04:00
|
|
|
// This must happen synchronously
|
|
|
|
size_t i = 0;
|
|
|
|
for (const auto &patch : patchQueue) {
|
|
|
|
Q_EMIT m_launcher.stageDeterminate(0, patchQueue.size(), i++);
|
|
|
|
processPatch(patch);
|
2022-07-20 18:00:42 -04:00
|
|
|
}
|
2023-09-16 20:12:42 -04:00
|
|
|
|
|
|
|
co_return;
|
2022-07-20 18:00:42 -04:00
|
|
|
}
|
|
|
|
|
2023-07-30 08:49:34 -04:00
|
|
|
void Patcher::processPatch(const QueuedPatch &patch)
|
|
|
|
{
|
2023-07-30 14:56:24 -04:00
|
|
|
// 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]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-15 11:14:37 -04:00
|
|
|
if (isBoot()) {
|
2022-08-09 22:44:10 -04:00
|
|
|
physis_bootdata_apply_patch(boot_data, patch.path.toStdString().c_str());
|
|
|
|
} else {
|
|
|
|
physis_gamedata_apply_patch(game_data, patch.path.toStdString().c_str());
|
|
|
|
}
|
2022-07-20 18:00:42 -04:00
|
|
|
|
|
|
|
QString verFilePath;
|
2022-08-15 11:14:37 -04:00
|
|
|
if (isBoot()) {
|
2023-09-16 20:14:37 -04:00
|
|
|
verFilePath = baseDirectory + QStringLiteral("/ffxivboot.ver");
|
2022-07-20 18:00:42 -04:00
|
|
|
} else {
|
2023-09-16 20:14:37 -04:00
|
|
|
if (patch.repository == QLatin1String("game")) {
|
|
|
|
verFilePath = baseDirectory + QStringLiteral("/ffxivgame.ver");
|
2022-07-20 18:00:42 -04:00
|
|
|
} else {
|
2023-09-16 20:14:37 -04:00
|
|
|
verFilePath = baseDirectory + QStringLiteral("/sqpack/") + patch.repository + QStringLiteral("/") + patch.repository + QStringLiteral(".ver");
|
2022-07-20 18:00:42 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QFile verFile(verFilePath);
|
|
|
|
verFile.open(QIODevice::WriteOnly | QIODevice::Text);
|
|
|
|
verFile.write(patch.version.toUtf8());
|
|
|
|
verFile.close();
|
|
|
|
}
|
2023-08-18 14:52:06 -04:00
|
|
|
|
|
|
|
void Patcher::setupDirectories()
|
|
|
|
{
|
|
|
|
QDir dataDir;
|
|
|
|
if (m_launcher.keepPatches()) {
|
|
|
|
dataDir.setPath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
|
|
|
|
} else {
|
|
|
|
dataDir.setPath(QStandardPaths::writableLocation(QStandardPaths::TempLocation));
|
|
|
|
}
|
|
|
|
|
2023-09-16 20:14:37 -04:00
|
|
|
patchesDir.setPath(dataDir.absoluteFilePath(QStringLiteral("patches")));
|
2023-08-18 14:52:06 -04:00
|
|
|
}
|