1
Fork 0
mirror of https://github.com/redstrate/Astra.git synced 2025-04-23 04:57:44 +00:00

Remove Sapphire support

I never really used this feature in Astra, and now that Kawari can
perform the login process for you (and just point it towards a Sapphire
server) I don't see a reason to support it anymore.
This commit is contained in:
Joshua Goins 2025-03-17 19:40:27 -04:00
parent 0b00a49402
commit 3e8e0acaae
14 changed files with 9 additions and 268 deletions

View file

@ -13,7 +13,7 @@ plugins!
* Encrypted Arguments: Game arguments are encrypted out of the box, and is as secure as other launchers. * Encrypted Arguments: Game arguments are encrypted out of the box, and is as secure as other launchers.
* Secure Password Storage: Login information is encrypted using your system keychain and is never stored plain-text. * Secure Password Storage: Login information is encrypted using your system keychain and is never stored plain-text.
* Game Patching Support: Can patch the game without the need to boot into the official launcher. * Game Patching Support: Can patch the game without the need to boot into the official launcher.
* Alternative Server Support: Can log into Sapphire servers and use alternative domains. * Alternative Server Support: Can use alternative servers in case the official ones ever disappear.
**Note:** Steam-linked Square Enix accounts are not currently supported. You will have to use the official launcher or XIVLauncher.Core. **Note:** Steam-linked Square Enix accounts are not currently supported. You will have to use the official launcher or XIVLauncher.Core.

View file

@ -49,7 +49,6 @@ target_sources(astra_static PRIVATE
include/launchercore.h include/launchercore.h
include/patcher.h include/patcher.h
include/processlogger.h include/processlogger.h
include/sapphirelogin.h
include/squareenixlogin.h include/squareenixlogin.h
include/steamapi.h include/steamapi.h
@ -70,7 +69,6 @@ target_sources(astra_static PRIVATE
src/utility.cpp src/utility.cpp
src/patcher.cpp src/patcher.cpp
src/processlogger.cpp src/processlogger.cpp
src/sapphirelogin.cpp
src/squareenixlogin.cpp src/squareenixlogin.cpp
src/steamapi.cpp) src/steamapi.cpp)
target_include_directories(astra_static PUBLIC include) target_include_directories(astra_static PUBLIC include)
@ -156,7 +154,6 @@ qt_target_qml_sources(astra_static
ui/Settings/SettingsPage.qml ui/Settings/SettingsPage.qml
ui/Settings/SyncSettings.qml ui/Settings/SyncSettings.qml
ui/Setup/AccountSetup.qml ui/Setup/AccountSetup.qml
ui/Setup/AddSapphire.qml
ui/Setup/AddSquareEnix.qml ui/Setup/AddSquareEnix.qml
ui/Setup/BenchmarkInstallProgress.qml ui/Setup/BenchmarkInstallProgress.qml
ui/Setup/ExistingSetup.qml ui/Setup/ExistingSetup.qml

View file

@ -36,9 +36,6 @@ SPDX-License-Identifier: CC0-1.0
</entry> </entry>
<entry key="LodestoneId" type="string"> <entry key="LodestoneId" type="string">
</entry> </entry>
<entry key="IsSapphire" type="bool">
<default>false</default>
</entry>
<entry key="RememberPassword" type="bool"> <entry key="RememberPassword" type="bool">
<default>false</default> <default>false</default>
</entry> </entry>

View file

@ -29,7 +29,6 @@ public:
[[nodiscard]] QHash<int, QByteArray> roleNames() const override; [[nodiscard]] QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE Account *createSquareEnixAccount(const QString &username, int licenseType, bool isFreeTrial); Q_INVOKABLE Account *createSquareEnixAccount(const QString &username, int licenseType, bool isFreeTrial);
Q_INVOKABLE Account *createSapphireAccount(const QString &lobbyUrl, const QString &username);
[[nodiscard]] Account *getByUuid(const QString &uuid) const; [[nodiscard]] Account *getByUuid(const QString &uuid) const;

View file

@ -14,7 +14,6 @@
#include "profilemanager.h" #include "profilemanager.h"
#include "steamapi.h" #include "steamapi.h"
class SapphireLogin;
class SquareEnixLogin; class SquareEnixLogin;
class AssetUpdater; class AssetUpdater;
class GameInstaller; class GameInstaller;
@ -82,7 +81,7 @@ public:
/// Initializes the Steamworks API. /// Initializes the Steamworks API.
void initializeSteam(); void initializeSteam();
/// Begins the login process, and may call SquareBoot or SapphireLauncher depending on the profile type. /// Begins the login process.
/// It's designed to be opaque as possible to the caller. /// It's designed to be opaque as possible to the caller.
/// \note The login process is asynchronous. /// \note The login process is asynchronous.
Q_INVOKABLE void login(Profile *profile, const QString &username, const QString &password, const QString &oneTimePassword); Q_INVOKABLE void login(Profile *profile, const QString &username, const QString &password, const QString &oneTimePassword);
@ -185,7 +184,6 @@ private:
ProfileManager *m_profileManager = nullptr; ProfileManager *m_profileManager = nullptr;
AccountManager *m_accountManager = nullptr; AccountManager *m_accountManager = nullptr;
SapphireLogin *m_sapphireLogin = nullptr;
SquareEnixLogin *m_squareEnixLogin = nullptr; SquareEnixLogin *m_squareEnixLogin = nullptr;
QNetworkAccessManager *m_mgr = nullptr; QNetworkAccessManager *m_mgr = nullptr;

View file

@ -1,22 +0,0 @@
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "launchercore.h"
class SapphireLogin : QObject
{
public:
explicit SapphireLogin(LauncherCore &window, QObject *parent = nullptr);
/// Begins the login process for Sapphire servers
/// \param lobbyUrl The URL to the Sapphire lobby server
/// \param info The required login information
QCoro::Task<std::optional<LoginAuth>> login(const QString &lobbyUrl, const LoginInformation &info);
void registerAccount(const QString &lobbyUrl, const LoginInformation &info);
private:
LauncherCore &m_launcher;
};

View file

@ -58,7 +58,6 @@ QHash<int, QByteArray> AccountManager::roleNames() const
Account *AccountManager::createSquareEnixAccount(const QString &username, const int licenseType, const bool isFreeTrial) Account *AccountManager::createSquareEnixAccount(const QString &username, const int licenseType, const bool isFreeTrial)
{ {
const auto account = new Account(QUuid::createUuid().toString(), this); const auto account = new Account(QUuid::createUuid().toString(), this);
account->config()->setIsSapphire(false);
account->config()->setLicense(static_cast<Account::GameLicense>(licenseType)); account->config()->setLicense(static_cast<Account::GameLicense>(licenseType));
account->config()->setIsFreeTrial(isFreeTrial); account->config()->setIsFreeTrial(isFreeTrial);
account->config()->setName(username); account->config()->setName(username);
@ -68,18 +67,6 @@ Account *AccountManager::createSquareEnixAccount(const QString &username, const
return account; return account;
} }
Account *AccountManager::createSapphireAccount(const QString &lobbyUrl, const QString &username)
{
const auto account = new Account(QUuid::createUuid().toString(), this);
account->config()->setIsSapphire(true);
account->config()->setName(username);
account->config()->setLobbyHost(lobbyUrl);
insertAccount(account);
return account;
}
Account *AccountManager::getByUuid(const QString &uuid) const Account *AccountManager::getByUuid(const QString &uuid) const
{ {
for (const auto &account : m_accounts) { for (const auto &account : m_accounts) {

View file

@ -20,7 +20,6 @@
#include "gamerunner.h" #include "gamerunner.h"
#include "launchercore.h" #include "launchercore.h"
#include "profileconfig.h" #include "profileconfig.h"
#include "sapphirelogin.h"
#include "squareenixlogin.h" #include "squareenixlogin.h"
#include "utility.h" #include "utility.h"
@ -42,7 +41,6 @@ LauncherCore::LauncherCore()
{ {
m_config = new Config(KSharedConfig::openConfig(QStringLiteral("astrarc"), KConfig::SimpleConfig, QStandardPaths::AppConfigLocation), this); m_config = new Config(KSharedConfig::openConfig(QStringLiteral("astrarc"), KConfig::SimpleConfig, QStandardPaths::AppConfigLocation), this);
m_mgr = new QNetworkAccessManager(this); m_mgr = new QNetworkAccessManager(this);
m_sapphireLogin = new SapphireLogin(*this, this);
m_squareEnixLogin = new SquareEnixLogin(*this, this); m_squareEnixLogin = new SquareEnixLogin(*this, this);
m_profileManager = new ProfileManager(this); m_profileManager = new ProfileManager(this);
m_accountManager = new AccountManager(this); m_accountManager = new AccountManager(this);
@ -470,12 +468,8 @@ QCoro::Task<> LauncherCore::beginLogin(LoginInformation &info)
std::optional<LoginAuth> auth; std::optional<LoginAuth> auth;
if (!info.profile->config()->isBenchmark()) { if (!info.profile->config()->isBenchmark()) {
if (info.profile->account()->config()->isSapphire()) {
auth = co_await m_sapphireLogin->login(info.profile->account()->config()->lobbyHost(), info);
} else {
auth = co_await m_squareEnixLogin->login(&info); auth = co_await m_squareEnixLogin->login(&info);
} }
}
const auto assetUpdater = new AssetUpdater(*info.profile, *this, this); const auto assetUpdater = new AssetUpdater(*info.profile, *this, this);
if (co_await assetUpdater->update()) { if (co_await assetUpdater->update()) {

View file

@ -1,77 +0,0 @@
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "sapphirelogin.h"
#include "utility.h"
#include <KLocalizedString>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkReply>
#include <qcoronetwork.h>
using namespace Qt::StringLiterals;
SapphireLogin::SapphireLogin(LauncherCore &window, QObject *parent)
: QObject(parent)
, m_launcher(window)
{
}
QCoro::Task<std::optional<LoginAuth>> SapphireLogin::login(const QString &lobbyUrl, const LoginInformation &info)
{
const QJsonObject data{{QStringLiteral("username"), info.username}, {QStringLiteral("pass"), info.password}};
const QUrl url(lobbyUrl + QStringLiteral("/sapphire-api/lobby/login"));
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, QByteArrayLiteral("application/x-www-form-urlencoded"));
Utility::printRequest(QStringLiteral("POST"), request);
const auto reply = m_launcher.mgr()->post(request, QJsonDocument(data).toJson(QJsonDocument::JsonFormat::Compact));
co_await reply;
if (reply->error() != QNetworkReply::NetworkError::NoError) {
Q_EMIT m_launcher.loginError(i18n("Could not contact lobby server.\n\n%1", reply->errorString()));
co_return std::nullopt;
}
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
if (!document.isEmpty()) {
LoginAuth auth;
auth.SID = document["sId"_L1].toString();
auth.lobbyHost = document["lobbyHost"_L1].toString();
auth.frontierHost = document["frontierHost"_L1].toString();
auth.lobbyHostPort = 54994; // TODO: where did this come from?
auth.region = 3;
co_return auth;
} else {
Q_EMIT m_launcher.loginError(i18n("Invalid username or password."));
co_return std::nullopt;
}
}
void SapphireLogin::registerAccount(const QString &lobbyUrl, const LoginInformation &info)
{
const QJsonObject data{{QStringLiteral("username"), info.username}, {QStringLiteral("pass"), info.password}};
const QUrl url(lobbyUrl + QStringLiteral("/sapphire-api/lobby/createAccount"));
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, QByteArrayLiteral("application/x-www-form-urlencoded"));
Utility::printRequest(QStringLiteral("POST"), request);
const auto reply = m_launcher.mgr()->post(request, QJsonDocument(data).toJson(QJsonDocument::JsonFormat::Compact));
connect(reply, &QNetworkReply::finished, [&] {
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
LoginAuth auth;
auth.SID = document["sId"_L1].toString();
auth.lobbyHost = document["lobbyHost"_L1].toString();
auth.frontierHost = document["frontierHost"_L1].toString();
auth.region = 3;
// m_launcher.launchGame(*info.profile, auth);
});
}

View file

@ -214,7 +214,7 @@ QQC2.Control {
FormCard.FormTextFieldDelegate { FormCard.FormTextFieldDelegate {
id: usernameField id: usernameField
label: LauncherCore.currentProfile.account.config.isSapphire ? i18n("Username") : i18n("Square Enix ID") label: i18n("Square Enix ID")
text: LauncherCore.currentProfile.account.config.name text: LauncherCore.currentProfile.account.config.name
enabled: false enabled: false
@ -230,7 +230,7 @@ QQC2.Control {
FormCard.FormPasswordFieldDelegate { FormCard.FormPasswordFieldDelegate {
id: passwordField id: passwordField
label: LauncherCore.currentProfile.account.config.isSapphire ? i18n("Password") : i18n("Square Enix Password") label: i18n("Square Enix Password")
focus: true focus: true
onAccepted: { onAccepted: {
if (otpField.visible) { if (otpField.visible) {
@ -281,7 +281,6 @@ QQC2.Control {
FormCard.FormDelegateSeparator { FormCard.FormDelegateSeparator {
above: loginButton above: loginButton
below: forgotPasswordButton below: forgotPasswordButton
visible: !LauncherCore.currentProfile.account.config.isSapphire
} }
FormCard.FormButtonDelegate { FormCard.FormButtonDelegate {
@ -289,7 +288,6 @@ QQC2.Control {
text: i18n("Forgot ID or Password") text: i18n("Forgot ID or Password")
icon.name: "question-symbolic" icon.name: "question-symbolic"
visible: !LauncherCore.currentProfile.account.config.isSapphire
onClicked: applicationWindow().openUrl('https://secure.square-enix.com/account/app/svc/reminder') onClicked: applicationWindow().openUrl('https://secure.square-enix.com/account/app/svc/reminder')
} }
} }
@ -325,7 +323,6 @@ QQC2.Control {
FormCard.FormLinkDelegate { FormCard.FormLinkDelegate {
text: i18nc("@action:button", "The Lodestone") text: i18nc("@action:button", "The Lodestone")
icon.name: "internet-services-symbolic" icon.name: "internet-services-symbolic"
visible: !LauncherCore.currentProfile.account.config.isSapphire
// TODO: how do we link to a "worldwide" lodestone, if that even exists? // TODO: how do we link to a "worldwide" lodestone, if that even exists?
url: 'https://na.finalfantasyxiv.com/lodestone/' url: 'https://na.finalfantasyxiv.com/lodestone/'
onClicked: applicationWindow().openUrl(url) onClicked: applicationWindow().openUrl(url)
@ -336,7 +333,6 @@ QQC2.Control {
FormCard.FormLinkDelegate { FormCard.FormLinkDelegate {
text: i18nc("@action:button", "Mog Station") text: i18nc("@action:button", "Mog Station")
icon.name: "internet-services-symbolic" icon.name: "internet-services-symbolic"
visible: !LauncherCore.currentProfile.account.config.isSapphire
url: 'https://secure.square-enix.com/account/app/svc/mogstation/' url: 'https://secure.square-enix.com/account/app/svc/mogstation/'
onClicked: applicationWindow().openUrl(url) onClicked: applicationWindow().openUrl(url)
} }

View file

@ -77,20 +77,6 @@ FormCard.FormCardPage {
FormCard.FormDelegateSeparator { FormCard.FormDelegateSeparator {
above: usernameDelegate above: usernameDelegate
below: accountTypeDelegate
}
FormCard.FormComboBoxDelegate {
id: accountTypeDelegate
text: i18n("Account type")
model: ["Square Enix", "Sapphire"]
currentIndex: page.account.config.isSapphire ? 1 : 0
onCurrentIndexChanged: page.account.config.isSapphire = (currentIndex === 1)
}
FormCard.FormDelegateSeparator {
above: accountTypeDelegate
below: languageDelegate below: languageDelegate
} }
@ -106,7 +92,7 @@ FormCard.FormCardPage {
} }
FormCard.FormCard { FormCard.FormCard {
visible: accountAction.checked && !page.account.config.isSapphire visible: accountAction.checked
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.largeSpacing * 4 Layout.topMargin: Kirigami.Units.largeSpacing * 4
@ -178,23 +164,6 @@ FormCard.FormCardPage {
} }
} }
FormCard.FormCard {
visible: accountAction.checked && page.account.config.isSapphire
Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.largeSpacing * 4
FormCard.FormTextFieldDelegate {
id: lobbyURLDelegate
label: i18n("Lobby URL")
text: page.account.config.lobbyUrl
onTextChanged: page.account.config.lobbyUrl = text
visible: page.account.config.isSapphire
placeholderText: "neolobby0X.ffxiv.com"
}
}
FormCard.FormCard { FormCard.FormCard {
visible: loginAction.checked visible: loginAction.checked
@ -224,7 +193,6 @@ FormCard.FormCardPage {
checked: page.account.config.rememberOTP checked: page.account.config.rememberOTP
onCheckedChanged: page.account.config.rememberOTP = checked onCheckedChanged: page.account.config.rememberOTP = checked
enabled: page.account.config.useOTP enabled: page.account.config.useOTP
visible: !page.account.config.isSapphire
} }
FormCard.FormDelegateSeparator { FormCard.FormDelegateSeparator {

View file

@ -22,16 +22,8 @@ FormCard.FormCardPage {
Kirigami.Action { Kirigami.Action {
text: i18n("Add Account…") text: i18n("Add Account…")
icon.name: "list-add-symbolic" icon.name: "list-add-symbolic"
Kirigami.Action {
text: i18n("Square Enix")
onTriggered: root.Window.window.pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "AddSquareEnix")) onTriggered: root.Window.window.pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "AddSquareEnix"))
} }
Kirigami.Action {
text: i18n("Sapphire")
onTriggered: root.Window.window.pageStack.layers.push(Qt.createComponent("zone.xiv.astra", "AddSapphire"))
}
}
] ]
FormCard.FormCard { FormCard.FormCard {
@ -53,7 +45,7 @@ FormCard.FormCardPage {
FormCard.FormButtonDelegate { FormCard.FormButtonDelegate {
text: layout.account.config.name text: layout.account.config.name
description: layout.account.config.isSapphire ? i18n("Sapphire") : i18n("Square Enix") description: i18n("Square Enix")
leading: Components.Avatar leading: Components.Avatar
{ {

View file

@ -74,21 +74,5 @@ FormCard.FormCardPage {
profile: page.profile profile: page.profile
}) })
} }
FormCard.FormDelegateSeparator {
above: addSquareEnixButton
below: addSapphireButton
}
FormCard.FormButtonDelegate {
id: addSapphireButton
text: i18n("Sapphire Account…")
description: i18n("Only for Sapphire servers, don't select this if unless you need 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,72 +0,0 @@
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick
import QtQuick.Layouts
import QtQuick.Window
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("Add Sapphire Account")
readonly property bool isValid: usernameField.text.length !== 0 && lobbyUrlField.text.length !== 0
FormCard.FormCard {
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true
FormCard.FormTextDelegate {
id: helpTextDelegate
description: i18n("The password will be entered on the login page. A username will be associated with this account but can always be changed later.")
}
FormCard.FormDelegateSeparator {
above: helpTextDelegate
below: usernameField
}
FormCard.FormTextFieldDelegate {
id: usernameField
label: i18n("Username")
}
FormCard.FormDelegateSeparator {
above: usernameField
below: lobbyUrlField
}
FormCard.FormTextFieldDelegate {
id: lobbyUrlField
label: i18n("Lobby URL")
}
FormCard.FormDelegateSeparator {
above: lobbyUrlField
below: buttonDelegate
}
FormCard.FormButtonDelegate {
id: buttonDelegate
text: i18n("Add Account")
icon.name: "list-add-symbolic"
enabled: page.isValid
onClicked: {
let account = LauncherCore.accountManager.createSapphireAccount(lobbyUrlField.text, usernameField.text)
if (page.profile) {
page.profile.account = account
applicationWindow().checkSetup()
} else {
page.Window.window.pageStack.layers.pop()
}
}
}
}
}