复审 r2 的 2 条 P1 + 3 条 p2 小账,全部只动文档(5 份,0 个 .rs)。
P1-1(`SLINT_BACKEND` → `ICED_BACKEND` 换了个名字的死开关):
`packaging/release-notes.md` 的「No GPU」小节原来把 `ICED_BACKEND=tiny-skia` 当作
`--software-renderer` 的等价替代命令给用户。实测该值会被本进程无条件覆盖:
- `ICED_BACKEND=tiny-skia ./unlucky-launcher-iced.exe --screenshot …`
→ `[probe] … request=auto -> ICED_BACKEND=wgpu` / `[shot] … renderer=wgpu` / `[backend] final=wgpu`
- `--software-renderer --screenshot …` → `request=cpu -> ICED_BACKEND=tiny-skia` / `renderer=tiny-skia`
- `--renderer=cpu --screenshot …` → `request=cpu -> ICED_BACKEND=tiny-skia` / `renderer=tiny-skia`
所以改成两条都实测可用的形态(`--software-renderer` / `--renderer=cpu`),不再把环境变量当用户开关。
P1-2(RF-2.0 履行方式的措辞与上游 §2 冲突):
`NOTICE` 第 1 条原写「由 v0.1.0 的 NOTICE、发布页说明与本文件的历史记录共同承担」——§2 只承认
(a) AboutSlint 部件 / (b) 公开下载页的官方徽章两种履行,NOTICE 文本不是履行。改为:义务仍挂在
v0.1.0 已发布资产上(条件挂在每次分发,不挂版本号);§2 只承认两种形式;**按本仓库记录,该项义务
在 v0.1.0 上尚未以任一形式落地**(部件一直只有排期,发布页那行是文字链接不是徽章图);NOTICE /
发布页说明 / 本节都只是记录。
p2 小账:
- `NOTICE` 第 3 条的依赖许可:`tiny-skia` 从「Apache-2.0 / MIT」改正为 BSD-3-Clause,并把
winit(Apache-2.0)/ wgpu(MIT OR Apache-2.0)分别写明;README(中英)同款写法一并对齐。
- `packaging/README.md` §4:`libGL/libEGL` 行改成「Slint 版 femtovg / iced 版 wgpu(推断,未实测)」,
并把限定语改成「除标注『推断,未实测』的那一行外,整表是 Slint 版实测」,消除表内与限定语打架。
- `README.md` / `README.zh-CN.md` 的 Slint 义务段补 `v0.1.0` 限定并按 §2 口径重写,避免读者以为
「Made with Slint」说的是下一版。
证据:`git diff --name-only` = NOTICE, README.md, README.zh-CN.md, packaging/README.md,
packaging/release-notes.md;`packaging/README.md` 仍 399 行(守卫上限 400);release body 模板用
`{{VERSION}}=0.2.0` 渲染通过(5956 字符,全篇 Slint 命中 4 行,均在「本版不含 Slint」的解释段)。
254 lines
16 KiB
Markdown
254 lines
16 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. Upstream §2 admits exactly two ways to discharge it — an `AboutSlint` widget on an about
|
||
screen, or the official Slint attribution badge on a public download page. By this repository's own
|
||
record that obligation has **not yet been discharged in either form** for `v0.1.0`: the widget was
|
||
only ever scheduled, and the `v0.1.0` release page carries a text link rather than the badge. The
|
||
`v0.1.0` NOTICE, that page's text and this section are **records, not discharge**. 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 a Slint-linked binary.
|
||
- **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.
|