1
Fork 0
mirror of https://github.com/redstrate/Astra.git synced 2025-06-08 15:07:45 +00:00

Add support for installing the official benchmark

This uses the same profile system as the regular game, and can be used
to download the current benchmark (currently hardcoded, to be fixed
later.) Or as always, install it offline from an existing zip.
This commit is contained in:
Joshua Goins 2024-04-19 20:29:28 -04:00
parent 9bfab75c68
commit 49f8aae877
16 changed files with 430 additions and 58 deletions

View file

@ -44,6 +44,7 @@ target_sources(astra PRIVATE
include/account.h include/account.h
include/accountmanager.h include/accountmanager.h
include/assetupdater.h include/assetupdater.h
include/benchmarkinstaller.h
include/compatibilitytoolinstaller.h include/compatibilitytoolinstaller.h
include/encryptedarg.h include/encryptedarg.h
include/gamerunner.h include/gamerunner.h
@ -64,6 +65,7 @@ target_sources(astra PRIVATE
src/account.cpp src/account.cpp
src/accountmanager.cpp src/accountmanager.cpp
src/assetupdater.cpp src/assetupdater.cpp
src/benchmarkinstaller.cpp
src/compatibilitytoolinstaller.cpp src/compatibilitytoolinstaller.cpp
src/encryptedarg.cpp src/encryptedarg.cpp
src/gamerunner.cpp src/gamerunner.cpp
@ -105,6 +107,7 @@ qt_target_qml_sources(astra
ui/Setup/AccountSetup.qml ui/Setup/AccountSetup.qml
ui/Setup/AddSapphire.qml ui/Setup/AddSapphire.qml
ui/Setup/AddSquareEnix.qml ui/Setup/AddSquareEnix.qml
ui/Setup/BenchmarkInstallProgress.qml
ui/Setup/ExistingSetup.qml ui/Setup/ExistingSetup.qml
ui/Setup/InstallProgress.qml ui/Setup/InstallProgress.qml
ui/Setup/SetupPage.qml ui/Setup/SetupPage.qml

View file

@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <QObject>
#include <QString>
#include <QtQml/qqmlregistration.h>
class LauncherCore;
class Profile;
class BenchmarkInstaller : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("Use LauncherCore.createBenchmarkInstaller")
public:
BenchmarkInstaller(LauncherCore &launcher, Profile &profile, QObject *parent = nullptr);
BenchmarkInstaller(LauncherCore &launcher, Profile &profile, const QString &filePath, QObject *parent = nullptr);
Q_INVOKABLE void start();
Q_SIGNALS:
void installFinished();
void error(QString message);
private:
void installGame();
LauncherCore &m_launcher;
Profile &m_profile;
QString m_localInstallerPath;
};

View file

@ -16,17 +16,17 @@ public:
explicit GameRunner(LauncherCore &launcher, QObject *parent = nullptr); explicit GameRunner(LauncherCore &launcher, QObject *parent = nullptr);
/// Begins the game executable, but calls to Dalamud if needed. /// Begins the game executable, but calls to Dalamud if needed.
void beginGameExecutable(Profile &settings, const LoginAuth &auth); void beginGameExecutable(Profile &settings, const std::optional<LoginAuth> &auth);
private: private:
/// Starts a vanilla game session with no Dalamud injection. /// Starts a vanilla game session with no Dalamud injection.
void beginVanillaGame(const QString &gameExecutablePath, Profile &profile, const LoginAuth &auth); void beginVanillaGame(const QString &gameExecutablePath, Profile &profile, const std::optional<LoginAuth> &auth);
/// Starts a game session with Dalamud injected. /// Starts a game session with Dalamud injected.
void beginDalamudGame(const QString &gameExecutablePath, Profile &profile, const LoginAuth &auth); void beginDalamudGame(const QString &gameExecutablePath, Profile &profile, const std::optional<LoginAuth> &auth);
/// Returns the game arguments needed to properly launch the game. This encrypts it too if needed, and it's already joined! /// Returns the game arguments needed to properly launch the game. This encrypts it too if needed, and it's already joined!
QString getGameArgs(const Profile &profile, const LoginAuth &auth); QString getGameArgs(const Profile &profile, const std::optional<LoginAuth> &auth);
/// This wraps it in wine if needed. /// This wraps it in wine if needed.
void launchExecutable(const Profile &settings, QProcess *process, const QStringList &args, bool isGame, bool needsRegistrySetup); void launchExecutable(const Profile &settings, QProcess *process, const QStringList &args, bool isGame, bool needsRegistrySetup);

View file

@ -22,6 +22,7 @@ class AssetUpdater;
class GameInstaller; class GameInstaller;
class CompatibilityToolInstaller; class CompatibilityToolInstaller;
class GameRunner; class GameRunner;
class BenchmarkInstaller;
class LoginInformation : public QObject class LoginInformation : public QObject
{ {
@ -88,6 +89,8 @@ public:
Q_INVOKABLE GameInstaller *createInstaller(Profile *profile); Q_INVOKABLE GameInstaller *createInstaller(Profile *profile);
Q_INVOKABLE GameInstaller *createInstallerFromExisting(Profile *profile, const QString &filePath); Q_INVOKABLE GameInstaller *createInstallerFromExisting(Profile *profile, const QString &filePath);
Q_INVOKABLE CompatibilityToolInstaller *createCompatInstaller(); Q_INVOKABLE CompatibilityToolInstaller *createCompatInstaller();
Q_INVOKABLE BenchmarkInstaller *createBenchmarkInstaller(Profile *profile);
Q_INVOKABLE BenchmarkInstaller *createBenchmarkInstallerFromExisting(Profile *profile, const QString &filePath);
Q_INVOKABLE void clearAvatarCache(); Q_INVOKABLE void clearAvatarCache();
Q_INVOKABLE void refreshNews(); Q_INVOKABLE void refreshNews();

View file

@ -42,6 +42,8 @@ class Profile : public QObject
Q_PROPERTY(QString dalamudVersionText READ dalamudVersionText NOTIFY gameInstallChanged) Q_PROPERTY(QString dalamudVersionText READ dalamudVersionText NOTIFY gameInstallChanged)
Q_PROPERTY(QString wineVersionText READ wineVersionText NOTIFY wineChanged) Q_PROPERTY(QString wineVersionText READ wineVersionText NOTIFY wineChanged)
Q_PROPERTY(bool loggedIn READ loggedIn NOTIFY loggedInChanged) Q_PROPERTY(bool loggedIn READ loggedIn NOTIFY loggedInChanged)
Q_PROPERTY(bool isBenchmark READ isBenchmark WRITE setIsBenchmark NOTIFY isBenchmarkChanged)
Q_PROPERTY(QString subtitle READ subtitle NOTIFY gameInstallChanged)
public: public:
explicit Profile(LauncherCore &launcher, const QString &key, QObject *parent = nullptr); explicit Profile(LauncherCore &launcher, const QString &key, QObject *parent = nullptr);
@ -110,6 +112,9 @@ public:
[[nodiscard]] int dalamudInjectDelay() const; [[nodiscard]] int dalamudInjectDelay() const;
void setDalamudInjectDelay(int value); void setDalamudInjectDelay(int value);
[[nodiscard]] bool isBenchmark() const;
void setIsBenchmark(bool value);
[[nodiscard]] Account *account() const; [[nodiscard]] Account *account() const;
[[nodiscard]] QString accountUuid() const; [[nodiscard]] QString accountUuid() const;
void setAccount(Account *account); void setAccount(Account *account);
@ -147,6 +152,8 @@ public:
[[nodiscard]] bool loggedIn() const; [[nodiscard]] bool loggedIn() const;
void setLoggedIn(bool value); void setLoggedIn(bool value);
[[nodiscard]] QString subtitle() const;
Q_SIGNALS: Q_SIGNALS:
void gameInstallChanged(); void gameInstallChanged();
void nameChanged(); void nameChanged();
@ -166,6 +173,7 @@ Q_SIGNALS:
void dalamudChannelChanged(); void dalamudChannelChanged();
void dalamudInjectMethodChanged(); void dalamudInjectMethodChanged();
void dalamudInjectDelayChanged(); void dalamudInjectDelayChanged();
void isBenchmarkChanged();
void accountChanged(); void accountChanged();
void wineChanged(); void wineChanged();
void loggedInChanged(); void loggedInChanged();

View file

@ -83,5 +83,8 @@ SPDX-License-Identifier: CC0-1.0
<entry key="DalamudInjectDelay" type="int"> <entry key="DalamudInjectDelay" type="int">
<default>0</default> <default>0</default>
</entry> </entry>
<entry key="IsBenchmark" type="bool">
<default>false</default>
</entry>
</group> </group>
</kcfg> </kcfg>

View file

@ -0,0 +1,89 @@
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "benchmarkinstaller.h"
#include <KArchiveDirectory>
#include <KLocalizedString>
#include <KZip>
#include <QFile>
#include <QNetworkReply>
#include <QStandardPaths>
#include "astra_log.h"
#include "launchercore.h"
#include "profile.h"
#include "utility.h"
// TODO: this should be dynamically grabbed from the webpage to avoid hardcoding it
const QString installerUrl = QStringLiteral("https://download.finalfantasyxiv.com/s9qmq6SJfMMqYM4o/ffxiv-dawntrail-bench.zip");
BenchmarkInstaller::BenchmarkInstaller(LauncherCore &launcher, Profile &profile, QObject *parent)
: QObject(parent)
, m_launcher(launcher)
, m_profile(profile)
{
}
BenchmarkInstaller::BenchmarkInstaller(LauncherCore &launcher, Profile &profile, const QString &filePath, QObject *parent)
: BenchmarkInstaller(launcher, profile, parent)
{
m_localInstallerPath = filePath;
}
void BenchmarkInstaller::start()
{
if (m_localInstallerPath.isEmpty()) {
const QNetworkRequest request = QNetworkRequest(QUrl(installerUrl));
Utility::printRequest(QStringLiteral("GET"), request);
// TODO: benchmarks are usually quite large, and need download progress reporting
auto reply = m_launcher.mgr()->get(request);
QObject::connect(reply, &QNetworkReply::finished, [this, reply] {
if (reply->error() != QNetworkReply::NetworkError::NoError) {
Q_EMIT error(reply->errorString());
return;
}
const QDir dataDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
const QByteArray data = reply->readAll();
QFile file(dataDir.absoluteFilePath(QStringLiteral("ffxiv-bench.zip")));
file.open(QIODevice::WriteOnly);
file.write(data);
file.close();
m_localInstallerPath = file.fileName();
installGame();
});
} else {
installGame();
}
}
void BenchmarkInstaller::installGame()
{
const QDir installDirectory = m_profile.gamePath();
KZip archive(m_localInstallerPath);
if (!archive.open(QIODevice::ReadOnly)) {
qCritical(ASTRA_LOG) << "Failed to extract benchmark files";
Q_EMIT error(i18n("Failed to extract benchmark files."));
return;
}
// the first directory is the same as the version we download
const KArchiveDirectory *root = archive.directory();
root->copyTo(installDirectory.absolutePath(), true);
archive.close();
m_profile.readGameVersion();
Q_EMIT installFinished();
qInfo(ASTRA_LOG) << "Installed game in" << installDirectory;
}
#include "moc_benchmarkinstaller.cpp"

View file

@ -20,7 +20,7 @@ GameRunner::GameRunner(LauncherCore &launcher, QObject *parent)
{ {
} }
void GameRunner::beginGameExecutable(Profile &profile, const LoginAuth &auth) void GameRunner::beginGameExecutable(Profile &profile, const std::optional<LoginAuth> &auth)
{ {
QString gameExectuable; QString gameExectuable;
if (profile.directx9Enabled() && profile.hasDirectx9()) { if (profile.directx9Enabled() && profile.hasDirectx9()) {
@ -38,7 +38,7 @@ void GameRunner::beginGameExecutable(Profile &profile, const LoginAuth &auth)
Q_EMIT m_launcher.successfulLaunch(); Q_EMIT m_launcher.successfulLaunch();
} }
void GameRunner::beginVanillaGame(const QString &gameExecutablePath, Profile &profile, const LoginAuth &auth) void GameRunner::beginVanillaGame(const QString &gameExecutablePath, Profile &profile, const std::optional<LoginAuth> &auth)
{ {
profile.setLoggedIn(true); profile.setLoggedIn(true);
@ -57,7 +57,7 @@ void GameRunner::beginVanillaGame(const QString &gameExecutablePath, Profile &pr
launchExecutable(profile, gameProcess, {gameExecutablePath, args}, true, true); launchExecutable(profile, gameProcess, {gameExecutablePath, args}, true, true);
} }
void GameRunner::beginDalamudGame(const QString &gameExecutablePath, Profile &profile, const LoginAuth &auth) void GameRunner::beginDalamudGame(const QString &gameExecutablePath, Profile &profile, const std::optional<LoginAuth> &auth)
{ {
profile.setLoggedIn(true); profile.setLoggedIn(true);
@ -123,28 +123,67 @@ void GameRunner::beginDalamudGame(const QString &gameExecutablePath, Profile &pr
true); true);
} }
QString GameRunner::getGameArgs(const Profile &profile, const LoginAuth &auth) QString GameRunner::getGameArgs(const Profile &profile, const std::optional<LoginAuth> &auth)
{ {
QList<std::pair<QString, QString>> gameArgs; QList<std::pair<QString, QString>> gameArgs;
if (!profile.isBenchmark()) {
gameArgs.push_back({QStringLiteral("DEV.DataPathType"), QString::number(1)}); gameArgs.push_back({QStringLiteral("DEV.DataPathType"), QString::number(1)});
gameArgs.push_back({QStringLiteral("DEV.UseSqPack"), QString::number(1)}); gameArgs.push_back({QStringLiteral("DEV.UseSqPack"), QString::number(1)});
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)});
gameArgs.push_back({QStringLiteral("language"), QString::number(profile.account()->language())});
gameArgs.push_back({QStringLiteral("ver"), profile.baseGameVersion()}); gameArgs.push_back({QStringLiteral("ver"), profile.baseGameVersion()});
gameArgs.push_back({QStringLiteral("UserPath"), Utility::toWindowsPath(profile.account()->getConfigDir().absolutePath())});
gameArgs.push_back({QStringLiteral("resetconfig"), QStringLiteral("0")}); gameArgs.push_back({QStringLiteral("resetconfig"), QStringLiteral("0")});
}
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)});
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());
} else 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)});
}
// FIXME: this should belong somewhere else... // FIXME: this should belong somewhere else...
Utility::createPathIfNeeded(profile.account()->getConfigDir().absolutePath());
Utility::createPathIfNeeded(profile.winePrefixPath()); Utility::createPathIfNeeded(profile.winePrefixPath());
if (!auth.lobbyhost.isEmpty()) { if (auth) {
gameArgs.push_back({QStringLiteral("DEV.GMServerHost"), auth.frontierHost}); if (!auth->lobbyhost.isEmpty()) {
gameArgs.push_back({QStringLiteral("DEV.GMServerHost"), auth->frontierHost});
for (int i = 1; i < 9; i++) { 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.LobbyHost0%1").arg(QString::number(i)), auth->lobbyhost});
gameArgs.push_back({QStringLiteral("DEV.LobbyPort0%1").arg(QString::number(i)), QString::number(54994)}); gameArgs.push_back({QStringLiteral("DEV.LobbyPort0%1").arg(QString::number(i)), QString::number(54994)});
} }
} }
@ -152,15 +191,16 @@ QString GameRunner::getGameArgs(const Profile &profile, const LoginAuth &auth)
if (profile.account()->license() == Account::GameLicense::WindowsSteam) { if (profile.account()->license() == Account::GameLicense::WindowsSteam) {
gameArgs.push_back({QStringLiteral("IsSteam"), QString::number(1)}); gameArgs.push_back({QStringLiteral("IsSteam"), QString::number(1)});
} }
}
const QString argFormat = m_launcher.settings()->argumentsEncrypted() ? QStringLiteral(" /%1 =%2") : QStringLiteral(" %1=%2"); const QString argFormat = !profile.isBenchmark() && m_launcher.settings()->argumentsEncrypted() ? QStringLiteral(" /%1 =%2") : QStringLiteral(" %1=%2");
QString argJoined; QString argJoined;
for (const auto &[key, value] : gameArgs) { for (const auto &[key, value] : gameArgs) {
argJoined += argFormat.arg(key, value); argJoined += argFormat.arg(key, value);
} }
return m_launcher.settings()->argumentsEncrypted() ? encryptGameArg(argJoined) : argJoined; 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) void GameRunner::launchExecutable(const Profile &profile, QProcess *process, const QStringList &args, bool isGame, bool needsRegistrySetup)
@ -171,8 +211,10 @@ void GameRunner::launchExecutable(const Profile &profile, QProcess *process, con
if (needsRegistrySetup) { if (needsRegistrySetup) {
#if defined(Q_OS_LINUX) || defined(Q_OS_MAC) #if defined(Q_OS_LINUX) || defined(Q_OS_MAC)
// FFXIV detects this as a "macOS" build by checking if Wine shows up // 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; const int value = profile.account()->license() == Account::GameLicense::macOS ? 0 : 1;
addRegistryKey(profile, QStringLiteral("HKEY_CURRENT_USER\\Software\\Wine"), QStringLiteral("HideWineExports"), QString::number(value)); addRegistryKey(profile, QStringLiteral("HKEY_CURRENT_USER\\Software\\Wine"), QStringLiteral("HideWineExports"), QString::number(value));
}
setWindowsVersion(profile, QStringLiteral("win7")); setWindowsVersion(profile, QStringLiteral("win7"));
@ -256,7 +298,7 @@ void GameRunner::launchExecutable(const Profile &profile, QProcess *process, con
arguments.push_back(profile.winePath()); arguments.push_back(profile.winePath());
#endif #endif
if (profile.account()->license() == Account::GameLicense::WindowsSteam) { if (!profile.isBenchmark() && profile.account()->license() == Account::GameLicense::WindowsSteam) {
env.insert(QStringLiteral("IS_FFXIV_LAUNCH_FROM_STEAM"), QStringLiteral("1")); env.insert(QStringLiteral("IS_FFXIV_LAUNCH_FROM_STEAM"), QStringLiteral("1"));
} }
@ -264,8 +306,14 @@ void GameRunner::launchExecutable(const Profile &profile, QProcess *process, con
const QString executable = arguments.takeFirst(); const QString executable = arguments.takeFirst();
if (isGame) 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->setWorkingDirectory(profile.gamePath() + QStringLiteral("/game/"));
}
}
process->setProcessEnvironment(env); process->setProcessEnvironment(env);

View file

@ -14,6 +14,7 @@
#include "account.h" #include "account.h"
#include "assetupdater.h" #include "assetupdater.h"
#include "astra_log.h" #include "astra_log.h"
#include "benchmarkinstaller.h"
#include "compatibilitytoolinstaller.h" #include "compatibilitytoolinstaller.h"
#include "gamerunner.h" #include "gamerunner.h"
#include "launchercore.h" #include "launchercore.h"
@ -65,6 +66,9 @@ void LauncherCore::login(Profile *profile, const QString &username, const QStrin
auto loginInformation = new LoginInformation(this); auto loginInformation = new LoginInformation(this);
loginInformation->profile = profile; loginInformation->profile = profile;
// Benchmark never has to login, of course
if (!profile->isBenchmark()) {
loginInformation->username = username; loginInformation->username = username;
loginInformation->password = password; loginInformation->password = password;
loginInformation->oneTimePassword = oneTimePassword; loginInformation->oneTimePassword = oneTimePassword;
@ -72,6 +76,7 @@ void LauncherCore::login(Profile *profile, const QString &username, const QStrin
if (profile->account()->rememberPassword()) { if (profile->account()->rememberPassword()) {
profile->account()->setPassword(password); profile->account()->setPassword(password);
} }
}
beginLogin(*loginInformation); beginLogin(*loginInformation);
} }
@ -117,6 +122,20 @@ CompatibilityToolInstaller *LauncherCore::createCompatInstaller()
return new CompatibilityToolInstaller(*this, this); return new CompatibilityToolInstaller(*this, this);
} }
BenchmarkInstaller *LauncherCore::createBenchmarkInstaller(Profile *profile)
{
Q_ASSERT(profile != nullptr);
return new BenchmarkInstaller(*this, *profile, this);
}
BenchmarkInstaller *LauncherCore::createBenchmarkInstallerFromExisting(Profile *profile, const QString &filePath)
{
Q_ASSERT(profile != nullptr);
return new BenchmarkInstaller(*this, *profile, filePath, this);
}
void LauncherCore::clearAvatarCache() void LauncherCore::clearAvatarCache()
{ {
const QString cacheLocation = QStandardPaths::standardLocations(QStandardPaths::CacheLocation)[0] + QStringLiteral("/avatars"); const QString cacheLocation = QStandardPaths::standardLocations(QStandardPaths::CacheLocation)[0] + QStringLiteral("/avatars");
@ -320,26 +339,34 @@ QString LauncherCore::cachedLogoImage() const
QCoro::Task<> LauncherCore::beginLogin(LoginInformation &info) QCoro::Task<> LauncherCore::beginLogin(LoginInformation &info)
{ {
// Hmm, I don't think we're set up for this yet?
if (!info.profile->isBenchmark()) {
info.profile->account()->updateConfig(); info.profile->account()->updateConfig();
}
auto assetUpdater = new AssetUpdater(*info.profile, *this, this); auto assetUpdater = new AssetUpdater(*info.profile, *this, this);
if (co_await assetUpdater->update()) { if (co_await assetUpdater->update()) {
std::optional<LoginAuth> auth; std::optional<LoginAuth> auth;
if (!info.profile->isBenchmark()) {
if (info.profile->account()->isSapphire()) { if (info.profile->account()->isSapphire()) {
auth = co_await m_sapphireLogin->login(info.profile->account()->lobbyUrl(), info); auth = co_await m_sapphireLogin->login(info.profile->account()->lobbyUrl(), info);
} else { } else {
auth = co_await m_squareEnixLogin->login(&info); auth = co_await m_squareEnixLogin->login(&info);
} }
}
// If we expect an auth ticket, don't continue if missing
if (!info.profile->isBenchmark() && auth == std::nullopt) {
co_return;
}
if (auth != std::nullopt) {
Q_EMIT stageChanged(i18n("Launching game...")); Q_EMIT stageChanged(i18n("Launching game..."));
if (isSteam()) { if (isSteam()) {
m_steamApi->setLauncherMode(false); m_steamApi->setLauncherMode(false);
} }
m_runner->beginGameExecutable(*info.profile, *auth); m_runner->beginGameExecutable(*info.profile, auth);
}
} }
assetUpdater->deleteLater(); assetUpdater->deleteLater();

View file

@ -371,6 +371,20 @@ void Profile::setDalamudInjectDelay(const int value)
} }
} }
bool Profile::isBenchmark() const
{
return m_config->isBenchmark();
}
void Profile::setIsBenchmark(bool value)
{
if (m_config->isBenchmark() != value) {
m_config->setIsBenchmark(value);
m_config->save();
Q_EMIT isBenchmarkChanged();
}
}
Account *Profile::account() const Account *Profile::account() const
{ {
return m_account; return m_account;
@ -575,4 +589,21 @@ void Profile::setLoggedIn(const bool value)
} }
} }
QString Profile::subtitle() const
{
if (isBenchmark()) {
return i18n("Benchmark");
} else if (m_repositories.repositories_count > 0) {
const unsigned int latestExpansion = m_repositories.repositories_count - 1;
QString expansionName = i18n("Unknown Expansion");
if (latestExpansion < static_cast<unsigned int>(m_expansionNames.size())) {
expansionName = m_expansionNames[latestExpansion];
}
return QStringLiteral("%1 (%2)").arg(expansionName, QString::fromLatin1(m_repositories.repositories[latestExpansion].version));
} else {
return i18n("Unknown");
}
}
#include "moc_profile.cpp" #include "moc_profile.cpp"

View file

@ -58,7 +58,7 @@ Kirigami.ApplicationWindow {
pageStack.push(Qt.createComponent("zone.xiv.astra", "SetupPage"), { pageStack.push(Qt.createComponent("zone.xiv.astra", "SetupPage"), {
profile: LauncherCore.currentProfile profile: LauncherCore.currentProfile
}) })
} else if (!LauncherCore.currentProfile.account) { } else if (!LauncherCore.currentProfile.account && !LauncherCore.currentProfile.isBenchmark) {
// User must select an account for the profile // User must select an account for the profile
pageStack.push(Qt.createComponent("zone.xiv.astra", "AccountSetup"), { pageStack.push(Qt.createComponent("zone.xiv.astra", "AccountSetup"), {
profile: LauncherCore.currentProfile profile: LauncherCore.currentProfile

View file

@ -158,6 +158,9 @@ QQC2.Control {
} }
FormCard.FormCard { FormCard.FormCard {
id: regularLoginCard
visible: !LauncherCore.currentProfile.isBenchmark
maximumWidth: Kirigami.Units.gridUnit * 25 maximumWidth: Kirigami.Units.gridUnit * 25
Layout.fillWidth: true Layout.fillWidth: true
@ -296,6 +299,25 @@ QQC2.Control {
} }
} }
FormCard.FormCard {
id: benchmarkLaunchCard
visible: LauncherCore.currentProfile.isBenchmark
maximumWidth: Kirigami.Units.gridUnit * 25
Layout.fillWidth: true
FormCard.FormButtonDelegate {
id: launchBenchmarkDelegate
text: i18n("Launch Benchmark")
onClicked: {
LauncherCore.login(LauncherCore.currentProfile, "", "", "")
page.Window.window.pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "StatusPage"))
}
}
}
Item { Item {
Layout.fillHeight: true Layout.fillHeight: true
} }

View file

@ -35,10 +35,12 @@ FormCard.FormCardPage {
] ]
FormCard.FormCard { FormCard.FormCard {
Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.largeSpacing Layout.topMargin: Kirigami.Units.largeSpacing
visible: repeater.count !== 0
Repeater { Repeater {
id: repeater
model: LauncherCore.accountManager model: LauncherCore.accountManager
ColumnLayout { ColumnLayout {
@ -53,7 +55,8 @@ FormCard.FormCardPage {
text: layout.account.name text: layout.account.name
description: layout.account.isSapphire ? i18n("Sapphire") : i18n("Square Enix") description: layout.account.isSapphire ? i18n("Sapphire") : i18n("Square Enix")
leading: Components.Avatar { leading: Components.Avatar
{
name: layout.account.name name: layout.account.name
source: layout.account.avatarUrl source: layout.account.avatarUrl
} }

View file

@ -88,6 +88,7 @@ FormCard.FormCardPage {
id: buttonDelegate id: buttonDelegate
text: layout.profile.name text: layout.profile.name
description: layout.profile.subtitle
onClicked: page.Window.window.pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "ProfileSettings"), { onClicked: page.Window.window.pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "ProfileSettings"), {
profile: layout.profile profile: layout.profile
}) })

View file

@ -0,0 +1,51 @@
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick
import QtQuick.Window
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import zone.xiv.astra
Kirigami.Page {
id: page
property var benchmarkInstaller
title: i18n("Benchmark Installation")
Kirigami.LoadingPlaceholder {
anchors.centerIn: parent
text: i18n("Downloading…")
}
Kirigami.PromptDialog {
id: errorDialog
title: i18n("Installation Error")
showCloseButton: false
standardButtons: Kirigami.Dialog.Ok
onAccepted: page.Window.window.pageStack.layers.pop()
onRejected: page.Window.window.pageStack.layers.pop()
}
Component.onCompleted: benchmarkInstaller.start()
Connections {
target: page.benchmarkInstaller
function onInstallFinished() {
// Prevents it from failing to push the page if the install happens too quickly.
Qt.callLater(() => applicationWindow().checkSetup());
}
function onError(message) {
errorDialog.subtitle = i18n("An error has occurred while installing the benchmark:\n\n%1", message);
errorDialog.open();
}
}
}

View file

@ -97,8 +97,7 @@ FormCard.FormCardPage {
} }
FormCard.FormHeader { FormCard.FormHeader {
title: i18n("Install Game") title: i18n("Retail Game")
visible: LauncherCore.accountManager.hasAnyAccounts()
} }
FormCard.FormCard { FormCard.FormCard {
@ -145,4 +144,53 @@ FormCard.FormCardPage {
onClicked: dialog.open() onClicked: dialog.open()
} }
} }
FormCard.FormHeader {
title: i18n("Benchmark")
}
FormCard.FormCard {
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true
FormCard.FormButtonDelegate {
id: downloadBenchmark
text: i18n("Download & Install Benchmark")
description: i18n("Download the official benchmark from Square Enix.")
icon.name: "cloud-download"
onClicked: page.Window.window.pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "BenchmarkInstallProgress"), {
benchmarkInstaller: LauncherCore.createBenchmarkInstaller(page.profile)
})
}
FormCard.FormDelegateSeparator {
above: downloadBenchmark
below: selectBenchmark
}
FormCard.FormButtonDelegate {
id: selectBenchmark
text: i18n("Select Existing Benchmark…")
description: i18n("Use a previously downloaded benchmark. Useful if offline or can't otherwise access the official servers.")
icon.name: "edit-find"
FileDialog {
id: benchmarkDialog
currentFolder: StandardPaths.standardLocations(StandardPaths.DownloadLocation)[0]
nameFilters: [i18n("Benchmark zip archive (*.zip)")]
onAccepted: {
const url = decodeURIComponent(selectedFile.toString().replace("file://", ""));
page.Window.window.pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "BenchmarkInstallProgress"), {
benchmarkInstaller: LauncherCore.createBenchmarkInstallerFromExisting(page.profile, url)
});
}
}
onClicked: benchmarkDialog.open()
}
}
} }