core: 新增 game/launch_overrides(LaunchOverrides 五项 All-Option + 三级回落 + 文本补丁校验)与 launch/overrides(拼装落位:全局 java.extra_args 进 user.jvm、实例 jvmArgs 进 override_jvm、gameArgs 进 user.game、memory 进 -Xmx、backend 进 --graphicsBackend 并复用既有版本门槛告警);实例 schema 登记 launch.* 五键。 presenter: dto/LaunchArgsView+LaunchArgsPatch,主机侧读/写与门槛判定(backend_allowed),AppCommand::SetInstanceLaunchArgs。 cli: 新命令 instances launch-args(读/写/--clear),launch 装配链消费回落值。 app-iced: 实例详情页「启动参数」区(JVM/游戏参数、内存、Java、三态后端 chips + 灰化提示 + 保存/恢复),表单状态归 App、i18n 中英文案同步。
190 lines
8.3 KiB
Rust
190 lines
8.3 KiB
Rust
use super::*;
|
||
|
||
/// 版本 id → DTO 加载器的接口面(解析在 core,这里只验翻译没走样)。
|
||
///
|
||
/// 抓得住:显示路径把某个种类翻错/翻塌(例如 Quilt 写成 Forge)——`dto_kind` 的委派与
|
||
/// `dto` 的 `from_core` 都落在这一条上;以及 core 解析端退回老口径(`fabric-loader-…` 才认、
|
||
/// 自家 `1.20.4-fabric-0.16.5` 认不出)——后两条正是 M122 的 bug 面。
|
||
/// 抓不住:core 解析规则本身(真值表在 `unlucky_core::game::loader` 的测试里,那边才是
|
||
/// 真相源);也抓不住「版本号为空时不显示品牌名」那条(在 `LoaderInfo::badge` 与
|
||
/// app-iced 的 `loader_badge`,M120 已修,别在这里重复)。
|
||
#[test]
|
||
fn loader_parsing_covers_all_shapes() {
|
||
let cases = [
|
||
("1.20.4", LoaderKind::Vanilla, ""),
|
||
// 自家口径(生成端 `LoaderKind::version_id`):`<mc>-<kind>-<loader>`
|
||
("1.20.4-fabric-0.16.5", LoaderKind::Fabric, "0.16.5"),
|
||
("1.20.4-quilt-0.27.0", LoaderKind::Quilt, "0.27.0"),
|
||
("1.20.1-forge-47.2.0", LoaderKind::Forge, "47.2.0"),
|
||
("1.21.1-neoforge-21.1.80", LoaderKind::NeoForge, "21.1.80"),
|
||
// 老 HMCL 口径(存量实例还会带着它们)
|
||
("fabric-loader-0.16.5-1.20.1", LoaderKind::Fabric, "0.16.5"),
|
||
("quilt-loader-0.24.0-1.20.1", LoaderKind::Quilt, "0.24.0"),
|
||
// 第三方口径
|
||
("1.13.2-OptiFine_HD_U_E6", LoaderKind::OptiFine, "HD_U_E6"),
|
||
("1.12.2-LiteLoader", LoaderKind::LiteLoader, ""),
|
||
];
|
||
for (id, kind, version) in cases {
|
||
let info = loader_from_id(id);
|
||
assert_eq!(info.kind, kind, "{id}");
|
||
assert_eq!(info.version, version, "{id}");
|
||
assert!(!info.badge().is_empty());
|
||
}
|
||
}
|
||
|
||
/// 显示路径的**全量**锚:core 的每一种加载器,从自家版本 id 走到磁贴时仍然是**它自己**。
|
||
///
|
||
/// 清单取自 [`unlucky_core::game::LoaderKind::ALL`],**不手抄**:M126 r3 复审实测出的缺口正是
|
||
/// 「手抄的清单跟着漏」——core 加第八家、契约与 help 都补齐、只在 `dto_kind` 那一支写
|
||
/// `=> Vanilla`,三个 crate 全绿而磁贴静默显示「原版」。`ALL` 由 core 的宏表长出来,加一家
|
||
/// 自动进圈,测试作者不需要(也没法)记得补一行。
|
||
///
|
||
/// 抓得住:显示路径任何一环把种类翻塌成原版——`dto_kind` 又变回手写映射、`dto` 的
|
||
/// `from_core` 那一支接错、`label()` 被写成「原版」;**包括新加的那种**(那时 `ALL` 先变长)。
|
||
/// 抓不住:core 的 id 解析规则本身(真值表在 `unlucky_core::game::loader`);版本号为空的
|
||
/// 徽标判据(M120 的 `loader_badge`,本 crate 不渲染)。
|
||
#[test]
|
||
fn every_core_loader_kind_reaches_the_tile_as_itself() {
|
||
use unlucky_core::game::LoaderKind as Core;
|
||
for kind in Core::ALL {
|
||
let info = loader_from_id(&format!("1.20.4-{}-0.16.5", kind.as_str()));
|
||
assert_eq!(
|
||
info.kind.as_str(),
|
||
kind.as_str(),
|
||
"core 的 {kind:?} 走到磁贴时变成了 `{}`(徽标 `{}`)",
|
||
info.kind.as_str(),
|
||
info.badge()
|
||
);
|
||
assert_eq!(
|
||
info.badge().starts_with(LoaderKind::Vanilla.label()),
|
||
kind == Core::Vanilla,
|
||
"core 的 {kind:?} 在磁贴上的徽标是 `{}`",
|
||
info.badge()
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 端到端(数据层):真装出来的四个加载器实例,走 [`instances`] 的快照各自带对加载器。
|
||
///
|
||
/// 造的是本启动器**真实产物**的形状:`profiles.json` 里 `versionId` 是自家 id,
|
||
/// `versions/<id>/<id>.json` 里 `inheritsFrom` 指向原版层(生成端见
|
||
/// `unlucky_core::addon::loader`)。原版版本号由此取自继承链根,不是从 id 里切的。
|
||
///
|
||
/// 抓得住:`describe_version` ↔ `loader_from_id` ↔ `instances` 这条链上任何一环把加载器
|
||
/// 丢掉(磁贴会显示「原版」——M122 的用户可见症状);以及继承链根取错(磁贴的版本号)。
|
||
/// 抓不住:实例页真出图(走 app-iced 的视图模型与 `loader_badge`,本 crate 不渲染);
|
||
/// 也抓不住「真下载安装了 Fabric/Quilt/Forge/NeoForge」——这里只伪造落盘结果。
|
||
#[test]
|
||
fn own_installed_instances_snapshot_carries_their_loader() {
|
||
let dir = std::env::temp_dir().join(format!("ull-host-game-own-{}", std::process::id()));
|
||
let _ = std::fs::remove_dir_all(&dir);
|
||
let env = HostEnv::at_root(&dir);
|
||
std::fs::create_dir_all(env.layout.config()).expect("config 目录");
|
||
|
||
let cases = [
|
||
(
|
||
"1.20.4-fabric-0.16.5",
|
||
"1.20.4",
|
||
LoaderKind::Fabric,
|
||
"Fabric 0.16.5",
|
||
),
|
||
(
|
||
"1.20.4-quilt-0.27.0",
|
||
"1.20.4",
|
||
LoaderKind::Quilt,
|
||
"Quilt 0.27.0",
|
||
),
|
||
(
|
||
"1.20.1-forge-47.2.0",
|
||
"1.20.1",
|
||
LoaderKind::Forge,
|
||
"Forge 47.2.0",
|
||
),
|
||
(
|
||
"1.21.1-neoforge-21.1.80",
|
||
"1.21.1",
|
||
LoaderKind::NeoForge,
|
||
"NeoForge 21.1.80",
|
||
),
|
||
];
|
||
for (version_id, mc_version, _, _) in cases {
|
||
for (id, inherits) in [(mc_version, None), (version_id, Some(mc_version))] {
|
||
let json = match inherits {
|
||
Some(parent) => format!(r#"{{"id":"{id}","inheritsFrom":"{parent}"}}"#),
|
||
None => format!(r#"{{"id":"{id}"}}"#),
|
||
};
|
||
let path = env.game.version_json(id);
|
||
std::fs::create_dir_all(path.parent().expect("版本目录")).expect("版本目录");
|
||
std::fs::write(&path, json).expect("写版本 JSON");
|
||
}
|
||
}
|
||
|
||
let profiles = ProfileList {
|
||
profiles: cases
|
||
.iter()
|
||
.map(
|
||
|(version_id, _, _, _)| unlucky_core::game::InstanceProfile {
|
||
id: unlucky_core::game::ProfileId::generate(),
|
||
name: format!("装好的 {version_id}"),
|
||
version_id: (*version_id).to_string(),
|
||
game_dir: None,
|
||
running_dir: None,
|
||
java_path: None,
|
||
created_at: None,
|
||
last_played_at: None,
|
||
extra: BTreeMap::new(),
|
||
},
|
||
)
|
||
.collect(),
|
||
..ProfileList::default()
|
||
};
|
||
unlucky_core::game::install::instance::write_profiles(&env.layout, &profiles).expect("写清单");
|
||
|
||
let snapshot = instances(&env);
|
||
assert_eq!(snapshot.len(), cases.len());
|
||
for ((version_id, mc_version, kind, badge), tile) in cases.iter().zip(&snapshot) {
|
||
assert_eq!(tile.version_id.as_str(), *version_id);
|
||
assert_eq!(
|
||
tile.mc_version.as_str(),
|
||
*mc_version,
|
||
"{version_id} 的版本号取错"
|
||
);
|
||
assert_eq!(tile.loader.kind, *kind, "{version_id} 的加载器");
|
||
assert_eq!(tile.loader.badge().as_str(), *badge, "{version_id} 的徽标");
|
||
assert!(tile.version_present, "{version_id} 的载荷应当存在");
|
||
}
|
||
std::fs::remove_dir_all(&dir).ok();
|
||
}
|
||
|
||
#[test]
|
||
fn isolation_write_then_read_round_trips() {
|
||
let dir = std::env::temp_dir().join(format!("ull-host-game-{}", std::process::id()));
|
||
let _ = std::fs::remove_dir_all(&dir);
|
||
let env = HostEnv::at_root(&dir);
|
||
std::fs::create_dir_all(env.layout.config()).expect("config 目录");
|
||
|
||
// 直接建一份 profiles.json(register 需要版本本体,这里只测隔离写读)
|
||
let profiles = ProfileList {
|
||
profiles: vec![unlucky_core::game::InstanceProfile {
|
||
id: unlucky_core::game::ProfileId::generate(),
|
||
name: "demo".to_string(),
|
||
version_id: "1.20.1".to_string(),
|
||
game_dir: None,
|
||
running_dir: None,
|
||
java_path: None,
|
||
created_at: None,
|
||
last_played_at: None,
|
||
extra: BTreeMap::new(),
|
||
}],
|
||
..ProfileList::default()
|
||
};
|
||
unlucky_core::game::install::instance::write_profiles(&env.layout, &profiles).expect("写清单");
|
||
let id = profiles.profiles[0].id.as_str().to_string();
|
||
|
||
assert_eq!(isolation_of(&env, &id), "isolated");
|
||
set_isolation(&env, &id, "shared").expect("切档位");
|
||
assert_eq!(isolation_of(&env, &id), "shared");
|
||
assert!(set_isolation(&env, &id, "nope").is_err());
|
||
std::fs::remove_dir_all(&dir).ok();
|
||
}
|