Files
Unlucky-Launcher/crates/core/src/launch/argument.rs
T
AskaEth bc41dfcf7d
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
Guards / core 零 UI 守门 (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Unlucky Launcher v0.0.1-alpha
Rust 原生单文件 Minecraft 启动器:CLI 完整可用(install/launch/accounts/java/diagnose/agent),
MCP server 接入外部 Agent 工具,GUI 开发中。
2026-10-31 21:54:10 +08:00

206 lines
6.0 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.
//! 清单参数模型:`arguments.jvm` / `arguments.game` 的 `{rules, value}` 求值。
//!
//! 平移自 `analyse/02` §4.7:`Argument` 是 `StringArgument | RuledArgument`,
//! `CompatibilityRule` 默认 `DISALLOW`、按顺序取最后一个命中的 action;
//! `os` 不匹配的规则整体“无效”(不是“不满足”),`features` 必须逐项相等。
use crate::launch::request::FeatureSet;
/// 规则动作。
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RuleAction {
/// 允许。
Allow,
/// 禁止。
Disallow,
}
/// `rules.os` 子句。
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct OsRule {
/// `windows` / `osx` / `linux`。
pub name: Option<String>,
/// `x86` / `x86_64` / `arm64`。
pub arch: Option<String>,
/// 系统版本模式(Mojang 用正则,这里只做前缀/子串匹配)。
pub version: Option<String>,
}
/// 单条规则。
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Rule {
/// 命中后的动作。
pub action: RuleAction,
/// 系统条件。
pub os: Option<OsRule>,
/// 特性条件(逐项相等才算命中)。
pub features: Vec<(String, bool)>,
}
impl Rule {
/// 允许规则。
pub fn allow() -> Self {
Self {
action: RuleAction::Allow,
os: None,
features: Vec::new(),
}
}
/// 禁止规则。
pub fn disallow() -> Self {
Self {
action: RuleAction::Disallow,
os: None,
features: Vec::new(),
}
}
/// 加系统条件。
pub fn with_os_name(mut self, name: impl Into<String>) -> Self {
let mut os = self.os.take().unwrap_or_default();
os.name = Some(name.into());
self.os = Some(os);
self
}
/// 加特性条件。
pub fn with_feature(mut self, name: impl Into<String>, expected: bool) -> Self {
self.features.push((name.into(), expected));
self
}
/// 求值:`Some(true)` 命中且允许,`Some(false)` 命中且禁止,`None` 不适用(规则无效)。
pub fn evaluate(&self, ctx: &RuleContext<'_>) -> Option<bool> {
if let Some(os) = &self.os {
if let Some(name) = &os.name {
if name != ctx.os_name {
return None;
}
}
if let Some(arch) = &os.arch {
if !arch_matches(arch, ctx.os_arch) {
return None;
}
}
if let Some(pattern) = &os.version {
if !version_matches(pattern, ctx.os_version) {
return None;
}
}
}
for (name, expected) in &self.features {
if ctx.features.get(name) != Some(*expected) {
return None;
}
}
Some(self.action == RuleAction::Allow)
}
}
/// 一个参数项:一组规则 + 一组取值(取值里可含 `${...}` 占位符)。
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ArgumentEntry {
/// 规则(空表示无条件适用)。
pub rules: Vec<Rule>,
/// 取值(每个元素是一个独立命令行 token)。
pub values: Vec<String>,
}
impl ArgumentEntry {
/// 无条件参数项。
pub fn plain(values: impl IntoIterator<Item = impl Into<String>>) -> Self {
Self {
rules: Vec::new(),
values: values.into_iter().map(Into::into).collect(),
}
}
/// 带规则参数项。
pub fn ruled(rules: Vec<Rule>, values: impl IntoIterator<Item = impl Into<String>>) -> Self {
Self {
rules,
values: values.into_iter().map(Into::into).collect(),
}
}
/// 是否在当前环境下适用。
pub fn applies(&self, ctx: &RuleContext<'_>) -> bool {
if self.rules.is_empty() {
return true;
}
let mut allowed = false;
for rule in &self.rules {
if let Some(action) = rule.evaluate(ctx) {
allowed = action;
}
}
allowed
}
}
/// rules 求值上下文。
#[derive(Debug, Clone, Copy)]
pub struct RuleContext<'a> {
/// `rules.os.name` 取值。
pub os_name: &'a str,
/// `rules.os.arch` 取值。
pub os_arch: &'a str,
/// `rules.os.version` 取值。
pub os_version: &'a str,
/// 特性开关。
pub features: &'a FeatureSet,
}
impl<'a> RuleContext<'a> {
/// 构造。
pub fn new(
os_name: &'a str,
os_arch: &'a str,
os_version: &'a str,
features: &'a FeatureSet,
) -> Self {
Self {
os_name,
os_arch,
os_version,
features,
}
}
}
fn arch_matches(rule_arch: &str, actual: &str) -> bool {
if rule_arch == actual {
return true;
}
// Mojang 用 "x86" 表示 32 位,我们的实际值可能是 "i686" 之类。
matches!(
(rule_arch, actual),
("x86", "i686") | ("x86_64", "amd64") | ("arm64", "aarch64")
)
}
/// 只做保守匹配:支持 `^10\.` / `^10\.15` 这类前缀模式与纯子串,无法识别的模式按“不匹配”处理
/// (宁可少加一个参数,也不要给错误平台加参数)。
///
/// 处理:去掉前置 `^` 与结尾 `$`、`\.` 反转义成 `.`、去掉结尾的点(`^10\.` 的语义就是
/// “以 `10.` 开头”,点本身不参与前缀比较)。含 `[ ( { | * + ?` 等真需要正则的模式一律不匹配。
fn version_matches(pattern: &str, actual: &str) -> bool {
if actual.is_empty() {
return false;
}
let trimmed = pattern.trim();
let stripped = trimmed.strip_prefix('^').unwrap_or(trimmed);
if stripped.contains(['[', '(', '{', '|', '*', '+', '?']) {
return false;
}
let mut literal = stripped.replace("\\.", ".");
while literal.ends_with(['$', '.']) {
literal.pop();
}
if literal.is_empty() {
return false;
}
actual.starts_with(&literal) || actual.contains(&literal)
}