1
Fork 0
mirror of https://github.com/redstrate/Astra.git synced 2025-04-20 03:37:47 +00:00

Port to Physis' patchlist parsing, remove our own

Also start checking the available space before downloading patches to
avoid really bad situations. This will be expanded in the future.
This commit is contained in:
Joshua Goins 2024-08-22 20:42:35 -04:00
parent 732b1cdada
commit 0775a463e8
11 changed files with 54 additions and 204 deletions

View file

@ -7,12 +7,6 @@ ecm_add_test(accountmanagertest.cpp
NAME_PREFIX "astra-"
)
ecm_add_test(patchlisttest.cpp
TEST_NAME patchlisttest
LINK_LIBRARIES astra_static Qt::Test
NAME_PREFIX "astra-"
)
ecm_add_test(processwatchertest.cpp
TEST_NAME processwatchertest
LINK_LIBRARIES astra_static Qt::Test

View file

@ -1,94 +0,0 @@
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include <QtTest/QtTest>
#include "patchlist.h"
class PatchListTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testBootPatchList()
{
const QString testCase{
QStringLiteral("--477D80B1_38BC_41d4_8B48_5273ADB89CAC\r\nContent-Type: application/octet-stream\r\nContent-Location: "
"ffxivpatch/2b5cbc63/metainfo/D2023.04.28.0000.0001.http\r\nX-Patch-Length: "
"22221335\r\n\r\n22221335\t69674819\t19\t18\t2023.09.14.0000.0001\thttp://patch-dl.ffxiv.com/boot/2b5cbc63/"
"D2023.09.14.0000.0001.patch\r\n--477D80B1_38BC_41d4_8B48_5273ADB89CAC--\r\n")};
const PatchList patchList{testCase};
QCOMPARE(patchList.patches().size(), 1);
QCOMPARE(patchList.patches()[0].version, QStringLiteral("2023.09.14.0000.0001"));
QCOMPARE(patchList.patches()[0].url, QStringLiteral("http://patch-dl.ffxiv.com/boot/2b5cbc63/D2023.09.14.0000.0001.patch"));
}
void testGamePatchList()
{
const QString testCase{QStringLiteral(
"--477D80B1_38BC_41d4_8B48_5273ADB89CAC\r\nContent-Type: application/octet-stream\r\nContent-Location: "
"ffxivpatch/4e9a232b/metainfo/2023.07.26.0000.0000.http\r\nX-Patch-Length: "
"1664916486\r\n\r\n1479062470\t44145529682\t71\t11\t2023.09.15.0000.0000\tsha1\t50000000\t1c66becde2a8cf26a99d0fc7c06f15f8bab2d87c,"
"950725418366c965d824228bf20f0496f81e0b9a,cabef48f7bf00fbf18b72843bdae2f61582ad264,53608de567b52f5fdb43fdb8b623156317e26704,"
"f0bc06cabf9ff6490f36114b25f62619d594dbe8,3c5e4b962cd8445bd9ee29011ecdb331d108abd8,88e1a2a322f09de3dc28173d4130a2829950d4e0,"
"1040667917dc99b9215dfccff0e458c2e8a724a8,149c7e20e9e3e376377a130e0526b35fd7f43df2,1bb4e33807355cdf46af93ce828b6e145a9a8795,"
"a79daff43db488f087da8e22bb4c21fd3a390f3c,6b04fadb656d467fb8318eba1c7f5ee8f030d967,a6641e1c894db961a49b70fda2b0d6d87be487a7,"
"edf419de49f42ef19bd6814f8184b35a25e9e977,c1525c4df6001b66b575e2891db0284dc3a16566,01b7628095b07fa3c9c1aed2d66d32d118020321,"
"991b137ea0ebb11bd668f82149bc2392a4cbcf52,ad3f74d4fca143a6cf507fc859544a4bcd501d85,936a0f1711e273519cae6b2da0d8b435fe6aa020,"
"023f19d8d8b3ecaaf865e3170e8243dd437a384c,2d9e934de152956961a849e81912ca8d848265ca,8e32f9aa76c95c60a9dbe0967aee5792b812d5ec,"
"dee052b9aa1cc8863efd61afc63ac3c2d56f9acc,fa81225aea53fa13a9bae1e8e02dea07de6d7052,59b24693b1b62ea1660bc6f96a61f7d41b3f7878,"
"349b691db1853f6c0120a8e66093c763ba6e3671,4561eb6f954d80cdb1ece3cc4d58cbd864bf2b50,de94175c4db39a11d5334aefc7a99434eea8e4f9,"
"55dd7215f24441d6e47d1f9b32cebdb041f2157f,2ca09db645cfeefa41a04251dfcb13587418347a\thttp://patch-dl.ffxiv.com/game/4e9a232b/"
"D2023.09.15.0000.0000.patch\r\n61259063\t44145955874\t71\t11\t2023.09.21.0000.0001\tsha1\t50000000\t88c9bbfe2af4eea7b56384baeeafd59afb47ddeb,"
"095c26e87b4d25505845515c389dd22dd429ea7e\thttp://patch-dl.ffxiv.com/game/4e9a232b/"
"D2023.09.21.0000.0001.patch\r\n63776300\t44146911186\t71\t11\t2023.09.23.0000.0001\tsha1\t50000000\tc8fc6910be12d10b39e4e6ae980d4c219cfe56a1,"
"5c0199b7147a47f620a2b50654a87c9b0cbcf43b\thttp://patch-dl.ffxiv.com/game/4e9a232b/"
"D2023.09.23.0000.0001.patch\r\n32384649\t44146977234\t71\t11\t2023.09.26.0000."
"0000\tsha1\t50000000\t519a5e46edb67ba6edb9871df5eb3991276da254\thttp://patch-dl.ffxiv.com/game/4e9a232b/"
"D2023.09.26.0000.0000.patch\r\n28434004\t44147154898\t71\t11\t2023.09.28.0000."
"0000\tsha1\t50000000\ta08e8a071b615b0babc45a09979ab6bc70affe14\thttp://patch-dl.ffxiv.com/game/4e9a232b/"
"D2023.09.28.0000.0000.patch\r\n82378953\t5854598228\t30\t4\t2023.07.26.0000.0001\tsha1\t50000000\t07d9fecb3975028fdf81166aa5a4cca48bc5a4b0,"
"c10677985f809df93a739ed7b244d90d37456353\thttp://patch-dl.ffxiv.com/game/ex1/6b936f08/"
"D2023.07.26.0000.0001.patch\r\n29384945\t5855426508\t30\t4\t2023.09.23.0000.0001\tsha1\t50000000\tcf4970957e846e6cacfdad521252de18afc7e29b\thttp:/"
"/patch-dl.ffxiv.com/game/ex1/6b936f08/"
"D2023.09.23.0000.0001.patch\r\n136864\t5855426508\t30\t4\t2023.09.26.0000.0000\tsha1\t50000000\t3ca5e0160e1cedb7a2801048408b247095f432ea\thttp://"
"patch-dl.ffxiv.com/game/ex1/6b936f08/"
"D2023.09.26.0000.0000.patch\r\n126288\t5855426508\t30\t4\t2023.09.28.0000.0000\tsha1\t50000000\t88d201defb32366004c88b236d03278f95d9b442\thttp://"
"patch-dl.ffxiv.com/game/ex1/6b936f08/"
"D2023.09.28.0000.0000.patch\r\n49352444\t7620831756\t24\t4\t2023.09.23.0000.0001\tsha1\t50000000\t2a05a452281d119241f222f4eae43266a22560fe\thttp:/"
"/patch-dl.ffxiv.com/game/ex2/f29a3eb2/"
"D2023.09.23.0000.0001.patch\r\n301600\t7620831756\t24\t4\t2023.09.26.0000.0000\tsha1\t50000000\t215de572fe51bca45f83e19d719f52220818bc39\thttp://"
"patch-dl.ffxiv.com/game/ex2/f29a3eb2/"
"D2023.09.26.0000.0000.patch\r\n385096\t7620831756\t24\t4\t2023.09.28.0000.0000\tsha1\t50000000\t427c3fd61f2ca79698ecd6dc34e3457f6d8c01cd\thttp://"
"patch-dl.ffxiv.com/game/ex2/f29a3eb2/"
"D2023.09.28.0000.0000.patch\r\n60799419\t9737248724\t26\t4\t2023.09.23.0000.0001\tsha1\t50000000\tab064df7aec0526e8306a78e51adbbba8b129c3f,"
"4e1bb58a987b3157c16f59821bc67b1464a301e5\thttp://patch-dl.ffxiv.com/game/ex3/859d0e24/"
"D2023.09.23.0000.0001.patch\r\n555712\t9737248724\t26\t4\t2023.09.26.0000.0000\tsha1\t50000000\tdb6b1f34b0b58ca0d0621ff6cebcc63e7eb692c5\thttp://"
"patch-dl.ffxiv.com/game/ex3/859d0e24/"
"D2023.09.26.0000.0000.patch\r\n579560\t9737248724\t26\t4\t2023.09.28.0000.0000\tsha1\t50000000\t67b5d62ee8202fe045c763deea38c136c5324195\thttp://"
"patch-dl.ffxiv.com/game/ex3/859d0e24/"
"D2023.09.28.0000.0000.patch\r\n867821466\t12469514712\t40\t4\t2023.09.15.0000.0001\tsha1\t50000000\t08f6164685b4363d719d09a8d0ef0910b48ec4a1,"
"05819ea5182885b59f0dfb981ecab159f46d7343,6ab6106ce4153cac5edb26ad0aabc6ba718ee500,3c552f54cc3c101d9f1786156c1cbd9880a7c02f,"
"e0d425f6032da1ceb60ff9ca14a10e5e89f23218,245402087bf6d535cb08bbc189647e8f56301722,a3a0630bb4ddd36b532be0e0a8982dbce1bb9053,"
"eca8a1394db1e84b9ec11a99bd970e6335326da5,40546e6d37cc5ea21d26c2e00a11f46a13d99a77,f41f312ad72ee65dc97645c8f7426c35970187ca,"
"e5a9966528ecab5a51059de3d5cd83a921c73a1c,c03855127d135d22c65e34e615eddbe6f38769e9,befab30a77c14743f53b20910b564bb6a97dfe86,"
"dcce8ea707f03606b583d51d947a4cf99b52635e,c4a33a8c51a047706b65887bed804ec6c2c29016,a17bc8bd8709c2a0c5725c134101132d3536e67d,"
"d2b277de55a65697d80cfe8ee46199a8d7482c30,6b97cc2862c6f8f5d279be5f28cc13ed011763e5\thttp://patch-dl.ffxiv.com/game/ex4/1bf99b87/"
"D2023.09.15.0000.0001.patch\r\n17717567\t12471821656\t40\t4\t2023.09.23.0000.0001\tsha1\t50000000\tda144d5c1c173ef1d98e4e7b558414ae53bcd392\thttp:"
"//patch-dl.ffxiv.com/game/ex4/1bf99b87/"
"D2023.09.23.0000.0001.patch\r\n3117253\t12471859944\t40\t4\t2023.09.26.0000.0000\tsha1\t50000000\t7acdab61e99d69ffa53e9136f65a1a1d3b33732b\thttp:/"
"/patch-dl.ffxiv.com/game/ex4/1bf99b87/"
"D2023.09.26.0000.0000.patch\r\n4676853\t12471859944\t40\t4\t2023.09.28.0000.0000\tsha1\t50000000\tce26ccb2115af612ccd4c42c1a27ef2ec925c81e\thttp:/"
"/patch-dl.ffxiv.com/game/ex4/1bf99b87/D2023.09.28.0000.0000.patch\r\n--477D80B1_38BC_41d4_8B48_5273ADB89CAC--\r\n")};
const PatchList patchList{testCase};
QCOMPARE(patchList.patches().size(), 19);
QCOMPARE(patchList.patches()[5].version, QStringLiteral("2023.07.26.0000.0001"));
QCOMPARE(patchList.patches()[5].url, QStringLiteral("http://patch-dl.ffxiv.com/game/ex1/6b936f08/D2023.07.26.0000.0001.patch"));
}
};
QTEST_MAIN(PatchListTest)
#include "patchlisttest.moc"

2
external/libphysis vendored

@ -1 +1 @@
Subproject commit 4dbc6917fb1b9f73ecd1ec98a8b17c13bf180273
Subproject commit 1862f3a81f93178f55bda4ee7f88f3476f7f0062

View file

@ -33,7 +33,6 @@ ecm_qt_declare_logging_category(astra_static
target_sources(astra_static PRIVATE
include/account.h
include/patchlist.h
include/processwatcher.h
include/profilemanager.h
include/profile.h
@ -67,7 +66,6 @@ target_sources(astra_static PRIVATE
src/launchercore.cpp
src/launchersettings.cpp
src/account.cpp
src/patchlist.cpp
src/processwatcher.cpp
src/profilemanager.cpp
src/profile.cpp

View file

@ -3,16 +3,14 @@
#pragma once
#include <QDir>
#include <QMutex>
#include <QNetworkAccessManager>
#include <QStorageInfo>
#include <QString>
#include <qcorotask.h>
#include <physis.hpp>
#include "patchlist.h"
class LauncherCore;
// General-purpose patcher routine. It opens a nice dialog box, handles downloading
@ -26,7 +24,7 @@ public:
Patcher(LauncherCore &launcher, const QString &baseDirectory, BootData &bootData, QObject *parent = nullptr);
~Patcher() override;
QCoro::Task<bool> patch(const PatchList &patchList);
QCoro::Task<bool> patch(const physis_PatchList &patchList);
private:
void setupDirectories();
@ -65,6 +63,7 @@ private:
QString m_baseDirectory;
BootData *m_bootData = nullptr;
GameData *m_gameData = nullptr;
QStorageInfo m_patchesDirStorageInfo;
int m_remainingPatches = -1;

View file

@ -1,29 +0,0 @@
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <QList>
#include <QString>
class Patch
{
public:
QString name, url, repository, version;
QList<QString> hashes;
long hashBlockSize = 0;
long length = 0;
};
class PatchList
{
public:
explicit PatchList(const QString &patchList);
[[nodiscard]] QList<Patch> patches() const;
[[nodiscard]] bool isEmpty() const;
private:
QList<Patch> m_patches;
};

View file

@ -15,4 +15,5 @@ void setSSL(QNetworkRequest &request);
QString readVersion(const QString &path);
void writeVersion(const QString &path, const QString &version);
bool isSteamDeck();
QString repositoryFromPatchUrl(const QString &url);
}

View file

@ -3,6 +3,7 @@
#include "patcher.h"
#include <KFormat>
#include <KLocalizedString>
#include <QDir>
#include <QFile>
@ -13,7 +14,6 @@
#include "astra_patcher_log.h"
#include "launchercore.h"
#include "patchlist.h"
#include "utility.h"
using namespace Qt::StringLiterals;
@ -49,50 +49,65 @@ Patcher::~Patcher()
m_launcher.m_isPatching = false;
}
QCoro::Task<bool> Patcher::patch(const PatchList &patchList)
QCoro::Task<bool> Patcher::patch(const physis_PatchList &patchList)
{
if (patchList.isEmpty()) {
if (patchList.num_entries == 0) {
co_return false;
}
const qint64 neededSpace = patchList.patch_length - m_patchesDirStorageInfo.bytesAvailable();
if (neededSpace > 0) {
KFormat format;
QString neededSpaceStr = format.formatByteSize(neededSpace);
Q_EMIT m_launcher.miscError(i18n("There is not enough space available on disk to update the game. You need %1 of free space.", neededSpaceStr));
co_return false;
}
Q_EMIT m_launcher.stageIndeterminate();
Q_EMIT m_launcher.stageChanged(i18n("Updating %1", getBaseString()));
m_remainingPatches = static_cast<int>(patchList.patches().size());
m_remainingPatches = patchList.num_entries;
m_patchQueue.resize(m_remainingPatches);
QFutureSynchronizer<void> synchronizer;
int patchIndex = 0;
for (auto &patch : patchList.patches()) {
for (int i = 0; i < patchList.num_entries; i++) {
const auto &patch = patchList.entries[i];
const int ourIndex = patchIndex++;
const QString filename = QStringLiteral("%1.patch").arg(patch.name);
const QString tempFilename = QStringLiteral("%1.patch~").arg(patch.name); // tilde afterwards to hide it easily
const QString filename = QStringLiteral("%1.patch").arg(QLatin1String(patch.version));
const QString tempFilename = QStringLiteral("%1.patch~").arg(QLatin1String(patch.version)); // tilde afterwards to hide it easily
const QDir repositoryDir = m_patchesDir.absoluteFilePath(patch.repository);
const QString repository = Utility::repositoryFromPatchUrl(QLatin1String(patch.url));
const QDir repositoryDir = m_patchesDir.absoluteFilePath(repository);
Utility::createPathIfNeeded(repositoryDir);
const QString patchPath = repositoryDir.absoluteFilePath(filename);
const QString tempPatchPath = repositoryDir.absoluteFilePath(tempFilename);
const QueuedPatch queuedPatch{.name = patch.name,
.repository = patch.repository,
.version = patch.version,
QStringList convertedHashes;
for (uint64_t i = 0; i < patch.hash_count; i++) {
convertedHashes.push_back(QLatin1String(patch.hashes[i]));
}
const QueuedPatch queuedPatch{.name = QLatin1String(patch.version),
.repository = repository,
.version = QLatin1String(patch.version),
.path = patchPath,
.hashes = patch.hashes,
.hashBlockSize = patch.hashBlockSize,
.hashes = convertedHashes,
.hashBlockSize = patch.hash_block_size,
.length = patch.length,
.isBoot = isBoot()};
qDebug(ASTRA_PATCHER) << "Adding a queued patch:";
qDebug(ASTRA_PATCHER) << "- Name:" << patch.name;
qDebug(ASTRA_PATCHER) << "- Repository or is boot:" << (isBoot() ? QStringLiteral("boot") : patch.repository);
qDebug(ASTRA_PATCHER) << "- Repository or is boot:" << (isBoot() ? QStringLiteral("boot") : repository);
qDebug(ASTRA_PATCHER) << "- Version:" << patch.version;
qDebug(ASTRA_PATCHER) << "- Downloaded Path:" << patchPath;
qDebug(ASTRA_PATCHER) << "- Hashes:" << patch.hashes;
qDebug(ASTRA_PATCHER) << "- Hash Block Size:" << patch.hashBlockSize;
qDebug(ASTRA_PATCHER) << "- Hash Block Size:" << patch.hash_block_size;
qDebug(ASTRA_PATCHER) << "- Length:" << patch.length;
m_patchQueue[ourIndex] = queuedPatch;
@ -103,7 +118,7 @@ QCoro::Task<bool> Patcher::patch(const PatchList &patchList)
QFile::remove(tempPatchPath);
}
const auto patchRequest = QNetworkRequest(QUrl(patch.url));
const auto patchRequest = QNetworkRequest(QUrl(QLatin1String(patch.url)));
Utility::printRequest(QStringLiteral("GET"), patchRequest);
auto patchReply = m_launcher.mgr()->get(patchRequest);
@ -135,7 +150,7 @@ QCoro::Task<bool> Patcher::patch(const PatchList &patchList)
} else {
m_patchQueue[ourIndex].downloaded = true;
m_finishedPatches++;
qDebug(ASTRA_PATCHER) << "Found existing patch: " << patch.name;
qDebug(ASTRA_PATCHER) << "Found existing patch: " << patch.version;
}
}
@ -243,6 +258,7 @@ void Patcher::setupDirectories()
dataDir.setPath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
m_patchesDir.setPath(dataDir.absoluteFilePath(QStringLiteral("patch")));
m_patchesDirStorageInfo = QStorageInfo(m_patchesDir);
}
QString Patcher::getBaseString() const

View file

@ -1,47 +0,0 @@
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "patchlist.h"
#include "astra_patcher_log.h"
using namespace Qt::StringLiterals;
PatchList::PatchList(const QString &patchList)
{
const QStringList parts = patchList.split(QStringLiteral("\r\n"));
for (int i = 5; i < parts.size() - 2; i++) {
const QStringList patchParts = parts[i].split('\t'_L1);
const int length = patchParts[0].toInt();
const QString &version = patchParts[4];
const long hashBlockSize = patchParts.size() == 9 ? patchParts[6].toLong() : 0;
const QString &name = version;
const QStringList hashes = patchParts.size() == 9 ? (patchParts[7].split(','_L1)) : QStringList();
const QString &url = patchParts[patchParts.size() == 9 ? 8 : 5];
auto url_parts = url.split('/'_L1);
const QString repository = url_parts[url_parts.size() - 3];
m_patches.push_back(
{.name = name, .url = url, .repository = repository, .version = version, .hashes = hashes, .hashBlockSize = hashBlockSize, .length = length});
}
Q_ASSERT_X(m_patches.isEmpty() ? patchList.isEmpty() : true,
"PatchList",
"Patch parsing failed, we were given an non-empty patchlist body but nothing were parsed.");
qDebug(ASTRA_PATCHER) << "Finished parsing patch list. # of patches:" << m_patches.size();
}
QList<Patch> PatchList::patches() const
{
return m_patches;
}
bool PatchList::isEmpty() const
{
return m_patches.isEmpty();
}

View file

@ -177,14 +177,19 @@ QCoro::Task<bool> SquareEnixLogin::checkBootUpdates()
if (reply->error() == QNetworkReply::NoError) {
const QString patchList = QString::fromUtf8(reply->readAll());
if (!patchList.isEmpty()) {
qDebug(ASTRA_LOG) << "Boot patch list:" << patchList;
m_patcher = new Patcher(m_launcher, m_info->profile->gamePath() + QStringLiteral("/boot"), *m_info->profile->bootData(), this);
const bool hasPatched = co_await m_patcher->patch(PatchList(patchList));
const std::string patchListStd = patchList.toStdString();
const bool hasPatched = co_await m_patcher->patch(physis_parse_patchlist(PatchListType::Boot, patchListStd.c_str()));
if (hasPatched) {
// update game version information
m_info->profile->readGameVersion();
} else {
co_return false;
}
m_patcher->deleteLater();
m_lastRunHasPatched = true;
m_patcher->deleteLater();
}
} else {
qWarning(ASTRA_LOG) << "Unknown error when verifying boot files:" << reply->errorString();
@ -376,7 +381,8 @@ QCoro::Task<bool> SquareEnixLogin::registerSession()
if (!body.isEmpty()) {
m_patcher = new Patcher(m_launcher, m_info->profile->gamePath() + QStringLiteral("/game"), *m_info->profile->gameData(), this);
const bool hasPatched = co_await m_patcher->patch(PatchList(body));
std::string bodyStd = body.toStdString();
const bool hasPatched = co_await m_patcher->patch(physis_parse_patchlist(PatchListType::Game, bodyStd.c_str()));
if (hasPatched) {
// re-read game version if it has updated
m_info->profile->readGameVersion();

View file

@ -54,3 +54,9 @@ bool Utility::isSteamDeck()
{
return qEnvironmentVariable("SteamDeck") == QStringLiteral("1");
}
QString Utility::repositoryFromPatchUrl(const QString &url)
{
auto url_parts = url.split('/'_L1);
return url_parts[url_parts.size() - 3];
}