Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into fix_mc_launch

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
Trial97 2024-09-09 08:11:11 +03:00
commit 2622d3daa9
No known key found for this signature in database
GPG Key ID: 55EF5DA53DB36318
14 changed files with 596 additions and 84 deletions

View File

@ -207,6 +207,11 @@ set(ICONS_SOURCES
# Support for Minecraft instances and launch
set(MINECRAFT_SOURCES
# Logging
minecraft/Logging.h
minecraft/Logging.cpp
# Minecraft support
minecraft/auth/AccountData.cpp
minecraft/auth/AccountData.h
@ -661,6 +666,22 @@ ecm_qt_declare_logging_category(CORE_SOURCES
EXPORT "${Launcher_Name}"
)
ecm_qt_export_logging_category(
IDENTIFIER instanceProfileC
CATEGORY_NAME "launcher.instance.profile"
DEFAULT_SEVERITY Debug
DESCRIPTION "Profile actions"
EXPORT "${Launcher_Name}"
)
ecm_qt_export_logging_category(
IDENTIFIER instanceProfileResolveC
CATEGORY_NAME "launcher.instance.profile.resolve"
DEFAULT_SEVERITY Debug
DESCRIPTION "Profile component resolusion actions"
EXPORT "${Launcher_Name}"
)
ecm_qt_export_logging_category(
IDENTIFIER taskLogC
CATEGORY_NAME "launcher.task"
@ -673,7 +694,7 @@ ecm_qt_export_logging_category(
IDENTIFIER taskNetLogC
CATEGORY_NAME "launcher.task.net"
DEFAULT_SEVERITY Debug
DESCRIPTION "task network action"
DESCRIPTION "Task network action"
EXPORT "${Launcher_Name}"
)
@ -681,14 +702,14 @@ ecm_qt_export_logging_category(
IDENTIFIER taskDownloadLogC
CATEGORY_NAME "launcher.task.net.download"
DEFAULT_SEVERITY Debug
DESCRIPTION "task network download actions"
DESCRIPTION "Task network download actions"
EXPORT "${Launcher_Name}"
)
ecm_qt_export_logging_category(
IDENTIFIER taskUploadLogC
CATEGORY_NAME "launcher.task.net.upload"
DEFAULT_SEVERITY Debug
DESCRIPTION "task network upload actions"
DESCRIPTION "Task network upload actions"
EXPORT "${Launcher_Name}"
)

View File

@ -921,6 +921,10 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
if (destination.isEmpty()) {
destination = PathCombine(getDesktopDir(), RemoveInvalidFilenameChars(name));
}
if (!ensureFilePathExists(destination)) {
qWarning() << "Destination path can't be created!";
return false;
}
#if defined(Q_OS_MACOS)
// Create the Application
QDir applicationDirectory =

View File

@ -16,6 +16,7 @@
#include "VersionList.h"
#include <QDateTime>
#include <algorithm>
#include "Application.h"
#include "Index.h"
@ -99,7 +100,7 @@ QVariant VersionList::data(const QModelIndex& index, int role) const
case VersionPtrRole:
return QVariant::fromValue(version);
case RecommendedRole:
return version->isRecommended();
return version->isRecommended() || m_externalRecommendsVersions.contains(version->version());
case JavaMajorRole: {
auto major = version->version();
if (major.startsWith("java")) {
@ -192,6 +193,16 @@ void VersionList::parse(const QJsonObject& obj)
parseVersionList(obj, this);
}
void VersionList::addExternalRecommends(const QStringList& recommends)
{
m_externalRecommendsVersions.append(recommends);
}
void VersionList::clearExternalRecommends()
{
m_externalRecommendsVersions.clear();
}
// FIXME: this is dumb, we have 'recommended' as part of the metadata already...
static const Meta::Version::Ptr& getBetterVersion(const Meta::Version::Ptr& a, const Meta::Version::Ptr& b)
{
@ -276,4 +287,35 @@ void VersionList::waitToLoad()
task->start();
ev.exec();
}
Version::Ptr VersionList::getRecommendedForParent(const QString& uid, const QString& version)
{
auto foundExplicit = std::find_if(m_versions.begin(), m_versions.end(), [uid, version](Version::Ptr ver) -> bool {
auto& reqs = ver->requiredSet();
auto parentReq = std::find_if(reqs.begin(), reqs.end(), [uid, version](const Require& req) -> bool {
return req.uid == uid && req.equalsVersion == version;
});
return parentReq != reqs.end() && ver->isRecommended();
});
if (foundExplicit != m_versions.end()) {
return *foundExplicit;
}
return nullptr;
}
Version::Ptr VersionList::getLatestForParent(const QString& uid, const QString& version)
{
Version::Ptr latestCompat = nullptr;
for (auto ver : m_versions) {
auto& reqs = ver->requiredSet();
auto parentReq = std::find_if(reqs.begin(), reqs.end(), [uid, version](const Require& req) -> bool {
return req.uid == uid && req.equalsVersion == version;
});
if (parentReq != reqs.end()) {
latestCompat = getBetterVersion(latestCompat, ver);
}
}
return latestCompat;
}
} // namespace Meta

View File

@ -43,6 +43,8 @@ class VersionList : public BaseVersionList, public BaseEntity {
void sortVersions() override;
BaseVersion::Ptr getRecommended() const override;
Version::Ptr getRecommendedForParent(const QString& uid, const QString& version);
Version::Ptr getLatestForParent(const QString& uid, const QString& version);
QVariant data(const QModelIndex& index, int role) const override;
RoleList providesRoles() const override;
@ -70,6 +72,8 @@ class VersionList : public BaseVersionList, public BaseEntity {
void merge(const VersionList::Ptr& other);
void mergeFromIndex(const VersionList::Ptr& other);
void parse(const QJsonObject& obj) override;
void addExternalRecommends(const QStringList& recommends);
void clearExternalRecommends();
signals:
void nameChanged(const QString& name);
@ -79,6 +83,7 @@ class VersionList : public BaseVersionList, public BaseEntity {
private:
QVector<Version::Ptr> m_versions;
QStringList m_externalRecommendsVersions;
QHash<QString, Version::Ptr> m_lookup;
QString m_uid;
QString m_name;

View File

@ -44,10 +44,19 @@
#include "OneSixVersionFormat.h"
#include "VersionFile.h"
#include "meta/Version.h"
#include "minecraft/Component.h"
#include "minecraft/PackProfile.h"
#include <assert.h>
const QMap<QString, ModloaderMapEntry> Component::KNOWN_MODLOADERS = {
{ "net.neoforged", { ModPlatform::NeoForge, { "net.minecraftforge", "net.fabricmc.fabric-loader", "org.quiltmc.quilt-loader" } } },
{ "net.minecraftforge", { ModPlatform::Forge, { "net.neoforged", "net.fabricmc.fabric-loader", "org.quiltmc.quilt-loader" } } },
{ "net.fabricmc.fabric-loader", { ModPlatform::Fabric, { "net.minecraftforge", "net.neoforged", "org.quiltmc.quilt-loader" } } },
{ "org.quiltmc.quilt-loader", { ModPlatform::Quilt, { "net.minecraftforge", "net.neoforged", "net.fabricmc.fabric-loader" } } },
{ "com.mumfrey.liteloader", { ModPlatform::LiteLoader, {} } }
};
Component::Component(PackProfile* parent, const QString& uid)
{
assert(parent);
@ -223,6 +232,22 @@ bool Component::isVersionChangeable()
return false;
}
bool Component::isKnownModloader()
{
auto iter = KNOWN_MODLOADERS.find(m_uid);
return iter != KNOWN_MODLOADERS.cend();
}
QStringList Component::knownConflictingComponents()
{
auto iter = KNOWN_MODLOADERS.find(m_uid);
if (iter != KNOWN_MODLOADERS.cend()) {
return (*iter).knownConflictingComponents;
} else {
return {};
}
}
void Component::setImportant(bool state)
{
if (m_important != state) {
@ -235,7 +260,8 @@ ProblemSeverity Component::getProblemSeverity() const
{
auto file = getVersionFile();
if (file) {
return file->getProblemSeverity();
auto severity = file->getProblemSeverity();
return m_componentProblemSeverity > severity ? m_componentProblemSeverity : severity;
}
return ProblemSeverity::Error;
}
@ -244,11 +270,31 @@ const QList<PatchProblem> Component::getProblems() const
{
auto file = getVersionFile();
if (file) {
return file->getProblems();
auto problems = file->getProblems();
problems.append(m_componentProblems);
return problems;
}
return { { ProblemSeverity::Error, QObject::tr("Patch is not loaded yet.") } };
}
void Component::addComponentProblem(ProblemSeverity severity, const QString& description)
{
if (severity > m_componentProblemSeverity) {
m_componentProblemSeverity = severity;
}
m_componentProblems.append({ severity, description });
emit dataChanged();
}
void Component::resetComponentProblems()
{
m_componentProblems.clear();
m_componentProblemSeverity = ProblemSeverity::None;
emit dataChanged();
}
void Component::setVersion(const QString& version)
{
if (version == m_version) {
@ -402,3 +448,36 @@ void Component::updateCachedData()
emit dataChanged();
}
}
void Component::waitLoadMeta()
{
if (!m_loaded) {
if (!m_metaVersion || !m_metaVersion->isLoaded()) {
// wait for the loaded version from meta
m_metaVersion = APPLICATION->metadataIndex()->getLoadedVersion(m_uid, m_version);
}
m_loaded = true;
updateCachedData();
}
}
void Component::setUpdateAction(UpdateAction action)
{
m_updateAction = action;
}
UpdateAction Component::getUpdateAction()
{
return m_updateAction;
}
void Component::clearUpdateAction()
{
m_updateAction = UpdateAction{ UpdateActionNone{} };
}
QDebug operator<<(QDebug d, const Component& comp)
{
d << "Component(" << comp.m_uid << " : " << comp.m_cachedVersion << ")";
return d;
}

View File

@ -4,9 +4,12 @@
#include <QJsonDocument>
#include <QList>
#include <memory>
#include <optional>
#include <variant>
#include "ProblemProvider.h"
#include "QObjectPtr.h"
#include "meta/JsonFormat.h"
#include "modplatform/ModIndex.h"
class PackProfile;
class LaunchProfile;
@ -16,6 +19,36 @@ class VersionList;
} // namespace Meta
class VersionFile;
struct UpdateActionChangeVersion {
/// version to change to
QString targetVersion;
};
struct UpdateActionLatestRecommendedCompatible {
/// Parent uid
QString parentUid;
QString parentName;
/// Parent version
QString version;
///
};
struct UpdateActionRemove {};
struct UpdateActionImportantChanged {
QString oldVersion;
};
using UpdateActionNone = std::monostate;
using UpdateAction = std::variant<UpdateActionNone,
UpdateActionChangeVersion,
UpdateActionLatestRecommendedCompatible,
UpdateActionRemove,
UpdateActionImportantChanged>;
struct ModloaderMapEntry {
ModPlatform::ModLoaderType type;
QStringList knownConflictingComponents;
};
class Component : public QObject, public ProblemProvider {
Q_OBJECT
public:
@ -26,6 +59,8 @@ class Component : public QObject, public ProblemProvider {
virtual ~Component() {}
static const QMap<QString, ModloaderMapEntry> KNOWN_MODLOADERS;
void applyTo(LaunchProfile* profile);
bool isEnabled();
@ -38,6 +73,8 @@ class Component : public QObject, public ProblemProvider {
bool isRemovable();
bool isCustom();
bool isVersionChangeable();
bool isKnownModloader();
QStringList knownConflictingComponents();
// DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code
void setOrder(int order);
@ -58,6 +95,8 @@ class Component : public QObject, public ProblemProvider {
const QList<PatchProblem> getProblems() const override;
ProblemSeverity getProblemSeverity() const override;
void addComponentProblem(ProblemSeverity severity, const QString& description);
void resetComponentProblems();
void setVersion(const QString& version);
bool customize();
@ -65,6 +104,12 @@ class Component : public QObject, public ProblemProvider {
void updateCachedData();
void waitLoadMeta();
void setUpdateAction(UpdateAction action);
void clearUpdateAction();
UpdateAction getUpdateAction();
signals:
void dataChanged();
@ -102,6 +147,11 @@ class Component : public QObject, public ProblemProvider {
std::shared_ptr<Meta::Version> m_metaVersion;
std::shared_ptr<VersionFile> m_file;
bool m_loaded = false;
private:
QList<PatchProblem> m_componentProblems;
ProblemSeverity m_componentProblemSeverity = ProblemSeverity::None;
UpdateAction m_updateAction = UpdateAction{ UpdateActionNone{} };
};
using ComponentPtr = shared_qobject_ptr<Component>;

View File

@ -1,13 +1,16 @@
#include "ComponentUpdateTask.h"
#include <algorithm>
#include "Component.h"
#include "ComponentUpdateTask_p.h"
#include "PackProfile.h"
#include "PackProfile_p.h"
#include "ProblemProvider.h"
#include "Version.h"
#include "cassert"
#include "meta/Index.h"
#include "meta/Version.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/OneSixVersionFormat.h"
#include "minecraft/ProfileUtils.h"
#include "net/Mode.h"
@ -15,6 +18,8 @@
#include "Application.h"
#include "tasks/Task.h"
#include "minecraft/Logging.h"
/*
* This is responsible for loading the components of a component list AND resolving dependency issues between them
*/
@ -36,7 +41,7 @@
ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list, QObject* parent) : Task(parent)
{
d.reset(new ComponentUpdateTaskData);
d->m_list = list;
d->m_profile = list;
d->mode = mode;
d->netmode = netmode;
}
@ -45,7 +50,7 @@ ComponentUpdateTask::~ComponentUpdateTask() {}
void ComponentUpdateTask::executeTask()
{
qDebug() << "Loading components";
qCDebug(instanceProfileResolveC) << "Loading components";
loadComponents();
}
@ -63,7 +68,7 @@ LoadResult composeLoadResult(LoadResult a, LoadResult b)
static LoadResult loadComponent(ComponentPtr component, Task::Ptr& loadTask, Net::Mode netmode)
{
if (component->m_loaded) {
qDebug() << component->getName() << "is already loaded";
qCDebug(instanceProfileResolveC) << component->getName() << "is already loaded";
return LoadResult::LoadedLocal;
}
@ -144,10 +149,11 @@ void ComponentUpdateTask::loadComponents()
d->remoteLoadSuccessful = true;
// load all the components OR their lists...
for (auto component : d->m_list->d->components) {
for (auto component : d->m_profile->d->components) {
Task::Ptr loadTask;
LoadResult singleResult;
RemoteLoadStatus::Type loadType;
component->resetComponentProblems();
// FIXME: to do this right, we need to load the lists and decide on which versions to use during dependency resolution. For now,
// ignore all that...
#if 0
@ -175,7 +181,8 @@ void ComponentUpdateTask::loadComponents()
}
result = composeLoadResult(result, singleResult);
if (loadTask) {
qDebug() << "Remote loading is being run for" << component->getName();
qCDebug(instanceProfileResolveC) << d->m_profile->d->m_instance->name() << "|"
<< "Remote loading is being run for" << component->getName();
connect(loadTask.get(), &Task::succeeded, this, [this, taskIndex]() { remoteLoadSucceeded(taskIndex); });
connect(loadTask.get(), &Task::failed, this, [this, taskIndex](const QString& error) { remoteLoadFailed(taskIndex, error); });
connect(loadTask.get(), &Task::aborted, this, [this, taskIndex]() { remoteLoadFailed(taskIndex, tr("Aborted")); });
@ -192,6 +199,7 @@ void ComponentUpdateTask::loadComponents()
switch (result) {
case LoadResult::LoadedLocal: {
// Everything got loaded. Advance to dependency resolution.
performUpdateActions();
resolveDependencies(d->mode == Mode::Launch || d->netmode == Net::Mode::Offline);
break;
}
@ -270,8 +278,8 @@ static bool gatherRequirementsFromComponents(const ComponentContainer& input, Re
output.erase(componenRequireEx);
output.insert(result.outcome);
} else {
qCritical() << "Conflicting requirements:" << componentRequire.uid << "versions:" << componentRequire.equalsVersion
<< ";" << (*found).equalsVersion;
qCCritical(instanceProfileResolveC) << "Conflicting requirements:" << componentRequire.uid
<< "versions:" << componentRequire.equalsVersion << ";" << (*found).equalsVersion;
}
succeeded &= result.ok;
} else {
@ -353,22 +361,22 @@ static bool getTrivialComponentChanges(const ComponentIndex& index, const Requir
} while (false);
switch (decision) {
case Decision::Undetermined:
qCritical() << "No decision for" << reqStr;
qCCritical(instanceProfileResolveC) << "No decision for" << reqStr;
succeeded = false;
break;
case Decision::Met:
qDebug() << reqStr << "Is met.";
qCDebug(instanceProfileResolveC) << reqStr << "Is met.";
break;
case Decision::Missing:
qDebug() << reqStr << "Is missing and should be added at" << req.indexOfFirstDependee;
qCDebug(instanceProfileResolveC) << reqStr << "Is missing and should be added at" << req.indexOfFirstDependee;
toAdd.insert(req);
break;
case Decision::VersionNotSame:
qDebug() << reqStr << "already has different version that can be changed.";
qCDebug(instanceProfileResolveC) << reqStr << "already has different version that can be changed.";
toChange.insert(req);
break;
case Decision::LockedVersionNotSame:
qDebug() << reqStr << "already has different version that cannot be changed.";
qCDebug(instanceProfileResolveC) << reqStr << "already has different version that cannot be changed.";
succeeded = false;
break;
}
@ -376,12 +384,48 @@ static bool getTrivialComponentChanges(const ComponentIndex& index, const Requir
return succeeded;
}
ComponentContainer ComponentUpdateTask::collectTreeLinked(const QString& uid)
{
ComponentContainer linked;
auto& components = d->m_profile->d->components;
auto& componentIndex = d->m_profile->d->componentIndex;
auto& instance = d->m_profile->d->m_instance;
for (auto comp : components) {
qCDebug(instanceProfileResolveC) << instance->name() << "|"
<< "scanning" << comp->getID() << ":" << comp->getVersion() << "for tree link";
auto dep = std::find_if(comp->m_cachedRequires.cbegin(), comp->m_cachedRequires.cend(),
[uid](const Meta::Require& req) -> bool { return req.uid == uid; });
if (dep != comp->m_cachedRequires.cend()) {
qCDebug(instanceProfileResolveC) << instance->name() << "|" << comp->getID() << ":" << comp->getVersion() << "depends on"
<< uid;
linked.append(comp);
}
}
auto iter = componentIndex.find(uid);
if (iter != componentIndex.end()) {
ComponentPtr comp = *iter;
comp->updateCachedData();
qCDebug(instanceProfileResolveC) << instance->name() << "|" << comp->getID() << ":" << comp->getVersion() << "has"
<< comp->m_cachedRequires.size() << "dependencies";
for (auto dep : comp->m_cachedRequires) {
qCDebug(instanceProfileC) << instance->name() << "|" << uid << "depends on" << dep.uid;
auto found = componentIndex.find(dep.uid);
if (found != componentIndex.end()) {
qCDebug(instanceProfileC) << instance->name() << "|" << (*found)->getID() << "is present";
linked.append(*found);
}
}
}
return linked;
}
// FIXME, TODO: decouple dependency resolution from loading
// FIXME: This works directly with the PackProfile internals. It shouldn't! It needs richer data types than PackProfile uses.
// FIXME: throw all this away and use a graph
void ComponentUpdateTask::resolveDependencies(bool checkOnly)
{
qDebug() << "Resolving dependencies";
qCDebug(instanceProfileResolveC) << "Resolving dependencies";
/*
* this is a naive dependency resolving algorithm. all it does is check for following conditions and react in simple ways:
* 1. There are conflicting dependencies on the same uid with different exact version numbers
@ -393,8 +437,8 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
*
* NOTE: this is a placeholder and should eventually be replaced with something 'serious'
*/
auto& components = d->m_list->d->components;
auto& componentIndex = d->m_list->d->componentIndex;
auto& components = d->m_profile->d->components;
auto& componentIndex = d->m_profile->d->componentIndex;
RequireExSet allRequires;
QStringList toRemove;
@ -402,15 +446,16 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
allRequires.clear();
toRemove.clear();
if (!gatherRequirementsFromComponents(components, allRequires)) {
finalizeComponents();
emitFailed(tr("Conflicting requirements detected during dependency checking!"));
return;
}
getTrivialRemovals(components, allRequires, toRemove);
if (!toRemove.isEmpty()) {
qDebug() << "Removing obsolete components...";
qCDebug(instanceProfileResolveC) << "Removing obsolete components...";
for (auto& remove : toRemove) {
qDebug() << "Removing" << remove;
d->m_list->remove(remove);
qCDebug(instanceProfileResolveC) << "Removing" << remove;
d->m_profile->remove(remove);
}
}
} while (!toRemove.isEmpty());
@ -418,10 +463,12 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
RequireExSet toChange;
bool succeeded = getTrivialComponentChanges(componentIndex, allRequires, toAdd, toChange);
if (!succeeded) {
finalizeComponents();
emitFailed(tr("Instance has conflicting dependencies."));
return;
}
if (checkOnly) {
finalizeComponents();
if (toAdd.size() || toChange.size()) {
emitFailed(tr("Instance has unresolved dependencies while loading/checking for launch."));
} else {
@ -434,14 +481,15 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
if (toAdd.size()) {
// add stuff...
for (auto& add : toAdd) {
auto component = makeShared<Component>(d->m_list, add.uid);
auto component = makeShared<Component>(d->m_profile, add.uid);
if (!add.equalsVersion.isEmpty()) {
// exact version
qDebug() << "Adding" << add.uid << "version" << add.equalsVersion << "at position" << add.indexOfFirstDependee;
qCDebug(instanceProfileResolveC)
<< "Adding" << add.uid << "version" << add.equalsVersion << "at position" << add.indexOfFirstDependee;
component->m_version = add.equalsVersion;
} else {
// version needs to be decided
qDebug() << "Adding" << add.uid << "at position" << add.indexOfFirstDependee;
qCDebug(instanceProfileResolveC) << "Adding" << add.uid << "at position" << add.indexOfFirstDependee;
// ############################################################################################################
// HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded.
if (!add.suggests.isEmpty()) {
@ -464,7 +512,7 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
}
component->m_dependencyOnly = true;
// FIXME: this should not work directly with the component list
d->m_list->insertComponent(add.indexOfFirstDependee, component);
d->m_profile->insertComponent(add.indexOfFirstDependee, component);
componentIndex[add.uid] = component;
}
recursionNeeded = true;
@ -473,7 +521,7 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
// change a version of something that exists
for (auto& change : toChange) {
// FIXME: this should not work directly with the component list
qDebug() << "Setting version of " << change.uid << "to" << change.equalsVersion;
qCDebug(instanceProfileResolveC) << "Setting version of " << change.uid << "to" << change.equalsVersion;
auto component = componentIndex[change.uid];
component->setVersion(change.equalsVersion);
}
@ -483,14 +531,182 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
if (recursionNeeded) {
loadComponents();
} else {
finalizeComponents();
emitSucceeded();
}
}
// Variant visitation via lambda
template <class... Ts>
struct overload : Ts... {
using Ts::operator()...;
};
template <class... Ts>
overload(Ts...) -> overload<Ts...>;
void ComponentUpdateTask::performUpdateActions()
{
auto& instance = d->m_profile->d->m_instance;
bool addedActions;
QStringList toRemove;
do {
addedActions = false;
toRemove.clear();
auto& components = d->m_profile->d->components;
auto& componentIndex = d->m_profile->d->componentIndex;
for (auto component : components) {
if (!component) {
continue;
}
auto action = component->getUpdateAction();
auto visitor =
overload{ [](const UpdateActionNone&) {
// noop
},
[&component, &instance](const UpdateActionChangeVersion& cv) {
qCDebug(instanceProfileResolveC) << instance->name() << "|"
<< "UpdateActionChangeVersion" << component->getID() << ":"
<< component->getVersion() << "change to" << cv.targetVersion;
component->setVersion(cv.targetVersion);
component->waitLoadMeta();
},
[&component, &instance](const UpdateActionLatestRecommendedCompatible lrc) {
qCDebug(instanceProfileResolveC)
<< instance->name() << "|"
<< "UpdateActionLatestRecommendedCompatible" << component->getID() << ":" << component->getVersion()
<< "updating to latest recommend or compatible with" << lrc.parentUid << lrc.version;
auto versionList = APPLICATION->metadataIndex()->get(component->getID());
if (versionList) {
versionList->waitToLoad();
auto recommended = versionList->getRecommendedForParent(lrc.parentUid, lrc.version);
if (!recommended) {
recommended = versionList->getLatestForParent(lrc.parentUid, lrc.version);
}
if (recommended) {
component->setVersion(recommended->version());
component->waitLoadMeta();
return;
} else {
component->addComponentProblem(ProblemSeverity::Error,
QObject::tr("No compatible version of %1 found for %2 %3")
.arg(component->getName(), lrc.parentName, lrc.version));
}
} else {
component->addComponentProblem(
ProblemSeverity::Error,
QObject::tr("No version list in metadata index for %1").arg(component->getID()));
}
},
[&component, &instance, &toRemove](const UpdateActionRemove&) {
qCDebug(instanceProfileResolveC)
<< instance->name() << "|"
<< "UpdateActionRemove" << component->getID() << ":" << component->getVersion() << "removing";
toRemove.append(component->getID());
},
[this, &component, &instance, &addedActions, &componentIndex](const UpdateActionImportantChanged& ic) {
qCDebug(instanceProfileResolveC)
<< instance->name() << "|"
<< "UpdateImportantChanged" << component->getID() << ":" << component->getVersion() << "was changed from"
<< ic.oldVersion << "updating linked components";
auto oldVersion = APPLICATION->metadataIndex()->getLoadedVersion(component->getID(), ic.oldVersion);
for (auto oldReq : oldVersion->requiredSet()) {
auto currentlyRequired = component->m_cachedRequires.find(oldReq);
if (currentlyRequired == component->m_cachedRequires.cend()) {
auto oldReqComp = componentIndex.find(oldReq.uid);
if (oldReqComp != componentIndex.cend()) {
(*oldReqComp)->setUpdateAction(UpdateAction{ UpdateActionRemove{} });
addedActions = true;
}
}
}
auto linked = collectTreeLinked(component->getID());
for (auto comp : linked) {
if (comp->isCustom()) {
continue;
}
auto compUid = comp->getID();
auto parentReq = std::find_if(component->m_cachedRequires.begin(), component->m_cachedRequires.end(),
[compUid](const Meta::Require& req) { return req.uid == compUid; });
if (parentReq != component->m_cachedRequires.end()) {
auto newVersion = parentReq->equalsVersion.isEmpty() ? parentReq->suggests : parentReq->equalsVersion;
if (!newVersion.isEmpty()) {
comp->setUpdateAction(UpdateAction{ UpdateActionChangeVersion{ newVersion } });
} else {
comp->setUpdateAction(UpdateAction{ UpdateActionLatestRecommendedCompatible{
component->getID(),
component->getName(),
component->getVersion(),
} });
}
} else {
comp->setUpdateAction(UpdateAction{ UpdateActionLatestRecommendedCompatible{
component->getID(),
component->getName(),
component->getVersion(),
} });
}
addedActions = true;
}
} };
std::visit(visitor, action);
component->clearUpdateAction();
for (auto uid : toRemove) {
d->m_profile->remove(uid);
}
}
} while (addedActions);
}
void ComponentUpdateTask::finalizeComponents()
{
auto& components = d->m_profile->d->components;
auto& componentIndex = d->m_profile->d->componentIndex;
for (auto component : components) {
for (auto req : component->m_cachedRequires) {
auto found = componentIndex.find(req.uid);
if (found == componentIndex.cend()) {
component->addComponentProblem(
ProblemSeverity::Error,
QObject::tr("%1 is missing requirement %2 %3")
.arg(component->getName(), req.uid, req.equalsVersion.isEmpty() ? req.suggests : req.equalsVersion));
} else {
auto reqComp = *found;
if (!reqComp->getProblems().isEmpty()) {
component->addComponentProblem(
reqComp->getProblemSeverity(),
QObject::tr("%1, a dependency of this component, has reported issues").arg(reqComp->getName()));
}
if (!req.equalsVersion.isEmpty() && req.equalsVersion != reqComp->getVersion()) {
component->addComponentProblem(ProblemSeverity::Error,
QObject::tr("%1, a dependency of this component, is not the required version %2")
.arg(reqComp->getName(), req.equalsVersion));
} else if (!req.suggests.isEmpty() && req.suggests != reqComp->getVersion()) {
component->addComponentProblem(ProblemSeverity::Warning,
QObject::tr("%1, a dependency of this component, is not the suggested version %2")
.arg(reqComp->getName(), req.suggests));
}
}
}
for (auto conflict : component->knownConflictingComponents()) {
auto found = componentIndex.find(conflict);
if (found != componentIndex.cend()) {
auto foundComp = *found;
if (foundComp->isCustom()) {
continue;
}
component->addComponentProblem(
ProblemSeverity::Warning,
QObject::tr("%1 and %2 are known to not work together. It is recommended to remove one of them.")
.arg(component->getName(), foundComp->getName()));
}
}
}
}
void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex)
{
if (static_cast<size_t>(d->remoteLoadStatusList.size()) < taskIndex) {
qWarning() << "Got task index outside of results" << taskIndex;
qCWarning(instanceProfileResolveC) << "Got task index outside of results" << taskIndex;
return;
}
auto& taskSlot = d->remoteLoadStatusList[taskIndex];
@ -498,16 +714,16 @@ void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex)
disconnect(taskSlot.task.get(), &Task::failed, this, nullptr);
disconnect(taskSlot.task.get(), &Task::aborted, this, nullptr);
if (taskSlot.finished) {
qWarning() << "Got multiple results from remote load task" << taskIndex;
qCWarning(instanceProfileResolveC) << "Got multiple results from remote load task" << taskIndex;
return;
}
qDebug() << "Remote task" << taskIndex << "succeeded";
qCDebug(instanceProfileResolveC) << "Remote task" << taskIndex << "succeeded";
taskSlot.succeeded = false;
taskSlot.finished = true;
d->remoteTasksInProgress--;
// update the cached data of the component from the downloaded version file.
if (taskSlot.type == RemoteLoadStatus::Type::Version) {
auto component = d->m_list->getComponent(taskSlot.PackProfileIndex);
auto component = d->m_profile->getComponent(taskSlot.PackProfileIndex);
component->m_loaded = true;
component->updateCachedData();
}
@ -517,7 +733,7 @@ void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex)
void ComponentUpdateTask::remoteLoadFailed(size_t taskIndex, const QString& msg)
{
if (static_cast<size_t>(d->remoteLoadStatusList.size()) < taskIndex) {
qWarning() << "Got task index outside of results" << taskIndex;
qCWarning(instanceProfileResolveC) << "Got task index outside of results" << taskIndex;
return;
}
auto& taskSlot = d->remoteLoadStatusList[taskIndex];
@ -525,10 +741,10 @@ void ComponentUpdateTask::remoteLoadFailed(size_t taskIndex, const QString& msg)
disconnect(taskSlot.task.get(), &Task::failed, this, nullptr);
disconnect(taskSlot.task.get(), &Task::aborted, this, nullptr);
if (taskSlot.finished) {
qWarning() << "Got multiple results from remote load task" << taskIndex;
qCWarning(instanceProfileResolveC) << "Got multiple results from remote load task" << taskIndex;
return;
}
qDebug() << "Remote task" << taskIndex << "failed: " << msg;
qCDebug(instanceProfileResolveC) << "Remote task" << taskIndex << "failed: " << msg;
d->remoteLoadSuccessful = false;
taskSlot.succeeded = false;
taskSlot.finished = true;
@ -546,6 +762,7 @@ void ComponentUpdateTask::checkIfAllFinished()
if (d->remoteLoadSuccessful) {
// nothing bad happened... clear the temp load status and proceed with looking at dependencies
d->remoteLoadStatusList.clear();
performUpdateActions();
resolveDependencies(d->mode == Mode::Launch);
} else {
// remote load failed... report error and bail

View File

@ -1,5 +1,6 @@
#pragma once
#include "minecraft/Component.h"
#include "net/Mode.h"
#include "tasks/Task.h"
@ -21,7 +22,11 @@ class ComponentUpdateTask : public Task {
private:
void loadComponents();
/// collects components that are dependent on or dependencies of the component
QList<ComponentPtr> collectTreeLinked(const QString& uid);
void resolveDependencies(bool checkOnly);
void performUpdateActions();
void finalizeComponents();
void remoteLoadSucceeded(size_t index);
void remoteLoadFailed(size_t index, const QString& msg);

View File

@ -6,6 +6,8 @@
#include "net/Mode.h"
#include "tasks/Task.h"
#include "minecraft/ComponentUpdateTask.h"
class PackProfile;
struct RemoteLoadStatus {
@ -18,7 +20,7 @@ struct RemoteLoadStatus {
};
struct ComponentUpdateTaskData {
PackProfile* m_list = nullptr;
PackProfile* m_profile = nullptr;
QList<RemoteLoadStatus> remoteLoadStatusList;
bool remoteLoadSuccessful = true;
size_t remoteTasksInProgress = 0;

View File

@ -0,0 +1,25 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* 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, see <https://www.gnu.org/licenses/>.
*
*/
#include "minecraft/Logging.h"
#include <qloggingcategory.h>
Q_LOGGING_CATEGORY(instanceProfileC, "launcher.instance.profile")
Q_LOGGING_CATEGORY(instanceProfileResolveC, "launcher.instance.profile.resolve")

View File

@ -0,0 +1,26 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* 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, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(instanceProfileC)
Q_DECLARE_LOGGING_CATEGORY(instanceProfileResolveC)

View File

@ -38,6 +38,7 @@
*/
#include <Version.h>
#include <qlogging.h>
#include <QCryptographicHash>
#include <QDebug>
#include <QDir>
@ -47,10 +48,16 @@
#include <QSaveFile>
#include <QTimer>
#include <QUuid>
#include <algorithm>
#include <utility>
#include "Application.h"
#include "Exception.h"
#include "FileSystem.h"
#include "Json.h"
#include "meta/Index.h"
#include "meta/JsonFormat.h"
#include "minecraft/Component.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/OneSixVersionFormat.h"
#include "minecraft/ProfileUtils.h"
@ -60,11 +67,9 @@
#include "PackProfile_p.h"
#include "modplatform/ModIndex.h"
static const QMap<QString, ModPlatform::ModLoaderType> modloaderMapping{ { "net.neoforged", ModPlatform::NeoForge },
{ "net.minecraftforge", ModPlatform::Forge },
{ "net.fabricmc.fabric-loader", ModPlatform::Fabric },
{ "org.quiltmc.quilt-loader", ModPlatform::Quilt },
{ "com.mumfrey.liteloader", ModPlatform::LiteLoader } };
#include "minecraft/Logging.h"
#include "ui/dialogs/CustomMessageBox.h"
PackProfile::PackProfile(MinecraftInstance* instance) : QAbstractListModel()
{
@ -153,16 +158,16 @@ static bool savePackProfile(const QString& filename, const ComponentContainer& c
obj.insert("components", orderArray);
QSaveFile outFile(filename);
if (!outFile.open(QFile::WriteOnly)) {
qCritical() << "Couldn't open" << outFile.fileName() << "for writing:" << outFile.errorString();
qCCritical(instanceProfileC) << "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();
qCCritical(instanceProfileC) << "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();
qCCritical(instanceProfileC) << "Couldn't save" << outFile.fileName() << "because:" << outFile.errorString();
}
return true;
}
@ -175,12 +180,12 @@ static bool loadPackProfile(PackProfile* parent,
{
QFile componentsFile(filename);
if (!componentsFile.exists()) {
qWarning() << "Components file doesn't exist. This should never happen.";
qCWarning(instanceProfileC) << "Components file" << filename << "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 overridden order";
qCCritical(instanceProfileC) << "Couldn't open" << componentsFile.fileName() << " for reading:" << componentsFile.errorString();
qCWarning(instanceProfileC) << "Ignoring overridden order";
return false;
}
@ -188,8 +193,8 @@ static bool loadPackProfile(PackProfile* parent,
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(componentsFile.readAll(), &error);
if (error.error != QJsonParseError::NoError) {
qCritical() << "Couldn't parse" << componentsFile.fileName() << ":" << error.errorString();
qWarning() << "Ignoring overridden order";
qCCritical(instanceProfileC) << "Couldn't parse" << componentsFile.fileName() << ":" << error.errorString();
qCWarning(instanceProfileC) << "Ignoring overridden order";
return false;
}
@ -207,7 +212,7 @@ static bool loadPackProfile(PackProfile* parent,
container.append(componentFromJsonV1(parent, componentJsonPattern, comp_obj));
}
} catch ([[maybe_unused]] const JSONValidationError& err) {
qCritical() << "Couldn't parse" << componentsFile.fileName() << ": bad file format";
qCCritical(instanceProfileC) << "Couldn't parse" << componentsFile.fileName() << ": bad file format";
container.clear();
return false;
}
@ -240,12 +245,12 @@ void PackProfile::buildingFromScratch()
void PackProfile::scheduleSave()
{
if (!d->loaded) {
qDebug() << "Component list should never save if it didn't successfully load, instance:" << d->m_instance->name();
qDebug() << d->m_instance->name() << "|" << "Component list should never save if it didn't successfully load";
return;
}
if (!d->dirty) {
d->dirty = true;
qDebug() << "Component list save is scheduled for" << d->m_instance->name();
qDebug() << d->m_instance->name() << "|" << "Component list save is scheduled";
}
d->m_saveTimer.start();
}
@ -272,7 +277,7 @@ QString PackProfile::patchFilePathForUid(const QString& uid) const
void PackProfile::save_internal()
{
qDebug() << "Component list save performed now for" << d->m_instance->name();
qDebug() << d->m_instance->name() << "|" << "Component list save performed now";
auto filename = componentsFilePath();
savePackProfile(filename, d->components);
d->dirty = false;
@ -285,7 +290,7 @@ bool PackProfile::load()
// 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();
qCritical() << d->m_instance->name() << "|" << "Failed to load the component config";
return false;
} else {
// FIXME: actually use fine-grained updates, not this...
@ -298,7 +303,7 @@ bool PackProfile::load()
d->componentIndex.clear();
for (auto component : newComponents) {
if (d->componentIndex.contains(component->m_uid)) {
qWarning() << "Ignoring duplicate component entry" << component->m_uid;
qWarning() << d->m_instance->name() << "|" << "Ignoring duplicate component entry" << component->m_uid;
continue;
}
connect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged);
@ -346,14 +351,14 @@ void PackProfile::resolve(Net::Mode netmode)
void PackProfile::updateSucceeded()
{
qDebug() << "Component list update/resolve task succeeded for" << d->m_instance->name();
qCDebug(instanceProfileC) << d->m_instance->name() << "|" << "Component list update/resolve task succeeded";
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;
qCDebug(instanceProfileC) << d->m_instance->name() << "|" << "Component list update/resolve task failed " << "Reason:" << error;
d->m_updateTask.reset();
invalidateLaunchProfile();
}
@ -369,11 +374,11 @@ void PackProfile::insertComponent(size_t index, ComponentPtr component)
{
auto id = component->getID();
if (id.isEmpty()) {
qWarning() << "Attempt to add a component with empty ID!";
qCWarning(instanceProfileC) << d->m_instance->name() << "|" << "Attempt to add a component with empty ID!";
return;
}
if (d->componentIndex.contains(id)) {
qWarning() << "Attempt to add a component that is already present!";
qCWarning(instanceProfileC) << d->m_instance->name() << "|" << "Attempt to add a component that is already present!";
return;
}
beginInsertRows(QModelIndex(), static_cast<int>(index), static_cast<int>(index));
@ -388,7 +393,7 @@ void PackProfile::componentDataChanged()
{
auto objPtr = qobject_cast<Component*>(sender());
if (!objPtr) {
qWarning() << "PackProfile got dataChanged signal from a non-Component!";
qCWarning(instanceProfileC) << d->m_instance->name() << "|" << "PackProfile got dataChanged signal from a non-Component!";
return;
}
if (objPtr->getID() == "net.minecraft") {
@ -404,19 +409,20 @@ void PackProfile::componentDataChanged()
}
index++;
}
qWarning() << "PackProfile got dataChanged signal from a Component which does not belong to it!";
qCWarning(instanceProfileC) << d->m_instance->name() << "|"
<< "PackProfile got dataChanged 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";
qCWarning(instanceProfileC) << d->m_instance->name() << "|" << "Patch" << patch->getID() << "is non-removable";
return false;
}
if (!removeComponent_internal(patch)) {
qCritical() << "Patch" << patch->getID() << "could not be removed";
qCCritical(instanceProfileC) << d->m_instance->name() << "|" << "Patch" << patch->getID() << "could not be removed";
return false;
}
@ -445,11 +451,11 @@ bool PackProfile::customize(int index)
{
auto patch = getComponent(index);
if (!patch->isCustomizable()) {
qDebug() << "Patch" << patch->getID() << "is not customizable";
qCDebug(instanceProfileC) << d->m_instance->name() << "|" << "Patch" << patch->getID() << "is not customizable";
return false;
}
if (!patch->customize()) {
qCritical() << "Patch" << patch->getID() << "could not be customized";
qCCritical(instanceProfileC) << d->m_instance->name() << "|" << "Patch" << patch->getID() << "could not be customized";
return false;
}
invalidateLaunchProfile();
@ -461,11 +467,11 @@ bool PackProfile::revertToBase(int index)
{
auto patch = getComponent(index);
if (!patch->isRevertible()) {
qDebug() << "Patch" << patch->getID() << "is not revertible";
qCDebug(instanceProfileC) << d->m_instance->name() << "|" << "Patch" << patch->getID() << "is not revertible";
return false;
}
if (!patch->revert()) {
qCritical() << "Patch" << patch->getID() << "could not be reverted";
qCCritical(instanceProfileC) << d->m_instance->name() << "|" << "Patch" << patch->getID() << "could not be reverted";
return false;
}
invalidateLaunchProfile();
@ -678,7 +684,8 @@ bool PackProfile::installComponents(QStringList selectedFiles)
const QString target = FS::PathCombine(patchDir, versionFile->uid + ".json");
if (!QFile::copy(source, target)) {
qWarning() << "Component" << source << "could not be copied to target" << target;
qCWarning(instanceProfileC) << d->m_instance->name() << "|" << "Component" << source << "could not be copied to target"
<< target;
result = false;
continue;
}
@ -711,7 +718,8 @@ bool PackProfile::installEmpty(const QString& uid, const QString& name)
QString patchFileName = FS::PathCombine(patchDir, uid + ".json");
QFile file(patchFileName);
if (!file.open(QFile::WriteOnly)) {
qCritical() << "Error opening" << file.fileName() << "for reading:" << file.errorString();
qCCritical(instanceProfileC) << d->m_instance->name() << "|" << "Error opening" << file.fileName()
<< "for reading:" << file.errorString();
return false;
}
file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
@ -731,7 +739,8 @@ bool PackProfile::removeComponent_internal(ComponentPtr patch)
if (fileName.size()) {
QFile patchFile(fileName);
if (patchFile.exists() && !patchFile.remove()) {
qCritical() << "File" << fileName << "could not be removed because:" << patchFile.errorString();
qCCritical(instanceProfileC) << d->m_instance->name() << "|" << "File" << fileName
<< "could not be removed because:" << patchFile.errorString();
return false;
}
}
@ -747,7 +756,8 @@ bool PackProfile::removeComponent_internal(ComponentPtr patch)
if (finfo.exists()) {
QFile jarModFile(jar[0]);
if (!jarModFile.remove()) {
qCritical() << "File" << jar[0] << "could not be removed because:" << jarModFile.errorString();
qCCritical(instanceProfileC) << d->m_instance->name() << "|" << "File" << jar[0]
<< "could not be removed because:" << jarModFile.errorString();
return false;
}
return true;
@ -804,7 +814,8 @@ bool PackProfile::installJarMods_internal(QStringList filepaths)
QFile file(patchFileName);
if (!file.open(QFile::WriteOnly)) {
qCritical() << "Error opening" << file.fileName() << "for reading:" << file.errorString();
qCCritical(instanceProfileC) << d->m_instance->name() << "|" << "Error opening" << file.fileName()
<< "for reading:" << file.errorString();
return false;
}
file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
@ -858,7 +869,8 @@ bool PackProfile::installCustomJar_internal(QString filepath)
QFile file(patchFileName);
if (!file.open(QFile::WriteOnly)) {
qCritical() << "Error opening" << file.fileName() << "for reading:" << file.errorString();
qCCritical(instanceProfileC) << d->m_instance->name() << "|" << "Error opening" << file.fileName()
<< "for reading:" << file.errorString();
return false;
}
file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
@ -913,7 +925,8 @@ bool PackProfile::installAgents_internal(QStringList filepaths)
QFile patchFile(FS::PathCombine(patchDir, targetId + ".json"));
if (!patchFile.open(QFile::WriteOnly)) {
qCritical() << "Error opening" << patchFile.fileName() << "for reading:" << patchFile.errorString();
qCCritical(instanceProfileC) << d->m_instance->name() << "|" << "Error opening" << patchFile.fileName()
<< "for reading:" << patchFile.errorString();
return false;
}
@ -935,12 +948,13 @@ std::shared_ptr<LaunchProfile> PackProfile::getProfile() const
try {
auto profile = std::make_shared<LaunchProfile>();
for (auto file : d->components) {
qDebug() << "Applying" << file->getID() << (file->getProblemSeverity() == ProblemSeverity::Error ? "ERROR" : "GOOD");
qCDebug(instanceProfileC) << d->m_instance->name() << "|" << "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();
qCWarning(instanceProfileC) << d->m_instance->name() << "|" << "Couldn't apply profile patches because: " << error.cause();
}
}
return d->m_profile;
@ -953,8 +967,16 @@ bool PackProfile::setComponentVersion(const QString& uid, const QString& version
ComponentPtr component = *iter;
// set existing
if (component->revert()) {
// set new version
auto oldVersion = component->getVersion();
component->setVersion(version);
component->setImportant(important);
if (important) {
component->setUpdateAction(UpdateAction{ UpdateActionImportantChanged{ oldVersion } });
resolve(Net::Mode::Online);
}
return true;
}
return false;
@ -993,12 +1015,12 @@ std::optional<ModPlatform::ModLoaderTypes> PackProfile::getModLoaders()
ModPlatform::ModLoaderTypes result;
bool has_any_loader = false;
QMapIterator<QString, ModPlatform::ModLoaderType> i(modloaderMapping);
QMapIterator<QString, ModloaderMapEntry> i(Component::KNOWN_MODLOADERS);
while (i.hasNext()) {
i.next();
if (auto c = getComponent(i.key()); c != nullptr && c->isEnabled()) {
result |= i.value();
result |= i.value().type;
has_any_loader = true;
}
}
@ -1026,8 +1048,8 @@ QList<ModPlatform::ModLoaderType> PackProfile::getModLoadersList()
{
QList<ModPlatform::ModLoaderType> result;
for (auto c : d->components) {
if (c->isEnabled() && modloaderMapping.contains(c->getID())) {
result.append(modloaderMapping[c->getID()]);
if (c->isEnabled() && Component::KNOWN_MODLOADERS.contains(c->getID())) {
result.append(Component::KNOWN_MODLOADERS[c->getID()].type);
}
}

View File

@ -3,8 +3,8 @@
#include <QList>
#include <QMap>
#include <QTimer>
#include <map>
#include "Component.h"
#include "tasks/Task.h"
class MinecraftInstance;
using ComponentContainer = QList<ComponentPtr>;

View File

@ -49,9 +49,11 @@
#include <QMessageBox>
#include <QString>
#include <QUrl>
#include <algorithm>
#include "QObjectPtr.h"
#include "VersionPage.h"
#include "meta/JsonFormat.h"
#include "tasks/SequentialTask.h"
#include "ui/dialogs/InstallLoaderDialog.h"
#include "ui_VersionPage.h"
@ -65,11 +67,9 @@
#include "DesktopServices.h"
#include "Exception.h"
#include "Version.h"
#include "icons/IconList.h"
#include "minecraft/PackProfile.h"
#include "minecraft/auth/AccountList.h"
#include "minecraft/mod/Mod.h"
#include "meta/Index.h"
#include "meta/VersionList.h"
@ -372,11 +372,25 @@ void VersionPage::on_actionChange_version_triggered()
auto patch = m_profile->getComponent(versionRow);
auto name = patch->getName();
auto list = patch->getVersionList();
list->clearExternalRecommends();
if (!list) {
return;
}
auto uid = list->uid();
// recommend the correct lwjgl version for the current minecraft version
if (uid == "org.lwjgl" || uid == "org.lwjgl3") {
auto minecraft = m_profile->getComponent("net.minecraft");
auto lwjglReq = std::find_if(minecraft->m_cachedRequires.cbegin(), minecraft->m_cachedRequires.cend(),
[uid](const Meta::Require& req) -> bool { return req.uid == uid; });
if (lwjglReq != minecraft->m_cachedRequires.cend()) {
auto lwjglVersion = !lwjglReq->equalsVersion.isEmpty() ? lwjglReq->equalsVersion : lwjglReq->suggests;
if (!lwjglVersion.isEmpty()) {
list->addExternalRecommends({ lwjglVersion });
}
}
}
VersionSelectDialog vselect(list.get(), tr("Change %1 version").arg(name), this);
if (uid == "net.fabricmc.intermediary" || uid == "org.quiltmc.hashed") {
vselect.setEmptyString(tr("No intermediary mappings versions are currently available."));