Skip to content

2026-11-01 sweep-small (batch 2026-11-01-r1, mac)

Seven small %Analysis items (audio, input, content, bcat). Layout questions were settled against public reference sources read for this note (libnx hid.h, the yuzu-derived audio-core performance structures, LibHac's BCAT storage code, nsz conventions) and against the user's own Testing Material (real prod.keys, SMO NSP). Status words: verified = ran here with the real file, tested = unit test only.

audio: performance metrics (layout from reference code, tested; no title uses it)

audren.c wrote a performance frame from memory; the layouts were wrong. Now (skyline-emu/audio-core performance_frame_header.h, performance_entry.h, performance_detail.h): - format 1 (REV1..REV4): header 0x18 {magic 'PERF', entry_count, detail_count, next_offset, total_processing_time, frame_index}, entry 0x10, detail 0x10. - format 2 (REV5 and later, REV11+ included; PerformanceMetricsDataFormatVersion2 support tag = revision 5): header 0x30 (+voices_dropped, u64 start_time, frame_index at 0x20, render_time_exceeded at 0x24), entry 0x18, detail 0x18. - Both formats carry the PERF magic (the old format 1 header had none); the byte after the last frame is zeroed (null header ends the chain); at most 100 detail entries per frame; data-source detail types are the PerformanceSysDetailType numbers (PcmInt16 15, PcmFloat 16, Adpcm 17), ramp / mix use the CommandId numbers (8 / 10; least certain). - test_audren.c: layout checks for REV1, 3, 4, 5, 7, 9, 11, 12 (entries, details, history, small buffers, chain end). The first update that involves performance metrics logs performance buffer: N bytes written into the M byte buffer ... (verbose runtime log). Headless 60 s boots of SMO (REV3), MK8D (REV1), ACNH (REV15) and LAS (REV7) ran clean (~12,000 RequestUpdate calls each, no audren errors), but none of them supplies a performance buffer, so the log line is only seen in the unit tests.

audio: audin microphone (verified capture, default-on)

With NAPPLE_AUDIN_SOURCE unset, napple_audin_install_default_source now chooses mic when the process may use it: AVCaptureDevice authorisation read through the ObjC runtime (no AVFoundation link), granted -> capture; undetermined -> only from an app bundle carrying NSMicrophoneUsageDescription (the OS prompt; a command-line run would be killed by TCC); denied / restricted / failed start -> silence, return 0. NAPPLE_AUDIN_SOURCE=silence turns it off; NAPPLE_AUDIN_MIC_PERMISSION=granted|undetermined|denied overrides the detection for tests. Test: denied/undetermined/hook-refusal all leave audin returning exact zeros. A real capture ran on this Mac (Terminal holds mic access): 4800 frames pulled from the ring after 0.6 s with non-zero RMS.

input: GameCube / Palma / NES / SNES / N64 / Mega Drive styles

  • Bug fix: the GC trigger lifo was at 0x4C20; libnx HidNpadInternalState puts gc_trigger_lifo at 0x4228 (after the 0x4208 mutex block), with lark_type_l_and_main 0x43E0, lark_type_r, lucia_type, lager_type after it (a third-party libnx-derived offset table agrees). Nothing else in the tree used the old number.
  • napple_hid_set_retro_types writes NpadLuciaType / NpadLagerType (SNES / Mega Drive pads report type U); napple_hid_device_type_for gives the retro / GC / Palma styles their HidDeviceTypeBits (FullKey 0, Palma 6, NES left 9, handheld NES left 13, Lucia 15, Lagon 16, Lager 17); the standard styles keep device type 0 as before.
  • HidNpadButton bits 28..34 as constants (Palma, Verification, HandheldLeftB, LagonCLeft/Up/Right/Down); napple_style_buttons: the Palma pad folds A into bit 28, the N64 pad turns right-stick directions into the C buttons, the handheld NES pad mirrors B into HandheldLeftB.
  • Tests: input/tests/test_styles.c (49 checks).

content: NSZ / XCZ (verified on a real dump)

crates/napple-core/tests/nsz_real.rs converts the SMO NSP (NAPPLE_TESTING_MATERIAL) to an NSZ with a minimal NCZ writer (decrypt the AES-CTR sections via the new Nca::ctr_sections, NCZSECTN table, solid zstd stream or NAPPLE_NSZ_BLOCK=1 1 MiB NCZBLOCK, NAPPLE_NSZ_FULL=1 includes the 5.5 GB RomFS NCA) and reads every NCA back through NczSource: SHA-256 identical to the NSP's. napple build --data-only on the NSZ (partial and full) writes modules with the same sha256 / build id as the existing NSP build. Finding: --update-romfs in-place refuses an NCZ ("compressed container layer"); materialize works (5,505,414,328-byte romfs, same size as the NSP's).

content: personalized tickets (key layout verified, wiring was already complete)

crates/napple-core/tests/ticket_real.rs: the real eticket_rsa_keypair of the Testing Material prod.keys is 0x210 bytes (D 0x100, N 0x100, E 4; ticket.rs accepted >= 0x204 already), E = 65537, and D inverts E modulo N. A personalized ticket built by encrypting a title key to that public key (RSA-OAEP SHA-256, test-side encoder) resolves back through encrypted_title_key. The container wiring (Container::load_with_keys, Containers::with_keys in the pipeline, legacy.rs, ingest.rs) existed already; the doc comment on DEVICE_KEY said 0x230 and is corrected. No real personalized ticket is in the material (SMO and the others ship common tickets).

content: NCA2 / NCA0

crates/napple-core/tests/nca_versions_real.rs scans every NCA of the Testing Material NSPs: 39 NCAs in 11 NSPs, all NCA3. No retail NCA2 / NCA0 exists here, so these stay synthetic-fixture-tested (status unchanged: partial).

bcat: storage layout

bcat_core.[ch]: bcat_save_provider reads the delivery cache as the BCAT service lays it out in the title's save data (LibHac / public ports): directories.meta (u32 header 1, entries {name[0x20], digest[0x10], reserved[0x10]}), directories/<dir>/files.meta (header 1, entries {name[0x20], s64 id, s64 size, digest[0x10], reserved[0x40]}), directories/<dir>/files/<file>. NAPPLE_BCAT_DIR auto-detects it (bcat_auto_provider: directories.meta present -> storage layout, else the plain folder). Fix: DeliveryCacheProgressStatus::Done is 9, not 6 (yuzu bcat_types.h). Tests: runtime/tests/test_svc_bcat.c (137 checks).

Files

audio/src/audren.c, audio/src/internal.h, audio/src/audin_mic.c, audio/src/audin_source.c, audio/include/napple_audio.h, audio/tests/test_audren.c, audio/tests/test_audin_source.c, input/include/napple_input.h, input/src/hid_shm.c, input/src/input.c, input/tests/test_styles.c, crates/napple-core/src/infrastructure/{nca,ticket}.rs, crates/napple-core/tests/{nsz_real,ticket_real,nca_versions_real}.rs, runtime/src/services/bcat_core.[ch], runtime/tests/test_svc_bcat.c.