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

167 lines
6.9 KiB
Markdown

# 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:
```text
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:
```text
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:
```text
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:
```c
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`:
```text
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:
```text
python3 -m py_compile analysis/topper-emulator/topper_emulator.py
```
Frame self-test:
```text
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.