637 lines
23 KiB
C++
637 lines
23 KiB
C++
#include "launcherbackend.h"
|
|
|
|
#include "authservice.h"
|
|
#include "gamelauncher.h"
|
|
#include "javalocator.h"
|
|
#include "launcherpaths.h"
|
|
|
|
#include <QDesktopServices>
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QJsonArray>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QRegularExpression>
|
|
#include <QTimer>
|
|
#include <QUrl>
|
|
#include <algorithm>
|
|
|
|
namespace {
|
|
|
|
const QString kOffline = QStringLiteral("offline");
|
|
const QString kElyBy = QStringLiteral("elyby");
|
|
|
|
int settingInt(const QVariantMap &settings, const QString &key, int fallback)
|
|
{
|
|
const QVariant value = settings.value(key);
|
|
bool ok = false;
|
|
const int parsed = value.toInt(&ok);
|
|
return ok ? parsed : fallback;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
LauncherBackend::LauncherBackend(QObject *parent)
|
|
: QObject(parent)
|
|
, m_auth(new AuthService(this))
|
|
, m_launcher(new GameLauncher(this))
|
|
{
|
|
// Папка лаунчера создаётся при первом запуске и проверяется при каждом
|
|
// следующем — если её удалили, она появится снова.
|
|
LauncherPaths::ensureRootExists(&m_storageError);
|
|
|
|
loadSettings();
|
|
loadData();
|
|
ensureDataFiles();
|
|
|
|
connect(m_auth, &AuthService::progress, this, &LauncherBackend::launchProgress);
|
|
connect(m_launcher, &GameLauncher::progress, this, &LauncherBackend::launchProgress);
|
|
connect(m_launcher, &GameLauncher::output, this, &LauncherBackend::gameOutput);
|
|
connect(m_launcher, &GameLauncher::gameStarted, this, [this](const QString &commandLine) {
|
|
emit gameOutput(commandLine);
|
|
emit gameRunningChanged();
|
|
});
|
|
connect(m_launcher, &GameLauncher::gameFinished, this, [this](int exitCode, bool crashed) {
|
|
emit gameRunningChanged();
|
|
emit gameFinished(exitCode, crashed);
|
|
});
|
|
|
|
// Интерфейс подключается к сигналам уже после конструктора — сообщаем отложенно.
|
|
if (!m_storageError.isEmpty()) {
|
|
QTimer::singleShot(0, this, [this] {
|
|
emit launchError(tr("Не удалось создать папку лаунчера: %1").arg(m_storageError));
|
|
});
|
|
}
|
|
}
|
|
|
|
LauncherBackend::~LauncherBackend() = default;
|
|
|
|
QStringList LauncherBackend::profileNames() const
|
|
{
|
|
QStringList names;
|
|
for (const auto &p : m_profiles)
|
|
names << p.name;
|
|
return names;
|
|
}
|
|
|
|
QStringList LauncherBackend::versionNames() const
|
|
{
|
|
QStringList names;
|
|
for (const auto &v : m_versions)
|
|
names << v.name;
|
|
return names;
|
|
}
|
|
|
|
QStringList LauncherBackend::installedVersions() const
|
|
{
|
|
return VersionLoader::installedVersions(gameDir());
|
|
}
|
|
|
|
bool LauncherBackend::gameRunning() const
|
|
{
|
|
return m_launcher->isRunning();
|
|
}
|
|
|
|
void LauncherBackend::addProfile(const QString &name,
|
|
const QString &login,
|
|
const QString &password,
|
|
const QString &authType)
|
|
{
|
|
Profile profile;
|
|
profile.id = m_nextProfileId++;
|
|
profile.name = name;
|
|
profile.login = login;
|
|
profile.password = password;
|
|
profile.authType = (authType == kElyBy) ? kElyBy : kOffline;
|
|
profile.clientToken = AuthService::generateClientToken();
|
|
m_profiles.append(profile);
|
|
saveProfiles();
|
|
emit profilesChanged();
|
|
}
|
|
|
|
void LauncherBackend::addVersion(const QString &name,
|
|
const QString &serverUrl,
|
|
const QString &versionId)
|
|
{
|
|
m_versions.append({m_nextVersionId++, name, serverUrl, versionId});
|
|
saveVersions();
|
|
emit versionsChanged();
|
|
}
|
|
|
|
void LauncherBackend::updateProfile(int index,
|
|
const QString &name,
|
|
const QString &login,
|
|
const QString &password,
|
|
const QString &authType)
|
|
{
|
|
if (index < 0 || index >= m_profiles.size())
|
|
return;
|
|
|
|
Profile &profile = m_profiles[index];
|
|
// Смена логина или пароля обесценивает сохранённый токен Ely.by.
|
|
if (profile.login != login || profile.password != password) {
|
|
profile.accessToken.clear();
|
|
profile.uuid.clear();
|
|
profile.resolvedName.clear();
|
|
}
|
|
profile.name = name;
|
|
profile.login = login;
|
|
profile.password = password;
|
|
profile.authType = (authType == kElyBy) ? kElyBy : kOffline;
|
|
if (profile.clientToken.isEmpty())
|
|
profile.clientToken = AuthService::generateClientToken();
|
|
|
|
saveProfiles();
|
|
emit profilesChanged();
|
|
}
|
|
|
|
void LauncherBackend::updateVersion(int index,
|
|
const QString &name,
|
|
const QString &serverUrl,
|
|
const QString &versionId)
|
|
{
|
|
if (index < 0 || index >= m_versions.size())
|
|
return;
|
|
m_versions[index].name = name;
|
|
m_versions[index].serverUrl = serverUrl;
|
|
m_versions[index].versionId = versionId;
|
|
saveVersions();
|
|
emit versionsChanged();
|
|
}
|
|
|
|
QVariantMap LauncherBackend::profileAt(int index) const
|
|
{
|
|
if (index < 0 || index >= m_profiles.size())
|
|
return {};
|
|
const auto &p = m_profiles[index];
|
|
return {{"name", p.name},
|
|
{"login", p.login},
|
|
{"password", p.password},
|
|
{"authType", p.authType}};
|
|
}
|
|
|
|
QVariantMap LauncherBackend::versionAt(int index) const
|
|
{
|
|
if (index < 0 || index >= m_versions.size())
|
|
return {};
|
|
const auto &v = m_versions[index];
|
|
return {{"name", v.name}, {"serverUrl", v.serverUrl}, {"versionId", versionIdOf(v)}};
|
|
}
|
|
|
|
void LauncherBackend::removeProfile(int index)
|
|
{
|
|
if (index < 0 || index >= m_profiles.size())
|
|
return;
|
|
m_profiles.removeAt(index);
|
|
saveProfiles();
|
|
emit profilesChanged();
|
|
}
|
|
|
|
void LauncherBackend::removeVersion(int index)
|
|
{
|
|
if (index < 0 || index >= m_versions.size())
|
|
return;
|
|
m_versions.removeAt(index);
|
|
saveVersions();
|
|
emit versionsChanged();
|
|
}
|
|
|
|
QString LauncherBackend::versionIdOf(const Version &version) const
|
|
{
|
|
// Пользователь мог назвать запись как угодно — тогда версией считаем само имя.
|
|
return version.versionId.isEmpty() ? version.name : version.versionId;
|
|
}
|
|
|
|
QStringList LauncherBackend::checkInstallation(int versionIndex) const
|
|
{
|
|
if (versionIndex < 0 || versionIndex >= m_versions.size())
|
|
return {tr("Версия не выбрана")};
|
|
|
|
const QString id = versionIdOf(m_versions[versionIndex]);
|
|
QString error;
|
|
const MinecraftVersion version = VersionLoader::load(gameDir(), id, {}, &error);
|
|
if (!version.isValid())
|
|
return {error.isEmpty() ? tr("Версия «%1» не установлена").arg(id) : error};
|
|
|
|
LaunchOptions options;
|
|
options.gameDir = gameDir();
|
|
options.versionId = id;
|
|
return GameLauncher::missingFiles(options, version);
|
|
}
|
|
|
|
void LauncherBackend::launchGame(int profileIndex, int versionIndex)
|
|
{
|
|
if (m_busy) {
|
|
emit launchError(tr("Запуск уже выполняется"));
|
|
return;
|
|
}
|
|
if (m_launcher->isRunning()) {
|
|
emit launchError(tr("Игра уже запущена"));
|
|
return;
|
|
}
|
|
if (profileIndex < 0 || profileIndex >= m_profiles.size()) {
|
|
emit launchError(tr("Выберите профиль перед запуском"));
|
|
return;
|
|
}
|
|
if (versionIndex < 0 || versionIndex >= m_versions.size()) {
|
|
emit launchError(tr("Выберите версию перед запуском"));
|
|
return;
|
|
}
|
|
|
|
const QString directory = gameDir();
|
|
if (!QDir(directory).exists()) {
|
|
emit launchError(tr("Папка игры не найдена: %1").arg(QDir::toNativeSeparators(directory)));
|
|
return;
|
|
}
|
|
|
|
const QString id = versionIdOf(m_versions[versionIndex]);
|
|
const QStringList installed = VersionLoader::installedVersions(directory);
|
|
if (!installed.contains(id)) {
|
|
emit launchError(installed.isEmpty() ? tr("В %1 нет ни одной установленной версии")
|
|
.arg(QDir::toNativeSeparators(
|
|
directory + QStringLiteral("/versions")))
|
|
: tr("Версия «%1» не установлена. Доступны: %2")
|
|
.arg(id, installed.join(QStringLiteral(", "))));
|
|
return;
|
|
}
|
|
|
|
setBusy(true);
|
|
emit launchProgress(tr("Чтение описания версии «%1»…").arg(id));
|
|
|
|
QSet<QString> features;
|
|
if (settingInt(m_settings, QStringLiteral("windowWidth"), 0) > 0
|
|
&& settingInt(m_settings, QStringLiteral("windowHeight"), 0) > 0)
|
|
features << QStringLiteral("has_custom_resolution");
|
|
|
|
QString error;
|
|
m_pendingVersionData = VersionLoader::load(directory, id, features, &error);
|
|
if (!m_pendingVersionData.isValid()) {
|
|
failLaunch(error.isEmpty() ? tr("Не удалось прочитать версию «%1»").arg(id) : error);
|
|
return;
|
|
}
|
|
|
|
LaunchOptions probe;
|
|
probe.gameDir = directory;
|
|
probe.versionId = id;
|
|
const QStringList missing = GameLauncher::missingFiles(probe, m_pendingVersionData);
|
|
if (!missing.isEmpty()) {
|
|
failLaunch(tr("В папке игры не хватает файлов (%1):\n%2")
|
|
.arg(missing.size())
|
|
.arg(missing.join(u'\n')));
|
|
return;
|
|
}
|
|
|
|
m_pendingProfile = profileIndex;
|
|
m_pendingVersion = versionIndex;
|
|
m_pendingInjectorPath.clear();
|
|
beginAuthentication();
|
|
}
|
|
|
|
void LauncherBackend::beginAuthentication()
|
|
{
|
|
const Profile &profile = m_profiles[m_pendingProfile];
|
|
|
|
if (profile.authType != kElyBy) {
|
|
const QString nickname = profile.login.isEmpty() ? profile.name : profile.login;
|
|
const AuthResult result = AuthService::offline(nickname);
|
|
if (!result.ok) {
|
|
failLaunch(result.error);
|
|
return;
|
|
}
|
|
continueLaunch(result);
|
|
return;
|
|
}
|
|
|
|
// Ely.by работает только вместе с authlib-injector — без него игра пойдёт
|
|
// в сессионный сервер Mojang и токен окажется недействительным.
|
|
m_auth->ensureAuthlibInjector(LauncherPaths::runtimeDir(),
|
|
[this](const QString &path, const QString &error) {
|
|
if (m_pendingProfile < 0)
|
|
return; // запуск отменили, пока качался инжектор
|
|
if (!error.isEmpty()) {
|
|
failLaunch(error);
|
|
return;
|
|
}
|
|
m_pendingInjectorPath = path;
|
|
|
|
const Profile &pending = m_profiles[m_pendingProfile];
|
|
m_auth->loginElyBy(pending.login,
|
|
pending.password,
|
|
pending.clientToken,
|
|
pending.accessToken,
|
|
[this](const AuthResult &result) {
|
|
if (m_pendingProfile < 0)
|
|
return;
|
|
if (result.twoFactorRequired) {
|
|
setBusy(false);
|
|
emit twoFactorRequired(m_profiles[m_pendingProfile].name);
|
|
return;
|
|
}
|
|
if (!result.ok) {
|
|
failLaunch(result.error);
|
|
return;
|
|
}
|
|
continueLaunch(result);
|
|
});
|
|
});
|
|
}
|
|
|
|
void LauncherBackend::submitTwoFactorCode(const QString &code)
|
|
{
|
|
if (m_pendingProfile < 0 || m_pendingProfile >= m_profiles.size()) {
|
|
emit launchError(tr("Запуск уже отменён"));
|
|
return;
|
|
}
|
|
if (code.trimmed().isEmpty()) {
|
|
emit launchError(tr("Введите код двухфакторной аутентификации"));
|
|
return;
|
|
}
|
|
|
|
setBusy(true);
|
|
const Profile &profile = m_profiles[m_pendingProfile];
|
|
m_auth->loginElyByWithTotp(profile.login,
|
|
profile.password,
|
|
code.trimmed(),
|
|
profile.clientToken,
|
|
[this](const AuthResult &result) {
|
|
if (m_pendingProfile < 0)
|
|
return;
|
|
if (!result.ok) {
|
|
failLaunch(result.error);
|
|
return;
|
|
}
|
|
continueLaunch(result);
|
|
});
|
|
}
|
|
|
|
void LauncherBackend::cancelPendingLaunch()
|
|
{
|
|
m_pendingProfile = -1;
|
|
m_pendingVersion = -1;
|
|
m_pendingInjectorPath.clear();
|
|
setBusy(false);
|
|
}
|
|
|
|
void LauncherBackend::continueLaunch(const AuthResult &auth)
|
|
{
|
|
if (m_pendingProfile < 0 || m_pendingVersion < 0) {
|
|
failLaunch(tr("Запуск был отменён"));
|
|
return;
|
|
}
|
|
|
|
Profile &profile = m_profiles[m_pendingProfile];
|
|
const Version &version = m_versions[m_pendingVersion];
|
|
|
|
// Ely.by возвращает игровой ник и UUID аккаунта — их и запоминаем,
|
|
// чтобы в следующий раз обойтись без пароля.
|
|
if (!auth.accessToken.isEmpty() && profile.authType == kElyBy) {
|
|
profile.accessToken = auth.accessToken;
|
|
profile.clientToken = auth.clientToken;
|
|
if (!auth.uuid.isEmpty())
|
|
profile.uuid = auth.uuid;
|
|
if (!auth.playerName.isEmpty())
|
|
profile.resolvedName = auth.playerName;
|
|
profile.userType = auth.userType;
|
|
saveProfiles();
|
|
}
|
|
|
|
LaunchOptions options;
|
|
options.gameDir = gameDir();
|
|
options.versionId = m_pendingVersionData.id;
|
|
options.playerName = auth.playerName.isEmpty() ? profile.resolvedName : auth.playerName;
|
|
if (options.playerName.isEmpty())
|
|
options.playerName = profile.login.isEmpty() ? profile.name : profile.login;
|
|
options.uuid = auth.uuid.isEmpty() ? profile.uuid : auth.uuid;
|
|
options.accessToken = auth.accessToken;
|
|
options.clientToken = auth.clientToken;
|
|
options.userType = auth.userType;
|
|
|
|
options.javaPath = m_settings.value(QStringLiteral("javaPath")).toString();
|
|
options.minMemoryMb = settingInt(m_settings, QStringLiteral("minMemoryMb"), 512);
|
|
options.maxMemoryMb = settingInt(m_settings, QStringLiteral("maxMemoryMb"), 4096);
|
|
options.windowWidth = settingInt(m_settings, QStringLiteral("windowWidth"), 0);
|
|
options.windowHeight = settingInt(m_settings, QStringLiteral("windowHeight"), 0);
|
|
options.fullscreen = m_settings.value(QStringLiteral("fullscreen")).toBool();
|
|
options.serverAddress = version.serverUrl.trimmed();
|
|
options.launcherName = m_settings.value(QStringLiteral("launcherName")).toString();
|
|
options.launcherVersion = m_settings.value(QStringLiteral("launcherVersion")).toString();
|
|
|
|
const QString extra = m_settings.value(QStringLiteral("jvmArgs")).toString().trimmed();
|
|
if (!extra.isEmpty())
|
|
options.extraJvmArgs = extra.split(QRegularExpression(QStringLiteral("\\s+")),
|
|
Qt::SkipEmptyParts);
|
|
|
|
if (profile.authType == kElyBy)
|
|
options.authlibInjectorPath = m_pendingInjectorPath;
|
|
|
|
QString error;
|
|
const bool started = m_launcher->launch(options, m_pendingVersionData, &error);
|
|
|
|
const QString profileName = profile.name;
|
|
const QString versionName = version.name;
|
|
const QString serverUrl = version.serverUrl;
|
|
cancelPendingLaunch();
|
|
|
|
if (!started) {
|
|
emit launchError(error);
|
|
return;
|
|
}
|
|
emit gameRunningChanged();
|
|
emit launched(profileName, versionName, serverUrl);
|
|
}
|
|
|
|
void LauncherBackend::stopGame()
|
|
{
|
|
m_launcher->terminate();
|
|
}
|
|
|
|
void LauncherBackend::failLaunch(const QString &message)
|
|
{
|
|
cancelPendingLaunch();
|
|
emit launchError(message);
|
|
}
|
|
|
|
void LauncherBackend::setBusy(bool busy)
|
|
{
|
|
if (m_busy == busy)
|
|
return;
|
|
m_busy = busy;
|
|
emit busyChanged();
|
|
}
|
|
|
|
QString LauncherBackend::gameDir() const
|
|
{
|
|
const QString configured = m_settings.value(QStringLiteral("gameDir")).toString().trimmed();
|
|
return configured.isEmpty() ? LauncherPaths::defaultMinecraftDir()
|
|
: QDir::fromNativeSeparators(configured);
|
|
}
|
|
|
|
QVariantMap LauncherBackend::settings() const
|
|
{
|
|
QVariantMap result = m_settings;
|
|
result[QStringLiteral("resolvedGameDir")] = QDir::toNativeSeparators(gameDir());
|
|
return result;
|
|
}
|
|
|
|
void LauncherBackend::updateSettings(const QVariantMap &values)
|
|
{
|
|
const QString previousGameDir = gameDir();
|
|
for (auto it = values.cbegin(); it != values.cend(); ++it)
|
|
m_settings.insert(it.key(), it.value());
|
|
saveSettings();
|
|
emit settingsChanged();
|
|
if (gameDir() != previousGameDir)
|
|
emit installedVersionsChanged();
|
|
}
|
|
|
|
QStringList LauncherBackend::detectedJava() const
|
|
{
|
|
QStringList result;
|
|
const QStringList candidates = JavaLocator::findAll(gameDir());
|
|
for (const QString &candidate : candidates) {
|
|
const int major = JavaLocator::majorVersion(candidate);
|
|
result << (major > 0 ? QStringLiteral("Java %1 — %2")
|
|
.arg(major)
|
|
.arg(QDir::toNativeSeparators(candidate))
|
|
: QDir::toNativeSeparators(candidate));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void LauncherBackend::openMinecraftFolder()
|
|
{
|
|
const QString modsDir = gameDir() + QStringLiteral("/mods");
|
|
QDir().mkpath(modsDir);
|
|
|
|
if (!QDesktopServices::openUrl(QUrl::fromLocalFile(modsDir)))
|
|
emit launchError(tr("Не удалось открыть папку с модами"));
|
|
}
|
|
|
|
void LauncherBackend::openGameFolder()
|
|
{
|
|
const QString directory = gameDir();
|
|
QDir().mkpath(directory);
|
|
|
|
if (!QDesktopServices::openUrl(QUrl::fromLocalFile(directory)))
|
|
emit launchError(tr("Не удалось открыть папку игры"));
|
|
}
|
|
|
|
void LauncherBackend::loadData()
|
|
{
|
|
auto loadFile = [](const QString &path, auto handler) {
|
|
QFile f(path);
|
|
if (!f.open(QIODevice::ReadOnly))
|
|
return;
|
|
for (const auto &val : QJsonDocument::fromJson(f.readAll()).array())
|
|
handler(val.toObject());
|
|
};
|
|
|
|
loadFile(LauncherPaths::profilesFile(), [this](const QJsonObject &o) {
|
|
Profile profile;
|
|
profile.id = o["id"].toInt();
|
|
profile.name = o["name"].toString();
|
|
profile.login = o["login"].toString();
|
|
profile.password = o["password"].toString();
|
|
profile.authType = o.value("authType").toString(kOffline);
|
|
profile.clientToken = o.value("clientToken").toString();
|
|
profile.accessToken = o.value("accessToken").toString();
|
|
profile.uuid = o.value("uuid").toString();
|
|
profile.resolvedName = o.value("resolvedName").toString();
|
|
profile.userType = o.value("userType").toString();
|
|
if (profile.clientToken.isEmpty())
|
|
profile.clientToken = AuthService::generateClientToken();
|
|
m_profiles.append(profile);
|
|
if (profile.id >= m_nextProfileId)
|
|
m_nextProfileId = profile.id + 1;
|
|
});
|
|
std::sort(m_profiles.begin(), m_profiles.end(), [](const Profile &a, const Profile &b) {
|
|
return a.id < b.id;
|
|
});
|
|
|
|
loadFile(LauncherPaths::versionsFile(), [this](const QJsonObject &o) {
|
|
const int id = o["id"].toInt();
|
|
m_versions.append(
|
|
{id, o["name"].toString(), o["serverUrl"].toString(), o.value("versionId").toString()});
|
|
if (id >= m_nextVersionId)
|
|
m_nextVersionId = id + 1;
|
|
});
|
|
std::sort(m_versions.begin(), m_versions.end(), [](const Version &a, const Version &b) {
|
|
return a.id < b.id;
|
|
});
|
|
}
|
|
|
|
// Файлы могли не появиться при первом запуске или быть удалены — создаём заново,
|
|
// чтобы папка лаунчера всегда была укомплектована.
|
|
void LauncherBackend::ensureDataFiles()
|
|
{
|
|
if (!QFile::exists(LauncherPaths::settingsFile()))
|
|
saveSettings();
|
|
if (!QFile::exists(LauncherPaths::profilesFile()))
|
|
saveProfiles();
|
|
if (!QFile::exists(LauncherPaths::versionsFile()))
|
|
saveVersions();
|
|
}
|
|
|
|
void LauncherBackend::saveProfiles()
|
|
{
|
|
QJsonArray arr;
|
|
for (const auto &p : m_profiles)
|
|
arr.append(QJsonObject{{"id", p.id},
|
|
{"name", p.name},
|
|
{"login", p.login},
|
|
{"password", p.password},
|
|
{"authType", p.authType},
|
|
{"clientToken", p.clientToken},
|
|
{"accessToken", p.accessToken},
|
|
{"uuid", p.uuid},
|
|
{"resolvedName", p.resolvedName},
|
|
{"userType", p.userType}});
|
|
|
|
QFile f(LauncherPaths::profilesFile());
|
|
if (f.open(QIODevice::WriteOnly))
|
|
f.write(QJsonDocument(arr).toJson());
|
|
}
|
|
|
|
void LauncherBackend::saveVersions()
|
|
{
|
|
QJsonArray arr;
|
|
for (const auto &v : m_versions)
|
|
arr.append(QJsonObject{{"id", v.id},
|
|
{"name", v.name},
|
|
{"serverUrl", v.serverUrl},
|
|
{"versionId", v.versionId}});
|
|
|
|
QFile f(LauncherPaths::versionsFile());
|
|
if (f.open(QIODevice::WriteOnly))
|
|
f.write(QJsonDocument(arr).toJson());
|
|
}
|
|
|
|
void LauncherBackend::loadSettings()
|
|
{
|
|
m_settings = {{QStringLiteral("gameDir"), QString()},
|
|
{QStringLiteral("javaPath"), QString()},
|
|
{QStringLiteral("minMemoryMb"), 512},
|
|
{QStringLiteral("maxMemoryMb"), 4096},
|
|
{QStringLiteral("jvmArgs"), QString()},
|
|
{QStringLiteral("windowWidth"), 0},
|
|
{QStringLiteral("windowHeight"), 0},
|
|
{QStringLiteral("fullscreen"), false},
|
|
{QStringLiteral("launcherName"), QStringLiteral("KishkaLauncher")},
|
|
{QStringLiteral("launcherVersion"), QStringLiteral("1.0")}};
|
|
|
|
QFile f(LauncherPaths::settingsFile());
|
|
if (!f.open(QIODevice::ReadOnly))
|
|
return;
|
|
|
|
const QJsonObject stored = QJsonDocument::fromJson(f.readAll()).object();
|
|
for (auto it = stored.constBegin(); it != stored.constEnd(); ++it)
|
|
m_settings.insert(it.key(), it.value().toVariant());
|
|
}
|
|
|
|
void LauncherBackend::saveSettings()
|
|
{
|
|
QFile f(LauncherPaths::settingsFile());
|
|
if (f.open(QIODevice::WriteOnly))
|
|
f.write(QJsonDocument(QJsonObject::fromVariantMap(m_settings)).toJson());
|
|
}
|