summaryrefslogtreecommitdiff
path: root/launcher/minecraft/launch/VerifyJavaInstall.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'launcher/minecraft/launch/VerifyJavaInstall.cpp')
-rw-r--r--launcher/minecraft/launch/VerifyJavaInstall.cpp536
1 files changed, 286 insertions, 250 deletions
diff --git a/launcher/minecraft/launch/VerifyJavaInstall.cpp b/launcher/minecraft/launch/VerifyJavaInstall.cpp
index 3368bfa8c1..0f58b5efa4 100644
--- a/launcher/minecraft/launch/VerifyJavaInstall.cpp
+++ b/launcher/minecraft/launch/VerifyJavaInstall.cpp
@@ -23,9 +23,11 @@
#include <QDir>
#include <QDirIterator>
+#include <QFileInfo>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
+#include <QProcess>
#include <QRegularExpression>
#include <launch/LaunchTask.h>
@@ -33,306 +35,340 @@
#include <minecraft/PackProfile.h>
#include <minecraft/VersionFilterData.h>
+#include "Application.h"
#include "FileSystem.h"
#include "Json.h"
#include "java/JavaUtils.h"
#ifndef MeshMC_DISABLE_JAVA_DOWNLOADER
-#include "Application.h"
#include "BuildConfig.h"
#include "net/Download.h"
#endif
#ifdef major
- #undef major
+#undef major
#endif
#ifdef minor
- #undef minor
+#undef minor
#endif
+namespace
+{
+ std::optional<JavaVersion> probeJavaVersion(const QString& javaPath)
+ {
+ const auto checkerJar =
+ FS::PathCombine(APPLICATION->getJarsPath(), "JavaCheck.jar");
+ if (!QFileInfo::exists(checkerJar)) {
+ return std::nullopt;
+ }
+
+ QProcess process;
+ process.setProgram(javaPath);
+ process.setArguments({"-jar", checkerJar});
+ process.setProcessEnvironment(CleanEnviroment());
+ process.setProcessChannelMode(QProcess::SeparateChannels);
+ process.start();
+
+ if (!process.waitForFinished(15000) ||
+ process.exitStatus() != QProcess::NormalExit ||
+ process.exitCode() != 0) {
+ return std::nullopt;
+ }
+
+ const auto stdoutData =
+ QString::fromLocal8Bit(process.readAllStandardOutput());
+ const auto lines = stdoutData.split('\n', Qt::SkipEmptyParts);
+ for (const auto& rawLine : lines) {
+ const auto line = rawLine.trimmed();
+ if (!line.startsWith("java.version=")) {
+ continue;
+ }
+ return JavaVersion(line.mid(QString("java.version=").size()));
+ }
+
+ return std::nullopt;
+ }
+} // namespace
+
int VerifyJavaInstall::determineRequiredJavaMajor() const
{
- auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance());
- auto minecraftComponent = m_inst->getPackProfile()->getComponent("net.minecraft");
-
- if (minecraftComponent->getReleaseDateTime() >= g_VersionFilterData.java25BeginsDate)
- return 25;
- if (minecraftComponent->getReleaseDateTime() >= g_VersionFilterData.java21BeginsDate)
- return 21;
- if (minecraftComponent->getReleaseDateTime() >= g_VersionFilterData.java17BeginsDate)
- return 17;
- if (minecraftComponent->getReleaseDateTime() >= g_VersionFilterData.java16BeginsDate)
- return 16;
- if (minecraftComponent->getReleaseDateTime() >= g_VersionFilterData.java8BeginsDate)
- return 8;
- return 0;
+ auto m_inst =
+ std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance());
+ auto minecraftComponent =
+ m_inst->getPackProfile()->getComponent("net.minecraft");
+
+ if (minecraftComponent->getReleaseDateTime() >=
+ g_VersionFilterData.java25BeginsDate)
+ return 25;
+ if (minecraftComponent->getReleaseDateTime() >=
+ g_VersionFilterData.java21BeginsDate)
+ return 21;
+ if (minecraftComponent->getReleaseDateTime() >=
+ g_VersionFilterData.java17BeginsDate)
+ return 17;
+ if (minecraftComponent->getReleaseDateTime() >=
+ g_VersionFilterData.java16BeginsDate)
+ return 16;
+ if (minecraftComponent->getReleaseDateTime() >=
+ g_VersionFilterData.java8BeginsDate)
+ return 8;
+ return 0;
}
QString VerifyJavaInstall::javaInstallDir() const
{
- return FS::PathCombine(QDir::currentPath(), "java");
+ return JavaUtils::managedJavaRoot();
}
QString VerifyJavaInstall::findInstalledJava(int requiredMajor) const
{
-#if defined(Q_OS_WIN)
- QString binaryName = "javaw.exe";
-#else
- QString binaryName = "java";
-#endif
-
- // Pattern matching Java major version in directory names
- // Matches: "java-8-openjdk", "jdk-17", "java-21-openjdk-amd64", "zulu25.30.31-ca-jdk25.0.0-linux_x64"
- QRegularExpression re(QString("(?:jdk|java|jre)[-.]?%1(?:[^0-9]|$)").arg(requiredMajor));
-
- // 1. Scan MeshMC's managed java/{vendor}/{version}/bin/java directory
- QString javaBaseDir = javaInstallDir();
- QDir baseDir(javaBaseDir);
- if (baseDir.exists()) {
- QDirIterator vendorIt(javaBaseDir, QDir::Dirs | QDir::NoDotAndDotDot);
- while (vendorIt.hasNext()) {
- vendorIt.next();
- QString vendorPath = vendorIt.filePath();
-
- QDirIterator versionIt(vendorPath, QDir::Dirs | QDir::NoDotAndDotDot);
- while (versionIt.hasNext()) {
- versionIt.next();
- QString versionPath = versionIt.filePath();
-
- QDirIterator binIt(versionPath, QStringList() << binaryName,
- QDir::Files, QDirIterator::Subdirectories);
- while (binIt.hasNext()) {
- binIt.next();
- QString javaPath = binIt.filePath();
- if (!javaPath.contains("/bin/"))
- continue;
- QString dirName = versionIt.fileName().toLower();
- if (re.match(dirName).hasMatch())
- return javaPath;
- }
- }
- }
- }
-
- // 2. Scan system-installed Java paths
- // Use JavaUtils to discover all Java installations on the system, then
- // check if any match the required major version by parsing the path.
- JavaUtils javaUtils;
- QList<QString> systemJavas = javaUtils.FindJavaPaths();
- for (const QString &javaPath : systemJavas) {
- // Resolve to absolute path and verify it exists
- QString resolved = FS::ResolveExecutable(javaPath);
- if (resolved.isEmpty())
- continue;
-
- // Extract the parent directory components to check for version info
- // Typical paths:
- // /usr/lib/jvm/java-8-openjdk/bin/java
- // /usr/lib/jvm/java-8-openjdk/jre/bin/java
- // /usr/lib64/jvm/java-17-openjdk/bin/java
- // /opt/jdk/jdk-21/bin/java
- QFileInfo fi(resolved);
- QString fullPath = fi.absoluteFilePath().toLower();
-
- // Check if any component in the path matches the required Java version
- QStringList parts = fullPath.split('/');
- for (const QString &part : parts) {
- if (re.match(part).hasMatch()) {
- return resolved;
- }
- }
- }
-
- return {};
+ JavaUtils javaUtils;
+ QList<QString> systemJavas = javaUtils.FindJavaPaths();
+ QSet<QString> seenPaths;
+ for (const QString& javaPath : systemJavas) {
+ QString resolved = FS::ResolveExecutable(javaPath);
+ if (resolved.isEmpty() || seenPaths.contains(resolved))
+ continue;
+
+ seenPaths.insert(resolved);
+ const auto version = probeJavaVersion(resolved);
+ if (version.has_value() && version->major() >= requiredMajor) {
+ return resolved;
+ }
+ }
+
+ return {};
}
-void VerifyJavaInstall::executeTask() {
- auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance());
-
- auto javaVersion = m_inst->getJavaVersion();
- int requiredMajor = determineRequiredJavaMajor();
-
- // No Java requirement or already met
- if (requiredMajor == 0 || javaVersion.major() >= requiredMajor) {
- emitSucceeded();
- return;
- }
-
- // Java version insufficient — try to find an already-downloaded one
- emit logLine(tr("Current Java version %1 does not meet the requirement of Java %2.")
- .arg(javaVersion.toString()).arg(requiredMajor), MessageLevel::Warning);
-
- QString existingJava = findInstalledJava(requiredMajor);
- if (!existingJava.isEmpty()) {
- emit logLine(tr("Found installed Java %1 at: %2").arg(requiredMajor).arg(existingJava), MessageLevel::MeshMC);
+void VerifyJavaInstall::executeTask()
+{
+ auto m_inst =
+ std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance());
+
+ auto javaVersion = m_inst->getJavaVersion();
+ int requiredMajor = determineRequiredJavaMajor();
+
+ // No Java requirement or already met
+ if (requiredMajor == 0 || javaVersion.major() >= requiredMajor) {
+ emitSucceeded();
+ return;
+ }
+
+ // Java version insufficient — try to find an already-downloaded one
+ emit logLine(
+ tr("Current Java version %1 does not meet the requirement of Java %2.")
+ .arg(javaVersion.toString())
+ .arg(requiredMajor),
+ MessageLevel::Warning);
+
+ QString existingJava = findInstalledJava(requiredMajor);
+ if (!existingJava.isEmpty()) {
+ emit logLine(tr("Found installed Java %1 at: %2")
+ .arg(requiredMajor)
+ .arg(existingJava),
+ MessageLevel::MeshMC);
#ifndef MeshMC_DISABLE_JAVA_DOWNLOADER
- setJavaPathAndSucceed(existingJava);
+ setJavaPathAndSucceed(existingJava);
#else
- m_inst->settings()->set("OverrideJavaLocation", true);
- m_inst->settings()->set("JavaPath", existingJava);
- emit logLine(tr("Java path set to: %1").arg(existingJava), MessageLevel::MeshMC);
- emitSucceeded();
+ m_inst->settings()->set("OverrideJavaLocation", true);
+ m_inst->settings()->set("JavaPath", existingJava);
+ emit logLine(tr("Java path set to: %1").arg(existingJava),
+ MessageLevel::MeshMC);
+ emitSucceeded();
#endif
- return;
- }
+ return;
+ }
#ifndef MeshMC_DISABLE_JAVA_DOWNLOADER
- // Not found — auto-download
- emit logLine(tr("No installed Java %1 found. Downloading...").arg(requiredMajor), MessageLevel::MeshMC);
- autoDownloadJava(requiredMajor);
+ // Not found — auto-download
+ emit logLine(
+ tr("No installed Java %1 found. Downloading...").arg(requiredMajor),
+ MessageLevel::MeshMC);
+ autoDownloadJava(requiredMajor);
#else
- emitFailed(tr("Java %1 is required but not installed. Please install it manually.").arg(requiredMajor));
+ emitFailed(
+ tr("Java %1 is required but not installed. Please install it manually.")
+ .arg(requiredMajor));
#endif
}
#ifndef MeshMC_DISABLE_JAVA_DOWNLOADER
void VerifyJavaInstall::autoDownloadJava(int requiredMajor)
{
- // Fetch version list from net.minecraft.java (Mojang)
- fetchVersionList(requiredMajor);
+ // Fetch version list from net.minecraft.java (Mojang)
+ fetchVersionList(requiredMajor);
}
void VerifyJavaInstall::fetchVersionList(int requiredMajor)
{
- m_fetchData.clear();
- QString uid = "net.minecraft.java";
- QString url = QString("%1%2/index.json").arg(BuildConfig.META_URL, uid);
-
- m_fetchJob = new NetJob(tr("Fetch Java versions"), APPLICATION->network());
- auto dl = Net::Download::makeByteArray(QUrl(url), &m_fetchData);
- m_fetchJob->addNetAction(dl);
-
- connect(m_fetchJob.get(), &NetJob::succeeded, this, [this, uid, requiredMajor]() {
- m_fetchJob.reset();
-
- QJsonDocument doc;
- try {
- doc = Json::requireDocument(m_fetchData);
- } catch (const Exception &e) {
- emitFailed(tr("Failed to parse Java version list from meta server: %1").arg(e.cause()));
- return;
- }
- if (!doc.isObject()) {
- emitFailed(tr("Failed to parse Java version list from meta server."));
- return;
- }
-
- auto versions = JavaDownload::parseVersionIndex(doc.object(), uid);
-
- // Find the matching version (e.g., "java25" for requiredMajor=25)
- QString targetVersionId = QString("java%1").arg(requiredMajor);
- bool found = false;
- for (const auto &ver : versions) {
- if (ver.versionId == targetVersionId) {
- found = true;
- fetchRuntimes(ver.versionId, requiredMajor);
- return;
- }
- }
-
- if (!found) {
- emitFailed(tr("Java %1 is not available for download from Mojang. Please install it manually.")
- .arg(requiredMajor));
- }
- });
-
- connect(m_fetchJob.get(), &NetJob::failed, this, [this, requiredMajor](QString reason) {
- emitFailed(tr("Failed to fetch Java version list: %1. Please install Java %2 manually.")
- .arg(reason).arg(requiredMajor));
- m_fetchJob.reset();
- });
-
- m_fetchJob->start();
+ m_fetchData.clear();
+ QString uid = "net.minecraft.java";
+ QString url = QString("%1%2/index.json").arg(BuildConfig.META_URL, uid);
+
+ m_fetchJob = new NetJob(tr("Fetch Java versions"), APPLICATION->network());
+ auto dl = Net::Download::makeByteArray(QUrl(url), &m_fetchData);
+ m_fetchJob->addNetAction(dl);
+
+ connect(
+ m_fetchJob.get(), &NetJob::succeeded, this,
+ [this, uid, requiredMajor]() {
+ m_fetchJob.reset();
+
+ QJsonDocument doc;
+ try {
+ doc = Json::requireDocument(m_fetchData);
+ } catch (const Exception& e) {
+ emitFailed(
+ tr("Failed to parse Java version list from meta server: %1")
+ .arg(e.cause()));
+ return;
+ }
+ if (!doc.isObject()) {
+ emitFailed(
+ tr("Failed to parse Java version list from meta server."));
+ return;
+ }
+
+ auto versions = JavaDownload::parseVersionIndex(doc.object(), uid);
+
+ // Find the matching version (e.g., "java25" for requiredMajor=25)
+ QString targetVersionId = QString("java%1").arg(requiredMajor);
+ bool found = false;
+ for (const auto& ver : versions) {
+ if (ver.versionId == targetVersionId) {
+ found = true;
+ fetchRuntimes(ver.versionId, requiredMajor);
+ return;
+ }
+ }
+
+ if (!found) {
+ emitFailed(tr("Java %1 is not available for download from "
+ "Mojang. Please install it manually.")
+ .arg(requiredMajor));
+ }
+ });
+
+ connect(m_fetchJob.get(), &NetJob::failed, this,
+ [this, requiredMajor](QString reason) {
+ emitFailed(tr("Failed to fetch Java version list: %1. Please "
+ "install Java %2 manually.")
+ .arg(reason)
+ .arg(requiredMajor));
+ m_fetchJob.reset();
+ });
+
+ m_fetchJob->start();
}
-void VerifyJavaInstall::fetchRuntimes(const QString &versionId, int requiredMajor)
+void VerifyJavaInstall::fetchRuntimes(const QString& versionId,
+ int requiredMajor)
{
- m_fetchData.clear();
- QString uid = "net.minecraft.java";
- QString url = QString("%1%2/%3.json").arg(BuildConfig.META_URL, uid, versionId);
-
- m_fetchJob = new NetJob(tr("Fetch Java runtime details"), APPLICATION->network());
- auto dl = Net::Download::makeByteArray(QUrl(url), &m_fetchData);
- m_fetchJob->addNetAction(dl);
-
- connect(m_fetchJob.get(), &NetJob::succeeded, this, [this, requiredMajor]() {
- auto fetchJob = std::move(m_fetchJob);
-
- QJsonDocument doc;
- try {
- doc = Json::requireDocument(m_fetchData);
- } catch (const Exception &e) {
- emitFailed(tr("Failed to parse Java runtime details: %1").arg(e.cause()));
- return;
- }
- if (!doc.isObject()) {
- emitFailed(tr("Failed to parse Java runtime details."));
- return;
- }
-
- auto allRuntimes = JavaDownload::parseRuntimes(doc.object());
- QString myOS = JavaDownload::currentRuntimeOS();
-
- // Filter for current platform
- for (const auto &rt : allRuntimes) {
- if (rt.runtimeOS == myOS) {
- emit logLine(tr("Downloading %1 (%2)...")
- .arg(rt.name, rt.version.toString()), MessageLevel::MeshMC);
- startDownload(rt, requiredMajor);
- return;
- }
- }
-
- emitFailed(tr("No Java %1 download available for your platform (%2). Please install it manually.")
- .arg(requiredMajor).arg(myOS));
- });
-
- connect(m_fetchJob.get(), &NetJob::failed, this, [this](QString reason) {
- emitFailed(tr("Failed to fetch Java runtime details: %1").arg(reason));
- m_fetchJob.reset();
- });
-
- m_fetchJob->start();
+ m_fetchData.clear();
+ QString uid = "net.minecraft.java";
+ QString url =
+ QString("%1%2/%3.json").arg(BuildConfig.META_URL, uid, versionId);
+
+ m_fetchJob =
+ new NetJob(tr("Fetch Java runtime details"), APPLICATION->network());
+ auto dl = Net::Download::makeByteArray(QUrl(url), &m_fetchData);
+ m_fetchJob->addNetAction(dl);
+
+ connect(m_fetchJob.get(), &NetJob::succeeded, this,
+ [this, requiredMajor]() {
+ auto fetchJob = std::move(m_fetchJob);
+
+ QJsonDocument doc;
+ try {
+ doc = Json::requireDocument(m_fetchData);
+ } catch (const Exception& e) {
+ emitFailed(tr("Failed to parse Java runtime details: %1")
+ .arg(e.cause()));
+ return;
+ }
+ if (!doc.isObject()) {
+ emitFailed(tr("Failed to parse Java runtime details."));
+ return;
+ }
+
+ auto allRuntimes = JavaDownload::parseRuntimes(doc.object());
+ QString myOS = JavaDownload::currentRuntimeOS();
+
+ // Filter for current platform
+ for (const auto& rt : allRuntimes) {
+ if (rt.runtimeOS == myOS) {
+ emit logLine(tr("Downloading %1 (%2)...")
+ .arg(rt.name, rt.version.toString()),
+ MessageLevel::MeshMC);
+ startDownload(rt, requiredMajor);
+ return;
+ }
+ }
+
+ emitFailed(tr("No Java %1 download available for your platform "
+ "(%2). Please install it manually.")
+ .arg(requiredMajor)
+ .arg(myOS));
+ });
+
+ connect(m_fetchJob.get(), &NetJob::failed, this, [this](QString reason) {
+ emitFailed(tr("Failed to fetch Java runtime details: %1").arg(reason));
+ m_fetchJob.reset();
+ });
+
+ m_fetchJob->start();
}
-void VerifyJavaInstall::startDownload(const JavaDownload::RuntimeEntry &runtime, int requiredMajor)
+void VerifyJavaInstall::startDownload(const JavaDownload::RuntimeEntry& runtime,
+ int requiredMajor)
{
- QString dirName = QString("%1-%2").arg(runtime.name, runtime.version.toString());
- QString targetDir = FS::PathCombine(javaInstallDir(), runtime.vendor, dirName);
-
- m_downloadTask = std::make_unique<JavaDownloadTask>(runtime, targetDir, this);
-
- connect(m_downloadTask.get(), &Task::succeeded, this, [this]() {
- QString javaPath = m_downloadTask->installedJavaPath();
-
- if (javaPath.isEmpty()) {
- emitFailed(tr("Java was downloaded but the binary could not be found."));
- return;
- }
-
- emit logLine(tr("Java downloaded and installed at: %1").arg(javaPath), MessageLevel::MeshMC);
- setJavaPathAndSucceed(javaPath);
- });
-
- connect(m_downloadTask.get(), &Task::failed, this, [this, requiredMajor](const QString &reason) {
- emitFailed(tr("Failed to download Java %1: %2").arg(requiredMajor).arg(reason));
- m_downloadTask.reset();
- });
-
- connect(m_downloadTask.get(), &Task::status, this, [this](const QString &status) {
- emit logLine(status, MessageLevel::MeshMC);
- });
-
- m_downloadTask->start();
+ QString dirName =
+ QString("%1-%2").arg(runtime.name, runtime.version.toString());
+ QString targetDir =
+ FS::PathCombine(javaInstallDir(), runtime.vendor, dirName);
+
+ m_downloadTask =
+ std::make_unique<JavaDownloadTask>(runtime, targetDir, this);
+
+ connect(m_downloadTask.get(), &Task::succeeded, this, [this]() {
+ QString javaPath = m_downloadTask->installedJavaPath();
+
+ if (javaPath.isEmpty()) {
+ emitFailed(
+ tr("Java was downloaded but the binary could not be found."));
+ return;
+ }
+
+ emit logLine(tr("Java downloaded and installed at: %1").arg(javaPath),
+ MessageLevel::MeshMC);
+ setJavaPathAndSucceed(javaPath);
+ });
+
+ connect(m_downloadTask.get(), &Task::failed, this,
+ [this, requiredMajor](const QString& reason) {
+ emitFailed(tr("Failed to download Java %1: %2")
+ .arg(requiredMajor)
+ .arg(reason));
+ m_downloadTask.reset();
+ });
+
+ connect(m_downloadTask.get(), &Task::status, this,
+ [this](const QString& status) {
+ emit logLine(status, MessageLevel::MeshMC);
+ });
+
+ m_downloadTask->start();
}
-void VerifyJavaInstall::setJavaPathAndSucceed(const QString &javaPath)
+void VerifyJavaInstall::setJavaPathAndSucceed(const QString& javaPath)
{
- auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance());
- // Set Java path override on the instance only, not globally
- m_inst->settings()->set("OverrideJavaLocation", true);
- m_inst->settings()->set("JavaPath", javaPath);
- emit logLine(tr("Java path set to: %1").arg(javaPath), MessageLevel::MeshMC);
- emitSucceeded();
+ auto m_inst =
+ std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance());
+ // Set Java path override on the instance only, not globally
+ m_inst->settings()->set("OverrideJavaLocation", true);
+ m_inst->settings()->set("JavaPath", javaPath);
+ emit logLine(tr("Java path set to: %1").arg(javaPath),
+ MessageLevel::MeshMC);
+ emitSucceeded();
}
#endif