# Stern Spike 2 SD / game validation — what the machine checks Findings from the 2026-10-23 investigation of the **"GAME VALIDATION ERROR — #6 12:1:0 UPDATE SD CARD"** message that appeared on King Kong LE 0.97 after the 2026-10-22 card reflash. King Kong LE 0.97 binary analyzed (ELF32 ARM, stripped, BuildID `b7e3cc96…8a8a`, the `game` on `/games`). ## Short version There are **two independent validation layers**, and they fail with different messages: | Layer | Who runs it | What it checks | Failure looks like | |---|---|---|---| | **sidx manifest** | `/usr/local/bin/spk` (and the `spike_menu` installer) | every file the manifest lists, on `/games` | update/install refuses; `spk` prints `FILE NOT FOUND` / `FILE SIZE MISMATCH` / `wrong checksum` / `FAILED TO VERIFY` | | **game's own boot check** | the `game` binary itself, at boot | SD card geometry + game files (details below) | the permanent service-menu message `GAME VALIDATION ERROR #N … UPDATE SD CARD` — **this is the one that printed `#6 12:1:0`** | The game binary does **not** parse `.sidx` files and does **not** link `HMAC` (its OpenSSL imports are `SHA1_*`, `EVP_*` only), so the service-menu error is produced by the game's own check, not by the sidx layer. The two can fail independently. ## Layer 1 — the sidx manifest (the known one) Format fully documented; tooling lives in `pinball-asset-decryptor/pinball_decryptor/plugins/stern/sidx.py` (plus `sidx_append.py`, `sidx_deliver.py`, `tools/spike2_emu/sidx_invariants.py`). Restated here because it is what "UPDATE SD CARD" usually means: - Manifest: `/spk/index/.sidx` on the `/games` partition (same partition as `image.bin`). - Layout: `SIDXLU` + version 3 + title (32 bytes), an `SKK` block, `0x30` = record count, then `STRS` (NUL-separated relative paths), then one record per path in the same order, then a `FEND` trailer. - **`0x34` is the sum of all record sizes, not a checksum** (FINF cards; FI64 cards store `0xffffffff` there and the sum in an `SZ64` block). King Kong is **FINF** (60-byte payloads: size u32 @4, keyed **HMAC-SHA1** @21, plain **MD5** @41). - HMAC key (global across titles and cards, recovered by RE): `8e1f5543c2f54a11673a282a2f87c006`. - King Kong LE 0.97 manifest: **1306 records, all under `king_kong_le/`**: 17 node `.hex` firmwares, `game`, `conagent`, `image.bin`, 2 files under `data/`, 1126 `assets/lcd` + 158 `assets/nuk` assets. **Nothing on the rootfs (p2) is indexed** — rootfs mods (dropbear, PAD mode, log shipper) cannot trip this layer. - Verification semantics (from `spk`'s strings): it walks the manifest records and checks each file's presence, size and digests. A file added to `/games` that the manifest does not list is not itself the failure; a manifest file that is missing, resized or modified is. - After any size-neutral write to an indexed file, the record's HMAC + MD5 must be rewritten with the key (that's what `sidx.py`'s `record_field_writes` does). ## Layer 2 — the game's own validation (the `#N` messages) Everything below is from the `game` binary. ### The message set Six permanent (FG) message templates, stored in a 5-language localized catalog (each entry = 5 identical pointer slots + NUL; all languages English on this build): ``` #6 %d:%d:%d UPDATE SD CARD (3 runtime numbers) #5 %d:%d UPDATE SD CARD (2) #4 %d:%d UPDATE SD CARD (2) #3 UPDATE SD CARD (0) #2 UPDATE SD CARD (0) #1 UPDATE SD CARD (0) ``` The older `spike_menu` installer carries the same six with `#6 %d:%d` (two numbers) — the game build added the third field. The FG fatal-state enum includes `FG_GAME_VALIDATION_ERROR_PERM_MESSAGE` (enum value 5, between `FG_LOCKOUT_START_AND_TEST` and `FG_FATAL_NON_FATAL_ERROR_DISPLAY`); the service-menu strings around it are the SD-diagnostics block: ``` GameSD card: %s %dGB GameSD card required sector %lld: %lld MicroSD card: … ``` ### What the game checks (observed strings/imports, exact logic not yet decoded) - **SD card geometry**: it runs `/sbin/hdparm -g /dev/mmcblk0` (GameSD) and `/sbin/hdparm -g /dev/mmcblk2` (connectivity microSD), parses `sectors`; failure strings are `hdparm failed` and `unknown card size`. The service menu's "required sector" line suggests the game expects a specific sector count for the GameSD card. - **Connectivity card setup**: `/sbin/fdisk /dev/mmcblk2 < /tmp/fdskXXXXXX`, `/sbin/mkfs.ext3 -m0 /dev/mmcblk2p1`, `/bin/mount /dev/mmcblk2p1 /connectivity` — it (re)partitions and formats the microSD itself. - **Node firmware**: glob patterns `./%s-%s-%d_%d_[0-9]*.hex` / `./%s-%s-[0-9]*_[0-9]*_[0-9]*.hex` for the node `.hex` files, and the installer-side `checksum error node %d: is 0x%04x, should be 0x%04x` (spike_menu) show node firmware is checksummed against the card's files. - **Crypto available in-process**: `SHA1_Init/Update/Final`, `EVP_*` (the EVP set is the asset-decryption path), `popen`, `system`, `memcmp`, `fopen`. No HMAC — so no sidx recomputation in-process. **Not yet decoded**: which check maps to `#6` and what `12:1:0` means (check id : sub-code : result? record index? sector index?). The static trace stopped at the message-catalog indirection — see "Binary layout gotchas" below for why this binary resists naive cross-reference hunting. ### Diagnostic path for the live error The machine logs its trace to `/dump` (rw partition): ```sh ssh root@<machine> grep -iE 'valid|sector|sd card|mmcblk|checksum|node' /dump/debug_log.txt | tail -50 tail -50 /dump/log/connectivity/game.log hdparm -g /dev/mmcblk0 ``` The line(s) immediately before the permanent message should name the failing check and the values that became `12:1:0`. ## Binary layout gotchas (why the trace stalled) For anyone re-attacking the `game` ELF: - **Two LOAD segments, two VA mappings**: RX segment `fo+0x8000` (covers file offsets `< 0x7f9cb4`, i.e. `.text`/`.rodata`); RW segment `fo+0x80a000` (covers `.data`, `.got`, and the non-standard **`.flash`** (fo `0x80ee60`, ~470 KB) and **`.nonvol`** sections). Using `fo+0x8000` for data-segment addresses silently finds no references. - **All external calls are indirect**: a full scan of `.text` found **zero** direct `bl` to any PLT slot; every libc call goes `ldr rN, [pc,#lit]; blx rN`. PLT-slot hunting via `bl` targets comes up empty by design. - **The localized catalog is referenced only via data**: 5-slot entries in `.flash`, a 3940-entry pointer array (`.flash` fo `0x846c6c`, message id → catalog slot, descending), and `(ptr, count, 4)` descriptor triples in `.data` (the pointer-array entry is at fo `0x7fff10`: `0x856c6c, 0xf64, 4`). No code constant (literal pool, ADR/ADRL or MOVW/MOVT) references any of these — the head of the descriptor chain is reached some other way (still unknown). - Hopper's function boundaries in this binary were unreliable (disassembly misaligned vs. raw bytes in at least one traced function); verify any decompilation against raw words before trusting it. ## Open questions 1. Meaning of `#6 12:1:0` — needs the pre-error lines in `/dump/debug_log.txt` (machine was unreachable at `192.168.1.167` on 2026-10-23; retry when it's back on the network). 2. Whether the game's check includes a whole-card sector-count comparison (the "required sector" string suggests yes) — a different physical SD card with different geometry would trip it without touching a single file. 3. Where the validation result is cached (`.nonvol`? `/data`?) — a stale cached result could keep the message up after the underlying problem is fixed.