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

+165
View File
@@ -0,0 +1,165 @@
//! launch 模块统一错误类型。
//!
//! 设计依据:`DevDocs/PlanDoc/06-launch-and-diagnostics.md` §1、`analyse/02` §8.4-3
//! (缺库/缺组件必须结构化报错并显式失败,而不是交给运行时 `NoClassDefFoundError`)。
use std::fmt;
use std::path::PathBuf;
/// launch 相关操作的统一结果类型。
pub type Result<T> = std::result::Result<T, LaunchError>;
/// 缺失的组件(库 / natives / 资产),供 UI 与 agent 直接展示与修复。
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MissingComponent {
/// 逻辑名,例如 `org.lwjgl:lwjgl:3.3.1` 或 `libraries/foo/bar.jar`。
pub name: String,
/// 期望落盘路径(未知时为 `None`)。
pub path: Option<PathBuf>,
}
impl MissingComponent {
/// 只按名字构造(路径未知)。
pub fn named(name: impl Into<String>) -> Self {
Self {
name: name.into(),
path: None,
}
}
/// 带期望路径构造。
pub fn at_path(name: impl Into<String>, path: impl Into<PathBuf>) -> Self {
Self {
name: name.into(),
path: Some(path.into()),
}
}
}
/// 命令行组装阶段的失败原因(全部是“启动前就能确定”的错误)。
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AssemblyError {
/// 清单里没有主类,无法启动(HMCL 在此直接抛异常)。
MissingMainClass,
/// classpath 为空(一个库和主 jar 都没有)。
EmptyClasspath,
/// Java 可执行文件路径为空。
EmptyJavaPath,
/// 组件不完整:启动前显式失败,附结构化清单。
MissingComponents(Vec<MissingComponent>),
/// 清单参数畸形(例如空参数项)。
MalformedArgument {
/// 原始参数项文本。
entry: String,
/// 原因说明。
reason: &'static str,
},
/// natives 解压失败(jar 缺失/损坏、目录不可写)。
///
/// 归到「启动前失败」而不是运行时错误:native 库没落地时游戏只会给出
/// `UnsatisfiedLinkError`,那时候已经离「到底哪个 jar 坏了」太远了。
NativesExtraction {
/// 人读原因(含失败路径)。
message: String,
},
}
impl fmt::Display for AssemblyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MissingMainClass => f.write_str("版本清单缺少主类(mainClass)"),
Self::EmptyClasspath => f.write_str("classpath 为空:没有任何库或主 jar"),
Self::EmptyJavaPath => f.write_str("Java 可执行文件路径为空"),
Self::MissingComponents(items) => {
write!(f, "组件缺失 {} 项", items.len())?;
for item in items.iter().take(8) {
write!(f, "; {}", item.name)?;
}
Ok(())
}
Self::MalformedArgument { entry, reason } => {
write!(f, "参数畸形({reason}):{entry}")
}
Self::NativesExtraction { message } => {
write!(f, "natives 解压失败:{message}")
}
}
}
}
impl std::error::Error for AssemblyError {}
/// launch 模块的统一错误。
#[derive(Debug)]
pub enum LaunchError {
/// 命令行组装失败(纯计算,可单测)。
Assembly(AssemblyError),
/// 文件系统操作失败。
Io {
/// 操作名(`create_session` / `read_argfile` 等)。
op: &'static str,
/// 相关路径。
path: PathBuf,
/// 底层错误。
source: std::io::Error,
},
/// 进程派生失败(Java 不存在、权限不足等)。
Spawn {
/// 被执行的程序。
program: PathBuf,
/// 底层错误。
source: std::io::Error,
},
/// 会话事件流写入/解析失败。
Session {
/// 会话文件路径。
path: PathBuf,
/// 原因说明。
reason: String,
},
/// 进程树 kill 失败。
Kill {
/// 目标进程号。
pid: u32,
/// 底层错误。
source: std::io::Error,
},
}
impl fmt::Display for LaunchError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Assembly(err) => write!(f, "命令行组装失败:{err}"),
Self::Io { op, path, source } => {
write!(f, "{op} 失败({}):{source}", path.display())
}
Self::Spawn { program, source } => {
write!(f, "启动进程失败({}):{source}", program.display())
}
Self::Session { path, reason } => {
write!(f, "会话事件流错误({}):{reason}", path.display())
}
Self::Kill { pid, source } => {
write!(f, "结束进程树失败(pid {pid}):{source}")
}
}
}
}
impl std::error::Error for LaunchError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Assembly(err) => Some(err),
Self::Io { source, .. } | Self::Spawn { source, .. } | Self::Kill { source, .. } => {
Some(source)
}
Self::Session { .. } => None,
}
}
}
impl From<AssemblyError> for LaunchError {
fn from(err: AssemblyError) -> Self {
Self::Assembly(err)
}
}