mirror of
https://github.com/redstrate/Astra.git
synced 2025-05-07 02:57: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.
|
* 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.
|
* 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
|
## Get It
|
||||||
|
|
||||||
Details on where to find stable releases of Astra can be found on its [homepage](https://xiv.zone/astra/install).
|
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
|
LINK_LIBRARIES astra_static Qt::Test
|
||||||
NAME_PREFIX "astra-"
|
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/processlogger.h
|
||||||
include/squareenixlogin.h
|
include/squareenixlogin.h
|
||||||
include/steamapi.h
|
include/steamapi.h
|
||||||
|
include/crtrand.h
|
||||||
|
|
||||||
src/accountmanager.cpp
|
src/accountmanager.cpp
|
||||||
src/assetupdater.cpp
|
src/assetupdater.cpp
|
||||||
|
@ -70,7 +71,8 @@ target_sources(astra_static PRIVATE
|
||||||
src/patcher.cpp
|
src/patcher.cpp
|
||||||
src/processlogger.cpp
|
src/processlogger.cpp
|
||||||
src/squareenixlogin.cpp
|
src/squareenixlogin.cpp
|
||||||
src/steamapi.cpp)
|
src/steamapi.cpp
|
||||||
|
src/crtrand.cpp)
|
||||||
target_include_directories(astra_static PUBLIC include)
|
target_include_directories(astra_static PUBLIC include)
|
||||||
target_link_libraries(astra_static PUBLIC
|
target_link_libraries(astra_static PUBLIC
|
||||||
physis
|
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>
|
#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();
|
||||||
~LauncherCore() override;
|
~LauncherCore() override;
|
||||||
|
|
||||||
/// Initializes the Steamworks API.
|
|
||||||
void initializeSteam();
|
|
||||||
|
|
||||||
/// Begins the login process.
|
/// 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.
|
||||||
|
@ -130,6 +127,7 @@ public:
|
||||||
[[nodiscard]] AccountManager *accountManager();
|
[[nodiscard]] AccountManager *accountManager();
|
||||||
[[nodiscard]] Headline *headline() const;
|
[[nodiscard]] Headline *headline() const;
|
||||||
[[nodiscard]] QString cachedLogoImage() const;
|
[[nodiscard]] QString cachedLogoImage() const;
|
||||||
|
[[nodiscard]] SteamAPI *steamApi() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Opens the official launcher. Useful if Astra decides not to work that day!
|
* @brief Opens the official launcher. Useful if Astra decides not to work that day!
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QCoroTask>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
class LauncherCore;
|
class LauncherCore;
|
||||||
|
@ -12,7 +14,10 @@ class SteamAPI : public QObject
|
||||||
public:
|
public:
|
||||||
explicit SteamAPI(QObject *parent = nullptr);
|
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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "encryptedarg.h"
|
#include "encryptedarg.h"
|
||||||
|
#include "crtrand.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
#include <physis.hpp>
|
#include <physis.hpp>
|
||||||
|
|
||||||
#if defined(Q_OS_MAC)
|
#if defined(Q_OS_MAC)
|
||||||
|
@ -83,4 +85,83 @@ QString encryptGameArg(const QString &arg)
|
||||||
physis_blowfish_free(blowfish);
|
physis_blowfish_free(blowfish);
|
||||||
|
|
||||||
return QStringLiteral("//**sqex0003%1%2**//").arg(base64, QString(QLatin1Char(checksum)));
|
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_profileManager = new ProfileManager(this);
|
||||||
m_accountManager = new AccountManager(this);
|
m_accountManager = new AccountManager(this);
|
||||||
m_runner = new GameRunner(*this, this);
|
m_runner = new GameRunner(*this, this);
|
||||||
|
m_steamApi = new SteamAPI(this);
|
||||||
|
|
||||||
connect(m_accountManager, &AccountManager::accountAdded, this, &LauncherCore::fetchAvatar);
|
connect(m_accountManager, &AccountManager::accountAdded, this, &LauncherCore::fetchAvatar);
|
||||||
connect(m_accountManager, &AccountManager::accountLodestoneIdChanged, this, &LauncherCore::fetchAvatar);
|
connect(m_accountManager, &AccountManager::accountLodestoneIdChanged, this, &LauncherCore::fetchAvatar);
|
||||||
|
@ -79,12 +80,6 @@ LauncherCore::~LauncherCore()
|
||||||
m_config->save();
|
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)
|
void LauncherCore::login(Profile *profile, const QString &username, const QString &password, const QString &oneTimePassword)
|
||||||
{
|
{
|
||||||
Q_ASSERT(profile != nullptr);
|
Q_ASSERT(profile != nullptr);
|
||||||
|
@ -445,6 +440,11 @@ QString LauncherCore::cachedLogoImage() const
|
||||||
return m_cachedLogoImage;
|
return m_cachedLogoImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SteamAPI *LauncherCore::steamApi() const
|
||||||
|
{
|
||||||
|
return m_steamApi;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef BUILD_SYNC
|
#ifdef BUILD_SYNC
|
||||||
SyncManager *LauncherCore::syncManager() const
|
SyncManager *LauncherCore::syncManager() const
|
||||||
{
|
{
|
||||||
|
@ -480,10 +480,6 @@ QCoro::Task<> LauncherCore::beginLogin(LoginInformation &info)
|
||||||
|
|
||||||
Q_EMIT stageChanged(i18n("Launching game..."));
|
Q_EMIT stageChanged(i18n("Launching game..."));
|
||||||
|
|
||||||
if (isSteam()) {
|
|
||||||
m_steamApi->setLauncherMode(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_runner->beginGameExecutable(*info.profile, auth);
|
m_runner->beginGameExecutable(*info.profile, auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -133,11 +133,6 @@ int main(int argc, char *argv[])
|
||||||
|
|
||||||
QQmlApplicationEngine engine;
|
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));
|
engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
|
||||||
QObject::connect(&engine, &QQmlApplicationEngine::quit, &app, &QCoreApplication::quit);
|
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) {
|
if (m_info->profile->account()->config()->license() == Account::GameLicense::WindowsSteam) {
|
||||||
query.addQueryItem(QStringLiteral("issteam"), QString::number(1));
|
query.addQueryItem(QStringLiteral("issteam"), QString::number(1));
|
||||||
|
|
||||||
// TODO: get steam ticket information from steam api
|
// initialize the steam api
|
||||||
query.addQueryItem(QStringLiteral("session_ticket"), QString::number(1));
|
co_await m_launcher.steamApi()->initialize();
|
||||||
query.addQueryItem(QStringLiteral("ticket_size"), QString::number(1));
|
|
||||||
|
// 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;
|
QUrl url;
|
||||||
|
@ -251,22 +255,9 @@ QCoro::Task<std::optional<SquareEnixLogin::StoredInfo>> SquareEnixLogin::getStor
|
||||||
const auto reply = m_launcher.mgr()->get(request);
|
const auto reply = m_launcher.mgr()->get(request);
|
||||||
co_await reply;
|
co_await reply;
|
||||||
|
|
||||||
|
m_username = m_info->username;
|
||||||
|
|
||||||
const QString str = QString::fromUtf8(reply->readAll());
|
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 static QRegularExpression re(QStringLiteral(R"lit(\t<\s*input .* name="_STORED_" value="(?<stored>.*)">)lit"));
|
||||||
const QRegularExpressionMatch match = re.match(str);
|
const QRegularExpressionMatch match = re.match(str);
|
||||||
if (match.hasMatch()) {
|
if (match.hasMatch()) {
|
||||||
|
|
|
@ -2,23 +2,50 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "steamapi.h"
|
#include "steamapi.h"
|
||||||
|
#include "encryptedarg.h"
|
||||||
#include "launchercore.h"
|
#include "launchercore.h"
|
||||||
|
|
||||||
|
#include <QCoroNetwork>
|
||||||
|
|
||||||
SteamAPI::SteamAPI(QObject *parent)
|
SteamAPI::SteamAPI(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
{
|
{
|
||||||
// TODO: stub
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SteamAPI::setLauncherMode(bool isLauncher)
|
QCoro::Task<> SteamAPI::initialize()
|
||||||
{
|
{
|
||||||
Q_UNUSED(isLauncher)
|
QUrl url;
|
||||||
// TODO: stub
|
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
|
QUrl url;
|
||||||
return false;
|
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.")
|
description: i18n("If the account holds multiple licenses, choose the preferred one.")
|
||||||
model: ["Windows", "Steam", "macOS"]
|
model: ["Windows", "Steam", "macOS"]
|
||||||
text: i18n("License")
|
text: i18n("License")
|
||||||
|
|
||||||
onCurrentIndexChanged: {
|
|
||||||
if (currentIndex === 1) {
|
|
||||||
currentIndex = 0;
|
|
||||||
errorDialog.open();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
FormCard.FormDelegateSeparator {
|
FormCard.FormDelegateSeparator {
|
||||||
above: licenseField
|
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