forge on linux fix
This commit is contained in:
+72
-2
@@ -1,6 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
project(Minecraft_launcher VERSION 0.1 LANGUAGES CXX)
|
||||
# C нужен для эталонного zlib: см. блок про установщики Forge ниже.
|
||||
project(Minecraft_launcher VERSION 0.1 LANGUAGES CXX C)
|
||||
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
@@ -24,6 +25,8 @@ qt_add_qml_module(appMinecraft_launcher
|
||||
minecraftversion.cpp
|
||||
javalocator.h
|
||||
javalocator.cpp
|
||||
zlibreference.h
|
||||
zlibreference.cpp
|
||||
javaruntime.h
|
||||
javaruntime.cpp
|
||||
javaruntimeservice.h
|
||||
@@ -75,6 +78,73 @@ target_link_libraries(appMinecraft_launcher
|
||||
)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
# ── Эталонный zlib для установщиков Forge и NeoForge ────────────────────────
|
||||
#
|
||||
# Установщик Forge сверяет sha1 каждого jar, который сам же и собирает, с
|
||||
# эталоном из install_profile.json. Эталон посчитан на обычном zlib, а
|
||||
# дистрибутивы вроде CachyOS и Fedora подставляют вместо него zlib-ng: сжатие
|
||||
# корректное, но побайтово другое, поэтому установка падает на любой версии
|
||||
# игры («Processor failed, invalid outputs»). Сменить Java не выйдет — сборки
|
||||
# OpenJDK под Linux берут libz.so.1 из системы.
|
||||
#
|
||||
# Поэтому собираем рядом обычный zlib и подсовываем его через LD_PRELOAD
|
||||
# только процессу установщика. Сам лаунчер с ним не линкуется, системный
|
||||
# zlib-ng остаётся на месте.
|
||||
if(UNIX AND NOT APPLE)
|
||||
include(FetchContent)
|
||||
# SOURCE_SUBDIR указывает на несуществующий каталог: нужны только исходники.
|
||||
# add_subdirectory от zlib притащил бы свои install-правила и цели example
|
||||
# и minigzip.
|
||||
FetchContent_Declare(zlib_reference
|
||||
GIT_REPOSITORY https://github.com/madler/zlib.git
|
||||
GIT_TAG v1.3.1
|
||||
GIT_SHALLOW TRUE
|
||||
SOURCE_SUBDIR do-not-configure
|
||||
)
|
||||
FetchContent_MakeAvailable(zlib_reference)
|
||||
|
||||
set(LAUNCHER_ZLIB_DIR "${CMAKE_INSTALL_LIBDIR}/galeonLauncher")
|
||||
|
||||
add_library(launcher_zlib_reference SHARED
|
||||
${zlib_reference_SOURCE_DIR}/adler32.c
|
||||
${zlib_reference_SOURCE_DIR}/compress.c
|
||||
${zlib_reference_SOURCE_DIR}/crc32.c
|
||||
${zlib_reference_SOURCE_DIR}/deflate.c
|
||||
${zlib_reference_SOURCE_DIR}/gzclose.c
|
||||
${zlib_reference_SOURCE_DIR}/gzlib.c
|
||||
${zlib_reference_SOURCE_DIR}/gzread.c
|
||||
${zlib_reference_SOURCE_DIR}/gzwrite.c
|
||||
${zlib_reference_SOURCE_DIR}/infback.c
|
||||
${zlib_reference_SOURCE_DIR}/inffast.c
|
||||
${zlib_reference_SOURCE_DIR}/inflate.c
|
||||
${zlib_reference_SOURCE_DIR}/inftrees.c
|
||||
${zlib_reference_SOURCE_DIR}/trees.c
|
||||
${zlib_reference_SOURCE_DIR}/uncompr.c
|
||||
${zlib_reference_SOURCE_DIR}/zutil.c
|
||||
)
|
||||
target_include_directories(launcher_zlib_reference PRIVATE ${zlib_reference_SOURCE_DIR})
|
||||
# То же, что выставляет собственный configure zlib: без этого gz*-часть не
|
||||
# видит unistd.h и не собирается.
|
||||
target_compile_definitions(launcher_zlib_reference PRIVATE
|
||||
HAVE_UNISTD_H=1 HAVE_STDARG_H=1 _LARGEFILE64_SOURCE=1)
|
||||
# SONAME libz.so.1 обязателен: с ним ld.so считает предзагруженный объект
|
||||
# закрывающим зависимость, и системный zlib-ng в процессе java не появится.
|
||||
set_target_properties(launcher_zlib_reference PROPERTIES
|
||||
OUTPUT_NAME z
|
||||
VERSION 1.3.1
|
||||
SOVERSION 1
|
||||
C_VISIBILITY_PRESET default
|
||||
LIBRARY_OUTPUT_DIRECTORY $<TARGET_FILE_DIR:appMinecraft_launcher>/zlib
|
||||
)
|
||||
add_dependencies(appMinecraft_launcher launcher_zlib_reference)
|
||||
|
||||
target_compile_definitions(appMinecraft_launcher PRIVATE
|
||||
LAUNCHER_ZLIB_INSTALL_DIR="${CMAKE_INSTALL_FULL_LIBDIR}/galeonLauncher"
|
||||
)
|
||||
install(TARGETS launcher_zlib_reference LIBRARY DESTINATION ${LAUNCHER_ZLIB_DIR})
|
||||
endif()
|
||||
|
||||
install(TARGETS appMinecraft_launcher
|
||||
BUNDLE DESTINATION .
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
|
||||
+168
-29
@@ -1,10 +1,12 @@
|
||||
#include "modloaderinstaller.h"
|
||||
|
||||
#include "gamelauncher.h"
|
||||
#include "javalocator.h"
|
||||
#include "launcherpaths.h"
|
||||
#include "minecraftversion.h"
|
||||
#include "modloaderversionservice.h"
|
||||
#include "versioninstaller.h"
|
||||
#include "zlibreference.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
@@ -15,9 +17,12 @@
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QProcess>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QSaveFile>
|
||||
#include <QTimer>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kTransferTimeoutMs = 30000;
|
||||
@@ -136,10 +141,15 @@ void ModLoaderInstaller::install(const QString &gameDir,
|
||||
: warning);
|
||||
return;
|
||||
}
|
||||
if (loader == ModLoader::Fabric || loader == ModLoader::Quilt)
|
||||
beginFabricLike();
|
||||
else
|
||||
beginInstallerJar();
|
||||
// Любой лоадер — надстройка над ванильной версией: и профиль Fabric с его
|
||||
// inheritsFrom, и патч клиентского jar у Forge упрутся в недокачанную базу.
|
||||
// Поэтому шлюз один на всех и стоит до выбора ветки.
|
||||
ensureBaseVersion([this, loader] {
|
||||
if (loader == ModLoader::Fabric || loader == ModLoader::Quilt)
|
||||
beginFabricLike();
|
||||
else
|
||||
beginInstallerJar();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -153,8 +163,12 @@ void ModLoaderInstaller::cancel()
|
||||
m_reply->abort();
|
||||
m_reply = nullptr;
|
||||
}
|
||||
if (m_process && m_process->state() != QProcess::NotRunning)
|
||||
if (m_process && m_process->state() != QProcess::NotRunning) {
|
||||
m_process->kill();
|
||||
m_process->waitForFinished(2000);
|
||||
}
|
||||
if (m_installerStarted)
|
||||
removeVersionsAddedByInstaller();
|
||||
if (!m_awaitingVersionId.isEmpty()) {
|
||||
m_versionInstaller->cancel();
|
||||
return; // финал придёт из VersionInstaller::canceled
|
||||
@@ -260,19 +274,26 @@ void ModLoaderInstaller::beginInstallerJar()
|
||||
return;
|
||||
}
|
||||
|
||||
const QUrl installerUrl = entry->installerUrl;
|
||||
// Установщик патчит versions/<mc>/<mc>.jar — без ванильной версии он не отработает.
|
||||
ensureBaseVersion([this, installerUrl] {
|
||||
downloadInstaller(installerUrl, [this](const QString &path) { runInstaller(path); });
|
||||
});
|
||||
// Ванильную версию к этому моменту уже проверил ensureBaseVersion из install().
|
||||
downloadInstaller(entry->installerUrl, [this](const QString &path) { runInstaller(path); });
|
||||
}
|
||||
|
||||
void ModLoaderInstaller::ensureBaseVersion(std::function<void()> next)
|
||||
{
|
||||
if (QFile::exists(versionJsonPath(m_gameDir, m_gameVersion))
|
||||
&& VersionLoader::installedVersions(m_gameDir).contains(m_gameVersion)) {
|
||||
next();
|
||||
return;
|
||||
// Наличие versions/<mc>/<mc>.json ничего не значит: VersionInstaller кладёт его
|
||||
// до загрузки файлов, так что оборванная установка оставляет ровно такой след.
|
||||
// Проверяем тем же, чем пользуются приёмка VersionInstaller и запуск игры.
|
||||
QString loadError;
|
||||
const MinecraftVersion installed =
|
||||
VersionLoader::load(m_gameDir, m_gameVersion, {}, &loadError);
|
||||
if (installed.isValid()) {
|
||||
LaunchOptions probe;
|
||||
probe.gameDir = m_gameDir;
|
||||
probe.versionId = m_gameVersion;
|
||||
if (GameLauncher::missingFiles(probe, installed, 1).isEmpty()) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setStage(tr("Установка Minecraft %1").arg(m_gameVersion));
|
||||
@@ -413,6 +434,20 @@ void ModLoaderInstaller::runInstaller(const QString &installerPath)
|
||||
}
|
||||
|
||||
m_versionsBefore = VersionLoader::installedVersions(m_gameDir);
|
||||
m_installerStarted = true;
|
||||
|
||||
// Весь вывод установщика уходит в файл: в интерфейс попадает только хвост,
|
||||
// а разбираться с «Processor failed» по двенадцати строкам невозможно.
|
||||
m_installerLogPath = LauncherPaths::loaderDownloadDir() + u'/' + loaderKey(m_loader) + u'-'
|
||||
+ QString(m_loaderVersion).replace(u'/', u'_')
|
||||
+ QStringLiteral("-install.log");
|
||||
m_installerLogFile = new QFile(m_installerLogPath, this);
|
||||
if (!m_installerLogFile->open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||||
delete m_installerLogFile;
|
||||
m_installerLogFile = nullptr;
|
||||
m_installerLogPath.clear();
|
||||
}
|
||||
|
||||
setStage(tr("Установка %1").arg(loaderTitle(m_loader)));
|
||||
m_currentFile = tr("работает установщик, это может занять несколько минут");
|
||||
// Прогресс установщика неизвестен: панель покажет «…».
|
||||
@@ -427,12 +462,25 @@ void ModLoaderInstaller::runInstaller(const QString &installerPath)
|
||||
m_process->setWorkingDirectory(LauncherPaths::loaderDownloadDir());
|
||||
m_process->setProcessChannelMode(QProcess::MergedChannels);
|
||||
|
||||
// Установщик сверяет sha1 всего, что собрал сам, а на системах с zlib-ng
|
||||
// сжатие получается другое — без подмены он падает на любой версии игры.
|
||||
QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
|
||||
QString zlibNote;
|
||||
m_zlibNgUnpatched = !ZlibReference::applyTo(environment, &zlibNote);
|
||||
if (!zlibNote.isEmpty()) {
|
||||
appendInstallerLog(zlibNote);
|
||||
emit log(zlibNote);
|
||||
}
|
||||
m_process->setProcessEnvironment(environment);
|
||||
|
||||
connect(m_process, &QProcess::readyReadStandardOutput, this, [this] {
|
||||
const QStringList lines = QString::fromLocal8Bit(m_process->readAllStandardOutput())
|
||||
.split(u'\n', Qt::SkipEmptyParts);
|
||||
for (const QString &line : lines) {
|
||||
m_installerLog << line.trimmed();
|
||||
emit log(line.trimmed());
|
||||
const QString text = line.trimmed();
|
||||
m_installerLog << text;
|
||||
appendInstallerLog(text);
|
||||
emit log(text);
|
||||
}
|
||||
// Хвоста хватает, чтобы показать причину отказа; весь лог не нужен.
|
||||
while (m_installerLog.size() > kLogTailLines)
|
||||
@@ -471,31 +519,49 @@ void ModLoaderInstaller::handleInstallerFinished(int exitCode, bool crashed)
|
||||
m_process->deleteLater();
|
||||
m_process = nullptr;
|
||||
}
|
||||
closeInstallerLog();
|
||||
if (!m_running || m_canceling)
|
||||
return;
|
||||
|
||||
if (crashed || exitCode != 0) {
|
||||
fail(tr("Установщик %1 завершился с ошибкой (код %2).\n%3")
|
||||
.arg(loaderTitle(m_loader))
|
||||
.arg(exitCode)
|
||||
.arg(m_installerLog.join(u'\n')));
|
||||
removeVersionsAddedByInstaller();
|
||||
QString message = tr("Установщик %1 завершился с ошибкой (код %2).")
|
||||
.arg(loaderTitle(m_loader))
|
||||
.arg(exitCode);
|
||||
const QString hint = installerFailureHint();
|
||||
if (!hint.isEmpty())
|
||||
message += u'\n' + hint;
|
||||
if (!m_installerLog.isEmpty())
|
||||
message += u'\n' + m_installerLog.join(u'\n');
|
||||
if (!m_installerLogPath.isEmpty())
|
||||
message += u'\n'
|
||||
+ tr("Полный лог: %1").arg(QDir::toNativeSeparators(m_installerLogPath));
|
||||
fail(message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Какой профиль появился — выясняем разницей: id у Forge разные по эпохам
|
||||
// (1.20.1-forge-47.4.0, 1.12.2-forge-14.23.5.2859, neoforge-21.1.66).
|
||||
const QStringList after = VersionLoader::installedVersions(m_gameDir);
|
||||
QStringList added;
|
||||
for (const QString &id : after) {
|
||||
if (!m_versionsBefore.contains(id))
|
||||
added << id;
|
||||
QStringList added = versionsAddedByInstaller();
|
||||
if (added.isEmpty()) {
|
||||
// Разницы нет, если профиль лежал в versions ещё до запуска: переустановка
|
||||
// поверх или остаток прошлой попытки. Тогда ищем его среди наследников
|
||||
// выбранной ванильной версии.
|
||||
const QStringList dependents = VersionLoader::dependentsOf(m_gameDir, m_gameVersion);
|
||||
for (const QString &id : dependents) {
|
||||
if (id.contains(m_loaderVersion))
|
||||
added << id;
|
||||
}
|
||||
}
|
||||
|
||||
if (added.isEmpty()) {
|
||||
fail(tr("Установщик %1 отработал, но новая версия в %2 не появилась.\n%3")
|
||||
.arg(loaderTitle(m_loader),
|
||||
QDir::toNativeSeparators(m_gameDir + QStringLiteral("/versions")),
|
||||
m_installerLog.join(u'\n')));
|
||||
QString message = tr("Установщик %1 отработал, но новая версия в %2 не появилась.")
|
||||
.arg(loaderTitle(m_loader),
|
||||
QDir::toNativeSeparators(m_gameDir
|
||||
+ QStringLiteral("/versions")));
|
||||
if (!m_installerLog.isEmpty())
|
||||
message += u'\n' + m_installerLog.join(u'\n');
|
||||
fail(message);
|
||||
return;
|
||||
}
|
||||
if (added.size() > 1) {
|
||||
@@ -507,9 +573,78 @@ void ModLoaderInstaller::handleInstallerFinished(int exitCode, bool crashed)
|
||||
added = {match != added.cend() ? *match : added.first()};
|
||||
}
|
||||
|
||||
// Профиль на месте — дальше его судьба в руках VersionInstaller, мусором он
|
||||
// больше не считается.
|
||||
m_installerStarted = false;
|
||||
m_versionsBefore.clear();
|
||||
handOffToVersionInstaller(added.first());
|
||||
}
|
||||
|
||||
QStringList ModLoaderInstaller::versionsAddedByInstaller() const
|
||||
{
|
||||
if (!m_installerStarted)
|
||||
return {};
|
||||
|
||||
QStringList added;
|
||||
const QStringList after = VersionLoader::installedVersions(m_gameDir);
|
||||
for (const QString &id : after) {
|
||||
if (!m_versionsBefore.contains(id))
|
||||
added << id;
|
||||
}
|
||||
return added;
|
||||
}
|
||||
|
||||
void ModLoaderInstaller::removeVersionsAddedByInstaller()
|
||||
{
|
||||
// Forge пишет versions/<id>/<id>.json до запуска процессоров, поэтому после
|
||||
// неудачи остаётся профиль-призрак: в списке версий он выглядит установленным,
|
||||
// а следующей попытке мешает — разница по versions у неё выйдет пустой.
|
||||
const QStringList added = versionsAddedByInstaller();
|
||||
for (const QString &id : added) {
|
||||
QString error;
|
||||
if (!VersionLoader::remove(m_gameDir, id, &error))
|
||||
emit log(tr("Не удалось убрать незавершённый профиль «%1»: %2").arg(id, error));
|
||||
}
|
||||
m_versionsBefore.clear();
|
||||
m_installerStarted = false;
|
||||
}
|
||||
|
||||
QString ModLoaderInstaller::installerFailureHint() const
|
||||
{
|
||||
if (!m_zlibNgUnpatched)
|
||||
return {};
|
||||
|
||||
const bool checksumFailure =
|
||||
std::any_of(m_installerLog.cbegin(), m_installerLog.cend(), [](const QString &line) {
|
||||
return line.contains(QLatin1String("invalid outputs"))
|
||||
|| line.contains(QLatin1String("Processor failed"));
|
||||
});
|
||||
if (!checksumFailure)
|
||||
return {};
|
||||
|
||||
return tr("Системный libz.so.1 — это zlib-ng: он сжимает иначе, чем обычный zlib, "
|
||||
"поэтому sha1 собранных установщиком файлов не совпадает с эталоном. "
|
||||
"Пересоберите лаунчер, чтобы рядом с ним появился эталонный libz.so.1, "
|
||||
"либо замените zlib-ng в системе: sudo pacman -S zlib lib32-zlib");
|
||||
}
|
||||
|
||||
void ModLoaderInstaller::appendInstallerLog(const QString &line)
|
||||
{
|
||||
if (!m_installerLogFile)
|
||||
return;
|
||||
m_installerLogFile->write(line.toUtf8());
|
||||
m_installerLogFile->write("\n");
|
||||
}
|
||||
|
||||
void ModLoaderInstaller::closeInstallerLog()
|
||||
{
|
||||
if (!m_installerLogFile)
|
||||
return;
|
||||
m_installerLogFile->close();
|
||||
delete m_installerLogFile;
|
||||
m_installerLogFile = nullptr;
|
||||
}
|
||||
|
||||
// ── Общий хвост ────────────────────────────────────────────────────────────
|
||||
|
||||
void ModLoaderInstaller::handOffToVersionInstaller(const QString &versionId)
|
||||
@@ -537,6 +672,10 @@ void ModLoaderInstaller::reset()
|
||||
m_bytesDone = 0;
|
||||
m_bytesTotal = 0;
|
||||
m_versionsBefore.clear();
|
||||
m_installerStarted = false;
|
||||
m_zlibNgUnpatched = false;
|
||||
closeInstallerLog();
|
||||
m_installerLogPath.clear();
|
||||
if (m_process) {
|
||||
m_process->deleteLater();
|
||||
m_process = nullptr;
|
||||
|
||||
+19
-1
@@ -6,6 +6,7 @@
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
class QFile;
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
class QProcess;
|
||||
@@ -20,7 +21,11 @@ class VersionInstaller;
|
||||
//
|
||||
// * Forge и NeoForge так не умеют: их установка — это патч клиентского jar,
|
||||
// поэтому запускаем официальный installer.jar найденной Java в headless-режиме
|
||||
// и смотрим, какой профиль появился в versions.
|
||||
// и смотрим, какой профиль появился в versions. Процессу установщика при этом
|
||||
// подсовывается эталонный zlib — см. ZlibReference.
|
||||
//
|
||||
// Оба пути начинаются одинаково: пока ванильная версия не скачана целиком,
|
||||
// ставить лоадер некуда, поэтому ensureBaseVersion стоит перед обеими ветками.
|
||||
//
|
||||
// Набор геттеров повторяет VersionInstaller: панель загрузки в интерфейсе
|
||||
// читает их одинаково, независимо от того, кто сейчас работает.
|
||||
@@ -69,6 +74,11 @@ private:
|
||||
bool writeLauncherProfilesStub();
|
||||
void runInstaller(const QString &installerPath);
|
||||
void handleInstallerFinished(int exitCode, bool crashed);
|
||||
QStringList versionsAddedByInstaller() const;
|
||||
void removeVersionsAddedByInstaller();
|
||||
QString installerFailureHint() const;
|
||||
void appendInstallerLog(const QString &line);
|
||||
void closeInstallerLog();
|
||||
|
||||
void handOffToVersionInstaller(const QString &versionId);
|
||||
void setStage(const QString &stage);
|
||||
@@ -97,6 +107,14 @@ private:
|
||||
// разницей, а не угадыванием строки — id у Forge разные по эпохам.
|
||||
QStringList m_versionsBefore;
|
||||
QStringList m_installerLog;
|
||||
// Установщик успел отработать — значит, за появившиеся в versions папки
|
||||
// отвечаем мы и при неудаче их надо убрать.
|
||||
bool m_installerStarted = false;
|
||||
// В системе zlib-ng, а подменить его было нечем: почти наверняка причина
|
||||
// расхождения sha1, о которой стоит сказать прямым текстом.
|
||||
bool m_zlibNgUnpatched = false;
|
||||
QFile *m_installerLogFile = nullptr;
|
||||
QString m_installerLogPath;
|
||||
|
||||
// Ждём, пока VersionInstaller докачает то, что установщик не положил.
|
||||
QString m_awaitingVersionId;
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
#include "zlibreference.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QStringList>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#if defined(Q_OS_LINUX)
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
QString translate(const char *text)
|
||||
{
|
||||
return QCoreApplication::translate("ZlibReference", text);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ZlibReference::systemIsZlibNg()
|
||||
{
|
||||
#if defined(Q_OS_LINUX)
|
||||
// Спрашиваем ровно ту библиотеку, которую подберёт ld.so процессу java.
|
||||
// zlib-ng в режиме совместимости честно пишет о себе в zlibVersion().
|
||||
static const bool result = [] {
|
||||
void *handle = dlopen("libz.so.1", RTLD_LAZY);
|
||||
if (!handle)
|
||||
return false;
|
||||
using VersionFn = const char *(*)();
|
||||
const auto zlibVersion = reinterpret_cast<VersionFn>(dlsym(handle, "zlibVersion"));
|
||||
const QString version = zlibVersion ? QString::fromLatin1(zlibVersion()) : QString();
|
||||
dlclose(handle);
|
||||
return version.contains(QLatin1String("zlib-ng"), Qt::CaseInsensitive);
|
||||
}();
|
||||
return result;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
QString ZlibReference::bundledPath()
|
||||
{
|
||||
#if defined(Q_OS_LINUX)
|
||||
static const QString path = [] {
|
||||
QStringList candidates;
|
||||
const QString appDir = QCoreApplication::applicationDirPath();
|
||||
if (!appDir.isEmpty()) {
|
||||
candidates << appDir + QStringLiteral("/zlib/libz.so.1") // дерево сборки
|
||||
<< appDir + QStringLiteral("/../lib/galeonLauncher/libz.so.1");
|
||||
}
|
||||
#ifdef LAUNCHER_ZLIB_INSTALL_DIR
|
||||
candidates << QStringLiteral(LAUNCHER_ZLIB_INSTALL_DIR) + QStringLiteral("/libz.so.1");
|
||||
#endif
|
||||
for (const QString &candidate : std::as_const(candidates)) {
|
||||
const QFileInfo info(candidate);
|
||||
if (info.exists() && info.isFile())
|
||||
return info.canonicalFilePath();
|
||||
}
|
||||
return QString();
|
||||
}();
|
||||
return path;
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ZlibReference::applyTo(QProcessEnvironment &env, QString *note)
|
||||
{
|
||||
#if defined(Q_OS_LINUX)
|
||||
if (!systemIsZlibNg()) {
|
||||
if (note)
|
||||
note->clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
const QString path = bundledPath();
|
||||
if (path.isEmpty()) {
|
||||
if (note)
|
||||
*note = translate("В системе zlib-ng, а эталонный libz.so.1 рядом с лаунчером "
|
||||
"не найден — установщик почти наверняка не сойдётся по sha1.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Не затираем чужой LD_PRELOAD: наш идёт первым, остальное остаётся.
|
||||
const QString existing = env.value(QStringLiteral("LD_PRELOAD"));
|
||||
env.insert(QStringLiteral("LD_PRELOAD"), existing.isEmpty() ? path : path + u':' + existing);
|
||||
if (note)
|
||||
*note = translate("Системный zlib-ng подменён эталонным zlib: %1")
|
||||
.arg(QDir::toNativeSeparators(path));
|
||||
return true;
|
||||
#else
|
||||
Q_UNUSED(env);
|
||||
if (note)
|
||||
note->clear();
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
class QProcessEnvironment;
|
||||
|
||||
// Установщики Forge и NeoForge сверяют sha1 каждого jar, который сами же и
|
||||
// собирают, с эталоном из install_profile.json. Эталон посчитан на обычном
|
||||
// zlib, а CachyOS, Fedora и другие подставляют вместо него zlib-ng: сжатие
|
||||
// корректное, но побайтово другое, поэтому установка падает на любой версии
|
||||
// игры сообщением «Processor failed, invalid outputs».
|
||||
//
|
||||
// Сменить Java не поможет — сборки OpenJDK под Linux берут libz.so.1 из
|
||||
// системы. Поэтому рядом с лаунчером лежит собранный обычный zlib, и процессу
|
||||
// установщика мы подсовываем его через LD_PRELOAD. Системный zlib-ng при этом
|
||||
// остаётся на месте: подмена живёт ровно один процесс.
|
||||
namespace ZlibReference {
|
||||
|
||||
// libz.so.1, который достанется процессу java, — это zlib-ng?
|
||||
bool systemIsZlibNg();
|
||||
|
||||
// Путь к собранному рядом эталонному libz.so.1; пусто, если его нет.
|
||||
QString bundledPath();
|
||||
|
||||
// Дописывает LD_PRELOAD, если подмена нужна. true — окружение готово (в том
|
||||
// числе когда подменять нечего). note — строка для лога и текста ошибки.
|
||||
bool applyTo(QProcessEnvironment &env, QString *note = nullptr);
|
||||
|
||||
} // namespace ZlibReference
|
||||
Reference in New Issue
Block a user