fix(download): LiteLoader 不再套 bmclapi 改写,Central 库接上 bmclapi/maven

bmclapi-liteloader 改成 enabled=false(规则保留作形状留档):2026-11-10 实探
`apply()` 的真实产物 https://bmclapi2.bangbang93.com/maven/versions.json 404 / 9 B、
/maven/com/mumfrey/liteloader/1.7.10/liteloader-1.7.10_04.jar 404 / 9 B、
/maven/com/mumfrey/liteloader/{maven-metadata.xml,1.12.2-SNAPSHOT/maven-metadata.xml}
全 404 / 9 B;官方 dl.liteloader.com 的 versions.json 200 / 49129 B、artifact
200 / 533903 B。引擎遇 404 只会白丢一次请求再前进(NotFound 不重试),官方不可达时
更是直接失败,所以镜像优先用户不该再为每个 LiteLoader 文件试一次镜像。

新增 bmclapi-maven-central(repo1.maven.org/maven2 → bmclapi/maven):bmclapi 的
/maven 不只是 libraries.minecraft.net 的镜像,Central 的路径逐字节一致(asm-all 5.2
247787 B、gson 2.10.1 283367 B、log4j-core 2.17.1 1790452 B,sha1 逐一相同),不存在
的坐标两侧都是 404(镜像回 9 B 纯文本,没有「200 + HTML」假阳性)。

快照仓 repo.mumfrey.com 不加规则:bmclapi /maven、maven.aliyun.com/repository/central、
repo.huaweicloud.com/repository/maven 对 com/mumfrey/liteloader/1.12.2-SNAPSHOT/
maven-metadata.xml 全是 404,只有官方 200 / 358 B,所以只登记探测结果、不铺未验证的规则。

测试:新增 liteloader_urls_stay_official_only(镜像优先模式也只出官方一条,把 enabled
翻回 true 的变异实测会红);library_urls_expand_to_the_maven_subpath_on_bmclapi 补
Central 用例;形态判据的 EXPECTED 表加 bmclapi-maven-central。不探外网。

version 保持 3 不升 4:download/jdk_sources.rs 与 tests/game/java_vendor_chain.rs 两处
pin 了 version == 3,后者不在本 mission 的 scope(第一轮 workspace test 就是被这条 pin
抓红的),升版留给专门一轮;规则改动与探测记录写进 mirrors.json 的 notes。
This commit is contained in:
AskaEth committed 2026-11-10 13:56:58 +08:00
1 parent 54995a4af8
commit 146eb3d8db
2 files changed
+55 -33

No files matched your search

+42 -31
View File
@@ -30,29 +30,21 @@ fn prefix_rule_preserves_sub_path() {
.expect("libraries 规则缺失");
let candidate = rule.apply(LIBRARIES_URL).expect("重写失败");
assert_eq!(candidate.source, "bmclapi");
// 子路径逐字保留(`/maven` 是镜像侧的固定前缀,不属于子路径)。
//
// M210/D1:这条断言此前写的是「换成 `https://bmclapi2.bangbang93.com` 就完事」,等于把
// **丢 inject 的 bug 钉成了期望**——实测 70/70 个库 404(无 `/maven` 回 404 + 9 B,
// 带 `/maven` 回 200、sha1 与官方逐字节一致)。现在把 `/maven` 写进期望值。
assert_eq!(
candidate.url,
LIBRARIES_URL.replace(
"https://libraries.minecraft.net",
"https://bmclapi2.bangbang93.com/maven"
)
// 子路径逐字保留(`/maven` 是镜像侧的固定前缀,不属于子路径)。M210/D1:这条断言此前写成
// 「换成 `https://bmclapi2.bangbang93.com` 就完事」,把**丢 inject 的 bug 钉成了期望**——实测
// 无 `/maven` 是 70/70 个库 404 + 9 B,带 `/maven` 回 200 且 sha1 与官方逐字节一致。
let expected = LIBRARIES_URL.replace(
"https://libraries.minecraft.net",
"https://bmclapi2.bangbang93.com/maven",
);
assert_eq!(candidate.url, expected);
}
/// **守卫(M210/D1)**:每一条「给 libraries 用」的镜像规则,镜像前缀都必须带 `/maven`。
///
/// 官方库域名(`libraries.minecraft.net`)与各 loader 的 maven 在 BMCLAPI 上统一挂在 `/maven`
/// 下;`apply()` 是纯前缀替换,`to` 里少了这一段就会把 `com/google/guava/...` 直接拼到镜像根上。
///
/// 抓得住:删掉/改错任一条库镜像规则的 `inject`(本用例对 `bmclapi-libraries` 的变异会红)。
/// 抓不住:`/maven` **写歪**(如 `/maven2`、`/maven/` 之外的等价路径)只能靠行为用例
/// [`library_urls_expand_to_the_maven_subpath_on_bmclapi`];路径结构本就不同的镜像表(资源对象走
/// `/assets`、元数据走 `/fabric-meta`)不在此判据内;新增但 `from` 不像 maven 仓库的库镜像也不在此列。
/// **守卫(M210/D1)**:每一条「给 libraries 用」的镜像规则,镜像前缀都必须带 `/maven`。官方库域名与
/// 各 loader 的 maven 在 BMCLAPI 上都挂在 `/maven` 下,而 `apply()` 是纯前缀替换——`to` 里少了这一段
/// 就会把 `com/google/guava/...` 直接拼到镜像根上。抓得住:删掉/改错任一条库镜像规则的 `inject`;
/// 抓不住:`/maven` **写歪**(如 `/maven2`)只能靠行为用例 [`library_urls_expand_to_the_maven_subpath_on_bmclapi`];
/// `/assets`、`/fabric-meta` 这类结构本就不同的镜像表,以及 `from` 不像 maven 仓库的新库镜像,都不在列。
#[test]
fn every_library_mirror_rule_carries_the_maven_segment() {
let table = MirrorTable::builtin();
@@ -63,14 +55,14 @@ fn every_library_mirror_rule_carries_the_maven_segment() {
|| from.contains("maven")
|| host_of(from) == Some("libraries.minecraft.net")
}
// 判据覆盖的规则 id(内置表里就是这 5 条;`bmclapi-liteloader` 的 `from` 不像 maven 仓库,不在列,
// 它指向的镜像路径另有一个「镜像上不存在」的观察,见 M210 报告的 §5)。
const EXPECTED: [&str; 5] = [
// 判据覆盖的规则 id(内置表里就是这 6 条;`bmclapi-liteloader` 的 `from` 不像 maven 仓库,且已因 M225 关闭,不在列)。
const EXPECTED: [&str; 6] = [
"bmclapi-libraries",
"bmclapi-forge-maven",
"bmclapi-forge-maven-net",
"bmclapi-neoforge",
"bmclapi-fabric-maven",
"bmclapi-maven-central",
];
let mut checked: Vec<&str> = Vec::new();
for rule in table.rules.iter().filter(|rule| serves_maven(&rule.from)) {
@@ -99,11 +91,10 @@ fn every_library_mirror_rule_carries_the_maven_segment() {
);
}
/// **行为守卫(M210/D1)**:真实库 URL 展开后必须落在镜像的 `/maven/<子路径>` 上。
///
/// 与上一条互为补充:那条看规则形态,这条看用户实际会请求到的 URL。
/// 抓得住:`inject` 丢失、`to` 里手写 `/maven` 被删、或 `from`/`to` 被改成拼不出 `/maven` 的形态。
/// 抓不住:只覆盖这 4 个官方 maven 根(各处未必都写全);镜像侧真实可达性(要网络,留给 e2e)。
/// **行为守卫(M210/D1)**:真实库 URL 展开后必须落在镜像的 `/maven/<子路径>` 上。与上一条互为
/// 补充:那条看规则形态,这条看用户实际会请求到的 URL。抓得住:`inject` 丢失、`to` 里手写 `/maven`
/// 被删、或 `from`/`to` 被改成拼不出 `/maven` 的形态;抓不住:只覆盖这 5 个官方 maven 根(各处未必
/// 都写全),镜像侧真实可达性要网络(留给 e2e)。
#[test]
fn library_urls_expand_to_the_maven_subpath_on_bmclapi() {
let table = MirrorTable::builtin();
@@ -125,6 +116,11 @@ fn library_urls_expand_to_the_maven_subpath_on_bmclapi() {
"https://maven.neoforged.net/releases/net/neoforged/neoforge/21.1.80/neoforge-21.1.80-installer.jar",
"https://bmclapi2.bangbang93.com/maven/net/neoforged/neoforge/21.1.80/neoforge-21.1.80-installer.jar",
),
// M225:LiteLoader 的库来自 Central(`net.minecraft:launchwrapper` 才是 Mojang 库仓)。
(
"https://repo1.maven.org/maven2/org/ow2/asm/asm-all/5.2/asm-all-5.2.jar",
"https://bmclapi2.bangbang93.com/maven/org/ow2/asm/asm-all/5.2/asm-all-5.2.jar",
),
];
for (official, expected) in cases {
let candidates = table.expand(official, ResourceKind::Library, &region);
@@ -139,6 +135,22 @@ fn library_urls_expand_to_the_maven_subpath_on_bmclapi() {
}
}
/// **守卫(M225)**:LiteLoader 不套 bmclapi 改写——镜像侧 `versions.json` 与 artifact 实测 404 / 9 B,
/// 官方 200(curl 记录见 `mirrors.json` 的 notes)。抓得住:`bmclapi-liteloader.enabled` 被翻回 true、
/// 或有人给快照仓补来源不明的镜像规则;抓不住:镜像侧哪天真的补上了(那时改期望与 notes,别删测试)。
#[test]
fn liteloader_urls_stay_official_only() {
let (table, region) = (MirrorTable::builtin(), RegionProfile::mirror_first(0));
for url in [
"https://dl.liteloader.com/versions/versions.json",
"https://dl.liteloader.com/versions/com/mumfrey/liteloader/1.7.10/liteloader-1.7.10_04.jar",
"https://repo.mumfrey.com/content/repositories/snapshots/com/mumfrey/liteloader/1.12.2-SNAPSHOT/maven-metadata.xml",
] {
let got = table.expand(url, ResourceKind::Library, &region);
assert_eq!(got.len(), 1, "{url} 不该有镜像候选:{got:?}");
}
}
#[test]
fn asset_rule_injects_path_prefix() {
let table = MirrorTable::builtin();
@@ -222,9 +234,8 @@ fn pinned_or_disabled_gh_proxy_is_respected() {
.expect("自定义前缀未生效");
assert_eq!(proxy.url, format!("https://proxy.example/{GITHUB_URL}"));
// 「默认前缀」的唯一真相源是**地区档案**(M193:`MirrorTable::set_gh_proxy` /
// `default_gh_proxy` 已删除——它们改/读的是表里的前缀目录,而 `expand()` 读档案,
// 属于「看起来能改、其实没人读」。这条断言从此钉在档案上)。
// 「默认前缀」的唯一真相源是**地区档案**(M193:`MirrorTable::set_gh_proxy` / `default_gh_proxy`
// 已删除——它们改/读的是表里的前缀目录,而 `expand()` 读档案,属于「看起来能改、其实没人读」)。
assert_eq!(
RegionProfile::mirror_first(0).gh_proxy.as_deref(),
Some(unlucky_core::download::region::DEFAULT_GH_PROXY)