diff options
Diffstat (limited to 'meshmc/launcher/minecraft/Component.cpp')
| -rw-r--r-- | meshmc/launcher/minecraft/Component.cpp | 408 |
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(); + } +} |
