Files

194 lines
8.9 KiB
C++
Raw Permalink Normal View History

2026-10-12 13:14:08 +03:00
#include "fakemojang.h"
#include "fixtures.h"
#include "minecraftversion.h"
#include <QJsonArray>
#include <QJsonDocument>
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<QString, QByteArray> 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 = "<Configuration/>";
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<QString, QByteArray> FakeMojang::expectedFiles(const QString &id) const
{
return m_expected.value(id);
}