Files
pokemon_pinball_wiki/topper-serial-key-emulator.md
2026-07-06 20:06:06 -05:00

6.9 KiB

Magikarp Topper Serial Key and Emulator

Scope

Target:

  • /games/pokemon_pro/game

Generated artifacts:

  • analysis/topper-emulator/topper_emulator.py
  • analysis/topper-emulator/README.md

Static caveat: this page is based on static rootfs files and targeted Ghidra decompilation. It does not include live cabinet or logic-analyzer validation.

Transport Finding

The Magikarp topper path uses the generic SPIKE node-board bus.

Evidence:

  • /games/pokemon_pro/spike3/netbridge/netbridge.elf contains debug/source strings including uart_node.c, NODEBUS_Poll, NODEUART_Write, NODEUART_WritePoll, and NODEUART_WriteAddress.
  • /games/pokemon_pro/game function FUN_00a21a20 constructs addressed node frames and calls lower transport helper FUN_00a4c4c0.
  • /games/pokemon_pro/game functions FUN_0090aff0, FUN_00a23450, FUN_00a26690, and FUN_00a26700 implement discovery/address-management around node addresses, not USB or cloud registration.

Inference: the MicroPython emulator should target a UART-style nodebus interface first. The rootfs does load i2c-dev, but the topper-specific evidence reviewed here points to nodebus UART. Exact pins, voltage, baud, and half-duplex behavior still need hardware validation.

Frame Format Recovered From FUN_00a21a20

For addressed commands, higher-level helpers pass an application frame shaped like:

byte 0: node_address | 0x80
byte 1: application argument count
byte 2..N: command and arguments
last: response payload length

FUN_00a21a20 converts that to this wire frame:

byte 0: node_address | 0x80
byte 1: application argument count + 1
byte 2..N: command and arguments
next: subtractive checksum over bytes before the checksum
last: response payload length + 2

The response is expected as:

bytes 0..payload_len-1: payload
next: subtractive checksum over payload
last: status bits; status bits 0x0c cause the game to reject the response

Discovery is special: FUN_00a23450 sends a single zero byte and expects one raw response byte containing a discovered node address.

Serial Number Key Algorithm

The strongest recovered key path is:

  • FUN_006a9620 gets logical node device 10 with FUN_007cbc80(10).
  • It verifies the node is available with FUN_007cbfb0(10, 1).
  • It passes the configured node address byte from device entry offset 0x1a to FUN_00a28a00(address, out).
  • FUN_00a28a00 sends command 0xfc with expected response length 0x10.
  • FUN_006a9620 treats the 16 returned bytes as four little-endian 32-bit words and computes:
key = (word0 ^ word1 ^ word2 ^ word3) & 0x7fffffff;

This is the recovered algorithm for the Magikarp topper serial-number key material read from the control board.

Important namespace note:

  • The public adjustment name table places AD_MAGIKARP_TOPPER_SERIAL_NUMBER_KEY at index 0x1a3. Nearby entries are:
    • 0x19b: AD_MAGIKARP_MECH_DISABLED
    • 0x19c: AD_MAGIKARP_HEAD_HI_LIMIT
    • 0x19d: AD_MAGIKARP_HEAD_LOW_LIMIT
    • 0x19e: AD_MAGIKARP_TAIL_HI_LIMIT
    • 0x19f: AD_MAGIKARP_TAIL_LOW_LIMIT
    • 0x1a0: AD_MAGIKARP_WATER_CENTER
    • 0x1a1: AD_MAGIKARP_ATTRACT_BEHAVIOR
    • 0x1a2: AD_MAGIKARP_ATTRACT_FREQUENCY
    • 0x1a3: AD_MAGIKARP_TOPPER_SERIAL_NUMBER_KEY
  • The Magikarp runtime functions use constants such as 0xa6..0xaa and 0xad with FUN_007d3330 / FUN_007d39c0. Static table dumping showed those are not direct indexes into the public AD_* name-pointer table, so they should be treated as a separate adjustment-access namespace or translated runtime IDs.

Persistence and Calibration Path

FUN_006a9570 compares the recovered key from object offset +0x0c against FUN_007d3330(0xad). If the stored value is zero or mismatched, and the calibration/config payload is present and valid, it stores the recovered key with FUN_007d39c0(0xad, key) and marks the local state as synchronized.

Calibration validity is checked by FUN_006a8f50:

byte 0: 0x01
byte 1..3: ASCII "-15"
byte 4..8: five Magikarp calibration bytes
byte 9..10: little-endian ((sum(bytes 4..8) ^ 0x93d3) & 0xffff)

FUN_006a9920 constructs the same payload from five calibration adjustment reads. Static dump of the pointer used as the three-byte magic resolves to target address 0x0268ab40, whose first bytes are ASCII -15.

FUN_0090c290 and FUN_0090c310 copy a 64-byte node config/cache block from the generic node record. They treat bytes 0x3c..0x3f equal to 0x3254789a as a stored-valid marker.

Emulator

The generated emulator is analysis/topper-emulator/topper_emulator.py.

It implements:

  • Discovery request 0x00.
  • Address-management command 0xf0 as a no-op ACK path.
  • Config read command 0xf2 in 8-byte chunks.
  • Serial-key command 0xfc, returning the 16-byte key material.
  • Adjacent serial/cache helper command 0xfd.
  • Identity command 0xfe.
  • Status command 0xff.
  • Selected provisioning/runtime commands as ACK/no-op paths: 0x14, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x48, 0x4d, 0x72, 0xf4, 0xf8.

Host-side validation performed:

python3 -m py_compile analysis/topper-emulator/topper_emulator.py

Frame self-test:

request: 8a02fc7812
response length: 18
response payload matched serial material: True
response status: 0

The sample request is command 0xfc for node address 10, using the recovered wire-frame checksum and expected response length.

Open Hardware Variables

These values must be matched against a real board or a known-good capture:

  • NODE_ADDRESS: logical device 10 resolves to a configured node address at runtime via FUN_007cbc80(10), device-entry offset 0x1a. This value was not recovered as a fixed static literal.
  • UART ID, pins, baud, electrical level, and bus direction control.
  • IDENTITY_BOARD_PART_ID: command 0xfe returns an 11-byte identity payload. The game compares its part ID against the expected node-device table. This part ID was not confidently recovered from static analysis.
  • Exact serial/version fields expected by diagnostics.
  • Whether live netbridge behavior requires breaks, XON/XOFF, timing gaps, or address arbitration before the game accepts a MicroPython device on the physical bus.

Confidence

High confidence:

  • Command 0xfc supplies 16 bytes used for the key calculation.
  • The key calculation is XOR of four little-endian dwords masked with 0x7fffffff.
  • Logical topper node device ID is 10.
  • Calibration block predicate and checksum are recovered.
  • The implemented node response checksum/status shape matches FUN_00a21a20.

Moderate confidence:

  • The implemented no-op handling is enough for startup/provisioning if identity fields match.
  • Returning zero in the second status word for command 0xff avoids the generic unsigned/problem bit paths.

Low confidence until hardware-tested:

  • Physical-layer UART settings and direction handling.
  • Correct IDENTITY_BOARD_PART_ID for a live Magikarp topper board.