# Unlucky Launcher (ULL) [English](README.md) · **简体中文** > **官方项目主页**:https://github.com/AskaEth/Unlucky-Launcher | 下载、议题、发布说明都在这里: > Unlucky Launcher(ULL)是一个用 Rust 写的免费开源 Minecraft: Java 版启动器(Apache-2.0)。 每个平台就一个原生二进制——没有安装包、没有捆绑运行时、没有 WebView。启动器内置 AI Agent 客户端(对话 / 改配置 / 崩溃诊断),也能作为 MCP server 接进别的 Agent 工具。 ## 特性 - **单文件原生二进制**:启动器本体不需要 JVM、不需要 WebView、不带旁挂运行时。游戏要的 JDK 由 启动器按实例下载管理,不是启动器的依赖。 - **一壳两形**:GUI 二进制在跑任何 UI 代码之前就认得全部 CLI 子命令——不开窗、不探渲染器、不扫字体。 另有一份更轻的 `ull` 产物,命令集相同,却一个图形栈都不链接。 - **默认构建里只有一套 UI**:UI 是 iced 0.14 + Material 3 Expressive。早先的 Slint 实现 (`crates/app`)作为**历史参考**保留源码:已被 workspace `exclude`,默认构建不编、测试不跑、 发布不发。`v0.1.0` 那个 tag 发的仍是 Slint 版;iced 重写后的第一个 tag 是 `v0.2.0`(见「当前状态」)。 - **CPU 软渲染兜底**:`--software-renderer` 让没有可用 GPU 或驱动有问题的老机也能正常显示界面。 - **AI Agent + MCP**:启动器内可对话、改设置、读崩溃诊断;`ull agent serve` 把同一个内核以 MCP server 形态(stdio 或 Streamable HTTP)开放给外部 Agent 工具。 - **加载器**:Fabric / Quilt / NeoForge / Forge;CurseForge 清单、Modrinth `.mrpack`、 MultiMC/Prism 整合包导入,mrpack / CurseForge 导出。 - **Java 管理**:Temurin → Oracle → Microsoft → Zulu 回退链,带地区判定。 - **账户**:三轨——Microsoft 账户登录(OAuth 2.0 device code flow → Xbox Live → XSTS → `api.minecraftservices.com`)、第三方社区服、本地离线档案;账户令牌只保存在本机。**微软登录需要 一个 Azure 公共客户端 id,而发布版二进制里没有**——这个 id 不在仓库里,CI 也不注入,所以启动前设 `ULL_MSA_CLIENT_ID`(或构建时注入)才会有;没配时点登录只会弹一条通用的「登录失败」,带 `代码:other`——那句话不会告诉你是缺了 id。第三方社区服与本地档案不需要 id。 - **引擎**:多源下载(健康度评分、镜像、分块传输、边下边算)与 YAML 规则引擎的崩溃诊断。 - **许可**:Apache-2.0 © AskaEth (YeiJ)。 ## 当前状态 `v0.1.0`(2026-11-01)早于 UI 重写:那批资产是 Slint 版,它也是最后一个 Slint 版 tag。重写之后的 第一个 tag 是 `v0.2.0`——也就是 iced 这条线,下面说的这套东西都在线上,也正是发布流水线现在会打 出来的东西。 - **iced 版界面上有什么**:实例、版本管理(三栏 + 搜索)、内容库(整合包 / Mod / 资源包 / 光影, 带来源与版本筛选)、日志、设置(通用 / 外观 / Java / 下载)、账户(微软设备码、第三方社区服、本地 档案)、AI 右栏,以及安装流程——共用一套组件库与一套令牌。页面清单就是 `presenter::router::Route` 里的那些——实例、版本管理、内容库、日志、设置、账户、实例详情、AI 页;`pages::Screen::ALL` 是其中 带 slug 的较小一组(`--screen=` 与页面测试矩阵用它,`crates/app-iced/src/pages/tests.rs`),账户、 实例详情与 AI 页不在这一组里——它们能路由到,但入口是导航轨与实例磁贴。 - **文案跟随系统语言**:自动探测、设置里可显式切换、重启后保持。界面文案统一走一份消息目录 (`crates/i18n`)。 - **文案守门抓得住什么、抓不住什么**:有一条测试扫实例页的源码,凡是**带 ASCII 单词**的字符串字面量 不在短白名单里就红——新写一句英文界面文案,落地即红 (`crates/app-iced/src/pages/instances/tests/i18n.rs`)。已知边界:这条扫描只覆盖实例页,不是每一屏; 而且中文写死的字面量里没有 ASCII 单词,扫不到——这一类只能靠 review 与出图。 - **外观**:窗口背景图、面板透明度、整窗透明度(Windows)、浅色 / 深色 / 跟随系统,以及在系统愿意给 的时候使用系统窗口圆角。CPU 渲染档会关掉半透明层,并在界面上说清楚。 - **整合包与安装链路都在构建里**:CurseForge 清单 / Modrinth `.mrpack` / MultiMC·Prism 导入,mrpack 与 CurseForge 导出,安装选项(选加载器)与**真取消**。 - **还没做**:安装器与自动更新通道;Windows 定型后的 macOS 真机复核;整合包导入的**逐文件**冲突策略 (当前只有一份全局策略,界面如实写明而不是假装支持);原生文件选择框(导入支持拖拽与粘贴路径)。 - **Slint 版已退出构建,只留源码作参考**:`crates/app` 不再是 workspace 成员(M118)——源码留作历史 参考,不编译、不测试、不发布,也不再有可用的构建方式。还欠「翻牌」的只有**仓库内的 bin 名**(现在叫 `unlucky-launcher-iced`);**产物名不欠**:`packaging/make_artifacts.py` 落盘时用的是 `unlucky-launcher_<版本>_<平台>_<架构>`——两个 Windows 槽是裸 `.exe`,其余四个 Linux / macOS 槽 是 `.tar.gz`——归档里的二进制名是 `unlucky-launcher`。 可能会遇到的 bin,先认清楚: | 二进制 | crate | 状态 | |--------|-------|------| | `unlucky-launcher-iced` | `crates/app-iced`(iced 0.14) | workspace 里**唯一**的 GUI bin;发布流水线把文件落成 `unlucky-launcher_<版本>_<平台>_<架构>`(两个 Windows 槽是裸 `.exe`,其余四个 Linux / macOS 槽是 `.tar.gz`),归档里的二进制名是 `unlucky-launcher`;子命令分发同款 | | `unlucky-launcher` | `crates/app`(Slint) | **历史**:该 crate 已被 workspace `exclude`,这个 bin 不再被构建或分发;`v0.1.0` 的资产是最后一批 Slint 版 | | `ull` | `crates/cli` | 同一套命令的零 UI 构建,作为独立资产随发布分发 | GUI 二进制与 `ull` 不是重复品。归档解压出来那个二进制叫 `unlucky-launcher`;Windows 上是裸的 `unlucky-launcher_<版本>_<平台>_<架构>.exe`。开窗时 GUI 二进制要用图形栈(X11 / Wayland / GL 走 `dlopen`,启动时直接链接 `libfontconfig`);`ull` 除了 glibc 什么都不需要,所以在没有窗口的服务器、 CI 容器上跑得起来。这个地板是 glibc ≥ 2.35(Linux 槽位在 Ubuntu 22.04 上构建),更老的发行版——以及 根本不在发布矩阵里的 Android/Termux——得源码自建(`packaging/README.md` §4、§9)。没有桌面就用 `ull`, 有桌面就用 `unlucky-launcher <子命令>`。两者打出来的 CLI 文案是同一套,自称的名字跟着**你敲的那个 文件**走——`argv[0]` 去掉目录与结尾的 `.exe`,别的都不剥(`crates/cli/src/cli/program.rs`)。所以 `ull` 打印 `ull ·`,归档里那个 `unlucky-launcher` 打印 `unlucky-launcher ·`,本地 `cargo build` 出来 的打印 `unlucky-launcher-iced ·`,而 Windows 的裸 exe 文件名带版本段,打印的是 `unlucky-launcher_<版本>_windows_x64 ·`。 ### 子命令分发 `unlucky-launcher <参数>` 在跑任何 UI 代码之前先问一句「这是不是一次 CLI 调用」: ```bash unlucky-launcher # GUI unlucky-launcher --software-renderer # GUI,CPU 渲染(行为不变) unlucky-launcher instances list # CLI,不开窗 unlucky-launcher --json launch 1.20.1 # CLI,全局选项排在子命令前面 unlucky-launcher --version # CLI,输出版本 + 系统摘要 ``` 判据刻意收得很窄,所以原本能用的用法一条都没变: - 第一个参数是裸词(不以 `-` 开头)→ 它就在子命令的位置上,交给 CLI。GUI 的 flag 永远不会出现在 那个位置:`--screenshot` / `--diff` / `--data-dir` 的取值都排在 flag 之后。 - 第一个参数是 flag → 只有当 `crates/cli` 的解析器也解析出一条真命令时才算 CLI 调用 (`--help`、`--version`、`--json instances list`)。单独 `--data-dir X` 解析成「没有子命令」, 仍然开 GUI;GUI 专属参数(`--software-renderer`、`--screenshot*`、`--renderer`、`--theme`、 `--diff`、`--force-state` 等)在 CLI 里一律解析失败,也仍然开 GUI。 **未知子命令是错误,不是开窗**:`unlucky-launcher lst` 打印「未知命令 `lst`」、提示 `unlucky-launcher --help`,以 1 退出。静默开窗会让脚本以为自己那个拼错的参数生效了。子命令表只有 一处定义(`crates/cli/src/cli/parse.rs`),所以 GUI 二进制和 `ull` 认的命令集天然一致。 ## 模块地图 ``` Unlucky-Launcher (cargo workspace) ├── crates/core # 领域库:零 UI 依赖,八模块 ├── crates/agent # AI Agent:ToolSpec 注册表 + 内置客户端 + MCP server(双形态共用一个内核) ├── crates/presenter # 业务宿主层:与框架无关的 state / host / dto / 转换 ├── crates/design # 设计令牌:真 HCT 色彩 / 排版 / 形状 / 间距 / 高程 / 动效 / 图标 / 组件度量 │ # —— 零 UI 依赖、零第三方 crate ├── crates/i18n # 语言探测、消息目录、中文字体策略 ├── crates/palette-extract # 唯一解码图片的 crate,供壁纸取色链路使用 ├── crates/app # 历史 Slint UI(bin `unlucky-launcher`):已被 workspace `exclude`, │ # 仅作源码参考——不再被构建 / 测试 / 发布 ├── crates/app-iced # iced UI(bin `unlucky-launcher-iced`):workspace 里唯一的 GUI bin ├── crates/cli # CLI(bin `ull`):headless,零 UI └── packaging # 单文件产物矩阵与发布脚本 ``` `crates/core` 的八个模块: | 模块 | 职责 | |------|------| | `setting` | 三模式数据目录、schema 配置、原子写、迁移 | | `download` | 多源健康度、镜像、分块下载、边下边算 | | `game` | 版本/实例仓库、快照与继承解析 | | `launch` | 10 阶段启动管线、进程、JSONL 事件流 | | `auth` | Microsoft 账户登录(OAuth 2.0 device code → Xbox Live → XSTS → Minecraft Services)、第三方社区服、本地档案 | | `addon` | 加载器安装、整合包、CurseForge / Modrinth | | `diagnose` | 五类信号采集、YAML 规则引擎、hs_err 解析 | | `platform` | OS 抽象:路径、进程、文件锁、能力探测、运行期 OS 检测 | ## 构建 前置: - Rust stable(`rust-toolchain.toml` 会自动选工具链;门禁还需要 `rustfmt` / `clippy` 组件) - Windows:MSVC 工具链(CI 用这套)或 MinGW-w64(`x86_64-pc-windows-gnu`) - Linux:`libxkbcommon-dev libfontconfig1-dev libgl1-mesa-dev libegl1-mesa-dev libwayland-dev libxcb1-dev`——winit + wgpu/tiny-skia 编译期要用(Slint 版当年还要 femtovg) ```bash cargo fmt --all --check # 格式 cargo clippy --workspace --all-targets -- -D warnings # 静态检查(零 warning 门禁) cargo test --workspace # 测试 cargo build --release # bin 落在 target/release/ ``` `--workspace` 就是模块地图里那八个成员:`crates/app` 已在 workspace 层被排除(M118),上面这些命令 都不会编 Slint 那棵树——而且**它没有构建方式**:该 crate 只作源码参考,作为 workspace 成员已不可编。 编出来的 bin: - `unlucky-launcher-iced`——GUI(iced)。它同时也是 CLI:同一个二进制认上面列的全部子命令。名字里的 `iced` 是 cargo 的,不是下载链接的:还欠「翻牌」的只有这个 bin 名;发布文件里不带实现细节——流水线编 这个 bin,`packaging/make_artifacts.py` 落盘时改成 `unlucky-launcher_<版本>_<平台>_<架构>`——两个 Windows 槽是裸 `.exe`,其余四个 Linux / macOS 槽 是 `.tar.gz`——归档里的二进制名是 `unlucky-launcher`。 - `unlucky-launcher`——**历史**:workspace 构建已不产出它(它属于被排除的 `crates/app`)。 `v0.1.0` 的资产是最后一批用这个名字发出去的构建。 - `ull`——CLI 的独立无 UI 构建:同一套命令、不链接图形栈、体积更小,给完全没有显示设备的机器用。 渲染开关: - `unlucky-launcher-iced --software-renderer` 强制 tiny-skia(`ICED_BACKEND=tiny-skia`); `--screenshot ` 出一帧图后退出,这是 CI 无头冒烟 job 用的验收通道 - 没有可用适配器时,iced 版靠显式后端探测自己退到 CPU - **历史**(`v0.1.0` 的 Slint 版仍认这条):`unlucky-launcher --software-renderer`,即 `SLINT_BACKEND=winit-software`,让那一版走它自己的渲染器回退链 依赖许可检查(本地可选,CI 必跑):`cargo deny check licenses bans sources`。 ## CI 门禁 每次推送到 `main` 与每个 PR 都跑 fmt / clippy(warnings 视为错误)/ 全量测试,Linux 无头渲染冒烟 与它们同级、是硬门禁:红了就不许合。macOS 运行期探针还不算——它只在手动 `workflow_dispatch` 时跑, 结果留证据但不作为流水线结论(`continue-on-error`),直到该槽位在真实 runner 上首跑通过。 | Workflow | 内容 | |----------|------| | `.github/workflows/ci.yml` | fmt + clippy + test + release build(Windows / Linux 矩阵);Linux runtime smoke(Xvfb 里真渲染一帧,再断言 PNG 非空白);macOS 运行期探针(仅手动 dispatch) | | `.github/workflows/guards.yml` | `crates/core` 零 UI 守门(依赖表 + `cargo tree`)、单文件 ≤400 行守门、cargo-deny 许可与来源守门 | | `.github/workflows/release.yml` | tag `v*` → 分槽位构建 → 单文件产物 + `SHA256SUMS.txt` → GitHub Release | ## 平台基线 最低基线 **Windows 10**;Windows 8.1 / 8 / 7 不在支持范围内。 - Windows 10/11 的产物由**标准工具链**(MSVC / GNU)构建,构建方式与上游 cargo 一致 - macOS 12+(Apple Silicon 与 Intel)与 Linux x64 / arm64 都是支持槽位;Linux 上 X11 是当前首选路径, Wayland 是大方向。macOS 产物未签名,首次启动要自己清掉 Gatekeeper 隔离标记 (`xattr -d com.apple.quarantine`) - Android Termux 试验级、不承诺:发布矩阵里没有面向它的资产(要在 Termux 上跑得源码自建),GUI 未验证 - 每个平台跑各自的测试矩阵;能力差异用 feature gate + 运行期探测表达。 ## 许可证与致谢 - **本项目**:Apache License 2.0,见 [LICENSE](LICENSE);第三方声明见 [NOTICE](NOTICE)。 - **Slint**(),Royalty-Free 2.0:义务针对 `v0.1.0` 发布的那批构建——条件挂在每一次 「随应用分发」上,而不是挂在某个版本号上。上游 §2 给出两种形式:(a) 在可从顶层菜单进入的「关于」页/ 对话框里显示官方 `AboutSlint` 部件(没有该页时改为启动画面);(b) 在可下载该二进制的公开网页上显示 官方归属徽章。**`v0.1.0` 的设置页「关于」分组里内置了 `AboutSlint` 部件**(`crates/app/ui/pages/ settings.slint`,从侧栏进入);该部件是否满足 §2(a) 的全部条件、以及 `v0.1.0` 发布页上那是文字链接 而不是 §2(b) 点名的官方徽章图这两点,**本节不作法律判断**,具体以 Slint 官方许可原文为准。`v0.1.0` 的 NOTICE、发布页说明与本节**都只是记录,不构成履行声明**。后续版本不再链接 Slint,该义务不随新产物 新增。义务跟着**链接了 Slint 的产物**走:已发布的 `v0.1.0` 资产(以及将来任何重新链接 Slint 的构建) 适用;`crates/app` 已被 workspace `exclude`,流水线不再产出链接 Slint 的二进制,所以新的 iced 版不适用。 - **iced**(),MIT:现役 UI 的框架;与它同栈的 `winit`(Apache-2.0)、 以及经它引入的 `wgpu`(MIT / Apache-2.0)/ `tiny-skia`(BSD-3-Clause)。 - **Google Material Color Utilities**(Apache-2.0):`crates/design` 的色彩令牌是它 HCT / ColorSpec2021 算法的 Rust 重写移植,移植清单与逐函数对照写在 [NOTICE](NOTICE)。 - **其余主要依赖**:MCP server 的 `rmcp` 与 `axum`(Apache-2.0 / MIT)、`tokio`(MIT)、`reqwest` 与 rustls(MIT / Apache-2.0)、`serde` / `serde_json`(MIT / Apache-2.0)、`zip` / `flate2` / `tar` (MIT / Apache-2.0)。cargo-deny 强制执行的白名单在 `deny.toml`;GPL 系依赖不得进主干。 感谢 [HMCL](https://github.com/HMCL-dev/HMCL) 和 [PCL2](https://github.com/Meloong-Git/PCL) (均 GPL-3.0)。这两款启动器陪着绝大多数 MC 玩家走了很多年, 本项目在设计取舍与实现细节上都从中受益良多。