Unlucky Launcher v0.0.1-alpha
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

Rust 原生单文件 Minecraft 启动器:CLI 完整可用(install/launch/accounts/java/diagnose/agent),
MCP server 接入外部 Agent 工具,GUI 开发中。
This commit is contained in:
AskaEth committed 2026-10-31 21:54:10 +08:00
commit bc41dfcf7d
517 files changed
+92844

No files matched your search

+279
View File
@@ -0,0 +1,279 @@
//! 引擎内置结论:退出码 / 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,
)
}