diff options
Diffstat (limited to 'logic/minecraft')
-rw-r--r-- | logic/minecraft/MinecraftVersion.h | 92 | ||||
-rw-r--r-- | logic/minecraft/MinecraftVersionList.cpp | 330 | ||||
-rw-r--r-- | logic/minecraft/MinecraftVersionList.h | 76 | ||||
-rw-r--r-- | logic/minecraft/OneSixLibrary.cpp | 274 | ||||
-rw-r--r-- | logic/minecraft/OneSixLibrary.h | 148 | ||||
-rw-r--r-- | logic/minecraft/OneSixRule.cpp | 89 | ||||
-rw-r--r-- | logic/minecraft/OneSixRule.h | 98 | ||||
-rw-r--r-- | logic/minecraft/OneSixVersionBuilder.cpp | 249 | ||||
-rw-r--r-- | logic/minecraft/OneSixVersionBuilder.h | 46 | ||||
-rw-r--r-- | logic/minecraft/OpSys.cpp | 42 | ||||
-rw-r--r-- | logic/minecraft/OpSys.h | 37 | ||||
-rw-r--r-- | logic/minecraft/VersionFile.cpp | 635 | ||||
-rw-r--r-- | logic/minecraft/VersionFile.h | 145 | ||||
-rw-r--r-- | logic/minecraft/VersionFinal.cpp | 429 | ||||
-rw-r--r-- | logic/minecraft/VersionFinal.h | 172 |
15 files changed, 2862 insertions, 0 deletions
diff --git a/logic/minecraft/MinecraftVersion.h b/logic/minecraft/MinecraftVersion.h new file mode 100644 index 00000000..3be25912 --- /dev/null +++ b/logic/minecraft/MinecraftVersion.h @@ -0,0 +1,92 @@ +/* Copyright 2013 Andrew Okin + * + * 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 "logic/BaseVersion.h" +#include <QStringList> +#include <QSet> + +struct MinecraftVersion : public BaseVersion +{ + /// The version's timestamp - this is primarily used for sorting versions in a list. + qint64 timestamp; + + /// The URL that this version will be downloaded from. maybe. + QString download_url; + + /// is this the latest version? + bool is_latest = false; + + /// is this a snapshot? + bool is_snapshot = false; + + /// is this a built-in version that comes with MultiMC? + bool is_builtin = false; + + /// the human readable version name + QString m_name; + + /// the version ID. + QString m_descriptor; + + /// version traits. generally launcher business... + QSet<QString> m_traits; + + /// The main class this version uses (if any, can be empty). + QString m_mainClass; + + /// The applet class this version uses (if any, can be empty). + QString m_appletClass; + + bool usesLegacyLauncher() + { + return m_traits.contains("legacyLaunch") || m_traits.contains("aplhaLaunch"); + } + + virtual QString descriptor() override + { + return m_descriptor; + } + + virtual QString name() override + { + return m_name; + } + + virtual QString typeString() const override + { + if (is_latest && is_snapshot) + { + return QObject::tr("Latest snapshot"); + } + else if(is_latest) + { + return QObject::tr("Latest release"); + } + else if(is_snapshot) + { + return QObject::tr("Snapshot"); + } + else if(is_builtin) + { + return QObject::tr("Museum piece"); + } + else + { + return QObject::tr("Regular release"); + } + } +}; diff --git a/logic/minecraft/MinecraftVersionList.cpp b/logic/minecraft/MinecraftVersionList.cpp new file mode 100644 index 00000000..cdf5fa77 --- /dev/null +++ b/logic/minecraft/MinecraftVersionList.cpp @@ -0,0 +1,330 @@ +/* Copyright 2013 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 "MinecraftVersionList.h" +#include "MultiMC.h" +#include "logic/net/URLConstants.h" +#include <logic/MMCJson.h> + +#include <QtXml> + +#include <QJsonDocument> +#include <QJsonObject> +#include <QJsonArray> +#include <QJsonValue> +#include <QJsonParseError> + +#include <QtAlgorithms> + +#include <QtNetwork> + +inline QDateTime timeFromS3Time(QString str) +{ + return QDateTime::fromString(str, Qt::ISODate); +} + +MinecraftVersionList::MinecraftVersionList(QObject *parent) : BaseVersionList(parent) +{ + loadBuiltinList(); +} + +Task *MinecraftVersionList::getLoadTask() +{ + return new MCVListLoadTask(this); +} + +bool MinecraftVersionList::isLoaded() +{ + return m_loaded; +} + +const BaseVersionPtr MinecraftVersionList::at(int i) const +{ + return m_vlist.at(i); +} + +int MinecraftVersionList::count() const +{ + return m_vlist.count(); +} + +static bool cmpVersions(BaseVersionPtr first, BaseVersionPtr second) +{ + auto left = std::dynamic_pointer_cast<MinecraftVersion>(first); + auto right = std::dynamic_pointer_cast<MinecraftVersion>(second); + return left->timestamp > right->timestamp; +} + +void MinecraftVersionList::sortInternal() +{ + qSort(m_vlist.begin(), m_vlist.end(), cmpVersions); +} + +void MinecraftVersionList::loadBuiltinList() +{ + // grab the version list data from internal resources. + QResource versionList(":/versions/minecraft.json"); + QFile filez(versionList.absoluteFilePath()); + filez.open(QIODevice::ReadOnly); + auto data = filez.readAll(); + + // parse the data as json + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + QJsonObject root = jsonDoc.object(); + + // parse all the versions + for (const auto version : MMCJson::ensureArray(root.value("versions"))) + { + QJsonObject versionObj = version.toObject(); + QString versionID = versionObj.value("id").toString(""); + QString versionTimeStr = versionObj.value("releaseTime").toString(""); + QString versionTypeStr = versionObj.value("type").toString(""); + QSet<QString> traits; + if (versionObj.contains("+traits")) + { + for (auto traitVal : MMCJson::ensureArray(versionObj.value("+traits"))) + { + traits.insert(MMCJson::ensureString(traitVal)); + } + } + if (versionID.isEmpty() || versionTimeStr.isEmpty() || versionTypeStr.isEmpty()) + { + // FIXME: log this somewhere + continue; + } + // Parse the timestamp. + QDateTime versionTime = timeFromS3Time(versionTimeStr); + if (!versionTime.isValid()) + { + // FIXME: log this somewhere + continue; + } + // Get the download URL. + QString dlUrl = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + versionID + "/"; + + // main class and applet class + QString mainClass = versionObj.value("type").toString(""); + QString appletClass = versionObj.value("type").toString(""); + + // Now, we construct the version object and add it to the list. + std::shared_ptr<MinecraftVersion> mcVersion(new MinecraftVersion()); + mcVersion->m_name = mcVersion->m_descriptor = versionID; + mcVersion->timestamp = versionTime.toMSecsSinceEpoch(); + mcVersion->download_url = dlUrl; + mcVersion->is_builtin = true; + mcVersion->m_appletClass = appletClass; + mcVersion->m_mainClass = mainClass; + mcVersion->m_traits = traits; + m_vlist.append(mcVersion); + } +} + +void MinecraftVersionList::sort() +{ + beginResetModel(); + sortInternal(); + endResetModel(); +} + +BaseVersionPtr MinecraftVersionList::getLatestStable() const +{ + for (int i = 0; i < m_vlist.length(); i++) + { + auto ver = std::dynamic_pointer_cast<MinecraftVersion>(m_vlist.at(i)); + if (ver->is_latest && !ver->is_snapshot) + { + return m_vlist.at(i); + } + } + return BaseVersionPtr(); +} + +void MinecraftVersionList::updateListData(QList<BaseVersionPtr> versions) +{ + beginResetModel(); + for (auto version : versions) + { + auto descr = version->descriptor(); + for (auto builtin_v : m_vlist) + { + if (descr == builtin_v->descriptor()) + { + goto SKIP_THIS_ONE; + } + } + m_vlist.append(version); + SKIP_THIS_ONE: + { + } + } + m_loaded = true; + sortInternal(); + endResetModel(); +} + +inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname) +{ + QDomNodeList elementList = parent.elementsByTagName(tagname); + if (elementList.count()) + return elementList.at(0).toElement(); + else + return QDomElement(); +} + +MCVListLoadTask::MCVListLoadTask(MinecraftVersionList *vlist) +{ + m_list = vlist; + m_currentStable = NULL; + vlistReply = nullptr; +} + +MCVListLoadTask::~MCVListLoadTask() +{ +} + +void MCVListLoadTask::executeTask() +{ + setStatus(tr("Loading instance version list...")); + auto worker = MMC->qnam(); + vlistReply = worker->get(QNetworkRequest( + QUrl("http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + "versions.json"))); + connect(vlistReply, SIGNAL(finished()), this, SLOT(list_downloaded())); +} + +void MCVListLoadTask::list_downloaded() +{ + if (vlistReply->error() != QNetworkReply::NoError) + { + vlistReply->deleteLater(); + emitFailed("Failed to load Minecraft main version list" + vlistReply->errorString()); + return; + } + + auto foo = vlistReply->readAll(); + QJsonParseError jsonError; + QLOG_INFO() << foo; + QJsonDocument jsonDoc = QJsonDocument::fromJson(foo, &jsonError); + vlistReply->deleteLater(); + + if (jsonError.error != QJsonParseError::NoError) + { + emitFailed("Error parsing version list JSON:" + jsonError.errorString()); + return; + } + + if (!jsonDoc.isObject()) + { + emitFailed("Error parsing version list JSON: jsonDoc is not an object"); + return; + } + + QJsonObject root = jsonDoc.object(); + + QString latestReleaseID = "INVALID"; + QString latestSnapshotID = "INVALID"; + try + { + QJsonObject latest = MMCJson::ensureObject(root.value("latest")); + latestReleaseID = MMCJson::ensureString(latest.value("release")); + latestSnapshotID = MMCJson::ensureString(latest.value("snapshot")); + } + catch (MMCError &err) + { + QLOG_ERROR() + << tr("Error parsing version list JSON: couldn't determine latest versions"); + } + + // Now, get the array of versions. + if (!root.value("versions").isArray()) + { + emitFailed( + "Error parsing version list JSON: version list object is missing 'versions' array"); + return; + } + QJsonArray versions = root.value("versions").toArray(); + + QList<BaseVersionPtr> tempList; + for (auto version : versions) + { + bool is_snapshot = false; + bool is_latest = false; + + // Load the version info. + if (!version.isObject()) + { + // FIXME: log this somewhere + continue; + } + QJsonObject versionObj = version.toObject(); + QString versionID = versionObj.value("id").toString(""); + QString versionTimeStr = versionObj.value("releaseTime").toString(""); + QString versionTypeStr = versionObj.value("type").toString(""); + if (versionID.isEmpty() || versionTimeStr.isEmpty() || versionTypeStr.isEmpty()) + { + // FIXME: log this somewhere + continue; + } + + // Parse the timestamp. + QDateTime versionTime = timeFromS3Time(versionTimeStr); + if (!versionTime.isValid()) + { + // FIXME: log this somewhere + continue; + } + // OneSix or Legacy. use filter to determine type + if (versionTypeStr == "release") + { + is_latest = (versionID == latestReleaseID); + is_snapshot = false; + } + else if (versionTypeStr == "snapshot") // It's a snapshot... yay + { + is_latest = (versionID == latestSnapshotID); + is_snapshot = true; + } + else if (versionTypeStr == "old_alpha") + { + is_latest = false; + is_snapshot = false; + } + else if (versionTypeStr == "old_beta") + { + is_latest = false; + is_snapshot = false; + } + else + { + // FIXME: log this somewhere + continue; + } + // Get the download URL. + QString dlUrl = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + versionID + "/"; + + // Now, we construct the version object and add it to the list. + std::shared_ptr<MinecraftVersion> mcVersion(new MinecraftVersion()); + mcVersion->m_name = mcVersion->m_descriptor = versionID; + mcVersion->timestamp = versionTime.toMSecsSinceEpoch(); + mcVersion->download_url = dlUrl; + mcVersion->is_latest = is_latest; + mcVersion->is_snapshot = is_snapshot; + tempList.append(mcVersion); + } + m_list->updateListData(tempList); + + emitSucceeded(); + return; +} diff --git a/logic/minecraft/MinecraftVersionList.h b/logic/minecraft/MinecraftVersionList.h new file mode 100644 index 00000000..18b9b21e --- /dev/null +++ b/logic/minecraft/MinecraftVersionList.h @@ -0,0 +1,76 @@ +/* Copyright 2013 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 <QSet> + +#include "logic/BaseVersionList.h" +#include "logic/tasks/Task.h" +#include "logic/minecraft/MinecraftVersion.h" + +class MCVListLoadTask; +class QNetworkReply; + +class MinecraftVersionList : public BaseVersionList +{ + Q_OBJECT +private: + void sortInternal(); + void loadBuiltinList(); +public: + friend class MCVListLoadTask; + + explicit MinecraftVersionList(QObject *parent = 0); + + virtual Task *getLoadTask(); + virtual bool isLoaded(); + virtual const BaseVersionPtr at(int i) const; + virtual int count() const; + virtual void sort(); + + virtual BaseVersionPtr getLatestStable() const; + +protected: + QList<BaseVersionPtr> m_vlist; + + bool m_loaded = false; + +protected +slots: + virtual void updateListData(QList<BaseVersionPtr> versions); +}; + +class MCVListLoadTask : public Task +{ + Q_OBJECT + +public: + explicit MCVListLoadTask(MinecraftVersionList *vlist); + ~MCVListLoadTask(); + + virtual void executeTask(); + +protected +slots: + void list_downloaded(); + +protected: + QNetworkReply *vlistReply; + MinecraftVersionList *m_list; + MinecraftVersion *m_currentStable; +}; diff --git a/logic/minecraft/OneSixLibrary.cpp b/logic/minecraft/OneSixLibrary.cpp new file mode 100644 index 00000000..45fa169e --- /dev/null +++ b/logic/minecraft/OneSixLibrary.cpp @@ -0,0 +1,274 @@ +/* Copyright 2013 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 <QJsonArray> + +#include "OneSixLibrary.h" +#include "OneSixRule.h" +#include "OpSys.h" +#include "logic/net/URLConstants.h" +#include <pathutils.h> +#include <JlCompress.h> +#include "logger/QsLog.h" + +void OneSixLibrary::finalize() +{ + QStringList parts = m_name.split(':'); + QString relative = parts[0]; + relative.replace('.', '/'); + relative += '/' + parts[1] + '/' + parts[2] + '/' + parts[1] + '-' + parts[2]; + + if (!m_is_native) + relative += ".jar"; + else + { + if (m_native_suffixes.contains(currentSystem)) + { + relative += "-" + m_native_suffixes[currentSystem] + ".jar"; + } + else + { + // really, bad. + relative += ".jar"; + } + } + + m_decentname = parts[1]; + m_decentversion = minVersion = parts[2]; + m_storage_path = relative; + m_download_url = m_base_url + relative; + + if (m_rules.empty()) + { + m_is_active = true; + } + else + { + RuleAction result = Disallow; + for (auto rule : m_rules) + { + RuleAction temp = rule->apply(this); + if (temp != Defer) + result = temp; + } + m_is_active = (result == Allow); + } + if (m_is_native) + { + m_is_active = m_is_active && m_native_suffixes.contains(currentSystem); + m_decenttype = "Native"; + } + else + { + m_decenttype = "Java"; + } +} + +void OneSixLibrary::setName(const QString &name) +{ + m_name = name; +} +void OneSixLibrary::setBaseUrl(const QString &base_url) +{ + m_base_url = base_url; +} +void OneSixLibrary::setIsNative() +{ + m_is_native = true; +} +void OneSixLibrary::addNative(OpSys os, const QString &suffix) +{ + m_is_native = true; + m_native_suffixes[os] = suffix; +} +void OneSixLibrary::clearSuffixes() +{ + m_native_suffixes.clear(); +} +void OneSixLibrary::setRules(QList<std::shared_ptr<Rule>> rules) +{ + m_rules = rules; +} +bool OneSixLibrary::isActive() const +{ + return m_is_active; +} +bool OneSixLibrary::isNative() const +{ + return m_is_native; +} +QString OneSixLibrary::downloadUrl() const +{ + if (m_absolute_url.size()) + return m_absolute_url; + return m_download_url; +} +QString OneSixLibrary::storagePath() const +{ + return m_storage_path; +} + +void OneSixLibrary::setAbsoluteUrl(const QString &absolute_url) +{ + m_absolute_url = absolute_url; +} + +QString OneSixLibrary::absoluteUrl() const +{ + return m_absolute_url; +} + +void OneSixLibrary::setHint(const QString &hint) +{ + m_hint = hint; +} + +QString OneSixLibrary::hint() const +{ + return m_hint; +} + +QStringList OneSixLibrary::files() +{ + QStringList retval; + QString storage = storagePath(); + if (storage.contains("${arch}")) + { + QString cooked_storage = storage; + cooked_storage.replace("${arch}", "32"); + retval.append(cooked_storage); + cooked_storage = storage; + cooked_storage.replace("${arch}", "64"); + retval.append(cooked_storage); + } + else + retval.append(storage); + return retval; +} + +bool OneSixLibrary::filesExist(const QDir &base) +{ + auto libFiles = files(); + for(auto file: libFiles) + { + QFileInfo info(base, file); + QLOG_WARN() << info.absoluteFilePath() << "doesn't exist"; + if (!info.exists()) + return false; + } + return true; +} + +bool OneSixLibrary::extractTo(QString target_dir) +{ + QString storage = storagePath(); + if (storage.contains("${arch}")) + { + QString cooked_storage = storage; + cooked_storage.replace("${arch}", "32"); + QString origin = PathCombine("libraries", cooked_storage); + QString target_dir_cooked = PathCombine(target_dir, "32"); + if (!ensureFolderPathExists(target_dir_cooked)) + { + QLOG_ERROR() << "Couldn't create folder " + target_dir_cooked; + return false; + } + if (JlCompress::extractWithExceptions(origin, target_dir_cooked, extract_excludes) + .isEmpty()) + { + QLOG_ERROR() << "Couldn't extract " + origin; + return false; + } + cooked_storage = storage; + cooked_storage.replace("${arch}", "64"); + origin = PathCombine("libraries", cooked_storage); + target_dir_cooked = PathCombine(target_dir, "64"); + if (!ensureFolderPathExists(target_dir_cooked)) + { + QLOG_ERROR() << "Couldn't create folder " + target_dir_cooked; + return false; + } + if (JlCompress::extractWithExceptions(origin, target_dir_cooked, extract_excludes) + .isEmpty()) + { + QLOG_ERROR() << "Couldn't extract " + origin; + return false; + } + } + else + { + if (!ensureFolderPathExists(target_dir)) + { + QLOG_ERROR() << "Couldn't create folder " + target_dir; + return false; + } + QString path = PathCombine("libraries", storage); + if (JlCompress::extractWithExceptions(path, target_dir, extract_excludes).isEmpty()) + { + QLOG_ERROR() << "Couldn't extract " + path; + return false; + } + } + return true; +} + +QJsonObject OneSixLibrary::toJson() +{ + QJsonObject libRoot; + libRoot.insert("name", m_name); + if (m_absolute_url.size()) + libRoot.insert("MMC-absoluteUrl", m_absolute_url); + if (m_hint.size()) + libRoot.insert("MMC-hint", m_hint); + if (m_base_url != "http://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && + m_base_url != "https://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && + m_base_url != "https://" + URLConstants::LIBRARY_BASE && !m_base_url.isEmpty()) + { + libRoot.insert("url", m_base_url); + } + if (isNative() && m_native_suffixes.size()) + { + QJsonObject nativeList; + auto iter = m_native_suffixes.begin(); + while (iter != m_native_suffixes.end()) + { + nativeList.insert(OpSys_toString(iter.key()), iter.value()); + iter++; + } + libRoot.insert("natives", nativeList); + } + if (isNative() && extract_excludes.size()) + { + QJsonArray excludes; + QJsonObject extract; + for (auto exclude : extract_excludes) + { + excludes.append(exclude); + } + extract.insert("exclude", excludes); + libRoot.insert("extract", extract); + } + if (m_rules.size()) + { + QJsonArray allRules; + for (auto &rule : m_rules) + { + QJsonObject ruleObj = rule->toJson(); + allRules.append(ruleObj); + } + libRoot.insert("rules", allRules); + } + return libRoot; +} diff --git a/logic/minecraft/OneSixLibrary.h b/logic/minecraft/OneSixLibrary.h new file mode 100644 index 00000000..13df0606 --- /dev/null +++ b/logic/minecraft/OneSixLibrary.h @@ -0,0 +1,148 @@ +/* Copyright 2013 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 <QString> +#include <QStringList> +#include <QMap> +#include <QJsonObject> +#include <QDir> +#include <memory> + +#include "logic/net/URLConstants.h" +#include "logic/minecraft/OpSys.h" + +class Rule; + +class OneSixLibrary; +typedef std::shared_ptr<OneSixLibrary> OneSixLibraryPtr; + +class OneSixLibrary +{ +private: + // basic values used internally (so far) + QString m_name; + QString m_base_url = "https://" + URLConstants::LIBRARY_BASE; + QList<std::shared_ptr<Rule>> m_rules; + + // custom values + /// absolute URL. takes precedence over m_download_path, if defined + QString m_absolute_url; + /// type hint - modifies how the library is treated + QString m_hint; + + // derived values used for real things + /// a decent name fit for display + QString m_decentname; + /// a decent version fit for display + QString m_decentversion; + /// a decent type fit for display + QString m_decenttype; + /// where to store the lib locally + QString m_storage_path; + /// where to download the lib from + QString m_download_url; + /// is this lib actually active on the current OS? + bool m_is_active = false; + /// is the library a native? + bool m_is_native = false; + /// native suffixes per OS + QMap<OpSys, QString> m_native_suffixes; + +public: + QStringList extract_excludes; + QString minVersion; + + enum DependType + { + Soft, + Hard + }; + DependType dependType; + +public: + /// Constructor + OneSixLibrary(const QString &name, const DependType type = Soft) + { + m_name = name; + dependType = type; + } + + /// Returns the raw name field + QString rawName() const + { + return m_name; + } + + QJsonObject toJson(); + + /** + * finalize the library, processing the input values into derived values and state + * + * This SHALL be called after all the values are parsed or after any further change. + */ + void finalize(); + + /// Set the library composite name + void setName(const QString &name); + /// get a decent-looking name + QString name() const + { + return m_decentname; + } + /// get a decent-looking version + QString version() const + { + return m_decentversion; + } + /// what kind of library is it? (for display) + QString type() const + { + return m_decenttype; + } + /// Set the url base for downloads + void setBaseUrl(const QString &base_url); + + /// Call this to mark the library as 'native' (it's a zip archive with DLLs) + void setIsNative(); + /// Attach a name suffix to the specified OS native + void addNative(OpSys os, const QString &suffix); + /// Clears all suffixes + void clearSuffixes(); + /// Set the load rules + void setRules(QList<std::shared_ptr<Rule>> rules); + + /// Returns true if the library should be loaded (or extracted, in case of natives) + bool isActive() const; + /// Returns true if the library is native + bool isNative() const; + /// Get the URL to download the library from + QString downloadUrl() const; + /// Get the relative path where the library should be saved + QString storagePath() const; + + /// set an absolute URL for the library. This is an MMC extension. + void setAbsoluteUrl(const QString &absolute_url); + QString absoluteUrl() const; + + /// set a hint about how to treat the library. This is an MMC extension. + void setHint(const QString &hint); + QString hint() const; + + bool extractTo(QString target_dir); + bool filesExist(const QDir &base); + QStringList files(); +}; diff --git a/logic/minecraft/OneSixRule.cpp b/logic/minecraft/OneSixRule.cpp new file mode 100644 index 00000000..d8d13b50 --- /dev/null +++ b/logic/minecraft/OneSixRule.cpp @@ -0,0 +1,89 @@ +/* Copyright 2013 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 <QJsonObject> +#include <QJsonArray> + +#include "OneSixRule.h" + +QList<std::shared_ptr<Rule>> rulesFromJsonV4(const QJsonObject &objectWithRules) +{ + QList<std::shared_ptr<Rule>> rules; + auto rulesVal = objectWithRules.value("rules"); + if (!rulesVal.isArray()) + return rules; + + QJsonArray ruleList = rulesVal.toArray(); + for (auto ruleVal : ruleList) + { + std::shared_ptr<Rule> rule; + if (!ruleVal.isObject()) + continue; + auto ruleObj = ruleVal.toObject(); + auto actionVal = ruleObj.value("action"); + if (!actionVal.isString()) + continue; + auto action = RuleAction_fromString(actionVal.toString()); + if (action == Defer) + continue; + + auto osVal = ruleObj.value("os"); + if (!osVal.isObject()) + { + // add a new implicit action rule + rules.append(ImplicitRule::create(action)); + continue; + } + + auto osObj = osVal.toObject(); + auto osNameVal = osObj.value("name"); + if (!osNameVal.isString()) + continue; + OpSys requiredOs = OpSys_fromString(osNameVal.toString()); + QString versionRegex = osObj.value("version").toString(); + // add a new OS rule + rules.append(OsRule::create(action, requiredOs, versionRegex)); + } + return rules; +} + +QJsonObject ImplicitRule::toJson() +{ + QJsonObject ruleObj; + ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); + return ruleObj; +} + +QJsonObject OsRule::toJson() +{ + QJsonObject ruleObj; + ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); + QJsonObject osObj; + { + osObj.insert("name", OpSys_toString(m_system)); + osObj.insert("version", m_version_regexp); + } + ruleObj.insert("os", osObj); + return ruleObj; +} + +RuleAction RuleAction_fromString(QString name) +{ + if (name == "allow") + return Allow; + if (name == "disallow") + return Disallow; + return Defer; +} diff --git a/logic/minecraft/OneSixRule.h b/logic/minecraft/OneSixRule.h new file mode 100644 index 00000000..2c569b9f --- /dev/null +++ b/logic/minecraft/OneSixRule.h @@ -0,0 +1,98 @@ +/* Copyright 2013 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 <QString> + +#include "logic/minecraft/OneSixLibrary.h" + +enum RuleAction +{ + Allow, + Disallow, + Defer +}; + +RuleAction RuleAction_fromString(QString); +QList<std::shared_ptr<Rule>> rulesFromJsonV4(const QJsonObject &objectWithRules); + +class Rule +{ +protected: + RuleAction m_result; + virtual bool applies(OneSixLibrary *parent) = 0; + +public: + Rule(RuleAction result) : m_result(result) + { + } + virtual ~Rule() {}; + virtual QJsonObject toJson() = 0; + RuleAction apply(OneSixLibrary *parent) + { + if (applies(parent)) + return m_result; + else + return Defer; + } + ; +}; + +class OsRule : public Rule +{ +private: + // the OS + OpSys m_system; + // the OS version regexp + QString m_version_regexp; + +protected: + virtual bool applies(OneSixLibrary *) + { + return (m_system == currentSystem); + } + OsRule(RuleAction result, OpSys system, QString version_regexp) + : Rule(result), m_system(system), m_version_regexp(version_regexp) + { + } + +public: + virtual QJsonObject toJson(); + static std::shared_ptr<OsRule> create(RuleAction result, OpSys system, + QString version_regexp) + { + return std::shared_ptr<OsRule>(new OsRule(result, system, version_regexp)); + } +}; + +class ImplicitRule : public Rule +{ +protected: + virtual bool applies(OneSixLibrary *) + { + return true; + } + ImplicitRule(RuleAction result) : Rule(result) + { + } + +public: + virtual QJsonObject toJson(); + static std::shared_ptr<ImplicitRule> create(RuleAction result) + { + return std::shared_ptr<ImplicitRule>(new ImplicitRule(result)); + } +}; diff --git a/logic/minecraft/OneSixVersionBuilder.cpp b/logic/minecraft/OneSixVersionBuilder.cpp new file mode 100644 index 00000000..da8f956c --- /dev/null +++ b/logic/minecraft/OneSixVersionBuilder.cpp @@ -0,0 +1,249 @@ +/* Copyright 2013 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 <QList> +#include <QJsonObject> +#include <QJsonArray> +#include <QJsonDocument> +#include <QFile> +#include <QFileInfo> +#include <QMessageBox> +#include <QObject> +#include <QDir> +#include <QDebug> +#include <modutils.h> + +#include "logic/minecraft/OneSixVersionBuilder.h" +#include "logic/minecraft/VersionFinal.h" +#include "logic/minecraft/OneSixRule.h" +#include "logic/minecraft/VersionFile.h" + +#include "logic/OneSixInstance.h" +#include "logic/MMCJson.h" + +#include "logger/QsLog.h" + +OneSixVersionBuilder::OneSixVersionBuilder() +{ +} + +void OneSixVersionBuilder::build(VersionFinal *version, OneSixInstance *instance, const QStringList &external) +{ + OneSixVersionBuilder builder; + builder.m_version = version; + builder.m_instance = instance; + builder.buildInternal(external); +} + +void OneSixVersionBuilder::readJsonAndApplyToVersion(VersionFinal *version, + const QJsonObject &obj) +{ + OneSixVersionBuilder builder; + builder.m_version = version; + builder.m_instance = 0; + builder.readJsonAndApply(obj); +} + +void OneSixVersionBuilder::buildInternal(const QStringList &external) +{ + m_version->versionFiles.clear(); + + QDir root(m_instance->instanceRoot()); + QDir patches(root.absoluteFilePath("patches/")); + + // if we do external files, do just those. + if (!external.isEmpty()) + { + int externalOrder = -1; + for (auto fileName : external) + { + QLOG_INFO() << "Reading" << fileName; + auto file = + parseJsonFile(QFileInfo(fileName), false, fileName.endsWith("pack.json")); + file->name = QFileInfo(fileName).fileName(); + file->fileId = "org.multimc.external." + file->name; + file->order = (externalOrder += 1); + file->version = QString(); + file->mcVersion = QString(); + m_version->versionFiles.append(file); + } + } + // else, if there's custom json, we just do that. + else if (QFile::exists(root.absoluteFilePath("custom.json"))) + { + QLOG_INFO() << "Reading custom.json"; + auto file = parseJsonFile(QFileInfo(root.absoluteFilePath("custom.json")), false); + file->name = "custom.json"; + file->filename = "custom.json"; + file->fileId = "org.multimc.custom.json"; + file->order = -1; + file->version = QString(); + m_version->versionFiles.append(file); + // QObject::tr("The version descriptors of this instance are not compatible with the + // current version of MultiMC")); + // QObject::tr("Error while applying %1. Please check MultiMC-0.log for more info.") + } + // version.json -> patches/*.json -> user.json + else + do + { + // version.json + QLOG_INFO() << "Reading version.json"; + auto file = parseJsonFile(QFileInfo(root.absoluteFilePath("version.json")), false); + file->name = "Minecraft"; + file->fileId = "org.multimc.version.json"; + file->order = -1; + file->version = m_instance->intendedVersionId(); + file->mcVersion = m_instance->intendedVersionId(); + m_version->versionFiles.append(file); + // QObject::tr("Error while applying %1. Please check MultiMC-0.log for more + // info.").arg(root.absoluteFilePath("version.json"))); + + // patches/ + // load all, put into map for ordering, apply in the right order + + QMap<int, QPair<QString, VersionFilePtr>> files; + for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) + { + QLOG_INFO() << "Reading" << info.fileName(); + auto file = parseJsonFile(info, true); + if (files.contains(file->order)) + { + throw VersionBuildError(QObject::tr("%1 has the same order as %2").arg( + file->fileId, files[file->order].second->fileId)); + } + files.insert(file->order, qMakePair(info.fileName(), file)); + } + for (auto order : files.keys()) + { + auto &filePair = files[order]; + m_version->versionFiles.append(filePair.second); + } + } while (0); + + // some final touches + m_version->finalize(); +} + + + +void OneSixVersionBuilder::readJsonAndApply(const QJsonObject &obj) +{ + m_version->clear(); + + auto file = VersionFile::fromJson(QJsonDocument(obj), QString(), false); + // QObject::tr("Error while reading. Please check MultiMC-0.log for more info.")); + + file->applyTo(m_version); + m_version->versionFiles.append(file); + // QObject::tr("Error while applying. Please check MultiMC-0.log for more info.")); + // QObject::tr("The version descriptors of this instance are not compatible with the current + // version of MultiMC")); +} + +VersionFilePtr OneSixVersionBuilder::parseJsonFile(const QFileInfo &fileInfo, + const bool requireOrder, bool isFTB) +{ + QFile file(fileInfo.absoluteFilePath()); + if (!file.open(QFile::ReadOnly)) + { + throw JSONValidationError(QObject::tr("Unable to open the version file %1: %2.") + .arg(fileInfo.fileName(), file.errorString())); + } + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error); + if (error.error != QJsonParseError::NoError) + { + throw JSONValidationError(QObject::tr("Unable to process the version file %1: %2 at %3.") + .arg(fileInfo.fileName(), error.errorString()) + .arg(error.offset)); + } + return VersionFile::fromJson(doc, file.fileName(), requireOrder, isFTB); + // QObject::tr("Error while reading %1. Please check MultiMC-0.log for more + // info.").arg(file.fileName()); +} + +QMap<QString, int> OneSixVersionBuilder::readOverrideOrders(OneSixInstance *instance) +{ + QMap<QString, int> out; + + // make sure the order file exists + if (!QDir(instance->instanceRoot()).exists("order.json")) + return out; + + // and it can be opened + QFile orderFile(instance->instanceRoot() + "/order.json"); + if (!orderFile.open(QFile::ReadOnly)) + { + QLOG_ERROR() << "Couldn't open" << orderFile.fileName() + << " for reading:" << orderFile.errorString(); + QLOG_WARN() << "Ignoring overriden order"; + return out; + } + + // and it's valid JSON + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(orderFile.readAll(), &error); + if (error.error != QJsonParseError::NoError) + { + QLOG_ERROR() << "Couldn't parse" << orderFile.fileName() << ":" << error.errorString(); + QLOG_WARN() << "Ignoring overriden order"; + return out; + } + + // and then read it and process it if all above is true. + try + { + auto obj = MMCJson::ensureObject(doc); + for (auto it = obj.begin(); it != obj.end(); ++it) + { + if (it.key().startsWith("org.multimc.")) + { + continue; + } + out.insert(it.key(), MMCJson::ensureInteger(it.value())); + } + } + catch (JSONValidationError &err) + { + QLOG_ERROR() << "Couldn't parse" << orderFile.fileName() << ": bad file format"; + QLOG_WARN() << "Ignoring overriden order"; + return out; + } + return out; +} + +bool OneSixVersionBuilder::writeOverrideOrders(const QMap<QString, int> &order, + OneSixInstance *instance) +{ + QJsonObject obj; + for (auto it = order.cbegin(); it != order.cend(); ++it) + { + if (it.key().startsWith("org.multimc.")) + { + continue; + } + obj.insert(it.key(), it.value()); + } + QFile orderFile(instance->instanceRoot() + "/order.json"); + if (!orderFile.open(QFile::WriteOnly)) + { + QLOG_ERROR() << "Couldn't open" << orderFile.fileName() + << "for writing:" << orderFile.errorString(); + return false; + } + orderFile.write(QJsonDocument(obj).toJson(QJsonDocument::Indented)); + return true; +} diff --git a/logic/minecraft/OneSixVersionBuilder.h b/logic/minecraft/OneSixVersionBuilder.h new file mode 100644 index 00000000..6646584e --- /dev/null +++ b/logic/minecraft/OneSixVersionBuilder.h @@ -0,0 +1,46 @@ +/* Copyright 2013 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 <QString> +#include <QMap> +#include "VersionFile.h" + +class VersionFinal; +class OneSixInstance; +class QJsonObject; +class QFileInfo; + +class OneSixVersionBuilder +{ + OneSixVersionBuilder(); +public: + static void build(VersionFinal *version, OneSixInstance *instance, const QStringList &external); + static void readJsonAndApplyToVersion(VersionFinal *version, const QJsonObject &obj); + + static QMap<QString, int> readOverrideOrders(OneSixInstance *instance); + static bool writeOverrideOrders(const QMap<QString, int> &order, OneSixInstance *instance); + +private: + VersionFinal *m_version; + OneSixInstance *m_instance; + + void buildInternal(const QStringList& external); + void readJsonAndApply(const QJsonObject &obj); + + VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder, + bool isFTB = false); +}; diff --git a/logic/minecraft/OpSys.cpp b/logic/minecraft/OpSys.cpp new file mode 100644 index 00000000..e001b7f3 --- /dev/null +++ b/logic/minecraft/OpSys.cpp @@ -0,0 +1,42 @@ +/* Copyright 2013 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 "OpSys.h" + +OpSys OpSys_fromString(QString name) +{ + if (name == "linux") + return Os_Linux; + if (name == "windows") + return Os_Windows; + if (name == "osx") + return Os_OSX; + return Os_Other; +} + +QString OpSys_toString(OpSys name) +{ + switch (name) + { + case Os_Linux: + return "linux"; + case Os_OSX: + return "osx"; + case Os_Windows: + return "windows"; + default: + return "other"; + } +}
\ No newline at end of file diff --git a/logic/minecraft/OpSys.h b/logic/minecraft/OpSys.h new file mode 100644 index 00000000..363c87d7 --- /dev/null +++ b/logic/minecraft/OpSys.h @@ -0,0 +1,37 @@ +/* Copyright 2013 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 <QString> +enum OpSys +{ + Os_Windows, + Os_Linux, + Os_OSX, + Os_Other +}; + +OpSys OpSys_fromString(QString); +QString OpSys_toString(OpSys); + +#ifdef Q_OS_WIN32 +#define currentSystem Os_Windows +#else +#ifdef Q_OS_MAC +#define currentSystem Os_OSX +#else +#define currentSystem Os_Linux +#endif +#endif
\ No newline at end of file diff --git a/logic/minecraft/VersionFile.cpp b/logic/minecraft/VersionFile.cpp new file mode 100644 index 00000000..a011ce1a --- /dev/null +++ b/logic/minecraft/VersionFile.cpp @@ -0,0 +1,635 @@ +#include <QJsonArray> +#include <QJsonDocument> +#include <modutils.h> + +#include "logger/QsLog.h" + +#include "logic/minecraft/VersionFile.h" +#include "logic/minecraft/OneSixLibrary.h" +#include "logic/minecraft/VersionFinal.h" +#include "logic/MMCJson.h" + +using namespace MMCJson; + +#define CURRENT_MINIMUM_LAUNCHER_VERSION 14 + +JarmodPtr Jarmod::fromJson(const QJsonObject &libObj, const QString &filename) +{ + JarmodPtr out(new Jarmod()); + if (!libObj.contains("name")) + { + throw JSONValidationError(filename + + "contains a jarmod that doesn't have a 'name' field"); + } + out->name = libObj.value("name").toString(); + + auto readString = [libObj, filename](const QString & key, QString & variable) + { + if (libObj.contains(key)) + { + QJsonValue val = libObj.value(key); + if (!val.isString()) + { + QLOG_WARN() << key << "is not a string in" << filename << "(skipping)"; + } + else + { + variable = val.toString(); + } + } + }; + + readString("url", out->baseurl); + readString("MMC-absoluteUrl", out->absoluteUrl); + if(!out->baseurl.isEmpty() && out->absoluteUrl.isEmpty()) + { + out->absoluteUrl = out->baseurl + out->name; + } + return out; + +} + + +RawLibraryPtr RawLibrary::fromJson(const QJsonObject &libObj, const QString &filename) +{ + RawLibraryPtr out(new RawLibrary()); + if (!libObj.contains("name")) + { + throw JSONValidationError(filename + + "contains a library that doesn't have a 'name' field"); + } + out->name = libObj.value("name").toString(); + + auto readString = [libObj, filename](const QString & key, QString & variable) + { + if (libObj.contains(key)) + { + QJsonValue val = libObj.value(key); + if (!val.isString()) + { + QLOG_WARN() << key << "is not a string in" << filename << "(skipping)"; + } + else + { + variable = val.toString(); + } + } + }; + + readString("url", out->url); + readString("MMC-hint", out->hint); + readString("MMC-absulute_url", out->absoluteUrl); + readString("MMC-absoluteUrl", out->absoluteUrl); + if (libObj.contains("extract")) + { + out->applyExcludes = true; + auto extractObj = ensureObject(libObj.value("extract")); + for (auto excludeVal : ensureArray(extractObj.value("exclude"))) + { + out->excludes.append(ensureString(excludeVal)); + } + } + if (libObj.contains("natives")) + { + out->applyNatives = true; + QJsonObject nativesObj = ensureObject(libObj.value("natives")); + for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it) + { + if (!it.value().isString()) + { + QLOG_WARN() << filename << "contains an invalid native (skipping)"; + } + OpSys opSys = OpSys_fromString(it.key()); + if (opSys != Os_Other) + { + out->natives.append(qMakePair(opSys, it.value().toString())); + } + } + } + if (libObj.contains("rules")) + { + out->applyRules = true; + out->rules = rulesFromJsonV4(libObj); + } + return out; +} + +VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &filename, + const bool requireOrder, const bool isFTB) +{ + VersionFilePtr out(new VersionFile()); + if (doc.isEmpty() || doc.isNull()) + { + throw JSONValidationError(filename + " is empty or null"); + } + if (!doc.isObject()) + { + throw JSONValidationError("The root of " + filename + " is not an object"); + } + + QJsonObject root = doc.object(); + + if (requireOrder) + { + if (root.contains("order")) + { + out->order = ensureInteger(root.value("order")); + } + else + { + // FIXME: evaluate if we don't want to throw exceptions here instead + QLOG_ERROR() << filename << "doesn't contain an order field"; + } + } + + out->name = root.value("name").toString(); + out->fileId = root.value("fileId").toString(); + out->version = root.value("version").toString(); + out->mcVersion = root.value("mcVersion").toString(); + out->filename = filename; + + auto readString = [root, filename](const QString & key, QString & variable) + { + if (root.contains(key)) + { + variable = ensureString(root.value(key)); + } + }; + + // FIXME: This should be ignored when applying. + if (!isFTB) + { + readString("id", out->id); + } + + readString("mainClass", out->mainClass); + readString("appletClass", out->appletClass); + readString("processArguments", out->processArguments); + readString("minecraftArguments", out->overwriteMinecraftArguments); + readString("+minecraftArguments", out->addMinecraftArguments); + readString("-minecraftArguments", out->removeMinecraftArguments); + readString("type", out->type); + readString("releaseTime", out->versionReleaseTime); + readString("time", out->versionFileUpdateTime); + readString("assets", out->assets); + + if (root.contains("minimumLauncherVersion")) + { + out->minimumLauncherVersion = ensureInteger(root.value("minimumLauncherVersion")); + } + + if (root.contains("tweakers")) + { + out->shouldOverwriteTweakers = true; + for (auto tweakerVal : ensureArray(root.value("tweakers"))) + { + out->overwriteTweakers.append(ensureString(tweakerVal)); + } + } + + if (root.contains("+tweakers")) + { + for (auto tweakerVal : ensureArray(root.value("+tweakers"))) + { + out->addTweakers.append(ensureString(tweakerVal)); + } + } + + if (root.contains("-tweakers")) + { + for (auto tweakerVal : ensureArray(root.value("-tweakers"))) + { + out->removeTweakers.append(ensureString(tweakerVal)); + } + } + + if (root.contains("+traits")) + { + for (auto tweakerVal : ensureArray(root.value("+traits"))) + { + out->traits.insert(ensureString(tweakerVal)); + } + } + + if (root.contains("libraries")) + { + // FIXME: This should be done when applying. + out->shouldOverwriteLibs = !isFTB; + for (auto libVal : ensureArray(root.value("libraries"))) + { + auto libObj = ensureObject(libVal); + + auto lib = RawLibrary::fromJson(libObj, filename); + // FIXME: This should be done when applying. + if (isFTB) + { + lib->hint = "local"; + lib->insertType = RawLibrary::Prepend; + out->addLibs.prepend(lib); + } + else + { + out->overwriteLibs.append(lib); + } + } + } + + if (root.contains("+jarMods")) + { + for (auto libVal : ensureArray(root.value("+jarMods"))) + { + QJsonObject libObj = ensureObject(libVal); + // parse the jarmod + auto lib = Jarmod::fromJson(libObj, filename); + // and add to jar mods + out->jarMods.append(lib); + } + } + + if (root.contains("+libraries")) + { + for (auto libVal : ensureArray(root.value("+libraries"))) + { + QJsonObject libObj = ensureObject(libVal); + QJsonValue insertVal = ensureExists(libObj.value("insert")); + + // parse the library + auto lib = RawLibrary::fromJson(libObj, filename); + + // TODO: utility functions for handling this case. templates? + QString insertString; + { + if (insertVal.isString()) + { + insertString = insertVal.toString(); + } + else if (insertVal.isObject()) + { + QJsonObject insertObj = insertVal.toObject(); + if (insertObj.isEmpty()) + { + throw JSONValidationError("One library has an empty insert object in " + + filename); + } + insertString = insertObj.keys().first(); + lib->insertData = insertObj.value(insertString).toString(); + } + } + if (insertString == "apply") + { + lib->insertType = RawLibrary::Apply; + } + else if (insertString == "prepend") + { + lib->insertType = RawLibrary::Prepend; + } + else if (insertString == "append") + { + lib->insertType = RawLibrary::Prepend; + } + else if (insertString == "replace") + { + lib->insertType = RawLibrary::Replace; + } + else + { + throw JSONValidationError("A '+' library in " + filename + + " contains an invalid insert type"); + } + if (libObj.contains("MMC-depend")) + { + const QString dependString = ensureString(libObj.value("MMC-depend")); + if (dependString == "hard") + { + lib->dependType = RawLibrary::Hard; + } + else if (dependString == "soft") + { + lib->dependType = RawLibrary::Soft; + } + else + { + throw JSONValidationError("A '+' library in " + filename + + " contains an invalid depend type"); + } + } + out->addLibs.append(lib); + } + } + + if (root.contains("-libraries")) + { + for (auto libVal : ensureArray(root.value("-libraries"))) + { + auto libObj = ensureObject(libVal); + out->removeLibs.append(ensureString(libObj.value("name"))); + } + } + return out; +} + +OneSixLibraryPtr VersionFile::createLibrary(RawLibraryPtr lib) +{ + std::shared_ptr<OneSixLibrary> out(new OneSixLibrary(lib->name)); + if (!lib->url.isEmpty()) + { + out->setBaseUrl(lib->url); + } + out->setHint(lib->hint); + if (!lib->absoluteUrl.isEmpty()) + { + out->setAbsoluteUrl(lib->absoluteUrl); + } + out->setAbsoluteUrl(lib->absoluteUrl); + out->extract_excludes = lib->excludes; + for (auto native : lib->natives) + { + out->addNative(native.first, native.second); + } + out->setRules(lib->rules); + out->finalize(); + return out; +} + +int VersionFile::findLibrary(QList<OneSixLibraryPtr> haystack, const QString &needle) +{ + int retval = -1; + for (int i = 0; i < haystack.size(); ++i) + { + QString chunk = haystack.at(i)->rawName(); + if (QRegExp(needle, Qt::CaseSensitive, QRegExp::WildcardUnix).indexIn(chunk) != -1) + { + // only one is allowed. + if(retval != -1) + return -1; + retval = i; + } + } + return retval; +} + +bool VersionFile::isVanilla() +{ + return fileId == "org.multimc.version.json"; +} + +bool VersionFile::hasJarMods() +{ + return !jarMods.isEmpty(); +} + +void VersionFile::applyTo(VersionFinal *version) +{ + if (minimumLauncherVersion != -1) + { + if (minimumLauncherVersion > CURRENT_MINIMUM_LAUNCHER_VERSION) + { + throw LauncherVersionError(minimumLauncherVersion, CURRENT_MINIMUM_LAUNCHER_VERSION); + } + } + + if (!version->id.isNull() && !mcVersion.isNull()) + { + if (QRegExp(mcVersion, Qt::CaseInsensitive, QRegExp::Wildcard).indexIn(version->id) == + -1) + { + throw MinecraftVersionMismatch(fileId, mcVersion, version->id); + } + } + + if (!id.isNull()) + { + version->id = id; + } + if (!mainClass.isNull()) + { + version->mainClass = mainClass; + } + if (!appletClass.isNull()) + { + version->appletClass = appletClass; + } + if (!processArguments.isNull()) + { + if(isVanilla()) + { + version->vanillaProcessArguments = processArguments; + } + version->processArguments = processArguments; + } + if (!type.isNull()) + { + version->type = type; + } + if (!versionReleaseTime.isNull()) + { + version->versionReleaseTime = versionReleaseTime; + } + if (!versionFileUpdateTime.isNull()) + { + version->time = versionFileUpdateTime; + } + if (!assets.isNull()) + { + version->assets = assets; + } + if (minimumLauncherVersion >= 0) + { + version->minimumLauncherVersion = minimumLauncherVersion; + } + if (!overwriteMinecraftArguments.isNull()) + { + if(isVanilla()) + { + version->vanillaMinecraftArguments = overwriteMinecraftArguments; + } + version->minecraftArguments = overwriteMinecraftArguments; + } + if (!addMinecraftArguments.isNull()) + { + version->minecraftArguments += addMinecraftArguments; + } + if (!removeMinecraftArguments.isNull()) + { + version->minecraftArguments.remove(removeMinecraftArguments); + } + if (shouldOverwriteTweakers) + { + version->tweakers = overwriteTweakers; + } + for (auto tweaker : addTweakers) + { + version->tweakers += tweaker; + } + for (auto tweaker : removeTweakers) + { + version->tweakers.removeAll(tweaker); + } + version->jarMods.append(jarMods); + version->traits.unite(traits); + if (shouldOverwriteLibs) + { + QList<OneSixLibraryPtr> libs; + for (auto lib : overwriteLibs) + { + libs.append(createLibrary(lib)); + } + if(isVanilla()) + version->vanillaLibraries = libs; + version->libraries = libs; + } + for (auto lib : addLibs) + { + switch (lib->insertType) + { + case RawLibrary::Apply: + { + // QLOG_INFO() << "Applying lib " << lib->name; + int index = findLibrary(version->libraries, lib->name); + if (index >= 0) + { + auto library = version->libraries[index]; + if (!lib->url.isNull()) + { + library->setBaseUrl(lib->url); + } + if (!lib->hint.isNull()) + { + library->setHint(lib->hint); + } + if (!lib->absoluteUrl.isNull()) + { + library->setAbsoluteUrl(lib->absoluteUrl); + } + if (lib->applyExcludes) + { + library->extract_excludes = lib->excludes; + } + if (lib->applyNatives) + { + library->clearSuffixes(); + for (auto native : lib->natives) + { + library->addNative(native.first, native.second); + } + } + if (lib->applyRules) + { + library->setRules(lib->rules); + } + library->finalize(); + } + else + { + QLOG_WARN() << "Couldn't find" << lib->name << "(skipping)"; + } + break; + } + case RawLibrary::Append: + case RawLibrary::Prepend: + { + // QLOG_INFO() << "Adding lib " << lib->name; + const int startOfVersion = lib->name.lastIndexOf(':') + 1; + const int index = findLibrary( + version->libraries, QString(lib->name).replace(startOfVersion, INT_MAX, '*')); + if (index < 0) + { + if (lib->insertType == RawLibrary::Append) + { + version->libraries.append(createLibrary(lib)); + } + else + { + version->libraries.prepend(createLibrary(lib)); + } + } + else + { + auto otherLib = version->libraries.at(index); + const Util::Version ourVersion = lib->name.mid(startOfVersion, INT_MAX); + const Util::Version otherVersion = otherLib->version(); + // if the existing version is a hard dependency we can either use it or + // fail, but we can't change it + if (otherLib->dependType == OneSixLibrary::Hard) + { + // we need a higher version, or we're hard to and the versions aren't + // equal + if (ourVersion > otherVersion || + (lib->dependType == RawLibrary::Hard && ourVersion != otherVersion)) + { + throw VersionBuildError( + QObject::tr( + "Error resolving library dependencies between %1 and %2 in %3.") + .arg(otherLib->rawName(), lib->name, filename)); + } + else + { + // the library is already existing, so we don't have to do anything + } + } + else if (otherLib->dependType == OneSixLibrary::Soft) + { + // if we are higher it means we should update + if (ourVersion > otherVersion) + { + auto library = createLibrary(lib); + if (Util::Version(otherLib->minVersion) < ourVersion) + { + library->minVersion = ourVersion.toString(); + } + version->libraries.replace(index, library); + } + else + { + // our version is smaller than the existing version, but we require + // it: fail + if (lib->dependType == RawLibrary::Hard) + { + throw VersionBuildError(QObject::tr( + "Error resolving library dependencies between %1 and %2 in %3.") + .arg(otherLib->rawName(), lib->name, + filename)); + } + } + } + } + break; + } + case RawLibrary::Replace: + { + QString toReplace; + if(lib->insertData.isEmpty()) + { + const int startOfVersion = lib->name.lastIndexOf(':') + 1; + toReplace = QString(lib->name).replace(startOfVersion, INT_MAX, '*'); + } + else + toReplace = lib->insertData; + // QLOG_INFO() << "Replacing lib " << toReplace << " with " << lib->name; + int index = findLibrary(version->libraries, toReplace); + if (index >= 0) + { + version->libraries.replace(index, createLibrary(lib)); + } + else + { + QLOG_WARN() << "Couldn't find" << toReplace << "(skipping)"; + } + break; + } + } + } + for (auto lib : removeLibs) + { + int index = findLibrary(version->libraries, lib); + if (index >= 0) + { + // QLOG_INFO() << "Removing lib " << lib; + version->libraries.removeAt(index); + } + else + { + QLOG_WARN() << "Couldn't find" << lib << "(skipping)"; + } + } +} diff --git a/logic/minecraft/VersionFile.h b/logic/minecraft/VersionFile.h new file mode 100644 index 00000000..8234445b --- /dev/null +++ b/logic/minecraft/VersionFile.h @@ -0,0 +1,145 @@ +#pragma once + +#include <QString> +#include <QStringList> +#include <memory> +#include "logic/minecraft/OpSys.h" +#include "logic/minecraft/OneSixRule.h" +#include "MMCError.h" + +class VersionFinal; + +class VersionBuildError : public MMCError +{ +public: + VersionBuildError(QString cause) : MMCError(cause) {}; + virtual ~VersionBuildError() noexcept {} +}; + +/** + * the base version file was meant for a newer version of the vanilla launcher than we support + */ +class LauncherVersionError : public VersionBuildError +{ +public: + LauncherVersionError(int actual, int supported) + : VersionBuildError(QObject::tr( + "The base version file of this instance was meant for a newer (%1) " + "version of the vanilla launcher than this version of MultiMC supports (%2).") + .arg(actual) + .arg(supported)) {}; + virtual ~LauncherVersionError() noexcept {} +}; + +/** + * some patch was intended for a different version of minecraft + */ +class MinecraftVersionMismatch : public VersionBuildError +{ +public: + MinecraftVersionMismatch(QString fileId, QString mcVersion, QString parentMcVersion) + : VersionBuildError(QObject::tr("The patch %1 is for a different version of Minecraft " + "(%2) than that of the instance (%3).") + .arg(fileId) + .arg(mcVersion) + .arg(parentMcVersion)) {}; + virtual ~MinecraftVersionMismatch() noexcept {} +}; + +struct RawLibrary; +typedef std::shared_ptr<RawLibrary> RawLibraryPtr; +struct RawLibrary +{ + QString name; + QString url; + QString hint; + QString absoluteUrl; + bool applyExcludes = false; + QStringList excludes; + bool applyNatives = false; + QList<QPair<OpSys, QString>> natives; + bool applyRules = false; + QList<std::shared_ptr<Rule>> rules; + + // user for '+' libraries + enum InsertType + { + Apply, + Append, + Prepend, + Replace + }; + InsertType insertType = Append; + QString insertData; + enum DependType + { + Soft, + Hard + }; + DependType dependType = Soft; + + static RawLibraryPtr fromJson(const QJsonObject &libObj, const QString &filename); +}; + +struct Jarmod; +typedef std::shared_ptr<Jarmod> JarmodPtr; +struct Jarmod +{ + QString name; + QString baseurl; + QString hint; + QString absoluteUrl; + + static JarmodPtr fromJson(const QJsonObject &libObj, const QString &filename); +}; + +struct VersionFile; +typedef std::shared_ptr<VersionFile> VersionFilePtr; +struct VersionFile +{ +public: /* methods */ + static VersionFilePtr fromJson(const QJsonDocument &doc, const QString &filename, + const bool requireOrder, const bool isFTB = false); + + static OneSixLibraryPtr createLibrary(RawLibraryPtr lib); + int findLibrary(QList<OneSixLibraryPtr> haystack, const QString &needle); + void applyTo(VersionFinal *version); + bool isVanilla(); + bool hasJarMods(); +public: /* data */ + int order = 0; + QString name; + QString fileId; + QString version; + // TODO use the mcVersion to determine if a version file should be removed on update + QString mcVersion; + QString filename; + // TODO requirements + // QMap<QString, QString> requirements; + QString id; + QString mainClass; + QString appletClass; + QString overwriteMinecraftArguments; + QString addMinecraftArguments; + QString removeMinecraftArguments; + QString processArguments; + QString type; + QString versionReleaseTime; + QString versionFileUpdateTime; + QString assets; + int minimumLauncherVersion = -1; + + bool shouldOverwriteTweakers = false; + QStringList overwriteTweakers; + QStringList addTweakers; + QStringList removeTweakers; + + bool shouldOverwriteLibs = false; + QList<RawLibraryPtr> overwriteLibs; + QList<RawLibraryPtr> addLibs; + QList<QString> removeLibs; + + QSet<QString> traits; + + QList<JarmodPtr> jarMods; +}; diff --git a/logic/minecraft/VersionFinal.cpp b/logic/minecraft/VersionFinal.cpp new file mode 100644 index 00000000..fbf6a160 --- /dev/null +++ b/logic/minecraft/VersionFinal.cpp @@ -0,0 +1,429 @@ +/* Copyright 2013 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 <QFile> +#include <QDir> +#include <pathutils.h> + +#include "logic/minecraft/VersionFinal.h" +#include "logic/minecraft/OneSixVersionBuilder.h" +#include "logic/OneSixInstance.h" + +VersionFinal::VersionFinal(OneSixInstance *instance, QObject *parent) + : QAbstractListModel(parent), m_instance(instance) +{ + clear(); +} + +void VersionFinal::reload(const QStringList &external) +{ + beginResetModel(); + OneSixVersionBuilder::build(this, m_instance, external); + reapply(true); + endResetModel(); +} + +void VersionFinal::clear() +{ + id.clear(); + time.clear(); + versionReleaseTime.clear(); + type.clear(); + assets.clear(); + processArguments.clear(); + minecraftArguments.clear(); + minimumLauncherVersion = 0xDEADBEAF; + mainClass.clear(); + appletClass.clear(); + libraries.clear(); + tweakers.clear(); + jarMods.clear(); + traits.clear(); +} + +bool VersionFinal::canRemove(const int index) const +{ + if (index < versionFiles.size()) + { + return versionFiles.at(index)->fileId != "org.multimc.version.json"; + } + return false; +} + +bool VersionFinal::preremove(VersionFilePtr versionfile) +{ + bool ok = true; + for(auto & jarmod: versionfile->jarMods) + { + QString fullpath =PathCombine(m_instance->jarModsDir(), jarmod->name); + QFileInfo finfo (fullpath); + if(finfo.exists(fullpath)) + ok &= QFile::remove(fullpath); + } + return ok; +} + +bool VersionFinal::remove(const int index) +{ + if (!canRemove(index)) + return false; + if(!preremove(versionFiles[index])) + { + return false; + } + if(!QFile::remove(versionFiles.at(index)->filename)) + return false; + beginResetModel(); + versionFiles.removeAt(index); + reapply(true); + endResetModel(); + return true; +} + +bool VersionFinal::remove(const QString id) +{ + int i = 0; + for (auto file : versionFiles) + { + if (file->fileId == id) + { + return remove(i); + } + i++; + } + return false; +} + +QString VersionFinal::versionFileId(const int index) const +{ + if (index < 0 || index >= versionFiles.size()) + { + return QString(); + } + return versionFiles.at(index)->fileId; +} + +VersionFilePtr VersionFinal::versionFile(const QString &id) +{ + for (auto file : versionFiles) + { + if (file->fileId == id) + { + return file; + } + } + return 0; +} + +bool VersionFinal::hasJarMods() +{ + return !jarMods.isEmpty(); +} + +bool VersionFinal::hasFtbPack() +{ + return versionFile("org.multimc.ftb.pack.json") != nullptr; +} + +bool VersionFinal::removeFtbPack() +{ + return remove("org.multimc.ftb.pack.json"); +} + +bool VersionFinal::isVanilla() +{ + QDir patches(PathCombine(m_instance->instanceRoot(), "patches/")); + if(versionFiles.size() > 1) + return false; + if(QFile::exists(PathCombine(m_instance->instanceRoot(), "custom.json"))) + return false; + return true; +} + +bool VersionFinal::revertToVanilla() +{ + beginResetModel(); + auto it = versionFiles.begin(); + while (it != versionFiles.end()) + { + if ((*it)->fileId != "org.multimc.version.json") + { + if(!preremove(*it)) + { + endResetModel(); + return false; + } + if(!QFile::remove((*it)->filename)) + { + endResetModel(); + return false; + } + it = versionFiles.erase(it); + } + else + it++; + } + reapply(true); + endResetModel(); + return true; +} + +bool VersionFinal::usesLegacyCustomJson() +{ + return QFile::exists(PathCombine(m_instance->instanceRoot(), "custom.json")); +} + +QList<std::shared_ptr<OneSixLibrary> > VersionFinal::getActiveNormalLibs() +{ + QList<std::shared_ptr<OneSixLibrary> > output; + for (auto lib : libraries) + { + if (lib->isActive() && !lib->isNative()) + { + output.append(lib); + } + } + return output; +} +QList<std::shared_ptr<OneSixLibrary> > VersionFinal::getActiveNativeLibs() +{ + QList<std::shared_ptr<OneSixLibrary> > output; + for (auto lib : libraries) + { + if (lib->isActive() && lib->isNative()) + { + output.append(lib); + } + } + return output; +} + +std::shared_ptr<VersionFinal> VersionFinal::fromJson(const QJsonObject &obj) +{ + std::shared_ptr<VersionFinal> version(new VersionFinal(0)); + try + { + OneSixVersionBuilder::readJsonAndApplyToVersion(version.get(), obj); + } + catch(MMCError & err) + { + return 0; + } + return version; +} + +QVariant VersionFinal::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + int column = index.column(); + + if (row < 0 || row >= versionFiles.size()) + return QVariant(); + + if (role == Qt::DisplayRole) + { + switch (column) + { + case 0: + return versionFiles.at(row)->name; + case 1: + return versionFiles.at(row)->version; + default: + return QVariant(); + } + } + return QVariant(); +} +QVariant VersionFinal::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 VersionFinal::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; +} + +int VersionFinal::rowCount(const QModelIndex &parent) const +{ + return versionFiles.size(); +} + +int VersionFinal::columnCount(const QModelIndex &parent) const +{ + return 2; +} + +QMap<QString, int> VersionFinal::getExistingOrder() const +{ + + QMap<QString, int> order; + // default + { + for (auto file : versionFiles) + { + order.insert(file->fileId, file->order); + } + } + // overriden + { + QMap<QString, int> overridenOrder = OneSixVersionBuilder::readOverrideOrders(m_instance); + for (auto id : order.keys()) + { + if (overridenOrder.contains(id)) + { + order[id] = overridenOrder[id]; + } + } + } + return order; +} + +void VersionFinal::move(const int index, const MoveDirection direction) +{ + int theirIndex; + if (direction == MoveUp) + { + theirIndex = index - 1; + } + else + { + theirIndex = index + 1; + } + if (theirIndex < 0 || theirIndex >= versionFiles.size()) + { + return; + } + const QString ourId = versionFileId(index); + const QString theirId = versionFileId(theirIndex); + if (ourId.isNull() || ourId.startsWith("org.multimc.") || + theirId.isNull() || theirId.startsWith("org.multimc.")) + { + return; + } + if(direction == MoveDown) + { + beginMoveRows(QModelIndex(), index, index, QModelIndex(), theirIndex+1); + } + else + { + beginMoveRows(QModelIndex(), index, index, QModelIndex(), theirIndex); + } + versionFiles.swap(index, theirIndex); + endMoveRows(); + + auto order = getExistingOrder(); + order[ourId] = theirIndex; + order[theirId] = index; + + if (!OneSixVersionBuilder::writeOverrideOrders(order, m_instance)) + { + throw MMCError(tr("Couldn't save the new order")); + } + else + { + reapply(); + } +} +void VersionFinal::resetOrder() +{ + QDir(m_instance->instanceRoot()).remove("order.json"); + reapply(); +} + +void VersionFinal::reapply(const bool alreadyReseting) +{ + if (!alreadyReseting) + { + beginResetModel(); + } + + clear(); + + auto existingOrders = getExistingOrder(); + QList<int> orders = existingOrders.values(); + std::sort(orders.begin(), orders.end()); + QList<VersionFilePtr> newVersionFiles; + for (auto order : orders) + { + auto file = versionFile(existingOrders.key(order)); + newVersionFiles.append(file); + file->applyTo(this); + } + versionFiles.swap(newVersionFiles); + finalize(); + if (!alreadyReseting) + { + endResetModel(); + } +} + +void VersionFinal::finalize() +{ + // HACK: deny april fools. my head hurts enough already. + QDate now = QDate::currentDate(); + bool isAprilFools = now.month() == 4 && now.day() == 1; + if (assets.endsWith("_af") && !isAprilFools) + { + assets = assets.left(assets.length() - 3); + } + if (assets.isEmpty()) + { + assets = "legacy"; + } + auto finalizeArguments = [&]( QString & minecraftArguments, const QString & processArguments ) -> void + { + if (!minecraftArguments.isEmpty()) + return; + QString toCompare = processArguments.toLower(); + if (toCompare == "legacy") + { + minecraftArguments = " ${auth_player_name} ${auth_session}"; + } + else if (toCompare == "username_session") + { + minecraftArguments = "--username ${auth_player_name} --session ${auth_session}"; + } + else if (toCompare == "username_session_version") + { + minecraftArguments = "--username ${auth_player_name} " + "--session ${auth_session} " + "--version ${profile_name}"; + } + }; + finalizeArguments(vanillaMinecraftArguments, vanillaProcessArguments); + finalizeArguments(minecraftArguments, processArguments); +} + diff --git a/logic/minecraft/VersionFinal.h b/logic/minecraft/VersionFinal.h new file mode 100644 index 00000000..ceb90f57 --- /dev/null +++ b/logic/minecraft/VersionFinal.h @@ -0,0 +1,172 @@ +/* Copyright 2013 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 "OneSixLibrary.h" +#include "VersionFile.h" + +class OneSixInstance; + +class VersionFinal : public QAbstractListModel +{ + Q_OBJECT +public: + explicit VersionFinal(OneSixInstance *instance, QObject *parent = 0); + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual int columnCount(const QModelIndex &parent) const; + virtual Qt::ItemFlags flags(const QModelIndex &index) const; + + void reload(const QStringList &external = QStringList()); + void clear(); + + bool canRemove(const int index) const; + + QString versionFileId(const int index) const; + + // is this version unmodded vanilla minecraft? + bool isVanilla(); + // remove any customizations on top of vanilla + bool revertToVanilla(); + + // does this version have an FTB pack patch file? + bool hasFtbPack(); + // remove FTB pack + bool removeFtbPack(); + + // does this version have any jar mods? + bool hasJarMods(); + + // does this version still use a legacy custom.json file? + bool usesLegacyCustomJson(); + + + enum MoveDirection { MoveUp, MoveDown }; + void move(const int index, const MoveDirection direction); + void resetOrder(); + + // clears and reapplies all version files + void reapply(const bool alreadyReseting = false); + void finalize(); + +public +slots: + bool remove(const int index); + bool remove(const QString id); + +public: + QList<std::shared_ptr<OneSixLibrary>> getActiveNormalLibs(); + QList<std::shared_ptr<OneSixLibrary>> getActiveNativeLibs(); + + static std::shared_ptr<VersionFinal> fromJson(const QJsonObject &obj); + +private: + bool preremove(VersionFilePtr); + + // data members +public: + /// the ID - determines which jar to use! ACTUALLY IMPORTANT! + QString id; + /// Last updated time - as a string + QString time; + /// Release time - as a string + QString versionReleaseTime; + /// Release type - "release" or "snapshot" + QString type; + /// Assets type - "legacy" or a version ID + QString assets; + /** + * DEPRECATED: Old versions of the new vanilla launcher used this + * ex: "username_session_version" + */ + QString processArguments; + /// Same as above, but only for vanilla + QString vanillaProcessArguments; + /** + * 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 minecraftArguments; + /// Same as above, but only for vanilla + QString vanillaMinecraftArguments; + /** + * the minimum launcher version required by this version ... current is 4 (at point of + * writing) + */ + int minimumLauncherVersion = 0xDEADBEEF; + /** + * A list of all tweaker classes + */ + QStringList tweakers; + /** + * The main class to load first + */ + QString mainClass; + /** + * The applet class, for some very old minecraft releases + */ + QString appletClass; + + /// the list of libs - both active and inactive, native and java + QList<std::shared_ptr<OneSixLibrary>> libraries; + + /// same, but only vanilla. + QList<std::shared_ptr<OneSixLibrary>> vanillaLibraries; + + /// traits, collected from all the version files (version files can only add) + QSet<QString> traits; + + /// A list of jar mods. version files can add those. + QList<JarmodPtr> jarMods; + + /* + 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; + + QList<VersionFilePtr> versionFiles; + VersionFilePtr versionFile(const QString &id); + +private: + OneSixInstance *m_instance; + QMap<QString, int> getExistingOrder() const; +}; |