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:
1 parent
307d29239a
commit
944b1610c8
37 files changed
+2427
-518
No files matched your search
@@ -22,8 +22,8 @@ pub const fn general() -> &'static str {
|
||||
[--dry-run] 把已铺好的版本登记成实例(只读外部、不搬文件;缺省运行目录
|
||||
落 ULL 数据区的独立目录;--dry-run 只检查不写盘)
|
||||
install <version> [--loader X] [--loader-version V] [--name N] [--dry-run]
|
||||
下载并安装版本本体(--loader fabric|quilt|neoforge|forge
|
||||
时再叠一层加载器)→ 登记实例;--dry-run 只出计划
|
||||
下载并安装版本本体(--loader fabric|quilt|neoforge|forge|
|
||||
liteloader 时再叠一层加载器)→ 登记实例;--dry-run 只出计划
|
||||
launch <instance> [--dry-run] [--account <name>] [--java <path>] [--username <name>]
|
||||
[--max-memory <MB>] 启动实例;--dry-run 只打印组装后的命令行
|
||||
pack import <file> [--target <dir>] [--name N] [--overwrite] [--no-register]
|
||||
@@ -184,10 +184,18 @@ pub fn topic(name: &str) -> Option<&'static str> {
|
||||
(profiles.json + config/instances/<id>.json)。已存在且校验通过的文件会跳过,
|
||||
重复装同一版本不重复下载、不重复建实例。
|
||||
--dry-run:只输出计划(文件清单 + 总大小 + 每类资源的源选择),不下载、不登记。
|
||||
--loader:vanilla(缺省)/ fabric / quilt / neoforge / forge。装加载器时先生成
|
||||
versions/<原版>-<加载器>-<版本>/<…>.json(inheritsFrom 原版 + 主类 + loader 库),
|
||||
--loader:vanilla(缺省)/ fabric / quilt / neoforge / forge / liteloader。装加载器时先生成
|
||||
versions/<原版>-<加载器>-<版本>/<…>.json(inheritsFrom 父版本 + 主类 + loader 库),
|
||||
原版没装会先装原版,实例的 versionId 指向 loader 版本。
|
||||
--loader-version:加载器版本;缺省取官方默认(Fabric 系首个稳定版,maven 系最新)。
|
||||
optifine 认得出但**没接入安装管线**(要跑外部 Patcher 并原地改 zip),报 loader_unsupported;
|
||||
其余认不出的拼法报 loader_unknown —— 两者不是一回事。
|
||||
liteloader 的特殊之处:清单是从官方版本表**从零合成**的(mainClass = launchwrapper,
|
||||
--tweakClass 追加在父链参数之后),不跑任何外部进程;不叠加载器时只占一个 loader 库。
|
||||
<version> 位还可以写**已装的加载器版本 id**(`1.12.2-forge-14.23.5.2860 --loader liteloader`)
|
||||
表示「叠在它上面」:原版版本从盘上的继承链读出来,父版本没装会具名报错(不会猜一个原版版本号)。
|
||||
只有 liteloader 接受这种叠法,别家给 usage 错误说清「只能叠在原版上」。
|
||||
--loader-version:加载器版本;缺省取官方默认(Fabric 系首个稳定版,maven 系最新,
|
||||
LiteLoader 优先 release 分支、没有则最新快照)。
|
||||
Forge 可写 47.2.0 或 1.20.1-47.2.0 两种形态。
|
||||
NeoForge/Forge 里需要跑官方 installer processors 的版本会真跑一遍(M35 已实装):
|
||||
缺 Java 时按 D23 的回退链自动装一个托管 JDK;--dry-run 只列出要跑的 processor 与依赖库。
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
//! `ull install <version> --loader <kind> [--loader-version <v>]`:加载器层的编排。
|
||||
//!
|
||||
//! ```text
|
||||
//! LoaderSource::resolve(官方 meta:版本列表 → profile JSON / installer profile)
|
||||
//! 目标解析:<version> 位是原版版本 → 叠原版;是已装的加载器版本 id → 叠在它上面(仅 LiteLoader)
|
||||
//! → LoaderSource::resolve(官方 meta:版本列表 → profile JSON / installer profile / LiteLoader 版本表)
|
||||
//! → LoaderInstallPlan::build(纯计算:库清单 + loader 版本 JSON 落点)
|
||||
//! → [--dry-run 到此为止,但多带一段 processors 计划]
|
||||
//! → 原版已装?否 → vanilla::plan_and_install(先装原版)
|
||||
@@ -13,16 +14,22 @@
|
||||
//! 「先装原版」是**硬前置**:loader 版本 JSON 只声明 `inheritsFrom`,jar 与资源都在原版那层,
|
||||
//! 原版缺失时启动链必然断裂。已装则跳过(`is_version_installed` 判据 = 版本 JSON 在盘上)。
|
||||
//!
|
||||
//! **父版本**:`<version>` 位写成加载器版本 id(`1.12.2-forge-14.23.5.2860`)时,它就是父版本,
|
||||
//! 原版版本从盘上的继承链读出来(`addon::loader::parent`)。只有 LiteLoader 接受这种叠法
|
||||
//! (它靠 `--tweakClass` 追加生效);别家**具名拒绝**,不会默默改成叠原版。
|
||||
//!
|
||||
//! processors 段的顺序也是硬约束:**先跑 processors,再写版本 JSON**。版本清单存在是
|
||||
//! "这版装完了"的判据,抢在 processors 前写会留下一个启动必崩却被报成已安装的实例。
|
||||
|
||||
use unlucky_core::addon::loader::{
|
||||
LoaderError, LoaderInstallPlan, LoaderKind, LoaderPlanInput, LoaderSource,
|
||||
parent, LoaderError, LoaderInstallPlan, LoaderKind, LoaderPlanInput, LoaderSource,
|
||||
};
|
||||
use unlucky_core::download::blocking::block_on;
|
||||
use unlucky_core::download::cache::now_ms;
|
||||
use unlucky_core::game::arguments::Environment;
|
||||
use unlucky_core::game::install::execute;
|
||||
use unlucky_core::game::loader::{InstanceLoader, LoaderKind as WideLoaderKind};
|
||||
use unlucky_core::game::GameLayout;
|
||||
|
||||
use super::report_loader::ProcessorSection;
|
||||
use super::{
|
||||
@@ -38,11 +45,11 @@ use crate::output::Report;
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// 用法错误、加载器未知/不支持、元数据读取失败、原版装不上、库下载失败、
|
||||
/// installer processors 失败、实例登记失败。
|
||||
/// 用法错误、加载器未知/不支持、父版本缺失或组合不支持、元数据读取失败、原版装不上、
|
||||
/// 库下载失败、installer processors 失败、实例登记失败。
|
||||
pub(super) fn run(ctx: &Ctx, args: &InstallArgs, kind: LoaderKind) -> Result<Report, CliError> {
|
||||
let version = args.version.trim().to_string();
|
||||
if version.is_empty() {
|
||||
let requested = args.version.trim().to_string();
|
||||
if requested.is_empty() {
|
||||
return Err(CliError::usage(
|
||||
"install 需要版本 id:%prog% install <version> --loader fabric",
|
||||
));
|
||||
@@ -54,10 +61,17 @@ pub(super) fn run(ctx: &Ctx, args: &InstallArgs, kind: LoaderKind) -> Result<Rep
|
||||
let meta_cache = layout.meta_cache();
|
||||
let now = now_ms();
|
||||
|
||||
let (version, parent) = resolve_target(ctx, &game, &requested, kind)?;
|
||||
|
||||
let release = block_on(async {
|
||||
let source = LoaderSource::new(&engine, &meta_cache);
|
||||
source
|
||||
.resolve(kind, &version, args.loader_version.as_deref())
|
||||
.resolve_on(
|
||||
kind,
|
||||
&version,
|
||||
args.loader_version.as_deref(),
|
||||
parent.as_deref(),
|
||||
)
|
||||
.await
|
||||
})
|
||||
.map_err(|error: LoaderError| map_loader_error(&error, ctx))?;
|
||||
@@ -73,8 +87,9 @@ pub(super) fn run(ctx: &Ctx, args: &InstallArgs, kind: LoaderKind) -> Result<Rep
|
||||
layout: &game,
|
||||
release: &release,
|
||||
environment: &Environment::host(),
|
||||
vanilla_installed: game.is_version_installed(&version),
|
||||
});
|
||||
parent_installed: game.is_version_installed(&release.parent_version_id),
|
||||
})
|
||||
.map_err(|error| map_loader_error(&error, ctx))?;
|
||||
let processor_plan = loader_processors::plan(ctx, &release)?;
|
||||
|
||||
if args.dry_run {
|
||||
@@ -162,3 +177,32 @@ fn map_loader_error(error: &LoaderError, ctx: &Ctx) -> CliError {
|
||||
};
|
||||
CliError::user(error.kind().as_str(), error.message().to_string()).with_hint(hint)
|
||||
}
|
||||
|
||||
/// `<version>` 位 → (原版版本, 父版本 id)。
|
||||
///
|
||||
/// 两种写法(`InstallArgs.version` 的文档口径允许版本 id):
|
||||
/// - 原版版本(`1.12.2`)→ 叠原版,父版本 = 原版;
|
||||
/// - **已装的加载器版本 id**(`1.12.2-forge-14.23.5.2860`)→ 叠在它上面,原版版本从盘上的
|
||||
/// 继承链读出来。只有 LiteLoader 接受这种叠法;别家给具名错误,不静默改成叠原版。
|
||||
fn resolve_target(
|
||||
ctx: &Ctx,
|
||||
game: &GameLayout,
|
||||
requested: &str,
|
||||
kind: LoaderKind,
|
||||
) -> Result<(String, Option<String>), CliError> {
|
||||
if InstanceLoader::parse(requested).kind == WideLoaderKind::Vanilla {
|
||||
return Ok((requested.to_string(), None));
|
||||
}
|
||||
if kind != LoaderKind::LiteLoader {
|
||||
return Err(CliError::usage(format!(
|
||||
"`{requested}` 是一个已装加载器版本 id:只有 LiteLoader 支持叠在别的加载器层上,{} 只能叠在原版上",
|
||||
kind.label()
|
||||
))
|
||||
.with_hint(
|
||||
"换成原版版本号:`%prog% install 1.20.1 --loader forge`;或先 `%prog% instances list` 看已装版本",
|
||||
));
|
||||
}
|
||||
let mc_version =
|
||||
parent::root_version_id(game, requested).map_err(|error| map_loader_error(&error, ctx))?;
|
||||
Ok((mc_version, Some(requested.to_string())))
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
//! --loader 缺省 → vanilla:M27 原版安装(resolve → plan → execute → register)
|
||||
//! --loader fabric|quilt → addon::loader:原版(已装则跳)→ loader 版本 JSON → 补 loader 库 → register
|
||||
//! --loader neoforge|forge → 同上;需要 installer processors 的版本再跑 M35 执行器(依赖库 → java → 版本 JSON)
|
||||
//! --loader liteloader → 官方版本表 → 从零合成 launchwrapper 清单(不跑任何外部进程)
|
||||
//! ```
|
||||
//!
|
||||
//! CLI 只做四件事:建下载引擎、把计划/结果排版成人读与 `--json` 两种形态
|
||||
@@ -22,6 +23,7 @@ use std::sync::Arc;
|
||||
use unlucky_core::addon::loader::LoaderKind;
|
||||
use unlucky_core::download::{DownloadEngine, DownloadErrorKind};
|
||||
use unlucky_core::game::install::{InstallError, InstallReport, Registration};
|
||||
use unlucky_core::game::LoaderKind as WideLoaderKind;
|
||||
|
||||
use crate::cli::InstallArgs;
|
||||
use crate::context::Ctx;
|
||||
@@ -33,9 +35,14 @@ pub(super) const CONCURRENCY: usize = 16;
|
||||
|
||||
/// 执行 `install`:按 `--loader` 分发。
|
||||
///
|
||||
/// "认不出" 与 "认得但装不了" 是两件事,错误码也分开(`loader_unknown` / `loader_unsupported`):
|
||||
/// `optifine` 属于后者——core 的识别词表里有它(`ull help instances` 也这么写),只是安装管线
|
||||
/// 没接(要跑外部 `optifine.Patcher` + 原地改 zip)。两者混成一句"未知加载器"会让用户以为
|
||||
/// 是自己的版本 id 写错了。
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// 用法错误、加载器未知、版本不存在、网络失败、校验不符、写盘失败、实例清单损坏。
|
||||
/// 用法错误、加载器未知/认得但装不了、版本不存在、网络失败、校验不符、写盘失败、实例清单损坏。
|
||||
pub fn run(ctx: &Ctx, args: &InstallArgs) -> Result<Report, CliError> {
|
||||
match args
|
||||
.loader
|
||||
@@ -46,10 +53,18 @@ pub fn run(ctx: &Ctx, args: &InstallArgs) -> Result<Report, CliError> {
|
||||
None | Some("vanilla") | Some("none") => vanilla::run(ctx, args),
|
||||
Some(raw) => match LoaderKind::parse(raw) {
|
||||
Some(kind) => loader::run(ctx, args, kind),
|
||||
None => Err(
|
||||
CliError::user("loader_unknown", format!("未知加载器 `{raw}`"))
|
||||
.with_hint("--loader 取值:vanilla / fabric / quilt / neoforge / forge"),
|
||||
),
|
||||
None => Err(match WideLoaderKind::parse(raw) {
|
||||
Some(WideLoaderKind::OptiFine) => CliError::user(
|
||||
"loader_unsupported",
|
||||
"optifine 认得出但还没接入安装管线(要跑外部 Patcher 并原地改 zip)",
|
||||
)
|
||||
.with_hint(
|
||||
"先用官方安装器装好,再 `%prog% instances list` 确认它已在;或用 fabric / quilt / neoforge / forge / liteloader",
|
||||
),
|
||||
_ => CliError::user("loader_unknown", format!("未知加载器 `{raw}`")).with_hint(
|
||||
"--loader 取值:vanilla / fabric / quilt / neoforge / forge / liteloader(optifine 认得出但装不了)",
|
||||
),
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,6 +334,7 @@ mod tests {
|
||||
let release = LoaderRelease {
|
||||
kind: LoaderKind::Fabric,
|
||||
mc_version: "1.20.1".to_string(),
|
||||
parent_version_id: "1.20.1".to_string(),
|
||||
loader_version: "0.15.11".to_string(),
|
||||
version_id: "1.20.1-fabric-0.15.11".to_string(),
|
||||
version_json: VersionJson::from_json_str(
|
||||
@@ -349,8 +350,9 @@ mod tests {
|
||||
layout: &layout,
|
||||
release: &release,
|
||||
environment: &Environment::host(),
|
||||
vanilla_installed: true,
|
||||
parent_installed: true,
|
||||
})
|
||||
.expect("计划应可组")
|
||||
}
|
||||
|
||||
/// **M216 r2 P2-2**:装加载器时「先装原版」那一步替换了共享 `.minecraft` 里的原版清单,
|
||||
@@ -372,7 +374,6 @@ mod tests {
|
||||
}),
|
||||
warnings: vec!["已替换版本清单 /g/.minecraft/versions/1.20.1/1.20.1.json".to_string()],
|
||||
};
|
||||
|
||||
let text = human_lines(false, &plan, None, Some(&vanilla), None, &[], "n", None).join("\n");
|
||||
assert!(
|
||||
text.contains("已替换版本清单"),
|
||||
|
||||
@@ -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}"
|
||||
);
|
||||
}
|
||||
@@ -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(),
|
||||
|
||||
Reference in new issue
Block a user