Files
Unlucky-Launcher/crates/core/tests/liteloader/install.rs
T
AskaEth 944b1610c8 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 之前);父版本缺失/组合不支持/取消/坏表各自具名错误。
2026-11-10 19:34:09 +08:00

175 lines
6.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 解析 → 计划 → 落盘 → 继承解析 → 启动参数顺序(两条父链:原版 / Forge)。
use unlucky_core::addon::loader::{LoaderInstallPlan, LoaderKind, LoaderPlanInput};
use unlucky_core::download::blocking::block_on;
use unlucky_core::game::arguments::Environment;
use unlucky_core::game::inherit::resolve;
use unlucky_core::game::install::execute;
use unlucky_core::game::layout::GameLayout;
use unlucky_core::launch::command_builder::tokenize;
use crate::support::{
engine, liteloader_release, prefill, put_version, read_version, TempDir, FORGE_1122,
VANILLA_1122,
};
/// 原版父链:装完能落盘,清单指向原版,`arguments.game` 带 tweakClass。
#[test]
fn installs_over_vanilla_and_writes_the_manifest() {
let temp = TempDir::new("vanilla");
let layout = GameLayout::new(temp.path());
put_version(&layout, "1.12.2", VANILLA_1122);
let release = liteloader_release("1.12.2");
assert_eq!(release.version_id, "1.12.2-liteloader-1.12.2-SNAPSHOT");
let plan = LoaderInstallPlan::build(LoaderPlanInput {
layout: &layout,
release: &release,
environment: &Environment::host(),
parent_installed: true,
})
.expect("父版本已装,计划应可组");
assert!(!plan.vanilla_install_needed);
assert!(plan.install.client_jar().is_none(), "jar 归原版层");
// 三个库:launchwrapper(Mojang 库仓)+ asm-all(Central)+ 本体 jar(官方直链)。
let names: Vec<&str> = plan.files().iter().map(|file| file.name.as_str()).collect();
assert_eq!(names.len(), 3, "{names:?}");
let loader_jar = plan
.files()
.iter()
.find(|file| file.name == "com.mumfrey:liteloader:1.12.2-SNAPSHOT")
.expect("缺本体 jar");
assert!(
loader_jar
.url
.ends_with("/1.12.2-SNAPSHOT/liteloader-1.12.2-20171128.144431-4-release.jar"),
"本体直链:{}",
loader_jar.url
);
let asm = plan
.files()
.iter()
.find(|file| file.name.contains("asm-all"))
.expect("缺 asm-all");
assert!(
asm.url.starts_with("https://repo1.maven.org/maven2/"),
"asm-all 只能从 Central 取:{}",
asm.url
);
// 全部落点先铺好占位文件(库无 sha1/size → 存在即信),执行段只写版本 JSON。
let engine = engine(temp.path());
let prefilled = prefill(&plan.install);
assert_eq!(prefilled, 3);
let report = block_on(execute(&engine, &plan.install, 4, None)).expect("执行应成功");
assert!(report.is_complete(), "{:?}", report.failures(3));
let written = read_version(&layout, &release.version_id);
assert_eq!(written.inherits_from.as_deref(), Some("1.12.2"));
assert_eq!(
written.main_class.as_deref(),
Some("net.minecraft.launchwrapper.Launch")
);
assert!(
written.minecraft_arguments.is_none(),
"loader 层不许写 minecraftArguments:那会顶掉父链"
);
}
/// Forge 父链:**父链的 FMLTweaker 必须还在**,LiteLoader 的 tweakClass 追加在它后面。
///
/// 这条是 M218 SURVEY-p1 的「准入条件」:靠 `--tweakClass` 生效,就要求继承时
/// `arguments.game` 是追加、`minecraftArguments` 不被子层覆盖。启动参数顺序照
/// `core::launch::game_args` 的第 24 项(legacy 子串)→ 第 25 项(`arguments.game`)复现,
/// 真实 argv 由 `tests/launch/cmdline_snapshot.rs` 钉住。
#[test]
fn stacks_on_forge_and_keeps_the_forge_tweak_class() {
let temp = TempDir::new("forge");
let layout = GameLayout::new(temp.path());
put_version(&layout, "1.12.2", VANILLA_1122);
put_version(&layout, "1.12.2-forge-14.23.5.2860", FORGE_1122);
let release = liteloader_release("1.12.2-forge-14.23.5.2860");
assert!(!release.parent_is_vanilla());
let plan = LoaderInstallPlan::build(LoaderPlanInput {
layout: &layout,
release: &release,
environment: &Environment::host(),
parent_installed: true,
})
.expect("父版本已装,计划应可组");
assert_eq!(plan.install.jar_id, "1.12.2", "jar 仍取原版那层");
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 installed = read_version(&layout, &release.version_id);
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-forge-14.23.5.2860".to_string(),
read_version(&layout, "1.12.2-forge-14.23.5.2860"),
);
source.insert(release.version_id.clone(), installed);
let resolved = resolve(&source, &source[&release.version_id]).expect("三层继承应可解析");
// ① 父链的 legacy 参数整串还在(子层没写 minecraftArguments)。
let legacy = resolved
.minecraft_arguments
.as_deref()
.expect("Forge 的 minecraftArguments 不许被子层顶掉");
assert!(
legacy.contains("net.minecraftforge.fml.common.launcher.FMLTweaker"),
"Forge 的 tweakClass 丢了:{legacy}"
);
// ② 启动顺序:legacy(第 24 项)在前,arguments.game(第 25 项)在后。
let mut game: Vec<String> = tokenize(legacy);
game.extend(
resolved
.arguments
.as_ref()
.expect("arguments 应保留")
.expand(&Environment::host())
.game,
);
let forge_at = game
.iter()
.position(|token| token.contains("FMLTweaker"))
.expect("Forge tweakClass 不在游戏参数里");
let liteloader_at = game
.iter()
.position(|token| token == "com.mumfrey.liteloader.launch.LiteLoaderTweaker")
.expect("LiteLoader tweakClass 不在游戏参数里");
assert!(
forge_at < liteloader_at,
"launchwrapper 按出现顺序应用 tweak:Forge 必须在前,实际 {game:?}"
);
assert_eq!(game[liteloader_at - 1], "--tweakClass");
// ③ 主类与库顺序:Forge 的库在前(classpath 先到先得),LiteLoader 的在后。
assert_eq!(
resolved.main_class.as_deref(),
Some("net.minecraft.launchwrapper.Launch")
);
let libraries: Vec<&str> = resolved
.libraries
.as_deref()
.expect("libraries")
.iter()
.map(|library| library.name.as_str())
.collect();
let forge_lib = libraries
.iter()
.position(|name| name.starts_with("net.minecraftforge:forge:"))
.expect("缺 Forge 本体库");
let liteloader_lib = libraries
.iter()
.position(|name| name.starts_with("com.mumfrey:liteloader:"))
.expect("缺 LiteLoader 本体库");
assert!(forge_lib < liteloader_lib, "{libraries:?}");
}