new builds menu and modloaders implementation

This commit is contained in:
2026-08-27 09:21:51 +03:00
parent 5bc59f8018
commit 168884127b
23 changed files with 3979 additions and 1081 deletions
+59 -30
View File
@@ -64,6 +64,11 @@ bool isSafeRelativeSegment(const QString &value)
return !value.contains(u'/') && isSafeRelativePath(value);
}
QString versionJsonPath(const QString &gameDir, const QString &id)
{
return gameDir + QStringLiteral("/versions/") + id + u'/' + id + QStringLiteral(".json");
}
bool isTransient(QNetworkReply::NetworkError error, int httpStatus)
{
if (httpStatus == 429 || httpStatus >= 500)
@@ -234,20 +239,34 @@ void VersionInstaller::resolveManifestEntry()
return;
if (!ok) {
QFile local(versionJsonPath(m_gameDir, id));
if (local.open(QIODevice::ReadOnly)) {
adoptVersionJson(local.readAll(), /*alreadySaved=*/true);
return;
}
failInstall(warning.isEmpty() ? tr("Список версий Mojang недоступен") : warning);
return;
}
const auto entry = m_manifest->find(id);
if (!entry) {
// Обычно это вручную вписанный id сборки — сообщение не должно
// выглядеть как сетевая ошибка.
failInstall(tr("Версия «%1» отсутствует в манифесте Mojang — "
"установить её автоматически нельзя")
.arg(id));
if (entry) {
fetchVersionJson(entry->url, entry->sha1);
return;
}
fetchVersionJson(entry->url, entry->sha1);
// Версии нет в манифесте, но описание могло уже лечь на диск: так
// ставятся сборки модлоадеров, чьи id Mojang не знает.
QFile local(versionJsonPath(m_gameDir, id));
if (local.open(QIODevice::ReadOnly)) {
adoptVersionJson(local.readAll(), /*alreadySaved=*/true);
return;
}
// Иначе это вручную вписанный id сборки — сообщение не должно
// выглядеть как сетевая ошибка.
failInstall(tr("Версия «%1» отсутствует в манифесте Mojang — "
"установить её автоматически нельзя")
.arg(id));
});
}
@@ -283,12 +302,23 @@ void VersionInstaller::fetchVersionJson(const QUrl &url, const QString &expected
}
}
m_versionJson = QJsonDocument::fromJson(payload).object();
if (m_versionJson.isEmpty()) {
failInstall(tr("Описание версии «%1» не удалось разобрать").arg(id));
return;
}
adoptVersionJson(payload, /*alreadySaved=*/false);
});
}
// Общий хвост для описания версии, откуда бы оно ни пришло: из сети или уже
// лежащим на диске (сборки модлоадеров кладут его туда сами).
void VersionInstaller::adoptVersionJson(const QByteArray &payload, bool alreadySaved)
{
const QString id = m_versionId;
m_versionJson = QJsonDocument::fromJson(payload).object();
if (m_versionJson.isEmpty()) {
failInstall(tr("Описание версии «%1» не удалось разобрать").arg(id));
return;
}
if (!alreadySaved) {
const QString directory = m_gameDir + QStringLiteral("/versions/") + id;
if (!QDir().mkpath(directory)) {
failInstall(tr("Не удалось создать %1").arg(QDir::toNativeSeparators(directory)));
@@ -302,28 +332,27 @@ void VersionInstaller::fetchVersionJson(const QUrl &url, const QString &expected
failInstall(tr("Не удалось сохранить описание версии «%1»").arg(id));
return;
}
}
// Ванильные версии ничего не наследуют, но сборка могла бы — тогда
// сначала ставим родителя, а потом возвращаемся к этой версии.
const QString parentId = m_versionJson.value(QStringLiteral("inheritsFrom")).toString();
if (!parentId.isEmpty() && isSafeRelativeSegment(parentId)
&& !QFile::exists(m_gameDir + QStringLiteral("/versions/") + parentId + u'/' + parentId
+ QStringLiteral(".json"))) {
if (m_inheritDepth >= kMaxInheritDepth) {
failInstall(tr("Слишком длинная цепочка inheritsFrom у версии «%1»").arg(id));
return;
}
++m_inheritDepth;
m_pendingInstalls.prepend({m_gameDir, id});
m_pendingInstalls.prepend({m_gameDir, parentId});
abandonCurrent();
// Ванильные версии ничего не наследуют, но сборка могла бы — тогда
// сначала ставим родителя, а потом возвращаемся к этой версии.
const QString parentId = m_versionJson.value(QStringLiteral("inheritsFrom")).toString();
if (!parentId.isEmpty() && isSafeRelativeSegment(parentId)
&& !QFile::exists(versionJsonPath(m_gameDir, parentId))) {
if (m_inheritDepth >= kMaxInheritDepth) {
failInstall(tr("Слишком длинная цепочка inheritsFrom у версии «%1»").arg(id));
return;
}
++m_inheritDepth;
m_pendingInstalls.prepend({m_gameDir, id});
m_pendingInstalls.prepend({m_gameDir, parentId});
abandonCurrent();
return;
}
m_inheritDepth = 0;
if (prepareTasks())
pump();
});
m_inheritDepth = 0;
if (prepareTasks())
pump();
}
// ── Составление списка файлов ──────────────────────────────────────────────