Files
Unlucky-Launcher/crates/core/src/diagnose/synthetic.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

280 lines
11 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.
//! 引擎内置结论:退出码 / hs_err 这类**结构化信号**没有文本签名,塞 YAML 别扭,放在这里。
//!
//! 它们参与和其它规则一样的排序与抑制流程(`priority`/`confidence`/`suppress` 语义相同),
//! 所以「主因是驱动、次生是图形规则」这种关系能自动成立。
//! 由 `engine.rs` 用 `#[path]` 声明,拆分只为守单文件 400 行上限。
use super::{DiagnosisInput, Evidence, Finding};
use crate::diagnose::collectors::{
ExitSignal, ExitType, SignalOrigin, EXIT_DLL_NOT_FOUND, EXIT_STACK_BUFFER_OVERRUN,
};
use crate::diagnose::hs_err::{HsErrKind, HsErrReport};
use crate::diagnose::rules::{
AdjustMemory, ChangeGraphics, ChangeJava, ComponentKind, FixAction, GraphicsSetting,
JavaVendor, Reinstall, RuleCategory, SeekHelp, Severity,
};
/// 所有内置结论。
#[must_use]
pub fn synthetic_findings(input: &DiagnosisInput) -> Vec<Finding> {
let mut out = Vec::new();
if let Some(exit) = &input.exit {
out.extend(exit_findings(exit));
}
if let Some(report) = &input.hs_err {
out.extend(hs_err_findings(report));
}
out
}
fn exit_findings(exit: &ExitSignal) -> Vec<Finding> {
let mut out = Vec::new();
if exit.is_access_violation() {
let mut finding = base(
"signal.access_violation",
RuleCategory::Graphics,
Severity::Critical,
0.55,
82,
"diagnose.cause.signal.access_violation",
&format!(
"进程以 0xC0000005(访问违例)退出:多半是显卡驱动/渲染栈崩溃,建议先更新显卡驱动或换渲染器({})",
exit.summary()
),
);
finding.fix_actions = vec![FixAction::ChangeGraphics(ChangeGraphics {
setting: GraphicsSetting::Driver,
update_driver: true,
suggested_value: None,
note: Some("访问违例优先怀疑显卡驱动".to_owned()),
})];
finding.suppress = vec!["graphics.graphics_driver".to_owned()];
finding.evidence = vec![Evidence::new(SignalOrigin::Launcher, 0, exit.summary())];
out.push(finding);
}
if exit.code == Some(EXIT_DLL_NOT_FOUND) {
let mut finding = base(
"signal.dll_not_found",
RuleCategory::Integrity,
Severity::Critical,
0.7,
85,
"diagnose.cause.signal.dll_not_found",
"进程以 0xC0000135(找不到 DLL)退出:缺原生库或 Java 安装损坏,重装 natives/Java 组件",
);
finding.fix_actions = vec![FixAction::Reinstall(Reinstall {
component: ComponentKind::Natives,
path_hint: None,
note: Some("找不到 DLL:优先修 natives,其次重装 Java".to_owned()),
})];
finding.evidence = vec![Evidence::new(SignalOrigin::Launcher, 0, exit.summary())];
out.push(finding);
}
if exit.code == Some(EXIT_STACK_BUFFER_OVERRUN) {
let mut finding = base(
"signal.stack_buffer_overrun",
RuleCategory::Jvm,
Severity::Warning,
0.5,
60,
"diagnose.cause.signal.stack_buffer_overrun",
"进程以 0xC0000409(栈缓冲区溢出)退出:JVM/原生库内部错误,需要 hs_err 或崩溃报告进一步判断",
);
finding.fix_actions = vec![FixAction::SeekHelp(SeekHelp {
export_diagnostics: true,
needs_more_info: vec!["run 目录下的 hs_err_pid*.log".to_owned()],
note: None,
})];
finding.evidence = vec![Evidence::new(SignalOrigin::Launcher, 0, exit.summary())];
out.push(finding);
}
if exit.exit_type == ExitType::Sigkill {
let mut finding = base(
"signal.sigkill",
RuleCategory::Memory,
Severity::Warning,
0.6,
70,
"diagnose.cause.signal.sigkill",
"进程被 SIGKILL 强杀:常见于系统内存耗尽(OOM killer)或外部清理进程,建议降低 -Xmx 或扩大虚拟内存",
);
finding.fix_actions = vec![FixAction::AdjustMemory(AdjustMemory {
increase_system_swap: true,
note: Some("被强杀:先确认系统内存是否够".to_owned()),
..AdjustMemory::default()
})];
finding.evidence = vec![Evidence::new(SignalOrigin::Launcher, 0, exit.summary())];
out.push(finding);
}
if exit.exit_type == ExitType::JvmError {
let mut finding = base(
"signal.jvm_error",
RuleCategory::Jvm,
Severity::Critical,
0.55,
72,
"diagnose.cause.signal.jvm_error",
"JVM 未能启动(游戏根本没跑起来):先查 -Xmx 是否超过可用内存、JVM 参数是否非法",
);
finding.fix_actions = vec![
FixAction::AdjustMemory(AdjustMemory {
increase_system_swap: true,
note: Some("JVM 初始化失败,优先怀疑 -Xmx 过大".to_owned()),
..AdjustMemory::default()
}),
FixAction::ChangeJava(ChangeJava {
vendor: JavaVendor::Hotspot,
note: Some("换成 HotSpot 的官方 Java 试试".to_owned()),
..ChangeJava::default()
}),
];
finding.evidence = vec![Evidence::new(SignalOrigin::Launcher, 0, exit.summary())];
out.push(finding);
}
if exit.silent_failure {
let mut finding = base(
"signal.silent_failure",
RuleCategory::Other,
Severity::Warning,
0.4,
55,
"diagnose.cause.signal.silent_failure",
&format!(
"退出码为 0 但疑似静默失败({} 条错误行):游戏可能刚起来就被 mod 异常终止,需要更长的日志确认",
exit.error_lines
),
);
finding.fix_actions = vec![FixAction::SeekHelp(SeekHelp {
export_diagnostics: true,
needs_more_info: vec!["把日志行数上限调到 20000 后复现一次".to_owned()],
note: None,
})];
finding.evidence = vec![Evidence::new(SignalOrigin::Launcher, 0, exit.summary())];
out.push(finding);
}
out
}
fn hs_err_findings(report: &HsErrReport) -> Vec<Finding> {
let mut out = Vec::new();
match report.kind {
HsErrKind::AccessViolation => {
let module = report
.failed_frame
.as_ref()
.and_then(|frame| frame.module.clone())
.unwrap_or_else(|| "未知模块".to_owned());
let mut finding = base(
"hs_err.access_violation",
RuleCategory::Graphics,
Severity::Critical,
0.75,
88,
"diagnose.cause.hs_err.access_violation",
&format!(
"JVM 致命错误 {module}(访问违例,失败帧 {}):原生层崩溃,显卡驱动/渲染栈是首要嫌疑",
report
.failed_frame
.as_ref()
.map_or("未知", |frame| frame.raw.as_str())
),
);
finding.fix_actions = vec![
FixAction::ChangeGraphics(ChangeGraphics {
setting: GraphicsSetting::Driver,
update_driver: true,
suggested_value: None,
note: Some(format!("失败帧落在 {module}")),
}),
FixAction::SeekHelp(SeekHelp {
export_diagnostics: true,
needs_more_info: Vec::new(),
note: Some("把 hs_err 一并上报,便于确认驱动版本".to_owned()),
}),
];
finding.suppress = vec!["graphics.graphics_driver".to_owned()];
finding.evidence = vec![Evidence::new(SignalOrigin::HsErr, 1, report.summary())];
out.push(finding);
}
HsErrKind::OutOfMemory => {
let memory = report
.memory
.as_ref()
.map_or_else(String::new, |memory| memory.summary());
let mut finding = base(
"hs_err.out_of_memory",
RuleCategory::Memory,
Severity::Critical,
0.85,
86,
"diagnose.cause.hs_err.out_of_memory",
&format!("JVM 因内存不足终止:{memory},降低 -Xmx 或扩大系统虚拟内存"),
);
finding.fix_actions = vec![FixAction::AdjustMemory(AdjustMemory {
increase_system_swap: true,
note: Some(memory),
..AdjustMemory::default()
})];
finding.evidence = vec![Evidence::new(SignalOrigin::HsErr, 1, report.summary())];
out.push(finding);
}
HsErrKind::StackOverflow => {
let mut finding = base(
"hs_err.stack_overflow",
RuleCategory::ModRuntime,
Severity::Warning,
0.6,
62,
"diagnose.cause.hs_err.stack_overflow",
"JVM 栈溢出导致的致命错误:通常是某个 mod 的递归/死循环,禁用最近装的 mod 再试",
);
finding.fix_actions = vec![FixAction::SeekHelp(SeekHelp {
export_diagnostics: true,
needs_more_info: vec!["最近安装/更新的 mod 列表".to_owned()],
note: None,
})];
finding.evidence = vec![Evidence::new(SignalOrigin::HsErr, 1, report.summary())];
out.push(finding);
}
HsErrKind::InternalError => {
let mut finding = base(
"hs_err.internal_error",
RuleCategory::Jvm,
Severity::Warning,
0.5,
62,
"diagnose.cause.hs_err.internal_error",
"JVM 内部致命错误:优先换官方 HotSpot Java,其次怀疑原生库(natives)损坏",
);
finding.fix_actions = vec![FixAction::ChangeJava(ChangeJava {
vendor: JavaVendor::Hotspot,
..ChangeJava::default()
})];
finding.evidence = vec![Evidence::new(SignalOrigin::HsErr, 1, report.summary())];
out.push(finding);
}
HsErrKind::Other => {}
}
out
}
fn base(
rule_id: &str,
category: RuleCategory,
severity: Severity,
confidence: f32,
priority: u8,
message_key: &str,
message: &str,
) -> Finding {
Finding::skeleton(
rule_id,
category,
severity,
confidence,
priority,
message_key,
message,
)
}