new home folder for launcher
This commit is contained in:
@@ -18,6 +18,8 @@ qt_add_qml_module(appMinecraft_launcher
|
|||||||
SOURCES
|
SOURCES
|
||||||
launcherbackend.h
|
launcherbackend.h
|
||||||
launcherbackend.cpp
|
launcherbackend.cpp
|
||||||
|
launcherpaths.h
|
||||||
|
launcherpaths.cpp
|
||||||
minecraftversion.h
|
minecraftversion.h
|
||||||
minecraftversion.cpp
|
minecraftversion.cpp
|
||||||
javalocator.h
|
javalocator.h
|
||||||
|
|||||||
+179
-48
@@ -3,6 +3,7 @@
|
|||||||
#include "authservice.h"
|
#include "authservice.h"
|
||||||
#include "gamelauncher.h"
|
#include "gamelauncher.h"
|
||||||
#include "javalocator.h"
|
#include "javalocator.h"
|
||||||
|
#include "launcherpaths.h"
|
||||||
|
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
@@ -11,7 +12,7 @@
|
|||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QStandardPaths>
|
#include <QTimer>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
@@ -32,13 +33,18 @@ int settingInt(const QVariantMap &settings, const QString &key, int fallback)
|
|||||||
|
|
||||||
LauncherBackend::LauncherBackend(QObject *parent)
|
LauncherBackend::LauncherBackend(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, m_dataDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))
|
|
||||||
, m_auth(new AuthService(this))
|
, m_auth(new AuthService(this))
|
||||||
, m_launcher(new GameLauncher(this))
|
, m_launcher(new GameLauncher(this))
|
||||||
{
|
{
|
||||||
QDir().mkpath(m_dataDir);
|
// Папка лаунчера создаётся при первом запуске и проверяется при каждом
|
||||||
|
// следующем — если её удалили, она появится снова.
|
||||||
|
QString storageError;
|
||||||
|
if (!LauncherPaths::ensureRootExists(&storageError))
|
||||||
|
m_storageIssues << tr("Не удалось создать папку лаунчера: %1").arg(storageError);
|
||||||
|
|
||||||
loadSettings();
|
loadSettings();
|
||||||
loadData();
|
loadData();
|
||||||
|
ensureDataFiles();
|
||||||
|
|
||||||
connect(m_auth, &AuthService::progress, this, &LauncherBackend::launchProgress);
|
connect(m_auth, &AuthService::progress, this, &LauncherBackend::launchProgress);
|
||||||
connect(m_launcher, &GameLauncher::progress, this, &LauncherBackend::launchProgress);
|
connect(m_launcher, &GameLauncher::progress, this, &LauncherBackend::launchProgress);
|
||||||
@@ -51,6 +57,13 @@ LauncherBackend::LauncherBackend(QObject *parent)
|
|||||||
emit gameRunningChanged();
|
emit gameRunningChanged();
|
||||||
emit gameFinished(exitCode, crashed);
|
emit gameFinished(exitCode, crashed);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Интерфейс подключается к сигналам уже после конструктора — сообщаем отложенно.
|
||||||
|
if (!m_storageIssues.isEmpty()) {
|
||||||
|
QTimer::singleShot(0, this, [this] {
|
||||||
|
emit launchError(m_storageIssues.join(u'\n'));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LauncherBackend::~LauncherBackend() = default;
|
LauncherBackend::~LauncherBackend() = default;
|
||||||
@@ -194,11 +207,22 @@ QStringList LauncherBackend::checkInstallation(int versionIndex) const
|
|||||||
if (versionIndex < 0 || versionIndex >= m_versions.size())
|
if (versionIndex < 0 || versionIndex >= m_versions.size())
|
||||||
return {tr("Версия не выбрана")};
|
return {tr("Версия не выбрана")};
|
||||||
|
|
||||||
const QString id = versionIdOf(m_versions[versionIndex]);
|
const Version &entry = m_versions[versionIndex];
|
||||||
|
const QString id = versionIdOf(entry);
|
||||||
|
if (id.trimmed().isEmpty())
|
||||||
|
return {tr("У записи лаунчера не указан ID версии. Проверьте %1")
|
||||||
|
.arg(QDir::toNativeSeparators(LauncherPaths::versionsFile()))};
|
||||||
|
|
||||||
QString error;
|
QString error;
|
||||||
const MinecraftVersion version = VersionLoader::load(gameDir(), id, {}, &error);
|
const MinecraftVersion version = VersionLoader::load(gameDir(), id, {}, &error);
|
||||||
if (!version.isValid())
|
if (!version.isValid()) {
|
||||||
return {error.isEmpty() ? tr("Версия «%1» не установлена").arg(id) : error};
|
return {error.isEmpty()
|
||||||
|
? tr("Версия «%1» из записи «%2» не установлена в %3")
|
||||||
|
.arg(id,
|
||||||
|
entry.name,
|
||||||
|
QDir::toNativeSeparators(gameDir() + QStringLiteral("/versions")))
|
||||||
|
: error};
|
||||||
|
}
|
||||||
|
|
||||||
LaunchOptions options;
|
LaunchOptions options;
|
||||||
options.gameDir = gameDir();
|
options.gameDir = gameDir();
|
||||||
@@ -225,20 +249,33 @@ void LauncherBackend::launchGame(int profileIndex, int versionIndex)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Дальше проверяется только папка игры: свои файлы лаунчер уже разложил
|
||||||
|
// в galeonLauncher при запуске.
|
||||||
const QString directory = gameDir();
|
const QString directory = gameDir();
|
||||||
if (!QDir(directory).exists()) {
|
if (!QDir(directory).exists()) {
|
||||||
emit launchError(tr("Папка игры не найдена: %1").arg(QDir::toNativeSeparators(directory)));
|
emit launchError(tr("Папка .minecraft не найдена: %1. Укажите путь к ней в настройках.")
|
||||||
|
.arg(QDir::toNativeSeparators(directory)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString id = versionIdOf(m_versions[versionIndex]);
|
const Version &entry = m_versions[versionIndex];
|
||||||
|
const QString id = versionIdOf(entry);
|
||||||
|
if (id.trimmed().isEmpty()) {
|
||||||
|
emit launchError(tr("У записи лаунчера не указан ID версии. Проверьте %1")
|
||||||
|
.arg(QDir::toNativeSeparators(LauncherPaths::versionsFile())));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString versionsDir = QDir::toNativeSeparators(directory + QStringLiteral("/versions"));
|
||||||
const QStringList installed = VersionLoader::installedVersions(directory);
|
const QStringList installed = VersionLoader::installedVersions(directory);
|
||||||
if (!installed.contains(id)) {
|
if (!installed.contains(id)) {
|
||||||
emit launchError(installed.isEmpty()
|
emit launchError(installed.isEmpty()
|
||||||
? tr("В %1 нет ни одной установленной версии")
|
? tr("В %1 нет ни одной установленной версии").arg(versionsDir)
|
||||||
.arg(QDir::toNativeSeparators(directory + QStringLiteral("/versions")))
|
: tr("Версия «%1» из записи «%2» не установлена в %3. Доступны: %4")
|
||||||
: tr("Версия «%1» не установлена. Доступны: %2")
|
.arg(id,
|
||||||
.arg(id, installed.join(QStringLiteral(", "))));
|
entry.name,
|
||||||
|
versionsDir,
|
||||||
|
installed.join(QStringLiteral(", "))));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,7 +328,8 @@ void LauncherBackend::beginAuthentication()
|
|||||||
|
|
||||||
// Ely.by работает только вместе с authlib-injector — без него игра пойдёт
|
// Ely.by работает только вместе с authlib-injector — без него игра пойдёт
|
||||||
// в сессионный сервер Mojang и токен окажется недействительным.
|
// в сессионный сервер Mojang и токен окажется недействительным.
|
||||||
m_auth->ensureAuthlibInjector(m_dataDir, [this](const QString &path, const QString &error) {
|
m_auth->ensureAuthlibInjector(LauncherPaths::runtimeDir(),
|
||||||
|
[this](const QString &path, const QString &error) {
|
||||||
if (m_pendingProfile < 0)
|
if (m_pendingProfile < 0)
|
||||||
return; // запуск отменили, пока качался инжектор
|
return; // запуск отменили, пока качался инжектор
|
||||||
if (!error.isEmpty()) {
|
if (!error.isEmpty()) {
|
||||||
@@ -445,21 +483,11 @@ void LauncherBackend::setBusy(bool busy)
|
|||||||
emit busyChanged();
|
emit busyChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString LauncherBackend::defaultMinecraftDir() const
|
|
||||||
{
|
|
||||||
#if defined(Q_OS_WIN)
|
|
||||||
return QDir::homePath() + QStringLiteral("/AppData/Roaming/.minecraft");
|
|
||||||
#elif defined(Q_OS_MACOS)
|
|
||||||
return QDir::homePath() + QStringLiteral("/Library/Application Support/minecraft");
|
|
||||||
#else // Linux и прочие Unix-системы
|
|
||||||
return QDir::homePath() + QStringLiteral("/.minecraft");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
QString LauncherBackend::gameDir() const
|
QString LauncherBackend::gameDir() const
|
||||||
{
|
{
|
||||||
const QString configured = m_settings.value(QStringLiteral("gameDir")).toString().trimmed();
|
const QString configured = m_settings.value(QStringLiteral("gameDir")).toString().trimmed();
|
||||||
return configured.isEmpty() ? defaultMinecraftDir() : QDir::fromNativeSeparators(configured);
|
return configured.isEmpty() ? LauncherPaths::defaultMinecraftDir()
|
||||||
|
: QDir::fromNativeSeparators(configured);
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariantMap LauncherBackend::settings() const
|
QVariantMap LauncherBackend::settings() const
|
||||||
@@ -512,17 +540,105 @@ void LauncherBackend::openGameFolder()
|
|||||||
emit launchError(tr("Не удалось открыть папку игры"));
|
emit launchError(tr("Не удалось открыть папку игры"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Повреждённый файл не удаляем: отводим в .bak, чтобы данные можно было спасти
|
||||||
|
// руками, а ensureDataFiles() создал на его месте пустой.
|
||||||
|
bool LauncherBackend::moveAsideBroken(const QString &path, const QString &reason)
|
||||||
|
{
|
||||||
|
const QString backup = path + QStringLiteral(".bak");
|
||||||
|
QFile::remove(backup);
|
||||||
|
if (!QFile::rename(path, backup)) {
|
||||||
|
m_storageIssues << tr("Файл %1 повреждён (%2), и его не удалось отложить в сторону")
|
||||||
|
.arg(QDir::toNativeSeparators(path), reason);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_storageIssues << tr("Файл %1 повреждён (%2). Копия сохранена как %3, файл создан заново.")
|
||||||
|
.arg(QDir::toNativeSeparators(path),
|
||||||
|
reason,
|
||||||
|
QDir::toNativeSeparators(backup));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonArray LauncherBackend::readArrayFile(const QString &path)
|
||||||
|
{
|
||||||
|
QFile file(path);
|
||||||
|
if (!file.exists())
|
||||||
|
return {}; // первый запуск — это не ошибка
|
||||||
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
|
m_storageIssues << tr("Не удалось прочитать %1: %2")
|
||||||
|
.arg(QDir::toNativeSeparators(path), file.errorString());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonParseError parseError;
|
||||||
|
const QJsonDocument document = QJsonDocument::fromJson(file.readAll(), &parseError);
|
||||||
|
if (parseError.error != QJsonParseError::NoError) {
|
||||||
|
file.close();
|
||||||
|
moveAsideBroken(path, parseError.errorString());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (!document.isArray()) {
|
||||||
|
file.close();
|
||||||
|
moveAsideBroken(path, tr("ожидался список записей"));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return document.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject LauncherBackend::readObjectFile(const QString &path)
|
||||||
|
{
|
||||||
|
QFile file(path);
|
||||||
|
if (!file.exists())
|
||||||
|
return {};
|
||||||
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
|
m_storageIssues << tr("Не удалось прочитать %1: %2")
|
||||||
|
.arg(QDir::toNativeSeparators(path), file.errorString());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonParseError parseError;
|
||||||
|
const QJsonDocument document = QJsonDocument::fromJson(file.readAll(), &parseError);
|
||||||
|
if (parseError.error != QJsonParseError::NoError) {
|
||||||
|
file.close();
|
||||||
|
moveAsideBroken(path, parseError.errorString());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (!document.isObject()) {
|
||||||
|
file.close();
|
||||||
|
moveAsideBroken(path, tr("ожидался набор настроек"));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return document.object();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Перед каждой записью убеждаемся, что папка лаунчера на месте: её могли удалить
|
||||||
|
// уже после запуска, и тогда данные молча пропадали бы.
|
||||||
|
bool LauncherBackend::writeJsonFile(const QString &path, const QJsonDocument &document)
|
||||||
|
{
|
||||||
|
QString storageError;
|
||||||
|
if (!LauncherPaths::ensureRootExists(&storageError)) {
|
||||||
|
emit launchError(tr("Не удалось создать папку лаунчера: %1").arg(storageError));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray payload = document.toJson();
|
||||||
|
QFile file(path);
|
||||||
|
if (!file.open(QIODevice::WriteOnly) || file.write(payload) != payload.size()) {
|
||||||
|
emit launchError(tr("Не удалось сохранить %1: %2")
|
||||||
|
.arg(QDir::toNativeSeparators(path), file.errorString()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void LauncherBackend::loadData()
|
void LauncherBackend::loadData()
|
||||||
{
|
{
|
||||||
auto loadFile = [](const QString &path, auto handler) {
|
auto loadFile = [this](const QString &path, auto handler) {
|
||||||
QFile f(path);
|
for (const auto &val : readArrayFile(path))
|
||||||
if (!f.open(QIODevice::ReadOnly))
|
|
||||||
return;
|
|
||||||
for (const auto &val : QJsonDocument::fromJson(f.readAll()).array())
|
|
||||||
handler(val.toObject());
|
handler(val.toObject());
|
||||||
};
|
};
|
||||||
|
|
||||||
loadFile(m_dataDir + "/profiles.json", [this](const QJsonObject &o) {
|
loadFile(LauncherPaths::profilesFile(), [this](const QJsonObject &o) {
|
||||||
Profile profile;
|
Profile profile;
|
||||||
profile.id = o["id"].toInt();
|
profile.id = o["id"].toInt();
|
||||||
profile.name = o["name"].toString();
|
profile.name = o["name"].toString();
|
||||||
@@ -543,17 +659,41 @@ void LauncherBackend::loadData()
|
|||||||
std::sort(m_profiles.begin(), m_profiles.end(),
|
std::sort(m_profiles.begin(), m_profiles.end(),
|
||||||
[](const Profile &a, const Profile &b) { return a.id < b.id; });
|
[](const Profile &a, const Profile &b) { return a.id < b.id; });
|
||||||
|
|
||||||
loadFile(m_dataDir + "/versions.json", [this](const QJsonObject &o) {
|
int skippedVersions = 0;
|
||||||
|
loadFile(LauncherPaths::versionsFile(), [this, &skippedVersions](const QJsonObject &o) {
|
||||||
const int id = o["id"].toInt();
|
const int id = o["id"].toInt();
|
||||||
m_versions.append({id, o["name"].toString(), o["serverUrl"].toString(),
|
const QString name = o["name"].toString();
|
||||||
o.value("versionId").toString()});
|
const QString versionId = o.value("versionId").toString();
|
||||||
|
// Без имени и без ID запись нерабочая: versionIdOf() вернёт пустую строку.
|
||||||
|
if (name.trimmed().isEmpty() && versionId.trimmed().isEmpty()) {
|
||||||
|
++skippedVersions;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_versions.append({id, name, o["serverUrl"].toString(), versionId});
|
||||||
if (id >= m_nextVersionId)
|
if (id >= m_nextVersionId)
|
||||||
m_nextVersionId = id + 1;
|
m_nextVersionId = id + 1;
|
||||||
});
|
});
|
||||||
|
if (skippedVersions > 0) {
|
||||||
|
m_storageIssues << tr("В %1 пропущены записи без имени и ID версии: %2")
|
||||||
|
.arg(QDir::toNativeSeparators(LauncherPaths::versionsFile()))
|
||||||
|
.arg(skippedVersions);
|
||||||
|
}
|
||||||
std::sort(m_versions.begin(), m_versions.end(),
|
std::sort(m_versions.begin(), m_versions.end(),
|
||||||
[](const Version &a, const Version &b) { return a.id < b.id; });
|
[](const Version &a, const Version &b) { return a.id < b.id; });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Файлы могли не появиться при первом запуске, быть удалены или отложены в .bak —
|
||||||
|
// создаём заново, чтобы папка лаунчера всегда была укомплектована.
|
||||||
|
void LauncherBackend::ensureDataFiles()
|
||||||
|
{
|
||||||
|
if (!QFile::exists(LauncherPaths::settingsFile()))
|
||||||
|
saveSettings();
|
||||||
|
if (!QFile::exists(LauncherPaths::profilesFile()))
|
||||||
|
saveProfiles();
|
||||||
|
if (!QFile::exists(LauncherPaths::versionsFile()))
|
||||||
|
saveVersions();
|
||||||
|
}
|
||||||
|
|
||||||
void LauncherBackend::saveProfiles()
|
void LauncherBackend::saveProfiles()
|
||||||
{
|
{
|
||||||
QJsonArray arr;
|
QJsonArray arr;
|
||||||
@@ -569,9 +709,7 @@ void LauncherBackend::saveProfiles()
|
|||||||
{"resolvedName", p.resolvedName},
|
{"resolvedName", p.resolvedName},
|
||||||
{"userType", p.userType}});
|
{"userType", p.userType}});
|
||||||
|
|
||||||
QFile f(m_dataDir + "/profiles.json");
|
writeJsonFile(LauncherPaths::profilesFile(), QJsonDocument(arr));
|
||||||
if (f.open(QIODevice::WriteOnly))
|
|
||||||
f.write(QJsonDocument(arr).toJson());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LauncherBackend::saveVersions()
|
void LauncherBackend::saveVersions()
|
||||||
@@ -583,9 +721,7 @@ void LauncherBackend::saveVersions()
|
|||||||
{"serverUrl", v.serverUrl},
|
{"serverUrl", v.serverUrl},
|
||||||
{"versionId", v.versionId}});
|
{"versionId", v.versionId}});
|
||||||
|
|
||||||
QFile f(m_dataDir + "/versions.json");
|
writeJsonFile(LauncherPaths::versionsFile(), QJsonDocument(arr));
|
||||||
if (f.open(QIODevice::WriteOnly))
|
|
||||||
f.write(QJsonDocument(arr).toJson());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LauncherBackend::loadSettings()
|
void LauncherBackend::loadSettings()
|
||||||
@@ -601,18 +737,13 @@ void LauncherBackend::loadSettings()
|
|||||||
{QStringLiteral("launcherName"), QStringLiteral("KishkaLauncher")},
|
{QStringLiteral("launcherName"), QStringLiteral("KishkaLauncher")},
|
||||||
{QStringLiteral("launcherVersion"), QStringLiteral("1.0")}};
|
{QStringLiteral("launcherVersion"), QStringLiteral("1.0")}};
|
||||||
|
|
||||||
QFile f(m_dataDir + "/settings.json");
|
const QJsonObject stored = readObjectFile(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)
|
for (auto it = stored.constBegin(); it != stored.constEnd(); ++it)
|
||||||
m_settings.insert(it.key(), it.value().toVariant());
|
m_settings.insert(it.key(), it.value().toVariant());
|
||||||
}
|
}
|
||||||
|
|
||||||
void LauncherBackend::saveSettings()
|
void LauncherBackend::saveSettings()
|
||||||
{
|
{
|
||||||
QFile f(m_dataDir + "/settings.json");
|
writeJsonFile(LauncherPaths::settingsFile(),
|
||||||
if (f.open(QIODevice::WriteOnly))
|
QJsonDocument(QJsonObject::fromVariantMap(m_settings)));
|
||||||
f.write(QJsonDocument(QJsonObject::fromVariantMap(m_settings)).toJson());
|
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-2
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
#include "minecraftversion.h"
|
#include "minecraftversion.h"
|
||||||
|
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QQmlEngine>
|
#include <QQmlEngine>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
@@ -101,11 +104,20 @@ private:
|
|||||||
|
|
||||||
void loadData();
|
void loadData();
|
||||||
void loadSettings();
|
void loadSettings();
|
||||||
|
void ensureDataFiles();
|
||||||
void saveProfiles();
|
void saveProfiles();
|
||||||
void saveVersions();
|
void saveVersions();
|
||||||
void saveSettings();
|
void saveSettings();
|
||||||
|
|
||||||
QString defaultMinecraftDir() const;
|
// Чтение файлов лаунчера. Отсутствие файла — норма (первый запуск), а вот
|
||||||
|
// повреждённое содержимое отводится в .bak, чтобы файл создался заново.
|
||||||
|
QJsonArray readArrayFile(const QString &path);
|
||||||
|
QJsonObject readObjectFile(const QString &path);
|
||||||
|
bool moveAsideBroken(const QString &path, const QString &reason);
|
||||||
|
|
||||||
|
// Проверяет папку лаунчера и пишет документ. Об ошибке сообщает через launchError.
|
||||||
|
bool writeJsonFile(const QString &path, const QJsonDocument &document);
|
||||||
|
|
||||||
QString gameDir() const;
|
QString gameDir() const;
|
||||||
QString versionIdOf(const Version &version) const;
|
QString versionIdOf(const Version &version) const;
|
||||||
|
|
||||||
@@ -117,7 +129,9 @@ private:
|
|||||||
QList<Profile> m_profiles;
|
QList<Profile> m_profiles;
|
||||||
QList<Version> m_versions;
|
QList<Version> m_versions;
|
||||||
QVariantMap m_settings;
|
QVariantMap m_settings;
|
||||||
QString m_dataDir;
|
// Проблемы хранилища, замеченные на старте: показываются одним сообщением,
|
||||||
|
// когда интерфейс уже подключился к сигналам.
|
||||||
|
QStringList m_storageIssues;
|
||||||
int m_nextProfileId = 1;
|
int m_nextProfileId = 1;
|
||||||
int m_nextVersionId = 1;
|
int m_nextVersionId = 1;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
#include "launcherpaths.h"
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFileInfo>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
const QString kFolderName = QStringLiteral("galeonLauncher");
|
||||||
|
|
||||||
|
QString translate(const char *text)
|
||||||
|
{
|
||||||
|
return QCoreApplication::translate("LauncherPaths", text);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
QString LauncherPaths::containerDir()
|
||||||
|
{
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
return QDir::homePath() + QStringLiteral("/AppData/Roaming");
|
||||||
|
#elif defined(Q_OS_MACOS)
|
||||||
|
return QDir::homePath() + QStringLiteral("/Library/Application Support");
|
||||||
|
#else // Linux и прочие Unix-системы
|
||||||
|
return QDir::homePath();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
QString LauncherPaths::rootDir()
|
||||||
|
{
|
||||||
|
return containerDir() + u'/' + kFolderName;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString LauncherPaths::defaultMinecraftDir()
|
||||||
|
{
|
||||||
|
#if defined(Q_OS_MACOS)
|
||||||
|
// На macOS папка игры исторически без точки.
|
||||||
|
return containerDir() + QStringLiteral("/minecraft");
|
||||||
|
#else
|
||||||
|
return containerDir() + QStringLiteral("/.minecraft");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
QString LauncherPaths::settingsFile()
|
||||||
|
{
|
||||||
|
return rootDir() + QStringLiteral("/settings.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString LauncherPaths::profilesFile()
|
||||||
|
{
|
||||||
|
return rootDir() + QStringLiteral("/profiles.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString LauncherPaths::versionsFile()
|
||||||
|
{
|
||||||
|
return rootDir() + QStringLiteral("/versions.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString LauncherPaths::runtimeDir()
|
||||||
|
{
|
||||||
|
return rootDir() + QStringLiteral("/runtime");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LauncherPaths::ensureRootExists(QString *error)
|
||||||
|
{
|
||||||
|
const QStringList required{rootDir(), runtimeDir()};
|
||||||
|
for (const QString &path : required) {
|
||||||
|
if (!QDir().mkpath(path)) {
|
||||||
|
if (error)
|
||||||
|
*error = translate("не удалось создать %1").arg(QDir::toNativeSeparators(path));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mkpath отвечает true и для существующего каталога, в который нельзя писать,
|
||||||
|
// и для подсунутого вместо папки файла — тогда все сохранения провалились бы молча.
|
||||||
|
const QFileInfo info(path);
|
||||||
|
if (!info.isDir() || !info.isWritable()) {
|
||||||
|
if (error)
|
||||||
|
*error = translate("нет доступа на запись в %1").arg(QDir::toNativeSeparators(path));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
// Пути к данным лаунчера. Собственная папка galeonLauncher лежит рядом со
|
||||||
|
// стандартной .minecraft, а не в системном каталоге приложения.
|
||||||
|
namespace LauncherPaths {
|
||||||
|
|
||||||
|
// Родительская папка, в которой лежит .minecraft (и наша galeonLauncher).
|
||||||
|
QString containerDir();
|
||||||
|
|
||||||
|
// <containerDir>/galeonLauncher — корень данных лаунчера.
|
||||||
|
QString rootDir();
|
||||||
|
|
||||||
|
// Стандартная папка игры.
|
||||||
|
QString defaultMinecraftDir();
|
||||||
|
|
||||||
|
QString settingsFile();
|
||||||
|
QString profilesFile();
|
||||||
|
QString versionsFile();
|
||||||
|
|
||||||
|
// Сюда качается authlib-injector.
|
||||||
|
QString runtimeDir();
|
||||||
|
|
||||||
|
// Создаёт папку лаунчера, если её нет. Вызывается при каждом запуске и перед
|
||||||
|
// каждой записью. false и текст в error, если папки нет или в неё не пишется.
|
||||||
|
bool ensureRootExists(QString *error = nullptr);
|
||||||
|
|
||||||
|
} // namespace LauncherPaths
|
||||||
Reference in New Issue
Block a user