From 8ad9792abdcb397cd4391e8482350092a5146105 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Thu, 5 Nov 2026 20:53:30 +0800 Subject: [PATCH 1/2] =?UTF-8?q?docs(readme):=20=E9=87=8D=E6=8E=92=E6=88=90?= =?UTF-8?q?=E9=9D=A2=E5=90=91=E7=AC=AC=E4=B8=80=E6=AC=A1=E6=89=93=E5=BC=80?= =?UTF-8?q?=E7=9A=84=E4=BA=BA=E7=9A=84=E6=AC=A1=E5=BA=8F=EF=BC=8C=E5=B9=B6?= =?UTF-8?q?=E8=A1=A5=E4=B8=80=E5=BC=A0=E7=9C=9F=E6=88=AA=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 发布前把两份 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、浅色、英文界面;实例名与档案名均为测试数据,无路径/令牌)。 --- README.md | 427 ++++++++++++++++------------- README.zh-CN.md | 376 ++++++++++++++----------- docs/images/instances-en-light.png | Bin 0 -> 97742 bytes 3 files changed, 458 insertions(+), 345 deletions(-) create mode 100644 docs/images/instances-en-light.png diff --git a/README.md b/README.md index ae9079d..e327847 100644 --- a/README.md +++ b/README.md @@ -2,117 +2,144 @@ **English** · [简体中文](README.zh-CN.md) -> **Official project home**: https://github.com/AskaEth/Unlucky-Launcher | downloads, issues and +> **Official project home**: | downloads, issues and > release notes all live there: -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 +![The instances page: the local instance list on the left; on the right the selected instance's +version, loader, isolation mode and last-played time](docs/images/instances-en-light.png) -- **Single-file native binary**: the launcher itself needs no JVM, no WebView and no sidecar - runtime. The JDK a game needs is downloaded and managed per instance, not as a launcher - dependency. -- **One binary, two shapes**: the GUI binary also answers every CLI subcommand before any UI code - runs — no window, no renderer probe, no font scan. A second, lighter `ull` artifact serves the - same command set from a binary that links no graphics stack at all. -- **One UI stack**: iced 0.14 + Material 3 Expressive, with self-drawn title bar, animated - navigation rail, light / dark / follow-system, and a background-image colour pipeline. The earlier - Slint implementation (`crates/app`) is kept as source-only history: excluded from the cargo - workspace, never built, tested or shipped. -- **CPU software-rendering fallback**: `--software-renderer` keeps the interface rendering normally - on machines with no usable GPU or with broken drivers; the backend probe falls back on its own when - no adapter is usable. -- **AI agent + MCP**: chat, setting edits and crash diagnosis inside the launcher; `ull agent serve` - exposes the same core as an MCP server (stdio or Streamable HTTP) to external agent tools. The - agent rail runs on real presenter state, and says so plainly when no model endpoint is wired. -- **Mod loaders and modpacks**: Fabric, Quilt, NeoForge, Forge; CurseForge manifest, Modrinth - `.mrpack` and MultiMC/Prism packs import, plus mrpack / CurseForge export. -- **Java management**: a Temurin → Oracle → Microsoft → Zulu fallback chain, region-aware. -- **Accounts**: Microsoft sign-in over OAuth 2.0 device code flow → Xbox Live → XSTS → - `api.minecraftservices.com`; third-party community auth servers; local offline profiles for - isolated testing. Account tokens are kept on this machine only. **Microsoft sign-in needs an Azure - public client id, and no release binary carries one** — the id is not in this repo and CI injects - none, so set `ULL_MSA_CLIENT_ID=` before launching, or build with it. Without one, 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=` (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___` — 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___.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 ` 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__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 ` fetches +one. The game's JDK is the launcher's data, not its own dependency. -`unlucky-launcher ` 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=` before launching, or build with +it (`ULL_MSA_CLIENT_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=` (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___`; the single source of truth for that shape is +`packaging/make_artifacts.py::asset_stem()`: -## Building +| Slot | Asset | Shape | +|------|-------|-------| +| `windows-x64` | `unlucky-launcher__windows_x64.exe` | bare `.exe`, double-click to run | +| `windows-arm64` | `unlucky-launcher__windows_arm64.exe` | bare `.exe` | +| `linux-x64` | `unlucky-launcher__linux_x64.tar.gz` | one archive, one top-level directory | +| `linux-arm64` | `unlucky-launcher__linux_arm64.tar.gz` | same | +| `macos-arm64` | `unlucky-launcher__macos_arm64.tar.gz` | same | +| `macos-x64` | `unlucky-launcher__macos_x64.tar.gz` | same | +| every slot | `ull___[.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 ` 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___` — 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___` 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 ` writes one frame and exits, which is the acceptance channel CI's headless + `--screenshot ` 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** (), 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** (), 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 diff --git a/README.zh-CN.md b/README.zh-CN.md index 55d2194..8b1d78a 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -2,114 +2,237 @@ [English](README.md) · **简体中文** -> **官方项目主页**:https://github.com/AskaEth/Unlucky-Launcher | 下载、议题、发布说明都在这里: +> **官方项目主页**: | 下载、议题、发布说明都在这里: > -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 规则跑的崩溃诊断。你的数据不经过我们自己的任何服务:账户令牌只 +留在本机。 -## 特性 +![实例页:左侧是本地实例列表,右侧是所选实例的版本、加载器、隔离方式与上次游玩时间](docs/images/instances-en-light.png) -- **单文件原生二进制**:启动器本体不需要 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=`(或构建时注入)才会有。没配时点登录**不会联网**,弹窗直接说清是这一项 - 没配:标题「微软登录未配置」,正文点名 `ULL_MSA_CLIENT_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=`(或构建时注入:`ULL_MSA_CLIENT_ID= cargo build`,它通过 +`option_env!` 烧进二进制)。没有它时点登录**不会联网**,弹窗直接说清是这一项没配:标题「微软登录未配置」, +正文点名 `ULL_MSA_CLIENT_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 ` 出一帧图后退出——这是 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 ` 出一帧图后退出,这是 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**(),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**(),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 / diff --git a/docs/images/instances-en-light.png b/docs/images/instances-en-light.png new file mode 100644 index 0000000000000000000000000000000000000000..ef989299f03292c591262d4d4b90d8015b198da1 GIT binary patch literal 97742 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
`+wo}x>8?+IHl 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 Jdh0K%{ukGoXXyX{ literal 0 HcmV?d00001 From a3f26d424ba28e52c9ad1060011c9dedc2094389 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Thu, 5 Nov 2026 20:59:16 +0800 Subject: [PATCH 2/2] =?UTF-8?q?docs(readme):=20r1=20=E5=A4=8D=E5=AE=A1=20P?= =?UTF-8?q?2=20=E4=B8=A4=E6=9D=A1=E6=8E=AA=E8=BE=9E=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=EF=BC=88dry-run=20=E5=8F=A3=E5=BE=84=E3=80=81=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E5=8F=96=E6=B6=88=E7=9A=84=E7=9C=9F=E5=AE=9E=E5=90=AB?= =?UTF-8?q?=E4=B9=89=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P2-1:`--dry-run` 原来写成「file list, total size, source per file」,比实现强。人类 可读输出是按角色的文件数与字节数 + 合计 + 每类一个源链样本 (crates/cli/src/commands/install/report.rs:161-201),逐文件列源是被刻意否决的设计 (crates/core/src/game/install/plan_view.rs:46-49:「只取每类的首个文件做样本……逐文件 列源选择既没用也没法读」)。整份逐文件清单只在 --json 的 data["files"] 里。 P2-2:「安装是真取消」原来挂在整合包条目下,读者会读成「整合包导入也能真停」。实际 导入取消是界面级的:面板收回 + 结果作废,后台导入跑到底(crates/presenter/src/host/ addon/pack.rs:28-42 模块文档原话「取消是界面级的……core 的 ImportRequest 没有取消 令牌」)。真取消是版本安装那条路(crates/presenter/src/host/dispatch.rs:61-88:在飞 文件就地掐断、段边界收手、不登记实例),已移到「版本管理」条目。 中英两版同步改,未动结构。 --- README.md | 12 ++++++++---- README.zh-CN.md | 8 +++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index e327847..351f04e 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,11 @@ gap: the reason text is still raw Chinese straight out of the launch layer (see 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). + version when you install. Cancelling an installation really stops it — the in-flight file is cut, the + job ends at the next stage boundary, and no instance is registered + (`crates/presenter/src/host/dispatch.rs:61-88`). The CLI can plan the same install without + downloading (`ull install --dry-run`: per-role file counts, total size, and one source-chain sample + per file category; the full file list is in `--json`). - **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. @@ -50,8 +53,9 @@ gap: the reason text is still raw Chinese straight out of the launch layer (see - **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. + export as `.mrpack` or a CurseForge manifest. Cancelling an **import** is interface-level only: the + panel closes and the result is discarded, but the import thread runs to the end, because `core`'s + import request has no cancel token yet (`crates/presenter/src/host/addon/pack.rs:28-42`). - **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 diff --git a/README.zh-CN.md b/README.zh-CN.md index 8b1d78a..5a772f6 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -28,8 +28,9 @@ Unlucky Launcher 是一个用 Rust 写的免费开源 **Minecraft: Java 版**启 stderr 写一行(`crates/app-iced/src/app/update/page_messages.rs`),欠的是实例向导 (`crates/app-iced/src/pages/mod.rs` 偏离 #8)。 - **版本管理**——三栏(版本类型 / 主版本 / 搜索)读 Mojang 版本清单:正式版、快照、远古版;安装时可选 - 加载器与加载器版本。命令行还能只出计划不下载(`ull install --dry-run`:文件清单、总大小、每个文件的 - 选用源)。 + 加载器与加载器版本。取消安装是真停:在下一个段边界收手,在飞的那个文件就地掐断,且不登记实例 + (`crates/presenter/src/host/dispatch.rs:61-88`)。命令行还能只出计划不下载(`--dry-run`:按类的文件数 + 与总大小,人类可读输出里每类给一个源链样本;整份文件清单在 `--json` 里)。 - **启动**——10 阶段命令行组装(`crates/core/src/launch/`)、逐实例 JVM 与游戏参数、原生库解压落位, 每次运行留一份 JSONL 会话记录。进程按**进程树**杀,不是只杀一个 PID。 - **崩溃诊断是真的、也是本地的**——`crates/core/src/diagnose` 采五类信号(退出码、`latest.log`、 @@ -41,7 +42,8 @@ Unlucky Launcher 是一个用 Rust 写的免费开源 **Minecraft: Java 版**启 - **加载器**——Fabric、Quilt、NeoForge、Forge。需要跑官方 installer `processors` 的 Forge/NeoForge 版本 会真跑一遍;机器上没有 Java 时先装一个托管 JDK。 - **整合包**——导入 CurseForge 清单、Modrinth `.mrpack`、MultiMC/Prism 实例;导出为 `.mrpack` 或 - CurseForge 清单。安装是真取消,不是「等这一批文件下完再说」。 + CurseForge 清单。**导入**的取消只是界面级:面板立刻收回、结果作废,但后台那次导入会跑到底——core 的 + 导入请求还没有取消令牌(`crates/presenter/src/host/addon/pack.rs:28-42`)。 - **内容库**——整合包 / Mod / 资源包 / 光影,带关键词、来源、版本、加载器与排序筛选,分页结果, 以及一键**装入这个实例**。 - **这一页会拒绝什么,就写在那一行上**——整合包**不能**从内容库直接装(整合包走上面的导入管线);