summaryrefslogtreecommitdiffstats
path: root/api/logic/minecraft
diff options
context:
space:
mode:
Diffstat (limited to 'api/logic/minecraft')
-rw-r--r--api/logic/minecraft/AssetsUtils.cpp2
-rw-r--r--api/logic/minecraft/AssetsUtils.h2
-rw-r--r--api/logic/minecraft/Component.cpp439
-rw-r--r--api/logic/minecraft/Component.h111
-rw-r--r--api/logic/minecraft/ComponentList.cpp1204
-rw-r--r--api/logic/minecraft/ComponentList.h146
-rw-r--r--api/logic/minecraft/ComponentList_p.h42
-rw-r--r--api/logic/minecraft/ComponentUpdateTask.cpp691
-rw-r--r--api/logic/minecraft/ComponentUpdateTask.h37
-rw-r--r--api/logic/minecraft/ComponentUpdateTask_p.h32
-rw-r--r--api/logic/minecraft/LaunchProfile.cpp297
-rw-r--r--api/logic/minecraft/LaunchProfile.h99
-rw-r--r--api/logic/minecraft/Library.cpp74
-rw-r--r--api/logic/minecraft/Library_test.cpp8
-rw-r--r--api/logic/minecraft/MinecraftInstance.cpp620
-rw-r--r--api/logic/minecraft/MinecraftInstance.h120
-rw-r--r--api/logic/minecraft/MinecraftLoadAndCheck.cpp45
-rw-r--r--api/logic/minecraft/MinecraftLoadAndCheck.h47
-rw-r--r--api/logic/minecraft/MinecraftProfile.cpp681
-rw-r--r--api/logic/minecraft/MinecraftProfile.h211
-rw-r--r--api/logic/minecraft/MinecraftUpdate.cpp (renamed from api/logic/minecraft/onesix/OneSixUpdate.cpp)83
-rw-r--r--api/logic/minecraft/MinecraftUpdate.h (renamed from api/logic/minecraft/onesix/OneSixUpdate.h)10
-rw-r--r--api/logic/minecraft/Mod.cpp2
-rw-r--r--api/logic/minecraft/Mod.h5
-rw-r--r--api/logic/minecraft/ModList.cpp2
-rw-r--r--api/logic/minecraft/ModList.h2
-rw-r--r--api/logic/minecraft/MojangVersionFormat.cpp2
-rw-r--r--api/logic/minecraft/OneSixVersionFormat.cpp (renamed from api/logic/minecraft/onesix/OneSixVersionFormat.cpp)56
-rw-r--r--api/logic/minecraft/OneSixVersionFormat.h (renamed from api/logic/minecraft/onesix/OneSixVersionFormat.h)4
-rw-r--r--api/logic/minecraft/OpSys.cpp2
-rw-r--r--api/logic/minecraft/OpSys.h2
-rw-r--r--api/logic/minecraft/ProfilePatch.cpp188
-rw-r--r--api/logic/minecraft/ProfilePatch.h70
-rw-r--r--api/logic/minecraft/ProfileStrategy.h36
-rw-r--r--api/logic/minecraft/ProfileUtils.cpp53
-rw-r--r--api/logic/minecraft/ProfileUtils.h3
-rw-r--r--api/logic/minecraft/Rule.cpp2
-rw-r--r--api/logic/minecraft/Rule.h2
-rw-r--r--api/logic/minecraft/SkinUpload.cpp4
-rw-r--r--api/logic/minecraft/SkinUpload.h4
-rw-r--r--api/logic/minecraft/VersionFile.cpp4
-rw-r--r--api/logic/minecraft/VersionFile.h28
-rw-r--r--api/logic/minecraft/World.cpp4
-rw-r--r--api/logic/minecraft/World.h2
-rw-r--r--api/logic/minecraft/WorldList.cpp2
-rw-r--r--api/logic/minecraft/WorldList.h2
-rw-r--r--api/logic/minecraft/auth/MojangAccount.cpp29
-rw-r--r--api/logic/minecraft/auth/MojangAccount.h2
-rw-r--r--api/logic/minecraft/auth/MojangAccountList.cpp75
-rw-r--r--api/logic/minecraft/auth/MojangAccountList.h2
-rw-r--r--api/logic/minecraft/auth/YggdrasilTask.cpp3
-rw-r--r--api/logic/minecraft/auth/YggdrasilTask.h2
-rw-r--r--api/logic/minecraft/auth/flows/AuthenticateTask.cpp2
-rw-r--r--api/logic/minecraft/auth/flows/AuthenticateTask.h2
-rw-r--r--api/logic/minecraft/auth/flows/RefreshTask.cpp2
-rw-r--r--api/logic/minecraft/auth/flows/RefreshTask.h2
-rw-r--r--api/logic/minecraft/auth/flows/ValidateTask.cpp2
-rw-r--r--api/logic/minecraft/auth/flows/ValidateTask.h2
-rw-r--r--api/logic/minecraft/flame/FileResolvingTask.cpp46
-rw-r--r--api/logic/minecraft/flame/PackManifest.cpp1
-rw-r--r--api/logic/minecraft/flame/PackManifest.h15
-rw-r--r--api/logic/minecraft/forge/ForgeXzDownload.cpp2
-rw-r--r--api/logic/minecraft/forge/ForgeXzDownload.h2
-rw-r--r--api/logic/minecraft/ftb/FTBInstanceProvider.cpp262
-rw-r--r--api/logic/minecraft/ftb/FTBInstanceProvider.h45
-rw-r--r--api/logic/minecraft/ftb/FTBPlugin.cpp115
-rw-r--r--api/logic/minecraft/ftb/FTBPlugin.h12
-rw-r--r--api/logic/minecraft/ftb/FTBProfileStrategy.cpp129
-rw-r--r--api/logic/minecraft/ftb/FTBProfileStrategy.h21
-rw-r--r--api/logic/minecraft/ftb/LegacyFTBInstance.cpp24
-rw-r--r--api/logic/minecraft/ftb/LegacyFTBInstance.h17
-rw-r--r--api/logic/minecraft/ftb/OneSixFTBInstance.cpp135
-rw-r--r--api/logic/minecraft/ftb/OneSixFTBInstance.h30
-rw-r--r--api/logic/minecraft/launch/ClaimAccount.h2
-rw-r--r--api/logic/minecraft/launch/CreateServerResourcePacksFolder.h2
-rw-r--r--api/logic/minecraft/launch/DirectJavaLaunch.cpp2
-rw-r--r--api/logic/minecraft/launch/DirectJavaLaunch.h2
-rw-r--r--api/logic/minecraft/launch/ExtractNatives.cpp2
-rw-r--r--api/logic/minecraft/launch/ExtractNatives.h2
-rw-r--r--api/logic/minecraft/launch/LauncherPartLaunch.cpp67
-rw-r--r--api/logic/minecraft/launch/LauncherPartLaunch.h2
-rw-r--r--api/logic/minecraft/launch/ModMinecraftJar.cpp70
-rw-r--r--api/logic/minecraft/launch/ModMinecraftJar.h9
-rw-r--r--api/logic/minecraft/launch/PrintInstanceInfo.cpp70
-rw-r--r--api/logic/minecraft/launch/PrintInstanceInfo.h2
-rw-r--r--api/logic/minecraft/legacy/LegacyInstance.cpp359
-rw-r--r--api/logic/minecraft/legacy/LegacyInstance.h115
-rw-r--r--api/logic/minecraft/legacy/LegacyModList.cpp533
-rw-r--r--api/logic/minecraft/legacy/LegacyModList.h112
-rw-r--r--api/logic/minecraft/legacy/LegacyUpdate.cpp399
-rw-r--r--api/logic/minecraft/legacy/LegacyUpdate.h70
-rw-r--r--api/logic/minecraft/legacy/LegacyUpgradeTask.cpp142
-rw-r--r--api/logic/minecraft/legacy/LegacyUpgradeTask.h38
-rw-r--r--api/logic/minecraft/legacy/LwjglVersionList.cpp169
-rw-r--r--api/logic/minecraft/legacy/LwjglVersionList.h116
-rw-r--r--api/logic/minecraft/onesix/OneSixInstance.cpp698
-rw-r--r--api/logic/minecraft/onesix/OneSixInstance.h127
-rw-r--r--api/logic/minecraft/onesix/OneSixProfileStrategy.cpp407
-rw-r--r--api/logic/minecraft/onesix/OneSixProfileStrategy.h26
-rw-r--r--api/logic/minecraft/update/AssetUpdateTask.cpp (renamed from api/logic/minecraft/onesix/update/AssetUpdateTask.cpp)11
-rw-r--r--api/logic/minecraft/update/AssetUpdateTask.h (renamed from api/logic/minecraft/onesix/update/AssetUpdateTask.h)7
-rw-r--r--api/logic/minecraft/update/FMLLibrariesTask.cpp (renamed from api/logic/minecraft/onesix/update/FMLLibrariesTask.cpp)21
-rw-r--r--api/logic/minecraft/update/FMLLibrariesTask.h (renamed from api/logic/minecraft/onesix/update/FMLLibrariesTask.h)7
-rw-r--r--api/logic/minecraft/update/FoldersTask.cpp (renamed from api/logic/minecraft/onesix/update/FoldersTask.cpp)5
-rw-r--r--api/logic/minecraft/update/FoldersTask.h (renamed from api/logic/minecraft/onesix/update/FoldersTask.h)7
-rw-r--r--api/logic/minecraft/update/LibrariesTask.cpp (renamed from api/logic/minecraft/onesix/update/LibrariesTask.cpp)16
-rw-r--r--api/logic/minecraft/update/LibrariesTask.h (renamed from api/logic/minecraft/onesix/update/LibrariesTask.h)7
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;
};