1
Fork 0
mirror of https://github.com/redstrate/Astra.git synced 2025-04-21 12:17:45 +00:00
astra/launcher/src/gamerunner.cpp
Joshua Goins 498391bbb9 Check if Dalamud version is applicable to this game version before using
This stops it from trying to launch even though goatcorp said no. The
check is basic but should allow Dawntrail and future expansions to work
without having to toggle Dalamud off manually.
2024-06-27 16:38:18 -04:00

352 lines
No EOL
16 KiB
C++

// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "gamerunner.h"
#ifdef ENABLE_GAMEMODE
#include <gamemode_client.h>
#endif
#include "encryptedarg.h"
#include "launchercore.h"
#include "processlogger.h"
#include "utility.h"
using namespace Qt::StringLiterals;
GameRunner::GameRunner(LauncherCore &launcher, QObject *parent)
: QObject(parent)
, m_launcher(launcher)
{
}
void GameRunner::beginGameExecutable(Profile &profile, const std::optional<LoginAuth> &auth)
{
QString gameExectuable;
if (profile.directx9Enabled() && profile.hasDirectx9()) {
gameExectuable = profile.gamePath() + QStringLiteral("/game/ffxiv.exe");
} else {
gameExectuable = profile.gamePath() + QStringLiteral("/game/ffxiv_dx11.exe");
}
if (profile.dalamudShouldLaunch()) {
beginDalamudGame(gameExectuable, profile, auth);
} else {
beginVanillaGame(gameExectuable, profile, auth);
}
Q_EMIT m_launcher.successfulLaunch();
}
void GameRunner::beginVanillaGame(const QString &gameExecutablePath, Profile &profile, const std::optional<LoginAuth> &auth)
{
profile.setLoggedIn(true);
auto gameProcess = new QProcess(this);
gameProcess->setProcessEnvironment(QProcessEnvironment::systemEnvironment());
connect(gameProcess, &QProcess::finished, this, [this, &profile](int exitCode) {
profile.setLoggedIn(false);
Q_UNUSED(exitCode)
Q_EMIT m_launcher.gameClosed();
});
auto args = getGameArgs(profile, auth);
new ProcessLogger(gameProcess);
launchExecutable(profile, gameProcess, {gameExecutablePath, args}, true, true);
}
void GameRunner::beginDalamudGame(const QString &gameExecutablePath, Profile &profile, const std::optional<LoginAuth> &auth)
{
profile.setLoggedIn(true);
const QDir dataDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
const QDir configDir = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
const QDir dalamudDir = dataDir.absoluteFilePath(QStringLiteral("dalamud"));
const QDir dalamudConfigDir = configDir.absoluteFilePath(QStringLiteral("dalamud"));
const QDir userDalamudConfigDir = dalamudConfigDir.absoluteFilePath(profile.account()->uuid());
const QDir dalamudBasePluginDir = dalamudDir.absoluteFilePath(QStringLiteral("plugins"));
const QDir dalamudUserPluginDir = dalamudBasePluginDir.absoluteFilePath(profile.account()->uuid());
// Some really dumb plugins check for "installedPlugins" in their assembly directory FOR SOME REASON,
// 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"));
Utility::createPathIfNeeded(logDir);
const QDir dalamudRuntimeDir = dalamudDir.absoluteFilePath(QStringLiteral("runtime"));
const QDir dalamudAssetDir = dalamudDir.absoluteFilePath(QStringLiteral("assets"));
const QDir dalamudConfigPath = userDalamudConfigDir.absoluteFilePath(QStringLiteral("dalamud-config.json"));
const QDir dalamudInstallDir = dalamudDir.absoluteFilePath(profile.dalamudChannelName());
const QString dalamudInjector = dalamudInstallDir.absoluteFilePath(QStringLiteral("Dalamud.Injector.exe"));
auto dalamudProcess = new QProcess(this);
connect(dalamudProcess, &QProcess::finished, this, [this, &profile](int exitCode) {
profile.setLoggedIn(false);
Q_UNUSED(exitCode)
Q_EMIT m_launcher.gameClosed();
});
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
env.insert(QStringLiteral("DALAMUD_RUNTIME"), Utility::toWindowsPath(dalamudRuntimeDir));
#if defined(Q_OS_LINUX) || defined(Q_OS_MAC)
env.insert(QStringLiteral("XL_WINEONLINUX"), QStringLiteral("true"));
#endif
dalamudProcess->setProcessEnvironment(env);
new ProcessLogger(dalamudProcess);
const auto args = getGameArgs(profile, auth);
launchExecutable(profile,
dalamudProcess,
{Utility::toWindowsPath(dalamudInjector),
QStringLiteral("launch"),
QStringLiteral("-m"),
profile.dalamudInjectMethod() == Profile::DalamudInjectMethod::Entrypoint ? QStringLiteral("entrypoint") : QStringLiteral("inject"),
QStringLiteral("--game=") + Utility::toWindowsPath(gameExecutablePath),
QStringLiteral("--dalamud-configuration-path=") + Utility::toWindowsPath(dalamudConfigPath),
QStringLiteral("--dalamud-plugin-directory=") + Utility::toWindowsPath(dalamudPluginDir),
QStringLiteral("--dalamud-asset-directory=") + Utility::toWindowsPath(dalamudAssetDir),
QStringLiteral("--dalamud-client-language=") + QString::number(profile.account()->language()),
QStringLiteral("--dalamud-delay-initialize=") + QString::number(profile.dalamudInjectDelay()),
QStringLiteral("--logpath=") + Utility::toWindowsPath(logDir),
QStringLiteral("--"),
args},
true,
true);
}
QString GameRunner::getGameArgs(const Profile &profile, const std::optional<LoginAuth> &auth)
{
QList<std::pair<QString, QString>> gameArgs;
if (profile.isBenchmark()) {
gameArgs.push_back({QStringLiteral("SYS.Language"), QString::number(1)});
gameArgs.push_back({QStringLiteral("SYS.Fps"), QString::number(0)});
gameArgs.push_back({QStringLiteral("SYS.WaterWet_DX11"), QString::number(1)});
gameArgs.push_back({QStringLiteral("SYS.OcclusionCulling_DX11"), QString::number(0)});
gameArgs.push_back({QStringLiteral("SYS.LodType_DX11"), QString::number(0)});
gameArgs.push_back({QStringLiteral("SYS.ReflectionType_DX11"), QString::number(3)});
gameArgs.push_back({QStringLiteral("SYS.AntiAliasing_DX11"), QString::number(1)});
gameArgs.push_back({QStringLiteral("SYS.TranslucentQuality_DX11"), QString::number(1)});
gameArgs.push_back({QStringLiteral("SYS.GrassQuality_DX11"), QString::number(3)});
gameArgs.push_back({QStringLiteral("SYS.ShadowLOD_DX11"), QString::number(0)});
gameArgs.push_back({QStringLiteral("SYS.ShadowVisibilityTypeSelf_DX11"), QString::number(1)});
gameArgs.push_back({QStringLiteral("SYS.ShadowVisibilityTypeOther_DX11"), QString::number(1)});
gameArgs.push_back({QStringLiteral("SYS.ShadowTextureSizeType_DX11"), QString::number(2)});
gameArgs.push_back({QStringLiteral("SYS.ShadowCascadeCountType_DX11"), QString::number(2)});
gameArgs.push_back({QStringLiteral("SYS.ShadowSoftShadowType_DX11"), QString::number(1)});
gameArgs.push_back({QStringLiteral("SYS.PhysicsTypeSelf_DX11"), QString::number(2)});
gameArgs.push_back({QStringLiteral("SYS.PhysicsTypeOther_DX11"), QString::number(2)});
gameArgs.push_back({QStringLiteral("SYS.TextureFilterQuality_DX11"), QString::number(2)});
gameArgs.push_back({QStringLiteral("SYS.TextureAnisotropicQuality_DX11"), QString::number(2)});
gameArgs.push_back({QStringLiteral("SYS.Vignetting_DX11"), QString::number(1)});
gameArgs.push_back({QStringLiteral("SYS.RadialBlur_DX11"), QString::number(1)});
gameArgs.push_back({QStringLiteral("SYS.SSAO_DX11"), QString::number(2)});
gameArgs.push_back({QStringLiteral("SYS.Glare_DX11"), QString::number(2)});
gameArgs.push_back({QStringLiteral("SYS.DepthOfField_DX11"), QString::number(1)});
gameArgs.push_back({QStringLiteral("SYS.ParallaxOcclusion_DX11"), QString::number(1)});
gameArgs.push_back({QStringLiteral("SYS.Tessellation_DX11"), QString::number(0)});
gameArgs.push_back({QStringLiteral("SYS.GlareRepresentation_DX11"), QString::number(1)});
gameArgs.push_back({QStringLiteral("SYS.DistortionWater_DX11"), QString::number(2)});
gameArgs.push_back({QStringLiteral("SYS.ScreenMode"), QString::number(0)});
gameArgs.push_back({QStringLiteral("SYS.ScreenWidth"), QString::number(1920)});
gameArgs.push_back({QStringLiteral("SYS.ScreenHeight"), QString::number(1080)});
} else {
gameArgs.push_back({QStringLiteral("DEV.DataPathType"), QString::number(1)});
gameArgs.push_back({QStringLiteral("DEV.UseSqPack"), QString::number(1)});
gameArgs.push_back({QStringLiteral("ver"), profile.baseGameVersion()});
gameArgs.push_back({QStringLiteral("resetconfig"), QStringLiteral("0")});
gameArgs.push_back({QStringLiteral("language"), QString::number(profile.account()->language())});
gameArgs.push_back({QStringLiteral("UserPath"), Utility::toWindowsPath(profile.account()->getConfigDir().absolutePath())});
Utility::createPathIfNeeded(profile.account()->getConfigDir().absolutePath());
if (auth) {
gameArgs.push_back({QStringLiteral("DEV.MaxEntitledExpansionID"), QString::number(auth->maxExpansion)});
gameArgs.push_back({QStringLiteral("DEV.TestSID"), auth->SID});
gameArgs.push_back({QStringLiteral("SYS.Region"), QString::number(auth->region)});
} else {
// fallback just needed to get to the title screen
gameArgs.push_back({QStringLiteral("DEV.MaxEntitledExpansionID"), QString::number(1)});
gameArgs.push_back({QStringLiteral("DEV.TestSID"), QString::number(1)});
gameArgs.push_back({QStringLiteral("SYS.Region"), QString::number(1)});
}
}
// FIXME: this should belong somewhere else...
if (LauncherCore::needsCompatibilityTool())
Utility::createPathIfNeeded(profile.winePrefixPath());
if (auth) {
if (!auth->lobbyhost.isEmpty()) {
gameArgs.push_back({QStringLiteral("DEV.GMServerHost"), auth->frontierHost});
for (int i = 1; i < 9; i++) {
gameArgs.push_back({QStringLiteral("DEV.LobbyHost0%1").arg(QString::number(i)), auth->lobbyhost});
gameArgs.push_back({QStringLiteral("DEV.LobbyPort0%1").arg(QString::number(i)), QString::number(54994)});
}
}
if (profile.account()->license() == Account::GameLicense::WindowsSteam) {
gameArgs.push_back({QStringLiteral("IsSteam"), QString::number(1)});
}
}
const QString argFormat = !profile.isBenchmark() && m_launcher.settings()->argumentsEncrypted() ? QStringLiteral(" /%1 =%2") : QStringLiteral(" %1=%2");
QString argJoined;
for (const auto &[key, value] : gameArgs) {
argJoined += argFormat.arg(key, value);
}
return !profile.isBenchmark() && m_launcher.settings()->argumentsEncrypted() ? encryptGameArg(argJoined) : argJoined;
}
void GameRunner::launchExecutable(const Profile &profile, QProcess *process, const QStringList &args, bool isGame, bool needsRegistrySetup)
{
QList<QString> arguments;
auto env = process->processEnvironment();
if (needsRegistrySetup) {
#if defined(Q_OS_LINUX) || defined(Q_OS_MAC)
// FFXIV detects this as a "macOS" build by checking if Wine shows up
if (!profile.isBenchmark()) {
const int value = profile.account()->license() == Account::GameLicense::macOS ? 0 : 1;
addRegistryKey(profile, QStringLiteral("HKEY_CURRENT_USER\\Software\\Wine"), QStringLiteral("HideWineExports"), QString::number(value));
}
setWindowsVersion(profile, QStringLiteral("win7"));
// TODO: band-aid fix to wait for wine to exit, otherwise gamescope will collide
QThread::sleep(2);
// copy DXVK
const QDir dataDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
const QDir compatibilityToolDir = dataDir.absoluteFilePath(QStringLiteral("tool"));
const QDir dxvkDir = compatibilityToolDir.absoluteFilePath(QStringLiteral("dxvk"));
const QDir dxvk64Dir = dxvkDir.absoluteFilePath(QStringLiteral("x64"));
const QDir winePrefix = profile.winePrefixPath();
const QDir driveC = winePrefix.absoluteFilePath(QStringLiteral("drive_c"));
const QDir windows = driveC.absoluteFilePath(QStringLiteral("windows"));
const QDir system32 = windows.absoluteFilePath(QStringLiteral("system32"));
for (const auto &entry : dxvk64Dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot)) {
if (QFile::exists(system32.absoluteFilePath(entry.fileName()))) {
QFile::remove(system32.absoluteFilePath(entry.fileName()));
}
QFile::copy(entry.absoluteFilePath(), system32.absoluteFilePath(entry.fileName()));
}
#endif
}
if (isGame && profile.gamescopeEnabled()) {
arguments.push_back(QStringLiteral("gamescope"));
if (profile.gamescopeFullscreen())
arguments.push_back(QStringLiteral("-f"));
if (profile.gamescopeBorderless())
arguments.push_back(QStringLiteral("-b"));
if (profile.gamescopeWidth() > 0) {
arguments.push_back(QStringLiteral("-w"));
arguments.push_back(QString::number(profile.gamescopeWidth()));
}
if (profile.gamescopeHeight() > 0) {
arguments.push_back(QStringLiteral("-h"));
arguments.push_back(QString::number(profile.gamescopeHeight()));
}
if (profile.gamescopeRefreshRate() > 0) {
arguments.push_back(QStringLiteral("-r"));
arguments.push_back(QString::number(profile.gamescopeRefreshRate()));
}
arguments.push_back(QStringLiteral("--"));
}
#ifdef ENABLE_GAMEMODE
if (isGame && profile.gamemodeEnabled()) {
gamemode_request_start();
}
#endif
if (m_launcher.settings()->enableRenderDocCapture()) {
env.insert(QStringLiteral("VK_LAYER_RENDERDOC_Capture"), QStringLiteral("VK_LAYER_RENDERDOC_Capture"));
env.insert(QStringLiteral("ENABLE_VULKAN_RENDERDOC_CAPTURE"), QString::number(1));
}
#if defined(Q_OS_LINUX)
env.insert(QStringLiteral("WINEESYNC"), QString::number(1));
env.insert(QStringLiteral("WINEFSYNC"), QString::number(1));
env.insert(QStringLiteral("WINEFSYNC_FUTEX2"), QString::number(1));
const QDir dataDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
const QString logDir = dataDir.absoluteFilePath(QStringLiteral("log"));
env.insert(QStringLiteral("DXVK_LOG_PATH"), logDir);
#endif
#if defined(Q_OS_MAC) || defined(Q_OS_LINUX)
env.insert(QStringLiteral("WINEPREFIX"), profile.winePrefixPath());
if (profile.wineType() == Profile::WineType::BuiltIn) {
env.insert(QStringLiteral("WINEDLLOVERRIDES"), QStringLiteral("msquic=,mscoree=n,b;d3d9,d3d11,d3d10core,dxgi=n,b"));
}
arguments.push_back(profile.winePath());
#endif
if (!profile.isBenchmark() && profile.account()->license() == Account::GameLicense::WindowsSteam) {
env.insert(QStringLiteral("IS_FFXIV_LAUNCH_FROM_STEAM"), QStringLiteral("1"));
}
arguments.append(args);
const QString executable = arguments.takeFirst();
if (isGame) {
if (profile.isBenchmark()) {
// Benchmarks usually have some data located in the root
process->setWorkingDirectory(profile.gamePath());
} else {
process->setWorkingDirectory(profile.gamePath() + QStringLiteral("/game/"));
}
}
process->setProcessEnvironment(env);
process->setProgram(executable);
process->setArguments(arguments);
process->start();
}
void GameRunner::addRegistryKey(const Profile &settings, const QString &key, const QString &value, const QString &data)
{
auto process = new QProcess(this);
process->setProcessEnvironment(QProcessEnvironment::systemEnvironment());
launchExecutable(settings,
process,
{QStringLiteral("reg"), QStringLiteral("add"), key, QStringLiteral("/v"), value, QStringLiteral("/d"), data, QStringLiteral("/f")},
false,
false);
process->waitForFinished();
}
void GameRunner::setWindowsVersion(const Profile &settings, const QString &version)
{
auto process = new QProcess(this);
process->setProcessEnvironment(QProcessEnvironment::systemEnvironment());
launchExecutable(settings, process, {QStringLiteral("winecfg"), QStringLiteral("/v"), version}, false, false);
process->waitForFinished();
}