#include "fakemojang.h" #include "fixtures.h" #include "minecraftversion.h" #include #include namespace { const QString kMeta = QStringLiteral("https://piston-meta.mojang.com/v1/packages/"); const QString kData = QStringLiteral("https://piston-data.mojang.com/v1/objects/"); const QString kLibraries = QStringLiteral("https://libraries.minecraft.net/"); const QString kResources = QStringLiteral("https://resources.download.minecraft.net/"); QJsonObject download(const QString &url, const QByteArray &data, const QString &path = {}) { QJsonObject object{{"url", url}, {"sha1", Fixtures::sha1(data)}, {"size", data.size()}}; if (!path.isEmpty()) object.insert("path", path); return object; } } // namespace FakeMojang::FakeMojang(FakeInternet &net) : m_net(net) { } QString FakeMojang::manifestUrl() { return QStringLiteral("https://launchermeta.mojang.com/mc/game/version_manifest_v2.json"); } QString FakeMojang::addObject(const QString &relativePath, const QByteArray &data) { QString url; if (relativePath.startsWith(QLatin1String("libraries/"))) url = kLibraries + relativePath.mid(int(sizeof("libraries/") - 1)); else if (relativePath.startsWith(QLatin1String("assets/objects/"))) url = kResources + relativePath.mid(int(sizeof("assets/objects/") - 1)); else url = kData + Fixtures::sha1(data) + u'/' + relativePath.section(u'/', -1); m_files.insert(relativePath, {url, data}); m_net.route(url, FakeResponse::bytes(data)); return url; } void FakeMojang::overrideFile(const QString &relativePath, const FakeResponse &response) { m_net.route(m_files.value(relativePath).url, response); } QString FakeMojang::fileUrl(const QString &relativePath) const { return m_files.value(relativePath).url; } void FakeMojang::publish(const VersionSpec &spec) { QHash expected; QJsonObject json{{"id", spec.id}, {"type", spec.type}, {"releaseTime", spec.releaseTime}}; if (!spec.inheritsFrom.isEmpty()) { json.insert("inheritsFrom", spec.inheritsFrom); json.insert("mainClass", "net.fabricmc.loader.impl.launch.knot.KnotClient"); const QString libPath = QStringLiteral("net/fabricmc/fabric-loader/0.16.10/fabric-loader-0.16.10.jar"); const QByteArray libData = "fabric-loader-" + spec.id.toUtf8(); const QString url = addObject("libraries/" + libPath, libData); json.insert("libraries", QJsonArray{QJsonObject{ {"name", "net.fabricmc:fabric-loader:0.16.10"}, {"downloads", QJsonObject{{"artifact", download(url, libData, libPath)}}}}}); expected.insert("libraries/" + libPath, libData); } else { json.insert("mainClass", "net.minecraft.client.main.Main"); json.insert("arguments", QJsonObject{ {"game", QJsonArray{"--username", "${auth_player_name}", "--version", "${version_name}", "--gameDir", "${game_directory}", "--assetsDir", "${assets_root}", "--assetIndex", "${assets_index_name}", "--uuid", "${auth_uuid}", "--accessToken", "${auth_access_token}", "--userType", "${user_type}", "--versionType", "${version_type}"}}, {"jvm", QJsonArray{"-Djava.library.path=${natives_directory}", "-cp", "${classpath}"}}}); json.insert("javaVersion", QJsonObject{{"component", "java-runtime-gamma"}, {"majorVersion", spec.javaMajor}}); // client.jar const QString clientPath = "versions/" + spec.id + u'/' + spec.id + ".jar"; const QByteArray clientData = "client-jar-of-" + spec.id.toUtf8(); const QString clientUrl = addObject(clientPath, clientData); json.insert("downloads", QJsonObject{{"client", download(clientUrl, clientData)}}); expected.insert(clientPath, clientData); // Обычная библиотека и натив под текущую платформу. const QString native = VersionLoader::nativeClassifier(); const QString libPath = QStringLiteral("com/mojang/brigadier/1.1.8/brigadier-1.1.8.jar"); const QByteArray libData = "brigadier"; const QString nativePath = QStringLiteral("org/lwjgl/lwjgl/3.3.1/lwjgl-3.3.1-%1.jar").arg(native); const QByteArray nativeData = Fixtures::zipBytes({{"liblwjgl.so", "so"}, {"lwjgl.dll", "dll"}, {"liblwjgl.dylib", "dylib"}}); const QString libUrl = addObject("libraries/" + libPath, libData); const QString nativeUrl = addObject("libraries/" + nativePath, nativeData); json.insert("libraries", QJsonArray{ QJsonObject{{"name", "com.mojang:brigadier:1.1.8"}, {"downloads", QJsonObject{{"artifact", download(libUrl, libData, libPath)}}}}, QJsonObject{{"name", "org.lwjgl:lwjgl:3.3.1:" + native}, {"downloads", QJsonObject{{"artifact", download(nativeUrl, nativeData, nativePath)}}}, {"rules", QJsonArray{QJsonObject{{"action", "allow"}, {"os", QJsonObject{{"name", VersionLoader::osName()}}}}}}}}); expected.insert("libraries/" + libPath, libData); expected.insert("libraries/" + nativePath, nativeData); // Ассеты. const QString indexId = spec.legacyAssets ? QStringLiteral("legacy") : spec.id + "-assets"; QJsonObject objects; qint64 total = 0; for (const QString &name : {QStringLiteral("minecraft/sounds/click.ogg"), QStringLiteral("icons/icon_16x16.png")}) { const QByteArray data = (name + "@" + spec.id).toUtf8(); const QString hash = Fixtures::sha1(data); const QString objectPath = "assets/objects/" + hash.left(2) + u'/' + hash; addObject(objectPath, data); objects.insert(name, QJsonObject{{"hash", hash}, {"size", data.size()}}); expected.insert(objectPath, data); if (spec.legacyAssets) expected.insert("assets/virtual/legacy/" + name, data); total += data.size(); } QJsonObject index{{"objects", objects}}; if (spec.legacyAssets) index.insert("virtual", true); const QByteArray indexData = QJsonDocument(index).toJson(QJsonDocument::Compact); const QString indexPath = "assets/indexes/" + indexId + ".json"; const QString indexUrl = addObject(indexPath, indexData); QJsonObject assetIndex = download(indexUrl, indexData); assetIndex.insert("id", indexId); assetIndex.insert("totalSize", total); json.insert("assetIndex", assetIndex); json.insert("assets", indexId); expected.insert(indexPath, indexData); // Конфиг логирования. const QByteArray logData = ""; const QString logPath = QStringLiteral("assets/log_configs/client-1.12.xml"); const QString logUrl = addObject(logPath, logData); QJsonObject logFile = download(logUrl, logData); logFile.insert("id", "client-1.12.xml"); json.insert("logging", QJsonObject{{"client", QJsonObject{ {"argument", "-Dlog4j.configurationFile=${path}"}, {"file", logFile}, {"type", "log4j2-xml"}}}}); expected.insert(logPath, logData); } const QByteArray jsonData = QJsonDocument(json).toJson(QJsonDocument::Compact); m_versionJson.insert(spec.id, jsonData); expected.insert("versions/" + spec.id + u'/' + spec.id + ".json", jsonData); m_expected.insert(spec.id, expected); m_net.route(versionJsonUrl(spec.id), FakeResponse::json(jsonData)); m_specs.removeIf([&](const VersionSpec &s) { return s.id == spec.id; }); m_specs << spec; routeManifest(); } QString FakeMojang::versionJsonUrl(const QString &id) const { return kMeta + Fixtures::sha1(m_versionJson.value(id)) + u'/' + id + ".json"; } QByteArray FakeMojang::versionJson(const QString &id) const { return m_versionJson.value(id); } void FakeMojang::routeManifest() { QJsonArray versions; for (const VersionSpec &spec : std::as_const(m_specs)) { if (!spec.listed) continue; versions << QJsonObject{{"id", spec.id}, {"type", spec.type}, {"url", versionJsonUrl(spec.id)}, {"sha1", Fixtures::sha1(m_versionJson.value(spec.id))}, {"releaseTime", spec.releaseTime}, {"time", spec.releaseTime}}; } m_net.route(manifestUrl(), FakeResponse::json(QJsonObject{{"versions", versions}})); } QHash FakeMojang::expectedFiles(const QString &id) const { return m_expected.value(id); }