Files
mattlqxandClaude Code faafb72236 docs: SD/game validation findings — sidx layer vs the game's own #N check
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 <noreply@anthropic.com>
2026-10-23 11:22:59 -04:00

7.5 KiB

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/<title>.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):

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.