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

+186
View File
@@ -0,0 +1,186 @@
//! 宿主平台探测与标准目录解析。
//!
//! 环境变量读取走 [`EnvSource`] 注入,因此三模式落点可以在单测里确定性地覆盖各 OS。
//! 设计出处:`DevDocs/PlanDoc/04-data-and-config.md` §1(三模式落点)。
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
/// 需要分支处理的宿主平台族。
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Platform {
/// Windows。
Windows,
/// macOS。
MacOs,
/// Linux 及其它 Unix。
Linux,
/// 其它(按 Linux 的 XDG 习惯兜底)。
Other,
}
impl Platform {
/// 编译目标决定的当前平台。
#[must_use]
pub const fn current() -> Self {
if cfg!(windows) {
Self::Windows
} else if cfg!(target_os = "macos") {
Self::MacOs
} else if cfg!(unix) {
Self::Linux
} else {
Self::Other
}
}
/// 稳定名(写日志 / 序列化用)。
#[must_use]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Windows => "windows",
Self::MacOs => "macos",
Self::Linux => "linux",
Self::Other => "other",
}
}
}
/// 环境变量来源(生产用 [`ProcessEnv`],测试用 [`MapEnv`])。
pub trait EnvSource {
/// 读一个环境变量;空串按不存在处理。
fn var(&self, name: &str) -> Option<String>;
}
/// 真实进程环境。
#[derive(Debug, Clone, Copy, Default)]
pub struct ProcessEnv;
impl ProcessEnv {
/// 构造。
#[must_use]
pub const fn new() -> Self {
Self
}
}
impl EnvSource for ProcessEnv {
fn var(&self, name: &str) -> Option<String> {
std::env::var(name)
.ok()
.filter(|value| !value.trim().is_empty())
}
}
/// 固定环境(测试 / 便携化封装用)。
#[derive(Debug, Clone, Default)]
pub struct MapEnv {
vars: BTreeMap<String, String>,
}
impl MapEnv {
/// 空环境。
#[must_use]
pub fn new() -> Self {
Self::default()
}
/// 链式塞一条变量:`MapEnv::new().with("HOME", "/home/u")`。
#[must_use]
pub fn with(mut self, name: &str, value: &str) -> Self {
self.vars.insert(name.to_owned(), value.to_owned());
self
}
}
impl EnvSource for MapEnv {
fn var(&self, name: &str) -> Option<String> {
self.vars.get(name).cloned()
}
}
/// 用户主目录。
#[must_use]
pub fn home_dir(env: &dyn EnvSource) -> Option<PathBuf> {
if cfg!(windows) {
env.var("USERPROFILE")
.or_else(|| env.var("HOME"))
.map(PathBuf::from)
} else {
env.var("HOME").map(PathBuf::from)
}
}
/// 可执行文件所在目录(Portable 模式的根)。
///
/// Linux 上 `/proc/self/exe` 由 `std` 处理;AppImage 场景下指向挂载点内的 exe,
/// 因此 AppImage 想要「便携到 exe 旁边」需由打包层设置环境变量接管(见 §1 细节)。
#[must_use]
pub fn exe_dir() -> Option<PathBuf> {
std::env::current_exe()
.ok()
.and_then(|exe| exe.parent().map(Path::to_path_buf))
}
/// Standard 模式下的启动器数据目录(`.ULL` 的等价物)。
///
/// - Windows:`%APPDATA%\ULL`
/// - macOS:`~/Library/Application Support/ULL`
/// - Linux / 其它:`$XDG_DATA_HOME/ull`,回落 `~/.local/share/ull`
#[must_use]
pub fn standard_launcher_home(platform: Platform, env: &dyn EnvSource) -> Option<PathBuf> {
match platform {
Platform::Windows => roaming_app_data(env).map(|dir| dir.join("ULL")),
Platform::MacOs => home_dir(env).map(|home| home.join("Library/Application Support/ULL")),
Platform::Linux | Platform::Other => xdg_data_home(env).map(|dir| dir.join("ull")),
}
}
/// Standard 模式下的游戏目录(Mojang 约定,与 HMCL 的 HOME 目录一致)。
///
/// - Windows:`%APPDATA%\.minecraft`
/// - macOS:`~/Library/Application Support/minecraft`
/// - Linux / 其它:`~/.minecraft`
#[must_use]
pub fn standard_game_home(platform: Platform, env: &dyn EnvSource) -> Option<PathBuf> {
match platform {
Platform::Windows => roaming_app_data(env).map(|dir| dir.join(".minecraft")),
Platform::MacOs => {
home_dir(env).map(|home| home.join("Library/Application Support/minecraft"))
}
Platform::Linux | Platform::Other => home_dir(env).map(|home| home.join(".minecraft")),
}
}
/// `%APPDATA%`,回落 `%USERPROFILE%\AppData\Roaming`。
fn roaming_app_data(env: &dyn EnvSource) -> Option<PathBuf> {
env.var("APPDATA").map(PathBuf::from).or_else(|| {
env.var("USERPROFILE")
.map(|profile| PathBuf::from(profile).join("AppData/Roaming"))
})
}
/// `$XDG_DATA_HOME`,回落 `~/.local/share`。
fn xdg_data_home(env: &dyn EnvSource) -> Option<PathBuf> {
env.var("XDG_DATA_HOME")
.map(PathBuf::from)
.filter(|dir| dir.is_absolute())
.or_else(|| home_dir(env).map(|home| home.join(".local/share")))
}
/// 写成可搬运字符串:相对路径统一用 `/`(HMCL `PortablePath` 做法),绝对路径原样保留。
#[must_use]
pub fn to_portable_string(path: &Path) -> String {
let text = path.to_string_lossy();
if path.is_absolute() {
text.into_owned()
} else {
text.replace('\\', "/")
}
}
/// 解析可搬运字符串。
#[must_use]
pub fn from_portable_string(text: &str) -> PathBuf {
PathBuf::from(text)
}