复审 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)。
258 lines
17 KiB
Markdown
258 lines
17 KiB
Markdown
# Unlucky Launcher (ULL)
|
||
|
||
**English** · [简体中文](README.zh-CN.md)
|
||
|
||
> **Official project home**: https://github.com/AskaEth/Unlucky-Launcher | downloads, issues and
|
||
> release notes all live there: <https://github.com/AskaEth/Unlucky-Launcher/releases>
|
||
|
||
Unlucky Launcher (ULL) is a free, open-source Minecraft: Java Edition launcher written in Rust
|
||
(Apache-2.0). It ships as one native binary per platform — no installer, no bundled runtime, no
|
||
WebView. The launcher embeds an AI agent client (chat / configuration / crash diagnosis) and can be
|
||
attached to other agent tooling as an MCP server.
|
||
|
||
## Highlights
|
||
|
||
- **Single-file native binary**: the launcher itself needs no JVM, no WebView and no sidecar
|
||
runtime. The JDK a game needs is downloaded and managed per instance, not as a launcher
|
||
dependency.
|
||
- **One UI stack in the build**: the UI is iced 0.14 + Material 3 Expressive. The earlier Slint
|
||
implementation (`crates/app`) is kept as source-only history: it is excluded from the cargo
|
||
workspace, so the workspace never builds, tests or ships it. v0.1.0 — the only tagged release so
|
||
far — still carries a Slint build (see Status).
|
||
- **CPU software-rendering fallback**: `--software-renderer` keeps the interface rendering
|
||
normally on machines with no usable GPU or with broken drivers.
|
||
- **AI agent + MCP**: chat, setting edits and crash diagnosis inside the launcher; `ull agent
|
||
serve` exposes the same core as an MCP server (stdio or Streamable HTTP) to external agent tools.
|
||
- **Mod loaders**: Fabric, Quilt, NeoForge, Forge, with modpack and CurseForge / Modrinth clients in
|
||
the core.
|
||
- **Java management**: a Temurin → Oracle → Microsoft → Zulu fallback chain, region-aware.
|
||
- **Accounts**: sign in with a Microsoft account over OAuth 2.0 device code flow → Xbox Live →
|
||
XSTS → `api.minecraftservices.com`; account tokens are kept on this machine only.
|
||
- **Engine**: multi-source download (health scoring, mirrors, chunked transfers, hash while
|
||
downloading) and a YAML rule-based crash diagnosis engine.
|
||
- **License**: Apache-2.0 © AskaEth (YeiJ).
|
||
|
||
## Status
|
||
|
||
Two tracks. They are not the same product yet, so read the headings before downloading anything.
|
||
|
||
### Track A — historical: v0.1.0, Slint UI (the only tagged release so far)
|
||
|
||
> **Historical implementation — no longer in the default build or the release matrix.** The Slint
|
||
> tree (`crates/app`) is kept in the repository as source-only reference: it is excluded from the
|
||
> cargo workspace, so `cargo build` / `cargo test` / `cargo clippy` never touch it and no artifact is
|
||
> built from it. Read the source if you want to see how that UI was put together; do not expect to
|
||
> build it. The list below is kept because it records what the v0.1.0 assets actually contain —
|
||
> since v0.1.0 the pipeline builds the iced binary and ships it under the same public asset names.
|
||
|
||
- **GitHub Releases `v0.1.0`** (2026-11-01, flagged pre-release): single-file assets for Windows
|
||
x64 (a bare `.exe`), Linux x64 and macOS (arm64 and x64) `.tar.gz`. Every release also carries
|
||
`SHA256SUMS.txt` and the `LICENSE` / `NOTICE` / `README.md` texts as separate assets; on Windows
|
||
those texts sit next to the `.exe` because a bare exe cannot hold them, while the unix archives
|
||
already contain them.
|
||
- **UI**: Slint + Metro. This is the shipped GUI — the instance, download, account, Java, log and
|
||
settings pages all run on the core engine (the in-app AI chat panel is a scripted placeholder).
|
||
- **CLI in the same binary**: `unlucky-launcher instances list`, `unlucky-launcher launch <instance>`,
|
||
`unlucky-launcher logs <sid>` and the rest of the command set run before any UI is built — no
|
||
window, no renderer probe, no font scan. Commands: `instances`, `install`, `launch`, `accounts`,
|
||
`sessions`, `logs`, `diag`, `config`, `java`, `agent`, plus `--help` / `--version`.
|
||
- **A second, lighter artifact `ull`** (`cargo build --release --bin ull`, zero UI dependencies): the
|
||
same command set from a binary that links no graphics stack at all — not even fontconfig. Servers,
|
||
CI containers and Termux get `ull`; desktops get `unlucky-launcher` and one binary covers both GUI
|
||
and CLI. Both are release assets from the next tagged release (the `v0.1.0` assets predate this).
|
||
- Windows ARM64 and Linux ARM64 slots are wired into the release pipeline; the next tagged release
|
||
builds them. The `v0.1.0` assets predate those two slots.
|
||
|
||
### Track B — in progress on `dev`: next-generation UI (iced + Material 3 Expressive)
|
||
|
||
**This track is not released yet — you can see it only by building from source.**
|
||
|
||
- **Locked direction**: the look is Material 3 Expressive and the framework is iced 0.14; the UI
|
||
layer is written from scratch. Neither the style nor the framework will change again.
|
||
- **Done**: the token crate `crates/design` (no UI framework, no third-party crates — real HCT
|
||
color, type scale, shape scale, spacing, elevation, motion, icon geometry, per-component
|
||
metrics) and the skeleton crate `crates/app-iced` (opens a window, self-drawn title bar and
|
||
window buttons, navigation rail, colors and type taken from tokens only, GPU→CPU backend probe
|
||
with automatic fallback, and the screenshot/diff channel the visual reviews use).
|
||
- **Not done**: the component library (buttons, dialogs, lists, menus, search, and the rest of the
|
||
M3 set), page migration, and the i18n pass. Every page in the iced binary is a placeholder.
|
||
- **Slint is retired from the build, kept as reference**: `crates/app` is no longer a workspace member
|
||
(M118) — the source stays for historical reference only: not compiled, not tested, not shipped, and
|
||
no longer buildable as a workspace member. What is left of the flip is the *rename*: until the iced
|
||
build passes equivalence acceptance its binary is still called `unlucky-launcher-iced`, while the
|
||
release pipeline already ships it under the public `unlucky-launcher` asset name.
|
||
|
||
Binaries you may run into, so you can tell them apart:
|
||
|
||
| Binary | Crate | State |
|
||
|--------|-------|-------|
|
||
| `unlucky-launcher-iced` | `crates/app-iced` (iced 0.14) | the only GUI binary the workspace builds; the release pipeline stages it under the public name `unlucky-launcher`; same subcommand dispatch |
|
||
| `unlucky-launcher` | `crates/app` (Slint) | **historical**: that crate is excluded from the workspace, so this binary is no longer built or shipped; the v0.1.0 assets are the last Slint builds |
|
||
| `ull` | `crates/cli` | the zero-UI build of the same command set, shipped as its own asset |
|
||
|
||
`unlucky-launcher` and `ull` are not duplicates. The GUI binary needs the graphics stack when it
|
||
opens a window (it `dlopen`s X11 / Wayland / GL, and links `libfontconfig` at load); `ull` needs
|
||
nothing beyond glibc, so it runs on a server, a CI container or Termux where no window can exist.
|
||
Pick `ull` when there is no desktop, `unlucky-launcher <command>` when there is. Both print the same
|
||
CLI text and name themselves after the binary you invoked: `unlucky-launcher --help` starts with
|
||
`unlucky-launcher ·`, `ull --help` starts with `ull ·`.
|
||
|
||
### Subcommand dispatch
|
||
|
||
`unlucky-launcher <args>` first asks "is this a CLI call?" before any UI code runs:
|
||
|
||
```bash
|
||
unlucky-launcher # GUI
|
||
unlucky-launcher --software-renderer # GUI, CPU renderer (unchanged)
|
||
unlucky-launcher instances list # CLI, no window
|
||
unlucky-launcher --json launch 1.20.1 # CLI, global options before the command
|
||
unlucky-launcher --version # CLI, prints version + OS summary
|
||
```
|
||
|
||
The rule is deliberately narrow, so nothing that used to work changed meaning:
|
||
|
||
- The first argument is a bare word (not `-…`) → it sits in the subcommand position, so the CLI gets
|
||
it. GUI flags never appear there: `--screenshot` / `--diff` / `--data-dir` take their values
|
||
*after* the flag.
|
||
- The first argument is a flag → the line is a CLI call only if `crates/cli`'s parser also produces a
|
||
real command (`--help`, `--version`, `--json instances list`). `--data-dir X` alone parses to "no
|
||
subcommand", so it still opens the GUI; every GUI-only flag (`--software-renderer`,
|
||
`--screenshot*`, `--renderer`, `--theme`, `--diff`, `--force-state`, …) fails CLI parsing, so it
|
||
still opens the GUI.
|
||
|
||
**Unknown subcommands are an error, not a GUI launch**: `unlucky-launcher lst` prints
|
||
"未知命令 `lst`", hints `unlucky-launcher --help`, and exits 1. Silently opening a window would tell
|
||
a script that its typo took effect. The subcommand table itself lives in exactly one place —
|
||
`crates/cli/src/cli/parse.rs` — so both GUI binaries and `ull` accept the same set by construction.
|
||
|
||
## Module map
|
||
|
||
```
|
||
Unlucky-Launcher (cargo workspace)
|
||
├── crates/core # domain library: zero UI dependencies, eight modules
|
||
├── crates/agent # AI agent: ToolSpec registry + built-in client + MCP server (one core, two shapes)
|
||
├── crates/design # design tokens: real HCT color / type / shape / spacing / elevation / motion / icons
|
||
│ # / component metrics — zero UI dependencies, no third-party crates
|
||
├── crates/app # HISTORICAL Slint UI (bin `unlucky-launcher`): excluded from the workspace,
|
||
│ # source-only reference — never built, tested or shipped any more
|
||
├── crates/app-iced # the iced UI (bin `unlucky-launcher-iced`): the only GUI the workspace builds
|
||
├── crates/cli # CLI (bin `ull`): headless, zero UI
|
||
└── packaging # single-file artifact matrix and release scripts
|
||
```
|
||
|
||
The eight modules of `crates/core`:
|
||
|
||
| Module | Responsibility |
|
||
|--------|----------------|
|
||
| `setting` | three data-directory modes, schema-based config, atomic writes, migrations |
|
||
| `download` | multi-source health scoring, mirrors, chunked downloads, hash-while-downloading |
|
||
| `game` | version / instance repository, snapshots and inheritance resolution |
|
||
| `launch` | 10-stage launch pipeline, processes, JSONL event stream |
|
||
| `auth` | Microsoft sign-in (OAuth 2.0 device code → Xbox Live → XSTS → Minecraft Services) |
|
||
| `addon` | mod loader installation, modpacks, CurseForge / Modrinth |
|
||
| `diagnose` | five signal collectors, YAML rule engine, hs_err parsing |
|
||
| `platform` | OS abstraction: paths, processes, file locks, capability probing, runtime OS detection |
|
||
|
||
## Building
|
||
|
||
Prerequisites:
|
||
|
||
- Rust stable (`rust-toolchain.toml` selects the toolchain automatically; the gates also need the
|
||
`rustfmt` and `clippy` components)
|
||
- Windows: MSVC toolchain (what CI uses) or 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 ask for these at build time (the Slint build also needed femtovg)
|
||
|
||
```bash
|
||
cargo fmt --all --check # formatting
|
||
cargo clippy --workspace --all-targets -- -D warnings # lints (zero-warning gate)
|
||
cargo test --workspace # tests
|
||
cargo build --release # binaries land in target/release/
|
||
```
|
||
|
||
`--workspace` covers the eight members in the module map. `crates/app` is excluded at the workspace
|
||
level (M118), so none of these commands compiles the Slint tree — and there is no build command for
|
||
it: that crate is kept as source-only reference and can no longer be built as a workspace member.
|
||
|
||
What the build produces:
|
||
|
||
- `unlucky-launcher-iced` — the GUI (iced). It is also the CLI: the same binary answers every
|
||
subcommand listed above. The name is temporary — it becomes `unlucky-launcher` at the flip. The
|
||
release artifacts already carry the public name: the pipeline builds this bin and
|
||
`packaging/make_artifacts.py` stages it as `unlucky-launcher[.exe]`, so download links never change.
|
||
- `unlucky-launcher` — **historical**: no workspace build produces this binary any more; it belonged
|
||
to `crates/app`, which is excluded. The v0.1.0 assets are the last builds under this name.
|
||
- `ull` — the CLI as a standalone, UI-free binary. Same command set, no graphics stack, smaller, and
|
||
the right artifact for a machine that has no display at all.
|
||
|
||
Rendering switches:
|
||
|
||
- `unlucky-launcher-iced --software-renderer` forces tiny-skia (`ICED_BACKEND=tiny-skia`);
|
||
`--screenshot <png>` writes one frame and exits, which is the acceptance channel CI's headless
|
||
smoke job uses
|
||
- With no usable adapter the iced build's explicit probe falls back to the CPU path on its own
|
||
- **Historical** (v0.1.0's Slint build still takes it): `unlucky-launcher --software-renderer`, i.e.
|
||
`SLINT_BACKEND=winit-software`, forced the CPU renderer of that build through its own fallback chain
|
||
|
||
Dependency license check (optional locally, required in CI):
|
||
`cargo deny check licenses bans sources`.
|
||
|
||
## CI gates
|
||
|
||
Every push to `main` and every pull request runs fmt / clippy (warnings are errors) / the full test
|
||
suite, and the Linux headless render smoke is a hard gate on the same footing: a red result blocks
|
||
the merge. The macOS runtime probe is not there yet — it only runs on a manual `workflow_dispatch`
|
||
and its result is recorded, not enforced (`continue-on-error`), until the slot passes a first run on
|
||
a real runner.
|
||
|
||
| Workflow | Content |
|
||
|----------|---------|
|
||
| `.github/workflows/ci.yml` | fmt + clippy + test + release build (Windows / Linux matrix); Linux runtime smoke (Xvfb: renders one real frame, then asserts the PNG is not blank); macOS runtime probe (manual dispatch only) |
|
||
| `.github/workflows/guards.yml` | zero-UI guard for `crates/core` (manifest plus `cargo tree`), 400-lines-per-file guard, cargo-deny license and source guard |
|
||
| `.github/workflows/release.yml` | tag `v*` → per-slot builds → single-file artifacts + `SHA256SUMS.txt` → GitHub Release |
|
||
|
||
## Platform baseline
|
||
|
||
Minimum baseline: **Windows 10**; Windows 8.1 / 8 / 7 are not supported.
|
||
|
||
- Windows 10/11 builds use the **standard toolchain** (MSVC / GNU), built the same way as
|
||
upstream cargo
|
||
- macOS 12+ (Apple Silicon and Intel) and Linux x64 / arm64 are supported build slots; on Linux,
|
||
X11 is the preferred path today and Wayland is the long-term direction. macOS builds are unsigned,
|
||
so the first launch needs the Gatekeeper quarantine attribute cleared
|
||
(`xattr -d com.apple.quarantine`)
|
||
- Android Termux is experimental and not promised: CLI only, GUI unverified
|
||
- Each platform runs its own test matrix; capability differences use feature gates with runtime
|
||
probing.
|
||
|
||
## License and acknowledgements
|
||
|
||
- **This project**: Apache License 2.0, see [LICENSE](LICENSE); third-party notices in
|
||
[NOTICE](NOTICE).
|
||
- **Slint** (<https://slint.dev>), Royalty-Free 2.0: obligation for the Slint build that `v0.1.0`
|
||
ships — the condition attaches to each distribution of an Application, not to a version number.
|
||
Upstream §2 offers two forms: (a) the official `AboutSlint` widget on an "About" screen or dialog
|
||
reachable from the top level menu (or the splash screen when there is none); (b) the official Slint
|
||
attribution badge on a public page where the binaries can be downloaded. **`v0.1.0`'s settings page
|
||
does embed the `AboutSlint` widget** (`crates/app/ui/pages/settings.slint`, reachable from the
|
||
sidebar); whether that satisfies §2(a), and the fact that the `v0.1.0` release page carries a text
|
||
link rather than the §2(b) badge, are **not judged here** — Slint's own licence text is the
|
||
authority. The `v0.1.0` NOTICE, that page's text and this section are **records, not a statement of
|
||
discharge**. Later builds link no Slint, so they add nothing to this obligation. The obligation
|
||
follows a **Slint-linked binary**: it applies to the `v0.1.0` assets (and to any later build that
|
||
links Slint again), not to new iced builds — `crates/app` is excluded from the workspace, so the
|
||
pipeline no longer produces one.
|
||
- **iced** (<https://github.com/iced-rs/iced>), MIT: the framework of the UI, together with `winit`
|
||
(Apache-2.0) and, through it, `wgpu` (MIT / Apache-2.0) / `tiny-skia` (BSD-3-Clause).
|
||
- **Google Material Color Utilities** (Apache-2.0): the color tokens in `crates/design` are a Rust
|
||
port of its HCT / ColorSpec2021 algorithms; details and the exact ported functions are listed in
|
||
[NOTICE](NOTICE).
|
||
- **Other major dependencies**: `rmcp` and `axum` (Apache-2.0 / MIT) for the MCP server, `tokio`
|
||
(MIT), `reqwest` with rustls (MIT / Apache-2.0), `serde` / `serde_json` (MIT / Apache-2.0),
|
||
`zip` / `flate2` / `tar` (MIT / Apache-2.0). The allow-list cargo-deny enforces is in `deny.toml`;
|
||
GPL-family dependencies may not enter the main line.
|
||
|
||
Thanks to [HMCL](https://github.com/HMCL-dev/HMCL) and [PCL2](https://github.com/Meloong-Git/PCL)
|
||
(both GPL-3.0). They have been with the vast majority of Minecraft players for years, and this
|
||
project has benefited a great deal from their design choices and implementation details.
|