diff options
author | Petr Mrázek <peterix@gmail.com> | 2014-05-11 12:37:21 +0200 |
---|---|---|
committer | Petr Mrázek <peterix@gmail.com> | 2014-06-09 01:38:30 +0200 |
commit | 69c3e7111f93290d1278d6116e9fd50079b4fe79 (patch) | |
tree | cbaed3022e8705f1da29777afea0fca16c1abe60 /logic/minecraft | |
parent | 92abe4c603e1f4931cd02ae6b752cb7054d8e30d (diff) | |
download | MultiMC-69c3e7111f93290d1278d6116e9fd50079b4fe79.tar MultiMC-69c3e7111f93290d1278d6116e9fd50079b4fe79.tar.gz MultiMC-69c3e7111f93290d1278d6116e9fd50079b4fe79.tar.lz MultiMC-69c3e7111f93290d1278d6116e9fd50079b4fe79.tar.xz MultiMC-69c3e7111f93290d1278d6116e9fd50079b4fe79.zip |
Make 1.6+ work with new instance format.
Diffstat (limited to 'logic/minecraft')
-rw-r--r-- | logic/minecraft/JarMod.cpp | 12 | ||||
-rw-r--r-- | logic/minecraft/JarMod.h | 1 | ||||
-rw-r--r-- | logic/minecraft/MinecraftVersion.cpp | 60 | ||||
-rw-r--r-- | logic/minecraft/MinecraftVersion.h | 73 | ||||
-rw-r--r-- | logic/minecraft/MinecraftVersionList.cpp | 518 | ||||
-rw-r--r-- | logic/minecraft/MinecraftVersionList.h | 36 | ||||
-rw-r--r-- | logic/minecraft/OneSixLibrary.cpp | 79 | ||||
-rw-r--r-- | logic/minecraft/OneSixLibrary.h | 32 | ||||
-rw-r--r-- | logic/minecraft/OneSixRule.h | 7 | ||||
-rw-r--r-- | logic/minecraft/ParseUtils.cpp | 7 | ||||
-rw-r--r-- | logic/minecraft/ParseUtils.h | 2 | ||||
-rw-r--r-- | logic/minecraft/RawLibrary.cpp | 155 | ||||
-rw-r--r-- | logic/minecraft/RawLibrary.h | 37 | ||||
-rw-r--r-- | logic/minecraft/VersionBuilder.h | 6 | ||||
-rw-r--r-- | logic/minecraft/VersionFile.cpp | 203 | ||||
-rw-r--r-- | logic/minecraft/VersionFile.h | 6 | ||||
-rw-r--r-- | logic/minecraft/VersionPatch.h | 2 | ||||
-rw-r--r-- | logic/minecraft/VersionSource.h | 9 |
18 files changed, 822 insertions, 423 deletions
diff --git a/logic/minecraft/JarMod.cpp b/logic/minecraft/JarMod.cpp index 99a30aa5..18a9411c 100644 --- a/logic/minecraft/JarMod.cpp +++ b/logic/minecraft/JarMod.cpp @@ -1,5 +1,6 @@ #include "JarMod.h" #include "logic/MMCJson.h" +using namespace MMCJson; JarmodPtr Jarmod::fromJson(const QJsonObject &libObj, const QString &filename) { @@ -28,6 +29,7 @@ JarmodPtr Jarmod::fromJson(const QJsonObject &libObj, const QString &filename) }; readString("url", out->baseurl); + readString("MMC-hint", out->hint); readString("MMC-absoluteUrl", out->absoluteUrl); if(!out->baseurl.isEmpty() && out->absoluteUrl.isEmpty()) { @@ -36,6 +38,16 @@ JarmodPtr Jarmod::fromJson(const QJsonObject &libObj, const QString &filename) return out; } +QJsonObject Jarmod::toJson() +{ + QJsonObject out; + writeString(out, "name", name); + writeString(out, "url", baseurl); + writeString(out, "MMC-absoluteUrl", absoluteUrl); + writeString(out, "MMC-hint", hint); + return out; +} + QString Jarmod::url() { if(!absoluteUrl.isEmpty()) diff --git a/logic/minecraft/JarMod.h b/logic/minecraft/JarMod.h index da5d8db0..c438dbcd 100644 --- a/logic/minecraft/JarMod.h +++ b/logic/minecraft/JarMod.h @@ -8,6 +8,7 @@ class Jarmod { public: /* methods */ static JarmodPtr fromJson(const QJsonObject &libObj, const QString &filename); + QJsonObject toJson(); QString url(); public: /* data */ QString name; diff --git a/logic/minecraft/MinecraftVersion.cpp b/logic/minecraft/MinecraftVersion.cpp index 2191e8af..e0cbce8d 100644 --- a/logic/minecraft/MinecraftVersion.cpp +++ b/logic/minecraft/MinecraftVersion.cpp @@ -1,29 +1,26 @@ #include "MinecraftVersion.h" #include "VersionFinal.h" +#include "VersionBuildError.h" +#include "VersionBuilder.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) + if (is_snapshot) { return QObject::tr("Snapshot"); } @@ -32,22 +29,41 @@ QString MinecraftVersion::typeString() const return QObject::tr("Regular release"); } } + bool MinecraftVersion::hasJarMods() { return false; } -bool MinecraftVersion::isVanilla() + +bool MinecraftVersion::isMinecraftVersion() { return true; } +// 1. assume the local file is good. load, check. If it's good, apply. +// 2. if discrepancies are found, fall out and fail (impossible to apply incomplete version). +void MinecraftVersion::applyFileTo(VersionFinal *version) +{ + QFileInfo versionFile(QString("versions/%1/%1.json").arg(m_descriptor)); + + auto versionObj = VersionBuilder::parseJsonFile(versionFile, false, false); + versionObj->applyTo(version); +} + void MinecraftVersion::applyTo(VersionFinal *version) { - // FIXME: make this work. - if(m_versionSource != Builtin) + // do we have this one cached? + if (m_versionSource == Local) { + applyFileTo(version); return; } + // if not builtin, do not proceed any further. + if (m_versionSource != Builtin) + { + throw VersionIncomplete(QObject::tr( + "Minecraft version %1 could not be applied: version files are missing.").arg(m_descriptor)); + } if (!m_descriptor.isNull()) { version->id = m_descriptor; @@ -81,15 +97,35 @@ void MinecraftVersion::applyTo(VersionFinal *version) } 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>(); } + +QString MinecraftVersion::getPatchName() +{ + return "Minecraft"; +} +QString MinecraftVersion::getPatchVersion() +{ + return m_descriptor; +} +QString MinecraftVersion::getPatchID() +{ + return "net.minecraft"; +} +QString MinecraftVersion::getPatchFilename() +{ + return QString(); +} diff --git a/logic/minecraft/MinecraftVersion.h b/logic/minecraft/MinecraftVersion.h index 6a1c54cb..02afd709 100644 --- a/logic/minecraft/MinecraftVersion.h +++ b/logic/minecraft/MinecraftVersion.h @@ -22,26 +22,49 @@ #include "logic/BaseVersion.h" #include "VersionPatch.h" #include "VersionFile.h" +#include "VersionSource.h" class VersionFinal; +class MinecraftVersion; +typedef std::shared_ptr<MinecraftVersion> MinecraftVersionPtr; -struct MinecraftVersion : public BaseVersion, public VersionPatch +class MinecraftVersion : public BaseVersion, public VersionPatch { +public: /* methods */ + bool usesLegacyLauncher(); + virtual QString descriptor() override; + virtual QString name() override; + virtual QString typeString() const override; + virtual bool hasJarMods() override; + virtual bool isMinecraftVersion() override; + virtual void applyTo(VersionFinal *version) override; + virtual int getOrder(); + virtual void setOrder(int order); + virtual QList<JarmodPtr> getJarMods() override; + virtual QString getPatchID() override; + virtual QString getPatchVersion() override; + virtual QString getPatchName() override; + virtual QString getPatchFilename() override; + bool needsUpdate() + { + return m_versionSource == Remote; + } + bool hasUpdate() + { + return m_versionSource == Remote || (m_versionSource == Local && upstreamUpdate); + } + +private: /* methods */ + void applyFileTo(VersionFinal *version); + +public: /* data */ /// 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; - /// where is this from? - enum VersionSource - { - Builtin, - Mojang - } m_versionSource = Builtin; + VersionSource m_versionSource = Builtin; /// the human readable version name QString m_name; @@ -74,31 +97,7 @@ struct MinecraftVersion : public BaseVersion, public VersionPatch /// 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() - { - return "net.minecraft"; - } - virtual QString getPatchVersion() - { - return m_descriptor; - } - virtual QString getPatchName() - { - return "Minecraft"; - } - virtual QString getPatchFilename() - { - return QString(); - }; + + /// an update available from Mojang + MinecraftVersionPtr upstreamUpdate; }; diff --git a/logic/minecraft/MinecraftVersionList.cpp b/logic/minecraft/MinecraftVersionList.cpp index 1aa220e8..5a5ea348 100644 --- a/logic/minecraft/MinecraftVersionList.cpp +++ b/logic/minecraft/MinecraftVersionList.cpp @@ -13,27 +13,35 @@ * limitations under the License. */ -#include "MinecraftVersionList.h" -#include "MultiMC.h" -#include "logic/net/URLConstants.h" +#include <QtXml> #include "logic/MMCJson.h" -#include "ParseUtils.h" +#include <QtAlgorithms> +#include <QtNetwork> -#include <QtXml> +#include "MultiMC.h" +#include "MMCError.h" -#include <QJsonDocument> -#include <QJsonObject> -#include <QJsonArray> -#include <QJsonValue> -#include <QJsonParseError> +#include "MinecraftVersionList.h" +#include "logic/net/URLConstants.h" -#include <QtAlgorithms> +#include "ParseUtils.h" +#include "VersionBuilder.h" +#include <logic/VersionFilterData.h> +#include <pathutils.h> -#include <QtNetwork> +class ListLoadError : public MMCError +{ +public: + ListLoadError(QString cause) : MMCError(cause) {}; + virtual ~ListLoadError() noexcept + { + } +}; MinecraftVersionList::MinecraftVersionList(QObject *parent) : BaseVersionList(parent) { loadBuiltinList(); + loadCachedList(); } Task *MinecraftVersionList::getLoadTask() @@ -68,6 +76,35 @@ void MinecraftVersionList::sortInternal() qSort(m_vlist.begin(), m_vlist.end(), cmpVersions); } +void MinecraftVersionList::loadCachedList() +{ + QFile localIndex("versions/versions.json"); + if (!localIndex.exists()) + { + return; + } + if (!localIndex.open(QIODevice::ReadOnly)) + { + // FIXME: this is actually a very bad thing! How do we deal with this? + QLOG_ERROR() << "The minecraft version cache can't be read."; + return; + } + auto data = localIndex.readAll(); + try + { + loadMojangList(data, Local); + } + catch (MMCError &e) + { + // the cache has gone bad for some reason... flush it. + QLOG_ERROR() << "The minecraft version cache is corrupted. Flushing cache."; + localIndex.close(); + localIndex.remove(); + return; + } + m_hasLocalIndex = true; +} + void MinecraftVersionList::loadBuiltinList() { // grab the version list data from internal resources. @@ -93,19 +130,22 @@ void MinecraftVersionList::loadBuiltinList() continue; } + if (g_VersionFilterData.legacyBlacklist.contains(versionID)) + { + QLOG_ERROR() << "Blacklisted legacy version ignored: " << versionID; + 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. - try - { - parse_timestamp(versionObj.value("releaseTime").toString(""), - mcVersion->m_releaseTimeString, mcVersion->m_releaseTime); - } - catch (MMCError &e) + if (!parse_timestamp(versionObj.value("releaseTime").toString(""), + mcVersion->m_releaseTimeString, mcVersion->m_releaseTime)) { - QLOG_ERROR() << "Error while parsing version" << versionID << ":" << e.cause(); + QLOG_ERROR() << "Error while parsing version" << versionID + << ": invalid version timestamp"; continue; } @@ -113,7 +153,7 @@ void MinecraftVersionList::loadBuiltinList() mcVersion->download_url = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + versionID + "/"; - mcVersion->m_versionSource = MinecraftVersion::Builtin; + mcVersion->m_versionSource = Builtin; mcVersion->m_appletClass = versionObj.value("appletClass").toString(""); mcVersion->m_mainClass = versionObj.value("mainClass").toString(""); mcVersion->m_processArguments = versionObj.value("processArguments").toString("legacy"); @@ -124,10 +164,141 @@ void MinecraftVersionList::loadBuiltinList() mcVersion->m_traits.insert(MMCJson::ensureString(traitVal)); } } + m_lookup[versionID] = mcVersion; m_vlist.append(mcVersion); } } +void MinecraftVersionList::loadMojangList(QByteArray data, VersionSource source) +{ + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + + if (jsonError.error != QJsonParseError::NoError) + { + throw ListLoadError( + tr("Error parsing version list JSON: %1").arg(jsonError.errorString())); + } + + QLOG_INFO() << ((source == Remote) ? "Remote version list: " : "Local version list:") << data; + if (!jsonDoc.isObject()) + { + throw ListLoadError(tr("Error parsing version list JSON: jsonDoc is not an object")); + } + + QJsonObject root = jsonDoc.object(); + + try + { + QJsonObject latest = MMCJson::ensureObject(root.value("latest")); + m_latestReleaseID = MMCJson::ensureString(latest.value("release")); + m_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()) + { + throw ListLoadError(tr("Error parsing version list JSON: version list object is " + "missing 'versions' array")); + } + QJsonArray versions = root.value("versions").toArray(); + + QList<BaseVersionPtr> tempList; + for (auto version : versions) + { + bool is_snapshot = false; + + // Load the version info. + if (!version.isObject()) + { + QLOG_ERROR() << "Error while parsing version list : invalid JSON structure"; + continue; + } + + QJsonObject versionObj = version.toObject(); + QString versionID = versionObj.value("id").toString(""); + if (versionID.isEmpty()) + { + QLOG_ERROR() << "Error while parsing version : version ID is missing"; + continue; + } + + if (g_VersionFilterData.legacyBlacklist.contains(versionID)) + { + QLOG_ERROR() << "Blacklisted legacy version ignored: " << versionID; + 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; + + if (!parse_timestamp(versionObj.value("releaseTime").toString(""), + mcVersion->m_releaseTimeString, mcVersion->m_releaseTime)) + { + QLOG_ERROR() << "Error while parsing version" << versionID + << ": invalid release timestamp"; + continue; + } + if (!parse_timestamp(versionObj.value("time").toString(""), + mcVersion->m_updateTimeString, mcVersion->m_updateTime)) + { + QLOG_ERROR() << "Error while parsing version" << versionID + << ": invalid update timestamp"; + continue; + } + + if (mcVersion->m_releaseTime < g_VersionFilterData.legacyCutoffDate) + { + QLOG_ERROR() << "Ignoring Mojang version: " << versionID; + continue; + } + + // depends on where we load the version from -- network request or local file? + mcVersion->m_versionSource = source; + + QString dlUrl = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + versionID + "/"; + mcVersion->download_url = dlUrl; + 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_snapshot = false; + } + else if (versionTypeStr == "snapshot") // It's a snapshot... yay + { + is_snapshot = true; + } + else if (versionTypeStr == "old_alpha") + { + is_snapshot = false; + } + else if (versionTypeStr == "old_beta") + { + is_snapshot = false; + } + else + { + // FIXME: log this somewhere + continue; + } + mcVersion->m_type = versionTypeStr; + mcVersion->is_snapshot = is_snapshot; + tempList.append(mcVersion); + } + updateListData(tempList); +} + void MinecraftVersionList::sort() { beginResetModel(); @@ -137,14 +308,8 @@ void MinecraftVersionList::sort() 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); - } - } + if(m_lookup.contains(m_latestReleaseID)) + return m_lookup[m_latestReleaseID]; return BaseVersionPtr(); } @@ -154,17 +319,29 @@ void MinecraftVersionList::updateListData(QList<BaseVersionPtr> versions) for (auto version : versions) { auto descr = version->descriptor(); - for (auto builtin_v : m_vlist) + + if (!m_lookup.contains(descr)) { - if (descr == builtin_v->descriptor()) - { - goto SKIP_THIS_ONE; - } + m_vlist.append(version); + continue; } - m_vlist.append(version); - SKIP_THIS_ONE: - { - } + auto orig = std::dynamic_pointer_cast<MinecraftVersion>(m_lookup[descr]); + auto added = std::dynamic_pointer_cast<MinecraftVersion>(version); + // updateListData is called after Mojang list loads. those can be local or remote + // remote comes always after local + // any other options are ignored + if (orig->m_versionSource != Local || added->m_versionSource != Remote) + { + continue; + } + // is it actually an update? + if (orig->m_updateTime >= added->m_updateTime) + { + // nope. + continue; + } + // alright, it's an update. put it inside the original, for further processing. + orig->upstreamUpdate = added; } m_loaded = true; sortInternal(); @@ -187,10 +364,6 @@ MCVListLoadTask::MCVListLoadTask(MinecraftVersionList *vlist) vlistReply = nullptr; } -MCVListLoadTask::~MCVListLoadTask() -{ -} - void MCVListLoadTask::executeTask() { setStatus(tr("Loading instance version list...")); @@ -209,133 +382,196 @@ void MCVListLoadTask::list_downloaded() return; } - auto foo = vlistReply->readAll(); - QJsonParseError jsonError; - QLOG_INFO() << foo; - QJsonDocument jsonDoc = QJsonDocument::fromJson(foo, &jsonError); + auto data = vlistReply->readAll(); vlistReply->deleteLater(); - - if (jsonError.error != QJsonParseError::NoError) + try { - emitFailed("Error parsing version list JSON:" + jsonError.errorString()); - return; + m_list->loadMojangList(data, Remote); } - - if (!jsonDoc.isObject()) + catch (MMCError &e) { - emitFailed("Error parsing version list JSON: jsonDoc is not an object"); + emitFailed(e.cause()); return; } - QJsonObject root = jsonDoc.object(); + emitSucceeded(); + return; +} - QString latestReleaseID = "INVALID"; - QString latestSnapshotID = "INVALID"; - try +MCVListVersionUpdateTask::MCVListVersionUpdateTask(MinecraftVersionList *vlist, + QString updatedVersion) + : Task() +{ + m_list = vlist; + versionToUpdate = updatedVersion; +} + +void MCVListVersionUpdateTask::executeTask() +{ + QString urlstr = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + versionToUpdate + "/" + + versionToUpdate + ".json"; + auto job = new NetJob("Version index"); + job->addNetAction(ByteArrayDownload::make(QUrl(urlstr))); + specificVersionDownloadJob.reset(job); + connect(specificVersionDownloadJob.get(), SIGNAL(succeeded()), SLOT(json_downloaded())); + connect(specificVersionDownloadJob.get(), SIGNAL(failed(QString)), SIGNAL(failed(QString))); + connect(specificVersionDownloadJob.get(), SIGNAL(progress(qint64, qint64)), + SIGNAL(progress(qint64, qint64))); + specificVersionDownloadJob->start(); +} + +void MCVListVersionUpdateTask::json_downloaded() +{ + NetActionPtr DlJob = specificVersionDownloadJob->first(); + auto data = std::dynamic_pointer_cast<ByteArrayDownload>(DlJob)->m_data; + specificVersionDownloadJob.reset(); + + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + + if (jsonError.error != QJsonParseError::NoError) { - QJsonObject latest = MMCJson::ensureObject(root.value("latest")); - latestReleaseID = MMCJson::ensureString(latest.value("release")); - latestSnapshotID = MMCJson::ensureString(latest.value("snapshot")); + emitFailed(tr("The download version file is not valid.")); + return; } - catch (MMCError &err) + VersionFilePtr file; + try { - QLOG_ERROR() - << tr("Error parsing version list JSON: couldn't determine latest versions"); + file = VersionFile::fromJson(jsonDoc, "net.minecraft.json", false); } - - // Now, get the array of versions. - if (!root.value("versions").isArray()) + catch (MMCError &e) { - emitFailed( - "Error parsing version list JSON: version list object is missing 'versions' array"); + emitFailed(tr("Couldn't process version file: %1").arg(e.cause())); return; } - QJsonArray versions = root.value("versions").toArray(); - - QList<BaseVersionPtr> tempList; - for (auto version : versions) + QList<RawLibraryPtr> filteredLibs; + QList<RawLibraryPtr> lwjglLibs; + QSet<QString> lwjglFilter = { + "net.java.jinput:jinput", "net.java.jinput:jinput-platform", + "net.java.jutils:jutils", "org.lwjgl.lwjgl:lwjgl", + "org.lwjgl.lwjgl:lwjgl_util", "org.lwjgl.lwjgl:lwjgl-platform"}; + for (auto lib : file->overwriteLibs) { - bool is_snapshot = false; - bool is_latest = false; - - // Load the version info. - if (!version.isObject()) + if (lwjglFilter.contains(lib->fullname())) { - QLOG_ERROR() << "Error while parsing version list : invalid JSON structure"; - continue; + lwjglLibs.append(lib); } - QJsonObject versionObj = version.toObject(); - QString versionID = versionObj.value("id").toString(""); - if (versionID.isEmpty()) + else { - QLOG_ERROR() << "Error while parsing version : version ID is missing"; - continue; + filteredLibs.append(lib); } - // Get the download URL. - QString dlUrl = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + versionID + "/"; + } + file->overwriteLibs = filteredLibs; - // 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; + // TODO: recognize and add LWJGL versions here. - try - { - // Parse the timestamps. - parse_timestamp(versionObj.value("releaseTime").toString(""), - mcVersion->m_releaseTimeString, mcVersion->m_releaseTime); + file->fileId = "net.minecraft"; - parse_timestamp(versionObj.value("time").toString(""), - mcVersion->m_updateTimeString, mcVersion->m_updateTime); - } - catch (MMCError &e) - { - QLOG_ERROR() << "Error while parsing version" << versionID << ":" << e.cause(); + // now dump the file to disk + auto doc = file->toJson(false); + auto newdata = doc.toJson(); + QLOG_INFO() << newdata; + QString targetPath = "versions/" + versionToUpdate + "/" + versionToUpdate + ".json"; + ensureFilePathExists(targetPath); + QSaveFile vfile1(targetPath); + if (!vfile1.open(QIODevice::Truncate | QIODevice::WriteOnly)) + { + emitFailed(tr("Can't open %1 for writing.").arg(targetPath)); + return; + } + qint64 actual = 0; + if ((actual = vfile1.write(newdata)) != newdata.size()) + { + emitFailed(tr("Failed to write into %1. Written %2 out of %3.") + .arg(targetPath) + .arg(actual) + .arg(newdata.size())); + return; + } + if (!vfile1.commit()) + { + emitFailed(tr("Can't commit changes to %1").arg(targetPath)); + return; + } + + m_list->finalizeUpdate(versionToUpdate); + emitSucceeded(); +} + +std::shared_ptr<Task> MinecraftVersionList::createUpdateTask(QString version) +{ + return std::shared_ptr<Task>(new MCVListVersionUpdateTask(this, version)); +} + +void MinecraftVersionList::saveCachedList() +{ + // FIXME: throw. + if (!ensureFilePathExists("versions/versions.json")) + return; + QSaveFile tfile("versions/versions.json"); + if (!tfile.open(QIODevice::WriteOnly | QIODevice::Truncate)) + return; + QJsonObject toplevel; + QJsonArray entriesArr; + for (auto version : m_vlist) + { + auto mcversion = std::dynamic_pointer_cast<MinecraftVersion>(version); + // do not save the remote versions. + if (mcversion->m_versionSource != Local) continue; - } + QJsonObject entryObj; - mcVersion->m_versionSource = MinecraftVersion::Builtin; - mcVersion->download_url = dlUrl; + entryObj.insert("id", mcversion->descriptor()); + entryObj.insert("time", mcversion->m_updateTimeString); + entryObj.insert("releaseTime", mcversion->m_releaseTimeString); + entryObj.insert("type", mcversion->m_type); + entriesArr.append(entryObj); + } + toplevel.insert("versions", entriesArr); + QJsonDocument doc(toplevel); + QByteArray jsonData = doc.toJson(); + qint64 result = tfile.write(jsonData); + if (result == -1) + return; + if (result != jsonData.size()) + return; + tfile.commit(); +} + +void MinecraftVersionList::finalizeUpdate(QString version) +{ + int idx = -1; + for (int i = 0; i < m_vlist.size(); i++) + { + if (version == m_vlist[i]->descriptor()) { - 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; + idx = i; + break; } - tempList.append(mcVersion); } - m_list->updateListData(tempList); + if (idx == -1) + { + return; + } - emitSucceeded(); - return; + auto updatedVersion = std::dynamic_pointer_cast<MinecraftVersion>(m_vlist[idx]); + + if (updatedVersion->m_versionSource == Builtin) + return; + + if (updatedVersion->upstreamUpdate) + { + auto updatedWith = updatedVersion->upstreamUpdate; + updatedWith->m_versionSource = Local; + m_vlist[idx] = updatedWith; + m_lookup[version] = updatedWith; + } + else + { + updatedVersion->m_versionSource = Local; + } + + dataChanged(index(idx), index(idx)); + + saveCachedList(); } diff --git a/logic/minecraft/MinecraftVersionList.h b/logic/minecraft/MinecraftVersionList.h index 18b9b21e..bbbd71e1 100644 --- a/logic/minecraft/MinecraftVersionList.h +++ b/logic/minecraft/MinecraftVersionList.h @@ -22,8 +22,10 @@ #include "logic/BaseVersionList.h" #include "logic/tasks/Task.h" #include "logic/minecraft/MinecraftVersion.h" +#include <logic/net/NetJob.h> class MCVListLoadTask; +class MCVListVersionUpdateTask; class QNetworkReply; class MinecraftVersionList : public BaseVersionList @@ -32,11 +34,18 @@ class MinecraftVersionList : public BaseVersionList private: void sortInternal(); void loadBuiltinList(); + void loadMojangList(QByteArray data, VersionSource source); + void loadCachedList(); + void saveCachedList(); + void finalizeUpdate(QString version); public: friend class MCVListLoadTask; + friend class MCVListVersionUpdateTask; explicit MinecraftVersionList(QObject *parent = 0); + std::shared_ptr<Task> createUpdateTask(QString version); + virtual Task *getLoadTask(); virtual bool isLoaded(); virtual const BaseVersionPtr at(int i) const; @@ -47,8 +56,12 @@ public: protected: QList<BaseVersionPtr> m_vlist; + QMap<QString, BaseVersionPtr> m_lookup; bool m_loaded = false; + bool m_hasLocalIndex = false; + QString m_latestReleaseID = "INVALID"; + QString m_latestSnapshotID = "INVALID"; protected slots: @@ -61,9 +74,9 @@ class MCVListLoadTask : public Task public: explicit MCVListLoadTask(MinecraftVersionList *vlist); - ~MCVListLoadTask(); + virtual ~MCVListLoadTask() override{}; - virtual void executeTask(); + virtual void executeTask() override; protected slots: @@ -74,3 +87,22 @@ protected: MinecraftVersionList *m_list; MinecraftVersion *m_currentStable; }; + +class MCVListVersionUpdateTask : public Task +{ + Q_OBJECT + +public: + explicit MCVListVersionUpdateTask(MinecraftVersionList *vlist, QString updatedVersion); + virtual ~MCVListVersionUpdateTask() override{}; + virtual void executeTask() override; + +protected +slots: + void json_downloaded(); + +protected: + NetJobPtr specificVersionDownloadJob; + QString versionToUpdate; + MinecraftVersionList *m_list; +}; diff --git a/logic/minecraft/OneSixLibrary.cpp b/logic/minecraft/OneSixLibrary.cpp index 45fa169e..7f69d9f8 100644 --- a/logic/minecraft/OneSixLibrary.cpp +++ b/logic/minecraft/OneSixLibrary.cpp @@ -23,6 +23,23 @@ #include <JlCompress.h> #include "logger/QsLog.h" +OneSixLibrary::OneSixLibrary(RawLibraryPtr base) +{ + m_name = base->m_name; + m_base_url = base->m_base_url; + m_hint = base->m_hint; + m_absolute_url = base->m_absolute_url; + extract_excludes = base->extract_excludes; + m_native_suffixes = base->m_native_suffixes; + m_rules = base->m_rules; + finalize(); +} + +OneSixLibraryPtr OneSixLibrary::fromRawLibrary(RawLibraryPtr lib) +{ + return OneSixLibraryPtr(new OneSixLibrary(lib)); +} + void OneSixLibrary::finalize() { QStringList parts = m_name.split(':'); @@ -30,7 +47,7 @@ void OneSixLibrary::finalize() relative.replace('.', '/'); relative += '/' + parts[1] + '/' + parts[2] + '/' + parts[1] + '-' + parts[2]; - if (!m_is_native) + if (!isNative()) relative += ".jar"; else { @@ -65,7 +82,7 @@ void OneSixLibrary::finalize() } m_is_active = (result == Allow); } - if (m_is_native) + if (isNative()) { m_is_active = m_is_active && m_native_suffixes.contains(currentSystem); m_decenttype = "Native"; @@ -84,13 +101,8 @@ 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() @@ -105,10 +117,6 @@ 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()) @@ -223,52 +231,3 @@ bool OneSixLibrary::extractTo(QString target_dir) } 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 index 13df0606..3d38985b 100644 --- a/logic/minecraft/OneSixLibrary.h +++ b/logic/minecraft/OneSixLibrary.h @@ -24,27 +24,16 @@ #include "logic/net/URLConstants.h" #include "logic/minecraft/OpSys.h" +#include "logic/minecraft/RawLibrary.h" class Rule; class OneSixLibrary; typedef std::shared_ptr<OneSixLibrary> OneSixLibraryPtr; -class OneSixLibrary +class OneSixLibrary : public RawLibrary { 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 @@ -57,13 +46,9 @@ private: 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 @@ -80,15 +65,16 @@ public: m_name = name; dependType = type; } - + /// Constructor + OneSixLibrary(RawLibraryPtr base); + static OneSixLibraryPtr fromRawLibrary(RawLibraryPtr lib); + /// 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 * @@ -116,8 +102,6 @@ public: /// 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 @@ -127,8 +111,6 @@ public: /// 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 diff --git a/logic/minecraft/OneSixRule.h b/logic/minecraft/OneSixRule.h index 23d04151..a18093b0 100644 --- a/logic/minecraft/OneSixRule.h +++ b/logic/minecraft/OneSixRule.h @@ -16,8 +16,13 @@ #pragma once #include <QString> +#include <QList> +#include <QJsonObject> +#include <memory> +#include "OpSys.h" -#include "logic/minecraft/OneSixLibrary.h" +class OneSixLibrary; +class Rule; enum RuleAction { diff --git a/logic/minecraft/ParseUtils.cpp b/logic/minecraft/ParseUtils.cpp index 67460ca4..f94de6ff 100644 --- a/logic/minecraft/ParseUtils.cpp +++ b/logic/minecraft/ParseUtils.cpp @@ -8,16 +8,17 @@ QDateTime timeFromS3Time(QString str) return QDateTime::fromString(str, Qt::ISODate); } -void parse_timestamp (const QString & raw, QString & save_here, QDateTime & parse_here) +bool parse_timestamp (const QString & raw, QString & save_here, QDateTime & parse_here) { save_here = raw; if (save_here.isEmpty()) { - throw JSONValidationError("The timestamp is empty!"); + return false; } parse_here = timeFromS3Time(save_here); if (!parse_here.isValid()) { - throw JSONValidationError("The timestamp not a valid timestamp!"); + return false; } + return true; } diff --git a/logic/minecraft/ParseUtils.h b/logic/minecraft/ParseUtils.h index 73cf526a..bd2f6ffb 100644 --- a/logic/minecraft/ParseUtils.h +++ b/logic/minecraft/ParseUtils.h @@ -6,7 +6,7 @@ * 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); +bool parse_timestamp (const QString &raw, QString &save_here, QDateTime &parse_here); /** * take the timestamp used by S3 and turn it into QDateTime diff --git a/logic/minecraft/RawLibrary.cpp b/logic/minecraft/RawLibrary.cpp index 12aac8c8..7e0ebff0 100644 --- a/logic/minecraft/RawLibrary.cpp +++ b/logic/minecraft/RawLibrary.cpp @@ -11,7 +11,7 @@ RawLibraryPtr RawLibrary::fromJson(const QJsonObject &libObj, const QString &fil throw JSONValidationError(filename + "contains a library that doesn't have a 'name' field"); } - out->name = libObj.value("name").toString(); + out->m_name = libObj.value("name").toString(); auto readString = [libObj, filename](const QString & key, QString & variable) { @@ -29,22 +29,21 @@ RawLibraryPtr RawLibrary::fromJson(const QJsonObject &libObj, const QString &fil } }; - readString("url", out->url); - readString("MMC-hint", out->hint); - readString("MMC-absulute_url", out->absoluteUrl); - readString("MMC-absoluteUrl", out->absoluteUrl); + readString("url", out->m_base_url); + readString("MMC-hint", out->m_hint); + readString("MMC-absulute_url", out->m_absolute_url); + readString("MMC-absoluteUrl", out->m_absolute_url); 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)); + out->extract_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) { @@ -55,14 +54,152 @@ RawLibraryPtr RawLibrary::fromJson(const QJsonObject &libObj, const QString &fil OpSys opSys = OpSys_fromString(it.key()); if (opSys != Os_Other) { - out->natives.append(qMakePair(opSys, it.value().toString())); + out->m_native_suffixes[opSys] = it.value().toString(); } } } if (libObj.contains("rules")) { out->applyRules = true; - out->rules = rulesFromJsonV4(libObj); + out->m_rules = rulesFromJsonV4(libObj); } return out; } + +RawLibraryPtr RawLibrary::fromJsonPlus(const QJsonObject &libObj, const QString &filename) +{ + auto lib = RawLibrary::fromJson(libObj, filename); + if (libObj.contains("insert")) + { + QJsonValue insertVal = ensureExists(libObj.value("insert"), "library insert rule"); + 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::Append; + } + 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"); + } + } + return lib; +} + +bool RawLibrary::isNative() const +{ + return m_native_suffixes.size() != 0; +} + +QJsonObject RawLibrary::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()) + { + 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 (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; +} + +QString RawLibrary::fullname() +{ + QStringList parts = m_name.split(':'); + return parts[0] + ":" + parts[1]; +} + +QString RawLibrary::group() +{ + QStringList parts = m_name.split(':'); + return parts[0]; +} + +QString RawLibrary::version() +{ + QStringList parts = m_name.split(':'); + return parts[2]; +} diff --git a/logic/minecraft/RawLibrary.h b/logic/minecraft/RawLibrary.h index 46178d8e..f5a28c61 100644 --- a/logic/minecraft/RawLibrary.h +++ b/logic/minecraft/RawLibrary.h @@ -1,9 +1,14 @@ #pragma once #include <QString> #include <QPair> +#include <QList> +#include <QStringList> +#include <QMap> #include <memory> -#include "OneSixRule.h" +#include "logic/minecraft/OneSixRule.h" +#include "logic/minecraft/OpSys.h" +#include "logic/net/URLConstants.h" class RawLibrary; typedef std::shared_ptr<RawLibrary> RawLibraryPtr; @@ -11,24 +16,36 @@ typedef std::shared_ptr<RawLibrary> RawLibraryPtr; class RawLibrary { public: /* methods */ + /// read and create a basic library static RawLibraryPtr fromJson(const QJsonObject &libObj, const QString &filename); + /// read and create a MultiMC '+' library. Those have some extra fields. + static RawLibraryPtr fromJsonPlus(const QJsonObject &libObj, const QString &filename); + QJsonObject toJson(); + + QString fullname(); + QString version(); + QString group(); public: /* data */ - QString name; - QString url; - QString hint; - QString absoluteUrl; + QString m_name; + QString m_base_url = "https://" + URLConstants::LIBRARY_BASE; + /// type hint - modifies how the library is treated + QString m_hint; + /// absolute URL. takes precedence over m_download_path, if defined + QString m_absolute_url; bool applyExcludes = false; - QStringList excludes; + QStringList extract_excludes; - bool applyNatives = false; - QList<QPair<OpSys, QString>> natives; + /// Returns true if the library is native + bool isNative() const; + /// native suffixes per OS + QMap<OpSys, QString> m_native_suffixes; bool applyRules = false; - QList<std::shared_ptr<Rule>> rules; + QList<std::shared_ptr<Rule>> m_rules; - // user for '+' libraries + // used for '+' libraries enum InsertType { Apply, diff --git a/logic/minecraft/VersionBuilder.h b/logic/minecraft/VersionBuilder.h index c56e6c18..fc06e614 100644 --- a/logic/minecraft/VersionBuilder.h +++ b/logic/minecraft/VersionBuilder.h @@ -30,7 +30,8 @@ class VersionBuilder public: static void build(VersionFinal *version, OneSixInstance *instance, const QStringList &external); static void readJsonAndApplyToVersion(VersionFinal *version, const QJsonObject &obj); - + static VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder, bool isFTB = false); + static QMap<QString, int> readOverrideOrders(OneSixInstance *instance); static bool writeOverrideOrders(const QMap<QString, int> &order, OneSixInstance *instance); @@ -49,7 +50,4 @@ private: void readInstancePatches(); void readJsonAndApply(const QJsonObject &obj); - - VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder, - bool isFTB = false); }; diff --git a/logic/minecraft/VersionFile.cpp b/logic/minecraft/VersionFile.cpp index bc9c42c1..74e7ba98 100644 --- a/logic/minecraft/VersionFile.cpp +++ b/logic/minecraft/VersionFile.cpp @@ -98,14 +98,10 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi readString("+minecraftArguments", out->addMinecraftArguments); readString("-minecraftArguments", out->removeMinecraftArguments); readString("type", out->type); - 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"); + parse_timestamp(readStringRet("releaseTime"), out->m_releaseTimeString, out->m_releaseTime); + parse_timestamp(readStringRet("time"), out->m_updateTimeString, out->m_updateTime); + readString("assets", out->assets); if (root.contains("minimumLauncherVersion")) @@ -158,7 +154,7 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi // FIXME: This should be done when applying. if (isFTB) { - lib->hint = "local"; + lib->m_hint = "local"; lib->insertType = RawLibrary::Prepend; out->addLibs.prepend(lib); } @@ -186,68 +182,8 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi 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"); - } - } + auto lib = RawLibrary::fromJsonPlus(libObj, filename); out->addLibs.append(lib); } } @@ -263,32 +199,66 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi return out; } -OneSixLibraryPtr VersionFile::createLibrary(RawLibraryPtr lib) +QJsonDocument VersionFile::toJson(bool saveOrder) { - std::shared_ptr<OneSixLibrary> out(new OneSixLibrary(lib->name)); - if (!lib->url.isEmpty()) + QJsonObject root; + if (saveOrder) + { + root.insert("order", order); + } + writeString(root, "name", name); + writeString(root, "fileId", fileId); + writeString(root, "version", version); + writeString(root, "mcVersion", mcVersion); + writeString(root, "id", id); + writeString(root, "mainClass", mainClass); + writeString(root, "appletClass", appletClass); + writeString(root, "processArguments", processArguments); + writeString(root, "minecraftArguments", overwriteMinecraftArguments); + writeString(root, "+minecraftArguments", addMinecraftArguments); + writeString(root, "-minecraftArguments", removeMinecraftArguments); + writeString(root, "type", type); + writeString(root, "assets", assets); + if (isMinecraftVersion()) + { + writeString(root, "releaseTime", m_releaseTimeString); + writeString(root, "time", m_updateTimeString); + } + if (minimumLauncherVersion != -1) { - out->setBaseUrl(lib->url); + root.insert("minimumLauncherVersion", minimumLauncherVersion); } - out->setHint(lib->hint); - if (!lib->absoluteUrl.isEmpty()) + writeStringList(root, "tweakers", overwriteTweakers); + writeStringList(root, "+tweakers", addTweakers); + writeStringList(root, "-tweakers", removeTweakers); + writeStringList(root, "+traits", traits.toList()); + writeObjectList(root, "libraries", overwriteLibs); + writeObjectList(root, "+libraries", addLibs); + writeObjectList(root, "+jarMods", jarMods); + // FIXME: removed libs are special snowflakes. + if (removeLibs.size()) { - out->setAbsoluteUrl(lib->absoluteUrl); + QJsonArray array; + for (auto lib : removeLibs) + { + QJsonObject rmlibobj; + rmlibobj.insert("name", lib); + array.append(rmlibobj); + } + root.insert("-libraries", array); } - out->setAbsoluteUrl(lib->absoluteUrl); - out->extract_excludes = lib->excludes; - for (auto native : lib->natives) + // write the contents to a json document. { - out->addNative(native.first, native.second); + QJsonDocument out; + out.setObject(root); + return out; } - out->setRules(lib->rules); - out->finalize(); - return out; } -bool VersionFile::isVanilla() +bool VersionFile::isMinecraftVersion() { - return fileId == "org.multimc.version.json"; + return (fileId == "org.multimc.version.json") || (fileId == "net.minecraft") || + (fileId == "org.multimc.custom.json"); } bool VersionFile::hasJarMods() @@ -330,13 +300,13 @@ void VersionFile::applyTo(VersionFinal *version) } if (!processArguments.isNull()) { - if (isVanilla()) + if (isMinecraftVersion()) { version->vanillaProcessArguments = processArguments; } version->processArguments = processArguments; } - if(isVanilla()) + if (isMinecraftVersion()) { if (!type.isNull()) { @@ -359,12 +329,12 @@ void VersionFile::applyTo(VersionFinal *version) } if (minimumLauncherVersion >= 0) { - if(version->minimumLauncherVersion < minimumLauncherVersion) + if (version->minimumLauncherVersion < minimumLauncherVersion) version->minimumLauncherVersion = minimumLauncherVersion; } if (!overwriteMinecraftArguments.isNull()) { - if (isVanilla()) + if (isMinecraftVersion()) { version->vanillaMinecraftArguments = overwriteMinecraftArguments; } @@ -397,10 +367,12 @@ void VersionFile::applyTo(VersionFinal *version) QList<OneSixLibraryPtr> libs; for (auto lib : overwriteLibs) { - libs.append(createLibrary(lib)); + libs.append(OneSixLibrary::fromRawLibrary(lib)); } - if (isVanilla()) + if (isMinecraftVersion()) + { version->vanillaLibraries = libs; + } version->libraries = libs; } for (auto lib : addLibs) @@ -410,43 +382,46 @@ void VersionFile::applyTo(VersionFinal *version) case RawLibrary::Apply: { // QLOG_INFO() << "Applying lib " << lib->name; - int index = findLibrary(version->libraries, lib->name); + int index = findLibrary(version->libraries, lib->m_name); if (index >= 0) { auto library = version->libraries[index]; - if (!lib->url.isNull()) + if (!lib->m_base_url.isNull()) { - library->setBaseUrl(lib->url); + library->setBaseUrl(lib->m_base_url); } - if (!lib->hint.isNull()) + if (!lib->m_hint.isNull()) { - library->setHint(lib->hint); + library->setHint(lib->m_hint); } - if (!lib->absoluteUrl.isNull()) + if (!lib->m_absolute_url.isNull()) { - library->setAbsoluteUrl(lib->absoluteUrl); + library->setAbsoluteUrl(lib->m_absolute_url); } if (lib->applyExcludes) { - library->extract_excludes = lib->excludes; + library->extract_excludes = lib->extract_excludes; } - if (lib->applyNatives) + if (lib->isNative()) { - library->clearSuffixes(); + // library->clearSuffixes(); + library->m_native_suffixes = lib->m_native_suffixes; + /* for (auto native : lib->natives) { library->addNative(native.first, native.second); } + */ } if (lib->applyRules) { - library->setRules(lib->rules); + library->setRules(lib->m_rules); } library->finalize(); } else { - QLOG_WARN() << "Couldn't find" << lib->name << "(skipping)"; + QLOG_WARN() << "Couldn't find" << lib->m_name << "(skipping)"; } break; } @@ -454,24 +429,24 @@ void VersionFile::applyTo(VersionFinal *version) case RawLibrary::Prepend: { // QLOG_INFO() << "Adding lib " << lib->name; - const int startOfVersion = lib->name.lastIndexOf(':') + 1; + const int startOfVersion = lib->m_name.lastIndexOf(':') + 1; const int index = findLibrary( - version->libraries, QString(lib->name).replace(startOfVersion, INT_MAX, '*')); + version->libraries, QString(lib->m_name).replace(startOfVersion, INT_MAX, '*')); if (index < 0) { if (lib->insertType == RawLibrary::Append) { - version->libraries.append(createLibrary(lib)); + version->libraries.append(OneSixLibrary::fromRawLibrary(lib)); } else { - version->libraries.prepend(createLibrary(lib)); + version->libraries.prepend(OneSixLibrary::fromRawLibrary(lib)); } } else { auto otherLib = version->libraries.at(index); - const Util::Version ourVersion = lib->name.mid(startOfVersion, INT_MAX); + const Util::Version ourVersion = lib->m_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 @@ -485,7 +460,7 @@ void VersionFile::applyTo(VersionFinal *version) throw VersionBuildError( QObject::tr( "Error resolving library dependencies between %1 and %2 in %3.") - .arg(otherLib->rawName(), lib->name, filename)); + .arg(otherLib->rawName(), lib->m_name, filename)); } else { @@ -497,7 +472,7 @@ void VersionFile::applyTo(VersionFinal *version) // if we are higher it means we should update if (ourVersion > otherVersion) { - auto library = createLibrary(lib); + auto library = OneSixLibrary::fromRawLibrary(lib); if (Util::Version(otherLib->minVersion) < ourVersion) { library->minVersion = ourVersion.toString(); @@ -512,7 +487,7 @@ void VersionFile::applyTo(VersionFinal *version) { throw VersionBuildError(QObject::tr( "Error resolving library dependencies between %1 and %2 in %3.") - .arg(otherLib->rawName(), lib->name, + .arg(otherLib->rawName(), lib->m_name, filename)); } } @@ -525,8 +500,8 @@ void VersionFile::applyTo(VersionFinal *version) QString toReplace; if (lib->insertData.isEmpty()) { - const int startOfVersion = lib->name.lastIndexOf(':') + 1; - toReplace = QString(lib->name).replace(startOfVersion, INT_MAX, '*'); + const int startOfVersion = lib->m_name.lastIndexOf(':') + 1; + toReplace = QString(lib->m_name).replace(startOfVersion, INT_MAX, '*'); } else toReplace = lib->insertData; @@ -534,7 +509,7 @@ void VersionFile::applyTo(VersionFinal *version) int index = findLibrary(version->libraries, toReplace); if (index >= 0) { - version->libraries.replace(index, createLibrary(lib)); + version->libraries.replace(index, OneSixLibrary::fromRawLibrary(lib)); } else { diff --git a/logic/minecraft/VersionFile.h b/logic/minecraft/VersionFile.h index 9c632d6f..2d3781cb 100644 --- a/logic/minecraft/VersionFile.h +++ b/logic/minecraft/VersionFile.h @@ -8,7 +8,7 @@ #include "logic/minecraft/OneSixRule.h" #include "VersionPatch.h" #include "MMCError.h" -#include "RawLibrary.h" +#include "OneSixLibrary.h" #include "JarMod.h" class VersionFinal; @@ -20,10 +20,10 @@ class VersionFile : public VersionPatch public: /* methods */ static VersionFilePtr fromJson(const QJsonDocument &doc, const QString &filename, const bool requireOrder, const bool isFTB = false); + QJsonDocument toJson(bool saveOrder); - static OneSixLibraryPtr createLibrary(RawLibraryPtr lib); virtual void applyTo(VersionFinal *version) override; - virtual bool isVanilla() override; + virtual bool isMinecraftVersion() override; virtual bool hasJarMods() override; virtual int getOrder() override { diff --git a/logic/minecraft/VersionPatch.h b/logic/minecraft/VersionPatch.h index cdb80199..ef082b37 100644 --- a/logic/minecraft/VersionPatch.h +++ b/logic/minecraft/VersionPatch.h @@ -11,7 +11,7 @@ public: virtual ~VersionPatch(){}; virtual void applyTo(VersionFinal *version) = 0; - virtual bool isVanilla() = 0; + virtual bool isMinecraftVersion() = 0; virtual bool hasJarMods() = 0; virtual QList<JarmodPtr> getJarMods() = 0; diff --git a/logic/minecraft/VersionSource.h b/logic/minecraft/VersionSource.h new file mode 100644 index 00000000..75b2c24b --- /dev/null +++ b/logic/minecraft/VersionSource.h @@ -0,0 +1,9 @@ +#pragma once + +/// where is a version from? +enum VersionSource +{ + Builtin, //!< version loaded from the internal resources. + Local, //!< version loaded from a file in the cache. + Remote, //!< incomplete version on a remote server. +};
\ No newline at end of file |