From 047dbfc1b851ae65167a113c9d2d6c325612f979 Mon Sep 17 00:00:00 2001 From: redstrate Date: Tue, 23 Nov 2021 14:37:37 -0500 Subject: [PATCH] Add Dalamud injection support * Also includes a basic asset downloader for Dalamud + NativeLauncher --- CMakeLists.txt | 9 +++-- external/CMakeLists.txt | 9 ++++- src/assetupdater.cpp | 87 +++++++++++++++++++++++++++++++++++++++++ src/assetupdater.h | 25 ++++++++++++ src/settingswindow.cpp | 11 +++++- src/settingswindow.h | 1 + src/xivlauncher.cpp | 49 +++++++++++++++++++++-- src/xivlauncher.h | 7 +++- 8 files changed, 187 insertions(+), 11 deletions(-) create mode 100644 src/assetupdater.cpp create mode 100644 src/assetupdater.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f558aa..236ee48 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,14 +17,15 @@ add_executable(xivlauncher src/squareboot.cpp src/squarelauncher.cpp src/settingswindow.cpp - src/blowfish.cpp) + src/blowfish.cpp src/assetupdater.cpp src/assetupdater.h) -target_link_libraries(xivlauncher Qt5::Core Qt5::Widgets Qt5::Network qt5keychain) +target_link_libraries(xivlauncher Qt5::Core Qt5::Widgets Qt5::Network qt5keychain QuaZip) -# disgusting, thanks qtkeychain +# disgusting, thanks qtkeychain and quazip target_include_directories(xivlauncher PRIVATE ${CMAKE_BINARY_DIR}/_deps/qtkeychain-src - ${CMAKE_BINARY_DIR}/_deps/qtkeychain-build) + ${CMAKE_BINARY_DIR}/_deps/qtkeychain-build + ${CMAKE_BINARY_DIR}/_deps/quazip-src) install(TARGETS xivlauncher DESTINATION "${INSTALL_BIN_PATH}" diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 7a18dbc..7fc77f3 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -9,4 +9,11 @@ FetchContent_Declare( set(BUILD_WITH_QT6 OFF CACHE BOOL "" FORCE) set(QTKEYCHAIN_STATIC ON CACHE BOOL "" FORCE) -FetchContent_MakeAvailable(qtkeychain) \ No newline at end of file +FetchContent_MakeAvailable(qtkeychain) + +FetchContent_Declare( + quazip + GIT_REPOSITORY https://github.com/stachenov/quazip.git + GIT_TAG v1.2 +) +FetchContent_MakeAvailable(quazip) \ No newline at end of file diff --git a/src/assetupdater.cpp b/src/assetupdater.cpp new file mode 100644 index 0000000..1f46e8f --- /dev/null +++ b/src/assetupdater.cpp @@ -0,0 +1,87 @@ +#include "assetupdater.h" + +#include +#include +#include + +#include + +#include "xivlauncher.h" + +const QString dalamudRemotePath = "https://goatcorp.github.io/dalamud-distrib/"; +const QString dalamudVersion = "latest"; + +const QString nativeLauncherRemotePath = "https://github.com/redstrate/nativelauncher/releases/download/"; +const QString nativeLauncherVersion = "v1.0.0"; + +AssetUpdater::AssetUpdater(LauncherWindow &launcher) : launcher(launcher) { + connect(launcher.mgr, &QNetworkAccessManager::finished, this, &AssetUpdater::finishDownload); + + launcher.mgr->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy); +} + +void AssetUpdater::update() { + QString dataDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + + const bool hasDalamud = QFile::exists(dataDir + "/NativeLauncher.exe") && QFile::exists(dataDir + "/Dalamud"); + + // first we determine if we need dalamud + const bool needsDalamud = launcher.currentProfile().enableDalamud && !hasDalamud; + if(needsDalamud) { + // download nativelauncher release (needed to launch the game with fixed ACLs) + { + QNetworkRequest request(nativeLauncherRemotePath + nativeLauncherVersion + "/NativeLauncher.exe"); + + auto reply = launcher.mgr->get(request); + reply->setObjectName("NativeLauncher"); + } + + // download dalamud (... duh) + { + QNetworkRequest request(dalamudRemotePath + dalamudVersion + ".zip"); + + auto reply = launcher.mgr->get(request); + reply->setObjectName("Dalamud"); + } + } else { + // non-dalamud users can bypass this process since it's not needed + finishedUpdating(); + } +} + +void AssetUpdater::finishDownload(QNetworkReply* reply) { + const auto checkIfFinished = [=] { + if(QFile::exists(tempDir.path() + "/NativeLauncher.exe") && QFile::exists(tempDir.path() + "/latest.zip")) { + beginInstall(); + } + }; + + if(reply->objectName() == "Dalamud") { + QFile file(tempDir.path() + "/latest.zip"); + file.open(QIODevice::WriteOnly); + file.write(reply->readAll()); + file.close(); + + checkIfFinished(); + } else if(reply->objectName() == "NativeLauncher") { + QFile file(tempDir.path() + "/NativeLauncher.exe"); + file.open(QIODevice::WriteOnly); + file.write(reply->readAll()); + file.close(); + + checkIfFinished(); + } +} + +void AssetUpdater::beginInstall() { + QString dataDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + + bool success = !JlCompress::extractDir(tempDir.path() + "/latest.zip", dataDir + "/Dalamud").empty(); + if(success) { + QFile::copy(tempDir.path() + "/NativeLauncher.exe", dataDir + "/NativeLauncher.exe"); + + finishedUpdating(); + } else { + // STUB: install failure + } +} \ No newline at end of file diff --git a/src/assetupdater.h b/src/assetupdater.h new file mode 100644 index 0000000..d762726 --- /dev/null +++ b/src/assetupdater.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +class LauncherWindow; +class QNetworkReply; + +class AssetUpdater : public QObject { + Q_OBJECT +public: + AssetUpdater(LauncherWindow& launcher); + + void update(); + void finishDownload(QNetworkReply* reply); + void beginInstall(); + +signals: + void finishedUpdating(); + +private: + LauncherWindow& launcher; + + QTemporaryDir tempDir; +}; \ No newline at end of file diff --git a/src/settingswindow.cpp b/src/settingswindow.cpp index dbd3595..d47f0fd 100644 --- a/src/settingswindow.cpp +++ b/src/settingswindow.cpp @@ -100,11 +100,18 @@ SettingsWindow::SettingsWindow(LauncherWindow& window, QWidget* parent) : window connect(encryptArgumentsBox, &QCheckBox::stateChanged, [=](int) { getCurrentProfile().encryptArguments = encryptArgumentsBox->isChecked(); - this->window.reloadControls(); this->window.saveSettings(); }); loginBoxLayout->addRow("Encrypt Game Arguments", encryptArgumentsBox); + enableDalamudBox = new QCheckBox(); + connect(enableDalamudBox, &QCheckBox::stateChanged, [=](int) { + getCurrentProfile().enableDalamud = enableDalamudBox->isChecked(); + + this->window.saveSettings(); + }); + loginBoxLayout->addRow("Enable Dalamud Injection", enableDalamudBox); + serverType = new QComboBox(); serverType->insertItem(0, "Square Enix"); serverType->insertItem(1, "Sapphire"); @@ -310,6 +317,8 @@ void SettingsWindow::reloadControls() { rememberUsernameBox->setChecked(profile.rememberUsername); rememberPasswordBox->setChecked(profile.rememberPassword); + enableDalamudBox->setChecked(profile.enableDalamud); + window.reloadControls(); currentlyReloadingControls = false; diff --git a/src/settingswindow.h b/src/settingswindow.h index 453cbfb..f433911 100644 --- a/src/settingswindow.h +++ b/src/settingswindow.h @@ -40,6 +40,7 @@ private: // login QCheckBox* encryptArgumentsBox = nullptr; + QCheckBox* enableDalamudBox = nullptr; QComboBox* serverType = nullptr; QLineEdit* lobbyServerURL = nullptr; QCheckBox* rememberUsernameBox = nullptr, *rememberPasswordBox = nullptr; diff --git a/src/xivlauncher.cpp b/src/xivlauncher.cpp index 049ba6c..4cb7c4b 100755 --- a/src/xivlauncher.cpp +++ b/src/xivlauncher.cpp @@ -15,6 +15,8 @@ #include #include #include +#include +#include #if defined(Q_OS_MAC) #include @@ -31,6 +33,7 @@ #include "squareboot.h" #include "settingswindow.h" #include "blowfish.h" +#include "assetupdater.h" void LauncherWindow::setSSL(QNetworkRequest& request) { QSslConfiguration config; @@ -106,6 +109,12 @@ QString encryptGameArg(QString arg) { void LauncherWindow::launchGame(const LoginAuth auth) { QList arguments; + QString dataDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + + if(currentProfile().enableDalamud) { + arguments.push_back(dataDir + "/NativeLauncher.exe"); + } + // now for the actual game... if(currentProfile().useDX9) { arguments.push_back(currentProfile().gamePath + "\\game\\ffxiv.exe"); @@ -135,6 +144,25 @@ void LauncherWindow::launchGame(const LoginAuth auth) { } } + auto gameProcess = new QProcess(this); + + if(currentProfile().enableDalamud) { + connect(gameProcess, &QProcess::readyReadStandardOutput, [this, gameProcess] { + QString output = gameProcess->readAllStandardOutput(); + + auto dalamudProcess = new QProcess(); + + QStringList dalamudEnv = gameProcess->environment(); + dalamudEnv << "XL_WINEONLINUX=true"; + + dalamudProcess->setEnvironment(dalamudEnv); + + QString dataDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + + dalamudProcess->start(currentProfile().winePath, {dataDir + "/Dalamud/" + "Dalamud.Injector.exe", output}); + }); + } + if(currentProfile().encryptArguments) { QString argJoined; for(auto arg : gameArgs) { @@ -143,20 +171,22 @@ void LauncherWindow::launchGame(const LoginAuth auth) { auto earg = encryptGameArg(argJoined); arguments.append(earg); - launchExecutable(arguments); + launchExecutable(gameProcess, arguments); } else { for(auto arg : gameArgs) { arguments.push_back(QString(" %1=%2").arg(arg.key, arg.value)); } - launchExecutable(arguments); + launchExecutable(gameProcess, arguments); } } void LauncherWindow::launchExecutable(const QStringList args) { auto process = new QProcess(this); - process->setProcessChannelMode(QProcess::ForwardedChannels); + launchExecutable(process, args); +} +void LauncherWindow::launchExecutable(QProcess* process, const QStringList args) { QList arguments; QStringList env = QProcess::systemEnvironment(); @@ -191,6 +221,7 @@ void LauncherWindow::launchExecutable(const QStringList args) { process->setWorkingDirectory(currentProfile().gamePath + "/game/"); process->setEnvironment(env); + process->start(executable, arguments); } @@ -268,6 +299,8 @@ void LauncherWindow::readInitialInformation() { profile.useGamescope = settings.value("useGamescope", false).toBool(); profile.enableDXVKhud = settings.value("enableDXVKhud", false).toBool(); + profile.enableDalamud = settings.value("enableDalamud", false).toBool(); + profileSettings[settings.value("index").toInt()] = profile; settings.endGroup(); @@ -316,6 +349,7 @@ LauncherWindow::LauncherWindow(QWidget* parent) : sapphireLauncher = new SapphireLauncher(*this); squareLauncher = new SquareLauncher(*this); squareBoot = new SquareBoot(*this, *squareLauncher); + assetUpdater = new AssetUpdater(*this); readInitialInformation(); @@ -406,7 +440,7 @@ LauncherWindow::LauncherWindow(QWidget* parent) : emptyWidget->setLayout(layout); setCentralWidget(emptyWidget); - connect(loginButton, &QPushButton::released, [=] { + connect(assetUpdater, &AssetUpdater::finishedUpdating, [=] { auto info = LoginInformation{usernameEdit->text(), passwordEdit->text(), otpEdit->text()}; if(currentProfile().rememberUsername) { @@ -430,6 +464,11 @@ LauncherWindow::LauncherWindow(QWidget* parent) : } }); + connect(loginButton, &QPushButton::released, [=] { + // update the assets first if needed, then it calls the slot above :-) + assetUpdater->update(); + }); + connect(registerButton, &QPushButton::released, [=] { if(currentProfile().isSapphire) { auto info = LoginInformation{usernameEdit->text(), passwordEdit->text(), otpEdit->text()}; @@ -536,6 +575,8 @@ void LauncherWindow::saveSettings() { settings.setValue("rememberUsername", profile.rememberUsername); settings.setValue("rememberPassword", profile.rememberPassword); + settings.setValue("enableDalamud", profile.enableDalamud); + settings.endGroup(); } } diff --git a/src/xivlauncher.h b/src/xivlauncher.h index d2eea85..d90ec3c 100755 --- a/src/xivlauncher.h +++ b/src/xivlauncher.h @@ -8,10 +8,12 @@ #include #include #include +#include class SapphireLauncher; class SquareLauncher; class SquareBoot; +class AssetUpdater; struct ProfileSettings { QUuid uuid; @@ -33,6 +35,7 @@ struct ProfileSettings { bool useEsync = false, useGamescope = false, useGamemode = false; bool useDX9 = false; bool enableDXVKhud = false; + bool enableDalamud = false; // login bool encryptArguments = false; @@ -75,7 +78,8 @@ public: int deleteProfile(QString name); void launchGame(const LoginAuth auth); - void launchExecutable(const QStringList args); + void launchExecutable(QStringList args); + void launchExecutable(QProcess* process, QStringList args); void buildRequest(QNetworkRequest& request); void setSSL(QNetworkRequest& request); QString readVersion(QString path); @@ -98,6 +102,7 @@ private: SapphireLauncher* sapphireLauncher; SquareBoot* squareBoot; SquareLauncher* squareLauncher; + AssetUpdater* assetUpdater; QComboBox* profileSelect; QLineEdit* usernameEdit, *passwordEdit;