diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 9351f31..ae3c9d7 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -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 diff --git a/launcher/include/benchmarkinstaller.h b/launcher/include/benchmarkinstaller.h new file mode 100644 index 0000000..197976a --- /dev/null +++ b/launcher/include/benchmarkinstaller.h @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2024 Joshua Goins +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include + +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; +}; \ No newline at end of file diff --git a/launcher/include/gamerunner.h b/launcher/include/gamerunner.h index 57b8f88..a680067 100644 --- a/launcher/include/gamerunner.h +++ b/launcher/include/gamerunner.h @@ -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 &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 &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 &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 &auth); /// This wraps it in wine if needed. void launchExecutable(const Profile &settings, QProcess *process, const QStringList &args, bool isGame, bool needsRegistrySetup); diff --git a/launcher/include/launchercore.h b/launcher/include/launchercore.h index 374fe32..f55d53b 100755 --- a/launcher/include/launchercore.h +++ b/launcher/include/launchercore.h @@ -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(); diff --git a/launcher/include/profile.h b/launcher/include/profile.h index f9a43fe..112d6a6 100644 --- a/launcher/include/profile.h +++ b/launcher/include/profile.h @@ -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(); diff --git a/launcher/profileconfig.kcfg b/launcher/profileconfig.kcfg index b85d9a0..53c3a53 100644 --- a/launcher/profileconfig.kcfg +++ b/launcher/profileconfig.kcfg @@ -83,5 +83,8 @@ SPDX-License-Identifier: CC0-1.0 0 + + false + diff --git a/launcher/src/benchmarkinstaller.cpp b/launcher/src/benchmarkinstaller.cpp new file mode 100644 index 0000000..1717fc3 --- /dev/null +++ b/launcher/src/benchmarkinstaller.cpp @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2024 Joshua Goins +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "benchmarkinstaller.h" + +#include +#include +#include +#include +#include +#include + +#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" \ No newline at end of file diff --git a/launcher/src/gamerunner.cpp b/launcher/src/gamerunner.cpp index 09a7b7c..885232f 100644 --- a/launcher/src/gamerunner.cpp +++ b/launcher/src/gamerunner.cpp @@ -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 &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 &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 &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 &auth) { QList> 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); diff --git a/launcher/src/launchercore.cpp b/launcher/src/launchercore.cpp index cd7828c..de2a340 100755 --- a/launcher/src/launchercore.cpp +++ b/launcher/src/launchercore.cpp @@ -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 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(); diff --git a/launcher/src/profile.cpp b/launcher/src/profile.cpp index 142b546..05a8f28 100644 --- a/launcher/src/profile.cpp +++ b/launcher/src/profile.cpp @@ -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(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" \ No newline at end of file diff --git a/launcher/ui/Main.qml b/launcher/ui/Main.qml index 1c91e22..1b42c1a 100644 --- a/launcher/ui/Main.qml +++ b/launcher/ui/Main.qml @@ -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 diff --git a/launcher/ui/Pages/LoginPage.qml b/launcher/ui/Pages/LoginPage.qml index 7ab06e9..83cef58 100644 --- a/launcher/ui/Pages/LoginPage.qml +++ b/launcher/ui/Pages/LoginPage.qml @@ -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 } diff --git a/launcher/ui/Settings/AccountsPage.qml b/launcher/ui/Settings/AccountsPage.qml index 8799e54..580736e 100644 --- a/launcher/ui/Settings/AccountsPage.qml +++ b/launcher/ui/Settings/AccountsPage.qml @@ -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 } diff --git a/launcher/ui/Settings/ProfilesPage.qml b/launcher/ui/Settings/ProfilesPage.qml index 5f26791..0ae8ac2 100644 --- a/launcher/ui/Settings/ProfilesPage.qml +++ b/launcher/ui/Settings/ProfilesPage.qml @@ -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 }) diff --git a/launcher/ui/Setup/BenchmarkInstallProgress.qml b/launcher/ui/Setup/BenchmarkInstallProgress.qml new file mode 100644 index 0000000..13d1db3 --- /dev/null +++ b/launcher/ui/Setup/BenchmarkInstallProgress.qml @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2024 Joshua Goins +// 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(); + } + } +} \ No newline at end of file diff --git a/launcher/ui/Setup/SetupPage.qml b/launcher/ui/Setup/SetupPage.qml index fd51b4c..c6f4204 100644 --- a/launcher/ui/Setup/SetupPage.qml +++ b/launcher/ui/Setup/SetupPage.qml @@ -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() + } + } } \ No newline at end of file