feat(core): LiteLoader 安装路线(官方版本表 + 从零合成 launchwrapper 清单)

按 M218 SURVEY-p1 §4.1 的清单接上 LiteLoader 的安装路线,不动 OptiFine。

- core: 新增 addon::loader::liteloader(官方 versions.json 解析 + 合成清单)与
  addon::loader::parent(父版本链读盘定位原版版本);LoaderKind 加 LiteLoader;
  LoaderRelease 加 parent_version_id、LoaderPlanInput::vanilla_installed → parent_installed;
  LoaderInstallPlan::build 改返回 Result(父版本缺失具名报错);LoaderSource::resolve_on
  支持指定父版本(仅 LiteLoader,其它组合具名 unsupported)。
- 继承边界(本 mission 第一步核实):arguments.game 父在前子在后追加,minecraftArguments
  子覆盖父,启动顺序 legacy(24) → arguments.game(25)。所以 tweakClass 只写 arguments.game,
  绝不写 minecraftArguments(会顶掉 Forge 1.12.2 的 FMLTweaker)。
- maven/fabric 的 `_ =>` 通配改成穷尽分支:非 maven 族不再静默回落 Forge 的仓库;
  maven::{metadata_url,installer_url} 改返回 Option<String>。
- presenter: supported() 收 LiteLoader、wizard_choices 六选、dto::instances 按关注点拆成
  instances/{loader,runtime,create,tests}.rs(原文件 395 行,动手前先拆)。
- cli: --loader 区分「认不出」(loader_unknown) 与「认得但装不了」(loader_unsupported);
  <version> 位写加载器版本 id 时按父版本处理(只有 liteloader 接受);help 只改 install 段。
- app-iced: 下载页加载器 chip 加 LiteLoader(1.5.2~1.12.2,两头按官方没有构建拒),
  chip 清单改为委派 Loader::ALL。
- 测试: 真实响应的裁剪夹具(dl.liteloader.com 与 repo.mumfrey.com 的 maven-metadata)
  驱动「解析→计划→落盘→继承→启动参数顺序」全链;Forge 父链有回归(FMLTweaker 必须还在
  且排在 LiteLoader 之前);父版本缺失/组合不支持/取消/坏表各自具名错误。
This commit is contained in:
AskaEth committed 2026-11-10 19:34:09 +08:00
1 parent 307d29239a
commit 944b1610c8
37 files changed
+2427 -518

No files matched your search

+140
View File
@@ -0,0 +1,140 @@
//! `ull install --loader liteloader` 的**离线**可验面:词表分流、父版本缺失、help 文案。
//!
//! 这里刻意不联网:LiteLoader 的版本表在 `dl.liteloader.com`,把「拉到表之后」的链路放在
//! `crates/core/tests/liteloader/**`(真实响应夹具 + 合成 + 计划 + 落盘 + 启动参数)。
//! 本文件守的是「进门那一步」:`optifine` 与随机拼法的错误码必须分开、父版本没装必须具名报错、
//! 非 LiteLoader 不许叠在别的加载器层上。
#[path = "support/mod.rs"]
mod support;
use support::{parse_json, run_cli, TempDir};
#[test]
fn optifine_is_recognized_but_not_supported() {
let dir = TempDir::new("liteloader-optifine");
let (code, stdout, _) = run_cli(&[
"install",
"1.20.1",
"--loader",
"optifine",
"--json",
"--data-dir",
&dir.arg(),
]);
assert_eq!(code, 1);
let value = parse_json(&stdout);
assert_eq!(
value["error"]["code"], "loader_unsupported",
"optifine 是「认得但装不了」,不是未知加载器:{stdout}"
);
assert!(
value["error"]["message"]
.as_str()
.unwrap_or_default()
.contains("optifine"),
"{stdout}"
);
}
/// `optifine` 之外,真正不认识的拼法仍是 `loader_unknown`(样本用**明显无效**的名字,
/// 不拿任何真实加载器名当「未知」前提)。
#[test]
fn genuinely_unknown_loader_stays_unknown() {
let dir = TempDir::new("liteloader-unknown");
let (code, stdout, _) = run_cli(&[
"install",
"1.20.1",
"--loader",
"ull-invalid-loader-test",
"--json",
"--data-dir",
&dir.arg(),
]);
assert_eq!(code, 1);
let value = parse_json(&stdout);
assert_eq!(value["error"]["code"], "loader_unknown", "{stdout}");
let hint = value["error"]["hint"].as_str().unwrap_or_default();
assert!(hint.contains("liteloader"), "提示要列全六家:{hint}");
}
/// 父版本(`1.12.2-forge-…`)没装时:具名报缺哪个父版本,且**不发网络请求**。
#[test]
fn liteloader_on_a_missing_forge_parent_names_the_parent() {
let dir = TempDir::new("liteloader-missing-parent");
let (code, stdout, _) = run_cli(&[
"install",
"1.12.2-forge-14.23.5.2860",
"--loader",
"liteloader",
"--json",
"--data-dir",
&dir.arg(),
]);
assert_eq!(code, 1);
let value = parse_json(&stdout);
assert_eq!(value["error"]["code"], "loader_not_found", "{stdout}");
let message = value["error"]["message"].as_str().unwrap_or_default();
assert!(
message.contains("1.12.2-forge-14.23.5.2860"),
"要点名缺哪个父版本:{message}"
);
}
/// 只有 LiteLoader 接受「叠在别的加载器层上」;别家在进门处就说清(不静默改成叠原版)。
#[test]
fn only_liteloader_accepts_stacking_on_another_loader_layer() {
let dir = TempDir::new("liteloader-stack-guard");
let (code, stdout, _) = run_cli(&[
"install",
"1.12.2-forge-14.23.5.2860",
"--loader",
"fabric",
"--json",
"--data-dir",
&dir.arg(),
]);
assert_eq!(code, 1);
let value = parse_json(&stdout);
assert_eq!(value["error"]["code"], "usage", "{stdout}");
let message = value["error"]["message"].as_str().unwrap_or_default();
assert!(
message.contains("LiteLoader") && message.contains("Fabric"),
"{message}"
);
}
#[test]
fn install_help_names_the_liteloader_route() {
let (code, stdout, _) = run_cli(&["help", "install"]);
assert_eq!(code, 0);
for needle in [
"liteloader",
"optifine",
"launchwrapper",
"tweakClass",
"loader_unsupported",
"loader_unknown",
] {
assert!(
stdout.contains(needle),
"`ull help install` 少了 `{needle}`:\n{stdout}"
);
}
}
#[test]
fn general_help_lists_liteloader_under_install() {
let (code, stdout, _) = run_cli(&["--help"]);
assert_eq!(code, 0);
let install_block = stdout
.lines()
.skip_while(|line| !line.trim_start().starts_with("install <version>"))
.take(3)
.collect::<Vec<_>>()
.join("\n");
assert!(
install_block.contains("liteloader"),
"总帮助的 install 段要提到 liteloader:\n{install_block}"
);
}
+3 -1
View File
@@ -69,11 +69,13 @@ fn instances_list_json_shape() {
#[test]
fn install_rejects_unknown_loader_before_touching_network() {
let dir = TempDir::new("install-loader");
// 样本必须是一个**任何词表里都没有**的拼法:`liteloader` 自 M222 起是受支持加载器,
// 拿它当「未知」会让这条用例变成真去拉 dl.liteloader.com 的联网测试。
let (code, stdout, _) = run_cli(&[
"install",
"1.20.1",
"--loader",
"liteloader",
"ull-invalid-loader-test",
"--json",
"--data-dir",
&dir.arg(),