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:
parent
9bfab75c68
commit
49f8aae877
16 changed files with 430 additions and 58 deletions
|
@ -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
|
||||||
|
|
35
launcher/include/benchmarkinstaller.h
Normal file
35
launcher/include/benchmarkinstaller.h
Normal 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;
|
||||||
|
};
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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>
|
||||||
|
|
89
launcher/src/benchmarkinstaller.cpp
Normal file
89
launcher/src/benchmarkinstaller.cpp
Normal 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"
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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"
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
})
|
})
|
||||||
|
|
51
launcher/ui/Setup/BenchmarkInstallProgress.qml
Normal file
51
launcher/ui/Setup/BenchmarkInstallProgress.qml
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue