From 69c3e7111f93290d1278d6116e9fd50079b4fe79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 11 May 2014 12:37:21 +0200 Subject: Make 1.6+ work with new instance format. --- logic/minecraft/MinecraftVersionList.cpp | 518 ++++++++++++++++++++++--------- 1 file changed, 377 insertions(+), 141 deletions(-) (limited to 'logic/minecraft/MinecraftVersionList.cpp') 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 #include "logic/MMCJson.h" -#include "ParseUtils.h" +#include +#include -#include +#include "MultiMC.h" +#include "MMCError.h" -#include -#include -#include -#include -#include +#include "MinecraftVersionList.h" +#include "logic/net/URLConstants.h" -#include +#include "ParseUtils.h" +#include "VersionBuilder.h" +#include +#include -#include +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 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 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 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(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 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(m_lookup[descr]); + auto added = std::dynamic_pointer_cast(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(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 tempList; - for (auto version : versions) + QList filteredLibs; + QList lwjglLibs; + QSet 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 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 MinecraftVersionList::createUpdateTask(QString version) +{ + return std::shared_ptr(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(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(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(); } -- cgit v1.2.3