aboutsummaryrefslogtreecommitdiff
path: root/api/logic/minecraft/PackProfile.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'api/logic/minecraft/PackProfile.cpp')
-rw-r--r--api/logic/minecraft/PackProfile.cpp1225
1 files changed, 0 insertions, 1225 deletions
diff --git a/api/logic/minecraft/PackProfile.cpp b/api/logic/minecraft/PackProfile.cpp
deleted file mode 100644
index f6918116..00000000
--- a/api/logic/minecraft/PackProfile.cpp
+++ /dev/null
@@ -1,1225 +0,0 @@
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <QFile>
-#include <QCryptographicHash>
-#include <Version.h>
-#include <QDir>
-#include <QJsonDocument>
-#include <QJsonArray>
-#include <QDebug>
-
-#include "Exception.h"
-#include <minecraft/OneSixVersionFormat.h>
-#include <FileSystem.h>
-#include <QSaveFile>
-#include <Env.h>
-#include <meta/Index.h>
-#include <minecraft/MinecraftInstance.h>
-#include <QUuid>
-#include <QTimer>
-#include <Json.h>
-
-#include "PackProfile.h"
-#include "PackProfile_p.h"
-#include "ComponentUpdateTask.h"
-
-PackProfile::PackProfile(MinecraftInstance * instance)
- : QAbstractListModel()
-{
- d.reset(new PackProfileData);
- d->m_instance = instance;
- d->m_saveTimer.setSingleShot(true);
- d->m_saveTimer.setInterval(5000);
- d->interactionDisabled = instance->isRunning();
- connect(d->m_instance, &BaseInstance::runningStatusChanged, this, &PackProfile::disableInteraction);
- connect(&d->m_saveTimer, &QTimer::timeout, this, &PackProfile::save_internal);
-}
-
-PackProfile::~PackProfile()
-{
- saveNow();
-}
-
-// BEGIN: component file format
-
-static const int currentComponentsFileVersion = 1;
-
-static QJsonObject componentToJsonV1(ComponentPtr component)
-{
- QJsonObject obj;
- // critical
- obj.insert("uid", component->m_uid);
- if(!component->m_version.isEmpty())
- {
- obj.insert("version", component->m_version);
- }
- if(component->m_dependencyOnly)
- {
- obj.insert("dependencyOnly", true);
- }
- if(component->m_important)
- {
- obj.insert("important", true);
- }
- if(component->m_disabled)
- {
- obj.insert("disabled", true);
- }
-
- // cached
- if(!component->m_cachedVersion.isEmpty())
- {
- obj.insert("cachedVersion", component->m_cachedVersion);
- }
- if(!component->m_cachedName.isEmpty())
- {
- obj.insert("cachedName", component->m_cachedName);
- }
- Meta::serializeRequires(obj, &component->m_cachedRequires, "cachedRequires");
- Meta::serializeRequires(obj, &component->m_cachedConflicts, "cachedConflicts");
- if(component->m_cachedVolatile)
- {
- obj.insert("cachedVolatile", true);
- }
- return obj;
-}
-
-static ComponentPtr componentFromJsonV1(PackProfile * parent, const QString & componentJsonPattern, const QJsonObject &obj)
-{
- // critical
- auto uid = Json::requireString(obj.value("uid"));
- auto filePath = componentJsonPattern.arg(uid);
- auto component = new Component(parent, uid);
- component->m_version = Json::ensureString(obj.value("version"));
- component->m_dependencyOnly = Json::ensureBoolean(obj.value("dependencyOnly"), false);
- component->m_important = Json::ensureBoolean(obj.value("important"), false);
-
- // cached
- // TODO @RESILIENCE: ignore invalid values/structure here?
- component->m_cachedVersion = Json::ensureString(obj.value("cachedVersion"));
- component->m_cachedName = Json::ensureString(obj.value("cachedName"));
- Meta::parseRequires(obj, &component->m_cachedRequires, "cachedRequires");
- Meta::parseRequires(obj, &component->m_cachedConflicts, "cachedConflicts");
- component->m_cachedVolatile = Json::ensureBoolean(obj.value("volatile"), false);
- bool disabled = Json::ensureBoolean(obj.value("disabled"), false);
- component->setEnabled(!disabled);
- return component;
-}
-
-// Save the given component container data to a file
-static bool savePackProfile(const QString & filename, const ComponentContainer & container)
-{
- QJsonObject obj;
- obj.insert("formatVersion", currentComponentsFileVersion);
- QJsonArray orderArray;
- for(auto component: container)
- {
- orderArray.append(componentToJsonV1(component));
- }
- obj.insert("components", orderArray);
- QSaveFile outFile(filename);
- if (!outFile.open(QFile::WriteOnly))
- {
- qCritical() << "Couldn't open" << outFile.fileName()
- << "for writing:" << outFile.errorString();
- return false;
- }
- auto data = QJsonDocument(obj).toJson(QJsonDocument::Indented);
- if(outFile.write(data) != data.size())
- {
- qCritical() << "Couldn't write all the data into" << outFile.fileName()
- << "because:" << outFile.errorString();
- return false;
- }
- if(!outFile.commit())
- {
- qCritical() << "Couldn't save" << outFile.fileName()
- << "because:" << outFile.errorString();
- }
- return true;
-}
-
-// Read the given file into component containers
-static bool loadPackProfile(PackProfile * parent, const QString & filename, const QString & componentJsonPattern, ComponentContainer & container)
-{
- QFile componentsFile(filename);
- if (!componentsFile.exists())
- {
- qWarning() << "Components file doesn't exist. This should never happen.";
- return false;
- }
- if (!componentsFile.open(QFile::ReadOnly))
- {
- qCritical() << "Couldn't open" << componentsFile.fileName()
- << " for reading:" << componentsFile.errorString();
- qWarning() << "Ignoring overriden order";
- return false;
- }
-
- // and it's valid JSON
- QJsonParseError error;
- QJsonDocument doc = QJsonDocument::fromJson(componentsFile.readAll(), &error);
- if (error.error != QJsonParseError::NoError)
- {
- qCritical() << "Couldn't parse" << componentsFile.fileName() << ":" << error.errorString();
- qWarning() << "Ignoring overriden order";
- return false;
- }
-
- // and then read it and process it if all above is true.
- try
- {
- auto obj = Json::requireObject(doc);
- // check order file version.
- auto version = Json::requireInteger(obj.value("formatVersion"));
- if (version != currentComponentsFileVersion)
- {
- throw JSONValidationError(QObject::tr("Invalid component file version, expected %1")
- .arg(currentComponentsFileVersion));
- }
- auto orderArray = Json::requireArray(obj.value("components"));
- for(auto item: orderArray)
- {
- auto obj = Json::requireObject(item, "Component must be an object.");
- container.append(componentFromJsonV1(parent, componentJsonPattern, obj));
- }
- }
- catch (const JSONValidationError &err)
- {
- qCritical() << "Couldn't parse" << componentsFile.fileName() << ": bad file format";
- container.clear();
- return false;
- }
- return true;
-}
-
-// END: component file format
-
-// BEGIN: save/load logic
-
-void PackProfile::saveNow()
-{
- if(saveIsScheduled())
- {
- d->m_saveTimer.stop();
- save_internal();
- }
-}
-
-bool PackProfile::saveIsScheduled() const
-{
- return d->dirty;
-}
-
-void PackProfile::buildingFromScratch()
-{
- d->loaded = true;
- d->dirty = true;
-}
-
-void PackProfile::scheduleSave()
-{
- if(!d->loaded)
- {
- qDebug() << "Component list should never save if it didn't successfully load, instance:" << d->m_instance->name();
- return;
- }
- if(!d->dirty)
- {
- d->dirty = true;
- qDebug() << "Component list save is scheduled for" << d->m_instance->name();
- }
- d->m_saveTimer.start();
-}
-
-QString PackProfile::componentsFilePath() const
-{
- return FS::PathCombine(d->m_instance->instanceRoot(), "mmc-pack.json");
-}
-
-QString PackProfile::patchesPattern() const
-{
- return FS::PathCombine(d->m_instance->instanceRoot(), "patches", "%1.json");
-}
-
-QString PackProfile::patchFilePathForUid(const QString& uid) const
-{
- return patchesPattern().arg(uid);
-}
-
-void PackProfile::save_internal()
-{
- qDebug() << "Component list save performed now for" << d->m_instance->name();
- auto filename = componentsFilePath();
- savePackProfile(filename, d->components);
- d->dirty = false;
-}
-
-bool PackProfile::load()
-{
- auto filename = componentsFilePath();
- QFile componentsFile(filename);
-
- // migrate old config to new one, if needed
- if(!componentsFile.exists())
- {
- if(!migratePreComponentConfig())
- {
- // FIXME: the user should be notified...
- qCritical() << "Failed to convert old pre-component config for instance" << d->m_instance->name();
- return false;
- }
- }
-
- // load the new component list and swap it with the current one...
- ComponentContainer newComponents;
- if(!loadPackProfile(this, filename, patchesPattern(), newComponents))
- {
- qCritical() << "Failed to load the component config for instance" << d->m_instance->name();
- return false;
- }
- else
- {
- // FIXME: actually use fine-grained updates, not this...
- beginResetModel();
- // disconnect all the old components
- for(auto component: d->components)
- {
- disconnect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged);
- }
- d->components.clear();
- d->componentIndex.clear();
- for(auto component: newComponents)
- {
- if(d->componentIndex.contains(component->m_uid))
- {
- qWarning() << "Ignoring duplicate component entry" << component->m_uid;
- continue;
- }
- connect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged);
- d->components.append(component);
- d->componentIndex[component->m_uid] = component;
- }
- endResetModel();
- d->loaded = true;
- return true;
- }
-}
-
-void PackProfile::reload(Net::Mode netmode)
-{
- // Do not reload when the update/resolve task is running. It is in control.
- if(d->m_updateTask)
- {
- return;
- }
-
- // flush any scheduled saves to not lose state
- saveNow();
-
- // FIXME: differentiate when a reapply is required by propagating state from components
- invalidateLaunchProfile();
-
- if(load())
- {
- resolve(netmode);
- }
-}
-
-shared_qobject_ptr<Task> PackProfile::getCurrentTask()
-{
- return d->m_updateTask;
-}
-
-void PackProfile::resolve(Net::Mode netmode)
-{
- auto updateTask = new ComponentUpdateTask(ComponentUpdateTask::Mode::Resolution, netmode, this);
- d->m_updateTask.reset(updateTask);
- connect(updateTask, &ComponentUpdateTask::succeeded, this, &PackProfile::updateSucceeded);
- connect(updateTask, &ComponentUpdateTask::failed, this, &PackProfile::updateFailed);
- d->m_updateTask->start();
-}
-
-
-void PackProfile::updateSucceeded()
-{
- qDebug() << "Component list update/resolve task succeeded for" << d->m_instance->name();
- d->m_updateTask.reset();
- invalidateLaunchProfile();
-}
-
-void PackProfile::updateFailed(const QString& error)
-{
- qDebug() << "Component list update/resolve task failed for" << d->m_instance->name() << "Reason:" << error;
- d->m_updateTask.reset();
- invalidateLaunchProfile();
-}
-
-// NOTE this is really old stuff, and only needs to be used when loading the old hardcoded component-unaware format (loadPreComponentConfig).
-static void upgradeDeprecatedFiles(QString root, QString instanceName)
-{
- auto versionJsonPath = FS::PathCombine(root, "version.json");
- auto customJsonPath = FS::PathCombine(root, "custom.json");
- auto mcJson = FS::PathCombine(root, "patches" , "net.minecraft.json");
-
- QString sourceFile;
- QString renameFile;
-
- // convert old crap.
- if(QFile::exists(customJsonPath))
- {
- sourceFile = customJsonPath;
- renameFile = versionJsonPath;
- }
- else if(QFile::exists(versionJsonPath))
- {
- sourceFile = versionJsonPath;
- }
- if(!sourceFile.isEmpty() && !QFile::exists(mcJson))
- {
- if(!FS::ensureFilePathExists(mcJson))
- {
- qWarning() << "Couldn't create patches folder for" << instanceName;
- return;
- }
- if(!renameFile.isEmpty() && QFile::exists(renameFile))
- {
- if(!QFile::rename(renameFile, renameFile + ".old"))
- {
- qWarning() << "Couldn't rename" << renameFile << "to" << renameFile + ".old" << "in" << instanceName;
- return;
- }
- }
- auto file = ProfileUtils::parseJsonFile(QFileInfo(sourceFile), false);
- ProfileUtils::removeLwjglFromPatch(file);
- file->uid = "net.minecraft";
- file->version = file->minecraftVersion;
- file->name = "Minecraft";
-
- Meta::Require needsLwjgl;
- needsLwjgl.uid = "org.lwjgl";
- file->requires.insert(needsLwjgl);
-
- if(!ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), mcJson))
- {
- return;
- }
- if(!QFile::rename(sourceFile, sourceFile + ".old"))
- {
- qWarning() << "Couldn't rename" << sourceFile << "to" << sourceFile + ".old" << "in" << instanceName;
- return;
- }
- }
-}
-
-/*
- * Migrate old layout to the component based one...
- * - Part of the version information is taken from `instance.cfg` (fed to this class from outside).
- * - Part is taken from the old order.json file.
- * - Part is loaded from loose json files in the instance's `patches` directory.
- */
-bool PackProfile::migratePreComponentConfig()
-{
- // upgrade the very old files from the beginnings of MultiMC 5
- upgradeDeprecatedFiles(d->m_instance->instanceRoot(), d->m_instance->name());
-
- QList<ComponentPtr> components;
- QSet<QString> loaded;
-
- auto addBuiltinPatch = [&](const QString &uid, bool asDependency, const QString & emptyVersion, const Meta::Require & req, const Meta::Require & conflict)
- {
- auto jsonFilePath = FS::PathCombine(d->m_instance->instanceRoot(), "patches" , uid + ".json");
- auto intendedVersion = d->getOldConfigVersion(uid);
- // load up the base minecraft patch
- ComponentPtr component;
- if(QFile::exists(jsonFilePath))
- {
- if(intendedVersion.isEmpty())
- {
- intendedVersion = emptyVersion;
- }
- auto file = ProfileUtils::parseJsonFile(QFileInfo(jsonFilePath), false);
- // fix uid
- file->uid = uid;
- // if version is missing, add it from the outside.
- if(file->version.isEmpty())
- {
- file->version = intendedVersion;
- }
- // if this is a dependency (LWJGL), mark it also as volatile
- if(asDependency)
- {
- file->m_volatile = true;
- }
- // insert requirements if needed
- if(!req.uid.isEmpty())
- {
- file->requires.insert(req);
- }
- // insert conflicts if needed
- if(!conflict.uid.isEmpty())
- {
- file->conflicts.insert(conflict);
- }
- // FIXME: @QUALITY do not ignore return value
- ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), jsonFilePath);
- component = new Component(this, uid, file);
- component->m_version = intendedVersion;
- }
- else if(!intendedVersion.isEmpty())
- {
- auto metaVersion = ENV.metadataIndex()->get(uid, intendedVersion);
- component = new Component(this, metaVersion);
- }
- else
- {
- return;
- }
- component->m_dependencyOnly = asDependency;
- component->m_important = !asDependency;
- components.append(component);
- };
- // TODO: insert depends and conflicts here if these are customized files...
- Meta::Require reqLwjgl;
- reqLwjgl.uid = "org.lwjgl";
- reqLwjgl.suggests = "2.9.1";
- Meta::Require conflictLwjgl3;
- conflictLwjgl3.uid = "org.lwjgl3";
- Meta::Require nullReq;
- addBuiltinPatch("org.lwjgl", true, "2.9.1", nullReq, conflictLwjgl3);
- addBuiltinPatch("net.minecraft", false, QString(), reqLwjgl, nullReq);
-
- // first, collect all other file-based patches and load them
- QMap<QString, ComponentPtr> loadedComponents;
- QDir patchesDir(FS::PathCombine(d->m_instance->instanceRoot(),"patches"));
- for (auto info : patchesDir.entryInfoList(QStringList() << "*.json", QDir::Files))
- {
- // parse the file
- qDebug() << "Reading" << info.fileName();
- auto file = ProfileUtils::parseJsonFile(info, true);
-
- // correct missing or wrong uid based on the file name
- QString uid = info.completeBaseName();
-
- // ignore builtins, they've been handled already
- if (uid == "net.minecraft")
- continue;
- if (uid == "org.lwjgl")
- continue;
-
- // handle horrible corner cases
- if(uid.isEmpty())
- {
- // if you have a file named '.json', make it just go away.
- // FIXME: @QUALITY do not ignore return value
- QFile::remove(info.absoluteFilePath());
- continue;
- }
- file->uid = uid;
- // FIXME: @QUALITY do not ignore return value
- ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), info.absoluteFilePath());
-
- auto component = new Component(this, file->uid, file);
- auto version = d->getOldConfigVersion(file->uid);
- if(!version.isEmpty())
- {
- component->m_version = version;
- }
- loadedComponents[file->uid] = component;
- }
- // try to load the other 'hardcoded' patches (forge, liteloader), if they weren't loaded from files
- auto loadSpecial = [&](const QString & uid, int order)
- {
- auto patchVersion = d->getOldConfigVersion(uid);
- if(!patchVersion.isEmpty() && !loadedComponents.contains(uid))
- {
- auto patch = new Component(this, ENV.metadataIndex()->get(uid, patchVersion));
- patch->setOrder(order);
- loadedComponents[uid] = patch;
- }
- };
- loadSpecial("net.minecraftforge", 5);
- loadSpecial("com.mumfrey.liteloader", 10);
-
- // load the old order.json file, if present
- ProfileUtils::PatchOrder userOrder;
- ProfileUtils::readOverrideOrders(FS::PathCombine(d->m_instance->instanceRoot(), "order.json"), userOrder);
-
- // now add all the patches by user sort order
- for (auto uid : userOrder)
- {
- // ignore builtins
- if (uid == "net.minecraft")
- continue;
- if (uid == "org.lwjgl")
- continue;
- // ordering has a patch that is gone?
- if(!loadedComponents.contains(uid))
- {
- continue;
- }
- components.append(loadedComponents.take(uid));
- }
-
- // is there anything left to sort? - this is used when there are leftover components that aren't part of the order.json
- if(!loadedComponents.isEmpty())
- {
- // inserting into multimap by order number as key sorts the patches and detects duplicates
- QMultiMap<int, ComponentPtr> files;
- auto iter = loadedComponents.begin();
- while(iter != loadedComponents.end())
- {
- files.insert((*iter)->getOrder(), *iter);
- iter++;
- }
-
- // then just extract the patches and put them in the list
- for (auto order : files.keys())
- {
- const auto &values = files.values(order);
- for(auto &value: values)
- {
- // TODO: put back the insertion of problem messages here, so the user knows about the id duplication
- components.append(value);
- }
- }
- }
- // new we have a complete list of components...
- return savePackProfile(componentsFilePath(), components);
-}
-
-// END: save/load
-
-void PackProfile::appendComponent(ComponentPtr component)
-{
- insertComponent(d->components.size(), component);
-}
-
-void PackProfile::insertComponent(size_t index, ComponentPtr component)
-{
- auto id = component->getID();
- if(id.isEmpty())
- {
- qWarning() << "Attempt to add a component with empty ID!";
- return;
- }
- if(d->componentIndex.contains(id))
- {
- qWarning() << "Attempt to add a component that is already present!";
- return;
- }
- beginInsertRows(QModelIndex(), index, index);
- d->components.insert(index, component);
- d->componentIndex[id] = component;
- endInsertRows();
- connect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged);
- scheduleSave();
-}
-
-void PackProfile::componentDataChanged()
-{
- auto objPtr = qobject_cast<Component *>(sender());
- if(!objPtr)
- {
- qWarning() << "PackProfile got dataChenged signal from a non-Component!";
- return;
- }
- if(objPtr->getID() == "net.minecraft") {
- emit minecraftChanged();
- }
- // figure out which one is it... in a seriously dumb way.
- int index = 0;
- for (auto component: d->components)
- {
- if(component.get() == objPtr)
- {
- emit dataChanged(createIndex(index, 0), createIndex(index, columnCount(QModelIndex()) - 1));
- scheduleSave();
- return;
- }
- index++;
- }
- qWarning() << "PackProfile got dataChenged signal from a Component which does not belong to it!";
-}
-
-bool PackProfile::remove(const int index)
-{
- auto patch = getComponent(index);
- if (!patch->isRemovable())
- {
- qWarning() << "Patch" << patch->getID() << "is non-removable";
- return false;
- }
-
- if(!removeComponent_internal(patch))
- {
- qCritical() << "Patch" << patch->getID() << "could not be removed";
- return false;
- }
-
- beginRemoveRows(QModelIndex(), index, index);
- d->components.removeAt(index);
- d->componentIndex.remove(patch->getID());
- endRemoveRows();
- invalidateLaunchProfile();
- scheduleSave();
- return true;
-}
-
-bool PackProfile::remove(const QString id)
-{
- int i = 0;
- for (auto patch : d->components)
- {
- if (patch->getID() == id)
- {
- return remove(i);
- }
- i++;
- }
- return false;
-}
-
-bool PackProfile::customize(int index)
-{
- auto patch = getComponent(index);
- if (!patch->isCustomizable())
- {
- qDebug() << "Patch" << patch->getID() << "is not customizable";
- return false;
- }
- if(!patch->customize())
- {
- qCritical() << "Patch" << patch->getID() << "could not be customized";
- return false;
- }
- invalidateLaunchProfile();
- scheduleSave();
- return true;
-}
-
-bool PackProfile::revertToBase(int index)
-{
- auto patch = getComponent(index);
- if (!patch->isRevertible())
- {
- qDebug() << "Patch" << patch->getID() << "is not revertible";
- return false;
- }
- if(!patch->revert())
- {
- qCritical() << "Patch" << patch->getID() << "could not be reverted";
- return false;
- }
- invalidateLaunchProfile();
- scheduleSave();
- return true;
-}
-
-Component * PackProfile::getComponent(const QString &id)
-{
- auto iter = d->componentIndex.find(id);
- if (iter == d->componentIndex.end())
- {
- return nullptr;
- }
- return (*iter).get();
-}
-
-Component * PackProfile::getComponent(int index)
-{
- if(index < 0 || index >= d->components.size())
- {
- return nullptr;
- }
- return d->components[index].get();
-}
-
-QVariant PackProfile::data(const QModelIndex &index, int role) const
-{
- if (!index.isValid())
- return QVariant();
-
- int row = index.row();
- int column = index.column();
-
- if (row < 0 || row >= d->components.size())
- return QVariant();
-
- auto patch = d->components.at(row);
-
- switch (role)
- {
- case Qt::CheckStateRole:
- {
- switch (column)
- {
- case NameColumn: {
- return patch->isEnabled() ? Qt::Checked : Qt::Unchecked;
- }
- default:
- return QVariant();
- }
- }
- case Qt::DisplayRole:
- {
- switch (column)
- {
- case NameColumn:
- return patch->getName();
- case VersionColumn:
- {
- if(patch->isCustom())
- {
- return QString("%1 (Custom)").arg(patch->getVersion());
- }
- else
- {
- return patch->getVersion();
- }
- }
- default:
- return QVariant();
- }
- }
- case Qt::DecorationRole:
- {
- switch(column)
- {
- case NameColumn:
- {
- auto severity = patch->getProblemSeverity();
- switch (severity)
- {
- case ProblemSeverity::Warning:
- return "warning";
- case ProblemSeverity::Error:
- return "error";
- default:
- return QVariant();
- }
- }
- default:
- {
- return QVariant();
- }
- }
- }
- }
- return QVariant();
-}
-
-bool PackProfile::setData(const QModelIndex& index, const QVariant& value, int role)
-{
- if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index))
- {
- return false;
- }
-
- if (role == Qt::CheckStateRole)
- {
- auto component = d->components[index.row()];
- if (component->setEnabled(!component->isEnabled()))
- {
- return true;
- }
- }
- return false;
-}
-
-QVariant PackProfile::headerData(int section, Qt::Orientation orientation, int role) const
-{
- if (orientation == Qt::Horizontal)
- {
- if (role == Qt::DisplayRole)
- {
- switch (section)
- {
- case NameColumn:
- return tr("Name");
- case VersionColumn:
- return tr("Version");
- default:
- return QVariant();
- }
- }
- }
- return QVariant();
-}
-
-// FIXME: zero precision mess
-Qt::ItemFlags PackProfile::flags(const QModelIndex &index) const
-{
- if (!index.isValid()) {
- return Qt::NoItemFlags;
- }
-
- Qt::ItemFlags outFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
-
- int row = index.row();
-
- if (row < 0 || row >= d->components.size()) {
- return Qt::NoItemFlags;
- }
-
- auto patch = d->components.at(row);
- // TODO: this will need fine-tuning later...
- if(patch->canBeDisabled() && !d->interactionDisabled)
- {
- outFlags |= Qt::ItemIsUserCheckable;
- }
- return outFlags;
-}
-
-int PackProfile::rowCount(const QModelIndex &parent) const
-{
- return d->components.size();
-}
-
-int PackProfile::columnCount(const QModelIndex &parent) const
-{
- return NUM_COLUMNS;
-}
-
-void PackProfile::move(const int index, const MoveDirection direction)
-{
- int theirIndex;
- if (direction == MoveUp)
- {
- theirIndex = index - 1;
- }
- else
- {
- theirIndex = index + 1;
- }
-
- if (index < 0 || index >= d->components.size())
- return;
- if (theirIndex >= rowCount())
- theirIndex = rowCount() - 1;
- if (theirIndex == -1)
- theirIndex = rowCount() - 1;
- if (index == theirIndex)
- return;
- int togap = theirIndex > index ? theirIndex + 1 : theirIndex;
-
- auto from = getComponent(index);
- auto to = getComponent(theirIndex);
-
- if (!from || !to || !to->isMoveable() || !from->isMoveable())
- {
- return;
- }
- beginMoveRows(QModelIndex(), index, index, QModelIndex(), togap);
- d->components.swap(index, theirIndex);
- endMoveRows();
- invalidateLaunchProfile();
- scheduleSave();
-}
-
-void PackProfile::invalidateLaunchProfile()
-{
- d->m_profile.reset();
-}
-
-void PackProfile::installJarMods(QStringList selectedFiles)
-{
- installJarMods_internal(selectedFiles);
-}
-
-void PackProfile::installCustomJar(QString selectedFile)
-{
- installCustomJar_internal(selectedFile);
-}
-
-bool PackProfile::installEmpty(const QString& uid, const QString& name)
-{
- QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches");
- if(!FS::ensureFolderPathExists(patchDir))
- {
- return false;
- }
- auto f = std::make_shared<VersionFile>();
- f->name = name;
- f->uid = uid;
- f->version = "1";
- QString patchFileName = FS::PathCombine(patchDir, uid + ".json");
- QFile file(patchFileName);
- if (!file.open(QFile::WriteOnly))
- {
- qCritical() << "Error opening" << file.fileName()
- << "for reading:" << file.errorString();
- return false;
- }
- file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
- file.close();
-
- appendComponent(new Component(this, f->uid, f));
- scheduleSave();
- invalidateLaunchProfile();
- return true;
-}
-
-bool PackProfile::removeComponent_internal(ComponentPtr patch)
-{
- bool ok = true;
- // first, remove the patch file. this ensures it's not used anymore
- auto fileName = patch->getFilename();
- if(fileName.size())
- {
- QFile patchFile(fileName);
- if(patchFile.exists() && !patchFile.remove())
- {
- qCritical() << "File" << fileName << "could not be removed because:" << patchFile.errorString();
- return false;
- }
- }
-
- // FIXME: we need a generic way of removing local resources, not just jar mods...
- auto preRemoveJarMod = [&](LibraryPtr jarMod) -> bool
- {
- if (!jarMod->isLocal())
- {
- return true;
- }
- QStringList jar, temp1, temp2, temp3;
- jarMod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, d->m_instance->jarmodsPath().absolutePath());
- QFileInfo finfo (jar[0]);
- if(finfo.exists())
- {
- QFile jarModFile(jar[0]);
- if(!jarModFile.remove())
- {
- qCritical() << "File" << jar[0] << "could not be removed because:" << jarModFile.errorString();
- return false;
- }
- return true;
- }
- return true;
- };
-
- auto vFile = patch->getVersionFile();
- if(vFile)
- {
- auto &jarMods = vFile->jarMods;
- for(auto &jarmod: jarMods)
- {
- ok &= preRemoveJarMod(jarmod);
- }
- }
- return ok;
-}
-
-bool PackProfile::installJarMods_internal(QStringList filepaths)
-{
- QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches");
- if(!FS::ensureFolderPathExists(patchDir))
- {
- return false;
- }
-
- if (!FS::ensureFolderPathExists(d->m_instance->jarModsDir()))
- {
- return false;
- }
-
- for(auto filepath:filepaths)
- {
- QFileInfo sourceInfo(filepath);
- auto uuid = QUuid::createUuid();
- QString id = uuid.toString().remove('{').remove('}');
- QString target_filename = id + ".jar";
- QString target_id = "org.multimc.jarmod." + id;
- QString target_name = sourceInfo.completeBaseName() + " (jar mod)";
- QString finalPath = FS::PathCombine(d->m_instance->jarModsDir(), target_filename);
-
- QFileInfo targetInfo(finalPath);
- if(targetInfo.exists())
- {
- return false;
- }
-
- if (!QFile::copy(sourceInfo.absoluteFilePath(),QFileInfo(finalPath).absoluteFilePath()))
- {
- return false;
- }
-
- auto f = std::make_shared<VersionFile>();
- auto jarMod = std::make_shared<Library>();
- jarMod->setRawName(GradleSpecifier("org.multimc.jarmods:" + id + ":1"));
- jarMod->setFilename(target_filename);
- jarMod->setDisplayName(sourceInfo.completeBaseName());
- jarMod->setHint("local");
- f->jarMods.append(jarMod);
- f->name = target_name;
- f->uid = target_id;
- QString patchFileName = FS::PathCombine(patchDir, target_id + ".json");
-
- QFile file(patchFileName);
- if (!file.open(QFile::WriteOnly))
- {
- qCritical() << "Error opening" << file.fileName()
- << "for reading:" << file.errorString();
- return false;
- }
- file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
- file.close();
-
- appendComponent(new Component(this, f->uid, f));
- }
- scheduleSave();
- invalidateLaunchProfile();
- return true;
-}
-
-bool PackProfile::installCustomJar_internal(QString filepath)
-{
- QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches");
- if(!FS::ensureFolderPathExists(patchDir))
- {
- return false;
- }
-
- QString libDir = d->m_instance->getLocalLibraryPath();
- if (!FS::ensureFolderPathExists(libDir))
- {
- return false;
- }
-
- auto specifier = GradleSpecifier("org.multimc:customjar:1");
- QFileInfo sourceInfo(filepath);
- QString target_filename = specifier.getFileName();
- QString target_id = specifier.artifactId();
- QString target_name = sourceInfo.completeBaseName() + " (custom jar)";
- QString finalPath = FS::PathCombine(libDir, target_filename);
-
- QFileInfo jarInfo(finalPath);
- if (jarInfo.exists())
- {
- if(!QFile::remove(finalPath))
- {
- return false;
- }
- }
- if (!QFile::copy(filepath, finalPath))
- {
- return false;
- }
-
- auto f = std::make_shared<VersionFile>();
- auto jarMod = std::make_shared<Library>();
- jarMod->setRawName(specifier);
- jarMod->setDisplayName(sourceInfo.completeBaseName());
- jarMod->setHint("local");
- f->mainJar = jarMod;
- f->name = target_name;
- f->uid = target_id;
- QString patchFileName = FS::PathCombine(patchDir, target_id + ".json");
-
- QFile file(patchFileName);
- if (!file.open(QFile::WriteOnly))
- {
- qCritical() << "Error opening" << file.fileName()
- << "for reading:" << file.errorString();
- return false;
- }
- file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
- file.close();
-
- appendComponent(new Component(this, f->uid, f));
-
- scheduleSave();
- invalidateLaunchProfile();
- return true;
-}
-
-std::shared_ptr<LaunchProfile> PackProfile::getProfile() const
-{
- if(!d->m_profile)
- {
- try
- {
- auto profile = std::make_shared<LaunchProfile>();
- for(auto file: d->components)
- {
- qDebug() << "Applying" << file->getID() << (file->getProblemSeverity() == ProblemSeverity::Error ? "ERROR" : "GOOD");
- file->applyTo(profile.get());
- }
- d->m_profile = profile;
- }
- catch (const Exception &error)
- {
- qWarning() << "Couldn't apply profile patches because: " << error.cause();
- }
- }
- return d->m_profile;
-}
-
-void PackProfile::setOldConfigVersion(const QString& uid, const QString& version)
-{
- if(version.isEmpty())
- {
- return;
- }
- d->m_oldConfigVersions[uid] = version;
-}
-
-bool PackProfile::setComponentVersion(const QString& uid, const QString& version, bool important)
-{
- auto iter = d->componentIndex.find(uid);
- if(iter != d->componentIndex.end())
- {
- ComponentPtr component = *iter;
- // set existing
- if(component->revert())
- {
- component->setVersion(version);
- component->setImportant(important);
- return true;
- }
- return false;
- }
- else
- {
- // add new
- auto component = new Component(this, uid);
- component->m_version = version;
- component->m_important = important;
- appendComponent(component);
- return true;
- }
-}
-
-QString PackProfile::getComponentVersion(const QString& uid) const
-{
- const auto iter = d->componentIndex.find(uid);
- if (iter != d->componentIndex.end())
- {
- return (*iter)->getVersion();
- }
- return QString();
-}
-
-void PackProfile::disableInteraction(bool disable)
-{
- if(d->interactionDisabled != disable) {
- d->interactionDisabled = disable;
- auto size = d->components.size();
- if(size) {
- emit dataChanged(index(0), index(size - 1));
- }
- }
-}