Skip to content

feat: add agent-based install, update, repair, and maintenance support - #48

Open
danielsreichenbach wants to merge 35 commits into
mainfrom
feat/new-crates
Open

feat: add agent-based install, update, repair, and maintenance support#48
danielsreichenbach wants to merge 35 commits into
mainfrom
feat/new-crates

Conversation

@danielsreichenbach

@danielsreichenbach danielsreichenbach commented Mar 7, 2026

Copy link
Copy Markdown
Member

Pull Request

Summary

This PR delivers the ability to install, update, verify, repair, and maintain Blizzard CASC/NGDP game installations from Rust. It also adds a compatible Blizzard Agent HTTP API server on port 1120, so existing tooling that speaks the agent protocol can interact with cascette-rs.

New crates:

  • cascette-agent: HTTP REST server on port 1120, compatible with the Blizzard Agent API — handles game session tracking, progress reporting, hardware info, and operation control
  • cascette-installation: full pipeline for install, verify, repair, and update of CASC game installations; includes patch application (ZBSDIFF1/block patches) and CDN endpoint failover
  • cascette-maintenance: storage garbage collection, archive compaction, and data repair
  • cascette-containerless: containerless storage backend using loose files and Turso SQLite for metadata
  • cascette-import: community data importers for BlizzTrack, wago.tools, TACTKeys listfiles
  • cascette-metadata: FileDataID resolution and content categorization
  • cascette-proto: prost-generated types for the Blizzard Agent product.db protobuf format

Extended crates:

  • cascette-crypto: murmur3_fmix64, Armadillo key reader, CSPRNG key generation, ChainedKeyProvider
  • cascette-ribbit: file watcher for hot-reload, TLS server, BPSV seqn fix
  • cascette-protocol: CDN endpoint failover, retry policy for range/rate-limit responses
  • cascette-client-storage: UpdateSection LSM L0 writes; KMT hash guard and checksum fixes
  • cascette-formats: fixes for BLTE, ESpec, encoding, root, download, install, TVFS, ZBSDIFF1, patch archive, patch index, CDN archive index; integration tests for all major formats

Type of Change

  • New feature (non-breaking change that adds functionality)
  • Bug fix (non-breaking change that fixes an issue)
  • Documentation update
  • Build system/dependency changes

Related Issues

N/A

Testing

Unit and integration tests included per crate. CDN fixture tests cover real data from WoW Classic Era, WoW Classic, WoW Retail, and StarCraft 2.

Tested On

  • Linux

WoW Versions Tested

  • Classic Era (1.13.x/1.14.x/1.15.x)
  • N/A (not related to WoW file formats)

Quality Checklist

  • Code follows project style guidelines
  • Self-review of code completed
  • Tests pass locally
  • Documentation updated (if applicable)

By submitting this PR, I confirm that:

@danielsreichenbach danielsreichenbach changed the title feat: add 8 new crates for CASC installation, agent, maintenance, and more feat: add agent-based install, update, repair, and maintenance support Mar 7, 2026
- Apply rustfmt across workspace (line wrapping, import grouping)
- Fix clippy lints: format_push_string -> write!, map_unwrap_or ->
  map_or, write_with_newline -> writeln!, needless_range_loop ->
  iterator, useless_conversion removal
- Fix 12 failing tests in cascette-client-storage:
  - archive_file: always remap mmap after file growth (64MB threshold
    prevented remaps on small test writes)
  - segment/archive_file: fix key round-trip expectations (only first
    9 bytes survive LocalHeader reverse+zero-pad)
  - segment/archive_file: compute checksums before validation
  - index/update: correct capacity assertion from 60 to 12 pages
- Update lz4_flex 0.12.0 -> 0.12.1 (RUSTSEC-2026-0041)
- Update rustls-webpki 0.103.9 -> 0.103.10 (RUSTSEC-2026-0049)
- Run cargo update for 50 semver-compatible dependency bumps
- clap 4.5 -> 4.6
- tokio 1.49 -> 1.50 (workspace + WASM-target overrides)
- lz4_flex 0.12 -> 0.13
- gloo-timers 0.3 -> 0.4
- mockall 0.13 -> 0.14
- notify 6 -> 8
- sysinfo 0.32 -> 0.38
- axum-server 0.7 -> 0.8

No code changes required. All 1976 tests pass.
- BLTE: "Block Table Encoded" -> "Block Table Encoding" (canonical term)
- TACT: "Tooling And Content Technology" -> "Trusted Application Content Transfer"

Terminology standardized across wiki, management, and cascette repos.
Two issues found by testing cascette-agent against the WoW 1.13.2
client under WINE:

1. Historical builds with pinned build_config and cdn_config triggered
   concurrent BTS bootstrapper and launcher installs. The BTS pipeline
   resolved the current live BTS version and wrote its own .build.info
   with product="bts" and cdn_path="tpr/bnt004", overwriting the game's
   .build.info. The client then rejected the install because the version
   string didn't match "1.13.2.31650". Fix: skip resolve_bts_configs()
   entirely when custom configs are provided.

2. The V7 IDX header segment_size field was written as 1 << 30 (1 GB)
   instead of (1 << 30) * 256 (256 GB). The Blizzard agent writes the
   total addressable space (single segment size * max archive count),
   not just one segment. The client uses this to compute archive file
   indices from offsets. Fix: multiply by 256.

Also updates PROMPT.md to let the user choose CDN source (local mirror,
project mirror at tactic.wowemu.dev, or custom).

A third issue remains: the CASC data file layout differs from the
reference (data.000 starts with zeros, total size ~200 MB less). With
the reference Data/ directory swapped in, the client boots to the login
screen. This needs further investigation in the ArchiveManager.
…rades, remove 3 unused deps

- cargo update: 146 semver-compatible patches (tokio, axum, hyper, clap, etc.)
- lz4_flex 0.13 -> 0.14 (trivial, no API change for block::compress/decompress)
- tower-http 0.6 -> 0.7 (no API change for CorsLayer, TraceLayer, CompressionLayer)
- mockall 0.14 -> 0.15 (test-only, additive change)
- Remove unused deps: salsa20, cipher, asn1
- Fix clippy: assert! -> assert_eq! in cascette-ribbit server.rs test

Deferred: crypto group (digest/sha2/md-5) blocked by rsa 0.10 rc;
ASN.1/PKI group (der/x509-cert/cms) blocked by cms 0.3.0 pre-release;
turso 0.4->0.7 engine swap needs dedicated testing.
- cascette-crypto: salsa20_vector example emits a Salsa20 test vector for
  cross-verification with the C++ Salsa20Cipher
- cascette-formats: gen_lz4_cross/decode_lz4_cross, gen_encrypted_blte/
  decode_enc, encrypt_vector examples prove BLTE interop with C++ CascLib
- tools: add wow_classic_titan 3.80.2.68943 to generate_wow_paths.sh and
  verify_mirror_sizes.sh (1061 builds); drop stale per-product count comments
- CHANGELOG: document the new examples and tool update
- Rotate 5 builds with new CDN configs (d2a7d30a → 9a824cce)
- Add wow 12.1.0.69214 and wow 12.1.0.69273
- Sync comments to match values
- New build detected in DB after upstream sync
…le headers

The client's BuildLocalFileHeader stores the complete reversed encoding
key (not the 9-byte truncated form cascette-rs wrote) and writes
flags=0 for normal data entries (flags=1 only for segment reconstruction
headers). Verified byte-identical against a client-written 1.13.2.31650
store: key field 1b856f765a48ae137418c07d2dd0ca59 for ekey
59cad02d7dc0187413ae485a766f851b, enc_size 13061932 including the
30-byte header, checksum_a/b matching.

LocalHeader::new now takes an explicit flags argument (0 = data entry,
1 = reconstruction header); SegmentHeader::generate and the dynamic
container reconstruction-header update pass 1.
- DB dropped wow_classic_era 1.13.x builds (42 removed total)
- Rotate 6 builds with new CDN configs (cc values)
- Add wow 12.1.0.69299
- Preserve script structure: only build/comment lines touched
Patched VFS shards (1.15.4+) store the CFT as a flat array of 9-byte
ekeys; span cft_offsets are multiples of ekey_size (9), not the
flag-derived entry size (27 with flags=0x7). resolve_path() used a
linear search over flag-stride entries, dropping every rem-9/rem-18
span and capping resolution at 44k/130k paths.

- TvfsFile::resolve_path now returns Option<Vec<u8>> (the ekey) via a
  new ContainerFileTable::ekey_at() that reads bytes at any offset
- cft_stride() derives the true entry stride as the GCD of all span
  cft_offsets (fallback: flag-derived entry size for degenerate cases)
- Added gcd() helper, test_resolve_path_flat_cft and test_gcd
- Removed 4 leftover scratch tests (cft_raw_check, vfs_raw_check,
  vfs_resolve_check, vfs_table_check) that hardcoded /tmp/patched_vfs32.blte

Workspace: 2080 tests pass, 0 failures.
Ports cascette-py dc0dfb8 + 17339be to the Rust install pipeline.

- root_content::fetch_loose_build_files: fetches the build-config loose
  files the client reads on a fresh install but the download manifest
  omits — vfs-root/vfs-N TVFS manifests (1.14.4+), the patch-index
  manifest, and the patched VFS shards named by patch-config
  'patch-entry = vfs:' records (1.15.4+). Without these the client
  re-fetches them on first start (149 requests on 1.14.4.51001, 5382
  on 1.15.4.56738).
- root_content::fetch_root_content: resolves the root manifest's
  content set (TVFS CFT ekeys, or MFST/TSFM content keys through the
  encoding table), intersects with the tag-selected download ekeys,
  and fetches missing content via archive range + loose fallback
  (skips keys already in the store). On 1.15.4.56738 the root
  enumerates 214k keys; without this the client re-fetches ~544 files
  (7200 requests, no login).

Both run in the Downloading state after the CASC phase. Failures are
per-file warnings, not fatal. Adds parse_vfs_shards helper + 2 unit
tests. Workspace: 2082 tests, 0 failures.
- Rename gen's CDN to CDN_URL; make CDN_URL/CDN_PATH overridable in all scripts
- mirror-tact-from-cdn: use MIRROR, CDN_URL, OUTDIR (was CDN_MIRROR, PATHS_DIR)
- Document environment variables in all three headers
The prior format (8-byte LE decompressed-size prefix + single LZ4 block)
matched no Blizzard binary: the Battle.net-Setup binary (RE via Ghidra,
DecoderZ::Decode 0x4e65b0) parses a 10-byte header — format 0x01, BE32
compressed total, BE32 decompressed total, block-size exponent k with
0x400 < 2^k <= 0x10000 — followed by back-to-back raw LZ4 blocks with a
64 KiB dictionary window across blocks. The Agent binary stubs BLTE '4'
(error 5), so its codec was never a valid reference.

- compress: 16 KiB blocks, 64 KiB dict-tail window, header-only for empty
- decompress: manual LZ4 token walk per block + decompress_with_dict
- rejects bad format bytes, out-of-range exponents, size mismatches
- cross-validated: gen_lz4_cross output decodes identically with the
  battle.net-setup C++ decoder (single- and multi-chunk)

Tests: cascette-formats 739+, client-storage 234 pass.
- Add --product-config <hash> to 648 build lines that have a product
  config in the cascette-py DB; comment gains pc=<hash>
- build_file_tree already supports --product-config and emits the config
  from tpr/configs/data on the official CDN; no binary change needed
- 7 builds with empty-string product config left without the flag
Add #[allow(clippy::unused_async_trait_impl)] to methods in the
Container trait implementations across all container types, and to
the CdnClient fetch methods that implement a trait with async methods.
Remove unused BinReaderExt import and convert all trait method calls
to explicit binrw::BinReaderExt qualified calls to satisfy clippy.
Use is_multiple_of() instead of manual modulo check. Move gcd() to
module level before tests. Simplify cft_stride if/else expression.
Reorder imports alphabetically. Wrap long function calls and log
macros for readability. Add implicit_hasher allow on HashSet param.
dump_segments: rename size_with_header -> encoded_size field.
dump_shmem: improve shmem detection, add per-field round-trip check,
print unmodeled client fields for debugging.
local_verification: support env vars for hash overrides.
build_file_tree: report missing files as MISSING instead of ignoring.
Full CAS container storage dump: ordered walk of every data.NNN file
cross-referenced against the IDX index (KMT). Validates segment
headers, reconstruction header checksums, key matches, contiguity,
and reports per-bucket stats.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant