From faafb722366ab4c333c7c755caa1bcab12ab6136 Mon Sep 17 00:00:00 2001 From: Matt Kulka Date: Fri, 23 Oct 2026 11:22:59 -0400 Subject: [PATCH] =?UTF-8?q?docs:=20SD/game=20validation=20findings=20?= =?UTF-8?q?=E2=80=94=20sidx=20layer=20vs=20the=20game's=20own=20#N=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit What Stern checks when it says GAME VALIDATION ERROR ... UPDATE SD CARD: two independent layers. (1) the sidx per-file HMAC-SHA1+MD5 manifest over the 1306 /games files, verified by spk/spike_menu; (2) the game's own boot check (SD card geometry via hdparm, node firmware, SHA1) that produces the permanent #1..#6 service-menu message — the #6 12:1:0 we got after the 2026-10-22 card reflash. Includes the game ELF's layout gotchas (two VA mappings, all-indirect PLT calls, unreferenced localized catalog) that stalled the static trace, and the open questions with the on-machine diagnostic path. Co-Authored-By: Claude Code --- docs/sd-validation.md | 156 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 docs/sd-validation.md diff --git a/docs/sd-validation.md b/docs/sd-validation.md new file mode 100644 index 0000000..c31d520 --- /dev/null +++ b/docs/sd-validation.md @@ -0,0 +1,156 @@ +# 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.