Skip to content

small-gaps (2026-10-27): nine small catalog items

Branch claude/small-gaps-2026-10-27, Linux cloud session. Status words: tested = unit tests pass on Linux, unbuilt = never compiled on macOS, proposed = reasoned, not checked against a file or a title. Nothing here was run on a Mac, a title or a real dump.

What changed

Item Result Status
Personalized tickets ticket.rs encrypted_title_key: RSA-2048 OAEP (SHA-256, MGF1-SHA-256, empty label) with the user's eticket_rsa_keypair (hex of the 0x230-byte device key: D, N, E). num-bigint for the modular exponentiation, padding checks reject every failure with one error. ingest/package pick the title key through it. A personalized ticket without the key is skipped as before tested against an OpenSSL vector and corrupted blocks
IVFC verification new ivfc.rs: parses the header, checks the master hash and every hash level; nca::romfs_data does that on open (levels above the data are tiny), romfs_data_verified also streams the data level through the tree. Headers with no tree (the old synthetic images) pass through tested on built trees (valid, level corruption, data corruption, bad headers)
NCA2 / NCA0 nca.rs reads NCA2 (each FS header XTS-decrypted at sector 0 instead of the continued numbering) and NCA0 (same, plus a 0x100-byte RSA-OAEP key area decrypted with the user key nca0_key_area_rsa = D||N). NCA1 stays unsupported NCA2 tested on a synthetic image; NCA0 proposed (key name, OAEP parameters and layout are from memory)
NSZ / XCZ ncz.rs: a first section that straddles the raw 0x4000 header boundary (recorded from 0xC00 by some writers) is trimmed instead of rejected, for both the stream and the block form. The existing round trip, hostile-input and XCZ-ingest tests still cover the parser tested on synthetic NCZ; the real dump check is a Mac/user step
audren performance metrics the performance frame is kept in a history of performance_frame_count frames and handed over oldest first (whole frames only, chained by next_offset); the single-frame behaviour is the count 0/1 case tested; header layout and modelled costs are still unverified
audin source wiring the first OpenAudioIn installs the source named by NAPPLE_AUDIN_SOURCE: silence, tone:HZ[:AMP], file:PATH (WAV or raw s16le, looped, resampled), mic. mic is an AUHAL input unit into a 48 kHz mono ring (audin_mic.c). The microphone stays opt-in: without NSMicrophoneUsageDescription and the audio-input entitlement a bundled app would be killed on first access sources and the ring resampler tested; mic unbuilt (syntax checked with stubs in tools/linux-check)
GameCube / Palma / NES styles NAPPLE_STYLE_* bits for the Lark, Lucia, Lagon and Lager pads; choose_style picks GC/Palma/NES when the title announces them and the player's binding style asks for them (or the title supports nothing else); style to lifo mapping, attributes (GC wired) and the six-axis index; binding JSON gc/palma/nes. The GC trigger lifo is not written (its offset in the entry is unverified) tested through the real writers and napple_input_update
irs leftovers rows 302, 315, 316, 317, 319 are implemented (activation state, function level, the Ex transfer = the transfer processor, LED accepted); 310 (tera plugin) and 312 (pointing) stay stubs because their output layouts are not documented tested
bcat storage provider hook (bcat_set_provider), folder provider (NAPPLE_BCAT_DIR), directory Open/Read/GetCount/Close, file Open/Read/GetSize/GetDigest, EnumerateDeliveryCacheDirectory, MD5 digests; sync succeeds when a provider exists. No provider = unchanged offline answers. The user's folder is read only tested with a folder fixture and MD5 vectors

Scan (python3 -I .claude/skills/percent-analysis/scan.py, base 273606a -> this branch)

Measured coverage 94.5 % -> 94.9 %. Content domain 4.5 -> 4.9 points (+0.36): IVFC verification missing -> impl (+0.14), NCA2/NCA0 missing -> partial (+0.11), personalized tickets missing -> partial (+0.11). IPC command depth +0.02 (irs.c 11/7 -> 16/2). The other five items stay partial by the rule that an item not verified on a Mac or a title is partial (performance metrics, audin, styles, irs, bcat, NSZ/XCZ), so they do not move yet; they are the ones the Mac checks below can turn into impl. verified.json entries for the nine items were updated to match (statuses and notes only).

Decisions

  • Keys the user must supply, never committed: eticket_rsa_keypair, nca0_key_area_rsa. Both are looked up by name in prod.keys with any length (KeySet::get_vec).
  • The microphone is off unless asked for (env). The camera rule of the user (ask only when a game needs it) is the model: an audin stream opening is the "game needs it" moment, but the usage description has to exist first.
  • bcat reads a folder the user fills; the repository carries no delivery client. A private build plugs a downloader in through BcatProvider.
  • The GC trigger lifo and a Palma-specific state layout were left out rather than guessed at.

Tests and gates run here (Linux x86_64)

  • cargo test --workspace, cargo clippy --workspace --all-targets -- -D warnings, cargo fmt --check: pass.
  • make -f audio/Makefile.linux test asan (new test_audin_source, history test in test_audren): pass. make -f input/Makefile.linux test (new test_styles): pass.
  • sh runtime/tests/linux/run.sh (41 tests built and run; applet applet_libs fs fs_dataid time do not build on Linux): everything passes except test_svc_mii.c:35, which also fails on the base commit 273606a (the host user's name on Linux), not touched here.
  • python3 Napple/tools/check_docs.py: OK.

Mac checks that remain

  1. make -C audio test, make -C input test, make -C runtime test test-services test-host.
  2. Four boots (LAS, SMO, ACNH, MK8D, 45 s headless, exit 0, no CRASH): the changes are inert unless a title opens audin or announces a new style, so a regression would show as a build break.
  3. NAPPLE_AUDIN_SOURCE=tone:440 on a title that records (any voice-chat/mic title), then NAPPLE_AUDIN_SOURCE=mic from a signed app with the usage description: first permission prompt, level in the buffers.
  4. A real NSZ/XCZ of a base game through napple ingest, and a real RomFS through romfs_data_verified (expect Ok; a mismatch means the IVFC parsing assumptions, listed in the integrator file, are wrong).
  5. A personalized ticket from an installed title with the matching eticket_rsa_keypair (the OAEP parameters are the unverified part).

Files

crates/napple-core (ticket.rs, ivfc.rs, nca.rs, ncz.rs, domain/keys.rs, application/ingest.rs and package.rs, tests), audio/ (audin_source.c, audin_mic.c, audren.c, header, tests, Makefiles), input/ (input.c, hid_shm.c, bindings.c, header, test_styles.c, Makefiles), runtime/src/services/ (audio.c one line, irs.c, bcat.c, bcat_core.[ch]) and their tests, tools/linux-check/ (mic syntax-check stubs, README section), docs (runtime-services.md appended, environment-variables.md two rows), .claude/skills/percent-analysis/verified.json (nine entries).