diff options
Diffstat (limited to 'logic/minecraft')
-rw-r--r-- | logic/minecraft/MinecraftVersion.cpp | 93 | ||||
-rw-r--r-- | logic/minecraft/MinecraftVersion.h | 102 | ||||
-rw-r--r-- | logic/minecraft/MinecraftVersionList.cpp | 167 | ||||
-rw-r--r-- | logic/minecraft/ParseUtils.cpp | 23 | ||||
-rw-r--r-- | logic/minecraft/ParseUtils.h | 14 | ||||
-rw-r--r-- | logic/minecraft/VersionBuildError.h | 58 | ||||
-rw-r--r-- | logic/minecraft/VersionBuilder.cpp | 220 | ||||
-rw-r--r-- | logic/minecraft/VersionBuilder.h | 13 | ||||
-rw-r--r-- | logic/minecraft/VersionFile.cpp | 69 | ||||
-rw-r--r-- | logic/minecraft/VersionFile.h | 83 | ||||
-rw-r--r-- | logic/minecraft/VersionFinal.cpp | 99 | ||||
-rw-r--r-- | logic/minecraft/VersionFinal.h | 20 | ||||
-rw-r--r-- | logic/minecraft/VersionPatch.h | 16 |
13 files changed, 646 insertions, 331 deletions
diff --git a/logic/minecraft/MinecraftVersion.cpp b/logic/minecraft/MinecraftVersion.cpp index a2e5a50a..2191e8af 100644 --- a/logic/minecraft/MinecraftVersion.cpp +++ b/logic/minecraft/MinecraftVersion.cpp @@ -1,2 +1,95 @@ #include "MinecraftVersion.h" +#include "VersionFinal.h" +bool MinecraftVersion::usesLegacyLauncher() +{ + return m_traits.contains("legacyLaunch") || m_traits.contains("aplhaLaunch"); +} +QString MinecraftVersion::descriptor() +{ + return m_descriptor; +} +QString MinecraftVersion::name() +{ + return m_name; +} +QString MinecraftVersion::typeString() const +{ + 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 + { + return QObject::tr("Regular release"); + } +} +bool MinecraftVersion::hasJarMods() +{ + return false; +} +bool MinecraftVersion::isVanilla() +{ + return true; +} + +void MinecraftVersion::applyTo(VersionFinal *version) +{ + // FIXME: make this work. + if(m_versionSource != Builtin) + { + return; + } + if (!m_descriptor.isNull()) + { + version->id = m_descriptor; + } + if (!m_mainClass.isNull()) + { + version->mainClass = m_mainClass; + } + if (!m_appletClass.isNull()) + { + version->appletClass = m_appletClass; + } + if (!m_processArguments.isNull()) + { + version->vanillaProcessArguments = m_processArguments; + version->processArguments = m_processArguments; + } + if (!m_type.isNull()) + { + version->type = m_type; + } + if (!m_releaseTimeString.isNull()) + { + version->m_releaseTimeString = m_releaseTimeString; + version->m_releaseTime = m_releaseTime; + } + if (!m_updateTimeString.isNull()) + { + version->m_updateTimeString = m_updateTimeString; + version->m_updateTime = m_updateTime; + } + version->traits.unite(m_traits); +} +int MinecraftVersion::getOrder() +{ + return order; +} +void MinecraftVersion::setOrder(int order) +{ + this->order = order; +} +QList<JarmodPtr> MinecraftVersion::getJarMods() +{ + return QList<JarmodPtr>(); +} diff --git a/logic/minecraft/MinecraftVersion.h b/logic/minecraft/MinecraftVersion.h index dab08bb1..6a1c54cb 100644 --- a/logic/minecraft/MinecraftVersion.h +++ b/logic/minecraft/MinecraftVersion.h @@ -15,16 +15,18 @@ #pragma once -#include "logic/BaseVersion.h" -#include "VersionPatch.h" #include <QStringList> #include <QSet> +#include <QDateTime> + +#include "logic/BaseVersion.h" +#include "VersionPatch.h" +#include "VersionFile.h" + +class VersionFinal; struct MinecraftVersion : public BaseVersion, public VersionPatch { - /// 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; @@ -34,16 +36,20 @@ struct MinecraftVersion : public BaseVersion, public VersionPatch /// is this a snapshot? bool is_snapshot = false; - /// is this a built-in version that comes with MultiMC? - bool is_builtin = false; - + /// where is this from? + enum VersionSource + { + Builtin, + Mojang + } m_versionSource = Builtin; + /// the human readable version name QString m_name; /// the version ID. QString m_descriptor; - /// version traits. generally launcher business... + /// version traits. added by MultiMC QSet<QString> m_traits; /// The main class this version uses (if any, can be empty). @@ -52,57 +58,47 @@ struct MinecraftVersion : public BaseVersion, public VersionPatch /// 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; - } + /// The process arguments used by this version + QString m_processArguments; - virtual QString name() override - { - return m_name; - } + /// The type of this release + QString m_type; - virtual QString typeString() const override + /// the time this version was actually released by Mojang, as string and as QDateTime + QString m_releaseTimeString; + QDateTime m_releaseTime; + + /// the time this version was last updated by Mojang, as string and as QDateTime + QString m_updateTimeString; + QDateTime m_updateTime; + + /// order of this file... default = -2 + int order = -2; + + bool usesLegacyLauncher(); + virtual QString descriptor() override; + virtual QString name() override; + virtual QString typeString() const override; + virtual bool hasJarMods() override; + virtual bool isVanilla() override; + virtual void applyTo(VersionFinal *version) override; + virtual int getOrder(); + virtual void setOrder(int order); + virtual QList<JarmodPtr> getJarMods() override; + virtual QString getPatchID() { - 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"); - } + return "net.minecraft"; } - - virtual bool hasJarMods() override + virtual QString getPatchVersion() { - return false; + return m_descriptor; } - - virtual bool isVanilla() override + virtual QString getPatchName() { - return true; + return "Minecraft"; } - - virtual void applyTo(VersionFinal *version) + virtual QString getPatchFilename() { - // umm... what now? - } + return QString(); + }; }; diff --git a/logic/minecraft/MinecraftVersionList.cpp b/logic/minecraft/MinecraftVersionList.cpp index cdf5fa77..1aa220e8 100644 --- a/logic/minecraft/MinecraftVersionList.cpp +++ b/logic/minecraft/MinecraftVersionList.cpp @@ -16,7 +16,8 @@ #include "MinecraftVersionList.h" #include "MultiMC.h" #include "logic/net/URLConstants.h" -#include <logic/MMCJson.h> +#include "logic/MMCJson.h" +#include "ParseUtils.h" #include <QtXml> @@ -30,11 +31,6 @@ #include <QtNetwork> -inline QDateTime timeFromS3Time(QString str) -{ - return QDateTime::fromString(str, Qt::ISODate); -} - MinecraftVersionList::MinecraftVersionList(QObject *parent) : BaseVersionList(parent) { loadBuiltinList(); @@ -64,7 +60,7 @@ 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; + return left->m_releaseTime > right->m_releaseTime; } void MinecraftVersionList::sortInternal() @@ -79,55 +75,55 @@ void MinecraftVersionList::loadBuiltinList() 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")) + if (versionID.isEmpty() || versionTypeStr.isEmpty()) { - for (auto traitVal : MMCJson::ensureArray(versionObj.value("+traits"))) - { - traits.insert(MMCJson::ensureString(traitVal)); - } - } - if (versionID.isEmpty() || versionTimeStr.isEmpty() || versionTypeStr.isEmpty()) - { - // FIXME: log this somewhere + QLOG_ERROR() << "Parsed version is missing ID or type"; continue; } + + // 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; + // Parse the timestamp. - QDateTime versionTime = timeFromS3Time(versionTimeStr); - if (!versionTime.isValid()) + try + { + parse_timestamp(versionObj.value("releaseTime").toString(""), + mcVersion->m_releaseTimeString, mcVersion->m_releaseTime); + } + catch (MMCError &e) { - // FIXME: log this somewhere + QLOG_ERROR() << "Error while parsing version" << versionID << ":" << e.cause(); 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(""); + // Get the download URL. + mcVersion->download_url = + "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_builtin = true; - mcVersion->m_appletClass = appletClass; - mcVersion->m_mainClass = mainClass; - mcVersion->m_traits = traits; + mcVersion->m_versionSource = MinecraftVersion::Builtin; + mcVersion->m_appletClass = versionObj.value("appletClass").toString(""); + mcVersion->m_mainClass = versionObj.value("mainClass").toString(""); + mcVersion->m_processArguments = versionObj.value("processArguments").toString("legacy"); + if (versionObj.contains("+traits")) + { + for (auto traitVal : MMCJson::ensureArray(versionObj.value("+traits"))) + { + mcVersion->m_traits.insert(MMCJson::ensureString(traitVal)); + } + } m_vlist.append(mcVersion); } } @@ -265,62 +261,77 @@ void MCVListLoadTask::list_downloaded() // Load the version info. if (!version.isObject()) { - // FIXME: log this somewhere + QLOG_ERROR() << "Error while parsing version list : invalid JSON structure"; 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()) + if (versionID.isEmpty()) { - // FIXME: log this somewhere + QLOG_ERROR() << "Error while parsing version : version ID is missing"; continue; } + // Get the download URL. + QString dlUrl = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + versionID + "/"; - // 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") + // 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; + + try { - is_latest = false; - is_snapshot = false; + // Parse the timestamps. + parse_timestamp(versionObj.value("releaseTime").toString(""), + mcVersion->m_releaseTimeString, mcVersion->m_releaseTime); + + parse_timestamp(versionObj.value("time").toString(""), + mcVersion->m_updateTimeString, mcVersion->m_updateTime); } - else + catch (MMCError &e) { - // FIXME: log this somewhere + QLOG_ERROR() << "Error while parsing version" << versionID << ":" << e.cause(); 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->m_versionSource = MinecraftVersion::Builtin; mcVersion->download_url = dlUrl; - mcVersion->is_latest = is_latest; - mcVersion->is_snapshot = is_snapshot; + { + QString versionTypeStr = versionObj.value("type").toString(""); + if (versionTypeStr.isEmpty()) + { + // 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; + } + mcVersion->m_type = versionTypeStr; + mcVersion->is_latest = is_latest; + mcVersion->is_snapshot = is_snapshot; + } tempList.append(mcVersion); } m_list->updateListData(tempList); diff --git a/logic/minecraft/ParseUtils.cpp b/logic/minecraft/ParseUtils.cpp new file mode 100644 index 00000000..67460ca4 --- /dev/null +++ b/logic/minecraft/ParseUtils.cpp @@ -0,0 +1,23 @@ +#include <QDateTime> +#include <QString> +#include "ParseUtils.h" +#include <logic/MMCJson.h> + +QDateTime timeFromS3Time(QString str) +{ + return QDateTime::fromString(str, Qt::ISODate); +} + +void parse_timestamp (const QString & raw, QString & save_here, QDateTime & parse_here) +{ + save_here = raw; + if (save_here.isEmpty()) + { + throw JSONValidationError("The timestamp is empty!"); + } + parse_here = timeFromS3Time(save_here); + if (!parse_here.isValid()) + { + throw JSONValidationError("The timestamp not a valid timestamp!"); + } +} diff --git a/logic/minecraft/ParseUtils.h b/logic/minecraft/ParseUtils.h new file mode 100644 index 00000000..73cf526a --- /dev/null +++ b/logic/minecraft/ParseUtils.h @@ -0,0 +1,14 @@ +#pragma once +#include <QString> +#include <QDateTime> + +/** + * parse the S3 timestamp in 'raw' and fill the forwarded variables. + * return true/false for success/failure + */ +void parse_timestamp (const QString &raw, QString &save_here, QDateTime &parse_here); + +/** + * take the timestamp used by S3 and turn it into QDateTime + */ +QDateTime timeFromS3Time(QString str); diff --git a/logic/minecraft/VersionBuildError.h b/logic/minecraft/VersionBuildError.h new file mode 100644 index 00000000..ae479851 --- /dev/null +++ b/logic/minecraft/VersionBuildError.h @@ -0,0 +1,58 @@ +#include "MMCError.h" + +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 + { + } +}; + +/** + * files required for the version are not (yet?) present + */ +class VersionIncomplete : public VersionBuildError +{ +public: + VersionIncomplete(QString missingPatch) + : VersionBuildError(QObject::tr("Version is incomplete: missing %1.") + .arg(missingPatch)) {}; + virtual ~VersionIncomplete() noexcept + { + } +};
\ No newline at end of file diff --git a/logic/minecraft/VersionBuilder.cpp b/logic/minecraft/VersionBuilder.cpp index 56eef424..d19c2877 100644 --- a/logic/minecraft/VersionBuilder.cpp +++ b/logic/minecraft/VersionBuilder.cpp @@ -23,12 +23,17 @@ #include <QObject> #include <QDir> #include <QDebug> +#include <qresource.h> #include <modutils.h> +#include "MultiMC.h" #include "logic/minecraft/VersionBuilder.h" #include "logic/minecraft/VersionFinal.h" #include "logic/minecraft/OneSixRule.h" +#include "logic/minecraft/VersionPatch.h" #include "logic/minecraft/VersionFile.h" +#include "VersionBuildError.h" +#include "MinecraftVersionList.h" #include "logic/OneSixInstance.h" #include "logic/MMCJson.h" @@ -39,16 +44,17 @@ VersionBuilder::VersionBuilder() { } -void VersionBuilder::build(VersionFinal *version, OneSixInstance *instance, const QStringList &external) +void VersionBuilder::build(VersionFinal *version, OneSixInstance *instance, + const QStringList &external) { VersionBuilder builder; builder.m_version = version; builder.m_instance = instance; - builder.buildInternal(external); + builder.external_patches = external; + builder.buildInternal(); } -void VersionBuilder::readJsonAndApplyToVersion(VersionFinal *version, - const QJsonObject &obj) +void VersionBuilder::readJsonAndApplyToVersion(VersionFinal *version, const QJsonObject &obj) { VersionBuilder builder; builder.m_version = version; @@ -56,88 +62,143 @@ void VersionBuilder::readJsonAndApplyToVersion(VersionFinal *version, builder.readJsonAndApply(obj); } -void VersionBuilder::buildInternal(const QStringList &external) +void VersionBuilder::buildFromCustomJson() { - m_version->versionFiles.clear(); + QLOG_INFO() << "Building version from custom.json within the instance."; + QLOG_INFO() << "Reading custom.json"; + auto file = parseJsonFile(QFileInfo(instance_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->VersionPatches.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.") + // some final touches + m_version->finalize(); + return; +} - QDir root(m_instance->instanceRoot()); - QDir patches(root.absoluteFilePath("patches/")); +void VersionBuilder::buildFromVersionJson() +{ + QLOG_INFO() << "Building version from version.json and patches within the instance."; + QLOG_INFO() << "Reading version.json"; + auto file = parseJsonFile(QFileInfo(instance_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->VersionPatches.append(file); - // if we do external files, do just those. - if (!external.isEmpty()) + // load all patches, put into map for ordering, apply in the right order + readInstancePatches(); + + // some final touches + m_version->finalize(); +} + +void VersionBuilder::readInstancePatches() +{ + QDir patches(instance_root.absoluteFilePath("patches/")); + QMap<int, QPair<QString, VersionFilePtr>> files; + for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) { - int externalOrder = -1; - for (auto fileName : external) + QLOG_INFO() << "Reading" << info.fileName(); + auto file = parseJsonFile(info, true); + if(file->fileId == "net.minecraft") + continue; + if(file->fileId == "org.lwjgl") + continue; + if (files.contains(file->order)) { - 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); + 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)); } - // 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.") + for (auto order : files.keys()) + { + auto &filePair = files[order]; + m_version->VersionPatches.append(filePair.second); } - // 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); +} +void VersionBuilder::buildFromExternalPatches() +{ + QLOG_INFO() << "Building version from external files."; + int externalOrder = -1; + for (auto fileName : external_patches) + { + 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->VersionPatches.append(file); + } // some final touches m_version->finalize(); } +void VersionBuilder::buildFromMultilayer() +{ + QLOG_INFO() << "Building version from multilayered sources."; + // just the builtin stuff for now + auto minecraftList = MMC->minecraftlist(); + auto mcversion = minecraftList->findVersion(m_instance->intendedVersionId()); + auto minecraftPatch = std::dynamic_pointer_cast<VersionPatch>(mcversion); + if(!minecraftPatch) + { + throw VersionIncomplete("net.minecraft"); + } + minecraftPatch->setOrder(-2); + m_version->VersionPatches.append(minecraftPatch); + QResource LWJGL(":/versions/LWJGL/2.9.1.json"); + auto lwjgl = parseJsonFile(LWJGL.absoluteFilePath(), false, false); + auto lwjglPatch = std::dynamic_pointer_cast<VersionPatch>(lwjgl); + if(!lwjglPatch) + { + throw VersionIncomplete("org.lwjgl"); + } + lwjglPatch->setOrder(-1); + m_version->VersionPatches.append(lwjglPatch); + + // load all patches, put into map for ordering, apply in the right order + readInstancePatches(); + + m_version->finalize(); +} + +void VersionBuilder::buildInternal() +{ + m_version->VersionPatches.clear(); + instance_root = QDir(m_instance->instanceRoot()); + // if we do external files, do just those. + if (!external_patches.isEmpty()) + { + buildFromExternalPatches(); + } + // else, if there's custom json, we just do that. + else if (QFile::exists(instance_root.absoluteFilePath("custom.json"))) + { + buildFromCustomJson(); + } + // version.json -> patches/*.json + else if (QFile::exists(instance_root.absoluteFilePath("version.json"))) + { + buildFromVersionJson(); + } + else + { + buildFromMultilayer(); + } +} void VersionBuilder::readJsonAndApply(const QJsonObject &obj) { @@ -147,14 +208,14 @@ void VersionBuilder::readJsonAndApply(const QJsonObject &obj) // QObject::tr("Error while reading. Please check MultiMC-0.log for more info.")); file->applyTo(m_version); - m_version->versionFiles.append(file); + m_version->VersionPatches.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 VersionBuilder::parseJsonFile(const QFileInfo &fileInfo, - const bool requireOrder, bool isFTB) +VersionFilePtr VersionBuilder::parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder, + bool isFTB) { QFile file(fileInfo.absoluteFilePath()); if (!file.open(QFile::ReadOnly)) @@ -166,9 +227,10 @@ VersionFilePtr VersionBuilder::parseJsonFile(const QFileInfo &fileInfo, 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)); + 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 @@ -226,7 +288,7 @@ QMap<QString, int> VersionBuilder::readOverrideOrders(OneSixInstance *instance) } bool VersionBuilder::writeOverrideOrders(const QMap<QString, int> &order, - OneSixInstance *instance) + OneSixInstance *instance) { QJsonObject obj; for (auto it = order.cbegin(); it != order.cend(); ++it) diff --git a/logic/minecraft/VersionBuilder.h b/logic/minecraft/VersionBuilder.h index ac93ef11..c56e6c18 100644 --- a/logic/minecraft/VersionBuilder.h +++ b/logic/minecraft/VersionBuilder.h @@ -37,8 +37,17 @@ public: private: VersionFinal *m_version; OneSixInstance *m_instance; - - void buildInternal(const QStringList& external); + QStringList external_patches; + QDir instance_root; + + void buildInternal(); + void buildFromExternalPatches(); + void buildFromCustomJson(); + void buildFromVersionJson(); + void buildFromMultilayer(); + + void readInstancePatches(); + void readJsonAndApply(const QJsonObject &obj); VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder, diff --git a/logic/minecraft/VersionFile.cpp b/logic/minecraft/VersionFile.cpp index e1b03e6c..bc9c42c1 100644 --- a/logic/minecraft/VersionFile.cpp +++ b/logic/minecraft/VersionFile.cpp @@ -8,10 +8,13 @@ #include "logic/minecraft/OneSixLibrary.h" #include "logic/minecraft/VersionFinal.h" #include "logic/minecraft/JarMod.h" +#include "ParseUtils.h" #include "logic/MMCJson.h" using namespace MMCJson; +#include "VersionBuildError.h" + #define CURRENT_MINIMUM_LAUNCHER_VERSION 14 int findLibrary(QList<OneSixLibraryPtr> haystack, const QString &needle) @@ -23,7 +26,7 @@ int findLibrary(QList<OneSixLibraryPtr> haystack, const QString &needle) if (QRegExp(needle, Qt::CaseSensitive, QRegExp::WildcardUnix).indexIn(chunk) != -1) { // only one is allowed. - if(retval != -1) + if (retval != -1) return -1; retval = i; } @@ -32,7 +35,7 @@ int findLibrary(QList<OneSixLibraryPtr> haystack, const QString &needle) } VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &filename, - const bool requireOrder, const bool isFTB) + const bool requireOrder, const bool isFTB) { VersionFilePtr out(new VersionFile()); if (doc.isEmpty() || doc.isNull()) @@ -41,7 +44,6 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi } if (!doc.isObject()) { - throw JSONValidationError("The root of " + filename + " is not an object"); } QJsonObject root = doc.object(); @@ -65,7 +67,7 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi out->mcVersion = root.value("mcVersion").toString(); out->filename = filename; - auto readString = [root, filename](const QString & key, QString & variable) + auto readString = [root](const QString & key, QString & variable) { if (root.contains(key)) { @@ -73,6 +75,16 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi } }; + auto readStringRet = [root](const QString & key)->QString + { + if (root.contains(key)) + { + return ensureString(root.value(key)); + } + return QString(); + } + ; + // FIXME: This should be ignored when applying. if (!isFTB) { @@ -86,8 +98,14 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi readString("+minecraftArguments", out->addMinecraftArguments); readString("-minecraftArguments", out->removeMinecraftArguments); readString("type", out->type); - readString("releaseTime", out->versionReleaseTime); - readString("time", out->versionFileUpdateTime); + if (out->isVanilla()) + { + parse_timestamp(readStringRet("releaseTime"), out->m_releaseTimeString, + out->m_releaseTime); + parse_timestamp(readStringRet("time"), out->m_updateTimeString, out->m_updateTime); + } + + readStringRet("time"); readString("assets", out->assets); if (root.contains("minimumLauncherVersion")) @@ -284,7 +302,8 @@ void VersionFile::applyTo(VersionFinal *version) { if (minimumLauncherVersion > CURRENT_MINIMUM_LAUNCHER_VERSION) { - throw LauncherVersionError(minimumLauncherVersion, CURRENT_MINIMUM_LAUNCHER_VERSION); + throw LauncherVersionError(minimumLauncherVersion, + CURRENT_MINIMUM_LAUNCHER_VERSION); } } @@ -311,23 +330,28 @@ void VersionFile::applyTo(VersionFinal *version) } if (!processArguments.isNull()) { - if(isVanilla()) + if (isVanilla()) { version->vanillaProcessArguments = processArguments; } version->processArguments = processArguments; } - if (!type.isNull()) + if(isVanilla()) { - version->type = type; - } - if (!versionReleaseTime.isNull()) - { - version->versionReleaseTime = versionReleaseTime; - } - if (!versionFileUpdateTime.isNull()) - { - version->time = versionFileUpdateTime; + if (!type.isNull()) + { + version->type = type; + } + if (!m_releaseTimeString.isNull()) + { + version->m_releaseTimeString = m_releaseTimeString; + version->m_releaseTime = m_releaseTime; + } + if (!m_updateTimeString.isNull()) + { + version->m_updateTimeString = m_updateTimeString; + version->m_updateTime = m_updateTime; + } } if (!assets.isNull()) { @@ -335,11 +359,12 @@ void VersionFile::applyTo(VersionFinal *version) } if (minimumLauncherVersion >= 0) { - version->minimumLauncherVersion = minimumLauncherVersion; + if(version->minimumLauncherVersion < minimumLauncherVersion) + version->minimumLauncherVersion = minimumLauncherVersion; } if (!overwriteMinecraftArguments.isNull()) { - if(isVanilla()) + if (isVanilla()) { version->vanillaMinecraftArguments = overwriteMinecraftArguments; } @@ -374,7 +399,7 @@ void VersionFile::applyTo(VersionFinal *version) { libs.append(createLibrary(lib)); } - if(isVanilla()) + if (isVanilla()) version->vanillaLibraries = libs; version->libraries = libs; } @@ -498,7 +523,7 @@ void VersionFile::applyTo(VersionFinal *version) case RawLibrary::Replace: { QString toReplace; - if(lib->insertData.isEmpty()) + if (lib->insertData.isEmpty()) { const int startOfVersion = lib->name.lastIndexOf(':') + 1; toReplace = QString(lib->name).replace(startOfVersion, INT_MAX, '*'); diff --git a/logic/minecraft/VersionFile.h b/logic/minecraft/VersionFile.h index 82b16913..9c632d6f 100644 --- a/logic/minecraft/VersionFile.h +++ b/logic/minecraft/VersionFile.h @@ -2,6 +2,7 @@ #include <QString> #include <QStringList> +#include <QDateTime> #include <memory> #include "logic/minecraft/OpSys.h" #include "logic/minecraft/OneSixRule.h" @@ -11,46 +12,8 @@ #include "JarMod.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 VersionFile; + typedef std::shared_ptr<VersionFile> VersionFilePtr; class VersionFile : public VersionPatch { @@ -62,7 +25,35 @@ public: /* methods */ virtual void applyTo(VersionFinal *version) override; virtual bool isVanilla() override; virtual bool hasJarMods() override; - + virtual int getOrder() override + { + return order; + } + virtual void setOrder(int order) override + { + this->order = order; + } + virtual QList<JarmodPtr> getJarMods() override + { + return jarMods; + } + virtual QString getPatchID() override + { + return fileId; + } + virtual QString getPatchName() override + { + return name; + } + virtual QString getPatchVersion() override + { + return version; + } + virtual QString getPatchFilename() override + { + return filename; + } + public: /* data */ int order = 0; QString name; @@ -81,8 +72,16 @@ public: /* data */ QString removeMinecraftArguments; QString processArguments; QString type; - QString versionReleaseTime; - QString versionFileUpdateTime; + + /// the time this version was actually released by Mojang, as string and as QDateTime + QString m_releaseTimeString; + QDateTime m_releaseTime; + + /// the time this version was last updated by Mojang, as string and as QDateTime + QString m_updateTimeString; + QDateTime m_updateTime; + + /// asset group used by this ... thing. QString assets; int minimumLauncherVersion = -1; diff --git a/logic/minecraft/VersionFinal.cpp b/logic/minecraft/VersionFinal.cpp index 07f58778..cc61da71 100644 --- a/logic/minecraft/VersionFinal.cpp +++ b/logic/minecraft/VersionFinal.cpp @@ -39,8 +39,10 @@ void VersionFinal::reload(const QStringList &external) void VersionFinal::clear() { id.clear(); - time.clear(); - versionReleaseTime.clear(); + m_updateTimeString.clear(); + m_updateTime = QDateTime(); + m_releaseTimeString.clear(); + m_releaseTime = QDateTime(); type.clear(); assets.clear(); processArguments.clear(); @@ -56,17 +58,13 @@ void VersionFinal::clear() bool VersionFinal::canRemove(const int index) const { - if (index < versionFiles.size()) - { - return versionFiles.at(index)->fileId != "org.multimc.version.json"; - } - return false; + return VersionPatches.at(index)->isMoveable(); } -bool VersionFinal::preremove(VersionFilePtr versionfile) +bool VersionFinal::preremove(VersionPatchPtr patch) { bool ok = true; - for(auto & jarmod: versionfile->jarMods) + for(auto & jarmod: patch->getJarMods()) { QString fullpath =PathCombine(m_instance->jarModsDir(), jarmod->name); QFileInfo finfo (fullpath); @@ -80,14 +78,14 @@ bool VersionFinal::remove(const int index) { if (!canRemove(index)) return false; - if(!preremove(versionFiles[index])) + if(!preremove(VersionPatches[index])) { return false; } - if(!QFile::remove(versionFiles.at(index)->filename)) + if(!QFile::remove(VersionPatches.at(index)->getPatchFilename())) return false; beginResetModel(); - versionFiles.removeAt(index); + VersionPatches.removeAt(index); reapply(true); endResetModel(); return true; @@ -96,9 +94,9 @@ bool VersionFinal::remove(const int index) bool VersionFinal::remove(const QString id) { int i = 0; - for (auto file : versionFiles) + for (auto patch : VersionPatches) { - if (file->fileId == id) + if (patch->getPatchID() == id) { return remove(i); } @@ -109,18 +107,18 @@ bool VersionFinal::remove(const QString id) QString VersionFinal::versionFileId(const int index) const { - if (index < 0 || index >= versionFiles.size()) + if (index < 0 || index >= VersionPatches.size()) { return QString(); } - return versionFiles.at(index)->fileId; + return VersionPatches.at(index)->getPatchID(); } -VersionFilePtr VersionFinal::versionPatch(const QString &id) +VersionPatchPtr VersionFinal::versionPatch(const QString &id) { - for (auto file : versionFiles) + for (auto file : VersionPatches) { - if (file->fileId == id) + if (file->getPatchID() == id) { return file; } @@ -128,6 +126,14 @@ VersionFilePtr VersionFinal::versionPatch(const QString &id) return 0; } +VersionPatchPtr VersionFinal::versionPatch(int index) +{ + if(index < 0 || index >= VersionPatches.size()) + return 0; + return VersionPatches[index]; +} + + bool VersionFinal::hasJarMods() { return !jarMods.isEmpty(); @@ -146,7 +152,7 @@ bool VersionFinal::removeFtbPack() bool VersionFinal::isVanilla() { QDir patches(PathCombine(m_instance->instanceRoot(), "patches/")); - if(versionFiles.size() > 1) + if(VersionPatches.size() > 1) return false; if(QFile::exists(PathCombine(m_instance->instanceRoot(), "custom.json"))) return false; @@ -156,22 +162,22 @@ bool VersionFinal::isVanilla() bool VersionFinal::revertToVanilla() { beginResetModel(); - auto it = versionFiles.begin(); - while (it != versionFiles.end()) + auto it = VersionPatches.begin(); + while (it != VersionPatches.end()) { - if ((*it)->fileId != "org.multimc.version.json") + if ((*it)->isMoveable()) { if(!preremove(*it)) { endResetModel(); return false; } - if(!QFile::remove((*it)->filename)) + if(!QFile::remove((*it)->getPatchFilename())) { endResetModel(); return false; } - it = versionFiles.erase(it); + it = VersionPatches.erase(it); } else it++; @@ -233,7 +239,7 @@ QVariant VersionFinal::data(const QModelIndex &index, int role) const int row = index.row(); int column = index.column(); - if (row < 0 || row >= versionFiles.size()) + if (row < 0 || row >= VersionPatches.size()) return QVariant(); if (role == Qt::DisplayRole) @@ -241,9 +247,9 @@ QVariant VersionFinal::data(const QModelIndex &index, int role) const switch (column) { case 0: - return versionFiles.at(row)->name; + return VersionPatches.at(row)->getPatchName(); case 1: - return versionFiles.at(row)->version; + return VersionPatches.at(row)->getPatchVersion(); default: return QVariant(); } @@ -278,7 +284,7 @@ Qt::ItemFlags VersionFinal::flags(const QModelIndex &index) const int VersionFinal::rowCount(const QModelIndex &parent) const { - return versionFiles.size(); + return VersionPatches.size(); } int VersionFinal::columnCount(const QModelIndex &parent) const @@ -288,13 +294,12 @@ int VersionFinal::columnCount(const QModelIndex &parent) const QMap<QString, int> VersionFinal::getExistingOrder() const { - QMap<QString, int> order; // default { - for (auto file : versionFiles) + for (auto file : VersionPatches) { - order.insert(file->fileId, file->order); + order.insert(file->getPatchID(), file->getOrder()); } } // overriden @@ -314,39 +319,37 @@ QMap<QString, int> VersionFinal::getExistingOrder() const void VersionFinal::move(const int index, const MoveDirection direction) { int theirIndex; + int theirIndex_qt; if (direction == MoveUp) { - theirIndex = index - 1; + theirIndex_qt = theirIndex = index - 1; } else { theirIndex = index + 1; + theirIndex_qt = index + 2; } - 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.")) + auto from = versionPatch(index); + auto to = versionPatch(theirIndex); + + if (!from || !to || !from->isMoveable() || !from->isMoveable()) { return; } if(direction == MoveDown) { - beginMoveRows(QModelIndex(), index, index, QModelIndex(), theirIndex+1); + beginMoveRows(QModelIndex(), index, index, QModelIndex(), theirIndex_qt); } else { - beginMoveRows(QModelIndex(), index, index, QModelIndex(), theirIndex); + beginMoveRows(QModelIndex(), index, index, QModelIndex(), theirIndex_qt); } - versionFiles.swap(index, theirIndex); + VersionPatches.swap(index, theirIndex); endMoveRows(); auto order = getExistingOrder(); - order[ourId] = theirIndex; - order[theirId] = index; + order[from->getPatchID()] = theirIndex; + order[to->getPatchID()] = index; if (!VersionBuilder::writeOverrideOrders(order, m_instance)) { @@ -375,14 +378,14 @@ void VersionFinal::reapply(const bool alreadyReseting) auto existingOrders = getExistingOrder(); QList<int> orders = existingOrders.values(); std::sort(orders.begin(), orders.end()); - QList<VersionFilePtr> newVersionFiles; + QList<VersionPatchPtr> newVersionFiles; for (auto order : orders) { auto file = versionPatch(existingOrders.key(order)); newVersionFiles.append(file); file->applyTo(this); } - versionFiles.swap(newVersionFiles); + VersionPatches.swap(newVersionFiles); finalize(); if (!alreadyReseting) { diff --git a/logic/minecraft/VersionFinal.h b/logic/minecraft/VersionFinal.h index 7be981ed..83a88263 100644 --- a/logic/minecraft/VersionFinal.h +++ b/logic/minecraft/VersionFinal.h @@ -83,16 +83,21 @@ public: static std::shared_ptr<VersionFinal> fromJson(const QJsonObject &obj); private: - bool preremove(VersionFilePtr); + bool preremove(VersionPatchPtr patch); // 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; + + /// the time this version was actually released by Mojang, as string and as QDateTime + QString m_releaseTimeString; + QDateTime m_releaseTime; + + /// the time this version was last updated by Mojang, as string and as QDateTime + QString m_updateTimeString; + QDateTime m_updateTime; + /// Release type - "release" or "snapshot" QString type; /// Assets type - "legacy" or a version ID @@ -164,8 +169,9 @@ public: */ // QList<Rule> rules; - QList<VersionFilePtr> versionFiles; - VersionFilePtr versionPatch(const QString &id); + QList<VersionPatchPtr> VersionPatches; + VersionPatchPtr versionPatch(const QString &id); + VersionPatchPtr versionPatch(int index); private: OneSixInstance *m_instance; diff --git a/logic/minecraft/VersionPatch.h b/logic/minecraft/VersionPatch.h index c2258787..cdb80199 100644 --- a/logic/minecraft/VersionPatch.h +++ b/logic/minecraft/VersionPatch.h @@ -1,6 +1,8 @@ #pragma once #include <memory> +#include <QList> +#include "JarMod.h" class VersionFinal; class VersionPatch @@ -8,8 +10,22 @@ class VersionPatch public: virtual ~VersionPatch(){}; virtual void applyTo(VersionFinal *version) = 0; + virtual bool isVanilla() = 0; virtual bool hasJarMods() = 0; + virtual QList<JarmodPtr> getJarMods() = 0; + + virtual bool isMoveable() + { + return getOrder() >= 0; + } + virtual void setOrder(int order) = 0; + virtual int getOrder() = 0; + + virtual QString getPatchID() = 0; + virtual QString getPatchName() = 0; + virtual QString getPatchVersion() = 0; + virtual QString getPatchFilename() = 0; }; typedef std::shared_ptr<VersionPatch> VersionPatchPtr; |