加载器版本怎么选
四家加载器(Forge / NeoForge / Fabric / Quilt)的版本列表来源各不相同:有的是全量 Maven 元数据,有的是只给「推荐/最新」两个的 promotions 文件,有的是 meta API 的简单数组。这页讲各自怎么取、离线怎么办、以及为什么推荐版不一定等于最新版。
四条数据源
Forge —— maven 元数据 + promotions 文件,见安装 Forge 与 NeoForge。列表是全量的,一个 MC 版本下可能有上百个构建。
NeoForge —— maven.neoforged.net 的 releases API,一次返回该构件下的全部版本号,自己按前缀过滤再排序。
Fabric / Quilt —— meta API 返回一个简单数组:
https://meta.fabricmc.net/v2/versions/loader/{mcVersion}
https://meta.quiltmc.org/v3/versions/loader/{mcVersion}每项的形状是:
{
"loader": {
"version": "0.16.9",
"stable": true
}
}我们把 stable 映射成「预览版」标记:
return loaders
.Select(l => new LoaderBuild(l.Version, l.Loader.Stable ? null : Loc.T("预览版"), l.Loader.Stable))
.ToList();Fabric 和 Quilt 的 meta v2/v3 响应结构相同,所以 FabricLoaderEntry 一个类复用两个 API。
推荐版 ≠ 最新版
这是最容易误解的一点。Forge 的 promotions 文件里 recommended 和 latest 是两个独立字段,经常不相等:
{
"1.8-recommended": "11.14.4.1563",
"1.8-latest": "11.14.4.1577",
"1.20.1-recommended": "47.4.28",
"1.20.1-latest": "47.4.28"
}latest 是「构建号排序最靠后的那个」,recommended 是 Forge 团队自己标记的稳定版本。1.8 这种老版本上,最新构建往往已经修了推荐版里的问题,但也可能引入了新问题——对老版本我们更信推荐版。
推荐卡的选择逻辑因此是分层的:
LoaderKind.Forge => builds.FirstOrDefault(b => b.Note == "推荐")
?? builds.FirstOrDefault(b => b.Note == "最新"),
_ => builds.FirstOrDefault(),NeoForge 没有 promotions 文件,只有构建列表,所以取第一个非 beta 作为「最新稳定版」。Fabric / Quilt 同理,取列表第一项。
标记只在列表里排一次
promos 里的值会被翻译成构建全名再比对:
v == $"{prefix}{recommended}" ? "推荐"
: v == $"{prefix}{latest}" ? "最新" : nullrecommended 和 latest 恰好指向同一个构建时(1.20.1 就是),推荐 优先,最新 标记就消失了。这是有意的——同一个构建挂两个标签没意义,但界面上的「推荐」比「最新」更能引导用户选稳的那个。
离线:缓存优先
加载器列表要在详情页立刻显示,不能等网络。四家加载器的拉取是并行的:
var forgeTask = SafeBuildsAsync(() => _s.Forge.ListForgeBuildsAsync(id));
var neoTask = SafeBuildsAsync(() => _s.Forge.ListNeoForgeBuildsAsync(id));
var fabricTask = SafeFabricBuildsAsync(id);
var quiltTask = SafeQuiltBuildsAsync(id);
await Task.WhenAll(forgeTask, neoTask, fabricTask, quiltTask);渲染分两段。先渲染本地缓存,界面立刻有东西看;网络回来后再覆盖:
foreach (var kind in LoaderKeys)
{
_builds[kind] = LoaderBuildCache.Read(dotMc, LoaderInfo[kind].CacheKind, id);
}kind 到缓存文件名的映射:
private static readonly Dictionary<LoaderKind, (string Name, string CacheKind)> LoaderInfo = new()
{
[LoaderKind.Forge] = ("Forge", "forge"),
[LoaderKind.NeoForge] = ("NeoForge", "neoforge"),
[LoaderKind.Fabric] = ("Fabric", "fabric"),
[LoaderKind.Quilt] = ("Quilt", "quilt"),
};缓存路径:
<dotMc>/cache/loaders/<kind>-<mcVersion>.json比如 <.minecraft>/cache/loaders/forge-1.20.1.json。注意它在游戏目录里,不在启动器数据目录——缓存跟着游戏目录走,换游戏目录就是另一份缓存。
写入只在拉到非空列表时做:
if (fetched.Count > 0)
{
_builds[kind] = fetched;
LoaderBuildCache.Write(_s.Settings.DotMinecraft, LoaderInfo[kind].CacheKind, id, fetched);
}拉取失败(SafeBuildsAsync 吞掉异常返回空列表)时,缓存原样保留,界面继续显示上次的内容。空列表不会覆盖缓存——否则一次网络抖动就把离线可用的列表清了。
缓存损坏静默降级
LoaderBuildCache.Read 整个包在 try 里,任何异常都返回 null:
public static List<LoaderBuild>? Read(string dotMinecraft, string kind, string mcVersion)
{
try
{
var json = File.ReadAllText(PathFor(dotMinecraft, kind, mcVersion));
return JsonSerializer.Deserialize<List<LoaderBuild>>(json) is { Count: > 0 } b ? b : null;
}
catch { return null; }
}返回 null 的含义是「这个加载器这个版本没有可用列表」,调用方按空处理。文件被截断、被别的程序写坏、编码不对——都表现为缓存消失,而不是报错。
写缓存同样吞异常:缓存写失败不影响主流程,用户下次联网重新拉就是了。
选完之后
选中某个构建之后:
- Forge / NeoForge 走官方安装器,URL 是拼出来的(
ForgeInstallerUrl/NeoForgeInstallerUrl),前置条件见安装 Forge 与 NeoForge - Fabric / Quilt 走 meta profile 合并,见安装 Fabric 与 Quilt
同一次安装可以同时选 Fabric 和 Forge 吗?不能——下载页的四个抽屉是互斥选装的,选了一个别的就锁住。技术上两者可以共存(Forge 可以跑 Fabric mod),但那需要额外的依赖处理,不是我们默认支持的路径。