AskaEth 95131d856e docs(readme): M130 补微软登录的 client id 前提,删「十四页」计数,把 i18n 守门边界与产物名写成实测口径
- Accounts:发布版二进制不含 Azure 公共客户端 id(CI 0 处注入),需设 ULL_MSA_CLIENT_ID;
  没配时点登录只拿到通用「登录失败 / Code: other」。
- 删「feature-complete: fourteen pages」:代码里 Route 8 个变体、Screen::ALL 5 个,没有读法得到 14;
  改成按 Route 列举并说明 Screen::ALL 为何更小。
- i18n:删「视图层没有硬编码字符串」,改成白名单守门抓英文、中文与其它页扫不到的已知边界。
- 自报名例子改成 argv[0] 实测口径;翻牌措辞区分「cargo bin 名未翻」与「产物名已翻」。
- 中文版补「一壳两形」highlight,账户与 auth 模块行补第三方社区服 / 本地档案。
- 顺手改掉「ull 能跑在 Termux」:Linux 资产 glibc ≥ 2.35,Termux 不在发布矩阵内。
2026-11-05 18:05:38 +08:00
2026-10-31 21:54:10 +08:00
2026-10-31 21:54:10 +08:00
2026-10-31 21:54:10 +08:00
2026-10-31 21:54:10 +08:00
2026-10-31 21:54:10 +08:00

Unlucky Launcher (ULL)

English · 简体中文

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. Microsoft sign-in needs an Azure public client id, and no release binary carries one — the id is not in this repo and CI injects none, so set ULL_MSA_CLIENT_ID before launching, or build with it. Without one, pressing sign-in only raises a generic "Sign-in failed" alert with Code: other; nothing on that screen tells you the id is what is missing. Community auth servers and local profiles need no id.
  • 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.

  • What the iced UI has: instances (list-detail), 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 — all on one component library and one token set. The page list is the one in presenter::router::Route — instances, version management, content library, logs, settings, accounts, instance detail, the AI page; pages::Screen::ALL, the smaller set that has a slug (--screen= and the page test matrix, crates/app-iced/src/pages/tests.rs), leaves out accounts, instance detail and the AI page, which are routed but opened from the nav rail or an instance tile.
  • Localisation follows the system language: detection, an explicit override in settings, and persistence across restarts. UI copy goes through one message catalogue (crates/i18n).
  • What the copy guard catches, and what it does not: a test reads the instances page sources and fails on any string literal that contains an ASCII word unless it is on a short whitelist, so a newly hard-coded English string turns red as it lands (crates/app-iced/src/pages/instances/tests/i18n.rs). Known boundary: that scan covers the instances page rather than every screen, and a hard-coded Chinese literal has no ASCII word to match, so nothing catches it — that class relies on review and the screenshot run.
  • 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 the file as unlucky-launcher_<version>_<platform>_<arch>[.exe], and the binary inside each archive is named 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

The GUI binary (unlucky-launcher once extracted or downloaded) and ull are not duplicates. The GUI binary needs the graphics stack when it opens a window (it dlopens X11 / Wayland / GL, and links libfontconfig at load); ull needs nothing beyond glibc, so it runs on a server or a CI container where no window can exist. That floor is glibc ≥ 2.35 (the Linux slots build on Ubuntu 22.04), so an older distribution — and Android/Termux, which is not a released slot at all — needs a source build (packaging/README.md §4, §9). Pick ull when there is no desktop, unlucky-launcher <command> when there is. Both print the same CLI text, and each names itself after the file you ran — argv[0] with its directory and a trailing .exe removed, and nothing else (crates/cli/src/cli/program.rs). So ull prints ull ·, the unlucky-launcher inside an archive prints unlucky-launcher ·, a local cargo build prints unlucky-launcher-iced ·, and the bare Windows asset, whose file name keeps the version segment, prints unlucky-launcher_<version>_windows_x64 ·.

Subcommand dispatch

unlucky-launcher <args> first asks "is this a CLI call?" before any UI code runs:

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
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 iced in this name is cargo's, not the download's: renaming the bin is what the "flip" still owes. The release file name carries no implementation detail — the pipeline builds this bin and packaging/make_artifacts.py stages it as unlucky-launcher_<version>_<platform>_<arch>[.exe], with the binary inside each archive named unlucky-launcher, so download links never carry iced.
  • 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: no released asset targets it (build from source there), and the GUI is 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; third-party notices in 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.
  • 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 and PCL2 (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.

S
Description
A modern MC launcher.
Readme Apache-2.0
4.8 MiB
0 Stars 1 Watchers 0 Forks
Languages
Rust 96.8%
Slint 2.8%
Python 0.3%