Files
pokemon_pinball_wiki/main-game-behavior.md
2026-07-06 20:06:06 -05:00

14 KiB

Main Game Behavior Notes

Scope

Target:

  • /games/pokemon_pro/game

Derived analysis artifacts:

  • analysis/static-triage/game.strings: strings -a -t x /games/pokemon_pro/game.
  • analysis/static-triage/game.objdump-p.txt: objdump -p /games/pokemon_pro/game.

Ghidra project:

  • Project stern, program /games/pokemon_pro/game.

Static caveat: this page is based on rootfs files, strings with offsets, and targeted Ghidra decompilation. It does not include live machine or hardware validation.

Main Game Binary

Confirmed type:

  • /games/pokemon_pro/game is a stripped AArch64 PIE executable, 52,829,400 bytes.
  • SHA-256: 1e5becb85840d0cc7a1d334b7cc43bdb83e51c7c7f8243bead8db679e9ffb22d.

Dynamic dependencies from objdump -p include:

  • libcrypto.so.3
  • libcurl.so.4
  • libusb-1.0.so.0
  • libgpiod.so.2
  • libEGL.so.1, libGLESv2.so.2, libdrm.so.2, libgbm.so.1
  • libgstapp-1.0.so.0, libgstvideo-1.0.so.0, libgstreamer-1.0.so.0
  • libavcodec.so.59, libavformat.so.59, libavfilter.so.8, libavutil.so.57

Topper Connection and Authentication

The Pokemon topper is represented as an optional Magikarp node-board/mechanism subsystem, not as a USB or network accessory in the evidence reviewed here.

Evidence from /games/pokemon_pro/game strings:

  • 0x258bf30: AD_MAGIKARP_TOPPER_SERIAL_NUMBER_KEY
  • 0x258f808: MAGIKARP TOPPER SERIAL NUMBER KEY
  • 0x2594a70: Adjustments related to Magikarp Topper.
  • 0x2594a98: Magikarp Topper
  • 0x25a03c8: Press Select for Magikarp Topper Calibration
  • 0x25a03f8: Magikarp Topper Node Board Not Signed
  • 0x25a0420: Magikarp Topper Mech Needs Calibration
  • 0x25a0448: Magikarp Topper Mech is Disabled
  • 0x25b8388: Topper Servos (Optional)
  • 0x25b83a8: Topper LEDs (Optional)
  • 0x25ce291: Cannot detect Magikarp Topper.
  • 0x25ce2b9: Check Interlock switch and cable connections
  • 0x25d2f20: Magikarp Node Board Device:
  • 0x25d2f3c: Calibrated %d %d %d %d %d
  • 0x25d2f74: Not Calibrated
  • 0x25f3100: 31MagikarpTopperNodeBoardSettings

Inference: the topper connects over the SPIKE node-board bus as one or more node devices, with servos and LED channels exposed to the game. Use is gated by node detection, a node-board signature check, and calibration state. The AD_MAGIKARP_TOPPER_SERIAL_NUMBER_KEY string maps to a serial-derived node-board key path; see topper-serial-key-emulator.md for the recovered 0xfc command and XOR key algorithm.

Topper Node Registration Communication

The Magikarp topper registration/authentication path appears to reuse the generic SPIKE node-board registration path. Static evidence supports this sequence:

  1. The game powers and scans the node bus during startup. In Ghidra, FUN_0090bb00 toggles node-bus power/control helpers, calls a node locator (FUN_0090aff0), and updates the expected-node table with FUN_007cbee0(..., 0x20, 2).

  2. FUN_0090aff0 clears a 32-byte discovery buffer, polls node addresses through FUN_00a23450, stores each discovered address, acknowledges it with FUN_00a26690(address), and marks it through FUN_00a26700(address, 1).

  3. Discovered node records live in a global 32-entry table beginning at DAT_033c0258. FUN_007cbd10(start_address) returns the next present board record. FUN_008e7090 formats the diagnostic inventory from this table as:

     %2d: ver %d.%d.%d, part %s%c, serial %d-%d-%d
    

    The formatted fields come from the board record: node address at offset 0x00, firmware version bytes at 0x24..0x26, part string at 0x38, part suffix at 0x37, and serial fields at 0x58, 0x5a, and 0x5c.

  4. For each discovered board whose status is 2, FUN_0090bb00 calls FUN_00a2b410(address, out_status). FUN_00a2b410 sends a node command frame with:

    byte 0: address | 0x80
    byte 1: 0x01
    byte 2: 0xff
    byte 3: 0x08
    

    The response is treated as two 32-bit status words. FUN_0090bb00 stores an inverted view of bit 0x80000 from the second word into DAT_03431830[address].

  5. If board byte 0x9e is not already set, FUN_0090bb00 calls FUN_0090a9c0(board_record). This helper performs additional board provisioning/configuration checks. It sends several node commands through the shared frame sender FUN_00a22aa0, including commands 0x14, 0x40, 0x44, 0x46, 0x48, 0x72, 0xf2, and 0xf8 depending on board state. It also reads 64 bytes of board data via FUN_00a2aad0, which issues repeated command 0xf2 reads in 8-byte chunks:

    byte 0: address | 0x80
    byte 1: 0x02
    byte 2: 0xf2
    byte 3: offset
    byte 4: 0x08
    
  6. FUN_0090a9c0 sets board-record flag bit 0x4000 when its probe/provisioning checks find the board in the unsigned/problem state. The node-board test UI separately enumerates these boards: FUN_00853f80 prints Unsigned Board #%d when the same flag bit is set.

  7. After the per-board checks, FUN_0090bb00 calls runtime/configuration helpers including FUN_00a25290, FUN_007c7b40, FUN_006b4b40, FUN_008cc100, FUN_008eb840, and FUN_007c0710. FUN_006b4b40 is the recurring node-board service loop; it sends command frames for runtime/config/device operations such as 0x40, 0x41, 0x42, 0x43, 0x45, 0x4d, and 0xf4.

For the topper specifically:

  • The topper is exposed to the game as MagikarpTopperNodeBoardSettings, TopperMech, Topper Servos (Optional), Topper LEDs (Optional), and named servo/LED devices such as SERVO HEAD, SERVO TAIL, and ETOPPER ... LED ....
  • The topper test/calibration UI uses the generic node-board state. It can display Cannot detect Magikarp Topper., Magikarp Topper Node Board Not Signed, Magikarp Topper Mech Needs Calibration, or Magikarp Topper Mech is Disabled.
  • The calibration details callback at FUN_006a9380 reports Magikarp Node Board Device: Calibrated %d %d %d %d %d using adjustment IDs 0xa6 through 0xaa, or Magikarp Node Board Device: Not Calibrated if the calibration predicate fails.

Interpretation: the game communicates with the topper control board as a SPIKE node-board at a discovered node address. Registration is not a cloud registration exchange and not a USB/network exchange; it is a local node-bus handshake. The game discovers the board, reads identity/status/version/part/serial fields, sends a status command (0xff), reads additional 64-byte board data with command 0xf2, and then marks the board as signed/usable or unsigned/problem via board-record flags. The recovered Magikarp-specific serial key path reads 16 bytes with command 0xfc and computes (word0 ^ word1 ^ word2 ^ word3) & 0x7fffffff; details and a MicroPython emulator are documented in topper-serial-key-emulator.md.

Stuck-Ball and Lost-Ball Handling

The game has explicit ball-search, lost-ball, and trough replacement behavior.

Evidence from /games/pokemon_pro/game strings:

  • 0x258b878: AD_LOST_BALL_RECOVERY
  • 0x258eaa0: Serve a new ball in the event of a lost/stuck ball.
  • 0x2595ea0: AUD_TOTAL_BALL_SEARCH_STARTS
  • 0x2595ec0: AUD_TOTAL_LOST_BALL_FEEDS
  • 0x2595ee0: AUD_TOTAL_LOST_BALL_GAME_STARTS
  • 0x25981c0: Total count times the game attempted to find a stuck ball via ball search.
  • 0x2598220: Total count times the game determined a stuck ball was lost, and was replaced with a ball from the trough.
  • 0x25982a8: Total count times the game determined a stuck ball was lost while starting a game.
  • 0x25aba50: SELECT: TROUGH TEST
  • 0x25abaa8: %d : Ball%P1//s/% Missing
  • 0x25ada30: BALL MISSING
  • 0x25ada90: LOCATING BALLS
  • 0x25beaf0: TROUGH JAM
  • 0x25beb18: SHOOTER LANE

Targeted Ghidra decompilation:

  • String 0x026cdee0, %s: MD_EVENT_STABLE - balls_on_playfield = %d, endball_count = %d\n, is referenced by FUN_005e4f20.
  • FUN_005e4f20 handles trough_event_handler stable events. It computes balls_on_playfield from the total balls in game minus the stable count of balls in devices, logs the number of balls in the trough and endball_count, then pushes balls_on_playfield into game state through virtual calls.
  • For MD_EVENT_KICK, the same function fires the trough eject coil using a strength adjustment.
  • For MD_EVENT_ENDBALL, it logs whether endball processing will be ignored or processed.

Behavior summary: when the game cannot find a ball, it starts ball search and records the event. If it decides the ball is lost/stuck and lost-ball recovery is enabled, it serves a replacement ball from the trough and records a lost-ball feed. If all balls dispensed onto the playfield are physically stuck and no ball returns to a device/trough, static evidence says the game will continue treating those balls as on the playfield, then eventually show missing/locating-ball or device-malfunction paths. It cannot create more replacement balls once the trough is depleted; operator intervention is required to recover the physical balls.

Unexpected Extra Balls

Confirmed behavior:

  • The multiball-device subsystem validates impossible device states such as Too many balls locked, Too many balls in device, Bad lock count after lost ball, and Multiball device system is not stable at offsets 0x259eba0, 0x259ebe8, 0x259ebc8, and 0x259ed38.
  • The trough event handler calculates and propagates a live balls_on_playfield count on stable events.
  • Game diagnostic text exposes FG_VALID_PLAYFIELD_ACHIEVED_THIS_BALL at 0x259fa88 and 0x25a4120.

Inference: the game logic does track physical ball accounting and detects inconsistent ball counts in devices. I did not find a distinct string or decompiled branch named "unexpected extra ball on playfield." A loose extra ball on the playfield would be reflected in balls_on_playfield once the trough/device counts stabilize, but the static evidence found here does not prove a special penalty, tilt, or operator alert solely for an unexpected extra playfield ball.

Hidden Buttons and Debug Controls

Accessible without opening the cabinet:

  • 0x258d460: Home Team can be used by holding the left flipper after setup.
  • 0x258e138: restart behavior can be allowed by holding the start button.
  • 0x258e190: end current game by holding Start plus Left Flipper.
  • 0x2597e98: audit text counts games ended by holding left flipper and start.
  • 0x25a33b0: HOLD BOTH FLIPPER BUTTONS FOR MORE GAME MODES
  • 0x25a3868: HOLD BOTH FLIPPER BUTTONS FOR MENU
  • 0x25a5bb8: PRESS BOTH FLIPPER BUTTONS TO SELECT PLAYER
  • 0x25a5c38: HOLD BOTH FLIPPER BUTTONS TO MOVE PLAYER BACK TO ROSTER
  • 0x25a5e50: HOLD BOTH FLIPPER BUTTONS TO REMOVE GUEST FROM HOME TEAM™
  • 0x25a6140: Scan QR to log in or hold left flipper to activate Home Team™

Debug/test controls found in the binary:

  • 0x258c768: AD_DEBUG_MENU_SHOW
  • 0x2590af0: DEBUG MENU SHOW
  • 0x2590bd8: Test Only: Award Magikarp Letters
  • 0x2590c20: Test Only: Start Pikachu vs. Magikarp
  • 0x2590c90: GODOT ASSET DEMO
  • 0x2590ce0: POKEMON PREVIEW
  • 0x2590d40: GODOT PROGRESSION TEST
  • 0x259fa40: FG_TEST_MENU_SHOW_DEBUG_ADJUSTMENTS
  • 0x259fa68: FG_TEST_MENU_SHOW_DEBUG_AUDITS

Conclusion: I found player-accessible hold/press combinations for login, player selection, game-mode UI, ball launch, restart, and end-game. I did not find evidence of a cabinet-closed secret sequence that directly opens operator diagnostics or debug commands. Debug/test items exist, but the names indicate they are gated by service/test-menu feature flags or adjustments rather than exposed as a hidden public button sequence.

QR Scanner and QR Input Handling

The QR scanner appears as a SPIKE node-board peripheral, not as host-side camera decode logic.

Evidence:

  • 0x25b8418: QR Scanner
  • 0x25b8a40: QR SCANNER BACKLIGHT
  • 0x25b8a58: QR SCANNER STATUS READY
  • 0x25b8a70: QR SCANNER STATUS STRING
  • 0x25b8a90: QR SCANNER STATUS ENCODED DATA
  • 0x25b8ab0: QR SCANNER STATUS MISSED STRING
  • 0x25b8ad0: QR SCANNER STATUS OVERRUN
  • 0x25b8af0: QR SCANNER STATUS UNSUPPORTED_AP
  • 0x25b8b18: QR SCANNER STATUS FIFO OVERRUN
  • 0x25b8c00: QR SCANNER STATUS NO READER
  • 0x25dd3e8: Check QR scanner RJ45 nodebus cable
  • 0x25dd410: Check QR scanner ribbon cable
  • 0x25e26e8: /connectivity/qr_code_storage/
  • 0x25e2708: wifi_qr_code

Targeted Ghidra decompilation:

  • FUN_00913720 builds QR scanner status/diagnostic display text. It explicitly chooses Check QR scanner RJ45 nodebus cable or Check QR scanner ribbon cable depending on scanner/node state.
  • No host-side QR decode library strings such as zbar, quirc, qrencode, or libcamera were found in the game strings. libusb-1.0.so.0 is linked, but the QR scanner strings point to nodebus status and cabling.

Insider Connected scan flow evidence:

  • 0x25dd568: ScannedUserAccountUUID
  • 0x25dedd0: QET_SCANNED_USER_ACCOUNT
  • 0x25de1a0: Authenticating player with back-end server.
  • 0x25de1d0: Failed to begin construction of PLAYER_AUTHENTICATE message.
  • 0x25ecf18: PLAYER_AUTHENTICATE
  • 0x25d3b10: user_uuid
  • 0x25df370: { "machine_uuid": "%s", "user_uuids": [
  • 0x25dfa20: /api/v2/game/player_properties
  • 0x25e0cc8: Server response to retrieve_player_properties has no body; returned error status %s.
  • 0x25e0d20: Server response to retrieve_player_properties has invalid JSON; returned error status %s.
  • 0x25e0d80: Server response to retrieve_player_properties has invalid user %s.

Targeted Ghidra decompilation:

  • FUN_00914520 maps netgame action 0x10 to ScannedUserAccountUUID.
  • FUN_00919250 maps queue entry type 7 to QET_SCANNED_USER_ACCOUNT and 0x1f to QET_RETRIEVE_PLAYER_PROPERTIES.
  • FUN_0092d270 handles the retrieve-player-properties response. It parses JSON, extracts user_uuid, converts it with uuid_copy / uuid_unparse, checks whether the UUID maps to a known player object, and logs invalid JSON, no body, and invalid user failures.

Security assessment: the game treats scanned login data as a UUID/account identifier and sends it through the connectivity agent to Stern back-end authentication and player-property APIs. The recovered response-handling path validates JSON shape and user identity against the local player/account model before applying properties. I did not find a static evidence path where arbitrary QR text is directly executed as a command, file path, SQL statement, or shell input. The scanner also reports encoded-data, overrun, unsupported-AP, FIFO-overrun, and no-reader status states. Remaining uncertainty: this pass did not recover the exact QR payload parser or maximum input length checks, so QR input should be considered partially assessed rather than proven memory-safe.