1
Fork 0
mirror of https://github.com/redstrate/Astra.git synced 2025-04-21 20:27:45 +00:00

Overhaul parts of the initial setup flow

This removes the separate "download new game" page and rolls in into the
main profile setup. Also adds a feature to install the game from an
existing executable, in the event the official servers are down or
missing. Also shifts around some of the buttons and text.
This commit is contained in:
Joshua Goins 2024-04-01 14:54:41 -04:00
parent becb5b9289
commit dc01f3e214
9 changed files with 137 additions and 116 deletions

View file

@ -105,7 +105,6 @@ qt_target_qml_sources(astra
ui/Setup/AccountSetup.qml
ui/Setup/AddSapphire.qml
ui/Setup/AddSquareEnix.qml
ui/Setup/DownloadSetup.qml
ui/Setup/ExistingSetup.qml
ui/Setup/InstallProgress.qml
ui/Setup/SetupPage.qml

View file

@ -18,14 +18,18 @@ class GameInstaller : public QObject
public:
GameInstaller(LauncherCore &launcher, Profile &profile, QObject *parent = nullptr);
GameInstaller(LauncherCore &launcher, Profile &profile, const QString &filePath, QObject *parent = nullptr);
Q_INVOKABLE void installGame();
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

@ -86,6 +86,7 @@ public:
Q_INVOKABLE bool autoLogin(Profile *profile);
Q_INVOKABLE GameInstaller *createInstaller(Profile *profile);
Q_INVOKABLE GameInstaller *createInstallerFromExisting(Profile *profile, const QString &filePath);
Q_INVOKABLE CompatibilityToolInstaller *createCompatInstaller();
Q_INVOKABLE void clearAvatarCache();
@ -121,6 +122,7 @@ Q_SIGNALS:
void gameClosed();
void loginError(QString message);
void dalamudError(QString message);
void miscError(QString message);
void stageChanged(QString message, QString explanation = {});
void stageIndeterminate();
void stageDeterminate(int min, int max, int value);

View file

@ -24,45 +24,63 @@ GameInstaller::GameInstaller(LauncherCore &launcher, Profile &profile, QObject *
{
}
GameInstaller::GameInstaller(LauncherCore &launcher, Profile &profile, const QString &filePath, QObject *parent)
: GameInstaller(launcher, profile, parent)
{
m_localInstallerPath = filePath;
}
void GameInstaller::start()
{
if (m_localInstallerPath.isEmpty()) {
const QNetworkRequest request = QNetworkRequest(QUrl(installerUrl));
Utility::printRequest(QStringLiteral("GET"), request);
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();
QCryptographicHash hash(QCryptographicHash::Sha256);
hash.addData(data);
if (hash.result() != installerSha256) {
Q_EMIT error(i18n("The installer failed the integrity check!"));
return;
}
QFile file(dataDir.absoluteFilePath(QStringLiteral("ffxivsetup.exe")));
file.open(QIODevice::WriteOnly);
file.write(data);
file.close();
m_localInstallerPath = file.fileName();
installGame();
});
} else {
installGame();
}
}
void GameInstaller::installGame()
{
const QDir installDirectory = m_profile.gamePath();
const QNetworkRequest request = QNetworkRequest(QUrl(installerUrl));
Utility::printRequest(QStringLiteral("GET"), request);
const std::string installDirectoryStd = installDirectory.absolutePath().toStdString();
const std::string fileNameStd = m_localInstallerPath.toStdString();
auto reply = m_launcher.mgr()->get(request);
physis_install_game(fileNameStd.c_str(), installDirectoryStd.c_str());
QObject::connect(reply, &QNetworkReply::finished, [this, reply, installDirectory] {
if (reply->error() != QNetworkReply::NetworkError::NoError) {
Q_EMIT error(i18n("An error has occurred when downloading the installer.\n\n%1", reply->errorString()));
return;
}
m_profile.readGameVersion();
const QDir dataDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
const QByteArray data = reply->readAll();
QCryptographicHash hash(QCryptographicHash::Sha256);
hash.addData(data);
// TODO: turn into a proper error
Q_ASSERT(hash.result() == installerSha256);
QFile file(dataDir.absoluteFilePath(QStringLiteral("ffxivsetup.exe")));
file.open(QIODevice::WriteOnly);
file.write(data);
file.close();
const std::string installDirectoryStd = installDirectory.absolutePath().toStdString();
const std::string fileNameStd = file.fileName().toStdString();
physis_install_game(fileNameStd.c_str(), installDirectoryStd.c_str());
m_profile.readGameVersion();
Q_EMIT installFinished();
qInfo(ASTRA_LOG) << "Installed game in" << installDirectory;
});
Q_EMIT installFinished();
qInfo(ASTRA_LOG) << "Installed game in" << installDirectory;
}
#include "moc_gameinstaller.cpp"

View file

@ -105,6 +105,13 @@ GameInstaller *LauncherCore::createInstaller(Profile *profile)
return new GameInstaller(*this, *profile, this);
}
GameInstaller *LauncherCore::createInstallerFromExisting(Profile *profile, const QString &filePath)
{
Q_ASSERT(profile != nullptr);
return new GameInstaller(*this, *profile, filePath, this);
}
CompatibilityToolInstaller *LauncherCore::createCompatInstaller()
{
return new CompatibilityToolInstaller(*this, this);

View file

@ -25,7 +25,7 @@ FormCard.FormCardPage {
FormCard.FormTextDelegate {
id: helpTextDelegate
text: i18n("Select an account to use for '%1'", LauncherCore.currentProfile.name)
text: i18n("Select an account to use for '%1'.", LauncherCore.currentProfile.name)
}
}
@ -39,16 +39,6 @@ FormCard.FormCardPage {
Layout.fillWidth: true
FormCard.FormTextDelegate {
id: existingHelpDelegate
text: i18n("You can select an existing account.")
}
FormCard.FormDelegateSeparator {
above: existingHelpDelegate
}
Repeater {
model: LauncherCore.accountManager
@ -65,6 +55,11 @@ FormCard.FormCardPage {
}
}
FormCard.FormHeader {
title: i18n("Create New Account")
visible: LauncherCore.accountManager.hasAnyAccounts()
}
FormCard.FormCard {
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true
@ -72,7 +67,8 @@ FormCard.FormCardPage {
FormCard.FormButtonDelegate {
id: addSquareEnixButton
text: i18n("Add Square Enix Account")
text: i18n("Add Square Enix Account…")
description: i18n("Used for logging into the official game servers.")
icon.name: "list-add-symbolic"
onClicked: page.Window.window.pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "AddSquareEnix"), {
profile: page.profile
@ -87,7 +83,8 @@ FormCard.FormCardPage {
FormCard.FormButtonDelegate {
id: addSapphireButton
text: i18n("Add Sapphire Account")
text: i18n("Add Sapphire Account…")
description: i18n("Only used for Sapphire servers, don't select this if unless you plan to connect to one.")
icon.name: "list-add-symbolic"
onClicked: page.Window.window.pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "AddSapphire"), {
profile: page.profile

View file

@ -1,45 +0,0 @@
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import zone.xiv.astra
FormCard.FormCardPage {
id: page
property var profile
title: i18n("Download Game")
FormCard.FormCard {
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true
FormCard.FormTextDelegate {
id: helpTextDelegate
text: i18n("Press the button below to download and setup the game.")
description: i18n("This only installs the base files required for the initial update. Astra can only download patches with a legitimate Square Enix account.")
}
FormCard.FormDelegateSeparator {
above: helpTextDelegate
below: buttonDelegate
}
FormCard.FormButtonDelegate {
id: buttonDelegate
text: i18n("Begin installation")
icon.name: "cloud-download"
onClicked: page.Window.window.pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "InstallProgress"), {
gameInstaller: LauncherCore.createInstaller(page.profile)
})
}
}
}

View file

@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick
import QtQuick.Window
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
@ -18,32 +19,33 @@ Kirigami.Page {
Kirigami.LoadingPlaceholder {
anchors.centerIn: parent
text: i18n("Installing...")
text: i18n("Installing")
}
Kirigami.PromptDialog {
id: errorDialog
title: i18n("Install error")
title: i18n("Installation Error")
showCloseButton: false
standardButtons: Kirigami.Dialog.Ok
onAccepted: applicationWindow().pageStack.layers.pop()
onRejected: applicationWindow().pageStack.layers.pop()
onAccepted: page.Window.window.pageStack.layers.pop()
onRejected: page.Window.window.pageStack.layers.pop()
}
Component.onCompleted: gameInstaller.installGame()
Component.onCompleted: gameInstaller.start()
Connections {
target: page.gameInstaller
function onInstallFinished() {
applicationWindow().checkSetup();
// Prevents it from failing to push the page if the install happens too quickly.
Qt.callLater(() => applicationWindow().checkSetup());
}
function onError(message) {
errorDialog.subtitle = message
errorDialog.open()
errorDialog.subtitle = i18n("An error has occurred while installing the game:\n\n%1", message);
errorDialog.open();
}
}
}

View file

@ -1,7 +1,9 @@
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-or-later
import QtCore
import QtQuick
import QtQuick.Dialogs
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
@ -35,14 +37,14 @@ FormCard.FormCardPage {
if (page.isInitialSetup) {
return i18n("You must have a legitimate installation of the FFXIV to continue.");
} else {
return i18n("You must select a legitimate installation of FFXIV for '%1'", page.profile.name);
return i18n("Select a game installation of FFXIV for '%1'.", page.profile.name);
}
}
}
}
FormCard.FormHeader {
title: i18n("Existing Installations")
title: i18n("Existing Installation")
visible: LauncherCore.profileManager.hasAnyExistingInstallations()
}
@ -54,7 +56,7 @@ FormCard.FormCardPage {
FormCard.FormTextDelegate {
id: existingHelpDelegate
text: i18n("You can select an existing installation from another profile.")
text: i18n("You can select an existing game installation from another profile.")
}
FormCard.FormDelegateSeparator {
@ -77,6 +79,26 @@ FormCard.FormCardPage {
}
}
}
FormCard.FormDelegateSeparator {
below: importDelegate
}
FormCard.FormButtonDelegate {
id: importDelegate
text: i18n("Import Existing Installation…")
description: i18n("Select an existing installation on disk or import from another launcher.")
icon.name: "document-import-symbolic"
onClicked: page.Window.window.pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "ExistingSetup"), {
profile: page.profile
})
}
}
FormCard.FormHeader {
title: i18n("Install Game")
visible: LauncherCore.accountManager.hasAnyAccounts()
}
FormCard.FormCard {
@ -84,28 +106,43 @@ FormCard.FormCardPage {
Layout.fillWidth: true
FormCard.FormButtonDelegate {
id: findExistingDelegate
id: downloadDelegate
text: i18n("Find Existing Installation")
icon.name: "edit-find"
onClicked: page.Window.window.pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "ExistingSetup"), {
profile: page.profile
text: i18n("Download & Install Game")
description: i18n("Download the retail installer online from Square Enix.")
icon.name: "cloud-download"
onClicked: page.Window.window.pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "InstallProgress"), {
gameInstaller: LauncherCore.createInstaller(page.profile)
})
}
FormCard.FormDelegateSeparator {
above: findExistingDelegate
below: downloadDelegate
above: downloadDelegate
below: selectInstallDelegate
}
FormCard.FormButtonDelegate {
id: downloadDelegate
id: selectInstallDelegate
text: i18n("Download Game")
icon.name: "cloud-download"
onClicked: page.Window.window.pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "DownloadSetup"), {
profile: page.profile
})
text: i18n("Select Existing Installer…")
description: i18n("Use a previously downloaded installer. Useful if offline or can't otherwise access the official servers.")
icon.name: "edit-find"
FileDialog {
id: dialog
currentFolder: StandardPaths.standardLocations(StandardPaths.DownloadLocation)[0]
nameFilters: [i18n("Windows executable (*.exe)")]
onAccepted: {
const url = decodeURIComponent(selectedFile.toString().replace("file://", ""));
page.Window.window.pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "InstallProgress"), {
gameInstaller: LauncherCore.createInstallerFromExisting(page.profile, url)
});
}
}
onClicked: dialog.open()
}
}
}