1
Fork 0
mirror of https://github.com/redstrate/Astra.git synced 2025-04-22 04:37:46 +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/AccountSetup.qml
ui/Setup/AddSapphire.qml ui/Setup/AddSapphire.qml
ui/Setup/AddSquareEnix.qml ui/Setup/AddSquareEnix.qml
ui/Setup/DownloadSetup.qml
ui/Setup/ExistingSetup.qml ui/Setup/ExistingSetup.qml
ui/Setup/InstallProgress.qml ui/Setup/InstallProgress.qml
ui/Setup/SetupPage.qml ui/Setup/SetupPage.qml

View file

@ -18,14 +18,18 @@ class GameInstaller : public QObject
public: public:
GameInstaller(LauncherCore &launcher, Profile &profile, QObject *parent = nullptr); 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: Q_SIGNALS:
void installFinished(); void installFinished();
void error(QString message); void error(QString message);
private: private:
void installGame();
LauncherCore &m_launcher; LauncherCore &m_launcher;
Profile &m_profile; Profile &m_profile;
QString m_localInstallerPath;
}; };

View file

@ -86,6 +86,7 @@ public:
Q_INVOKABLE bool autoLogin(Profile *profile); Q_INVOKABLE bool autoLogin(Profile *profile);
Q_INVOKABLE GameInstaller *createInstaller(Profile *profile); Q_INVOKABLE GameInstaller *createInstaller(Profile *profile);
Q_INVOKABLE GameInstaller *createInstallerFromExisting(Profile *profile, const QString &filePath);
Q_INVOKABLE CompatibilityToolInstaller *createCompatInstaller(); Q_INVOKABLE CompatibilityToolInstaller *createCompatInstaller();
Q_INVOKABLE void clearAvatarCache(); Q_INVOKABLE void clearAvatarCache();
@ -121,6 +122,7 @@ Q_SIGNALS:
void gameClosed(); void gameClosed();
void loginError(QString message); void loginError(QString message);
void dalamudError(QString message); void dalamudError(QString message);
void miscError(QString message);
void stageChanged(QString message, QString explanation = {}); void stageChanged(QString message, QString explanation = {});
void stageIndeterminate(); void stageIndeterminate();
void stageDeterminate(int min, int max, int value); 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() void GameInstaller::installGame()
{ {
const QDir installDirectory = m_profile.gamePath(); const QDir installDirectory = m_profile.gamePath();
const QNetworkRequest request = QNetworkRequest(QUrl(installerUrl)); const std::string installDirectoryStd = installDirectory.absolutePath().toStdString();
Utility::printRequest(QStringLiteral("GET"), request); 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] { m_profile.readGameVersion();
if (reply->error() != QNetworkReply::NetworkError::NoError) {
Q_EMIT error(i18n("An error has occurred when downloading the installer.\n\n%1", reply->errorString()));
return;
}
const QDir dataDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation); Q_EMIT installFinished();
qInfo(ASTRA_LOG) << "Installed game in" << installDirectory;
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;
});
} }
#include "moc_gameinstaller.cpp" #include "moc_gameinstaller.cpp"

View file

@ -105,6 +105,13 @@ GameInstaller *LauncherCore::createInstaller(Profile *profile)
return new GameInstaller(*this, *profile, this); 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() CompatibilityToolInstaller *LauncherCore::createCompatInstaller()
{ {
return new CompatibilityToolInstaller(*this, this); return new CompatibilityToolInstaller(*this, this);

View file

@ -25,7 +25,7 @@ FormCard.FormCardPage {
FormCard.FormTextDelegate { FormCard.FormTextDelegate {
id: helpTextDelegate 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 Layout.fillWidth: true
FormCard.FormTextDelegate {
id: existingHelpDelegate
text: i18n("You can select an existing account.")
}
FormCard.FormDelegateSeparator {
above: existingHelpDelegate
}
Repeater { Repeater {
model: LauncherCore.accountManager model: LauncherCore.accountManager
@ -65,6 +55,11 @@ FormCard.FormCardPage {
} }
} }
FormCard.FormHeader {
title: i18n("Create New Account")
visible: LauncherCore.accountManager.hasAnyAccounts()
}
FormCard.FormCard { FormCard.FormCard {
Layout.topMargin: Kirigami.Units.largeSpacing Layout.topMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true Layout.fillWidth: true
@ -72,7 +67,8 @@ FormCard.FormCardPage {
FormCard.FormButtonDelegate { FormCard.FormButtonDelegate {
id: addSquareEnixButton 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" icon.name: "list-add-symbolic"
onClicked: page.Window.window.pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "AddSquareEnix"), { onClicked: page.Window.window.pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "AddSquareEnix"), {
profile: page.profile profile: page.profile
@ -87,7 +83,8 @@ FormCard.FormCardPage {
FormCard.FormButtonDelegate { FormCard.FormButtonDelegate {
id: addSapphireButton 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" icon.name: "list-add-symbolic"
onClicked: page.Window.window.pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "AddSapphire"), { onClicked: page.Window.window.pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "AddSapphire"), {
profile: page.profile 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 // SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick import QtQuick
import QtQuick.Window
import QtQuick.Layouts import QtQuick.Layouts
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
@ -18,32 +19,33 @@ Kirigami.Page {
Kirigami.LoadingPlaceholder { Kirigami.LoadingPlaceholder {
anchors.centerIn: parent anchors.centerIn: parent
text: i18n("Installing...") text: i18n("Installing")
} }
Kirigami.PromptDialog { Kirigami.PromptDialog {
id: errorDialog id: errorDialog
title: i18n("Install error") title: i18n("Installation Error")
showCloseButton: false showCloseButton: false
standardButtons: Kirigami.Dialog.Ok standardButtons: Kirigami.Dialog.Ok
onAccepted: applicationWindow().pageStack.layers.pop() onAccepted: page.Window.window.pageStack.layers.pop()
onRejected: applicationWindow().pageStack.layers.pop() onRejected: page.Window.window.pageStack.layers.pop()
} }
Component.onCompleted: gameInstaller.installGame() Component.onCompleted: gameInstaller.start()
Connections { Connections {
target: page.gameInstaller target: page.gameInstaller
function onInstallFinished() { 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) { function onError(message) {
errorDialog.subtitle = message errorDialog.subtitle = i18n("An error has occurred while installing the game:\n\n%1", message);
errorDialog.open() errorDialog.open();
} }
} }
} }

View file

@ -1,7 +1,9 @@
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com> // SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import QtCore
import QtQuick import QtQuick
import QtQuick.Dialogs
import QtQuick.Layouts import QtQuick.Layouts
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
@ -35,14 +37,14 @@ FormCard.FormCardPage {
if (page.isInitialSetup) { if (page.isInitialSetup) {
return i18n("You must have a legitimate installation of the FFXIV to continue."); return i18n("You must have a legitimate installation of the FFXIV to continue.");
} else { } 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 { FormCard.FormHeader {
title: i18n("Existing Installations") title: i18n("Existing Installation")
visible: LauncherCore.profileManager.hasAnyExistingInstallations() visible: LauncherCore.profileManager.hasAnyExistingInstallations()
} }
@ -54,7 +56,7 @@ FormCard.FormCardPage {
FormCard.FormTextDelegate { FormCard.FormTextDelegate {
id: existingHelpDelegate 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 { 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 { FormCard.FormCard {
@ -84,28 +106,43 @@ FormCard.FormCardPage {
Layout.fillWidth: true Layout.fillWidth: true
FormCard.FormButtonDelegate { FormCard.FormButtonDelegate {
id: findExistingDelegate id: downloadDelegate
text: i18n("Find Existing Installation") text: i18n("Download & Install Game")
icon.name: "edit-find" description: i18n("Download the retail installer online from Square Enix.")
onClicked: page.Window.window.pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "ExistingSetup"), { icon.name: "cloud-download"
profile: page.profile onClicked: page.Window.window.pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "InstallProgress"), {
gameInstaller: LauncherCore.createInstaller(page.profile)
}) })
} }
FormCard.FormDelegateSeparator { FormCard.FormDelegateSeparator {
above: findExistingDelegate above: downloadDelegate
below: downloadDelegate below: selectInstallDelegate
} }
FormCard.FormButtonDelegate { FormCard.FormButtonDelegate {
id: downloadDelegate id: selectInstallDelegate
text: i18n("Download Game") text: i18n("Select Existing Installer…")
icon.name: "cloud-download" description: i18n("Use a previously downloaded installer. Useful if offline or can't otherwise access the official servers.")
onClicked: page.Window.window.pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "DownloadSetup"), { icon.name: "edit-find"
profile: page.profile
}) 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()
} }
} }
} }