Unlucky Launcher v0.2.0
CI / runtime smoke (macos arm64, probe) (push) Skipped
CI / fmt (rustfmt --check) (push) Canceled after 0s
CI / clippy (ubuntu-latest) (push) Canceled after 0s
CI / clippy (windows-latest) (push) Canceled after 0s
CI / test (ubuntu-latest) (push) Canceled after 0s
CI / test (windows-latest) (push) Canceled after 0s
CI / build release (ubuntu-latest) (push) Canceled after 0s
CI / build release (windows-latest) (push) Canceled after 0s
CI / runtime smoke (ubuntu x64, xvfb) (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好一个文件) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
CI / runtime smoke (macos arm64, probe) (push) Skipped
CI / fmt (rustfmt --check) (push) Canceled after 0s
CI / clippy (ubuntu-latest) (push) Canceled after 0s
CI / clippy (windows-latest) (push) Canceled after 0s
CI / test (ubuntu-latest) (push) Canceled after 0s
CI / test (windows-latest) (push) Canceled after 0s
CI / build release (ubuntu-latest) (push) Canceled after 0s
CI / build release (windows-latest) (push) Canceled after 0s
CI / runtime smoke (ubuntu x64, xvfb) (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Guards / presenter 零 UI 守门 (push) Canceled after 0s
Guards / design 零第三方依赖守门 (push) Canceled after 0s
Guards / unsafe 落点守门(集合必须恰好一个文件) (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
首个 iced + Material 3 Expressive 版本的源码快照。 完整历史与日常提交在 dev 分支;main 只放阶段性快照。
This commit is contained in:
commit
8a8e5ca0c0
1079 files changed
+208035
No files matched your search
@@ -0,0 +1,186 @@
|
||||
//! 版本 JSON 解析:宽松解析、未知字段保真、旧版字段并存。
|
||||
|
||||
use unlucky_core::game::arguments::{Argument, ArgumentValue, RuleAction};
|
||||
use unlucky_core::game::manifest::VersionJson;
|
||||
use unlucky_core::game::version::ReleaseType;
|
||||
|
||||
use crate::helpers::parse;
|
||||
|
||||
const VANILLA_1_20_1: &str = r#"{
|
||||
"id": "1.20.1",
|
||||
"type": "release",
|
||||
"time": "2023-06-12T13:25:51+00:00",
|
||||
"releaseTime": "2023-06-12T13:25:51+00:00",
|
||||
"complianceLevel": 1,
|
||||
"minimumLauncherVersion": 21,
|
||||
"mainClass": "net.minecraft.client.main.Main",
|
||||
"assets": "5",
|
||||
"assetIndex": {
|
||||
"id": "5",
|
||||
"sha1": "a3b3b1b0e0d8f2a8f0fbe1a4b0a8f6a1c6c6a7a2",
|
||||
"size": 404297,
|
||||
"totalSize": 598385820,
|
||||
"url": "https://piston-meta.mojang.com/v1/packages/sha/5.json"
|
||||
},
|
||||
"javaVersion": { "component": "java-runtime-gamma", "majorVersion": 17 },
|
||||
"downloads": {
|
||||
"client": { "sha1": "abc", "size": 100, "url": "https://example.invalid/client.jar" },
|
||||
"client_mappings": { "sha1": "def", "size": 50, "url": "https://example.invalid/mappings.txt" }
|
||||
},
|
||||
"arguments": {
|
||||
"game": [
|
||||
"--username",
|
||||
"${auth_player_name}",
|
||||
{ "rules": [{ "action": "allow", "features": { "has_custom_resolution": true } }],
|
||||
"value": ["--width", "${resolution_width}"] }
|
||||
],
|
||||
"jvm": [
|
||||
{ "rules": [{ "action": "allow", "os": { "name": "windows", "version": "^10\\." } }],
|
||||
"value": "-Dos.name=Windows 10" }
|
||||
]
|
||||
},
|
||||
"libraries": [
|
||||
{ "name": "com.mojang:logging:1.1.1",
|
||||
"downloads": { "artifact": { "path": "com/mojang/logging/1.1.1/logging-1.1.1.jar",
|
||||
"sha1": "aaa", "size": 10,
|
||||
"url": "https://example.invalid/logging.jar" } } }
|
||||
],
|
||||
"logging": {
|
||||
"client": { "argument": "-Dlog4j.configurationFile=${path}",
|
||||
"file": { "id": "client-1.12.xml", "sha1": "bbb", "size": 20,
|
||||
"url": "https://example.invalid/log4j.xml" },
|
||||
"type": "log4j2-xml" }
|
||||
},
|
||||
"tlauncherVersion": 1,
|
||||
"MMC-hint": "local"
|
||||
}"#;
|
||||
|
||||
#[test]
|
||||
fn parses_full_version_json() {
|
||||
let version = parse(VANILLA_1_20_1);
|
||||
|
||||
assert_eq!(version.id.as_deref(), Some("1.20.1"));
|
||||
assert_eq!(version.version_type, Some(ReleaseType::Release));
|
||||
assert_eq!(
|
||||
version.main_class.as_deref(),
|
||||
Some("net.minecraft.client.main.Main")
|
||||
);
|
||||
assert_eq!(version.compliance_level, Some(1));
|
||||
assert_eq!(version.minimum_launcher_version, Some(21));
|
||||
assert_eq!(version.assets_id(), "5");
|
||||
|
||||
let asset_index = version.asset_index.as_ref().expect("assetIndex 应解析");
|
||||
assert_eq!(asset_index.id, "5");
|
||||
assert_eq!(asset_index.total_size, Some(598_385_820));
|
||||
|
||||
let java = version.java_version.as_ref().expect("javaVersion 应解析");
|
||||
assert_eq!(java.component.as_deref(), Some("java-runtime-gamma"));
|
||||
assert_eq!(java.major_version, Some(17));
|
||||
|
||||
let downloads = version.downloads.as_ref().expect("downloads 应解析");
|
||||
assert_eq!(downloads.len(), 2, "未知键也要保留在映射里");
|
||||
assert_eq!(
|
||||
downloads.client().and_then(|c| c.url.as_deref()),
|
||||
Some("https://example.invalid/client.jar")
|
||||
);
|
||||
assert!(downloads.get("client_mappings").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_unknown_fields_verbatim() {
|
||||
let version = parse(VANILLA_1_20_1);
|
||||
// 第三方启动器私有字段(rawJson 保真,analyse/03 §4.2)。
|
||||
assert_eq!(
|
||||
version.extra.get("tlauncherVersion"),
|
||||
Some(&serde_json::json!(1))
|
||||
);
|
||||
assert_eq!(
|
||||
version.extra.get("MMC-hint"),
|
||||
Some(&serde_json::json!("local"))
|
||||
);
|
||||
|
||||
let roundtrip = version.to_json_string().expect("应可序列化");
|
||||
let reparsed = VersionJson::from_json_str(&roundtrip).expect("应可再解析");
|
||||
assert_eq!(reparsed.extra, version.extra);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_arguments_with_rules() {
|
||||
let version = parse(VANILLA_1_20_1);
|
||||
let arguments = version.arguments.as_ref().expect("arguments 应解析");
|
||||
|
||||
assert_eq!(arguments.game.len(), 3);
|
||||
assert_eq!(arguments.jvm.len(), 1);
|
||||
assert!(matches!(&arguments.game[0], Argument::Plain(value) if value == "--username"));
|
||||
|
||||
let conditional = match &arguments.game[2] {
|
||||
Argument::Conditional(cond) => cond,
|
||||
Argument::Plain(_) => panic!("第三项应是条件参数"),
|
||||
};
|
||||
assert_eq!(conditional.rules.len(), 1);
|
||||
assert_eq!(conditional.rules[0].action, RuleAction::Allow);
|
||||
assert_eq!(
|
||||
conditional.rules[0]
|
||||
.features
|
||||
.as_ref()
|
||||
.and_then(|f| f.get("has_custom_resolution")),
|
||||
Some(&true)
|
||||
);
|
||||
assert_eq!(
|
||||
conditional.value,
|
||||
ArgumentValue::Many(vec!["--width".into(), "${resolution_width}".into()])
|
||||
);
|
||||
|
||||
// jvm 里的 os 限制带正则版本模式。
|
||||
let jvm = match &arguments.jvm[0] {
|
||||
Argument::Conditional(cond) => cond,
|
||||
Argument::Plain(_) => panic!("jvm 第一项应是条件参数"),
|
||||
};
|
||||
let os = jvm.rules[0].os.as_ref().expect("应有 os 限制");
|
||||
assert_eq!(os.name.as_deref(), Some("windows"));
|
||||
assert_eq!(os.version.as_deref(), Some("^10\\."));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_libraries_and_logging() {
|
||||
let version = parse(VANILLA_1_20_1);
|
||||
let libraries = version.libraries.as_ref().expect("libraries 应解析");
|
||||
assert_eq!(libraries.len(), 1);
|
||||
let artifact = libraries[0].artifact().expect("应有 artifact");
|
||||
assert_eq!(
|
||||
artifact.path.as_deref(),
|
||||
Some("com/mojang/logging/1.1.1/logging-1.1.1.jar")
|
||||
);
|
||||
|
||||
let logging = version.logging.as_ref().expect("logging 应解析");
|
||||
let client = logging.client().expect("应有 client 日志配置");
|
||||
assert_eq!(client.kind.as_deref(), Some("log4j2-xml"));
|
||||
assert_eq!(client.file.id.as_deref(), Some("client-1.12.xml"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tolerant_defaults_for_minimal_json() {
|
||||
let version = parse(r#"{ "id": "b1.7.3" }"#);
|
||||
assert_eq!(version.id.as_deref(), Some("b1.7.3"));
|
||||
assert_eq!(version.version_type, None, "未知 type 不报错");
|
||||
assert_eq!(version.client_jar(), None, "老 JSON 没有 downloads.client");
|
||||
assert_eq!(version.assets_id(), "legacy", "assets 缺失按 legacy");
|
||||
assert!(!version.is_resolved());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_type_value_falls_back_to_unknown() {
|
||||
let version = parse(r#"{ "id": "x", "type": "some_new_kind" }"#);
|
||||
assert_eq!(version.version_type, Some(ReleaseType::Unknown));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_id_is_reported() {
|
||||
let version = parse(r#"{ "mainClass": "a.B" }"#);
|
||||
let error = version.require_id().expect_err("缺 id 应报错");
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
"版本 JSON 缺少 id",
|
||||
"错误文案要给用户看得懂"
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user