From e85f2e9a8533541f124b14124b93a68cab7a4529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sat, 9 Jul 2022 13:18:18 +0200 Subject: [PATCH] Import some useful code from MultiMC, add future TeamCity build index --- CMakeLists.txt | 36 ++ src/Exception.h | 32 ++ src/FileSystem.cpp | 457 ++++++++++++++++++++++ src/FileSystem.h | 127 ++++++ src/Json.cpp | 272 +++++++++++++ src/Json.h | 249 ++++++++++++ src/QObjectPtr.h | 89 +++++ src/mojang/PackageManifest.cpp | 434 ++++++++++++++++++++ src/mojang/PackageManifest.h | 178 +++++++++ src/mojang/PackageManifest_test.cpp | 351 +++++++++++++++++ src/mojang/testdata/1.8.0_202-x64.json | 1 + src/mojang/testdata/inspect/a/b.txt | 0 src/mojang/testdata/inspect/a/b/b.txt | 1 + src/mojang/testdata/inspect_win/a/b.txt | 0 src/mojang/testdata/inspect_win/a/b/b.txt | 0 src/net/ByteArraySink.h | 62 +++ src/net/ChecksumValidator.h | 55 +++ src/net/Download.cpp | 296 ++++++++++++++ src/net/Download.h | 75 ++++ src/net/FileSink.cpp | 114 ++++++ src/net/FileSink.h | 28 ++ src/net/Mode.h | 10 + src/net/NetAction.h | 122 ++++++ src/net/NetJob.cpp | 218 +++++++++++ src/net/NetJob.h | 91 +++++ src/net/Sink.h | 70 ++++ src/net/Validator.h | 18 + src/pathmatcher/FSTreeMatcher.h | 21 + src/pathmatcher/IPathMatcher.h | 13 + src/pathmatcher/MultiMatcher.h | 31 ++ src/pathmatcher/RegexpMatcher.h | 42 ++ src/tasks/SequentialTask.cpp | 55 +++ src/tasks/SequentialTask.h | 30 ++ src/tasks/Task.cpp | 168 ++++++++ src/tasks/Task.h | 110 ++++++ src/teamcity/BuildIndex.cpp | 2 + src/teamcity/BuildIndex.h | 12 + 37 files changed, 3870 insertions(+) create mode 100644 src/Exception.h create mode 100644 src/FileSystem.cpp create mode 100644 src/FileSystem.h create mode 100644 src/Json.cpp create mode 100644 src/Json.h create mode 100644 src/QObjectPtr.h create mode 100644 src/mojang/PackageManifest.cpp create mode 100644 src/mojang/PackageManifest.h create mode 100644 src/mojang/PackageManifest_test.cpp create mode 100644 src/mojang/testdata/1.8.0_202-x64.json create mode 100755 src/mojang/testdata/inspect/a/b.txt create mode 120000 src/mojang/testdata/inspect/a/b/b.txt create mode 100644 src/mojang/testdata/inspect_win/a/b.txt create mode 100644 src/mojang/testdata/inspect_win/a/b/b.txt create mode 100644 src/net/ByteArraySink.h create mode 100644 src/net/ChecksumValidator.h create mode 100644 src/net/Download.cpp create mode 100644 src/net/Download.h create mode 100644 src/net/FileSink.cpp create mode 100644 src/net/FileSink.h create mode 100644 src/net/Mode.h create mode 100644 src/net/NetAction.h create mode 100644 src/net/NetJob.cpp create mode 100644 src/net/NetJob.h create mode 100644 src/net/Sink.h create mode 100644 src/net/Validator.h create mode 100644 src/pathmatcher/FSTreeMatcher.h create mode 100644 src/pathmatcher/IPathMatcher.h create mode 100644 src/pathmatcher/MultiMatcher.h create mode 100644 src/pathmatcher/RegexpMatcher.h create mode 100644 src/tasks/SequentialTask.cpp create mode 100644 src/tasks/SequentialTask.h create mode 100644 src/tasks/Task.cpp create mode 100644 src/tasks/Task.h create mode 100644 src/teamcity/BuildIndex.cpp create mode 100644 src/teamcity/BuildIndex.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7cf15f2..e76e69e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,6 +71,35 @@ target_include_directories(QtWebApp PUBLIC target_link_libraries(QtWebApp Qt5::Core Qt5::Network) add_executable(squeezer + src/tasks/SequentialTask.cpp + src/tasks/SequentialTask.h + src/tasks/Task.cpp + src/tasks/Task.h + + src/net/Validator.h + src/net/Sink.h + src/net/NetJob.h + src/net/NetJob.cpp + src/net/NetAction.h + src/net/Mode.h + src/net/FileSink.h + src/net/FileSink.cpp + src/net/Download.h + src/net/Download.cpp + src/net/ChecksumValidator.h + src/net/ByteArraySink.h + + src/mojang/PackageManifest.h + src/mojang/PackageManifest.cpp + + src/pathmatcher/FSTreeMatcher.h + src/pathmatcher/IPathMatcher.h + src/pathmatcher/MultiMatcher.h + src/pathmatcher/RegexpMatcher.h + + src/teamcity/BuildIndex.h + src/teamcity/BuildIndex.cpp + src/controller/dumpcontroller.cpp src/controller/dumpcontroller.h src/controller/fileuploadcontroller.cpp @@ -89,8 +118,15 @@ add_executable(squeezer src/main.cpp src/requestmapper.cpp src/requestmapper.h + + src/FileSystem.cpp + src/FileSystem.h + src/Json.cpp + src/Json.h + src/QObjectPtr.h ) target_link_libraries(squeezer QtWebApp) +target_include_directories(squeezer PRIVATE src) install(TARGETS squeezer DESTINATION bin) install(DIRECTORY data/ DESTINATION .) diff --git a/src/Exception.h b/src/Exception.h new file mode 100644 index 0000000..fe0b86b --- /dev/null +++ b/src/Exception.h @@ -0,0 +1,32 @@ +// Licensed under the Apache-2.0 license. See README.md for details. + +#pragma once + +#include +#include +#include + +class Exception : public std::exception +{ +public: + Exception(const QString &message) : std::exception(), m_message(message) + { + qCritical() << "Exception:" << message; + } + Exception(const Exception &other) + : std::exception(), m_message(other.cause()) + { + } + virtual ~Exception() noexcept {} + const char *what() const noexcept + { + return m_message.toLatin1().constData(); + } + QString cause() const + { + return m_message; + } + +private: + QString m_message; +}; diff --git a/src/FileSystem.cpp b/src/FileSystem.cpp new file mode 100644 index 0000000..6de20de --- /dev/null +++ b/src/FileSystem.cpp @@ -0,0 +1,457 @@ +// Licensed under the Apache-2.0 license. See README.md for details. + +#include "FileSystem.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined Q_OS_WIN32 + #include + #include + #include + #include + #include + #include + #include + #include + #include +#else + #include +#endif + +namespace FS { + +void ensureExists(const QDir &dir) +{ + if (!QDir().mkpath(dir.absolutePath())) + { + throw FileSystemException("Unable to create folder " + dir.dirName() + " (" + + dir.absolutePath() + ")"); + } +} + +void write(const QString &filename, const QByteArray &data) +{ + ensureExists(QFileInfo(filename).dir()); + QSaveFile file(filename); + if (!file.open(QSaveFile::WriteOnly)) + { + throw FileSystemException("Couldn't open " + filename + " for writing: " + + file.errorString()); + } + if (data.size() != file.write(data)) + { + throw FileSystemException("Error writing data to " + filename + ": " + + file.errorString()); + } + if (!file.commit()) + { + throw FileSystemException("Error while committing data to " + filename + ": " + + file.errorString()); + } +} + +QByteArray read(const QString &filename) +{ + QFile file(filename); + if (!file.open(QFile::ReadOnly)) + { + throw FileSystemException("Unable to open " + filename + " for reading: " + + file.errorString()); + } + const qint64 size = file.size(); + QByteArray data(int(size), 0); + const qint64 ret = file.read(data.data(), size); + if (ret == -1 || ret != size) + { + throw FileSystemException("Error reading data from " + filename + ": " + + file.errorString()); + } + return data; +} + +bool updateTimestamp(const QString& filename) +{ +#ifdef Q_OS_WIN32 + std::wstring filename_utf_16 = filename.toStdWString(); + return (_wutime64(filename_utf_16.c_str(), nullptr) == 0); +#else + QByteArray filenameBA = QFile::encodeName(filename); + return (utime(filenameBA.data(), nullptr) == 0); +#endif +} + +bool ensureFilePathExists(QString filenamepath) +{ + QFileInfo a(filenamepath); + QDir dir; + QString ensuredPath = a.path(); + bool success = dir.mkpath(ensuredPath); + return success; +} + +bool ensureFolderPathExists(QString foldernamepath) +{ + QFileInfo a(foldernamepath); + QDir dir; + QString ensuredPath = a.filePath(); + bool success = dir.mkpath(ensuredPath); + return success; +} + +bool copy::operator()(const QString &offset) +{ + //NOTE always deep copy on windows. the alternatives are too messy. + #if defined Q_OS_WIN32 + m_followSymlinks = true; + #endif + + auto src = PathCombine(m_src.absolutePath(), offset); + auto dst = PathCombine(m_dst.absolutePath(), offset); + + QFileInfo currentSrc(src); + if (!currentSrc.exists()) + return false; + + if(!m_followSymlinks && currentSrc.isSymLink()) + { + qDebug() << "creating symlink" << src << " - " << dst; + if (!ensureFilePathExists(dst)) + { + qWarning() << "Cannot create path!"; + return false; + } + return QFile::link(currentSrc.symLinkTarget(), dst); + } + else if(currentSrc.isFile()) + { + qDebug() << "copying file" << src << " - " << dst; + if (!ensureFilePathExists(dst)) + { + qWarning() << "Cannot create path!"; + return false; + } + return QFile::copy(src, dst); + } + else if(currentSrc.isDir()) + { + qDebug() << "recursing" << offset; + if (!ensureFolderPathExists(dst)) + { + qWarning() << "Cannot create path!"; + return false; + } + QDir currentDir(src); + for(auto & f : currentDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System)) + { + auto inner_offset = PathCombine(offset, f); + // ignore and skip stuff that matches the blacklist. + if(m_blacklist && m_blacklist->matches(inner_offset)) + { + continue; + } + if(!operator()(inner_offset)) + { + qWarning() << "Failed to copy" << inner_offset; + return false; + } + } + } + else + { + qCritical() << "Copy ERROR: Unknown filesystem object:" << src; + return false; + } + return true; +} + +bool deletePath(QString path) +{ + bool OK = true; + QFileInfo finfo(path); + if(finfo.isFile()) { + return QFile::remove(path); + } + + QDir dir(path); + + if (!dir.exists()) + { + return OK; + } + auto allEntries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | + QDir::AllDirs | QDir::Files, + QDir::DirsFirst); + + for(auto & info: allEntries) + { +#if defined Q_OS_WIN32 + QString nativePath = QDir::toNativeSeparators(info.absoluteFilePath()); + auto wString = nativePath.toStdWString(); + DWORD dwAttrs = GetFileAttributesW(wString.c_str()); + // Windows: check for junctions, reparse points and other nasty things of that sort + if(dwAttrs & FILE_ATTRIBUTE_REPARSE_POINT) + { + if (info.isFile()) + { + OK &= QFile::remove(info.absoluteFilePath()); + } + else if (info.isDir()) + { + OK &= dir.rmdir(info.absoluteFilePath()); + } + } +#else + // We do not trust Qt with reparse points, but do trust it with unix symlinks. + if(info.isSymLink()) + { + OK &= QFile::remove(info.absoluteFilePath()); + } +#endif + else if (info.isDir()) + { + OK &= deletePath(info.absoluteFilePath()); + } + else if (info.isFile()) + { + OK &= QFile::remove(info.absoluteFilePath()); + } + else + { + OK = false; + qCritical() << "Delete ERROR: Unknown filesystem object:" << info.absoluteFilePath(); + } + } + OK &= dir.rmdir(dir.absolutePath()); + return OK; +} + + +QString PathCombine(const QString & path1, const QString & path2) +{ + if(!path1.size()) + return path2; + if(!path2.size()) + return path1; + return QDir::cleanPath(path1 + QDir::separator() + path2); +} + +QString PathCombine(const QString & path1, const QString & path2, const QString & path3) +{ + return PathCombine(PathCombine(path1, path2), path3); +} + +QString PathCombine(const QString & path1, const QString & path2, const QString & path3, const QString & path4) +{ + return PathCombine(PathCombine(path1, path2, path3), path4); +} + +QString AbsolutePath(QString path) +{ + return QFileInfo(path).absolutePath(); +} + +QString ResolveExecutable(QString path) +{ + if (path.isEmpty()) + { + return QString(); + } + if(!path.contains('/')) + { + path = QStandardPaths::findExecutable(path); + } + QFileInfo pathInfo(path); + if(!pathInfo.exists() || !pathInfo.isExecutable()) + { + return QString(); + } + return pathInfo.absoluteFilePath(); +} + +/** + * Normalize path + * + * Any paths inside the current folder will be normalized to relative paths (to current) + * Other paths will be made absolute + */ +QString NormalizePath(QString path) +{ + QDir a = QDir::currentPath(); + QString currentAbsolute = a.absolutePath(); + + QDir b(path); + QString newAbsolute = b.absolutePath(); + + if (newAbsolute.startsWith(currentAbsolute)) + { + return a.relativeFilePath(newAbsolute); + } + else + { + return newAbsolute; + } +} + +QString badFilenameChars = "\"\\/?<>:;*|!+\r\n"; + +QString RemoveInvalidFilenameChars(QString string, QChar replaceWith) +{ + for (int i = 0; i < string.length(); i++) + { + if (badFilenameChars.contains(string[i])) + { + string[i] = replaceWith; + } + } + return string; +} + +QString DirNameFromString(QString string, QString inDir) +{ + int num = 0; + QString baseName = RemoveInvalidFilenameChars(string, '-'); + QString dirName; + do + { + if(num == 0) + { + dirName = baseName; + } + else + { + dirName = baseName + QString::number(num);; + } + + // If it's over 9000 + if (num > 9000) + return ""; + num++; + } while (QFileInfo(PathCombine(inDir, dirName)).exists()); + return dirName; +} + +// Does the folder path contain any '!'? If yes, return true, otherwise false. +// (This is a problem for Java) +bool checkProblemticPathJava(QDir folder) +{ + QString pathfoldername = folder.absolutePath(); + return pathfoldername.contains("!", Qt::CaseInsensitive); +} + +// Win32 crap +#if defined Q_OS_WIN + +bool called_coinit = false; + +HRESULT CreateLink(LPCSTR linkPath, LPCSTR targetPath, LPCSTR args) +{ + HRESULT hres; + + if (!called_coinit) + { + hres = CoInitialize(NULL); + called_coinit = true; + + if (!SUCCEEDED(hres)) + { + qWarning("Failed to initialize COM. Error 0x%08lX", hres); + return hres; + } + } + + IShellLink *link; + hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, + (LPVOID *)&link); + + if (SUCCEEDED(hres)) + { + IPersistFile *persistFile; + + link->SetPath(targetPath); + link->SetArguments(args); + + hres = link->QueryInterface(IID_IPersistFile, (LPVOID *)&persistFile); + if (SUCCEEDED(hres)) + { + WCHAR wstr[MAX_PATH]; + + MultiByteToWideChar(CP_ACP, 0, linkPath, -1, wstr, MAX_PATH); + + hres = persistFile->Save(wstr, TRUE); + persistFile->Release(); + } + link->Release(); + } + return hres; +} + +#endif + +QString getDesktopDir() +{ + return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); +} + +// Cross-platform Shortcut creation +bool createShortCut(QString location, QString dest, QStringList args, QString name, + QString icon) +{ +#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) + location = PathCombine(location, name + ".desktop"); + + QFile f(location); + f.open(QIODevice::WriteOnly | QIODevice::Text); + QTextStream stream(&f); + + QString argstring; + if (!args.empty()) + argstring = " '" + args.join("' '") + "'"; + + stream << "[Desktop Entry]" + << "\n"; + stream << "Type=Application" + << "\n"; + stream << "TryExec=" << dest.toLocal8Bit() << "\n"; + stream << "Exec=" << dest.toLocal8Bit() << argstring.toLocal8Bit() << "\n"; + stream << "Name=" << name.toLocal8Bit() << "\n"; + stream << "Icon=" << icon.toLocal8Bit() << "\n"; + + stream.flush(); + f.close(); + + f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | + QFileDevice::ExeOther); + + return true; +#elif defined Q_OS_WIN + // TODO: Fix + // QFile file(PathCombine(location, name + ".lnk")); + // WCHAR *file_w; + // WCHAR *dest_w; + // WCHAR *args_w; + // file.fileName().toWCharArray(file_w); + // dest.toWCharArray(dest_w); + + // QString argStr; + // for (int i = 0; i < args.count(); i++) + // { + // argStr.append(args[i]); + // argStr.append(" "); + // } + // argStr.toWCharArray(args_w); + + // return SUCCEEDED(CreateLink(file_w, dest_w, args_w)); + return false; +#else + qWarning("Desktop Shortcuts not supported on your platform!"); + return false; +#endif +} +} diff --git a/src/FileSystem.h b/src/FileSystem.h new file mode 100644 index 0000000..8f6e8b4 --- /dev/null +++ b/src/FileSystem.h @@ -0,0 +1,127 @@ +// Licensed under the Apache-2.0 license. See README.md for details. + +#pragma once + +#include "Exception.h" +#include "pathmatcher/IPathMatcher.h" + +#include +#include + +namespace FS +{ + +class FileSystemException : public ::Exception +{ +public: + FileSystemException(const QString &message) : Exception(message) {} +}; + +/** + * write data to a file safely + */ +void write(const QString &filename, const QByteArray &data); + +/** + * read data from a file safely\ + */ +QByteArray read(const QString &filename); + +/** + * Update the last changed timestamp of an existing file + */ +bool updateTimestamp(const QString & filename); + +/** + * Creates all the folders in a path for the specified path + * last segment of the path is treated as a file name and is ignored! + */ +bool ensureFilePathExists(QString filenamepath); + +/** + * Creates all the folders in a path for the specified path + * last segment of the path is treated as a folder name and is created! + */ +bool ensureFolderPathExists(QString filenamepath); + +class copy +{ +public: + copy(const QString & src, const QString & dst) + { + m_src = src; + m_dst = dst; + } + copy & followSymlinks(const bool follow) + { + m_followSymlinks = follow; + return *this; + } + copy & blacklist(const IPathMatcher * filter) + { + m_blacklist = filter; + return *this; + } + bool operator()() + { + return operator()(QString()); + } + +private: + bool operator()(const QString &offset); + +private: + bool m_followSymlinks = true; + const IPathMatcher * m_blacklist = nullptr; + QDir m_src; + QDir m_dst; +}; + +/** + * Delete a folder recursively + */ +bool deletePath(QString path); + +QString PathCombine(const QString &path1, const QString &path2); +QString PathCombine(const QString &path1, const QString &path2, const QString &path3); +QString PathCombine(const QString &path1, const QString &path2, const QString &path3, const QString &path4); + +QString AbsolutePath(QString path); + +/** + * Resolve an executable + * + * Will resolve: + * single executable (by name) + * relative path + * absolute path + * + * @return absolute path to executable or null string + */ +QString ResolveExecutable(QString path); + +/** + * Normalize path + * + * Any paths inside the current directory will be normalized to relative paths (to current) + * Other paths will be made absolute + * + * Returns false if the path logic somehow filed (and normalizedPath in invalid) + */ +QString NormalizePath(QString path); + +QString RemoveInvalidFilenameChars(QString string, QChar replaceWith = '-'); + +QString DirNameFromString(QString string, QString inDir = "."); + +/// Checks if the a given Path contains "!" +bool checkProblemticPathJava(QDir folder); + +// Get the Directory representing the User's Desktop +QString getDesktopDir(); + +// Create a shortcut at *location*, pointing to *dest* called with the arguments *args* +// call it *name* and assign it the icon *icon* +// return true if operation succeeded +bool createShortCut(QString location, QString dest, QStringList args, QString name, QString iconLocation); +} diff --git a/src/Json.cpp b/src/Json.cpp new file mode 100644 index 0000000..37ada1a --- /dev/null +++ b/src/Json.cpp @@ -0,0 +1,272 @@ +// Licensed under the Apache-2.0 license. See README.md for details. + +#include "Json.h" + +#include + +#include "FileSystem.h" +#include + +namespace Json +{ +void write(const QJsonDocument &doc, const QString &filename) +{ + FS::write(filename, doc.toJson()); +} +void write(const QJsonObject &object, const QString &filename) +{ + write(QJsonDocument(object), filename); +} +void write(const QJsonArray &array, const QString &filename) +{ + write(QJsonDocument(array), filename); +} + +QByteArray toBinary(const QJsonObject &obj) +{ + return QJsonDocument(obj).toBinaryData(); +} +QByteArray toBinary(const QJsonArray &array) +{ + return QJsonDocument(array).toBinaryData(); +} +QByteArray toText(const QJsonObject &obj) +{ + return QJsonDocument(obj).toJson(QJsonDocument::Compact); +} +QByteArray toText(const QJsonArray &array) +{ + return QJsonDocument(array).toJson(QJsonDocument::Compact); +} + +static bool isBinaryJson(const QByteArray &data) +{ + decltype(QJsonDocument::BinaryFormatTag) tag = QJsonDocument::BinaryFormatTag; + return memcmp(data.constData(), &tag, sizeof(QJsonDocument::BinaryFormatTag)) == 0; +} +QJsonDocument requireDocument(const QByteArray &data, const QString &what) +{ + if (isBinaryJson(data)) + { + QJsonDocument doc = QJsonDocument::fromBinaryData(data); + if (doc.isNull()) + { + throw JsonException(what + ": Invalid JSON (binary JSON detected)"); + } + return doc; + } + else + { + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) + { + throw JsonException(what + ": Error parsing JSON: " + error.errorString()); + } + return doc; + } +} +QJsonDocument requireDocument(const QString &filename, const QString &what) +{ + return requireDocument(FS::read(filename), what); +} +QJsonObject requireObject(const QJsonDocument &doc, const QString &what) +{ + if (!doc.isObject()) + { + throw JsonException(what + " is not an object"); + } + return doc.object(); +} +QJsonArray requireArray(const QJsonDocument &doc, const QString &what) +{ + if (!doc.isArray()) + { + throw JsonException(what + " is not an array"); + } + return doc.array(); +} + +void writeString(QJsonObject &to, const QString &key, const QString &value) +{ + if (!value.isEmpty()) + { + to.insert(key, value); + } +} + +void writeStringList(QJsonObject &to, const QString &key, const QStringList &values) +{ + if (!values.isEmpty()) + { + QJsonArray array; + for(auto value: values) + { + array.append(value); + } + to.insert(key, array); + } +} + +template<> +QJsonValue toJson(const QUrl &url) +{ + return QJsonValue(url.toString(QUrl::FullyEncoded)); +} +template<> +QJsonValue toJson(const QByteArray &data) +{ + return QJsonValue(QString::fromLatin1(data.toHex())); +} +template<> +QJsonValue toJson(const QDateTime &datetime) +{ + return QJsonValue(datetime.toString(Qt::ISODate)); +} +template<> +QJsonValue toJson(const QDir &dir) +{ + return QDir::current().relativeFilePath(dir.absolutePath()); +} +template<> +QJsonValue toJson(const QUuid &uuid) +{ + return uuid.toString(); +} +template<> +QJsonValue toJson(const QVariant &variant) +{ + return QJsonValue::fromVariant(variant); +} + + +template<> QByteArray requireIsType(const QJsonValue &value, const QString &what) +{ + const QString string = ensureIsType(value, what); + // ensure that the string can be safely cast to Latin1 + if (string != QString::fromLatin1(string.toLatin1())) + { + throw JsonException(what + " is not encodable as Latin1"); + } + return QByteArray::fromHex(string.toLatin1()); +} + +template<> QJsonArray requireIsType(const QJsonValue &value, const QString &what) +{ + if (!value.isArray()) + { + throw JsonException(what + " is not an array"); + } + return value.toArray(); +} + + +template<> QString requireIsType(const QJsonValue &value, const QString &what) +{ + if (!value.isString()) + { + throw JsonException(what + " is not a string"); + } + return value.toString(); +} + +template<> bool requireIsType(const QJsonValue &value, const QString &what) +{ + if (!value.isBool()) + { + throw JsonException(what + " is not a bool"); + } + return value.toBool(); +} + +template<> double requireIsType(const QJsonValue &value, const QString &what) +{ + if (!value.isDouble()) + { + throw JsonException(what + " is not a double"); + } + return value.toDouble(); +} + +template<> int requireIsType(const QJsonValue &value, const QString &what) +{ + const double doubl = requireIsType(value, what); + if (fmod(doubl, 1) != 0) + { + throw JsonException(what + " is not an integer"); + } + return int(doubl); +} + +template<> QDateTime requireIsType(const QJsonValue &value, const QString &what) +{ + const QString string = requireIsType(value, what); + const QDateTime datetime = QDateTime::fromString(string, Qt::ISODate); + if (!datetime.isValid()) + { + throw JsonException(what + " is not a ISO formatted date/time value"); + } + return datetime; +} + +template<> QUrl requireIsType(const QJsonValue &value, const QString &what) +{ + const QString string = ensureIsType(value, what); + if (string.isEmpty()) + { + return QUrl(); + } + const QUrl url = QUrl(string, QUrl::StrictMode); + if (!url.isValid()) + { + throw JsonException(what + " is not a correctly formatted URL"); + } + return url; +} + +template<> QDir requireIsType(const QJsonValue &value, const QString &what) +{ + const QString string = requireIsType(value, what); + // FIXME: does not handle invalid characters! + return QDir::current().absoluteFilePath(string); +} + +template<> QUuid requireIsType(const QJsonValue &value, const QString &what) +{ + const QString string = requireIsType(value, what); + const QUuid uuid = QUuid(string); + if (uuid.toString() != string) // converts back => valid + { + throw JsonException(what + " is not a valid UUID"); + } + return uuid; +} + +template<> QJsonObject requireIsType(const QJsonValue &value, const QString &what) +{ + if (!value.isObject()) + { + throw JsonException(what + " is not an object"); + } + return value.toObject(); +} + +template<> QVariant requireIsType(const QJsonValue &value, const QString &what) +{ + if (value.isNull() || value.isUndefined()) + { + throw JsonException(what + " is null or undefined"); + } + return value.toVariant(); +} + +template<> QJsonValue requireIsType(const QJsonValue &value, const QString &what) +{ + if (value.isNull() || value.isUndefined()) + { + throw JsonException(what + " is null or undefined"); + } + return value; +} + +} diff --git a/src/Json.h b/src/Json.h new file mode 100644 index 0000000..dd70bf5 --- /dev/null +++ b/src/Json.h @@ -0,0 +1,249 @@ +// Licensed under the Apache-2.0 license. See README.md for details. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Exception.h" + +namespace Json +{ +class JsonException : public ::Exception +{ +public: + JsonException(const QString &message) : Exception(message) {} +}; + +/// @throw FileSystemException +void write(const QJsonDocument &doc, const QString &filename); +/// @throw FileSystemException +void write(const QJsonObject &object, const QString &filename); +/// @throw FileSystemException +void write(const QJsonArray &array, const QString &filename); + +QByteArray toBinary(const QJsonObject &obj); +QByteArray toBinary(const QJsonArray &array); +QByteArray toText(const QJsonObject &obj); +QByteArray toText(const QJsonArray &array); + +/// @throw JsonException +QJsonDocument requireDocument(const QByteArray &data, const QString &what = "Document"); +/// @throw JsonException +QJsonDocument requireDocument(const QString &filename, const QString &what = "Document"); +/// @throw JsonException +QJsonObject requireObject(const QJsonDocument &doc, const QString &what = "Document"); +/// @throw JsonException +QJsonArray requireArray(const QJsonDocument &doc, const QString &what = "Document"); + +/////////////////// WRITING //////////////////// + +void writeString(QJsonObject & to, const QString &key, const QString &value); +void writeStringList(QJsonObject & to, const QString &key, const QStringList &values); + +template +QJsonValue toJson(const T &t) +{ + return QJsonValue(t); +} +template<> +QJsonValue toJson(const QUrl &url); +template<> +QJsonValue toJson(const QByteArray &data); +template<> +QJsonValue toJson(const QDateTime &datetime); +template<> +QJsonValue toJson(const QDir &dir); +template<> +QJsonValue toJson(const QUuid &uuid); +template<> +QJsonValue toJson(const QVariant &variant); + +template +QJsonArray toJsonArray(const QList &container) +{ + QJsonArray array; + for (const T item : container) + { + array.append(toJson(item)); + } + return array; +} + +////////////////// READING //////////////////// + +/// @throw JsonException +template +T requireIsType(const QJsonValue &value, const QString &what = "Value"); + +/// @throw JsonException +template<> double requireIsType(const QJsonValue &value, const QString &what); +/// @throw JsonException +template<> bool requireIsType(const QJsonValue &value, const QString &what); +/// @throw JsonException +template<> int requireIsType(const QJsonValue &value, const QString &what); +/// @throw JsonException +template<> QJsonObject requireIsType(const QJsonValue &value, const QString &what); +/// @throw JsonException +template<> QJsonArray requireIsType(const QJsonValue &value, const QString &what); +/// @throw JsonException +template<> QJsonValue requireIsType(const QJsonValue &value, const QString &what); +/// @throw JsonException +template<> QByteArray requireIsType(const QJsonValue &value, const QString &what); +/// @throw JsonException +template<> QDateTime requireIsType(const QJsonValue &value, const QString &what); +/// @throw JsonException +template<> QVariant requireIsType(const QJsonValue &value, const QString &what); +/// @throw JsonException +template<> QString requireIsType(const QJsonValue &value, const QString &what); +/// @throw JsonException +template<> QUuid requireIsType(const QJsonValue &value, const QString &what); +/// @throw JsonException +template<> QDir requireIsType(const QJsonValue &value, const QString &what); +/// @throw JsonException +template<> QUrl requireIsType(const QJsonValue &value, const QString &what); + +// the following functions are higher level functions, that make use of the above functions for +// type conversion +template +T ensureIsType(const QJsonValue &value, const T default_ = T(), const QString &what = "Value") +{ + if (value.isUndefined() || value.isNull()) + { + return default_; + } + try + { + return requireIsType(value, what); + } + catch (const JsonException &) + { + return default_; + } +} + +/// @throw JsonException +template +T requireIsType(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") +{ + const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); + if (!parent.contains(key)) + { + throw JsonException(localWhat + "s parent does not contain " + localWhat); + } + return requireIsType(parent.value(key), localWhat); +} + +template +T ensureIsType(const QJsonObject &parent, const QString &key, const T default_ = T(), const QString &what = "__placeholder__") +{ + const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); + if (!parent.contains(key)) + { + return default_; + } + return ensureIsType(parent.value(key), default_, localWhat); +} + +template +QVector requireIsArrayOf(const QJsonDocument &doc) +{ + const QJsonArray array = requireArray(doc); + QVector out; + for (const QJsonValue val : array) + { + out.append(requireIsType(val, "Document")); + } + return out; +} + +template +QVector ensureIsArrayOf(const QJsonValue &value, const QString &what = "Value") +{ + const QJsonArray array = ensureIsType(value, QJsonArray(), what); + QVector out; + for (const QJsonValue val : array) + { + out.append(requireIsType(val, what)); + } + return out; +} + +template +QVector ensureIsArrayOf(const QJsonValue &value, const QVector default_, const QString &what = "Value") +{ + if (value.isUndefined()) + { + return default_; + } + return ensureIsArrayOf(value, what); +} + +/// @throw JsonException +template +QVector requireIsArrayOf(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") +{ + const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); + if (!parent.contains(key)) + { + throw JsonException(localWhat + "s parent does not contain " + localWhat); + } + return ensureIsArrayOf(parent.value(key), localWhat); +} + +template +QVector ensureIsArrayOf(const QJsonObject &parent, const QString &key, + const QVector &default_ = QVector(), const QString &what = "__placeholder__") +{ + const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); + if (!parent.contains(key)) + { + return default_; + } + return ensureIsArrayOf(parent.value(key), default_, localWhat); +} + +// this macro part could be replaced by variadic functions that just pass on their arguments, but that wouldn't work well with IDE helpers +#define JSON_HELPERFUNCTIONS(NAME, TYPE) \ + inline TYPE requireValue##NAME(const QJsonValue &value, const QString &what = "Value") \ + { \ + return requireIsType(value, what); \ + } \ + inline TYPE ensureValue##NAME(const QJsonValue &value, const TYPE default_ = TYPE(), const QString &what = "Value") \ + { \ + return ensureIsType(value, default_, what); \ + } \ + inline TYPE require##NAME(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") \ + { \ + return requireIsType(parent, key, what); \ + } \ + inline TYPE ensure##NAME(const QJsonObject &parent, const QString &key, const TYPE default_ = TYPE(), const QString &what = "__placeholder") \ + { \ + return ensureIsType(parent, key, default_, what); \ + } + +JSON_HELPERFUNCTIONS(Array, QJsonArray) +JSON_HELPERFUNCTIONS(Object, QJsonObject) +JSON_HELPERFUNCTIONS(JsonValue, QJsonValue) +JSON_HELPERFUNCTIONS(String, QString) +JSON_HELPERFUNCTIONS(Boolean, bool) +JSON_HELPERFUNCTIONS(Double, double) +JSON_HELPERFUNCTIONS(Integer, int) +JSON_HELPERFUNCTIONS(DateTime, QDateTime) +JSON_HELPERFUNCTIONS(Url, QUrl) +JSON_HELPERFUNCTIONS(ByteArray, QByteArray) +JSON_HELPERFUNCTIONS(Dir, QDir) +JSON_HELPERFUNCTIONS(Uuid, QUuid) +JSON_HELPERFUNCTIONS(Variant, QVariant) + +#undef JSON_HELPERFUNCTIONS + +} +using JSONValidationError = Json::JsonException; diff --git a/src/QObjectPtr.h b/src/QObjectPtr.h new file mode 100644 index 0000000..5797493 --- /dev/null +++ b/src/QObjectPtr.h @@ -0,0 +1,89 @@ +#pragma once + +#include +#include +#include + +namespace details +{ +struct DeleteQObjectLater +{ + void operator()(QObject *obj) const + { + obj->deleteLater(); + } +}; +} +/** + * A unique pointer class with unique pointer semantics intended for derivates of QObject + * Calls deleteLater() instead of destroying the contained object immediately + */ +template using unique_qobject_ptr = std::unique_ptr; + +/** + * A shared pointer class with shared pointer semantics intended for derivates of QObject + * Calls deleteLater() instead of destroying the contained object immediately + */ +template +class shared_qobject_ptr +{ +public: + shared_qobject_ptr(){} + shared_qobject_ptr(T * wrap) + { + reset(wrap); + } + shared_qobject_ptr(const shared_qobject_ptr& other) + { + m_ptr = other.m_ptr; + } + template + shared_qobject_ptr(const shared_qobject_ptr &other) + { + m_ptr = other.unwrap(); + } + +public: + void reset(T * wrap) + { + using namespace std::placeholders; + m_ptr.reset(wrap, std::bind(&QObject::deleteLater, _1)); + } + void reset(const shared_qobject_ptr &other) + { + m_ptr = other.m_ptr; + } + void reset() + { + m_ptr.reset(); + } + T * get() const + { + return m_ptr.get(); + } + T * operator->() const + { + return m_ptr.get(); + } + T & operator*() const + { + return *m_ptr.get(); + } + operator bool() const + { + return m_ptr.get() != nullptr; + } + const std::shared_ptr unwrap() const + { + return m_ptr; + } + bool operator==(const shared_qobject_ptr& other) { + return m_ptr == other.m_ptr; + } + bool operator!=(const shared_qobject_ptr& other) { + return m_ptr != other.m_ptr; + } + +private: + std::shared_ptr m_ptr; +}; diff --git a/src/mojang/PackageManifest.cpp b/src/mojang/PackageManifest.cpp new file mode 100644 index 0000000..6c03943 --- /dev/null +++ b/src/mojang/PackageManifest.cpp @@ -0,0 +1,434 @@ +/* + * Copyright 2020 Petr Mrázek + * + * This source is subject to the Microsoft Permissive License (MS-PL). + * Please see the COPYING.md file for more information. + */ + +#include "PackageManifest.h" +#include +#include +#include +#include +#include + +#ifndef Q_OS_WIN32 +#include +#include +#include +#endif + +namespace mojang_files { + +const Hash hash_of_empty_string = "da39a3ee5e6b4b0d3255bfef95601890afd80709"; + +int Path::compare(const Path& rhs) const +{ + auto left_cursor = begin(); + auto left_end = end(); + auto right_cursor = rhs.begin(); + auto right_end = rhs.end(); + + while (left_cursor != left_end && right_cursor != right_end) + { + if(*left_cursor < *right_cursor) + { + return -1; + } + else if(*left_cursor > *right_cursor) + { + return 1; + } + left_cursor++; + right_cursor++; + } + + if(left_cursor == left_end) + { + if(right_cursor == right_end) + { + return 0; + } + return -1; + } + return 1; +} + +void Package::addFile(const Path& path, const File& file) { + addFolder(path.parent_path()); + files[path] = file; +} + +void Package::addFolder(Path folder) { + if(!folder.has_parent_path()) { + return; + } + do { + folders.insert(folder); + folder = folder.parent_path(); + } while(folder.has_parent_path()); +} + +void Package::addLink(const Path& path, const Path& target) { + addFolder(path.parent_path()); + symlinks[path] = target; +} + +void Package::addSource(const FileSource& source) { + sources[source.hash] = source; +} + + +namespace { +void fromJson(QJsonDocument & doc, Package & out) { + std::set seen_paths; + if (!doc.isObject()) + { + throw JSONValidationError("file manifest is not an object"); + } + QJsonObject root = doc.object(); + + auto filesObj = Json::ensureObject(root, "files"); + auto iter = filesObj.begin(); + while (iter != filesObj.end()) + { + Path objectPath = Path(iter.key()); + auto value = iter.value(); + iter++; + if(seen_paths.count(objectPath)) { + throw JSONValidationError("duplicate path inside manifest, the manifest is invalid"); + } + if (!value.isObject()) + { + throw JSONValidationError("file entry inside manifest is not an an object"); + } + seen_paths.insert(objectPath); + + auto fileObject = value.toObject(); + auto type = Json::requireString(fileObject, "type"); + if(type == "directory") { + out.addFolder(objectPath); + continue; + } + else if(type == "file") { + FileSource bestSource; + File file; + file.executable = Json::ensureBoolean(fileObject, QString("executable"), false); + auto downloads = Json::requireObject(fileObject, "downloads"); + for(auto iter2 = downloads.begin(); iter2 != downloads.end(); iter2++) { + FileSource source; + + auto downloadObject = Json::requireValueObject(iter2.value()); + source.hash = Json::requireString(downloadObject, "sha1"); + source.size = Json::requireInteger(downloadObject, "size"); + source.url = Json::requireString(downloadObject, "url"); + + auto compression = iter2.key(); + if(compression == "raw") { + file.hash = source.hash; + file.size = source.size; + source.compression = Compression::Raw; + } + else if (compression == "lzma") { + source.compression = Compression::Lzma; + } + else { + continue; + } + bestSource.upgrade(source); + } + if(bestSource.isBad()) { + throw JSONValidationError("No valid compression method for file " + iter.key()); + } + out.addFile(objectPath, file); + out.addSource(bestSource); + } + else if(type == "link") { + auto target = Json::requireString(fileObject, "target"); + out.symlinks[objectPath] = target; + out.addLink(objectPath, target); + } + else { + throw JSONValidationError("Invalid item type in manifest: " + type); + } + } + // make sure the containing folder exists + out.folders.insert(Path()); +} +} + +Package Package::fromManifestContents(const QByteArray& contents) +{ + Package out; + try + { + auto doc = Json::requireDocument(contents, "Manifest"); + fromJson(doc, out); + return out; + } + catch (const Exception &e) + { + qDebug() << QString("Unable to parse manifest: %1").arg(e.cause()); + out.valid = false; + return out; + } +} + +Package Package::fromManifestFile(const QString & filename) { + Package out; + try + { + auto doc = Json::requireDocument(filename, filename); + fromJson(doc, out); + return out; + } + catch (const Exception &e) + { + qDebug() << QString("Unable to parse manifest file %1: %2").arg(filename, e.cause()); + out.valid = false; + return out; + } +} + +#ifndef Q_OS_WIN32 + +#include +#include +#include + +namespace { +// FIXME: Qt obscures symlink targets by making them absolute. that is useless. this is the workaround - we do it ourselves +bool actually_read_symlink_target(const QString & filepath, Path & out) +{ + struct ::stat st; + // FIXME: here, we assume the native filesystem encoding. May the Gods have mercy upon our Souls. + QByteArray nativePath = filepath.toUtf8(); + const char * filepath_cstr = nativePath.data(); + + if (lstat(filepath_cstr, &st) != 0) + { + return false; + } + + auto size = st.st_size ? st.st_size + 1 : PATH_MAX; + std::string temp(size, '\0'); + // because we don't realiably know how long the damn thing actually is, we loop and expand. POSIX is naff + do + { + auto link_length = ::readlink(filepath_cstr, &temp[0], temp.size()); + if(link_length == -1) + { + return false; + } + if(std::string::size_type(link_length) < temp.size()) + { + // buffer was long enough and we managed to read the link target. RETURN here. + temp.resize(link_length); + out = Path(QString::fromUtf8(temp.c_str())); + return true; + } + temp.resize(temp.size() * 2); + } while (true); +} +} +#endif + +// FIXME: Qt filesystem abstraction is bad, but ... let's hope it doesn't break too much? +// FIXME: The error handling is just DEFICIENT +Package Package::fromInspectedFolder(const QString& folderPath) +{ + QDir root(folderPath); + + Package out; + QDirIterator iterator(folderPath, QDir::NoDotAndDotDot | QDir::AllEntries | QDir::System | QDir::Hidden, QDirIterator::Subdirectories); + while(iterator.hasNext()) { + iterator.next(); + + auto fileInfo = iterator.fileInfo(); + auto relPath = root.relativeFilePath(fileInfo.filePath()); + // FIXME: this is probably completely busted on Windows anyway, so just disable it. + // Qt makes shit up and doesn't understand the platform details + // TODO: Actually use a filesystem library that isn't terrible and has decen license. + // I only know one, and I wrote it. Sadly, currently proprietary. PAIN. +#ifndef Q_OS_WIN32 + if(fileInfo.isSymLink()) { + Path targetPath; + if(!actually_read_symlink_target(fileInfo.filePath(), targetPath)) { + qCritical() << "Folder inspection: Unknown filesystem object:" << fileInfo.absoluteFilePath(); + out.valid = false; + } + out.addLink(relPath, targetPath); + } + else +#endif + if(fileInfo.isDir()) { + out.addFolder(relPath); + } + else if(fileInfo.isFile()) { + File f; + f.executable = fileInfo.isExecutable(); + f.size = fileInfo.size(); + // FIXME: async / optimize the hashing + QFile input(fileInfo.absoluteFilePath()); + if(!input.open(QIODevice::ReadOnly)) { + qCritical() << "Folder inspection: Failed to open file:" << fileInfo.absoluteFilePath(); + out.valid = false; + break; + } + f.hash = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Sha1).toHex().constData(); + out.addFile(relPath, f); + } + else { + // Something else... oh my + qCritical() << "Folder inspection: Unknown filesystem object:" << fileInfo.absoluteFilePath(); + out.valid = false; + break; + } + } + out.folders.insert(Path(".")); + out.valid = true; + return out; +} + +namespace { +struct shallow_first_sort +{ + bool operator()(const Path &lhs, const Path &rhs) const + { + auto lhs_depth = lhs.length(); + auto rhs_depth = rhs.length(); + if(lhs_depth < rhs_depth) + { + return true; + } + else if(lhs_depth == rhs_depth) + { + if(lhs < rhs) + { + return true; + } + } + return false; + } +}; + +struct deep_first_sort +{ + bool operator()(const Path &lhs, const Path &rhs) const + { + auto lhs_depth = lhs.length(); + auto rhs_depth = rhs.length(); + if(lhs_depth > rhs_depth) + { + return true; + } + else if(lhs_depth == rhs_depth) + { + if(lhs < rhs) + { + return true; + } + } + return false; + } +}; +} + +UpdateOperations UpdateOperations::resolve(const Package& from, const Package& to) +{ + UpdateOperations out; + + if(!from.valid || !to.valid) { + out.valid = false; + return out; + } + + // Files + for(auto iter = from.files.begin(); iter != from.files.end(); iter++) { + const auto ¤t_hash = iter->second.hash; + const auto ¤t_executable = iter->second.executable; + const auto &path = iter->first; + + auto iter2 = to.files.find(path); + if(iter2 == to.files.end()) { + // removed + out.deletes.push_back(path); + continue; + } + auto new_hash = iter2->second.hash; + auto new_executable = iter2->second.executable; + if (current_hash != new_hash) { + out.deletes.push_back(path); + out.downloads.emplace( + std::pair{ + path, + FileDownload(to.sources.at(iter2->second.hash), iter2->second.executable) + } + ); + } + else if (current_executable != new_executable) { + out.executable_fixes[path] = new_executable; + } + } + for(auto iter = to.files.begin(); iter != to.files.end(); iter++) { + auto path = iter->first; + if(!from.files.count(path)) { + out.downloads.emplace( + std::pair{ + path, + FileDownload(to.sources.at(iter->second.hash), iter->second.executable) + } + ); + } + } + + // Folders + std::set remove_folders; + std::set make_folders; + for(auto from_path: from.folders) { + auto iter = to.folders.find(from_path); + if(iter == to.folders.end()) { + remove_folders.insert(from_path); + } + } + for(auto & rmdir: remove_folders) { + out.rmdirs.push_back(rmdir); + } + for(auto to_path: to.folders) { + auto iter = from.folders.find(to_path); + if(iter == from.folders.end()) { + make_folders.insert(to_path); + } + } + for(auto & mkdir: make_folders) { + out.mkdirs.push_back(mkdir); + } + + // Symlinks + for(auto iter = from.symlinks.begin(); iter != from.symlinks.end(); iter++) { + const auto ¤t_target = iter->second; + const auto &path = iter->first; + + auto iter2 = to.symlinks.find(path); + if(iter2 == to.symlinks.end()) { + // removed + out.deletes.push_back(path); + continue; + } + const auto &new_target = iter2->second; + if (current_target != new_target) { + out.deletes.push_back(path); + out.mklinks[path] = iter2->second; + } + } + for(auto iter = to.symlinks.begin(); iter != to.symlinks.end(); iter++) { + auto path = iter->first; + if(!from.symlinks.count(path)) { + out.mklinks[path] = iter->second; + } + } + out.valid = true; + return out; +} + +} diff --git a/src/mojang/PackageManifest.h b/src/mojang/PackageManifest.h new file mode 100644 index 0000000..c495ac9 --- /dev/null +++ b/src/mojang/PackageManifest.h @@ -0,0 +1,178 @@ +/* + * Copyright 2020 Petr Mrázek + * + * This source is subject to the Microsoft Permissive License (MS-PL). + * Please see the COPYING.md file for more information. + */ + +#pragma once + +#include +#include +#include +#include +#include "tasks/Task.h" + +namespace mojang_files { + +using Hash = QString; +extern const Hash empty_hash; + +// simple-ish path implementation. assumes always relative and does not allow '..' entries +class Path +{ +public: + using parts_type = QStringList; + + Path() = default; + Path(QString string) { + auto parts_in = string.split('/'); + for(auto & part: parts_in) { + if(part.isEmpty() || part == ".") { + continue; + } + if(part == "..") { + if(parts.size()) { + parts.pop_back(); + } + continue; + } + parts.push_back(part); + } + } + + bool has_parent_path() const + { + return parts.size() > 0; + } + + Path parent_path() const + { + if (parts.empty()) + return Path(); + return Path(parts.begin(), std::prev(parts.end())); + } + + bool empty() const + { + return parts.empty(); + } + + int length() const + { + return parts.length(); + } + + bool operator==(const Path & rhs) const { + return parts == rhs.parts; + } + + bool operator!=(const Path & rhs) const { + return parts != rhs.parts; + } + + inline bool operator<(const Path& rhs) const + { + return compare(rhs) < 0; + } + + parts_type::const_iterator begin() const + { + return parts.begin(); + } + + parts_type::const_iterator end() const + { + return parts.end(); + } + + QString toString() const { + return parts.join("/"); + } + +private: + Path(const parts_type::const_iterator & start, const parts_type::const_iterator & end) { + auto cursor = start; + while(cursor != end) { + parts.push_back(*cursor); + cursor++; + } + } + int compare(const Path& p) const; + + parts_type parts; +}; + + +enum class Compression { + Raw, + Lzma, + Unknown +}; + + +struct FileSource +{ + Compression compression = Compression::Unknown; + Hash hash; + QString url; + std::size_t size = 0; + void upgrade(const FileSource & other) { + if(compression == Compression::Unknown || other.size < size) { + *this = other; + } + } + bool isBad() const { + return compression == Compression::Unknown; + } +}; + +struct File +{ + Hash hash; + bool executable; + std::uint64_t size = 0; +}; + +struct Package { + static Package fromInspectedFolder(const QString &folderPath); + static Package fromManifestFile(const QString &path); + static Package fromManifestContents(const QByteArray& contents); + + explicit operator bool() const + { + return valid; + } + void addFolder(Path folder); + void addFile(const Path & path, const File & file); + void addLink(const Path & path, const Path & target); + void addSource(const FileSource & source); + + std::map sources; + bool valid = true; + std::set folders; + std::map files; + std::map symlinks; +}; + +struct FileDownload : FileSource +{ + FileDownload(const FileSource& source, bool executable) { + static_cast (*this) = source; + this->executable = executable; + } + bool executable = false; +}; + +struct UpdateOperations { + static UpdateOperations resolve(const Package & from, const Package & to); + bool valid = false; + std::vector deletes; + std::vector rmdirs; + std::vector mkdirs; + std::map downloads; + std::map mklinks; + std::map executable_fixes; +}; + +} diff --git a/src/mojang/PackageManifest_test.cpp b/src/mojang/PackageManifest_test.cpp new file mode 100644 index 0000000..47c7cbb --- /dev/null +++ b/src/mojang/PackageManifest_test.cpp @@ -0,0 +1,351 @@ +/* + * Copyright 2020 Petr Mrázek + * + * This source is subject to the Microsoft Permissive License (MS-PL). + * Please see the COPYING.md file for more information. + */ + +#include +#include +#include "TestUtil.h" + +#include "mojang/PackageManifest.h" + +using namespace mojang_files; + +QDebug operator<<(QDebug debug, const Path &path) +{ + debug << path.toString(); + return debug; +} + +class PackageManifestTest : public QObject +{ + Q_OBJECT + +private slots: + void test_parse(); + void test_parse_file(); + void test_inspect(); +#ifndef Q_OS_WIN32 + void test_inspect_symlinks(); +#endif + void mkdir_deep(); + void rmdir_deep(); + + void identical_file(); + void changed_file(); + void added_file(); + void removed_file(); +}; + +namespace { +QByteArray basic_manifest = R"END( +{ + "files": { + "a/b.txt": { + "type": "file", + "downloads": { + "raw": { + "url": "http://dethware.org/b.txt", + "sha1": "da39a3ee5e6b4b0d3255bfef95601890afd80709", + "size": 0 + } + }, + "executable": true + }, + "a/b/c": { + "type": "directory" + }, + "a/b/c.txt": { + "type": "link", + "target": "../b.txt" + } + } +} +)END"; +} + +void PackageManifestTest::test_parse() +{ + auto manifest = Package::fromManifestContents(basic_manifest); + QVERIFY(manifest.valid == true); + QVERIFY(manifest.files.size() == 1); + QVERIFY(manifest.files.count(Path("a/b.txt"))); + auto &file = manifest.files[Path("a/b.txt")]; + QVERIFY(file.executable == true); + QVERIFY(file.hash == "da39a3ee5e6b4b0d3255bfef95601890afd80709"); + QVERIFY(file.size == 0); + QVERIFY(manifest.folders.size() == 4); + QVERIFY(manifest.folders.count(Path("."))); + QVERIFY(manifest.folders.count(Path("a"))); + QVERIFY(manifest.folders.count(Path("a/b"))); + QVERIFY(manifest.folders.count(Path("a/b/c"))); + QVERIFY(manifest.symlinks.size() == 1); + auto symlinkPath = Path("a/b/c.txt"); + QVERIFY(manifest.symlinks.count(symlinkPath)); + auto &symlink = manifest.symlinks[symlinkPath]; + QVERIFY(symlink == Path("../b.txt")); + QVERIFY(manifest.sources.size() == 1); +} + +void PackageManifestTest::test_parse_file() { + auto path = QFINDTESTDATA("testdata/1.8.0_202-x64.json"); + auto manifest = Package::fromManifestFile(path); + QVERIFY(manifest.valid == true); +} + + +void PackageManifestTest::test_inspect() { + auto path = QFINDTESTDATA("testdata/inspect_win/"); + auto manifest = Package::fromInspectedFolder(path); + QVERIFY(manifest.valid == true); + QVERIFY(manifest.files.size() == 2); + QVERIFY(manifest.files.count(Path("a/b.txt"))); + auto &file1 = manifest.files[Path("a/b.txt")]; + QVERIFY(file1.executable == false); + QVERIFY(file1.hash == "da39a3ee5e6b4b0d3255bfef95601890afd80709"); + QVERIFY(file1.size == 0); + QVERIFY(manifest.files.count(Path("a/b/b.txt"))); + auto &file2 = manifest.files[Path("a/b/b.txt")]; + QVERIFY(file2.executable == false); + QVERIFY(file2.hash == "da39a3ee5e6b4b0d3255bfef95601890afd80709"); + QVERIFY(file2.size == 0); + QVERIFY(manifest.folders.size() == 3); + QVERIFY(manifest.folders.count(Path("."))); + QVERIFY(manifest.folders.count(Path("a"))); + QVERIFY(manifest.folders.count(Path("a/b"))); + QVERIFY(manifest.symlinks.size() == 0); +} + +#ifndef Q_OS_WIN32 +void PackageManifestTest::test_inspect_symlinks() { + auto path = QFINDTESTDATA("testdata/inspect/"); + auto manifest = Package::fromInspectedFolder(path); + QVERIFY(manifest.valid == true); + QVERIFY(manifest.files.size() == 1); + QVERIFY(manifest.files.count(Path("a/b.txt"))); + auto &file = manifest.files[Path("a/b.txt")]; + QVERIFY(file.executable == true); + QVERIFY(file.hash == "da39a3ee5e6b4b0d3255bfef95601890afd80709"); + QVERIFY(file.size == 0); + QVERIFY(manifest.folders.size() == 3); + QVERIFY(manifest.folders.count(Path("."))); + QVERIFY(manifest.folders.count(Path("a"))); + QVERIFY(manifest.folders.count(Path("a/b"))); + QVERIFY(manifest.symlinks.size() == 1); + QVERIFY(manifest.symlinks.count(Path("a/b/b.txt"))); + qDebug() << manifest.symlinks[Path("a/b/b.txt")]; + QVERIFY(manifest.symlinks[Path("a/b/b.txt")] == Path("../b.txt")); +} +#endif + +void PackageManifestTest::mkdir_deep() { + + Package from; + auto to = Package::fromManifestContents(R"END( +{ + "files": { + "a/b/c/d/e": { + "type": "directory" + } + } +} +)END"); + auto operations = UpdateOperations::resolve(from, to); + QVERIFY(operations.deletes.size() == 0); + QVERIFY(operations.rmdirs.size() == 0); + + QVERIFY(operations.mkdirs.size() == 6); + QVERIFY(operations.mkdirs[0] == Path(".")); + QVERIFY(operations.mkdirs[1] == Path("a")); + QVERIFY(operations.mkdirs[2] == Path("a/b")); + QVERIFY(operations.mkdirs[3] == Path("a/b/c")); + QVERIFY(operations.mkdirs[4] == Path("a/b/c/d")); + QVERIFY(operations.mkdirs[5] == Path("a/b/c/d/e")); + + QVERIFY(operations.downloads.size() == 0); + QVERIFY(operations.mklinks.size() == 0); + QVERIFY(operations.executable_fixes.size() == 0); +} + +void PackageManifestTest::rmdir_deep() { + + Package to; + auto from = Package::fromManifestContents(R"END( +{ + "files": { + "a/b/c/d/e": { + "type": "directory" + } + } +} +)END"); + auto operations = UpdateOperations::resolve(from, to); + QVERIFY(operations.deletes.size() == 0); + + QVERIFY(operations.rmdirs.size() == 6); + QVERIFY(operations.rmdirs[0] == Path("a/b/c/d/e")); + QVERIFY(operations.rmdirs[1] == Path("a/b/c/d")); + QVERIFY(operations.rmdirs[2] == Path("a/b/c")); + QVERIFY(operations.rmdirs[3] == Path("a/b")); + QVERIFY(operations.rmdirs[4] == Path("a")); + QVERIFY(operations.rmdirs[5] == Path(".")); + + QVERIFY(operations.mkdirs.size() == 0); + QVERIFY(operations.downloads.size() == 0); + QVERIFY(operations.mklinks.size() == 0); + QVERIFY(operations.executable_fixes.size() == 0); +} + +void PackageManifestTest::identical_file() { + QByteArray manifest = R"END( +{ + "files": { + "a/b/c/d/empty.txt": { + "type": "file", + "downloads": { + "raw": { + "url": "http://dethware.org/empty.txt", + "sha1": "da39a3ee5e6b4b0d3255bfef95601890afd80709", + "size": 0 + } + }, + "executable": false + } + } +} +)END"; + auto from = Package::fromManifestContents(manifest); + auto to = Package::fromManifestContents(manifest); + auto operations = UpdateOperations::resolve(from, to); + QVERIFY(operations.deletes.size() == 0); + QVERIFY(operations.rmdirs.size() == 0); + QVERIFY(operations.mkdirs.size() == 0); + QVERIFY(operations.downloads.size() == 0); + QVERIFY(operations.mklinks.size() == 0); + QVERIFY(operations.executable_fixes.size() == 0); +} + +void PackageManifestTest::changed_file() { + auto from = Package::fromManifestContents(R"END( +{ + "files": { + "a/b/c/d/file": { + "type": "file", + "downloads": { + "raw": { + "url": "http://dethware.org/empty.txt", + "sha1": "da39a3ee5e6b4b0d3255bfef95601890afd80709", + "size": 0 + } + }, + "executable": false + } + } +} +)END"); + auto to = Package::fromManifestContents(R"END( +{ + "files": { + "a/b/c/d/file": { + "type": "file", + "downloads": { + "raw": { + "url": "http://dethware.org/space.txt", + "sha1": "dd122581c8cd44d0227f9c305581ffcb4b6f1b46", + "size": 1 + } + }, + "executable": false + } + } +} +)END"); + auto operations = UpdateOperations::resolve(from, to); + QVERIFY(operations.deletes.size() == 1); + QCOMPARE(operations.deletes[0], Path("a/b/c/d/file")); + QVERIFY(operations.rmdirs.size() == 0); + QVERIFY(operations.mkdirs.size() == 0); + QVERIFY(operations.downloads.size() == 1); + QVERIFY(operations.mklinks.size() == 0); + QVERIFY(operations.executable_fixes.size() == 0); +} + +void PackageManifestTest::added_file() { + auto from = Package::fromManifestContents(R"END( +{ + "files": { + "a/b/c/d": { + "type": "directory" + } + } +} +)END"); + auto to = Package::fromManifestContents(R"END( +{ + "files": { + "a/b/c/d/file": { + "type": "file", + "downloads": { + "raw": { + "url": "http://dethware.org/space.txt", + "sha1": "dd122581c8cd44d0227f9c305581ffcb4b6f1b46", + "size": 1 + } + }, + "executable": false + } + } +} +)END"); + auto operations = UpdateOperations::resolve(from, to); + QVERIFY(operations.deletes.size() == 0); + QVERIFY(operations.rmdirs.size() == 0); + QVERIFY(operations.mkdirs.size() == 0); + QVERIFY(operations.downloads.size() == 1); + QVERIFY(operations.mklinks.size() == 0); + QVERIFY(operations.executable_fixes.size() == 0); +} + +void PackageManifestTest::removed_file() { + auto from = Package::fromManifestContents(R"END( +{ + "files": { + "a/b/c/d/file": { + "type": "file", + "downloads": { + "raw": { + "url": "http://dethware.org/space.txt", + "sha1": "dd122581c8cd44d0227f9c305581ffcb4b6f1b46", + "size": 1 + } + }, + "executable": false + } + } +} +)END"); + auto to = Package::fromManifestContents(R"END( +{ + "files": { + "a/b/c/d": { + "type": "directory" + } + } +} +)END"); + auto operations = UpdateOperations::resolve(from, to); + QVERIFY(operations.deletes.size() == 1); + QCOMPARE(operations.deletes[0], Path("a/b/c/d/file")); + QVERIFY(operations.rmdirs.size() == 0); + QVERIFY(operations.mkdirs.size() == 0); + QVERIFY(operations.downloads.size() == 0); + QVERIFY(operations.mklinks.size() == 0); + QVERIFY(operations.executable_fixes.size() == 0); +} + +QTEST_GUILESS_MAIN(PackageManifestTest) + +#include "PackageManifest_test.moc" + diff --git a/src/mojang/testdata/1.8.0_202-x64.json b/src/mojang/testdata/1.8.0_202-x64.json new file mode 100644 index 0000000..3d99d71 --- /dev/null +++ b/src/mojang/testdata/1.8.0_202-x64.json @@ -0,0 +1 @@ +{"files": {"COPYRIGHT": {"downloads": {"lzma": {"sha1": "dd860e040807f7e53ae89da5f28dd73d57ac605d", "size": 1431, "url": "https://launcher.mojang.com/v1/objects/dd860e040807f7e53ae89da5f28dd73d57ac605d/COPYRIGHT"}, "raw": {"sha1": "c725183c757011e7ba96c83c1e86ee7e8b516a2b", "size": 3244, "url": "https://launcher.mojang.com/v1/objects/c725183c757011e7ba96c83c1e86ee7e8b516a2b/COPYRIGHT"}}, "executable": false, "type": "file"}, "LICENSE": {"downloads": {"raw": {"sha1": "3e86865deec0814c958bcf7fb87f790bccc0e8bd", "size": 40, "url": "https://launcher.mojang.com/v1/objects/3e86865deec0814c958bcf7fb87f790bccc0e8bd/LICENSE"}}, "executable": false, "type": "file"}, "README": {"downloads": {"raw": {"sha1": "f90331df1e5badeadc501d8dd70714c62a920204", "size": 46, "url": "https://launcher.mojang.com/v1/objects/f90331df1e5badeadc501d8dd70714c62a920204/README"}}, "executable": false, "type": "file"}, "THIRDPARTYLICENSEREADME-JAVAFX.txt": {"downloads": {"lzma": {"sha1": "4fee85109d7ff04b982d0576dabd15397f599125", "size": 15455, "url": "https://launcher.mojang.com/v1/objects/4fee85109d7ff04b982d0576dabd15397f599125/THIRDPARTYLICENSEREADME-JAVAFX.txt"}, "raw": {"sha1": "56ff42f87607b997b52ae0ef8bf315e36932e870", "size": 112724, "url": "https://launcher.mojang.com/v1/objects/56ff42f87607b997b52ae0ef8bf315e36932e870/THIRDPARTYLICENSEREADME-JAVAFX.txt"}}, "executable": false, "type": "file"}, "THIRDPARTYLICENSEREADME.txt": {"downloads": {"lzma": {"sha1": "419c1414ba46ae9dbfd38cf4e0601fff61644429", "size": 32266, "url": "https://launcher.mojang.com/v1/objects/419c1414ba46ae9dbfd38cf4e0601fff61644429/THIRDPARTYLICENSEREADME.txt"}, "raw": {"sha1": "b83c3f32261de3e48ccd20614a11e066b1ec9027", "size": 153824, "url": "https://launcher.mojang.com/v1/objects/b83c3f32261de3e48ccd20614a11e066b1ec9027/THIRDPARTYLICENSEREADME.txt"}}, "executable": false, "type": "file"}, "Welcome.html": {"downloads": {"lzma": {"sha1": "01c21a74b4aafb7cbe0388233c43cbdf77dcaaea", "size": 528, "url": "https://launcher.mojang.com/v1/objects/01c21a74b4aafb7cbe0388233c43cbdf77dcaaea/Welcome.html"}, "raw": {"sha1": "d98ae54f03dac87419abc19b97e315830c2da55f", "size": 955, "url": "https://launcher.mojang.com/v1/objects/d98ae54f03dac87419abc19b97e315830c2da55f/Welcome.html"}}, "executable": false, "type": "file"}, "bin": {"type": "directory"}, "bin/ControlPanel": {"target": "jcontrol", "type": "link"}, "bin/java": {"downloads": {"lzma": {"sha1": "3857eea1d59e1bc545c67a753ed2768254807b8a", "size": 2088, "url": "https://launcher.mojang.com/v1/objects/3857eea1d59e1bc545c67a753ed2768254807b8a/java"}, "raw": {"sha1": "3d20560fb5d1a49cb689c2226972e92e06d27ba6", "size": 8464, "url": "https://launcher.mojang.com/v1/objects/3d20560fb5d1a49cb689c2226972e92e06d27ba6/java"}}, "executable": true, "type": "file"}, "bin/javaws": {"downloads": {"lzma": {"sha1": "a6bec5c049e76c4488294a256a2084ea23ddb440", "size": 38173, "url": "https://launcher.mojang.com/v1/objects/a6bec5c049e76c4488294a256a2084ea23ddb440/javaws"}, "raw": {"sha1": "955c0f0066e2f893b0c2b3ccd83e223722e4ab74", "size": 140296, "url": "https://launcher.mojang.com/v1/objects/955c0f0066e2f893b0c2b3ccd83e223722e4ab74/javaws"}}, "executable": true, "type": "file"}, "bin/jcontrol": {"downloads": {"lzma": {"sha1": "40c5e33748f252e1d950b579a4185ab2c23fc908", "size": 2166, "url": "https://launcher.mojang.com/v1/objects/40c5e33748f252e1d950b579a4185ab2c23fc908/jcontrol"}, "raw": {"sha1": "ed541733c8b51e34349c1f8010b277e58ad73f1e", "size": 6264, "url": "https://launcher.mojang.com/v1/objects/ed541733c8b51e34349c1f8010b277e58ad73f1e/jcontrol"}}, "executable": true, "type": "file"}, "bin/jjs": {"downloads": {"lzma": {"sha1": "d44d1ac421979f7671921986214812095a5b0e3b", "size": 2168, "url": "https://launcher.mojang.com/v1/objects/d44d1ac421979f7671921986214812095a5b0e3b/jjs"}, "raw": {"sha1": "f00f944c3dbe556793b5dc686aaeee3e5722e99b", "size": 8584, "url": "https://launcher.mojang.com/v1/objects/f00f944c3dbe556793b5dc686aaeee3e5722e99b/jjs"}}, "executable": true, "type": "file"}, "bin/keytool": {"downloads": {"lzma": {"sha1": "93c607dce450976667c382f609a367167bdec05c", "size": 2175, "url": "https://launcher.mojang.com/v1/objects/93c607dce450976667c382f609a367167bdec05c/keytool"}, "raw": {"sha1": "7114b561546270e441e9ed1bcc24e5188c068a42", "size": 8584, "url": "https://launcher.mojang.com/v1/objects/7114b561546270e441e9ed1bcc24e5188c068a42/keytool"}}, "executable": true, "type": "file"}, "bin/orbd": {"downloads": {"lzma": {"sha1": "b27dfded5e2b2f6f02c555971c94e46ca14ac81b", "size": 2254, "url": "https://launcher.mojang.com/v1/objects/b27dfded5e2b2f6f02c555971c94e46ca14ac81b/orbd"}, "raw": {"sha1": "7f31217fecb3dbbd89f1dd3783fca58793a66fd2", "size": 8656, "url": "https://launcher.mojang.com/v1/objects/7f31217fecb3dbbd89f1dd3783fca58793a66fd2/orbd"}}, "executable": true, "type": "file"}, "bin/pack200": {"downloads": {"lzma": {"sha1": "b52da4497b49b1508b6225a5740857ddb8f52e97", "size": 2183, "url": "https://launcher.mojang.com/v1/objects/b52da4497b49b1508b6225a5740857ddb8f52e97/pack200"}, "raw": {"sha1": "16ef3e801efb57e50bc6477a27a9d95d02d0775b", "size": 8584, "url": "https://launcher.mojang.com/v1/objects/16ef3e801efb57e50bc6477a27a9d95d02d0775b/pack200"}}, "executable": true, "type": "file"}, "bin/policytool": {"downloads": {"lzma": {"sha1": "87da4c07da45f3d1a1a9d732af197cd39bf69d10", "size": 2182, "url": "https://launcher.mojang.com/v1/objects/87da4c07da45f3d1a1a9d732af197cd39bf69d10/policytool"}, "raw": {"sha1": "a52a29424470cb9b8db5c2fb1751d0b697a7ec8e", "size": 8592, "url": "https://launcher.mojang.com/v1/objects/a52a29424470cb9b8db5c2fb1751d0b697a7ec8e/policytool"}}, "executable": true, "type": "file"}, "bin/rmid": {"downloads": {"lzma": {"sha1": "1494c1174fde0c0a93ea117bc7edf7eb936c0512", "size": 2172, "url": "https://launcher.mojang.com/v1/objects/1494c1174fde0c0a93ea117bc7edf7eb936c0512/rmid"}, "raw": {"sha1": "5c8710e1ab924e5b09a07bcb4c6e106293bbd1a8", "size": 8584, "url": "https://launcher.mojang.com/v1/objects/5c8710e1ab924e5b09a07bcb4c6e106293bbd1a8/rmid"}}, "executable": true, "type": "file"}, "bin/rmiregistry": {"downloads": {"lzma": {"sha1": "7070cf2ec5a5e520a880bae699431edf02083e7e", "size": 2174, "url": "https://launcher.mojang.com/v1/objects/7070cf2ec5a5e520a880bae699431edf02083e7e/rmiregistry"}, "raw": {"sha1": "5f518daa7050028d5d9d849634c73136f2b23a54", "size": 8592, "url": "https://launcher.mojang.com/v1/objects/5f518daa7050028d5d9d849634c73136f2b23a54/rmiregistry"}}, "executable": true, "type": "file"}, "bin/servertool": {"downloads": {"lzma": {"sha1": "1db683a11cc9b7313426c84412f4d95be2fa7ccd", "size": 2185, "url": "https://launcher.mojang.com/v1/objects/1db683a11cc9b7313426c84412f4d95be2fa7ccd/servertool"}, "raw": {"sha1": "49d0ebfeb265ce5a8733e1014541ea2525674a60", "size": 8592, "url": "https://launcher.mojang.com/v1/objects/49d0ebfeb265ce5a8733e1014541ea2525674a60/servertool"}}, "executable": true, "type": "file"}, "bin/tnameserv": {"downloads": {"lzma": {"sha1": "36da9c9a2c5a8b662a3f8d52ca67339bce1c2714", "size": 2291, "url": "https://launcher.mojang.com/v1/objects/36da9c9a2c5a8b662a3f8d52ca67339bce1c2714/tnameserv"}, "raw": {"sha1": "09d998f8efcb6f55d0d87f59e08f8b89662796d9", "size": 8656, "url": "https://launcher.mojang.com/v1/objects/09d998f8efcb6f55d0d87f59e08f8b89662796d9/tnameserv"}}, "executable": true, "type": "file"}, "bin/unpack200": {"downloads": {"lzma": {"sha1": "344959e32fc7ee19eebe7b3cf5ab6d1a7d6641f2", "size": 79721, "url": "https://launcher.mojang.com/v1/objects/344959e32fc7ee19eebe7b3cf5ab6d1a7d6641f2/unpack200"}, "raw": {"sha1": "5dd933132f1b202e19e0c8e093f7113711cfdfc1", "size": 182616, "url": "https://launcher.mojang.com/v1/objects/5dd933132f1b202e19e0c8e093f7113711cfdfc1/unpack200"}}, "executable": true, "type": "file"}, "lib": {"type": "directory"}, "lib/amd64": {"type": "directory"}, "lib/amd64/jli": {"type": "directory"}, "lib/amd64/jli/libjli.so": {"downloads": {"lzma": {"sha1": "372331ee8e375888f798a2e88180a94493e141b0", "size": 48327, "url": "https://launcher.mojang.com/v1/objects/372331ee8e375888f798a2e88180a94493e141b0/libjli.so"}, "raw": {"sha1": "73b0cf8b7415686bc40c561ff77ff2740ccf7a44", "size": 108616, "url": "https://launcher.mojang.com/v1/objects/73b0cf8b7415686bc40c561ff77ff2740ccf7a44/libjli.so"}}, "executable": true, "type": "file"}, "lib/amd64/jvm.cfg": {"downloads": {"lzma": {"sha1": "86bcfebec37b38415525ffd77d3eaf70d0b1b4ca", "size": 435, "url": "https://launcher.mojang.com/v1/objects/86bcfebec37b38415525ffd77d3eaf70d0b1b4ca/jvm.cfg"}, "raw": {"sha1": "84b38bdc745de446ba0ca0232ea3aaf2efd721da", "size": 627, "url": "https://launcher.mojang.com/v1/objects/84b38bdc745de446ba0ca0232ea3aaf2efd721da/jvm.cfg"}}, "executable": false, "type": "file"}, "lib/amd64/libavplugin-53.so": {"downloads": {"lzma": {"sha1": "a332366762d9efc7b845a682b7edce62db44618c", "size": 14747, "url": "https://launcher.mojang.com/v1/objects/a332366762d9efc7b845a682b7edce62db44618c/libavplugin-53.so"}, "raw": {"sha1": "9bd1473dd8a0dc7950c7af1cc69a45548df26eb5", "size": 51720, "url": "https://launcher.mojang.com/v1/objects/9bd1473dd8a0dc7950c7af1cc69a45548df26eb5/libavplugin-53.so"}}, "executable": true, "type": "file"}, "lib/amd64/libavplugin-54.so": {"downloads": {"lzma": {"sha1": "2c615852a0720a275163e00597c1f711f11341da", "size": 15153, "url": "https://launcher.mojang.com/v1/objects/2c615852a0720a275163e00597c1f711f11341da/libavplugin-54.so"}, "raw": {"sha1": "8808050c5949c4800b42d1b19b1f8b0d120bcacb", "size": 51768, "url": "https://launcher.mojang.com/v1/objects/8808050c5949c4800b42d1b19b1f8b0d120bcacb/libavplugin-54.so"}}, "executable": true, "type": "file"}, "lib/amd64/libavplugin-55.so": {"downloads": {"lzma": {"sha1": "39ee8e7fe14f0010c78973962800f539c3e4c16b", "size": 15168, "url": "https://launcher.mojang.com/v1/objects/39ee8e7fe14f0010c78973962800f539c3e4c16b/libavplugin-55.so"}, "raw": {"sha1": "f10ea4ea3489e96d8d161a96790133c417ec44e1", "size": 51784, "url": "https://launcher.mojang.com/v1/objects/f10ea4ea3489e96d8d161a96790133c417ec44e1/libavplugin-55.so"}}, "executable": true, "type": "file"}, "lib/amd64/libavplugin-56.so": {"downloads": {"lzma": {"sha1": "abe7feced5a559f1bdc868526dc69484e0e591a0", "size": 15169, "url": "https://launcher.mojang.com/v1/objects/abe7feced5a559f1bdc868526dc69484e0e591a0/libavplugin-56.so"}, "raw": {"sha1": "e5bfcbff5a5a5a5993a3e689a05ef358c131a3ed", "size": 51784, "url": "https://launcher.mojang.com/v1/objects/e5bfcbff5a5a5a5993a3e689a05ef358c131a3ed/libavplugin-56.so"}}, "executable": true, "type": "file"}, "lib/amd64/libavplugin-57.so": {"downloads": {"lzma": {"sha1": "4dd26b4ef2294b6929dcb2c7546b47eac5cc78a9", "size": 15174, "url": "https://launcher.mojang.com/v1/objects/4dd26b4ef2294b6929dcb2c7546b47eac5cc78a9/libavplugin-57.so"}, "raw": {"sha1": "2949e7ff9b0ac90e8943c211cff141ab12eec3f8", "size": 51784, "url": "https://launcher.mojang.com/v1/objects/2949e7ff9b0ac90e8943c211cff141ab12eec3f8/libavplugin-57.so"}}, "executable": true, "type": "file"}, "lib/amd64/libavplugin-ffmpeg-56.so": {"downloads": {"lzma": {"sha1": "c688ba1cfa442bf18bee43b2fa870b4dc1ce3fb6", "size": 15231, "url": "https://launcher.mojang.com/v1/objects/c688ba1cfa442bf18bee43b2fa870b4dc1ce3fb6/libavplugin-ffmpeg-56.so"}, "raw": {"sha1": "0d36c971a9ad99fc2292092fdec3a4179b1021b9", "size": 51920, "url": "https://launcher.mojang.com/v1/objects/0d36c971a9ad99fc2292092fdec3a4179b1021b9/libavplugin-ffmpeg-56.so"}}, "executable": true, "type": "file"}, "lib/amd64/libavplugin-ffmpeg-57.so": {"downloads": {"lzma": {"sha1": "087426bdbffebcfa372a438e863785f4ffbe9a6b", "size": 15180, "url": "https://launcher.mojang.com/v1/objects/087426bdbffebcfa372a438e863785f4ffbe9a6b/libavplugin-ffmpeg-57.so"}, "raw": {"sha1": "5e9c4eb4b49eb8e57c01003ec73a1eb8d6d8c462", "size": 51784, "url": "https://launcher.mojang.com/v1/objects/5e9c4eb4b49eb8e57c01003ec73a1eb8d6d8c462/libavplugin-ffmpeg-57.so"}}, "executable": true, "type": "file"}, "lib/amd64/libawt.so": {"downloads": {"lzma": {"sha1": "018be58b205b73c842a55df811b70d0e8237216e", "size": 195720, "url": "https://launcher.mojang.com/v1/objects/018be58b205b73c842a55df811b70d0e8237216e/libawt.so"}, "raw": {"sha1": "02632cd326e3161c00a7e784599dd7b9ee053dce", "size": 759184, "url": "https://launcher.mojang.com/v1/objects/02632cd326e3161c00a7e784599dd7b9ee053dce/libawt.so"}}, "executable": true, "type": "file"}, "lib/amd64/libawt_headless.so": {"downloads": {"lzma": {"sha1": "7ac2517cff75d4bbb0a0412a9b5f18c74ea188fa", "size": 11211, "url": "https://launcher.mojang.com/v1/objects/7ac2517cff75d4bbb0a0412a9b5f18c74ea188fa/libawt_headless.so"}, "raw": {"sha1": "862157ec957008d0911c5daedc004b3a202623a4", "size": 39176, "url": "https://launcher.mojang.com/v1/objects/862157ec957008d0911c5daedc004b3a202623a4/libawt_headless.so"}}, "executable": true, "type": "file"}, "lib/amd64/libawt_xawt.so": {"downloads": {"lzma": {"sha1": "d536a96af27dfe35de6bb2c8759d51c488cdd8d4", "size": 149598, "url": "https://launcher.mojang.com/v1/objects/d536a96af27dfe35de6bb2c8759d51c488cdd8d4/libawt_xawt.so"}, "raw": {"sha1": "28232b3e01b6f11bfe098bfc6eafc3a513dcebf1", "size": 470232, "url": "https://launcher.mojang.com/v1/objects/28232b3e01b6f11bfe098bfc6eafc3a513dcebf1/libawt_xawt.so"}}, "executable": true, "type": "file"}, "lib/amd64/libbci.so": {"downloads": {"lzma": {"sha1": "c36fad091d11e64c815d5ca17c0ef7a55b0776b1", "size": 3509, "url": "https://launcher.mojang.com/v1/objects/c36fad091d11e64c815d5ca17c0ef7a55b0776b1/libbci.so"}, "raw": {"sha1": "33824051db1ccb6332e22c2b63231055240d0af0", "size": 12760, "url": "https://launcher.mojang.com/v1/objects/33824051db1ccb6332e22c2b63231055240d0af0/libbci.so"}}, "executable": true, "type": "file"}, "lib/amd64/libdcpr.so": {"downloads": {"lzma": {"sha1": "70c6b0933a37f2b1124d6e7c131039241fe796ee", "size": 75969, "url": "https://launcher.mojang.com/v1/objects/70c6b0933a37f2b1124d6e7c131039241fe796ee/libdcpr.so"}, "raw": {"sha1": "fa7001bc5d80579e2716590f3eee8027da0beae7", "size": 204456, "url": "https://launcher.mojang.com/v1/objects/fa7001bc5d80579e2716590f3eee8027da0beae7/libdcpr.so"}}, "executable": true, "type": "file"}, "lib/amd64/libdecora_sse.so": {"downloads": {"lzma": {"sha1": "514acc017dfb6cefaf8cc6d18006ce55781cc9bc", "size": 24397, "url": "https://launcher.mojang.com/v1/objects/514acc017dfb6cefaf8cc6d18006ce55781cc9bc/libdecora_sse.so"}, "raw": {"sha1": "d0c84233504c916e548e29f513e25f6a7479abfc", "size": 74912, "url": "https://launcher.mojang.com/v1/objects/d0c84233504c916e548e29f513e25f6a7479abfc/libdecora_sse.so"}}, "executable": true, "type": "file"}, "lib/amd64/libdeploy.so": {"downloads": {"lzma": {"sha1": "6cf31fd98301c749ac0d2c7825f6d925a4409760", "size": 168999, "url": "https://launcher.mojang.com/v1/objects/6cf31fd98301c749ac0d2c7825f6d925a4409760/libdeploy.so"}, "raw": {"sha1": "b3832e97ed8ca794884b56a591b83d02a2c0c06f", "size": 642368, "url": "https://launcher.mojang.com/v1/objects/b3832e97ed8ca794884b56a591b83d02a2c0c06f/libdeploy.so"}}, "executable": true, "type": "file"}, "lib/amd64/libdt_socket.so": {"downloads": {"lzma": {"sha1": "4cc5c880dbb6fa180436d12d60f0abec8ebb59dc", "size": 7784, "url": "https://launcher.mojang.com/v1/objects/4cc5c880dbb6fa180436d12d60f0abec8ebb59dc/libdt_socket.so"}, "raw": {"sha1": "91ce96f252b8139fc12f0f224ed5b1a041767ab7", "size": 24616, "url": "https://launcher.mojang.com/v1/objects/91ce96f252b8139fc12f0f224ed5b1a041767ab7/libdt_socket.so"}}, "executable": true, "type": "file"}, "lib/amd64/libfontmanager.so": {"downloads": {"lzma": {"sha1": "f94e5e94c71c603ff4d3cd1e7e3d9e181fcc145d", "size": 146951, "url": "https://launcher.mojang.com/v1/objects/f94e5e94c71c603ff4d3cd1e7e3d9e181fcc145d/libfontmanager.so"}, "raw": {"sha1": "2428e805f2c53d1283a033dfd11a86fbb7bd7159", "size": 490672, "url": "https://launcher.mojang.com/v1/objects/2428e805f2c53d1283a033dfd11a86fbb7bd7159/libfontmanager.so"}}, "executable": true, "type": "file"}, "lib/amd64/libfxplugins.so": {"downloads": {"lzma": {"sha1": "a640143365d382a5ad743a784bc2f3706d9d6d67", "size": 50048, "url": "https://launcher.mojang.com/v1/objects/a640143365d382a5ad743a784bc2f3706d9d6d67/libfxplugins.so"}, "raw": {"sha1": "0fd4ac04a84c131f1aaee9e6b0898ff9ea69e3ee", "size": 151448, "url": "https://launcher.mojang.com/v1/objects/0fd4ac04a84c131f1aaee9e6b0898ff9ea69e3ee/libfxplugins.so"}}, "executable": true, "type": "file"}, "lib/amd64/libglass.so": {"downloads": {"lzma": {"sha1": "f1ff517714fa5f2c861f33b32db823fe851541f1", "size": 2856, "url": "https://launcher.mojang.com/v1/objects/f1ff517714fa5f2c861f33b32db823fe851541f1/libglass.so"}, "raw": {"sha1": "e7f4fece30ac727be8148d33b8256abd3a41cef9", "size": 13072, "url": "https://launcher.mojang.com/v1/objects/e7f4fece30ac727be8148d33b8256abd3a41cef9/libglass.so"}}, "executable": true, "type": "file"}, "lib/amd64/libglassgtk2.so": {"downloads": {"lzma": {"sha1": "15b90f7a2baacd15e80aa9785d87cf1e4258376d", "size": 220476, "url": "https://launcher.mojang.com/v1/objects/15b90f7a2baacd15e80aa9785d87cf1e4258376d/libglassgtk2.so"}, "raw": {"sha1": "e30a634c2ff2143bdee512360553d6e0304f33b2", "size": 844984, "url": "https://launcher.mojang.com/v1/objects/e30a634c2ff2143bdee512360553d6e0304f33b2/libglassgtk2.so"}}, "executable": true, "type": "file"}, "lib/amd64/libglassgtk3.so": {"downloads": {"lzma": {"sha1": "868c231165f8c9043b7f0e7de208ec023f06a6e7", "size": 220560, "url": "https://launcher.mojang.com/v1/objects/868c231165f8c9043b7f0e7de208ec023f06a6e7/libglassgtk3.so"}, "raw": {"sha1": "762a11a2b376b7b5a2a7cad780715524fdd176d5", "size": 845304, "url": "https://launcher.mojang.com/v1/objects/762a11a2b376b7b5a2a7cad780715524fdd176d5/libglassgtk3.so"}}, "executable": true, "type": "file"}, "lib/amd64/libglib-lite.so": {"downloads": {"lzma": {"sha1": "61b8871242febe1be262de167dc20ae94bf964b4", "size": 457046, "url": "https://launcher.mojang.com/v1/objects/61b8871242febe1be262de167dc20ae94bf964b4/libglib-lite.so"}, "raw": {"sha1": "63afa060fc3f120af76128e51d32603fc4336fa8", "size": 1538352, "url": "https://launcher.mojang.com/v1/objects/63afa060fc3f120af76128e51d32603fc4336fa8/libglib-lite.so"}}, "executable": true, "type": "file"}, "lib/amd64/libgstreamer-lite.so": {"downloads": {"lzma": {"sha1": "2447dc368406ba1b989a29937d41924620e01988", "size": 673056, "url": "https://launcher.mojang.com/v1/objects/2447dc368406ba1b989a29937d41924620e01988/libgstreamer-lite.so"}, "raw": {"sha1": "5505e7ca592ac64371d3db8fe53bcb602e9723d3", "size": 2263872, "url": "https://launcher.mojang.com/v1/objects/5505e7ca592ac64371d3db8fe53bcb602e9723d3/libgstreamer-lite.so"}}, "executable": true, "type": "file"}, "lib/amd64/libhprof.so": {"downloads": {"lzma": {"sha1": "94a5589c818db1fb1cf1881e24e217c309fce2e4", "size": 64471, "url": "https://launcher.mojang.com/v1/objects/94a5589c818db1fb1cf1881e24e217c309fce2e4/libhprof.so"}, "raw": {"sha1": "4bb9bdeef6133b6dd558d52d691b077c03e9b0ee", "size": 175504, "url": "https://launcher.mojang.com/v1/objects/4bb9bdeef6133b6dd558d52d691b077c03e9b0ee/libhprof.so"}}, "executable": true, "type": "file"}, "lib/amd64/libinstrument.so": {"downloads": {"lzma": {"sha1": "84ffea356caf725b42c86a8ebc9587f477ddde29", "size": 18603, "url": "https://launcher.mojang.com/v1/objects/84ffea356caf725b42c86a8ebc9587f477ddde29/libinstrument.so"}, "raw": {"sha1": "cb8009769601e3fecd7ea2b36c344f737b1a9da7", "size": 51560, "url": "https://launcher.mojang.com/v1/objects/cb8009769601e3fecd7ea2b36c344f737b1a9da7/libinstrument.so"}}, "executable": true, "type": "file"}, "lib/amd64/libj2gss.so": {"downloads": {"lzma": {"sha1": "4b2aa699506b126098b585a9617ce1c05707fa29", "size": 14132, "url": "https://launcher.mojang.com/v1/objects/4b2aa699506b126098b585a9617ce1c05707fa29/libj2gss.so"}, "raw": {"sha1": "cbce4a302b255d4d1924ef7606f038af766c5e86", "size": 47688, "url": "https://launcher.mojang.com/v1/objects/cbce4a302b255d4d1924ef7606f038af766c5e86/libj2gss.so"}}, "executable": true, "type": "file"}, "lib/amd64/libj2pcsc.so": {"downloads": {"lzma": {"sha1": "2361d3b2e3da48593c391b29b0d2b5409e4c55e5", "size": 5074, "url": "https://launcher.mojang.com/v1/objects/2361d3b2e3da48593c391b29b0d2b5409e4c55e5/libj2pcsc.so"}, "raw": {"sha1": "1274178492e7a3e997e12f67794616f7c3d8d0b9", "size": 18296, "url": "https://launcher.mojang.com/v1/objects/1274178492e7a3e997e12f67794616f7c3d8d0b9/libj2pcsc.so"}}, "executable": true, "type": "file"}, "lib/amd64/libj2pkcs11.so": {"downloads": {"lzma": {"sha1": "ef927e2790ba05931d0f0bdd63da3d275a834946", "size": 21573, "url": "https://launcher.mojang.com/v1/objects/ef927e2790ba05931d0f0bdd63da3d275a834946/libj2pkcs11.so"}, "raw": {"sha1": "bd4f2af9bfdc6168633d1920c1a1415de06bb45a", "size": 79472, "url": "https://launcher.mojang.com/v1/objects/bd4f2af9bfdc6168633d1920c1a1415de06bb45a/libj2pkcs11.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjaas_unix.so": {"downloads": {"lzma": {"sha1": "7f7e843544ee1eb1454a5826bdd4218685b79430", "size": 2404, "url": "https://launcher.mojang.com/v1/objects/7f7e843544ee1eb1454a5826bdd4218685b79430/libjaas_unix.so"}, "raw": {"sha1": "4c517925c7d464a5b719898eb0bea1b04df31f1f", "size": 8192, "url": "https://launcher.mojang.com/v1/objects/4c517925c7d464a5b719898eb0bea1b04df31f1f/libjaas_unix.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjava.so": {"downloads": {"lzma": {"sha1": "5eee7a42600a44a8bb8d6d7f510fd96a29637ac0", "size": 63113, "url": "https://launcher.mojang.com/v1/objects/5eee7a42600a44a8bb8d6d7f510fd96a29637ac0/libjava.so"}, "raw": {"sha1": "e280aeddf3fc0ec664aef7efc0e0e197a54aaf02", "size": 227672, "url": "https://launcher.mojang.com/v1/objects/e280aeddf3fc0ec664aef7efc0e0e197a54aaf02/libjava.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjava_crw_demo.so": {"downloads": {"lzma": {"sha1": "b197cf23ae3556eb0b45c663f0a8cb62408b961e", "size": 10412, "url": "https://launcher.mojang.com/v1/objects/b197cf23ae3556eb0b45c663f0a8cb62408b961e/libjava_crw_demo.so"}, "raw": {"sha1": "18f20f906977c90d0090b41dbda8dd5cfead5a4c", "size": 26144, "url": "https://launcher.mojang.com/v1/objects/18f20f906977c90d0090b41dbda8dd5cfead5a4c/libjava_crw_demo.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjavafx_font.so": {"downloads": {"lzma": {"sha1": "ffbba0e5022f829412b86063d8a90f95f16709b1", "size": 5608, "url": "https://launcher.mojang.com/v1/objects/ffbba0e5022f829412b86063d8a90f95f16709b1/libjavafx_font.so"}, "raw": {"sha1": "8634a0aca612fc40420a4a7cc8af4cc46cfc6725", "size": 17104, "url": "https://launcher.mojang.com/v1/objects/8634a0aca612fc40420a4a7cc8af4cc46cfc6725/libjavafx_font.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjavafx_font_freetype.so": {"downloads": {"lzma": {"sha1": "310271eda8a2ac264ffc3640a9d847b49438d0bd", "size": 6942, "url": "https://launcher.mojang.com/v1/objects/310271eda8a2ac264ffc3640a9d847b49438d0bd/libjavafx_font_freetype.so"}, "raw": {"sha1": "3e7572d047c12ba2bc43acec7f98a67c20af8042", "size": 27616, "url": "https://launcher.mojang.com/v1/objects/3e7572d047c12ba2bc43acec7f98a67c20af8042/libjavafx_font_freetype.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjavafx_font_pango.so": {"downloads": {"lzma": {"sha1": "a7bcf0669e70b0f43099a99c81e6b6440cb40ac0", "size": 5820, "url": "https://launcher.mojang.com/v1/objects/a7bcf0669e70b0f43099a99c81e6b6440cb40ac0/libjavafx_font_pango.so"}, "raw": {"sha1": "f0b775cc9a514c7ee8b4d6fb300653ce548caf10", "size": 25560, "url": "https://launcher.mojang.com/v1/objects/f0b775cc9a514c7ee8b4d6fb300653ce548caf10/libjavafx_font_pango.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjavafx_font_t2k.so": {"downloads": {"lzma": {"sha1": "551c29dc7c7fc83223aa36a6187f7e0c5d650538", "size": 431450, "url": "https://launcher.mojang.com/v1/objects/551c29dc7c7fc83223aa36a6187f7e0c5d650538/libjavafx_font_t2k.so"}, "raw": {"sha1": "91e5813057c3b852d411540160f8ad05fb9f1ed3", "size": 1486128, "url": "https://launcher.mojang.com/v1/objects/91e5813057c3b852d411540160f8ad05fb9f1ed3/libjavafx_font_t2k.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjavafx_iio.so": {"downloads": {"lzma": {"sha1": "c832998fd5e06ed6dcd6428816194c350785420c", "size": 101479, "url": "https://launcher.mojang.com/v1/objects/c832998fd5e06ed6dcd6428816194c350785420c/libjavafx_iio.so"}, "raw": {"sha1": "dcdf68cb25677b76c1cf0bb94294e6e9880a6678", "size": 256336, "url": "https://launcher.mojang.com/v1/objects/dcdf68cb25677b76c1cf0bb94294e6e9880a6678/libjavafx_iio.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjawt.so": {"downloads": {"lzma": {"sha1": "c1ced6aad5c69ff444dc67d0fd7e333558953831", "size": 1872, "url": "https://launcher.mojang.com/v1/objects/c1ced6aad5c69ff444dc67d0fd7e333558953831/libjawt.so"}, "raw": {"sha1": "c5032f2c6fa40bea24e56605cf76b26a27e87b67", "size": 8048, "url": "https://launcher.mojang.com/v1/objects/c5032f2c6fa40bea24e56605cf76b26a27e87b67/libjawt.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjdwp.so": {"downloads": {"lzma": {"sha1": "c1aabbb3f5a624b9ad10ed871a1d83510a99b646", "size": 94884, "url": "https://launcher.mojang.com/v1/objects/c1aabbb3f5a624b9ad10ed871a1d83510a99b646/libjdwp.so"}, "raw": {"sha1": "a043e97be47937f6f552e94cf79c76c1c57f9594", "size": 272248, "url": "https://launcher.mojang.com/v1/objects/a043e97be47937f6f552e94cf79c76c1c57f9594/libjdwp.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjfr.so": {"downloads": {"lzma": {"sha1": "11b8e6bfffdccbacbf9dd29dea4b90b753f3c1b7", "size": 8780, "url": "https://launcher.mojang.com/v1/objects/11b8e6bfffdccbacbf9dd29dea4b90b753f3c1b7/libjfr.so"}, "raw": {"sha1": "312392dd186b11c418183e818f1928e8685a07e5", "size": 28384, "url": "https://launcher.mojang.com/v1/objects/312392dd186b11c418183e818f1928e8685a07e5/libjfr.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjfxmedia.so": {"downloads": {"lzma": {"sha1": "a4e7a126eb648ce6e5e6dc151831da37d8334139", "size": 391897, "url": "https://launcher.mojang.com/v1/objects/a4e7a126eb648ce6e5e6dc151831da37d8334139/libjfxmedia.so"}, "raw": {"sha1": "5fa54944327a6012c3d34cb5c1c4432762178dc8", "size": 1636376, "url": "https://launcher.mojang.com/v1/objects/5fa54944327a6012c3d34cb5c1c4432762178dc8/libjfxmedia.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjfxwebkit.so": {"downloads": {"lzma": {"sha1": "b274debd222cdcc2ee84160ebb95144b3880bc97", "size": 20492825, "url": "https://launcher.mojang.com/v1/objects/b274debd222cdcc2ee84160ebb95144b3880bc97/libjfxwebkit.so"}, "raw": {"sha1": "ecee564c3b2f645131b35bb3004abd4caeabd291", "size": 91014584, "url": "https://launcher.mojang.com/v1/objects/ecee564c3b2f645131b35bb3004abd4caeabd291/libjfxwebkit.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjpeg.so": {"downloads": {"lzma": {"sha1": "9ad55e370c5eaaa73c3158339db3c368b1aaf0cb", "size": 113072, "url": "https://launcher.mojang.com/v1/objects/9ad55e370c5eaaa73c3158339db3c368b1aaf0cb/libjpeg.so"}, "raw": {"sha1": "651e6d53ae67db1f0efbf7f104447a9b49b7e333", "size": 292520, "url": "https://launcher.mojang.com/v1/objects/651e6d53ae67db1f0efbf7f104447a9b49b7e333/libjpeg.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjsdt.so": {"downloads": {"lzma": {"sha1": "04b6d1361a34c496b5f652b2477784d69b8b6baf", "size": 3964, "url": "https://launcher.mojang.com/v1/objects/04b6d1361a34c496b5f652b2477784d69b8b6baf/libjsdt.so"}, "raw": {"sha1": "82b48a82bf6183d34cf00a0f81661b45c616f31b", "size": 12904, "url": "https://launcher.mojang.com/v1/objects/82b48a82bf6183d34cf00a0f81661b45c616f31b/libjsdt.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjsig.so": {"downloads": {"lzma": {"sha1": "37d3b89abde397216cc4ecb1339d8543d99b8428", "size": 3536, "url": "https://launcher.mojang.com/v1/objects/37d3b89abde397216cc4ecb1339d8543d99b8428/libjsig.so"}, "raw": {"sha1": "42e52ba1bcbe0362ab24bcf65c93797354db6fb9", "size": 13336, "url": "https://launcher.mojang.com/v1/objects/42e52ba1bcbe0362ab24bcf65c93797354db6fb9/libjsig.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjsound.so": {"downloads": {"lzma": {"sha1": "7e3c565d74d8ffae716f32b05544fa4a6f108adc", "size": 2002, "url": "https://launcher.mojang.com/v1/objects/7e3c565d74d8ffae716f32b05544fa4a6f108adc/libjsound.so"}, "raw": {"sha1": "0c0fc63b92d7b83c9960fa80d45c80553ea20254", "size": 8232, "url": "https://launcher.mojang.com/v1/objects/0c0fc63b92d7b83c9960fa80d45c80553ea20254/libjsound.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjsoundalsa.so": {"downloads": {"lzma": {"sha1": "b06c51858a25ff776519495f1b9b3d9f604b089f", "size": 23097, "url": "https://launcher.mojang.com/v1/objects/b06c51858a25ff776519495f1b9b3d9f604b089f/libjsoundalsa.so"}, "raw": {"sha1": "281d37f0326d4a12dc7ea316ead09c198ff7bdf7", "size": 83256, "url": "https://launcher.mojang.com/v1/objects/281d37f0326d4a12dc7ea316ead09c198ff7bdf7/libjsoundalsa.so"}}, "executable": true, "type": "file"}, "lib/amd64/liblcms.so": {"downloads": {"lzma": {"sha1": "7a239baba2086cae49114b382b74b971da02f08e", "size": 176175, "url": "https://launcher.mojang.com/v1/objects/7a239baba2086cae49114b382b74b971da02f08e/liblcms.so"}, "raw": {"sha1": "c8895cc3c3d023d9e059225969ab67954772c0a1", "size": 526872, "url": "https://launcher.mojang.com/v1/objects/c8895cc3c3d023d9e059225969ab67954772c0a1/liblcms.so"}}, "executable": true, "type": "file"}, "lib/amd64/libmanagement.so": {"downloads": {"lzma": {"sha1": "aed3fdbcefd1716abfc6a306687c8b741cbb318e", "size": 12838, "url": "https://launcher.mojang.com/v1/objects/aed3fdbcefd1716abfc6a306687c8b741cbb318e/libmanagement.so"}, "raw": {"sha1": "eba35f61e0d50e30874b7c7b335edf2d52662423", "size": 51808, "url": "https://launcher.mojang.com/v1/objects/eba35f61e0d50e30874b7c7b335edf2d52662423/libmanagement.so"}}, "executable": true, "type": "file"}, "lib/amd64/libmlib_image.so": {"downloads": {"lzma": {"sha1": "1bb181f079492d55c7a458e96488cd17fe0a7b86", "size": 310272, "url": "https://launcher.mojang.com/v1/objects/1bb181f079492d55c7a458e96488cd17fe0a7b86/libmlib_image.so"}, "raw": {"sha1": "c973c450d33873675945d4694be484e3427f58f1", "size": 1048136, "url": "https://launcher.mojang.com/v1/objects/c973c450d33873675945d4694be484e3427f58f1/libmlib_image.so"}}, "executable": true, "type": "file"}, "lib/amd64/libnet.so": {"downloads": {"lzma": {"sha1": "9dd79703b6deb86e0321afe01c6ac508263c8312", "size": 38123, "url": "https://launcher.mojang.com/v1/objects/9dd79703b6deb86e0321afe01c6ac508263c8312/libnet.so"}, "raw": {"sha1": "b3a17b7d53fcdf1e689e1ec29ce851eee6022ead", "size": 116920, "url": "https://launcher.mojang.com/v1/objects/b3a17b7d53fcdf1e689e1ec29ce851eee6022ead/libnet.so"}}, "executable": true, "type": "file"}, "lib/amd64/libnio.so": {"downloads": {"lzma": {"sha1": "5697c89d5d5d9b74f2e1555fcbba79dd4049e287", "size": 24445, "url": "https://launcher.mojang.com/v1/objects/5697c89d5d5d9b74f2e1555fcbba79dd4049e287/libnio.so"}, "raw": {"sha1": "573bf8f64dbcc397f8abd3e1da28f90ab0679f5b", "size": 93872, "url": "https://launcher.mojang.com/v1/objects/573bf8f64dbcc397f8abd3e1da28f90ab0679f5b/libnio.so"}}, "executable": true, "type": "file"}, "lib/amd64/libnpjp2.so": {"downloads": {"lzma": {"sha1": "6fe53b5951ff740e7f2ef7ffe5975af26da06718", "size": 57892, "url": "https://launcher.mojang.com/v1/objects/6fe53b5951ff740e7f2ef7ffe5975af26da06718/libnpjp2.so"}, "raw": {"sha1": "2bb13c53a4280379253475e51216b97eed1d4ce3", "size": 216592, "url": "https://launcher.mojang.com/v1/objects/2bb13c53a4280379253475e51216b97eed1d4ce3/libnpjp2.so"}}, "executable": true, "type": "file"}, "lib/amd64/libnpt.so": {"downloads": {"lzma": {"sha1": "1b170b09a32b1b8b6624fa5d1f94ec60b2bf3876", "size": 5070, "url": "https://launcher.mojang.com/v1/objects/1b170b09a32b1b8b6624fa5d1f94ec60b2bf3876/libnpt.so"}, "raw": {"sha1": "6b1ff6b9b4624f3cc7801f221c82b8046fb76364", "size": 17504, "url": "https://launcher.mojang.com/v1/objects/6b1ff6b9b4624f3cc7801f221c82b8046fb76364/libnpt.so"}}, "executable": true, "type": "file"}, "lib/amd64/libprism_common.so": {"downloads": {"lzma": {"sha1": "f4aca04c90bc7505851c074a08b2c31cae1f94fa", "size": 23315, "url": "https://launcher.mojang.com/v1/objects/f4aca04c90bc7505851c074a08b2c31cae1f94fa/libprism_common.so"}, "raw": {"sha1": "b00866b6ed8646a29a334d46e297267552f27de8", "size": 59008, "url": "https://launcher.mojang.com/v1/objects/b00866b6ed8646a29a334d46e297267552f27de8/libprism_common.so"}}, "executable": true, "type": "file"}, "lib/amd64/libprism_es2.so": {"downloads": {"lzma": {"sha1": "7ff4173c338c7a6f370f231670055737e032da3e", "size": 18416, "url": "https://launcher.mojang.com/v1/objects/7ff4173c338c7a6f370f231670055737e032da3e/libprism_es2.so"}, "raw": {"sha1": "1390a1dc14345e5a948148e59195d62f3a83863f", "size": 63808, "url": "https://launcher.mojang.com/v1/objects/1390a1dc14345e5a948148e59195d62f3a83863f/libprism_es2.so"}}, "executable": true, "type": "file"}, "lib/amd64/libprism_sw.so": {"downloads": {"lzma": {"sha1": "6728e8bf7b214067d715be6fe0325910d63c2468", "size": 29457, "url": "https://launcher.mojang.com/v1/objects/6728e8bf7b214067d715be6fe0325910d63c2468/libprism_sw.so"}, "raw": {"sha1": "7a6c34cb2bbcde411778d1b3f8677c39e32c3ac4", "size": 71608, "url": "https://launcher.mojang.com/v1/objects/7a6c34cb2bbcde411778d1b3f8677c39e32c3ac4/libprism_sw.so"}}, "executable": true, "type": "file"}, "lib/amd64/libresource.so": {"downloads": {"lzma": {"sha1": "1e35e63f1e74915fba620f1febf420b919d49bc5", "size": 2633, "url": "https://launcher.mojang.com/v1/objects/1e35e63f1e74915fba620f1febf420b919d49bc5/libresource.so"}, "raw": {"sha1": "57490353ad0d83ab1930355213dea269795434fe", "size": 13456, "url": "https://launcher.mojang.com/v1/objects/57490353ad0d83ab1930355213dea269795434fe/libresource.so"}}, "executable": true, "type": "file"}, "lib/amd64/libsctp.so": {"downloads": {"lzma": {"sha1": "4340132ed250d7849a016e071be773eaedd33aa8", "size": 9332, "url": "https://launcher.mojang.com/v1/objects/4340132ed250d7849a016e071be773eaedd33aa8/libsctp.so"}, "raw": {"sha1": "4a80e743750f127c0d5a359f5cd60b97e7ee12ae", "size": 29552, "url": "https://launcher.mojang.com/v1/objects/4a80e743750f127c0d5a359f5cd60b97e7ee12ae/libsctp.so"}}, "executable": true, "type": "file"}, "lib/amd64/libsplashscreen.so": {"downloads": {"lzma": {"sha1": "b226c8dbd73a548fc2b042ee6db6cc80e727597c", "size": 190305, "url": "https://launcher.mojang.com/v1/objects/b226c8dbd73a548fc2b042ee6db6cc80e727597c/libsplashscreen.so"}, "raw": {"sha1": "87d6a491f5ba7e6c4d972264a0c9063afea567a2", "size": 441376, "url": "https://launcher.mojang.com/v1/objects/87d6a491f5ba7e6c4d972264a0c9063afea567a2/libsplashscreen.so"}}, "executable": true, "type": "file"}, "lib/amd64/libsunec.so": {"downloads": {"lzma": {"sha1": "6ebba98fab1e3d872d1363235b76497f6f9babdc", "size": 88829, "url": "https://launcher.mojang.com/v1/objects/6ebba98fab1e3d872d1363235b76497f6f9babdc/libsunec.so"}, "raw": {"sha1": "3b262a0a530f6e4e539aed2cd27b4de1d0ed8859", "size": 283368, "url": "https://launcher.mojang.com/v1/objects/3b262a0a530f6e4e539aed2cd27b4de1d0ed8859/libsunec.so"}}, "executable": true, "type": "file"}, "lib/amd64/libt2k.so": {"downloads": {"lzma": {"sha1": "602cb812ef0b350ccf56ce209a260ddbe3743d92", "size": 190720, "url": "https://launcher.mojang.com/v1/objects/602cb812ef0b350ccf56ce209a260ddbe3743d92/libt2k.so"}, "raw": {"sha1": "b072c56df997f61e15e6b5a43b8907b0d25c2043", "size": 504840, "url": "https://launcher.mojang.com/v1/objects/b072c56df997f61e15e6b5a43b8907b0d25c2043/libt2k.so"}}, "executable": true, "type": "file"}, "lib/amd64/libunpack.so": {"downloads": {"lzma": {"sha1": "7107b615e941074f0b14c31c88fb67200aacd37f", "size": 70308, "url": "https://launcher.mojang.com/v1/objects/7107b615e941074f0b14c31c88fb67200aacd37f/libunpack.so"}, "raw": {"sha1": "b05ff862ed87928ed91e80e5604673c5ea710a53", "size": 197712, "url": "https://launcher.mojang.com/v1/objects/b05ff862ed87928ed91e80e5604673c5ea710a53/libunpack.so"}}, "executable": true, "type": "file"}, "lib/amd64/libverify.so": {"downloads": {"lzma": {"sha1": "ecd98efb8c7da441a8c3580e8f5598f3cb4165b1", "size": 21885, "url": "https://launcher.mojang.com/v1/objects/ecd98efb8c7da441a8c3580e8f5598f3cb4165b1/libverify.so"}, "raw": {"sha1": "e2c8d92531c45ab9be69ffb72c87fa12e9e59827", "size": 66112, "url": "https://launcher.mojang.com/v1/objects/e2c8d92531c45ab9be69ffb72c87fa12e9e59827/libverify.so"}}, "executable": true, "type": "file"}, "lib/amd64/libzip.so": {"downloads": {"lzma": {"sha1": "7c562342e3f7b138dc978495447e3e6a96c2cf45", "size": 54876, "url": "https://launcher.mojang.com/v1/objects/7c562342e3f7b138dc978495447e3e6a96c2cf45/libzip.so"}, "raw": {"sha1": "5f4bf35a5c3e8f8c650e891d1031589b8ab6d77f", "size": 127016, "url": "https://launcher.mojang.com/v1/objects/5f4bf35a5c3e8f8c650e891d1031589b8ab6d77f/libzip.so"}}, "executable": true, "type": "file"}, "lib/amd64/server": {"type": "directory"}, "lib/amd64/server/Xusage.txt": {"downloads": {"lzma": {"sha1": "acb2da24a4c765887df83985e4c26d6be302a0a3", "size": 629, "url": "https://launcher.mojang.com/v1/objects/acb2da24a4c765887df83985e4c26d6be302a0a3/Xusage.txt"}, "raw": {"sha1": "6983727eafe140f9dd793c78aa6f3e007438243a", "size": 1423, "url": "https://launcher.mojang.com/v1/objects/6983727eafe140f9dd793c78aa6f3e007438243a/Xusage.txt"}}, "executable": false, "type": "file"}, "lib/amd64/server/libjsig.so": {"target": "../libjsig.so", "type": "link"}, "lib/amd64/server/libjvm.so": {"downloads": {"lzma": {"sha1": "d5c6f3338aaa6712f79d680ac8c3e31beebaa886", "size": 4154311, "url": "https://launcher.mojang.com/v1/objects/d5c6f3338aaa6712f79d680ac8c3e31beebaa886/libjvm.so"}, "raw": {"sha1": "23a98e1eb505cc3fb91bc0cb2adb71ab9270e9ca", "size": 17045016, "url": "https://launcher.mojang.com/v1/objects/23a98e1eb505cc3fb91bc0cb2adb71ab9270e9ca/libjvm.so"}}, "executable": true, "type": "file"}, "lib/applet": {"type": "directory"}, "lib/calendars.properties": {"downloads": {"lzma": {"sha1": "4a757c23f2942bd802a4f80235131146d9267750", "size": 558, "url": "https://launcher.mojang.com/v1/objects/4a757c23f2942bd802a4f80235131146d9267750/calendars.properties"}, "raw": {"sha1": "42ebb0988124433b8f2a6e5d9a74ed41240bcfc6", "size": 1378, "url": "https://launcher.mojang.com/v1/objects/42ebb0988124433b8f2a6e5d9a74ed41240bcfc6/calendars.properties"}}, "executable": false, "type": "file"}, "lib/charsets.jar": {"downloads": {"lzma": {"sha1": "2bf44143b2ad9d7d55045a4de4a562330c194dc0", "size": 412367, "url": "https://launcher.mojang.com/v1/objects/2bf44143b2ad9d7d55045a4de4a562330c194dc0/charsets.jar"}, "raw": {"sha1": "d73ab9f8de255a7e112ddd13622bf7f6b18c8447", "size": 3135615, "url": "https://launcher.mojang.com/v1/objects/d73ab9f8de255a7e112ddd13622bf7f6b18c8447/charsets.jar"}}, "executable": false, "type": "file"}, "lib/classlist": {"downloads": {"lzma": {"sha1": "14e7c73d21b8513b0aff8d86e5cb34c52021fbca", "size": 15024, "url": "https://launcher.mojang.com/v1/objects/14e7c73d21b8513b0aff8d86e5cb34c52021fbca/classlist"}, "raw": {"sha1": "9c0404b63c87e2fed35e3a6cd137d6cf876c42bd", "size": 84311, "url": "https://launcher.mojang.com/v1/objects/9c0404b63c87e2fed35e3a6cd137d6cf876c42bd/classlist"}}, "executable": false, "type": "file"}, "lib/cmm": {"type": "directory"}, "lib/cmm/CIEXYZ.pf": {"downloads": {"lzma": {"sha1": "fcc5ca2fd3f45cac3434b480fa3ce00007e96529", "size": 8964, "url": "https://launcher.mojang.com/v1/objects/fcc5ca2fd3f45cac3434b480fa3ce00007e96529/CIEXYZ.pf"}, "raw": {"sha1": "b7779924c70554647b87c2a86159ca7781e929f8", "size": 51236, "url": "https://launcher.mojang.com/v1/objects/b7779924c70554647b87c2a86159ca7781e929f8/CIEXYZ.pf"}}, "executable": false, "type": "file"}, "lib/cmm/GRAY.pf": {"downloads": {"lzma": {"sha1": "5388ccfe67d3131d6d02143d8e8895003ab14ff6", "size": 299, "url": "https://launcher.mojang.com/v1/objects/5388ccfe67d3131d6d02143d8e8895003ab14ff6/GRAY.pf"}, "raw": {"sha1": "27f93961d66b8230d0cdb8b166bc8b4153d5bc2d", "size": 632, "url": "https://launcher.mojang.com/v1/objects/27f93961d66b8230d0cdb8b166bc8b4153d5bc2d/GRAY.pf"}}, "executable": false, "type": "file"}, "lib/cmm/LINEAR_RGB.pf": {"downloads": {"lzma": {"sha1": "2bd90f09c8deb64b1729d6b8173c78f9e9cab27b", "size": 678, "url": "https://launcher.mojang.com/v1/objects/2bd90f09c8deb64b1729d6b8173c78f9e9cab27b/LINEAR_RGB.pf"}, "raw": {"sha1": "7913274c2f73bafcf888f09ff60990b100214ede", "size": 1044, "url": "https://launcher.mojang.com/v1/objects/7913274c2f73bafcf888f09ff60990b100214ede/LINEAR_RGB.pf"}}, "executable": false, "type": "file"}, "lib/cmm/PYCC.pf": {"downloads": {"lzma": {"sha1": "dbb2197ecff3fcdd142e9006490c8cb5c8d19af8", "size": 171521, "url": "https://launcher.mojang.com/v1/objects/dbb2197ecff3fcdd142e9006490c8cb5c8d19af8/PYCC.pf"}, "raw": {"sha1": "4f7eed05b8f0eea7bcdc8f8f7aaeb1925ce7b144", "size": 274474, "url": "https://launcher.mojang.com/v1/objects/4f7eed05b8f0eea7bcdc8f8f7aaeb1925ce7b144/PYCC.pf"}}, "executable": false, "type": "file"}, "lib/cmm/sRGB.pf": {"downloads": {"raw": {"sha1": "9eaea0911d89d63e39e95f2e2116eaec7e0bb91e", "size": 3144, "url": "https://launcher.mojang.com/v1/objects/9eaea0911d89d63e39e95f2e2116eaec7e0bb91e/sRGB.pf"}}, "executable": false, "type": "file"}, "lib/content-types.properties": {"downloads": {"lzma": {"sha1": "43a23d9a6c637c128b14cfa3feced93cbcf85b1a", "size": 1617, "url": "https://launcher.mojang.com/v1/objects/43a23d9a6c637c128b14cfa3feced93cbcf85b1a/content-types.properties"}, "raw": {"sha1": "b21698017c4a2866b5fabe59681b7592e72c83b1", "size": 5916, "url": "https://launcher.mojang.com/v1/objects/b21698017c4a2866b5fabe59681b7592e72c83b1/content-types.properties"}}, "executable": false, "type": "file"}, "lib/currency.data": {"downloads": {"lzma": {"sha1": "451b3f166ea34ef2aefbb01606ea5adcc0d65b42", "size": 1184, "url": "https://launcher.mojang.com/v1/objects/451b3f166ea34ef2aefbb01606ea5adcc0d65b42/currency.data"}, "raw": {"sha1": "bf524381a7a9b9d5bbab48069c583d2936e367a1", "size": 4134, "url": "https://launcher.mojang.com/v1/objects/bf524381a7a9b9d5bbab48069c583d2936e367a1/currency.data"}}, "executable": false, "type": "file"}, "lib/deploy": {"type": "directory"}, "lib/deploy.jar": {"downloads": {"raw": {"sha1": "fbe1de8fcd9a3d482c59414dce9311e4194766c9", "size": 2255881, "url": "https://launcher.mojang.com/v1/objects/fbe1de8fcd9a3d482c59414dce9311e4194766c9/deploy.jar"}}, "executable": false, "type": "file"}, "lib/deploy/MixedCodeMainDialog.ui": {"downloads": {"lzma": {"sha1": "7d812964343d1e978442f5c847c709784fc18fc0", "size": 683, "url": "https://launcher.mojang.com/v1/objects/7d812964343d1e978442f5c847c709784fc18fc0/MixedCodeMainDialog.ui"}, "raw": {"sha1": "c9b1af1c229e54b2d8a3d642d4f0bb31dc15be79", "size": 4507, "url": "https://launcher.mojang.com/v1/objects/c9b1af1c229e54b2d8a3d642d4f0bb31dc15be79/MixedCodeMainDialog.ui"}}, "executable": false, "type": "file"}, "lib/deploy/MixedCodeMainDialogJs.ui": {"downloads": {"lzma": {"sha1": "54fb58dbcc59e35e0ae896d0e266ae0c5bcf85c2", "size": 792, "url": "https://launcher.mojang.com/v1/objects/54fb58dbcc59e35e0ae896d0e266ae0c5bcf85c2/MixedCodeMainDialogJs.ui"}, "raw": {"sha1": "ad6337fb6d46750e14c12b439a5856f4b6864d0d", "size": 6110, "url": "https://launcher.mojang.com/v1/objects/ad6337fb6d46750e14c12b439a5856f4b6864d0d/MixedCodeMainDialogJs.ui"}}, "executable": false, "type": "file"}, "lib/deploy/cautionshield.icns": {"downloads": {"lzma": {"sha1": "7cea751dc168605054ec38ce8bfa71812be405c1", "size": 2333, "url": "https://launcher.mojang.com/v1/objects/7cea751dc168605054ec38ce8bfa71812be405c1/cautionshield.icns"}, "raw": {"sha1": "1de7ed5d5fc75aa1bcede088c655bee3bde64c38", "size": 3588, "url": "https://launcher.mojang.com/v1/objects/1de7ed5d5fc75aa1bcede088c655bee3bde64c38/cautionshield.icns"}}, "executable": false, "type": "file"}, "lib/deploy/ffjcext.zip": {"downloads": {"lzma": {"sha1": "80bcb9b3794f69d87dba93e90230f288e651e798", "size": 1809, "url": "https://launcher.mojang.com/v1/objects/80bcb9b3794f69d87dba93e90230f288e651e798/ffjcext.zip"}, "raw": {"sha1": "76d051ca7d3666ff25ea8eb9957a05574a45287f", "size": 13454, "url": "https://launcher.mojang.com/v1/objects/76d051ca7d3666ff25ea8eb9957a05574a45287f/ffjcext.zip"}}, "executable": false, "type": "file"}, "lib/deploy/java-icon.ico": {"downloads": {"lzma": {"sha1": "2a24f0207d7ab5976a8b0d92b4b381d49e895c9d", "size": 8468, "url": "https://launcher.mojang.com/v1/objects/2a24f0207d7ab5976a8b0d92b4b381d49e895c9d/java-icon.ico"}, "raw": {"sha1": "2997ceb26ff49a7d7c5e7a2405b5fb50b62c7914", "size": 29926, "url": "https://launcher.mojang.com/v1/objects/2997ceb26ff49a7d7c5e7a2405b5fb50b62c7914/java-icon.ico"}}, "executable": false, "type": "file"}, "lib/deploy/messages.properties": {"downloads": {"lzma": {"sha1": "c1e16f80dc0b1f1a591cecf3cbab4ba5e47492f4", "size": 1225, "url": "https://launcher.mojang.com/v1/objects/c1e16f80dc0b1f1a591cecf3cbab4ba5e47492f4/messages.properties"}, "raw": {"sha1": "dc52841c708e3c1eb2a044088a43396d1291bb5e", "size": 2860, "url": "https://launcher.mojang.com/v1/objects/dc52841c708e3c1eb2a044088a43396d1291bb5e/messages.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_de.properties": {"downloads": {"lzma": {"sha1": "42b42e6e1d2cb2d781f2226bde612ce519b29bc8", "size": 1394, "url": "https://launcher.mojang.com/v1/objects/42b42e6e1d2cb2d781f2226bde612ce519b29bc8/messages_de.properties"}, "raw": {"sha1": "d989fe1b8f7904888d5102294ebefd28d932ecdb", "size": 3306, "url": "https://launcher.mojang.com/v1/objects/d989fe1b8f7904888d5102294ebefd28d932ecdb/messages_de.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_es.properties": {"downloads": {"lzma": {"sha1": "c4a653e9802ca982e892b45d88c1e259c09e8c8e", "size": 1404, "url": "https://launcher.mojang.com/v1/objects/c4a653e9802ca982e892b45d88c1e259c09e8c8e/messages_es.properties"}, "raw": {"sha1": "1b0334b79db481c3a59be6915d5118d760c97baa", "size": 3600, "url": "https://launcher.mojang.com/v1/objects/1b0334b79db481c3a59be6915d5118d760c97baa/messages_es.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_fr.properties": {"downloads": {"lzma": {"sha1": "2d8dee07e3f5aab7318a22e169810b216ac44f97", "size": 1401, "url": "https://launcher.mojang.com/v1/objects/2d8dee07e3f5aab7318a22e169810b216ac44f97/messages_fr.properties"}, "raw": {"sha1": "69bd2d03c2064f8679de5b4e430ea61b567c69c5", "size": 3409, "url": "https://launcher.mojang.com/v1/objects/69bd2d03c2064f8679de5b4e430ea61b567c69c5/messages_fr.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_it.properties": {"downloads": {"lzma": {"sha1": "7c28cdd8d9e34355ba0fc03004c4f64749cae57e", "size": 1375, "url": "https://launcher.mojang.com/v1/objects/7c28cdd8d9e34355ba0fc03004c4f64749cae57e/messages_it.properties"}, "raw": {"sha1": "dbe49949308f28540a42ae6cd2ad58afbf615592", "size": 3223, "url": "https://launcher.mojang.com/v1/objects/dbe49949308f28540a42ae6cd2ad58afbf615592/messages_it.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_ja.properties": {"downloads": {"lzma": {"sha1": "9a6a4c086e48b9e615b72b6bbebb3c724d178ff4", "size": 1680, "url": "https://launcher.mojang.com/v1/objects/9a6a4c086e48b9e615b72b6bbebb3c724d178ff4/messages_ja.properties"}, "raw": {"sha1": "751170a7cdefcb1226604ac3f8196e06a04fd7ac", "size": 6349, "url": "https://launcher.mojang.com/v1/objects/751170a7cdefcb1226604ac3f8196e06a04fd7ac/messages_ja.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_ko.properties": {"downloads": {"lzma": {"sha1": "0c57c2ebfa0830f816657a384898487fc492efac", "size": 1645, "url": "https://launcher.mojang.com/v1/objects/0c57c2ebfa0830f816657a384898487fc492efac/messages_ko.properties"}, "raw": {"sha1": "bf9e055b5ab138ad6d49769e2b7630b7938848d6", "size": 5712, "url": "https://launcher.mojang.com/v1/objects/bf9e055b5ab138ad6d49769e2b7630b7938848d6/messages_ko.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_pt_BR.properties": {"downloads": {"lzma": {"sha1": "f8364dba0aa0a7e445a1a8d0e7ad66b996f70063", "size": 1388, "url": "https://launcher.mojang.com/v1/objects/f8364dba0aa0a7e445a1a8d0e7ad66b996f70063/messages_pt_BR.properties"}, "raw": {"sha1": "24e4951743521ab9a11381c77bd0cdb1ed30f5b5", "size": 3285, "url": "https://launcher.mojang.com/v1/objects/24e4951743521ab9a11381c77bd0cdb1ed30f5b5/messages_pt_BR.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_sv.properties": {"downloads": {"lzma": {"sha1": "65e5897d552258141aacf02f087c7c9c33ad0727", "size": 1355, "url": "https://launcher.mojang.com/v1/objects/65e5897d552258141aacf02f087c7c9c33ad0727/messages_sv.properties"}, "raw": {"sha1": "bb5a4aa0ba499f6b1916a83e3c7922a4583b4adb", "size": 3384, "url": "https://launcher.mojang.com/v1/objects/bb5a4aa0ba499f6b1916a83e3c7922a4583b4adb/messages_sv.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_zh_CN.properties": {"downloads": {"lzma": {"sha1": "de7d39a6e6748e9f47e842c9da90f515921c222c", "size": 1506, "url": "https://launcher.mojang.com/v1/objects/de7d39a6e6748e9f47e842c9da90f515921c222c/messages_zh_CN.properties"}, "raw": {"sha1": "1c2b96673dddd3596890ef4fc22017d484a1f652", "size": 4072, "url": "https://launcher.mojang.com/v1/objects/1c2b96673dddd3596890ef4fc22017d484a1f652/messages_zh_CN.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_zh_HK.properties": {"downloads": {"lzma": {"sha1": "e8d0e3a63caa2535a4f361033941f34dcc170a7e", "size": 1529, "url": "https://launcher.mojang.com/v1/objects/e8d0e3a63caa2535a4f361033941f34dcc170a7e/messages_zh_TW.properties"}, "raw": {"sha1": "37a57aad121c14c25e149206179728fa62203bf0", "size": 3752, "url": "https://launcher.mojang.com/v1/objects/37a57aad121c14c25e149206179728fa62203bf0/messages_zh_TW.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_zh_TW.properties": {"downloads": {"lzma": {"sha1": "e8d0e3a63caa2535a4f361033941f34dcc170a7e", "size": 1529, "url": "https://launcher.mojang.com/v1/objects/e8d0e3a63caa2535a4f361033941f34dcc170a7e/messages_zh_TW.properties"}, "raw": {"sha1": "37a57aad121c14c25e149206179728fa62203bf0", "size": 3752, "url": "https://launcher.mojang.com/v1/objects/37a57aad121c14c25e149206179728fa62203bf0/messages_zh_TW.properties"}}, "executable": false, "type": "file"}, "lib/deploy/mixcode_s.png": {"downloads": {"raw": {"sha1": "4604e9f265eec97bccd0151c3a81afa9e69499e5", "size": 3115, "url": "https://launcher.mojang.com/v1/objects/4604e9f265eec97bccd0151c3a81afa9e69499e5/mixcode_s.png"}}, "executable": false, "type": "file"}, "lib/deploy/splash.gif": {"downloads": {"raw": {"sha1": "20e7aec75f6d036d504277542e507eb7dc24aae8", "size": 8590, "url": "https://launcher.mojang.com/v1/objects/20e7aec75f6d036d504277542e507eb7dc24aae8/splash.gif"}}, "executable": false, "type": "file"}, "lib/deploy/splash@2x.gif": {"downloads": {"raw": {"sha1": "0ae4a5bda2a6d628fac51462390b503c99509fdc", "size": 15276, "url": "https://launcher.mojang.com/v1/objects/0ae4a5bda2a6d628fac51462390b503c99509fdc/splash2x.gif"}}, "executable": false, "type": "file"}, "lib/deploy/splash_11-lic.gif": {"downloads": {"raw": {"sha1": "8def364e07f40142822df84b5bb4f50846cb5e4e", "size": 7805, "url": "https://launcher.mojang.com/v1/objects/8def364e07f40142822df84b5bb4f50846cb5e4e/splash_11-lic.gif"}}, "executable": false, "type": "file"}, "lib/deploy/splash_11@2x-lic.gif": {"downloads": {"raw": {"sha1": "d2bff9bbf7920ca743b81a0ee23b0719b4d057ca", "size": 12250, "url": "https://launcher.mojang.com/v1/objects/d2bff9bbf7920ca743b81a0ee23b0719b4d057ca/splash_11%402x-lic.gif"}}, "executable": false, "type": "file"}, "lib/desktop": {"type": "directory"}, "lib/desktop/applications": {"type": "directory"}, "lib/desktop/applications/sun-java.desktop": {"downloads": {"lzma": {"sha1": "109d1cdf165f38da92da70b403ca940192a7a9a8", "size": 536, "url": "https://launcher.mojang.com/v1/objects/109d1cdf165f38da92da70b403ca940192a7a9a8/sun-java.desktop"}, "raw": {"sha1": "d346dfe90505603ce5aff5a3c6c2e4a23d5bd990", "size": 777, "url": "https://launcher.mojang.com/v1/objects/d346dfe90505603ce5aff5a3c6c2e4a23d5bd990/sun-java.desktop"}}, "executable": false, "type": "file"}, "lib/desktop/applications/sun-javaws.desktop": {"downloads": {"lzma": {"sha1": "5e1815e7f83515881e6998584dc6bb02c5bef09a", "size": 451, "url": "https://launcher.mojang.com/v1/objects/5e1815e7f83515881e6998584dc6bb02c5bef09a/sun-javaws.desktop"}, "raw": {"sha1": "50ce8e519b836e0f53d58ce1a359d98b6cafdda6", "size": 619, "url": "https://launcher.mojang.com/v1/objects/50ce8e519b836e0f53d58ce1a359d98b6cafdda6/sun-javaws.desktop"}}, "executable": false, "type": "file"}, "lib/desktop/applications/sun_java.desktop": {"downloads": {"lzma": {"sha1": "49ab0ccb54c3be68281d05055bc56a88b1281d3c", "size": 447, "url": "https://launcher.mojang.com/v1/objects/49ab0ccb54c3be68281d05055bc56a88b1281d3c/sun_java.desktop"}, "raw": {"sha1": "79120ee8160ad6f3c9b90c2641fb7edf3af96b5d", "size": 624, "url": "https://launcher.mojang.com/v1/objects/79120ee8160ad6f3c9b90c2641fb7edf3af96b5d/sun_java.desktop"}}, "executable": false, "type": "file"}, "lib/desktop/icons": {"type": "directory"}, "lib/desktop/icons/HighContrast": {"type": "directory"}, "lib/desktop/icons/HighContrast/16x16": {"type": "directory"}, "lib/desktop/icons/HighContrast/16x16/apps": {"type": "directory"}, "lib/desktop/icons/HighContrast/16x16/apps/sun-java.png": {"downloads": {"raw": {"sha1": "366e7a48e9e4fb92eaeabbcaeb4626122a66cecb", "size": 417, "url": "https://launcher.mojang.com/v1/objects/366e7a48e9e4fb92eaeabbcaeb4626122a66cecb/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/16x16/apps/sun-javaws.png": {"downloads": {"raw": {"sha1": "366e7a48e9e4fb92eaeabbcaeb4626122a66cecb", "size": 417, "url": "https://launcher.mojang.com/v1/objects/366e7a48e9e4fb92eaeabbcaeb4626122a66cecb/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/16x16/apps/sun-jcontrol.png": {"downloads": {"raw": {"sha1": "366e7a48e9e4fb92eaeabbcaeb4626122a66cecb", "size": 417, "url": "https://launcher.mojang.com/v1/objects/366e7a48e9e4fb92eaeabbcaeb4626122a66cecb/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/16x16/mimetypes": {"type": "directory"}, "lib/desktop/icons/HighContrast/16x16/mimetypes/gnome-mime-application-x-java-archive.png": {"downloads": {"raw": {"sha1": "629c48907368ecf32d2395b6459c367f79d84689", "size": 464, "url": "https://launcher.mojang.com/v1/objects/629c48907368ecf32d2395b6459c367f79d84689/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/16x16/mimetypes/gnome-mime-application-x-java-jnlp-file.png": {"downloads": {"raw": {"sha1": "629c48907368ecf32d2395b6459c367f79d84689", "size": 464, "url": "https://launcher.mojang.com/v1/objects/629c48907368ecf32d2395b6459c367f79d84689/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/16x16/mimetypes/gnome-mime-text-x-java.png": {"downloads": {"raw": {"sha1": "629c48907368ecf32d2395b6459c367f79d84689", "size": 464, "url": "https://launcher.mojang.com/v1/objects/629c48907368ecf32d2395b6459c367f79d84689/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/48x48": {"type": "directory"}, "lib/desktop/icons/HighContrast/48x48/apps": {"type": "directory"}, "lib/desktop/icons/HighContrast/48x48/apps/sun-java.png": {"downloads": {"raw": {"sha1": "8373482d072684e09830dbdb97a76ea264c9f4e9", "size": 3451, "url": "https://launcher.mojang.com/v1/objects/8373482d072684e09830dbdb97a76ea264c9f4e9/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/48x48/apps/sun-javaws.png": {"downloads": {"raw": {"sha1": "8373482d072684e09830dbdb97a76ea264c9f4e9", "size": 3451, "url": "https://launcher.mojang.com/v1/objects/8373482d072684e09830dbdb97a76ea264c9f4e9/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/48x48/apps/sun-jcontrol.png": {"downloads": {"raw": {"sha1": "8373482d072684e09830dbdb97a76ea264c9f4e9", "size": 3451, "url": "https://launcher.mojang.com/v1/objects/8373482d072684e09830dbdb97a76ea264c9f4e9/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/48x48/mimetypes": {"type": "directory"}, "lib/desktop/icons/HighContrast/48x48/mimetypes/gnome-mime-application-x-java-archive.png": {"downloads": {"raw": {"sha1": "56a4996519f8f3541eba7b7a7a69bcdcd8ed0410", "size": 2088, "url": "https://launcher.mojang.com/v1/objects/56a4996519f8f3541eba7b7a7a69bcdcd8ed0410/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/48x48/mimetypes/gnome-mime-application-x-java-jnlp-file.png": {"downloads": {"raw": {"sha1": "56a4996519f8f3541eba7b7a7a69bcdcd8ed0410", "size": 2088, "url": "https://launcher.mojang.com/v1/objects/56a4996519f8f3541eba7b7a7a69bcdcd8ed0410/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/48x48/mimetypes/gnome-mime-text-x-java.png": {"downloads": {"raw": {"sha1": "56a4996519f8f3541eba7b7a7a69bcdcd8ed0410", "size": 2088, "url": "https://launcher.mojang.com/v1/objects/56a4996519f8f3541eba7b7a7a69bcdcd8ed0410/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse": {"type": "directory"}, "lib/desktop/icons/HighContrastInverse/16x16": {"type": "directory"}, "lib/desktop/icons/HighContrastInverse/16x16/apps": {"type": "directory"}, "lib/desktop/icons/HighContrastInverse/16x16/apps/sun-java.png": {"downloads": {"raw": {"sha1": "bf0995acb94aa794e73c5b971282ff13ffe42793", "size": 402, "url": "https://launcher.mojang.com/v1/objects/bf0995acb94aa794e73c5b971282ff13ffe42793/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/16x16/apps/sun-javaws.png": {"downloads": {"raw": {"sha1": "bf0995acb94aa794e73c5b971282ff13ffe42793", "size": 402, "url": "https://launcher.mojang.com/v1/objects/bf0995acb94aa794e73c5b971282ff13ffe42793/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/16x16/apps/sun-jcontrol.png": {"downloads": {"raw": {"sha1": "bf0995acb94aa794e73c5b971282ff13ffe42793", "size": 402, "url": "https://launcher.mojang.com/v1/objects/bf0995acb94aa794e73c5b971282ff13ffe42793/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/16x16/mimetypes": {"type": "directory"}, "lib/desktop/icons/HighContrastInverse/16x16/mimetypes/gnome-mime-application-x-java-archive.png": {"downloads": {"raw": {"sha1": "1477eceda25e162fcda2e69ee3906091973d8344", "size": 473, "url": "https://launcher.mojang.com/v1/objects/1477eceda25e162fcda2e69ee3906091973d8344/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/16x16/mimetypes/gnome-mime-application-x-java-jnlp-file.png": {"downloads": {"raw": {"sha1": "1477eceda25e162fcda2e69ee3906091973d8344", "size": 473, "url": "https://launcher.mojang.com/v1/objects/1477eceda25e162fcda2e69ee3906091973d8344/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/16x16/mimetypes/gnome-mime-text-x-java.png": {"downloads": {"raw": {"sha1": "1477eceda25e162fcda2e69ee3906091973d8344", "size": 473, "url": "https://launcher.mojang.com/v1/objects/1477eceda25e162fcda2e69ee3906091973d8344/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/48x48": {"type": "directory"}, "lib/desktop/icons/HighContrastInverse/48x48/apps": {"type": "directory"}, "lib/desktop/icons/HighContrastInverse/48x48/apps/sun-java.png": {"downloads": {"raw": {"sha1": "413da160dd9899b95f53d4cc11f5ee0550cc6585", "size": 3410, "url": "https://launcher.mojang.com/v1/objects/413da160dd9899b95f53d4cc11f5ee0550cc6585/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/48x48/apps/sun-javaws.png": {"downloads": {"raw": {"sha1": "413da160dd9899b95f53d4cc11f5ee0550cc6585", "size": 3410, "url": "https://launcher.mojang.com/v1/objects/413da160dd9899b95f53d4cc11f5ee0550cc6585/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/48x48/apps/sun-jcontrol.png": {"downloads": {"raw": {"sha1": "413da160dd9899b95f53d4cc11f5ee0550cc6585", "size": 3410, "url": "https://launcher.mojang.com/v1/objects/413da160dd9899b95f53d4cc11f5ee0550cc6585/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/48x48/mimetypes": {"type": "directory"}, "lib/desktop/icons/HighContrastInverse/48x48/mimetypes/gnome-mime-application-x-java-archive.png": {"downloads": {"raw": {"sha1": "d66e04dfa25c196bec2e201547325b79846ab674", "size": 2085, "url": "https://launcher.mojang.com/v1/objects/d66e04dfa25c196bec2e201547325b79846ab674/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/48x48/mimetypes/gnome-mime-application-x-java-jnlp-file.png": {"downloads": {"raw": {"sha1": "d66e04dfa25c196bec2e201547325b79846ab674", "size": 2085, "url": "https://launcher.mojang.com/v1/objects/d66e04dfa25c196bec2e201547325b79846ab674/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/48x48/mimetypes/gnome-mime-text-x-java.png": {"downloads": {"raw": {"sha1": "d66e04dfa25c196bec2e201547325b79846ab674", "size": 2085, "url": "https://launcher.mojang.com/v1/objects/d66e04dfa25c196bec2e201547325b79846ab674/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast": {"type": "directory"}, "lib/desktop/icons/LowContrast/16x16": {"type": "directory"}, "lib/desktop/icons/LowContrast/16x16/apps": {"type": "directory"}, "lib/desktop/icons/LowContrast/16x16/apps/sun-java.png": {"downloads": {"raw": {"sha1": "f93b7cf0a6d27d664a7f09dab6933b2768536f52", "size": 519, "url": "https://launcher.mojang.com/v1/objects/f93b7cf0a6d27d664a7f09dab6933b2768536f52/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/16x16/apps/sun-javaws.png": {"downloads": {"raw": {"sha1": "f93b7cf0a6d27d664a7f09dab6933b2768536f52", "size": 519, "url": "https://launcher.mojang.com/v1/objects/f93b7cf0a6d27d664a7f09dab6933b2768536f52/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/16x16/apps/sun-jcontrol.png": {"downloads": {"raw": {"sha1": "f93b7cf0a6d27d664a7f09dab6933b2768536f52", "size": 519, "url": "https://launcher.mojang.com/v1/objects/f93b7cf0a6d27d664a7f09dab6933b2768536f52/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/16x16/mimetypes": {"type": "directory"}, "lib/desktop/icons/LowContrast/16x16/mimetypes/gnome-mime-application-x-java-archive.png": {"downloads": {"raw": {"sha1": "0aa1605877280b88de1f1cc3e7e4bdbeed968a73", "size": 525, "url": "https://launcher.mojang.com/v1/objects/0aa1605877280b88de1f1cc3e7e4bdbeed968a73/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/16x16/mimetypes/gnome-mime-application-x-java-jnlp-file.png": {"downloads": {"raw": {"sha1": "0aa1605877280b88de1f1cc3e7e4bdbeed968a73", "size": 525, "url": "https://launcher.mojang.com/v1/objects/0aa1605877280b88de1f1cc3e7e4bdbeed968a73/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/16x16/mimetypes/gnome-mime-text-x-java.png": {"downloads": {"raw": {"sha1": "0aa1605877280b88de1f1cc3e7e4bdbeed968a73", "size": 525, "url": "https://launcher.mojang.com/v1/objects/0aa1605877280b88de1f1cc3e7e4bdbeed968a73/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/48x48": {"type": "directory"}, "lib/desktop/icons/LowContrast/48x48/apps": {"type": "directory"}, "lib/desktop/icons/LowContrast/48x48/apps/sun-java.png": {"downloads": {"raw": {"sha1": "1fcf4fd6da61873b5f21b39412da26509734b7cc", "size": 1507, "url": "https://launcher.mojang.com/v1/objects/1fcf4fd6da61873b5f21b39412da26509734b7cc/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/48x48/apps/sun-javaws.png": {"downloads": {"raw": {"sha1": "1fcf4fd6da61873b5f21b39412da26509734b7cc", "size": 1507, "url": "https://launcher.mojang.com/v1/objects/1fcf4fd6da61873b5f21b39412da26509734b7cc/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/48x48/apps/sun-jcontrol.png": {"downloads": {"raw": {"sha1": "1fcf4fd6da61873b5f21b39412da26509734b7cc", "size": 1507, "url": "https://launcher.mojang.com/v1/objects/1fcf4fd6da61873b5f21b39412da26509734b7cc/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/48x48/mimetypes": {"type": "directory"}, "lib/desktop/icons/LowContrast/48x48/mimetypes/gnome-mime-application-x-java-archive.png": {"downloads": {"raw": {"sha1": "e36636b1c04dc283c18adf669b892d54b15d3ee6", "size": 1948, "url": "https://launcher.mojang.com/v1/objects/e36636b1c04dc283c18adf669b892d54b15d3ee6/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/48x48/mimetypes/gnome-mime-application-x-java-jnlp-file.png": {"downloads": {"raw": {"sha1": "e36636b1c04dc283c18adf669b892d54b15d3ee6", "size": 1948, "url": "https://launcher.mojang.com/v1/objects/e36636b1c04dc283c18adf669b892d54b15d3ee6/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/48x48/mimetypes/gnome-mime-text-x-java.png": {"downloads": {"raw": {"sha1": "e36636b1c04dc283c18adf669b892d54b15d3ee6", "size": 1948, "url": "https://launcher.mojang.com/v1/objects/e36636b1c04dc283c18adf669b892d54b15d3ee6/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor": {"type": "directory"}, "lib/desktop/icons/hicolor/16x16": {"type": "directory"}, "lib/desktop/icons/hicolor/16x16/apps": {"type": "directory"}, "lib/desktop/icons/hicolor/16x16/apps/sun-java.png": {"downloads": {"raw": {"sha1": "e91d05bfe9b889bf8a227908b597cab4630da8f2", "size": 383, "url": "https://launcher.mojang.com/v1/objects/e91d05bfe9b889bf8a227908b597cab4630da8f2/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/16x16/apps/sun-javaws.png": {"downloads": {"raw": {"sha1": "e91d05bfe9b889bf8a227908b597cab4630da8f2", "size": 383, "url": "https://launcher.mojang.com/v1/objects/e91d05bfe9b889bf8a227908b597cab4630da8f2/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/16x16/apps/sun-jcontrol.png": {"downloads": {"raw": {"sha1": "e91d05bfe9b889bf8a227908b597cab4630da8f2", "size": 383, "url": "https://launcher.mojang.com/v1/objects/e91d05bfe9b889bf8a227908b597cab4630da8f2/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/16x16/mimetypes": {"type": "directory"}, "lib/desktop/icons/hicolor/16x16/mimetypes/gnome-mime-application-x-java-archive.png": {"downloads": {"raw": {"sha1": "d2f6abe8e498aeecb334fb43f63001d34dbf6ea5", "size": 783, "url": "https://launcher.mojang.com/v1/objects/d2f6abe8e498aeecb334fb43f63001d34dbf6ea5/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/16x16/mimetypes/gnome-mime-application-x-java-jnlp-file.png": {"downloads": {"raw": {"sha1": "d2f6abe8e498aeecb334fb43f63001d34dbf6ea5", "size": 783, "url": "https://launcher.mojang.com/v1/objects/d2f6abe8e498aeecb334fb43f63001d34dbf6ea5/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/16x16/mimetypes/gnome-mime-text-x-java.png": {"downloads": {"raw": {"sha1": "d2f6abe8e498aeecb334fb43f63001d34dbf6ea5", "size": 783, "url": "https://launcher.mojang.com/v1/objects/d2f6abe8e498aeecb334fb43f63001d34dbf6ea5/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/48x48": {"type": "directory"}, "lib/desktop/icons/hicolor/48x48/apps": {"type": "directory"}, "lib/desktop/icons/hicolor/48x48/apps/sun-java.png": {"downloads": {"raw": {"sha1": "6c90a38eaada9c32a678a282be18ec5b43a84264", "size": 1439, "url": "https://launcher.mojang.com/v1/objects/6c90a38eaada9c32a678a282be18ec5b43a84264/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/48x48/apps/sun-javaws.png": {"downloads": {"raw": {"sha1": "6c90a38eaada9c32a678a282be18ec5b43a84264", "size": 1439, "url": "https://launcher.mojang.com/v1/objects/6c90a38eaada9c32a678a282be18ec5b43a84264/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/48x48/apps/sun-jcontrol.png": {"downloads": {"raw": {"sha1": "6c90a38eaada9c32a678a282be18ec5b43a84264", "size": 1439, "url": "https://launcher.mojang.com/v1/objects/6c90a38eaada9c32a678a282be18ec5b43a84264/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/48x48/mimetypes": {"type": "directory"}, "lib/desktop/icons/hicolor/48x48/mimetypes/gnome-mime-application-x-java-archive.png": {"downloads": {"raw": {"sha1": "4d5e6e0c41d1076bc86f3ab157c88a41a5716997", "size": 3202, "url": "https://launcher.mojang.com/v1/objects/4d5e6e0c41d1076bc86f3ab157c88a41a5716997/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/48x48/mimetypes/gnome-mime-application-x-java-jnlp-file.png": {"downloads": {"raw": {"sha1": "4d5e6e0c41d1076bc86f3ab157c88a41a5716997", "size": 3202, "url": "https://launcher.mojang.com/v1/objects/4d5e6e0c41d1076bc86f3ab157c88a41a5716997/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/48x48/mimetypes/gnome-mime-text-x-java.png": {"downloads": {"raw": {"sha1": "4d5e6e0c41d1076bc86f3ab157c88a41a5716997", "size": 3202, "url": "https://launcher.mojang.com/v1/objects/4d5e6e0c41d1076bc86f3ab157c88a41a5716997/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/mime": {"type": "directory"}, "lib/desktop/mime/packages": {"type": "directory"}, "lib/desktop/mime/packages/x-java-archive.xml": {"downloads": {"lzma": {"sha1": "841230729f0a59de2a1071d155d96358232b2ba1", "size": 591, "url": "https://launcher.mojang.com/v1/objects/841230729f0a59de2a1071d155d96358232b2ba1/x-java-archive.xml"}, "raw": {"sha1": "b6297fd36efa799312961f95ebf0c85c920d5037", "size": 1822, "url": "https://launcher.mojang.com/v1/objects/b6297fd36efa799312961f95ebf0c85c920d5037/x-java-archive.xml"}}, "executable": false, "type": "file"}, "lib/desktop/mime/packages/x-java-jnlp-file.xml": {"downloads": {"lzma": {"sha1": "abf9acbe7c18027c4f036c4e42bb2cf1115525fa", "size": 302, "url": "https://launcher.mojang.com/v1/objects/abf9acbe7c18027c4f036c4e42bb2cf1115525fa/x-java-jnlp-file.xml"}, "raw": {"sha1": "72f03da83ddb76c9105f619fcfa4dbdad70e6b30", "size": 412, "url": "https://launcher.mojang.com/v1/objects/72f03da83ddb76c9105f619fcfa4dbdad70e6b30/x-java-jnlp-file.xml"}}, "executable": false, "type": "file"}, "lib/ext": {"type": "directory"}, "lib/ext/cldrdata.jar": {"downloads": {"raw": {"sha1": "6cacc961942d3f02a88907aa8f2eaae8e20c95a0", "size": 3860502, "url": "https://launcher.mojang.com/v1/objects/6cacc961942d3f02a88907aa8f2eaae8e20c95a0/cldrdata.jar"}}, "executable": false, "type": "file"}, "lib/ext/dnsns.jar": {"downloads": {"raw": {"sha1": "93bebdd7514e53ae31d60c5daba673878c8822ec", "size": 8286, "url": "https://launcher.mojang.com/v1/objects/93bebdd7514e53ae31d60c5daba673878c8822ec/dnsns.jar"}}, "executable": false, "type": "file"}, "lib/ext/jaccess.jar": {"downloads": {"raw": {"sha1": "2f54879df7c29ec67c40d40cfc95c0d4a968bea1", "size": 44516, "url": "https://launcher.mojang.com/v1/objects/2f54879df7c29ec67c40d40cfc95c0d4a968bea1/jaccess.jar"}}, "executable": false, "type": "file"}, "lib/ext/jfxrt.jar": {"downloads": {"lzma": {"sha1": "a6c5b6a782ba360ada6651f5322dcab88c75711c", "size": 3374270, "url": "https://launcher.mojang.com/v1/objects/a6c5b6a782ba360ada6651f5322dcab88c75711c/jfxrt.jar"}, "raw": {"sha1": "1ad7a876f045399c23ee4ab7dee380a04ca2ac08", "size": 18508094, "url": "https://launcher.mojang.com/v1/objects/1ad7a876f045399c23ee4ab7dee380a04ca2ac08/jfxrt.jar"}}, "executable": false, "type": "file"}, "lib/ext/localedata.jar": {"downloads": {"raw": {"sha1": "0cc9f550d4e410b5aa29dbfd2c1b5c99391c7f70", "size": 1178926, "url": "https://launcher.mojang.com/v1/objects/0cc9f550d4e410b5aa29dbfd2c1b5c99391c7f70/localedata.jar"}}, "executable": false, "type": "file"}, "lib/ext/meta-index": {"downloads": {"lzma": {"sha1": "1359457529f42bacf495afcb68149ae036442dd9", "size": 594, "url": "https://launcher.mojang.com/v1/objects/1359457529f42bacf495afcb68149ae036442dd9/meta-index"}, "raw": {"sha1": "334649c6e2d5d7248211f30855e97cfcb4558851", "size": 1269, "url": "https://launcher.mojang.com/v1/objects/334649c6e2d5d7248211f30855e97cfcb4558851/meta-index"}}, "executable": false, "type": "file"}, "lib/ext/nashorn.jar": {"downloads": {"raw": {"sha1": "dec5dd17a0f52ae79dfbfb38840bffb8b7a679a5", "size": 2023869, "url": "https://launcher.mojang.com/v1/objects/dec5dd17a0f52ae79dfbfb38840bffb8b7a679a5/nashorn.jar"}}, "executable": false, "type": "file"}, "lib/ext/sunec.jar": {"downloads": {"raw": {"sha1": "bf1c817820341a246f7130fe046e8310b03d04f6", "size": 41672, "url": "https://launcher.mojang.com/v1/objects/bf1c817820341a246f7130fe046e8310b03d04f6/sunec.jar"}}, "executable": false, "type": "file"}, "lib/ext/sunjce_provider.jar": {"downloads": {"raw": {"sha1": "bb3494e4b5cb3c3e60da767207731f18b267cb34", "size": 279326, "url": "https://launcher.mojang.com/v1/objects/bb3494e4b5cb3c3e60da767207731f18b267cb34/sunjce_provider.jar"}}, "executable": false, "type": "file"}, "lib/ext/sunpkcs11.jar": {"downloads": {"raw": {"sha1": "5bb1dedc3344cd3bb86828d4aa8ca82f4a606ed4", "size": 250218, "url": "https://launcher.mojang.com/v1/objects/5bb1dedc3344cd3bb86828d4aa8ca82f4a606ed4/sunpkcs11.jar"}}, "executable": false, "type": "file"}, "lib/ext/zipfs.jar": {"downloads": {"raw": {"sha1": "37b338f0e8e60d6396f51275130e8110816d7b30", "size": 68964, "url": "https://launcher.mojang.com/v1/objects/37b338f0e8e60d6396f51275130e8110816d7b30/zipfs.jar"}}, "executable": false, "type": "file"}, "lib/flavormap.properties": {"downloads": {"lzma": {"sha1": "2d5ef19ee77ccfc95c9413eea155cde59a48fadd", "size": 1541, "url": "https://launcher.mojang.com/v1/objects/2d5ef19ee77ccfc95c9413eea155cde59a48fadd/flavormap.properties"}, "raw": {"sha1": "4e66e8fe12d7f8b3b0c4e1a1915f329bb1fbf6d2", "size": 3901, "url": "https://launcher.mojang.com/v1/objects/4e66e8fe12d7f8b3b0c4e1a1915f329bb1fbf6d2/flavormap.properties"}}, "executable": false, "type": "file"}, "lib/fontconfig.RedHat.5.bfc": {"downloads": {"lzma": {"sha1": "5197f6e387f16458b7408134e38b06f20f625e4c", "size": 795, "url": "https://launcher.mojang.com/v1/objects/5197f6e387f16458b7408134e38b06f20f625e4c/fontconfig.RedHat.5.bfc"}, "raw": {"sha1": "fb806ada6e68f16a9fe2b726a39d9ef5a835c0c2", "size": 4532, "url": "https://launcher.mojang.com/v1/objects/fb806ada6e68f16a9fe2b726a39d9ef5a835c0c2/fontconfig.RedHat.5.bfc"}}, "executable": false, "type": "file"}, "lib/fontconfig.RedHat.5.properties.src": {"downloads": {"lzma": {"sha1": "3897ae198e96e5a687c9c9b218ff5df60c868e0d", "size": 1089, "url": "https://launcher.mojang.com/v1/objects/3897ae198e96e5a687c9c9b218ff5df60c868e0d/fontconfig.RedHat.5.properties.src"}, "raw": {"sha1": "c67d1a06cb37b66e69560c9f5e4be7cf08af0493", "size": 8841, "url": "https://launcher.mojang.com/v1/objects/c67d1a06cb37b66e69560c9f5e4be7cf08af0493/fontconfig.RedHat.5.properties.src"}}, "executable": false, "type": "file"}, "lib/fontconfig.RedHat.6.bfc": {"downloads": {"lzma": {"sha1": "ef2f5d1f8d620be9927db45d3a28bd75777245cb", "size": 818, "url": "https://launcher.mojang.com/v1/objects/ef2f5d1f8d620be9927db45d3a28bd75777245cb/fontconfig.RedHat.6.bfc"}, "raw": {"sha1": "9ba3b3e2c621c31d0ef1b2053c80f77419a19965", "size": 4250, "url": "https://launcher.mojang.com/v1/objects/9ba3b3e2c621c31d0ef1b2053c80f77419a19965/fontconfig.RedHat.6.bfc"}}, "executable": false, "type": "file"}, "lib/fontconfig.RedHat.6.properties.src": {"downloads": {"lzma": {"sha1": "74f4148f9d7ec3d67bbd724834d478a72cfdb0db", "size": 1111, "url": "https://launcher.mojang.com/v1/objects/74f4148f9d7ec3d67bbd724834d478a72cfdb0db/fontconfig.RedHat.6.properties.src"}, "raw": {"sha1": "768e58d4d314621c38daf9fde6d67119f370acd9", "size": 8735, "url": "https://launcher.mojang.com/v1/objects/768e58d4d314621c38daf9fde6d67119f370acd9/fontconfig.RedHat.6.properties.src"}}, "executable": false, "type": "file"}, "lib/fontconfig.SuSE.10.bfc": {"downloads": {"lzma": {"sha1": "d8fe9b1d8d02368dcd452de93024c6f60670eb87", "size": 1083, "url": "https://launcher.mojang.com/v1/objects/d8fe9b1d8d02368dcd452de93024c6f60670eb87/fontconfig.SuSE.10.bfc"}, "raw": {"sha1": "ffd0dfbd1553e15b11649a73a0b3f48318138e0d", "size": 6702, "url": "https://launcher.mojang.com/v1/objects/ffd0dfbd1553e15b11649a73a0b3f48318138e0d/fontconfig.SuSE.10.bfc"}}, "executable": false, "type": "file"}, "lib/fontconfig.SuSE.10.properties.src": {"downloads": {"lzma": {"sha1": "2c382bd741a9e23114be3da82dee8290ebfca8a9", "size": 1555, "url": "https://launcher.mojang.com/v1/objects/2c382bd741a9e23114be3da82dee8290ebfca8a9/fontconfig.SuSE.10.properties.src"}, "raw": {"sha1": "a38dbdbbc514567b8281e1aea726865f37e97894", "size": 16772, "url": "https://launcher.mojang.com/v1/objects/a38dbdbbc514567b8281e1aea726865f37e97894/fontconfig.SuSE.10.properties.src"}}, "executable": false, "type": "file"}, "lib/fontconfig.SuSE.11.bfc": {"downloads": {"lzma": {"sha1": "2b78cbf11289c9858951fea7180696ba3b7176d6", "size": 1092, "url": "https://launcher.mojang.com/v1/objects/2b78cbf11289c9858951fea7180696ba3b7176d6/fontconfig.SuSE.11.bfc"}, "raw": {"sha1": "a4d8500fcb47f6327460a95851b1368660da8302", "size": 7032, "url": "https://launcher.mojang.com/v1/objects/a4d8500fcb47f6327460a95851b1368660da8302/fontconfig.SuSE.11.bfc"}}, "executable": false, "type": "file"}, "lib/fontconfig.SuSE.11.properties.src": {"downloads": {"lzma": {"sha1": "5c1635803906e2c59d36492dec724dd7ae49a5ab", "size": 1589, "url": "https://launcher.mojang.com/v1/objects/5c1635803906e2c59d36492dec724dd7ae49a5ab/fontconfig.SuSE.11.properties.src"}, "raw": {"sha1": "c4b69589e41a7279a71866a9134213be19cdf88d", "size": 16781, "url": "https://launcher.mojang.com/v1/objects/c4b69589e41a7279a71866a9134213be19cdf88d/fontconfig.SuSE.11.properties.src"}}, "executable": false, "type": "file"}, "lib/fontconfig.Turbo.bfc": {"downloads": {"lzma": {"sha1": "1c771325d9ee4af209a3db92294451d58962c7a4", "size": 822, "url": "https://launcher.mojang.com/v1/objects/1c771325d9ee4af209a3db92294451d58962c7a4/fontconfig.Turbo.bfc"}, "raw": {"sha1": "f24368deeb85cc9d0781083bc56e773518d72219", "size": 4668, "url": "https://launcher.mojang.com/v1/objects/f24368deeb85cc9d0781083bc56e773518d72219/fontconfig.Turbo.bfc"}}, "executable": false, "type": "file"}, "lib/fontconfig.Turbo.properties.src": {"downloads": {"lzma": {"sha1": "7748ffa17e2c8a34754138efa963ba39bd1cbbb3", "size": 1113, "url": "https://launcher.mojang.com/v1/objects/7748ffa17e2c8a34754138efa963ba39bd1cbbb3/fontconfig.Turbo.properties.src"}, "raw": {"sha1": "2bb7258bed7ccd4f117e4e5f892c9b13424b0c82", "size": 9192, "url": "https://launcher.mojang.com/v1/objects/2bb7258bed7ccd4f117e4e5f892c9b13424b0c82/fontconfig.Turbo.properties.src"}}, "executable": false, "type": "file"}, "lib/fontconfig.bfc": {"downloads": {"lzma": {"sha1": "be6d49ee8c64f458c4f0e64254963fec48d25150", "size": 286, "url": "https://launcher.mojang.com/v1/objects/be6d49ee8c64f458c4f0e64254963fec48d25150/fontconfig.bfc"}, "raw": {"sha1": "de39b0e19637f58d92a0188122514aa7247ebb5b", "size": 1678, "url": "https://launcher.mojang.com/v1/objects/de39b0e19637f58d92a0188122514aa7247ebb5b/fontconfig.bfc"}}, "executable": false, "type": "file"}, "lib/fontconfig.properties.src": {"downloads": {"lzma": {"sha1": "9498d5e00e5401200667687e826e28c60fa60ba4", "size": 417, "url": "https://launcher.mojang.com/v1/objects/9498d5e00e5401200667687e826e28c60fa60ba4/fontconfig.properties.src"}, "raw": {"sha1": "3617ff1424fd204415242565541facf862b16eb4", "size": 1938, "url": "https://launcher.mojang.com/v1/objects/3617ff1424fd204415242565541facf862b16eb4/fontconfig.properties.src"}}, "executable": false, "type": "file"}, "lib/fonts": {"type": "directory"}, "lib/fonts/LucidaBrightDemiBold.ttf": {"downloads": {"lzma": {"sha1": "4f748750831a7719440dff5457f4d207d0f24d21", "size": 42347, "url": "https://launcher.mojang.com/v1/objects/4f748750831a7719440dff5457f4d207d0f24d21/LucidaBrightDemiBold.ttf"}, "raw": {"sha1": "b5c97f985639e19a3b712193ee48b55dda581fd1", "size": 75144, "url": "https://launcher.mojang.com/v1/objects/b5c97f985639e19a3b712193ee48b55dda581fd1/LucidaBrightDemiBold.ttf"}}, "executable": false, "type": "file"}, "lib/fonts/LucidaBrightDemiItalic.ttf": {"downloads": {"lzma": {"sha1": "f82e9a688553c100ecb98412b985807ed56dff5d", "size": 43119, "url": "https://launcher.mojang.com/v1/objects/f82e9a688553c100ecb98412b985807ed56dff5d/LucidaBrightDemiItalic.ttf"}, "raw": {"sha1": "1fd1f757febf3e5f5fbb7fbf7a56587a40d57de7", "size": 75124, "url": "https://launcher.mojang.com/v1/objects/1fd1f757febf3e5f5fbb7fbf7a56587a40d57de7/LucidaBrightDemiItalic.ttf"}}, "executable": false, "type": "file"}, "lib/fonts/LucidaBrightItalic.ttf": {"downloads": {"lzma": {"sha1": "6d630df719271319c3d53f90a3d425118b908266", "size": 46206, "url": "https://launcher.mojang.com/v1/objects/6d630df719271319c3d53f90a3d425118b908266/LucidaBrightItalic.ttf"}, "raw": {"sha1": "aa5c037865c563726ecd63d61ca26443589be425", "size": 80856, "url": "https://launcher.mojang.com/v1/objects/aa5c037865c563726ecd63d61ca26443589be425/LucidaBrightItalic.ttf"}}, "executable": false, "type": "file"}, "lib/fonts/LucidaBrightRegular.ttf": {"downloads": {"lzma": {"sha1": "4b2e31aaec2238b6ecf9f845bad0a1c6d09fbbfe", "size": 181085, "url": "https://launcher.mojang.com/v1/objects/4b2e31aaec2238b6ecf9f845bad0a1c6d09fbbfe/LucidaBrightRegular.ttf"}, "raw": {"sha1": "5d7ed564791c900a8786936930ba99385653139c", "size": 344908, "url": "https://launcher.mojang.com/v1/objects/5d7ed564791c900a8786936930ba99385653139c/LucidaBrightRegular.ttf"}}, "executable": false, "type": "file"}, "lib/fonts/LucidaSansDemiBold.ttf": {"downloads": {"lzma": {"sha1": "079b16dc3c4918ab1f4f760b6dc5e6586c219042", "size": 173229, "url": "https://launcher.mojang.com/v1/objects/079b16dc3c4918ab1f4f760b6dc5e6586c219042/LucidaSansDemiBold.ttf"}, "raw": {"sha1": "92b79fefc35e96190250c602a8fed85276b32a95", "size": 317896, "url": "https://launcher.mojang.com/v1/objects/92b79fefc35e96190250c602a8fed85276b32a95/LucidaSansDemiBold.ttf"}}, "executable": false, "type": "file"}, "lib/fonts/LucidaSansRegular.ttf": {"downloads": {"lzma": {"sha1": "64a65d7b94d7153d20957ef6d06bebb4dd0f48e4", "size": 326062, "url": "https://launcher.mojang.com/v1/objects/64a65d7b94d7153d20957ef6d06bebb4dd0f48e4/LucidaSansRegular.ttf"}, "raw": {"sha1": "39cc8bcb8d4a71d4657fc92ef0b9f4e3e9e67add", "size": 698236, "url": "https://launcher.mojang.com/v1/objects/39cc8bcb8d4a71d4657fc92ef0b9f4e3e9e67add/LucidaSansRegular.ttf"}}, "executable": false, "type": "file"}, "lib/fonts/LucidaTypewriterBold.ttf": {"downloads": {"lzma": {"sha1": "cdb017f7c34bea0802bc5ea5583aef721ed99c49", "size": 130412, "url": "https://launcher.mojang.com/v1/objects/cdb017f7c34bea0802bc5ea5583aef721ed99c49/LucidaTypewriterBold.ttf"}, "raw": {"sha1": "a5da2eb49448f461470387c939f0e69119310e0b", "size": 234068, "url": "https://launcher.mojang.com/v1/objects/a5da2eb49448f461470387c939f0e69119310e0b/LucidaTypewriterBold.ttf"}}, "executable": false, "type": "file"}, "lib/fonts/LucidaTypewriterRegular.ttf": {"downloads": {"lzma": {"sha1": "aeda4a09a53783b0dc97de8e20071bea874cbfe5", "size": 135184, "url": "https://launcher.mojang.com/v1/objects/aeda4a09a53783b0dc97de8e20071bea874cbfe5/LucidaTypewriterRegular.ttf"}, "raw": {"sha1": "c144dcafe4faf2e79cfd74d8134a631f30234db1", "size": 242700, "url": "https://launcher.mojang.com/v1/objects/c144dcafe4faf2e79cfd74d8134a631f30234db1/LucidaTypewriterRegular.ttf"}}, "executable": false, "type": "file"}, "lib/fonts/fonts.dir": {"downloads": {"lzma": {"sha1": "68f2dd93b215ec8b8d9409d2b9c825632c6b907d", "size": 273, "url": "https://launcher.mojang.com/v1/objects/68f2dd93b215ec8b8d9409d2b9c825632c6b907d/fonts.dir"}, "raw": {"sha1": "97f40cca185c954adf5cc582345a7cb8e4c50578", "size": 4041, "url": "https://launcher.mojang.com/v1/objects/97f40cca185c954adf5cc582345a7cb8e4c50578/fonts.dir"}}, "executable": false, "type": "file"}, "lib/hijrah-config-umalqura.properties": {"downloads": {"lzma": {"sha1": "02e8d296e3b18a450f1ed1547cbf2b7275664c9a", "size": 1969, "url": "https://launcher.mojang.com/v1/objects/02e8d296e3b18a450f1ed1547cbf2b7275664c9a/hijrah-config-umalqura.properties"}, "raw": {"sha1": "84aa425100740722e91f4725caf849e7863d12ba", "size": 13962, "url": "https://launcher.mojang.com/v1/objects/84aa425100740722e91f4725caf849e7863d12ba/hijrah-config-umalqura.properties"}}, "executable": false, "type": "file"}, "lib/images": {"type": "directory"}, "lib/images/cursors": {"type": "directory"}, "lib/images/cursors/cursors.properties": {"downloads": {"lzma": {"sha1": "612bd0f610ee1023947c4a2a8d3fc7d6f97e7d8f", "size": 385, "url": "https://launcher.mojang.com/v1/objects/612bd0f610ee1023947c4a2a8d3fc7d6f97e7d8f/cursors.properties"}, "raw": {"sha1": "f2b9a22ddd0a77869497a64f28f07e89a7d41f48", "size": 1274, "url": "https://launcher.mojang.com/v1/objects/f2b9a22ddd0a77869497a64f28f07e89a7d41f48/cursors.properties"}}, "executable": false, "type": "file"}, "lib/images/cursors/invalid32x32.gif": {"downloads": {"raw": {"sha1": "259edc45b4569427e8319895a444f4295d54348f", "size": 153, "url": "https://launcher.mojang.com/v1/objects/259edc45b4569427e8319895a444f4295d54348f/invalid32x32.gif"}}, "executable": false, "type": "file"}, "lib/images/cursors/motif_CopyDrop32x32.gif": {"downloads": {"raw": {"sha1": "eb7620fae702172aa663a19d170a0b929d3b11d1", "size": 158, "url": "https://launcher.mojang.com/v1/objects/eb7620fae702172aa663a19d170a0b929d3b11d1/motif_CopyDrop32x32.gif"}}, "executable": false, "type": "file"}, "lib/images/cursors/motif_CopyNoDrop32x32.gif": {"downloads": {"raw": {"sha1": "259edc45b4569427e8319895a444f4295d54348f", "size": 153, "url": "https://launcher.mojang.com/v1/objects/259edc45b4569427e8319895a444f4295d54348f/invalid32x32.gif"}}, "executable": false, "type": "file"}, "lib/images/cursors/motif_LinkDrop32x32.gif": {"downloads": {"raw": {"sha1": "9699137f990c240e714481563181069c8f6c17bb", "size": 162, "url": "https://launcher.mojang.com/v1/objects/9699137f990c240e714481563181069c8f6c17bb/motif_LinkDrop32x32.gif"}}, "executable": false, "type": "file"}, "lib/images/cursors/motif_LinkNoDrop32x32.gif": {"downloads": {"raw": {"sha1": "259edc45b4569427e8319895a444f4295d54348f", "size": 153, "url": "https://launcher.mojang.com/v1/objects/259edc45b4569427e8319895a444f4295d54348f/invalid32x32.gif"}}, "executable": false, "type": "file"}, "lib/images/cursors/motif_MoveDrop32x32.gif": {"downloads": {"raw": {"sha1": "03c1617ce3c5ab8af03e46d30a8c8f31ab57fb1b", "size": 141, "url": "https://launcher.mojang.com/v1/objects/03c1617ce3c5ab8af03e46d30a8c8f31ab57fb1b/motif_MoveDrop32x32.gif"}}, "executable": false, "type": "file"}, "lib/images/cursors/motif_MoveNoDrop32x32.gif": {"downloads": {"raw": {"sha1": "259edc45b4569427e8319895a444f4295d54348f", "size": 153, "url": "https://launcher.mojang.com/v1/objects/259edc45b4569427e8319895a444f4295d54348f/invalid32x32.gif"}}, "executable": false, "type": "file"}, "lib/images/icons": {"type": "directory"}, "lib/images/icons/sun-java.png": {"downloads": {"raw": {"sha1": "d101b693aa054f51097eebdfeed8b8a6ca7b55b8", "size": 4707, "url": "https://launcher.mojang.com/v1/objects/d101b693aa054f51097eebdfeed8b8a6ca7b55b8/sun-java.png"}}, "executable": false, "type": "file"}, "lib/images/icons/sun-java_HighContrast.png": {"downloads": {"raw": {"sha1": "a6b1e418d6b5d03719b96f61f0c5236a60970151", "size": 3729, "url": "https://launcher.mojang.com/v1/objects/a6b1e418d6b5d03719b96f61f0c5236a60970151/sun-java_HighContrast.png"}}, "executable": false, "type": "file"}, "lib/images/icons/sun-java_HighContrastInverse.png": {"downloads": {"raw": {"sha1": "2dda28b9bddc9b5b018e3e8a8b062a99d9b2f887", "size": 3777, "url": "https://launcher.mojang.com/v1/objects/2dda28b9bddc9b5b018e3e8a8b062a99d9b2f887/sun-java_HighContrastInverse.png"}}, "executable": false, "type": "file"}, "lib/images/icons/sun-java_LowContrast.png": {"downloads": {"raw": {"sha1": "7714cc4e894c3626c8da6fe742ed22b2829122d9", "size": 4012, "url": "https://launcher.mojang.com/v1/objects/7714cc4e894c3626c8da6fe742ed22b2829122d9/sun-java_LowContrast.png"}}, "executable": false, "type": "file"}, "lib/javafx.properties": {"downloads": {"raw": {"sha1": "49e6b75d109e5fd3f6cbe7cc5fa9a7980796d14d", "size": 56, "url": "https://launcher.mojang.com/v1/objects/49e6b75d109e5fd3f6cbe7cc5fa9a7980796d14d/javafx.properties"}}, "executable": false, "type": "file"}, "lib/javaws.jar": {"downloads": {"raw": {"sha1": "04fa5ae04ead65b91be5dee575497e49ffd49fe9", "size": 488118, "url": "https://launcher.mojang.com/v1/objects/04fa5ae04ead65b91be5dee575497e49ffd49fe9/javaws.jar"}}, "executable": false, "type": "file"}, "lib/jce.jar": {"downloads": {"raw": {"sha1": "5460adee09cc5fc8829c0acfc46c34670a7d70a0", "size": 115646, "url": "https://launcher.mojang.com/v1/objects/5460adee09cc5fc8829c0acfc46c34670a7d70a0/jce.jar"}}, "executable": false, "type": "file"}, "lib/jexec": {"downloads": {"lzma": {"sha1": "2d4323d4e060f8126d026ca6c03b8972aedd2fab", "size": 3311, "url": "https://launcher.mojang.com/v1/objects/2d4323d4e060f8126d026ca6c03b8972aedd2fab/jexec"}, "raw": {"sha1": "6aa01f1d8d103974164bcfaea03c04eeeefd7d41", "size": 13376, "url": "https://launcher.mojang.com/v1/objects/6aa01f1d8d103974164bcfaea03c04eeeefd7d41/jexec"}}, "executable": true, "type": "file"}, "lib/jfr": {"type": "directory"}, "lib/jfr.jar": {"downloads": {"lzma": {"sha1": "5b9d615c91c72f4fe356d9b4105946679452d1e1", "size": 137982, "url": "https://launcher.mojang.com/v1/objects/5b9d615c91c72f4fe356d9b4105946679452d1e1/jfr.jar"}, "raw": {"sha1": "0f3fd66a336703d935bdc22ad8082bc51d34e534", "size": 560713, "url": "https://launcher.mojang.com/v1/objects/0f3fd66a336703d935bdc22ad8082bc51d34e534/jfr.jar"}}, "executable": false, "type": "file"}, "lib/jfr/default.jfc": {"downloads": {"lzma": {"sha1": "373ddd878146dd8ce8991c2c5115a05a82859bdb", "size": 2207, "url": "https://launcher.mojang.com/v1/objects/373ddd878146dd8ce8991c2c5115a05a82859bdb/default.jfc"}, "raw": {"sha1": "1a64b68d0e7d43f8149faba94440be54f4f24527", "size": 20109, "url": "https://launcher.mojang.com/v1/objects/1a64b68d0e7d43f8149faba94440be54f4f24527/default.jfc"}}, "executable": false, "type": "file"}, "lib/jfr/profile.jfc": {"downloads": {"lzma": {"sha1": "3dcdc5feee3ccfb66bc8726b666944cd4bdadae3", "size": 2199, "url": "https://launcher.mojang.com/v1/objects/3dcdc5feee3ccfb66bc8726b666944cd4bdadae3/profile.jfc"}, "raw": {"sha1": "5d7d08a595f76322c51ae43ea966fbba6b69eebe", "size": 20065, "url": "https://launcher.mojang.com/v1/objects/5d7d08a595f76322c51ae43ea966fbba6b69eebe/profile.jfc"}}, "executable": false, "type": "file"}, "lib/jfxswt.jar": {"downloads": {"raw": {"sha1": "99d9a264c898d84c01e1c42565e7fe1a89dcd72d", "size": 33932, "url": "https://launcher.mojang.com/v1/objects/99d9a264c898d84c01e1c42565e7fe1a89dcd72d/jfxswt.jar"}}, "executable": false, "type": "file"}, "lib/jsse.jar": {"downloads": {"lzma": {"sha1": "94a17dfbc2e76cd12c33970a15341424f875a9ce", "size": 187549, "url": "https://launcher.mojang.com/v1/objects/94a17dfbc2e76cd12c33970a15341424f875a9ce/jsse.jar"}, "raw": {"sha1": "92c5c626e8a2d16f41272c0e404d4f992dd8310a", "size": 675599, "url": "https://launcher.mojang.com/v1/objects/92c5c626e8a2d16f41272c0e404d4f992dd8310a/jsse.jar"}}, "executable": false, "type": "file"}, "lib/jvm.hprof.txt": {"downloads": {"lzma": {"sha1": "eccdb240a815b2a83a502749339b27bb8669965b", "size": 1863, "url": "https://launcher.mojang.com/v1/objects/eccdb240a815b2a83a502749339b27bb8669965b/jvm.hprof.txt"}, "raw": {"sha1": "fbd61d52534cdd0c15df332114d469c65d001e33", "size": 4226, "url": "https://launcher.mojang.com/v1/objects/fbd61d52534cdd0c15df332114d469c65d001e33/jvm.hprof.txt"}}, "executable": false, "type": "file"}, "lib/locale": {"type": "directory"}, "lib/locale/de": {"type": "directory"}, "lib/locale/de/LC_MESSAGES": {"type": "directory"}, "lib/locale/de/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "3061d922907cc557208109088fc6ab81d577ff6f", "size": 970, "url": "https://launcher.mojang.com/v1/objects/3061d922907cc557208109088fc6ab81d577ff6f/sunw_java_plugin.mo"}, "raw": {"sha1": "5b223a3d723ac1cfce63623fb109f2868d47d1b7", "size": 2483, "url": "https://launcher.mojang.com/v1/objects/5b223a3d723ac1cfce63623fb109f2868d47d1b7/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/es": {"type": "directory"}, "lib/locale/es/LC_MESSAGES": {"type": "directory"}, "lib/locale/es/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "24338049a89b323e17182b3a3006b50565d4fa0f", "size": 979, "url": "https://launcher.mojang.com/v1/objects/24338049a89b323e17182b3a3006b50565d4fa0f/sunw_java_plugin.mo"}, "raw": {"sha1": "6cc63dc97f2fdb2ed799e48b1dc98c4f37cdecc1", "size": 2477, "url": "https://launcher.mojang.com/v1/objects/6cc63dc97f2fdb2ed799e48b1dc98c4f37cdecc1/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/fr": {"type": "directory"}, "lib/locale/fr/LC_MESSAGES": {"type": "directory"}, "lib/locale/fr/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "22796a48ef39f57d2d6fa70f41308e493d7f05c1", "size": 1033, "url": "https://launcher.mojang.com/v1/objects/22796a48ef39f57d2d6fa70f41308e493d7f05c1/sunw_java_plugin.mo"}, "raw": {"sha1": "d9d5b458db6e83fdf85c3526aeee3f57c4929840", "size": 2746, "url": "https://launcher.mojang.com/v1/objects/d9d5b458db6e83fdf85c3526aeee3f57c4929840/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/it": {"type": "directory"}, "lib/locale/it/LC_MESSAGES": {"type": "directory"}, "lib/locale/it/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "59a4cae38bfb8927745674d0efc2f284bc277987", "size": 958, "url": "https://launcher.mojang.com/v1/objects/59a4cae38bfb8927745674d0efc2f284bc277987/sunw_java_plugin.mo"}, "raw": {"sha1": "f6e72e3b2141ccc3dffab10ae14a754e494577ba", "size": 2434, "url": "https://launcher.mojang.com/v1/objects/f6e72e3b2141ccc3dffab10ae14a754e494577ba/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/ja": {"type": "directory"}, "lib/locale/ja/LC_MESSAGES": {"type": "directory"}, "lib/locale/ja/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "7d6aeed563e1cefcf0224cf522048468088884a9", "size": 1036, "url": "https://launcher.mojang.com/v1/objects/7d6aeed563e1cefcf0224cf522048468088884a9/sunw_java_plugin.mo"}, "raw": {"sha1": "378881a8cb8dd2aebb43eacd0c68519be4f258b1", "size": 2415, "url": "https://launcher.mojang.com/v1/objects/378881a8cb8dd2aebb43eacd0c68519be4f258b1/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/ko": {"type": "directory"}, "lib/locale/ko.UTF-8": {"type": "directory"}, "lib/locale/ko.UTF-8/LC_MESSAGES": {"type": "directory"}, "lib/locale/ko.UTF-8/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "12ee3b21511e8497d95ea0ba9d6fe519227d0b16", "size": 1069, "url": "https://launcher.mojang.com/v1/objects/12ee3b21511e8497d95ea0ba9d6fe519227d0b16/sunw_java_plugin.mo"}, "raw": {"sha1": "cb19df01c59662dbe2f4050b1290d374b82fe1fa", "size": 2753, "url": "https://launcher.mojang.com/v1/objects/cb19df01c59662dbe2f4050b1290d374b82fe1fa/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/ko/LC_MESSAGES": {"type": "directory"}, "lib/locale/ko/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "6e2e47c64c360517fd436bc79c823b5679a1efe6", "size": 996, "url": "https://launcher.mojang.com/v1/objects/6e2e47c64c360517fd436bc79c823b5679a1efe6/sunw_java_plugin.mo"}, "raw": {"sha1": "12c8a118d150c78f719314df6dec49a967af71e9", "size": 2399, "url": "https://launcher.mojang.com/v1/objects/12c8a118d150c78f719314df6dec49a967af71e9/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/pt_BR": {"type": "directory"}, "lib/locale/pt_BR/LC_MESSAGES": {"type": "directory"}, "lib/locale/pt_BR/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "bcaa7e7916493f071f1bf64bf58c6b038e3569c9", "size": 940, "url": "https://launcher.mojang.com/v1/objects/bcaa7e7916493f071f1bf64bf58c6b038e3569c9/sunw_java_plugin.mo"}, "raw": {"sha1": "a3bc0c43994c53c59bba94982cf95f6d36283dd0", "size": 2420, "url": "https://launcher.mojang.com/v1/objects/a3bc0c43994c53c59bba94982cf95f6d36283dd0/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/sv": {"type": "directory"}, "lib/locale/sv/LC_MESSAGES": {"type": "directory"}, "lib/locale/sv/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "76017835d6261fe2eedbcbe5eb08a7484c3080c5", "size": 946, "url": "https://launcher.mojang.com/v1/objects/76017835d6261fe2eedbcbe5eb08a7484c3080c5/sunw_java_plugin.mo"}, "raw": {"sha1": "09a47686edec4bbb34e82fbd08559f8bb6266544", "size": 2359, "url": "https://launcher.mojang.com/v1/objects/09a47686edec4bbb34e82fbd08559f8bb6266544/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/zh": {"type": "directory"}, "lib/locale/zh.GBK": {"type": "directory"}, "lib/locale/zh.GBK/LC_MESSAGES": {"type": "directory"}, "lib/locale/zh.GBK/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "75fd04045bf5890b8bb822770bfdb90a2e9ea65b", "size": 902, "url": "https://launcher.mojang.com/v1/objects/75fd04045bf5890b8bb822770bfdb90a2e9ea65b/sunw_java_plugin.mo"}, "raw": {"sha1": "7006fe7767b8807441a1f359a90509b3e507b0d1", "size": 2002, "url": "https://launcher.mojang.com/v1/objects/7006fe7767b8807441a1f359a90509b3e507b0d1/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/zh/LC_MESSAGES": {"type": "directory"}, "lib/locale/zh/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "75fd04045bf5890b8bb822770bfdb90a2e9ea65b", "size": 902, "url": "https://launcher.mojang.com/v1/objects/75fd04045bf5890b8bb822770bfdb90a2e9ea65b/sunw_java_plugin.mo"}, "raw": {"sha1": "7006fe7767b8807441a1f359a90509b3e507b0d1", "size": 2002, "url": "https://launcher.mojang.com/v1/objects/7006fe7767b8807441a1f359a90509b3e507b0d1/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/zh_HK.BIG5HK": {"type": "directory"}, "lib/locale/zh_HK.BIG5HK/LC_MESSAGES": {"type": "directory"}, "lib/locale/zh_HK.BIG5HK/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "3a1397bb1b1741697be1479232b6d9599940c851", "size": 912, "url": "https://launcher.mojang.com/v1/objects/3a1397bb1b1741697be1479232b6d9599940c851/sunw_java_plugin.mo"}, "raw": {"sha1": "c6023544067278c78599921f1032de353ff7da42", "size": 2025, "url": "https://launcher.mojang.com/v1/objects/c6023544067278c78599921f1032de353ff7da42/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/zh_TW": {"type": "directory"}, "lib/locale/zh_TW.BIG5": {"type": "directory"}, "lib/locale/zh_TW.BIG5/LC_MESSAGES": {"type": "directory"}, "lib/locale/zh_TW.BIG5/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "3a1397bb1b1741697be1479232b6d9599940c851", "size": 912, "url": "https://launcher.mojang.com/v1/objects/3a1397bb1b1741697be1479232b6d9599940c851/sunw_java_plugin.mo"}, "raw": {"sha1": "c6023544067278c78599921f1032de353ff7da42", "size": 2025, "url": "https://launcher.mojang.com/v1/objects/c6023544067278c78599921f1032de353ff7da42/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/zh_TW/LC_MESSAGES": {"type": "directory"}, "lib/locale/zh_TW/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "c05e610e75182f0c4e77f3e7a4d9670ed62bf63c", "size": 897, "url": "https://launcher.mojang.com/v1/objects/c05e610e75182f0c4e77f3e7a4d9670ed62bf63c/sunw_java_plugin.mo"}, "raw": {"sha1": "f9b972dd059eae3cd337dfcef6a178e8ed8a7db6", "size": 2025, "url": "https://launcher.mojang.com/v1/objects/f9b972dd059eae3cd337dfcef6a178e8ed8a7db6/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/logging.properties": {"downloads": {"lzma": {"sha1": "642202a58e5216d086ad37c0b5a633be802edc78", "size": 896, "url": "https://launcher.mojang.com/v1/objects/642202a58e5216d086ad37c0b5a633be802edc78/logging.properties"}, "raw": {"sha1": "89da8094484891f9ec1fa40c6c8b61f94c5869d0", "size": 2455, "url": "https://launcher.mojang.com/v1/objects/89da8094484891f9ec1fa40c6c8b61f94c5869d0/logging.properties"}}, "executable": false, "type": "file"}, "lib/management": {"type": "directory"}, "lib/management-agent.jar": {"downloads": {"lzma": {"sha1": "3ea0bf17e14b3428296a0f4011bf4025fcbfa4bd", "size": 243, "url": "https://launcher.mojang.com/v1/objects/3ea0bf17e14b3428296a0f4011bf4025fcbfa4bd/management-agent.jar"}, "raw": {"sha1": "9fbed36522aa3a80bac08a328942cbc5ef39ca8e", "size": 381, "url": "https://launcher.mojang.com/v1/objects/9fbed36522aa3a80bac08a328942cbc5ef39ca8e/management-agent.jar"}}, "executable": false, "type": "file"}, "lib/management/jmxremote.access": {"downloads": {"lzma": {"sha1": "69042ff1b14165db19c9d728614639dec16d6a31", "size": 1419, "url": "https://launcher.mojang.com/v1/objects/69042ff1b14165db19c9d728614639dec16d6a31/jmxremote.access"}, "raw": {"sha1": "21200eaad898ba4a2a8834a032efb6616fabb930", "size": 3998, "url": "https://launcher.mojang.com/v1/objects/21200eaad898ba4a2a8834a032efb6616fabb930/jmxremote.access"}}, "executable": false, "type": "file"}, "lib/management/jmxremote.password.template": {"downloads": {"lzma": {"sha1": "556c64b1e920766f8867be3964de6e49f5b81a60", "size": 1129, "url": "https://launcher.mojang.com/v1/objects/556c64b1e920766f8867be3964de6e49f5b81a60/jmxremote.password.template"}, "raw": {"sha1": "c1e0f01408bf20fbbb8b4810520c725f70050db5", "size": 2856, "url": "https://launcher.mojang.com/v1/objects/c1e0f01408bf20fbbb8b4810520c725f70050db5/jmxremote.password.template"}}, "executable": false, "type": "file"}, "lib/management/management.properties": {"downloads": {"lzma": {"sha1": "3e52f9baa6394ca6956845424c607e5cde5d3c67", "size": 3176, "url": "https://launcher.mojang.com/v1/objects/3e52f9baa6394ca6956845424c607e5cde5d3c67/management.properties"}, "raw": {"sha1": "e0451d8d7d9e84d7b1c39ec7d00993307a5cbbf1", "size": 14630, "url": "https://launcher.mojang.com/v1/objects/e0451d8d7d9e84d7b1c39ec7d00993307a5cbbf1/management.properties"}}, "executable": false, "type": "file"}, "lib/management/snmp.acl.template": {"downloads": {"lzma": {"sha1": "9a4aa6396c3b488b0663bed5e5ecb762985669c9", "size": 1121, "url": "https://launcher.mojang.com/v1/objects/9a4aa6396c3b488b0663bed5e5ecb762985669c9/snmp.acl.template"}, "raw": {"sha1": "2e9f9ac287274532eb1f0d1afcefd7f3e97cc794", "size": 3376, "url": "https://launcher.mojang.com/v1/objects/2e9f9ac287274532eb1f0d1afcefd7f3e97cc794/snmp.acl.template"}}, "executable": false, "type": "file"}, "lib/meta-index": {"downloads": {"lzma": {"sha1": "1ac60b31362fda4725c665b591c5fbe384cbc8c1", "size": 788, "url": "https://launcher.mojang.com/v1/objects/1ac60b31362fda4725c665b591c5fbe384cbc8c1/meta-index"}, "raw": {"sha1": "bf204f09242203e713c31785158a0792f9edb600", "size": 2034, "url": "https://launcher.mojang.com/v1/objects/bf204f09242203e713c31785158a0792f9edb600/meta-index"}}, "executable": false, "type": "file"}, "lib/net.properties": {"downloads": {"lzma": {"sha1": "e9ec3981a0797bf55bb87b24d9eb651ce7e6916b", "size": 1830, "url": "https://launcher.mojang.com/v1/objects/e9ec3981a0797bf55bb87b24d9eb651ce7e6916b/net.properties"}, "raw": {"sha1": "fd9471742eb759f4478bb1de9a0dc0527265b6ea", "size": 5352, "url": "https://launcher.mojang.com/v1/objects/fd9471742eb759f4478bb1de9a0dc0527265b6ea/net.properties"}}, "executable": false, "type": "file"}, "lib/oblique-fonts": {"type": "directory"}, "lib/oblique-fonts/LucidaSansDemiOblique.ttf": {"downloads": {"lzma": {"sha1": "49c8980c1b89bbdbab59d0f5bd5bebf0afcb93b2", "size": 38580, "url": "https://launcher.mojang.com/v1/objects/49c8980c1b89bbdbab59d0f5bd5bebf0afcb93b2/LucidaSansDemiOblique.ttf"}, "raw": {"sha1": "53e4e12a675ac222469341c3dbc102464a1be4c7", "size": 91352, "url": "https://launcher.mojang.com/v1/objects/53e4e12a675ac222469341c3dbc102464a1be4c7/LucidaSansDemiOblique.ttf"}}, "executable": false, "type": "file"}, "lib/oblique-fonts/LucidaSansOblique.ttf": {"downloads": {"lzma": {"sha1": "553123c0edcd08035dede4ffd92b5b81c9a7538a", "size": 116575, "url": "https://launcher.mojang.com/v1/objects/553123c0edcd08035dede4ffd92b5b81c9a7538a/LucidaSansOblique.ttf"}, "raw": {"sha1": "95a195ad4fc520b3e395c85b747fc3024d118dd9", "size": 253724, "url": "https://launcher.mojang.com/v1/objects/95a195ad4fc520b3e395c85b747fc3024d118dd9/LucidaSansOblique.ttf"}}, "executable": false, "type": "file"}, "lib/oblique-fonts/LucidaTypewriterBoldOblique.ttf": {"downloads": {"lzma": {"sha1": "2475b08151556ad4d89bb1d2b6494c6bee9abd82", "size": 29954, "url": "https://launcher.mojang.com/v1/objects/2475b08151556ad4d89bb1d2b6494c6bee9abd82/LucidaTypewriterBoldOblique.ttf"}, "raw": {"sha1": "f331fc8b0cc494702bc46b690f2b8eed36469a02", "size": 63168, "url": "https://launcher.mojang.com/v1/objects/f331fc8b0cc494702bc46b690f2b8eed36469a02/LucidaTypewriterBoldOblique.ttf"}}, "executable": false, "type": "file"}, "lib/oblique-fonts/LucidaTypewriterOblique.ttf": {"downloads": {"lzma": {"sha1": "5b970bc3b7abb21dce1aa28ff7f03459d351e552", "size": 60133, "url": "https://launcher.mojang.com/v1/objects/5b970bc3b7abb21dce1aa28ff7f03459d351e552/LucidaTypewriterOblique.ttf"}, "raw": {"sha1": "f8ea00db73f8a89a27674d050edc37c2280930e1", "size": 137484, "url": "https://launcher.mojang.com/v1/objects/f8ea00db73f8a89a27674d050edc37c2280930e1/LucidaTypewriterOblique.ttf"}}, "executable": false, "type": "file"}, "lib/oblique-fonts/fonts.dir": {"downloads": {"lzma": {"sha1": "067528c789bd713c7c3f34e779aa6e2e8253dcf6", "size": 188, "url": "https://launcher.mojang.com/v1/objects/067528c789bd713c7c3f34e779aa6e2e8253dcf6/fonts.dir"}, "raw": {"sha1": "5aee54ffba9e33de56fd84ef64fa496b898585bb", "size": 2115, "url": "https://launcher.mojang.com/v1/objects/5aee54ffba9e33de56fd84ef64fa496b898585bb/fonts.dir"}}, "executable": false, "type": "file"}, "lib/plugin.jar": {"downloads": {"raw": {"sha1": "3f250842c79112bae5369e372025b166990820e8", "size": 950772, "url": "https://launcher.mojang.com/v1/objects/3f250842c79112bae5369e372025b166990820e8/plugin.jar"}}, "executable": false, "type": "file"}, "lib/psfont.properties.ja": {"downloads": {"lzma": {"sha1": "7ca1cc244ed251cd1eb2347f1eea37d7d18c8ad4", "size": 701, "url": "https://launcher.mojang.com/v1/objects/7ca1cc244ed251cd1eb2347f1eea37d7d18c8ad4/psfont.properties.ja"}, "raw": {"sha1": "56ed1c661eeede17b4fae8c9de7b5edbad387abc", "size": 2796, "url": "https://launcher.mojang.com/v1/objects/56ed1c661eeede17b4fae8c9de7b5edbad387abc/psfont.properties.ja"}}, "executable": false, "type": "file"}, "lib/psfontj2d.properties": {"downloads": {"lzma": {"sha1": "4252fa01af8739a3545e2b705e3383892e22ab40", "size": 2278, "url": "https://launcher.mojang.com/v1/objects/4252fa01af8739a3545e2b705e3383892e22ab40/psfontj2d.properties"}, "raw": {"sha1": "aa327a22a49967f4d74afeee6726f505f209692f", "size": 10393, "url": "https://launcher.mojang.com/v1/objects/aa327a22a49967f4d74afeee6726f505f209692f/psfontj2d.properties"}}, "executable": false, "type": "file"}, "lib/resources.jar": {"downloads": {"lzma": {"sha1": "1b0e08441750dc17efe4b527aa146da6cc14e8a6", "size": 579294, "url": "https://launcher.mojang.com/v1/objects/1b0e08441750dc17efe4b527aa146da6cc14e8a6/resources.jar"}, "raw": {"sha1": "daa021906e4648d4c37e798c11733dc2047f2da1", "size": 3505206, "url": "https://launcher.mojang.com/v1/objects/daa021906e4648d4c37e798c11733dc2047f2da1/resources.jar"}}, "executable": false, "type": "file"}, "lib/rt.jar": {"downloads": {"lzma": {"sha1": "fc4a8681aeda29c2a2a3fd11bad7729543283f3d", "size": 14378994, "url": "https://launcher.mojang.com/v1/objects/fc4a8681aeda29c2a2a3fd11bad7729543283f3d/rt.jar"}, "raw": {"sha1": "5396b0954a20f3210f1f4f1886ead30880d6ebfe", "size": 66334986, "url": "https://launcher.mojang.com/v1/objects/5396b0954a20f3210f1f4f1886ead30880d6ebfe/rt.jar"}}, "executable": false, "type": "file"}, "lib/security": {"type": "directory"}, "lib/security/blacklist": {"downloads": {"lzma": {"sha1": "8206fce6c1d91a39fdf78e8e79e953913994a1cd", "size": 1969, "url": "https://launcher.mojang.com/v1/objects/8206fce6c1d91a39fdf78e8e79e953913994a1cd/blacklist"}, "raw": {"sha1": "d4ffb3857eab403955ce9d156e46d056061e6a5a", "size": 4054, "url": "https://launcher.mojang.com/v1/objects/d4ffb3857eab403955ce9d156e46d056061e6a5a/blacklist"}}, "executable": false, "type": "file"}, "lib/security/blacklisted.certs": {"downloads": {"lzma": {"sha1": "8311bead054caf6cfe678d4b7998de4caaabfa53", "size": 806, "url": "https://launcher.mojang.com/v1/objects/8311bead054caf6cfe678d4b7998de4caaabfa53/blacklisted.certs"}, "raw": {"sha1": "c5c005c29a80493f5c31cd7eb629ac1b9c752404", "size": 1273, "url": "https://launcher.mojang.com/v1/objects/c5c005c29a80493f5c31cd7eb629ac1b9c752404/blacklisted.certs"}}, "executable": false, "type": "file"}, "lib/security/cacerts": {"downloads": {"lzma": {"sha1": "654dd94809655d5b28385cbb5eba8d6ad9f2c1aa", "size": 67802, "url": "https://launcher.mojang.com/v1/objects/654dd94809655d5b28385cbb5eba8d6ad9f2c1aa/cacerts"}, "raw": {"sha1": "2917859c443c68e19f93abcd1315c3c2904cbef9", "size": 104430, "url": "https://launcher.mojang.com/v1/objects/2917859c443c68e19f93abcd1315c3c2904cbef9/cacerts"}}, "executable": false, "type": "file"}, "lib/security/java.policy": {"downloads": {"lzma": {"sha1": "b601c420d02ef3dbd8595453d08fdef91134e8b5", "size": 647, "url": "https://launcher.mojang.com/v1/objects/b601c420d02ef3dbd8595453d08fdef91134e8b5/java.policy"}, "raw": {"sha1": "c0112209a567b3b523cfed7041709f9440227968", "size": 2466, "url": "https://launcher.mojang.com/v1/objects/c0112209a567b3b523cfed7041709f9440227968/java.policy"}}, "executable": false, "type": "file"}, "lib/security/java.security": {"downloads": {"lzma": {"sha1": "531620e82ca0365ce8dc97096bb0ac5a7ace5952", "size": 10959, "url": "https://launcher.mojang.com/v1/objects/531620e82ca0365ce8dc97096bb0ac5a7ace5952/java.security"}, "raw": {"sha1": "5dcc17a168c53d0b366784e520bd4d55aa61ac18", "size": 41528, "url": "https://launcher.mojang.com/v1/objects/5dcc17a168c53d0b366784e520bd4d55aa61ac18/java.security"}}, "executable": false, "type": "file"}, "lib/security/javaws.policy": {"downloads": {"raw": {"sha1": "4384ca5e4d32f7dd86d8baddd1e690730d74e694", "size": 98, "url": "https://launcher.mojang.com/v1/objects/4384ca5e4d32f7dd86d8baddd1e690730d74e694/javaws.policy"}}, "executable": false, "type": "file"}, "lib/security/policy": {"type": "directory"}, "lib/security/policy/limited": {"type": "directory"}, "lib/security/policy/limited/US_export_policy.jar": {"downloads": {"raw": {"sha1": "7d69ea3b385bc067738520f1b5c549e1084be285", "size": 3026, "url": "https://launcher.mojang.com/v1/objects/7d69ea3b385bc067738520f1b5c549e1084be285/US_export_policy.jar"}}, "executable": false, "type": "file"}, "lib/security/policy/limited/local_policy.jar": {"downloads": {"raw": {"sha1": "238b8826e110f58acb2e1959773b0a577cd4d569", "size": 3527, "url": "https://launcher.mojang.com/v1/objects/238b8826e110f58acb2e1959773b0a577cd4d569/local_policy.jar"}}, "executable": false, "type": "file"}, "lib/security/policy/unlimited": {"type": "directory"}, "lib/security/policy/unlimited/US_export_policy.jar": {"downloads": {"raw": {"sha1": "f6fb2af1e87fc622cda194a7d6b5f5f069653ff1", "size": 3023, "url": "https://launcher.mojang.com/v1/objects/f6fb2af1e87fc622cda194a7d6b5f5f069653ff1/US_export_policy.jar"}}, "executable": false, "type": "file"}, "lib/security/policy/unlimited/local_policy.jar": {"downloads": {"raw": {"sha1": "517368ab2cbaf6b42ea0b963f98eeedd996e83e3", "size": 3035, "url": "https://launcher.mojang.com/v1/objects/517368ab2cbaf6b42ea0b963f98eeedd996e83e3/local_policy.jar"}}, "executable": false, "type": "file"}, "lib/security/trusted.libraries": {"downloads": {"raw": {"sha1": "da39a3ee5e6b4b0d3255bfef95601890afd80709", "size": 0, "url": "https://launcher.mojang.com/v1/objects/da39a3ee5e6b4b0d3255bfef95601890afd80709/trusted.libraries"}}, "executable": false, "type": "file"}, "lib/sound.properties": {"downloads": {"lzma": {"sha1": "3b5f7e4ec437d79048af35094290577f483b3fe1", "size": 473, "url": "https://launcher.mojang.com/v1/objects/3b5f7e4ec437d79048af35094290577f483b3fe1/sound.properties"}, "raw": {"sha1": "9afceb218059d981d0fa9f07aad3c5097cf41b0c", "size": 1210, "url": "https://launcher.mojang.com/v1/objects/9afceb218059d981d0fa9f07aad3c5097cf41b0c/sound.properties"}}, "executable": false, "type": "file"}, "lib/tzdb.dat": {"downloads": {"lzma": {"sha1": "39c69339965484afe89c14111baeeb862fdefd97", "size": 32547, "url": "https://launcher.mojang.com/v1/objects/39c69339965484afe89c14111baeeb862fdefd97/tzdb.dat"}, "raw": {"sha1": "b59c07e3619271a3b9861e999f4b138e971baf69", "size": 105734, "url": "https://launcher.mojang.com/v1/objects/b59c07e3619271a3b9861e999f4b138e971baf69/tzdb.dat"}}, "executable": false, "type": "file"}, "man": {"type": "directory"}, "man/ja": {"target": "ja_JP.UTF-8", "type": "link"}, "man/ja_JP.UTF-8": {"type": "directory"}, "man/ja_JP.UTF-8/man1": {"type": "directory"}, "man/ja_JP.UTF-8/man1/java.1": {"downloads": {"lzma": {"sha1": "f9da09710b6c6df23c256e324a0c4df00a0d6ded", "size": 25461, "url": "https://launcher.mojang.com/v1/objects/f9da09710b6c6df23c256e324a0c4df00a0d6ded/java.1"}, "raw": {"sha1": "b0b12a0bb66e6171771ca4b1dfca32fb759bcaec", "size": 148688, "url": "https://launcher.mojang.com/v1/objects/b0b12a0bb66e6171771ca4b1dfca32fb759bcaec/java.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/javaws.1": {"downloads": {"lzma": {"sha1": "6188fae453ca09ccb19be5c9f4d2059926b36267", "size": 2154, "url": "https://launcher.mojang.com/v1/objects/6188fae453ca09ccb19be5c9f4d2059926b36267/javaws.1"}, "raw": {"sha1": "8f39d928870268ace07bedfebd18db1e1d07fc37", "size": 6641, "url": "https://launcher.mojang.com/v1/objects/8f39d928870268ace07bedfebd18db1e1d07fc37/javaws.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/jjs.1": {"downloads": {"lzma": {"sha1": "6e42b989d28b185dc1aab50c0389834e649a37d4", "size": 3452, "url": "https://launcher.mojang.com/v1/objects/6e42b989d28b185dc1aab50c0389834e649a37d4/jjs.1"}, "raw": {"sha1": "e023322a2013912315a2bd1034e6f829a27c76e0", "size": 11365, "url": "https://launcher.mojang.com/v1/objects/e023322a2013912315a2bd1034e6f829a27c76e0/jjs.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/keytool.1": {"downloads": {"lzma": {"sha1": "a78134a4bddd53d684a70aa677e51a215db1c9cb", "size": 20698, "url": "https://launcher.mojang.com/v1/objects/a78134a4bddd53d684a70aa677e51a215db1c9cb/keytool.1"}, "raw": {"sha1": "148583c837eaaf6333ccfd8c9e8df08574e14b0c", "size": 111033, "url": "https://launcher.mojang.com/v1/objects/148583c837eaaf6333ccfd8c9e8df08574e14b0c/keytool.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/orbd.1": {"downloads": {"lzma": {"sha1": "326af0dcbff173ef8aee29163dbe146d7389cc3e", "size": 4225, "url": "https://launcher.mojang.com/v1/objects/326af0dcbff173ef8aee29163dbe146d7389cc3e/orbd.1"}, "raw": {"sha1": "95651622d33c08286858ec337edd3ea72acd93dc", "size": 16092, "url": "https://launcher.mojang.com/v1/objects/95651622d33c08286858ec337edd3ea72acd93dc/orbd.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/pack200.1": {"downloads": {"lzma": {"sha1": "e0eedafa748c61a44e5be4355fe9d44b05048e80", "size": 4293, "url": "https://launcher.mojang.com/v1/objects/e0eedafa748c61a44e5be4355fe9d44b05048e80/pack200.1"}, "raw": {"sha1": "aa21a0ab75707f7fc66e83c7a392e69b37ddf80e", "size": 14482, "url": "https://launcher.mojang.com/v1/objects/aa21a0ab75707f7fc66e83c7a392e69b37ddf80e/pack200.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/policytool.1": {"downloads": {"lzma": {"sha1": "3c766ed12dab58166169d35680c392a6be1814a1", "size": 1380, "url": "https://launcher.mojang.com/v1/objects/3c766ed12dab58166169d35680c392a6be1814a1/policytool.1"}, "raw": {"sha1": "80879c74e072a98fad6f32b3283331aaf9bd002f", "size": 4020, "url": "https://launcher.mojang.com/v1/objects/80879c74e072a98fad6f32b3283331aaf9bd002f/policytool.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/rmid.1": {"downloads": {"lzma": {"sha1": "1e20779d990beacc32a48237777d670fcc47ca14", "size": 4836, "url": "https://launcher.mojang.com/v1/objects/1e20779d990beacc32a48237777d670fcc47ca14/rmid.1"}, "raw": {"sha1": "7e40cb8003d098d6e36f45640b26f979ac94b5c5", "size": 19715, "url": "https://launcher.mojang.com/v1/objects/7e40cb8003d098d6e36f45640b26f979ac94b5c5/rmid.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/rmiregistry.1": {"downloads": {"lzma": {"sha1": "aaf4ffe07e954f8696eef1ecb7a5e244628d0ad9", "size": 1627, "url": "https://launcher.mojang.com/v1/objects/aaf4ffe07e954f8696eef1ecb7a5e244628d0ad9/rmiregistry.1"}, "raw": {"sha1": "c53c52f3ae7a011c135894c9fc51b741e729c33d", "size": 4557, "url": "https://launcher.mojang.com/v1/objects/c53c52f3ae7a011c135894c9fc51b741e729c33d/rmiregistry.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/servertool.1": {"downloads": {"lzma": {"sha1": "3b9e624e9d1cf2959b438a35061162e2100ddecd", "size": 2626, "url": "https://launcher.mojang.com/v1/objects/3b9e624e9d1cf2959b438a35061162e2100ddecd/servertool.1"}, "raw": {"sha1": "50ab8bcd9dd9d0b1a3d81348fbce1c8f82e7189e", "size": 9081, "url": "https://launcher.mojang.com/v1/objects/50ab8bcd9dd9d0b1a3d81348fbce1c8f82e7189e/servertool.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/tnameserv.1": {"downloads": {"lzma": {"sha1": "bb3106ff74c60a76de3d20659b9c2128c70f3bf2", "size": 4478, "url": "https://launcher.mojang.com/v1/objects/bb3106ff74c60a76de3d20659b9c2128c70f3bf2/tnameserv.1"}, "raw": {"sha1": "01e714671ecd1167edcb5310b16a9c59c33c3eaa", "size": 17722, "url": "https://launcher.mojang.com/v1/objects/01e714671ecd1167edcb5310b16a9c59c33c3eaa/tnameserv.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/unpack200.1": {"downloads": {"lzma": {"sha1": "c115a881cf800b08df294df55d9f250ae944e33c", "size": 1973, "url": "https://launcher.mojang.com/v1/objects/c115a881cf800b08df294df55d9f250ae944e33c/unpack200.1"}, "raw": {"sha1": "7c882bba0067367a41ad84868d18793b8a7397a3", "size": 5382, "url": "https://launcher.mojang.com/v1/objects/7c882bba0067367a41ad84868d18793b8a7397a3/unpack200.1"}}, "executable": false, "type": "file"}, "man/man1": {"type": "directory"}, "man/man1/java.1": {"downloads": {"lzma": {"sha1": "06a6b0275c202bf698d73ca71f95618d56d81c15", "size": 25796, "url": "https://launcher.mojang.com/v1/objects/06a6b0275c202bf698d73ca71f95618d56d81c15/java.1"}, "raw": {"sha1": "69fec7a341aa91f18dbdcdb95952dede7e1b689a", "size": 124796, "url": "https://launcher.mojang.com/v1/objects/69fec7a341aa91f18dbdcdb95952dede7e1b689a/java.1"}}, "executable": false, "type": "file"}, "man/man1/javaws.1": {"downloads": {"lzma": {"sha1": "4bae251c6dfb5420f56928815cf80d0b6d517a1f", "size": 1759, "url": "https://launcher.mojang.com/v1/objects/4bae251c6dfb5420f56928815cf80d0b6d517a1f/javaws.1"}, "raw": {"sha1": "e61e44e101b1bc119c2d2d4b10320f38b36a8036", "size": 4897, "url": "https://launcher.mojang.com/v1/objects/e61e44e101b1bc119c2d2d4b10320f38b36a8036/javaws.1"}}, "executable": false, "type": "file"}, "man/man1/jjs.1": {"downloads": {"lzma": {"sha1": "29683cf2bd47015c9461b688749ddffd95f6671d", "size": 1881, "url": "https://launcher.mojang.com/v1/objects/29683cf2bd47015c9461b688749ddffd95f6671d/jjs.1"}, "raw": {"sha1": "78d419bd3a7f3e0802d5220e690429194b5d1beb", "size": 4932, "url": "https://launcher.mojang.com/v1/objects/78d419bd3a7f3e0802d5220e690429194b5d1beb/jjs.1"}}, "executable": false, "type": "file"}, "man/man1/keytool.1": {"downloads": {"lzma": {"sha1": "b67e5126d43713ee3675706724b34061578b42db", "size": 19690, "url": "https://launcher.mojang.com/v1/objects/b67e5126d43713ee3675706724b34061578b42db/keytool.1"}, "raw": {"sha1": "4c976f86057ab779763fcfb98f5702ebef47f629", "size": 86925, "url": "https://launcher.mojang.com/v1/objects/4c976f86057ab779763fcfb98f5702ebef47f629/keytool.1"}}, "executable": false, "type": "file"}, "man/man1/orbd.1": {"downloads": {"lzma": {"sha1": "147064d6f7e027002e296bb246ae572d0ce0495b", "size": 3708, "url": "https://launcher.mojang.com/v1/objects/147064d6f7e027002e296bb246ae572d0ce0495b/orbd.1"}, "raw": {"sha1": "64201e1846fcf1dcc45c786ffeab89426d1c7742", "size": 12180, "url": "https://launcher.mojang.com/v1/objects/64201e1846fcf1dcc45c786ffeab89426d1c7742/orbd.1"}}, "executable": false, "type": "file"}, "man/man1/pack200.1": {"downloads": {"lzma": {"sha1": "fe17486bbe9c58cf4182fa056b9cd124e8295607", "size": 3724, "url": "https://launcher.mojang.com/v1/objects/fe17486bbe9c58cf4182fa056b9cd124e8295607/pack200.1"}, "raw": {"sha1": "26826cf52b89924f2d2a60d6cda798891875eae6", "size": 11623, "url": "https://launcher.mojang.com/v1/objects/26826cf52b89924f2d2a60d6cda798891875eae6/pack200.1"}}, "executable": false, "type": "file"}, "man/man1/policytool.1": {"downloads": {"lzma": {"sha1": "bd154e7c39aca71d15b2098c588866f8d95bc743", "size": 1122, "url": "https://launcher.mojang.com/v1/objects/bd154e7c39aca71d15b2098c588866f8d95bc743/policytool.1"}, "raw": {"sha1": "ab296625155d9a2b25ecc2b4feff2f741b3ad136", "size": 3235, "url": "https://launcher.mojang.com/v1/objects/ab296625155d9a2b25ecc2b4feff2f741b3ad136/policytool.1"}}, "executable": false, "type": "file"}, "man/man1/rmid.1": {"downloads": {"lzma": {"sha1": "6a7da234e7f43ebca5c4ba8cd862fda3be62fbaa", "size": 4255, "url": "https://launcher.mojang.com/v1/objects/6a7da234e7f43ebca5c4ba8cd862fda3be62fbaa/rmid.1"}, "raw": {"sha1": "6f10e214d7950a6a8460524e41dc700f112f89e5", "size": 15979, "url": "https://launcher.mojang.com/v1/objects/6f10e214d7950a6a8460524e41dc700f112f89e5/rmid.1"}}, "executable": false, "type": "file"}, "man/man1/rmiregistry.1": {"downloads": {"lzma": {"sha1": "f40dd17e3a734600ad1828b0c42d3a1685c4c520", "size": 1301, "url": "https://launcher.mojang.com/v1/objects/f40dd17e3a734600ad1828b0c42d3a1685c4c520/rmiregistry.1"}, "raw": {"sha1": "d9a3d23fab689df5bb9a792b88f462f939b49f70", "size": 3449, "url": "https://launcher.mojang.com/v1/objects/d9a3d23fab689df5bb9a792b88f462f939b49f70/rmiregistry.1"}}, "executable": false, "type": "file"}, "man/man1/servertool.1": {"downloads": {"lzma": {"sha1": "74f1e10712202cd3ca0ff5833de05b7ee67092e1", "size": 2307, "url": "https://launcher.mojang.com/v1/objects/74f1e10712202cd3ca0ff5833de05b7ee67092e1/servertool.1"}, "raw": {"sha1": "e6c7b510740ac8681a9bfb5f4ee1f0306125b728", "size": 7237, "url": "https://launcher.mojang.com/v1/objects/e6c7b510740ac8681a9bfb5f4ee1f0306125b728/servertool.1"}}, "executable": false, "type": "file"}, "man/man1/tnameserv.1": {"downloads": {"lzma": {"sha1": "4bec7f4e070d023f124f9352a8971d7acd249a15", "size": 3955, "url": "https://launcher.mojang.com/v1/objects/4bec7f4e070d023f124f9352a8971d7acd249a15/tnameserv.1"}, "raw": {"sha1": "a31dbbe800d49cb371fab9a4b73d22c3bf8799ad", "size": 15747, "url": "https://launcher.mojang.com/v1/objects/a31dbbe800d49cb371fab9a4b73d22c3bf8799ad/tnameserv.1"}}, "executable": false, "type": "file"}, "man/man1/unpack200.1": {"downloads": {"lzma": {"sha1": "f8e73863187929debf2ea6dadefb2995ec7917e7", "size": 1672, "url": "https://launcher.mojang.com/v1/objects/f8e73863187929debf2ea6dadefb2995ec7917e7/unpack200.1"}, "raw": {"sha1": "437f7233d738cb9b822e99003127049005663e0f", "size": 4244, "url": "https://launcher.mojang.com/v1/objects/437f7233d738cb9b822e99003127049005663e0f/unpack200.1"}}, "executable": false, "type": "file"}, "plugin": {"type": "directory"}, "plugin/desktop": {"type": "directory"}, "plugin/desktop/sun_java.desktop": {"downloads": {"lzma": {"sha1": "49ab0ccb54c3be68281d05055bc56a88b1281d3c", "size": 447, "url": "https://launcher.mojang.com/v1/objects/49ab0ccb54c3be68281d05055bc56a88b1281d3c/sun_java.desktop"}, "raw": {"sha1": "79120ee8160ad6f3c9b90c2641fb7edf3af96b5d", "size": 624, "url": "https://launcher.mojang.com/v1/objects/79120ee8160ad6f3c9b90c2641fb7edf3af96b5d/sun_java.desktop"}}, "executable": false, "type": "file"}, "plugin/desktop/sun_java.png": {"downloads": {"raw": {"sha1": "699c41e97a35414e72a80327a54d6e14e874e951", "size": 4351, "url": "https://launcher.mojang.com/v1/objects/699c41e97a35414e72a80327a54d6e14e874e951/sun_java.png"}}, "executable": false, "type": "file"}, "release": {"downloads": {"raw": {"sha1": "cb462682644c0275d94a45b759108815f3112064", "size": 424, "url": "https://launcher.mojang.com/v1/objects/cb462682644c0275d94a45b759108815f3112064/release"}}, "executable": false, "type": "file"}}} \ No newline at end of file diff --git a/src/mojang/testdata/inspect/a/b.txt b/src/mojang/testdata/inspect/a/b.txt new file mode 100755 index 0000000..e69de29 diff --git a/src/mojang/testdata/inspect/a/b/b.txt b/src/mojang/testdata/inspect/a/b/b.txt new file mode 120000 index 0000000..4e19a04 --- /dev/null +++ b/src/mojang/testdata/inspect/a/b/b.txt @@ -0,0 +1 @@ +../b.txt \ No newline at end of file diff --git a/src/mojang/testdata/inspect_win/a/b.txt b/src/mojang/testdata/inspect_win/a/b.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/mojang/testdata/inspect_win/a/b/b.txt b/src/mojang/testdata/inspect_win/a/b/b.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/net/ByteArraySink.h b/src/net/ByteArraySink.h new file mode 100644 index 0000000..20e6764 --- /dev/null +++ b/src/net/ByteArraySink.h @@ -0,0 +1,62 @@ +#pragma once + +#include "Sink.h" + +namespace Net { +/* + * Sink object for downloads that uses an external QByteArray it doesn't own as a target. + */ +class ByteArraySink : public Sink +{ +public: + ByteArraySink(QByteArray *output) + :m_output(output) + { + // nil + }; + + virtual ~ByteArraySink() + { + // nil + } + +public: + JobStatus init(QNetworkRequest & request) override + { + m_output->clear(); + if(initAllValidators(request)) + return Job_InProgress; + return Job_Failed; + }; + + JobStatus write(QByteArray & data) override + { + m_output->append(data); + if(writeAllValidators(data)) + return Job_InProgress; + return Job_Failed; + } + + JobStatus abort() override + { + m_output->clear(); + failAllValidators(); + return Job_Failed; + } + + JobStatus finalize(QNetworkReply &reply) override + { + if(finalizeAllValidators(reply)) + return Job_Finished; + return Job_Failed; + } + + bool hasLocalData() override + { + return false; + } + +private: + QByteArray * m_output; +}; +} diff --git a/src/net/ChecksumValidator.h b/src/net/ChecksumValidator.h new file mode 100644 index 0000000..0d6b19c --- /dev/null +++ b/src/net/ChecksumValidator.h @@ -0,0 +1,55 @@ +#pragma once + +#include "Validator.h" +#include +#include +#include + +namespace Net { +class ChecksumValidator: public Validator +{ +public: /* con/des */ + ChecksumValidator(QCryptographicHash::Algorithm algorithm, QByteArray expected = QByteArray()) + :m_checksum(algorithm), m_expected(expected) + { + }; + virtual ~ChecksumValidator() {}; + +public: /* methods */ + bool init(QNetworkRequest &) override + { + m_checksum.reset(); + return true; + } + bool write(QByteArray & data) override + { + m_checksum.addData(data); + return true; + } + bool abort() override + { + return true; + } + bool validate(QNetworkReply &) override + { + if(m_expected.size() && m_expected != hash()) + { + qWarning() << "Checksum mismatch, download is bad."; + return false; + } + return true; + } + QByteArray hash() + { + return m_checksum.result(); + } + void setExpected(QByteArray expected) + { + m_expected = expected; + } + +private: /* data */ + QCryptographicHash m_checksum; + QByteArray m_expected; +}; +} \ No newline at end of file diff --git a/src/net/Download.cpp b/src/net/Download.cpp new file mode 100644 index 0000000..9ac8b67 --- /dev/null +++ b/src/net/Download.cpp @@ -0,0 +1,296 @@ +/* Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Download.h" + +#include +#include +#include + +#include "FileSystem.h" +#include "ChecksumValidator.h" +#include "ByteArraySink.h" +#include "FileSink.h" + +namespace Net { + +Download::Download():NetAction() +{ + m_status = Job_NotStarted; +} + +Download::Ptr Download::makeByteArray(QUrl url, QByteArray *output, Options options) +{ + Download * dl = new Download(); + dl->m_url = url; + dl->m_options = options; + dl->m_sink.reset(new ByteArraySink(output)); + return dl; +} + +Download::Ptr Download::makeFile(QUrl url, QString path, Options options) +{ + Download * dl = new Download(); + dl->m_url = url; + dl->m_options = options; + dl->m_sink.reset(new FileSink(path)); + return dl; +} + +void Download::addValidator(Validator * v) +{ + m_sink->addValidator(v); +} + +void Download::startImpl() +{ + if(m_status == Job_Aborted) + { + qWarning() << "Attempt to start an aborted Download:" << m_url.toString(); + emit aborted(m_index_within_job); + return; + } + QNetworkRequest request(m_url); + m_status = m_sink->init(request); + switch(m_status) + { + case Job_Finished: + emit succeeded(m_index_within_job); + qDebug() << "Download cache hit " << m_url.toString(); + return; + case Job_InProgress: + qDebug() << "Downloading " << m_url.toString(); + break; + case Job_Failed_Proceed: // this is meaningless in this context. We do need a sink. + case Job_NotStarted: + case Job_Failed: + emit failed(m_index_within_job); + return; + case Job_Aborted: + return; + } + + request.setHeader(QNetworkRequest::UserAgentHeader, "Squeezer"); + + QNetworkReply *rep = m_network->get(request); + + m_reply.reset(rep); + connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64))); + connect(rep, SIGNAL(finished()), SLOT(downloadFinished())); + connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, &QNetworkReply::sslErrors, this, &Download::sslErrors); + connect(rep, &QNetworkReply::readyRead, this, &Download::downloadReadyRead); +} + +void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) +{ + m_total_progress = bytesTotal; + m_progress = bytesReceived; + emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal); +} + +void Download::downloadError(QNetworkReply::NetworkError error) +{ + if(error == QNetworkReply::OperationCanceledError) + { + qCritical() << "Aborted " << m_url.toString(); + m_status = Job_Aborted; + } + else + { + if(m_options & Option::AcceptLocalFiles) + { + if(m_sink->hasLocalData()) + { + m_status = Job_Failed_Proceed; + return; + } + } + // error happened during download. + qCritical() << "Failed " << m_url.toString() << " with reason " << error; + m_status = Job_Failed; + } +} + +void Download::sslErrors(const QList & errors) +{ + int i = 1; + for (auto error : errors) + { + qCritical() << "Download" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); + auto cert = error.certificate(); + qCritical() << "Certificate in question:\n" << cert.toText(); + i++; + } +} + +bool Download::handleRedirect() +{ + QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl(); + if(!redirect.isValid()) + { + if(!m_reply->hasRawHeader("Location")) + { + // no redirect -> it's fine to continue + return false; + } + // there is a Location header, but it's not correct. we need to apply some workarounds... + QByteArray redirectBA = m_reply->rawHeader("Location"); + if(redirectBA.size() == 0) + { + // empty, yet present redirect header? WTF? + return false; + } + QString redirectStr = QString::fromUtf8(redirectBA); + + if(redirectStr.startsWith("//")) + { + /* + * IF the URL begins with //, we need to insert the URL scheme. + * See: https://bugreports.qt.io/browse/QTBUG-41061 + * See: http://tools.ietf.org/html/rfc3986#section-4.2 + */ + redirectStr = m_reply->url().scheme() + ":" + redirectStr; + } + else if(redirectStr.startsWith("/")) + { + /* + * IF the URL begins with /, we need to process it as a relative URL + */ + auto url = m_reply->url(); + url.setPath(redirectStr, QUrl::TolerantMode); + redirectStr = url.toString(); + } + + /* + * Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues. + * FIXME: report Qt bug for this + */ + redirect = QUrl(redirectStr, QUrl::TolerantMode); + if(!redirect.isValid()) + { + qWarning() << "Failed to parse redirect URL:" << redirectStr; + downloadError(QNetworkReply::ProtocolFailure); + return false; + } + qDebug() << "Fixed location header:" << redirect; + } + else + { + qDebug() << "Location header:" << redirect; + } + + m_url = QUrl(redirect.toString()); + qDebug() << "Following redirect to " << m_url.toString(); + start(m_network); + return true; +} + + +void Download::downloadFinished() +{ + // handle HTTP redirection first + if(handleRedirect()) + { + qDebug() << "Download redirected:" << m_url.toString(); + return; + } + + // if the download failed before this point ... + if (m_status == Job_Failed_Proceed) + { + qDebug() << "Download failed but we are allowed to proceed:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emit succeeded(m_index_within_job); + return; + } + else if (m_status == Job_Failed) + { + qDebug() << "Download failed in previous step:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emit failed(m_index_within_job); + return; + } + else if(m_status == Job_Aborted) + { + qDebug() << "Download aborted in previous step:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emit aborted(m_index_within_job); + return; + } + + // make sure we got all the remaining data, if any + auto data = m_reply->readAll(); + if(data.size()) + { + qDebug() << "Writing extra" << data.size() << "bytes to" << m_target_path; + m_status = m_sink->write(data); + } + + // otherwise, finalize the whole graph + m_status = m_sink->finalize(*m_reply.get()); + if (m_status != Job_Finished) + { + qDebug() << "Download failed to finalize:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emit failed(m_index_within_job); + return; + } + m_reply.reset(); + qDebug() << "Download succeeded:" << m_url.toString(); + emit succeeded(m_index_within_job); +} + +void Download::downloadReadyRead() +{ + if(m_status == Job_InProgress) + { + auto data = m_reply->readAll(); + m_status = m_sink->write(data); + if(m_status == Job_Failed) + { + qCritical() << "Failed to process response chunk for " << m_target_path; + } + // qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes"; + } + else + { + qCritical() << "Cannot write to " << m_target_path << ", illegal status" << m_status; + } +} + +} + +bool Net::Download::abort() +{ + if(m_reply) + { + m_reply->abort(); + } + else + { + m_status = Job_Aborted; + } + return true; +} + +bool Net::Download::canAbort() +{ + return true; +} diff --git a/src/net/Download.h b/src/net/Download.h new file mode 100644 index 0000000..d543bc1 --- /dev/null +++ b/src/net/Download.h @@ -0,0 +1,75 @@ +/* Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "NetAction.h" +#include "Validator.h" +#include "Sink.h" + +#include "QObjectPtr.h" + +namespace Net { +class Download : public NetAction +{ + Q_OBJECT + +public: /* types */ + typedef shared_qobject_ptr Ptr; + enum class Option + { + NoOptions = 0, + AcceptLocalFiles = 1 + }; + Q_DECLARE_FLAGS(Options, Option) + +protected: /* con/des */ + explicit Download(); +public: + virtual ~Download(){}; + static Download::Ptr makeByteArray(QUrl url, QByteArray *output, Options options = Option::NoOptions); + static Download::Ptr makeFile(QUrl url, QString path, Options options = Option::NoOptions); + +public: /* methods */ + QString getTargetFilepath() + { + return m_target_path; + } + void addValidator(Validator * v); + bool abort() override; + bool canAbort() override; + +private: /* methods */ + bool handleRedirect(); + +protected slots: + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override; + void downloadError(QNetworkReply::NetworkError error) override; + void sslErrors(const QList & errors); + void downloadFinished() override; + void downloadReadyRead() override; + +public slots: + void startImpl() override; + +private: /* data */ + // FIXME: remove this, it has no business being here. + QString m_target_path; + std::unique_ptr m_sink; + Options m_options; +}; +} + +Q_DECLARE_OPERATORS_FOR_FLAGS(Net::Download::Options) diff --git a/src/net/FileSink.cpp b/src/net/FileSink.cpp new file mode 100644 index 0000000..7e9b892 --- /dev/null +++ b/src/net/FileSink.cpp @@ -0,0 +1,114 @@ +#include "FileSink.h" +#include +#include +#include "FileSystem.h" + +namespace Net { + +FileSink::FileSink(QString filename) + :m_filename(filename) +{ + // nil +} + +FileSink::~FileSink() +{ + // nil +} + +JobStatus FileSink::init(QNetworkRequest& request) +{ + auto result = initCache(request); + if(result != Job_InProgress) + { + return result; + } + // create a new save file and open it for writing + if (!FS::ensureFilePathExists(m_filename)) + { + qCritical() << "Could not create folder for " + m_filename; + return Job_Failed; + } + wroteAnyData = false; + m_output_file.reset(new QSaveFile(m_filename)); + if (!m_output_file->open(QIODevice::WriteOnly)) + { + qCritical() << "Could not open " + m_filename + " for writing"; + return Job_Failed; + } + + if(initAllValidators(request)) + return Job_InProgress; + return Job_Failed; +} + +JobStatus FileSink::initCache(QNetworkRequest &) +{ + return Job_InProgress; +} + +JobStatus FileSink::write(QByteArray& data) +{ + if (!writeAllValidators(data) || m_output_file->write(data) != data.size()) + { + qCritical() << "Failed writing into " + m_filename; + m_output_file->cancelWriting(); + m_output_file.reset(); + wroteAnyData = false; + return Job_Failed; + } + wroteAnyData = true; + return Job_InProgress; +} + +JobStatus FileSink::abort() +{ + m_output_file->cancelWriting(); + failAllValidators(); + return Job_Failed; +} + +JobStatus FileSink::finalize(QNetworkReply& reply) +{ + bool gotFile = false; + QVariant statusCodeV = reply.attribute(QNetworkRequest::HttpStatusCodeAttribute); + bool validStatus = false; + int statusCode = statusCodeV.toInt(&validStatus); + if(validStatus) + { + // this leaves out 304 Not Modified + gotFile = statusCode == 200 || statusCode == 203; + } + // if we wrote any data to the save file, we try to commit the data to the real file. + // if it actually got a proper file, we write it even if it was empty + if (gotFile || wroteAnyData) + { + // ask validators for data consistency + // we only do this for actual downloads, not 'your data is still the same' cache hits + if(!finalizeAllValidators(reply)) + return Job_Failed; + // nothing went wrong... + if (!m_output_file->commit()) + { + qCritical() << "Failed to commit changes to " << m_filename; + m_output_file->cancelWriting(); + return Job_Failed; + } + } + // then get rid of the save file + m_output_file.reset(); + + return finalizeCache(reply); +} + +JobStatus FileSink::finalizeCache(QNetworkReply &) +{ + return Job_Finished; +} + +bool FileSink::hasLocalData() +{ + QFileInfo info(m_filename); + return info.exists() && info.size() != 0; +} +} diff --git a/src/net/FileSink.h b/src/net/FileSink.h new file mode 100644 index 0000000..875fe51 --- /dev/null +++ b/src/net/FileSink.h @@ -0,0 +1,28 @@ +#pragma once +#include "Sink.h" +#include + +namespace Net { +class FileSink : public Sink +{ +public: /* con/des */ + FileSink(QString filename); + virtual ~FileSink(); + +public: /* methods */ + JobStatus init(QNetworkRequest & request) override; + JobStatus write(QByteArray & data) override; + JobStatus abort() override; + JobStatus finalize(QNetworkReply & reply) override; + bool hasLocalData() override; + +protected: /* methods */ + virtual JobStatus initCache(QNetworkRequest &); + virtual JobStatus finalizeCache(QNetworkReply &reply); + +protected: /* data */ + QString m_filename; + bool wroteAnyData = false; + std::unique_ptr m_output_file; +}; +} diff --git a/src/net/Mode.h b/src/net/Mode.h new file mode 100644 index 0000000..9a95f5a --- /dev/null +++ b/src/net/Mode.h @@ -0,0 +1,10 @@ +#pragma once + +namespace Net +{ +enum class Mode +{ + Offline, + Online +}; +} diff --git a/src/net/NetAction.h b/src/net/NetAction.h new file mode 100644 index 0000000..efb2095 --- /dev/null +++ b/src/net/NetAction.h @@ -0,0 +1,122 @@ +/* Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include + +enum JobStatus +{ + Job_NotStarted, + Job_InProgress, + Job_Finished, + Job_Failed, + Job_Aborted, + /* + * FIXME: @NUKE this confuses the task failing with us having a fallback in the form of local data. Clear up the confusion. + * Same could be true for aborted task - the presence of pre-existing result is a separate concern + */ + Job_Failed_Proceed +}; + +class NetAction : public QObject +{ + Q_OBJECT +protected: + explicit NetAction() : QObject(nullptr) {}; + +public: + using Ptr = shared_qobject_ptr; + + virtual ~NetAction() {}; + + bool isRunning() const + { + return m_status == Job_InProgress; + } + bool isFinished() const + { + return m_status >= Job_Finished; + } + bool wasSuccessful() const + { + return m_status == Job_Finished || m_status == Job_Failed_Proceed; + } + + qint64 totalProgress() const + { + return m_total_progress; + } + qint64 currentProgress() const + { + return m_progress; + } + virtual bool abort() + { + return false; + } + virtual bool canAbort() + { + return false; + } + QUrl url() + { + return m_url; + } + +signals: + void started(int index); + void netActionProgress(int index, qint64 current, qint64 total); + void succeeded(int index); + void failed(int index); + void aborted(int index); + +protected slots: + virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0; + virtual void downloadError(QNetworkReply::NetworkError error) = 0; + virtual void downloadFinished() = 0; + virtual void downloadReadyRead() = 0; + +public slots: + void start(shared_qobject_ptr network) { + m_network = network; + startImpl(); + } + +protected: + virtual void startImpl() = 0; + +public: + shared_qobject_ptr m_network; + + /// index within the parent job, FIXME: nuke + int m_index_within_job = 0; + + /// the network reply + unique_qobject_ptr m_reply; + + /// source URL + QUrl m_url; + + qint64 m_progress = 0; + qint64 m_total_progress = 1; + +protected: + JobStatus m_status = Job_NotStarted; +}; diff --git a/src/net/NetJob.cpp b/src/net/NetJob.cpp new file mode 100644 index 0000000..9bad89e --- /dev/null +++ b/src/net/NetJob.cpp @@ -0,0 +1,218 @@ +/* Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "NetJob.h" +#include "Download.h" + +#include + +void NetJob::partSucceeded(int index) +{ + // do progress. all slots are 1 in size at least + auto &slot = parts_progress[index]; + partProgress(index, slot.total_progress, slot.total_progress); + + m_doing.remove(index); + m_done.insert(index); + downloads[index].get()->disconnect(this); + startMoreParts(); +} + +void NetJob::partFailed(int index) +{ + m_doing.remove(index); + auto &slot = parts_progress[index]; + if (slot.failures == 3) + { + m_failed.insert(index); + } + else + { + slot.failures++; + m_todo.enqueue(index); + } + downloads[index].get()->disconnect(this); + startMoreParts(); +} + +void NetJob::partAborted(int index) +{ + m_aborted = true; + m_doing.remove(index); + m_failed.insert(index); + downloads[index].get()->disconnect(this); + startMoreParts(); +} + +void NetJob::partProgress(int index, qint64 bytesReceived, qint64 bytesTotal) +{ + auto &slot = parts_progress[index]; + slot.current_progress = bytesReceived; + slot.total_progress = bytesTotal; + + int done = m_done.size(); + int doing = m_doing.size(); + int all = parts_progress.size(); + + qint64 bytesAll = 0; + qint64 bytesTotalAll = 0; + for(auto & partIdx: m_doing) + { + auto part = parts_progress[partIdx]; + // do not count parts with unknown/nonsensical total size + if(part.total_progress <= 0) + { + continue; + } + bytesAll += part.current_progress; + bytesTotalAll += part.total_progress; + } + + qint64 inprogress = (bytesTotalAll == 0) ? 0 : (bytesAll * 1000) / bytesTotalAll; + auto current = done * 1000 + doing * inprogress; + auto current_total = all * 1000; + // HACK: make sure it never jumps backwards. + // FAIL: This breaks if the size is not known (or is it something else?) and jumps to 1000, so if it is 1000 reset it to inprogress + if(m_current_progress == 1000) { + m_current_progress = inprogress; + } + if(m_current_progress > current) + { + current = m_current_progress; + } + m_current_progress = current; + setProgress(current, current_total); +} + +void NetJob::executeTask() +{ + // hack that delays early failures so they can be caught easier + QMetaObject::invokeMethod(this, "startMoreParts", Qt::QueuedConnection); +} + +void NetJob::startMoreParts() +{ + if(!isRunning()) + { + // this actually makes sense. You can put running downloads into a NetJob and then not start it until much later. + return; + } + // OK. We are actively processing tasks, proceed. + // Check for final conditions if there's nothing in the queue. + if(!m_todo.size()) + { + if(!m_doing.size()) + { + if(!m_failed.size()) + { + emitSucceeded(); + } + else if(m_aborted) + { + emitAborted(); + } + else + { + emitFailed(tr("Job '%1' failed to process:\n%2").arg(objectName()).arg(getFailedFiles().join("\n"))); + } + } + return; + } + // There's work to do, try to start more parts. + while (m_doing.size() < 6) + { + if(!m_todo.size()) + return; + int doThis = m_todo.dequeue(); + m_doing.insert(doThis); + auto part = downloads[doThis]; + // connect signals :D + connect(part.get(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int))); + connect(part.get(), SIGNAL(failed(int)), SLOT(partFailed(int))); + connect(part.get(), SIGNAL(aborted(int)), SLOT(partAborted(int))); + connect(part.get(), SIGNAL(netActionProgress(int, qint64, qint64)), + SLOT(partProgress(int, qint64, qint64))); + part->start(m_network); + } +} + + +QStringList NetJob::getFailedFiles() +{ + QStringList failed; + for (auto index: m_failed) + { + failed.push_back(downloads[index]->url().toString()); + } + failed.sort(); + return failed; +} + +bool NetJob::canAbort() const +{ + bool canFullyAbort = true; + // can abort the waiting? + for(auto index: m_todo) + { + auto part = downloads[index]; + canFullyAbort &= part->canAbort(); + } + // can abort the active? + for(auto index: m_doing) + { + auto part = downloads[index]; + canFullyAbort &= part->canAbort(); + } + return canFullyAbort; +} + +bool NetJob::abort() +{ + bool fullyAborted = true; + // fail all waiting + m_failed.unite(m_todo.toSet()); + m_todo.clear(); + // abort active + auto toKill = m_doing.toList(); + for(auto index: toKill) + { + auto part = downloads[index]; + fullyAborted &= part->abort(); + } + return fullyAborted; +} + +bool NetJob::addNetAction(NetAction::Ptr action) +{ + action->m_index_within_job = downloads.size(); + downloads.append(action); + part_info pi; + parts_progress.append(pi); + partProgress(parts_progress.count() - 1, action->currentProgress(), action->totalProgress()); + + if(action->isRunning()) + { + connect(action.get(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int))); + connect(action.get(), SIGNAL(failed(int)), SLOT(partFailed(int))); + connect(action.get(), SIGNAL(netActionProgress(int, qint64, qint64)), SLOT(partProgress(int, qint64, qint64))); + } + else + { + m_todo.append(parts_progress.size() - 1); + } + return true; +} + +NetJob::~NetJob() = default; diff --git a/src/net/NetJob.h b/src/net/NetJob.h new file mode 100644 index 0000000..638e725 --- /dev/null +++ b/src/net/NetJob.h @@ -0,0 +1,91 @@ +/* Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include "NetAction.h" +#include "Download.h" +#include "tasks/Task.h" +#include "QObjectPtr.h" + +class NetJob; + +class NetJob : public Task +{ + Q_OBJECT +public: + using Ptr = shared_qobject_ptr; + + explicit NetJob(QString job_name, shared_qobject_ptr network) : Task(), m_network(network) + { + setObjectName(job_name); + } + virtual ~NetJob(); + + bool addNetAction(NetAction::Ptr action); + + NetAction::Ptr operator[](int index) + { + return downloads[index]; + } + const NetAction::Ptr at(const int index) + { + return downloads.at(index); + } + NetAction::Ptr first() + { + if (downloads.size()) + return downloads[0]; + return NetAction::Ptr(); + } + int size() const + { + return downloads.size(); + } + QStringList getFailedFiles(); + + bool canAbort() const override; + +private slots: + void startMoreParts(); + +public slots: + virtual void executeTask() override; + virtual bool abort() override; + +private slots: + void partProgress(int index, qint64 bytesReceived, qint64 bytesTotal); + void partSucceeded(int index); + void partFailed(int index); + void partAborted(int index); + +private: + shared_qobject_ptr m_network; + + struct part_info + { + qint64 current_progress = 0; + qint64 total_progress = 1; + int failures = 0; + }; + QList downloads; + QList parts_progress; + QQueue m_todo; + QSet m_doing; + QSet m_done; + QSet m_failed; + qint64 m_current_progress = 0; + bool m_aborted = false; +}; diff --git a/src/net/Sink.h b/src/net/Sink.h new file mode 100644 index 0000000..d367fb1 --- /dev/null +++ b/src/net/Sink.h @@ -0,0 +1,70 @@ +#pragma once + +#include "net/NetAction.h" + +#include "Validator.h" + +namespace Net { +class Sink +{ +public: /* con/des */ + Sink() {}; + virtual ~Sink() {}; + +public: /* methods */ + virtual JobStatus init(QNetworkRequest & request) = 0; + virtual JobStatus write(QByteArray & data) = 0; + virtual JobStatus abort() = 0; + virtual JobStatus finalize(QNetworkReply & reply) = 0; + virtual bool hasLocalData() = 0; + + void addValidator(Validator * validator) + { + if(validator) + { + validators.push_back(std::shared_ptr(validator)); + } + } + +protected: /* methods */ + bool finalizeAllValidators(QNetworkReply & reply) + { + for(auto & validator: validators) + { + if(!validator->validate(reply)) + return false; + } + return true; + } + bool failAllValidators() + { + bool success = true; + for(auto & validator: validators) + { + success &= validator->abort(); + } + return success; + } + bool initAllValidators(QNetworkRequest & request) + { + for(auto & validator: validators) + { + if(!validator->init(request)) + return false; + } + return true; + } + bool writeAllValidators(QByteArray & data) + { + for(auto & validator: validators) + { + if(!validator->write(data)) + return false; + } + return true; + } + +protected: /* data */ + std::vector> validators; +}; +} diff --git a/src/net/Validator.h b/src/net/Validator.h new file mode 100644 index 0000000..59b72a0 --- /dev/null +++ b/src/net/Validator.h @@ -0,0 +1,18 @@ +#pragma once + +#include "net/NetAction.h" + +namespace Net { +class Validator +{ +public: /* con/des */ + Validator() {}; + virtual ~Validator() {}; + +public: /* methods */ + virtual bool init(QNetworkRequest & request) = 0; + virtual bool write(QByteArray & data) = 0; + virtual bool abort() = 0; + virtual bool validate(QNetworkReply & reply) = 0; +}; +} diff --git a/src/pathmatcher/FSTreeMatcher.h b/src/pathmatcher/FSTreeMatcher.h new file mode 100644 index 0000000..b84af29 --- /dev/null +++ b/src/pathmatcher/FSTreeMatcher.h @@ -0,0 +1,21 @@ +#pragma once + +#include "IPathMatcher.h" +#include +#include + +class FSTreeMatcher : public IPathMatcher +{ +public: + virtual ~FSTreeMatcher() {}; + FSTreeMatcher(SeparatorPrefixTree<'/'> & tree) : m_fsTree(tree) + { + } + + bool matches(const QString &string) const override + { + return m_fsTree.covers(string); + } + + SeparatorPrefixTree<'/'> & m_fsTree; +}; diff --git a/src/pathmatcher/IPathMatcher.h b/src/pathmatcher/IPathMatcher.h new file mode 100644 index 0000000..192782d --- /dev/null +++ b/src/pathmatcher/IPathMatcher.h @@ -0,0 +1,13 @@ +#pragma once +#include +#include + +class IPathMatcher +{ +public: + typedef std::shared_ptr Ptr; + +public: + virtual ~IPathMatcher(){}; + virtual bool matches(const QString &string) const = 0; +}; diff --git a/src/pathmatcher/MultiMatcher.h b/src/pathmatcher/MultiMatcher.h new file mode 100644 index 0000000..8bc1b6e --- /dev/null +++ b/src/pathmatcher/MultiMatcher.h @@ -0,0 +1,31 @@ +#include "IPathMatcher.h" +#include +#include + +class MultiMatcher : public IPathMatcher +{ +public: + virtual ~MultiMatcher() {}; + MultiMatcher() + { + } + MultiMatcher &add(Ptr add) + { + m_matchers.append(add); + return *this; + } + + virtual bool matches(const QString &string) const override + { + for(auto iter: m_matchers) + { + if(iter->matches(string)) + { + return true; + } + } + return false; + } + + QList m_matchers; +}; diff --git a/src/pathmatcher/RegexpMatcher.h b/src/pathmatcher/RegexpMatcher.h new file mode 100644 index 0000000..825d488 --- /dev/null +++ b/src/pathmatcher/RegexpMatcher.h @@ -0,0 +1,42 @@ +#include "IPathMatcher.h" +#include + +class RegexpMatcher : public IPathMatcher +{ +public: + virtual ~RegexpMatcher() {}; + RegexpMatcher(const QString ®exp) + { + m_regexp.setPattern(regexp); + m_onlyFilenamePart = !regexp.contains('/'); + } + + RegexpMatcher &caseSensitive(bool cs = true) + { + if(cs) + { + m_regexp.setPatternOptions(QRegularExpression::CaseInsensitiveOption); + } + else + { + m_regexp.setPatternOptions(QRegularExpression::NoPatternOption); + } + return *this; + } + + virtual bool matches(const QString &string) const override + { + if(m_onlyFilenamePart) + { + auto slash = string.lastIndexOf('/'); + if(slash != -1) + { + auto part = string.mid(slash + 1); + return m_regexp.match(part).hasMatch(); + } + } + return m_regexp.match(string).hasMatch(); + } + QRegularExpression m_regexp; + bool m_onlyFilenamePart = false; +}; diff --git a/src/tasks/SequentialTask.cpp b/src/tasks/SequentialTask.cpp new file mode 100644 index 0000000..a66b9d7 --- /dev/null +++ b/src/tasks/SequentialTask.cpp @@ -0,0 +1,55 @@ +#include "SequentialTask.h" + +SequentialTask::SequentialTask(QObject *parent) : Task(parent), m_currentIndex(-1) +{ +} + +void SequentialTask::addTask(Task::Ptr task) +{ + m_queue.append(task); +} + +void SequentialTask::executeTask() +{ + m_currentIndex = -1; + startNext(); +} + +void SequentialTask::startNext() +{ + if (m_currentIndex != -1) + { + Task::Ptr previous = m_queue[m_currentIndex]; + disconnect(previous.get(), 0, this, 0); + } + m_currentIndex++; + if (m_queue.isEmpty() || m_currentIndex >= m_queue.size()) + { + emitSucceeded(); + return; + } + Task::Ptr next = m_queue[m_currentIndex]; + connect(next.get(), SIGNAL(failed(QString)), this, SLOT(subTaskFailed(QString))); + connect(next.get(), SIGNAL(status(QString)), this, SLOT(subTaskStatus(QString))); + connect(next.get(), SIGNAL(progress(qint64, qint64)), this, SLOT(subTaskProgress(qint64, qint64))); + connect(next.get(), SIGNAL(succeeded()), this, SLOT(startNext())); + next->start(); +} + +void SequentialTask::subTaskFailed(const QString &msg) +{ + emitFailed(msg); +} +void SequentialTask::subTaskStatus(const QString &msg) +{ + setStatus(msg); +} +void SequentialTask::subTaskProgress(qint64 current, qint64 total) +{ + if(total == 0) + { + setProgress(0, 100); + return; + } + setProgress(current, total); +} diff --git a/src/tasks/SequentialTask.h b/src/tasks/SequentialTask.h new file mode 100644 index 0000000..027744f --- /dev/null +++ b/src/tasks/SequentialTask.h @@ -0,0 +1,30 @@ +#pragma once + +#include "Task.h" +#include "QObjectPtr.h" + +#include + +class SequentialTask : public Task +{ + Q_OBJECT +public: + explicit SequentialTask(QObject *parent = 0); + virtual ~SequentialTask() {}; + + void addTask(Task::Ptr task); + +protected: + void executeTask(); + +private +slots: + void startNext(); + void subTaskFailed(const QString &msg); + void subTaskStatus(const QString &msg); + void subTaskProgress(qint64 current, qint64 total); + +private: + QQueue m_queue; + int m_currentIndex; +}; diff --git a/src/tasks/Task.cpp b/src/tasks/Task.cpp new file mode 100644 index 0000000..57307b4 --- /dev/null +++ b/src/tasks/Task.cpp @@ -0,0 +1,168 @@ +/* Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Task.h" + +#include + +Task::Task(QObject *parent) : QObject(parent) +{ +} + +void Task::setStatus(const QString &new_status) +{ + if(m_status != new_status) + { + m_status = new_status; + emit status(m_status); + } +} + +void Task::setProgress(qint64 current, qint64 total) +{ + m_progress = current; + m_progressTotal = total; + emit progress(m_progress, m_progressTotal); +} + +void Task::start() +{ + switch(m_state) + { + case State::Inactive: + { + qDebug() << "Task" << describe() << "starting for the first time"; + break; + } + case State::AbortedByUser: + { + qDebug() << "Task" << describe() << "restarting for after being aborted by user"; + break; + } + case State::Failed: + { + qDebug() << "Task" << describe() << "restarting for after failing at first"; + break; + } + case State::Succeeded: + { + qDebug() << "Task" << describe() << "restarting for after succeeding at first"; + break; + } + case State::Running: + { + qWarning() << "The launcher tried to start task" << describe() << "while it was already running!"; + return; + } + } + // NOTE: only fall thorugh to here in end states + m_state = State::Running; + emit started(); + executeTask(); +} + +void Task::emitFailed(QString reason) +{ + // Don't fail twice. + if (!isRunning()) + { + qCritical() << "Task" << describe() << "failed while not running!!!!: " << reason; + return; + } + m_state = State::Failed; + m_failReason = reason; + qCritical() << "Task" << describe() << "failed: " << reason; + emit failed(reason); + emit finished(); +} + +void Task::emitAborted() +{ + // Don't abort twice. + if (!isRunning()) + { + qCritical() << "Task" << describe() << "aborted while not running!!!!"; + return; + } + m_state = State::AbortedByUser; + m_failReason = "Aborted."; + qDebug() << "Task" << describe() << "aborted."; + emit failed(m_failReason); + emit finished(); +} + +void Task::emitSucceeded() +{ + // Don't succeed twice. + if (!isRunning()) + { + qCritical() << "Task" << describe() << "succeeded while not running!!!!"; + return; + } + m_state = State::Succeeded; + qDebug() << "Task" << describe() << "succeeded"; + emit succeeded(); + emit finished(); +} + +QString Task::describe() +{ + QString outStr; + QTextStream out(&outStr); + out << metaObject()->className() << QChar('('); + auto name = objectName(); + if(name.isEmpty()) + { + out << QString("0x%1").arg((quintptr)this, 0, 16); + } + else + { + out << name; + } + out << QChar(')'); + out.flush(); + return outStr; +} + +bool Task::isRunning() const +{ + return m_state == State::Running; +} + +bool Task::isFinished() const +{ + return m_state != State::Running && m_state != State::Inactive; +} + +bool Task::wasSuccessful() const +{ + return m_state == State::Succeeded; +} + +QString Task::failReason() const +{ + return m_failReason; +} + +void Task::logWarning(const QString& line) +{ + qWarning() << line; + m_Warnings.append(line); +} + +QStringList Task::warnings() const +{ + return m_Warnings; +} diff --git a/src/tasks/Task.h b/src/tasks/Task.h new file mode 100644 index 0000000..9cf08db --- /dev/null +++ b/src/tasks/Task.h @@ -0,0 +1,110 @@ +/* Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#include "QObjectPtr.h" + +class Task : public QObject +{ + Q_OBJECT +public: + using Ptr = shared_qobject_ptr; + + enum class State + { + Inactive, + Running, + Succeeded, + Failed, + AbortedByUser + }; + +public: + explicit Task(QObject *parent = 0); + virtual ~Task() {}; + + bool isRunning() const; + bool isFinished() const; + bool wasSuccessful() const; + + /*! + * Returns the string that was passed to emitFailed as the error message when the task failed. + * If the task hasn't failed, returns an empty string. + */ + QString failReason() const; + + virtual QStringList warnings() const; + + virtual bool canAbort() const { return false; } + + QString getStatus() + { + return m_status; + } + + qint64 getProgress() + { + return m_progress; + } + + qint64 getTotalProgress() + { + return m_progressTotal; + } + +protected: + void logWarning(const QString & line); + +private: + QString describe(); + +signals: + void started(); + void progress(qint64 current, qint64 total); + void finished(); + void succeeded(); + void failed(QString reason); + void status(QString status); + +public slots: + virtual void start(); + virtual bool abort() { return false; }; + +protected: + virtual void executeTask() = 0; + +protected slots: + virtual void emitSucceeded(); + virtual void emitAborted(); + virtual void emitFailed(QString reason); + +public slots: + void setStatus(const QString &status); + void setProgress(qint64 current, qint64 total); + +private: + State m_state = State::Inactive; + QStringList m_Warnings; + QString m_failReason = ""; + QString m_status; + int m_progress = 0; + int m_progressTotal = 100; +}; + diff --git a/src/teamcity/BuildIndex.cpp b/src/teamcity/BuildIndex.cpp new file mode 100644 index 0000000..f9be170 --- /dev/null +++ b/src/teamcity/BuildIndex.cpp @@ -0,0 +1,2 @@ +#include "BuildIndex.h" + diff --git a/src/teamcity/BuildIndex.h b/src/teamcity/BuildIndex.h new file mode 100644 index 0000000..bd7c7ce --- /dev/null +++ b/src/teamcity/BuildIndex.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +class BuildIndex : public QObject { +protected: + Q_OBJECT + Q_DISABLE_COPY(BuildIndex) +public: + BuildIndex (QObject * parent = nullptr); + virtual ~BuildIndex() = default; +};