1
Fork 0
mirror of https://github.com/redstrate/Astra.git synced 2025-06-07 22:47:46 +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/accountmanager.h
include/assetupdater.h
include/benchmarkinstaller.h
include/compatibilitytoolinstaller.h
include/encryptedarg.h
include/gamerunner.h
@ -64,6 +65,7 @@ target_sources(astra PRIVATE
src/account.cpp
src/accountmanager.cpp
src/assetupdater.cpp
src/benchmarkinstaller.cpp
src/compatibilitytoolinstaller.cpp
src/encryptedarg.cpp
src/gamerunner.cpp
@ -105,6 +107,7 @@ qt_target_qml_sources(astra
ui/Setup/AccountSetup.qml
ui/Setup/AddSapphire.qml
ui/Setup/AddSquareEnix.qml
ui/Setup/BenchmarkInstallProgress.qml
ui/Setup/ExistingSetup.qml
ui/Setup/InstallProgress.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);
/// 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:
/// 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.
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!
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.
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 CompatibilityToolInstaller;
class GameRunner;
class BenchmarkInstaller;
class LoginInformation : public QObject
{
@ -88,6 +89,8 @@ public:
Q_INVOKABLE GameInstaller *createInstaller(Profile *profile);
Q_INVOKABLE GameInstaller *createInstallerFromExisting(Profile *profile, const QString &filePath);
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 refreshNews();

View file

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

View file

@ -83,5 +83,8 @@ SPDX-License-Identifier: CC0-1.0
<entry key="DalamudInjectDelay" type="int">
<default>0</default>
</entry>
<entry key="IsBenchmark" type="bool">
<default>false</default>
</entry>
</group>
</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;
if (profile.directx9Enabled() && profile.hasDirectx9()) {
@ -38,7 +38,7 @@ void GameRunner::beginGameExecutable(Profile &profile, const LoginAuth &auth)
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);
@ -57,7 +57,7 @@ void GameRunner::beginVanillaGame(const QString &gameExecutablePath, Profile &pr
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);
@ -123,44 +123,84 @@ void GameRunner::beginDalamudGame(const QString &gameExecutablePath, Profile &pr
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;
gameArgs.push_back({QStringLiteral("DEV.DataPathType"), 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("UserPath"), Utility::toWindowsPath(profile.account()->getConfigDir().absolutePath())});
gameArgs.push_back({QStringLiteral("resetconfig"), QStringLiteral("0")});
if (!profile.isBenchmark()) {
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")});
}
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...
Utility::createPathIfNeeded(profile.account()->getConfigDir().absolutePath());
Utility::createPathIfNeeded(profile.winePrefixPath());
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 (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)});
}
}
if (profile.account()->license() == Account::GameLicense::WindowsSteam) {
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;
for (const auto &[key, value] : gameArgs) {
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)
@ -171,8 +211,10 @@ void GameRunner::launchExecutable(const Profile &profile, QProcess *process, con
if (needsRegistrySetup) {
#if defined(Q_OS_LINUX) || defined(Q_OS_MAC)
// FFXIV detects this as a "macOS" build by checking if Wine shows up
const int value = profile.account()->license() == Account::GameLicense::macOS ? 0 : 1;
addRegistryKey(profile, QStringLiteral("HKEY_CURRENT_USER\\Software\\Wine"), QStringLiteral("HideWineExports"), QString::number(value));
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"));
@ -256,7 +298,7 @@ void GameRunner::launchExecutable(const Profile &profile, QProcess *process, con
arguments.push_back(profile.winePath());
#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"));
}
@ -264,8 +306,14 @@ void GameRunner::launchExecutable(const Profile &profile, QProcess *process, con
const QString executable = arguments.takeFirst();
if (isGame)
process->setWorkingDirectory(profile.gamePath() + QStringLiteral("/game/"));
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);

View file

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

View file

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

View file

@ -158,6 +158,9 @@ QQC2.Control {
}
FormCard.FormCard {
id: regularLoginCard
visible: !LauncherCore.currentProfile.isBenchmark
maximumWidth: Kirigami.Units.gridUnit * 25
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 {
Layout.fillHeight: true
}

View file

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

View file

@ -88,6 +88,7 @@ FormCard.FormCardPage {
id: buttonDelegate
text: layout.profile.name
description: layout.profile.subtitle
onClicked: page.Window.window.pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "ProfileSettings"), {
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 {
title: i18n("Install Game")
visible: LauncherCore.accountManager.hasAnyAccounts()
title: i18n("Retail Game")
}
FormCard.FormCard {
@ -145,4 +144,53 @@ FormCard.FormCardPage {
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()
}
}
}