Files
Unlucky-Launcher/crates/core/src/diagnose/environment.rs
T
AskaEth bf6b0c4126 build(release): workspace 版本 0.1.0 → 0.2.0,README 的 tag 事实改成 v0.2.0 口径
- Cargo.toml [workspace.package] version = 0.2.0;Cargo.lock 由 cargo 重新解出一致
  (只有 8 个 unlucky-* 成员条目变动,第三方版本一字未动)
- 各 crate 的 `unlucky-*` path 依赖 `version = "0.1"` 同步提到 "0.2",否则工作区
  根本解析不出来(cargo metadata 会报 `failed to select a version for
  unlucky-core = "^0.1"`);crates/app 虽被 exclude、cargo 不读,也一并跟上避免将来
  重新纳入时立刻解析失败
- core::launch::LauncherConfig 兜底的 launcher_version 改成 env!("CARGO_PKG_VERSION"),
  不再是把版本写死一份(CLI / presenter 本来就这么写)
- README.md / README.zh-CN.md:删掉「v0.1.0 是目前唯一的 tag / the only tagged release」
  这类随 v0.2.0 发布即变假的陈述,改写成「v0.1.0 是最后一个 Slint 版 tag,v0.2.0 是
  重写后的第一个 tag」;所有讲 v0.1.0 资产 / Slint Royalty-Free 义务的历史记录原样保留
- packaging/README.md:命名与校验示例跟当前 tag 走(v0.2.0);§7 实测记录里的 0.1.0
  输出加一句「属实测记录、不回填」,并把过期的「四个 workspace member」改成「任何一个」
2026-11-05 14:08:37 +08:00

156 lines
5.2 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.
//! 环境快照与 mod 清单(analyse/02 §7.1 信号 E)。
//!
//! HMCL 只在崩溃窗口里散着展示这些信息;我们做成结构化数据,
//! 既进诊断输出(`environment.json` 一样的机器可读上下文),也让规则能按版本号做前置判断。
//! 由 `collectors.rs` 用 `#[path]` 声明并统一导出。
use std::path::PathBuf;
/// 一个已装组件的版本(Forge/Fabric/OptiFine…)。
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ComponentVersion {
/// 组件名(`forge`/`fabric`/`optifine`/`minecraft`…)。
pub name: String,
/// 版本串。
pub version: String,
}
impl ComponentVersion {
/// 构造。
#[must_use]
pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
Self {
name: name.into(),
version: version.into(),
}
}
}
/// 一个已装 mod(供交叉验证「冲突/mixin 失败」类结论,analyse/02 §8.4-10)。
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ModRecord {
/// modid(解析不出时为空)。
pub id: String,
/// 展示名。
pub name: Option<String>,
/// 版本。
pub version: Option<String>,
/// jar 文件名(诊断包里给人看)。
pub file: Option<String>,
/// 是否启用(`.disabled` 视为未启用)。
pub enabled: bool,
}
impl ModRecord {
/// 从文件名构造(不解 jar,P0 足够:冲突类结论要的是「装了哪些文件」)。
#[must_use]
pub fn from_file_name(file: &str) -> Self {
let enabled = !file.ends_with(".disabled");
let stem = file
.trim_end_matches(".disabled")
.trim_end_matches(".jar")
.to_owned();
let (name, version) = stem
.rsplit_once('-')
.filter(|(_, version)| version.chars().next().is_some_and(|ch| ch.is_ascii_digit()))
.map_or((stem.clone(), None), |(name, version)| {
(name.to_owned(), Some(version.to_owned()))
});
Self {
id: name.to_lowercase(),
name: Some(name),
version,
file: Some(file.to_owned()),
enabled,
}
}
}
/// 启动环境快照(Java/内存/OS/组件/mod 清单)。
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct EnvironmentSnapshot {
/// 启动器版本(`ull 0.2.0`)。
pub launcher: Option<String>,
/// 实例 id。
pub instance_id: Option<String>,
/// 游戏版本。
pub game_version: Option<String>,
/// 操作系统名(`Windows 10.0.19045`)。
pub os_name: Option<String>,
/// 架构(`x86_64` / `aarch64`)。
pub os_arch: Option<String>,
/// 物理内存(MB)。
pub physical_memory_mb: Option<u64>,
/// Java 可执行文件。
pub java_path: Option<PathBuf>,
/// Java 大版本。
pub java_major: Option<u32>,
/// Java 位数(32 / 64)。
pub java_bits: Option<u32>,
/// `-Xmx`(MB)。
pub xmx_mb: Option<u32>,
/// `-Xms`(MB)。
pub xms_mb: Option<u32>,
/// 工作目录(跑游戏的 cwd)。
pub run_dir: Option<PathBuf>,
/// 已装组件版本。
pub components: Vec<ComponentVersion>,
/// 已装 mod。
pub mods: Vec<ModRecord>,
}
impl EnvironmentSnapshot {
/// 一行人类可读摘要(证据行 / UI 头部)。
#[must_use]
pub fn summary(&self) -> String {
let os = format!(
"{} {}",
self.os_name.as_deref().unwrap_or("未知系统"),
self.os_arch.as_deref().unwrap_or("")
);
let java = self.java_major.map_or_else(
|| "Java 未知".to_owned(),
|major| match self.java_bits {
Some(bits) => format!("Java {major}({bits} 位)"),
None => format!("Java {major}"),
},
);
let memory = match (self.xmx_mb, self.physical_memory_mb) {
(Some(xmx), Some(physical)) => format!("-Xmx {xmx} MB / 物理 {physical} MB"),
(Some(xmx), None) => format!("-Xmx {xmx} MB"),
(None, Some(physical)) => format!("物理 {physical} MB"),
(None, None) => "内存未知".to_owned(),
};
let mods = self.mods.len();
format!("{} / {} / {} / {} 个 mod", os.trim(), java, memory, mods)
}
/// 是不是 32 位 Java(`-Xmx` 超过 ~1.5 GB 必崩的直接证据)。
#[must_use]
pub fn is_java_32bit(&self) -> bool {
self.java_bits == Some(32)
}
/// 组件版本(按名字查,`optifine` 之类的冲突规则用)。
#[must_use]
pub fn component(&self, name: &str) -> Option<&str> {
self.components
.iter()
.find(|component| component.name.eq_ignore_ascii_case(name))
.map(|component| component.version.as_str())
}
/// 是否装了某个 mod(按 id 或文件名模糊匹配)。
#[must_use]
pub fn has_mod(&self, id: &str) -> bool {
let needle = id.to_ascii_lowercase();
self.mods.iter().any(|record| {
record.id.contains(&needle)
|| record
.file
.as_deref()
.is_some_and(|file| file.to_ascii_lowercase().contains(&needle))
})
}
}