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
|
||||
|
||||
+214
-162
@@ -2,114 +2,237 @@
|
||||
|
||||
[English](README.md) · **简体中文**
|
||||
|
||||
> **官方项目主页**:https://github.com/AskaEth/Unlucky-Launcher | 下载、议题、发布说明都在这里:
|
||||
> **官方项目主页**:<https://github.com/AskaEth/Unlucky-Launcher> | 下载、议题、发布说明都在这里:
|
||||
> <https://github.com/AskaEth/Unlucky-Launcher/releases>
|
||||
|
||||
Unlucky Launcher(ULL)是一个用 Rust 写的免费开源 Minecraft: Java 版启动器(Apache-2.0)。
|
||||
每个平台就一个原生二进制——没有安装包、没有捆绑运行时、没有 WebView。启动器内置 AI Agent
|
||||
客户端(对话 / 改配置 / 崩溃诊断),也能作为 MCP server 接进别的 Agent 工具。
|
||||
Unlucky Launcher 是一个用 Rust 写的免费开源 **Minecraft: Java 版**启动器(Apache-2.0)。每个平台就是
|
||||
**一个原生二进制**——没有安装包、没有捆绑运行时、没有 WebView——从装一个版本到读懂一次崩溃,它把启动器
|
||||
该做的事都覆盖了:实例与版本、加载器与整合包、Mod / 资源包 / 光影、逐实例 Java、微软 / 第三方社区服 /
|
||||
本地离线账户,以及一套在本地按 YAML 规则跑的崩溃诊断。你的数据不经过我们自己的任何服务:账户令牌只
|
||||
留在本机。
|
||||
|
||||
## 特性
|
||||

|
||||
|
||||
- **单文件原生二进制**:启动器本体不需要 JVM、不需要 WebView、不带旁挂运行时。游戏要的 JDK 由
|
||||
启动器按实例下载管理,不是启动器的依赖。
|
||||
- **一壳两形**:GUI 二进制在跑任何 UI 代码之前就认得全部 CLI 子命令——不开窗、不探渲染器、不扫字体。
|
||||
另有一份更轻的 `ull` 产物,命令集相同,却一个图形栈都不链接。
|
||||
- **默认构建里只有一套 UI**:UI 是 iced 0.14 + Material 3 Expressive。早先的 Slint 实现
|
||||
(`crates/app`)作为**历史参考**保留源码:已被 workspace `exclude`,默认构建不编、测试不跑、
|
||||
发布不发。`v0.1.0` 那个 tag 发的仍是 Slint 版;iced 重写后的第一个 tag 是 `v0.2.0`(见「当前状态」)。
|
||||
- **CPU 软渲染兜底**:`--software-renderer` 让没有可用 GPU 或驱动有问题的老机也能正常显示界面。
|
||||
- **AI Agent + MCP**:启动器内可对话、改设置、读崩溃诊断;`ull agent serve` 把同一个内核以 MCP
|
||||
server 形态(stdio 或 Streamable HTTP)开放给外部 Agent 工具。
|
||||
- **加载器**:Fabric / Quilt / NeoForge / Forge;CurseForge 清单、Modrinth `.mrpack`、
|
||||
MultiMC/Prism 整合包导入,mrpack / CurseForge 导出。
|
||||
- **Java 管理**:Temurin → Oracle → Microsoft → Zulu 回退链,带地区判定。
|
||||
- **账户**:三轨——Microsoft 账户登录(OAuth 2.0 device code flow → Xbox Live → XSTS →
|
||||
`api.minecraftservices.com`)、第三方社区服、本地离线档案;账户令牌只保存在本机。**微软登录需要
|
||||
一个 Azure 公共客户端 id,而发布版二进制里没有**——这个 id 不在仓库里,CI 也不注入,所以启动前设
|
||||
`ULL_MSA_CLIENT_ID=<id>`(或构建时注入)才会有。没配时点登录**不会联网**,弹窗直接说清是这一项
|
||||
没配:标题「微软登录未配置」,正文点名 `ULL_MSA_CLIENT_ID=<id>`(设好或构建时注入后要
|
||||
**重启启动器**——已在运行的进程读不到新设的环境变量),并带 `代码:msa_not_configured`;它
|
||||
**不给重试按钮**,因为在同一个进程里重试必然是同一个错。其它登录失败仍是老样子:一句分类文案 +
|
||||
`代码:<码>`,完整原因只在启动器日志里。第三方社区服与本地档案不需要 id。
|
||||
- **引擎**:多源下载(健康度评分、镜像、分块传输、边下边算)与 YAML 规则引擎的崩溃诊断。
|
||||
- **许可**:Apache-2.0 © AskaEth (YeiJ)。
|
||||
*实例页——1180×760,英文界面,浅色档。实例名是测试数据,不是谁的真实存档。图里那句
|
||||
`Launch failed · exit0 静默失败` 也正好是一处已知缺口的诚实样子:那行原因仍是启动层直接抛出来的中文
|
||||
原文(见[还没做](#还没做))。*
|
||||
|
||||
## 当前状态
|
||||
## 现在能用什么
|
||||
|
||||
`v0.1.0`(2026-11-01)早于 UI 重写:那批资产是 Slint 版,它也是最后一个 Slint 版 tag。重写之后的
|
||||
第一个 tag 是 `v0.2.0`——也就是 iced 这条线,下面说的这套东西都在线上,也正是发布流水线现在会打
|
||||
出来的东西。
|
||||
### 实例、版本与启动
|
||||
|
||||
- **iced 版界面上有什么**:实例、版本管理(三栏 + 搜索)、内容库(整合包 / Mod / 资源包 / 光影,
|
||||
带来源与版本筛选)、日志、设置(通用 / 外观 / Java / 下载)、账户(微软设备码、第三方社区服、本地
|
||||
档案)、AI 右栏,以及安装流程——共用一套组件库与一套令牌。页面清单就是 `presenter::router::Route`
|
||||
里的那些——实例、版本管理、内容库、日志、设置、账户、实例详情、AI 页;`pages::Screen::ALL` 是其中
|
||||
带 slug 的较小一组(`--screen=` 与页面测试矩阵用它,`crates/app-iced/src/pages/tests.rs`),账户、
|
||||
实例详情与 AI 页不在这一组里——它们能路由到,但入口是导航轨与实例磁贴。
|
||||
- **文案跟随系统语言**:自动探测、设置里可显式切换、重启后保持。界面文案统一走一份消息目录
|
||||
(`crates/i18n`)。
|
||||
- **文案守门抓得住什么、抓不住什么**:有一条测试扫实例页的源码,凡是**带 ASCII 单词**的字符串字面量
|
||||
不在短白名单里就红——新写一句英文界面文案,落地即红
|
||||
(`crates/app-iced/src/pages/instances/tests/i18n.rs`)。已知边界:这条扫描只覆盖实例页,不是每一屏;
|
||||
而且中文写死的字面量里没有 ASCII 单词,扫不到——这一类只能靠 review 与出图。
|
||||
- **外观**:窗口背景图、面板透明度、整窗透明度(Windows)、浅色 / 深色 / 跟随系统,以及在系统愿意给
|
||||
的时候使用系统窗口圆角。CPU 渲染档会关掉半透明层,并在界面上说清楚。
|
||||
- **整合包与安装链路都在构建里**:CurseForge 清单 / Modrinth `.mrpack` / MultiMC·Prism 导入,mrpack
|
||||
与 CurseForge 导出,安装选项(选加载器)与**真取消**。
|
||||
- **还没做**:安装器与自动更新通道;Windows 定型后的 macOS 真机复核;整合包导入的**逐文件**冲突策略
|
||||
(当前只有一份全局策略,界面如实写明而不是假装支持);原生文件选择框(导入支持拖拽与粘贴路径)。
|
||||
- **Slint 版已退出构建,只留源码作参考**:`crates/app` 不再是 workspace 成员(M118)——源码留作历史
|
||||
参考,不编译、不测试、不发布,也不再有可用的构建方式。还欠「翻牌」的只有**仓库内的 bin 名**(现在叫
|
||||
`unlucky-launcher-iced`);**产物名不欠**:`packaging/make_artifacts.py` 落盘时用的是
|
||||
`unlucky-launcher_<版本>_<平台>_<架构>`——两个 Windows 槽是裸 `.exe`,其余四个 Linux / macOS 槽
|
||||
是 `.tar.gz`——归档里的二进制名是 `unlucky-launcher`。
|
||||
- **实例**——list-detail 版式:左侧是本地实例列表;右侧是所选实例的版本、加载器、隔离方式(独立游戏
|
||||
目录或共享目录)、文件是否在盘上、上次游玩时间,以及 Play / Stop。窄窗是把两个面板整页切换,不是上下堆。
|
||||
- **实例从哪来**——装一个版本(带实例名、加载器与加载器版本),或者导入一个整合包。实例页自己的
|
||||
**新建实例 / 导入 / 复制 / 打开文件夹 / 删除** 五个按钮**还没接后端**:它们是占位动作,点了只往
|
||||
stderr 写一行(`crates/app-iced/src/app/update/page_messages.rs`),欠的是实例向导
|
||||
(`crates/app-iced/src/pages/mod.rs` 偏离 #8)。
|
||||
- **版本管理**——三栏(版本类型 / 主版本 / 搜索)读 Mojang 版本清单:正式版、快照、远古版;安装时可选
|
||||
加载器与加载器版本。命令行还能只出计划不下载(`ull install --dry-run`:文件清单、总大小、每个文件的
|
||||
选用源)。
|
||||
- **启动**——10 阶段命令行组装(`crates/core/src/launch/`)、逐实例 JVM 与游戏参数、原生库解压落位,
|
||||
每次运行留一份 JSONL 会话记录。进程按**进程树**杀,不是只杀一个 PID。
|
||||
- **崩溃诊断是真的、也是本地的**——`crates/core/src/diagnose` 采五类信号(退出码、`latest.log`、
|
||||
`crash-reports`、`hs_err_pid`、含 mod 清单的环境快照),按 YAML 规则集匹配,把结论与修复动作显示在
|
||||
日志页上。不接模型、不联网:断网能用,没有 GPU 的机器也能用。
|
||||
|
||||
可能会遇到的 bin,先认清楚:
|
||||
### 加载器、整合包与内容库
|
||||
|
||||
| 二进制 | crate | 状态 |
|
||||
|--------|-------|------|
|
||||
| `unlucky-launcher-iced` | `crates/app-iced`(iced 0.14) | workspace 里**唯一**的 GUI bin;发布流水线把文件落成 `unlucky-launcher_<版本>_<平台>_<架构>`(两个 Windows 槽是裸 `.exe`,其余四个 Linux / macOS 槽是 `.tar.gz`),归档里的二进制名是 `unlucky-launcher`;子命令分发同款 |
|
||||
| `unlucky-launcher` | `crates/app`(Slint) | **历史**:该 crate 已被 workspace `exclude`,这个 bin 不再被构建或分发;`v0.1.0` 的资产是最后一批 Slint 版 |
|
||||
| `ull` | `crates/cli` | 同一套命令的零 UI 构建,作为独立资产随发布分发 |
|
||||
- **加载器**——Fabric、Quilt、NeoForge、Forge。需要跑官方 installer `processors` 的 Forge/NeoForge 版本
|
||||
会真跑一遍;机器上没有 Java 时先装一个托管 JDK。
|
||||
- **整合包**——导入 CurseForge 清单、Modrinth `.mrpack`、MultiMC/Prism 实例;导出为 `.mrpack` 或
|
||||
CurseForge 清单。安装是真取消,不是「等这一批文件下完再说」。
|
||||
- **内容库**——整合包 / Mod / 资源包 / 光影,带关键词、来源、版本、加载器与排序筛选,分页结果,
|
||||
以及一键**装入这个实例**。
|
||||
- **这一页会拒绝什么,就写在那一行上**——整合包**不能**从内容库直接装(整合包走上面的导入管线);
|
||||
CurseForge 的「装入」是禁用态,因为它的文件端点还没接通;游戏版本或加载器对不上目标实例的条目也是
|
||||
禁用态,并把不匹配写清楚。没有一条是静默失败的。
|
||||
- **CurseForge 检索要 key**——启动前设 `ULL_CF_API_KEY`,没有它这个来源在来源筛选里是灰的。key 只活在
|
||||
环境变量里:不写进配置文件、不进日志(`crates/presenter/src/host/addon.rs`)。
|
||||
|
||||
GUI 二进制与 `ull` 不是重复品。归档解压出来那个二进制叫 `unlucky-launcher`;Windows 上是裸的
|
||||
`unlucky-launcher_<版本>_<平台>_<架构>.exe`。开窗时 GUI 二进制要用图形栈(X11 / Wayland / GL 走
|
||||
`dlopen`,启动时直接链接 `libfontconfig`);`ull` 除了 glibc 什么都不需要,所以在没有窗口的服务器、
|
||||
CI 容器上跑得起来。这个地板是 glibc ≥ 2.35(Linux 槽位在 Ubuntu 22.04 上构建),更老的发行版——以及
|
||||
根本不在发布矩阵里的 Android/Termux——得源码自建(`packaging/README.md` §4、§9)。没有桌面就用 `ull`,
|
||||
有桌面就用 `unlucky-launcher <子命令>`。两者打出来的 CLI 文案是同一套,自称的名字跟着**你敲的那个
|
||||
文件**走——`argv[0]` 去掉目录与结尾的 `.exe`,别的都不剥(`crates/cli/src/cli/program.rs`)。所以
|
||||
`ull` 打印 `ull ·`,归档里那个 `unlucky-launcher` 打印 `unlucky-launcher ·`,本地 `cargo build` 出来
|
||||
的打印 `unlucky-launcher-iced ·`,而 Windows 的裸 exe 文件名带版本段,打印的是
|
||||
`unlucky-launcher_<版本>_windows_x64 ·`。
|
||||
### Java
|
||||
|
||||
### 子命令分发
|
||||
逐实例的托管 JDK,四家发行版的回退链**按地区排**——Temurin 打头,国内是 Oracle 先于 Microsoft
|
||||
(那边实测快 28×)、国外反过来,Zulu 收尾——可用 `java.vendorPriority` 设置整体覆盖
|
||||
(`crates/core/src/game/java/vendor.rs`)。镜像也按地区选。`java list` 把它真能找到的每个 JDK 都列出来
|
||||
(包括官方启动器自己下的那些),`java install <主版本>` 拉一个。游戏要的 JDK 是启动器的数据,不是启动器
|
||||
本身的依赖。
|
||||
|
||||
`unlucky-launcher <参数>` 在跑任何 UI 代码之前先问一句「这是不是一次 CLI 调用」:
|
||||
### 账户
|
||||
|
||||
三条轨道在同一页:
|
||||
|
||||
- **微软**——OAuth 2.0 设备码流程 → Xbox Live → XSTS → `api.minecraftservices.com`。
|
||||
- **第三方社区认证服**(authlib-injector 一类)。
|
||||
- **本地离线档案**,用于隔离测试。
|
||||
|
||||
**微软登录需要一个 Azure 公共客户端 id,而发布版二进制里没有。** 这个 id 不在仓库里,CI 也不注入,所以
|
||||
启动前设 `ULL_MSA_CLIENT_ID=<id>`(或构建时注入:`ULL_MSA_CLIENT_ID=<id> cargo build`,它通过
|
||||
`option_env!` 烧进二进制)。没有它时点登录**不会联网**,弹窗直接说清是这一项没配:标题「微软登录未配置」,
|
||||
正文点名 `ULL_MSA_CLIENT_ID=<id>`(设好或构建时注入后要**重启启动器**——已在运行的进程读不到新设的环境
|
||||
变量),并带 `代码:msa_not_configured`;它**不给重试按钮**,因为在同一个进程里重试必然是同一个错。其它
|
||||
登录失败仍是老样子:一句分类文案 + `代码:<码>`,完整原因只在启动器日志里。第三方社区服与本地档案不需要 id。
|
||||
|
||||
### 外观与语言
|
||||
|
||||
- **只有一套 UI 栈**——iced 0.14 + Material 3 Expressive,自绘标题栏、带动效的导航轨,浅色 / 深色 /
|
||||
跟随系统。
|
||||
- **外观**——窗口背景图与取色链路(面板配色可从壁纸推出来)、面板透明度、整窗透明度(Windows)。Windows 11
|
||||
与 Server 2025 上向系统请求外框圆角;macOS 与 Linux 交给桌面合成器自己的规则;我们不自己画圆角。
|
||||
- **CPU 兜底**——`--software-renderer` 让没有可用 GPU 或驱动有问题的机器照样正常显示;没有可用适配器时,
|
||||
后端探测自己退到 CPU。这条路上**面板级**半透明与模糊会关掉(**窗口级**透明度照常可用),设置面板会写明。
|
||||
- **本地化**——跟随系统语言探测、设置里可显式切换、重启后保持。绝大多数文案来自同一份消息目录
|
||||
(`crates/i18n`)。**边界,因为上面这句很容易被读大**:防硬编码的守门
|
||||
(`crates/app-iced/src/pages/instances/tests/i18n.rs`)只扫实例页源码,不是每一屏;写死的中文字面量里
|
||||
没有 ASCII 单词可以匹配,这一类它抓不到;外观面板的老控件还是硬编码英文
|
||||
(`crates/app-iced/src/pages/appearance/section.rs`),中文界面下它们仍显示英文。
|
||||
|
||||
### 助手,以及 MCP server
|
||||
|
||||
这是两件不同的事,README 不该含糊:
|
||||
|
||||
- **启动器内的助手今天还是占位壳。** 它的 transport 是脚本化的
|
||||
(`crates/app-iced/src/presenter/transport.rs`),没有接真实模型,而且界面上它出现的每一屏都用四处明说
|
||||
这一点——副标题、输入框占位符、空态正文,以及一枚「模拟」胶囊。会话状态、未读数、草稿、确认流程与具名
|
||||
失败码都是真的 presenter 状态;缺的只是它们后面的模型。
|
||||
- **MCP server 是真的。** `ull agent serve` 把启动器内核以 MCP server 形态(stdio 或 Streamable HTTP)
|
||||
开放给外部 Agent 工具,只监听回环(`127.0.0.1`,默认端口 47823),除非
|
||||
`agent.mcp.allow_write=true` 否则只读。十个工具规格(`crates/agent/src/tools/`)同时喂给两种形态,两条
|
||||
路都过同一个执行器——schema 校验、敏感分级、审计留痕。`ull agent config` 能打印现成的 Claude Code /
|
||||
Cursor 配置片段。
|
||||
|
||||
### 命令行
|
||||
|
||||
GUI 二进制在跑任何 UI 代码之前就先认全部 CLI 子命令——不开窗、不探渲染器、不扫字体。另有一份更轻的 `ull`
|
||||
产物,命令集相同,却一个图形栈都不链接。
|
||||
|
||||
```bash
|
||||
unlucky-launcher # GUI
|
||||
unlucky-launcher --software-renderer # GUI,CPU 渲染(行为不变)
|
||||
unlucky-launcher instances list # CLI,不开窗
|
||||
unlucky-launcher --json launch 1.20.1 # CLI,全局选项排在子命令前面
|
||||
unlucky-launcher --version # CLI,输出版本 + 系统摘要
|
||||
unlucky-launcher # GUI
|
||||
unlucky-launcher --software-renderer # GUI,CPU 渲染
|
||||
unlucky-launcher instances list # CLI,不开窗
|
||||
unlucky-launcher --json launch 1.20.1 # CLI,全局选项排在子命令前面
|
||||
unlucky-launcher --version # CLI,版本 + 系统摘要
|
||||
```
|
||||
|
||||
判据刻意收得很窄,所以原本能用的用法一条都没变:
|
||||
|
||||
- 第一个参数是裸词(不以 `-` 开头)→ 它就在子命令的位置上,交给 CLI。GUI 的 flag 永远不会出现在
|
||||
那个位置:`--screenshot` / `--diff` / `--data-dir` 的取值都排在 flag 之后。
|
||||
- 第一个参数是 flag → 只有当 `crates/cli` 的解析器也解析出一条真命令时才算 CLI 调用
|
||||
(`--help`、`--version`、`--json instances list`)。单独 `--data-dir X` 解析成「没有子命令」,
|
||||
仍然开 GUI;GUI 专属参数(`--software-renderer`、`--screenshot*`、`--renderer`、`--theme`、
|
||||
`--diff`、`--force-state` 等)在 CLI 里一律解析失败,也仍然开 GUI。
|
||||
- 第一个参数是裸词(不以 `-` 开头)→ 它就在子命令的位置上,交给 CLI。GUI 的 flag 永远不会出现在那个位置:
|
||||
`--screenshot` / `--diff` / `--data-dir` 的取值都排在 flag 之后。
|
||||
- 第一个参数是 flag → 只有当 `crates/cli` 的解析器也解析出一条真命令时才算 CLI 调用(`--help`、
|
||||
`--version`、`--json instances list`)。单独 `--data-dir X` 解析成「没有子命令」,仍然开 GUI;GUI 专属
|
||||
参数(`--software-renderer`、`--screenshot*`、`--renderer`、`--theme`、`--diff`、`--force-state` 等)
|
||||
在 CLI 里一律解析失败,也仍然开 GUI。
|
||||
|
||||
**未知子命令是错误,不是开窗**:`unlucky-launcher lst` 打印「未知命令 `lst`」、提示
|
||||
`unlucky-launcher --help`,以 1 退出。静默开窗会让脚本以为自己那个拼错的参数生效了。子命令表只有
|
||||
一处定义(`crates/cli/src/cli/parse.rs`),所以 GUI 二进制和 `ull` 认的命令集天然一致。
|
||||
`unlucky-launcher --help`,以 1 退出。静默开窗会让脚本以为自己那个拼错的参数生效了。子命令表只有一处定义
|
||||
(`crates/cli/src/cli/parse.rs`),所以两个二进制认的命令集天然一致。`ull --help` 会全列出来:`instances`、
|
||||
`install`、`launch`、`pack import`、`accounts`、`sessions`、`logs`、`diag`、`config`、`java`、`agent`、
|
||||
`version`、`help`。
|
||||
|
||||
## 模块地图
|
||||
### 还没做
|
||||
|
||||
这份清单是让人相信上面那些话的理由。没有一条藏在宣传话术后面。
|
||||
|
||||
- **没有安装包,也没有自动更新通道。** 下个文件直接跑,没有任何东西替你检查新版本,更新源也还没定。
|
||||
- **有一批按钮是占位。** 实例页:*新建实例*、*导入*、*复制*、*打开文件夹*、*删除*。日志页:*打开文件夹*。
|
||||
设置页框架:*保存*、*重置本组*、*添加覆盖*。点下去只往 stderr 写一行 `[page] 占位动作 …`,什么都不改
|
||||
——图形界面里的实例管理就是还欠着的那块实例向导;设置项是边改边生效,而不是靠「保存」。
|
||||
- **Linux 与 macOS 产物未验证。** 参考机是 Windows;`linux-x64`、`linux-arm64`、`macos-arm64`、
|
||||
`macos-x64` 四份归档由 CI 矩阵产出,还没被下载下来端到端跑过。把它们当**未经测试的产出**,而不是
|
||||
「矩阵里有就算支持」。
|
||||
- **macOS 未定型。** 有两轮真机证据(Apple Silicon),构建因此存在——但菜单栏显示名、三个窗口按钮的位置
|
||||
(macOS 习惯在左上,我们自绘在右上)、300s+ 稳态仍需真机复核。macOS 产物未签名,首次启动要自己清掉
|
||||
Gatekeeper 隔离标记(`xattr -d com.apple.quarantine`)。macOS 运行期冒烟是手动 `workflow_dispatch`
|
||||
的探针、带 `continue-on-error`,不是门禁。
|
||||
- **内容库页在低窗口高度下会挤**,而且窗口高度还没接进页面模型——这一页按一个名义高度排版,而不是按它
|
||||
真正拿到的高度。
|
||||
- **启动侧失败提示未结构化。** 磁贴直接采用启动层抛出的中文句子
|
||||
(`crates/core/src/launch/exit_analysis.rs`),而不是一个界面能翻译的原因码——上面那张英文截图里的
|
||||
`静默失败` 就是它。
|
||||
- **启动器内的助手背后没有模型**(见上)。MCP server 有。
|
||||
- **整合包导入只有两份全局策略**:保留用户侧或覆盖,不是逐文件选择;导入报告会逐条列出冲突,所以没有
|
||||
文件是被静默覆盖的。
|
||||
- **内容库的安装进度是阶段级**,不是字节级。
|
||||
- **没有原生文件选择框。** 导入靠拖拽或粘贴路径。
|
||||
- **Android/Termux 是试验级、不承诺**:发布矩阵里没有面向它的资产,那一侧的 GUI 也未验证。
|
||||
|
||||
## 下载与运行
|
||||
|
||||
从 [Releases](https://github.com/AskaEth/Unlucky-Launcher/releases) 拿你那个平台的文件。文件名形如
|
||||
`unlucky-launcher_<版本>_<平台>_<架构>`;这个形状的唯一真相源是
|
||||
`packaging/make_artifacts.py::asset_stem()`:
|
||||
|
||||
| 槽位 | 产物 | 形态 |
|
||||
|------|------|------|
|
||||
| `windows-x64` | `unlucky-launcher_<版本>_windows_x64.exe` | 裸 `.exe`,双击即用 |
|
||||
| `windows-arm64` | `unlucky-launcher_<版本>_windows_arm64.exe` | 裸 `.exe` |
|
||||
| `linux-x64` | `unlucky-launcher_<版本>_linux_x64.tar.gz` | 单个归档,解压出一个顶层目录 |
|
||||
| `linux-arm64` | `unlucky-launcher_<版本>_linux_arm64.tar.gz` | 同上 |
|
||||
| `macos-arm64` | `unlucky-launcher_<版本>_macos_arm64.tar.gz` | 同上 |
|
||||
| `macos-x64` | `unlucky-launcher_<版本>_macos_x64.tar.gz` | 同上 |
|
||||
| 每个槽位 | `ull_<版本>_<平台>_<架构>[.exe]` | 裸 CLI 文件,独立资产 |
|
||||
| 全平台 | `SHA256SUMS.txt` | `sha256sum -c` 可直吃 |
|
||||
|
||||
UNIX 归档里是二进制加 `LICENSE`、`NOTICE`、`README.md`,都在一个顶层目录下。Windows 发的是裸 `.exe`
|
||||
(一个文件装不下这三份文本),所以这三份作为并列资产挂在 Release 上——下 Windows 二进制不会把许可条款
|
||||
弄丢。下载链接里不带实现细节字样,归档里的二进制名永远是 `unlucky-launcher`。
|
||||
|
||||
这两种形态不是重复品。GUI 二进制开窗时要用图形栈(X11 / Wayland / GL 走 `dlopen`,启动时直接链接
|
||||
`libfontconfig`);`ull` 除了 glibc 什么都不需要,所以在没有窗口的服务器、CI 容器上跑得起来。Linux 上这个
|
||||
地板是 glibc ≥ 2.35(槽位在 Ubuntu 22.04 上构建)——更老的发行版得源码自建(`packaging/README.md` §4)。
|
||||
没有桌面就用 `ull`,有桌面就用 `unlucky-launcher <子命令>`。两者打出来的 CLI 文案是同一套,自称的名字跟着
|
||||
**你敲的那个文件**走——`argv[0]` 去掉目录与结尾的 `.exe`,别的都不剥
|
||||
(`crates/cli/src/cli/program.rs`),所以 `ull` 打印 `ull ·`,归档里那个二进制打印
|
||||
`unlucky-launcher ·`,本地 `cargo build` 出来的打印 `unlucky-launcher-iced ·`。
|
||||
|
||||
最低平台基线:**Windows 10**;Windows 8.1 / 8 / 7 不在支持范围内。macOS 12+(Apple Silicon 与 Intel),
|
||||
Linux x64 / arm64(基线判定在 `crates/core/src/platform/os_info.rs`)。Windows 10/11 的产物由标准工具链
|
||||
(MSVC / GNU)构建,构建方式与上游 cargo 一致。Linux 上 X11 是当前首选路径,Wayland 是大方向。能力差异
|
||||
一律用 feature gate + 运行期探测表达,绝不做成「这个平台天生少一块」的编译期裁剪。
|
||||
|
||||
第一次用微软账户?先设 `ULL_MSA_CLIENT_ID`——见[账户](#账户)。其它都不需要配置。
|
||||
|
||||
## 从源码构建
|
||||
|
||||
前置:
|
||||
|
||||
- Rust stable(`rust-toolchain.toml` 会自动选工具链;门禁还需要 `rustfmt` / `clippy` 组件)
|
||||
- Windows:MSVC 工具链(CI 用这套)或 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 编译期要用
|
||||
|
||||
```bash
|
||||
cargo fmt --all --check # 格式
|
||||
cargo clippy --workspace --all-targets -- -D warnings # 静态检查(零 warning 门禁)
|
||||
cargo test --workspace # 测试
|
||||
cargo build --release # bin 落在 target/release/
|
||||
cargo deny check licenses bans sources # 依赖许可(CI 必跑)
|
||||
```
|
||||
|
||||
编出来的 bin:
|
||||
|
||||
- `unlucky-launcher-iced`——GUI,同时也是 CLI:同一个文件认上面列的全部子命令。名字里的 `iced` 是 cargo
|
||||
的 bin 名,不是下载名——`packaging/make_artifacts.py` 把同一次构建落成
|
||||
`unlucky-launcher_<版本>_<平台>_<架构>`,归档里的二进制名写成 `unlucky-launcher`。
|
||||
- `ull`——CLI 的独立无 UI 构建:同一套命令、不链接图形栈、体积更小,给完全没有显示设备的机器用。
|
||||
|
||||
渲染开关:
|
||||
|
||||
- `unlucky-launcher-iced --software-renderer` 强制 tiny-skia(`ICED_BACKEND=tiny-skia`);
|
||||
`--screenshot <png>` 出一帧图后退出——这是 CI 无头冒烟 job 用的验收通道
|
||||
- 没有可用适配器时,iced 版靠显式后端探测自己退到 CPU
|
||||
|
||||
**CI 门禁。** 每次推送到 `main` 与每个 PR 都跑 fmt / clippy(warnings 视为错误)/ 全量测试,Linux 无头
|
||||
渲染冒烟与它们同级、是硬门禁:在 Xvfb 里真渲染一帧,再断言 PNG 非空白,红了就不许合。macOS 探针还不算
|
||||
——只手动 dispatch,结果留证据但不作为流水线结论。
|
||||
|
||||
| Workflow | 内容 |
|
||||
|----------|------|
|
||||
| `.github/workflows/ci.yml` | fmt + clippy + test + release build(Windows / Linux 矩阵);Linux runtime smoke(Xvfb 真渲染一帧、断言 PNG 非空白);macOS 运行期探针(仅手动 dispatch) |
|
||||
| `.github/workflows/guards.yml` | `crates/core` / `crates/presenter` / `crates/design` 零 UI 守门、单文件 ≤400 行守门、cargo-deny 许可与来源守门 |
|
||||
| `.github/workflows/release.yml` | tag `v*` → 六槽位构建 → 单文件产物 + `SHA256SUMS.txt` → GitHub Release |
|
||||
|
||||
## 架构与模块地图
|
||||
|
||||
```
|
||||
Unlucky-Launcher (cargo workspace)
|
||||
@@ -120,13 +243,14 @@ Unlucky-Launcher (cargo workspace)
|
||||
│ # —— 零 UI 依赖、零第三方 crate
|
||||
├── crates/i18n # 语言探测、消息目录、中文字体策略
|
||||
├── crates/palette-extract # 唯一解码图片的 crate,供壁纸取色链路使用
|
||||
├── crates/app # 历史 Slint UI(bin `unlucky-launcher`):已被 workspace `exclude`,
|
||||
│ # 仅作源码参考——不再被构建 / 测试 / 发布
|
||||
├── crates/app-iced # iced UI(bin `unlucky-launcher-iced`):workspace 里唯一的 GUI bin
|
||||
├── crates/cli # CLI(bin `ull`):headless,零 UI
|
||||
└── packaging # 单文件产物矩阵与发布脚本
|
||||
```
|
||||
|
||||
层间纪律:`app-iced` → `presenter` → `core` / `agent`。`design` 与 `i18n` 只被壳层与 presenter 消费;
|
||||
它们都不得反过来依赖渲染框架,CI 守门会拦。
|
||||
|
||||
`crates/core` 的八个模块:
|
||||
|
||||
| 模块 | 职责 |
|
||||
@@ -136,85 +260,13 @@ Unlucky-Launcher (cargo workspace)
|
||||
| `game` | 版本/实例仓库、快照与继承解析 |
|
||||
| `launch` | 10 阶段启动管线、进程、JSONL 事件流 |
|
||||
| `auth` | Microsoft 账户登录(OAuth 2.0 device code → Xbox Live → XSTS → Minecraft Services)、第三方社区服、本地档案 |
|
||||
| `addon` | 加载器安装、整合包、CurseForge / Modrinth |
|
||||
| `addon` | 加载器安装、整合包、CurseForge / Modrinth、整合包导出 |
|
||||
| `diagnose` | 五类信号采集、YAML 规则引擎、hs_err 解析 |
|
||||
| `platform` | OS 抽象:路径、进程、文件锁、能力探测、运行期 OS 检测 |
|
||||
|
||||
## 构建
|
||||
|
||||
前置:
|
||||
|
||||
- Rust stable(`rust-toolchain.toml` 会自动选工具链;门禁还需要 `rustfmt` / `clippy` 组件)
|
||||
- Windows:MSVC 工具链(CI 用这套)或 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 编译期要用(Slint 版当年还要 femtovg)
|
||||
|
||||
```bash
|
||||
cargo fmt --all --check # 格式
|
||||
cargo clippy --workspace --all-targets -- -D warnings # 静态检查(零 warning 门禁)
|
||||
cargo test --workspace # 测试
|
||||
cargo build --release # bin 落在 target/release/
|
||||
```
|
||||
|
||||
`--workspace` 就是模块地图里那八个成员:`crates/app` 已在 workspace 层被排除(M118),上面这些命令
|
||||
都不会编 Slint 那棵树——而且**它没有构建方式**:该 crate 只作源码参考,作为 workspace 成员已不可编。
|
||||
|
||||
编出来的 bin:
|
||||
|
||||
- `unlucky-launcher-iced`——GUI(iced)。它同时也是 CLI:同一个二进制认上面列的全部子命令。名字里的
|
||||
`iced` 是 cargo 的,不是下载链接的:还欠「翻牌」的只有这个 bin 名;发布文件里不带实现细节——流水线编
|
||||
这个 bin,`packaging/make_artifacts.py` 落盘时改成
|
||||
`unlucky-launcher_<版本>_<平台>_<架构>`——两个 Windows 槽是裸 `.exe`,其余四个 Linux / macOS 槽
|
||||
是 `.tar.gz`——归档里的二进制名是 `unlucky-launcher`。
|
||||
- `unlucky-launcher`——**历史**:workspace 构建已不产出它(它属于被排除的 `crates/app`)。
|
||||
`v0.1.0` 的资产是最后一批用这个名字发出去的构建。
|
||||
- `ull`——CLI 的独立无 UI 构建:同一套命令、不链接图形栈、体积更小,给完全没有显示设备的机器用。
|
||||
|
||||
渲染开关:
|
||||
|
||||
- `unlucky-launcher-iced --software-renderer` 强制 tiny-skia(`ICED_BACKEND=tiny-skia`);
|
||||
`--screenshot <png>` 出一帧图后退出,这是 CI 无头冒烟 job 用的验收通道
|
||||
- 没有可用适配器时,iced 版靠显式后端探测自己退到 CPU
|
||||
- **历史**(`v0.1.0` 的 Slint 版仍认这条):`unlucky-launcher --software-renderer`,即
|
||||
`SLINT_BACKEND=winit-software`,让那一版走它自己的渲染器回退链
|
||||
|
||||
依赖许可检查(本地可选,CI 必跑):`cargo deny check licenses bans sources`。
|
||||
|
||||
## CI 门禁
|
||||
|
||||
每次推送到 `main` 与每个 PR 都跑 fmt / clippy(warnings 视为错误)/ 全量测试,Linux 无头渲染冒烟
|
||||
与它们同级、是硬门禁:红了就不许合。macOS 运行期探针还不算——它只在手动 `workflow_dispatch` 时跑,
|
||||
结果留证据但不作为流水线结论(`continue-on-error`),直到该槽位在真实 runner 上首跑通过。
|
||||
|
||||
| Workflow | 内容 |
|
||||
|----------|------|
|
||||
| `.github/workflows/ci.yml` | fmt + clippy + test + release build(Windows / Linux 矩阵);Linux runtime smoke(Xvfb 里真渲染一帧,再断言 PNG 非空白);macOS 运行期探针(仅手动 dispatch) |
|
||||
| `.github/workflows/guards.yml` | `crates/core` 零 UI 守门(依赖表 + `cargo tree`)、单文件 ≤400 行守门、cargo-deny 许可与来源守门 |
|
||||
| `.github/workflows/release.yml` | tag `v*` → 分槽位构建 → 单文件产物 + `SHA256SUMS.txt` → GitHub Release |
|
||||
|
||||
## 平台基线
|
||||
|
||||
最低基线 **Windows 10**;Windows 8.1 / 8 / 7 不在支持范围内。
|
||||
|
||||
- Windows 10/11 的产物由**标准工具链**(MSVC / GNU)构建,构建方式与上游 cargo 一致
|
||||
- macOS 12+(Apple Silicon 与 Intel)与 Linux x64 / arm64 都是支持槽位;Linux 上 X11 是当前首选路径,
|
||||
Wayland 是大方向。macOS 产物未签名,首次启动要自己清掉 Gatekeeper 隔离标记
|
||||
(`xattr -d com.apple.quarantine`)
|
||||
- Android Termux 试验级、不承诺:发布矩阵里没有面向它的资产(要在 Termux 上跑得源码自建),GUI 未验证
|
||||
- 每个平台跑各自的测试矩阵;能力差异用 feature gate + 运行期探测表达。
|
||||
|
||||
## 许可证与致谢
|
||||
|
||||
- **本项目**:Apache License 2.0,见 [LICENSE](LICENSE);第三方声明见 [NOTICE](NOTICE)。
|
||||
- **Slint**(<https://slint.dev>),Royalty-Free 2.0:义务针对 `v0.1.0` 发布的那批构建——条件挂在每一次
|
||||
「随应用分发」上,而不是挂在某个版本号上。上游 §2 给出两种形式:(a) 在可从顶层菜单进入的「关于」页/
|
||||
对话框里显示官方 `AboutSlint` 部件(没有该页时改为启动画面);(b) 在可下载该二进制的公开网页上显示
|
||||
官方归属徽章。**`v0.1.0` 的设置页「关于」分组里内置了 `AboutSlint` 部件**(`crates/app/ui/pages/
|
||||
settings.slint`,从侧栏进入);该部件是否满足 §2(a) 的全部条件、以及 `v0.1.0` 发布页上那是文字链接
|
||||
而不是 §2(b) 点名的官方徽章图这两点,**本节不作法律判断**,具体以 Slint 官方许可原文为准。`v0.1.0`
|
||||
的 NOTICE、发布页说明与本节**都只是记录,不构成履行声明**。后续版本不再链接 Slint,该义务不随新产物
|
||||
新增。义务跟着**链接了 Slint 的产物**走:已发布的 `v0.1.0` 资产(以及将来任何重新链接 Slint 的构建)
|
||||
适用;`crates/app` 已被 workspace `exclude`,流水线不再产出链接 Slint 的二进制,所以新的 iced 版不适用。
|
||||
- **iced**(<https://github.com/iced-rs/iced>),MIT:现役 UI 的框架;与它同栈的 `winit`(Apache-2.0)、
|
||||
以及经它引入的 `wgpu`(MIT / Apache-2.0)/ `tiny-skia`(BSD-3-Clause)。
|
||||
- **Google Material Color Utilities**(Apache-2.0):`crates/design` 的色彩令牌是它 HCT /
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 95 KiB |
Reference in new issue
Block a user