安装原版
装一个 Minecraft 版本,说白了是把五个来源的东西凑齐并落到磁盘上:版本 JSON、client jar、依赖库、资源、natives 原生库。这页讲这五样各自怎么来,以及为什么资源必须全量下。
版本清单
入口是 piston-meta.mojang.com/mc/game/version_manifest_v2.json。清单里每个版本只有 id / type / url / releaseTime,url 指向 piston-data 上那份完整的版本 JSON。
清单缓存优先。ManifestService.GetManifestAsync 先读本地缓存,命中就立刻返回并把 LastLoadFromCache 置 true,网络刷新放到后台。否则每次打开下载页都要等一次境外请求。缓存有两处:统一缓存 ResourceCacheService 的 manifest.vanilla 键,以及老版本留下的 version_manifest_cache.json(MigrateLegacyManifest 一次性搬过去)。
版本 JSON 与 client jar
版本 JSON 从清单给出的 url 拉下来,原样写进 versions/<id>/<id>.json,不重新序列化——重写会丢掉我们不认识的字段。client jar 从 downloads.client 拿,落到 versions/<id>/<id>.jar。downloads.client_mappings 不下载,它只给开发者反编译用。
依赖库
库条目先过规则过滤。RulesAllow 的三个细节:features 非空的规则一律不匹配(避免 --demo 这类试玩参数);os.name 用 Mojang 那套命名,macOS 是 osx 不是 macos;32 位专用规则(arch: "x86")在 64 位进程上不匹配。
过了规则的库,每条都可能有两种来源:Mojang 的 downloads.artifact(带完整 url/sha1/size),或者纯 Maven 坐标(name + url + 顶层 sha1/size)。两种都要落到 libraries/ 下并进下载队列,解析规则见安装 Fabric 与 Quilt。
natives
natives 有两种形态。老版本放在 downloads.classifiers 里,键名按平台候选顺序回退(Windows 是 natives-windows;macOS 是 natives-macos-arm64 → natives-macos → natives-osx;Linux 是 natives-linux[-arm64])。
1.19 之后 Mojang 把 natives 拆成独立的库条目,name 以 :natives-windows 这类 classifier 结尾,downloads.artifact 直接就是那个 jar。安装器认得这种形态,会额外补一条解压任务——不补的话 java.library.path 指向空目录。
解压只取本机扩展名的条目(Windows .dll、macOS .dylib、Linux .so),落到 versions/<id>/natives/。
资源:从索引展开成 objects
资源是唯一一份「一个索引 + 一堆散文件」的结构。版本 JSON 的 assetIndex 给出索引 id、url 和 sha1,索引文件本身是名字到哈希的映射:
{
"objects": {
"minecraft/sounds/ambient/cave.ogg": { "hash": "b7e0b4e6c96d0e05b1e0d0a4b1b4b5b1", "size": 4123 },
"minecraft/lang/en_us.json": { "hash": "0d3d2b7b2c1f4e5a6b7c8d9e0f1a2b3c", "size": 182340 }
}
}安装器把它展开成 objects 目录,哈希前两位做一级目录:
files.Add((
$"https://resources.download.minecraft.net/{hashPrefix}/{obj.Hash}",
Path.Combine(objectsDir, hashPrefix, obj.Hash),
obj.Hash, obj.Size));assets/
├── indexes/19.json
├── objects/0d/0d3d2b… ← 哈希前两位分层
└── virtual/ ← 启动时 --game_assets 指向这里资源必须全量下,不能按需拉。启动参数里给的是 --assetsDir 和 --assetIndex,游戏自己按名字去 objects/<前两位>/<hash> 读文件,它不会告诉启动器「还缺哪个」。少下一个就是运行时缺纹理。哪怕资源占了大半个下载量,也只能整份拉。
索引损坏无法自愈
if (!File.Exists(indexPath))
await _downloader.DownloadFileAsync(index.Url, indexPath, index.Sha1, ...);文件在就不重下,也不校验 sha1。 这么设计是为了重复安装时不白下索引,代价是索引一旦损坏,重装也察觉不到,后续资源都会按错的哈希去找文件。自愈只有一个办法:删掉 assets/indexes/<id>.json 再重装。
预览功能必须 jar 优先
实验室的投影预览读 assets/objects,但现代版本的索引里没有 blockstates/models(实测索引 34 只有 sounds/lang 和几张纹理)。所以读取顺序必须是「客户端 jar 优先,索引兜底」,反过来做就只能拿到近似色。详见 实验室。
下载并发
所有文件最后进 DownloadBatchAsync,并发度由 settings.DownloadConcurrency 控制,默认 16。批处理内部会先做三级复用(本地已有跳过 → 从其他实例复制 → 局域网 P2P),见下载引擎总览。资源对象按哈希去重:同一资源在多个版本里引用同一个 URL 和同一个目标路径,DistinctBy(f => f.Dest) 直接去重。
版本目录名 ≠ 版本号
装同一版本的第二份时目录是 1.20.1-2,但下载到的 JSON 里 id 还是 1.20.1。我们用 InstanceLayout.PatchJsonId 改成目录名,并记一份 starcloudBaseVersion 留着原版号。细节见版本隔离与实例档案。