summaryrefslogtreecommitdiff
path: root/meshmc/launcher/minecraft/Component.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'meshmc/launcher/minecraft/Component.cpp')
-rw-r--r--meshmc/launcher/minecraft/Component.cpp408
1 files changed, 408 insertions, 0 deletions
diff --git a/meshmc/launcher/minecraft/Component.cpp b/meshmc/launcher/minecraft/Component.cpp
new file mode 100644
index 0000000000..e11c0fa492
--- /dev/null
+++ b/meshmc/launcher/minecraft/Component.cpp
@@ -0,0 +1,408 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * 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, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <meta/VersionList.h>
+#include <meta/Index.h>
+#include "Component.h"
+
+#include <QSaveFile>
+
+#include "meta/Version.h"
+#include "VersionFile.h"
+#include "minecraft/PackProfile.h"
+#include "FileSystem.h"
+#include "OneSixVersionFormat.h"
+#include "Application.h"
+
+#include <assert.h>
+
+Component::Component(PackProfile* parent, const QString& uid)
+{
+ assert(parent);
+ m_parent = parent;
+
+ m_uid = uid;
+}
+
+Component::Component(PackProfile* parent,
+ std::shared_ptr<Meta::Version> version)
+{
+ assert(parent);
+ m_parent = parent;
+
+ m_metaVersion = version;
+ m_uid = version->uid();
+ m_version = m_cachedVersion = version->version();
+ m_cachedName = version->name();
+ m_loaded = version->isLoaded();
+}
+
+Component::Component(PackProfile* parent, const QString& uid,
+ std::shared_ptr<VersionFile> file)
+{
+ assert(parent);
+ m_parent = parent;
+
+ m_file = file;
+ m_uid = uid;
+ m_cachedVersion = m_file->version;
+ m_cachedName = m_file->name;
+ m_loaded = true;
+}
+
+std::shared_ptr<Meta::Version> Component::getMeta()
+{
+ return m_metaVersion;
+}
+
+void Component::applyTo(LaunchProfile* profile)
+{
+ // do not apply disabled components
+ if (!isEnabled()) {
+ return;
+ }
+ auto vfile = getVersionFile();
+ if (vfile) {
+ vfile->applyTo(profile);
+ } else {
+ profile->applyProblemSeverity(getProblemSeverity());
+ }
+}
+
+std::shared_ptr<class VersionFile> Component::getVersionFile() const
+{
+ if (m_metaVersion) {
+ if (!m_metaVersion->isLoaded()) {
+ m_metaVersion->load(Net::Mode::Online);
+ }
+ return m_metaVersion->data();
+ } else {
+ return m_file;
+ }
+}
+
+std::shared_ptr<class Meta::VersionList> Component::getVersionList() const
+{
+ // FIXME: what if the metadata index isn't loaded yet?
+ if (APPLICATION->metadataIndex()->hasUid(m_uid)) {
+ return APPLICATION->metadataIndex()->get(m_uid);
+ }
+ return nullptr;
+}
+
+int Component::getOrder()
+{
+ if (m_orderOverride)
+ return m_order;
+
+ auto vfile = getVersionFile();
+ if (vfile) {
+ return vfile->order;
+ }
+ return 0;
+}
+void Component::setOrder(int order)
+{
+ m_orderOverride = true;
+ m_order = order;
+}
+QString Component::getID()
+{
+ return m_uid;
+}
+QString Component::getName()
+{
+ if (!m_cachedName.isEmpty())
+ return m_cachedName;
+ return m_uid;
+}
+QString Component::getVersion()
+{
+ return m_cachedVersion;
+}
+QString Component::getFilename()
+{
+ return m_parent->patchFilePathForUid(m_uid);
+}
+QDateTime Component::getReleaseDateTime()
+{
+ if (m_metaVersion) {
+ return m_metaVersion->time();
+ }
+ auto vfile = getVersionFile();
+ if (vfile) {
+ return vfile->releaseTime;
+ }
+ // FIXME: fake
+ return QDateTime::currentDateTime();
+}
+
+bool Component::isEnabled()
+{
+ return !canBeDisabled() || !m_disabled;
+}
+
+bool Component::canBeDisabled()
+{
+ return isRemovable() && !m_dependencyOnly;
+}
+
+bool Component::setEnabled(bool state)
+{
+ bool intendedDisabled = !state;
+ if (!canBeDisabled()) {
+ intendedDisabled = false;
+ }
+ if (intendedDisabled != m_disabled) {
+ m_disabled = intendedDisabled;
+ emit dataChanged();
+ return true;
+ }
+ return false;
+}
+
+bool Component::isCustom()
+{
+ return m_file != nullptr;
+}
+
+bool Component::isCustomizable()
+{
+ if (m_metaVersion) {
+ if (getVersionFile()) {
+ return true;
+ }
+ }
+ return false;
+}
+bool Component::isRemovable()
+{
+ return !m_important;
+}
+bool Component::isRevertible()
+{
+ if (isCustom()) {
+ if (APPLICATION->metadataIndex()->hasUid(m_uid)) {
+ return true;
+ }
+ }
+ return false;
+}
+bool Component::isMoveable()
+{
+ // HACK, FIXME: this was too dumb and wouldn't follow dependency constraints
+ // anyway. For now hardcoded to 'true'.
+ return true;
+}
+bool Component::isVersionChangeable()
+{
+ auto list = getVersionList();
+ if (list) {
+ if (!list->isLoaded()) {
+ list->load(Net::Mode::Online);
+ }
+ return list->count() != 0;
+ }
+ return false;
+}
+
+void Component::setImportant(bool state)
+{
+ if (m_important != state) {
+ m_important = state;
+ emit dataChanged();
+ }
+}
+
+ProblemSeverity Component::getProblemSeverity() const
+{
+ auto file = getVersionFile();
+ if (file) {
+ return file->getProblemSeverity();
+ }
+ return ProblemSeverity::Error;
+}
+
+const QList<PatchProblem> Component::getProblems() const
+{
+ auto file = getVersionFile();
+ if (file) {
+ return file->getProblems();
+ }
+ return {{ProblemSeverity::Error, QObject::tr("Patch is not loaded yet.")}};
+}
+
+void Component::setVersion(const QString& version)
+{
+ if (version == m_version) {
+ return;
+ }
+ m_version = version;
+ if (m_loaded) {
+ // we are loaded and potentially have state to invalidate
+ if (m_file) {
+ // we have a file... explicit version has been changed and there is
+ // nothing else to do.
+ } else {
+ // we don't have a file, therefore we are loaded with metadata
+ m_cachedVersion = version;
+ // see if the meta version is loaded
+ auto metaVersion =
+ APPLICATION->metadataIndex()->get(m_uid, version);
+ if (metaVersion->isLoaded()) {
+ // if yes, we can continue with that.
+ m_metaVersion = metaVersion;
+ } else {
+ // if not, we need loading
+ m_metaVersion.reset();
+ m_loaded = false;
+ }
+ updateCachedData();
+ }
+ } else {
+ // not loaded... assume it will be sorted out later by the update task
+ }
+ emit dataChanged();
+}
+
+bool Component::customize()
+{
+ if (isCustom()) {
+ return false;
+ }
+
+ auto filename = getFilename();
+ if (!FS::ensureFilePathExists(filename)) {
+ return false;
+ }
+ // FIXME: get rid of this try-catch.
+ try {
+ QSaveFile jsonFile(filename);
+ if (!jsonFile.open(QIODevice::WriteOnly)) {
+ return false;
+ }
+ auto vfile = getVersionFile();
+ if (!vfile) {
+ return false;
+ }
+ auto document = OneSixVersionFormat::versionFileToJson(vfile);
+ jsonFile.write(document.toJson());
+ if (!jsonFile.commit()) {
+ return false;
+ }
+ m_file = vfile;
+ m_metaVersion.reset();
+ emit dataChanged();
+ } catch (const Exception& error) {
+ qWarning() << "Version could not be loaded:" << error.cause();
+ }
+ return true;
+}
+
+bool Component::revert()
+{
+ if (!isCustom()) {
+ // already not custom
+ return true;
+ }
+ auto filename = getFilename();
+ bool result = true;
+ // just kill the file and reload
+ if (QFile::exists(filename)) {
+ result = QFile::remove(filename);
+ }
+ if (result) {
+ // file gone...
+ m_file.reset();
+
+ // check local cache for metadata...
+ auto version = APPLICATION->metadataIndex()->get(m_uid, m_version);
+ if (version->isLoaded()) {
+ m_metaVersion = version;
+ } else {
+ m_metaVersion.reset();
+ m_loaded = false;
+ }
+ emit dataChanged();
+ }
+ return result;
+}
+
+/**
+ * deep inspecting compare for requirement sets
+ * By default, only uids are compared for set operations.
+ * This compares all fields of the Require structs in the sets.
+ */
+static bool deepCompare(const std::set<Meta::Require>& a,
+ const std::set<Meta::Require>& b)
+{
+ // NOTE: this needs to be rewritten if the type of Meta::RequireSet changes
+ if (a.size() != b.size()) {
+ return false;
+ }
+ for (const auto& reqA : a) {
+ const auto& iter2 = b.find(reqA);
+ if (iter2 == b.cend()) {
+ return false;
+ }
+ const auto& reqB = *iter2;
+ if (!reqA.deepEquals(reqB)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void Component::updateCachedData()
+{
+ auto file = getVersionFile();
+ if (file) {
+ bool changed = false;
+ if (m_cachedName != file->name) {
+ m_cachedName = file->name;
+ changed = true;
+ }
+ if (m_cachedVersion != file->version) {
+ m_cachedVersion = file->version;
+ changed = true;
+ }
+ if (m_cachedVolatile != file->m_volatile) {
+ m_cachedVolatile = file->m_volatile;
+ changed = true;
+ }
+ if (!deepCompare(m_cachedRequires, file->requirements)) {
+ m_cachedRequires = file->requirements;
+ changed = true;
+ }
+ if (!deepCompare(m_cachedConflicts, file->conflicts)) {
+ m_cachedConflicts = file->conflicts;
+ changed = true;
+ }
+ if (changed) {
+ emit dataChanged();
+ }
+ } else {
+ // in case we removed all the metadata
+ m_cachedRequires.clear();
+ m_cachedConflicts.clear();
+ emit dataChanged();
+ }
+}