mirror of
https://github.com/redstrate/Astra.git
synced 2025-05-05 18:27:46 +00:00
Support Steam service accounts (#32)
This adds support for Steam service accounts, finally. Requires an updated steamwrap, and we're missing the infrastructure to launch it at the moment too.
This commit is contained in:
parent
74a3c5f5d2
commit
ea262ddfb7
17 changed files with 286 additions and 69 deletions
|
@ -15,8 +15,6 @@ plugins!
|
|||
* Game Patching Support: Can patch the game without the need to boot into the official launcher.
|
||||
* 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.
|
||||
|
||||
## Get It
|
||||
|
||||
Details on where to find stable releases of Astra can be found on its [homepage](https://xiv.zone/astra/install).
|
||||
|
|
|
@ -24,3 +24,15 @@ ecm_add_test(utilitytest.cpp
|
|||
LINK_LIBRARIES astra_static Qt::Test
|
||||
NAME_PREFIX "astra-"
|
||||
)
|
||||
|
||||
ecm_add_test(crtrandtest.cpp
|
||||
TEST_NAME crtrandtest
|
||||
LINK_LIBRARIES astra_static Qt::Test
|
||||
NAME_PREFIX "astra-"
|
||||
)
|
||||
|
||||
ecm_add_test(encryptedargtest.cpp
|
||||
TEST_NAME encryptedargtest
|
||||
LINK_LIBRARIES astra_static Qt::Test
|
||||
NAME_PREFIX "astra-"
|
||||
)
|
||||
|
|
44
autotests/crtrandtest.cpp
Normal file
44
autotests/crtrandtest.cpp
Normal file
|
@ -0,0 +1,44 @@
|
|||
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QtTest/QtTest>
|
||||
|
||||
#include "crtrand.h"
|
||||
|
||||
class CrtRandTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private Q_SLOTS:
|
||||
void randomSeed_data()
|
||||
{
|
||||
QTest::addColumn<uint32_t>("seed");
|
||||
QTest::addColumn<uint32_t>("value1");
|
||||
QTest::addColumn<uint32_t>("value2");
|
||||
QTest::addColumn<uint32_t>("value3");
|
||||
QTest::addColumn<uint32_t>("value4");
|
||||
|
||||
QTest::addRow("test 1") << static_cast<uint32_t>(5050) << static_cast<uint32_t>(16529) << static_cast<uint32_t>(23363) << static_cast<uint32_t>(25000)
|
||||
<< static_cast<uint32_t>(18427);
|
||||
QTest::addRow("test 2") << static_cast<uint32_t>(19147) << static_cast<uint32_t>(29796) << static_cast<uint32_t>(24416) << static_cast<uint32_t>(1377)
|
||||
<< static_cast<uint32_t>(24625);
|
||||
}
|
||||
|
||||
void randomSeed()
|
||||
{
|
||||
QFETCH(uint32_t, seed);
|
||||
QFETCH(uint32_t, value1);
|
||||
QFETCH(uint32_t, value2);
|
||||
QFETCH(uint32_t, value3);
|
||||
QFETCH(uint32_t, value4);
|
||||
|
||||
auto crtRand = CrtRand(seed);
|
||||
QCOMPARE(crtRand.next(), value1);
|
||||
QCOMPARE(crtRand.next(), value2);
|
||||
QCOMPARE(crtRand.next(), value3);
|
||||
QCOMPARE(crtRand.next(), value4);
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(CrtRandTest)
|
||||
#include "crtrandtest.moc"
|
50
autotests/encryptedargtest.cpp
Normal file
50
autotests/encryptedargtest.cpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QtTest/QtTest>
|
||||
|
||||
#include "encryptedarg.h"
|
||||
|
||||
class EncryptedArgTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private Q_SLOTS:
|
||||
void steamTicket_data()
|
||||
{
|
||||
QTest::addColumn<QString>("ticketBytes");
|
||||
QTest::addColumn<uint32_t>("time");
|
||||
QTest::addColumn<QString>("encryptedTicket");
|
||||
QTest::addColumn<int>("encryptedLength");
|
||||
|
||||
QTest::addRow("real ticket")
|
||||
<< QStringLiteral(
|
||||
"140000009efb2c79d200f07599f633050100100195c517681800000001000000020000002c9ea991afec1a49f7e72d000b000000b8000000380000000400000099f63305010"
|
||||
"010012a990000c47323496501a8c000000000807a1268002a2e680100908400000100c5000400000000004e408e518c259b8b329b5fcfdb00903fadad36d7e088813dc9b174"
|
||||
"b06e08b69f8f46d083cf4118d1eb4062e009147906d9c80759af3eb221f7fbb8e28d402e11ba80e3cf4a101487f87ccce9688daf99dd412e6bcaab6e58f70030ff99323e4c8"
|
||||
"1824659f7aa89188fadc6402bcb540843e1578cd112d4536fd65c4bd926f754")
|
||||
<< static_cast<uint32_t>(1746389891)
|
||||
<< QStringLiteral(
|
||||
"Tjr8Lv1O0HjJ7U4dOfkA9BdLAnEaCl_TU0GnYGGLTBM06TV9Ggf-fYb7WMqD-Xv758Q1zzSPTeaJctl8au-"
|
||||
"imM4ACRgl0Y4LqJpLFfgBhkumd4dne2P9oM6qLzMnHfspPq8AFQFHXaiSicu2gSaCwpk36ZK-WX17DaTOkYFncIKl_rSZAkb8OzTpNX0aB_590hUpAf74-"
|
||||
"TU368A1fgXLw2aunwn0wBNvz0ywFEiAjmD8PfgUzA6IrvkP1eoKoY4A_NNBXnirca7CjWxOoguXRGaHjzq9vrDm8ABTk2o0u29R,Nqmz_4LN1Fj9cNhtyhHTXuV6huLxmsflb_"
|
||||
"6DR5B8dwk8IMYup0z5AXHhLww0BmZkDKKCWjVehxWvoHkz8FNViV9Oduwv7ZGyHUYs47HUpOIr1Wirp6LEvsxBcDBf-T_XOK945j-z_"
|
||||
"MtxXiNKqtAuaL8iw7OOIpVnXqIa77yGuOFFW-u2wv1cK1M3s_OqmgEdj0JZfoYbjT6lIEVsSXKMYwwf9zkAjx23K-gqrM8c8nStv4EYT7ZU6o_"
|
||||
"I0KZ6OJVnCFElYLamz82NIRiPdzyuJcPoslNCXpQV_vWlyGJ0OIoR,2MrkkwMnTNx7HR4FJ6ACh0cQZmdBEB2pM4eQSqpJEC367JtCMzM*")
|
||||
<< 652;
|
||||
}
|
||||
|
||||
void steamTicket()
|
||||
{
|
||||
QFETCH(QString, ticketBytes);
|
||||
QFETCH(uint32_t, time);
|
||||
QFETCH(QString, encryptedTicket);
|
||||
QFETCH(int, encryptedLength);
|
||||
|
||||
auto pair = std::pair{encryptedTicket, encryptedLength};
|
||||
QCOMPARE(encryptSteamTicket(ticketBytes, time), pair);
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(EncryptedArgTest)
|
||||
#include "encryptedargtest.moc"
|
2
external/libphysis
vendored
2
external/libphysis
vendored
|
@ -1 +1 @@
|
|||
Subproject commit 806395eb4c6e76f9fbe8fb7ed4e2d864bdb1c522
|
||||
Subproject commit 7fff41a808ce28553d4e16c6b3d0d9c9c5f3832d
|
|
@ -51,6 +51,7 @@ target_sources(astra_static PRIVATE
|
|||
include/processlogger.h
|
||||
include/squareenixlogin.h
|
||||
include/steamapi.h
|
||||
include/crtrand.h
|
||||
|
||||
src/accountmanager.cpp
|
||||
src/assetupdater.cpp
|
||||
|
@ -70,7 +71,8 @@ target_sources(astra_static PRIVATE
|
|||
src/patcher.cpp
|
||||
src/processlogger.cpp
|
||||
src/squareenixlogin.cpp
|
||||
src/steamapi.cpp)
|
||||
src/steamapi.cpp
|
||||
src/crtrand.cpp)
|
||||
target_include_directories(astra_static PUBLIC include)
|
||||
target_link_libraries(astra_static PUBLIC
|
||||
physis
|
||||
|
|
17
launcher/include/crtrand.h
Normal file
17
launcher/include/crtrand.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class CrtRand
|
||||
{
|
||||
public:
|
||||
explicit CrtRand(uint32_t seed);
|
||||
|
||||
uint32_t next();
|
||||
|
||||
private:
|
||||
uint32_t seed;
|
||||
};
|
|
@ -5,4 +5,5 @@
|
|||
|
||||
#include <QString>
|
||||
|
||||
QString encryptGameArg(const QString &arg);
|
||||
QString encryptGameArg(const QString &arg);
|
||||
std::pair<QString, int> encryptSteamTicket(QString ticket, qint64 time);
|
||||
|
|
|
@ -75,9 +75,6 @@ public:
|
|||
LauncherCore();
|
||||
~LauncherCore() override;
|
||||
|
||||
/// Initializes the Steamworks API.
|
||||
void initializeSteam();
|
||||
|
||||
/// Begins the login process.
|
||||
/// It's designed to be opaque as possible to the caller.
|
||||
/// \note The login process is asynchronous.
|
||||
|
@ -130,6 +127,7 @@ public:
|
|||
[[nodiscard]] AccountManager *accountManager();
|
||||
[[nodiscard]] Headline *headline() const;
|
||||
[[nodiscard]] QString cachedLogoImage() const;
|
||||
[[nodiscard]] SteamAPI *steamApi() const;
|
||||
|
||||
/**
|
||||
* @brief Opens the official launcher. Useful if Astra decides not to work that day!
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <QCoroTask>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QObject>
|
||||
|
||||
class LauncherCore;
|
||||
|
@ -12,7 +14,10 @@ class SteamAPI : public QObject
|
|||
public:
|
||||
explicit SteamAPI(QObject *parent = nullptr);
|
||||
|
||||
void setLauncherMode(bool isLauncher);
|
||||
QCoro::Task<> initialize();
|
||||
QCoro::Task<> shutdown();
|
||||
QCoro::Task<std::pair<QString, int>> getTicket();
|
||||
|
||||
[[nodiscard]] bool isDeck() const;
|
||||
};
|
||||
private:
|
||||
QNetworkAccessManager m_qnam;
|
||||
};
|
||||
|
|
15
launcher/src/crtrand.cpp
Normal file
15
launcher/src/crtrand.cpp
Normal file
|
@ -0,0 +1,15 @@
|
|||
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "crtrand.h"
|
||||
|
||||
CrtRand::CrtRand(const uint32_t seed)
|
||||
{
|
||||
this->seed = seed;
|
||||
}
|
||||
|
||||
uint32_t CrtRand::next()
|
||||
{
|
||||
this->seed = 0x343FD * this->seed + 0x269EC3;
|
||||
return ((this->seed >> 16) & 0xFFFF) & 0x7FFF;
|
||||
}
|
|
@ -2,7 +2,9 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "encryptedarg.h"
|
||||
#include "crtrand.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <physis.hpp>
|
||||
|
||||
#if defined(Q_OS_MAC)
|
||||
|
@ -83,4 +85,83 @@ QString encryptGameArg(const QString &arg)
|
|||
physis_blowfish_free(blowfish);
|
||||
|
||||
return QStringLiteral("//**sqex0003%1%2**//").arg(base64, QString(QLatin1Char(checksum)));
|
||||
}
|
||||
}
|
||||
|
||||
// Based off of the XIVQuickLauncher implementation
|
||||
constexpr auto SQEX_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_";
|
||||
constexpr int SPLIT_SIZE = 300;
|
||||
|
||||
QStringList intoChunks(const QString &str, const int maxChunkSize)
|
||||
{
|
||||
QStringList chunks;
|
||||
for (int i = 0; i < str.length(); i += maxChunkSize) {
|
||||
chunks.push_back(str.mid(i, std::min(static_cast<qlonglong>(maxChunkSize), str.length() - i)));
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
std::pair<QString, int> encryptSteamTicket(QString ticket, qint64 time)
|
||||
{
|
||||
// Round the time down
|
||||
time -= 5;
|
||||
time -= time % 60;
|
||||
|
||||
auto ticketString = ticket.remove(QLatin1Char('-')).toLower();
|
||||
auto rawTicketBytes = ticketString.toLatin1();
|
||||
rawTicketBytes.append('\0');
|
||||
|
||||
ushort ticketSum = 0;
|
||||
for (const auto b : rawTicketBytes) {
|
||||
ticketSum += b;
|
||||
}
|
||||
|
||||
QByteArray binaryWriter;
|
||||
binaryWriter.append(reinterpret_cast<const char *>(&ticketSum), sizeof(ushort));
|
||||
binaryWriter.append(rawTicketBytes);
|
||||
|
||||
const int castTicketSum = static_cast<short>(ticketSum);
|
||||
const auto seed = time ^ castTicketSum;
|
||||
auto rand = CrtRand(seed);
|
||||
|
||||
const auto numRandomBytes = (static_cast<ulong>(rawTicketBytes.length() + 9) & 0xFFFFFFFFFFFFFFF8) - 2 - static_cast<ulong>(rawTicketBytes.length());
|
||||
auto garbage = QByteArray();
|
||||
garbage.resize(numRandomBytes);
|
||||
|
||||
uint badSum = *reinterpret_cast<uint32_t *>(binaryWriter.data());
|
||||
|
||||
for (auto i = 0u; i < numRandomBytes; i++) {
|
||||
const auto randChar = SQEX_ALPHABET[static_cast<int>(badSum + rand.next()) & 0x3F];
|
||||
garbage[i] = static_cast<char>(randChar);
|
||||
badSum += randChar;
|
||||
}
|
||||
|
||||
binaryWriter.append(garbage);
|
||||
|
||||
char blowfishKey[17]{};
|
||||
sprintf(blowfishKey, "%08x#un@e=x>", time);
|
||||
|
||||
binaryWriter.remove(0, 4);
|
||||
binaryWriter.insert(0, reinterpret_cast<const char *>(&badSum), sizeof(uint));
|
||||
|
||||
// swap first two bytes
|
||||
auto finalBytes = binaryWriter;
|
||||
std::swap(finalBytes[0], finalBytes[1]);
|
||||
|
||||
SteamTicketBlowfish *blowfish = miscel_steamticket_blowfish_initialize(reinterpret_cast<uint8_t *>(blowfishKey), 16);
|
||||
|
||||
miscel_steamticket_blowfish_encrypt(blowfish, reinterpret_cast<uint8_t *>(finalBytes.data()), finalBytes.size());
|
||||
Q_ASSERT(finalBytes.length() % 8 == 0);
|
||||
|
||||
miscel_steamticket_physis_blowfish_free(blowfish);
|
||||
|
||||
auto encoded = finalBytes.toBase64(QByteArray::Base64Option::Base64UrlEncoding | QByteArray::Base64Option::KeepTrailingEquals);
|
||||
encoded.replace('+', '-');
|
||||
encoded.replace('/', '_');
|
||||
encoded.replace('=', '*');
|
||||
|
||||
const auto parts = intoChunks(QString::fromLatin1(encoded), SPLIT_SIZE);
|
||||
const auto finalString = parts.join(QLatin1Char(','));
|
||||
|
||||
return {finalString, finalString.length() - (parts.length() - 1)};
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ LauncherCore::LauncherCore()
|
|||
m_profileManager = new ProfileManager(this);
|
||||
m_accountManager = new AccountManager(this);
|
||||
m_runner = new GameRunner(*this, this);
|
||||
m_steamApi = new SteamAPI(this);
|
||||
|
||||
connect(m_accountManager, &AccountManager::accountAdded, this, &LauncherCore::fetchAvatar);
|
||||
connect(m_accountManager, &AccountManager::accountLodestoneIdChanged, this, &LauncherCore::fetchAvatar);
|
||||
|
@ -79,12 +80,6 @@ LauncherCore::~LauncherCore()
|
|||
m_config->save();
|
||||
}
|
||||
|
||||
void LauncherCore::initializeSteam()
|
||||
{
|
||||
m_steamApi = new SteamAPI(this);
|
||||
m_steamApi->setLauncherMode(true);
|
||||
}
|
||||
|
||||
void LauncherCore::login(Profile *profile, const QString &username, const QString &password, const QString &oneTimePassword)
|
||||
{
|
||||
Q_ASSERT(profile != nullptr);
|
||||
|
@ -445,6 +440,11 @@ QString LauncherCore::cachedLogoImage() const
|
|||
return m_cachedLogoImage;
|
||||
}
|
||||
|
||||
SteamAPI *LauncherCore::steamApi() const
|
||||
{
|
||||
return m_steamApi;
|
||||
}
|
||||
|
||||
#ifdef BUILD_SYNC
|
||||
SyncManager *LauncherCore::syncManager() const
|
||||
{
|
||||
|
@ -480,10 +480,6 @@ QCoro::Task<> LauncherCore::beginLogin(LoginInformation &info)
|
|||
|
||||
Q_EMIT stageChanged(i18n("Launching game..."));
|
||||
|
||||
if (isSteam()) {
|
||||
m_steamApi->setLauncherMode(false);
|
||||
}
|
||||
|
||||
m_runner->beginGameExecutable(*info.profile, auth);
|
||||
}
|
||||
|
||||
|
|
|
@ -133,11 +133,6 @@ int main(int argc, char *argv[])
|
|||
|
||||
QQmlApplicationEngine engine;
|
||||
|
||||
const auto core = engine.singletonInstance<LauncherCore *>(QStringLiteral("zone.xiv.astra"), QStringLiteral("LauncherCore"));
|
||||
if (parser.isSet(steamOption)) {
|
||||
core->initializeSteam();
|
||||
}
|
||||
|
||||
engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
|
||||
QObject::connect(&engine, &QQmlApplicationEngine::quit, &app, &QCoreApplication::quit);
|
||||
|
||||
|
|
|
@ -232,9 +232,13 @@ QCoro::Task<std::optional<SquareEnixLogin::StoredInfo>> SquareEnixLogin::getStor
|
|||
if (m_info->profile->account()->config()->license() == Account::GameLicense::WindowsSteam) {
|
||||
query.addQueryItem(QStringLiteral("issteam"), QString::number(1));
|
||||
|
||||
// TODO: get steam ticket information from steam api
|
||||
query.addQueryItem(QStringLiteral("session_ticket"), QString::number(1));
|
||||
query.addQueryItem(QStringLiteral("ticket_size"), QString::number(1));
|
||||
// initialize the steam api
|
||||
co_await m_launcher.steamApi()->initialize();
|
||||
|
||||
// grab an auth ticket
|
||||
auto [ticket, ticketSize] = co_await m_launcher.steamApi()->getTicket();
|
||||
query.addQueryItem(QStringLiteral("session_ticket"), ticket);
|
||||
query.addQueryItem(QStringLiteral("ticket_size"), QString::number(ticketSize));
|
||||
}
|
||||
|
||||
QUrl url;
|
||||
|
@ -251,22 +255,9 @@ QCoro::Task<std::optional<SquareEnixLogin::StoredInfo>> SquareEnixLogin::getStor
|
|||
const auto reply = m_launcher.mgr()->get(request);
|
||||
co_await reply;
|
||||
|
||||
m_username = m_info->username;
|
||||
|
||||
const QString str = QString::fromUtf8(reply->readAll());
|
||||
|
||||
// fetches Steam username
|
||||
if (m_info->profile->account()->config()->license() == Account::GameLicense::WindowsSteam) {
|
||||
const static QRegularExpression re(QStringLiteral(R"lit(<input name=""sqexid"" type=""hidden"" value=""(?<sqexid>.*)""\/>)lit"));
|
||||
const QRegularExpressionMatch match = re.match(str);
|
||||
|
||||
if (match.hasMatch()) {
|
||||
m_username = match.captured(1);
|
||||
} else {
|
||||
Q_EMIT m_launcher.loginError(i18n("Could not get Steam username, have you attached your account?"));
|
||||
}
|
||||
} else {
|
||||
m_username = m_info->username;
|
||||
}
|
||||
|
||||
const static QRegularExpression re(QStringLiteral(R"lit(\t<\s*input .* name="_STORED_" value="(?<stored>.*)">)lit"));
|
||||
const QRegularExpressionMatch match = re.match(str);
|
||||
if (match.hasMatch()) {
|
||||
|
|
|
@ -2,23 +2,50 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "steamapi.h"
|
||||
|
||||
#include "encryptedarg.h"
|
||||
#include "launchercore.h"
|
||||
|
||||
#include <QCoroNetwork>
|
||||
|
||||
SteamAPI::SteamAPI(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
// TODO: stub
|
||||
}
|
||||
|
||||
void SteamAPI::setLauncherMode(bool isLauncher)
|
||||
QCoro::Task<> SteamAPI::initialize()
|
||||
{
|
||||
Q_UNUSED(isLauncher)
|
||||
// TODO: stub
|
||||
QUrl url;
|
||||
url.setScheme(QStringLiteral("http"));
|
||||
url.setHost(QStringLiteral("127.0.0.1"));
|
||||
url.setPort(50481);
|
||||
url.setPath(QStringLiteral("/init"));
|
||||
|
||||
Q_UNUSED(co_await m_qnam.post(QNetworkRequest(url), QByteArray{}))
|
||||
}
|
||||
|
||||
bool SteamAPI::isDeck() const
|
||||
QCoro::Task<> SteamAPI::shutdown()
|
||||
{
|
||||
// TODO: stub
|
||||
return false;
|
||||
QUrl url;
|
||||
url.setScheme(QStringLiteral("http"));
|
||||
url.setHost(QStringLiteral("127.0.0.1"));
|
||||
url.setPort(50481);
|
||||
url.setPath(QStringLiteral("/shutdown"));
|
||||
|
||||
Q_UNUSED(co_await m_qnam.post(QNetworkRequest(url), QByteArray{}))
|
||||
}
|
||||
|
||||
QCoro::Task<std::pair<QString, int>> SteamAPI::getTicket()
|
||||
{
|
||||
QUrl url;
|
||||
url.setScheme(QStringLiteral("http"));
|
||||
url.setHost(QStringLiteral("127.0.0.1"));
|
||||
url.setPort(50481);
|
||||
url.setPath(QStringLiteral("/ticket"));
|
||||
|
||||
const auto reply = co_await m_qnam.get(QNetworkRequest(url));
|
||||
const auto ticketBytes = reply->readAll();
|
||||
|
||||
const QJsonDocument document = QJsonDocument::fromJson(ticketBytes);
|
||||
|
||||
co_return encryptSteamTicket(document[QStringLiteral("ticket")].toString(), document[QStringLiteral("time")].toInteger());
|
||||
}
|
||||
|
|
|
@ -47,13 +47,6 @@ FormCard.FormCardPage {
|
|||
description: i18n("If the account holds multiple licenses, choose the preferred one.")
|
||||
model: ["Windows", "Steam", "macOS"]
|
||||
text: i18n("License")
|
||||
|
||||
onCurrentIndexChanged: {
|
||||
if (currentIndex === 1) {
|
||||
currentIndex = 0;
|
||||
errorDialog.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
FormCard.FormDelegateSeparator {
|
||||
above: licenseField
|
||||
|
@ -87,12 +80,4 @@ FormCard.FormCardPage {
|
|||
}
|
||||
}
|
||||
}
|
||||
Kirigami.PromptDialog {
|
||||
id: errorDialog
|
||||
|
||||
showCloseButton: false
|
||||
standardButtons: Kirigami.Dialog.Ok
|
||||
title: i18n("Steam Warning")
|
||||
subtitle: i18n("Steam linked Square Enix accounts are not currently supported. You will have to use another launcher that supports these, such as the official launcher or XIVLauncher.Core.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue