Skip to content

加载器版本怎么选 ​

四家加载器(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}

每项的形状是:

json
{
  "loader": {
    "version": "0.16.9",
    "stable": true
  }
}

我们把 stable 映射成「预览版」标记:

csharp
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 是两个独立字段,经常不相等:

json
{
  "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 这种老版本上,最新构建往往已经修了推荐版里的问题,但也可能引入了新问题——对老版本我们更信推荐版。

推荐卡的选择逻辑因此是分层的:

csharp
LoaderKind.Forge => builds.FirstOrDefault(b => b.Note == "推荐")
                 ?? builds.FirstOrDefault(b => b.Note == "最新"),
_ => builds.FirstOrDefault(),

NeoForge 没有 promotions 文件,只有构建列表,所以取第一个非 beta 作为「最新稳定版」。Fabric / Quilt 同理,取列表第一项。

标记只在列表里排一次

promos 里的值会被翻译成构建全名再比对:

csharp
v == $"{prefix}{recommended}" ? "推荐"
: v == $"{prefix}{latest}" ? "最新" : null

recommended 和 latest 恰好指向同一个构建时(1.20.1 就是),推荐 优先,最新 标记就消失了。这是有意的——同一个构建挂两个标签没意义,但界面上的「推荐」比「最新」更能引导用户选稳的那个。

离线:缓存优先 ​

加载器列表要在详情页立刻显示,不能等网络。四家加载器的拉取是并行的:

csharp
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);

渲染分两段。先渲染本地缓存,界面立刻有东西看;网络回来后再覆盖:

csharp
foreach (var kind in LoaderKeys)
{
    _builds[kind] = LoaderBuildCache.Read(dotMc, LoaderInfo[kind].CacheKind, id);
}

kind 到缓存文件名的映射:

csharp
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。注意它在游戏目录里,不在启动器数据目录——缓存跟着游戏目录走,换游戏目录就是另一份缓存。

写入只在拉到非空列表时做:

csharp
if (fetched.Count > 0)
{
    _builds[kind] = fetched;
    LoaderBuildCache.Write(_s.Settings.DotMinecraft, LoaderInfo[kind].CacheKind, id, fetched);
}

拉取失败(SafeBuildsAsync 吞掉异常返回空列表)时,缓存原样保留,界面继续显示上次的内容。空列表不会覆盖缓存——否则一次网络抖动就把离线可用的列表清了。

缓存损坏静默降级

LoaderBuildCache.Read 整个包在 try 里,任何异常都返回 null:

csharp
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 的含义是「这个加载器这个版本没有可用列表」,调用方按空处理。文件被截断、被别的程序写坏、编码不对——都表现为缓存消失,而不是报错。

写缓存同样吞异常:缓存写失败不影响主流程,用户下次联网重新拉就是了。

选完之后 ​

选中某个构建之后:

同一次安装可以同时选 Fabric 和 Forge 吗?不能——下载页的四个抽屉是互斥选装的,选了一个别的就锁住。技术上两者可以共存(Forge 可以跑 Fabric mod),但那需要额外的依赖处理,不是我们默认支持的路径。

相关链接:安装 Forge 与 NeoForge、安装 Fabric 与 Quilt、安装原版

基于 MIT 许可证(有修改)开源