复审 r3 的唯一 P1:r2 为了论证「未履行」,断言「`AboutSlint` 部件一直只有排期、从未落地」。
**这条是错的**,我先自己核了源码再改:
```
$ git grep -n 'AboutSlint' crates/app/
crates/app/ui/pages/settings.slint:4:// 关于页内嵌 AboutSlint —— Slint RF 2.0 许可义务(01c §8:关于页放官方部件)。
crates/app/ui/pages/settings.slint:15:import { AboutSlint, ScrollView } from "std-widgets.slint";
crates/app/ui/pages/settings.slint:274: AboutSlint { }
$ git grep -n 'AboutSlint' v0.1.0 -- crates/
(同上三行,v0.1.0 这个 tag 里就有)
$ sed -n '272,275p' crates/app/ui/pages/settings.slint
if root.section == SettingSection.about : Rectangle { height: 120px; AboutSlint { } }
$ sed -n '129p' crates/app/ui/pages/settings.slint → SectionRow { label: "关于"; … }
$ grep -n 'RouteKind.settings' crates/app/ui/components/nav.slint → :114/:116(侧栏入口)
$ grep -n compile crates/app/build.rs → slint_build::compile("ui/main.slint")
$ grep -n SettingsPage crates/app/ui/shell.slint → :23 import / :273 route==settings 时渲染
```
即:部件真实存在、经 build.rs 编译进二进制、侧栏 → 设置页「关于」一点即到;`packaging/README.md:317`
「(已在 UI 内)」本来就与代码一致,本轮未改。r2 的依据只是三份文档的自述(旧 NOTICE「P2 排期」、
README「still outstanding」),那份自述不可靠。
改法(三处同一句,`packaging/release-notes.md` 不动):
- 删掉「只有排期 / 从未落地 / 尚未以任一形式落地」这类断言;
- 改成中性陈述:义务挂在每次「随应用分发」上;`v0.1.0` 的设置页「关于」分组里**确实内置了**
`AboutSlint` 部件(`crates/app/ui/pages/settings.slint`,从侧栏进入);该部件是否满足 §2(a)、
以及 v0.1.0 发布页上是文字链接而非 §2(b) 官方徽章图这两点,**本文件不作法律判断**,以 Slint
官方许可原文为准;NOTICE / 发布页说明 / 本节都只是记录,不构成履行声明。
- 顺手按本机 registry 里 Slint 1.18.1 随包的 RF-2.0 原文把 §2(a) 补全:还允许「没有关于页时显示在
启动画面」这一变体(原文 `In the absence of such a screen or dialog, display the widget in the
"Splash Screen"`),r2 只写了前半句。
diff:`git diff --name-only` = NOTICE, README.md, README.zh-CN.md(3 份文档、零 .rs)。
224 lines
16 KiB
Markdown
224 lines
16 KiB
Markdown
# 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 由
|
||
启动器按实例下载管理,不是启动器的依赖。
|
||
- **默认构建里只有一套 UI**:UI 是 iced 0.14 + Material 3 Expressive。早先的 Slint 实现
|
||
(`crates/app`)作为**历史参考**保留源码:已被 workspace `exclude`,默认构建不编、测试不跑、
|
||
发布不发。唯一已发布的 tag `v0.1.0` 发的仍是 Slint 版(见「当前状态」)。
|
||
- **CPU 软渲染兜底**:`--software-renderer` 让没有可用 GPU 或驱动有问题的老机也能正常显示界面。
|
||
- **AI Agent + MCP**:启动器内可对话、改设置、读崩溃诊断;`ull agent serve` 把同一个内核以 MCP
|
||
server 形态(stdio 或 Streamable HTTP)开放给外部 Agent 工具。
|
||
- **加载器**:Fabric / Quilt / NeoForge / Forge;整合包与 CurseForge / Modrinth 客户端在 core 里。
|
||
- **Java 管理**:Temurin → Oracle → Microsoft → Zulu 回退链,带地区判定。
|
||
- **账户**:用 Microsoft 账户登录,认证走 OAuth 2.0 device code flow → Xbox Live → XSTS →
|
||
`api.minecraftservices.com`;账户令牌只保存在本机。
|
||
- **引擎**:多源下载(健康度评分、镜像、分块传输、边下边算)与 YAML 规则引擎的崩溃诊断。
|
||
- **许可**:Apache-2.0 © AskaEth (YeiJ)。
|
||
|
||
## 当前状态
|
||
|
||
两条轨道,还不是同一个东西——看小标题再决定下载什么。
|
||
|
||
### 轨道 A —— 历史:v0.1.0,Slint 版(目前唯一的 tag)
|
||
|
||
> **历史实现——已不在默认构建与发布矩阵内。** Slint 那棵树(`crates/app`)作为源码参考留在仓库里:
|
||
> 已被 cargo workspace `exclude`,所以 `cargo build` / `cargo test` / `cargo clippy` 都不会碰它,
|
||
> 也没有任何产物从它构建。想了解那一版 UI 怎么写的就直接读源码——**不要再尝试构建它**(它的 manifest
|
||
> 整份是 workspace 继承写法,离开 workspace 后连解析都过不了)。下面这段原文保留,因为它记录的是
|
||
> `v0.1.0` 资产里到底装了什么;从 `v0.1.0` 之后,流水线构建的是 iced 版,对外文件名不变。
|
||
|
||
- **GitHub Releases `v0.1.0`**(2026-11-01,标记为 pre-release):单文件产物覆盖 Windows x64(裸
|
||
`.exe`)、Linux x64、macOS(arm64 与 x64)`.tar.gz`。每个 Release 另挂 `SHA256SUMS.txt` 与
|
||
`LICENSE` / `NOTICE` / `README.md` 三份文本——Windows 是「一个 exe 装不下文本」所以并列挂出,
|
||
unix 归档里本来就装着。
|
||
- **UI**:Slint + Metro。这是已交付的图形界面——实例、下载、账户、Java、日志、设置
|
||
各页都由 core 引擎驱动(应用内 AI 对话面板仍是脚本化的占位)。
|
||
- **同一个二进制就是 CLI**:`unlucky-launcher instances list`、`unlucky-launcher launch <实例>`、
|
||
`unlucky-launcher logs <sid>` 等命令在任何 UI 代码之前分流——不开窗、不探渲染后端、不扫字体。
|
||
命令集:`instances`、`install`、`launch`、`accounts`、`sessions`、`logs`、`diag`、`config`、
|
||
`java`、`agent`,外加 `--help` / `--version`。
|
||
- **另有一个更轻的 `ull`**(`cargo build --release --bin ull`,零 UI 依赖):同一套命令,但二进制不
|
||
链接任何图形栈——连 fontconfig 都不需要。服务器、CI 容器、Termux 拿 `ull`;有桌面的机器拿
|
||
`unlucky-launcher`,一个文件同时管 GUI 与命令行。两者都从下一个 tag 起进发布资产
|
||
(`v0.1.0` 的资产早于这个变化)。
|
||
- Windows ARM64 与 Linux ARM64 两个槽位已接进发布流水线,随下一个 tag 发布;`v0.1.0` 的资产早于
|
||
这两个槽位。
|
||
|
||
### 轨道 B —— dev 上的下一代 UI(iced + M3 Expressive,施工中)
|
||
|
||
**这条轨道还没发布——要看只能自己从源码编。**
|
||
|
||
- **方向已锁死**:视觉 = Material 3 Expressive,框架 = iced 0.14,UI 层从头写;风格与框架都不会再换。
|
||
- **已完成**:令牌 crate `crates/design`(零 UI 框架依赖、零第三方 crate——真 HCT 色彩、排版档位、
|
||
形状刻度、间距、高程、动效、图标几何、组件度量)与骨架 crate `crates/app-iced`(能开窗、自绘标题栏
|
||
与窗口键、导航轨、颜色与字号只从令牌取、GPU→CPU 后端探测与自动降级、视觉验收用的截图与逐像素
|
||
对比通道)。
|
||
- **没完成**:组件库(按钮、对话框、列表、菜单、搜索栏,以及 M3 其余组件)、页面搬迁、i18n 文案。
|
||
iced 版里现在每一个页面都是占位。
|
||
- **Slint 版已退出构建,只留源码作参考**:`crates/app` 不再是 workspace 成员(M118)——源码留作历史
|
||
参考,不编译、不测试、不发布,也不再有可用的构建方式。翻牌只剩**改名**这一步:在 iced 版通过等价
|
||
验收前,它的 bin 还叫 `unlucky-launcher-iced`;发布流水线已经把它按对外名 `unlucky-launcher` 打出去。
|
||
|
||
可能会遇到的 bin,先认清楚:
|
||
|
||
| 二进制 | crate | 状态 |
|
||
|--------|-------|------|
|
||
| `unlucky-launcher-iced` | `crates/app-iced`(iced 0.14) | workspace 里**唯一**的 GUI bin;发布流水线把它按对外名 `unlucky-launcher` 落盘;子命令分发同款 |
|
||
| `unlucky-launcher` | `crates/app`(Slint) | **历史**:该 crate 已被 workspace `exclude`,这个 bin 不再被构建或分发;`v0.1.0` 的资产是最后一批 Slint 版 |
|
||
| `ull` | `crates/cli` | 同一套命令的零 UI 构建,作为独立资产随发布分发 |
|
||
|
||
`unlucky-launcher` 与 `ull` 不是重复品:开窗时 GUI 二进制要用图形栈(X11 / Wayland / GL 走
|
||
`dlopen`,启动时直接链接 `libfontconfig`);`ull` 除了 glibc 什么都不需要,所以在没有窗口的服务器、
|
||
CI 容器、Termux 上跑得起来。没有桌面就用 `ull`,有桌面就用 `unlucky-launcher <子命令>`。两者打出来的
|
||
CLI 文案是同一套,自称的名字跟着**你敲的那个二进制**走:`unlucky-launcher --help` 首行是
|
||
`unlucky-launcher ·`,`ull --help` 首行是 `ull ·`。
|
||
|
||
### 子命令分发
|
||
|
||
`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/design # 设计令牌:真 HCT 色彩 / 排版 / 形状 / 间距 / 高程 / 动效 / 图标 / 组件度量
|
||
│ # —— 零 UI 依赖、零第三方 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:同一个二进制认上面列的全部子命令。名字是
|
||
临时的,翻牌时才改成 `unlucky-launcher`;发布产物**已经**用对外名:流水线编这个 bin,
|
||
`packaging/make_artifacts.py` 落盘时改成 `unlucky-launcher[.exe]`,所以下载链接不用变。
|
||
- `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 试验级、不承诺:只保证 CLI,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 玩家走了很多年,
|
||
本项目在设计取舍与实现细节上都从中受益良多。
|