Files
minecraft-launcher/javalocator.cpp
T
galeon 2c51c4736a revert af4230500e
revert add launcher folder and some checks to it
2026-08-22 23:39:46 +00:00

204 lines
7.8 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "javalocator.h"
#include <QDir>
#include <QDirIterator>
#include <QFileInfo>
#include <QHash>
#include <QProcess>
#include <QProcessEnvironment>
#include <QObject>
#include <QRegularExpression>
#include <QStandardPaths>
#include <utility>
namespace {
#if defined(Q_OS_WIN)
constexpr auto kExecutable = "java.exe";
constexpr auto kWindowless = "javaw.exe";
#else
constexpr auto kExecutable = "java";
constexpr auto kWindowless = "java";
#endif
// Корни, в которых Windows/Linux/macOS обычно держат JDK и JRE.
QStringList installationRoots()
{
QStringList roots;
#if defined(Q_OS_WIN)
const QStringList programFiles = {qEnvironmentVariable("ProgramFiles"),
qEnvironmentVariable("ProgramFiles(x86)"),
qEnvironmentVariable("ProgramW6432")};
const QStringList vendors = {QStringLiteral("Java"),
QStringLiteral("Eclipse Adoptium"),
QStringLiteral("Eclipse Foundation"),
QStringLiteral("AdoptOpenJDK"),
QStringLiteral("Amazon Corretto"),
QStringLiteral("Zulu"),
QStringLiteral("BellSoft"),
QStringLiteral("Microsoft"),
QStringLiteral("Semeru"),
QStringLiteral("Android/Android Studio/jbr")};
for (const QString &base : programFiles) {
if (base.isEmpty())
continue;
for (const QString &vendor : vendors)
roots << base + u'/' + vendor;
}
#elif defined(Q_OS_MACOS)
roots << QStringLiteral("/Library/Java/JavaVirtualMachines")
<< QDir::homePath() + QStringLiteral("/Library/Java/JavaVirtualMachines");
#else
roots << QStringLiteral("/usr/lib/jvm") << QStringLiteral("/usr/java")
<< QStringLiteral("/opt/java") << QStringLiteral("/opt/jdk");
#endif
return roots;
}
// Ищем именно java.exe: у javaw.exe нет вывода, а версию мы определяем запуском.
void appendIfExecutable(QStringList &target, const QString &binDir)
{
const QFileInfo info(binDir + u'/' + QString::fromLatin1(kExecutable));
if (!info.isFile() || !info.isExecutable())
return;
const QString path = info.absoluteFilePath();
if (!target.contains(path))
target << path;
}
} // namespace
QStringList JavaLocator::findAll(const QString &gameDir)
{
QStringList result;
// 1. Рантайм, скачанный самим Minecraft: .minecraft/runtime/<component>/<os>/<component>/bin
const QString runtime = gameDir + QStringLiteral("/runtime");
if (QDir(runtime).exists()) {
QDirIterator it(runtime, QStringList{QString::fromLatin1(kExecutable)},
QDir::Files | QDir::Executable, QDirIterator::Subdirectories);
while (it.hasNext()) {
it.next();
appendIfExecutable(result, it.fileInfo().absolutePath());
}
}
// 2. JAVA_HOME
const QString javaHome = qEnvironmentVariable("JAVA_HOME");
if (!javaHome.isEmpty())
appendIfExecutable(result, javaHome + QStringLiteral("/bin"));
// 3. PATH
const QString onPath = QStandardPaths::findExecutable(QString::fromLatin1(kExecutable));
if (!onPath.isEmpty())
appendIfExecutable(result, QFileInfo(onPath).absolutePath());
// 4. Типовые каталоги установки
const QStringList roots = installationRoots();
for (const QString &root : roots) {
const QDir dir(root);
if (!dir.exists())
continue;
const QStringList entries = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name);
for (const QString &entry : entries) {
const QString home = dir.absoluteFilePath(entry);
appendIfExecutable(result, home + QStringLiteral("/bin"));
appendIfExecutable(result, home + QStringLiteral("/Contents/Home/bin")); // macOS
appendIfExecutable(result, home + QStringLiteral("/jre/bin"));
}
}
return result;
}
int JavaLocator::majorVersion(const QString &javaPath)
{
static QHash<QString, int> cache;
const auto cached = cache.constFind(javaPath);
if (cached != cache.constEnd())
return cached.value();
int major = 0;
QProcess process;
process.start(javaPath, {QStringLiteral("-version")});
if (process.waitForFinished(5000)) {
// java печатает версию в stderr: openjdk version "17.0.2" 2022-01-18
const QString output = QString::fromLocal8Bit(process.readAllStandardError())
+ QString::fromLocal8Bit(process.readAllStandardOutput());
static const QRegularExpression re(QStringLiteral("version\\s+\"(\\d+)(?:\\.(\\d+))?"));
const QRegularExpressionMatch match = re.match(output);
if (match.hasMatch()) {
const int first = match.captured(1).toInt();
// До Java 9 версия выглядела как 1.8.0_301 — мажорная тут вторая цифра.
major = (first == 1) ? match.captured(2).toInt() : first;
}
}
cache.insert(javaPath, major);
return major;
}
QString JavaLocator::windowlessVariant(const QString &javaPath)
{
const QFileInfo info(javaPath);
if (info.fileName() != QLatin1String(kExecutable))
return javaPath;
const QFileInfo windowless(info.absolutePath() + u'/' + QString::fromLatin1(kWindowless));
return windowless.isFile() ? windowless.absoluteFilePath() : javaPath;
}
QString JavaLocator::select(const QString &gameDir,
int requiredMajor,
const QString &preferred,
QString *error)
{
QStringList candidates;
if (!preferred.isEmpty()) {
QFileInfo info(preferred);
// В настройках можно указать как сам java.exe, так и корень JDK.
if (info.isDir())
appendIfExecutable(candidates, info.absoluteFilePath() + QStringLiteral("/bin"));
else if (info.isFile())
candidates << info.absoluteFilePath();
if (candidates.isEmpty()) {
if (error)
*error = QObject::tr("Java по указанному пути не найдена: %1")
.arg(QDir::toNativeSeparators(preferred));
return {};
}
}
candidates += findAll(gameDir);
if (candidates.isEmpty()) {
if (error)
*error = QObject::tr("Java не найдена. Установите JDK %1 или укажите путь в настройках.")
.arg(requiredMajor);
return {};
}
QString bestBelow;
int bestBelowMajor = 0;
for (const QString &candidate : std::as_const(candidates)) {
const int major = majorVersion(candidate);
if (major >= requiredMajor)
return candidate;
if (major > bestBelowMajor) {
bestBelowMajor = major;
bestBelow = candidate;
}
}
if (error) {
*error = bestBelowMajor > 0
? QObject::tr("Для этой версии нужна Java %1 или новее, найдена только Java %2 (%3).")
.arg(requiredMajor)
.arg(bestBelowMajor)
.arg(QDir::toNativeSeparators(bestBelow))
: QObject::tr("Не удалось определить версию установленной Java. "
"Укажите путь к JDK %1 в настройках.")
.arg(requiredMajor);
}
return {};
}