#include "javalocator.h" #include #include #include #include #include #include #include #include #include #include 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////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 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 {}; }