Files
Unlucky-Launcher/README.zh-CN.md
T
AskaEth 72ee57f0a8 docs(readme): M130 r1 修正产物名的槽位说明与自相矛盾的 GUI 二进制称谓
- P2-1:`[.exe]` 只对两个 Windows 槽成立,四个 Linux/macOS 槽是 .tar.gz
  (make_artifacts.py:76-82 SLOTS、:243 stage_archive;packaging/README.md §1)。
  三处产物名统一写成 `unlucky-launcher_<version>_<platform>_<arch>` + 按槽位说明形态。
- P2-2:删掉「`unlucky-launcher` once extracted or downloaded」与段尾「Windows 裸资产文件名带
  版本段」的互相拆台;改成归档解出叫 `unlucky-launcher`、Windows 裸资产叫
  `unlucky-launcher_<version>_<platform>_<arch>.exe`。中英同步。
2026-11-05 18:05:39 +08:00

226 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Unlucky Launcher (ULL)
[English](README.md) · **简体中文**
> **官方项目主页**:https://github.com/AskaEth/Unlucky-Launcher | 下载、议题、发布说明都在这里:
> <https://github.com/AskaEth/Unlucky-Launcher/releases>
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 <png>` 出一帧图后退出,这是 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**(<https://slint.dev>),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**(<https://github.com/iced-rs/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 玩家走了很多年,
本项目在设计取舍与实现细节上都从中受益良多。