diff --git a/crates/core/src/addon/loader/fabric.rs b/crates/core/src/addon/loader/fabric.rs index 4820159..7772e68 100644 --- a/crates/core/src/addon/loader/fabric.rs +++ b/crates/core/src/addon/loader/fabric.rs @@ -216,15 +216,28 @@ fn lift_flat_digest(library: &mut Library) { } /// loader 本体库必须在列表里:缺了就补一条(并用告警说明),别装出个"没有 loader"的实例。 +/// +/// 只有 fabric 系会走到这里(调用方 [`build_release`] 的入口已按 +/// [`LoaderKind::is_fabric_family`] 分流);这里仍然**穷尽 match**,因为过去那对 `_ =>` +/// 会在新加一支加载器时把它静默当成 `fabric-loader` 补进库表。 fn ensure_loader_library( kind: LoaderKind, loader_version: &str, libraries: &mut Vec, warnings: &mut Vec, ) { - let (group, artifact) = match kind { - LoaderKind::Quilt => ("org.quiltmc", "quilt-loader"), - _ => ("net.fabricmc", "fabric-loader"), + let (group, artifact, maven_base) = match kind { + LoaderKind::Fabric => ( + "net.fabricmc", + "fabric-loader", + "https://maven.fabricmc.net/", + ), + LoaderKind::Quilt => ( + "org.quiltmc", + "quilt-loader", + "https://maven.quiltmc.org/repository/release/", + ), + LoaderKind::NeoForge | LoaderKind::Forge | LoaderKind::LiteLoader => return, }; let present = libraries.iter().any(|library| { library @@ -234,10 +247,6 @@ fn ensure_loader_library( if present { return; } - let maven_base = match kind { - LoaderKind::Quilt => "https://maven.quiltmc.org/repository/release/", - _ => "https://maven.fabricmc.net/", - }; libraries.push(Library { name: format!("{group}:{artifact}:{loader_version}"), url: Some(maven_base.to_string()), diff --git a/crates/core/src/addon/loader/liteloader.rs b/crates/core/src/addon/loader/liteloader.rs index efe3299..c5e7627 100644 --- a/crates/core/src/addon/loader/liteloader.rs +++ b/crates/core/src/addon/loader/liteloader.rs @@ -45,6 +45,18 @@ pub const DEFAULT_REPO: &str = "https://dl.liteloader.com/versions/"; /// 非 Mojang 库的基址:Mojang 库仓里没有 `asm-all:5.2`(实测 404),Central 有(实测 206)。 pub const CENTRAL_REPO: &str = "https://repo1.maven.org/maven2/"; +/// 快照分支的仓库根——**固定这一个**,不跟着 `repo.url` 走。 +/// +/// 上游每个原版版本的 `repo.url` 有两种形态(`…/versions/` 与 `…/repo/`),而**快照只发布在 +/// snapshots 仓**:1.10.2 的 `repo.url` 是 `http://dl.liteloader.com/repo/`,按它拼 +/// `…/repo/com/mumfrey/liteloader/1.10.2-SNAPSHOT/maven-metadata.xml` 实测会 302 到 +/// `…/repositories/liteloader/…` 再 404;而 +/// `https://repo.mumfrey.com/content/repositories/snapshots/com/mumfrey/liteloader/1.10.2-SNAPSHOT/maven-metadata.xml` +/// 是 200。HMCL 的构建脚本同样把这个快照仓**写死**(`UpdateLiteLoaderVersionList.SNAPSHOT_FILE`, +/// 注释还引了它自己的 issue #3147)。1.12.2 的 `repo.url` 恰好就是这条路径,所以只测 1.12.2 +/// 看不出这个问题——单测专门钉了 1.10.2。 +pub const SNAPSHOT_REPO: &str = "https://repo.mumfrey.com/content/repositories/snapshots/"; + /// launchwrapper 主类(LiteLoader 靠它做 tweak 注入)。 pub const LAUNCHWRAPPER_MAIN: &str = "net.minecraft.launchwrapper.Launch"; diff --git a/crates/core/src/addon/loader/liteloader/versions.rs b/crates/core/src/addon/loader/liteloader/versions.rs index 4edc30e..94d6756 100644 --- a/crates/core/src/addon/loader/liteloader/versions.rs +++ b/crates/core/src/addon/loader/liteloader/versions.rs @@ -7,7 +7,9 @@ use std::collections::{BTreeMap, BTreeSet}; use serde::Deserialize; -use super::{LiteLoaderEntry, LoaderError, CENTRAL_REPO, DEFAULT_REPO, VERSION_LIST_URL}; +use super::{ + LiteLoaderEntry, LoaderError, CENTRAL_REPO, DEFAULT_REPO, SNAPSHOT_REPO, VERSION_LIST_URL, +}; use crate::game::library::Library; #[derive(Debug, Deserialize)] @@ -110,7 +112,8 @@ fn collect( let snapshot = snapshot_branch || record.stream.eq_ignore_ascii_case("SNAPSHOT"); let dir = format!("{repo}com/mumfrey/liteloader/{mc_version}/"); let (url, snapshot_dir) = if snapshot { - let snap_dir = format!("{repo}com/mumfrey/liteloader/{mc_version}-SNAPSHOT/"); + // 快照仓是**固定**的(见 `SNAPSHOT_REPO` 的注释),不跟 `repo.url` 走。 + let snap_dir = format!("{SNAPSHOT_REPO}com/mumfrey/liteloader/{mc_version}-SNAPSHOT/"); (None, Some(snap_dir)) } else { ( diff --git a/crates/core/src/addon/loader/tests/liteloader.rs b/crates/core/src/addon/loader/tests/liteloader.rs index 41c629c..3b1f8d0 100644 --- a/crates/core/src/addon/loader/tests/liteloader.rs +++ b/crates/core/src/addon/loader/tests/liteloader.rs @@ -1,8 +1,9 @@ //! LiteLoader 数据源与合成:官方版本表(**真实响应的裁剪版**)→ 版本条目 → 合成清单。 //! //! 夹具 `LITELOADER_VERSIONS` 是 2026-11 从 `https://dl.liteloader.com/versions/versions.json` -//! 真取的一份响应(200 / 49129 字节 / 16 个原版版本)里挑两个原版版本原样留下: -//! `1.7.10`(release 分支)+ `1.12.2`(只有 snapshots 分支)。删掉的是另外 14 个原版版本与 +//! 真取的一份响应(200 / 49129 字节 / 16 个原版版本)里挑三个原版版本原样留下: +//! `1.7.10`(只有 release 分支)、`1.10.2`(release + snapshots,且 `repo.url` 是 `…/repo/` +//! 形态——专门用来钉「快照仓固定」这条)、`1.12.2`(只有 snapshots 分支)。删掉的是另外 13 个原版版本与 //! 每个版本下的多余分支;**字段名与结构一个没改**(键仍是 md5、`latest` 别名也留着,用来 //! 钉「跳过别名 + 按 version 去重」)。`LITELOADER_METADATA` 是同一次探测里取的 //! `repo.mumfrey.com/…/liteloader/1.12.2-SNAPSHOT/maven-metadata.xml`(200 / 358 字节)。 @@ -52,6 +53,39 @@ const LITELOADER_VERSIONS: &str = r#"{ } }} }, + "1.10.2": { + "repo": {"stream": "RELEASE", "type": "m2", "url": "http://dl.liteloader.com/repo/", "classifier": ""}, + "artefacts": {"com.mumfrey:liteloader": { + "8a7c21f32d77ee08b393dd3921ced8eb": { + "tweakClass": "com.mumfrey.liteloader.launch.LiteLoaderTweaker", + "libraries": [{"name": "net.minecraft:launchwrapper:1.12"}, {"name": "org.ow2.asm:asm-all:5.0.3"}], + "stream": "RELEASE", + "file": "liteloader-1.10.2.jar", + "version": "1.10.2", + "md5": "8a7c21f32d77ee08b393dd3921ced8eb", + "timestamp": "1479473570" + } + }}, + "snapshots": { + "libraries": [ + {"name": "net.minecraft:launchwrapper:1.12"}, + {"name": "org.spongepowered:mixin:0.5.17-SNAPSHOT", "url": "https://repo.spongepowered.org/maven/"} + ], + "com.mumfrey:liteloader": { + "1cdd72f7232e45551f16cc8ffd27ccf3": { + "tweakClass": "com.mumfrey.liteloader.launch.LiteLoaderTweaker", + "libraries": [{"name": "net.minecraft:launchwrapper:1.12"}, {"name": "org.ow2.asm:asm-all:5.0.3"}], + "stream": "SNAPSHOT", + "file": "liteloader-1.10.2-SNAPSHOT.jar", + "version": "1.10.2-SNAPSHOT", + "build": "1.10.2-SNAPSHOT-rE85C8AE-b11-11", + "md5": "1cdd72f7232e45551f16cc8ffd27ccf3", + "timestamp": "1485987183", + "lastSuccessfulBuild": 11 + } + } + } + }, "1.12.2": { "repo": {"stream": "SNAPSHOT", "type": "m2", "url": "http://repo.mumfrey.com/content/repositories/snapshots/", "classifier": ""}, "snapshots": { @@ -105,7 +139,7 @@ const LITELOADER_METADATA: &str = r#" #[test] fn parses_the_real_version_table() { let table = parse_versions(LITELOADER_VERSIONS).expect("真实响应必须能解析"); - assert_eq!(table.len(), 2, "只留了两个原版版本"); + assert_eq!(table.len(), 3, "只留了三个原版版本"); // release 分支:按 version 去重、新的排前面。 let releases = &table["1.7.10"]; @@ -274,3 +308,31 @@ fn unknown_version_does_not_fall_back() { let error = parse_versions(r#"{"versions": []}"#).expect_err("形状不符必须报错"); assert_eq!(error.kind(), LoaderErrorKind::Metadata); } + +/// 快照仓**固定**在 `repo.mumfrey.com/content/repositories/snapshots/`,不跟 `repo.url` 走。 +/// +/// 抓得住:1.10.2 的 `repo.url` 是 `…/repo/`,照它拼快照路径会 302 到 +/// `…/repositories/liteloader/…` 再 404(实测)。1.12.2 的 `repo.url` 恰好就是快照仓, +/// 所以只测 1.12.2 会漏掉这个 bug。 +#[test] +fn snapshot_repo_is_fixed_not_taken_from_repo_url() { + let table = parse_versions(LITELOADER_VERSIONS).expect("解析"); + let entries = &table["1.10.2"]; + let snapshot = entries + .iter() + .find(|entry| entry.snapshot) + .expect("1.10.2 应有快照条目"); + assert_eq!( + snapshot.snapshot_dir.as_deref(), + Some("https://repo.mumfrey.com/content/repositories/snapshots/com/mumfrey/liteloader/1.10.2-SNAPSHOT/") + ); + // release 条目仍走该版本自己的 `repo.url`(上游 `/repo/` 会 302 到真地址,引擎跟重定向)。 + let release = entries + .iter() + .find(|entry| !entry.snapshot) + .expect("1.10.2 应有 release 条目"); + assert_eq!( + release.url.as_deref(), + Some("https://dl.liteloader.com/repo/com/mumfrey/liteloader/1.10.2/liteloader-1.10.2.jar") + ); +} diff --git a/crates/core/tests/liteloader/install.rs b/crates/core/tests/liteloader/install.rs index 8a055b3..1f80fcb 100644 --- a/crates/core/tests/liteloader/install.rs +++ b/crates/core/tests/liteloader/install.rs @@ -172,3 +172,69 @@ fn stacks_on_forge_and_keeps_the_forge_tweak_class() { .expect("缺 LiteLoader 本体库"); assert!(forge_lib < liteloader_lib, "{libraries:?}"); } + +/// **新式 args 的父链**:父版本用 `arguments.game`(没有 `minecraftArguments`)。 +/// +/// 现实里 LiteLoader 只到 1.12.2,碰不到这种父版本;但「追加语义」是这套路线的准入条件, +/// 两种父版本形状都得钉住——否则哪天上游把老版本清单改成新式,我们会静默丢参数。 +/// 这里用一份**手工夹具**(`--launchTarget forgeclient` 是 Forge 1.13+ 的真实形态)说明: +/// 父层参数在前、LiteLoader 的 tweakClass 在后,中间不互相覆盖。 +#[test] +fn new_style_parent_args_are_appended_not_replaced() { + const MODERN_PARENT: &str = r#"{ + "id": "1.12.2-modern-1.0", + "inheritsFrom": "1.12.2", + "mainClass": "net.minecraft.launchwrapper.Launch", + "arguments": {"game": ["--launchTarget", "forgeclient"], "jvm": ["-Dmodern=1"]}, + "libraries": [{"name": "example:modern:1.0", "url": "https://example.invalid/"}] + }"#; + + let temp = TempDir::new("modern-args"); + let layout = GameLayout::new(temp.path()); + put_version(&layout, "1.12.2", VANILLA_1122); + put_version(&layout, "1.12.2-modern-1.0", MODERN_PARENT); + + let release = liteloader_release("1.12.2-modern-1.0"); + let plan = LoaderInstallPlan::build(LoaderPlanInput { + layout: &layout, + release: &release, + environment: &Environment::host(), + parent_installed: true, + }) + .expect("计划应可组"); + let engine = engine(temp.path()); + prefill(&plan.install); + let report = block_on(execute(&engine, &plan.install, 4, None)).expect("执行应成功"); + assert!(report.is_complete(), "{:?}", report.failures(3)); + + let mut source = std::collections::BTreeMap::new(); + source.insert("1.12.2".to_string(), read_version(&layout, "1.12.2")); + source.insert( + "1.12.2-modern-1.0".to_string(), + read_version(&layout, "1.12.2-modern-1.0"), + ); + source.insert( + release.version_id.clone(), + read_version(&layout, &release.version_id), + ); + let resolved = resolve(&source, &source[&release.version_id]).expect("三层继承应可解析"); + + // 父层用的是新式参数:子层没有把 legacy 串写进来,继承后仍然没有。 + assert!(resolved.minecraft_arguments.is_none()); + let game = resolved + .arguments + .as_ref() + .expect("arguments") + .expand(&Environment::host()) + .game; + let assert_order = |needle: &str| { + game.iter() + .position(|token| token == needle) + .unwrap_or_else(|| panic!("缺 `{needle}`:{game:?}")) + }; + assert!( + assert_order("forgeclient") + < assert_order("com.mumfrey.liteloader.launch.LiteLoaderTweaker"), + "父层参数必须在前:{game:?}" + ); +}