2026-08-22 18:19:13 +03:00
|
|
|
|
#include "javalocator.h"
|
|
|
|
|
|
|
|
|
|
|
|
#include <QDir>
|
|
|
|
|
|
#include <QDirIterator>
|
|
|
|
|
|
#include <QFileInfo>
|
|
|
|
|
|
#include <QHash>
|
|
|
|
|
|
#include <QProcess>
|
|
|
|
|
|
#include <QProcessEnvironment>
|
2026-08-22 23:39:46 +00:00
|
|
|
|
#include <QObject>
|
2026-08-22 18:19:13 +03:00
|
|
|
|
#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()) {
|
2026-08-22 23:39:46 +00:00
|
|
|
|
QDirIterator it(runtime, QStringList{QString::fromLatin1(kExecutable)},
|
|
|
|
|
|
QDir::Files | QDir::Executable, QDirIterator::Subdirectories);
|
2026-08-22 18:19:13 +03:00
|
|
|
|
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"));
|
2026-08-22 23:39:46 +00:00
|
|
|
|
appendIfExecutable(result, home + QStringLiteral("/Contents/Home/bin")); // macOS
|
2026-08-22 18:19:13 +03:00
|
|
|
|
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)
|
2026-08-22 23:39:46 +00:00
|
|
|
|
*error = QObject::tr("Java не найдена. Установите JDK %1 или укажите путь в настройках.")
|
2026-08-22 18:19:13 +03:00
|
|
|
|
.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
|
2026-08-22 23:39:46 +00:00
|
|
|
|
? QObject::tr("Для этой версии нужна Java %1 или новее, найдена только Java %2 (%3).")
|
2026-08-22 18:19:13 +03:00
|
|
|
|
.arg(requiredMajor)
|
|
|
|
|
|
.arg(bestBelowMajor)
|
|
|
|
|
|
.arg(QDir::toNativeSeparators(bestBelow))
|
|
|
|
|
|
: QObject::tr("Не удалось определить версию установленной Java. "
|
|
|
|
|
|
"Укажите путь к JDK %1 в настройках.")
|
|
|
|
|
|
.arg(requiredMajor);
|
|
|
|
|
|
}
|
|
|
|
|
|
return {};
|
|
|
|
|
|
}
|