Files
Unlucky-Launcher/README.md
T
AskaEth bf6b0c4126 build(release): workspace 版本 0.1.0 → 0.2.0,README 的 tag 事实改成 v0.2.0 口径
- Cargo.toml [workspace.package] version = 0.2.0;Cargo.lock 由 cargo 重新解出一致
  (只有 8 个 unlucky-* 成员条目变动,第三方版本一字未动)
- 各 crate 的 `unlucky-*` path 依赖 `version = "0.1"` 同步提到 "0.2",否则工作区
  根本解析不出来(cargo metadata 会报 `failed to select a version for
  unlucky-core = "^0.1"`);crates/app 虽被 exclude、cargo 不读,也一并跟上避免将来
  重新纳入时立刻解析失败
- core::launch::LauncherConfig 兜底的 launcher_version 改成 env!("CARGO_PKG_VERSION"),
  不再是把版本写死一份(CLI / presenter 本来就这么写)
- README.md / README.zh-CN.md:删掉「v0.1.0 是目前唯一的 tag / the only tagged release」
  这类随 v0.2.0 发布即变假的陈述,改写成「v0.1.0 是最后一个 Slint 版 tag,v0.2.0 是
  重写后的第一个 tag」;所有讲 v0.1.0 资产 / Slint Royalty-Free 义务的历史记录原样保留
- packaging/README.md:命名与校验示例跟当前 tag 走(v0.2.0);§7 实测记录里的 0.1.0
  输出加一句「属实测记录、不回填」,并把过期的「四个 workspace member」改成「任何一个」
2026-11-05 14:08:37 +08:00

244 lines
16 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.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 binary, two shapes**: the GUI binary also answers every CLI subcommand before any UI code
runs — no window, no renderer probe, no font scan. A second, lighter `ull` artifact serves the
same command set from a binary that links no graphics stack at all.
- **One UI stack**: iced 0.14 + Material 3 Expressive, with self-drawn title bar, animated
navigation rail, light / dark / follow-system, and a background-image colour pipeline. The earlier
Slint implementation (`crates/app`) is kept as source-only history: excluded from the cargo
workspace, never built, tested or shipped.
- **CPU software-rendering fallback**: `--software-renderer` keeps the interface rendering normally
on machines with no usable GPU or with broken drivers; the backend probe falls back on its own when
no adapter is usable.
- **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. The
agent rail runs on real presenter state, and says so plainly when no model endpoint is wired.
- **Mod loaders and modpacks**: Fabric, Quilt, NeoForge, Forge; CurseForge manifest, Modrinth
`.mrpack` and MultiMC/Prism packs import, plus mrpack / CurseForge export.
- **Java management**: a Temurin → Oracle → Microsoft → Zulu fallback chain, region-aware.
- **Accounts**: Microsoft sign-in over OAuth 2.0 device code flow → Xbox Live → XSTS →
`api.minecraftservices.com`; third-party community auth servers; local offline profiles for
isolated testing. 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
`v0.1.0` (2026-11-01) predates the UI rewrite: its assets are Slint builds, and it stays the last
Slint tag. `v0.2.0` is the first tag after the rewrite — the iced line, which is what the release
pipeline builds now. Everything described below lives on that line.
- **The iced UI is feature-complete**: fourteen pages — instances, version management (three-column
rail layout with search), content library (modpacks / mods / resource packs / shaders with source
and version filters), logs, settings (general / appearance / Java / downloads), accounts
(Microsoft device code, third-party community servers, local profiles), the AI rail and the
installer flows — on top of one component library and one token set.
- **Localisation follows the system language**: detection, an explicit override in settings, and
persistence across restarts. UI copy goes through a single message catalogue; there is no
hard-coded string in the view layer.
- **Appearance**: window background image, per-panel transparency and whole-window transparency
(Windows), light / dark / follow-system, and system window corners where the platform grants them.
On CPU rendering the translucent layers are switched off and the UI says so.
- **Packaging and the installer paths** (curseforge / modrinth / multimc import, mrpack and
CurseForge export, install options with loader selection and real cancellation) are in the build.
- **What is not done yet**: installers / auto-update channel; macOS re-verification on a real
machine after the Windows pass; per-file conflict policy on pack import (the import pipeline has one
global policy today, and the UI states that rather than pretending otherwise); a native file
chooser (import accepts drag-and-drop and a pasted path).
Binaries you may run into, so you can tell them apart:
| Binary | Crate | State |
|--------|-------|-------|
| `unlucky-launcher-iced` | `crates/app-iced` (iced 0.14) | the GUI binary the workspace builds; the release pipeline stages it under the public name `unlucky-launcher`; the same binary answers every CLI subcommand |
| `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` (the GUI) 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 the GUI binary 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/presenter # business host layer: framework-free state / host / dto / conversion
├── crates/design # design tokens: real HCT color / type / shape / spacing / elevation / motion / icons
│ # / component metrics — zero UI dependencies, no third-party crates
├── crates/i18n # language detection, message catalogue, CJK font policy
├── crates/palette-extract # the only crate that decodes images, for the wallpaper colour pipeline
├── 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
```
Layering: `app-iced` → `presenter` → `core` / `agent`. `design` and `i18n` are consumed by the shell
and the presenter; none of them may depend on a rendering framework, and the guards enforce it.
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), third-party community servers, local profiles |
| `addon` | mod loader installation, modpack import, CurseForge / Modrinth, pack export |
| `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
```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 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 guards (`crates/core`, `crates/presenter`, `crates/design`), 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.