summaryrefslogtreecommitdiff
path: root/archived/projt-launcher/launcher/java/services
diff options
context:
space:
mode:
authorMehmet Samet Duman <yongdohyun@projecttick.org>2026-04-02 18:51:45 +0300
committerMehmet Samet Duman <yongdohyun@projecttick.org>2026-04-02 18:51:45 +0300
commitd3261e64152397db2dca4d691a990c6bc2a6f4dd (patch)
treefac2f7be638651181a72453d714f0f96675c2b8b /archived/projt-launcher/launcher/java/services
parent31b9a8949ed0a288143e23bf739f2eb64fdc63be (diff)
downloadProject-Tick-d3261e64152397db2dca4d691a990c6bc2a6f4dd.tar.gz
Project-Tick-d3261e64152397db2dca4d691a990c6bc2a6f4dd.zip
NOISSUE add archived projects
Signed-off-by: Mehmet Samet Duman <yongdohyun@projecttick.org>
Diffstat (limited to 'archived/projt-launcher/launcher/java/services')
-rw-r--r--archived/projt-launcher/launcher/java/services/RuntimeCatalog.cpp205
-rw-r--r--archived/projt-launcher/launcher/java/services/RuntimeCatalog.hpp97
-rw-r--r--archived/projt-launcher/launcher/java/services/RuntimeEnvironment.cpp105
-rw-r--r--archived/projt-launcher/launcher/java/services/RuntimeEnvironment.hpp30
-rw-r--r--archived/projt-launcher/launcher/java/services/RuntimeProbeTask.cpp297
-rw-r--r--archived/projt-launcher/launcher/java/services/RuntimeProbeTask.hpp93
-rw-r--r--archived/projt-launcher/launcher/java/services/RuntimeScanner.cpp441
-rw-r--r--archived/projt-launcher/launcher/java/services/RuntimeScanner.hpp52
8 files changed, 1320 insertions, 0 deletions
diff --git a/archived/projt-launcher/launcher/java/services/RuntimeCatalog.cpp b/archived/projt-launcher/launcher/java/services/RuntimeCatalog.cpp
new file mode 100644
index 0000000000..f7fbc00602
--- /dev/null
+++ b/archived/projt-launcher/launcher/java/services/RuntimeCatalog.cpp
@@ -0,0 +1,205 @@
+// SPDX-License-Identifier: GPL-3.0-only
+// SPDX-FileCopyrightText: 2026 Project Tick
+// SPDX-FileContributor: Project Tick Team
+/*
+ * ProjT Launcher - Minecraft Launcher
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "java/services/RuntimeCatalog.hpp"
+
+#include <QtNetwork>
+#include <QtXml>
+
+#include <QDebug>
+#include <algorithm>
+
+#include "Application.h"
+#include "java/services/RuntimeScanner.hpp"
+#include "tasks/ConcurrentTask.h"
+
+namespace projt::java
+{
+ RuntimeCatalog::RuntimeCatalog(QObject* parent, Scope scope) : BaseVersionList(parent), m_scope(scope)
+ {}
+
+ Task::Ptr RuntimeCatalog::getLoadTask()
+ {
+ load();
+ return currentTask();
+ }
+
+ Task::Ptr RuntimeCatalog::currentTask() const
+ {
+ if (m_state == State::Loading)
+ {
+ return m_task;
+ }
+ return nullptr;
+ }
+
+ void RuntimeCatalog::load()
+ {
+ if (m_state != State::Loading)
+ {
+ m_state = State::Loading;
+ m_task.reset(new RuntimeCatalogTask(this, m_scope));
+ m_task->start();
+ }
+ }
+
+ const BaseVersion::Ptr RuntimeCatalog::at(int i) const
+ {
+ return m_entries.at(i);
+ }
+
+ bool RuntimeCatalog::isLoaded()
+ {
+ return m_state == State::Ready;
+ }
+
+ int RuntimeCatalog::count() const
+ {
+ return m_entries.count();
+ }
+
+ QVariant RuntimeCatalog::data(const QModelIndex& index, int role) const
+ {
+ if (!index.isValid())
+ return QVariant();
+
+ if (index.row() > count())
+ return QVariant();
+
+ auto runtime = std::dynamic_pointer_cast<RuntimeInstall>(m_entries[index.row()]);
+ switch (role)
+ {
+ case SortRole: return -index.row();
+ case VersionPointerRole: return QVariant::fromValue(m_entries[index.row()]);
+ case VersionIdRole: return runtime->descriptor();
+ case VersionRole: return runtime->version.toString();
+ case RecommendedRole: return false;
+ case PathRole: return runtime->path;
+ case CPUArchitectureRole: return runtime->arch;
+ default: return QVariant();
+ }
+ }
+
+ BaseVersionList::RoleList RuntimeCatalog::providesRoles() const
+ {
+ return { VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, PathRole, CPUArchitectureRole };
+ }
+
+ void RuntimeCatalog::updateListData(QList<BaseVersion::Ptr> versions)
+ {
+ beginResetModel();
+ m_entries = std::move(versions);
+ sortVersions();
+ endResetModel();
+ m_state = State::Ready;
+ m_task.reset();
+ }
+
+ static bool sortRuntimes(BaseVersion::Ptr left, BaseVersion::Ptr right)
+ {
+ auto rleft = std::dynamic_pointer_cast<RuntimeInstall>(right);
+ auto rright = std::dynamic_pointer_cast<RuntimeInstall>(left);
+ return (*rleft) > (*rright);
+ }
+
+ void RuntimeCatalog::sortVersions()
+ {
+ std::sort(m_entries.begin(), m_entries.end(), sortRuntimes);
+ }
+
+ RuntimeCatalogTask::RuntimeCatalogTask(RuntimeCatalog* catalog, RuntimeCatalog::Scope scope)
+ : Task(),
+ m_catalog(catalog),
+ m_scope(scope)
+ {}
+
+ void RuntimeCatalogTask::executeTask()
+ {
+ setStatus(tr("Detecting Java installations..."));
+
+ RuntimeScanner scanner;
+ QStringList candidatePaths = scanner.collectPaths(m_scope == RuntimeCatalog::Scope::ManagedOnly);
+
+ ConcurrentTask::Ptr job(
+ new ConcurrentTask("Runtime detection", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
+ m_job.reset(job);
+ connect(m_job.get(), &Task::finished, this, &RuntimeCatalogTask::probeFinished);
+ connect(m_job.get(), &Task::progress, this, &Task::setProgress);
+
+ qDebug() << "Probing the following runtime paths:";
+ int token = 0;
+ for (const auto& candidate : candidatePaths)
+ {
+ RuntimeProbeTask::ProbeSettings settings;
+ settings.binaryPath = candidate;
+ settings.token = token;
+ auto probe = new RuntimeProbeTask(settings);
+ connect(probe,
+ &RuntimeProbeTask::probeFinished,
+ this,
+ [this](const RuntimeProbeTask::ProbeReport& report) { m_results << report; });
+ job->addTask(Task::Ptr(probe));
+ token++;
+ }
+
+ m_job->start();
+ }
+
+ void RuntimeCatalogTask::probeFinished()
+ {
+ QList<RuntimeInstallPtr> candidates;
+ std::sort(m_results.begin(),
+ m_results.end(),
+ [](const RuntimeProbeTask::ProbeReport& a, const RuntimeProbeTask::ProbeReport& b)
+ { return a.token < b.token; });
+
+ qDebug() << "Found the following valid Java installations:";
+ for (const auto& result : m_results)
+ {
+ if (result.status == RuntimeProbeTask::ProbeReport::Status::Valid)
+ {
+ RuntimeInstallPtr runtime(new RuntimeInstall());
+ runtime->version = result.version;
+ runtime->arch = result.platformArch;
+ runtime->path = result.path;
+ runtime->vendor = result.vendor;
+ runtime->is_64bit = result.is_64bit;
+ runtime->managed = (m_scope == RuntimeCatalog::Scope::ManagedOnly);
+ candidates.append(runtime);
+
+ qDebug() << " " << runtime->version.toString() << runtime->arch << runtime->path;
+ }
+ }
+
+ QList<BaseVersion::Ptr> entries;
+ for (const auto& runtime : candidates)
+ {
+ BaseVersion::Ptr base = std::dynamic_pointer_cast<BaseVersion>(runtime);
+ if (base)
+ {
+ entries.append(runtime);
+ }
+ }
+
+ m_catalog->updateListData(entries);
+ emitSucceeded();
+ }
+} // namespace projt::java
diff --git a/archived/projt-launcher/launcher/java/services/RuntimeCatalog.hpp b/archived/projt-launcher/launcher/java/services/RuntimeCatalog.hpp
new file mode 100644
index 0000000000..ddb2fc53a4
--- /dev/null
+++ b/archived/projt-launcher/launcher/java/services/RuntimeCatalog.hpp
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-3.0-only
+// SPDX-FileCopyrightText: 2026 Project Tick
+// SPDX-FileContributor: Project Tick Team
+/*
+ * ProjT Launcher - Minecraft Launcher
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#pragma once
+
+#include <QAbstractListModel>
+#include <QObject>
+
+#include "BaseVersionList.h"
+#include "QObjectPtr.h"
+#include "java/core/RuntimeInstall.hpp"
+#include "java/services/RuntimeProbeTask.hpp"
+#include "tasks/Task.h"
+
+namespace projt::java
+{
+ class RuntimeCatalogTask;
+
+ class RuntimeCatalog : public BaseVersionList
+ {
+ Q_OBJECT
+ enum class State
+ {
+ Idle,
+ Loading,
+ Ready
+ };
+
+ public:
+ enum class Scope
+ {
+ All,
+ ManagedOnly
+ };
+
+ explicit RuntimeCatalog(QObject* parent = nullptr, Scope scope = Scope::All);
+
+ Task::Ptr getLoadTask() override;
+ bool isLoaded() override;
+ const BaseVersion::Ptr at(int i) const override;
+ int count() const override;
+ void sortVersions() override;
+
+ QVariant data(const QModelIndex& index, int role) const override;
+ RoleList providesRoles() const override;
+
+ public slots:
+ void updateListData(QList<BaseVersion::Ptr> versions) override;
+
+ private:
+ void load();
+ Task::Ptr currentTask() const;
+
+ State m_state = State::Idle;
+ shared_qobject_ptr<RuntimeCatalogTask> m_task;
+ QList<BaseVersion::Ptr> m_entries;
+ Scope m_scope;
+ };
+
+ class RuntimeCatalogTask : public Task
+ {
+ Q_OBJECT
+
+ public:
+ explicit RuntimeCatalogTask(RuntimeCatalog* catalog, RuntimeCatalog::Scope scope);
+ ~RuntimeCatalogTask() override = default;
+
+ protected:
+ void executeTask() override;
+
+ public slots:
+ void probeFinished();
+
+ private:
+ Task::Ptr m_job;
+ RuntimeCatalog* m_catalog = nullptr;
+ QList<RuntimeProbeTask::ProbeReport> m_results;
+ RuntimeCatalog::Scope m_scope;
+ };
+} // namespace projt::java
diff --git a/archived/projt-launcher/launcher/java/services/RuntimeEnvironment.cpp b/archived/projt-launcher/launcher/java/services/RuntimeEnvironment.cpp
new file mode 100644
index 0000000000..e6f027e462
--- /dev/null
+++ b/archived/projt-launcher/launcher/java/services/RuntimeEnvironment.cpp
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-3.0-only
+// SPDX-FileCopyrightText: 2026 Project Tick
+// SPDX-FileContributor: Project Tick Team
+/*
+ * ProjT Launcher - Minecraft Launcher
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "java/services/RuntimeEnvironment.hpp"
+
+#include <QDebug>
+
+#define IBUS "@im=ibus"
+
+namespace projt::java
+{
+ QString stripEnvEntries(const QString& name, const QString& value, const QString& remove)
+ {
+ QChar delimiter = ':';
+#ifdef Q_OS_WIN32
+ delimiter = ';';
+#endif
+
+ QStringList targetItems = value.split(delimiter);
+ QStringList toRemove = remove.split(delimiter);
+
+ for (const QString& item : toRemove)
+ {
+ if (!targetItems.removeOne(item))
+ {
+ qWarning() << "Entry" << item << "could not be stripped from variable" << name;
+ }
+ }
+ return targetItems.join(delimiter);
+ }
+
+ QProcessEnvironment buildCleanEnvironment()
+ {
+ QProcessEnvironment rawenv = QProcessEnvironment::systemEnvironment();
+ QProcessEnvironment env;
+
+ QStringList ignored = { "JAVA_ARGS", "CLASSPATH", "CONFIGPATH", "JAVA_HOME",
+ "JRE_HOME", "_JAVA_OPTIONS", "JAVA_OPTIONS", "JAVA_TOOL_OPTIONS" };
+
+ QStringList stripped = {
+#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
+ "LD_LIBRARY_PATH",
+ "LD_PRELOAD",
+#endif
+ "QT_PLUGIN_PATH",
+ "QT_FONTPATH"
+ };
+
+ for (const auto& key : rawenv.keys())
+ {
+ auto current = rawenv.value(key);
+ if (ignored.contains(key))
+ {
+ qDebug() << "Env: ignoring" << key << current;
+ continue;
+ }
+ if (key.startsWith("LAUNCHER_"))
+ {
+ qDebug() << "Env: ignoring" << key << current;
+ continue;
+ }
+ if (stripped.contains(key))
+ {
+ QString cleaned = stripEnvEntries(key, current, rawenv.value("LAUNCHER_" + key));
+ qDebug() << "Env: stripped" << key << current << "to" << cleaned;
+ current = cleaned;
+ }
+#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
+ if (key == "XMODIFIERS" && current.contains(IBUS))
+ {
+ QString saved = current;
+ current.replace(IBUS, "");
+ qDebug() << "Env: stripped" << IBUS << "from" << saved << ":" << current;
+ }
+#endif
+ env.insert(key, current);
+ }
+#ifdef Q_OS_LINUX
+ if (!env.contains("LD_LIBRARY_PATH"))
+ {
+ env.insert("LD_LIBRARY_PATH", "");
+ }
+#endif
+
+ return env;
+ }
+} // namespace projt::java \ No newline at end of file
diff --git a/archived/projt-launcher/launcher/java/services/RuntimeEnvironment.hpp b/archived/projt-launcher/launcher/java/services/RuntimeEnvironment.hpp
new file mode 100644
index 0000000000..7101fc51ad
--- /dev/null
+++ b/archived/projt-launcher/launcher/java/services/RuntimeEnvironment.hpp
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-3.0-only
+// SPDX-FileCopyrightText: 2026 Project Tick
+// SPDX-FileContributor: Project Tick Team
+/*
+ * ProjT Launcher - Minecraft Launcher
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#pragma once
+
+#include <QProcessEnvironment>
+#include <QString>
+
+namespace projt::java
+{
+ QString stripEnvEntries(const QString& name, const QString& value, const QString& remove);
+ QProcessEnvironment buildCleanEnvironment();
+} // namespace projt::java \ No newline at end of file
diff --git a/archived/projt-launcher/launcher/java/services/RuntimeProbeTask.cpp b/archived/projt-launcher/launcher/java/services/RuntimeProbeTask.cpp
new file mode 100644
index 0000000000..b13b8d7ece
--- /dev/null
+++ b/archived/projt-launcher/launcher/java/services/RuntimeProbeTask.cpp
@@ -0,0 +1,297 @@
+// SPDX-License-Identifier: GPL-3.0-only
+// SPDX-FileCopyrightText: 2026 Project Tick
+// SPDX-FileContributor: Project Tick Team
+/*
+ * ProjT Launcher - Minecraft Launcher
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "java/services/RuntimeProbeTask.hpp"
+
+#include <QDebug>
+#include <QFile>
+#include <QFileInfo>
+#include <QMap>
+#include <utility>
+
+#include "Application.h"
+#include "Commandline.h"
+#include "FileSystem.h"
+#include "java/services/RuntimeEnvironment.hpp"
+
+namespace projt::java
+{
+ RuntimeProbeTask::RuntimeProbeTask(ProbeSettings settings) : Task(), m_settings(std::move(settings))
+ {}
+
+ QString RuntimeProbeTask::probeJarPath()
+ {
+ return APPLICATION->getJarPath("JavaCheck.jar");
+ }
+
+ void RuntimeProbeTask::executeTask()
+ {
+ QString checkerJar = probeJarPath();
+
+ if (checkerJar.isEmpty())
+ {
+ qDebug() << "Java checker library could not be found. Please check your installation.";
+ ProbeReport report;
+ report.path = m_settings.binaryPath;
+ report.token = m_settings.token;
+ report.status = ProbeReport::Status::Errored;
+ emit probeFinished(report);
+ emitSucceeded();
+ return;
+ }
+#ifdef Q_OS_WIN
+ checkerJar = FS::getPathNameInLocal8bit(checkerJar);
+#endif
+
+ QStringList args;
+ process.reset(new QProcess());
+ if (!m_settings.extraArgs.isEmpty())
+ {
+ args.append(Commandline::splitArgs(m_settings.extraArgs));
+ }
+ if (m_settings.minMem != 0)
+ {
+ args << QString("-Xms%1m").arg(m_settings.minMem);
+ }
+ if (m_settings.maxMem != 0)
+ {
+ args << QString("-Xmx%1m").arg(m_settings.maxMem);
+ }
+ if (m_settings.permGen != 64 && m_settings.permGen != 0)
+ {
+ args << QString("-XX:PermSize=%1m").arg(m_settings.permGen);
+ }
+
+ args.append({ "-jar", checkerJar, "--list", "os.arch", "java.version", "java.vendor" });
+ process->setArguments(args);
+ process->setProgram(m_settings.binaryPath);
+ process->setProcessChannelMode(QProcess::SeparateChannels);
+ process->setProcessEnvironment(buildCleanEnvironment());
+ qDebug() << "Running runtime probe:" << m_settings.binaryPath << args.join(" ");
+
+ connect(process.get(), &QProcess::finished, this, &RuntimeProbeTask::finished);
+ connect(process.get(), &QProcess::errorOccurred, this, &RuntimeProbeTask::error);
+ connect(process.get(), &QProcess::readyReadStandardOutput, this, &RuntimeProbeTask::stdoutReady);
+ connect(process.get(), &QProcess::readyReadStandardError, this, &RuntimeProbeTask::stderrReady);
+ connect(&killTimer, &QTimer::timeout, this, &RuntimeProbeTask::timeout);
+ killTimer.setSingleShot(true);
+ killTimer.start(15000);
+ process->start();
+ }
+
+ void RuntimeProbeTask::stdoutReady()
+ {
+ QByteArray data = process->readAllStandardOutput();
+ QString added = QString::fromLocal8Bit(data);
+ added.remove('\r');
+ m_stdout += added;
+ }
+
+ void RuntimeProbeTask::stderrReady()
+ {
+ QByteArray data = process->readAllStandardError();
+ QString added = QString::fromLocal8Bit(data);
+ added.remove('\r');
+ m_stderr += added;
+ }
+
+ void RuntimeProbeTask::finished(int exitcode, QProcess::ExitStatus status)
+ {
+ killTimer.stop();
+ QProcessPtr activeProcess = process;
+ process.reset();
+
+ ProbeReport report;
+ report.path = m_settings.binaryPath;
+ report.token = m_settings.token;
+ report.stderrLog = m_stderr;
+ report.stdoutLog = m_stdout;
+ qDebug() << "STDOUT" << m_stdout;
+ if (!m_stderr.isEmpty())
+ {
+ qWarning() << "STDERR" << m_stderr;
+ }
+ qDebug() << "Runtime probe finished with status" << status << "exit code" << exitcode;
+
+ if (status == QProcess::CrashExit)
+ {
+ report.status = ProbeReport::Status::Errored;
+ emit probeFinished(report);
+ emitSucceeded();
+ return;
+ }
+
+ auto parseBlocks = [](const QString& stdoutText)
+ {
+ QList<QMap<QString, QString>> blocks;
+ QMap<QString, QString> current;
+ const auto lines = stdoutText.split('\n');
+ for (QString line : lines)
+ {
+ line = line.trimmed();
+ if (line.isEmpty())
+ {
+ if (!current.isEmpty())
+ {
+ blocks.append(current);
+ current.clear();
+ }
+ continue;
+ }
+ if (line.contains("/bedrock/strata"))
+ {
+ continue;
+ }
+ const auto eq = line.indexOf('=');
+ if (eq <= 0)
+ {
+ continue;
+ }
+ const auto key = line.left(eq).trimmed();
+ const auto value = line.mid(eq + 1).trimmed();
+ if (!key.isEmpty() && !value.isEmpty())
+ {
+ current.insert(key, value);
+ }
+ }
+ if (!current.isEmpty())
+ {
+ blocks.append(current);
+ }
+ return blocks;
+ };
+
+ auto resolvePath = [](const QString& path)
+ {
+ if (path.isEmpty())
+ {
+ return QString();
+ }
+ auto resolved = FS::ResolveExecutable(path);
+ QString chosen = resolved.isEmpty() ? path : resolved;
+ QFileInfo info(chosen);
+ if (info.exists())
+ {
+ return info.canonicalFilePath();
+ }
+ return chosen;
+ };
+
+ auto matchesProbeTarget = [](const QString& targetPath, const QString& candidatePath)
+ {
+ if (targetPath.isEmpty() || candidatePath.isEmpty())
+ {
+ return false;
+ }
+ if (candidatePath == targetPath)
+ {
+ return true;
+ }
+#ifdef Q_OS_WIN
+ const QFileInfo targetInfo(targetPath);
+ const QFileInfo candidateInfo(candidatePath);
+ auto normalizeJavaName = [](QString fileName)
+ {
+ fileName = fileName.toLower();
+ if (fileName == "javaw.exe")
+ {
+ return QStringLiteral("java.exe");
+ }
+ return fileName;
+ };
+
+ return targetInfo.absolutePath().compare(candidateInfo.absolutePath(), Qt::CaseInsensitive) == 0
+ && normalizeJavaName(targetInfo.fileName()) == normalizeJavaName(candidateInfo.fileName());
+#else
+ return false;
+#endif
+ };
+
+ const auto targetPath = resolvePath(m_settings.binaryPath);
+ QMap<QString, QString> results;
+ auto blocks = parseBlocks(m_stdout);
+ for (const auto& block : blocks)
+ {
+ const auto candidatePath = resolvePath(block.value("java.path"));
+ if (matchesProbeTarget(targetPath, candidatePath))
+ {
+ results = block;
+ break;
+ }
+ }
+ if (results.isEmpty() && !blocks.isEmpty() && targetPath.isEmpty())
+ {
+ results = blocks.first();
+ }
+
+ if (results.isEmpty() || !results.contains("os.arch") || !results.contains("java.version")
+ || !results.contains("java.vendor"))
+ {
+ report.status = ProbeReport::Status::InvalidData;
+ emit probeFinished(report);
+ emitSucceeded();
+ return;
+ }
+
+ auto osArch = results["os.arch"];
+ auto javaVersion = results["java.version"];
+ auto javaVendor = results["java.vendor"];
+ bool is64 = osArch == "x86_64" || osArch == "amd64" || osArch == "aarch64" || osArch == "arm64"
+ || osArch == "riscv64" || osArch == "ppc64le" || osArch == "ppc64";
+
+ report.status = ProbeReport::Status::Valid;
+ report.is_64bit = is64;
+ report.platformTag = is64 ? "64" : "32";
+ report.platformArch = osArch;
+ report.version = javaVersion;
+ report.vendor = javaVendor;
+ qDebug() << "Runtime probe succeeded.";
+ emit probeFinished(report);
+ emitSucceeded();
+ }
+
+ void RuntimeProbeTask::error(QProcess::ProcessError err)
+ {
+ if (err == QProcess::FailedToStart)
+ {
+ qDebug() << "Runtime probe failed to start.";
+ qDebug() << "Process environment:";
+ qDebug() << process->environment();
+ qDebug() << "Native environment:";
+ qDebug() << QProcessEnvironment::systemEnvironment().toStringList();
+ killTimer.stop();
+ ProbeReport report;
+ report.path = m_settings.binaryPath;
+ report.token = m_settings.token;
+ emit probeFinished(report);
+ }
+ emitSucceeded();
+ }
+
+ void RuntimeProbeTask::timeout()
+ {
+ if (process)
+ {
+ qDebug() << "Runtime probe has been killed by timeout.";
+ process->kill();
+ }
+ }
+} // namespace projt::java
diff --git a/archived/projt-launcher/launcher/java/services/RuntimeProbeTask.hpp b/archived/projt-launcher/launcher/java/services/RuntimeProbeTask.hpp
new file mode 100644
index 0000000000..c2b78a4c57
--- /dev/null
+++ b/archived/projt-launcher/launcher/java/services/RuntimeProbeTask.hpp
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-3.0-only
+// SPDX-FileCopyrightText: 2026 Project Tick
+// SPDX-FileContributor: Project Tick Team
+/*
+ * ProjT Launcher - Minecraft Launcher
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#pragma once
+
+#include <QProcess>
+#include <QTimer>
+
+#include "QObjectPtr.h"
+#include "java/core/RuntimeVersion.hpp"
+#include "tasks/Task.h"
+
+namespace projt::java
+{
+ class RuntimeProbeTask : public Task
+ {
+ Q_OBJECT
+ public:
+ using QProcessPtr = shared_qobject_ptr<QProcess>;
+ using Ptr = shared_qobject_ptr<RuntimeProbeTask>;
+
+ struct ProbeSettings
+ {
+ QString binaryPath;
+ QString extraArgs;
+ int minMem = 0;
+ int maxMem = 0;
+ int permGen = 0;
+ int token = 0;
+ };
+
+ struct ProbeReport
+ {
+ QString path;
+ int token = 0;
+ QString platformTag;
+ QString platformArch;
+ RuntimeVersion version;
+ QString vendor;
+ QString stdoutLog;
+ QString stderrLog;
+ bool is_64bit = false;
+ enum class Status
+ {
+ Errored,
+ InvalidData,
+ Valid
+ } status = Status::Errored;
+ };
+
+ explicit RuntimeProbeTask(ProbeSettings settings);
+
+ static QString probeJarPath();
+
+ signals:
+ void probeFinished(const ProbeReport& report);
+
+ protected:
+ void executeTask() override;
+
+ private:
+ QProcessPtr process;
+ QTimer killTimer;
+ QString m_stdout;
+ QString m_stderr;
+
+ ProbeSettings m_settings;
+
+ private slots:
+ void timeout();
+ void finished(int exitcode, QProcess::ExitStatus status);
+ void error(QProcess::ProcessError err);
+ void stdoutReady();
+ void stderrReady();
+ };
+} // namespace projt::java \ No newline at end of file
diff --git a/archived/projt-launcher/launcher/java/services/RuntimeScanner.cpp b/archived/projt-launcher/launcher/java/services/RuntimeScanner.cpp
new file mode 100644
index 0000000000..5aed027d78
--- /dev/null
+++ b/archived/projt-launcher/launcher/java/services/RuntimeScanner.cpp
@@ -0,0 +1,441 @@
+// SPDX-License-Identifier: GPL-3.0-only
+// SPDX-FileCopyrightText: 2026 Project Tick
+// SPDX-FileContributor: Project Tick Team
+/*
+ * ProjT Launcher - Minecraft Launcher
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "java/services/RuntimeScanner.hpp"
+
+#include <QDir>
+#include <QFileInfo>
+#include <QProcessEnvironment>
+#include <QStandardPaths>
+#include <functional>
+#include <vector>
+
+#include "Application.h"
+#include "FileSystem.h"
+
+namespace projt::java
+{
+ static QStringList resolveExecutables(const QStringList& candidates)
+ {
+ QStringList resolved;
+ resolved.reserve(candidates.size());
+ for (const auto& candidate : candidates)
+ {
+ if (candidate.isEmpty())
+ {
+ continue;
+ }
+ if (QDir::isRelativePath(candidate))
+ {
+ const auto found = QStandardPaths::findExecutable(candidate);
+ if (!found.isEmpty())
+ {
+ resolved.append(QFileInfo(found).canonicalFilePath());
+ }
+ continue;
+ }
+ const QFileInfo info(candidate);
+ if (info.exists() && info.isFile())
+ {
+ resolved.append(info.canonicalFilePath());
+ }
+ }
+ resolved.removeDuplicates();
+ return resolved;
+ }
+
+ QString RuntimeScanner::executableName()
+ {
+#if defined(Q_OS_WIN32)
+ return QStringLiteral("javaw.exe");
+#else
+ return QStringLiteral("java");
+#endif
+ }
+
+ QStringList RuntimeScanner::appendEnvPaths(const QStringList& base) const
+ {
+ QStringList expanded = base;
+ auto env = qEnvironmentVariable("PROJTLAUNCHER_JAVA_PATHS");
+#if defined(Q_OS_WIN32)
+ QStringList javaPaths = env.replace("\\", "/").split(QLatin1String(";"), Qt::SkipEmptyParts);
+
+ auto envPath = qEnvironmentVariable("PATH");
+ QStringList pathEntries = envPath.replace("\\", "/").split(QLatin1String(";"), Qt::SkipEmptyParts);
+ for (const QString& entry : pathEntries)
+ {
+ javaPaths.append(entry + "/" + executableName());
+ }
+#else
+ QStringList javaPaths = env.split(QLatin1String(":"), Qt::SkipEmptyParts);
+#endif
+ for (const QString& entry : javaPaths)
+ {
+ expanded.append(entry);
+ }
+ return expanded;
+ }
+
+#if defined(Q_OS_WIN32)
+ QStringList RuntimeScanner::collectRegistryPaths(DWORD keyType,
+ const QString& keyName,
+ const QString& valueName,
+ const QString& suffix) const
+ {
+ QStringList entries;
+
+ for (HKEY baseRegistry : { HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE })
+ {
+ HKEY rootKey;
+ if (RegOpenKeyExW(baseRegistry,
+ keyName.toStdWString().c_str(),
+ 0,
+ KEY_READ | keyType | KEY_ENUMERATE_SUB_KEYS,
+ &rootKey)
+ != ERROR_SUCCESS)
+ {
+ continue;
+ }
+
+ DWORD subKeyCount = 0;
+ RegQueryInfoKeyW(rootKey, NULL, NULL, NULL, &subKeyCount, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+ if (subKeyCount > 0)
+ {
+ for (DWORD i = 0; i < subKeyCount; i++)
+ {
+ WCHAR subKeyName[255];
+ DWORD subKeyNameSize = 255;
+ auto retCode = RegEnumKeyExW(rootKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL, NULL);
+ if (retCode != ERROR_SUCCESS)
+ {
+ continue;
+ }
+
+ QString versionKey = keyName + "\\" + QString::fromWCharArray(subKeyName) + suffix;
+ HKEY versionHandle;
+ if (RegOpenKeyExW(baseRegistry,
+ versionKey.toStdWString().c_str(),
+ 0,
+ KEY_READ | keyType,
+ &versionHandle)
+ != ERROR_SUCCESS)
+ {
+ continue;
+ }
+
+ DWORD valueSize = 0;
+ if (RegQueryValueExW(versionHandle, valueName.toStdWString().c_str(), NULL, NULL, NULL, &valueSize)
+ == ERROR_SUCCESS)
+ {
+ std::vector<WCHAR> buffer(valueSize / sizeof(WCHAR) + 1, 0);
+ RegQueryValueExW(versionHandle,
+ valueName.toStdWString().c_str(),
+ NULL,
+ NULL,
+ reinterpret_cast<BYTE*>(buffer.data()),
+ &valueSize);
+ QString javaHome = QString::fromWCharArray(buffer.data());
+ QString javaPath = QDir(FS::PathCombine(javaHome, "bin")).absoluteFilePath(executableName());
+ entries.append(javaPath);
+ }
+ RegCloseKey(versionHandle);
+ }
+ }
+
+ RegCloseKey(rootKey);
+ }
+ return entries;
+ }
+#endif
+
+ QStringList RuntimeScanner::collectManagedBundles()
+ {
+ QStringList bundles;
+
+ auto addBundlePaths = [&bundles](const QString& prefix)
+ {
+ bundles.append(FS::PathCombine(prefix, "jre", "bin", RuntimeScanner::executableName()));
+ bundles.append(FS::PathCombine(prefix, "bin", RuntimeScanner::executableName()));
+ bundles.append(FS::PathCombine(prefix, RuntimeScanner::executableName()));
+ };
+
+ auto scanJavaDir = [&addBundlePaths](const QString& dirPath)
+ {
+ QDir dir(dirPath);
+ if (!dir.exists())
+ {
+ return;
+ }
+ auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
+ for (const auto& entry : entries)
+ {
+ addBundlePaths(entry.canonicalFilePath());
+ }
+ };
+
+ scanJavaDir(APPLICATION->javaPath());
+
+ return bundles;
+ }
+
+ QStringList RuntimeScanner::collectMinecraftBundles()
+ {
+ QStringList processPaths;
+#if defined(Q_OS_MACOS)
+ processPaths << FS::PathCombine(QDir::homePath(),
+ FS::PathCombine("Library", "Application Support", "minecraft", "runtime"));
+#elif defined(Q_OS_WIN32)
+ auto appDataPath = QProcessEnvironment::systemEnvironment().value("APPDATA", "");
+ processPaths << FS::PathCombine(QFileInfo(appDataPath).absoluteFilePath(), ".minecraft", "runtime");
+
+ auto localAppDataPath = QProcessEnvironment::systemEnvironment().value("LOCALAPPDATA", "");
+ auto minecraftStorePath = FS::PathCombine(QFileInfo(localAppDataPath).absoluteFilePath(),
+ "Packages",
+ "Microsoft.4297127D64EC6_8wekyb3d8bbwe");
+ processPaths << FS::PathCombine(minecraftStorePath, "LocalCache", "Local", "runtime");
+#else
+ processPaths << FS::PathCombine(QDir::homePath(), ".minecraft", "runtime");
+#endif
+
+ QStringList results;
+ while (!processPaths.isEmpty())
+ {
+ auto dirPath = processPaths.takeFirst();
+ QDir dir(dirPath);
+ if (!dir.exists())
+ {
+ continue;
+ }
+ auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
+ bool foundBin = false;
+ for (const auto& entry : entries)
+ {
+ if (entry.baseName() == "bin")
+ {
+ results.append(FS::PathCombine(entry.canonicalFilePath(), RuntimeScanner::executableName()));
+ foundBin = true;
+ break;
+ }
+ }
+ if (!foundBin)
+ {
+ for (const auto& entry : entries)
+ {
+ processPaths << entry.canonicalFilePath();
+ }
+ }
+ }
+ return results;
+ }
+
+ QStringList RuntimeScanner::collectPaths(bool managedOnly) const
+ {
+ QStringList candidates;
+ if (managedOnly)
+ {
+ candidates = collectManagedBundles();
+ return resolveExecutables(candidates);
+ }
+
+#if defined(Q_OS_WIN32)
+ candidates.append(
+ collectRegistryPaths(KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment", "JavaHome", ""));
+ candidates.append(
+ collectRegistryPaths(KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Development Kit", "JavaHome", ""));
+ candidates.append(
+ collectRegistryPaths(KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment", "JavaHome", ""));
+ candidates.append(
+ collectRegistryPaths(KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Development Kit", "JavaHome", ""));
+
+ candidates.append(collectRegistryPaths(KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\JRE", "JavaHome", ""));
+ candidates.append(collectRegistryPaths(KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\JDK", "JavaHome", ""));
+ candidates.append(collectRegistryPaths(KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\JRE", "JavaHome", ""));
+ candidates.append(collectRegistryPaths(KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\JDK", "JavaHome", ""));
+
+ candidates.append(
+ collectRegistryPaths(KEY_WOW64_32KEY, "SOFTWARE\\AdoptOpenJDK\\JRE", "Path", "\\hotspot\\MSI"));
+ candidates.append(
+ collectRegistryPaths(KEY_WOW64_64KEY, "SOFTWARE\\AdoptOpenJDK\\JRE", "Path", "\\hotspot\\MSI"));
+ candidates.append(
+ collectRegistryPaths(KEY_WOW64_32KEY, "SOFTWARE\\AdoptOpenJDK\\JDK", "Path", "\\hotspot\\MSI"));
+ candidates.append(
+ collectRegistryPaths(KEY_WOW64_64KEY, "SOFTWARE\\AdoptOpenJDK\\JDK", "Path", "\\hotspot\\MSI"));
+
+ candidates.append(
+ collectRegistryPaths(KEY_WOW64_32KEY, "SOFTWARE\\Eclipse Foundation\\JDK", "Path", "\\hotspot\\MSI"));
+ candidates.append(
+ collectRegistryPaths(KEY_WOW64_64KEY, "SOFTWARE\\Eclipse Foundation\\JDK", "Path", "\\hotspot\\MSI"));
+
+ candidates.append(
+ collectRegistryPaths(KEY_WOW64_32KEY, "SOFTWARE\\Eclipse Adoptium\\JRE", "Path", "\\hotspot\\MSI"));
+ candidates.append(
+ collectRegistryPaths(KEY_WOW64_64KEY, "SOFTWARE\\Eclipse Adoptium\\JRE", "Path", "\\hotspot\\MSI"));
+ candidates.append(
+ collectRegistryPaths(KEY_WOW64_32KEY, "SOFTWARE\\Eclipse Adoptium\\JDK", "Path", "\\hotspot\\MSI"));
+ candidates.append(
+ collectRegistryPaths(KEY_WOW64_64KEY, "SOFTWARE\\Eclipse Adoptium\\JDK", "Path", "\\hotspot\\MSI"));
+
+ candidates.append(collectRegistryPaths(KEY_WOW64_32KEY, "SOFTWARE\\Semeru\\JRE", "Path", "\\openj9\\MSI"));
+ candidates.append(collectRegistryPaths(KEY_WOW64_64KEY, "SOFTWARE\\Semeru\\JRE", "Path", "\\openj9\\MSI"));
+ candidates.append(collectRegistryPaths(KEY_WOW64_32KEY, "SOFTWARE\\Semeru\\JDK", "Path", "\\openj9\\MSI"));
+ candidates.append(collectRegistryPaths(KEY_WOW64_64KEY, "SOFTWARE\\Semeru\\JDK", "Path", "\\openj9\\MSI"));
+
+ candidates.append(collectRegistryPaths(KEY_WOW64_64KEY, "SOFTWARE\\Microsoft\\JDK", "Path", "\\hotspot\\MSI"));
+
+ candidates.append(
+ collectRegistryPaths(KEY_WOW64_64KEY, "SOFTWARE\\Azul Systems\\Zulu", "InstallationPath", ""));
+ candidates.append(
+ collectRegistryPaths(KEY_WOW64_32KEY, "SOFTWARE\\Azul Systems\\Zulu", "InstallationPath", ""));
+
+ candidates.append(
+ collectRegistryPaths(KEY_WOW64_64KEY, "SOFTWARE\\BellSoft\\Liberica", "InstallationPath", ""));
+ candidates.append(
+ collectRegistryPaths(KEY_WOW64_32KEY, "SOFTWARE\\BellSoft\\Liberica", "InstallationPath", ""));
+
+ candidates.append("C:/Program Files/Java/jre8/bin/javaw.exe");
+ candidates.append("C:/Program Files/Java/jre7/bin/javaw.exe");
+ candidates.append("C:/Program Files/Java/jre6/bin/javaw.exe");
+ candidates.append("C:/Program Files (x86)/Java/jre8/bin/javaw.exe");
+ candidates.append("C:/Program Files (x86)/Java/jre7/bin/javaw.exe");
+ candidates.append("C:/Program Files (x86)/Java/jre6/bin/javaw.exe");
+
+ candidates.append("javaw");
+#elif defined(Q_OS_MAC)
+ candidates.append("java");
+ candidates.append(
+ "/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/bin/java");
+ candidates.append("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java");
+ candidates.append("/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java");
+
+ QDir libraryJVMDir("/Library/Java/JavaVirtualMachines/");
+ QStringList libraryJVMJavas = libraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
+ for (const QString& java : libraryJVMJavas)
+ {
+ candidates.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
+ candidates.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/jre/bin/java");
+ }
+
+ QDir systemLibraryJVMDir("/System/Library/Java/JavaVirtualMachines/");
+ QStringList systemLibraryJVMJavas = systemLibraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
+ for (const QString& java : systemLibraryJVMJavas)
+ {
+ candidates.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
+ candidates.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java");
+ }
+
+ auto home = qEnvironmentVariable("HOME");
+
+ QString sdkmanDir = qEnvironmentVariable("SDKMAN_DIR", FS::PathCombine(home, ".sdkman"));
+ QDir sdkmanJavaDir(FS::PathCombine(sdkmanDir, "candidates/java"));
+ QStringList sdkmanJavas = sdkmanJavaDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
+ for (const QString& java : sdkmanJavas)
+ {
+ candidates.append(sdkmanJavaDir.absolutePath() + "/" + java + "/bin/java");
+ }
+
+ QString asdfDataDir = qEnvironmentVariable("ASDF_DATA_DIR", FS::PathCombine(home, ".asdf"));
+ QDir asdfJavaDir(FS::PathCombine(asdfDataDir, "installs/java"));
+ QStringList asdfJavas = asdfJavaDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
+ for (const QString& java : asdfJavas)
+ {
+ candidates.append(asdfJavaDir.absolutePath() + "/" + java + "/bin/java");
+ }
+
+ QDir userLibraryJVMDir(FS::PathCombine(home, "Library/Java/JavaVirtualMachines/"));
+ QStringList userLibraryJVMJavas = userLibraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
+ for (const QString& java : userLibraryJVMJavas)
+ {
+ candidates.append(userLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
+ candidates.append(userLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java");
+ }
+#elif defined(Q_OS_LINUX) || defined(Q_OS_OPENBSD) || defined(Q_OS_FREEBSD)
+ candidates.append("java");
+ auto scanJavaDir =
+ [&candidates](
+ const QString& dirPath,
+ const std::function<bool(const QFileInfo&)>& filter = [](const QFileInfo&) { return true; })
+ {
+ QDir dir(dirPath);
+ if (!dir.exists())
+ return;
+ auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
+ for (const auto& entry : entries)
+ {
+ if (!filter(entry))
+ continue;
+ QString prefix = entry.canonicalFilePath();
+ candidates.append(FS::PathCombine(prefix, "jre/bin/java"));
+ candidates.append(FS::PathCombine(prefix, "bin/java"));
+ }
+ };
+ auto snap = qEnvironmentVariable("SNAP");
+ auto scanJavaDirs = [scanJavaDir, snap](const QString& dirPath)
+ {
+ scanJavaDir(dirPath);
+ if (!snap.isNull())
+ {
+ scanJavaDir(snap + dirPath);
+ }
+ };
+#if defined(Q_OS_LINUX)
+ scanJavaDirs("/usr/java");
+ scanJavaDirs("/usr/lib/jvm");
+ scanJavaDirs("/usr/lib64/jvm");
+ scanJavaDirs("/usr/lib32/jvm");
+ auto gentooFilter = [](const QFileInfo& info)
+ {
+ QString fileName = info.fileName();
+ return fileName.startsWith("openjdk-") || fileName.startsWith("openj9-");
+ };
+ auto aoscFilter = [](const QFileInfo& info)
+ {
+ QString fileName = info.fileName();
+ return fileName == "java" || fileName.startsWith("java-");
+ };
+ scanJavaDir("/usr/lib64", gentooFilter);
+ scanJavaDir("/usr/lib", gentooFilter);
+ scanJavaDir("/opt", gentooFilter);
+ scanJavaDir("/usr/lib", aoscFilter);
+ scanJavaDirs("java");
+ scanJavaDirs("/opt/jdk");
+ scanJavaDirs("/opt/jdks");
+ scanJavaDirs("/opt/ibm");
+ scanJavaDirs("/app/jdk");
+#elif defined(Q_OS_OPENBSD) || defined(Q_OS_FREEBSD)
+ scanJavaDirs("/usr/local");
+#endif
+ auto home = qEnvironmentVariable("HOME");
+ scanJavaDirs(FS::PathCombine(home, ".jdks"));
+ QString sdkmanDir = qEnvironmentVariable("SDKMAN_DIR", FS::PathCombine(home, ".sdkman"));
+ scanJavaDirs(FS::PathCombine(sdkmanDir, "candidates/java"));
+ QString asdfDataDir = qEnvironmentVariable("ASDF_DATA_DIR", FS::PathCombine(home, ".asdf"));
+ scanJavaDirs(FS::PathCombine(asdfDataDir, "installs/java"));
+ scanJavaDirs(FS::PathCombine(home, ".gradle/jdks"));
+#else
+ candidates.append("java");
+#endif
+
+ candidates.append(collectMinecraftBundles());
+ candidates.append(collectManagedBundles());
+ candidates = appendEnvPaths(candidates);
+ return resolveExecutables(candidates);
+ }
+} // namespace projt::java
diff --git a/archived/projt-launcher/launcher/java/services/RuntimeScanner.hpp b/archived/projt-launcher/launcher/java/services/RuntimeScanner.hpp
new file mode 100644
index 0000000000..09000c914d
--- /dev/null
+++ b/archived/projt-launcher/launcher/java/services/RuntimeScanner.hpp
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-3.0-only
+// SPDX-FileCopyrightText: 2026 Project Tick
+// SPDX-FileContributor: Project Tick Team
+/*
+ * ProjT Launcher - Minecraft Launcher
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#pragma once
+
+#include <QStringList>
+
+#if defined(Q_OS_WIN32)
+#include <windows.h>
+#endif
+
+namespace projt::java
+{
+ class RuntimeScanner
+ {
+ public:
+ RuntimeScanner() = default;
+
+ QStringList collectPaths(bool managedOnly) const;
+
+ static QString executableName();
+ static QStringList collectManagedBundles();
+ static QStringList collectMinecraftBundles();
+
+ private:
+ QStringList appendEnvPaths(const QStringList& base) const;
+
+#if defined(Q_OS_WIN32)
+ QStringList collectRegistryPaths(DWORD keyType,
+ const QString& keyName,
+ const QString& valueName,
+ const QString& suffix) const;
+#endif
+ };
+} // namespace projt::java