diff options
Diffstat (limited to 'api/logic/minecraft')
107 files changed, 4771 insertions, 5316 deletions
diff --git a/api/logic/minecraft/AssetsUtils.cpp b/api/logic/minecraft/AssetsUtils.cpp index 5191e5bd..5b25bede 100644 --- a/api/logic/minecraft/AssetsUtils.cpp +++ b/api/logic/minecraft/AssetsUtils.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/AssetsUtils.h b/api/logic/minecraft/AssetsUtils.h index 34b49e7f..b7ea9cc1 100644 --- a/api/logic/minecraft/AssetsUtils.h +++ b/api/logic/minecraft/AssetsUtils.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/Component.cpp b/api/logic/minecraft/Component.cpp new file mode 100644 index 00000000..50a2ae16 --- /dev/null +++ b/api/logic/minecraft/Component.cpp @@ -0,0 +1,439 @@ +#include <meta/VersionList.h> +#include <meta/Index.h> +#include <Env.h> +#include "Component.h" + +#include "meta/Version.h" +#include "VersionFile.h" +#include "minecraft/ComponentList.h" +#include <FileSystem.h> +#include <QSaveFile> +#include "OneSixVersionFormat.h" +#include <assert.h> + +Component::Component(ComponentList * parent, const QString& uid) +{ + assert(parent); + m_parent = parent; + + m_uid = uid; +} + +Component::Component(ComponentList * 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(ComponentList * 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(ENV.metadataIndex()->hasUid(m_uid)) + { + return ENV.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(ENV.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 = ENV.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 (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 = ENV.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->requires)) + { + m_cachedRequires = file->requires; + 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(); + } +} diff --git a/api/logic/minecraft/Component.h b/api/logic/minecraft/Component.h new file mode 100644 index 00000000..778fbb18 --- /dev/null +++ b/api/logic/minecraft/Component.h @@ -0,0 +1,111 @@ +#pragma once + +#include <memory> +#include <QList> +#include <QJsonDocument> +#include <QDateTime> +#include "meta/JsonFormat.h" +#include "ProblemProvider.h" +#include "QObjectPtr.h" +#include "multimc_logic_export.h" + +class ComponentList; +class LaunchProfile; +namespace Meta +{ + class Version; + class VersionList; +} +class VersionFile; + +class MULTIMC_LOGIC_EXPORT Component : public QObject, public ProblemProvider +{ +Q_OBJECT +public: + Component(ComponentList * parent, const QString &uid); + + // DEPRECATED: remove these constructors? + Component(ComponentList * parent, std::shared_ptr<Meta::Version> version); + Component(ComponentList * parent, const QString & uid, std::shared_ptr<VersionFile> file); + + virtual ~Component(){}; + void applyTo(LaunchProfile *profile); + + bool isEnabled(); + bool setEnabled (bool state); + bool canBeDisabled(); + + bool isMoveable(); + bool isCustomizable(); + bool isRevertible(); + bool isRemovable(); + bool isCustom(); + bool isVersionChangeable(); + + // DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code + void setOrder(int order); + int getOrder(); + + QString getID(); + QString getName(); + QString getVersion(); + std::shared_ptr<Meta::Version> getMeta(); + QDateTime getReleaseDateTime(); + + QString getFilename(); + + std::shared_ptr<class VersionFile> getVersionFile() const; + std::shared_ptr<class Meta::VersionList> getVersionList() const; + + void setImportant (bool state); + + + const QList<PatchProblem> getProblems() const override; + ProblemSeverity getProblemSeverity() const override; + + void setVersion(const QString & version); + bool customize(); + bool revert(); + + void updateCachedData(); + +signals: + void dataChanged(); + +public: /* data */ + ComponentList * m_parent; + + // BEGIN: persistent component list properties + /// ID of the component + QString m_uid; + /// version of the component - when there's a custom json override, this is also the version the component reverts to + QString m_version; + /// if true, this has been added automatically to satisfy dependencies and may be automatically removed + bool m_dependencyOnly = false; + /// if true, the component is either the main component of the instance, or otherwise important and cannot be removed. + bool m_important = false; + /// if true, the component is disabled + bool m_disabled = false; + + /// cached name for display purposes, taken from the version file (meta or local override) + QString m_cachedName; + /// cached version for display AND other purposes, taken from the version file (meta or local override) + QString m_cachedVersion; + /// cached set of requirements, taken from the version file (meta or local override) + Meta::RequireSet m_cachedRequires; + Meta::RequireSet m_cachedConflicts; + /// if true, the component is volatile and may be automatically removed when no longer needed + bool m_cachedVolatile = false; + // END: persistent component list properties + + // DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code + bool m_orderOverride = false; + int m_order = 0; + + // load state + std::shared_ptr<Meta::Version> m_metaVersion; + std::shared_ptr<VersionFile> m_file; + bool m_loaded = false; +}; + +typedef shared_qobject_ptr<Component> ComponentPtr; diff --git a/api/logic/minecraft/ComponentList.cpp b/api/logic/minecraft/ComponentList.cpp new file mode 100644 index 00000000..a207e987 --- /dev/null +++ b/api/logic/minecraft/ComponentList.cpp @@ -0,0 +1,1204 @@ +/* Copyright 2013-2018 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 "ComponentList.h" +#include "ComponentList_p.h" +#include "ComponentUpdateTask.h" + +ComponentList::ComponentList(MinecraftInstance * instance) + : QAbstractListModel() +{ + d.reset(new ComponentListData); + d->m_instance = instance; + d->m_saveTimer.setSingleShot(true); + d->m_saveTimer.setInterval(5000); + connect(&d->m_saveTimer, &QTimer::timeout, this, &ComponentList::save_internal); +} + +ComponentList::~ComponentList() +{ + 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(ComponentList * 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 saveComponentList(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 loadComponentList(ComponentList * 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 (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 ComponentList::saveNow() +{ + if(saveIsScheduled()) + { + d->m_saveTimer.stop(); + save_internal(); + } +} + +bool ComponentList::saveIsScheduled() const +{ + return d->dirty; +} + +void ComponentList::buildingFromScratch() +{ + d->loaded = true; + d->dirty = true; +} + +void ComponentList::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 ComponentList::componentsFilePath() const +{ + return FS::PathCombine(d->m_instance->instanceRoot(), "mmc-pack.json"); +} + +QString ComponentList::patchesPattern() const +{ + return FS::PathCombine(d->m_instance->instanceRoot(), "patches", "%1.json"); +} + +QString ComponentList::patchFilePathForUid(const QString& uid) const +{ + return patchesPattern().arg(uid); +} + +void ComponentList::save_internal() +{ + qDebug() << "Component list save performed now for" << d->m_instance->name(); + auto filename = componentsFilePath(); + saveComponentList(filename, d->components); + d->dirty = false; +} + +bool ComponentList::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(!loadComponentList(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, &ComponentList::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, &ComponentList::componentDataChanged); + d->components.append(component); + d->componentIndex[component->m_uid] = component; + } + endResetModel(); + d->loaded = true; + return true; + } +} + +void ComponentList::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> ComponentList::getCurrentTask() +{ + return d->m_updateTask; +} + +void ComponentList::resolve(Net::Mode netmode) +{ + auto updateTask = new ComponentUpdateTask(ComponentUpdateTask::Mode::Resolution, netmode, this); + d->m_updateTask.reset(updateTask); + connect(updateTask, &ComponentUpdateTask::succeeded, this, &ComponentList::updateSucceeded); + connect(updateTask, &ComponentUpdateTask::failed, this, &ComponentList::updateFailed); + d->m_updateTask->start(); +} + + +void ComponentList::updateSucceeded() +{ + qDebug() << "Component list update/resolve task succeeded for" << d->m_instance->name(); + d->m_updateTask.reset(); + invalidateLaunchProfile(); +} + +void ComponentList::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 ComponentList::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 saveComponentList(componentsFilePath(), components); +} + +// END: save/load + +void ComponentList::appendComponent(ComponentPtr component) +{ + insertComponent(d->components.size(), component); +} + +void ComponentList::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, &ComponentList::componentDataChanged); + scheduleSave(); +} + +void ComponentList::componentDataChanged() +{ + auto objPtr = qobject_cast<Component *>(sender()); + if(!objPtr) + { + qWarning() << "ComponentList got dataChenged signal from a non-Component!"; + return; + } + // 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() << "ComponentList got dataChenged signal from a Component which does not belong to it!"; +} + +bool ComponentList::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 ComponentList::remove(const QString id) +{ + int i = 0; + for (auto patch : d->components) + { + if (patch->getID() == id) + { + return remove(i); + } + i++; + } + return false; +} + +bool ComponentList::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 ComponentList::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 * ComponentList::getComponent(const QString &id) +{ + auto iter = d->componentIndex.find(id); + if (iter == d->componentIndex.end()) + { + return nullptr; + } + return (*iter).get(); +} + +Component * ComponentList::getComponent(int index) +{ + if(index < 0 || index >= d->components.size()) + { + return nullptr; + } + return d->components[index].get(); +} + +QVariant ComponentList::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 d->components.at(row)->isEnabled() ? Qt::Checked : Qt::Unchecked; + default: + return QVariant(); + } + } + case Qt::DisplayRole: + { + switch (column) + { + case NameColumn: + return d->components.at(row)->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 ComponentList::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 ComponentList::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(); +} +Qt::ItemFlags ComponentList::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()) + { + outFlags |= Qt::ItemIsUserCheckable; + } + return outFlags; +} + +int ComponentList::rowCount(const QModelIndex &parent) const +{ + return d->components.size(); +} + +int ComponentList::columnCount(const QModelIndex &parent) const +{ + return NUM_COLUMNS; +} + +void ComponentList::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 ComponentList::invalidateLaunchProfile() +{ + d->m_profile.reset(); +} + +void ComponentList::installJarMods(QStringList selectedFiles) +{ + installJarMods_internal(selectedFiles); +} + +void ComponentList::installCustomJar(QString selectedFile) +{ + installCustomJar_internal(selectedFile); +} + +bool ComponentList::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 ComponentList::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 ComponentList::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 ComponentList::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> ComponentList::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 (Exception & error) + { + qWarning() << "Couldn't apply profile patches because: " << error.cause(); + } + } + return d->m_profile; +} + +void ComponentList::setOldConfigVersion(const QString& uid, const QString& version) +{ + if(version.isEmpty()) + { + return; + } + d->m_oldConfigVersions[uid] = version; +} + +bool ComponentList::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 ComponentList::getComponentVersion(const QString& uid) const +{ + const auto iter = d->componentIndex.find(uid); + if (iter != d->componentIndex.end()) + { + return (*iter)->getVersion(); + } + return QString(); +} diff --git a/api/logic/minecraft/ComponentList.h b/api/logic/minecraft/ComponentList.h new file mode 100644 index 00000000..cf4d9975 --- /dev/null +++ b/api/logic/minecraft/ComponentList.h @@ -0,0 +1,146 @@ +/* Copyright 2013-2018 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. + */ + +#pragma once + +#include <QAbstractListModel> + +#include <QString> +#include <QList> +#include <memory> + +#include "Library.h" +#include "LaunchProfile.h" +#include "Component.h" +#include "ProfileUtils.h" +#include "BaseVersion.h" +#include "MojangDownloadInfo.h" +#include "multimc_logic_export.h" +#include "net/Mode.h" + +class MinecraftInstance; +struct ComponentListData; +class ComponentUpdateTask; + +class MULTIMC_LOGIC_EXPORT ComponentList : public QAbstractListModel +{ + Q_OBJECT + friend ComponentUpdateTask; +public: + enum Columns + { + NameColumn = 0, + VersionColumn, + NUM_COLUMNS + }; + + explicit ComponentList(MinecraftInstance * instance); + virtual ~ComponentList(); + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; + virtual int columnCount(const QModelIndex &parent) const override; + virtual Qt::ItemFlags flags(const QModelIndex &index) const override; + + /// call this to explicitly mark the component list as loaded - this is used to build a new component list from scratch. + void buildingFromScratch(); + + /// install more jar mods + void installJarMods(QStringList selectedFiles); + + /// install a jar/zip as a replacement for the main jar + void installCustomJar(QString selectedFile); + + enum MoveDirection { MoveUp, MoveDown }; + /// move component file # up or down the list + void move(const int index, const MoveDirection direction); + + /// remove component file # - including files/records + bool remove(const int index); + + /// remove component file by id - including files/records + bool remove(const QString id); + + bool customize(int index); + + bool revertToBase(int index); + + /// reload the list, reload all components, resolve dependencies + void reload(Net::Mode netmode); + + // reload all components, resolve dependencies + void resolve(Net::Mode netmode); + + /// get current running task... + shared_qobject_ptr<Task> getCurrentTask(); + + std::shared_ptr<LaunchProfile> getProfile() const; + + // NOTE: used ONLY by MinecraftInstance to provide legacy version mappings from instance config + void setOldConfigVersion(const QString &uid, const QString &version); + + QString getComponentVersion(const QString &uid) const; + + bool setComponentVersion(const QString &uid, const QString &version, bool important = false); + + bool installEmpty(const QString &uid, const QString &name); + + QString patchFilePathForUid(const QString &uid) const; + + /// if there is a save scheduled, do it now. + void saveNow(); + +public: + /// get the profile component by id + Component * getComponent(const QString &id); + + /// get the profile component by index + Component * getComponent(int index); + +private: + void scheduleSave(); + bool saveIsScheduled() const; + + /// apply the component patches. Catches all the errors and returns true/false for success/failure + void invalidateLaunchProfile(); + + /// Add the component to the internal list of patches + void appendComponent(ComponentPtr component); + /// insert component so that its index is ideally the specified one (returns real index) + void insertComponent(size_t index, ComponentPtr component); + + QString componentsFilePath() const; + QString patchesPattern() const; + +private slots: + void save_internal(); + void updateSucceeded(); + void updateFailed(const QString & error); + void componentDataChanged(); + +private: + bool load(); + bool installJarMods_internal(QStringList filepaths); + bool installCustomJar_internal(QString filepath); + bool removeComponent_internal(ComponentPtr patch); + + bool migratePreComponentConfig(); + +private: /* data */ + + std::unique_ptr<ComponentListData> d; +}; diff --git a/api/logic/minecraft/ComponentList_p.h b/api/logic/minecraft/ComponentList_p.h new file mode 100644 index 00000000..26ca5049 --- /dev/null +++ b/api/logic/minecraft/ComponentList_p.h @@ -0,0 +1,42 @@ +#pragma once + +#include "Component.h" +#include <map> +#include <QTimer> +#include <QList> +#include <QMap> + +class MinecraftInstance; +using ComponentContainer = QList<ComponentPtr>; +using ComponentIndex = QMap<QString, ComponentPtr>; +using ConnectionList = QList<QMetaObject::Connection>; + +struct ComponentListData +{ + // the instance this belongs to + MinecraftInstance *m_instance; + + // the launch profile (volatile, temporary thing created on demand) + std::shared_ptr<LaunchProfile> m_profile; + + // version information migrated from instance.cfg file. Single use on migration! + std::map<QString, QString> m_oldConfigVersions; + QString getOldConfigVersion(const QString& uid) const + { + const auto iter = m_oldConfigVersions.find(uid); + if(iter != m_oldConfigVersions.cend()) + { + return (*iter).second; + } + return QString(); + } + + // persistent list of components and related machinery + ComponentContainer components; + ComponentIndex componentIndex; + bool dirty = false; + QTimer m_saveTimer; + shared_qobject_ptr<Task> m_updateTask; + bool loaded = false; +}; + diff --git a/api/logic/minecraft/ComponentUpdateTask.cpp b/api/logic/minecraft/ComponentUpdateTask.cpp new file mode 100644 index 00000000..2d6ceb91 --- /dev/null +++ b/api/logic/minecraft/ComponentUpdateTask.cpp @@ -0,0 +1,691 @@ +#include "ComponentUpdateTask.h" + +#include "ComponentList_p.h" +#include "ComponentList.h" +#include "Component.h" +#include <Env.h> +#include <meta/Index.h> +#include <meta/VersionList.h> +#include <meta/Version.h> +#include "ComponentUpdateTask_p.h" +#include <cassert> +#include <Version.h> +#include "net/Mode.h" +#include "OneSixVersionFormat.h" + +/* + * This is responsible for loading the components of a component list AND resolving dependency issues between them + */ + +/* + * FIXME: the 'one shot async task' nature of this does not fit the intended usage + * Really, it should be a reactor/state machine that receives input from the application + * and dynamically adapts to changing requirements... + * + * The reactor should be the only entry into manipulating the ComponentList. + * See: https://en.wikipedia.org/wiki/Reactor_pattern + */ + +/* + * Or make this operate on a snapshot of the ComponentList state, then merge results in as long as the snapshot and ComponentList didn't change? + * If the component list changes, start over. + */ + +ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, ComponentList* list, QObject* parent) + : Task(parent) +{ + d.reset(new ComponentUpdateTaskData); + d->m_list = list; + d->mode = mode; + d->netmode = netmode; +} + +ComponentUpdateTask::~ComponentUpdateTask() +{ +} + +void ComponentUpdateTask::executeTask() +{ + qDebug() << "Loading components"; + loadComponents(); +} + +namespace +{ +enum class LoadResult +{ + LoadedLocal, + RequiresRemote, + Failed +}; + +LoadResult composeLoadResult(LoadResult a, LoadResult b) +{ + if (a < b) + { + return b; + } + return a; +} + +static LoadResult loadComponent(ComponentPtr component, shared_qobject_ptr<Task>& loadTask, Net::Mode netmode) +{ + if(component->m_loaded) + { + qDebug() << component->getName() << "is already loaded"; + return LoadResult::LoadedLocal; + } + + LoadResult result = LoadResult::Failed; + auto customPatchFilename = component->getFilename(); + if(QFile::exists(customPatchFilename)) + { + // if local file exists... + + // check for uid problems inside... + bool fileChanged = false; + auto file = ProfileUtils::parseJsonFile(QFileInfo(customPatchFilename), false); + if(file->uid != component->m_uid) + { + file->uid = component->m_uid; + fileChanged = true; + } + if(fileChanged) + { + // FIXME: @QUALITY do not ignore return value + ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), customPatchFilename); + } + + component->m_file = file; + component->m_loaded = true; + result = LoadResult::LoadedLocal; + } + else + { + auto metaVersion = ENV.metadataIndex()->get(component->m_uid, component->m_version); + component->m_metaVersion = metaVersion; + if(metaVersion->isLoaded()) + { + component->m_loaded = true; + result = LoadResult::LoadedLocal; + } + else + { + metaVersion->load(netmode); + loadTask = metaVersion->getCurrentTask(); + if(loadTask) + result = LoadResult::RequiresRemote; + else if (metaVersion->isLoaded()) + result = LoadResult::LoadedLocal; + else + result = LoadResult::Failed; + } + } + return result; +} + +// FIXME: dead code. determine if this can still be useful? +/* +static LoadResult loadComponentList(ComponentPtr component, shared_qobject_ptr<Task>& loadTask, Net::Mode netmode) +{ + if(component->m_loaded) + { + qDebug() << component->getName() << "is already loaded"; + return LoadResult::LoadedLocal; + } + + LoadResult result = LoadResult::Failed; + auto metaList = ENV.metadataIndex()->get(component->m_uid); + if(metaList->isLoaded()) + { + component->m_loaded = true; + result = LoadResult::LoadedLocal; + } + else + { + metaList->load(netmode); + loadTask = metaList->getCurrentTask(); + result = LoadResult::RequiresRemote; + } + return result; +} +*/ + +static LoadResult loadIndex(shared_qobject_ptr<Task>& loadTask, Net::Mode netmode) +{ + // FIXME: DECIDE. do we want to run the update task anyway? + if(ENV.metadataIndex()->isLoaded()) + { + qDebug() << "Index is already loaded"; + return LoadResult::LoadedLocal; + } + ENV.metadataIndex()->load(netmode); + loadTask = ENV.metadataIndex()->getCurrentTask(); + if(loadTask) + { + return LoadResult::RequiresRemote; + } + // FIXME: this is assuming the load succeeded... did it really? + return LoadResult::LoadedLocal; +} +} + +void ComponentUpdateTask::loadComponents() +{ + LoadResult result = LoadResult::LoadedLocal; + size_t taskIndex = 0; + size_t componentIndex = 0; + d->remoteLoadSuccessful = true; + // load the main index (it is needed to determine if components can revert) + { + // FIXME: tear out as a method? or lambda? + shared_qobject_ptr<Task> indexLoadTask; + auto singleResult = loadIndex(indexLoadTask, d->netmode); + result = composeLoadResult(result, singleResult); + if(indexLoadTask) + { + qDebug() << "Remote loading is being run for metadata index"; + RemoteLoadStatus status; + status.type = RemoteLoadStatus::Type::Index; + d->remoteLoadStatusList.append(status); + connect(indexLoadTask.get(), &Task::succeeded, [=]() + { + remoteLoadSucceeded(taskIndex); + }); + connect(indexLoadTask.get(), &Task::failed, [=](const QString & error) + { + remoteLoadFailed(taskIndex, error); + }); + taskIndex++; + } + } + // load all the components OR their lists... + for (auto component: d->m_list->d->components) + { + shared_qobject_ptr<Task> loadTask; + LoadResult singleResult; + RemoteLoadStatus::Type loadType; + // 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 + switch(d->mode) + { + case Mode::Launch: + { + singleResult = loadComponent(component, loadTask, d->netmode); + loadType = RemoteLoadStatus::Type::Version; + break; + } + case Mode::Resolution: + { + singleResult = loadComponentList(component, loadTask, d->netmode); + loadType = RemoteLoadStatus::Type::List; + break; + } + } +#else + singleResult = loadComponent(component, loadTask, d->netmode); + loadType = RemoteLoadStatus::Type::Version; +#endif + if(singleResult == LoadResult::LoadedLocal) + { + component->updateCachedData(); + } + result = composeLoadResult(result, singleResult); + if (loadTask) + { + qDebug() << "Remote loading is being run for" << component->getName(); + connect(loadTask.get(), &Task::succeeded, [=]() + { + remoteLoadSucceeded(taskIndex); + }); + connect(loadTask.get(), &Task::failed, [=](const QString & error) + { + remoteLoadFailed(taskIndex, error); + }); + RemoteLoadStatus status; + status.type = loadType; + status.componentListIndex = componentIndex; + d->remoteLoadStatusList.append(status); + taskIndex++; + } + componentIndex++; + } + d->remoteTasksInProgress = taskIndex; + switch(result) + { + case LoadResult::LoadedLocal: + { + // Everything got loaded. Advance to dependency resolution. + resolveDependencies(d->mode == Mode::Launch || d->netmode == Net::Mode::Offline); + break; + } + case LoadResult::RequiresRemote: + { + // we wait for signals. + break; + } + case LoadResult::Failed: + { + emitFailed(tr("Some component metadata load tasks failed.")); + break; + } + } +} + +namespace +{ + struct RequireEx : public Meta::Require + { + size_t indexOfFirstDependee = 0; + }; + struct RequireCompositionResult + { + bool ok; + RequireEx outcome; + }; + using RequireExSet = std::set<RequireEx>; +} + +static RequireCompositionResult composeRequirement(const RequireEx & a, const RequireEx & b) +{ + assert(a.uid == b.uid); + RequireEx out; + out.uid = a.uid; + out.indexOfFirstDependee = std::min(a.indexOfFirstDependee, b.indexOfFirstDependee); + if(a.equalsVersion.isEmpty()) + { + out.equalsVersion = b.equalsVersion; + } + else if (b.equalsVersion.isEmpty()) + { + out.equalsVersion = a.equalsVersion; + } + else if (a.equalsVersion == b.equalsVersion) + { + out.equalsVersion = a.equalsVersion; + } + else + { + // FIXME: mark error as explicit version conflict + return {false, out}; + } + + if(a.suggests.isEmpty()) + { + out.suggests = b.suggests; + } + else if (b.suggests.isEmpty()) + { + out.suggests = a.suggests; + } + else + { + Version aVer(a.suggests); + Version bVer(b.suggests); + out.suggests = (aVer < bVer ? b.suggests : a.suggests); + } + return {true, out}; +} + +// gather the requirements from all components, finding any obvious conflicts +static bool gatherRequirementsFromComponents(const ComponentContainer & input, RequireExSet & output) +{ + bool succeeded = true; + size_t componentNum = 0; + for(auto component: input) + { + auto &componentRequires = component->m_cachedRequires; + for(const auto & componentRequire: componentRequires) + { + auto found = std::find_if(output.cbegin(), output.cend(), [componentRequire](const Meta::Require & req){ + return req.uid == componentRequire.uid; + }); + + RequireEx componenRequireEx; + componenRequireEx.uid = componentRequire.uid; + componenRequireEx.suggests = componentRequire.suggests; + componenRequireEx.equalsVersion = componentRequire.equalsVersion; + componenRequireEx.indexOfFirstDependee = componentNum; + + if(found != output.cend()) + { + // found... process it further + auto result = composeRequirement(componenRequireEx, *found); + if(result.ok) + { + output.erase(componenRequireEx); + output.insert(result.outcome); + } + else + { + qCritical() + << "Conflicting requirements:" + << componentRequire.uid + << "versions:" + << componentRequire.equalsVersion + << ";" + << (*found).equalsVersion; + } + succeeded &= result.ok; + } + else + { + // not found, accumulate + output.insert(componenRequireEx); + } + } + componentNum++; + } + return succeeded; +} + +/// Get list of uids that can be trivially removed because nothing is depending on them anymore (and they are installed as deps) +static void getTrivialRemovals(const ComponentContainer & components, const RequireExSet & reqs, QStringList &toRemove) +{ + for(const auto & component: components) + { + if(!component->m_dependencyOnly) + continue; + if(!component->m_cachedVolatile) + continue; + RequireEx reqNeedle; + reqNeedle.uid = component->m_uid; + const auto iter = reqs.find(reqNeedle); + if(iter == reqs.cend()) + { + toRemove.append(component->m_uid); + } + } +} + +/** + * handles: + * - trivial addition (there is an unmet requirement and it can be trivially met by adding something) + * - trivial version conflict of dependencies == explicit version required and installed is different + * + * toAdd - set of requirements than mean adding a new component + * toChange - set of requirements that mean changing version of an existing component + */ +static bool getTrivialComponentChanges(const ComponentIndex & index, const RequireExSet & input, RequireExSet & toAdd, RequireExSet & toChange) +{ + enum class Decision + { + Undetermined, + Met, + Missing, + VersionNotSame, + LockedVersionNotSame + } decision = Decision::Undetermined; + + QString reqStr; + bool succeeded = true; + // list the composed requirements and say if they are met or unmet + for(auto & req: input) + { + do + { + if(req.equalsVersion.isEmpty()) + { + reqStr = QString("Req: %1").arg(req.uid); + if(index.contains(req.uid)) + { + decision = Decision::Met; + } + else + { + toAdd.insert(req); + decision = Decision::Missing; + } + break; + } + else + { + reqStr = QString("Req: %1 == %2").arg(req.uid, req.equalsVersion); + const auto & compIter = index.find(req.uid); + if(compIter == index.cend()) + { + toAdd.insert(req); + decision = Decision::Missing; + break; + } + auto & comp = (*compIter); + if(comp->getVersion() != req.equalsVersion) + { + if(comp->m_dependencyOnly) + { + decision = Decision::VersionNotSame; + } + else + { + decision = Decision::LockedVersionNotSame; + } + break; + } + decision = Decision::Met; + } + } while(false); + switch(decision) + { + case Decision::Undetermined: + qCritical() << "No decision for" << reqStr; + succeeded = false; + break; + case Decision::Met: + qDebug() << reqStr << "Is met."; + break; + case Decision::Missing: + qDebug() << 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."; + toChange.insert(req); + break; + case Decision::LockedVersionNotSame: + qDebug() << reqStr << "already has different version that cannot be changed."; + succeeded = false; + break; + } + } + return succeeded; +} + +// FIXME, TODO: decouple dependency resolution from loading +// FIXME: This works directly with the ComponentList internals. It shouldn't! It needs richer data types than ComponentList uses. +// FIXME: throw all this away and use a graph +void ComponentUpdateTask::resolveDependencies(bool checkOnly) +{ + qDebug() << "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 + * -> hard error + * 2. A dependency has non-matching exact version number + * -> hard error + * 3. A dependency is entirely missing and needs to be injected before the dependee(s) + * -> requirements are injected + * + * 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; + + RequireExSet allRequires; + QStringList toRemove; + do + { + allRequires.clear(); + toRemove.clear(); + if(!gatherRequirementsFromComponents(components, allRequires)) + { + emitFailed(tr("Conflicting requirements detected during dependency checking!")); + return; + } + getTrivialRemovals(components, allRequires, toRemove); + if(!toRemove.isEmpty()) + { + qDebug() << "Removing obsolete components..."; + for(auto & remove : toRemove) + { + qDebug() << "Removing" << remove; + d->m_list->remove(remove); + } + } + } while (!toRemove.isEmpty()); + RequireExSet toAdd; + RequireExSet toChange; + bool succeeded = getTrivialComponentChanges(componentIndex, allRequires, toAdd, toChange); + if(!succeeded) + { + emitFailed(tr("Instance has conflicting dependencies.")); + return; + } + if(checkOnly) + { + if(toAdd.size() || toChange.size()) + { + emitFailed(tr("Instance has unresolved dependencies while loading/checking for launch.")); + } + else + { + emitSucceeded(); + } + return; + } + + bool recursionNeeded = false; + if(toAdd.size()) + { + // add stuff... + for(auto &add: toAdd) + { + ComponentPtr component = new Component(d->m_list, add.uid); + if(!add.equalsVersion.isEmpty()) + { + // exact version + qDebug() << "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; +// ############################################################################################################ +// HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded. + if(!add.suggests.isEmpty()) + { + component->m_version = add.suggests; + } + else + { + if(add.uid == "org.lwjgl") + { + component->m_version = "2.9.1"; + } + else if (add.uid == "org.lwjgl3") + { + component->m_version = "3.1.2"; + } + } +// HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded. +// ############################################################################################################ + } + component->m_dependencyOnly = true; + // FIXME: this should not work directly with the component list + d->m_list->insertComponent(add.indexOfFirstDependee, component); + componentIndex[add.uid] = component; + } + recursionNeeded = true; + } + if(toChange.size()) + { + // 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; + auto component = componentIndex[change.uid]; + component->setVersion(change.equalsVersion); + } + recursionNeeded = true; + } + + if(recursionNeeded) + { + loadComponents(); + } + else + { + emitSucceeded(); + } +} + +void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex) +{ + auto &taskSlot = d->remoteLoadStatusList[taskIndex]; + if(taskSlot.finished) + { + qWarning() << "Got multiple results from remote load task" << taskIndex; + return; + } + qDebug() << "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.componentListIndex); + component->m_loaded = true; + component->updateCachedData(); + } + checkIfAllFinished(); +} + + +void ComponentUpdateTask::remoteLoadFailed(size_t taskIndex, const QString& msg) +{ + auto &taskSlot = d->remoteLoadStatusList[taskIndex]; + if(taskSlot.finished) + { + qWarning() << "Got multiple results from remote load task" << taskIndex; + return; + } + qDebug() << "Remote task" << taskIndex << "failed: " << msg; + d->remoteLoadSuccessful = false; + taskSlot.succeeded = false; + taskSlot.finished = true; + taskSlot.error = msg; + d->remoteTasksInProgress --; + checkIfAllFinished(); +} + +void ComponentUpdateTask::checkIfAllFinished() +{ + if(d->remoteTasksInProgress) + { + // not yet... + return; + } + if(d->remoteLoadSuccessful) + { + // nothing bad happened... clear the temp load status and proceed with looking at dependencies + d->remoteLoadStatusList.clear(); + resolveDependencies(d->mode == Mode::Launch); + } + else + { + // remote load failed... report error and bail + QStringList allErrorsList; + for(auto & item: d->remoteLoadStatusList) + { + if(!item.succeeded) + { + allErrorsList.append(item.error); + } + } + auto allErrors = allErrorsList.join("\n"); + emitFailed(tr("Component metadata update task failed while downloading from remote server:\n%1").arg(allErrors)); + d->remoteLoadStatusList.clear(); + } +} diff --git a/api/logic/minecraft/ComponentUpdateTask.h b/api/logic/minecraft/ComponentUpdateTask.h new file mode 100644 index 00000000..11d122b6 --- /dev/null +++ b/api/logic/minecraft/ComponentUpdateTask.h @@ -0,0 +1,37 @@ +#pragma once + +#include "tasks/Task.h" +#include "net/Mode.h" + +#include <memory> +class ComponentList; +struct ComponentUpdateTaskData; + +class ComponentUpdateTask : public Task +{ + Q_OBJECT +public: + enum class Mode + { + Launch, + Resolution + }; + +public: + explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, ComponentList * list, QObject *parent = 0); + virtual ~ComponentUpdateTask(); + +protected: + void executeTask(); + +private: + void loadComponents(); + void resolveDependencies(bool checkOnly); + + void remoteLoadSucceeded(size_t index); + void remoteLoadFailed(size_t index, const QString &msg); + void checkIfAllFinished(); + +private: + std::unique_ptr<ComponentUpdateTaskData> d; +}; diff --git a/api/logic/minecraft/ComponentUpdateTask_p.h b/api/logic/minecraft/ComponentUpdateTask_p.h new file mode 100644 index 00000000..a5216506 --- /dev/null +++ b/api/logic/minecraft/ComponentUpdateTask_p.h @@ -0,0 +1,32 @@ +#pragma once + +#include <cstddef> +#include <QString> +#include <QList> +#include "net/Mode.h" + +class ComponentList; + +struct RemoteLoadStatus +{ + enum class Type + { + Index, + List, + Version + } type = Type::Version; + size_t componentListIndex = 0; + bool finished = false; + bool succeeded = false; + QString error; +}; + +struct ComponentUpdateTaskData +{ + ComponentList * m_list = nullptr; + QList<RemoteLoadStatus> remoteLoadStatusList; + bool remoteLoadSuccessful = true; + size_t remoteTasksInProgress = 0; + ComponentUpdateTask::Mode mode; + Net::Mode netmode; +}; diff --git a/api/logic/minecraft/LaunchProfile.cpp b/api/logic/minecraft/LaunchProfile.cpp new file mode 100644 index 00000000..436a39d9 --- /dev/null +++ b/api/logic/minecraft/LaunchProfile.cpp @@ -0,0 +1,297 @@ +#include "LaunchProfile.h" +#include <Version.h> + +void LaunchProfile::clear() +{ + m_minecraftVersion.clear(); + m_minecraftVersionType.clear(); + m_minecraftAssets.reset(); + m_minecraftArguments.clear(); + m_tweakers.clear(); + m_mainClass.clear(); + m_appletClass.clear(); + m_libraries.clear(); + m_traits.clear(); + m_jarMods.clear(); + m_mainJar.reset(); + m_problemSeverity = ProblemSeverity::None; +} + +static void applyString(const QString & from, QString & to) +{ + if(from.isEmpty()) + return; + to = from; +} + +void LaunchProfile::applyMinecraftVersion(const QString& id) +{ + applyString(id, this->m_minecraftVersion); +} + +void LaunchProfile::applyAppletClass(const QString& appletClass) +{ + applyString(appletClass, this->m_appletClass); +} + +void LaunchProfile::applyMainClass(const QString& mainClass) +{ + applyString(mainClass, this->m_mainClass); +} + +void LaunchProfile::applyMinecraftArguments(const QString& minecraftArguments) +{ + applyString(minecraftArguments, this->m_minecraftArguments); +} + +void LaunchProfile::applyMinecraftVersionType(const QString& type) +{ + applyString(type, this->m_minecraftVersionType); +} + +void LaunchProfile::applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets) +{ + if(assets) + { + m_minecraftAssets = assets; + } +} + +void LaunchProfile::applyTraits(const QSet<QString>& traits) +{ + this->m_traits.unite(traits); +} + +void LaunchProfile::applyTweakers(const QStringList& tweakers) +{ + // if the applied tweakers override an existing one, skip it. this effectively moves it later in the sequence + QStringList newTweakers; + for(auto & tweaker: m_tweakers) + { + if (tweakers.contains(tweaker)) + { + continue; + } + newTweakers.append(tweaker); + } + // then just append the new tweakers (or moved original ones) + newTweakers += tweakers; + m_tweakers = newTweakers; +} + +void LaunchProfile::applyJarMods(const QList<LibraryPtr>& jarMods) +{ + this->m_jarMods.append(jarMods); +} + +static int findLibraryByName(QList<LibraryPtr> *haystack, const GradleSpecifier &needle) +{ + int retval = -1; + for (int i = 0; i < haystack->size(); ++i) + { + if (haystack->at(i)->rawName().matchName(needle)) + { + // only one is allowed. + if (retval != -1) + return -1; + retval = i; + } + } + return retval; +} + +void LaunchProfile::applyMods(const QList<LibraryPtr>& mods) +{ + QList<LibraryPtr> * list = &m_mods; + for(auto & mod: mods) + { + auto modCopy = Library::limitedCopy(mod); + + // find the mod by name. + const int index = findLibraryByName(list, mod->rawName()); + // mod not found? just add it. + if (index < 0) + { + list->append(modCopy); + return; + } + + auto existingLibrary = list->at(index); + // if we are higher it means we should update + if (Version(mod->version()) > Version(existingLibrary->version())) + { + list->replace(index, modCopy); + } + } +} + +void LaunchProfile::applyLibrary(LibraryPtr library) +{ + if(!library->isActive()) + { + return; + } + + QList<LibraryPtr> * list = &m_libraries; + if(library->isNative()) + { + list = &m_nativeLibraries; + } + + auto libraryCopy = Library::limitedCopy(library); + + // find the library by name. + const int index = findLibraryByName(list, library->rawName()); + // library not found? just add it. + if (index < 0) + { + list->append(libraryCopy); + return; + } + + auto existingLibrary = list->at(index); + // if we are higher it means we should update + if (Version(library->version()) > Version(existingLibrary->version())) + { + list->replace(index, libraryCopy); + } +} + +const LibraryPtr LaunchProfile::getMainJar() const +{ + return m_mainJar; +} + +void LaunchProfile::applyMainJar(LibraryPtr jar) +{ + if(jar) + { + m_mainJar = jar; + } +} + +void LaunchProfile::applyProblemSeverity(ProblemSeverity severity) +{ + if (m_problemSeverity < severity) + { + m_problemSeverity = severity; + } +} + +const QList<PatchProblem> LaunchProfile::getProblems() const +{ + // FIXME: implement something that actually makes sense here + return {}; +} + +QString LaunchProfile::getMinecraftVersion() const +{ + return m_minecraftVersion; +} + +QString LaunchProfile::getAppletClass() const +{ + return m_appletClass; +} + +QString LaunchProfile::getMainClass() const +{ + return m_mainClass; +} + +const QSet<QString> &LaunchProfile::getTraits() const +{ + return m_traits; +} + +const QStringList & LaunchProfile::getTweakers() const +{ + return m_tweakers; +} + +bool LaunchProfile::hasTrait(const QString& trait) const +{ + return m_traits.contains(trait); +} + +ProblemSeverity LaunchProfile::getProblemSeverity() const +{ + return m_problemSeverity; +} + +QString LaunchProfile::getMinecraftVersionType() const +{ + return m_minecraftVersionType; +} + +std::shared_ptr<MojangAssetIndexInfo> LaunchProfile::getMinecraftAssets() const +{ + if(!m_minecraftAssets) + { + return std::make_shared<MojangAssetIndexInfo>("legacy"); + } + return m_minecraftAssets; +} + +QString LaunchProfile::getMinecraftArguments() const +{ + return m_minecraftArguments; +} + +const QList<LibraryPtr> & LaunchProfile::getJarMods() const +{ + return m_jarMods; +} + +const QList<LibraryPtr> & LaunchProfile::getLibraries() const +{ + return m_libraries; +} + +const QList<LibraryPtr> & LaunchProfile::getNativeLibraries() const +{ + return m_nativeLibraries; +} + +void LaunchProfile::getLibraryFiles( + const QString& architecture, + QStringList& jars, + QStringList& nativeJars, + const QString& overridePath, + const QString& tempPath +) const +{ + QStringList native32, native64; + jars.clear(); + nativeJars.clear(); + for (auto lib : getLibraries()) + { + lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); + } + // NOTE: order is important here, add main jar last to the lists + if(m_mainJar) + { + // FIXME: HACK!! jar modding is weird and unsystematic! + if(m_jarMods.size()) + { + QDir tempDir(tempPath); + jars.append(tempDir.absoluteFilePath("minecraft.jar")); + } + else + { + m_mainJar->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); + } + } + for (auto lib : getNativeLibraries()) + { + lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); + } + if(architecture == "32") + { + nativeJars.append(native32); + } + else if(architecture == "64") + { + nativeJars.append(native64); + } +} diff --git a/api/logic/minecraft/LaunchProfile.h b/api/logic/minecraft/LaunchProfile.h new file mode 100644 index 00000000..e7f5f4af --- /dev/null +++ b/api/logic/minecraft/LaunchProfile.h @@ -0,0 +1,99 @@ +#pragma once +#include <QString> +#include "Library.h" +#include <ProblemProvider.h> + +class LaunchProfile: public ProblemProvider +{ +public: + virtual ~LaunchProfile() {}; + +public: /* application of profile variables from patches */ + void applyMinecraftVersion(const QString& id); + void applyMainClass(const QString& mainClass); + void applyAppletClass(const QString& appletClass); + void applyMinecraftArguments(const QString& minecraftArguments); + void applyMinecraftVersionType(const QString& type); + void applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets); + void applyTraits(const QSet<QString> &traits); + void applyTweakers(const QStringList &tweakers); + void applyJarMods(const QList<LibraryPtr> &jarMods); + void applyMods(const QList<LibraryPtr> &jarMods); + void applyLibrary(LibraryPtr library); + void applyMainJar(LibraryPtr jar); + void applyProblemSeverity(ProblemSeverity severity); + /// clear the profile + void clear(); + +public: /* getters for profile variables */ + QString getMinecraftVersion() const; + QString getMainClass() const; + QString getAppletClass() const; + QString getMinecraftVersionType() const; + MojangAssetIndexInfo::Ptr getMinecraftAssets() const; + QString getMinecraftArguments() const; + const QSet<QString> & getTraits() const; + const QStringList & getTweakers() const; + const QList<LibraryPtr> & getJarMods() const; + const QList<LibraryPtr> & getLibraries() const; + const QList<LibraryPtr> & getNativeLibraries() const; + const LibraryPtr getMainJar() const; + void getLibraryFiles( + const QString & architecture, + QStringList & jars, + QStringList & nativeJars, + const QString & overridePath, + const QString & tempPath + ) const; + bool hasTrait(const QString & trait) const; + ProblemSeverity getProblemSeverity() const override; + const QList<PatchProblem> getProblems() const override; + +private: + /// the version of Minecraft - jar to use + QString m_minecraftVersion; + + /// Release type - "release" or "snapshot" + QString m_minecraftVersionType; + + /// Assets type - "legacy" or a version ID + MojangAssetIndexInfo::Ptr m_minecraftAssets; + + /** + * arguments that should be used for launching minecraft + * + * ex: "--username ${auth_player_name} --session ${auth_session} + * --version ${version_name} --gameDir ${game_directory} --assetsDir ${game_assets}" + */ + QString m_minecraftArguments; + + /// A list of all tweaker classes + QStringList m_tweakers; + + /// The main class to load first + QString m_mainClass; + + /// The applet class, for some very old minecraft releases + QString m_appletClass; + + /// the list of libraries + QList<LibraryPtr> m_libraries; + + /// the main jar + LibraryPtr m_mainJar; + + /// the list of libraries + QList<LibraryPtr> m_nativeLibraries; + + /// traits, collected from all the version files (version files can only add) + QSet<QString> m_traits; + + /// A list of jar mods. version files can add those. + QList<LibraryPtr> m_jarMods; + + /// the list of mods + QList<LibraryPtr> m_mods; + + ProblemSeverity m_problemSeverity = ProblemSeverity::None; + +}; diff --git a/api/logic/minecraft/Library.cpp b/api/logic/minecraft/Library.cpp index 22e1bd33..cd1afde4 100644 --- a/api/logic/minecraft/Library.cpp +++ b/api/logic/minecraft/Library.cpp @@ -104,6 +104,7 @@ QList< std::shared_ptr< NetAction > > Library::getDownloads(OpSys system, class } if (isForge) { + qDebug() << "XzDownload for:" << rawName() << "storage:" << storage << "url:" << url; out.append(ForgeXzDownload::make(storage, entry)); } else @@ -113,11 +114,14 @@ QList< std::shared_ptr< NetAction > > Library::getDownloads(OpSys system, class auto rawSha1 = QByteArray::fromHex(sha1.toLatin1()); auto dl = Net::Download::makeCached(url, entry, options); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); + qDebug() << "Checksummed Download for:" << rawName() << "storage:" << storage << "url:" << url; out.append(dl); } - else + { out.append(Net::Download::makeCached(url, entry, options)); + qDebug() << "Download for:" << rawName() << "storage:" << storage << "url:" << url; + } } return true; }; @@ -125,42 +129,56 @@ QList< std::shared_ptr< NetAction > > Library::getDownloads(OpSys system, class QString raw_storage = storageSuffix(system); if(m_mojangDownloads) { - if(m_mojangDownloads->artifact) - { - auto artifact = m_mojangDownloads->artifact; - add_download(raw_storage, artifact->url, artifact->sha1); - } - if(m_nativeClassifiers.contains(system)) + if(isNative()) { - auto nativeClassifier = m_nativeClassifiers[system]; - if(nativeClassifier.contains("${arch}")) + if(m_nativeClassifiers.contains(system)) { - auto nat32Classifier = nativeClassifier; - nat32Classifier.replace("${arch}", "32"); - auto nat64Classifier = nativeClassifier; - nat64Classifier.replace("${arch}", "64"); - auto nat32info = m_mojangDownloads->getDownloadInfo(nat32Classifier); - if(nat32info) + auto nativeClassifier = m_nativeClassifiers[system]; + if(nativeClassifier.contains("${arch}")) { - auto cooked_storage = raw_storage; - cooked_storage.replace("${arch}", "32"); - add_download(cooked_storage, nat32info->url, nat32info->sha1); + auto nat32Classifier = nativeClassifier; + nat32Classifier.replace("${arch}", "32"); + auto nat64Classifier = nativeClassifier; + nat64Classifier.replace("${arch}", "64"); + auto nat32info = m_mojangDownloads->getDownloadInfo(nat32Classifier); + if(nat32info) + { + auto cooked_storage = raw_storage; + cooked_storage.replace("${arch}", "32"); + add_download(cooked_storage, nat32info->url, nat32info->sha1); + } + auto nat64info = m_mojangDownloads->getDownloadInfo(nat64Classifier); + if(nat64info) + { + auto cooked_storage = raw_storage; + cooked_storage.replace("${arch}", "64"); + add_download(cooked_storage, nat64info->url, nat64info->sha1); + } } - auto nat64info = m_mojangDownloads->getDownloadInfo(nat64Classifier); - if(nat64info) + else { - auto cooked_storage = raw_storage; - cooked_storage.replace("${arch}", "64"); - add_download(cooked_storage, nat64info->url, nat64info->sha1); + auto info = m_mojangDownloads->getDownloadInfo(nativeClassifier); + if(info) + { + add_download(raw_storage, info->url, info->sha1); + } } } else { - auto info = m_mojangDownloads->getDownloadInfo(nativeClassifier); - if(info) - { - add_download(raw_storage, info->url, info->sha1); - } + qDebug() << "Ignoring native library" << m_name << "because it has no classifier for current OS"; + } + } + else + { + if(m_mojangDownloads->artifact) + { + auto artifact = m_mojangDownloads->artifact; + add_download(raw_storage, artifact->url, artifact->sha1); + } + else + { + qDebug() << "Ignoring java library" << m_name << "because it has no artifact"; } } } diff --git a/api/logic/minecraft/Library_test.cpp b/api/logic/minecraft/Library_test.cpp index 3f4828c9..1aac951b 100644 --- a/api/logic/minecraft/Library_test.cpp +++ b/api/logic/minecraft/Library_test.cpp @@ -2,7 +2,7 @@ #include "TestUtil.h" #include "minecraft/MojangVersionFormat.h" -#include "minecraft/onesix/OneSixVersionFormat.h" +#include "minecraft/OneSixVersionFormat.h" #include "minecraft/Library.h" #include "net/HttpMetaCache.h" #include "FileSystem.h" @@ -75,6 +75,7 @@ slots: test.setHint("local"); auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString("data")); QCOMPARE(downloads.size(), 0); + qDebug() << failedFiles; QCOMPARE(failedFiles.size(), 0); QStringList jar, native, native32, native64; @@ -240,10 +241,9 @@ slots: QCOMPARE(native64, {}); QStringList failedFiles; auto dls = test->getDownloads(Os_OSX, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 2); + QCOMPARE(dls.size(), 1); QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar")); - QCOMPARE(dls[1]->m_url, QUrl("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar")); + QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar")); } void test_onenine_native_arch() { diff --git a/api/logic/minecraft/MinecraftInstance.cpp b/api/logic/minecraft/MinecraftInstance.cpp index cb080bfe..0fffc99f 100644 --- a/api/logic/minecraft/MinecraftInstance.cpp +++ b/api/logic/minecraft/MinecraftInstance.cpp @@ -17,15 +17,24 @@ #include "launch/steps/PreLaunchCommand.h" #include "launch/steps/TextPrint.h" #include "minecraft/launch/LauncherPartLaunch.h" +#include "minecraft/launch/DirectJavaLaunch.h" #include "minecraft/launch/ModMinecraftJar.h" #include "minecraft/launch/ClaimAccount.h" #include "java/launch/CheckJava.h" -#include <meta/Index.h> -#include <meta/VersionList.h> +#include "java/JavaUtils.h" +#include "meta/Index.h" +#include "meta/VersionList.h" -#include <icons/IIconList.h> +#include "ModList.h" +#include "WorldList.h" + +#include "icons/IIconList.h" #include <QCoreApplication> +#include "ComponentList.h" +#include "AssetsUtils.h" +#include "MinecraftUpdate.h" +#include "MinecraftLoadAndCheck.h" #define IBUS "@im=ibus" @@ -87,6 +96,53 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO // Minecraft launch method auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false); m_settings->registerOverride(globalSettings->getSetting("MCLaunchMethod"), launchMethodOverride); + + // DEPRECATED: Read what versions the user configuration thinks should be used + m_settings->registerSetting({"IntendedVersion", "MinecraftVersion"}, ""); + m_settings->registerSetting("LWJGLVersion", ""); + m_settings->registerSetting("ForgeVersion", ""); + m_settings->registerSetting("LiteloaderVersion", ""); + + m_components.reset(new ComponentList(this)); + m_components->setOldConfigVersion("net.minecraft", m_settings->get("IntendedVersion").toString()); + auto setting = m_settings->getSetting("LWJGLVersion"); + m_components->setOldConfigVersion("org.lwjgl", m_settings->get("LWJGLVersion").toString()); + m_components->setOldConfigVersion("net.minecraftforge", m_settings->get("ForgeVersion").toString()); + m_components->setOldConfigVersion("com.mumfrey.liteloader", m_settings->get("LiteloaderVersion").toString()); +} + +void MinecraftInstance::init() +{ +} + +void MinecraftInstance::saveNow() +{ + m_components->saveNow(); +} + +QString MinecraftInstance::typeName() const +{ + return "Minecraft"; +} + +std::shared_ptr<ComponentList> MinecraftInstance::getComponentList() const +{ + return m_components; +} + +QSet<QString> MinecraftInstance::traits() const +{ + auto components = getComponentList(); + if (!components) + { + return {"version-incomplete"}; + } + auto profile = components->getProfile(); + if (!profile) + { + return {"version-incomplete"}; + } + return profile->getTraits(); } QString MinecraftInstance::minecraftRoot() const @@ -94,10 +150,10 @@ QString MinecraftInstance::minecraftRoot() const QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft")); QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft")); - if (dotMCDir.exists() && !mcDir.exists()) - return dotMCDir.filePath(); - else + if (mcDir.exists() && !dotMCDir.exists()) return mcDir.filePath(); + else + return dotMCDir.filePath(); } QString MinecraftInstance::binRoot() const @@ -105,9 +161,110 @@ QString MinecraftInstance::binRoot() const return FS::PathCombine(minecraftRoot(), "bin"); } -std::shared_ptr< BaseVersionList > MinecraftInstance::versionList() const +QString MinecraftInstance::getNativePath() const +{ + QDir natives_dir(FS::PathCombine(instanceRoot(), "natives/")); + return natives_dir.absolutePath(); +} + +QString MinecraftInstance::getLocalLibraryPath() const +{ + QDir libraries_dir(FS::PathCombine(instanceRoot(), "libraries/")); + return libraries_dir.absolutePath(); +} + +QString MinecraftInstance::loaderModsDir() const +{ + return FS::PathCombine(minecraftRoot(), "mods"); +} + +QString MinecraftInstance::coreModsDir() const +{ + return FS::PathCombine(minecraftRoot(), "coremods"); +} + +QString MinecraftInstance::resourcePacksDir() const +{ + return FS::PathCombine(minecraftRoot(), "resourcepacks"); +} + +QString MinecraftInstance::texturePacksDir() const +{ + return FS::PathCombine(minecraftRoot(), "texturepacks"); +} + +QString MinecraftInstance::instanceConfigFolder() const +{ + return FS::PathCombine(minecraftRoot(), "config"); +} + +QString MinecraftInstance::jarModsDir() const +{ + return FS::PathCombine(instanceRoot(), "jarmods"); +} + +QString MinecraftInstance::libDir() const +{ + return FS::PathCombine(minecraftRoot(), "lib"); +} + +QString MinecraftInstance::worldDir() const +{ + return FS::PathCombine(minecraftRoot(), "saves"); +} + +QDir MinecraftInstance::librariesPath() const +{ + return QDir::current().absoluteFilePath("libraries"); +} + +QDir MinecraftInstance::jarmodsPath() const +{ + return QDir(jarModsDir()); +} + +QDir MinecraftInstance::versionsPath() const +{ + return QDir::current().absoluteFilePath("versions"); +} + +QStringList MinecraftInstance::getClassPath() const +{ + QStringList jars, nativeJars; + auto javaArchitecture = settings()->get("JavaArchitecture").toString(); + auto profile = m_components->getProfile(); + profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); + return jars; +} + +QString MinecraftInstance::getMainClass() const +{ + auto profile = m_components->getProfile(); + return profile->getMainClass(); +} + +QStringList MinecraftInstance::getNativeJars() const { - return ENV.metadataIndex()->get("net.minecraft"); + QStringList jars, nativeJars; + auto javaArchitecture = settings()->get("JavaArchitecture").toString(); + auto profile = m_components->getProfile(); + profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); + return nativeJars; +} + +QStringList MinecraftInstance::extraArguments() const +{ + auto list = BaseInstance::extraArguments(); + auto version = getComponentList(); + if (!version) + return list; + auto jarMods = getJarMods(); + if (!jarMods.isEmpty()) + { + list.append({"-Dfml.ignoreInvalidMinecraftCertificates=true", + "-Dfml.ignorePatchDiscrepancies=true"}); + } + return list; } QStringList MinecraftInstance::javaArguments() const @@ -122,6 +279,15 @@ QStringList MinecraftInstance::javaArguments() const args << "-Xdock:icon=icon.png"; args << QString("-Xdock:name=\"%1\"").arg(windowTitle()); #endif + auto traits_ = traits(); + // HACK: fix issues on macOS with 1.13 snapshots + // NOTE: Oracle Java option. if there are alternate jvm implementations, this would be the place to customize this for them +#ifdef Q_OS_MAC + if(traits_.contains("FirstThreadOnMacOS")) + { + args << QString("-XstartOnFirstThread"); + } +#endif // HACK: Stupid hack for Intel drivers. See: https://mojang.atlassian.net/browse/MCL-767 #ifdef Q_OS_WIN32 @@ -129,8 +295,18 @@ QStringList MinecraftInstance::javaArguments() const "minecraft.exe.heapdump"); #endif - args << QString("-Xms%1m").arg(settings()->get("MinMemAlloc").toInt()); - args << QString("-Xmx%1m").arg(settings()->get("MaxMemAlloc").toInt()); + int min = settings()->get("MinMemAlloc").toInt(); + int max = settings()->get("MaxMemAlloc").toInt(); + if(min < max) + { + args << QString("-Xms%1m").arg(min); + args << QString("-Xmx%1m").arg(max); + } + else + { + args << QString("-Xms%1m").arg(max); + args << QString("-Xmx%1m").arg(min); + } // No PermGen in newer java. JavaVersion javaVersion = getJavaVersion(); @@ -160,100 +336,282 @@ QMap<QString, QString> MinecraftInstance::getVariables() const return out; } -static QString processLD_LIBRARY_PATH(const QString & LD_LIBRARY_PATH) +QProcessEnvironment MinecraftInstance::createEnvironment() +{ + // prepare the process environment + QProcessEnvironment env = CleanEnviroment(); + + // export some infos + auto variables = getVariables(); + for (auto it = variables.begin(); it != variables.end(); ++it) + { + env.insert(it.key(), it.value()); + } + return env; +} + +static QString replaceTokensIn(QString text, QMap<QString, QString> with) { - QDir mmcBin(QCoreApplication::applicationDirPath()); - auto items = LD_LIBRARY_PATH.split(':'); - QStringList final; - for(auto & item: items) + QString result; + QRegExp token_regexp("\\$\\{(.+)\\}"); + token_regexp.setMinimal(true); + QStringList list; + int tail = 0; + int head = 0; + while ((head = token_regexp.indexIn(text, head)) != -1) { - QDir test(item); - if(test == mmcBin) + result.append(text.mid(tail, head - tail)); + QString key = token_regexp.cap(1); + auto iter = with.find(key); + if (iter != with.end()) { - qDebug() << "Env:LD_LIBRARY_PATH ignoring path" << item; - continue; + result.append(*iter); } - final.append(item); + head += token_regexp.matchedLength(); + tail = head; } - return final.join(':'); + result.append(text.mid(tail)); + return result; } -QProcessEnvironment MinecraftInstance::createEnvironment() +QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session) const { - // prepare the process environment - QProcessEnvironment rawenv = QProcessEnvironment::systemEnvironment(); - QProcessEnvironment env; - - QStringList ignored = - { - "JAVA_ARGS", - "CLASSPATH", - "CONFIGPATH", - "JAVA_HOME", - "JRE_HOME", - "_JAVA_OPTIONS", - "JAVA_OPTIONS", - "JAVA_TOOL_OPTIONS" - }; - for(auto key: rawenv.keys()) + auto profile = m_components->getProfile(); + QString args_pattern = profile->getMinecraftArguments(); + for (auto tweaker : profile->getTweakers()) + { + args_pattern += " --tweakClass " + tweaker; + } + + QMap<QString, QString> token_mapping; + // yggdrasil! + if(session) + { + token_mapping["auth_username"] = session->username; + token_mapping["auth_session"] = session->session; + token_mapping["auth_access_token"] = session->access_token; + token_mapping["auth_player_name"] = session->player_name; + token_mapping["auth_uuid"] = session->uuid; + token_mapping["user_properties"] = session->serializeUserProperties(); + token_mapping["user_type"] = session->user_type; + } + + // blatant self-promotion. + token_mapping["profile_name"] = token_mapping["version_name"] = "MultiMC5"; + + token_mapping["version_type"] = profile->getMinecraftVersionType(); + + QString absRootDir = QDir(minecraftRoot()).absolutePath(); + token_mapping["game_directory"] = absRootDir; + QString absAssetsDir = QDir("assets/").absolutePath(); + auto assets = profile->getMinecraftAssets(); + // FIXME: this is wrong and should be run as an async task + token_mapping["game_assets"] = AssetsUtils::reconstructAssets(assets->id).absolutePath(); + + // 1.7.3+ assets tokens + token_mapping["assets_root"] = absAssetsDir; + token_mapping["assets_index_name"] = assets->id; + + QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts); + for (int i = 0; i < parts.length(); i++) { - auto value = rawenv.value(key); - // filter out dangerous java crap - if(ignored.contains(key)) + parts[i] = replaceTokensIn(parts[i], token_mapping); + } + return parts; +} + +QString MinecraftInstance::createLaunchScript(AuthSessionPtr session) +{ + QString launchScript; + + if (!m_components) + return QString(); + auto profile = m_components->getProfile(); + if(!profile) + return QString(); + + auto mainClass = getMainClass(); + if (!mainClass.isEmpty()) + { + launchScript += "mainClass " + mainClass + "\n"; + } + auto appletClass = profile->getAppletClass(); + if (!appletClass.isEmpty()) + { + launchScript += "appletClass " + appletClass + "\n"; + } + + // generic minecraft params + for (auto param : processMinecraftArgs(session)) + { + launchScript += "param " + param + "\n"; + } + + // window size, title and state, legacy + { + QString windowParams; + if (settings()->get("LaunchMaximized").toBool()) + windowParams = "max"; + else + windowParams = QString("%1x%2") + .arg(settings()->get("MinecraftWinWidth").toInt()) + .arg(settings()->get("MinecraftWinHeight").toInt()); + launchScript += "windowTitle " + windowTitle() + "\n"; + launchScript += "windowParams " + windowParams + "\n"; + } + + // legacy auth + if(session) + { + launchScript += "userName " + session->player_name + "\n"; + launchScript += "sessionId " + session->session + "\n"; + } + + // libraries and class path. + { + QStringList jars, nativeJars; + auto javaArchitecture = settings()->get("JavaArchitecture").toString(); + profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); + for(auto file: jars) { - qDebug() << "Env: ignoring" << key << value; - continue; + launchScript += "cp " + file + "\n"; } - // filter MultiMC-related things - if(key.startsWith("QT_")) + for(auto file: nativeJars) { - qDebug() << "Env: ignoring" << key << value; - continue; + launchScript += "ext " + file + "\n"; } -#ifdef Q_OS_LINUX - // Do not pass LD_* variables to java. They were intended for MultiMC - if(key.startsWith("LD_")) + launchScript += "natives " + getNativePath() + "\n"; + } + + for (auto trait : profile->getTraits()) + { + launchScript += "traits " + trait + "\n"; + } + launchScript += "launcher onesix\n"; + // qDebug() << "Generated launch script:" << launchScript; + return launchScript; +} + +QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session) +{ + QStringList out; + out << "Main Class:" << " " + getMainClass() << ""; + out << "Native path:" << " " + getNativePath() << ""; + + auto profile = m_components->getProfile(); + + auto alltraits = traits(); + if(alltraits.size()) + { + out << "Traits:"; + for (auto trait : alltraits) { - qDebug() << "Env: ignoring" << key << value; - continue; + out << "traits " + trait; } - // Strip IBus - // IBus is a Linux IME framework. For some reason, it breaks MC? - if (key == "XMODIFIERS" && value.contains(IBUS)) + out << ""; + } + + // libraries and class path. + { + out << "Libraries:"; + QStringList jars, nativeJars; + auto javaArchitecture = settings()->get("JavaArchitecture").toString(); + profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); + auto printLibFile = [&](const QString & path) + { + QFileInfo info(path); + if(info.exists()) + { + out << " " + path; + } + else + { + out << " " + path + " (missing)"; + } + }; + for(auto file: jars) { - QString save = value; - value.replace(IBUS, ""); - qDebug() << "Env: stripped" << IBUS << "from" << save << ":" << value; + printLibFile(file); } - if(key == "GAME_PRELOAD") + out << ""; + out << "Native libraries:"; + for(auto file: nativeJars) { - env.insert("LD_PRELOAD", value); - continue; + printLibFile(file); } - if(key == "GAME_LIBRARY_PATH") + out << ""; + } + + if(loaderModList()->size()) + { + out << "Mods:"; + for(auto & mod: loaderModList()->allMods()) { - env.insert("LD_LIBRARY_PATH", processLD_LIBRARY_PATH(value)); - continue; + if(!mod.enabled()) + continue; + if(mod.type() == Mod::MOD_FOLDER) + continue; + // TODO: proper implementation would need to descend into folders. + + out << " " + mod.filename().completeBaseName(); } -#endif - qDebug() << "Env: " << key << value; - env.insert(key, value); + out << ""; } -#ifdef Q_OS_LINUX - // HACK: Workaround for QTBUG42500 - if(!env.contains("LD_LIBRARY_PATH")) + + if(coreModList()->size()) { - env.insert("LD_LIBRARY_PATH", ""); + out << "Core Mods:"; + for(auto & coremod: coreModList()->allMods()) + { + if(!coremod.enabled()) + continue; + if(coremod.type() == Mod::MOD_FOLDER) + continue; + // TODO: proper implementation would need to descend into folders. + + out << " " + coremod.filename().completeBaseName(); + } + out << ""; } -#endif - // export some infos - auto variables = getVariables(); - for (auto it = variables.begin(); it != variables.end(); ++it) + auto & jarMods = profile->getJarMods(); + if(jarMods.size()) { - env.insert(it.key(), it.value()); + out << "Jar Mods:"; + for(auto & jarmod: jarMods) + { + auto displayname = jarmod->displayName(currentSystem); + auto realname = jarmod->filename(currentSystem); + if(displayname != realname) + { + out << " " + displayname + " (" + realname + ")"; + } + else + { + out << " " + realname; + } + } + out << ""; } - return env; + + auto params = processMinecraftArgs(nullptr); + out << "Params:"; + out << " " + params.join(' '); + out << ""; + + QString windowParams; + if (settings()->get("LaunchMaximized").toBool()) + { + out << "Window size: max (if available)"; + } + else + { + auto width = settings()->get("MinecraftWinWidth").toInt(); + auto height = settings()->get("MinecraftWinHeight").toInt(); + out << "Window size: " + QString::number(width) + " x " + QString::number(height); + } + out << ""; + return out; } QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSessionPtr session) @@ -278,7 +636,6 @@ QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSess addToFilter(sessionRef.access_token, tr("<ACCESS TOKEN>")); addToFilter(sessionRef.client_token, tr("<CLIENT TOKEN>")); addToFilter(sessionRef.uuid, tr("<PROFILE ID>")); - addToFilter(sessionRef.player_name, tr("<PROFILE NAME>")); auto i = sessionRef.u.properties.begin(); while (i != sessionRef.u.properties.end()) @@ -384,7 +741,7 @@ QString MinecraftInstance::getStatusbarDescription() } QString description; - description.append(tr("Minecraft %1 (%2)").arg(intendedVersionId()).arg(typeName())); + description.append(tr("Minecraft %1 (%2)").arg(m_components->getComponentVersion("net.minecraft")).arg(typeName())); if(totalTimePlayed() > 0) { description.append(tr(", played for %1").arg(prettifyTimeDuration(totalTimePlayed()))); @@ -396,6 +753,22 @@ QString MinecraftInstance::getStatusbarDescription() return description; } +shared_qobject_ptr<Task> MinecraftInstance::createUpdateTask(Net::Mode mode) +{ + switch (mode) + { + case Net::Mode::Offline: + { + return shared_qobject_ptr<Task>(new MinecraftLoadAndCheck(this)); + } + case Net::Mode::Online: + { + return shared_qobject_ptr<Task>(new OneSixUpdate(this)); + } + } + return nullptr; +} + std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session) { auto process = LaunchTask::create(std::dynamic_pointer_cast<MinecraftInstance>(getSharedPtr())); @@ -415,7 +788,7 @@ std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr s } // check launch method - QStringList validMethods = validLaunchMethods(); + QStringList validMethods = {"LauncherPart", "DirectJava"}; QString method = launchMethod(); if(!validMethods.contains(method)) { @@ -435,11 +808,14 @@ std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr s if(session->status != AuthSession::PlayableOffline) { process->appendStep(std::make_shared<ClaimAccount>(pptr, session)); - process->appendStep(std::make_shared<Update>(pptr)); + process->appendStep(std::make_shared<Update>(pptr, Net::Mode::Online)); + } + else + { + process->appendStep(std::make_shared<Update>(pptr, Net::Mode::Offline)); } // if there are any jar mods - if(getJarMods().size()) { auto step = std::make_shared<ModMinecraftJar>(pptr); process->appendStep(step); @@ -465,8 +841,21 @@ std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr s { // actually launch the game - auto step = createMainLaunchStep(pptr, session); - process->appendStep(step); + auto method = launchMethod(); + if(method == "LauncherPart") + { + auto step = std::make_shared<LauncherPartLaunch>(pptr); + step->setWorkingDirectory(minecraftRoot()); + step->setAuthSession(session); + process->appendStep(step); + } + else if (method == "DirectJava") + { + auto step = std::make_shared<DirectJavaLaunch>(pptr); + step->setWorkingDirectory(minecraftRoot()); + step->setAuthSession(session); + process->appendStep(step); + } } // run post-exit command if that's needed @@ -495,5 +884,68 @@ JavaVersion MinecraftInstance::getJavaVersion() const return JavaVersion(settings()->get("JavaVersion").toString()); } +std::shared_ptr<ModList> MinecraftInstance::loaderModList() const +{ + if (!m_loader_mod_list) + { + m_loader_mod_list.reset(new ModList(loaderModsDir())); + } + m_loader_mod_list->update(); + return m_loader_mod_list; +} + +std::shared_ptr<ModList> MinecraftInstance::coreModList() const +{ + if (!m_core_mod_list) + { + m_core_mod_list.reset(new ModList(coreModsDir())); + } + m_core_mod_list->update(); + return m_core_mod_list; +} + +std::shared_ptr<ModList> MinecraftInstance::resourcePackList() const +{ + if (!m_resource_pack_list) + { + m_resource_pack_list.reset(new ModList(resourcePacksDir())); + } + m_resource_pack_list->update(); + return m_resource_pack_list; +} + +std::shared_ptr<ModList> MinecraftInstance::texturePackList() const +{ + if (!m_texture_pack_list) + { + m_texture_pack_list.reset(new ModList(texturePacksDir())); + } + m_texture_pack_list->update(); + return m_texture_pack_list; +} + +std::shared_ptr<WorldList> MinecraftInstance::worldList() const +{ + if (!m_world_list) + { + m_world_list.reset(new WorldList(worldDir())); + } + return m_world_list; +} + +QList< Mod > MinecraftInstance::getJarMods() const +{ + auto profile = m_components->getProfile(); + QList<Mod> mods; + for (auto jarmod : profile->getJarMods()) + { + QStringList jar, temp1, temp2, temp3; + jarmod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, jarmodsPath().absolutePath()); + // QString filePath = jarmodsPath().absoluteFilePath(jarmod->filename(currentSystem)); + mods.push_back(Mod(QFileInfo(jar[0]))); + } + return mods; +} + #include "MinecraftInstance.moc" diff --git a/api/logic/minecraft/MinecraftInstance.h b/api/logic/minecraft/MinecraftInstance.h index 7f967ce0..446a39d5 100644 --- a/api/logic/minecraft/MinecraftInstance.h +++ b/api/logic/minecraft/MinecraftInstance.h @@ -3,88 +3,122 @@ #include <java/JavaVersion.h> #include "minecraft/Mod.h" #include <QProcess> - +#include <QDir> #include "multimc_logic_export.h" class ModList; class WorldList; class LaunchStep; +class ComponentList; class MULTIMC_LOGIC_EXPORT MinecraftInstance: public BaseInstance { + Q_OBJECT public: MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); virtual ~MinecraftInstance() {}; + virtual void init() override; + virtual void saveNow(); - /// Path to the instance's minecraft directory. - QString minecraftRoot() const; - - /// Path to the instance's minecraft/bin directory. - QString binRoot() const; + // FIXME: remove + QString typeName() const override; + // FIXME: remove + QSet<QString> traits() const override; - ////// Mod Lists ////// - virtual std::shared_ptr<ModList> resourcePackList() const - { - return nullptr; - } - virtual std::shared_ptr<ModList> texturePackList() const - { - return nullptr; - } - virtual std::shared_ptr<WorldList> worldList() const + bool canEdit() const override { - return nullptr; + return true; } - /// get all jar mods applicable to this instance's jar - virtual QList<Mod> getJarMods() const + + bool canExport() const override { - return QList<Mod>(); + return true; } - virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override; - virtual QString createLaunchScript(AuthSessionPtr session) = 0; - - //FIXME: nuke? - virtual std::shared_ptr<BaseVersionList> versionList() const override; + ////// Directories and files ////// + QString jarModsDir() const; + QString resourcePacksDir() const; + QString texturePacksDir() const; + QString loaderModsDir() const; + QString coreModsDir() const; + QString libDir() const; + QString worldDir() const; + QDir jarmodsPath() const; + QDir librariesPath() const; + QDir versionsPath() const; + QString instanceConfigFolder() const override; + QString minecraftRoot() const; // Path to the instance's minecraft directory. + QString binRoot() const; // Path to the instance's minecraft bin directory. + QString getNativePath() const; // where to put the natives during/before launch + QString getLocalLibraryPath() const; // where the instance-local libraries should be + + + ////// Profile management ////// + std::shared_ptr<ComponentList> getComponentList() const; + ////// Mod Lists ////// + std::shared_ptr<ModList> loaderModList() const; + std::shared_ptr<ModList> coreModList() const; + std::shared_ptr<ModList> resourcePackList() const; + std::shared_ptr<ModList> texturePackList() const; + std::shared_ptr<WorldList> worldList() const; + + + ////// Launch stuff ////// + shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override; + std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override; + QStringList extraArguments() const override; + QStringList verboseDescription(AuthSessionPtr session) override; + QList<Mod> getJarMods() const; + QString createLaunchScript(AuthSessionPtr session); /// get arguments passed to java QStringList javaArguments() const; /// get variables for launch command variable substitution/environment - virtual QMap<QString, QString> getVariables() const override; + QMap<QString, QString> getVariables() const override; /// create an environment for launching processes - virtual QProcessEnvironment createEnvironment() override; + QProcessEnvironment createEnvironment() override; /// guess log level from a line of minecraft log - virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) override; - - virtual IPathMatcher::Ptr getLogFileMatcher() override; - - virtual QString getLogFileRoot() override; + MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) override; - virtual QString getStatusbarDescription() override; + IPathMatcher::Ptr getLogFileMatcher() override; - virtual QStringList getClassPath() const = 0; - virtual QStringList getNativeJars() const = 0; + QString getLogFileRoot() override; - virtual QString getMainClass() const = 0; + QString getStatusbarDescription() override; - virtual QString getNativePath() const = 0; + // FIXME: remove + virtual QStringList getClassPath() const; + // FIXME: remove + virtual QStringList getNativeJars() const; + // FIXME: remove + virtual QString getMainClass() const; - virtual QString getLocalLibraryPath() const = 0; - - virtual QStringList processMinecraftArgs(AuthSessionPtr account) const = 0; + // FIXME: remove + virtual QStringList processMinecraftArgs(AuthSessionPtr account) const; virtual JavaVersion getJavaVersion() const; +signals: + void versionReloaded(); + protected: QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session); - virtual QStringList validLaunchMethods() = 0; - virtual QString launchMethod(); - virtual std::shared_ptr<LaunchStep> createMainLaunchStep(LaunchTask *parent, AuthSessionPtr session) = 0; + QStringList validLaunchMethods(); + QString launchMethod(); + private: QString prettifyTimeDuration(int64_t duration); + +protected: // data + std::shared_ptr<ComponentList> m_components; + mutable std::shared_ptr<ModList> m_loader_mod_list; + mutable std::shared_ptr<ModList> m_core_mod_list; + mutable std::shared_ptr<ModList> m_resource_pack_list; + mutable std::shared_ptr<ModList> m_texture_pack_list; + mutable std::shared_ptr<WorldList> m_world_list; }; typedef std::shared_ptr<MinecraftInstance> MinecraftInstancePtr; diff --git a/api/logic/minecraft/MinecraftLoadAndCheck.cpp b/api/logic/minecraft/MinecraftLoadAndCheck.cpp new file mode 100644 index 00000000..c64bbddf --- /dev/null +++ b/api/logic/minecraft/MinecraftLoadAndCheck.cpp @@ -0,0 +1,45 @@ +#include "MinecraftLoadAndCheck.h" +#include "MinecraftInstance.h" +#include "ComponentList.h" + +MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance *inst, QObject *parent) : Task(parent), m_inst(inst) +{ +} + +void MinecraftLoadAndCheck::executeTask() +{ + // add offline metadata load task + auto components = m_inst->getComponentList(); + components->reload(Net::Mode::Offline); + m_task = components->getCurrentTask(); + + if(!m_task) + { + emitSucceeded(); + return; + } + connect(m_task.get(), &Task::succeeded, this, &MinecraftLoadAndCheck::subtaskSucceeded); + connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::subtaskFailed); + connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::progress); + connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus); +} + +void MinecraftLoadAndCheck::subtaskSucceeded() +{ + if(isFinished()) + { + qCritical() << "OneSixUpdate: Subtask" << sender() << "succeeded, but work was already done!"; + return; + } + emitSucceeded(); +} + +void MinecraftLoadAndCheck::subtaskFailed(QString error) +{ + if(isFinished()) + { + qCritical() << "OneSixUpdate: Subtask" << sender() << "failed, but work was already done!"; + return; + } + emitFailed(error); +} diff --git a/api/logic/minecraft/MinecraftLoadAndCheck.h b/api/logic/minecraft/MinecraftLoadAndCheck.h new file mode 100644 index 00000000..00515f2d --- /dev/null +++ b/api/logic/minecraft/MinecraftLoadAndCheck.h @@ -0,0 +1,47 @@ +/* Copyright 2013-2018 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. + */ + +#pragma once + +#include <QObject> +#include <QList> +#include <QUrl> + +#include "tasks/Task.h" +#include <quazip.h> + +#include "QObjectPtr.h" + +class MinecraftVersion; +class MinecraftInstance; + +class MinecraftLoadAndCheck : public Task +{ + Q_OBJECT +public: + explicit MinecraftLoadAndCheck(MinecraftInstance *inst, QObject *parent = 0); + void executeTask() override; + +private slots: + void subtaskSucceeded(); + void subtaskFailed(QString error); + +private: + MinecraftInstance *m_inst = nullptr; + shared_qobject_ptr<Task> m_task; + QString m_preFailure; + QString m_fail_reason; +}; + diff --git a/api/logic/minecraft/MinecraftProfile.cpp b/api/logic/minecraft/MinecraftProfile.cpp deleted file mode 100644 index 4c1ab818..00000000 --- a/api/logic/minecraft/MinecraftProfile.cpp +++ /dev/null @@ -1,681 +0,0 @@ -/* Copyright 2013-2017 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 "minecraft/MinecraftProfile.h" -#include "ProfileUtils.h" -#include "ProfileStrategy.h" -#include "Exception.h" - -MinecraftProfile::MinecraftProfile(ProfileStrategy *strategy) - : QAbstractListModel() -{ - setStrategy(strategy); - clear(); -} - -MinecraftProfile::~MinecraftProfile() -{ - if(m_strategy) - { - delete m_strategy; - } -} - -void MinecraftProfile::setStrategy(ProfileStrategy* strategy) -{ - Q_ASSERT(strategy != nullptr); - - if(m_strategy != nullptr) - { - delete m_strategy; - m_strategy = nullptr; - } - m_strategy = strategy; - m_strategy->profile = this; -} - -ProfileStrategy* MinecraftProfile::strategy() -{ - return m_strategy; -} - -void MinecraftProfile::reload() -{ - beginResetModel(); - m_strategy->load(); - reapplyPatches(); - endResetModel(); -} - -void MinecraftProfile::clear() -{ - m_minecraftVersion.clear(); - m_minecraftVersionType.clear(); - m_minecraftAssets.reset(); - m_minecraftArguments.clear(); - m_tweakers.clear(); - m_mainClass.clear(); - m_appletClass.clear(); - m_libraries.clear(); - m_traits.clear(); - m_jarMods.clear(); - m_mainJar.reset(); - m_problemSeverity = ProblemSeverity::None; -} - -void MinecraftProfile::clearPatches() -{ - beginResetModel(); - m_patches.clear(); - endResetModel(); -} - -void MinecraftProfile::appendPatch(ProfilePatchPtr patch) -{ - int index = m_patches.size(); - beginInsertRows(QModelIndex(), index, index); - m_patches.append(patch); - endInsertRows(); -} - -bool MinecraftProfile::remove(const int index) -{ - auto patch = versionPatch(index); - if (!patch->isRemovable()) - { - qDebug() << "Patch" << patch->getID() << "is non-removable"; - return false; - } - - if(!m_strategy->removePatch(patch)) - { - qCritical() << "Patch" << patch->getID() << "could not be removed"; - return false; - } - - beginRemoveRows(QModelIndex(), index, index); - m_patches.removeAt(index); - endRemoveRows(); - reapplyPatches(); - saveCurrentOrder(); - return true; -} - -bool MinecraftProfile::remove(const QString id) -{ - int i = 0; - for (auto patch : m_patches) - { - if (patch->getID() == id) - { - return remove(i); - } - i++; - } - return false; -} - -bool MinecraftProfile::customize(int index) -{ - auto patch = versionPatch(index); - if (!patch->isCustomizable()) - { - qDebug() << "Patch" << patch->getID() << "is not customizable"; - return false; - } - if(!m_strategy->customizePatch(patch)) - { - qCritical() << "Patch" << patch->getID() << "could not be customized"; - return false; - } - reapplyPatches(); - saveCurrentOrder(); - // FIXME: maybe later in unstable - // emit dataChanged(createIndex(index, 0), createIndex(index, columnCount(QModelIndex()) - 1)); - return true; -} - -bool MinecraftProfile::revertToBase(int index) -{ - auto patch = versionPatch(index); - if (!patch->isRevertible()) - { - qDebug() << "Patch" << patch->getID() << "is not revertible"; - return false; - } - if(!m_strategy->revertPatch(patch)) - { - qCritical() << "Patch" << patch->getID() << "could not be reverted"; - return false; - } - reapplyPatches(); - saveCurrentOrder(); - // FIXME: maybe later in unstable - // emit dataChanged(createIndex(index, 0), createIndex(index, columnCount(QModelIndex()) - 1)); - return true; -} - -ProfilePatchPtr MinecraftProfile::versionPatch(const QString &id) -{ - for (auto patch : m_patches) - { - if (patch->getID() == id) - { - return patch; - } - } - return nullptr; -} - -ProfilePatchPtr MinecraftProfile::versionPatch(int index) -{ - if(index < 0 || index >= m_patches.size()) - return nullptr; - return m_patches[index]; -} - -bool MinecraftProfile::isVanilla() -{ - for(auto patchptr: m_patches) - { - if(patchptr->isCustom()) - return false; - } - return true; -} - -bool MinecraftProfile::revertToVanilla() -{ - // remove patches, if present - auto VersionPatchesCopy = m_patches; - for(auto & it: VersionPatchesCopy) - { - if (!it->isCustom()) - { - continue; - } - if(it->isRevertible() || it->isRemovable()) - { - if(!remove(it->getID())) - { - qWarning() << "Couldn't remove" << it->getID() << "from profile!"; - reapplyPatches(); - saveCurrentOrder(); - return false; - } - } - } - reapplyPatches(); - saveCurrentOrder(); - return true; -} - -QVariant MinecraftProfile::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - int row = index.row(); - int column = index.column(); - - if (row < 0 || row >= m_patches.size()) - return QVariant(); - - auto patch = m_patches.at(row); - - if (role == Qt::DisplayRole) - { - switch (column) - { - case 0: - return m_patches.at(row)->getName(); - case 1: - { - if(patch->isCustom()) - { - return QString("%1 (Custom)").arg(patch->getVersion()); - } - else - { - return patch->getVersion(); - } - } - default: - return QVariant(); - } - } - if(role == Qt::DecorationRole) - { - switch(column) - { - case 0: - { - auto severity = patch->getProblemSeverity(); - switch (severity) - { - case ProblemSeverity::Warning: - return "warning"; - case ProblemSeverity::Error: - return "error"; - default: - return QVariant(); - } - } - default: - { - return QVariant(); - } - } - } - return QVariant(); -} -QVariant MinecraftProfile::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (orientation == Qt::Horizontal) - { - if (role == Qt::DisplayRole) - { - switch (section) - { - case 0: - return tr("Name"); - case 1: - return tr("Version"); - default: - return QVariant(); - } - } - } - return QVariant(); -} -Qt::ItemFlags MinecraftProfile::flags(const QModelIndex &index) const -{ - if (!index.isValid()) - return Qt::NoItemFlags; - return Qt::ItemIsSelectable | Qt::ItemIsEnabled; -} - -int MinecraftProfile::rowCount(const QModelIndex &parent) const -{ - return m_patches.size(); -} - -int MinecraftProfile::columnCount(const QModelIndex &parent) const -{ - return 2; -} - -void MinecraftProfile::saveCurrentOrder() const -{ - ProfileUtils::PatchOrder order; - for(auto item: m_patches) - { - if(!item->isMoveable()) - continue; - order.append(item->getID()); - } - m_strategy->saveOrder(order); -} - -void MinecraftProfile::move(const int index, const MoveDirection direction) -{ - int theirIndex; - if (direction == MoveUp) - { - theirIndex = index - 1; - } - else - { - theirIndex = index + 1; - } - - if (index < 0 || index >= m_patches.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 = versionPatch(index); - auto to = versionPatch(theirIndex); - - if (!from || !to || !to->isMoveable() || !from->isMoveable()) - { - return; - } - beginMoveRows(QModelIndex(), index, index, QModelIndex(), togap); - m_patches.swap(index, theirIndex); - endMoveRows(); - reapplyPatches(); - saveCurrentOrder(); -} -void MinecraftProfile::resetOrder() -{ - m_strategy->resetOrder(); - reload(); -} - -bool MinecraftProfile::reapplyPatches() -{ - try - { - clear(); - for(auto file: m_patches) - { - qDebug() << "Applying" << file->getID() << (file->getProblemSeverity() == ProblemSeverity::Error ? "ERROR" : "GOOD"); - file->applyTo(this); - } - } - catch (Exception & error) - { - clear(); - qWarning() << "Couldn't apply profile patches because: " << error.cause(); - return false; - } - return true; -} - -static void applyString(const QString & from, QString & to) -{ - if(from.isEmpty()) - return; - to = from; -} - -void MinecraftProfile::applyMinecraftVersion(const QString& id) -{ - applyString(id, this->m_minecraftVersion); -} - -void MinecraftProfile::applyAppletClass(const QString& appletClass) -{ - applyString(appletClass, this->m_appletClass); -} - -void MinecraftProfile::applyMainClass(const QString& mainClass) -{ - applyString(mainClass, this->m_mainClass); -} - -void MinecraftProfile::applyMinecraftArguments(const QString& minecraftArguments) -{ - applyString(minecraftArguments, this->m_minecraftArguments); -} - -void MinecraftProfile::applyMinecraftVersionType(const QString& type) -{ - applyString(type, this->m_minecraftVersionType); -} - -void MinecraftProfile::applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets) -{ - if(assets) - { - m_minecraftAssets = assets; - } -} - -void MinecraftProfile::applyTraits(const QSet<QString>& traits) -{ - this->m_traits.unite(traits); -} - -void MinecraftProfile::applyTweakers(const QStringList& tweakers) -{ - // FIXME: check for dupes? - // FIXME: does order matter? - for (auto tweaker : tweakers) - { - this->m_tweakers += tweaker; - } -} - -void MinecraftProfile::applyJarMods(const QList<LibraryPtr>& jarMods) -{ - this->m_jarMods.append(jarMods); -} - -static int findLibraryByName(QList<LibraryPtr> *haystack, const GradleSpecifier &needle) -{ - int retval = -1; - for (int i = 0; i < haystack->size(); ++i) - { - if (haystack->at(i)->rawName().matchName(needle)) - { - // only one is allowed. - if (retval != -1) - return -1; - retval = i; - } - } - return retval; -} - -void MinecraftProfile::applyMods(const QList<LibraryPtr>& mods) -{ - QList<LibraryPtr> * list = &m_mods; - for(auto & mod: mods) - { - auto modCopy = Library::limitedCopy(mod); - - // find the mod by name. - const int index = findLibraryByName(list, mod->rawName()); - // mod not found? just add it. - if (index < 0) - { - list->append(modCopy); - return; - } - - auto existingLibrary = list->at(index); - // if we are higher it means we should update - if (Version(mod->version()) > Version(existingLibrary->version())) - { - list->replace(index, modCopy); - } - } -} - -void MinecraftProfile::applyLibrary(LibraryPtr library) -{ - if(!library->isActive()) - { - return; - } - - QList<LibraryPtr> * list = &m_libraries; - if(library->isNative()) - { - list = &m_nativeLibraries; - } - - auto libraryCopy = Library::limitedCopy(library); - - // find the library by name. - const int index = findLibraryByName(list, library->rawName()); - // library not found? just add it. - if (index < 0) - { - list->append(libraryCopy); - return; - } - - auto existingLibrary = list->at(index); - // if we are higher it means we should update - if (Version(library->version()) > Version(existingLibrary->version())) - { - list->replace(index, libraryCopy); - } -} - -const LibraryPtr MinecraftProfile::getMainJar() const -{ - return m_mainJar; -} - -void MinecraftProfile::applyMainJar(LibraryPtr jar) -{ - if(jar) - { - m_mainJar = jar; - } -} - -void MinecraftProfile::applyProblemSeverity(ProblemSeverity severity) -{ - if (m_problemSeverity < severity) - { - m_problemSeverity = severity; - } -} - - -QString MinecraftProfile::getMinecraftVersion() const -{ - return m_minecraftVersion; -} - -QString MinecraftProfile::getAppletClass() const -{ - return m_appletClass; -} - -QString MinecraftProfile::getMainClass() const -{ - return m_mainClass; -} - -const QSet<QString> &MinecraftProfile::getTraits() const -{ - return m_traits; -} - -const QStringList & MinecraftProfile::getTweakers() const -{ - return m_tweakers; -} - -bool MinecraftProfile::hasTrait(const QString& trait) const -{ - return m_traits.contains(trait); -} - -ProblemSeverity MinecraftProfile::getProblemSeverity() const -{ - return m_problemSeverity; -} - -QString MinecraftProfile::getMinecraftVersionType() const -{ - return m_minecraftVersionType; -} - -std::shared_ptr<MojangAssetIndexInfo> MinecraftProfile::getMinecraftAssets() const -{ - if(!m_minecraftAssets) - { - return std::make_shared<MojangAssetIndexInfo>("legacy"); - } - return m_minecraftAssets; -} - -QString MinecraftProfile::getMinecraftArguments() const -{ - return m_minecraftArguments; -} - -const QList<LibraryPtr> & MinecraftProfile::getJarMods() const -{ - return m_jarMods; -} - -const QList<LibraryPtr> & MinecraftProfile::getLibraries() const -{ - return m_libraries; -} - -const QList<LibraryPtr> & MinecraftProfile::getNativeLibraries() const -{ - return m_nativeLibraries; -} - -void MinecraftProfile::getLibraryFiles(const QString& architecture, QStringList& jars, QStringList& nativeJars, const QString& overridePath, const QString& tempPath) const -{ - QStringList native32, native64; - jars.clear(); - nativeJars.clear(); - for (auto lib : getLibraries()) - { - lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); - } - // NOTE: order is important here, add main jar last to the lists - if(m_mainJar) - { - // FIXME: HACK!! jar modding is weird and unsystematic! - if(m_jarMods.size()) - { - QDir tempDir(tempPath); - jars.append(tempDir.absoluteFilePath("minecraft.jar")); - } - else - { - m_mainJar->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); - } - } - for (auto lib : getNativeLibraries()) - { - lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); - } - if(architecture == "32") - { - nativeJars.append(native32); - } - else if(architecture == "64") - { - nativeJars.append(native64); - } -} - -void MinecraftProfile::installJarMods(QStringList selectedFiles) -{ - m_strategy->installJarMods(selectedFiles); -} - -/* - * TODO: get rid of this. Get rid of all order numbers. - */ -int MinecraftProfile::getFreeOrderNumber() -{ - int largest = 100; - // yes, I do realize this is dumb. The order thing itself is dumb. and to be removed next. - for(auto thing: m_patches) - { - int order = thing->getOrder(); - if(order > largest) - largest = order; - } - return largest + 1; -} diff --git a/api/logic/minecraft/MinecraftProfile.h b/api/logic/minecraft/MinecraftProfile.h deleted file mode 100644 index 192c77f9..00000000 --- a/api/logic/minecraft/MinecraftProfile.h +++ /dev/null @@ -1,211 +0,0 @@ -/* Copyright 2013-2017 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. - */ - -#pragma once - -#include <QAbstractListModel> - -#include <QString> -#include <QList> -#include <memory> - -#include "Library.h" -#include "ProfilePatch.h" -#include "BaseVersion.h" -#include "MojangDownloadInfo.h" - -#include "multimc_logic_export.h" - -class ProfileStrategy; -class OneSixInstance; - - -class MULTIMC_LOGIC_EXPORT MinecraftProfile : public QAbstractListModel -{ - Q_OBJECT - -public: - explicit MinecraftProfile(ProfileStrategy *strategy); - virtual ~MinecraftProfile(); - - void setStrategy(ProfileStrategy * strategy); - ProfileStrategy *strategy(); - - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; - virtual int columnCount(const QModelIndex &parent) const override; - virtual Qt::ItemFlags flags(const QModelIndex &index) const override; - - /// is this version unchanged by the user? - bool isVanilla(); - - /// remove any customizations on top of whatever 'vanilla' means - bool revertToVanilla(); - - /// install more jar mods - void installJarMods(QStringList selectedFiles); - - /// DEPRECATED, remove ASAP - int getFreeOrderNumber(); - - enum MoveDirection { MoveUp, MoveDown }; - /// move patch file # up or down the list - void move(const int index, const MoveDirection direction); - - /// remove patch file # - including files/records - bool remove(const int index); - - /// remove patch file by id - including files/records - bool remove(const QString id); - - bool customize(int index); - - bool revertToBase(int index); - - void resetOrder(); - - /// reload all profile patches from storage, clear the profile and apply the patches - void reload(); - - /// clear the profile - void clear(); - - /// apply the patches. Catches all the errors and returns true/false for success/failure - bool reapplyPatches(); - -public: /* application of profile variables from patches */ - void applyMinecraftVersion(const QString& id); - void applyMainClass(const QString& mainClass); - void applyAppletClass(const QString& appletClass); - void applyMinecraftArguments(const QString& minecraftArguments); - void applyMinecraftVersionType(const QString& type); - void applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets); - void applyTraits(const QSet<QString> &traits); - void applyTweakers(const QStringList &tweakers); - void applyJarMods(const QList<LibraryPtr> &jarMods); - void applyMods(const QList<LibraryPtr> &jarMods); - void applyLibrary(LibraryPtr library); - void applyMainJar(LibraryPtr jar); - void applyProblemSeverity(ProblemSeverity severity); - -public: /* getters for profile variables */ - QString getMinecraftVersion() const; - QString getMainClass() const; - QString getAppletClass() const; - QString getMinecraftVersionType() const; - MojangAssetIndexInfo::Ptr getMinecraftAssets() const; - QString getMinecraftArguments() const; - const QSet<QString> & getTraits() const; - const QStringList & getTweakers() const; - const QList<LibraryPtr> & getJarMods() const; - const QList<LibraryPtr> & getLibraries() const; - const QList<LibraryPtr> & getNativeLibraries() const; - const LibraryPtr getMainJar() const; - void getLibraryFiles(const QString & architecture, QStringList & jars, QStringList & nativeJars, const QString & overridePath, - const QString & tempPath) const; - bool hasTrait(const QString & trait) const; - ProblemSeverity getProblemSeverity() const; - -public: - /// get the profile patch by id - ProfilePatchPtr versionPatch(const QString &id); - - /// get the profile patch by index - ProfilePatchPtr versionPatch(int index); - - /// save the current patch order - void saveCurrentOrder() const; - - /// Remove all the patches - void clearPatches(); - - /// Add the patch object to the internal list of patches - void appendPatch(ProfilePatchPtr patch); - -private: /* data */ - /// the version of Minecraft - jar to use - QString m_minecraftVersion; - - /// Release type - "release" or "snapshot" - QString m_minecraftVersionType; - - /// Assets type - "legacy" or a version ID - MojangAssetIndexInfo::Ptr m_minecraftAssets; - - /** - * arguments that should be used for launching minecraft - * - * ex: "--username ${auth_player_name} --session ${auth_session} - * --version ${version_name} --gameDir ${game_directory} --assetsDir ${game_assets}" - */ - QString m_minecraftArguments; - - /// A list of all tweaker classes - QStringList m_tweakers; - - /// The main class to load first - QString m_mainClass; - - /// The applet class, for some very old minecraft releases - QString m_appletClass; - - /// the list of libraries - QList<LibraryPtr> m_libraries; - - /// the main jar - LibraryPtr m_mainJar; - - /// the list of libraries - QList<LibraryPtr> m_nativeLibraries; - - /// traits, collected from all the version files (version files can only add) - QSet<QString> m_traits; - - /// A list of jar mods. version files can add those. - QList<LibraryPtr> m_jarMods; - - /// the list of mods - QList<LibraryPtr> m_mods; - - ProblemSeverity m_problemSeverity = ProblemSeverity::None; - - /* - FIXME: add support for those rules here? Looks like a pile of quick hacks to me though. - - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "osx", - "version": "^10\\.5\\.\\d$" - } - } - ], - "incompatibilityReason": "There is a bug in LWJGL which makes it incompatible with OSX - 10.5.8. Please go to New Profile and use 1.5.2 for now. Sorry!" - } - */ - // QList<Rule> rules; - - /// list of attached profile patches - QList<ProfilePatchPtr> m_patches; - - /// strategy used for profile operations - ProfileStrategy *m_strategy = nullptr; -}; diff --git a/api/logic/minecraft/onesix/OneSixUpdate.cpp b/api/logic/minecraft/MinecraftUpdate.cpp index e0027032..86835fa4 100644 --- a/api/logic/minecraft/onesix/OneSixUpdate.cpp +++ b/api/logic/minecraft/MinecraftUpdate.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,8 +15,8 @@ #include "Env.h" #include <minecraft/forge/ForgeXzDownload.h> -#include "OneSixUpdate.h" -#include "OneSixInstance.h" +#include "MinecraftUpdate.h" +#include "MinecraftInstance.h" #include <QFile> #include <QFileInfo> @@ -24,7 +24,7 @@ #include <QDataStream> #include "BaseInstance.h" -#include "minecraft/MinecraftProfile.h" +#include "minecraft/ComponentList.h" #include "minecraft/Library.h" #include "net/URLConstants.h" #include <FileSystem.h> @@ -37,42 +37,26 @@ #include <meta/Index.h> #include <meta/Version.h> -OneSixUpdate::OneSixUpdate(OneSixInstance *inst, QObject *parent) : Task(parent), m_inst(inst) +OneSixUpdate::OneSixUpdate(MinecraftInstance *inst, QObject *parent) : Task(parent), m_inst(inst) { +} + +void OneSixUpdate::executeTask() +{ + m_tasks.clear(); // create folders { m_tasks.append(std::make_shared<FoldersTask>(m_inst)); } - // add metadata update tasks, if necessary + // add metadata update task if necessary { - /* - * FIXME: there are some corner cases here that remain unhandled: - * what if local load succeeds but remote fails? The version is still usable... - * We should not rely on the remote to be there... and prefer local files if it does not respond. - */ - qDebug() << "Updating patches..."; - auto profile = m_inst->getMinecraftProfile(); - m_inst->reloadProfile(); - for(int i = 0; i < profile->rowCount(); i++) + auto components = m_inst->getComponentList(); + components->reload(Net::Mode::Online); + auto task = components->getCurrentTask(); + if(task) { - auto patch = profile->versionPatch(i); - auto id = patch->getID(); - auto metadata = patch->getMeta(); - if(metadata) - { - metadata->load(); - auto task = metadata->getCurrentTask(); - if(task) - { - qDebug() << "Loading remote meta patch" << id; - m_tasks.append(task.unwrap()); - } - } - else - { - qDebug() << "Ignoring local patch" << id; - } + m_tasks.append(task.unwrap()); } } @@ -90,10 +74,7 @@ OneSixUpdate::OneSixUpdate(OneSixInstance *inst, QObject *parent) : Task(parent) { m_tasks.append(std::make_shared<AssetUpdateTask>(m_inst)); } -} -void OneSixUpdate::executeTask() -{ if(!m_preFailure.isEmpty()) { emitFailed(m_preFailure); @@ -109,6 +90,11 @@ void OneSixUpdate::next() emitFailed(tr("Aborted by user.")); return; } + if(m_failed_out_of_order) + { + emitFailed(m_fail_reason); + return; + } m_currentTask ++; if(m_currentTask > 0) { @@ -127,6 +113,7 @@ void OneSixUpdate::next() // if the task is already finished by the time we look at it, skip it if(task->isFinished()) { + qCritical() << "OneSixUpdate: Skipping finished subtask" << m_currentTask << ":" << task.get(); next(); } connect(task.get(), &Task::succeeded, this, &OneSixUpdate::subtaskSucceeded); @@ -142,11 +129,37 @@ void OneSixUpdate::next() void OneSixUpdate::subtaskSucceeded() { + if(isFinished()) + { + qCritical() << "OneSixUpdate: Subtask" << sender() << "succeeded, but work was already done!"; + return; + } + auto senderTask = QObject::sender(); + auto currentTask = m_tasks[m_currentTask].get(); + if(senderTask != currentTask) + { + qDebug() << "OneSixUpdate: Subtask" << sender() << "succeeded out of order."; + return; + } next(); } void OneSixUpdate::subtaskFailed(QString error) { + if(isFinished()) + { + qCritical() << "OneSixUpdate: Subtask" << sender() << "failed, but work was already done!"; + return; + } + auto senderTask = QObject::sender(); + auto currentTask = m_tasks[m_currentTask].get(); + if(senderTask != currentTask) + { + qDebug() << "OneSixUpdate: Subtask" << sender() << "failed out of order."; + m_failed_out_of_order = true; + m_fail_reason = error; + return; + } emitFailed(error); } diff --git a/api/logic/minecraft/onesix/OneSixUpdate.h b/api/logic/minecraft/MinecraftUpdate.h index 6bcfd41a..78c02049 100644 --- a/api/logic/minecraft/onesix/OneSixUpdate.h +++ b/api/logic/minecraft/MinecraftUpdate.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,13 +25,13 @@ #include <quazip.h> class MinecraftVersion; -class OneSixInstance; +class MinecraftInstance; class OneSixUpdate : public Task { Q_OBJECT public: - explicit OneSixUpdate(OneSixInstance *inst, QObject *parent = 0); + explicit OneSixUpdate(MinecraftInstance *inst, QObject *parent = 0); void executeTask() override; bool canAbort() const override; @@ -45,9 +45,11 @@ private: void next(); private: - OneSixInstance *m_inst = nullptr; + MinecraftInstance *m_inst = nullptr; QList<std::shared_ptr<Task>> m_tasks; QString m_preFailure; int m_currentTask = -1; bool m_abort = false; + bool m_failed_out_of_order = false; + QString m_fail_reason; }; diff --git a/api/logic/minecraft/Mod.cpp b/api/logic/minecraft/Mod.cpp index 5b4ecbb6..03e04b2b 100644 --- a/api/logic/minecraft/Mod.cpp +++ b/api/logic/minecraft/Mod.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/Mod.h b/api/logic/minecraft/Mod.h index 96ee8174..ccab1867 100644 --- a/api/logic/minecraft/Mod.h +++ b/api/logic/minecraft/Mod.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,7 +58,8 @@ public: } QString name() const { - if(m_name.trimmed().isEmpty()) + QString name = m_name.trimmed(); + if(name.isEmpty() || name == "Example Mod") { return m_mmc_id; } diff --git a/api/logic/minecraft/ModList.cpp b/api/logic/minecraft/ModList.cpp index 02b09eef..6ccf20e2 100644 --- a/api/logic/minecraft/ModList.cpp +++ b/api/logic/minecraft/ModList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/ModList.h b/api/logic/minecraft/ModList.h index 8cdd2d9a..72f50edb 100644 --- a/api/logic/minecraft/ModList.h +++ b/api/logic/minecraft/ModList.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/MojangVersionFormat.cpp b/api/logic/minecraft/MojangVersionFormat.cpp index f73c8e49..a0aa2894 100644 --- a/api/logic/minecraft/MojangVersionFormat.cpp +++ b/api/logic/minecraft/MojangVersionFormat.cpp @@ -1,5 +1,5 @@ #include "MojangVersionFormat.h" -#include "onesix/OneSixVersionFormat.h" +#include "OneSixVersionFormat.h" #include "MojangDownloadInfo.h" #include "Json.h" diff --git a/api/logic/minecraft/onesix/OneSixVersionFormat.cpp b/api/logic/minecraft/OneSixVersionFormat.cpp index 7ebf514f..f7ab25b3 100644 --- a/api/logic/minecraft/onesix/OneSixVersionFormat.cpp +++ b/api/logic/minecraft/OneSixVersionFormat.cpp @@ -52,6 +52,15 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc QJsonObject root = doc.object(); + Meta::MetadataVersion formatVersion = Meta::parseFormatVersion(root, false); + switch(formatVersion) + { + case Meta::MetadataVersion::InitialRelease: + break; + case Meta::MetadataVersion::Invalid: + throw JSONValidationError(filename + " does not contain a recognizable version of the metadata format."); + } + if (requireOrder) { if (root.contains("order")) @@ -77,8 +86,6 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc } out->version = root.value("version").toString(); - out->dependsOnMinecraftVersion = root.value("mcVersion").toString(); - // out->filename = filename; MojangVersionFormat::readVersionProperties(root, out.get()); @@ -192,6 +199,30 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc out->mainJar = lib; } + if (root.contains("requires")) + { + Meta::parseRequires(root, &out->requires); + } + QString dependsOnMinecraftVersion = root.value("mcVersion").toString(); + if(!dependsOnMinecraftVersion.isEmpty()) + { + Meta::Require mcReq; + mcReq.uid = "net.minecraft"; + mcReq.equalsVersion = dependsOnMinecraftVersion; + if (out->requires.count(mcReq) == 0) + { + out->requires.insert(mcReq); + } + } + if (root.contains("conflicts")) + { + Meta::parseRequires(root, &out->conflicts); + } + if (root.contains("volatile")) + { + out->m_volatile = requireBoolean(root, "volatile"); + } + /* removed features that shouldn't be used */ if (root.contains("tweakers")) { @@ -216,19 +247,16 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc return out; } -QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch, bool saveOrder) +QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch) { QJsonObject root; - if (saveOrder) - { - root.insert("order", patch->order); - } writeString(root, "name", patch->name); writeString(root, "uid", patch->uid); writeString(root, "version", patch->version); - writeString(root, "mcVersion", patch->dependsOnMinecraftVersion); + + Meta::serializeFormatVersion(root, Meta::MetadataVersion::InitialRelease); MojangVersionFormat::writeVersionProperties(patch.get(), root); @@ -266,6 +294,18 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch } root.insert("mods", array); } + if(!patch->requires.empty()) + { + Meta::serializeRequires(root, &patch->requires, "requires"); + } + if(!patch->conflicts.empty()) + { + Meta::serializeRequires(root, &patch->conflicts, "conflicts"); + } + if(patch->m_volatile) + { + root.insert("volatile", true); + } // write the contents to a json document. { QJsonDocument out; diff --git a/api/logic/minecraft/onesix/OneSixVersionFormat.h b/api/logic/minecraft/OneSixVersionFormat.h index 64f18da8..8b782db0 100644 --- a/api/logic/minecraft/onesix/OneSixVersionFormat.h +++ b/api/logic/minecraft/OneSixVersionFormat.h @@ -1,7 +1,7 @@ #pragma once #include <minecraft/VersionFile.h> -#include <minecraft/MinecraftProfile.h> +#include <minecraft/ComponentList.h> #include <minecraft/Library.h> #include <QJsonDocument> @@ -10,7 +10,7 @@ class OneSixVersionFormat public: // version files / profile patches static VersionFilePtr versionFileFromJson(const QJsonDocument &doc, const QString &filename, const bool requireOrder); - static QJsonDocument versionFileToJson(const VersionFilePtr &patch, bool saveOrder); + static QJsonDocument versionFileToJson(const VersionFilePtr &patch); // libraries static LibraryPtr libraryFromJson(const QJsonObject &libObj, const QString &filename); diff --git a/api/logic/minecraft/OpSys.cpp b/api/logic/minecraft/OpSys.cpp index c2c28d1b..2165fa7f 100644 --- a/api/logic/minecraft/OpSys.cpp +++ b/api/logic/minecraft/OpSys.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/OpSys.h b/api/logic/minecraft/OpSys.h index 8f43f480..e44b1e07 100644 --- a/api/logic/minecraft/OpSys.h +++ b/api/logic/minecraft/OpSys.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/ProfilePatch.cpp b/api/logic/minecraft/ProfilePatch.cpp deleted file mode 100644 index a2605278..00000000 --- a/api/logic/minecraft/ProfilePatch.cpp +++ /dev/null @@ -1,188 +0,0 @@ -#include <meta/VersionList.h> -#include <meta/Index.h> -#include <Env.h> -#include "ProfilePatch.h" - -#include "meta/Version.h" -#include "VersionFile.h" -#include "minecraft/MinecraftProfile.h" - -ProfilePatch::ProfilePatch(std::shared_ptr<Meta::Version> version) - :m_metaVersion(version) -{ -} - -ProfilePatch::ProfilePatch(std::shared_ptr<VersionFile> file, const QString& filename) - :m_file(file), m_filename(filename) -{ -} - -std::shared_ptr<Meta::Version> ProfilePatch::getMeta() -{ - return m_metaVersion; -} - -void ProfilePatch::applyTo(MinecraftProfile* profile) -{ - auto vfile = getVersionFile(); - if(vfile) - { - vfile->applyTo(profile); - } - else - { - profile->applyProblemSeverity(getProblemSeverity()); - } -} - -std::shared_ptr<class VersionFile> ProfilePatch::getVersionFile() -{ - if(m_metaVersion) - { - if(!m_metaVersion->isLoaded()) - { - m_metaVersion->load(); - } - return m_metaVersion->data(); - } - else - { - return m_file; - } -} - -std::shared_ptr<class Meta::VersionList> ProfilePatch::getVersionList() -{ - if(m_metaVersion) - { - return ENV.metadataIndex()->get(m_metaVersion->uid()); - } - return nullptr; -} - -int ProfilePatch::getOrder() -{ - if(m_orderOverride) - return m_order; - - auto vfile = getVersionFile(); - if(vfile) - { - return vfile->order; - } - return 0; -} -void ProfilePatch::setOrder(int order) -{ - m_orderOverride = true; - m_order = order; -} -QString ProfilePatch::getID() -{ - if(m_metaVersion) - return m_metaVersion->uid(); - return getVersionFile()->uid; -} -QString ProfilePatch::getName() -{ - if(m_metaVersion) - return m_metaVersion->name(); - return getVersionFile()->name; -} -QString ProfilePatch::getVersion() -{ - if(m_metaVersion) - return m_metaVersion->version(); - return getVersionFile()->version; -} -QString ProfilePatch::getFilename() -{ - return m_filename; -} -QDateTime ProfilePatch::getReleaseDateTime() -{ - if(m_metaVersion) - { - return m_metaVersion->time(); - } - return getVersionFile()->releaseTime; -} - -bool ProfilePatch::isCustom() -{ - return !m_isVanilla; -}; - -bool ProfilePatch::isCustomizable() -{ - if(m_metaVersion) - { - if(getVersionFile()) - { - return true; - } - } - return false; -} -bool ProfilePatch::isRemovable() -{ - return m_isRemovable; -} -bool ProfilePatch::isRevertible() -{ - return m_isRevertible; -} -bool ProfilePatch::isMoveable() -{ - return m_isMovable; -} -bool ProfilePatch::isVersionChangeable() -{ - auto list = getVersionList(); - if(list) - { - if(!list->isLoaded()) - { - list->load(); - } - return list->count() != 0; - } - return false; -} - -void ProfilePatch::setVanilla (bool state) -{ - m_isVanilla = state; -} -void ProfilePatch::setRemovable (bool state) -{ - m_isRemovable = state; -} -void ProfilePatch::setRevertible (bool state) -{ - m_isRevertible = state; -} -void ProfilePatch::setMovable (bool state) -{ - m_isMovable = state; -} - -ProblemSeverity ProfilePatch::getProblemSeverity() -{ - auto file = getVersionFile(); - if(file) - { - return file->getProblemSeverity(); - } - return ProblemSeverity::Error; -} - -const QList<PatchProblem> ProfilePatch::getProblems() -{ - auto file = getVersionFile(); - if(file) - { - return file->getProblems(); - } - return {PatchProblem(ProblemSeverity::Error, QObject::tr("Patch is not loaded yet."))}; -} diff --git a/api/logic/minecraft/ProfilePatch.h b/api/logic/minecraft/ProfilePatch.h deleted file mode 100644 index 59171a0a..00000000 --- a/api/logic/minecraft/ProfilePatch.h +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once - -#include <memory> -#include <QList> -#include <QJsonDocument> -#include <QDateTime> -#include "ProblemProvider.h" - -class MinecraftProfile; -namespace Meta -{ - class Version; - class VersionList; -} -class VersionFile; - -class ProfilePatch : public ProblemProvider -{ -public: - ProfilePatch(std::shared_ptr<Meta::Version> version); - ProfilePatch(std::shared_ptr<VersionFile> file, const QString &filename = QString()); - - virtual ~ProfilePatch(){}; - virtual void applyTo(MinecraftProfile *profile); - - virtual bool isMoveable(); - virtual bool isCustomizable(); - virtual bool isRevertible(); - virtual bool isRemovable(); - virtual bool isCustom(); - virtual bool isVersionChangeable(); - - virtual void setOrder(int order); - virtual int getOrder(); - - virtual QString getID(); - virtual QString getName(); - virtual QString getVersion(); - virtual std::shared_ptr<Meta::Version> getMeta(); - virtual QDateTime getReleaseDateTime(); - - virtual QString getFilename(); - - virtual std::shared_ptr<class VersionFile> getVersionFile(); - virtual std::shared_ptr<class Meta::VersionList> getVersionList(); - - void setVanilla (bool state); - void setRemovable (bool state); - void setRevertible (bool state); - void setMovable (bool state); - - const QList<PatchProblem> getProblems() override; - ProblemSeverity getProblemSeverity() override; - -protected: - // Properties for UI and version manipulation from UI in general - bool m_isMovable = false; - bool m_isRevertible = false; - bool m_isRemovable = false; - bool m_isVanilla = false; - - bool m_orderOverride = false; - int m_order = 0; - - std::shared_ptr<Meta::Version> m_metaVersion; - std::shared_ptr<VersionFile> m_file; - QString m_filename; -}; - -typedef std::shared_ptr<ProfilePatch> ProfilePatchPtr; diff --git a/api/logic/minecraft/ProfileStrategy.h b/api/logic/minecraft/ProfileStrategy.h deleted file mode 100644 index 26bdf661..00000000 --- a/api/logic/minecraft/ProfileStrategy.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include "ProfileUtils.h" -#include "ProfilePatch.h" - -class MinecraftProfile; - -class ProfileStrategy -{ - friend class MinecraftProfile; -public: - virtual ~ProfileStrategy(){}; - - /// load the patch files into the profile - virtual void load() = 0; - - /// reset the order of patches - virtual bool resetOrder() = 0; - - /// save the order of patches, given the order - virtual bool saveOrder(ProfileUtils::PatchOrder order) = 0; - - /// install a list of jar mods into the instance - virtual bool installJarMods(QStringList filepaths) = 0; - - /// remove any files or records that constitute the version patch - virtual bool removePatch(ProfilePatchPtr jarMod) = 0; - - /// make the patch custom, if possible - virtual bool customizePatch(ProfilePatchPtr patch) = 0; - - /// revert the custom patch to 'vanilla', if possible - virtual bool revertPatch(ProfilePatchPtr patch) = 0; -protected: - MinecraftProfile *profile; -}; diff --git a/api/logic/minecraft/ProfileUtils.cpp b/api/logic/minecraft/ProfileUtils.cpp index 8c5bc052..a6d2028d 100644 --- a/api/logic/minecraft/ProfileUtils.cpp +++ b/api/logic/minecraft/ProfileUtils.cpp @@ -1,6 +1,6 @@ #include "ProfileUtils.h" #include "minecraft/VersionFilterData.h" -#include "minecraft/onesix/OneSixVersionFormat.h" +#include "minecraft/OneSixVersionFormat.h" #include "Json.h" #include <QDebug> @@ -14,38 +14,6 @@ namespace ProfileUtils static const int currentOrderFileVersion = 1; -bool writeOverrideOrders(QString path, const PatchOrder &order) -{ - QJsonObject obj; - obj.insert("version", currentOrderFileVersion); - QJsonArray orderArray; - for(auto str: order) - { - orderArray.append(str); - } - obj.insert("order", orderArray); - QSaveFile orderFile(path); - if (!orderFile.open(QFile::WriteOnly)) - { - qCritical() << "Couldn't open" << orderFile.fileName() - << "for writing:" << orderFile.errorString(); - return false; - } - auto data = QJsonDocument(obj).toJson(QJsonDocument::Indented); - if(orderFile.write(data) != data.size()) - { - qCritical() << "Couldn't write all the data into" << orderFile.fileName() - << "because:" << orderFile.errorString(); - return false; - } - if(!orderFile.commit()) - { - qCritical() << "Couldn't save" << orderFile.fileName() - << "because:" << orderFile.errorString(); - } - return true; -} - bool readOverrideOrders(QString path, PatchOrder &order) { QFile orderFile(path); @@ -154,6 +122,25 @@ VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder) return guardedParseJson(doc, fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), requireOrder); } +bool saveJsonFile(const QJsonDocument doc, const QString & filename) +{ + auto data = doc.toJson(); + QSaveFile jsonFile(filename); + if(!jsonFile.open(QIODevice::WriteOnly)) + { + jsonFile.cancelWriting(); + qWarning() << "Couldn't open" << filename << "for writing"; + return false; + } + jsonFile.write(data); + if(!jsonFile.commit()) + { + qWarning() << "Couldn't save" << filename; + return false; + } + return true; +} + VersionFilePtr parseBinaryJsonFile(const QFileInfo &fileInfo) { QFile file(fileInfo.absoluteFilePath()); diff --git a/api/logic/minecraft/ProfileUtils.h b/api/logic/minecraft/ProfileUtils.h index 267fd42b..351c36cb 100644 --- a/api/logic/minecraft/ProfileUtils.h +++ b/api/logic/minecraft/ProfileUtils.h @@ -16,6 +16,9 @@ bool writeOverrideOrders(QString path, const PatchOrder &order); /// Parse a version file in JSON format VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder); +/// Save a JSON file (in any format) +bool saveJsonFile(const QJsonDocument doc, const QString & filename); + /// Parse a version file in binary JSON format VersionFilePtr parseBinaryJsonFile(const QFileInfo &fileInfo); diff --git a/api/logic/minecraft/Rule.cpp b/api/logic/minecraft/Rule.cpp index 8e2838ee..43d673c2 100644 --- a/api/logic/minecraft/Rule.cpp +++ b/api/logic/minecraft/Rule.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/Rule.h b/api/logic/minecraft/Rule.h index d5fd2492..dc1eee0b 100644 --- a/api/logic/minecraft/Rule.h +++ b/api/logic/minecraft/Rule.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/SkinUpload.cpp b/api/logic/minecraft/SkinUpload.cpp index 1d1e38f3..bd246139 100644 --- a/api/logic/minecraft/SkinUpload.cpp +++ b/api/logic/minecraft/SkinUpload.cpp @@ -6,9 +6,9 @@ QByteArray getModelString(SkinUpload::Model model) { switch (model) { case SkinUpload::STEVE: - return "steve"; + return ""; case SkinUpload::ALEX: - return "alex"; + return "slim"; default: qDebug() << "Unknown skin type!"; return ""; diff --git a/api/logic/minecraft/SkinUpload.h b/api/logic/minecraft/SkinUpload.h index 86944b82..5b331fa9 100644 --- a/api/logic/minecraft/SkinUpload.h +++ b/api/logic/minecraft/SkinUpload.h @@ -9,9 +9,9 @@ typedef std::shared_ptr<class SkinUpload> SkinUploadPtr; -class MULTIMC_LOGIC_EXPORT SkinUpload : public Task\ +class MULTIMC_LOGIC_EXPORT SkinUpload : public Task { -Q_OBJECT + Q_OBJECT public: enum Model { diff --git a/api/logic/minecraft/VersionFile.cpp b/api/logic/minecraft/VersionFile.cpp index 85989549..9f485c55 100644 --- a/api/logic/minecraft/VersionFile.cpp +++ b/api/logic/minecraft/VersionFile.cpp @@ -5,7 +5,7 @@ #include "minecraft/VersionFile.h" #include "minecraft/Library.h" -#include "minecraft/MinecraftProfile.h" +#include "minecraft/ComponentList.h" #include "ParseUtils.h" #include <Version.h> @@ -15,7 +15,7 @@ static bool isMinecraftVersion(const QString &uid) return uid == "net.minecraft"; } -void VersionFile::applyTo(MinecraftProfile *profile) +void VersionFile::applyTo(LaunchProfile *profile) { // Only real Minecraft can set those. Don't let anything override them. if (isMinecraftVersion(uid)) diff --git a/api/logic/minecraft/VersionFile.h b/api/logic/minecraft/VersionFile.h index e3fb46ed..5aea7a7a 100644 --- a/api/logic/minecraft/VersionFile.h +++ b/api/logic/minecraft/VersionFile.h @@ -10,27 +10,26 @@ #include "minecraft/Rule.h" #include "ProblemProvider.h" #include "Library.h" +#include <meta/JsonFormat.h> -class MinecraftProfile; +class ComponentList; class VersionFile; +class LaunchProfile; struct MojangDownloadInfo; struct MojangAssetIndexInfo; -typedef std::shared_ptr<VersionFile> VersionFilePtr; +using VersionFilePtr = std::shared_ptr<VersionFile>; class VersionFile : public ProblemContainer { friend class MojangVersionFormat; friend class OneSixVersionFormat; public: /* methods */ - void applyTo(MinecraftProfile *profile); + void applyTo(LaunchProfile* profile); public: /* data */ /// MultiMC: order hint for this version file if no explicit order is set int order = 0; - /// MultiMC: filename of the file this was loaded from - // QString filename; - /// MultiMC: human readable name of this package QString name; @@ -76,7 +75,7 @@ public: /* data */ /// Mojang: list of libraries to add to the version QList<LibraryPtr> libraries; - // The main jar (Minecraft version library, normally) + /// The main jar (Minecraft version library, normally) LibraryPtr mainJar; /// MultiMC: list of attached traits of this version file - used to enable features @@ -88,6 +87,21 @@ public: /* data */ /// MultiMC: list of mods added to this version QList<LibraryPtr> mods; + /** + * MultiMC: set of packages this depends on + * NOTE: this is shared with the meta format!!! + */ + Meta::RequireSet requires; + + /** + * MultiMC: set of packages this conflicts with + * NOTE: this is shared with the meta format!!! + */ + Meta::RequireSet conflicts; + + /// is volatile -- may be removed as soon as it is no longer needed by something else + bool m_volatile = false; + public: // Mojang: DEPRECATED list of 'downloads' - client jar, server jar, windows server exe, maybe more. QMap <QString, std::shared_ptr<MojangDownloadInfo>> mojangDownloads; diff --git a/api/logic/minecraft/World.cpp b/api/logic/minecraft/World.cpp index 81f52ff5..68c0a5cc 100644 --- a/api/logic/minecraft/World.cpp +++ b/api/logic/minecraft/World.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2017 MultiMC Contributors +/* Copyright 2015-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -146,7 +146,7 @@ void World::readFromZip(const QFileInfo &file) { return; } - auto location = MMCZip::findFileInZip(&zip, "level.dat"); + auto location = MMCZip::findFolderOfFileInZip(&zip, "level.dat"); is_valid = !location.isEmpty(); if (!is_valid) { diff --git a/api/logic/minecraft/World.h b/api/logic/minecraft/World.h index a87cb8ec..7087bf48 100644 --- a/api/logic/minecraft/World.h +++ b/api/logic/minecraft/World.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2017 MultiMC Contributors +/* Copyright 2015-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/WorldList.cpp b/api/logic/minecraft/WorldList.cpp index 47ce2379..4278c2f7 100644 --- a/api/logic/minecraft/WorldList.cpp +++ b/api/logic/minecraft/WorldList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2017 MultiMC Contributors +/* Copyright 2015-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/WorldList.h b/api/logic/minecraft/WorldList.h index 700fe2fd..811393cd 100644 --- a/api/logic/minecraft/WorldList.h +++ b/api/logic/minecraft/WorldList.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2017 MultiMC Contributors +/* Copyright 2015-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/auth/MojangAccount.cpp b/api/logic/minecraft/auth/MojangAccount.cpp index f4c628a6..edad4344 100644 --- a/api/logic/minecraft/auth/MojangAccount.cpp +++ b/api/logic/minecraft/auth/MojangAccount.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Authors: Orochimarufan <orochimarufan.x3@gmail.com> * @@ -170,8 +170,7 @@ AccountStatus MojangAccount::accountStatus() const return Verified; } -std::shared_ptr<YggdrasilTask> MojangAccount::login(AuthSessionPtr session, - QString password) +std::shared_ptr<YggdrasilTask> MojangAccount::login(AuthSessionPtr session, QString password) { Q_ASSERT(m_currentTask.get() == nullptr); @@ -186,18 +185,28 @@ std::shared_ptr<YggdrasilTask> MojangAccount::login(AuthSessionPtr session, return nullptr; } - if (password.isEmpty()) + if(accountStatus() == Verified && !session->wants_online) { - m_currentTask.reset(new RefreshTask(this)); + session->status = AuthSession::PlayableOffline; + session->auth_server_online = false; + fillSession(session); + return nullptr; } else { - m_currentTask.reset(new AuthenticateTask(this, password)); - } - m_currentTask->assignSession(session); + if (password.isEmpty()) + { + m_currentTask.reset(new RefreshTask(this)); + } + else + { + m_currentTask.reset(new AuthenticateTask(this, password)); + } + m_currentTask->assignSession(session); - connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); - connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); + connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); + connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); + } return m_currentTask; } diff --git a/api/logic/minecraft/auth/MojangAccount.h b/api/logic/minecraft/auth/MojangAccount.h index c7f15723..b2bbc357 100644 --- a/api/logic/minecraft/auth/MojangAccount.h +++ b/api/logic/minecraft/auth/MojangAccount.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/auth/MojangAccountList.cpp b/api/logic/minecraft/auth/MojangAccountList.cpp index bcda1703..21ae188a 100644 --- a/api/logic/minecraft/auth/MojangAccountList.cpp +++ b/api/logic/minecraft/auth/MojangAccountList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,34 +53,47 @@ const MojangAccountPtr MojangAccountList::at(int i) const void MojangAccountList::addAccount(const MojangAccountPtr account) { - beginResetModel(); + int row = m_accounts.count(); + beginInsertRows(QModelIndex(), row, row); connect(account.get(), SIGNAL(changed()), SLOT(accountChanged())); m_accounts.append(account); - endResetModel(); + endInsertRows(); onListChanged(); } void MojangAccountList::removeAccount(const QString &username) { - beginResetModel(); + int idx = 0; for (auto account : m_accounts) { if (account->username() == username) { + beginRemoveRows(QModelIndex(), idx, idx); m_accounts.removeOne(account); + endRemoveRows(); return; } + idx++; } - endResetModel(); onListChanged(); } void MojangAccountList::removeAccount(QModelIndex index) { - beginResetModel(); - m_accounts.removeAt(index.row()); - endResetModel(); - onListChanged(); + int row = index.row(); + if(index.isValid() && row >= 0 && row < m_accounts.size()) + { + auto & account = m_accounts[row]; + if(account == m_activeAccount) + { + m_activeAccount = nullptr; + onActiveChanged(); + } + beginRemoveRows(QModelIndex(), row, row); + m_accounts.removeAt(index.row()); + endRemoveRows(); + onListChanged(); + } } MojangAccountPtr MojangAccountList::activeAccount() const @@ -90,21 +103,49 @@ MojangAccountPtr MojangAccountList::activeAccount() const void MojangAccountList::setActiveAccount(const QString &username) { - beginResetModel(); - if (username.isEmpty()) + if (username.isEmpty() && m_activeAccount) { + int idx = 0; + auto prevActiveAcc = m_activeAccount; m_activeAccount = nullptr; + for (MojangAccountPtr account : m_accounts) + { + if (account == prevActiveAcc) + { + emit dataChanged(index(idx), index(idx)); + } + idx ++; + } + onActiveChanged(); } else { + auto currentActiveAccount = m_activeAccount; + int currentActiveAccountIdx = -1; + auto newActiveAccount = m_activeAccount; + int newActiveAccountIdx = -1; + int idx = 0; for (MojangAccountPtr account : m_accounts) { if (account->username() == username) - m_activeAccount = account; + { + newActiveAccount = account; + newActiveAccountIdx = idx; + } + if(currentActiveAccount == account) + { + currentActiveAccountIdx = idx; + } + idx++; + } + if(currentActiveAccount != newActiveAccount) + { + emit dataChanged(index(currentActiveAccountIdx), index(currentActiveAccountIdx)); + emit dataChanged(index(newActiveAccountIdx), index(newActiveAccountIdx)); + m_activeAccount = newActiveAccount; + onActiveChanged(); } } - endResetModel(); - onActiveChanged(); } void MojangAccountList::accountChanged() @@ -167,7 +208,7 @@ QVariant MojangAccountList::data(const QModelIndex &index, int role) const switch (index.column()) { case ActiveColumn: - return account == m_activeAccount; + return account == m_activeAccount ? Qt::Checked : Qt::Unchecked; } default: @@ -207,13 +248,13 @@ QVariant MojangAccountList::headerData(int section, Qt::Orientation orientation, } } -int MojangAccountList::rowCount(const QModelIndex &parent) const +int MojangAccountList::rowCount(const QModelIndex &) const { // Return count return count(); } -int MojangAccountList::columnCount(const QModelIndex &parent) const +int MojangAccountList::columnCount(const QModelIndex &) const { return 2; } diff --git a/api/logic/minecraft/auth/MojangAccountList.h b/api/logic/minecraft/auth/MojangAccountList.h index 0a7a10d9..dd6c07e8 100644 --- a/api/logic/minecraft/auth/MojangAccountList.h +++ b/api/logic/minecraft/auth/MojangAccountList.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/auth/YggdrasilTask.cpp b/api/logic/minecraft/auth/YggdrasilTask.cpp index 425da76a..f17d803f 100644 --- a/api/logic/minecraft/auth/YggdrasilTask.cpp +++ b/api/logic/minecraft/auth/YggdrasilTask.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -130,6 +130,7 @@ void YggdrasilTask::processReply() "</ul>")); return; // used for invalid credentials and similar errors. Fall through. + case QNetworkReply::ContentAccessDenied: case QNetworkReply::ContentOperationNotPermittedError: break; default: diff --git a/api/logic/minecraft/auth/YggdrasilTask.h b/api/logic/minecraft/auth/YggdrasilTask.h index f21c2733..b165d3e9 100644 --- a/api/logic/minecraft/auth/YggdrasilTask.h +++ b/api/logic/minecraft/auth/YggdrasilTask.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/auth/flows/AuthenticateTask.cpp b/api/logic/minecraft/auth/flows/AuthenticateTask.cpp index ad892073..ff4e7d47 100644 --- a/api/logic/minecraft/auth/flows/AuthenticateTask.cpp +++ b/api/logic/minecraft/auth/flows/AuthenticateTask.cpp @@ -1,5 +1,5 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/auth/flows/AuthenticateTask.h b/api/logic/minecraft/auth/flows/AuthenticateTask.h index a15c8a9e..ab7a6e64 100644 --- a/api/logic/minecraft/auth/flows/AuthenticateTask.h +++ b/api/logic/minecraft/auth/flows/AuthenticateTask.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/auth/flows/RefreshTask.cpp b/api/logic/minecraft/auth/flows/RefreshTask.cpp index 76738900..73050907 100644 --- a/api/logic/minecraft/auth/flows/RefreshTask.cpp +++ b/api/logic/minecraft/auth/flows/RefreshTask.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/auth/flows/RefreshTask.h b/api/logic/minecraft/auth/flows/RefreshTask.h index 3c99101e..a97b54e6 100644 --- a/api/logic/minecraft/auth/flows/RefreshTask.h +++ b/api/logic/minecraft/auth/flows/RefreshTask.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/auth/flows/ValidateTask.cpp b/api/logic/minecraft/auth/flows/ValidateTask.cpp index f82e6542..b6d6c823 100644 --- a/api/logic/minecraft/auth/flows/ValidateTask.cpp +++ b/api/logic/minecraft/auth/flows/ValidateTask.cpp @@ -1,5 +1,5 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/auth/flows/ValidateTask.h b/api/logic/minecraft/auth/flows/ValidateTask.h index a93bc76e..77de24c7 100644 --- a/api/logic/minecraft/auth/flows/ValidateTask.h +++ b/api/logic/minecraft/auth/flows/ValidateTask.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/flame/FileResolvingTask.cpp b/api/logic/minecraft/flame/FileResolvingTask.cpp index d55beb63..efc73621 100644 --- a/api/logic/minecraft/flame/FileResolvingTask.cpp +++ b/api/logic/minecraft/flame/FileResolvingTask.cpp @@ -48,7 +48,51 @@ void Flame::FileResolvingTask::netJobFinished() continue; } out.fileName = Json::requireString(obj, "FileNameOnDisk"); - out.url = Json::requireString(obj, "DownloadURL"); + QString rawUrl = Json::requireString(obj, "DownloadURL"); + out.url = QUrl(rawUrl, QUrl::TolerantMode); + if(!out.url.isValid()) + { + throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl)); + } + // This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience + // It is also optional + QJsonObject projObj = Json::ensureObject(obj, "_Project", {}); + if(!projObj.isEmpty()) + { + QString strType = Json::ensureString(projObj, "PackageType", "mod").toLower(); + if(strType == "singlefile") + { + out.type = File::Type::SingleFile; + } + else if(strType == "ctoc") + { + out.type = File::Type::Ctoc; + } + else if(strType == "cmod2") + { + out.type = File::Type::Cmod2; + } + else if(strType == "mod") + { + out.type = File::Type::Mod; + } + else if(strType == "folder") + { + out.type = File::Type::Folder; + } + else if(strType == "modpack") + { + out.type = File::Type::Modpack; + } + else + { + qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of unknown file type:" << strType; + out.type = File::Type::Unknown; + failed = true; + continue; + } + out.targetFolder = Json::ensureString(projObj, "Path", "mods"); + } out.resolved = true; } catch(JSONValidationError & e) diff --git a/api/logic/minecraft/flame/PackManifest.cpp b/api/logic/minecraft/flame/PackManifest.cpp index 62921493..6a9324fe 100644 --- a/api/logic/minecraft/flame/PackManifest.cpp +++ b/api/logic/minecraft/flame/PackManifest.cpp @@ -5,7 +5,6 @@ static void loadFileV1(Flame::File & f, QJsonObject & file) { f.projectId = Json::requireInteger(file, "projectID"); f.fileId = Json::requireInteger(file, "fileID"); - // FIXME: what does this mean? f.required = Json::ensureBoolean(file, QString("required"), true); } diff --git a/api/logic/minecraft/flame/PackManifest.h b/api/logic/minecraft/flame/PackManifest.h index ae91bffb..1a5254a8 100644 --- a/api/logic/minecraft/flame/PackManifest.h +++ b/api/logic/minecraft/flame/PackManifest.h @@ -2,6 +2,7 @@ #include <QString> #include <QVector> +#include <QUrl> namespace Flame { @@ -9,12 +10,24 @@ struct File { int projectId = 0; int fileId = 0; + // NOTE: the opposite to 'optional'. This is at the time of writing unused. bool required = true; // our bool resolved = false; QString fileName; - QString url; + QUrl url; + QString targetFolder = QLatin1Literal("mods"); + enum class Type + { + Unknown, + Folder, + Ctoc, + SingleFile, + Cmod2, + Modpack, + Mod + } type = Type::Mod; }; struct Modloader diff --git a/api/logic/minecraft/forge/ForgeXzDownload.cpp b/api/logic/minecraft/forge/ForgeXzDownload.cpp index 593aa24f..a05d0f8d 100644 --- a/api/logic/minecraft/forge/ForgeXzDownload.cpp +++ b/api/logic/minecraft/forge/ForgeXzDownload.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/forge/ForgeXzDownload.h b/api/logic/minecraft/forge/ForgeXzDownload.h index ef23809b..cee402ef 100644 --- a/api/logic/minecraft/forge/ForgeXzDownload.h +++ b/api/logic/minecraft/forge/ForgeXzDownload.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/ftb/FTBInstanceProvider.cpp b/api/logic/minecraft/ftb/FTBInstanceProvider.cpp deleted file mode 100644 index fe23a84e..00000000 --- a/api/logic/minecraft/ftb/FTBInstanceProvider.cpp +++ /dev/null @@ -1,262 +0,0 @@ -#include "FTBInstanceProvider.h" - -#include <QDir> -#include <QDebug> -#include <QXmlStreamReader> -#include <QRegularExpression> - -#include <settings/INISettingsObject.h> -#include <FileSystem.h> - -#include "Env.h" - -#include "LegacyFTBInstance.h" -#include "OneSixFTBInstance.h" - -inline uint qHash(FTBRecord record) -{ - return qHash(record.instanceDir); -} - -FTBInstanceProvider::FTBInstanceProvider(SettingsObjectPtr settings) - : BaseInstanceProvider(settings) -{ - // nil -} - -QList<InstanceId> FTBInstanceProvider::discoverInstances() -{ - // nothing to load when we don't have - if (m_globalSettings->get("TrackFTBInstances").toBool() != true) - { - return {}; - } - m_records.clear(); - discoverFTBEntries(); - return m_records.keys(); -} - -InstancePtr FTBInstanceProvider::loadInstance(const InstanceId& id) -{ - // process the records we acquired. - auto iter = m_records.find(id); - if(iter == m_records.end()) - { - qWarning() << "Cannot load instance" << id << "without a record"; - return nullptr; - } - auto & record = m_records[id]; - qDebug() << "Loading FTB instance from " << record.instanceDir; - QString iconKey = record.iconKey; - auto icons = ENV.icons(); - if(icons) - { - icons->addIcon(iconKey, iconKey, FS::PathCombine(record.templateDir, record.logo), IconType::Transient); - } - auto settingsFilePath = FS::PathCombine(record.instanceDir, "instance.cfg"); - qDebug() << "ICON get!"; - - if (QFileInfo(settingsFilePath).exists()) - { - auto instPtr = loadInstance(record); - if (!instPtr) - { - qWarning() << "Couldn't load instance config:" << settingsFilePath; - if(!QFile::remove(settingsFilePath)) - { - qWarning() << "Couldn't remove broken instance config!"; - return nullptr; - } - // failed to load, but removed the poisonous file - } - else - { - return InstancePtr(instPtr); - } - } - auto instPtr = createInstance(record); - if (!instPtr) - { - qWarning() << "Couldn't create FTB instance!"; - return nullptr; - } - return InstancePtr(instPtr); -} - -void FTBInstanceProvider::discoverFTBEntries() -{ - QDir dir = QDir(m_globalSettings->get("FTBLauncherLocal").toString()); - QDir dataDir = QDir(m_globalSettings->get("FTBRoot").toString()); - if (!dataDir.exists()) - { - qDebug() << "The FTB directory specified does not exist. Please check your settings"; - return; - } - else if (!dir.exists()) - { - qDebug() << "The FTB launcher data directory specified does not exist. Please check " - "your settings"; - return; - } - dir.cd("ModPacks"); - auto allFiles = dir.entryList(QDir::Readable | QDir::Files, QDir::Name); - for (auto filename : allFiles) - { - if (!filename.endsWith(".xml")) - continue; - auto fpath = dir.absoluteFilePath(filename); - QFile f(fpath); - qDebug() << "Discovering FTB instances -- " << fpath; - if (!f.open(QFile::ReadOnly)) - continue; - - // read the FTB packs XML. - QXmlStreamReader reader(&f); - while (!reader.atEnd()) - { - switch (reader.readNext()) - { - case QXmlStreamReader::StartElement: - { - if (reader.name() == "modpack") - { - QXmlStreamAttributes attrs = reader.attributes(); - FTBRecord record; - record.dirName = attrs.value("dir").toString(); - record.instanceDir = dataDir.absoluteFilePath(record.dirName); - record.templateDir = dir.absoluteFilePath(record.dirName); - QDir test(record.instanceDir); - qDebug() << dataDir.absolutePath() << record.instanceDir << record.dirName; - if (!test.exists()) - continue; - record.name = attrs.value("name").toString(); - record.logo = attrs.value("logo").toString(); - QString logo = record.logo; - record.iconKey = logo.remove(QRegularExpression("\\..*")); - auto customVersions = attrs.value("customMCVersions"); - if (!customVersions.isNull()) - { - QMap<QString, QString> versionMatcher; - QString customVersionsStr = customVersions.toString(); - QStringList list = customVersionsStr.split(';'); - for (auto item : list) - { - auto segment = item.split('^'); - if (segment.size() != 2) - { - qCritical() << "FTB: Segment of size < 2 in " - << customVersionsStr; - continue; - } - versionMatcher[segment[0]] = segment[1]; - } - auto actualVersion = attrs.value("version").toString(); - if (versionMatcher.contains(actualVersion)) - { - record.mcVersion = versionMatcher[actualVersion]; - } - else - { - record.mcVersion = attrs.value("mcVersion").toString(); - } - } - else - { - record.mcVersion = attrs.value("mcVersion").toString(); - } - record.description = attrs.value("description").toString(); - auto id = "FTB/" + record.dirName; - m_records[id] = record; - } - break; - } - case QXmlStreamReader::EndElement: - break; - case QXmlStreamReader::Characters: - break; - default: - break; - } - } - f.close(); - } -} - -InstancePtr FTBInstanceProvider::loadInstance(const FTBRecord & record) const -{ - InstancePtr inst; - - auto m_settings = std::make_shared<INISettingsObject>(FS::PathCombine(record.instanceDir, "instance.cfg")); - m_settings->registerSetting("InstanceType", "Legacy"); - - qDebug() << "Loading existing " << record.name; - - QString inst_type = m_settings->get("InstanceType").toString(); - if (inst_type == "LegacyFTB") - { - inst.reset(new LegacyFTBInstance(m_globalSettings, m_settings, record.instanceDir)); - } - else if (inst_type == "OneSixFTB") - { - inst.reset(new OneSixFTBInstance(m_globalSettings, m_settings, record.instanceDir)); - } - else - { - return nullptr; - } - qDebug() << "Construction " << record.instanceDir; - - SettingsObject::Lock lock(inst->settings()); - inst->init(); - qDebug() << "Init " << record.instanceDir; - inst->setGroupInitial("FTB"); - /** - * FIXME: this does not respect the user's preferences. BUT, it would work nicely with the planned pack support - * -> instead of changing the user values, change pack values (defaults you can look at and revert to) - */ - /* - inst->setName(record.name); - inst->setIconKey(record.iconKey); - inst->setNotes(record.description); - */ - if (inst->intendedVersionId() != record.mcVersion) - { - inst->setIntendedVersionId(record.mcVersion); - } - qDebug() << "Loaded instance " << inst->name() << " from " << inst->instanceRoot(); - return inst; -} - -InstancePtr FTBInstanceProvider::createInstance(const FTBRecord & record) const -{ - QDir rootDir(record.instanceDir); - - InstancePtr inst; - - qDebug() << "Converting " << record.name << " as new."; - - if (!rootDir.exists() && !rootDir.mkpath(".")) - { - qCritical() << "Can't create instance folder" << record.instanceDir; - return nullptr; - } - - auto m_settings = std::make_shared<INISettingsObject>(FS::PathCombine(record.instanceDir, "instance.cfg")); - m_settings->registerSetting("InstanceType", "Legacy"); - - // all legacy versions are built in. therefore we can do this even if we don't have ALL the versions Mojang has on their servers. - m_settings->set("InstanceType", "OneSixFTB"); - inst.reset(new OneSixFTBInstance(m_globalSettings, m_settings, record.instanceDir)); - - // initialize - { - SettingsObject::Lock lock(inst->settings()); - inst->setIntendedVersionId(record.mcVersion); - inst->init(); - inst->setGroupInitial("FTB"); - inst->setName(record.name); - inst->setIconKey(record.iconKey); - inst->setNotes(record.description); - } - return inst; -} diff --git a/api/logic/minecraft/ftb/FTBInstanceProvider.h b/api/logic/minecraft/ftb/FTBInstanceProvider.h deleted file mode 100644 index fb3ecb6c..00000000 --- a/api/logic/minecraft/ftb/FTBInstanceProvider.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include "BaseInstanceProvider.h" -#include <QMap> - -class QFileSystemWatcher; - -struct MULTIMC_LOGIC_EXPORT FTBRecord -{ - QString dirName; - QString name; - QString logo; - QString iconKey; - QString mcVersion; - QString description; - QString instanceDir; - QString templateDir; - bool operator==(const FTBRecord other) const - { - return instanceDir == other.instanceDir; - } -}; - -class MULTIMC_LOGIC_EXPORT FTBInstanceProvider : public BaseInstanceProvider -{ - Q_OBJECT - -public: - FTBInstanceProvider (SettingsObjectPtr settings); - -public: - QList<InstanceId> discoverInstances() override; - InstancePtr loadInstance(const InstanceId& id) override; - void loadGroupList() override {}; - void saveGroupList() override {}; - -private: /* methods */ - void discoverFTBEntries(); - InstancePtr createInstance(const FTBRecord & record) const; - InstancePtr loadInstance(const FTBRecord & record) const; - - -private: - QMap<InstanceId, FTBRecord> m_records; -}; diff --git a/api/logic/minecraft/ftb/FTBPlugin.cpp b/api/logic/minecraft/ftb/FTBPlugin.cpp deleted file mode 100644 index 541879a1..00000000 --- a/api/logic/minecraft/ftb/FTBPlugin.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include "FTBPlugin.h" -#include <Env.h> -#include "LegacyFTBInstance.h" -#include "OneSixFTBInstance.h" -#include <BaseInstance.h> -#include <InstanceList.h> -#include <settings/INISettingsObject.h> -#include <FileSystem.h> - -#include <QDebug> -#include <QRegularExpression> - -#ifdef Q_OS_WIN32 -#include <windows.h> -static const int APPDATA_BUFFER_SIZE = 1024; -#endif - -static QString getLocalCacheStorageLocation() -{ - QString ftbDefault; -#ifdef Q_OS_WIN32 - wchar_t buf[APPDATA_BUFFER_SIZE]; - if (GetEnvironmentVariableW(L"LOCALAPPDATA", buf, APPDATA_BUFFER_SIZE)) // local - { - ftbDefault = QDir(QString::fromWCharArray(buf)).absoluteFilePath("ftblauncher"); - } - else if (GetEnvironmentVariableW(L"APPDATA", buf, APPDATA_BUFFER_SIZE)) // roaming - { - ftbDefault = QDir(QString::fromWCharArray(buf)).absoluteFilePath("ftblauncher"); - } - else - { - qCritical() << "Your LOCALAPPDATA and APPDATA folders are missing!" - " If you are on windows, this means your system is broken."; - } -#elif defined(Q_OS_MAC) - ftbDefault = FS::PathCombine(QDir::homePath(), "Library/Application Support/ftblauncher"); -#else - ftbDefault = QDir::home().absoluteFilePath(".ftblauncher"); -#endif - return ftbDefault; -} - - -static QString getRoamingStorageLocation() -{ - QString ftbDefault; -#ifdef Q_OS_WIN32 - wchar_t buf[APPDATA_BUFFER_SIZE]; - QString cacheStorage; - if (GetEnvironmentVariableW(L"APPDATA", buf, APPDATA_BUFFER_SIZE)) - { - ftbDefault = QDir(QString::fromWCharArray(buf)).absoluteFilePath("ftblauncher"); - } - else - { - qCritical() << "Your APPDATA folder is missing! If you are on windows, this means your system is broken."; - } -#elif defined(Q_OS_MAC) - ftbDefault = FS::PathCombine(QDir::homePath(), "Library/Application Support/ftblauncher"); -#else - ftbDefault = QDir::home().absoluteFilePath(".ftblauncher"); -#endif - return ftbDefault; -} - -void FTBPlugin::initialize(SettingsObjectPtr globalSettings) -{ - // FTB - globalSettings->registerSetting("TrackFTBInstances", false); - QString ftbRoaming = getRoamingStorageLocation(); - QString ftbLocal = getLocalCacheStorageLocation(); - - globalSettings->registerSetting("FTBLauncherRoaming", ftbRoaming); - globalSettings->registerSetting("FTBLauncherLocal", ftbLocal); - qDebug() << "FTB Launcher paths:" << globalSettings->get("FTBLauncherRoaming").toString() - << "and" << globalSettings->get("FTBLauncherLocal").toString(); - - globalSettings->registerSetting("FTBRoot"); - if (globalSettings->get("FTBRoot").isNull()) - { - QString ftbRoot; - QFile f(QDir(globalSettings->get("FTBLauncherRoaming").toString()).absoluteFilePath("ftblaunch.cfg")); - qDebug() << "Attempting to read" << f.fileName(); - if (f.open(QFile::ReadOnly)) - { - const QString data = QString::fromLatin1(f.readAll()); - QRegularExpression exp("installPath=(.*)"); - ftbRoot = QDir::cleanPath(exp.match(data).captured(1)); -#ifdef Q_OS_WIN32 - if (!ftbRoot.isEmpty()) - { - if (ftbRoot.at(0).isLetter() && ftbRoot.size() > 1 && ftbRoot.at(1) == '/') - { - ftbRoot.remove(1, 1); - } - } -#endif - if (ftbRoot.isEmpty()) - { - qDebug() << "Failed to get FTB root path"; - } - else - { - qDebug() << "FTB is installed at" << ftbRoot; - globalSettings->set("FTBRoot", ftbRoot); - } - } - else - { - qWarning() << "Couldn't open" << f.fileName() << ":" << f.errorString(); - qWarning() << "This is perfectly normal if you don't have FTB installed"; - } - } -} diff --git a/api/logic/minecraft/ftb/FTBPlugin.h b/api/logic/minecraft/ftb/FTBPlugin.h deleted file mode 100644 index e1b56545..00000000 --- a/api/logic/minecraft/ftb/FTBPlugin.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include <BaseInstance.h> - -#include "multimc_logic_export.h" - -// Pseudo-plugin for FTB related things. Super derpy! -class MULTIMC_LOGIC_EXPORT FTBPlugin -{ -public: - static void initialize(SettingsObjectPtr globalSettings); -}; diff --git a/api/logic/minecraft/ftb/FTBProfileStrategy.cpp b/api/logic/minecraft/ftb/FTBProfileStrategy.cpp deleted file mode 100644 index 9fa4e6a1..00000000 --- a/api/logic/minecraft/ftb/FTBProfileStrategy.cpp +++ /dev/null @@ -1,129 +0,0 @@ -#include "FTBProfileStrategy.h" -#include "OneSixFTBInstance.h" - -#include <FileSystem.h> - -#include <QDir> -#include <QUuid> -#include <QJsonDocument> -#include <QJsonArray> - -FTBProfileStrategy::FTBProfileStrategy(OneSixFTBInstance* instance) : OneSixProfileStrategy(instance) -{ -} - -void FTBProfileStrategy::loadDefaultBuiltinPatches() -{ - // FIXME: this should be here, but it needs us to be able to deal with multiple libraries paths - // OneSixProfileStrategy::loadDefaultBuiltinPatches(); - auto mcVersion = m_instance->intendedVersionId(); - auto nativeInstance = dynamic_cast<OneSixFTBInstance *>(m_instance); - - ProfilePatchPtr minecraftPatch; - { - std::shared_ptr< VersionFile > file; - auto mcJson = m_instance->versionsPath().absoluteFilePath(mcVersion + "/" + mcVersion + ".json"); - // load up the base minecraft patch - if(QFile::exists(mcJson)) - { - file = ProfileUtils::parseJsonFile(QFileInfo(mcJson), false); - for(auto addLib: file->libraries) - { - addLib->setHint("local"); - addLib->setStoragePrefix(nativeInstance->librariesPath().absolutePath()); - } - } - else - { - file = std::make_shared<VersionFile>(); - file->addProblem(ProblemSeverity::Error, QObject::tr("Minecraft version is missing in the FTB data.")); - } - file->uid = "net.minecraft"; - file->name = QObject::tr("Minecraft (tracked)"); - if(file->version.isEmpty()) - { - file->version = mcVersion; - } - minecraftPatch = std::make_shared<ProfilePatch>(file); - minecraftPatch->setVanilla(true); - minecraftPatch->setOrder(-2); - } - profile->appendPatch(minecraftPatch); - - ProfilePatchPtr packPatch; - { - std::shared_ptr< VersionFile > file; - auto mcJson = m_instance->minecraftRoot() + "/pack.json"; - // load up the base minecraft patch, if it's there... - if(QFile::exists(mcJson)) - { - file = ProfileUtils::parseJsonFile(QFileInfo(mcJson), false); - // adapt the loaded file - the FTB patch file format is different than ours. - file->minecraftVersion.clear(); - file->mainJar = nullptr; - for(auto addLib: file->libraries) - { - addLib->setHint("local"); - addLib->setStoragePrefix(nativeInstance->librariesPath().absolutePath()); - } - } - else - { - file = std::make_shared<VersionFile>(); - file->addProblem(ProblemSeverity::Error, QObject::tr("Modpack version file is missing.")); - } - file->uid = "org.multimc.ftb.pack"; - file->name = QObject::tr("%1 (FTB pack)").arg(m_instance->name()); - if(file->version.isEmpty()) - { - file->version = QObject::tr("Unknown"); - QFile versionFile (FS::PathCombine(m_instance->instanceRoot(), "version")); - if(versionFile.exists()) - { - if(versionFile.open(QIODevice::ReadOnly)) - { - // FIXME: just guessing the encoding/charset here. - auto version = QString::fromUtf8(versionFile.readAll()); - file->version = version; - } - } - } - packPatch = std::make_shared<ProfilePatch>(file); - packPatch->setVanilla(true); - packPatch->setOrder(1); - } - profile->appendPatch(packPatch); -} - -void FTBProfileStrategy::load() -{ - profile->clearPatches(); - - loadDefaultBuiltinPatches(); - loadUserPatches(); -} - -bool FTBProfileStrategy::saveOrder(ProfileUtils::PatchOrder order) -{ - return false; -} - -bool FTBProfileStrategy::resetOrder() -{ - return false; -} - -bool FTBProfileStrategy::installJarMods(QStringList filepaths) -{ - return false; -} - -bool FTBProfileStrategy::customizePatch(ProfilePatchPtr patch) -{ - return false; -} - -bool FTBProfileStrategy::revertPatch(ProfilePatchPtr patch) -{ - return false; -} diff --git a/api/logic/minecraft/ftb/FTBProfileStrategy.h b/api/logic/minecraft/ftb/FTBProfileStrategy.h deleted file mode 100644 index 522af098..00000000 --- a/api/logic/minecraft/ftb/FTBProfileStrategy.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once -#include "minecraft/ProfileStrategy.h" -#include "minecraft/onesix/OneSixProfileStrategy.h" - -class OneSixFTBInstance; - -class FTBProfileStrategy : public OneSixProfileStrategy -{ -public: - FTBProfileStrategy(OneSixFTBInstance * instance); - virtual ~FTBProfileStrategy() {}; - virtual void load() override; - virtual bool resetOrder() override; - virtual bool saveOrder(ProfileUtils::PatchOrder order) override; - virtual bool installJarMods(QStringList filepaths) override; - virtual bool customizePatch (ProfilePatchPtr patch) override; - virtual bool revertPatch (ProfilePatchPtr patch) override; - -protected: - virtual void loadDefaultBuiltinPatches() override; -}; diff --git a/api/logic/minecraft/ftb/LegacyFTBInstance.cpp b/api/logic/minecraft/ftb/LegacyFTBInstance.cpp deleted file mode 100644 index 63412a33..00000000 --- a/api/logic/minecraft/ftb/LegacyFTBInstance.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "LegacyFTBInstance.h" -#include <settings/INISettingsObject.h> -#include <QDir> - -LegacyFTBInstance::LegacyFTBInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir) : - LegacyInstance(globalSettings, settings, rootDir) -{ -} - -QString LegacyFTBInstance::id() const -{ - return "FTB/" + BaseInstance::id(); -} - -void LegacyFTBInstance::copy(SettingsObjectPtr newSettings, const QDir& newDir) -{ - // set the target instance to be plain Legacy - newSettings->set("InstanceType", "Legacy"); -} - -QString LegacyFTBInstance::typeName() const -{ - return tr("Legacy FTB"); -} diff --git a/api/logic/minecraft/ftb/LegacyFTBInstance.h b/api/logic/minecraft/ftb/LegacyFTBInstance.h deleted file mode 100644 index 2f2e34a2..00000000 --- a/api/logic/minecraft/ftb/LegacyFTBInstance.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "minecraft/legacy/LegacyInstance.h" - -class LegacyFTBInstance : public LegacyInstance -{ - Q_OBJECT -public: - explicit LegacyFTBInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); - QString id() const override; - void copy(SettingsObjectPtr newSettings, const QDir &newDir) override; - QString typeName() const override; - bool canExport() const override - { - return false; - } -}; diff --git a/api/logic/minecraft/ftb/OneSixFTBInstance.cpp b/api/logic/minecraft/ftb/OneSixFTBInstance.cpp deleted file mode 100644 index edf31eb7..00000000 --- a/api/logic/minecraft/ftb/OneSixFTBInstance.cpp +++ /dev/null @@ -1,135 +0,0 @@ -#include "OneSixFTBInstance.h" -#include "FTBProfileStrategy.h" - -#include "minecraft/MinecraftProfile.h" -#include "minecraft/GradleSpecifier.h" -#include "tasks/SequentialTask.h" -#include <settings/INISettingsObject.h> -#include <FileSystem.h> - -#include <QJsonArray> - -OneSixFTBInstance::OneSixFTBInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir) : - OneSixInstance(globalSettings, settings, rootDir) -{ - m_globalSettings = globalSettings; -} - -void OneSixFTBInstance::copy(SettingsObjectPtr newSettings, const QDir &newDir) -{ - QStringList libraryNames; - // create patch file - { - qDebug()<< "Creating patch file for FTB instance..."; - QFile f(minecraftRoot() + "/pack.json"); - if (!f.open(QFile::ReadOnly)) - { - qCritical() << "Couldn't open" << f.fileName() << ":" << f.errorString(); - return; - } - QJsonObject root = QJsonDocument::fromJson(f.readAll()).object(); - QJsonArray libs = root.value("libraries").toArray(); - QJsonArray outLibs; - for (auto lib : libs) - { - QJsonObject libObj = lib.toObject(); - libObj.insert("MMC-hint", QString("local")); - libObj.insert("insert", QString("prepend")); - libraryNames.append(libObj.value("name").toString()); - outLibs.append(libObj); - } - root.remove("libraries"); - root.remove("id"); - - // HACK HACK HACK HACK - // A workaround for a problem in MultiMC, triggered by a historical problem in FTB, - // triggered by Mojang getting their library versions wrong in 1.7.10 - if(intendedVersionId() == "1.7.10") - { - auto insert = [&outLibs, &libraryNames](QString name) - { - QJsonObject libObj; - libObj.insert("insert", QString("replace")); - libObj.insert("name", name); - libraryNames.push_back(name); - outLibs.prepend(libObj); - }; - insert("com.google.guava:guava:16.0"); - insert("org.apache.commons:commons-lang3:3.2.1"); - } - root.insert("+libraries", outLibs); - root.insert("order", 1); - root.insert("fileId", QString("org.multimc.ftb.pack.json")); - root.insert("name", name()); - root.insert("mcVersion", intendedVersionId()); - root.insert("version", intendedVersionId()); - FS::ensureFilePathExists(newDir.absoluteFilePath("patches/ftb.json")); - QFile out(newDir.absoluteFilePath("patches/ftb.json")); - if (!out.open(QFile::WriteOnly | QFile::Truncate)) - { - qCritical() << "Couldn't open" << out.fileName() << ":" << out.errorString(); - return; - } - out.write(QJsonDocument(root).toJson()); - } - // copy libraries - { - qDebug() << "Copying FTB libraries"; - for (auto library : libraryNames) - { - GradleSpecifier lib(library); - const QString out = QDir::current().absoluteFilePath("libraries/" + lib.toPath()); - if (QFile::exists(out)) - { - continue; - } - if (!FS::ensureFilePathExists(out)) - { - qCritical() << "Couldn't create folder structure for" << out; - } - if (!QFile::copy(librariesPath().absoluteFilePath(lib.toPath()), out)) - { - qCritical() << "Couldn't copy" << QString(lib); - } - } - } - // now set the target instance to be plain OneSix - newSettings->set("InstanceType", "OneSix"); -} - -QString OneSixFTBInstance::id() const -{ - return "FTB/" + BaseInstance::id(); -} - -QDir OneSixFTBInstance::librariesPath() const -{ - return QDir(m_globalSettings->get("FTBRoot").toString() + "/libraries"); -} - -QDir OneSixFTBInstance::versionsPath() const -{ - return QDir(m_globalSettings->get("FTBRoot").toString() + "/versions"); -} - -bool OneSixFTBInstance::providesVersionFile() const -{ - return true; -} - -void OneSixFTBInstance::createProfile() -{ - m_profile.reset(new MinecraftProfile(new FTBProfileStrategy(this))); -} - -shared_qobject_ptr<Task> OneSixFTBInstance::createUpdateTask() -{ - return OneSixInstance::createUpdateTask(); -} - -QString OneSixFTBInstance::typeName() const -{ - return tr("OneSix FTB"); -} - -#include "OneSixFTBInstance.moc" diff --git a/api/logic/minecraft/ftb/OneSixFTBInstance.h b/api/logic/minecraft/ftb/OneSixFTBInstance.h deleted file mode 100644 index 640f609c..00000000 --- a/api/logic/minecraft/ftb/OneSixFTBInstance.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include "minecraft/onesix/OneSixInstance.h" - -class OneSixFTBInstance : public OneSixInstance -{ - Q_OBJECT -public: - explicit OneSixFTBInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); - virtual ~OneSixFTBInstance(){}; - - void copy(SettingsObjectPtr newSettings, const QDir &newDir) override; - - virtual void createProfile() override; - - virtual shared_qobject_ptr<Task> createUpdateTask() override; - - virtual QString id() const override; - - QDir librariesPath() const override; - QDir versionsPath() const override; - bool providesVersionFile() const override; - virtual QString typeName() const override; - bool canExport() const override - { - return false; - } -private: - SettingsObjectPtr m_globalSettings; -}; diff --git a/api/logic/minecraft/launch/ClaimAccount.h b/api/logic/minecraft/launch/ClaimAccount.h index 53f3cee9..de9007d1 100644 --- a/api/logic/minecraft/launch/ClaimAccount.h +++ b/api/logic/minecraft/launch/ClaimAccount.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/launch/CreateServerResourcePacksFolder.h b/api/logic/minecraft/launch/CreateServerResourcePacksFolder.h index 24daa81e..92026ecb 100644 --- a/api/logic/minecraft/launch/CreateServerResourcePacksFolder.h +++ b/api/logic/minecraft/launch/CreateServerResourcePacksFolder.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/launch/DirectJavaLaunch.cpp b/api/logic/minecraft/launch/DirectJavaLaunch.cpp index 07dbb86c..4ccc5c3c 100644 --- a/api/logic/minecraft/launch/DirectJavaLaunch.cpp +++ b/api/logic/minecraft/launch/DirectJavaLaunch.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/launch/DirectJavaLaunch.h b/api/logic/minecraft/launch/DirectJavaLaunch.h index 585b5c74..19087b50 100644 --- a/api/logic/minecraft/launch/DirectJavaLaunch.h +++ b/api/logic/minecraft/launch/DirectJavaLaunch.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/launch/ExtractNatives.cpp b/api/logic/minecraft/launch/ExtractNatives.cpp index f30dea89..7ddde374 100644 --- a/api/logic/minecraft/launch/ExtractNatives.cpp +++ b/api/logic/minecraft/launch/ExtractNatives.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/launch/ExtractNatives.h b/api/logic/minecraft/launch/ExtractNatives.h index 0ec021a9..6e1e7cd4 100644 --- a/api/logic/minecraft/launch/ExtractNatives.h +++ b/api/logic/minecraft/launch/ExtractNatives.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/launch/LauncherPartLaunch.cpp b/api/logic/minecraft/launch/LauncherPartLaunch.cpp index b641968c..1fe9c323 100644 --- a/api/logic/minecraft/launch/LauncherPartLaunch.cpp +++ b/api/logic/minecraft/launch/LauncherPartLaunch.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,32 @@ LauncherPartLaunch::LauncherPartLaunch(LaunchTask *parent) : LaunchStep(parent) connect(&m_process, &LoggedProcess::stateChanged, this, &LauncherPartLaunch::on_state); } +#ifdef Q_OS_WIN +// returns 8.3 file format from long path +#include <windows.h> +QString shortPathName(const QString & file) +{ + auto input = file.toStdWString(); + std::wstring output; + long length = GetShortPathNameW(input.c_str(), NULL, 0); + // NOTE: this resizing might seem weird... + // when GetShortPathNameW fails, it returns length including null character + // when it succeeds, it returns length excluding null character + // See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa364989(v=vs.85).aspx + output.resize(length); + GetShortPathNameW(input.c_str(),(LPWSTR)output.c_str(),length); + output.resize(length-1); + QString ret = QString::fromStdWString(output); + return ret; +} +#endif + +// if the string survives roundtrip through local 8bit encoding... +bool fitsInLocal8bit(const QString & string) +{ + return string == QString::fromLocal8Bit(string.toLocal8Bit()); +} + void LauncherPartLaunch::executeTask() { auto instance = m_parent->instance(); @@ -45,7 +71,44 @@ void LauncherPartLaunch::executeTask() // make detachable - this will keep the process running even if the object is destroyed m_process.setDetachable(true); - args << "-jar" << FS::PathCombine(ENV.getJarsPath(), "NewLaunch.jar"); + auto classPath = minecraftInstance->getClassPath(); + classPath.prepend(FS::PathCombine(ENV.getJarsPath(), "NewLaunch.jar")); + + auto natPath = minecraftInstance->getNativePath(); +#ifdef Q_OS_WIN + if (!fitsInLocal8bit(natPath)) + { + args << "-Djava.library.path=" + shortPathName(natPath); + } + else + { + args << "-Djava.library.path=" + natPath; + } +#else + args << "-Djava.library.path=" + natPath; +#endif + + args << "-cp"; +#ifdef Q_OS_WIN + QStringList processed; + for(auto & item: classPath) + { + if (!fitsInLocal8bit(item)) + { + processed << shortPathName(item); + } + else + { + processed << item; + } + } + args << processed.join(';'); +#else + args << classPath.join(':'); +#endif + args << "org.multimc.EntryPoint"; + + qDebug() << args.join(' '); QString wrapperCommandStr = instance->getWrapperCommand().trimmed(); if(!wrapperCommandStr.isEmpty()) diff --git a/api/logic/minecraft/launch/LauncherPartLaunch.h b/api/logic/minecraft/launch/LauncherPartLaunch.h index 01c73895..d384c2d1 100644 --- a/api/logic/minecraft/launch/LauncherPartLaunch.h +++ b/api/logic/minecraft/launch/LauncherPartLaunch.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/launch/ModMinecraftJar.cpp b/api/logic/minecraft/launch/ModMinecraftJar.cpp index dad41a98..34825b45 100644 --- a/api/logic/minecraft/launch/ModMinecraftJar.cpp +++ b/api/logic/minecraft/launch/ModMinecraftJar.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,31 +14,69 @@ */ #include "ModMinecraftJar.h" -#include <launch/LaunchTask.h> -#include <QStandardPaths> +#include "launch/LaunchTask.h" +#include "MMCZip.h" +#include "minecraft/OpSys.h" +#include "FileSystem.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/ComponentList.h" void ModMinecraftJar::executeTask() { - m_jarModTask = m_parent->instance()->createJarModdingTask(); - if(m_jarModTask) + auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance()); + + if(!m_inst->getJarMods().size()) { - connect(m_jarModTask.get(), SIGNAL(finished()), this, SLOT(jarModdingFinished())); - m_jarModTask->start(); + emitSucceeded(); return; } + // nuke obsolete stripped jar(s) if needed + if(!FS::ensureFolderPathExists(m_inst->binRoot())) + { + emitFailed(tr("Couldn't create the bin folder for Minecraft.jar")); + } + + auto finalJarPath = QDir(m_inst->binRoot()).absoluteFilePath("minecraft.jar"); + if(!removeJar()) + { + emitFailed(tr("Couldn't remove stale jar file: %1").arg(finalJarPath)); + } + + // create temporary modded jar, if needed + auto components = m_inst->getComponentList(); + auto profile = components->getProfile(); + auto jarMods = m_inst->getJarMods(); + if(jarMods.size()) + { + auto mainJar = profile->getMainJar(); + QStringList jars, temp1, temp2, temp3, temp4; + mainJar->getApplicableFiles(currentSystem, jars, temp1, temp2, temp3, m_inst->getLocalLibraryPath()); + auto sourceJarPath = jars[0]; + if(!MMCZip::createModdedJar(sourceJarPath, finalJarPath, jarMods)) + { + emitFailed(tr("Failed to create the custom Minecraft jar file.")); + return; + } + } emitSucceeded(); } -void ModMinecraftJar::jarModdingFinished() +void ModMinecraftJar::finalize() { - if(m_jarModTask->successful()) - { - emitSucceeded(); - } - else + removeJar(); +} + +bool ModMinecraftJar::removeJar() +{ + auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance()); + auto finalJarPath = QDir(m_inst->binRoot()).absoluteFilePath("minecraft.jar"); + QFile finalJar(finalJarPath); + if(finalJar.exists()) { - QString reason = tr("jar modding failed because: %1.\n\n").arg(m_jarModTask->failReason()); - emit logLine(reason, MessageLevel::Fatal); - emitFailed(reason); + if(!finalJar.remove()) + { + return false; + } } + return true; } diff --git a/api/logic/minecraft/launch/ModMinecraftJar.h b/api/logic/minecraft/launch/ModMinecraftJar.h index 1196c487..b9a2d35b 100644 --- a/api/logic/minecraft/launch/ModMinecraftJar.h +++ b/api/logic/minecraft/launch/ModMinecraftJar.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ #include <launch/LaunchStep.h> #include <memory> -// FIXME: temporary wrapper for existing task. class ModMinecraftJar: public LaunchStep { Q_OBJECT @@ -31,9 +30,7 @@ public: { return false; } -private slots: - void jarModdingFinished(); - + void finalize() override; private: - std::shared_ptr<Task> m_jarModTask; + bool removeJar(); }; diff --git a/api/logic/minecraft/launch/PrintInstanceInfo.cpp b/api/logic/minecraft/launch/PrintInstanceInfo.cpp index a9a87955..83bf584f 100644 --- a/api/logic/minecraft/launch/PrintInstanceInfo.cpp +++ b/api/logic/minecraft/launch/PrintInstanceInfo.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,13 +13,73 @@ * limitations under the License. */ +#include <fstream> +#include <string> + #include "PrintInstanceInfo.h" #include <launch/LaunchTask.h> void PrintInstanceInfo::executeTask() { - auto instance = m_parent->instance(); - auto lines = instance->verboseDescription(m_session); - logLines(lines, MessageLevel::MultiMC); - emitSucceeded(); + auto instance = m_parent->instance(); + auto lines = instance->verboseDescription(m_session); + +#ifdef Q_OS_LINUX + std::ifstream cpuin("/proc/cpuinfo"); + for (std::string line; std::getline(cpuin, line);) + { + if (strncmp(line.c_str(), "model name", 10) == 0) + { + QStringList clines = (QStringList() << QString::fromStdString(line.substr(13, std::string::npos))); + logLines(clines, MessageLevel::MultiMC); + break; + } + } + + char buff[512]; + int gpuline = -1; + int cline = 0; + FILE *fp = popen("lspci -k", "r"); + if (fp != NULL) + { + while (fgets(buff, 512, fp) != NULL) + { + std::string str(buff); + if (str.length() < 9) + continue; + if (str.substr(8, 3) == "VGA") + { + gpuline = cline; + QStringList glines = (QStringList() << QString::fromStdString(str.substr(35, std::string::npos))); + logLines(glines, MessageLevel::MultiMC); + } + if (gpuline > -1 && gpuline != cline) + { + if (cline - gpuline < 3) + { + QStringList alines = (QStringList() << QString::fromStdString(str.substr(1, std::string::npos))); + logLines(alines, MessageLevel::MultiMC); + } + } + cline++; + } + } + + FILE *fp2 = popen("glxinfo", "r"); + if (fp2 != NULL) + { + while (fgets(buff, 512, fp2) != NULL) + { + if (strncmp(buff, "OpenGL version string:", 22) == 0) + { + QStringList drlines = (QStringList() << QString::fromUtf8(buff)); + logLines(drlines, MessageLevel::MultiMC); + break; + } + } + } +#endif + + logLines(lines, MessageLevel::MultiMC); + emitSucceeded(); } diff --git a/api/logic/minecraft/launch/PrintInstanceInfo.h b/api/logic/minecraft/launch/PrintInstanceInfo.h index 6aed0865..61615ba1 100644 --- a/api/logic/minecraft/launch/PrintInstanceInfo.h +++ b/api/logic/minecraft/launch/PrintInstanceInfo.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/api/logic/minecraft/legacy/LegacyInstance.cpp b/api/logic/minecraft/legacy/LegacyInstance.cpp index 0987d56f..6e318458 100644 --- a/api/logic/minecraft/legacy/LegacyInstance.cpp +++ b/api/logic/minecraft/legacy/LegacyInstance.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,6 @@ #include "LegacyInstance.h" -#include "minecraft/legacy/LegacyUpdate.h" #include "minecraft/legacy/LegacyModList.h" #include "minecraft/ModList.h" #include "minecraft/WorldList.h" @@ -28,14 +27,12 @@ #include <FileSystem.h> LegacyInstance::LegacyInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir) - : MinecraftInstance(globalSettings, settings, rootDir) + : BaseInstance(globalSettings, settings, rootDir) { - m_lwjglFolderSetting = globalSettings->getSetting("LWJGLDir"); settings->registerSetting("NeedsRebuild", true); settings->registerSetting("ShouldUpdate", false); - settings->registerSetting("JarVersion", "Unknown"); - settings->registerSetting("LwjglVersion", "2.9.0"); - settings->registerSetting("IntendedJarVersion", ""); + settings->registerSetting("JarVersion", QString()); + settings->registerSetting("IntendedJarVersion", QString()); /* * custom base jar has no default. it is determined in code... see the accessor methods for *it @@ -68,178 +65,102 @@ QString LegacyInstance::customBaseJar() const return value; } -void LegacyInstance::setCustomBaseJar(QString val) -{ - if (val.isNull() || val.isEmpty() || val == defaultCustomBaseJar()) - m_settings->reset("CustomBaseJar"); - else - m_settings->set("CustomBaseJar", val); -} - -void LegacyInstance::setShouldUseCustomBaseJar(bool val) -{ - m_settings->set("UseCustomBaseJar", val); -} - bool LegacyInstance::shouldUseCustomBaseJar() const { return m_settings->get("UseCustomBaseJar").toBool(); } -shared_qobject_ptr<Task> LegacyInstance::createUpdateTask() +shared_qobject_ptr<Task> LegacyInstance::createUpdateTask(Net::Mode) { - // make sure the jar mods list is initialized by asking for it. - auto list = jarModList(); - // create an update task - return shared_qobject_ptr<Task>(new LegacyUpdate(this, this)); + return nullptr; } -std::shared_ptr<Task> LegacyInstance::createJarModdingTask() +/* +class LegacyJarModTask : public Task { - class JarModTask : public Task + //Q_OBJECT +public: + explicit LegacyJarModTask(std::shared_ptr<LegacyInstance> inst) : Task(nullptr), m_inst(inst) + { + } + virtual void executeTask() { - public: - explicit JarModTask(std::shared_ptr<LegacyInstance> inst) : Task(nullptr), m_inst(inst) + if (!m_inst->shouldRebuild()) { + emitSucceeded(); + return; } - virtual void executeTask() - { - if (!m_inst->shouldRebuild()) - { - emitSucceeded(); - return; - } - // Get the mod list - auto modList = m_inst->getJarMods(); + // Get the mod list + auto modList = m_inst->getJarMods(); - QFileInfo runnableJar(m_inst->runnableJar()); - QFileInfo baseJar(m_inst->baseJar()); - bool base_is_custom = m_inst->shouldUseCustomBaseJar(); + QFileInfo runnableJar(m_inst->runnableJar()); + QFileInfo baseJar(m_inst->baseJar()); + bool base_is_custom = m_inst->shouldUseCustomBaseJar(); - // Nothing to do if there are no jar mods to install, no backup and just the mc jar - if (base_is_custom) - { - // yes, this can happen if the instance only has the runnable jar and not the base jar - // it *could* be assumed that such an instance is vanilla, but that wouldn't be safe - // because that's not something mmc4 guarantees - if (runnableJar.isFile() && !baseJar.exists() && modList.empty()) - { - m_inst->setShouldRebuild(false); - emitSucceeded(); - return; - } - - setStatus(tr("Installing mods: Backing up minecraft.jar ...")); - if (!baseJar.exists() && !QFile::copy(runnableJar.filePath(), baseJar.filePath())) - { - emitFailed("It seems both the active and base jar are gone. A fresh base jar will " - "be used on next run."); - m_inst->setShouldRebuild(true); - m_inst->setShouldUpdate(true); - m_inst->setShouldUseCustomBaseJar(false); - return; - } - } - - if (!baseJar.exists()) + // Nothing to do if there are no jar mods to install, no backup and just the mc jar + if (base_is_custom) + { + // yes, this can happen if the instance only has the runnable jar and not the base jar + // it *could* be assumed that such an instance is vanilla, but that wouldn't be safe + // because that's not something mmc4 guarantees + if (runnableJar.isFile() && !baseJar.exists() && modList.empty()) { - emitFailed("The base jar " + baseJar.filePath() + " does not exist"); + m_inst->setShouldRebuild(false); + emitSucceeded(); return; } - if (runnableJar.exists() && !QFile::remove(runnableJar.filePath())) + setStatus(tr("Installing mods: Backing up minecraft.jar ...")); + if (!baseJar.exists() && !QFile::copy(runnableJar.filePath(), baseJar.filePath())) { - emitFailed("Failed to delete old minecraft.jar"); + emitFailed("It seems both the active and base jar are gone. A fresh base jar will " + "be used on next run."); + m_inst->setShouldRebuild(true); + m_inst->setShouldUpdate(true); + m_inst->setShouldUseCustomBaseJar(false); return; } + } - setStatus(tr("Installing mods: Opening minecraft.jar ...")); - - QString outputJarPath = runnableJar.filePath(); - QString inputJarPath = baseJar.filePath(); - - if(!MMCZip::createModdedJar(inputJarPath, outputJarPath, modList)) - { - emitFailed(tr("Failed to create the custom Minecraft jar file.")); - return; - } - m_inst->setShouldRebuild(false); - // inst->UpdateVersion(true); - emitSucceeded(); + if (!baseJar.exists()) + { + emitFailed("The base jar " + baseJar.filePath() + " does not exist"); return; - } - std::shared_ptr<LegacyInstance> m_inst; - }; - return std::make_shared<JarModTask>(std::dynamic_pointer_cast<LegacyInstance>(shared_from_this())); -} -QString LegacyInstance::createLaunchScript(AuthSessionPtr session) -{ - QString launchScript; - - // window size - QString windowParams; - if (settings()->get("LaunchMaximized").toBool()) - { - windowParams = "max"; - } - else - { - windowParams = QString("%1x%2").arg(settings()->get("MinecraftWinWidth").toInt()).arg(settings()->get("MinecraftWinHeight").toInt()); - } - - QString lwjgl = QDir(m_lwjglFolderSetting->get().toString() + "/" + lwjglVersion()).absolutePath(); - launchScript += "userName " + session->player_name + "\n"; - launchScript += "sessionId " + session->session + "\n"; - launchScript += "windowTitle " + windowTitle() + "\n"; - launchScript += "windowParams " + windowParams + "\n"; - launchScript += "cp bin/minecraft.jar\n"; - launchScript += "cp " + lwjgl + "/lwjgl.jar\n"; - launchScript += "cp " + lwjgl + "/lwjgl_util.jar\n"; - launchScript += "cp " + lwjgl + "/jinput.jar\n"; - launchScript += "natives " + lwjgl + "/natives\n"; - launchScript += "traits legacyLaunch\n"; - launchScript += "launcher onesix\n"; - return launchScript; -} + if (runnableJar.exists() && !QFile::remove(runnableJar.filePath())) + { + emitFailed("Failed to delete old minecraft.jar"); + return; + } -std::shared_ptr<LaunchStep> LegacyInstance::createMainLaunchStep(LaunchTask * parent, AuthSessionPtr session) -{ - auto step = std::make_shared<LauncherPartLaunch>(parent); - step->setWorkingDirectory(minecraftRoot()); - step->setAuthSession(session); - return step; -} + setStatus(tr("Installing mods: Opening minecraft.jar ...")); -QString LegacyInstance::launchMethod() -{ - return "Legacy"; -} + QString outputJarPath = runnableJar.filePath(); + QString inputJarPath = baseJar.filePath(); -QStringList LegacyInstance::validLaunchMethods() -{ - return {"Legacy"}; -} + if(!MMCZip::createModdedJar(inputJarPath, outputJarPath, modList)) + { + emitFailed(tr("Failed to create the custom Minecraft jar file.")); + return; + } + m_inst->setShouldRebuild(false); + // inst->UpdateVersion(true); + emitSucceeded(); + return; -std::shared_ptr<ModList> LegacyInstance::coreModList() const -{ - if (!core_mod_list) - { - core_mod_list.reset(new ModList(coreModsDir())); } - core_mod_list->update(); - return core_mod_list; -} + std::shared_ptr<LegacyInstance> m_inst; +}; +*/ std::shared_ptr<LegacyModList> LegacyInstance::jarModList() const { if (!jar_mod_list) { auto list = new LegacyModList(jarModsDir(), modListFile()); - connect(list, SIGNAL(changed()), SLOT(jarModsChanged())); jar_mod_list.reset(list); } jar_mod_list->update(); @@ -251,39 +172,20 @@ QList<Mod> LegacyInstance::getJarMods() const return jarModList()->allMods(); } -void LegacyInstance::jarModsChanged() +QString LegacyInstance::minecraftRoot() const { - qDebug() << "Jar mods of instance " << name() << " have changed. Jar will be rebuilt."; - setShouldRebuild(true); -} + QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft")); + QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft")); -std::shared_ptr<ModList> LegacyInstance::loaderModList() const -{ - if (!loader_mod_list) - { - loader_mod_list.reset(new ModList(loaderModsDir())); - } - loader_mod_list->update(); - return loader_mod_list; + if (mcDir.exists() && !dotMCDir.exists()) + return mcDir.filePath(); + else + return dotMCDir.filePath(); } -std::shared_ptr<ModList> LegacyInstance::texturePackList() const +QString LegacyInstance::binRoot() const { - if (!texture_pack_list) - { - texture_pack_list.reset(new ModList(texturePacksDir())); - } - texture_pack_list->update(); - return texture_pack_list; -} - -std::shared_ptr<WorldList> LegacyInstance::worldList() const -{ - if (!m_world_list) - { - m_world_list.reset(new WorldList(savesDir())); - } - return m_world_list; + return FS::PathCombine(minecraftRoot(), "bin"); } QString LegacyInstance::jarModsDir() const @@ -340,38 +242,16 @@ bool LegacyInstance::shouldRebuild() const return m_settings->get("NeedsRebuild").toBool(); } -void LegacyInstance::setShouldRebuild(bool val) -{ - m_settings->set("NeedsRebuild", val); -} - QString LegacyInstance::currentVersionId() const { return m_settings->get("JarVersion").toString(); } -QString LegacyInstance::lwjglVersion() const -{ - return m_settings->get("LwjglVersion").toString(); -} - -void LegacyInstance::setLWJGLVersion(QString val) -{ - m_settings->set("LwjglVersion", val); -} - QString LegacyInstance::intendedVersionId() const { return m_settings->get("IntendedJarVersion").toString(); } -bool LegacyInstance::setIntendedVersionId(QString version) -{ - settings()->set("IntendedJarVersion", version); - setShouldUpdate(true); - return true; -} - bool LegacyInstance::shouldUpdate() const { QVariant var = settings()->get("ShouldUpdate"); @@ -382,11 +262,6 @@ bool LegacyInstance::shouldUpdate() const return true; } -void LegacyInstance::setShouldUpdate(bool val) -{ - settings()->set("ShouldUpdate", val); -} - QString LegacyInstance::defaultBaseJar() const { return "versions/" + intendedVersionId() + "/" + intendedVersionId() + ".jar"; @@ -397,9 +272,13 @@ QString LegacyInstance::defaultCustomBaseJar() const return FS::PathCombine(binRoot(), "mcbackup.jar"); } -QString LegacyInstance::lwjglFolder() const +std::shared_ptr<WorldList> LegacyInstance::worldList() const { - return m_lwjglFolderSetting->get().toString(); + if (!m_world_list) + { + m_world_list.reset(new WorldList(savesDir())); + } + return m_world_list; } QString LegacyInstance::typeName() const @@ -407,6 +286,11 @@ QString LegacyInstance::typeName() const return tr("Legacy"); } +QString LegacyInstance::getStatusbarDescription() +{ + return tr("Instance from previous versions."); +} + QStringList LegacyInstance::verboseDescription(AuthSessionPtr session) { QStringList out; @@ -422,48 +306,6 @@ QStringList LegacyInstance::verboseDescription(AuthSessionPtr session) out << ""; } - if(loaderModList()->size()) - { - out << "Mods:"; - for(auto & mod: loaderModList()->allMods()) - { - if(!mod.enabled()) - continue; - if(mod.type() == Mod::MOD_FOLDER) - continue; - // TODO: proper implementation would need to descend into folders. - - out << " " + mod.filename().completeBaseName(); - } - out << ""; - } - - if(coreModList()->size()) - { - out << "Core Mods:"; - for(auto & coremod: coreModList()->allMods()) - { - if(!coremod.enabled()) - continue; - if(coremod.type() == Mod::MOD_FOLDER) - continue; - // TODO: proper implementation would need to descend into folders. - - out << " " + coremod.filename().completeBaseName(); - } - out << ""; - } - - if(jarModList()->size()) - { - out << "Jar Mods:"; - for(auto & jarmod: jarModList()->allMods()) - { - out << " " + jarmod.name() + " (" + jarmod.filename().filePath() + ")"; - } - out << ""; - } - QString windowParams; if (settings()->get("LaunchMaximized").toBool()) { @@ -478,40 +320,3 @@ QStringList LegacyInstance::verboseDescription(AuthSessionPtr session) out << ""; return out; } - -QStringList LegacyInstance::getClassPath() const -{ - QString launchScript; - QString lwjgl = getNativePath(); - QStringList out = - { - "bin/minecraft.jar", - lwjgl + "/lwjgl.jar", - lwjgl + "/lwjgl_util.jar", - lwjgl + "/jinput.jar" - }; - return out; -} - -QString LegacyInstance::getMainClass() const -{ - return "net.minecraft.client.Minecraft"; -} - -QString LegacyInstance::getNativePath() const -{ - return QDir(m_lwjglFolderSetting->get().toString() + "/" + lwjglVersion()).absolutePath(); -} - -QStringList LegacyInstance::getNativeJars() const -{ - return {}; -} - -QStringList LegacyInstance::processMinecraftArgs(AuthSessionPtr account) const -{ - QStringList out; - out.append(account->player_name); - out.append(account->session); - return out; -} diff --git a/api/logic/minecraft/legacy/LegacyInstance.h b/api/logic/minecraft/legacy/LegacyInstance.h index 15d1383f..4a8bc436 100644 --- a/api/logic/minecraft/legacy/LegacyInstance.h +++ b/api/logic/minecraft/legacy/LegacyInstance.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,22 +15,27 @@ #pragma once -#include "minecraft/MinecraftInstance.h" +#include "BaseInstance.h" +#include "minecraft/Mod.h" #include "multimc_logic_export.h" class ModList; class LegacyModList; +class WorldList; class Task; - -class MULTIMC_LOGIC_EXPORT LegacyInstance : public MinecraftInstance +/* + * WHY: Legacy instances - from MultiMC 3 and 4 - are here only to provide a way to upgrade them to the current format. + */ +class MULTIMC_LOGIC_EXPORT LegacyInstance : public BaseInstance { Q_OBJECT public: explicit LegacyInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); - virtual void init() override {}; + virtual void init() override {} + virtual void saveNow() override {} /// Path to the instance's minecraft.jar QString runnableJar() const; @@ -38,20 +43,6 @@ public: //! Path to the instance's modlist file. QString modListFile() const; - /* - ////// Edit Instance Dialog stuff ////// - virtual QList<BasePage *> getPages(); - virtual QString dialogTitle(); - */ - - ////// Mod Lists ////// - std::shared_ptr<LegacyModList> jarModList() const ; - virtual QList< Mod > getJarMods() const override; - std::shared_ptr<ModList> coreModList() const; - std::shared_ptr<ModList> loaderModList() const; - std::shared_ptr<ModList> texturePackList() const override; - std::shared_ptr<WorldList> worldList() const override; - ////// Directories ////// QString libDir() const; QString savesDir() const; @@ -61,8 +52,10 @@ public: QString coreModsDir() const; QString resourceDir() const; virtual QString instanceConfigFolder() const override; + QString minecraftRoot() const; // Path to the instance's minecraft directory. + QString binRoot() const; // Path to the instance's minecraft bin directory. - /// Get the curent base jar of this instance. By default, it's the + /// Get the curent base jar of this instance. By default, it's the /// versions/$version/$version.jar QString baseJar() const; @@ -75,13 +68,15 @@ public: * Whether or not custom base jar is used */ bool shouldUseCustomBaseJar() const; - void setShouldUseCustomBaseJar(bool val); /*! * The value of the custom base jar */ QString customBaseJar() const; - void setCustomBaseJar(QString val); + + std::shared_ptr<LegacyModList> jarModList() const; + QList<Mod> getJarMods() const; + std::shared_ptr<WorldList> worldList() const; /*! * Whether or not the instance's minecraft.jar needs to be rebuilt. @@ -89,67 +84,57 @@ public: * re-added to a fresh minecraft.jar file. */ bool shouldRebuild() const; - void setShouldRebuild(bool val); - - virtual QString currentVersionId() const override; - - //! The version of LWJGL that this instance uses. - QString lwjglVersion() const; - //! Where the lwjgl versions foor this instance can be found... HACK HACK HACK - QString lwjglFolder() const; + QString currentVersionId() const; + QString intendedVersionId() const; - /// st the version of LWJGL libs this instance will use - void setLWJGLVersion(QString val); - - virtual QString intendedVersionId() const override; - virtual bool setIntendedVersionId(QString version) override; - - virtual QSet<QString> traits() override + QSet<QString> traits() const override { return {"legacy-instance", "texturepacks"}; }; - virtual bool shouldUpdate() const override; - virtual void setShouldUpdate(bool val) override; - virtual shared_qobject_ptr<Task> createUpdateTask() override; - virtual std::shared_ptr<Task> createJarModdingTask() override; - virtual QString createLaunchScript(AuthSessionPtr session) override; + virtual bool shouldUpdate() const; + virtual shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override; virtual QString typeName() const override; - bool canExport() const override + bool canLaunch() const override + { + return false; + } + bool canEdit() const override { return true; } - - QStringList getClassPath() const override; - QString getMainClass() const override; - - QStringList getNativeJars() const override; - QString getNativePath() const override; - - QString getLocalLibraryPath() const override + bool canExport() const override + { + return false; + } + std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override + { + return nullptr; + } + IPathMatcher::Ptr getLogFileMatcher() override + { + return nullptr; + } + QString getLogFileRoot() override { - return QString(); + return minecraftRoot(); } - QStringList processMinecraftArgs(AuthSessionPtr account) const override; + QString getStatusbarDescription() override; QStringList verboseDescription(AuthSessionPtr session) override; -protected: - std::shared_ptr<LaunchStep> createMainLaunchStep(LaunchTask *parent, AuthSessionPtr session) override; - QStringList validLaunchMethods() override; - QString launchMethod() override; - + QProcessEnvironment createEnvironment() override + { + return QProcessEnvironment(); + } + QMap<QString, QString> getVariables() const override + { + return {}; + } protected: mutable std::shared_ptr<LegacyModList> jar_mod_list; - mutable std::shared_ptr<ModList> core_mod_list; - mutable std::shared_ptr<ModList> loader_mod_list; - mutable std::shared_ptr<ModList> texture_pack_list; mutable std::shared_ptr<WorldList> m_world_list; - std::shared_ptr<Setting> m_lwjglFolderSetting; -protected -slots: - virtual void jarModsChanged(); }; diff --git a/api/logic/minecraft/legacy/LegacyModList.cpp b/api/logic/minecraft/legacy/LegacyModList.cpp index 052f75fd..638b2e21 100644 --- a/api/logic/minecraft/legacy/LegacyModList.cpp +++ b/api/logic/minecraft/legacy/LegacyModList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,55 +15,26 @@ #include "LegacyModList.h" #include <FileSystem.h> -#include <QMimeData> -#include <QUrl> -#include <QUuid> #include <QString> -#include <QFileSystemWatcher> #include <QDebug> LegacyModList::LegacyModList(const QString &dir, const QString &list_file) - : QAbstractListModel(), m_dir(dir), m_list_file(list_file) + : m_dir(dir), m_list_file(list_file) { FS::ensureFolderPathExists(m_dir.absolutePath()); m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | QDir::NoSymLinks); m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); - m_list_id = QUuid::createUuid().toString(); - m_watcher = new QFileSystemWatcher(this); - is_watching = false; - connect(m_watcher, SIGNAL(directoryChanged(QString)), this, - SLOT(directoryChanged(QString))); } -void LegacyModList::startWatching() -{ - update(); - is_watching = m_watcher->addPath(m_dir.absolutePath()); - if (is_watching) - { - qDebug() << "Started watching " << m_dir.absolutePath(); - } - else - { - qDebug() << "Failed to start watching " << m_dir.absolutePath(); - } -} - -void LegacyModList::stopWatching() -{ - is_watching = !m_watcher->removePath(m_dir.absolutePath()); - if (!is_watching) + struct OrderItem { - qDebug() << "Stopped watching " << m_dir.absolutePath(); - } - else - { - qDebug() << "Failed to stop watching " << m_dir.absolutePath(); - } -} + QString id; + bool enabled = false; + }; + typedef QList<OrderItem> OrderList; -void LegacyModList::internalSort(QList<Mod> &what) +static void internalSort(QList<Mod> &what) { auto predicate = [](const Mod &left, const Mod &right) { @@ -76,9 +47,43 @@ void LegacyModList::internalSort(QList<Mod> &what) std::sort(what.begin(), what.end(), predicate); } +static OrderList readListFile(const QString &m_list_file) +{ + OrderList itemList; + if (m_list_file.isNull() || m_list_file.isEmpty()) + return itemList; + + QFile textFile(m_list_file); + if (!textFile.open(QIODevice::ReadOnly | QIODevice::Text)) + return OrderList(); + + QTextStream textStream; + textStream.setAutoDetectUnicode(true); + textStream.setDevice(&textFile); + while (true) + { + QString line = textStream.readLine(); + if (line.isNull() || line.isEmpty()) + break; + else + { + OrderItem it; + it.enabled = !line.endsWith(".disabled"); + if (!it.enabled) + { + line.chop(9); + } + it.id = line; + itemList.append(it); + } + } + textFile.close(); + return itemList; +} + bool LegacyModList::update() { - if (!isValid()) + if (!m_dir.exists() || !m_dir.isReadable()) return false; QList<Mod> orderedMods; @@ -88,7 +93,7 @@ bool LegacyModList::update() bool orderOrStateChanged = false; // first, process the ordered items (if any) - OrderList listOrder = readListFile(); + OrderList listOrder = readListFile(m_list_file); for (auto item : listOrder) { QFileInfo infoEnabled(m_dir.filePath(item.id)); @@ -157,460 +162,10 @@ bool LegacyModList::update() } } } - beginResetModel(); mods.swap(orderedMods); - endResetModel(); if (orderOrStateChanged && !m_list_file.isEmpty()) { qDebug() << "Mod list " << m_list_file << " changed!"; - saveListFile(); - emit changed(); - } - return true; -} - -void LegacyModList::directoryChanged(QString path) -{ - update(); -} - -LegacyModList::OrderList LegacyModList::readListFile() -{ - OrderList itemList; - if (m_list_file.isNull() || m_list_file.isEmpty()) - return itemList; - - QFile textFile(m_list_file); - if (!textFile.open(QIODevice::ReadOnly | QIODevice::Text)) - return OrderList(); - - QTextStream textStream; - textStream.setAutoDetectUnicode(true); - textStream.setDevice(&textFile); - while (true) - { - QString line = textStream.readLine(); - if (line.isNull() || line.isEmpty()) - break; - else - { - OrderItem it; - it.enabled = !line.endsWith(".disabled"); - if (!it.enabled) - { - line.chop(9); - } - it.id = line; - itemList.append(it); - } - } - textFile.close(); - return itemList; -} - -bool LegacyModList::saveListFile() -{ - if (m_list_file.isNull() || m_list_file.isEmpty()) - return false; - QFile textFile(m_list_file); - if (!textFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) - return false; - QTextStream textStream; - textStream.setGenerateByteOrderMark(true); - textStream.setCodec("UTF-8"); - textStream.setDevice(&textFile); - for (auto mod : mods) - { - textStream << mod.mmc_id(); - if (!mod.enabled()) - textStream << ".disabled"; - textStream << endl; - } - textFile.close(); - return false; -} - -bool LegacyModList::isValid() -{ - return m_dir.exists() && m_dir.isReadable(); -} - -bool LegacyModList::installMod(const QString &filename, int index) -{ - // NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName - QFileInfo fileinfo(FS::NormalizePath(filename)); - - qDebug() << "installing: " << fileinfo.absoluteFilePath(); - - if (!fileinfo.exists() || !fileinfo.isReadable() || index < 0) - { - return false; - } - Mod m(fileinfo); - if (!m.valid()) - return false; - - // if it's already there, replace the original mod (in place) - int idx = mods.indexOf(m); - if (idx != -1) - { - int idx2 = mods.indexOf(m, idx + 1); - if (idx2 != -1) - return false; - if (mods[idx].replace(m)) - { - - auto left = this->index(index); - auto right = this->index(index, columnCount(QModelIndex()) - 1); - emit dataChanged(left, right); - saveListFile(); - update(); - return true; - } - return false; - } - - auto type = m.type(); - if (type == Mod::MOD_UNKNOWN) - return false; - if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE || type == Mod::MOD_LITEMOD) - { - QString newpath = FS::PathCombine(m_dir.path(), fileinfo.fileName()); - if (!QFile::copy(fileinfo.filePath(), newpath)) - return false; - m.repath(newpath); - beginInsertRows(QModelIndex(), index, index); - mods.insert(index, m); - endInsertRows(); - saveListFile(); - update(); - return true; - } - else if (type == Mod::MOD_FOLDER) - { - - QString from = fileinfo.filePath(); - QString to = FS::PathCombine(m_dir.path(), fileinfo.fileName()); - if (!FS::copy(from, to)()) - return false; - m.repath(to); - beginInsertRows(QModelIndex(), index, index); - mods.insert(index, m); - endInsertRows(); - saveListFile(); - update(); - return true; } - return false; -} - -bool LegacyModList::deleteMod(int index) -{ - if (index >= mods.size() || index < 0) - return false; - Mod &m = mods[index]; - if (m.destroy()) - { - beginRemoveRows(QModelIndex(), index, index); - mods.removeAt(index); - endRemoveRows(); - saveListFile(); - emit changed(); - return true; - } - return false; -} - -bool LegacyModList::deleteMods(int first, int last) -{ - for (int i = first; i <= last; i++) - { - Mod &m = mods[i]; - m.destroy(); - } - beginRemoveRows(QModelIndex(), first, last); - mods.erase(mods.begin() + first, mods.begin() + last + 1); - endRemoveRows(); - saveListFile(); - emit changed(); - return true; -} - -bool LegacyModList::moveModTo(int from, int to) -{ - if (from < 0 || from >= mods.size()) - return false; - if (to >= rowCount()) - to = rowCount() - 1; - if (to == -1) - to = rowCount() - 1; - if (from == to) - return false; - int togap = to > from ? to + 1 : to; - beginMoveRows(QModelIndex(), from, from, QModelIndex(), togap); - mods.move(from, to); - endMoveRows(); - saveListFile(); - emit changed(); - return true; -} - -bool LegacyModList::moveModUp(int from) -{ - if (from > 0) - return moveModTo(from, from - 1); - return false; -} - -bool LegacyModList::moveModsUp(int first, int last) -{ - if (first == 0) - return false; - - beginMoveRows(QModelIndex(), first, last, QModelIndex(), first - 1); - mods.move(first - 1, last); - endMoveRows(); - saveListFile(); - emit changed(); return true; } - -bool LegacyModList::moveModDown(int from) -{ - if (from < 0) - return false; - if (from < mods.size() - 1) - return moveModTo(from, from + 1); - return false; -} - -bool LegacyModList::moveModsDown(int first, int last) -{ - if (last == mods.size() - 1) - return false; - - beginMoveRows(QModelIndex(), first, last, QModelIndex(), last + 2); - mods.move(last + 1, first); - endMoveRows(); - saveListFile(); - emit changed(); - return true; -} - -int LegacyModList::columnCount(const QModelIndex &parent) const -{ - return 3; -} - -QVariant LegacyModList::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - int row = index.row(); - int column = index.column(); - - if (row < 0 || row >= mods.size()) - return QVariant(); - - switch (role) - { - case Qt::DisplayRole: - switch (column) - { - case NameColumn: - return mods[row].name(); - case VersionColumn: - return mods[row].version(); - - default: - return QVariant(); - } - - case Qt::ToolTipRole: - return mods[row].mmc_id(); - - case Qt::CheckStateRole: - switch (column) - { - case ActiveColumn: - return mods[row].enabled() ? Qt::Checked : Qt::Unchecked; - default: - return QVariant(); - } - default: - return QVariant(); - } -} - -bool LegacyModList::setData(const QModelIndex &index, const QVariant &value, int role) -{ - if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid()) - { - return false; - } - - if (role == Qt::CheckStateRole) - { - auto &mod = mods[index.row()]; - if (mod.enable(!mod.enabled())) - { - emit dataChanged(index, index); - return true; - } - } - return false; -} - -QVariant LegacyModList::headerData(int section, Qt::Orientation orientation, int role) const -{ - switch (role) - { - case Qt::DisplayRole: - switch (section) - { - case ActiveColumn: - return QString(); - case NameColumn: - return tr("Name"); - case VersionColumn: - return tr("Version"); - default: - return QVariant(); - } - - case Qt::ToolTipRole: - switch (section) - { - case ActiveColumn: - return tr("Is the mod enabled?"); - case NameColumn: - return tr("The name of the mod."); - case VersionColumn: - return tr("The version of the mod."); - default: - return QVariant(); - } - default: - return QVariant(); - } - return QVariant(); -} - -Qt::ItemFlags LegacyModList::flags(const QModelIndex &index) const -{ - Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); - if (index.isValid()) - return Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | - defaultFlags; - else - return Qt::ItemIsDropEnabled | defaultFlags; -} - -QStringList LegacyModList::mimeTypes() const -{ - QStringList types; - types << "text/uri-list"; - types << "text/plain"; - return types; -} - -Qt::DropActions LegacyModList::supportedDropActions() const -{ - // copy from outside, move from within and other mod lists - return Qt::CopyAction | Qt::MoveAction; -} - -Qt::DropActions LegacyModList::supportedDragActions() const -{ - // move to other mod lists or VOID - return Qt::MoveAction; -} - -QMimeData *LegacyModList::mimeData(const QModelIndexList &indexes) const -{ - QMimeData *data = new QMimeData(); - - if (indexes.size() == 0) - return data; - - auto idx = indexes[0]; - int row = idx.row(); - if (row < 0 || row >= mods.size()) - return data; - - QStringList params; - params << m_list_id << QString::number(row); - data->setText(params.join('|')); - return data; -} - -bool LegacyModList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, - const QModelIndex &parent) -{ - if (action == Qt::IgnoreAction) - return true; - // check if the action is supported - if (!data || !(action & supportedDropActions())) - return false; - if (parent.isValid()) - { - row = parent.row(); - column = parent.column(); - } - - if (row > rowCount()) - row = rowCount(); - if (row == -1) - row = rowCount(); - if (column == -1) - column = 0; - qDebug() << "Drop row: " << row << " column: " << column; - - // files dropped from outside? - if (data->hasUrls()) - { - bool was_watching = is_watching; - if (was_watching) - stopWatching(); - auto urls = data->urls(); - for (auto url : urls) - { - // only local files may be dropped... - if (!url.isLocalFile()) - continue; - QString filename = url.toLocalFile(); - installMod(filename, row); - // if there is no ordering, re-sort the list - if (m_list_file.isEmpty()) - { - beginResetModel(); - internalSort(mods); - endResetModel(); - } - } - if (was_watching) - startWatching(); - return true; - } - else if (data->hasText()) - { - QString sourcestr = data->text(); - auto list = sourcestr.split('|'); - if (list.size() != 2) - return false; - QString remoteId = list[0]; - int remoteIndex = list[1].toInt(); - qDebug() << "move: " << sourcestr; - // no moving of things between two lists - if (remoteId != m_list_id) - return false; - // no point moving to the same place... - if (row == remoteIndex) - return false; - // otherwise, move the mod :D - moveModTo(remoteIndex, row); - return true; - } - return false; -} diff --git a/api/logic/minecraft/legacy/LegacyModList.h b/api/logic/minecraft/legacy/LegacyModList.h index d1bd0e8b..19b191a7 100644 --- a/api/logic/minecraft/legacy/LegacyModList.h +++ b/api/logic/minecraft/legacy/LegacyModList.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2017 MultiMC Contributors +/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ #include <QList> #include <QString> #include <QDir> -#include <QAbstractListModel> #include "minecraft/Mod.h" @@ -26,102 +25,19 @@ class LegacyInstance; class BaseInstance; -class QFileSystemWatcher; /** * A legacy mod list. * Backed by a folder. */ -class MULTIMC_LOGIC_EXPORT LegacyModList : public QAbstractListModel +class MULTIMC_LOGIC_EXPORT LegacyModList { - Q_OBJECT public: - enum Columns - { - ActiveColumn = 0, - NameColumn, - VersionColumn - }; - LegacyModList(const QString &dir, const QString &list_file = QString()); - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - virtual bool setData(const QModelIndex &index, const QVariant &value, - int role = Qt::EditRole); - - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const - { - return size(); - } - ; - virtual QVariant headerData(int section, Qt::Orientation orientation, - int role = Qt::DisplayRole) const; - virtual int columnCount(const QModelIndex &parent) const; - - size_t size() const - { - return mods.size(); - } - ; - bool empty() const - { - return size() == 0; - } - Mod &operator[](size_t index) - { - return mods[index]; - } + LegacyModList(const QString &dir, const QString &list_file = QString()); /// Reloads the mod list and returns true if the list changed. - virtual bool update(); - - /** - * Adds the given mod to the list at the given index - if the list supports custom ordering - */ - virtual bool installMod(const QString & filename, int index = 0); - - /// Deletes the mod at the given index. - virtual bool deleteMod(int index); - - /// Deletes all the selected mods - virtual bool deleteMods(int first, int last); - - /** - * move the mod at index to the position N - * 0 is the beginning of the list, length() is the end of the list. - */ - virtual bool moveModTo(int from, int to); - - /** - * move the mod at index one position upwards - */ - virtual bool moveModUp(int from); - virtual bool moveModsUp(int first, int last); - - /** - * move the mod at index one position downwards - */ - virtual bool moveModDown(int from); - virtual bool moveModsDown(int first, int last); - - /// flags, mostly to support drag&drop - virtual Qt::ItemFlags flags(const QModelIndex &index) const; - /// get data for drag action - virtual QMimeData *mimeData(const QModelIndexList &indexes) const; - /// get the supported mime types - virtual QStringList mimeTypes() const; - /// process data from drop action - virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, - const QModelIndex &parent); - /// what drag actions do we support? - virtual Qt::DropActions supportedDragActions() const; - - /// what drop actions do we support? - virtual Qt::DropActions supportedDropActions() const; - - void startWatching(); - void stopWatching(); - - virtual bool isValid(); + bool update(); QDir dir() { @@ -133,28 +49,8 @@ public: return mods; } -private: - void internalSort(QList<Mod> & what); - struct OrderItem - { - QString id; - bool enabled = false; - }; - typedef QList<OrderItem> OrderList; - OrderList readListFile(); - bool saveListFile(); -private -slots: - void directoryChanged(QString path); - -signals: - void changed(); - protected: - QFileSystemWatcher *m_watcher; - bool is_watching; QDir m_dir; QString m_list_file; - QString m_list_id; QList<Mod> mods; }; diff --git a/api/logic/minecraft/legacy/LegacyUpdate.cpp b/api/logic/minecraft/legacy/LegacyUpdate.cpp deleted file mode 100644 index e263d0de..00000000 --- a/api/logic/minecraft/legacy/LegacyUpdate.cpp +++ /dev/null @@ -1,399 +0,0 @@ -/* Copyright 2013-2017 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 <QStringList> -#include <quazip.h> -#include <quazipfile.h> -#include <QDebug> - -#include "Env.h" -#include "BaseInstance.h" -#include "net/URLConstants.h" -#include "MMCZip.h" - -#include "LegacyUpdate.h" -#include "LegacyModList.h" - -#include "LwjglVersionList.h" -#include "LegacyInstance.h" -#include <FileSystem.h> - -LegacyUpdate::LegacyUpdate(BaseInstance *inst, QObject *parent) : Task(parent), m_inst(inst) -{ -} - -void LegacyUpdate::executeTask() -{ - fmllibsStart(); -} - -void LegacyUpdate::fmllibsStart() -{ - // Get the mod list - LegacyInstance *inst = (LegacyInstance *)m_inst; - auto modList = inst->jarModList(); - - bool forge_present = false; - - QString version = inst->intendedVersionId(); - auto & fmlLibsMapping = g_VersionFilterData.fmlLibsMapping; - if (!fmlLibsMapping.contains(version)) - { - lwjglStart(); - return; - } - - auto &libList = fmlLibsMapping[version]; - - // determine if we need some libs for FML or forge - setStatus(tr("Checking for FML libraries...")); - for (unsigned i = 0; i < modList->size(); i++) - { - auto &mod = modList->operator[](i); - - // do not use disabled mods. - if (!mod.enabled()) - continue; - - if (mod.type() != Mod::MOD_ZIPFILE) - continue; - - if (mod.mmc_id().contains("forge", Qt::CaseInsensitive)) - { - forge_present = true; - break; - } - if (mod.mmc_id().contains("fml", Qt::CaseInsensitive)) - { - forge_present = true; - break; - } - } - // we don't... - if (!forge_present) - { - lwjglStart(); - return; - } - - // now check the lib folder inside the instance for files. - for (auto &lib : libList) - { - QFileInfo libInfo(FS::PathCombine(inst->libDir(), lib.filename)); - if (libInfo.exists()) - continue; - fmlLibsToProcess.append(lib); - } - - // if everything is in place, there's nothing to do here... - if (fmlLibsToProcess.isEmpty()) - { - lwjglStart(); - return; - } - - // download missing libs to our place - setStatus(tr("Dowloading FML libraries...")); - auto dljob = new NetJob("FML libraries"); - auto metacache = ENV.metacache(); - for (auto &lib : fmlLibsToProcess) - { - auto entry = metacache->resolveEntry("fmllibs", lib.filename); - QString urlString = lib.ours ? URLConstants::FMLLIBS_OUR_BASE_URL + lib.filename - : URLConstants::FMLLIBS_FORGE_BASE_URL + lib.filename; - dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry)); - } - - connect(dljob, &NetJob::succeeded, this, &LegacyUpdate::fmllibsFinished); - connect(dljob, &NetJob::failed, this, &LegacyUpdate::fmllibsFailed); - connect(dljob, &NetJob::progress, this, &LegacyUpdate::progress); - legacyDownloadJob.reset(dljob); - legacyDownloadJob->start(); -} - -void LegacyUpdate::fmllibsFinished() -{ - legacyDownloadJob.reset(); - if(!fmlLibsToProcess.isEmpty()) - { - setStatus(tr("Copying FML libraries into the instance...")); - LegacyInstance *inst = (LegacyInstance *)m_inst; - auto metacache = ENV.metacache(); - int index = 0; - for (auto &lib : fmlLibsToProcess) - { - progress(index, fmlLibsToProcess.size()); - auto entry = metacache->resolveEntry("fmllibs", lib.filename); - auto path = FS::PathCombine(inst->libDir(), lib.filename); - if(!FS::ensureFilePathExists(path)) - { - emitFailed(tr("Failed creating FML library folder inside the instance.")); - return; - } - if (!QFile::copy(entry->getFullPath(), FS::PathCombine(inst->libDir(), lib.filename))) - { - emitFailed(tr("Failed copying Forge/FML library: %1.").arg(lib.filename)); - return; - } - index++; - } - progress(index, fmlLibsToProcess.size()); - } - lwjglStart(); -} - -void LegacyUpdate::fmllibsFailed(QString reason) -{ - emitFailed(tr("Game update failed: it was impossible to fetch the required FML libraries. Reason: %1").arg(reason)); - return; -} - -void LegacyUpdate::lwjglStart() -{ - LegacyInstance *inst = (LegacyInstance *)m_inst; - - auto list = std::dynamic_pointer_cast<LWJGLVersionList>(ENV.getVersionList("org.lwjgl.legacy")); - if (!list->isLoaded()) - { - setStatus(tr("Checking the LWJGL version list...")); - list->loadList(); - auto task = list->getLoadTask(); - connect(task.get(), &Task::succeeded, this, &LegacyUpdate::lwjglStart); - connect(task.get(), &Task::failed, this, [&](const QString & error) - { - emitFailed(tr("Failed to refresh LWJGL list: %1.").arg(error)); - }); - return; - } - - lwjglVersion = inst->lwjglVersion(); - lwjglTargetPath = FS::PathCombine(inst->lwjglFolder(), lwjglVersion); - lwjglNativesPath = FS::PathCombine(lwjglTargetPath, "natives"); - - // if the 'done' file exists, we don't have to download this again - QFileInfo doneFile(FS::PathCombine(lwjglTargetPath, "done")); - if (doneFile.exists()) - { - jarStart(); - return; - } - - setStatus(tr("Downloading new LWJGL...")); - auto version = std::dynamic_pointer_cast<LWJGLVersion>(list->findVersion(lwjglVersion)); - if (!version) - { - emitFailed(QString("Game update failed: the selected LWJGL version is invalid: %1").arg(lwjglVersion)); - return; - } - - QString url = version->url(); - QUrl realUrl(url); - QString hostname = realUrl.host(); - auto worker = &ENV.qnam(); - QNetworkRequest req(realUrl); - req.setRawHeader("Host", hostname.toLatin1()); - req.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Cached)"); - QNetworkReply *rep = worker->get(req); - - m_reply = std::shared_ptr<QNetworkReply>(rep); - connect(rep, &QNetworkReply::downloadProgress, this, &LegacyUpdate::progress); - connect(worker, &QNetworkAccessManager::finished, this, &LegacyUpdate::lwjglFinished); -} - -void LegacyUpdate::lwjglFinished(QNetworkReply *reply) -{ - if (m_reply.get() != reply) - { - return; - } - if (reply->error() != QNetworkReply::NoError) - { - emitFailed("Failed to download: " + reply->errorString() + - "\nSometimes you have to wait a bit if you download many LWJGL versions in " - "a row. YMMV"); - return; - } - auto worker = &ENV.qnam(); - // Here i check if there is a cookie for me in the reply and extract it - QList<QNetworkCookie> cookies = - qvariant_cast<QList<QNetworkCookie>>(reply->header(QNetworkRequest::SetCookieHeader)); - if (cookies.count() != 0) - { - // you must tell which cookie goes with which url - worker->cookieJar()->setCookiesFromUrl(cookies, QUrl("sourceforge.net")); - } - - // here you can check for the 302 or whatever other header i need - QVariant newLoc = reply->header(QNetworkRequest::LocationHeader); - if (newLoc.isValid()) - { - QString redirectedTo = reply->header(QNetworkRequest::LocationHeader).toString(); - QUrl realUrl(redirectedTo); - QString hostname = realUrl.host(); - QNetworkRequest req(redirectedTo); - req.setRawHeader("Host", hostname.toLatin1()); - req.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Cached)"); - QNetworkReply *rep = worker->get(req); - connect(rep, &QNetworkReply::downloadProgress, this, &LegacyUpdate::progress); - m_reply = std::shared_ptr<QNetworkReply>(rep); - return; - } - QFile saveMe("lwjgl.zip"); - saveMe.open(QIODevice::WriteOnly); - saveMe.write(m_reply->readAll()); - saveMe.close(); - setStatus(tr("Installing new LWJGL...")); - extractLwjgl(); - jarStart(); -} -void LegacyUpdate::extractLwjgl() -{ - // make sure the directories are there - - bool success = FS::ensureFolderPathExists(lwjglNativesPath); - - if (!success) - { - emitFailed("Failed to extract the lwjgl libs - error when creating required folders."); - return; - } - - QuaZip zip("lwjgl.zip"); - if (!zip.open(QuaZip::mdUnzip)) - { - emitFailed("Failed to extract the lwjgl libs - not a valid archive."); - return; - } - - // and now we are going to access files inside it - QuaZipFile file(&zip); - const QString jarNames[] = {"jinput.jar", "lwjgl_util.jar", "lwjgl.jar"}; - for (bool more = zip.goToFirstFile(); more; more = zip.goToNextFile()) - { - if (!file.open(QIODevice::ReadOnly)) - { - zip.close(); - emitFailed("Failed to extract the lwjgl libs - error while reading archive."); - return; - } - QuaZipFileInfo info; - QString name = file.getActualFileName(); - if (name.endsWith('/')) - { - file.close(); - continue; - } - QString destFileName; - // Look for the jars - for (int i = 0; i < 3; i++) - { - if (name.endsWith(jarNames[i])) - { - destFileName = FS::PathCombine(lwjglTargetPath, jarNames[i]); - } - } - // Not found? look for the natives - if (destFileName.isEmpty()) - { -#ifdef Q_OS_WIN32 - QString nativesDir = "windows"; -#else -#ifdef Q_OS_MAC - QString nativesDir = "macosx"; -#else - QString nativesDir = "linux"; -#endif -#endif - if (name.contains(nativesDir)) - { - int lastSlash = name.lastIndexOf('/'); - int lastBackSlash = name.lastIndexOf('\\'); - if (lastSlash != -1) - name = name.mid(lastSlash + 1); - else if (lastBackSlash != -1) - name = name.mid(lastBackSlash + 1); - destFileName = FS::PathCombine(lwjglNativesPath, name); - } - } - // Now if destFileName is still empty, go to the next file. - if (!destFileName.isEmpty()) - { - setStatus(tr("Installing new LWJGL - extracting ") + name + "..."); - QFile output(destFileName); - output.open(QIODevice::WriteOnly); - output.write(file.readAll()); - output.close(); - } - file.close(); // do not forget to close! - } - zip.close(); - m_reply.reset(); - QFile doneFile(FS::PathCombine(lwjglTargetPath, "done")); - doneFile.open(QIODevice::WriteOnly); - doneFile.write("done."); - doneFile.close(); -} - -void LegacyUpdate::lwjglFailed(QString reason) -{ - emitFailed(tr("Bad stuff happened while trying to get the lwjgl libs: %1").arg(reason)); -} - -void LegacyUpdate::jarStart() -{ - LegacyInstance *inst = (LegacyInstance *)m_inst; - if (!inst->shouldUpdate() || inst->shouldUseCustomBaseJar()) - { - emitSucceeded(); - return; - } - - setStatus(tr("Checking for jar updates...")); - // Make directories - QDir binDir(inst->binRoot()); - if (!binDir.exists() && !binDir.mkpath(".")) - { - emitFailed("Failed to create bin folder."); - return; - } - - // Build a list of URLs that will need to be downloaded. - setStatus(tr("Downloading new minecraft.jar ...")); - - QString version_id = inst->intendedVersionId(); - - auto dljob = new NetJob("Minecraft.jar for version " + version_id); - - auto metacache = ENV.metacache(); - auto entry = metacache->resolveEntry("versions", URLConstants::getJarPath(version_id)); - dljob->addNetAction(Net::Download::makeCached(QUrl(URLConstants::getLegacyJarUrl(version_id)), entry)); - connect(dljob, SIGNAL(succeeded()), SLOT(jarFinished())); - connect(dljob, SIGNAL(failed(QString)), SLOT(jarFailed(QString))); - connect(dljob, SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64))); - legacyDownloadJob.reset(dljob); - legacyDownloadJob->start(); -} - -void LegacyUpdate::jarFinished() -{ - // process the jar - emitSucceeded(); -} - -void LegacyUpdate::jarFailed(QString reason) -{ - // bad, bad - emitFailed(tr("Failed to download the minecraft jar: %1.").arg(reason)); -} diff --git a/api/logic/minecraft/legacy/LegacyUpdate.h b/api/logic/minecraft/legacy/LegacyUpdate.h deleted file mode 100644 index caab978e..00000000 --- a/api/logic/minecraft/legacy/LegacyUpdate.h +++ /dev/null @@ -1,70 +0,0 @@ -/* Copyright 2013-2017 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. - */ - -#pragma once - -#include <QObject> -#include <QList> -#include <QUrl> - -#include "net/NetJob.h" -#include "tasks/Task.h" -#include "minecraft/VersionFilterData.h" - -class MinecraftVersion; -class BaseInstance; -class QuaZip; -class Mod; - -class LegacyUpdate : public Task -{ - Q_OBJECT -public: - explicit LegacyUpdate(BaseInstance *inst, QObject *parent = 0); - virtual void executeTask(); - -private -slots: - void lwjglStart(); - void lwjglFinished(QNetworkReply *); - void lwjglFailed(QString reason); - - void jarStart(); - void jarFinished(); - void jarFailed(QString reason); - - void fmllibsStart(); - void fmllibsFinished(); - void fmllibsFailed(QString reason); - - void extractLwjgl(); - -private: - - std::shared_ptr<QNetworkReply> m_reply; - - // target version, determined during this task - // MinecraftVersion *targetVersion; - QString lwjglURL; - QString lwjglVersion; - - QString lwjglTargetPath; - QString lwjglNativesPath; - -private: - NetJobPtr legacyDownloadJob; - BaseInstance *m_inst = nullptr; - QList<FMLlib> fmlLibsToProcess; -}; diff --git a/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp b/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp new file mode 100644 index 00000000..6cda3e4d --- /dev/null +++ b/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp @@ -0,0 +1,142 @@ +#include "LegacyUpgradeTask.h" +#include "BaseInstanceProvider.h" +#include "settings/INISettingsObject.h" +#include "FileSystem.h" +#include "NullInstance.h" +#include "pathmatcher/RegexpMatcher.h" +#include <QtConcurrentRun> +#include "LegacyInstance.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/ComponentList.h" +#include "classparser.h" + +LegacyUpgradeTask::LegacyUpgradeTask(SettingsObjectPtr settings, const QString & stagingPath, InstancePtr origInstance, const QString & newName) +{ + m_globalSettings = settings; + m_stagingPath = stagingPath; + m_origInstance = origInstance; + m_newName = newName; +} + +void LegacyUpgradeTask::executeTask() +{ + setStatus(tr("Copying instance %1").arg(m_origInstance->name())); + + FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath); + folderCopy.followSymlinks(true); + + m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), folderCopy); + connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &LegacyUpgradeTask::copyFinished); + connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &LegacyUpgradeTask::copyAborted); + m_copyFutureWatcher.setFuture(m_copyFuture); +} + +static QString decideVersion(const QString& currentVersion, const QString& intendedVersion) +{ + if(intendedVersion != currentVersion) + { + if(!intendedVersion.isEmpty()) + { + return intendedVersion; + } + else if(!currentVersion.isEmpty()) + { + return currentVersion; + } + } + else + { + if(!intendedVersion.isEmpty()) + { + return intendedVersion; + } + } + return QString(); +} + +void LegacyUpgradeTask::copyFinished() +{ + auto successful = m_copyFuture.result(); + if(!successful) + { + emitFailed(tr("Instance folder copy failed.")); + return; + } + auto legacyInst = std::dynamic_pointer_cast<LegacyInstance>(m_origInstance); + + auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg")); + instanceSettings->registerSetting("InstanceType", "Legacy"); + instanceSettings->set("InstanceType", "OneSix"); + // NOTE: this scope ensures the instance is fully saved before we emitSucceeded + { + MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath); + inst.setName(m_newName); + inst.init(); + + QString preferredVersionNumber = decideVersion(legacyInst->currentVersionId(), legacyInst->intendedVersionId()); + if(preferredVersionNumber.isNull()) + { + // try to decide version based on the jar(s?) + preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->baseJar()); + if(preferredVersionNumber.isNull()) + { + preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->runnableJar()); + if(preferredVersionNumber.isNull()) + { + emitFailed(tr("Could not decide Minecraft version.")); + return; + } + } + } + auto components = inst.getComponentList(); + components->buildingFromScratch(); + components->setComponentVersion("net.minecraft", preferredVersionNumber, true); + + if(legacyInst->shouldUseCustomBaseJar()) + { + QString jarPath = legacyInst->customBaseJar(); + qDebug() << "Base jar is custom! : " << jarPath; + // FIXME: handle case when the jar is unreadable? + // TODO: check the hash, if it's the same as the upstream jar, do not do this + components->installCustomJar(jarPath); + } + + auto jarMods = legacyInst->getJarMods(); + for(auto & jarMod: jarMods) + { + QString modPath = jarMod.filename().absoluteFilePath(); + qDebug() << "jarMod: " << modPath; + components->installJarMods({modPath}); + } + + // remove all the extra garbage we no longer need + auto removeAll = [&](const QString &root, const QStringList &things) + { + for(auto &thing : things) + { + auto removePath = FS::PathCombine(root, thing); + QFileInfo stat(removePath); + if(stat.isDir()) + { + FS::deletePath(removePath); + } + else + { + QFile::remove(removePath); + } + } + }; + QStringList rootRemovables = {"modlist", "version", "instMods"}; + QStringList mcRemovables = {"bin", "MultiMCLauncher.jar", "icon.png"}; + removeAll(inst.instanceRoot(), rootRemovables); + removeAll(inst.minecraftRoot(), mcRemovables); + } + emitSucceeded(); +} + +void LegacyUpgradeTask::copyAborted() +{ + emitFailed(tr("Instance folder copy has been aborted.")); + return; +} + diff --git a/api/logic/minecraft/legacy/LegacyUpgradeTask.h b/api/logic/minecraft/legacy/LegacyUpgradeTask.h new file mode 100644 index 00000000..56896385 --- /dev/null +++ b/api/logic/minecraft/legacy/LegacyUpgradeTask.h @@ -0,0 +1,38 @@ +#pragma once + +#include "tasks/Task.h" +#include "multimc_logic_export.h" +#include "net/NetJob.h" +#include <QUrl> +#include <QFuture> +#include <QFutureWatcher> +#include "settings/SettingsObject.h" +#include "BaseVersion.h" +#include "BaseInstance.h" + + +class BaseInstanceProvider; + +class MULTIMC_LOGIC_EXPORT LegacyUpgradeTask : public Task +{ + Q_OBJECT +public: + explicit LegacyUpgradeTask(SettingsObjectPtr settings, const QString & stagingPath, InstancePtr origInstance, const QString & newName); + +protected: + //! Entry point for tasks. + virtual void executeTask() override; + void copyFinished(); + void copyAborted(); + +private: /* data */ + SettingsObjectPtr m_globalSettings; + InstancePtr m_origInstance; + QString m_stagingPath; + QString m_newName; + QFuture<bool> m_copyFuture; + QFutureWatcher<bool> m_copyFutureWatcher; +}; + + + diff --git a/api/logic/minecraft/legacy/LwjglVersionList.cpp b/api/logic/minecraft/legacy/LwjglVersionList.cpp deleted file mode 100644 index 3d7ad2d4..00000000 --- a/api/logic/minecraft/legacy/LwjglVersionList.cpp +++ /dev/null @@ -1,169 +0,0 @@ -/* Copyright 2013-2017 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 "LwjglVersionList.h" -#include "Env.h" - -#include <QtNetwork> -#include <QtXml> -#include <QRegExp> - -#include <QDebug> - -#define RSS_URL "https://sourceforge.net/projects/java-game-lib/rss" - -LWJGLVersionList::LWJGLVersionList(QObject *parent) : BaseVersionList(parent) -{ -} - -QVariant LWJGLVersionList::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - if (index.row() > count()) - return QVariant(); - - const PtrLWJGLVersion version = m_vlist.at(index.row()); - - switch (role) - { - case Qt::DisplayRole: - return version->name(); - - case Qt::ToolTipRole: - return version->url(); - - default: - return QVariant(); - } -} - -QVariant LWJGLVersionList::headerData(int section, Qt::Orientation orientation, int role) const -{ - switch (role) - { - case Qt::DisplayRole: - return tr("Version"); - - case Qt::ToolTipRole: - return tr("LWJGL version name."); - - default: - return QVariant(); - } -} - -int LWJGLVersionList::columnCount(const QModelIndex &parent) const -{ - return 1; -} - -void LWJGLVersionList::loadList() -{ - if(m_loading) - { - return; - } - m_loading = true; - - qDebug() << "Downloading LWJGL RSS..."; - m_rssDLJob.reset(new NetJob("LWJGL RSS")); - m_rssDL = Net::Download::makeByteArray(QUrl(RSS_URL), &m_rssData); - m_rssDLJob->addNetAction(m_rssDL); - connect(m_rssDLJob.get(), &NetJob::failed, this, &LWJGLVersionList::rssFailed); - connect(m_rssDLJob.get(), &NetJob::succeeded, this, &LWJGLVersionList::rssSucceeded); - m_rssDLJob->start(); -} - -inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname) -{ - QDomNodeList elementList = parent.elementsByTagName(tagname); - if (elementList.count()) - return elementList.at(0).toElement(); - else - return QDomElement(); -} - -void LWJGLVersionList::rssFailed(const QString& reason) -{ - m_rssDLJob.reset(); - m_loading = false; - qWarning() << "Failed to load LWJGL list. Network error: " + reason; -} - -void LWJGLVersionList::rssSucceeded() -{ - QRegExp lwjglRegex("lwjgl-(([0-9]\\.?)+)\\.zip"); - Q_ASSERT_X(lwjglRegex.isValid(), "load LWJGL list", "LWJGL regex is invalid"); - - QDomDocument doc; - - QString xmlErrorMsg; - int errorLine; - - if (!doc.setContent(m_rssData, false, &xmlErrorMsg, &errorLine)) - { - qWarning() << "Failed to load LWJGL list. XML error: " + xmlErrorMsg + " at line " + QString::number(errorLine); - m_rssDLJob.reset(); - m_rssData.clear(); - m_loading = false; - return; - } - m_rssData.clear(); - - QDomNodeList items = doc.elementsByTagName("item"); - - QList<PtrLWJGLVersion> tempList; - - for (int i = 0; i < items.length(); i++) - { - Q_ASSERT_X(items.at(i).isElement(), "load LWJGL list", "XML element isn't an element... wat?"); - - QDomElement linkElement = getDomElementByTagName(items.at(i).toElement(), "link"); - if (linkElement.isNull()) - { - qDebug() << "Link element" << i << "in RSS feed doesn't exist! Skipping."; - continue; - } - - QString link = linkElement.text(); - - // Make sure it's a download link. - if (link.endsWith("/download") && link.contains(lwjglRegex)) - { - QString name = link.mid(lwjglRegex.indexIn(link) + 6); - // Subtract 4 here to remove the .zip file extension. - name = name.left(lwjglRegex.matchedLength() - 10); - - QUrl url(link); - if (!url.isValid()) - { - qWarning() << "LWJGL version URL isn't valid:" << link << "Skipping."; - continue; - } - qDebug() << "Discovered LWGL version" << name << "at" << link; - tempList.append(std::make_shared<LWJGLVersion>(name, link)); - } - } - - beginResetModel(); - m_vlist.swap(tempList); - endResetModel(); - - qDebug() << "Loaded LWJGL list."; - m_rssDLJob.reset(); - m_loading = false; -} diff --git a/api/logic/minecraft/legacy/LwjglVersionList.h b/api/logic/minecraft/legacy/LwjglVersionList.h deleted file mode 100644 index f5312e2c..00000000 --- a/api/logic/minecraft/legacy/LwjglVersionList.h +++ /dev/null @@ -1,116 +0,0 @@ -/* Copyright 2013-2017 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. - */ - -#pragma once - -#include <QObject> -#include <QAbstractListModel> -#include <QUrl> -#include <QNetworkReply> -#include <memory> - -#include "BaseVersion.h" -#include "BaseVersionList.h" - -#include "multimc_logic_export.h" -#include <net/NetJob.h> - -class LWJGLVersion; -typedef std::shared_ptr<LWJGLVersion> PtrLWJGLVersion; - -class MULTIMC_LOGIC_EXPORT LWJGLVersion : public BaseVersion -{ -public: - LWJGLVersion(const QString &name, const QString &url) - : m_name(name), m_url(url) - { - } - - virtual QString descriptor() - { - return m_name; - } - - virtual QString name() - { - return m_name; - } - - virtual QString typeString() const - { - return QObject::tr("Upstream"); - } - - QString url() const - { - return m_url; - } - -protected: - QString m_name; - QString m_url; -}; - -class MULTIMC_LOGIC_EXPORT LWJGLVersionList : public BaseVersionList -{ - Q_OBJECT -public: - explicit LWJGLVersionList(QObject *parent = 0); - - bool isLoaded() override - { - return m_vlist.length() > 0; - } - virtual const BaseVersionPtr at(int i) const override - { - return m_vlist[i]; - } - - virtual shared_qobject_ptr<Task> getLoadTask() override - { - return m_rssDLJob; - } - - virtual void sortVersions() override {}; - - virtual void updateListData(QList< BaseVersionPtr > versions) override {}; - - int count() const override - { - return m_vlist.length(); - } - - virtual QVariant data(const QModelIndex &index, int role) const override; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - virtual int rowCount(const QModelIndex &parent) const override - { - return count(); - } - virtual int columnCount(const QModelIndex &parent) const override; - -public slots: - virtual void loadList(); - -private slots: - void rssFailed(const QString & reason); - void rssSucceeded(); - -private: - QList<PtrLWJGLVersion> m_vlist; - Net::Download::Ptr m_rssDL; - NetJobPtr m_rssDLJob; - QByteArray m_rssData; - bool m_loading = false; -}; diff --git a/api/logic/minecraft/onesix/OneSixInstance.cpp b/api/logic/minecraft/onesix/OneSixInstance.cpp deleted file mode 100644 index ecfd0647..00000000 --- a/api/logic/minecraft/onesix/OneSixInstance.cpp +++ /dev/null @@ -1,698 +0,0 @@ -/* Copyright 2013-2017 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 <QDebug> -#include <minecraft/launch/DirectJavaLaunch.h> -#include <minecraft/launch/LauncherPartLaunch.h> -#include <Env.h> - -#include "OneSixInstance.h" -#include "OneSixUpdate.h" -#include "OneSixProfileStrategy.h" - -#include "minecraft/MinecraftProfile.h" -#include "minecraft/launch/ModMinecraftJar.h" -#include "MMCZip.h" - -#include "minecraft/AssetsUtils.h" -#include "minecraft/WorldList.h" -#include <FileSystem.h> - -OneSixInstance::OneSixInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir) - : MinecraftInstance(globalSettings, settings, rootDir) -{ - // set explicitly during instance creation - m_settings->registerSetting({"IntendedVersion", "MinecraftVersion"}, ""); - - // defaults to the version we've been using for years (2.9.1) - m_settings->registerSetting("LWJGLVersion", "2.9.1"); - - // optionals - m_settings->registerSetting("ForgeVersion", ""); - m_settings->registerSetting("LiteloaderVersion", ""); -} - -void OneSixInstance::init() -{ - createProfile(); -} - -void OneSixInstance::createProfile() -{ - m_profile.reset(new MinecraftProfile(new OneSixProfileStrategy(this))); -} - -QSet<QString> OneSixInstance::traits() -{ - auto version = getMinecraftProfile(); - if (!version) - { - return {"version-incomplete"}; - } - else - { - return version->getTraits(); - } -} - -shared_qobject_ptr<Task> OneSixInstance::createUpdateTask() -{ - return shared_qobject_ptr<Task>(new OneSixUpdate(this)); -} - -QString replaceTokensIn(QString text, QMap<QString, QString> with) -{ - QString result; - QRegExp token_regexp("\\$\\{(.+)\\}"); - token_regexp.setMinimal(true); - QStringList list; - int tail = 0; - int head = 0; - while ((head = token_regexp.indexIn(text, head)) != -1) - { - result.append(text.mid(tail, head - tail)); - QString key = token_regexp.cap(1); - auto iter = with.find(key); - if (iter != with.end()) - { - result.append(*iter); - } - head += token_regexp.matchedLength(); - tail = head; - } - result.append(text.mid(tail)); - return result; -} - -QStringList OneSixInstance::processMinecraftArgs(AuthSessionPtr session) const -{ - QString args_pattern = m_profile->getMinecraftArguments(); - for (auto tweaker : m_profile->getTweakers()) - { - args_pattern += " --tweakClass " + tweaker; - } - - QMap<QString, QString> token_mapping; - // yggdrasil! - if(session) - { - token_mapping["auth_username"] = session->username; - token_mapping["auth_session"] = session->session; - token_mapping["auth_access_token"] = session->access_token; - token_mapping["auth_player_name"] = session->player_name; - token_mapping["auth_uuid"] = session->uuid; - token_mapping["user_properties"] = session->serializeUserProperties(); - token_mapping["user_type"] = session->user_type; - } - - // blatant self-promotion. - token_mapping["profile_name"] = token_mapping["version_name"] = "MultiMC5"; - if(m_profile->isVanilla()) - { - token_mapping["version_type"] = m_profile->getMinecraftVersionType(); - } - else - { - token_mapping["version_type"] = "custom"; - } - - QString absRootDir = QDir(minecraftRoot()).absolutePath(); - token_mapping["game_directory"] = absRootDir; - QString absAssetsDir = QDir("assets/").absolutePath(); - auto assets = m_profile->getMinecraftAssets(); - token_mapping["game_assets"] = AssetsUtils::reconstructAssets(assets->id).absolutePath(); - - // 1.7.3+ assets tokens - token_mapping["assets_root"] = absAssetsDir; - token_mapping["assets_index_name"] = assets->id; - - QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts); - for (int i = 0; i < parts.length(); i++) - { - parts[i] = replaceTokensIn(parts[i], token_mapping); - } - return parts; -} - -QString OneSixInstance::getNativePath() const -{ - QDir natives_dir(FS::PathCombine(instanceRoot(), "natives/")); - return natives_dir.absolutePath(); -} - -QString OneSixInstance::getLocalLibraryPath() const -{ - QDir libraries_dir(FS::PathCombine(instanceRoot(), "libraries/")); - return libraries_dir.absolutePath(); -} - -QString OneSixInstance::createLaunchScript(AuthSessionPtr session) -{ - QString launchScript; - - if (!m_profile) - return nullptr; - - auto mainClass = getMainClass(); - if (!mainClass.isEmpty()) - { - launchScript += "mainClass " + mainClass + "\n"; - } - auto appletClass = m_profile->getAppletClass(); - if (!appletClass.isEmpty()) - { - launchScript += "appletClass " + appletClass + "\n"; - } - - // generic minecraft params - for (auto param : processMinecraftArgs(session)) - { - launchScript += "param " + param + "\n"; - } - - // window size, title and state, legacy - { - QString windowParams; - if (settings()->get("LaunchMaximized").toBool()) - windowParams = "max"; - else - windowParams = QString("%1x%2") - .arg(settings()->get("MinecraftWinWidth").toInt()) - .arg(settings()->get("MinecraftWinHeight").toInt()); - launchScript += "windowTitle " + windowTitle() + "\n"; - launchScript += "windowParams " + windowParams + "\n"; - } - - // legacy auth - if(session) - { - launchScript += "userName " + session->player_name + "\n"; - launchScript += "sessionId " + session->session + "\n"; - } - - // libraries and class path. - { - QStringList jars, nativeJars; - auto javaArchitecture = settings()->get("JavaArchitecture").toString(); - m_profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); - for(auto file: jars) - { - launchScript += "cp " + file + "\n"; - } - for(auto file: nativeJars) - { - launchScript += "ext " + file + "\n"; - } - launchScript += "natives " + getNativePath() + "\n"; - } - - for (auto trait : m_profile->getTraits()) - { - launchScript += "traits " + trait + "\n"; - } - launchScript += "launcher onesix\n"; - return launchScript; -} - -QStringList OneSixInstance::verboseDescription(AuthSessionPtr session) -{ - QStringList out; - out << "Main Class:" << " " + getMainClass() << ""; - out << "Native path:" << " " + getNativePath() << ""; - - - auto alltraits = traits(); - if(alltraits.size()) - { - out << "Traits:"; - for (auto trait : alltraits) - { - out << "traits " + trait; - } - out << ""; - } - - // libraries and class path. - { - out << "Libraries:"; - QStringList jars, nativeJars; - auto javaArchitecture = settings()->get("JavaArchitecture").toString(); - m_profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); - auto printLibFile = [&](const QString & path) - { - QFileInfo info(path); - if(info.exists()) - { - out << " " + path; - } - else - { - out << " " + path + " (missing)"; - } - }; - for(auto file: jars) - { - printLibFile(file); - } - out << ""; - out << "Native libraries:"; - for(auto file: nativeJars) - { - printLibFile(file); - } - out << ""; - } - - if(loaderModList()->size()) - { - out << "Mods:"; - for(auto & mod: loaderModList()->allMods()) - { - if(!mod.enabled()) - continue; - if(mod.type() == Mod::MOD_FOLDER) - continue; - // TODO: proper implementation would need to descend into folders. - - out << " " + mod.filename().completeBaseName(); - } - out << ""; - } - - if(coreModList()->size()) - { - out << "Core Mods:"; - for(auto & coremod: coreModList()->allMods()) - { - if(!coremod.enabled()) - continue; - if(coremod.type() == Mod::MOD_FOLDER) - continue; - // TODO: proper implementation would need to descend into folders. - - out << " " + coremod.filename().completeBaseName(); - } - out << ""; - } - - auto & jarMods = m_profile->getJarMods(); - if(jarMods.size()) - { - out << "Jar Mods:"; - for(auto & jarmod: jarMods) - { - auto displayname = jarmod->displayName(currentSystem); - auto realname = jarmod->filename(currentSystem); - if(displayname != realname) - { - out << " " + displayname + " (" + realname + ")"; - } - else - { - out << " " + realname; - } - } - out << ""; - } - - auto params = processMinecraftArgs(nullptr); - out << "Params:"; - out << " " + params.join(' '); - out << ""; - - QString windowParams; - if (settings()->get("LaunchMaximized").toBool()) - { - out << "Window size: max (if available)"; - } - else - { - auto width = settings()->get("MinecraftWinWidth").toInt(); - auto height = settings()->get("MinecraftWinHeight").toInt(); - out << "Window size: " + QString::number(width) + " x " + QString::number(height); - } - out << ""; - return out; -} - - -std::shared_ptr<LaunchStep> OneSixInstance::createMainLaunchStep(LaunchTask * parent, AuthSessionPtr session) -{ - auto method = launchMethod(); - if(method == "LauncherPart") - { - auto step = std::make_shared<LauncherPartLaunch>(parent); - step->setAuthSession(session); - step->setWorkingDirectory(minecraftRoot()); - return step; - } - else if (method == "DirectJava") - { - auto step = std::make_shared<DirectJavaLaunch>(parent); - step->setWorkingDirectory(minecraftRoot()); - step->setAuthSession(session); - return step; - } - return nullptr; -} - - -std::shared_ptr<Task> OneSixInstance::createJarModdingTask() -{ - class JarModTask : public Task - { - public: - explicit JarModTask(std::shared_ptr<OneSixInstance> inst) : Task(nullptr), m_inst(inst) - { - } - virtual void executeTask() - { - auto profile = m_inst->getMinecraftProfile(); - // nuke obsolete stripped jar(s) if needed - QString version_id = profile->getMinecraftVersion(); - if(!FS::ensureFolderPathExists(m_inst->binRoot())) - { - emitFailed(tr("Couldn't create the bin folder for Minecraft.jar")); - } - auto finalJarPath = QDir(m_inst->binRoot()).absoluteFilePath("minecraft.jar"); - QFile finalJar(finalJarPath); - if(finalJar.exists()) - { - if(!finalJar.remove()) - { - emitFailed(tr("Couldn't remove stale jar file: %1").arg(finalJarPath)); - return; - } - } - - // create temporary modded jar, if needed - auto jarMods = m_inst->getJarMods(); - if(jarMods.size()) - { - auto mainJar = profile->getMainJar(); - QStringList jars, temp1, temp2, temp3, temp4; - mainJar->getApplicableFiles(currentSystem, jars, temp1, temp2, temp3, m_inst->getLocalLibraryPath()); - auto sourceJarPath = jars[0]; - if(!MMCZip::createModdedJar(sourceJarPath, finalJarPath, jarMods)) - { - emitFailed(tr("Failed to create the custom Minecraft jar file.")); - return; - } - } - emitSucceeded(); - } - std::shared_ptr<OneSixInstance> m_inst; - }; - return std::make_shared<JarModTask>(std::dynamic_pointer_cast<OneSixInstance>(shared_from_this())); -} - -std::shared_ptr<ModList> OneSixInstance::loaderModList() const -{ - if (!m_loader_mod_list) - { - m_loader_mod_list.reset(new ModList(loaderModsDir())); - } - m_loader_mod_list->update(); - return m_loader_mod_list; -} - -std::shared_ptr<ModList> OneSixInstance::coreModList() const -{ - if (!m_core_mod_list) - { - m_core_mod_list.reset(new ModList(coreModsDir())); - } - m_core_mod_list->update(); - return m_core_mod_list; -} - -std::shared_ptr<ModList> OneSixInstance::resourcePackList() const -{ - if (!m_resource_pack_list) - { - m_resource_pack_list.reset(new ModList(resourcePacksDir())); - } - m_resource_pack_list->update(); - return m_resource_pack_list; -} - -std::shared_ptr<ModList> OneSixInstance::texturePackList() const -{ - if (!m_texture_pack_list) - { - m_texture_pack_list.reset(new ModList(texturePacksDir())); - } - m_texture_pack_list->update(); - return m_texture_pack_list; -} - -std::shared_ptr<WorldList> OneSixInstance::worldList() const -{ - if (!m_world_list) - { - m_world_list.reset(new WorldList(worldDir())); - } - return m_world_list; -} - -bool OneSixInstance::setIntendedVersionId(QString version) -{ - return setComponentVersion("net.minecraft", version); -} - -QString OneSixInstance::intendedVersionId() const -{ - return getComponentVersion("net.minecraft"); -} - -bool OneSixInstance::setComponentVersion(const QString& uid, const QString& version) -{ - if(uid == "net.minecraft") - { - settings()->set("IntendedVersion", version); - } - else if (uid == "org.lwjgl") - { - settings()->set("LWJGLVersion", version); - } - else if (uid == "net.minecraftforge") - { - settings()->set("ForgeVersion", version); - } - else if (uid == "com.mumfrey.liteloader") - { - settings()->set("LiteloaderVersion", version); - } - if(getMinecraftProfile()) - { - clearProfile(); - } - emit propertiesChanged(this); - return true; -} - -QString OneSixInstance::getComponentVersion(const QString& uid) const -{ - if(uid == "net.minecraft") - { - return settings()->get("IntendedVersion").toString(); - } - else if(uid == "org.lwjgl") - { - return settings()->get("LWJGLVersion").toString(); - } - else if(uid == "net.minecraftforge") - { - return settings()->get("ForgeVersion").toString(); - } - else if(uid == "com.mumfrey.liteloader") - { - return settings()->get("LiteloaderVersion").toString(); - } - return QString(); -} - -QList< Mod > OneSixInstance::getJarMods() const -{ - QList<Mod> mods; - for (auto jarmod : m_profile->getJarMods()) - { - QStringList jar, temp1, temp2, temp3; - jarmod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, jarmodsPath().absolutePath()); - // QString filePath = jarmodsPath().absoluteFilePath(jarmod->filename(currentSystem)); - mods.push_back(Mod(QFileInfo(jar[0]))); - } - return mods; -} - -void OneSixInstance::setShouldUpdate(bool) -{ -} - -bool OneSixInstance::shouldUpdate() const -{ - return true; -} - -QString OneSixInstance::currentVersionId() const -{ - return intendedVersionId(); -} - -void OneSixInstance::reloadProfile() -{ - m_profile->reload(); - setVersionBroken(m_profile->getProblemSeverity() == ProblemSeverity::Error); - emit versionReloaded(); -} - -void OneSixInstance::clearProfile() -{ - m_profile->clear(); - emit versionReloaded(); -} - -std::shared_ptr<MinecraftProfile> OneSixInstance::getMinecraftProfile() const -{ - return m_profile; -} - -QDir OneSixInstance::librariesPath() const -{ - return QDir::current().absoluteFilePath("libraries"); -} - -QDir OneSixInstance::jarmodsPath() const -{ - return QDir(jarModsDir()); -} - -QDir OneSixInstance::versionsPath() const -{ - return QDir::current().absoluteFilePath("versions"); -} - -bool OneSixInstance::providesVersionFile() const -{ - return false; -} - -bool OneSixInstance::reload() -{ - if (BaseInstance::reload()) - { - try - { - reloadProfile(); - return true; - } - catch (...) - { - return false; - } - } - return false; -} - -QString OneSixInstance::loaderModsDir() const -{ - return FS::PathCombine(minecraftRoot(), "mods"); -} - -QString OneSixInstance::coreModsDir() const -{ - return FS::PathCombine(minecraftRoot(), "coremods"); -} - -QString OneSixInstance::resourcePacksDir() const -{ - return FS::PathCombine(minecraftRoot(), "resourcepacks"); -} - -QString OneSixInstance::texturePacksDir() const -{ - return FS::PathCombine(minecraftRoot(), "texturepacks"); -} - -QString OneSixInstance::instanceConfigFolder() const -{ - return FS::PathCombine(minecraftRoot(), "config"); -} - -QString OneSixInstance::jarModsDir() const -{ - return FS::PathCombine(instanceRoot(), "jarmods"); -} - -QString OneSixInstance::libDir() const -{ - return FS::PathCombine(minecraftRoot(), "lib"); -} - -QString OneSixInstance::worldDir() const -{ - return FS::PathCombine(minecraftRoot(), "saves"); -} - -QStringList OneSixInstance::extraArguments() const -{ - auto list = BaseInstance::extraArguments(); - auto version = getMinecraftProfile(); - if (!version) - return list; - auto jarMods = getJarMods(); - if (!jarMods.isEmpty()) - { - list.append({"-Dfml.ignoreInvalidMinecraftCertificates=true", - "-Dfml.ignorePatchDiscrepancies=true"}); - } - return list; -} - -std::shared_ptr<OneSixInstance> OneSixInstance::getSharedPtr() -{ - return std::dynamic_pointer_cast<OneSixInstance>(BaseInstance::getSharedPtr()); -} - -QString OneSixInstance::typeName() const -{ - return tr("OneSix"); -} - -QStringList OneSixInstance::validLaunchMethods() -{ - return {"LauncherPart", "DirectJava"}; -} - -QStringList OneSixInstance::getClassPath() const -{ - QStringList jars, nativeJars; - auto javaArchitecture = settings()->get("JavaArchitecture").toString(); - m_profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); - return jars; -} - -QString OneSixInstance::getMainClass() const -{ - return m_profile->getMainClass(); -} - -QStringList OneSixInstance::getNativeJars() const -{ - QStringList jars, nativeJars; - auto javaArchitecture = settings()->get("JavaArchitecture").toString(); - m_profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); - return nativeJars; -} diff --git a/api/logic/minecraft/onesix/OneSixInstance.h b/api/logic/minecraft/onesix/OneSixInstance.h deleted file mode 100644 index bf12160e..00000000 --- a/api/logic/minecraft/onesix/OneSixInstance.h +++ /dev/null @@ -1,127 +0,0 @@ -/* Copyright 2013-2017 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. - */ - -#pragma once - -#include "minecraft/MinecraftInstance.h" - -#include "minecraft/MinecraftProfile.h" -#include "minecraft/ModList.h" - -#include "multimc_logic_export.h" - -class MULTIMC_LOGIC_EXPORT OneSixInstance : public MinecraftInstance -{ - Q_OBJECT -public: - explicit OneSixInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); - virtual ~OneSixInstance(){}; - - virtual void init() override; - - ////// Mod Lists ////// - std::shared_ptr<ModList> loaderModList() const; - std::shared_ptr<ModList> coreModList() const; - std::shared_ptr<ModList> resourcePackList() const override; - std::shared_ptr<ModList> texturePackList() const override; - std::shared_ptr<WorldList> worldList() const override; - virtual QList<Mod> getJarMods() const override; - virtual void createProfile(); - - virtual QSet<QString> traits() override; - - ////// Directories and files ////// - QString jarModsDir() const; - QString resourcePacksDir() const; - QString texturePacksDir() const; - QString loaderModsDir() const; - QString coreModsDir() const; - QString libDir() const; - QString worldDir() const; - virtual QString instanceConfigFolder() const override; - - virtual shared_qobject_ptr<Task> createUpdateTask() override; - virtual std::shared_ptr<Task> createJarModdingTask() override; - virtual QString createLaunchScript(AuthSessionPtr session) override; - QStringList verboseDescription(AuthSessionPtr session) override; - - virtual QString intendedVersionId() const override; - virtual bool setIntendedVersionId(QString version) override; - virtual QString currentVersionId() const override; - - QString getComponentVersion(const QString &uid) const; - bool setComponentVersion(const QString &uid, const QString &version); - - virtual bool shouldUpdate() const override; - virtual void setShouldUpdate(bool val) override; - - /** - * reload the profile, including version json files. - * - * throws various exceptions :3 - */ - void reloadProfile(); - - /// clears all version information in preparation for an update - void clearProfile(); - - /// get the current full version info - std::shared_ptr<MinecraftProfile> getMinecraftProfile() const; - - virtual QDir jarmodsPath() const; - virtual QDir librariesPath() const; - virtual QDir versionsPath() const; - virtual bool providesVersionFile() const; - - bool reload() override; - - virtual QStringList extraArguments() const override; - - std::shared_ptr<OneSixInstance> getSharedPtr(); - - virtual QString typeName() const override; - - bool canExport() const override - { - return true; - } - - QStringList getClassPath() const override; - QString getMainClass() const override; - - QStringList getNativeJars() const override; - QString getNativePath() const override; - - QString getLocalLibraryPath() const override; - - QStringList processMinecraftArgs(AuthSessionPtr account) const override; - -protected: - std::shared_ptr<LaunchStep> createMainLaunchStep(LaunchTask *parent, AuthSessionPtr session) override; - QStringList validLaunchMethods() override; - -signals: - void versionReloaded(); - -protected: - std::shared_ptr<MinecraftProfile> m_profile; - mutable std::shared_ptr<ModList> m_loader_mod_list; - mutable std::shared_ptr<ModList> m_core_mod_list; - mutable std::shared_ptr<ModList> m_resource_pack_list; - mutable std::shared_ptr<ModList> m_texture_pack_list; - mutable std::shared_ptr<WorldList> m_world_list; -}; - -Q_DECLARE_METATYPE(std::shared_ptr<OneSixInstance>) diff --git a/api/logic/minecraft/onesix/OneSixProfileStrategy.cpp b/api/logic/minecraft/onesix/OneSixProfileStrategy.cpp deleted file mode 100644 index ef2a7294..00000000 --- a/api/logic/minecraft/onesix/OneSixProfileStrategy.cpp +++ /dev/null @@ -1,407 +0,0 @@ -#include "OneSixProfileStrategy.h" -#include "OneSixInstance.h" -#include "OneSixVersionFormat.h" - -#include "Env.h" -#include <FileSystem.h> - -#include <QDir> -#include <QUuid> -#include <QJsonDocument> -#include <QJsonArray> -#include <QSaveFile> -#include <QResource> -#include <meta/Index.h> -#include <meta/Version.h> - -#include <tuple> - -OneSixProfileStrategy::OneSixProfileStrategy(OneSixInstance* instance) -{ - m_instance = instance; -} - -void OneSixProfileStrategy::upgradeDeprecatedFiles() -{ - auto versionJsonPath = FS::PathCombine(m_instance->instanceRoot(), "version.json"); - auto customJsonPath = FS::PathCombine(m_instance->instanceRoot(), "custom.json"); - auto mcJson = FS::PathCombine(m_instance->instanceRoot(), "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" << m_instance->name(); - return; - } - if(!renameFile.isEmpty() && QFile::exists(renameFile)) - { - if(!QFile::rename(renameFile, renameFile + ".old")) - { - qWarning() << "Couldn't rename" << renameFile << "to" << renameFile + ".old" << "in" << m_instance->name(); - return; - } - } - auto file = ProfileUtils::parseJsonFile(QFileInfo(sourceFile), false); - ProfileUtils::removeLwjglFromPatch(file); - file->uid = "net.minecraft"; - file->version = file->minecraftVersion; - file->name = "Minecraft"; - auto data = OneSixVersionFormat::versionFileToJson(file, false).toJson(); - QSaveFile newPatchFile(mcJson); - if(!newPatchFile.open(QIODevice::WriteOnly)) - { - newPatchFile.cancelWriting(); - qWarning() << "Couldn't open main patch for writing in" << m_instance->name(); - return; - } - newPatchFile.write(data); - if(!newPatchFile.commit()) - { - qWarning() << "Couldn't save main patch in" << m_instance->name(); - return; - } - if(!QFile::rename(sourceFile, sourceFile + ".old")) - { - qWarning() << "Couldn't rename" << sourceFile << "to" << sourceFile + ".old" << "in" << m_instance->name(); - return; - } - } -} - -void OneSixProfileStrategy::loadDefaultBuiltinPatches() -{ - auto addBuiltinPatch = [&](const QString &uid, const QString intendedVersion, int order) - { - auto jsonFilePath = FS::PathCombine(m_instance->instanceRoot(), "patches" , uid + ".json"); - // load up the base minecraft patch - ProfilePatchPtr profilePatch; - if(QFile::exists(jsonFilePath)) - { - auto file = ProfileUtils::parseJsonFile(QFileInfo(jsonFilePath), false); - if(file->version.isEmpty()) - { - file->version = intendedVersion; - } - profilePatch = std::make_shared<ProfilePatch>(file, jsonFilePath); - profilePatch->setVanilla(false); - profilePatch->setRevertible(true); - } - else - { - auto metaVersion = ENV.metadataIndex()->get(uid, intendedVersion); - profilePatch = std::make_shared<ProfilePatch>(metaVersion); - profilePatch->setVanilla(true); - } - profilePatch->setOrder(order); - profile->appendPatch(profilePatch); - }; - addBuiltinPatch("net.minecraft", m_instance->getComponentVersion("net.minecraft"), -2); - addBuiltinPatch("org.lwjgl", m_instance->getComponentVersion("org.lwjgl"), -1); -} - -void OneSixProfileStrategy::loadUserPatches() -{ - // first, collect all patches (that are not builtins of OneSix) and load them - QMap<QString, ProfilePatchPtr> loadedPatches; - QDir patchesDir(FS::PathCombine(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); - // ignore builtins - if (file->uid == "net.minecraft") - continue; - if (file->uid == "org.lwjgl") - continue; - auto patch = std::make_shared<ProfilePatch>(file, info.filePath()); - patch->setRemovable(true); - patch->setMovable(true); - if(ENV.metadataIndex()->hasUid(file->uid)) - { - // FIXME: requesting a uid/list creates it in the index... this allows reverting to possibly invalid versions... - patch->setRevertible(true); - } - loadedPatches[file->uid] = patch; - } - // these are 'special'... if not already loaded from instance files, grab them from the metadata repo. - auto loadSpecial = [&](const QString & uid, int order) - { - auto patchVersion = m_instance->getComponentVersion(uid); - if(!patchVersion.isEmpty() && !loadedPatches.contains(uid)) - { - auto patch = std::make_shared<ProfilePatch>(ENV.metadataIndex()->get(uid, patchVersion)); - patch->setOrder(order); - patch->setVanilla(true); - patch->setRemovable(true); - patch->setMovable(true); - loadedPatches[uid] = patch; - } - }; - loadSpecial("net.minecraftforge", 5); - loadSpecial("com.mumfrey.liteloader", 10); - - // now add all the patches by user sort order - ProfileUtils::PatchOrder userOrder; - ProfileUtils::readOverrideOrders(FS::PathCombine(m_instance->instanceRoot(), "order.json"), userOrder); - for (auto uid : userOrder) - { - // ignore builtins - if (uid == "net.minecraft") - continue; - if (uid == "org.lwjgl") - continue; - // ordering has a patch that is gone? - if(!loadedPatches.contains(uid)) - { - continue; - } - profile->appendPatch(loadedPatches.take(uid)); - } - - // is there anything left to sort? - if(loadedPatches.isEmpty()) - { - // TODO: save the order here? - return; - } - - // inserting into multimap by order number as key sorts the patches and detects duplicates - QMultiMap<int, ProfilePatchPtr> files; - auto iter = loadedPatches.begin(); - while(iter != loadedPatches.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 - profile->appendPatch(value); - } - } - // TODO: save the order here? -} - - -void OneSixProfileStrategy::load() -{ - profile->clearPatches(); - - upgradeDeprecatedFiles(); - loadDefaultBuiltinPatches(); - loadUserPatches(); -} - -bool OneSixProfileStrategy::saveOrder(ProfileUtils::PatchOrder order) -{ - return ProfileUtils::writeOverrideOrders(FS::PathCombine(m_instance->instanceRoot(), "order.json"), order); -} - -bool OneSixProfileStrategy::resetOrder() -{ - return QDir(m_instance->instanceRoot()).remove("order.json"); -} - -bool OneSixProfileStrategy::removePatch(ProfilePatchPtr 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; - } - } - if(!m_instance->getComponentVersion(patch->getID()).isEmpty()) - { - m_instance->setComponentVersion(patch->getID(), QString()); - } - - // 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, 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 &jarMods = patch->getVersionFile()->jarMods; - for(auto &jarmod: jarMods) - { - ok &= preRemoveJarMod(jarmod); - } - return ok; -} - -bool OneSixProfileStrategy::customizePatch(ProfilePatchPtr patch) -{ - if(patch->isCustom()) - { - return false; - } - - auto filename = FS::PathCombine(m_instance->instanceRoot(), "patches" , patch->getID() + ".json"); - 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 = patch->getVersionFile(); - if(!vfile) - { - return false; - } - auto document = OneSixVersionFormat::versionFileToJson(vfile, true); - jsonFile.write(document.toJson()); - if(!jsonFile.commit()) - { - return false; - } - load(); - } - catch (Exception &error) - { - qWarning() << "Version could not be loaded:" << error.cause(); - } - return true; -} - -bool OneSixProfileStrategy::revertPatch(ProfilePatchPtr patch) -{ - if(!patch->isCustom()) - { - // already not custom - return true; - } - auto filename = patch->getFilename(); - if(!QFile::exists(filename)) - { - // already gone / not custom - return true; - } - // just kill the file and reload - bool result = QFile::remove(filename); - // FIXME: get rid of this try-catch. - try - { - load(); - } - catch (Exception &error) - { - qWarning() << "Version could not be loaded:" << error.cause(); - } - return result; -} - -bool OneSixProfileStrategy::installJarMods(QStringList filepaths) -{ - QString patchDir = FS::PathCombine(m_instance->instanceRoot(), "patches"); - if(!FS::ensureFolderPathExists(patchDir)) - { - return false; - } - - if (!FS::ensureFolderPathExists(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(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; - f->order = profile->getFreeOrderNumber(); - 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, true).toJson()); - file.close(); - - auto patch = std::make_shared<ProfilePatch>(f, patchFileName); - patch->setMovable(true); - patch->setRemovable(true); - profile->appendPatch(patch); - } - profile->saveCurrentOrder(); - profile->reapplyPatches(); - return true; -} - diff --git a/api/logic/minecraft/onesix/OneSixProfileStrategy.h b/api/logic/minecraft/onesix/OneSixProfileStrategy.h deleted file mode 100644 index 96c1ba7b..00000000 --- a/api/logic/minecraft/onesix/OneSixProfileStrategy.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once -#include "minecraft/ProfileStrategy.h" - -class OneSixInstance; - -class OneSixProfileStrategy : public ProfileStrategy -{ -public: - OneSixProfileStrategy(OneSixInstance * instance); - virtual ~OneSixProfileStrategy() {}; - virtual void load() override; - virtual bool resetOrder() override; - virtual bool saveOrder(ProfileUtils::PatchOrder order) override; - virtual bool installJarMods(QStringList filepaths) override; - virtual bool removePatch(ProfilePatchPtr patch) override; - virtual bool customizePatch(ProfilePatchPtr patch) override; - virtual bool revertPatch(ProfilePatchPtr patch) override; - -protected: - virtual void loadDefaultBuiltinPatches(); - virtual void loadUserPatches(); - void upgradeDeprecatedFiles(); - -protected: - OneSixInstance *m_instance; -}; diff --git a/api/logic/minecraft/onesix/update/AssetUpdateTask.cpp b/api/logic/minecraft/update/AssetUpdateTask.cpp index 21600ff0..2ad2b5b2 100644 --- a/api/logic/minecraft/onesix/update/AssetUpdateTask.cpp +++ b/api/logic/minecraft/update/AssetUpdateTask.cpp @@ -1,17 +1,19 @@ #include "Env.h" #include "AssetUpdateTask.h" -#include "minecraft/onesix/OneSixInstance.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/ComponentList.h" #include "net/ChecksumValidator.h" #include "minecraft/AssetsUtils.h" -AssetUpdateTask::AssetUpdateTask(OneSixInstance * inst) +AssetUpdateTask::AssetUpdateTask(MinecraftInstance * inst) { m_inst = inst; } void AssetUpdateTask::executeTask() { setStatus(tr("Updating assets index...")); - auto profile = m_inst->getMinecraftProfile(); + auto components = m_inst->getComponentList(); + auto profile = components->getProfile(); auto assets = profile->getMinecraftAssets(); QUrl indexUrl = assets->url; QString localPath = assets->id + ".json"; @@ -47,7 +49,8 @@ void AssetUpdateTask::assetIndexFinished() AssetsIndex index; qDebug() << m_inst->name() << ": Finished asset index download"; - auto profile = m_inst->getMinecraftProfile(); + auto components = m_inst->getComponentList(); + auto profile = components->getProfile(); auto assets = profile->getMinecraftAssets(); QString asset_fname = "assets/indexes/" + assets->id + ".json"; diff --git a/api/logic/minecraft/onesix/update/AssetUpdateTask.h b/api/logic/minecraft/update/AssetUpdateTask.h index dff72571..c666faa6 100644 --- a/api/logic/minecraft/onesix/update/AssetUpdateTask.h +++ b/api/logic/minecraft/update/AssetUpdateTask.h @@ -1,12 +1,13 @@ #pragma once #include "tasks/Task.h" #include "net/NetJob.h" -class OneSixInstance; +class MinecraftInstance; class AssetUpdateTask : public Task { + Q_OBJECT public: - AssetUpdateTask(OneSixInstance * inst); + AssetUpdateTask(MinecraftInstance * inst); void executeTask() override; bool canAbort() const override; @@ -20,6 +21,6 @@ public slots: bool abort() override; private: - OneSixInstance *m_inst; + MinecraftInstance *m_inst; NetJobPtr downloadJob; }; diff --git a/api/logic/minecraft/onesix/update/FMLLibrariesTask.cpp b/api/logic/minecraft/update/FMLLibrariesTask.cpp index 1cbee95e..1bd339e4 100644 --- a/api/logic/minecraft/onesix/update/FMLLibrariesTask.cpp +++ b/api/logic/minecraft/update/FMLLibrariesTask.cpp @@ -2,26 +2,27 @@ #include <FileSystem.h> #include <minecraft/VersionFilterData.h> #include "FMLLibrariesTask.h" -#include "minecraft/onesix/OneSixInstance.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/ComponentList.h" - -FMLLibrariesTask::FMLLibrariesTask(OneSixInstance * inst) +FMLLibrariesTask::FMLLibrariesTask(MinecraftInstance * inst) { m_inst = inst; } void FMLLibrariesTask::executeTask() { // Get the mod list - OneSixInstance *inst = (OneSixInstance *)m_inst; - std::shared_ptr<MinecraftProfile> profile = inst->getMinecraftProfile(); - bool forge_present = false; + MinecraftInstance *inst = (MinecraftInstance *)m_inst; + auto components = inst->getComponentList(); + auto profile = components->getProfile(); if (!profile->hasTrait("legacyFML")) { emitSucceeded(); + return; } - QString version = inst->intendedVersionId(); + QString version = components->getComponentVersion("net.minecraft"); auto &fmlLibsMapping = g_VersionFilterData.fmlLibsMapping; if (!fmlLibsMapping.contains(version)) { @@ -33,9 +34,7 @@ void FMLLibrariesTask::executeTask() // determine if we need some libs for FML or forge setStatus(tr("Checking for FML libraries...")); - forge_present = (profile->versionPatch("net.minecraftforge") != nullptr); - // we don't... - if (!forge_present) + if(!components->getComponent("net.minecraftforge")) { emitSucceeded(); return; @@ -87,7 +86,7 @@ void FMLLibrariesTask::fmllibsFinished() if (!fmlLibsToProcess.isEmpty()) { setStatus(tr("Copying FML libraries into the instance...")); - OneSixInstance *inst = (OneSixInstance *)m_inst; + MinecraftInstance *inst = (MinecraftInstance *)m_inst; auto metacache = ENV.metacache(); int index = 0; for (auto &lib : fmlLibsToProcess) diff --git a/api/logic/minecraft/onesix/update/FMLLibrariesTask.h b/api/logic/minecraft/update/FMLLibrariesTask.h index d1c250e4..10f48f5b 100644 --- a/api/logic/minecraft/onesix/update/FMLLibrariesTask.h +++ b/api/logic/minecraft/update/FMLLibrariesTask.h @@ -1,12 +1,13 @@ #pragma once #include "tasks/Task.h" #include "net/NetJob.h" -class OneSixInstance; +class MinecraftInstance; class FMLLibrariesTask : public Task { + Q_OBJECT public: - FMLLibrariesTask(OneSixInstance * inst); + FMLLibrariesTask(MinecraftInstance * inst); void executeTask() override; @@ -20,7 +21,7 @@ public slots: bool abort() override; private: - OneSixInstance *m_inst; + MinecraftInstance *m_inst; NetJobPtr downloadJob; QList<FMLlib> fmlLibsToProcess; }; diff --git a/api/logic/minecraft/onesix/update/FoldersTask.cpp b/api/logic/minecraft/update/FoldersTask.cpp index 239a2675..34e2292a 100644 --- a/api/logic/minecraft/onesix/update/FoldersTask.cpp +++ b/api/logic/minecraft/update/FoldersTask.cpp @@ -1,8 +1,9 @@ #include "FoldersTask.h" -#include "minecraft/onesix/OneSixInstance.h" +#include "minecraft/MinecraftInstance.h" #include <QDir> -FoldersTask::FoldersTask(OneSixInstance * inst) +FoldersTask::FoldersTask(MinecraftInstance * inst) + :Task() { m_inst = inst; } diff --git a/api/logic/minecraft/onesix/update/FoldersTask.h b/api/logic/minecraft/update/FoldersTask.h index 552d3098..6e669b1e 100644 --- a/api/logic/minecraft/onesix/update/FoldersTask.h +++ b/api/logic/minecraft/update/FoldersTask.h @@ -2,13 +2,14 @@ #include "tasks/Task.h" -class OneSixInstance; +class MinecraftInstance; class FoldersTask : public Task { + Q_OBJECT public: - FoldersTask(OneSixInstance * inst); + FoldersTask(MinecraftInstance * inst); void executeTask() override; private: - OneSixInstance *m_inst; + MinecraftInstance *m_inst; }; diff --git a/api/logic/minecraft/onesix/update/LibrariesTask.cpp b/api/logic/minecraft/update/LibrariesTask.cpp index 2cd41ded..0bec61c1 100644 --- a/api/logic/minecraft/onesix/update/LibrariesTask.cpp +++ b/api/logic/minecraft/update/LibrariesTask.cpp @@ -1,8 +1,9 @@ #include "Env.h" #include "LibrariesTask.h" -#include "minecraft/onesix/OneSixInstance.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/ComponentList.h" -LibrariesTask::LibrariesTask(OneSixInstance * inst) +LibrariesTask::LibrariesTask(MinecraftInstance * inst) { m_inst = inst; } @@ -11,16 +12,11 @@ void LibrariesTask::executeTask() { setStatus(tr("Getting the library files from Mojang...")); qDebug() << m_inst->name() << ": downloading libraries"; - OneSixInstance *inst = (OneSixInstance *)m_inst; - inst->reloadProfile(); - if(inst->hasVersionBroken()) - { - emitFailed(tr("Failed to load the version description files - check the instance for errors.")); - return; - } + MinecraftInstance *inst = (MinecraftInstance *)m_inst; // Build a list of URLs that will need to be downloaded. - std::shared_ptr<MinecraftProfile> profile = inst->getMinecraftProfile(); + auto components = inst->getComponentList(); + auto profile = components->getProfile(); auto job = new NetJob(tr("Libraries for instance %1").arg(inst->name())); downloadJob.reset(job); diff --git a/api/logic/minecraft/onesix/update/LibrariesTask.h b/api/logic/minecraft/update/LibrariesTask.h index 80cf0d2a..d06a5037 100644 --- a/api/logic/minecraft/onesix/update/LibrariesTask.h +++ b/api/logic/minecraft/update/LibrariesTask.h @@ -1,12 +1,13 @@ #pragma once #include "tasks/Task.h" #include "net/NetJob.h" -class OneSixInstance; +class MinecraftInstance; class LibrariesTask : public Task { + Q_OBJECT public: - LibrariesTask(OneSixInstance * inst); + LibrariesTask(MinecraftInstance * inst); void executeTask() override; @@ -19,6 +20,6 @@ public slots: bool abort() override; private: - OneSixInstance *m_inst; + MinecraftInstance *m_inst; NetJobPtr downloadJob; }; |