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
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:
commit
bc41dfcf7d
517 files changed
+92844
No files matched your search
@@ -0,0 +1,223 @@
|
||||
//! `.ULL/` 四分区布局——所有模块取路径的唯一入口,禁止各自手拼路径。
|
||||
//!
|
||||
//! 布局依据 `DevDocs/PlanDoc/04-data-and-config.md` §2(承 HMCL 四分区):
|
||||
//! `config/`(schema 化 JSON)、`data/`(可重建但优先保留)、`cache/`(随时可删)、`logs/`。
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use super::error::{io_at, PlatformError, PlatformResult};
|
||||
|
||||
/// Portable 模式下启动器数据目录名(exe 同目录)。
|
||||
pub const LAUNCHER_HOME_DIR: &str = ".ULL";
|
||||
/// 游戏数据目录名。
|
||||
pub const GAME_HOME_DIR: &str = ".minecraft";
|
||||
/// 分区:配置。
|
||||
pub const CONFIG_DIR: &str = "config";
|
||||
/// 分区:状态数据。
|
||||
pub const DATA_DIR: &str = "data";
|
||||
/// 分区:纯缓存。
|
||||
pub const CACHE_DIR: &str = "cache";
|
||||
/// 分区:启动器日志。
|
||||
pub const LOGS_DIR: &str = "logs";
|
||||
|
||||
/// 全局设置文件名。
|
||||
pub const LAUNCHER_CONFIG_FILE: &str = "launcher.json";
|
||||
/// 实例档案列表文件名。
|
||||
pub const PROFILES_CONFIG_FILE: &str = "profiles.json";
|
||||
/// 每实例设置所在子目录。
|
||||
pub const INSTANCES_DIR: &str = "instances";
|
||||
/// 迁移后留在旧目录的指针文件名。
|
||||
pub const REDIRECT_FILE: &str = "redirect.json";
|
||||
/// 目录迁移收据文件名(写在目标 `config/`)。
|
||||
pub const MIGRATION_RECEIPT_FILE: &str = "migration-receipt.json";
|
||||
|
||||
/// `.ULL/` 下的四个分区。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Partition {
|
||||
/// `config/`:配置(迁移时优先搬)。
|
||||
Config,
|
||||
/// `data/`:状态数据(不可丢)。
|
||||
Data,
|
||||
/// `cache/`:纯缓存(可丢,迁移可选)。
|
||||
Cache,
|
||||
/// `logs/`:启动器日志。
|
||||
Logs,
|
||||
}
|
||||
|
||||
impl Partition {
|
||||
/// 全部四个分区。
|
||||
pub const ALL: [Self; 4] = [Self::Config, Self::Data, Self::Cache, Self::Logs];
|
||||
|
||||
/// 分区目录名。
|
||||
#[must_use]
|
||||
pub const fn dir_name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Config => CONFIG_DIR,
|
||||
Self::Data => DATA_DIR,
|
||||
Self::Cache => CACHE_DIR,
|
||||
Self::Logs => LOGS_DIR,
|
||||
}
|
||||
}
|
||||
|
||||
/// 稳定名(日志 / 收据用)。
|
||||
#[must_use]
|
||||
pub const fn as_str(&self) -> &'static str {
|
||||
self.dir_name()
|
||||
}
|
||||
|
||||
/// 是否为可丢弃分区(迁移时可跳过)。
|
||||
#[must_use]
|
||||
pub const fn is_disposable(&self) -> bool {
|
||||
matches!(self, Self::Cache)
|
||||
}
|
||||
}
|
||||
|
||||
/// 一个 launcher home(`.ULL` 的等价物)的路径视图。
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct LauncherLayout {
|
||||
home: PathBuf,
|
||||
}
|
||||
|
||||
impl LauncherLayout {
|
||||
/// 绑定到某个 home 目录(不创建任何东西,创建用 [`Self::ensure`])。
|
||||
#[must_use]
|
||||
pub fn new(home: impl Into<PathBuf>) -> Self {
|
||||
Self { home: home.into() }
|
||||
}
|
||||
|
||||
/// home 根目录。
|
||||
#[must_use]
|
||||
pub fn home(&self) -> &Path {
|
||||
&self.home
|
||||
}
|
||||
|
||||
/// 任一分区目录。
|
||||
#[must_use]
|
||||
pub fn partition(&self, partition: Partition) -> PathBuf {
|
||||
self.home.join(partition.dir_name())
|
||||
}
|
||||
|
||||
/// `config/`。
|
||||
#[must_use]
|
||||
pub fn config(&self) -> PathBuf {
|
||||
self.partition(Partition::Config)
|
||||
}
|
||||
|
||||
/// `data/`。
|
||||
#[must_use]
|
||||
pub fn data(&self) -> PathBuf {
|
||||
self.partition(Partition::Data)
|
||||
}
|
||||
|
||||
/// `cache/`。
|
||||
#[must_use]
|
||||
pub fn cache(&self) -> PathBuf {
|
||||
self.partition(Partition::Cache)
|
||||
}
|
||||
|
||||
/// `logs/`。
|
||||
#[must_use]
|
||||
pub fn logs(&self) -> PathBuf {
|
||||
self.partition(Partition::Logs)
|
||||
}
|
||||
|
||||
/// `config/<name>`。
|
||||
#[must_use]
|
||||
pub fn config_file(&self, name: &str) -> PathBuf {
|
||||
self.config().join(name)
|
||||
}
|
||||
|
||||
/// `config/launcher.json`。
|
||||
#[must_use]
|
||||
pub fn launcher_config(&self) -> PathBuf {
|
||||
self.config_file(LAUNCHER_CONFIG_FILE)
|
||||
}
|
||||
|
||||
/// `config/profiles.json`。
|
||||
#[must_use]
|
||||
pub fn profiles_config(&self) -> PathBuf {
|
||||
self.config_file(PROFILES_CONFIG_FILE)
|
||||
}
|
||||
|
||||
/// `config/instances/<id>.json`。
|
||||
///
|
||||
/// # Errors
|
||||
/// 实例 id 只允许 `[A-Za-z0-9_-]`(正常是稳定 UUID),否则返回
|
||||
/// [`super::error::PlatformError::InvalidInstanceId`],挡掉 `../` 穿越。
|
||||
pub fn instance_config(&self, id: &str) -> PlatformResult<PathBuf> {
|
||||
if !is_valid_instance_id(id) {
|
||||
return Err(PlatformError::InvalidInstanceId { id: id.to_owned() });
|
||||
}
|
||||
Ok(self.config().join(INSTANCES_DIR).join(format!("{id}.json")))
|
||||
}
|
||||
|
||||
/// `cache/sha1/<h2>`——内容寻址缓存的两级分片(避开 NTFS 单目录塌陷)。
|
||||
#[must_use]
|
||||
pub fn sha1_cache(&self, shard: &str) -> PathBuf {
|
||||
self.cache().join("sha1").join(shard)
|
||||
}
|
||||
|
||||
/// `cache/etag.json`——共享 ETag 表。
|
||||
#[must_use]
|
||||
pub fn etag_cache(&self) -> PathBuf {
|
||||
self.cache().join("etag.json")
|
||||
}
|
||||
|
||||
/// `cache/meta/`——列表 API 响应缓存(带 TTL)。
|
||||
#[must_use]
|
||||
pub fn meta_cache(&self) -> PathBuf {
|
||||
self.cache().join("meta")
|
||||
}
|
||||
|
||||
/// `data/java-managed/`——自动下载的 JDK。
|
||||
#[must_use]
|
||||
pub fn java_managed(&self) -> PathBuf {
|
||||
self.data().join("java-managed")
|
||||
}
|
||||
|
||||
/// `data/auth/`——账户元数据与凭据。
|
||||
#[must_use]
|
||||
pub fn auth_data(&self) -> PathBuf {
|
||||
self.data().join("auth")
|
||||
}
|
||||
|
||||
/// `data/agent/sessions/`——AI 会话 jsonl 与审计日志。
|
||||
#[must_use]
|
||||
pub fn agent_sessions(&self) -> PathBuf {
|
||||
self.data().join("agent").join("sessions")
|
||||
}
|
||||
|
||||
/// `<home>/redirect.json`——「我已迁走」指针。
|
||||
#[must_use]
|
||||
pub fn redirect_path(&self) -> PathBuf {
|
||||
self.home.join(REDIRECT_FILE)
|
||||
}
|
||||
|
||||
/// `config/migration-receipt.json`——迁移收据。
|
||||
#[must_use]
|
||||
pub fn migration_receipt(&self) -> PathBuf {
|
||||
self.config_file(MIGRATION_RECEIPT_FILE)
|
||||
}
|
||||
|
||||
/// 一次性建齐四分区。
|
||||
///
|
||||
/// # Errors
|
||||
/// 目录创建失败时返回带路径的 [`super::error::PlatformError::Io`]。
|
||||
pub fn ensure(&self) -> PlatformResult<()> {
|
||||
for partition in Partition::ALL {
|
||||
let dir = self.partition(partition);
|
||||
std::fs::create_dir_all(&dir).map_err(io_at(&dir))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// 实例 id 白名单:字母数字与 `_`/`-`。
|
||||
#[must_use]
|
||||
pub fn is_valid_instance_id(id: &str) -> bool {
|
||||
!id.is_empty()
|
||||
&& id.len() <= 64
|
||||
&& id
|
||||
.chars()
|
||||
.all(|ch| ch.is_ascii_alphanumeric() || ch == '_' || ch == '-')
|
||||
}
|
||||
Reference in new issue
Block a user