A native Linux installer and updater for Kuro Games titles — Wuthering
Waves (鸣潮) and Punishing: Gray Raven (战双帕弥什). No wine, no launcher.exe,
no hpatchz — one static Rust binary that talks to the official CDN directly
and applies Kuro's KrDiff patches natively. It downloads and updates game
files; the game itself is launched via Steam + GE-Proton (see below).
Inspired by ww-manager; the wine dependency is removed by applying patches via hdiffpatch-rs.
┌status─────────────────────────────────────────────┐
│game: punishing-gray-raven │
│server: global │
│local: 4.7.0 │
│remote: 4.7.0 │
│up to date │
└───────────────────────────────────────────────────┘
┌task───────────────────────────────────────────────┐
│task: sync │
│files done: 2429 queued: 26511 │
│[████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 9.8% │
│ 3.90/39.92 GiB overall │
│[████████████████████████] 100.0% 25.5 MiB/… │
└───────────────────────────────────────────────────┘
- Multi-game TUI — one binary manages WuWa and PGR side by side; installed games are auto-detected in your Steam libraries
- status — local vs remote version per game/server, live from the CDN
- predownload — stage the next update (
krpdiffpatches + full-file fallbacks) in the background, resumable - apply — merge
KrDiffpatches natively (no wine), MD5-verify every output, atomic.bakswap, version bump; full-file fallback if a merge fails - sync — parallel full-tree MD5 verify + repair of missing/corrupt files,
resumable across restarts; followed by a stale-artifact sweep that only
removes files the manifest has dropped (same directory and same extension as
manifest entries). Live game state is never deleted:
Client/Saved/**(the client's own resource/video channel, settings, saves, local storage, logs), the SDK / anti-cheat / crash-reporter payloads the client writes for itself, and anything in a directory the manifest doesn't ship into - persistent MD5 cache — a verified file is never hashed twice: entries in
<game>/.kuro_cache/md5.jsonare keyed by manifest path and validated on (size, mtime) likemake/git, so re-verify costs one stat per file. Pure I/O savings — delete.kuro_cache/any time to force a full re-hash - install — from-zero client download for any supported game
- quality packs (SD / HD / UHD) — WuWa's channel manifest carries the base
paks plus one
Client/Content/HDbody and nothing else, so SD and UHD cannot come fromsync.kuro pack <folder> <sd|hd|uhd>installs the tier from the launcher's own pack document (common+sd/hd/uhd, each with its own version, manifest and CDN) and then writes the two records the official launcher reads, so the tier counts as installed instead of as "repair game".--checkverifies without downloading.--qualityoninstall/syncstays the narrower thing: it picks which body those fetch from the channel. PGR (Unity) has no quality packs, so this is WuWa-only - checkout — CN ⇄ Bilibili channel switch for WuWa (diff-file swap + appId)
- built-in launcher —
kuro playstarts the game itself. Since WuWa 3.7 the client refuses to boot without-krqlv=<SD|HD|UHD>on its command line, and that argument can only be given atexectime, so kuro owns the spawn:proton run, orumu-runwhen no Proton is installed, in a kuro-managed prefix under the install.kuro killthen finds the client again by its recorded process group when it hangs on exit - Resumable, MD5-verified downloads with per-file progress bars
| Game | CN 官服 | Bilibili | Global |
|---|---|---|---|
| Wuthering Waves (G152/G153) | ✅ | ✅ | ✅ |
| Punishing: Gray Raven (G143/G148) | ✅ token | — | ✅ |
WuWa's launcher tokens are public and stable (same ones ww-manager ships), so
they're compiled in. PGR's tokens are runtime-only — they rotate, so they
live in ~/.config/kuro/tokens.toml (or KURO_PGR_GLOBAL_TOKEN /
KURO_PGR_CN_TOKEN), never in the binary:
[pgr]
global = "…token from the launcher's WebView2 storage on a Windows install…"
cn = "…same, from a CN launcher install's cache…"When PGR status starts failing (stale token), re-copy it from a real launcher install's cache and update the file.
From a release (Linux x86_64):
curl -L -o kuro https://github.com/vedaru/kuro/releases/latest/download/kuro
chmod +x kuro
sudo mv kuro /usr/local/bin/Or build from source (Rust ≥ 1.75):
cargo build --release
cp target/release/kuro ~/.local/bin/kuro # TUI — auto-detects installed games in Steam libraries
kuro <folder>... # or point at game folders explicitly
kuro status <folder> # CLI: print local/remote versions
kuro sync <folder> [--quality <sd|hd|uhd>] # CLI: verify + repair
kuro install <wuwa|pgr> <cn|bilibili|global> <folder> [--quality <sd|hd|uhd>]
kuro quality <folder> [sd|hd|uhd] # CLI: which pack the client mounts
kuro pack [folder] <sd|hd|uhd> [--check] # CLI: install the paks + register them--quality on install / sync is the download side of that choice: only
the named Client/Content/<TIER> body is fetched, together with the base paks
and binaries every install needs. It defaults to everything the channel serves,
and a body the channel does not serve is an error naming what it does serve —
so "sd" on an HD-only channel tells you instead of quietly installing 42 GiB of
HD. The saved preset (kuro quality <folder> hd) is what a plain sync follows
afterwards, best-effort: if the channel currently serves no such body, the run
falls back to the full manifest rather than failing. PGR has no quality packs at
all, so there --quality is refused rather than ignored.
Getting a tier the channel does not serve is kuro pack's job. It is
idempotent — a rerun verifies by MD5 and downloads only what is missing — and it
writes the launcher records only after every file of the tier verifies, because
a record for a pack the disk does not have is exactly what makes the launcher
answer "repair game". The records are withdrawn before the first byte of a
reinstall for the same reason: an interrupted run must read as "not installed",
not as a broken install. common is reconciled against the channel: where the
two disagree about a file (the 69 channel-SDK binaries, CN vs Bilibili), the
channel wins and the pack's copy is left alone. The manifest is also checked
against the indexFileMd5 the pack document publishes, and an offered version
older than the installed one is refused rather than rolled back.
| Key | Action |
|---|---|
Tab |
cycle focus: status → task → log (focused box is highlighted) |
← / → |
switch game |
↑ / ↓, PgUp / PgDn |
scroll the log (when focused) |
r |
refresh status |
d |
predownload update |
a |
apply predownloaded update |
s |
sync / repair files |
c |
checkout server (CN ⇄ Bilibili) |
i |
install a new game (w/p game · c/b/g server · f quality pack · t path · s Steam default) |
Q |
quality preset (s/h/u pick · i install that tier's paks · Enter pick + install · saved for p) |
h / ? |
help overlay |
q |
quit |
kuro installs game files only and launches them itself — the official
launcher doesn't run under wine, and dropping the game into Steam as a
non-Steam shortcut loses the -krqlv= argument the client now demands:
kuro play <folder> # saved preset / what is on disk / HD
kuro play <folder> --quality hd # this run only
kuro kill <folder> # a client that hung on exitIt runs under Proton (GE or dwproton, whichever $HOME holds) and falls back
to umu-run. Overrides: KURO_PROTON (a directory containing proton),
KURO_PREFIX, STEAM_ROOT. Only WuWa takes a tier argument; PGR launches
plain. First start is slow (shader compile); updates come from kuro, not Steam.
PGR ships ACE (AntiCheatExpert). Its kernel drivers can't load under Proton; most ACE games run fine with it absent, some refuse — no clean workaround.
kuro ──► prod[-cn]-alicdn-gamestarter.kurogame.com/launcher/game/<GID>/<appId>_<token>/index.json
│ weighted CDN list + patchConfig (old→new version transitions)
▼
resource.json ──► {dest, md5, size, chunkInfos}[]
│ · .krpdiff entries = native patch payloads (zstd)
│ · everything else = full-file fallbacks
▼
download (parallel ranged GETs, per-chunk MD5) → verify → atomic swap
- Incremental patches are KrDiff (Kuro's HDIFF19 variant) + zstd — applied
natively by
kuro-patch - Official merge keeps originals untouched until outputs are MD5-verified, then
swaps with
.bakrecovery — kuro mirrors that flow - Full tree hashing streams in 1 MiB chunks (WuWa ships a 26 GB pak — reading whole files would OOM)
crates/
├── kuro-api — launcher protocol types + HTTP client (game-agnostic)
├── kuro-patch — KrDiff / HDiff native patch engine + streaming MD5
├── kuro-core — GameManager: download / apply / sync / checkout / install
└── kuro-tui — ratatui frontend (binary: `kuro`)
Adding a new Kuro title = one entry in kuro-api/src/config.rs; the whole
pipeline is game-agnostic.
cargo test # unit + integration tests
cargo build --release # static-ish release binary
cargo run -p kuro-core --example pgr_proof # live CDN smoke test (PGR)
cargo run -p kuro-core --example status -- <folder> # live status, one installFormatting and lints are gates, not advice — both CI workflows run them before anything is published, so a PR that fails either never reaches a release:
cargo fmt --check # rustfmt.toml: width 120
cargo clippy --workspace --all-targets -- -D warningsNeither uses --all: vendor/hdiffpatch-rs is a path dependency, not a
workspace member, and its two dead-code warnings are not ours to fix.
- PGR launcher tokens rotate and are issued by Kuro's private launcher SDK at
runtime — kuro cannot fetch them (yet), so both PGR servers need a token you
supply yourself (
~/.config/kuro/tokens.toml, see Tokens). PGR CN's CDN host is derived from the shared launcher platform, not yet confirmed against a live session - ACE anti-cheat doesn't run under Proton (see above)
--qualityoninstall/synconly covers the bodies a channel serves:predownload/applystill stage the whole patch set for a version, because a partially-applied patch would record a version the install does not actually hold. Tiers the channel does not serve come fromkuro packkuro packinstalls one tier at a time; nothing switches between installed tiers on disk. Each tier is a disjoint ~20–62 GiB body, so keeping more than one installed is a disk-space decision, and the-krqlv=argument is what picks which is mounted- PGR has no quality packs to choose from: it is a Unity title whose assets live
under
PGR_Data/and whose client takes no-krqlvtier, so--qualityis refused for it rather than pretended at - The CDN manifest describes the base client only, so it is not an inventory
of a WuWa install: the game's own resource channel (
Client/Saved/**, i.e. Video/Lang/Resource packs — not theClient/Content/Paksbase paks) is fetched by the client itself and is invisible to Kuro - WuWa CN hotfixes (e.g. 3.6.0 → 3.6.1) have no
krpdiffgroups and nofromFolder:predownloadstages the changed files as full files, andapplyrefuses to record the new version unless every target file is already on disk at its target hash — usesyncfor those, which downloads and verifies them directly
MIT — see LICENSE.