add launcher folder and some checks to it
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
|
||||||
|
|||||||
+49
-29
@@ -13,7 +13,8 @@
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
const QString kAuthServer = QStringLiteral("https://authserver.ely.by");
|
const QString kAuthServer = QStringLiteral("https://authserver.ely.by");
|
||||||
const QString kInjectorManifest = QStringLiteral("https://authlib-injector.yushi.moe/artifact/latest.json");
|
const QString kInjectorManifest = QStringLiteral(
|
||||||
|
"https://authlib-injector.yushi.moe/artifact/latest.json");
|
||||||
const QString kInjectorFileName = QStringLiteral("authlib-injector.jar");
|
const QString kInjectorFileName = QStringLiteral("authlib-injector.jar");
|
||||||
|
|
||||||
// Ely.by отдаёт UUID без дефисов, вручную введённый мог прийти и с ними.
|
// Ely.by отдаёт UUID без дефисов, вручную введённый мог прийти и с ними.
|
||||||
@@ -38,8 +39,7 @@ QString describeElyError(const QJsonObject &response, int status)
|
|||||||
AuthService::AuthService(QObject *parent)
|
AuthService::AuthService(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, m_network(new QNetworkAccessManager(this))
|
, m_network(new QNetworkAccessManager(this))
|
||||||
{
|
{}
|
||||||
}
|
|
||||||
|
|
||||||
AuthResult AuthService::offline(const QString &nickname)
|
AuthResult AuthService::offline(const QString &nickname)
|
||||||
{
|
{
|
||||||
@@ -49,8 +49,9 @@ AuthResult AuthService::offline(const QString &nickname)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray digest = QCryptographicHash::hash(
|
QByteArray digest
|
||||||
QStringLiteral("OfflinePlayer:%1").arg(nickname).toUtf8(), QCryptographicHash::Md5);
|
= QCryptographicHash::hash(QStringLiteral("OfflinePlayer:%1").arg(nickname).toUtf8(),
|
||||||
|
QCryptographicHash::Md5);
|
||||||
digest[6] = static_cast<char>((digest[6] & 0x0f) | 0x30); // версия UUID = 3
|
digest[6] = static_cast<char>((digest[6] & 0x0f) | 0x30); // версия UUID = 3
|
||||||
digest[8] = static_cast<char>((digest[8] & 0x3f) | 0x80); // вариант RFC 4122
|
digest[8] = static_cast<char>((digest[8] & 0x3f) | 0x80); // вариант RFC 4122
|
||||||
|
|
||||||
@@ -68,8 +69,7 @@ QString AuthService::generateClientToken()
|
|||||||
return QUuid::createUuid().toString(QUuid::WithoutBraces);
|
return QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AuthService::postJson(
|
void AuthService::postJson(const QString &endpoint,
|
||||||
const QString &endpoint,
|
|
||||||
const QJsonObject &body,
|
const QJsonObject &body,
|
||||||
std::function<void(int, const QJsonObject &, const QString &)> handler)
|
std::function<void(int, const QJsonObject &, const QString &)> handler)
|
||||||
{
|
{
|
||||||
@@ -78,7 +78,8 @@ void AuthService::postJson(
|
|||||||
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute,
|
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute,
|
||||||
QNetworkRequest::NoLessSafeRedirectPolicy);
|
QNetworkRequest::NoLessSafeRedirectPolicy);
|
||||||
|
|
||||||
QNetworkReply *reply = m_network->post(request, QJsonDocument(body).toJson(QJsonDocument::Compact));
|
QNetworkReply *reply = m_network->post(request,
|
||||||
|
QJsonDocument(body).toJson(QJsonDocument::Compact));
|
||||||
connect(reply, &QNetworkReply::finished, this, [reply, handler = std::move(handler)]() {
|
connect(reply, &QNetworkReply::finished, this, [reply, handler = std::move(handler)]() {
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
|
|
||||||
@@ -118,8 +119,9 @@ void AuthService::loginElyBy(const QString &login,
|
|||||||
emit progress(tr("Проверка сохранённого токена Ely.by…"));
|
emit progress(tr("Проверка сохранённого токена Ely.by…"));
|
||||||
postJson(QStringLiteral("/auth/validate"),
|
postJson(QStringLiteral("/auth/validate"),
|
||||||
{{QStringLiteral("accessToken"), accessToken}},
|
{{QStringLiteral("accessToken"), accessToken}},
|
||||||
[this, login, password, token, accessToken, callback](
|
[this, login, password, token, accessToken, callback](int status,
|
||||||
int status, const QJsonObject &, const QString &transportError) {
|
const QJsonObject &,
|
||||||
|
const QString &transportError) {
|
||||||
if (!transportError.isEmpty()) {
|
if (!transportError.isEmpty()) {
|
||||||
AuthResult result;
|
AuthResult result;
|
||||||
result.error = tr("Нет связи с authserver.ely.by: %1").arg(transportError);
|
result.error = tr("Нет связи с authserver.ely.by: %1").arg(transportError);
|
||||||
@@ -143,17 +145,21 @@ void AuthService::loginElyBy(const QString &login,
|
|||||||
{{QStringLiteral("accessToken"), accessToken},
|
{{QStringLiteral("accessToken"), accessToken},
|
||||||
{QStringLiteral("clientToken"), token},
|
{QStringLiteral("clientToken"), token},
|
||||||
{QStringLiteral("requestUser"), true}},
|
{QStringLiteral("requestUser"), true}},
|
||||||
[this, login, password, token, callback](
|
[this, login, password, token, callback](int refreshStatus,
|
||||||
int refreshStatus, const QJsonObject &response, const QString &error) {
|
const QJsonObject &response,
|
||||||
|
const QString &error) {
|
||||||
if (error.isEmpty() && refreshStatus == 200) {
|
if (error.isEmpty() && refreshStatus == 200) {
|
||||||
AuthResult result;
|
AuthResult result;
|
||||||
result.ok = true;
|
result.ok = true;
|
||||||
result.accessToken = response.value(QStringLiteral("accessToken")).toString();
|
result.accessToken = response.value(QStringLiteral("accessToken"))
|
||||||
|
.toString();
|
||||||
result.clientToken = token;
|
result.clientToken = token;
|
||||||
const QJsonObject profile =
|
const QJsonObject profile
|
||||||
response.value(QStringLiteral("selectedProfile")).toObject();
|
= response.value(QStringLiteral("selectedProfile")).toObject();
|
||||||
result.playerName = profile.value(QStringLiteral("name")).toString();
|
result.playerName = profile.value(QStringLiteral("name"))
|
||||||
result.uuid = normalizeUuid(profile.value(QStringLiteral("id")).toString());
|
.toString();
|
||||||
|
result.uuid = normalizeUuid(
|
||||||
|
profile.value(QStringLiteral("id")).toString());
|
||||||
result.userType = QStringLiteral("ELYBY");
|
result.userType = QStringLiteral("ELYBY");
|
||||||
callback(result);
|
callback(result);
|
||||||
return;
|
return;
|
||||||
@@ -194,12 +200,14 @@ void AuthService::authenticate(const QString &login,
|
|||||||
{QStringLiteral("password"), password},
|
{QStringLiteral("password"), password},
|
||||||
{QStringLiteral("clientToken"), clientToken},
|
{QStringLiteral("clientToken"), clientToken},
|
||||||
{QStringLiteral("requestUser"), true}},
|
{QStringLiteral("requestUser"), true}},
|
||||||
[clientToken, callback](int status, const QJsonObject &response, const QString &transportError) {
|
[clientToken,
|
||||||
|
callback](int status, const QJsonObject &response, const QString &transportError) {
|
||||||
AuthResult result;
|
AuthResult result;
|
||||||
result.clientToken = clientToken;
|
result.clientToken = clientToken;
|
||||||
|
|
||||||
if (!transportError.isEmpty()) {
|
if (!transportError.isEmpty()) {
|
||||||
result.error = AuthService::tr("Нет связи с authserver.ely.by: %1").arg(transportError);
|
result.error = AuthService::tr("Нет связи с authserver.ely.by: %1")
|
||||||
|
.arg(transportError);
|
||||||
callback(result);
|
callback(result);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -210,27 +218,29 @@ void AuthService::authenticate(const QString &login,
|
|||||||
result.twoFactorRequired = message.contains(QStringLiteral("two factor"),
|
result.twoFactorRequired = message.contains(QStringLiteral("two factor"),
|
||||||
Qt::CaseInsensitive);
|
Qt::CaseInsensitive);
|
||||||
result.error = result.twoFactorRequired
|
result.error = result.twoFactorRequired
|
||||||
? AuthService::tr("Аккаунт защищён двухфакторной аутентификацией")
|
? AuthService::tr(
|
||||||
|
"Аккаунт защищён двухфакторной аутентификацией")
|
||||||
: message;
|
: message;
|
||||||
callback(result);
|
callback(result);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QJsonObject profile = response.value(QStringLiteral("selectedProfile")).toObject();
|
const QJsonObject profile = response.value(QStringLiteral("selectedProfile"))
|
||||||
|
.toObject();
|
||||||
result.playerName = profile.value(QStringLiteral("name")).toString();
|
result.playerName = profile.value(QStringLiteral("name")).toString();
|
||||||
result.uuid = normalizeUuid(profile.value(QStringLiteral("id")).toString());
|
result.uuid = normalizeUuid(profile.value(QStringLiteral("id")).toString());
|
||||||
result.accessToken = response.value(QStringLiteral("accessToken")).toString();
|
result.accessToken = response.value(QStringLiteral("accessToken")).toString();
|
||||||
result.userType = QStringLiteral("ELYBY");
|
result.userType = QStringLiteral("ELYBY");
|
||||||
result.ok = !result.accessToken.isEmpty() && !result.playerName.isEmpty();
|
result.ok = !result.accessToken.isEmpty() && !result.playerName.isEmpty();
|
||||||
if (!result.ok)
|
if (!result.ok)
|
||||||
result.error = AuthService::tr("Ely.by не вернул игровой профиль для этого аккаунта");
|
result.error = AuthService::tr(
|
||||||
|
"Ely.by не вернул игровой профиль для этого аккаунта");
|
||||||
callback(result);
|
callback(result);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void AuthService::ensureAuthlibInjector(
|
void AuthService::ensureAuthlibInjector(
|
||||||
const QString &targetDir,
|
const QString &targetDir, std::function<void(const QString &, const QString &)> callback)
|
||||||
std::function<void(const QString &, const QString &)> callback)
|
|
||||||
{
|
{
|
||||||
QDir().mkpath(targetDir);
|
QDir().mkpath(targetDir);
|
||||||
const QString jarPath = targetDir + u'/' + kInjectorFileName;
|
const QString jarPath = targetDir + u'/' + kInjectorFileName;
|
||||||
@@ -248,7 +258,8 @@ void AuthService::ensureAuthlibInjector(
|
|||||||
connect(manifestReply, &QNetworkReply::finished, this, [this, manifestReply, jarPath, callback]() {
|
connect(manifestReply, &QNetworkReply::finished, this, [this, manifestReply, jarPath, callback]() {
|
||||||
manifestReply->deleteLater();
|
manifestReply->deleteLater();
|
||||||
if (manifestReply->error() != QNetworkReply::NoError) {
|
if (manifestReply->error() != QNetworkReply::NoError) {
|
||||||
callback({}, tr("Не удалось получить сведения об authlib-injector: %1")
|
callback({},
|
||||||
|
tr("Не удалось получить сведения об authlib-injector: %1")
|
||||||
.arg(manifestReply->errorString()));
|
.arg(manifestReply->errorString()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -268,10 +279,15 @@ void AuthService::ensureAuthlibInjector(
|
|||||||
jarRequest.setAttribute(QNetworkRequest::RedirectPolicyAttribute,
|
jarRequest.setAttribute(QNetworkRequest::RedirectPolicyAttribute,
|
||||||
QNetworkRequest::NoLessSafeRedirectPolicy);
|
QNetworkRequest::NoLessSafeRedirectPolicy);
|
||||||
QNetworkReply *jarReply = m_network->get(jarRequest);
|
QNetworkReply *jarReply = m_network->get(jarRequest);
|
||||||
connect(jarReply, &QNetworkReply::finished, this, [jarReply, jarPath, expectedSha256, callback]() {
|
connect(jarReply,
|
||||||
|
&QNetworkReply::finished,
|
||||||
|
this,
|
||||||
|
[jarReply, jarPath, expectedSha256, callback]() {
|
||||||
jarReply->deleteLater();
|
jarReply->deleteLater();
|
||||||
if (jarReply->error() != QNetworkReply::NoError) {
|
if (jarReply->error() != QNetworkReply::NoError) {
|
||||||
callback({}, AuthService::tr("Не удалось скачать authlib-injector: %1").arg(jarReply->errorString()));
|
callback({},
|
||||||
|
AuthService::tr("Не удалось скачать authlib-injector: %1")
|
||||||
|
.arg(jarReply->errorString()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,14 +296,18 @@ void AuthService::ensureAuthlibInjector(
|
|||||||
const QString actual = QString::fromLatin1(
|
const QString actual = QString::fromLatin1(
|
||||||
QCryptographicHash::hash(payload, QCryptographicHash::Sha256).toHex());
|
QCryptographicHash::hash(payload, QCryptographicHash::Sha256).toHex());
|
||||||
if (actual != expectedSha256) {
|
if (actual != expectedSha256) {
|
||||||
callback({}, AuthService::tr("Контрольная сумма authlib-injector не совпала"));
|
callback({},
|
||||||
|
AuthService::tr(
|
||||||
|
"Контрольная сумма authlib-injector не совпала"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QFile file(jarPath);
|
QFile file(jarPath);
|
||||||
if (!file.open(QIODevice::WriteOnly) || file.write(payload) != payload.size()) {
|
if (!file.open(QIODevice::WriteOnly) || file.write(payload) != payload.size()) {
|
||||||
callback({}, AuthService::tr("Не удалось сохранить %1").arg(QDir::toNativeSeparators(jarPath)));
|
callback({},
|
||||||
|
AuthService::tr("Не удалось сохранить %1")
|
||||||
|
.arg(QDir::toNativeSeparators(jarPath)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
file.close();
|
file.close();
|
||||||
|
|||||||
+6
-3
@@ -56,16 +56,19 @@ public:
|
|||||||
|
|
||||||
// Скачивает authlib-injector в targetDir, если его там ещё нет.
|
// Скачивает authlib-injector в targetDir, если его там ещё нет.
|
||||||
// Колбэк получает путь к jar либо текст ошибки.
|
// Колбэк получает путь к jar либо текст ошибки.
|
||||||
void ensureAuthlibInjector(const QString &targetDir,
|
void ensureAuthlibInjector(
|
||||||
|
const QString &targetDir,
|
||||||
std::function<void(const QString &path, const QString &error)> callback);
|
std::function<void(const QString &path, const QString &error)> callback);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void progress(const QString &message);
|
void progress(const QString &message);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void postJson(const QString &endpoint,
|
void postJson(
|
||||||
|
const QString &endpoint,
|
||||||
const QJsonObject &body,
|
const QJsonObject &body,
|
||||||
std::function<void(int status, const QJsonObject &response, const QString &transportError)> handler);
|
std::function<void(int status, const QJsonObject &response, const QString &transportError)>
|
||||||
|
handler);
|
||||||
|
|
||||||
void authenticate(const QString &login,
|
void authenticate(const QString &login,
|
||||||
const QString &password,
|
const QString &password,
|
||||||
|
|||||||
+17
-14
@@ -52,8 +52,7 @@ bool isNativeLibraryFile(const QString &fileName)
|
|||||||
|
|
||||||
GameLauncher::GameLauncher(QObject *parent)
|
GameLauncher::GameLauncher(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
{
|
{}
|
||||||
}
|
|
||||||
|
|
||||||
bool GameLauncher::isRunning() const
|
bool GameLauncher::isRunning() const
|
||||||
{
|
{
|
||||||
@@ -114,11 +113,11 @@ QStringList GameLauncher::buildArguments(const LaunchOptions &options,
|
|||||||
variables[QStringLiteral("auth_player_name")] = options.playerName;
|
variables[QStringLiteral("auth_player_name")] = options.playerName;
|
||||||
variables[QStringLiteral("auth_uuid")] = options.uuid;
|
variables[QStringLiteral("auth_uuid")] = options.uuid;
|
||||||
variables[QStringLiteral("auth_access_token")] = options.accessToken;
|
variables[QStringLiteral("auth_access_token")] = options.accessToken;
|
||||||
variables[QStringLiteral("auth_session")] =
|
variables[QStringLiteral("auth_session")] = QStringLiteral("token:%1:%2")
|
||||||
QStringLiteral("token:%1:%2").arg(options.accessToken, options.uuid);
|
.arg(options.accessToken, options.uuid);
|
||||||
variables[QStringLiteral("auth_xuid")] = QStringLiteral("0");
|
variables[QStringLiteral("auth_xuid")] = QStringLiteral("0");
|
||||||
variables[QStringLiteral("clientid")] =
|
variables[QStringLiteral("clientid")] = options.clientToken.isEmpty() ? QStringLiteral("0")
|
||||||
options.clientToken.isEmpty() ? QStringLiteral("0") : options.clientToken;
|
: options.clientToken;
|
||||||
variables[QStringLiteral("user_type")] = options.userType;
|
variables[QStringLiteral("user_type")] = options.userType;
|
||||||
variables[QStringLiteral("user_properties")] = QStringLiteral("{}");
|
variables[QStringLiteral("user_properties")] = QStringLiteral("{}");
|
||||||
variables[QStringLiteral("version_name")] = version.id;
|
variables[QStringLiteral("version_name")] = version.id;
|
||||||
@@ -126,11 +125,11 @@ QStringList GameLauncher::buildArguments(const LaunchOptions &options,
|
|||||||
variables[QStringLiteral("game_directory")] = nativeToken(options.gameDir);
|
variables[QStringLiteral("game_directory")] = nativeToken(options.gameDir);
|
||||||
variables[QStringLiteral("assets_root")] = nativeToken(assetsRoot);
|
variables[QStringLiteral("assets_root")] = nativeToken(assetsRoot);
|
||||||
variables[QStringLiteral("assets_index_name")] = version.assetIndexId;
|
variables[QStringLiteral("assets_index_name")] = version.assetIndexId;
|
||||||
variables[QStringLiteral("game_assets")] =
|
variables[QStringLiteral("game_assets")] = nativeToken(
|
||||||
nativeToken(legacyAssets ? assetsRoot + QStringLiteral("/virtual/legacy") : assetsRoot);
|
legacyAssets ? assetsRoot + QStringLiteral("/virtual/legacy") : assetsRoot);
|
||||||
variables[QStringLiteral("natives_directory")] = nativeToken(nativesDir);
|
variables[QStringLiteral("natives_directory")] = nativeToken(nativesDir);
|
||||||
variables[QStringLiteral("library_directory")] =
|
variables[QStringLiteral("library_directory")] = nativeToken(options.gameDir
|
||||||
nativeToken(options.gameDir + QStringLiteral("/libraries"));
|
+ QStringLiteral("/libraries"));
|
||||||
variables[QStringLiteral("classpath")] = classpath.join(kClasspathSeparator);
|
variables[QStringLiteral("classpath")] = classpath.join(kClasspathSeparator);
|
||||||
variables[QStringLiteral("classpath_separator")] = QString(kClasspathSeparator);
|
variables[QStringLiteral("classpath_separator")] = QString(kClasspathSeparator);
|
||||||
variables[QStringLiteral("primary_jar")] = nativeToken(version.clientJarPath);
|
variables[QStringLiteral("primary_jar")] = nativeToken(version.clientJarPath);
|
||||||
@@ -174,8 +173,8 @@ QStringList GameLauncher::buildArguments(const LaunchOptions &options,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Разрешение окна: у старых версий фич-флага нет, аргументы всё равно понимаются.
|
// Разрешение окна: у старых версий фич-флага нет, аргументы всё равно понимаются.
|
||||||
if (options.windowWidth > 0 && options.windowHeight > 0
|
if (options.windowWidth > 0 && options.windowHeight > 0 && !version.hasCustomResolutionArgs
|
||||||
&& !version.hasCustomResolutionArgs && !arguments.contains(QStringLiteral("--width"))) {
|
&& !arguments.contains(QStringLiteral("--width"))) {
|
||||||
arguments << QStringLiteral("--width") << QString::number(options.windowWidth)
|
arguments << QStringLiteral("--width") << QString::number(options.windowWidth)
|
||||||
<< QStringLiteral("--height") << QString::number(options.windowHeight);
|
<< QStringLiteral("--height") << QString::number(options.windowHeight);
|
||||||
}
|
}
|
||||||
@@ -187,7 +186,8 @@ QStringList GameLauncher::buildArguments(const LaunchOptions &options,
|
|||||||
arguments << QStringLiteral("--quickPlayMultiplayer") << options.serverAddress;
|
arguments << QStringLiteral("--quickPlayMultiplayer") << options.serverAddress;
|
||||||
} else {
|
} else {
|
||||||
const int colon = options.serverAddress.lastIndexOf(u':');
|
const int colon = options.serverAddress.lastIndexOf(u':');
|
||||||
const QString host = colon > 0 ? options.serverAddress.left(colon) : options.serverAddress;
|
const QString host = colon > 0 ? options.serverAddress.left(colon)
|
||||||
|
: options.serverAddress;
|
||||||
const QString port = colon > 0 ? options.serverAddress.mid(colon + 1)
|
const QString port = colon > 0 ? options.serverAddress.mid(colon + 1)
|
||||||
: QStringLiteral("25565");
|
: QStringLiteral("25565");
|
||||||
arguments << QStringLiteral("--server") << host << QStringLiteral("--port") << port;
|
arguments << QStringLiteral("--server") << host << QStringLiteral("--port") << port;
|
||||||
@@ -289,7 +289,10 @@ bool GameLauncher::launch(const LaunchOptions &options,
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
emit progress(tr("Поиск Java %1…").arg(version.javaMajor));
|
emit progress(tr("Поиск Java %1…").arg(version.javaMajor));
|
||||||
const QString java = JavaLocator::select(options.gameDir, version.javaMajor, options.javaPath, error);
|
const QString java = JavaLocator::select(options.gameDir,
|
||||||
|
version.javaMajor,
|
||||||
|
options.javaPath,
|
||||||
|
error);
|
||||||
if (java.isEmpty())
|
if (java.isEmpty())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|||||||
+9
-5
@@ -4,9 +4,9 @@
|
|||||||
#include <QDirIterator>
|
#include <QDirIterator>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
|
#include <QObject>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include <QProcessEnvironment>
|
#include <QProcessEnvironment>
|
||||||
#include <QObject>
|
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -75,8 +75,10 @@ QStringList JavaLocator::findAll(const QString &gameDir)
|
|||||||
// 1. Рантайм, скачанный самим Minecraft: .minecraft/runtime/<component>/<os>/<component>/bin
|
// 1. Рантайм, скачанный самим Minecraft: .minecraft/runtime/<component>/<os>/<component>/bin
|
||||||
const QString runtime = gameDir + QStringLiteral("/runtime");
|
const QString runtime = gameDir + QStringLiteral("/runtime");
|
||||||
if (QDir(runtime).exists()) {
|
if (QDir(runtime).exists()) {
|
||||||
QDirIterator it(runtime, QStringList{QString::fromLatin1(kExecutable)},
|
QDirIterator it(runtime,
|
||||||
QDir::Files | QDir::Executable, QDirIterator::Subdirectories);
|
QStringList{QString::fromLatin1(kExecutable)},
|
||||||
|
QDir::Files | QDir::Executable,
|
||||||
|
QDirIterator::Subdirectories);
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
it.next();
|
it.next();
|
||||||
appendIfExecutable(result, it.fileInfo().absolutePath());
|
appendIfExecutable(result, it.fileInfo().absolutePath());
|
||||||
@@ -172,7 +174,8 @@ QString JavaLocator::select(const QString &gameDir,
|
|||||||
|
|
||||||
if (candidates.isEmpty()) {
|
if (candidates.isEmpty()) {
|
||||||
if (error)
|
if (error)
|
||||||
*error = QObject::tr("Java не найдена. Установите JDK %1 или укажите путь в настройках.")
|
*error = QObject::tr(
|
||||||
|
"Java не найдена. Установите JDK %1 или укажите путь в настройках.")
|
||||||
.arg(requiredMajor);
|
.arg(requiredMajor);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@@ -191,7 +194,8 @@ QString JavaLocator::select(const QString &gameDir,
|
|||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
*error = bestBelowMajor > 0
|
*error = bestBelowMajor > 0
|
||||||
? QObject::tr("Для этой версии нужна Java %1 или новее, найдена только Java %2 (%3).")
|
? QObject::tr(
|
||||||
|
"Для этой версии нужна Java %1 или новее, найдена только Java %2 (%3).")
|
||||||
.arg(requiredMajor)
|
.arg(requiredMajor)
|
||||||
.arg(bestBelowMajor)
|
.arg(bestBelowMajor)
|
||||||
.arg(QDir::toNativeSeparators(bestBelow))
|
.arg(QDir::toNativeSeparators(bestBelow))
|
||||||
|
|||||||
+1
-4
@@ -17,9 +17,6 @@ QString windowlessVariant(const QString &javaPath);
|
|||||||
|
|
||||||
// Выбирает java не ниже requiredMajor. preferred, если задан, проверяется первым.
|
// Выбирает java не ниже requiredMajor. preferred, если задан, проверяется первым.
|
||||||
// При неудаче возвращает пустую строку и заполняет error.
|
// При неудаче возвращает пустую строку и заполняет error.
|
||||||
QString select(const QString &gameDir,
|
QString select(const QString &gameDir, int requiredMajor, const QString &preferred, QString *error);
|
||||||
int requiredMajor,
|
|
||||||
const QString &preferred,
|
|
||||||
QString *error);
|
|
||||||
|
|
||||||
} // namespace JavaLocator
|
} // namespace JavaLocator
|
||||||
|
|||||||
+50
-32
@@ -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,16 @@ 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);
|
// Папка лаунчера создаётся при первом запуске и проверяется при каждом
|
||||||
|
// следующем — если её удалили, она появится снова.
|
||||||
|
LauncherPaths::ensureRootExists(&m_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 +55,13 @@ LauncherBackend::LauncherBackend(QObject *parent)
|
|||||||
emit gameRunningChanged();
|
emit gameRunningChanged();
|
||||||
emit gameFinished(exitCode, crashed);
|
emit gameFinished(exitCode, crashed);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Интерфейс подключается к сигналам уже после конструктора — сообщаем отложенно.
|
||||||
|
if (!m_storageError.isEmpty()) {
|
||||||
|
QTimer::singleShot(0, this, [this] {
|
||||||
|
emit launchError(tr("Не удалось создать папку лаунчера: %1").arg(m_storageError));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LauncherBackend::~LauncherBackend() = default;
|
LauncherBackend::~LauncherBackend() = default;
|
||||||
@@ -98,7 +109,9 @@ void LauncherBackend::addProfile(const QString &name,
|
|||||||
emit profilesChanged();
|
emit profilesChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LauncherBackend::addVersion(const QString &name, const QString &serverUrl, const QString &versionId)
|
void LauncherBackend::addVersion(const QString &name,
|
||||||
|
const QString &serverUrl,
|
||||||
|
const QString &versionId)
|
||||||
{
|
{
|
||||||
m_versions.append({m_nextVersionId++, name, serverUrl, versionId});
|
m_versions.append({m_nextVersionId++, name, serverUrl, versionId});
|
||||||
saveVersions();
|
saveVersions();
|
||||||
@@ -234,9 +247,9 @@ void LauncherBackend::launchGame(int profileIndex, int versionIndex)
|
|||||||
const QString id = versionIdOf(m_versions[versionIndex]);
|
const QString id = versionIdOf(m_versions[versionIndex]);
|
||||||
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(QDir::toNativeSeparators(
|
||||||
.arg(QDir::toNativeSeparators(directory + QStringLiteral("/versions")))
|
directory + QStringLiteral("/versions")))
|
||||||
: tr("Версия «%1» не установлена. Доступны: %2")
|
: tr("Версия «%1» не установлена. Доступны: %2")
|
||||||
.arg(id, installed.join(QStringLiteral(", "))));
|
.arg(id, installed.join(QStringLiteral(", "))));
|
||||||
return;
|
return;
|
||||||
@@ -291,7 +304,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 +459,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
|
||||||
@@ -522,7 +526,7 @@ void LauncherBackend::loadData()
|
|||||||
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();
|
||||||
@@ -540,18 +544,32 @@ void LauncherBackend::loadData()
|
|||||||
if (profile.id >= m_nextProfileId)
|
if (profile.id >= m_nextProfileId)
|
||||||
m_nextProfileId = profile.id + 1;
|
m_nextProfileId = profile.id + 1;
|
||||||
});
|
});
|
||||||
std::sort(m_profiles.begin(), m_profiles.end(),
|
std::sort(m_profiles.begin(), m_profiles.end(), [](const Profile &a, const Profile &b) {
|
||||||
[](const Profile &a, const Profile &b) { return a.id < b.id; });
|
return a.id < b.id;
|
||||||
|
});
|
||||||
|
|
||||||
loadFile(m_dataDir + "/versions.json", [this](const QJsonObject &o) {
|
loadFile(LauncherPaths::versionsFile(), [this](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(),
|
m_versions.append(
|
||||||
o.value("versionId").toString()});
|
{id, o["name"].toString(), o["serverUrl"].toString(), o.value("versionId").toString()});
|
||||||
if (id >= m_nextVersionId)
|
if (id >= m_nextVersionId)
|
||||||
m_nextVersionId = id + 1;
|
m_nextVersionId = id + 1;
|
||||||
});
|
});
|
||||||
std::sort(m_versions.begin(), m_versions.end(),
|
std::sort(m_versions.begin(), m_versions.end(), [](const Version &a, const Version &b) {
|
||||||
[](const Version &a, const Version &b) { return a.id < b.id; });
|
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()
|
void LauncherBackend::saveProfiles()
|
||||||
@@ -569,7 +587,7 @@ void LauncherBackend::saveProfiles()
|
|||||||
{"resolvedName", p.resolvedName},
|
{"resolvedName", p.resolvedName},
|
||||||
{"userType", p.userType}});
|
{"userType", p.userType}});
|
||||||
|
|
||||||
QFile f(m_dataDir + "/profiles.json");
|
QFile f(LauncherPaths::profilesFile());
|
||||||
if (f.open(QIODevice::WriteOnly))
|
if (f.open(QIODevice::WriteOnly))
|
||||||
f.write(QJsonDocument(arr).toJson());
|
f.write(QJsonDocument(arr).toJson());
|
||||||
}
|
}
|
||||||
@@ -583,7 +601,7 @@ void LauncherBackend::saveVersions()
|
|||||||
{"serverUrl", v.serverUrl},
|
{"serverUrl", v.serverUrl},
|
||||||
{"versionId", v.versionId}});
|
{"versionId", v.versionId}});
|
||||||
|
|
||||||
QFile f(m_dataDir + "/versions.json");
|
QFile f(LauncherPaths::versionsFile());
|
||||||
if (f.open(QIODevice::WriteOnly))
|
if (f.open(QIODevice::WriteOnly))
|
||||||
f.write(QJsonDocument(arr).toJson());
|
f.write(QJsonDocument(arr).toJson());
|
||||||
}
|
}
|
||||||
@@ -601,7 +619,7 @@ 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");
|
QFile f(LauncherPaths::settingsFile());
|
||||||
if (!f.open(QIODevice::ReadOnly))
|
if (!f.open(QIODevice::ReadOnly))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -612,7 +630,7 @@ void LauncherBackend::loadSettings()
|
|||||||
|
|
||||||
void LauncherBackend::saveSettings()
|
void LauncherBackend::saveSettings()
|
||||||
{
|
{
|
||||||
QFile f(m_dataDir + "/settings.json");
|
QFile f(LauncherPaths::settingsFile());
|
||||||
if (f.open(QIODevice::WriteOnly))
|
if (f.open(QIODevice::WriteOnly))
|
||||||
f.write(QJsonDocument(QJsonObject::fromVariantMap(m_settings)).toJson());
|
f.write(QJsonDocument(QJsonObject::fromVariantMap(m_settings)).toJson());
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -101,11 +101,11 @@ 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;
|
|
||||||
QString gameDir() const;
|
QString gameDir() const;
|
||||||
QString versionIdOf(const Version &version) const;
|
QString versionIdOf(const Version &version) const;
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ 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;
|
QString m_storageError;
|
||||||
int m_nextProfileId = 1;
|
int m_nextProfileId = 1;
|
||||||
int m_nextVersionId = 1;
|
int m_nextVersionId = 1;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
#include "launcherpaths.h"
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDir>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
const QString kFolderName = QStringLiteral("galeonLauncher");
|
||||||
|
|
||||||
|
} // 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))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
*error = QCoreApplication::translate("LauncherPaths",
|
||||||
|
"не удалось создать %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
|
||||||
+25
-15
@@ -47,8 +47,8 @@ QString mavenToPath(const QString &name)
|
|||||||
if (parts.size() > 3)
|
if (parts.size() > 3)
|
||||||
file += u'-' + parts[3];
|
file += u'-' + parts[3];
|
||||||
|
|
||||||
return QString(parts[0]).replace(u'.', u'/') + u'/' + parts[1] + u'/' + parts[2]
|
return QString(parts[0]).replace(u'.', u'/') + u'/' + parts[1] + u'/' + parts[2] + u'/' + file
|
||||||
+ u'/' + file + u'.' + extension;
|
+ u'.' + extension;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ключ для дедупликации: у наследника и родителя одна и та же библиотека часто
|
// Ключ для дедупликации: у наследника и родителя одна и та же библиотека часто
|
||||||
@@ -163,9 +163,12 @@ void parseLibraries(const QJsonObject &root,
|
|||||||
|
|
||||||
// Основной артефакт. В новом формате (1.19+) натив-библиотеки — это
|
// Основной артефакт. В новом формате (1.19+) натив-библиотеки — это
|
||||||
// отдельные записи с классификатором natives-*, у них тоже есть artifact.
|
// отдельные записи с классификатором natives-*, у них тоже есть artifact.
|
||||||
if (downloads.contains(QStringLiteral("artifact")) || !downloads.contains(QStringLiteral("classifiers"))) {
|
if (downloads.contains(QStringLiteral("artifact"))
|
||||||
MinecraftLibrary library = libraryFromDownload(
|
|| !downloads.contains(QStringLiteral("classifiers"))) {
|
||||||
name, downloads.value(QStringLiteral("artifact")).toObject(), relativePath);
|
MinecraftLibrary library
|
||||||
|
= libraryFromDownload(name,
|
||||||
|
downloads.value(QStringLiteral("artifact")).toObject(),
|
||||||
|
relativePath);
|
||||||
|
|
||||||
// У Fabric у части библиотек нет блока downloads вообще — только
|
// У Fabric у части библиотек нет блока downloads вообще — только
|
||||||
// name и адрес Maven-репозитория, путь собираем из координат.
|
// name и адрес Maven-репозитория, путь собираем из координат.
|
||||||
@@ -196,8 +199,7 @@ void parseLibraries(const QJsonObject &root,
|
|||||||
: QStringLiteral("64"));
|
: QStringLiteral("64"));
|
||||||
|
|
||||||
const QJsonObject classifiers = downloads.value(QStringLiteral("classifiers")).toObject();
|
const QJsonObject classifiers = downloads.value(QStringLiteral("classifiers")).toObject();
|
||||||
MinecraftLibrary library = libraryFromDownload(
|
MinecraftLibrary library = libraryFromDownload(name + u':' + classifier,
|
||||||
name + u':' + classifier,
|
|
||||||
classifiers.value(classifier).toObject(),
|
classifiers.value(classifier).toObject(),
|
||||||
mavenToPath(name + u':' + classifier));
|
mavenToPath(name + u':' + classifier));
|
||||||
if (library.path.isEmpty())
|
if (library.path.isEmpty())
|
||||||
@@ -326,12 +328,14 @@ MinecraftVersion VersionLoader::load(const QString &gameDir,
|
|||||||
while (!currentId.isEmpty()) {
|
while (!currentId.isEmpty()) {
|
||||||
if (chainIds.contains(currentId)) {
|
if (chainIds.contains(currentId)) {
|
||||||
if (error)
|
if (error)
|
||||||
*error = QObject::tr("Циклическая зависимость inheritsFrom в версии «%1»").arg(currentId);
|
*error = QObject::tr("Циклическая зависимость inheritsFrom в версии «%1»")
|
||||||
|
.arg(currentId);
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
if (chain.size() >= kMaxInheritanceDepth) {
|
if (chain.size() >= kMaxInheritanceDepth) {
|
||||||
if (error)
|
if (error)
|
||||||
*error = QObject::tr("Слишком длинная цепочка inheritsFrom у версии «%1»").arg(versionId);
|
*error = QObject::tr("Слишком длинная цепочка inheritsFrom у версии «%1»")
|
||||||
|
.arg(versionId);
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,7 +396,8 @@ MinecraftVersion VersionLoader::load(const QString &gameDir,
|
|||||||
.value(QStringLiteral("id"))
|
.value(QStringLiteral("id"))
|
||||||
.toString();
|
.toString();
|
||||||
if (!fileId.isEmpty())
|
if (!fileId.isEmpty())
|
||||||
version.loggingConfigPath = gameDir + QStringLiteral("/assets/log_configs/") + fileId;
|
version.loggingConfigPath = gameDir + QStringLiteral("/assets/log_configs/")
|
||||||
|
+ fileId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// minecraftArguments (до 1.13) заменяет список целиком, arguments.game — дополняет.
|
// minecraftArguments (до 1.13) заменяет список целиком, arguments.game — дополняет.
|
||||||
@@ -405,9 +410,12 @@ MinecraftVersion VersionLoader::load(const QString &gameDir,
|
|||||||
|
|
||||||
const QJsonObject arguments = object.value(QStringLiteral("arguments")).toObject();
|
const QJsonObject arguments = object.value(QStringLiteral("arguments")).toObject();
|
||||||
appendArguments(arguments.value(QStringLiteral("jvm")).toArray(), version.jvmArgs, features);
|
appendArguments(arguments.value(QStringLiteral("jvm")).toArray(), version.jvmArgs, features);
|
||||||
appendArguments(arguments.value(QStringLiteral("game")).toArray(), version.gameArgs, features);
|
appendArguments(arguments.value(QStringLiteral("game")).toArray(),
|
||||||
|
version.gameArgs,
|
||||||
|
features);
|
||||||
|
|
||||||
const QByteArray raw = QJsonDocument(arguments.value(QStringLiteral("game")).toArray()).toJson();
|
const QByteArray raw = QJsonDocument(arguments.value(QStringLiteral("game")).toArray())
|
||||||
|
.toJson();
|
||||||
if (raw.contains("quickPlayMultiplayer"))
|
if (raw.contains("quickPlayMultiplayer"))
|
||||||
version.supportsQuickPlay = true;
|
version.supportsQuickPlay = true;
|
||||||
if (raw.contains("resolution_width"))
|
if (raw.contains("resolution_width"))
|
||||||
@@ -437,9 +445,11 @@ MinecraftVersion VersionLoader::load(const QString &gameDir,
|
|||||||
pickNativesForCurrentArch(version.libraries);
|
pickNativesForCurrentArch(version.libraries);
|
||||||
|
|
||||||
// До 1.13 jvm-аргументов в json нет — собираем их сами.
|
// До 1.13 jvm-аргументов в json нет — собираем их сами.
|
||||||
const bool hasClasspathArg = std::any_of(
|
const bool hasClasspathArg = std::any_of(version.jvmArgs.cbegin(),
|
||||||
version.jvmArgs.cbegin(), version.jvmArgs.cend(), [](const QString &argument) {
|
version.jvmArgs.cend(),
|
||||||
return argument == QLatin1String("-cp") || argument == QLatin1String("-classpath");
|
[](const QString &argument) {
|
||||||
|
return argument == QLatin1String("-cp")
|
||||||
|
|| argument == QLatin1String("-classpath");
|
||||||
});
|
});
|
||||||
if (!hasClasspathArg) {
|
if (!hasClasspathArg) {
|
||||||
version.jvmArgs << QStringLiteral("-Djava.library.path=${natives_directory}")
|
version.jvmArgs << QStringLiteral("-Djava.library.path=${natives_directory}")
|
||||||
|
|||||||
Reference in New Issue
Block a user