docs(readme): 重排成面向第一次打开的人的次序,并补一张真截图
发布前把两份 README 的次序改成:一句话定位 → 截图 → 现在能用什么(含「还没做」) → 下载与运行 → 从源码构建 → 架构/模块地图 → 许可与致谢。原来的边界说明没删, 拉到对应功能旁边;「还没做」独立成节并写实。 同时按用户令清掉所有 v0.1.0 / Slint 时代的内容(版本考古段、历史 bin 行、Slint Royalty-Free 义务段、crates/app 的模块地图行与构建说明、「翻牌还欠 bin 改名」)。 NOTICE 未动。 内容上纠正了几处「写得比实现强」: - 启动器内的助手没有接真实模型(transport 是脚本化的),原句「chat 在启动器里」 会让读者以为能用;现在明说占位,并把真的那部分(MCP server)单独讲清。 - 「界面文案统一走一份目录」改成本地化边界:守门只扫实例页,且外观面板老控件 仍是硬编码英文。 - 实例页的 新建/导入/复制/打开文件夹/删除、日志页的 打开文件夹、设置帧的 保存/重置/加覆盖 都是没有后端的占位按钮(pages/mod.rs 偏离 #8),写进「还没做」。 - Java 回退链按地区不同,补上 java.vendorPriority 与区间差异。 - 补 ULL_CF_API_KEY(CurseForge 检索的前置,此前全仓文档都没提)。 截图:docs/images/instances-en-light.png(DevDocs/artifacts/m120/03-instances-en-light.png, 1180×760、浅色、英文界面;实例名与档案名均为测试数据,无路径/令牌)。
This commit is contained in:
1 parent
9cc3260644
commit
8ad9792abd
3 files changed
+458
-345
No files matched your search
@@ -2,117 +2,144 @@
|
||||
|
||||
**English** · [简体中文](README.zh-CN.md)
|
||||
|
||||
> **Official project home**: https://github.com/AskaEth/Unlucky-Launcher | downloads, issues and
|
||||
> **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.
|
||||
Unlucky Launcher 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 — and covers the launcher job from installing a version to reading a crash report: instances
|
||||
and versions, mod loaders and modpacks, mods / resource packs / shaders, per-instance Java, Microsoft
|
||||
/ third-party / offline accounts, and a crash diagnosis that runs locally on a YAML rule engine.
|
||||
Nothing of yours goes through a service of ours: account tokens stay on the machine.
|
||||
|
||||
## 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=<id>` before launching, or build with it. Without one, sign-in
|
||||
stops before any network call and the alert says what is missing: the title "Microsoft sign-in is
|
||||
not configured", a body naming `ULL_MSA_CLIENT_ID=<id>` (set it, or build with it, then restart the
|
||||
launcher — a running process does not pick up a new variable), and `Code: msa_not_configured`. It
|
||||
offers no Retry button, because retrying in that same process cannot help. Every other sign-in
|
||||
failure keeps the older shape — one category line plus its own `Code:` line, the full reason only
|
||||
in the launcher log. 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).
|
||||
*The instances page — 1180×760, English UI, light theme. The instance names are test data, not
|
||||
anybody's real setup. That `Launch failed · exit0 静默失败` line is also an honest sight of a known
|
||||
gap: the reason text is still raw Chinese straight out of the launch layer (see
|
||||
[Not done yet](#not-done-yet)).*
|
||||
|
||||
## Status
|
||||
## What works today
|
||||
|
||||
`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.
|
||||
### Instances, versions and launching
|
||||
|
||||
- **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).
|
||||
- **Instances** — a list-detail page: the local instance list on the left; on the right the selected
|
||||
instance's version, loader, isolation mode (an isolated game directory or the shared one),
|
||||
files-on-disk state and last-played time, with Play / Stop. Narrow windows switch panes instead of
|
||||
stacking them.
|
||||
- **Where an instance comes from** — installing a version (with a name, loader and loader version)
|
||||
or importing a modpack. The instances page's own **New instance / Import / Duplicate / Open folder
|
||||
/ Delete** buttons are **not wired yet**: they are placeholder actions that only write a line to
|
||||
stderr (`crates/app-iced/src/app/update/page_messages.rs`), and the instance wizard is the pending
|
||||
piece (`crates/app-iced/src/pages/mod.rs`, deviation #8).
|
||||
- **Version management** — a three-column rail (version type / major version / search) over the
|
||||
Mojang version list: release, snapshot and ancient versions, plus loader selection and a loader
|
||||
version when you install. The CLI can plan the same install without downloading
|
||||
(`ull install --dry-run`: file list, total size, source per file).
|
||||
- **Launching** — a 10-stage command-line assembly (`crates/core/src/launch/`), per-instance JVM and
|
||||
game arguments, a native-library pass, and a JSONL session record for every run. Process trees are
|
||||
killed as trees, not as one PID.
|
||||
- **Crash diagnosis is real and local** — `crates/core/src/diagnose` collects five kinds of signals
|
||||
(exit code, `latest.log`, `crash-reports`, `hs_err_pid` files, environment snapshot including the
|
||||
mod list), matches them against a YAML rule set, and shows the findings with fix actions on the
|
||||
logs page. No model, no network: it works offline and on a machine with no GPU.
|
||||
|
||||
Binaries you may run into, so you can tell them apart:
|
||||
### Mod loaders, packs and the content library
|
||||
|
||||
| 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>` — a bare `.exe` on the two Windows slots, a `.tar.gz` on the four Linux / macOS slots — 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 |
|
||||
- **Loaders** — Fabric, Quilt, NeoForge and Forge. Forge/NeoForge versions that need the official
|
||||
installer `processors` really run them, with a managed JDK if the machine has none.
|
||||
- **Modpacks** — import a CurseForge manifest, a Modrinth `.mrpack`, or a MultiMC/Prism instance;
|
||||
export as `.mrpack` or a CurseForge manifest. Installs are cancellable for real, not at the next
|
||||
file boundary.
|
||||
- **Content library** — modpacks / mods / resource packs / shaders with keyword, source, version,
|
||||
loader and sort filters, paged results, and one-click *install into this instance*.
|
||||
- **What that page will refuse, and says so on the row** — a modpack cannot be installed *from* the
|
||||
library (packs go through the import pipeline above); CurseForge's *install* is disabled because
|
||||
its file endpoint is not wired; and an item whose game version or loader does not match the target
|
||||
instance is disabled with the mismatch spelled out. Nothing fails silently.
|
||||
- **CurseForge search needs a key** — set `ULL_CF_API_KEY` before launching; without it that source
|
||||
is greyed out in the source filter. The key stays in the environment: it is not written to the
|
||||
settings file and not logged (`crates/presenter/src/host/addon.rs`).
|
||||
|
||||
The GUI binary and `ull` are not duplicates. An archive extracts the first as `unlucky-launcher`; on
|
||||
Windows it is a bare `unlucky-launcher_<version>_<platform>_<arch>.exe`. 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 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 ·`.
|
||||
### Java
|
||||
|
||||
### Subcommand dispatch
|
||||
A managed JDK per instance, with a four-vendor fallback chain ordered by region — Temurin first, then
|
||||
Oracle before Microsoft inside mainland China (measured 28× faster there) and Microsoft before Oracle
|
||||
elsewhere, Zulu last — overridable through the `java.vendorPriority` setting
|
||||
(`crates/core/src/game/java/vendor.rs`). Mirrors are region-aware too. `java list` shows every JDK it
|
||||
can actually see (including the ones the official launcher downloaded), `java install <major>` fetches
|
||||
one. The game's JDK is the launcher's data, not its own dependency.
|
||||
|
||||
`unlucky-launcher <args>` first asks "is this a CLI call?" before any UI code runs:
|
||||
### Accounts
|
||||
|
||||
Three tracks on one page:
|
||||
|
||||
- **Microsoft** — OAuth 2.0 device code flow → Xbox Live → XSTS → `api.minecraftservices.com`.
|
||||
- **Third-party community auth servers** (authlib-injector style).
|
||||
- **Local offline profiles**, for isolated testing.
|
||||
|
||||
**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=<id>` before launching, or build with
|
||||
it (`ULL_MSA_CLIENT_ID=<id> cargo build`, which bakes it in via `option_env!`). Without one, sign-in
|
||||
stops before any network call and the alert says exactly what is missing: the title "Microsoft
|
||||
sign-in is not configured", a body naming `ULL_MSA_CLIENT_ID=<id>` (set it, or build with it, then
|
||||
restart the launcher — a running process does not pick up a new variable), and
|
||||
`Code: msa_not_configured`. It offers no Retry button, because retrying in that same process cannot
|
||||
help. Every other sign-in failure keeps the older shape: one category line plus its own `Code:`
|
||||
line, with the full reason only in the launcher log. Community auth servers and local profiles need
|
||||
no id.
|
||||
|
||||
### Look and language
|
||||
|
||||
- **One UI stack** — iced 0.14 + Material 3 Expressive, a self-drawn title bar, an animated
|
||||
navigation rail, light / dark / follow-system.
|
||||
- **Appearance** — window background image with a colour pipeline (panel colours can be derived from
|
||||
the wallpaper), per-panel transparency, and whole-window transparency on Windows. On Windows 11
|
||||
and Server 2025 the system is asked for rounded corners; macOS and Linux leave corners to the
|
||||
desktop's own rules; nothing is drawn by hand.
|
||||
- **CPU fallback** — `--software-renderer` keeps the interface rendering on machines with no usable
|
||||
GPU or broken drivers, and the backend probe falls back on its own when no adapter is usable. On
|
||||
that path panel-level translucency and blur are switched off (window-level transparency still
|
||||
works) and the settings panel says so.
|
||||
- **Localisation** — system-language detection, an explicit override in settings, and persistence
|
||||
across restarts. Most copy comes from one message catalogue (`crates/i18n`).
|
||||
**Boundary, stated because it is easy to over-read that sentence**: the anti-hard-coding guard
|
||||
(`crates/app-iced/src/pages/instances/tests/i18n.rs`) scans the instances page sources only, not
|
||||
every screen; a hard-coded *Chinese* literal has no ASCII word for it to match, so nothing catches
|
||||
that class; and the appearance panel's older controls are still hard-coded English
|
||||
(`crates/app-iced/src/pages/appearance/section.rs`), so a Chinese UI still shows them in English.
|
||||
|
||||
### The assistant, and the MCP server
|
||||
|
||||
These are two different things and the README should not blur them:
|
||||
|
||||
- **The in-app assistant is a placeholder shell today.** Its transport is scripted
|
||||
(`crates/app-iced/src/presenter/transport.rs`), no real model is connected, and the UI says so in
|
||||
four places on every screen it appears — subtitle, input placeholder, empty-state body, and a
|
||||
"Simulated" chip. The session state, unread count, draft, confirm flow and named failure codes are
|
||||
real presenter state; only the model behind them is not there yet.
|
||||
- **The MCP server is real.** `ull agent serve` exposes the launcher core to external agent tools as
|
||||
an MCP server over stdio or Streamable HTTP, loopback only (`127.0.0.1`, default port 47823),
|
||||
read-only unless `agent.mcp.allow_write=true`. The same ten tool specs (`crates/agent/src/tools/`)
|
||||
feed both shapes, and both go through the same executor — schema validation, sensitivity tiers,
|
||||
audit trail. `ull agent config` prints ready-made Claude Code / Cursor snippets.
|
||||
|
||||
### Command line
|
||||
|
||||
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.
|
||||
|
||||
```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
|
||||
unlucky-launcher # GUI
|
||||
unlucky-launcher --software-renderer # GUI, CPU renderer
|
||||
unlucky-launcher instances list # CLI, no window
|
||||
unlucky-launcher --json launch 1.20.1 # CLI, global options before the command
|
||||
unlucky-launcher --version # CLI, version + OS summary
|
||||
```
|
||||
|
||||
The rule is deliberately narrow, so nothing that used to work changed meaning:
|
||||
The dispatch 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
|
||||
@@ -123,46 +150,92 @@ The rule is deliberately narrow, so nothing that used to work changed meaning:
|
||||
`--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.
|
||||
**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 lives in exactly one place
|
||||
(`crates/cli/src/cli/parse.rs`), so both binaries accept the same set by construction. `ull --help`
|
||||
lists them all: `instances`, `install`, `launch`, `pack import`, `accounts`, `sessions`, `logs`,
|
||||
`diag`, `config`, `java`, `agent`, `version`, `help`.
|
||||
|
||||
## Module map
|
||||
### Not done yet
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
This list is the reason to believe the rest. None of it is hidden behind marketing copy.
|
||||
|
||||
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.
|
||||
- **No installer and no auto-update channel.** You download a file and run it. Nothing checks for a
|
||||
newer version for you, and there is no update source decided yet.
|
||||
- **Several buttons are placeholders.** On the instances page: *New instance*, *Import*, *Duplicate*,
|
||||
*Open folder*, *Delete*. On the logs page: *Open folder*. In the settings frame: *Save*, *Reset
|
||||
section*, *Add override*. Each writes `[page] 占位动作 …` to stderr and changes nothing — instance
|
||||
management from the GUI is the instance-wizard work still outstanding, and settings change as you
|
||||
edit them rather than on a Save.
|
||||
- **Linux and macOS artifacts are unverified.** The reference machine is Windows; the `linux-x64`,
|
||||
`linux-arm64`, `macos-arm64` and `macos-x64` archives are produced by the CI matrix and have not
|
||||
been downloaded and run end to end. Treat them as untested output, not as "supported because the
|
||||
matrix lists them".
|
||||
- **macOS is not finalised.** It has two rounds of real-device evidence (Apple Silicon), which is why
|
||||
the build exists — but the menu-bar name, the placement of the three window buttons (macOS puts
|
||||
them top-left; ours are drawn top-right), and 300s+ steady state still need a real machine. macOS
|
||||
builds are unsigned, so the first launch needs the Gatekeeper quarantine attribute cleared
|
||||
(`xattr -d com.apple.quarantine`). The macOS runtime smoke job is a manual `workflow_dispatch`
|
||||
probe with `continue-on-error`, not a gate.
|
||||
- **The content library is cramped at low window heights**, and the window height is not wired into
|
||||
the page model yet — the page lays itself out for a nominal height rather than the one it gets.
|
||||
- **Launch-side failure text is not structured.** The tile takes a Chinese sentence straight from the
|
||||
launch layer (`crates/core/src/launch/exit_analysis.rs`) instead of a reason code the UI could
|
||||
translate — which is exactly the `静默失败` you can see in the English screenshot above.
|
||||
- **The in-app assistant has no model behind it** (see above). The MCP server does.
|
||||
- **Pack import has two global policies**, keep-user or overwrite, not a per-file choice; the import
|
||||
report lists every conflict so nothing is overwritten silently.
|
||||
- **Install progress is stage-level**, not byte-level, on the content-library install path.
|
||||
- **No native file chooser.** Import takes drag-and-drop or a pasted path.
|
||||
- **Android/Termux is experimental and not promised**: no released asset targets it, and the GUI is
|
||||
unverified there.
|
||||
|
||||
The eight modules of `crates/core`:
|
||||
## Download and run
|
||||
|
||||
| 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 |
|
||||
Grab the file for your platform from
|
||||
[Releases](https://github.com/AskaEth/Unlucky-Launcher/releases). Names follow
|
||||
`unlucky-launcher_<version>_<platform>_<arch>`; the single source of truth for that shape is
|
||||
`packaging/make_artifacts.py::asset_stem()`:
|
||||
|
||||
## Building
|
||||
| Slot | Asset | Shape |
|
||||
|------|-------|-------|
|
||||
| `windows-x64` | `unlucky-launcher_<version>_windows_x64.exe` | bare `.exe`, double-click to run |
|
||||
| `windows-arm64` | `unlucky-launcher_<version>_windows_arm64.exe` | bare `.exe` |
|
||||
| `linux-x64` | `unlucky-launcher_<version>_linux_x64.tar.gz` | one archive, one top-level directory |
|
||||
| `linux-arm64` | `unlucky-launcher_<version>_linux_arm64.tar.gz` | same |
|
||||
| `macos-arm64` | `unlucky-launcher_<version>_macos_arm64.tar.gz` | same |
|
||||
| `macos-x64` | `unlucky-launcher_<version>_macos_x64.tar.gz` | same |
|
||||
| every slot | `ull_<version>_<platform>_<arch>[.exe]` | bare CLI binary, its own asset |
|
||||
| all | `SHA256SUMS.txt` | `sha256sum -c` eats it directly |
|
||||
|
||||
Each UNIX archive contains the executable plus `LICENSE`, `NOTICE` and `README.md`, in one top-level
|
||||
directory. Windows ships a bare `.exe` (a single file cannot carry the texts), so those three
|
||||
documents are attached to the release next to it — downloading the Windows binary does not strip the
|
||||
licence. Download links never carry implementation detail in the name, and the binary inside an
|
||||
archive is always called `unlucky-launcher`.
|
||||
|
||||
The two shapes 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 or a CI container where no window can exist. On Linux that floor is glibc ≥ 2.35
|
||||
(the slots build on Ubuntu 22.04) — an older distribution needs a source build
|
||||
(`packaging/README.md` §4). 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, nothing else (`crates/cli/src/cli/program.rs`), so the
|
||||
`ull` asset prints `ull ·`, the binary inside an archive prints `unlucky-launcher ·`, and a local
|
||||
`cargo build` prints `unlucky-launcher-iced ·`.
|
||||
|
||||
Minimum platform baseline: **Windows 10**; Windows 8.1 / 8 / 7 are not supported. macOS 12+ on Apple
|
||||
Silicon and Intel, Linux x64 / arm64 (`crates/core/src/platform/os_info.rs` holds the baseline
|
||||
check). On Windows 10/11 the builds use the standard toolchain (MSVC / GNU), built the same way as
|
||||
upstream cargo. On Linux, X11 is the preferred path today and Wayland is the long-term direction.
|
||||
Capability differences are feature gates with runtime probing, never a compile-time "this platform
|
||||
gets less".
|
||||
|
||||
First run with a Microsoft account? Set `ULL_MSA_CLIENT_ID` first — see
|
||||
[Accounts](#accounts). Nothing else needs configuring.
|
||||
|
||||
## Build from source
|
||||
|
||||
Prerequisites:
|
||||
|
||||
@@ -177,84 +250,72 @@ 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/
|
||||
cargo deny check licenses bans sources # dependency licences (required in CI)
|
||||
```
|
||||
|
||||
`--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>` — a bare `.exe` on the two Windows slots, a
|
||||
`.tar.gz` on the four Linux / macOS slots — 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.
|
||||
- `unlucky-launcher-iced` — the GUI, and the CLI in the same file: it answers every subcommand listed
|
||||
above. `iced` is cargo's bin name, not a download name — `packaging/make_artifacts.py` stages that
|
||||
same build as `unlucky-launcher_<version>_<platform>_<arch>` and names the binary
|
||||
`unlucky-launcher` inside the archive.
|
||||
- `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
|
||||
`--screenshot <png>` writes one frame and exits — that 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.
|
||||
**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: it
|
||||
renders one real frame under Xvfb, then asserts the PNG is not blank, and a red result blocks the
|
||||
merge. The macOS probe is not there yet — manual dispatch only, result recorded but not enforced.
|
||||
|
||||
| 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 |
|
||||
| `.github/workflows/ci.yml` | fmt + clippy + test + release build (Windows / Linux matrix); Linux runtime smoke (Xvfb, one real frame, PNG asserted non-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 licence and source guard |
|
||||
| `.github/workflows/release.yml` | tag `v*` → six-slot build → single-file artifacts + `SHA256SUMS.txt` → GitHub Release |
|
||||
|
||||
## Platform baseline
|
||||
## Architecture and module map
|
||||
|
||||
Minimum baseline: **Windows 10**; Windows 8.1 / 8 / 7 are not supported.
|
||||
```
|
||||
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-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
|
||||
```
|
||||
|
||||
- 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.
|
||||
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 CI 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 |
|
||||
|
||||
## 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
|
||||
|
||||
Reference in new issue
Block a user