From c0d68c0b5632abafd5669b662a3515db8ae7353c Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Wed, 4 Oct 2023 11:09:50 -0400 Subject: [PATCH] Introduce PatchList class to consolidate patchlist body parsing This also adds tests for the class, which was desperately needed. Also adds plenty of debug messages to pick apart problems in the patching process. --- CMakeLists.txt | 4 +- launcher/CMakeLists.txt | 14 +++++ launcher/autotests/CMakeLists.txt | 8 +++ launcher/autotests/patchlisttest.cpp | 94 ++++++++++++++++++++++++++++ launcher/include/patcher.h | 3 +- launcher/include/patchlist.h | 29 +++++++++ launcher/src/patcher.cpp | 49 +++++++-------- launcher/src/patchlist.cpp | 46 ++++++++++++++ launcher/src/squareboot.cpp | 2 +- launcher/src/squarelauncher.cpp | 2 +- 10 files changed, 221 insertions(+), 30 deletions(-) create mode 100644 launcher/autotests/CMakeLists.txt create mode 100644 launcher/autotests/patchlisttest.cpp create mode 100644 launcher/include/patchlist.h create mode 100644 launcher/src/patchlist.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a269c2..821f720 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,7 @@ include(ECMGenerateHeaders) include(ECMPoQmTools) include(KDEGitCommitHooks) include(KDEClangFormat) +include(ECMAddTests) ecm_setup_version(${PROJECT_VERSION} VARIABLE_PREFIX ASTRA @@ -46,7 +47,8 @@ find_package(Qt6 ${QT_MIN_VERSION} REQUIRED COMPONENTS Network QuickControls2 WebView - Concurrent) + Concurrent + Test) find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS Kirigami2 I18n Config CoreAddons) find_package(PkgConfig REQUIRED) find_package(QCoro6 REQUIRED COMPONENTS Core Network Qml) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index b698747..e907fef 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1,6 +1,15 @@ # SPDX-FileCopyrightText: 2023 Joshua Goins # SPDX-License-Identifier: CC0-1.0 +add_library(astra_static STATIC) + +target_sources(astra_static PRIVATE + include/patchlist.h + src/patchlist.cpp) +target_include_directories(astra_static PUBLIC include) +target_link_libraries(astra_static PUBLIC + Qt6::Core) + add_executable(astra) qt_add_qml_module(astra @@ -75,6 +84,7 @@ qt_target_qml_sources(astra kconfig_add_kcfg_files(astra GENERATE_MOC config.kcfgc accountconfig.kcfgc profileconfig.kcfgc) target_include_directories(astra PRIVATE include ${CMAKE_BINARY_DIR}) target_link_libraries(astra PRIVATE + astra_static physis cotp QuaZip::QuaZip @@ -142,3 +152,7 @@ if (WIN32) COMMAND "${WINDEPLOYQT_ENV_SETUP}" && "${WINDEPLOYQT_EXECUTABLE}" \"$\" ) endif () + +if (BUILD_TESTING) + add_subdirectory(autotests) +endif() diff --git a/launcher/autotests/CMakeLists.txt b/launcher/autotests/CMakeLists.txt new file mode 100644 index 0000000..19a1fc3 --- /dev/null +++ b/launcher/autotests/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2023 Joshua Goins +# SPDX-License-Identifier: CC0-1.0 + +ecm_add_test(patchlisttest.cpp + TEST_NAME patchlisttest + LINK_LIBRARIES astra_static Qt::Test + NAME_PREFIX "astra-" +) \ No newline at end of file diff --git a/launcher/autotests/patchlisttest.cpp b/launcher/autotests/patchlisttest.cpp new file mode 100644 index 0000000..7c2e7fb --- /dev/null +++ b/launcher/autotests/patchlisttest.cpp @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: 2023 Joshua Goins +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#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" diff --git a/launcher/include/patcher.h b/launcher/include/patcher.h index 8002159..22a5caa 100644 --- a/launcher/include/patcher.h +++ b/launcher/include/patcher.h @@ -3,6 +3,7 @@ #pragma once +#include "patchlist.h" #include #include #include @@ -21,7 +22,7 @@ public: Patcher(LauncherCore &launcher, const QString &baseDirectory, GameData &gameData, QObject *parent = nullptr); Patcher(LauncherCore &launcher, const QString &baseDirectory, BootData &bootData, QObject *parent = nullptr); - QCoro::Task patch(const QString &patchList); + QCoro::Task patch(const PatchList &patchList); private: void setupDirectories(); diff --git a/launcher/include/patchlist.h b/launcher/include/patchlist.h new file mode 100644 index 0000000..a5c5f77 --- /dev/null +++ b/launcher/include/patchlist.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2023 Joshua Goins +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +class Patch +{ +public: + QString name, url, repository, version; + QVector hashes; + long hashBlockSize = 0; + long length = 0; +}; + +class PatchList +{ +public: + explicit PatchList(const QString &patchList); + + [[nodiscard]] QVector patches() const; + + [[nodiscard]] bool isEmpty() const; + +private: + QVector m_patches; +}; \ No newline at end of file diff --git a/launcher/src/patcher.cpp b/launcher/src/patcher.cpp index e2faa1d..9f30a02 100644 --- a/launcher/src/patcher.cpp +++ b/launcher/src/patcher.cpp @@ -14,6 +14,7 @@ #include #include "launchercore.h" +#include "patchlist.h" Patcher::Patcher(LauncherCore &launcher, const QString &baseDirectory, BootData &bootData, QObject *parent) : QObject(parent) @@ -37,7 +38,7 @@ Patcher::Patcher(LauncherCore &launcher, const QString &baseDirectory, GameData Q_EMIT m_launcher.stageChanged(i18n("Checking %1 version.", getBaseString())); } -QCoro::Task Patcher::patch(const QString &patchList) +QCoro::Task Patcher::patch(const PatchList &patchList) { if (patchList.isEmpty()) { co_return false; @@ -46,48 +47,42 @@ QCoro::Task Patcher::patch(const QString &patchList) Q_EMIT m_launcher.stageIndeterminate(); Q_EMIT m_launcher.stageChanged(i18n("Checking %1 version.", getBaseString())); - const QStringList parts = patchList.split("\r\n"); - - m_remainingPatches = parts.size() - 7; + m_remainingPatches = patchList.patches().size(); m_patchQueue.resize(m_remainingPatches); QFutureSynchronizer synchronizer; int patchIndex = 0; - for (int i = 5; i < parts.size() - 2; i++) { - const QStringList patchParts = parts[i].split(QLatin1Char('\t')); - - const int length = patchParts[0].toInt(); + for (auto &patch : patchList.patches()) { const int ourIndex = patchIndex++; - 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(QLatin1Char(','))) : QStringList(); - const QString &url = patchParts[patchParts.size() == 9 ? 8 : 5]; - const QString filename = QStringLiteral("%1.patch").arg(name); - - auto url_parts = url.split(QLatin1Char('/')); - const QString repository = url_parts[url_parts.size() - 3]; - - const QDir repositoryDir = m_patchesDir.absoluteFilePath(repository); + const QString filename = QStringLiteral("%1.patch").arg(patch.name); + const QDir repositoryDir = m_patchesDir.absoluteFilePath(patch.repository); if (!QDir().exists(repositoryDir.absolutePath())) QDir().mkpath(repositoryDir.absolutePath()); const QString patchPath = repositoryDir.absoluteFilePath(filename); - const QueuedPatch patch{name, repository, version, patchPath, hashes, hashBlockSize, length, isBoot()}; + const QueuedPatch queuedPatch{patch.name, patch.repository, patch.version, patchPath, patch.hashes, patch.hashBlockSize, patch.length, isBoot()}; - m_patchQueue[ourIndex] = patch; + qDebug() << "Adding a queued patch:"; + qDebug() << "- Name:" << patch.name; + qDebug() << "- Repository or is boot:" << (isBoot() ? QStringLiteral("boot") : patch.repository); + qDebug() << "- Version:" << patch.version; + qDebug() << "- Downloaded Path:" << patchPath; + qDebug() << "- Hashes:" << patch.hashes; + qDebug() << "- Hash Block Size:" << patch.hashBlockSize; + qDebug() << "- Length:" << patch.length; + + m_patchQueue[ourIndex] = queuedPatch; if (!QFile::exists(patchPath)) { - auto patchReply = m_launcher.mgr->get(QNetworkRequest(url)); + auto patchReply = m_launcher.mgr->get(QNetworkRequest(patch.url)); - connect(patchReply, &QNetworkReply::downloadProgress, this, [this, patch](int received, int total) { - Q_EMIT m_launcher.stageChanged(i18n("Updating %1.\nDownloading %2", getBaseString(), patch.getVersion())); + connect(patchReply, &QNetworkReply::downloadProgress, this, [this, queuedPatch](int received, int total) { + Q_EMIT m_launcher.stageChanged(i18n("Updating %1.\nDownloading %2", getBaseString(), queuedPatch.getVersion())); Q_EMIT m_launcher.stageDeterminate(0, total, received); }); @@ -98,7 +93,7 @@ QCoro::Task Patcher::patch(const QString &patchList) file.close(); })); } else { - qDebug() << "Found existing patch: " << name; + qDebug() << "Found existing patch: " << patch.name; } } @@ -152,6 +147,8 @@ void Patcher::processPatch(const QueuedPatch &patch) physis_gamedata_apply_patch(m_gameData, patch.path.toStdString().c_str()); } + qDebug() << "Installed" << patch.path << "to" << (isBoot() ? QStringLiteral("boot") : patch.repository); + QString verFilePath; if (isBoot()) { verFilePath = m_baseDirectory + QStringLiteral("/ffxivboot.ver"); diff --git a/launcher/src/patchlist.cpp b/launcher/src/patchlist.cpp new file mode 100644 index 0000000..0be7b45 --- /dev/null +++ b/launcher/src/patchlist.cpp @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2023 Joshua Goins +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "patchlist.h" + +#include + +PatchList::PatchList(const QString &patchList) +{ + const QStringList parts = patchList.split("\r\n"); + + for (int i = 5; i < parts.size() - 2; i++) { + const QStringList patchParts = parts[i].split(QLatin1Char('\t')); + + 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(QLatin1Char(','))) : QStringList(); + const QString &url = patchParts[patchParts.size() == 9 ? 8 : 5]; + + auto url_parts = url.split(QLatin1Char('/')); + 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() << "Finished parsing patch list. Num patches:" << m_patches.size(); +} + +QVector PatchList::patches() const +{ + return m_patches; +} + +bool PatchList::isEmpty() const +{ + return m_patches.isEmpty(); +} diff --git a/launcher/src/squareboot.cpp b/launcher/src/squareboot.cpp index e4ca018..a8b9fc5 100644 --- a/launcher/src/squareboot.cpp +++ b/launcher/src/squareboot.cpp @@ -50,7 +50,7 @@ QCoro::Task<> SquareBoot::bootCheck(const LoginInformation &info) const QString patchList = reply->readAll(); if (!patchList.isEmpty()) { patcher = new Patcher(window, info.profile->gamePath() + QStringLiteral("/boot"), *info.profile->bootData(), this); - const bool hasPatched = co_await patcher->patch(reply->readAll()); + const bool hasPatched = co_await patcher->patch(PatchList(patchList)); if (hasPatched) { // update game version information info.profile->readGameVersion(); diff --git a/launcher/src/squarelauncher.cpp b/launcher/src/squarelauncher.cpp index 1b319e5..bf6bfc1 100644 --- a/launcher/src/squarelauncher.cpp +++ b/launcher/src/squarelauncher.cpp @@ -191,7 +191,7 @@ QCoro::Task<> SquareLauncher::registerSession(const LoginInformation &info) if (!body.isEmpty()) { patcher = new Patcher(window, info.profile->gamePath() + QStringLiteral("/game"), *info.profile->gameData(), this); - const bool hasPatched = co_await patcher->patch(body); + const bool hasPatched = co_await patcher->patch(PatchList(body)); if (hasPatched) { // re-read game version if it has updated info.profile->readGameVersion();