summaryrefslogtreecommitdiff
path: root/archived/projt-launcher/launcher/java
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
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')
-rw-r--r--archived/projt-launcher/launcher/java/core/RuntimeInstall.cpp86
-rw-r--r--archived/projt-launcher/launcher/java/core/RuntimeInstall.hpp70
-rw-r--r--archived/projt-launcher/launcher/java/core/RuntimePackage.cpp142
-rw-r--r--archived/projt-launcher/launcher/java/core/RuntimePackage.hpp82
-rw-r--r--archived/projt-launcher/launcher/java/core/RuntimeVersion.cpp212
-rw-r--r--archived/projt-launcher/launcher/java/core/RuntimeVersion.hpp87
-rw-r--r--archived/projt-launcher/launcher/java/download/RuntimeArchiveTask.cpp174
-rw-r--r--archived/projt-launcher/launcher/java/download/RuntimeArchiveTask.hpp55
-rw-r--r--archived/projt-launcher/launcher/java/download/RuntimeLinkTask.cpp101
-rw-r--r--archived/projt-launcher/launcher/java/download/RuntimeLinkTask.hpp40
-rw-r--r--archived/projt-launcher/launcher/java/download/RuntimeManifestTask.cpp184
-rw-r--r--archived/projt-launcher/launcher/java/download/RuntimeManifestTask.hpp55
-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
20 files changed, 2608 insertions, 0 deletions
diff --git a/archived/projt-launcher/launcher/java/core/RuntimeInstall.cpp b/archived/projt-launcher/launcher/java/core/RuntimeInstall.cpp
new file mode 100644
index 0000000000..4eff57d0fe
--- /dev/null
+++ b/archived/projt-launcher/launcher/java/core/RuntimeInstall.cpp
@@ -0,0 +1,86 @@
+// 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/core/RuntimeInstall.hpp"
+
+#include "BaseVersion.h"
+#include "StringUtils.h"
+
+#include <typeinfo>
+
+namespace projt::java
+{
+ bool RuntimeInstall::operator<(const RuntimeInstall& rhs) const
+ {
+ if (version < rhs.version)
+ {
+ return true;
+ }
+ if (version > rhs.version)
+ {
+ return false;
+ }
+ if (is_64bit != rhs.is_64bit)
+ {
+ return rhs.is_64bit;
+ }
+ auto archCompare = StringUtils::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive);
+ if (archCompare != 0)
+ {
+ return archCompare < 0;
+ }
+ return StringUtils::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0;
+ }
+
+ bool RuntimeInstall::operator==(const RuntimeInstall& rhs) const
+ {
+ return version == rhs.version && arch == rhs.arch && path == rhs.path && is_64bit == rhs.is_64bit;
+ }
+
+ bool RuntimeInstall::operator>(const RuntimeInstall& rhs) const
+ {
+ return (!operator<(rhs)) && (!operator==(rhs));
+ }
+
+ bool RuntimeInstall::operator<(BaseVersion& a) const
+ {
+ try
+ {
+ return operator<(dynamic_cast<RuntimeInstall&>(a));
+ }
+ catch (const std::bad_cast&)
+ {
+ return BaseVersion::operator<(a);
+ }
+ }
+
+ bool RuntimeInstall::operator>(BaseVersion& a) const
+ {
+ try
+ {
+ return operator>(dynamic_cast<RuntimeInstall&>(a));
+ }
+ catch (const std::bad_cast&)
+ {
+ return BaseVersion::operator>(a);
+ }
+ }
+} // namespace projt::java
diff --git a/archived/projt-launcher/launcher/java/core/RuntimeInstall.hpp b/archived/projt-launcher/launcher/java/core/RuntimeInstall.hpp
new file mode 100644
index 0000000000..5039df2ecb
--- /dev/null
+++ b/archived/projt-launcher/launcher/java/core/RuntimeInstall.hpp
@@ -0,0 +1,70 @@
+// 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 "BaseVersion.h"
+#include "java/core/RuntimeVersion.hpp"
+
+#include <memory>
+#include <utility>
+
+namespace projt::java
+{
+ struct RuntimeInstall : public BaseVersion
+ {
+ RuntimeInstall() = default;
+ RuntimeInstall(RuntimeVersion version, QString arch, QString path)
+ : version(std::move(version)),
+ arch(std::move(arch)),
+ path(std::move(path))
+ {}
+
+ QString descriptor() const override
+ {
+ return version.toString();
+ }
+ QString name() const override
+ {
+ return version.toString();
+ }
+ QString typeString() const override
+ {
+ return arch;
+ }
+
+ bool operator<(BaseVersion& a) const override;
+ bool operator>(BaseVersion& a) const override;
+ bool operator<(const RuntimeInstall& rhs) const;
+ bool operator==(const RuntimeInstall& rhs) const;
+ bool operator>(const RuntimeInstall& rhs) const;
+
+ RuntimeVersion version;
+ QString arch;
+ QString path;
+ QString vendor;
+ bool recommended = false;
+ bool is_64bit = false;
+ bool managed = false;
+ };
+
+ using RuntimeInstallPtr = std::shared_ptr<RuntimeInstall>;
+} // namespace projt::java
diff --git a/archived/projt-launcher/launcher/java/core/RuntimePackage.cpp b/archived/projt-launcher/launcher/java/core/RuntimePackage.cpp
new file mode 100644
index 0000000000..727201d1bd
--- /dev/null
+++ b/archived/projt-launcher/launcher/java/core/RuntimePackage.cpp
@@ -0,0 +1,142 @@
+// 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/core/RuntimePackage.hpp"
+
+#include "Json.h"
+#include "StringUtils.h"
+#include "minecraft/ParseUtils.h"
+
+#include <typeinfo>
+
+namespace projt::java
+{
+ PackageKind parsePackageKind(const QString& raw)
+ {
+ if (raw == "manifest")
+ {
+ return PackageKind::Manifest;
+ }
+ if (raw == "archive")
+ {
+ return PackageKind::Archive;
+ }
+ return PackageKind::Unknown;
+ }
+
+ QString packageKindToString(PackageKind kind)
+ {
+ switch (kind)
+ {
+ case PackageKind::Manifest: return "manifest";
+ case PackageKind::Archive: return "archive";
+ case PackageKind::Unknown: break;
+ }
+ return "unknown";
+ }
+
+ RuntimePackagePtr parseRuntimePackage(const QJsonObject& in)
+ {
+ auto meta = std::make_shared<RuntimePackage>();
+
+ meta->displayName = Json::ensureString(in, "name", "");
+ meta->vendor = Json::ensureString(in, "vendor", "");
+ meta->url = Json::ensureString(in, "url", "");
+ meta->releaseTime = timeFromS3Time(Json::ensureString(in, "releaseTime", ""));
+ meta->downloadKind = parsePackageKind(Json::ensureString(in, "downloadType", ""));
+ meta->packageType = Json::ensureString(in, "packageType", "");
+ meta->runtimeOS = Json::ensureString(in, "runtimeOS", "unknown");
+
+ if (in.contains("checksum"))
+ {
+ auto obj = Json::requireObject(in, "checksum");
+ meta->checksumHash = Json::ensureString(obj, "hash", "");
+ meta->checksumType = Json::ensureString(obj, "type", "");
+ }
+
+ if (in.contains("version"))
+ {
+ auto obj = Json::requireObject(in, "version");
+ auto name = Json::ensureString(obj, "name", "");
+ auto major = Json::ensureInteger(obj, "major", 0);
+ auto minor = Json::ensureInteger(obj, "minor", 0);
+ auto security = Json::ensureInteger(obj, "security", 0);
+ auto build = Json::ensureInteger(obj, "build", 0);
+ meta->version = RuntimeVersion(major, minor, security, build, name);
+ }
+ return meta;
+ }
+
+ bool RuntimePackage::operator<(const RuntimePackage& rhs) const
+ {
+ if (version < rhs.version)
+ {
+ return true;
+ }
+ if (version > rhs.version)
+ {
+ return false;
+ }
+ if (releaseTime < rhs.releaseTime)
+ {
+ return true;
+ }
+ if (releaseTime > rhs.releaseTime)
+ {
+ return false;
+ }
+ return StringUtils::naturalCompare(displayName, rhs.displayName, Qt::CaseInsensitive) < 0;
+ }
+
+ bool RuntimePackage::operator==(const RuntimePackage& rhs) const
+ {
+ return version == rhs.version && displayName == rhs.displayName;
+ }
+
+ bool RuntimePackage::operator>(const RuntimePackage& rhs) const
+ {
+ return (!operator<(rhs)) && (!operator==(rhs));
+ }
+
+ bool RuntimePackage::operator<(BaseVersion& a) const
+ {
+ try
+ {
+ return operator<(dynamic_cast<RuntimePackage&>(a));
+ }
+ catch (const std::bad_cast&)
+ {
+ return BaseVersion::operator<(a);
+ }
+ }
+
+ bool RuntimePackage::operator>(BaseVersion& a) const
+ {
+ try
+ {
+ return operator>(dynamic_cast<RuntimePackage&>(a));
+ }
+ catch (const std::bad_cast&)
+ {
+ return BaseVersion::operator>(a);
+ }
+ }
+} // namespace projt::java
diff --git a/archived/projt-launcher/launcher/java/core/RuntimePackage.hpp b/archived/projt-launcher/launcher/java/core/RuntimePackage.hpp
new file mode 100644
index 0000000000..88ebaadf2c
--- /dev/null
+++ b/archived/projt-launcher/launcher/java/core/RuntimePackage.hpp
@@ -0,0 +1,82 @@
+// 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 <QDateTime>
+#include <QJsonObject>
+#include <QString>
+
+#include <memory>
+
+#include "BaseVersion.h"
+#include "java/core/RuntimeVersion.hpp"
+
+namespace projt::java
+{
+ enum class PackageKind
+ {
+ Manifest,
+ Archive,
+ Unknown
+ };
+
+ class RuntimePackage : public BaseVersion
+ {
+ public:
+ QString descriptor() const override
+ {
+ return version.toString();
+ }
+
+ QString name() const override
+ {
+ return displayName;
+ }
+
+ QString typeString() const override
+ {
+ return vendor;
+ }
+
+ bool operator<(BaseVersion& a) const override;
+ bool operator>(BaseVersion& a) const override;
+ bool operator<(const RuntimePackage& rhs) const;
+ bool operator==(const RuntimePackage& rhs) const;
+ bool operator>(const RuntimePackage& rhs) const;
+
+ QString displayName;
+ QString vendor;
+ QString url;
+ QDateTime releaseTime;
+ QString checksumType;
+ QString checksumHash;
+ PackageKind downloadKind = PackageKind::Unknown;
+ QString packageType;
+ RuntimeVersion version;
+ QString runtimeOS;
+ };
+
+ using RuntimePackagePtr = std::shared_ptr<RuntimePackage>;
+
+ PackageKind parsePackageKind(const QString& raw);
+ QString packageKindToString(PackageKind kind);
+ RuntimePackagePtr parseRuntimePackage(const QJsonObject& obj);
+} // namespace projt::java \ No newline at end of file
diff --git a/archived/projt-launcher/launcher/java/core/RuntimeVersion.cpp b/archived/projt-launcher/launcher/java/core/RuntimeVersion.cpp
new file mode 100644
index 0000000000..cd2e07a9d9
--- /dev/null
+++ b/archived/projt-launcher/launcher/java/core/RuntimeVersion.cpp
@@ -0,0 +1,212 @@
+// 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/core/RuntimeVersion.hpp"
+
+#include "StringUtils.h"
+
+#include <QStringList>
+
+namespace projt::java
+{
+ RuntimeVersion::RuntimeVersion(const QString& raw)
+ {
+ parse(raw);
+ }
+
+ RuntimeVersion::RuntimeVersion(int major, int minor, int security, int build, QString name)
+ : m_major(major),
+ m_minor(minor),
+ m_security(security),
+ m_name(name),
+ m_valid(true)
+ {
+ QStringList parts;
+ if (build != 0)
+ {
+ m_prerelease = QString::number(build);
+ parts.push_front(m_prerelease);
+ }
+ if (m_security != 0)
+ {
+ parts.push_front(QString::number(m_security));
+ }
+ else if (!parts.isEmpty())
+ {
+ parts.push_front("0");
+ }
+
+ if (m_minor != 0)
+ {
+ parts.push_front(QString::number(m_minor));
+ }
+ else if (!parts.isEmpty())
+ {
+ parts.push_front("0");
+ }
+ parts.push_front(QString::number(m_major));
+ m_raw = parts.join(".");
+ }
+
+ RuntimeVersion& RuntimeVersion::operator=(const QString& raw)
+ {
+ parse(raw);
+ return *this;
+ }
+
+ void RuntimeVersion::parse(const QString& raw)
+ {
+ m_raw = raw.trimmed();
+ m_major = 0;
+ m_minor = 0;
+ m_security = 0;
+ m_prerelease.clear();
+ m_valid = false;
+
+ QString numeric = m_raw;
+ auto dashIndex = numeric.indexOf('-');
+ if (dashIndex >= 0)
+ {
+ m_prerelease = numeric.mid(dashIndex + 1).trimmed();
+ numeric = numeric.left(dashIndex);
+ }
+
+ int updateNumber = 0;
+ auto updateIndex = numeric.indexOf('_');
+ if (updateIndex >= 0)
+ {
+ bool ok = false;
+ updateNumber = numeric.mid(updateIndex + 1).toInt(&ok);
+ if (!ok)
+ {
+ updateNumber = 0;
+ }
+ numeric = numeric.left(updateIndex);
+ }
+
+ numeric.replace('_', '.');
+ QStringList parts = numeric.split('.', Qt::SkipEmptyParts);
+ if (!parts.isEmpty() && parts[0] == "1" && parts.size() > 1)
+ {
+ parts.removeFirst();
+ }
+ if (parts.isEmpty())
+ {
+ return;
+ }
+
+ auto parsePart = [](const QString& value, int& out)
+ {
+ bool ok = false;
+ int parsed = value.toInt(&ok);
+ if (!ok)
+ {
+ return false;
+ }
+ out = parsed;
+ return true;
+ };
+
+ if (!parsePart(parts[0], m_major))
+ {
+ return;
+ }
+ m_valid = true;
+ if (parts.size() > 1)
+ {
+ parsePart(parts[1], m_minor);
+ }
+ if (parts.size() > 2)
+ {
+ parsePart(parts[2], m_security);
+ }
+ if (updateNumber != 0)
+ {
+ m_security = updateNumber;
+ }
+ }
+
+ QString RuntimeVersion::toString() const
+ {
+ return m_raw;
+ }
+
+ bool RuntimeVersion::needsPermGen() const
+ {
+ return !m_valid || m_major <= 7;
+ }
+
+ bool RuntimeVersion::defaultsToUtf8() const
+ {
+ return m_valid && m_major >= 18;
+ }
+
+ bool RuntimeVersion::supportsModules() const
+ {
+ return m_valid && m_major >= 9;
+ }
+
+ bool RuntimeVersion::operator<(const RuntimeVersion& rhs) const
+ {
+ if (m_valid && rhs.m_valid)
+ {
+ if (m_major != rhs.m_major)
+ {
+ return m_major < rhs.m_major;
+ }
+ if (m_minor != rhs.m_minor)
+ {
+ return m_minor < rhs.m_minor;
+ }
+ if (m_security != rhs.m_security)
+ {
+ return m_security < rhs.m_security;
+ }
+
+ bool thisPre = !m_prerelease.isEmpty();
+ bool rhsPre = !rhs.m_prerelease.isEmpty();
+ if (thisPre != rhsPre)
+ {
+ return thisPre;
+ }
+ if (thisPre && rhsPre)
+ {
+ return StringUtils::naturalCompare(m_prerelease, rhs.m_prerelease, Qt::CaseSensitive) < 0;
+ }
+ return false;
+ }
+ return StringUtils::naturalCompare(m_raw, rhs.m_raw, Qt::CaseSensitive) < 0;
+ }
+
+ bool RuntimeVersion::operator==(const RuntimeVersion& rhs) const
+ {
+ if (m_valid && rhs.m_valid)
+ {
+ return m_major == rhs.m_major && m_minor == rhs.m_minor && m_security == rhs.m_security
+ && m_prerelease == rhs.m_prerelease;
+ }
+ return m_raw == rhs.m_raw;
+ }
+
+ bool RuntimeVersion::operator>(const RuntimeVersion& rhs) const
+ {
+ return (!operator<(rhs)) && (!operator==(rhs));
+ }
+} // namespace projt::java
diff --git a/archived/projt-launcher/launcher/java/core/RuntimeVersion.hpp b/archived/projt-launcher/launcher/java/core/RuntimeVersion.hpp
new file mode 100644
index 0000000000..7199add778
--- /dev/null
+++ b/archived/projt-launcher/launcher/java/core/RuntimeVersion.hpp
@@ -0,0 +1,87 @@
+// 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 <QString>
+
+#ifdef major
+#undef major
+#endif
+#ifdef minor
+#undef minor
+#endif
+
+namespace projt::java
+{
+ class RuntimeVersion
+ {
+ friend class RuntimeVersionTest;
+
+ public:
+ RuntimeVersion() = default;
+ explicit RuntimeVersion(const QString& raw);
+ RuntimeVersion(int major, int minor, int security, int build = 0, QString name = "");
+
+ RuntimeVersion& operator=(const QString& raw);
+
+ bool operator<(const RuntimeVersion& rhs) const;
+ bool operator==(const RuntimeVersion& rhs) const;
+ bool operator>(const RuntimeVersion& rhs) const;
+
+ bool needsPermGen() const;
+ bool defaultsToUtf8() const;
+ bool supportsModules() const;
+
+ QString toString() const;
+
+ int major() const
+ {
+ return m_major;
+ }
+ int minor() const
+ {
+ return m_minor;
+ }
+ int security() const
+ {
+ return m_security;
+ }
+ QString prerelease() const
+ {
+ return m_prerelease;
+ }
+ QString name() const
+ {
+ return m_name;
+ }
+
+ private:
+ void parse(const QString& raw);
+
+ QString m_raw;
+ int m_major = 0;
+ int m_minor = 0;
+ int m_security = 0;
+ QString m_name;
+ bool m_valid = false;
+ QString m_prerelease;
+ };
+} // namespace projt::java \ No newline at end of file
diff --git a/archived/projt-launcher/launcher/java/download/RuntimeArchiveTask.cpp b/archived/projt-launcher/launcher/java/download/RuntimeArchiveTask.cpp
new file mode 100644
index 0000000000..9ab74afe62
--- /dev/null
+++ b/archived/projt-launcher/launcher/java/download/RuntimeArchiveTask.cpp
@@ -0,0 +1,174 @@
+// 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/download/RuntimeArchiveTask.hpp"
+
+#include <QDir>
+#include <QFile>
+#include <memory>
+#include <quazip.h>
+
+#include "Application.h"
+#include "MMCZip.h"
+#include "Untar.h"
+#include "net/ChecksumValidator.h"
+#include "net/NetJob.h"
+
+namespace projt::java
+{
+ RuntimeArchiveTask::RuntimeArchiveTask(QUrl url, QString finalPath, QString checksumType, QString checksumHash)
+ : m_url(url),
+ m_final_path(std::move(finalPath)),
+ m_checksum_type(std::move(checksumType)),
+ m_checksum_hash(std::move(checksumHash))
+ {}
+
+ void RuntimeArchiveTask::executeTask()
+ {
+ setStatus(tr("Downloading Java"));
+
+ MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("java", m_url.fileName());
+
+ auto download = makeShared<NetJob>(QString("JRE::DownloadJava"), APPLICATION->network());
+ auto action = Net::Download::makeCached(m_url, entry);
+ if (!m_checksum_hash.isEmpty() && !m_checksum_type.isEmpty())
+ {
+ auto hashType = QCryptographicHash::Algorithm::Sha1;
+ if (m_checksum_type == "sha256")
+ {
+ hashType = QCryptographicHash::Algorithm::Sha256;
+ }
+ action->addValidator(new Net::ChecksumValidator(hashType, QByteArray::fromHex(m_checksum_hash.toUtf8())));
+ }
+ download->addNetAction(action);
+ auto fullPath = entry->getFullPath();
+
+ connect(download.get(), &Task::failed, this, &RuntimeArchiveTask::emitFailed);
+ connect(download.get(), &Task::progress, this, &RuntimeArchiveTask::setProgress);
+ connect(download.get(), &Task::stepProgress, this, &RuntimeArchiveTask::propagateStepProgress);
+ connect(download.get(), &Task::status, this, &RuntimeArchiveTask::setStatus);
+ connect(download.get(), &Task::details, this, &RuntimeArchiveTask::setDetails);
+ connect(download.get(), &Task::aborted, this, &RuntimeArchiveTask::emitAborted);
+ connect(download.get(), &Task::succeeded, this, [this, fullPath] { extractRuntime(fullPath); });
+ m_task = download;
+ m_task->start();
+ }
+
+ void RuntimeArchiveTask::extractRuntime(QString input)
+ {
+ setStatus(tr("Extracting Java"));
+ if (input.endsWith("tar"))
+ {
+ setStatus(tr("Extracting Java (Progress is not reported for tar archives)"));
+ QFile in(input);
+ if (!in.open(QFile::ReadOnly))
+ {
+ emitFailed(tr("Unable to open supplied tar file."));
+ return;
+ }
+ if (!Tar::extract(&in, QDir(m_final_path).absolutePath()))
+ {
+ emitFailed(tr("Unable to extract supplied tar file."));
+ return;
+ }
+ emitSucceeded();
+ return;
+ }
+ if (input.endsWith("tar.gz") || input.endsWith("taz") || input.endsWith("tgz"))
+ {
+ setStatus(tr("Extracting Java (Progress is not reported for tar archives)"));
+ if (!GZTar::extract(input, QDir(m_final_path).absolutePath()))
+ {
+ emitFailed(tr("Unable to extract supplied tar file."));
+ return;
+ }
+ emitSucceeded();
+ return;
+ }
+ if (input.endsWith("zip"))
+ {
+ auto zip = std::make_shared<QuaZip>(input);
+ if (!zip->open(QuaZip::mdUnzip))
+ {
+ emitFailed(tr("Unable to open supplied zip file."));
+ return;
+ }
+ auto files = zip->getFileNameList();
+ if (files.isEmpty())
+ {
+ emitFailed(tr("No files were found in the supplied zip file."));
+ return;
+ }
+ m_task = makeShared<MMCZip::ExtractZipTask>(zip, m_final_path, files[0]);
+
+ auto progressStep = std::make_shared<TaskStepProgress>();
+ connect(m_task.get(),
+ &Task::finished,
+ this,
+ [this, progressStep]
+ {
+ progressStep->state = TaskStepState::Succeeded;
+ stepProgress(*progressStep);
+ });
+
+ connect(m_task.get(), &Task::succeeded, this, &RuntimeArchiveTask::emitSucceeded);
+ connect(m_task.get(), &Task::aborted, this, &RuntimeArchiveTask::emitAborted);
+ connect(m_task.get(),
+ &Task::failed,
+ this,
+ [this, progressStep](QString reason)
+ {
+ progressStep->state = TaskStepState::Failed;
+ stepProgress(*progressStep);
+ emitFailed(reason);
+ });
+ connect(m_task.get(), &Task::stepProgress, this, &RuntimeArchiveTask::propagateStepProgress);
+
+ connect(m_task.get(),
+ &Task::progress,
+ this,
+ [this, progressStep](qint64 current, qint64 total)
+ {
+ progressStep->update(current, total);
+ stepProgress(*progressStep);
+ });
+ connect(m_task.get(),
+ &Task::status,
+ this,
+ [this, progressStep](QString status)
+ {
+ progressStep->status = status;
+ stepProgress(*progressStep);
+ });
+ m_task->start();
+ return;
+ }
+
+ emitFailed(tr("Could not determine archive type!"));
+ }
+
+ bool RuntimeArchiveTask::abort()
+ {
+ auto aborted = canAbort();
+ if (m_task)
+ aborted = m_task->abort();
+ return aborted;
+ }
+} // namespace projt::java
diff --git a/archived/projt-launcher/launcher/java/download/RuntimeArchiveTask.hpp b/archived/projt-launcher/launcher/java/download/RuntimeArchiveTask.hpp
new file mode 100644
index 0000000000..6aa524ceb2
--- /dev/null
+++ b/archived/projt-launcher/launcher/java/download/RuntimeArchiveTask.hpp
@@ -0,0 +1,55 @@
+// 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 <QUrl>
+
+#include "tasks/Task.h"
+
+namespace projt::java
+{
+ class RuntimeArchiveTask : public Task
+ {
+ Q_OBJECT
+ public:
+ RuntimeArchiveTask(QUrl url, QString finalPath, QString checksumType = "", QString checksumHash = "");
+ ~RuntimeArchiveTask() override = default;
+
+ bool canAbort() const override
+ {
+ return true;
+ }
+
+ bool abort() override;
+
+ protected:
+ void executeTask() override;
+
+ private:
+ void extractRuntime(QString input);
+
+ QUrl m_url;
+ QString m_final_path;
+ QString m_checksum_type;
+ QString m_checksum_hash;
+ Task::Ptr m_task;
+ };
+} // namespace projt::java
diff --git a/archived/projt-launcher/launcher/java/download/RuntimeLinkTask.cpp b/archived/projt-launcher/launcher/java/download/RuntimeLinkTask.cpp
new file mode 100644
index 0000000000..fd5b381044
--- /dev/null
+++ b/archived/projt-launcher/launcher/java/download/RuntimeLinkTask.cpp
@@ -0,0 +1,101 @@
+// 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/download/RuntimeLinkTask.hpp"
+
+#include <QDir>
+#include <QFileInfo>
+
+#include "FileSystem.h"
+
+namespace
+{
+ QString findBinPath(const QString& root, const QString& pattern)
+ {
+ auto path = FS::PathCombine(root, pattern);
+ if (QFileInfo::exists(path))
+ {
+ return path;
+ }
+
+ auto entries = QDir(root).entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
+ for (auto& entry : entries)
+ {
+ path = FS::PathCombine(entry.absoluteFilePath(), pattern);
+ if (QFileInfo::exists(path))
+ {
+ return path;
+ }
+ }
+
+ return {};
+ }
+} // namespace
+
+namespace projt::java
+{
+ RuntimeLinkTask::RuntimeLinkTask(QString finalPath) : m_path(std::move(finalPath))
+ {}
+
+ void RuntimeLinkTask::executeTask()
+ {
+ setStatus(tr("Checking for Java binary path"));
+ const auto binPath = FS::PathCombine("bin", "java");
+ const auto wantedPath = FS::PathCombine(m_path, binPath);
+ if (QFileInfo::exists(wantedPath))
+ {
+ emitSucceeded();
+ return;
+ }
+
+ setStatus(tr("Searching for Java binary path"));
+ const auto contentsPartialPath = FS::PathCombine("Contents", "Home", binPath);
+ const auto relativePathToBin = findBinPath(m_path, contentsPartialPath);
+ if (relativePathToBin.isEmpty())
+ {
+ emitFailed(tr("Failed to find Java binary path"));
+ return;
+ }
+ const auto folderToLink = relativePathToBin.chopped(binPath.length());
+
+ setStatus(tr("Collecting folders to symlink"));
+ auto entries = QDir(folderToLink).entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries);
+ QList<FS::LinkPair> files;
+ setProgress(0, entries.length());
+ for (auto& entry : entries)
+ {
+ files.append({ entry.absoluteFilePath(), FS::PathCombine(m_path, entry.fileName()) });
+ }
+
+ setStatus(tr("Symlinking Java binary path"));
+ FS::create_link folderLink(files);
+ connect(&folderLink,
+ &FS::create_link::fileLinked,
+ [this](QString, QString) { setProgress(m_progress + 1, m_progressTotal); });
+ if (!folderLink())
+ {
+ emitFailed(folderLink.getOSError().message().c_str());
+ }
+ else
+ {
+ emitSucceeded();
+ }
+ }
+} // namespace projt::java
diff --git a/archived/projt-launcher/launcher/java/download/RuntimeLinkTask.hpp b/archived/projt-launcher/launcher/java/download/RuntimeLinkTask.hpp
new file mode 100644
index 0000000000..d4daf98a04
--- /dev/null
+++ b/archived/projt-launcher/launcher/java/download/RuntimeLinkTask.hpp
@@ -0,0 +1,40 @@
+// 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 "tasks/Task.h"
+
+namespace projt::java
+{
+ class RuntimeLinkTask : public Task
+ {
+ Q_OBJECT
+ public:
+ explicit RuntimeLinkTask(QString finalPath);
+ ~RuntimeLinkTask() override = default;
+
+ protected:
+ void executeTask() override;
+
+ private:
+ QString m_path;
+ };
+} // namespace projt::java \ No newline at end of file
diff --git a/archived/projt-launcher/launcher/java/download/RuntimeManifestTask.cpp b/archived/projt-launcher/launcher/java/download/RuntimeManifestTask.cpp
new file mode 100644
index 0000000000..3776078ecf
--- /dev/null
+++ b/archived/projt-launcher/launcher/java/download/RuntimeManifestTask.cpp
@@ -0,0 +1,184 @@
+// 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/download/RuntimeManifestTask.hpp"
+
+#include <QFile>
+#include <QJsonDocument>
+
+#include "Application.h"
+#include "FileSystem.h"
+#include "Json.h"
+#include "net/ChecksumValidator.h"
+#include "net/NetJob.h"
+
+namespace
+{
+ struct FileEntry
+ {
+ QString path;
+ QString url;
+ QByteArray hash;
+ bool isExec = false;
+ };
+} // namespace
+
+namespace projt::java
+{
+ RuntimeManifestTask::RuntimeManifestTask(QUrl url, QString finalPath, QString checksumType, QString checksumHash)
+ : m_url(url),
+ m_final_path(std::move(finalPath)),
+ m_checksum_type(std::move(checksumType)),
+ m_checksum_hash(std::move(checksumHash))
+ {}
+
+ void RuntimeManifestTask::executeTask()
+ {
+ setStatus(tr("Downloading Java"));
+ auto download = makeShared<NetJob>(QString("JRE::DownloadJava"), APPLICATION->network());
+ auto files = std::make_shared<QByteArray>();
+
+ auto action = Net::Download::makeByteArray(m_url, files);
+ if (!m_checksum_hash.isEmpty() && !m_checksum_type.isEmpty())
+ {
+ auto hashType = QCryptographicHash::Algorithm::Sha1;
+ if (m_checksum_type == "sha256")
+ {
+ hashType = QCryptographicHash::Algorithm::Sha256;
+ }
+ action->addValidator(new Net::ChecksumValidator(hashType, QByteArray::fromHex(m_checksum_hash.toUtf8())));
+ }
+ download->addNetAction(action);
+
+ connect(download.get(), &Task::failed, this, &RuntimeManifestTask::emitFailed);
+ connect(download.get(), &Task::progress, this, &RuntimeManifestTask::setProgress);
+ connect(download.get(), &Task::stepProgress, this, &RuntimeManifestTask::propagateStepProgress);
+ connect(download.get(), &Task::status, this, &RuntimeManifestTask::setStatus);
+ connect(download.get(), &Task::details, this, &RuntimeManifestTask::setDetails);
+
+ connect(download.get(),
+ &Task::succeeded,
+ [files, this]
+ {
+ QJsonParseError parse_error{};
+ QJsonDocument doc = QJsonDocument::fromJson(*files, &parse_error);
+ if (parse_error.error != QJsonParseError::NoError)
+ {
+ qWarning() << "Error while parsing JSON response at " << parse_error.offset
+ << ". Reason: " << parse_error.errorString();
+ qWarning() << *files;
+ emitFailed(parse_error.errorString());
+ return;
+ }
+ downloadRuntime(doc);
+ });
+ m_task = download;
+ m_task->start();
+ }
+
+ void RuntimeManifestTask::downloadRuntime(const QJsonDocument& doc)
+ {
+ FS::ensureFolderPathExists(m_final_path);
+ std::vector<FileEntry> toDownload;
+ auto list = Json::ensureObject(Json::ensureObject(doc.object()), "files");
+ for (const auto& pathKey : list.keys())
+ {
+ auto filePath = FS::PathCombine(m_final_path, pathKey);
+ const QJsonObject& meta = Json::ensureObject(list, pathKey);
+ auto type = Json::ensureString(meta, "type");
+ if (type == "directory")
+ {
+ FS::ensureFolderPathExists(filePath);
+ }
+ else if (type == "link")
+ {
+ auto target = Json::ensureString(meta, "target");
+ if (!target.isEmpty())
+ {
+ QFile::link(target, filePath);
+ }
+ }
+ else if (type == "file")
+ {
+ auto downloads = Json::ensureObject(meta, "downloads");
+ auto isExec = Json::ensureBoolean(meta, "executable", false);
+ QString url;
+ QByteArray hash;
+
+ if (downloads.contains("raw"))
+ {
+ auto raw = Json::ensureObject(downloads, "raw");
+ url = Json::ensureString(raw, "url");
+ hash = QByteArray::fromHex(Json::ensureString(raw, "sha1").toLatin1());
+ }
+ else
+ {
+ qWarning() << "No raw download available for file:" << pathKey;
+ qWarning() << "Skipping file without raw download - decompression not yet supported";
+ continue;
+ }
+
+ if (!url.isEmpty() && QUrl(url).isValid())
+ {
+ toDownload.push_back({ filePath, url, hash, isExec });
+ }
+ }
+ }
+ auto elementDownload = makeShared<NetJob>("JRE::FileDownload", APPLICATION->network());
+ for (const auto& file : toDownload)
+ {
+ auto dl = Net::Download::makeFile(file.url, file.path);
+ if (!file.hash.isEmpty())
+ {
+ dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, file.hash));
+ }
+ if (file.isExec)
+ {
+ connect(dl.get(),
+ &Net::Download::succeeded,
+ [file]
+ {
+ QFile(file.path).setPermissions(QFile(file.path).permissions()
+ | QFileDevice::Permissions(0x1111));
+ });
+ }
+ elementDownload->addNetAction(dl);
+ }
+
+ connect(elementDownload.get(), &Task::failed, this, &RuntimeManifestTask::emitFailed);
+ connect(elementDownload.get(), &Task::progress, this, &RuntimeManifestTask::setProgress);
+ connect(elementDownload.get(), &Task::stepProgress, this, &RuntimeManifestTask::propagateStepProgress);
+ connect(elementDownload.get(), &Task::status, this, &RuntimeManifestTask::setStatus);
+ connect(elementDownload.get(), &Task::details, this, &RuntimeManifestTask::setDetails);
+
+ connect(elementDownload.get(), &Task::succeeded, this, &RuntimeManifestTask::emitSucceeded);
+ m_task = elementDownload;
+ m_task->start();
+ }
+
+ bool RuntimeManifestTask::abort()
+ {
+ auto aborted = canAbort();
+ if (m_task)
+ aborted = m_task->abort();
+ emitAborted();
+ return aborted;
+ }
+} // namespace projt::java
diff --git a/archived/projt-launcher/launcher/java/download/RuntimeManifestTask.hpp b/archived/projt-launcher/launcher/java/download/RuntimeManifestTask.hpp
new file mode 100644
index 0000000000..6eb8427dbe
--- /dev/null
+++ b/archived/projt-launcher/launcher/java/download/RuntimeManifestTask.hpp
@@ -0,0 +1,55 @@
+// 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 <QUrl>
+
+#include "tasks/Task.h"
+
+namespace projt::java
+{
+ class RuntimeManifestTask : public Task
+ {
+ Q_OBJECT
+ public:
+ RuntimeManifestTask(QUrl url, QString finalPath, QString checksumType = "", QString checksumHash = "");
+ ~RuntimeManifestTask() override = default;
+
+ bool canAbort() const override
+ {
+ return true;
+ }
+
+ bool abort() override;
+
+ protected:
+ void executeTask() override;
+
+ private:
+ void downloadRuntime(const QJsonDocument& doc);
+
+ QUrl m_url;
+ QString m_final_path;
+ QString m_checksum_type;
+ QString m_checksum_hash;
+ Task::Ptr m_task;
+ };
+} // namespace projt::java \ No newline at end of file
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