diff options
Diffstat (limited to 'archived/projt-launcher/launcher/java')
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 |
