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>
This commit is contained in:
@@ -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/<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):
|
||||||
|
|
||||||
|
```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.
|
||||||
Reference in New Issue
Block a user