This commit is contained in:
2026-09-29 09:40:37 +03:00
parent 441021b9a6
commit 6567e731d1
34 changed files with 57 additions and 709 deletions
+2 -33
View File
@@ -21,8 +21,6 @@
namespace {
// Списки лоадеров меняются реже манифеста Mojang, но шесть часов держим те же:
// одна цифра на весь лаунчер понятнее двух разных.
constexpr qint64 kCacheTtlSeconds = 6 * 60 * 60;
constexpr int kTransferTimeoutMs = 20000;
@@ -34,7 +32,6 @@ const QString kForgePromotions =
QStringLiteral("https://files.minecraftforge.net/maven/net/minecraftforge/forge/promotions_slim.json");
const QString kNeoForgeMaven = QStringLiteral("https://maven.neoforged.net/releases/net/neoforged/neoforge/");
// Под 1.20.1 NeoForge выходил ещё под старым именем артефакта.
const QString kNeoForgeLegacyMaven =
QStringLiteral("https://maven.neoforged.net/releases/net/neoforged/forge/");
@@ -46,8 +43,6 @@ bool looksUnstable(const QString &version)
|| lower.contains(u'+');
}
// Сравнение версий по числовым компонентам: «47.4.23» новее «47.4.2», хотя как
// строка стоит раньше. Нечисловые хвосты (alpha, +snapshot) сравниваются как есть.
bool isNewer(const QString &a, const QString &b)
{
const QStringList pa = a.split(QRegularExpression(QStringLiteral("[.\\-+]")), Qt::SkipEmptyParts);
@@ -63,7 +58,6 @@ bool isNewer(const QString &a, const QString &b)
return na > nb;
continue;
}
// Число всегда «новее» суффикса: 26.1.0.0 идёт впереди 26.1.0.0-alpha.1.
if (okA != okB)
return okA;
if (sa != sb)
@@ -72,9 +66,6 @@ bool isNewer(const QString &a, const QString &b)
return false;
}
// «21.1.248» -> «1.21.1», «21.0.167» -> «1.21», «26.1.2.97» -> «26.1.2»,
// «26.2.0.68» -> «26.2». До 1.20.2 Minecraft нумеровался как 1.x.y, с 26.1 —
// как x.y, поэтому у NeoForge две схемы: три компонента и четыре.
QString neoForgeGameVersion(const QString &neoVersion)
{
const QString head = neoVersion.section(u'-', 0, 0).section(u'+', 0, 0);
@@ -111,7 +102,7 @@ LoaderVersionEntry entryFromJson(const QJsonObject &o)
return entry;
}
} // namespace
}
ModLoaderVersionService::ModLoaderVersionService(QObject *parent)
: QObject(parent)
@@ -138,8 +129,6 @@ const ModLoaderVersionService::Bucket &ModLoaderVersionService::bucket(ModLoader
QString ModLoaderVersionService::requestKey(ModLoader loader, const QString &gameVersion)
{
// У Forge и NeoForge один запрос покрывает все версии игры — они делят
// очередь колбэков, чтобы не качать maven-metadata по разу на версию.
return isPerGameVersion(loader) ? loaderKey(loader) + u'/' + gameVersion : loaderKey(loader);
}
@@ -194,8 +183,6 @@ void ModLoaderVersionService::ensureLoaded(ModLoader loader,
}
if (!forceRefresh && isFresh(loader, gameVersion)) {
// Колбэк, который то синхронный, то нет, ломает вызывающий код —
// на попадании в кэш отвечаем через событийный цикл.
if (callback)
QTimer::singleShot(0, this, [callback = std::move(callback)] { callback(true, {}); });
return;
@@ -236,8 +223,6 @@ void ModLoaderVersionService::startRefresh(ModLoader loader, const QString &game
}
const QByteArray payload = reply->readAll();
// Пустой список — законный ответ: этот лоадер не поддерживает такую
// версию игры. Отличаем его от испорченного ответа по самому JSON.
const QJsonDocument document = QJsonDocument::fromJson(payload);
if (!document.isArray()) {
finishRefresh(loader, gameVersion, false,
@@ -255,14 +240,11 @@ void ModLoaderVersionService::startRefresh(ModLoader loader, const QString &game
return;
}
// Forge и NeoForge: основной maven-metadata плюс вспомогательный запрос
// (рекомендованные сборки у Forge, старая схема 1.20.1 у NeoForge).
// Считаем оба и сводим результат, когда придёт последний.
struct Pending
{
int outstanding = 2;
QHash<QString, QList<LoaderVersionEntry>> merged;
QHash<QString, QString> recommended; // версия игры -> версия лоадера
QHash<QString, QString> recommended;
bool primaryOk = false;
QString warning;
};
@@ -327,7 +309,6 @@ void ModLoaderVersionService::startRefresh(ModLoader loader, const QString &game
it.value().toString());
}
}
// Метка «рекомендовано» необязательна: без неё список всё равно рабочий.
settle();
});
} else {
@@ -354,12 +335,9 @@ void ModLoaderVersionService::finishRefresh(ModLoader loader,
const QString key = requestKey(loader, gameVersion);
m_inFlight.remove(key);
emit refreshingChanged();
// Колбэк может снова позвать ensureLoaded — список забираем себе заранее.
const QList<Callback> pending = m_pending.take(key);
const bool haveData = !bucket(loader).byGame.isEmpty();
for (const Callback &callback : pending) {
// Сеть недоступна, но старые данные есть — работаем на них.
if (!ok && haveData)
callback(true, warning);
else
@@ -389,7 +367,6 @@ QList<LoaderVersionEntry> ModLoaderVersionService::parseFabricLike(const QByteAr
list << entry;
}
// Fabric и Quilt отдают новые сверху, но зависеть от этого не стоит.
std::stable_sort(list.begin(), list.end(),
[](const LoaderVersionEntry &a, const LoaderVersionEntry &b) {
return isNewer(a.loaderVersion, b.loaderVersion);
@@ -400,9 +377,6 @@ QList<LoaderVersionEntry> ModLoaderVersionService::parseFabricLike(const QByteAr
QHash<QString, QList<LoaderVersionEntry>> ModLoaderVersionService::parseMavenMetadata(
const QByteArray &xml, ModLoader loader, bool legacyNeoForge)
{
// Версия игры зашита в саму строку версии лоадера, поэтому раскладка по
// версиям игры — это и есть проверка совместимости: несовместимой пары в
// получившемся отображении просто не существует.
const bool splitOnDash = loader == ModLoader::Forge || legacyNeoForge;
const QString base = loader == ModLoader::Forge ? kForgeMaven
: legacyNeoForge ? kNeoForgeLegacyMaven
@@ -445,8 +419,6 @@ QHash<QString, QList<LoaderVersionEntry>> ModLoaderVersionService::parseMavenMet
if (reader.hasError())
return {};
// maven-metadata приходит неотсортированным: 1.20.1-47.4.23 стоит между
// версиями 26.x. Без сортировки «самой новой» оказалась бы случайная сборка.
for (auto it = byGame.begin(); it != byGame.end(); ++it) {
std::stable_sort(it.value().begin(), it.value().end(),
[](const LoaderVersionEntry &a, const LoaderVersionEntry &b) {
@@ -488,7 +460,6 @@ bool ModLoaderVersionService::readCache(ModLoader loader)
void ModLoaderVersionService::writeCache(ModLoader loader)
{
// Папку кэша могли удалить уже после запуска лаунчера.
if (!LauncherPaths::ensureRootExists())
return;
@@ -507,8 +478,6 @@ void ModLoaderVersionService::writeCache(ModLoader loader)
const QJsonObject root{{QStringLiteral("fetchedAt"), b.fetchedAt.toString(Qt::ISODate)},
{QStringLiteral("games"), games}};
// QSaveFile: оборванная запись не оставит обрезанный кэш, который потом
// пришлось бы отличать от настоящего.
QSaveFile file(LauncherPaths::loaderCacheFile(loaderKey(loader)));
if (!file.open(QIODevice::WriteOnly))
return;