mirror of
https://github.com/redstrate/Astra.git
synced 2025-04-23 21:07:45 +00:00
Add game patching support
Yes it's finally here! It's been tested to update the game from the base 2012 version all the way up to patch 6.18. This also works across all expansions, and the previous boot patches that were supported before. However, the patcher dialog is in need of an update, as patching happens on the same thread so the entire application freezes.
This commit is contained in:
parent
a48b6d630d
commit
3d32674663
7 changed files with 206 additions and 72 deletions
|
@ -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}
|
||||
|
|
37
launcher/core/include/patcher.h
Normal file
37
launcher/core/include/patcher.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QProgressDialog>
|
||||
#include <QNetworkAccessManager>
|
||||
|
||||
// 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<QueuedPatch> patchQueue;
|
||||
|
||||
bool isBoot = false;
|
||||
QString baseDirectory;
|
||||
|
||||
QProgressDialog* dialog = nullptr;
|
||||
|
||||
int remainingPatches = -1;
|
||||
};
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <QProgressDialog>
|
||||
#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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
136
launcher/core/src/patcher.cpp
Normal file
136
launcher/core/src/patcher.cpp
Normal file
|
@ -0,0 +1,136 @@
|
|||
#include "patcher.h"
|
||||
#include <QFile>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QStandardPaths>
|
||||
#include <physis.hpp>
|
||||
#include <QDir>
|
||||
|
||||
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();
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
#include <QPushButton>
|
||||
#include <QStandardPaths>
|
||||
#include <QFile>
|
||||
#include <patch.h>
|
||||
#include <physis.hpp>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Reference in a new issue