diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 2b17f71..ce7db77 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -56,6 +56,7 @@ target_sources(astra PRIVATE include/logger.h include/patcher.h include/processlogger.h + include/processwatcher.h include/profile.h include/profilemanager.h include/sapphirelogin.h @@ -79,6 +80,7 @@ target_sources(astra PRIVATE src/main.cpp src/patcher.cpp src/processlogger.cpp + src/processwatcher.cpp src/profile.cpp src/profilemanager.cpp src/sapphirelogin.cpp diff --git a/launcher/include/processlogger.h b/launcher/include/processlogger.h index 8542936..5fc8708 100644 --- a/launcher/include/processlogger.h +++ b/launcher/include/processlogger.h @@ -10,7 +10,7 @@ class ProcessLogger : public QObject { public: - explicit ProcessLogger(QProcess *process); + explicit ProcessLogger(const QString &baseName, QProcess *process); private: QFile m_file; diff --git a/launcher/include/processwatcher.h b/launcher/include/processwatcher.h new file mode 100644 index 0000000..5f71790 --- /dev/null +++ b/launcher/include/processwatcher.h @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2024 Joshua Goins +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +/// Listens and waits for a process to finish. +class ProcessWatcher : public QObject +{ + Q_OBJECT +public: + explicit ProcessWatcher(const int PID); + +Q_SIGNALS: + void finished(); + +private: + QTimer *m_timer = nullptr; +}; \ No newline at end of file diff --git a/launcher/src/gamerunner.cpp b/launcher/src/gamerunner.cpp index 98e45d6..942e41f 100644 --- a/launcher/src/gamerunner.cpp +++ b/launcher/src/gamerunner.cpp @@ -7,9 +7,11 @@ #include #endif +#include "astra_log.h" #include "encryptedarg.h" #include "launchercore.h" #include "processlogger.h" +#include "processwatcher.h" #include "utility.h" using namespace Qt::StringLiterals; @@ -52,7 +54,7 @@ void GameRunner::beginVanillaGame(const QString &gameExecutablePath, Profile &pr auto args = getGameArgs(profile, auth); - new ProcessLogger(gameProcess); + new ProcessLogger(QStringLiteral("ffxiv"), gameProcess); launchExecutable(profile, gameProcess, {gameExecutablePath, args}, true, true); } @@ -75,7 +77,7 @@ void GameRunner::beginDalamudGame(const QString &gameExecutablePath, Profile &pr // so we need to match typical XIVQuickLauncher behavior here. Why? I have no clue. const QDir dalamudPluginDir = dalamudUserPluginDir.absoluteFilePath(QStringLiteral("installedPlugins")); - const QString logDir = dataDir.absoluteFilePath(QStringLiteral("log")); + const QDir logDir = dataDir.absoluteFilePath(QStringLiteral("log")); Utility::createPathIfNeeded(logDir); const QDir dalamudRuntimeDir = dalamudDir.absoluteFilePath(QStringLiteral("runtime")); @@ -86,9 +88,35 @@ void GameRunner::beginDalamudGame(const QString &gameExecutablePath, Profile &pr const QString dalamudInjector = dalamudInstallDir.absoluteFilePath(QStringLiteral("Dalamud.Injector.exe")); const auto dalamudProcess = new QProcess(this); - connect(dalamudProcess, &QProcess::finished, this, [this, &profile](const int exitCode) { - profile.setLoggedIn(false); + connect(dalamudProcess, &QProcess::finished, this, [this, &profile, logDir](const int exitCode) { Q_UNUSED(exitCode) + + // So here's the kicker, we can't depend on Dalamud to give us an accurate finished signal for the game. + // finished() is called when the injector exits. + + // so what we'll do instead is get the game PID from Dalamud first. + QFile logFile(logDir.absoluteFilePath(QStringLiteral("dalamud-initial-injection.log"))); + logFile.open(QIODevice::ReadOnly); + + const static QRegularExpression pidRegex(QStringLiteral("{\"pid\": (\\d*),")); + const QString log = QString::fromUtf8(logFile.readAll()); + + const auto match = pidRegex.match(log); + if (match.hasCaptured(1)) { + const int PID = match.captured(1).toInt(); + if (PID > 0) { + qCInfo(ASTRA_LOG) << "Recieved PID from Dalamud:" << PID; + auto watcher = new ProcessWatcher(PID); + connect(watcher, &ProcessWatcher::finished, this, [this, &profile] { + profile.setLoggedIn(false); + Q_EMIT m_launcher.gameClosed(); + }); + return; + } + } + + // If Dalamud didn't give a valid PID, OK. Let's just do our previous status quo and inidcate we did log out. + profile.setLoggedIn(false); Q_EMIT m_launcher.gameClosed(); }); @@ -100,7 +128,7 @@ void GameRunner::beginDalamudGame(const QString &gameExecutablePath, Profile &pr #endif dalamudProcess->setProcessEnvironment(env); - new ProcessLogger(dalamudProcess); + new ProcessLogger(QStringLiteral("dalamud-initial-injection"), dalamudProcess); const auto args = getGameArgs(profile, auth); diff --git a/launcher/src/processlogger.cpp b/launcher/src/processlogger.cpp index 073360c..6330837 100644 --- a/launcher/src/processlogger.cpp +++ b/launcher/src/processlogger.cpp @@ -7,12 +7,12 @@ #include -ProcessLogger::ProcessLogger(QProcess *process) +ProcessLogger::ProcessLogger(const QString &baseName, QProcess *process) { const QDir dataDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); const QDir logDirectory = dataDir.absoluteFilePath(QStringLiteral("log")); - m_file.setFileName(logDirectory.absoluteFilePath(QStringLiteral("ffxiv.log"))); + m_file.setFileName(logDirectory.absoluteFilePath(QStringLiteral("%1.log").arg(baseName))); m_file.open(QIODevice::WriteOnly | QIODevice::Unbuffered); connect(process, &QProcess::readyReadStandardOutput, this, [this, process] { diff --git a/launcher/src/processwatcher.cpp b/launcher/src/processwatcher.cpp new file mode 100644 index 0000000..69339f8 --- /dev/null +++ b/launcher/src/processwatcher.cpp @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2024 Joshua Goins +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "processwatcher.h" + +#include + +#include "moc_processwatcher.cpp" + +ProcessWatcher::ProcessWatcher(const int PID) +{ + m_timer = new QTimer(); + connect(m_timer, &QTimer::timeout, this, [this, PID] { + const auto info = KProcessList::processInfo(PID); + // If we can't find the PID, bail! + if (!info.isValid()) { + m_timer->stop(); + deleteLater(); + Q_EMIT finished(); + } + }); + m_timer->setInterval(std::chrono::seconds(30)); + m_timer->start(); +} \ No newline at end of file