feat: add passive GAMELOG event logger for Spike 2 King Kong LE 0.97

Snapshot of the stern-event-agent work (as of 2026-10-12):

- gamelog.c: passive PAD mode logging bus events (game/ball/bonus/tilt),
  all named shots and all-players' scores to /dump/mode.log
- pad_mode_runtime.c: the SDK runtime + a GAMELOG section (passive
  stock-mode and award watching, gl_award_seq()/gl_last_award_id()),
  a per-edge switch logger to /dump/switch.log (switch_names.inc), and
  a passive Insider Connected gate; docs/runtime-patch.diff shows the
  exact changes vs the upstream runtime
- event_probe.c / event_read.py: event-bus measurement tooling for
  identifying bus event ids on another game
- ports/king_kong_le-0.97.port: 121 verified sites, 55 named shots, 6
  bus events (emulator-proven by the SDK repo authors)

Deployed and verified on the machine: events, shots, stock modes and
awards. The /dump/switch.log feature is implemented but NOT yet
deployed or verified, and the extra-ball award id (default 13) is
unconfirmed. See README.md for build/install/log usage and unfinished
work.

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
2026-10-12 13:57:12 -04:00
co-authored by Claude Code
parent 9b41e58e19
commit abc881f913
12 changed files with 12455 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
# build artifacts
*.so
*.o
+208
View File
@@ -1,2 +1,210 @@
# stern-event-agent
A passive gameplay-event logger for Stern Spike 2 pinball, developed against
**King Kong LE 0.97**. It records the game's own events with millisecond
timestamps — the game is not modified, nothing scores, nothing blocks.
The logger is a PAD mode: a small shared object (`mode.so`) that the game
process loads via `LD_PRELOAD`. It hooks function sites inside the game, and
every hook is verified against the first two machine instructions of that
function before it is touched — a wrong or missing port hooks nothing, logs
why, and the game runs stock.
## Status (as of 2026-10-12)
| Part | State |
|---|---|
| Bus events (`EV`): game/ball/bonus start/end, tilt, all players' scores at bonus end | **deployed on the machine, verified in play** |
| Shots (`SHOT`): every named shot dispatched while a ball is up | **deployed on the machine, verified in play** |
| Stock modes (`gamelog: MODE START/END`): the game's own 22 modes with score delta | **deployed on the machine, verified in play** (`mode_score` is approximate) |
| Award screens (`AWARD id=…`): every award the game shows, extra-ball tracking | **deployed on the machine, verified in play** — except the extra-ball award id is not yet confirmed (below) |
| Switch/button log (`/dump/switch.log`, `SW` lines with generated names) | **implemented in this tree, NOT yet deployed or verified** — the deployed `mode.so` (built 2026-10-12 10:46) predates it |
The machine currently runs the v2 build (events, shots, modes, awards). The
switch logger (v3) is in this tree and needs a rebuild + redeploy before it can
be relied on.
## Files
| File | What it is |
|---|---|
| `gamelog.c` | the logger: a passive PAD mode. It never `pm_begin()s`, never blocks, allocates nothing — it only watches and writes `[GAMELOG]` lines |
| `pad_mode_runtime.c` | the PAD Modes SDK runtime, **patched**: (a) a GAMELOG section — passive stock-mode and award watching, exporting `gl_award_seq()` / `gl_last_award_id()` to modes; (b) the per-edge switch logger to `/dump/switch.log`; (c) the Insider Connected gate made passive. Our exact changes vs upstream: `docs/runtime-patch.diff` |
| `switch_names.inc` | King Kong LE 0.97 switch id → name table (103 entries), `#include`d by the runtime's switch logger. Generated from the game's own data (method: `swnames.py` in the decryptor repo); fix names here if any are wrong |
| `pad_mode.h`, `pad_stock.h` | unmodified SDK headers (needed by the build) |
| `build_mode.sh` | unmodified SDK build script: compiles the runtime + your mode into one `-nostdlib` shared object and refuses banned libc calls |
| `event_probe.c`, `event_read.py` | the event-bus measurement instrument and its log reader — how bus event ids are identified for another game (MODE_SDK.md, "Measuring events on another game") |
| `ports/king_kong_le-0.97.port` | the King Kong LE 0.97 port: 121 verified sites, 55 named shots, 6 bus events. Installed on the machine as `/usr/local/padmode/game.port` |
| `docs/runtime-patch.diff` | this project's changes to the upstream runtime (base: `pinball-asset-decryptor` commit `7faf8f08`, the last upstream change to that file) |
## What gets logged
### `/dump/mode.log` (deployed)
Append-only, millisecond stamps relative to the runtime loading, survives
reboots:
```
[pad] port ...: king_kong_le 0.97, 121 sites, 55 shots <- runtime armed (all hook words verified)
[GAMELOG] up: game king_kong_le 0.97, 55 named shots; event ids 77/37/52/40/200/198; extra_ball_award=13
[GAMELOG] +2408751ms EV game_start - GAME START
[GAMELOG] +2408751ms EV ball_start player=1 ball=1 score=0
[GAMELOG] +2424007ms SHOT X1 player=1 ball=1 score=7,349,830 <- all 55 named shots
[GAMELOG] +2644003ms EV ball_end player=1 ball=1 score=91,153,090
[GAMELOG] +2644003ms EV bonus_end
[GAMELOG] +2644003ms SCORES P1=95,421,190 P2=0 P3=0 P4=0
[GAMELOG] +...ms EV tilt_warning player=1
[GAMELOG] +...ms EV tilt player=1 ball=2
[pad] gamelog: MODE START 5 (Island Scene Escape the Swamp) player=1 score=...
[pad] gamelog: MODE END 5 (Island Scene Escape the Swamp) player=1 score=... mode_score=... dur=...ms
[GAMELOG] +...ms AWARD EXTRA BALL (id=13) player=1 <- next ball logs as "ball N (EXTRA BALL)"
[GAMELOG] +...ms AWARD id=<n> player=1 score=... <- every award screen
```
Bus event ids (King Kong 0.97): `game_start`=0x4d, `ball_start`=0x25,
`ball_end`=0x34, `bonus_end`=0x28, `tilt_warning`=0xc8, `tilt`=0xc6. This build
has **no** bus id for game over (the last `ball_end`/`bonus_end` is the close)
or for bonus start.
**Extra balls**: the game re-fires `ball_start` for an extra ball. The runtime's
award-screen hook counts the awards, and one whose id matches
`extra_ball_award` (default **13**, overridable with a `value extra_ball_award`
line in the port file) makes the replayed ball log as `ball N (EXTRA BALL)`
instead of advancing the count.
**Mode detail**: the runtime polls each of the game's 22 mode objects' ACTIVE
state per tick and logs START/END with the player's score at each edge —
`mode_score=` is the score delta across the mode (approximate when other
scoring runs simultaneously).
**Insider Connected**: stock. The upstream runtime vetoes score/session reports
whenever modes are installed; this build makes that gate **passive** — reports
flow exactly as stock (and are logged once), and the logger adds no scores of
its own. Known cosmetic issue: the boot banner still prints the upstream "score
reports stay on this machine" line — the later `passive gate` line is the truth.
### `/dump/switch.log` (implemented, not yet deployed)
The runtime's switch-edge handler writes **every edge of every switch** to
`/dump/switch.log` — buttons included, attract included:
```
<ms> SW <id> <NAME> level=<0|1> hit=<0|1> game=<0|1>
```
`level` is the switch's raw state after the edge, `hit` is the game's own
"counts as a hit" verdict, `game` whether a player is up. Names come from
`switch_names.inc`; an id not in the table logs `?`.
## Build
Cross-compile on macOS in a Debian container (armhf, hard-float, `-nostdlib`):
```bash
docker run --rm --platform linux/arm64 -v "$PWD:/work" -w /work debian:12 \
sh -c 'apt-get update -qq && apt-get install -y -qq gcc-arm-linux-gnueabihf >/dev/null \
&& bash build_mode.sh -o mode.so gamelog.c'
```
`build_mode.sh` compiles `pad_mode_runtime.c` + `gamelog.c` into one object
(~228 KB) and fails the build if any `pm_` symbol does not exist or any banned
libc call is made (allocation, threads, sleeping, stdio, processes). The few
libc calls that are used (`clock_gettime`, `open`, `write`, `close`, …) resolve
from the game's own libc when the object loads.
The event probe builds without the runtime:
`build_mode.sh -p -o event_probe.so event_probe.c`
## Install / deploy (the machine)
The machine (hostname `stern-spike2`) has root SSH (key-only, port 22). The
logger lives on the rootfs, which is normally read-only:
| Path on the machine | From this repo |
|---|---|
| `/usr/local/padmode/mode.so` | the built `mode.so` |
| `/usr/local/padmode/game.port` | `ports/king_kong_le-0.97.port` |
| `/etc/init.d/game_monitor` | boot hook: preloads `mode.so` into the game **only if it exists** (stock script kept at `game_monitor.stock`) |
Deploy a new build:
```bash
scp mode.so root@stern-spike2:/tmp/
ssh root@stern-spike2 'mount -o remount,rw /; cp /tmp/mode.so /usr/local/padmode/mode.so;
chmod 755 /usr/local/padmode/mode.so; sync; mount -o remount,ro / || true; reboot'
```
Live view: `ssh root@stern-spike2 'tail -f /dump/mode.log'`
Housekeeping: `/dump` is a 532 MiB rw partition shared with crash dumps and
connectivity logs; `mode.log` grows a few MB per long session (a few hundred
`SHOT` lines per ball is normal). Pull it over SSH and rotate as needed;
`: > /dump/mode.log` truncates it safely while running (same for `switch.log`
once deployed).
Rollback: `rm -rf /usr/local/padmode`, restore `/etc/init.d/game_monitor.stock`,
reboot — the machine is stock again (SSH remains).
## Measuring events for another game
`event_probe.c` hooks the game's event bus (`subscribe` / `dispatch`) and its
tick, and logs every registration and per-window dispatch counts to
`/dump/event.log`. Procedure (the full version is MODE_SDK.md, "Measuring
events on another game"):
1. `build_mode.sh -p -o event_probe.so event_probe.c`
2. Put the target's `subscribe` / `dispatch` / `tick` sites (address + first two
instruction words) in `/dump/event.sites` — the SDK's `port_words.py`
(in the decryptor repo) fills and checks those words from the game's ELF.
3. Preload `event_probe.so` with `PAD_MODE_SO` (beside the stock runtime — its
trampoline keeps the moved words where the runtime still verifies them).
4. Play; at interesting moments `echo <label> > /dump/event.mark` to close a
window (and open the next); `echo <id> > /dump/event.force` starts/stops a
stock mode the way a rule does.
5. `event_read.py /dump/event.log` — which ids fired in which window, with
handlers, priorities and callers.
The ids you name that way become the `event` lines of a port (the six in
`king_kong_le-0.97.port` were found this way, in the SDK's emulator).
## Unfinished work / known issues
- **The switch logger is not on the machine yet.** The deployed `mode.so` is the
2026-10-12 10:46 build (events/shots/modes/awards). The `/dump/switch.log`
feature (runtime patch + `switch_names.inc`) was written later the same day:
it needs a rebuild, a redeploy, and verification that the log appears and the
names are right.
- **The extra-ball award id is unconfirmed.** Default 13 is a static guess from
the game's object-name tables; every award id is logged, so earn an extra
ball, read the `AWARD` line, and pin it down (or override it in the port).
- **The master SD image does not contain the logger** — it was installed over
SSH onto the card in the machine. Making it survive a re-flash needs a
debugfs inject of `mode.so` + `game.port` + the hooked `game_monitor` into
the master image.
- **No network streaming yet.** Logs are pulled over SSH; the plan is for the
mode to ship them itself (`socket()` is not on the build's banned list).
- **Cosmetic**: the boot banner still prints the upstream "score reports stay
on this machine" line (see Insider Connected above).
- `mode_score` is an approximation (see above).
## Provenance
Built on the PAD Modes SDK
(`pinball-asset-decryptor/tools/spike2_emu/modes/sdk/` — the
`mattlqx/pinball-asset-decryptor` Gitea repo, itself a fork of
`davidvanderburgh/pinball-asset-decryptor`; the SDK docs there — `MODE_SDK.md`,
`MODE_PARAMETERS.md`, `MODE_LIMITS.md` — apply to everything in this repo).
Everything in this repo is unmodified SDK content except:
- `gamelog.c` (new)
- `switch_names.inc` (new)
- `pad_mode_runtime.c` (upstream runtime + the patch in `docs/runtime-patch.diff`)
- `README.md`
`ports/king_kong_le-0.97.port`, `event_probe.c`, `event_read.py`,
`build_mode.sh` and the headers are the SDK's, unmodified (the port is
emulator-proven by the SDK repo authors).
The machine's SSH setup (dropbear + sftp-server on the Spike 2) is documented
separately in the local `pinball-assets/README.md` and is **not** part of this
project — this logger uses that SSH, it does not provide it.
Executable
+52
View File
@@ -0,0 +1,52 @@
#!/bin/bash
# build_mode.sh - compile one or more modes into the mode.so a machine preloads (item 134).
#
# build_mode.sh -o mode.so my_mode.c [another_mode.c ...]
# build_mode.sh -p -o probe.so call_probe.c a porting instrument: no runtime
#
# Every mode on a card goes into ONE object with the runtime: it installs each hook once
# and keeps our modes to one at a time. Needs only arm-linux-gnueabihf-gcc (the app's
# PAD-Runtime distro has it). Nothing is linked against a libc: the handful of libc calls
# the runtime makes resolve from the game's own libc when the object loads.
set -euo pipefail
SDK=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
OUT=
RUNTIME=$SDK/pad_mode_runtime.c
while getopts "o:p" opt; do
case $opt in
o) OUT=$OPTARG ;;
p) RUNTIME= ;;
*) echo "usage: build_mode.sh -o mode.so mode.c [mode.c ...]" >&2; exit 2 ;;
esac
done
shift $((OPTIND - 1))
[ -n "$OUT" ] && [ $# -ge 1 ] || { echo "usage: build_mode.sh -o mode.so mode.c [mode.c ...]" >&2; exit 2; }
CC=${CC:-arm-linux-gnueabihf-gcc}
command -v "$CC" >/dev/null || { echo "build_mode.sh: $CC not found" >&2; exit 1; }
# -funwind-tables (PAD-381): the game ENDS a process by throwing a C++ exception through its stack
# (do_stack_unwind_exception_t), at a tilt or the end of a ball. A frame of ours on that stack - the
# runtime's magnet grab runs as a game process - must carry unwind tables, or the unwind stops there
# and the game aborts ("terminate called ..."). They cost a few bytes a function and change no code.
"$CC" -std=gnu17 -marm -mfloat-abi=hard -fno-stack-protector -fPIC -fvisibility=hidden -shared -O2 -nostdlib -funwind-tables \
-Wall -Wextra -Wno-unused-parameter -Werror=implicit-function-declaration \
-I"$SDK" -Wl,-soname,mode.so \
-o "$OUT" ${RUNTIME:+"$RUNTIME"} "$@" -lgcc
# Undefined symbols are resolved from the game's libc when the object loads - so a
# misspelt pm_ call would only fail on the machine. Refuse that here, and refuse the libc
# calls that break the rules (MODE_SDK.md, rule 2): allocation, threads, sleeping, stdio,
# processes. Plain string and memory calls (strlen, memcpy...) are fine - gcc emits some
# on its own - and resolve from the game's libc.
NM=${CC%gcc}nm
if command -v "$NM" >/dev/null; then
undefined=$("$NM" -D --undefined-only "$OUT" | awk '{print $NF}' | sed 's/@.*//')
missing=$(echo "$undefined" | grep -E '^pm_' || true)
banned=$(echo "$undefined" | grep -E '^(malloc|calloc|realloc|free|posix_memalign|memalign|valloc|_Zn[wa]|_Zd[la]|pthread_|sleep|usleep|nanosleep|select|poll|epoll_|f?printf|puts|fopen|fread|fwrite|fclose|system|popen|fork|exec|wait|signal|sigaction)' || true)
if [ -n "$missing" ] || [ -n "$banned" ]; then
rm -f "$OUT"
[ -n "$missing" ] && echo "build_mode.sh: these pad_mode calls do not exist: $(echo $missing)" >&2
[ -n "$banned" ] && echo "build_mode.sh: a mode may not call these inside the game (MODE_SDK.md rule 2): $(echo $banned)" >&2
exit 1
fi
echo "libc calls it will resolve from the game: $(echo $undefined)"
fi
echo "built $OUT ($(stat -c %s "$OUT") bytes) from: $*"
+160
View File
@@ -0,0 +1,160 @@
--- /Users/matt/src/pinball-asset-decryptor/tools/spike2_emu/modes/sdk/pad_mode_runtime.c 2026-10-11 18:38:42
+++ /Users/matt/src/pinball-assets/.ssh-build/sdk-patched/pad_mode_runtime.c 2026-10-12 12:04:22
@@ -7914,6 +7914,90 @@
static void stock_tick(void); /* item 160 */
static void magnet_tick(void); /* PAD-381 */
static void scoop_tick(void); /* PAD-381 */
+
+/* ---- GAMELOG: passive stock-mode and award watching*/
+#ifndef O_WRONLY
+#define O_WRONLY 1
+#define O_CREAT 0100
+#define O_APPEND 02000
+#endif for the gameplay logger.
+ * Added on top of the stock runtime (2026-10-12): logs the game's own modes
+ * (Island Scenes and the rest of the port's block_* set) starting and ending -
+ * with the player's score at each edge, so the mode's score total is the
+ * difference - and every award screen the game shows (extra ball, special,
+ * replay...), exposed to modes through gl_award_seq()/gl_last_award_id(). */
+static unsigned long gl_start_ms[BLOCK_IDS];
+static unsigned long long gl_start_score[BLOCK_IDS];
+static unsigned gl_start_player[BLOCK_IDS];
+static unsigned char gl_running[BLOCK_IDS];
+static unsigned gl_award_v;
+static unsigned long gl_award_n;
+
+unsigned long gl_award_seq(void) { return gl_award_n; }
+unsigned gl_last_award_id(void) { return gl_award_v; }
+
+static void gamelog_mode_log(const char *kind, unsigned id, unsigned player,
+ unsigned long long score, unsigned long dur)
+{
+ char key[24];
+ const char *nm;
+ pm_snprintf(key, sizeof key, "block_name_%u", id);
+ nm = pm_port_text(key);
+ if (dur)
+ say("gamelog: MODE %s %u (%s) player=%u score=%llu mode_score=%llu dur=%lums",
+ kind, id, nm ? nm : "?", player, score,
+ score > gl_start_score[id] ? score - gl_start_score[id] : 0, dur);
+ else
+ say("gamelog: MODE %s %u (%s) player=%u score=%llu",
+ kind, id, nm ? nm : "?", player, score);
+}
+
+static void gamelog_stock_tick(void)
+{
+ unsigned id;
+ if (!pm_in_game()) {
+ for (id = 0; id < BLOCK_IDS; id++)
+ if (gl_running[id]) {
+ gamelog_mode_log("END", id, gl_start_player[id],
+ pm_score(gl_start_player[id]), pm_ms() - gl_start_ms[id]);
+ gl_running[id] = 0;
+ }
+ return;
+ }
+ for (id = 0; id < BLOCK_IDS; id++) {
+ const unsigned *o = (const unsigned *)(unsigned long)block_obj[id];
+ int active;
+ if (!o || !bm_has(block_named, id) || !wizm_vfn(o, "stock_slot_active")) continue;
+ active = ((int (*)(const void *))(unsigned long)wizm_vfn(o, "stock_slot_active"))(o) & 0xff;
+ if (active && !gl_running[id]) {
+ gl_start_ms[id] = pm_ms();
+ gl_start_player[id] = pm_player();
+ gl_start_score[id] = pm_score(pm_player());
+ gamelog_mode_log("START", id, gl_start_player[id], gl_start_score[id], 0);
+ } else if (!active && gl_running[id]) {
+ gamelog_mode_log("END", id, gl_start_player[id],
+ pm_score(gl_start_player[id]), pm_ms() - gl_start_ms[id]);
+ }
+ gl_running[id] = (unsigned char)(active != 0);
+ }
+}
+
+/* the game's award screens (extra ball, special, replay...): logged, never refused */
+static void on_award_screen(unsigned *r)
+{
+ gl_award_v = r[0];
+ gl_award_n++;
+ say("gamelog: AWARD id=%u player=%u score=%llu", r[0], pm_player(), pm_score(pm_player()));
+}
+
+static void gamelog_arm(void)
+{
+ struct site *as = site("award_screen");
+ if (as && as->ok && hook(as->addr, on_award_screen))
+ say("gamelog: award screen hooked at 0x%08x", as->addr);
+ else
+ say("gamelog: award screen NOT hooked");
+}
static void on_tick(unsigned *r)
{
@@ -7948,6 +8032,7 @@
roster_deferred_tick();
stock_generic_tick(); /* item 164: the game's base play, for the mode table route */
stock_tick(); /* item 160: the game's own rules' counts-as (after the modes: a probe wraps first) */
+ gamelog_stock_tick(); /* GAMELOG: the game's own modes' start/end edges */
lamps_tick(); /* named inserts: their patterns (lights section) */
}
@@ -8547,6 +8632,21 @@
mask = edge.mode_mask ? *(const unsigned short *)(unsigned long)edge.mode_mask : 0u;
hit = edge_is_hit(level, flags, pol, edge.mode_mask != 0, mask);
playing = pm_in_game(); /* attract, a tilt: no player up for a mode to score */
+ /* GAMELOG: every edge to /dump/switch.log (buttons included, attract included) */
+ {
+ static const struct { unsigned id; const char *name; } gl_sw[] = {
+#include "switch_names.inc"
+ };
+ unsigned k;
+ char nm[40] = "?", line[160];
+ int fd, len;
+ for (k = 0; k < sizeof gl_sw / sizeof gl_sw[0]; k++)
+ if (gl_sw[k].id == id) { pm_snprintf(nm, sizeof nm, "%s", gl_sw[k].name); break; }
+ len = pm_snprintf(line, sizeof line, "%lu SW %u %s level=%u hit=%d game=%d\n",
+ pm_ms(), id, nm, level & 1u, hit, playing);
+ fd = open("/dump/switch.log", O_WRONLY | O_CREAT | O_APPEND, 0644);
+ if (fd >= 0) { write(fd, line, len); close(fd); }
+ }
if (edge_log_left > 0 && __sync_sub_and_fetch(&edge_log_left, 1) >= 0)
say("edge: switch %u level %u (flags 0x%04x, polarity 0x%04x, mode mask 0x%04x, in game %d)%s", id, level & 1u,
flags, pol, mask, playing, hit && playing ? " - a hit" : "");
@@ -8781,18 +8881,17 @@
static int on_agent_begin(unsigned *r)
{
- int i = insider_blocked(insider_endpoint);
- unsigned n;
- (void)r;
- if (!i) return 0;
- n = ++insider_dropped[i - 1];
- if (n == 1)
- say("insider: %s is not sent to Insider Connected (the game logs a failed message and plays on)",
- insider_endpoint);
- else if ((n & 15) == 0)
- say("insider: %s not sent, %u times", insider_endpoint, n);
+ /* GAMELOG build: passive gate - Insider Connected stays fully stock. Every endpoint the
+ * game sends is logged once (the machine's own reports are genuine; this build adds no
+ * scores of its own), nothing is refused. */
+ (void)r;
+ if (insider_endpoint[0] && insider_blocked(insider_endpoint) && insider_dropped[0] < 1) {
+ insider_dropped[0] = 1;
+ say("insider: passive gate - %s (and all reports) go to Insider Connected as stock",
+ insider_endpoint);
+ }
insider_endpoint[0] = 0;
- return 1;
+ return 0;
}
/* 1 = the gate is up. 0 = this port cannot keep scores off Insider Connected: NOTHING is hooked
@@ -9266,6 +9365,7 @@
}
display_arm(); /* item 154 display: the clip_play hook, display priority */
block_arm(); /* PAD-347: a mode may keep the game's modes from starting */
+ gamelog_arm(); /* GAMELOG: watch the game's award screens */
backdrop_arm(); /* hud-layers: a clip behind the HUD */
ck_arm(); /* PAD-530: a mode's clip comes back after the game's */
status_bar_arm(); /* PAD-521: the game's own status bar */
+470
View File
@@ -0,0 +1,470 @@
/* event_probe.c - which of the game's EVENTS fire when? (item 147)
*
* The game's rules talk through one event bus: subscribe(_, id, handler, prio) puts a
* handler on id's list (ids 0..207), and dispatch(id, arg) calls every handler on that
* list in priority order. This instrument hooks both, and the game's tick:
*
* subscribe every registration: id, handler, priority, caller, thread
* dispatch per-id counts per WINDOW (a window runs from one mark to the next), and
* the first FIRST_CALLS calls of each id in each window with their argument,
* caller, thread, the player up and the game's mode mask
* tick `first tick` (with the whole table, handler by handler), then twice a
* second: /dump/event.mark "<label>" closes the window (its counts are
* logged) and opens a new one; /dump/event.force "<id>" starts stock mode
* <id> the way a rule does (cmode_manager get(mgr, id)->v[8]), and
* "stop <id>" stops it (v[11](0))
*
* Not a mode and not built with the runtime: build_mode.sh -p -o event_probe.so event_probe.c
* Preload it with PAD_MODE_SO, with /dump/event.sites (below; port_words.py fills and checks
* its words) and read /dump/event.log with event_read.py.
*
* site subscribe <addr> <w0> <w1> the bus (both start `cmp rN, #207; push`)
* site dispatch <addr> <w0> <w1>
* site tick <addr> <w0> <w1> the port's tick
* site mode_get <addr> <w0> <w1> optional: cmode_manager get, for event.force
* data table <addr> the bus's list heads (log only)
* data mode_mgr <addr> optional: the cmode_manager singleton
* data cur_player <addr> optional: logged with each call
* data mode_mask <addr> optional: logged with each call
* site watch_<x> <addr> <w0> <w1> optional, up to 8: calls of this function are counted
* per window like an id (0xd0 + k) and the first ones
* logged with r0..r3 - for a title's events that are
* not broadcast (a skill shot award, a mode's start)
*
* It acts only in the process whose program maps the tick (the game). The trampoline is
* the runtime's, and keeps the moved words at t[14..15], so a mode.so loaded beside it
* still verifies its own sites.
*/
#include <stdarg.h>
extern int open(const char *, int, ...);
extern long read(int, void *, __SIZE_TYPE__);
extern long write(int, const void *, __SIZE_TYPE__);
extern int close(int);
extern int unlink(const char *);
extern int clock_gettime(int, void *);
extern int mprotect(void *, __SIZE_TYPE__, int);
extern int vsnprintf(char *, __SIZE_TYPE__, const char *, va_list);
extern long syscall(long, ...);
#define O_RDONLY 0
#define O_WRONLY 1
#define O_CREAT 0100
#define O_APPEND 02000
#define CLOCK_MONOTONIC 1
#define SYS_GETTID 224
#define N_BUS_IDS 208
#define N_WATCH 8
#define N_IDS (N_BUS_IDS + N_WATCH)
#define FIRST_CALLS 3
#define MAX_REG 3000
#define MAX_LINES 60000
/* ---- the log ------------------------------------------------------------------------- */
static int log_fd = -1;
static volatile int log_lines;
static struct { long s, ns; } t0;
static unsigned long ms(void)
{
struct { long s, ns; } t;
clock_gettime(CLOCK_MONOTONIC, &t);
return (unsigned long)((t.s - t0.s) * 1000L + (t.ns - t0.ns) / 1000000L);
}
static void say(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
static void say(const char *fmt, ...)
{
char b[1024];
int n, m;
va_list ap;
if (log_fd < 0 || log_lines > MAX_LINES) return;
__sync_fetch_and_add(&log_lines, 1);
n = 0;
{
unsigned long v = ms();
char tmp[24];
int k = 0;
do { tmp[k++] = (char)('0' + v % 10); v /= 10; } while (v);
while (k) b[n++] = tmp[--k];
b[n++] = ' ';
}
va_start(ap, fmt);
m = vsnprintf(b + n, sizeof b - (unsigned long)n - 1, fmt, ap);
va_end(ap);
if (m < 0) return;
n += m;
if (n > (int)sizeof b - 2) n = (int)sizeof b - 2;
b[n++] = '\n';
write(log_fd, b, (unsigned long)n);
}
static long tid(void) { return syscall(SYS_GETTID); }
/* ---- the sites file ------------------------------------------------------------------ */
struct entry { char name[24]; unsigned addr, w0, w1; int site; };
static struct entry entries[24];
static int n_entries;
static int hexval(int c)
{
return (c >= '0' && c <= '9') ? c - '0' : (c >= 'a' && c <= 'f') ? c - 'a' + 10
: (c >= 'A' && c <= 'F') ? c - 'A' + 10 : -1;
}
static unsigned number(const char **p)
{
const char *s = *p;
unsigned x = 0;
int base = 10;
while (*s == ' ' || *s == '\t') s++;
if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { base = 16; s += 2; }
for (;; s++) {
int d = hexval(*s);
if (d < 0 || (base == 10 && d > 9)) break;
x = x * (unsigned)base + (unsigned)d;
}
*p = s;
return x;
}
static int same(const char *a, const char *b)
{
while (*a && *a == *b) { a++; b++; }
return *a == *b;
}
static const char *word(const char *s, char *out, unsigned cap)
{
unsigned n = 0;
while (*s == ' ' || *s == '\t') s++;
while (*s && *s != ' ' && *s != '\t' && *s != '\n' && *s != '\r') {
if (n + 1 < cap) out[n++] = *s;
s++;
}
out[n] = 0;
return s;
}
static void load_sites(void)
{
static char buf[4096];
long n, tot = 0;
const char *s;
int fd = open("/dump/event.sites", O_RDONLY);
if (fd < 0) return;
while (tot < (long)sizeof buf - 1 && (n = read(fd, buf + tot, sizeof buf - 1 - (unsigned long)tot)) > 0)
tot += n;
close(fd);
buf[tot] = 0;
for (s = buf; *s;) {
char key[8];
struct entry *e = &entries[n_entries];
const char *line = s;
while (*s && *s != '\n') s++;
if (*s) s++;
line = word(line, key, sizeof key);
if (n_entries >= (int)(sizeof entries / sizeof entries[0])) break;
if (!same(key, "site") && !same(key, "data")) continue;
e->site = same(key, "site");
line = word(line, e->name, sizeof e->name);
e->addr = number(&line);
if (e->site) { e->w0 = number(&line); e->w1 = number(&line); }
if (e->name[0] && e->addr) n_entries++;
}
}
static struct entry *find(const char *name, int site)
{
int i;
for (i = 0; i < n_entries; i++)
if (entries[i].site == site && same(entries[i].name, name)) return &entries[i];
return 0;
}
static unsigned data(const char *name) { struct entry *e = find(name, 0); return e ? e->addr : 0; }
/* the words, followed through another preloaded object's trampoline (pad_mode_runtime.c's shape) */
static int words_ok(struct entry *e)
{
const unsigned *p;
int depth;
if (!e) return 0;
p = (const unsigned *)(unsigned long)e->addr;
for (depth = 0; depth < 4; depth++) {
if (p[0] == e->w0 && p[1] == e->w1) return 1;
if (p[0] != 0xe51ff004u) break;
p = (const unsigned *)(unsigned long)p[1];
if (p[0] != 0xe92d500fu) break;
if (p[14] == e->w0 && p[15] == e->w1) return 1;
p += 6;
}
return 0;
}
/* ---- is this the game? ----------------------------------------------------------------- */
static int is_game_process(unsigned addr)
{
static char buf[65536];
long n, tot = 0;
char *s = buf, *e;
int fd = open("/proc/self/maps", O_RDONLY);
if (fd < 0) return 0;
while (tot < (long)sizeof buf - 1 && (n = read(fd, buf + tot, sizeof buf - 1 - (unsigned long)tot)) > 0)
tot += n;
close(fd);
buf[tot] = 0;
while (*s) {
unsigned long lo = 0, hi = 0;
char *q = s;
int d;
for (e = s; *e && *e != '\n'; e++) ;
for (; (d = hexval(*q)) >= 0; q++) lo = lo * 16 + (unsigned)d;
if (*q == '-') {
q++;
for (; (d = hexval(*q)) >= 0; q++) hi = hi * 16 + (unsigned)d;
if (lo <= addr && addr + 8 <= hi && q[0] == ' ' && q[1] == 'r' && q[3] == 'x') {
char *p;
for (p = q; p + 4 <= e; p++)
if (p[0] == 'g' && p[1] == 'a' && p[2] == 'm' && p[3] == 'e') return 1;
return 0;
}
}
s = *e ? e + 1 : e;
}
return 0;
}
/* ---- the runtime's trampoline ------------------------------------------------------------ */
typedef void (*hook_fn)(unsigned *regs); /* r0..r3, ip, lr, then stack arguments */
static unsigned tramp[16 * 16] __attribute__((aligned(4096)));
static int tramp_used;
static void relocate_literal(unsigned *t, int i, unsigned addr, unsigned w)
{
unsigned imm, at;
if ((w & 0xFF7F0000u) != 0xE51F0000u || ((w >> 12) & 0xF) == 15) return;
imm = w & 0xFFF;
at = addr + (unsigned)i * 4 + 8;
at = (w & 0x00800000u) ? at + imm : at - imm;
t[12 + i] = *(const unsigned *)(unsigned long)at;
t[6 + i] = 0xE59F0010u | (w & 0xF000u);
}
static int hook(unsigned addr, hook_fn logger)
{
unsigned *p = (unsigned *)(unsigned long)addr, *t;
int i;
if (!addr || tramp_used >= 16) return 0;
t = tramp + tramp_used++ * 16;
t[0] = 0xe92d500fu; /* push {r0,r1,r2,r3,ip,lr} */
t[1] = 0xe1a0000du; /* mov r0, sp */
t[2] = 0xe1a00000u; /* nop */
t[3] = 0xe59fc014u; /* ldr ip, [pc, #20] -> t[10] */
t[4] = 0xe12fff3cu; /* blx ip */
t[5] = 0xe8bd500fu; /* pop {r0,r1,r2,r3,ip,lr} */
t[6] = p[0];
t[7] = p[1];
t[8] = 0xe59ff004u; /* ldr pc, [pc, #4] -> t[11] */
t[9] = 0u;
t[10] = (unsigned)(unsigned long)logger;
t[11] = addr + 8u;
t[14] = p[0];
t[15] = p[1];
for (i = 0; i < 2; i++) relocate_literal(t, i, addr, p[i]);
mprotect(tramp, sizeof tramp, 7);
mprotect((void *)(unsigned long)(addr & ~0xfffu), 0x2000, 7);
p[1] = (unsigned)(unsigned long)t;
p[0] = 0xe51ff004u; /* ldr pc, [pc, #-4] */
__builtin___clear_cache((char *)t, (char *)(t + 16));
__builtin___clear_cache((char *)p, (char *)(p + 2));
return 1;
}
/* ---- the game, right now --------------------------------------------------------------- */
static unsigned player(void)
{
unsigned a = data("cur_player");
return a ? *(unsigned char *)(unsigned long)a : 0;
}
static unsigned mode_mask(void)
{
unsigned a = data("mode_mask");
return a ? *(unsigned short *)(unsigned long)a : 0;
}
/* ---- subscribe ----------------------------------------------------------------------------- */
static volatile int n_reg;
static void on_subscribe(unsigned *r)
{
if (__sync_fetch_and_add(&n_reg, 1) >= MAX_REG) return;
say("sub id=0x%02x handler=0x%08x prio=%u r0=0x%08x lr=0x%08x tid=%ld",
r[1], r[2], r[3] & 0xff, r[0], r[5], tid());
}
/* ---- dispatch ---------------------------------------------------------------------------- */
static volatile unsigned total[N_IDS], window[N_IDS], shown[N_IDS];
static volatile long disp_tid;
static void on_dispatch(unsigned *r)
{
unsigned id = r[0];
long t;
if (id >= N_BUS_IDS) { say("disp id=%u OUT OF RANGE lr=0x%08x", id, r[5]); return; }
__sync_fetch_and_add(&total[id], 1);
__sync_fetch_and_add(&window[id], 1);
if (__sync_fetch_and_add(&shown[id], 1) >= FIRST_CALLS) return;
t = tid();
say("disp id=0x%02x arg=0x%08x lr=0x%08x tid=%ld p=%u mask=0x%04x", id, r[1], r[5], t, player(), mode_mask());
if (disp_tid != t) {
say("dispatch thread %ld (was %ld)", t, disp_tid);
disp_tid = t;
}
}
/* ---- watched functions ------------------------------------------------------------------- */
static struct entry *watched[N_WATCH];
static int n_watched;
static void on_watch(int k, unsigned *r)
{
unsigned id = N_BUS_IDS + (unsigned)k;
__sync_fetch_and_add(&total[id], 1);
__sync_fetch_and_add(&window[id], 1);
if (__sync_fetch_and_add(&shown[id], 1) >= FIRST_CALLS) return;
say("watch %s id=0x%02x r0=0x%08x r1=0x%08x r2=0x%08x r3=0x%08x lr=0x%08x tid=%ld p=%u mask=0x%04x",
watched[k]->name, id, r[0], r[1], r[2], r[3], r[5], tid(), player(), mode_mask());
}
#define WATCH(k) static void on_watch##k(unsigned *r) { on_watch(k, r); }
WATCH(0) WATCH(1) WATCH(2) WATCH(3) WATCH(4) WATCH(5) WATCH(6) WATCH(7)
static const hook_fn watch_hooks[N_WATCH] = { on_watch0, on_watch1, on_watch2, on_watch3,
on_watch4, on_watch5, on_watch6, on_watch7 };
/* ---- tick ---------------------------------------------------------------------------------- */
static void dump_table(const char *why)
{
unsigned tab = data("table"), id;
if (!tab) return;
for (id = 0; id < N_BUS_IDS; id++) { /* the table holds bus ids only: past it is another global */
unsigned *node = *(unsigned **)(unsigned long)(tab + 4 * id);
char line[900];
int n = 0, guard = 40;
if (!node) continue;
while (node && guard-- > 0 && n < (int)sizeof line - 32) {
n += __builtin_snprintf(line + n, sizeof line - (unsigned long)n, " 0x%08x/%u",
node[0], (unsigned)(*(unsigned char *)(node + 1)));
node = (unsigned *)(unsigned long)node[2];
}
say("table(%s) id=0x%02x:%s", why, id, line);
}
}
static void close_window(const char *label)
{
static char open_label[64] = "boot";
static unsigned long opened;
char line[3000];
int n = 0;
unsigned id, now = (unsigned)ms();
for (id = 0; id < N_IDS; id++) {
unsigned c = window[id];
if (!c) continue;
__sync_fetch_and_add(&window[id], -c);
if (n < (int)sizeof line - 24)
n += __builtin_snprintf(line + n, sizeof line - (unsigned long)n, " %02x=%u", id, c);
shown[id] = 0;
}
line[n] = 0;
say("window %s %lu ms:%s", open_label, (unsigned long)(now - opened), line);
say("mark %s p=%u mask=0x%04x", label, player(), mode_mask());
opened = now;
for (n = 0; label[n] && n < (int)sizeof open_label - 1; n++) open_label[n] = label[n];
open_label[n] = 0;
}
static int read_trigger(const char *path, char *out, unsigned cap)
{
long n;
int fd = open(path, O_RDONLY);
if (fd < 0) return 0;
n = read(fd, out, cap - 1);
close(fd);
unlink(path);
out[n > 0 ? n : 0] = 0;
for (n = 0; out[n] && out[n] != '\n' && out[n] != '\r'; n++) ;
out[n] = 0;
return 1;
}
static void force(const char *what)
{
struct entry *get = find("mode_get", 1);
unsigned mgr = data("mode_mgr"), id, **obj;
const char *s = what;
int stop = 0;
if (s[0] == 's' && s[1] == 't' && s[2] == 'o' && s[3] == 'p') { stop = 1; s += 4; }
id = number(&s);
if (!get || !mgr) { say("force %s: no mode_get / mode_mgr in event.sites", what); return; }
obj = ((unsigned **(*)(unsigned, unsigned))(unsigned long)get->addr)(mgr, id);
if (!obj) { say("force %s: get returned 0", what); return; }
say("force %s mode %u obj=0x%08x vptr=0x%08x p=%u mask=0x%04x", stop ? "stop" : "start", id,
(unsigned)(unsigned long)obj, (unsigned)(unsigned long)*obj, player(), mode_mask());
if (stop) ((void (*)(void *, unsigned))(unsigned long)(*obj)[11])(obj, 0u);
else ((void (*)(void *))(unsigned long)(*obj)[8])(obj);
say("force %s mode %u returned, mask=0x%04x", stop ? "stop" : "start", id, mode_mask());
}
static void on_tick(unsigned *r)
{
static unsigned ticks;
char buf[64];
(void)r;
if (ticks++ == 0) {
say("first tick on thread %ld; %d registrations so far", tid(), n_reg);
dump_table("first tick");
}
if (ticks % 30) return;
if (read_trigger("/dump/event.mark", buf, sizeof buf)) {
close_window(buf[0] ? buf : "(empty)");
if (same(buf, "table")) dump_table("mark");
}
if (read_trigger("/dump/event.force", buf, sizeof buf)) force(buf);
}
__attribute__((constructor))
static void event_probe_start(void)
{
struct entry *sub, *disp, *tick, *get;
load_sites();
tick = find("tick", 1);
if (!tick || !is_game_process(tick->addr)) return;
clock_gettime(CLOCK_MONOTONIC, &t0);
log_fd = open("/dump/event.log", O_WRONLY | O_CREAT | O_APPEND, 0644);
sub = find("subscribe", 1);
disp = find("dispatch", 1);
get = find("mode_get", 1);
say("event probe: %d entries; tick %s, subscribe %s, dispatch %s, mode_get %s", n_entries,
words_ok(tick) ? "ok" : "WRONG", sub && words_ok(sub) ? "ok" : "WRONG/absent",
disp && words_ok(disp) ? "ok" : "WRONG/absent", get ? (words_ok(get) ? "ok" : "WRONG") : "absent");
if (get && !words_ok(get)) get->site = 2; /* never call a function that moved */
if (!words_ok(tick)) { say("tick words wrong: nothing hooked"); return; }
if (sub && words_ok(sub)) hook(sub->addr, on_subscribe);
if (disp && words_ok(disp)) hook(disp->addr, on_dispatch);
hook(tick->addr, on_tick);
{
int i;
for (i = 0; i < n_entries && n_watched < N_WATCH; i++) {
struct entry *e = &entries[i];
if (e->site != 1 || e->name[0] != 'w' || e->name[1] != 'a' || e->name[5] != '_') continue;
if (!words_ok(e)) { say("watch %s 0x%08x: words WRONG, not hooked", e->name, e->addr); continue; }
watched[n_watched] = e;
if (hook(e->addr, watch_hooks[n_watched]))
say("watch %s 0x%08x = id 0x%02x", e->name, e->addr, N_BUS_IDS + (unsigned)n_watched);
n_watched++;
}
}
say("hooked; pid thread %ld", tid());
}
+128
View File
@@ -0,0 +1,128 @@
#!/usr/bin/env python3
"""event_read.py - read event_probe.c's log: which event ids fired in which marked window.
event_read.py event.log [--callers args.txt] [--id 0x34 ...] [--diff A B]
event_probe.c logs every registration on the game's event bus (`sub`), the first calls
of each id in a window (`disp`), and, at each mark, the closing window's per-id counts
(`window <label> <ms> ms: id=count ...`). This prints:
1. every window with the ids that fired in it, leaving out the BACKGROUND ids (those
firing in nearly every window, such as the tick's own two) so what a mark caused
stands out;
2. per id: the windows it fired in, its handlers and priorities (from `sub` and the
table dump), and, with --callers, the functions that dispatch it statically
(`armxref.py args <elf> <dispatch>` output);
3. with --diff A B: ids in window A and not in B (a press and its control).
An id is named from this, never from its handlers' names: a drain tells the end of a
ball from the next ball's start by WHEN it fires (right after the drain, or ~10 s later).
"""
import argparse
import collections
import re
import sys
WINDOW = re.compile(r"^(\d+) window (\S+) (\d+) ms:(.*)$")
SUB = re.compile(r"^(\d+) sub id=0x([0-9a-f]+) handler=0x([0-9a-f]+) prio=(\d+) r0=0x([0-9a-f]+) lr=0x([0-9a-f]+) tid=(\d+)")
DISP = re.compile(r"^(\d+) disp id=0x([0-9a-f]+) arg=0x([0-9a-f]+) lr=0x([0-9a-f]+) tid=(\d+) p=(\d+) mask=0x([0-9a-f]+)")
TABLE = re.compile(r"^(\d+) table\(([^)]*)\) id=0x([0-9a-f]+):(.*)$")
ARGS = re.compile(r"^\s*(bl|b)\s+0x([0-9a-f]+)\s+fn 0x([0-9a-f?]+)(?: \((.*?)\))?\s+r0=0x([0-9a-f]+)")
def parse(path):
windows, subs, disps, table = [], collections.defaultdict(list), collections.defaultdict(list), {}
with open(path, encoding="utf-8", errors="replace") as f:
for line in f:
line = line.rstrip("\n")
m = WINDOW.match(line)
if m:
counts = {}
for tok in m.group(4).split():
k, v = tok.split("=")
counts[int(k, 16)] = int(v)
windows.append((m.group(2), int(m.group(3)), int(m.group(1)), counts))
continue
m = SUB.match(line)
if m:
subs[int(m.group(2), 16)].append((int(m.group(3), 16), int(m.group(4)), int(m.group(6), 16)))
continue
m = DISP.match(line)
if m:
disps[int(m.group(2), 16)].append((int(m.group(1)), int(m.group(3), 16), int(m.group(4), 16),
int(m.group(5)), int(m.group(6)), int(m.group(7), 16)))
continue
m = TABLE.match(line)
if m and int(m.group(3), 16) not in table:
table[int(m.group(3), 16)] = m.group(4).split()
return windows, subs, disps, table
def callers(path):
out = collections.defaultdict(list)
if not path:
return out
with open(path, encoding="utf-8", errors="replace") as f:
for line in f:
m = ARGS.match(line)
if m:
out[int(m.group(5), 16)].append("0x%s%s" % (m.group(3), " " + m.group(4) if m.group(4) else ""))
return out
def main(argv=None):
ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
ap.add_argument("log")
ap.add_argument("--callers", help="armxref.py args output for the dispatch function")
ap.add_argument("--id", action="append", default=[], help="show only these ids in section 2")
ap.add_argument("--diff", nargs=2, action="append", default=[], metavar=("A", "B"))
ap.add_argument("--background", type=float, default=0.8,
help="an id firing in at least this share of windows is background (0.8)")
a = ap.parse_args(argv)
windows, subs, disps, table = parse(a.log)
stat = callers(a.callers)
if not windows:
print("no windows in %s (no marks consumed?)" % a.log)
seen = collections.Counter()
for _label, _ms, _at, counts in windows:
seen.update(counts.keys())
background = {i for i, n in seen.items() if windows and n >= a.background * len(windows)}
print("== registrations: %d on %d ids; background ids (in >= %d%% of %d windows): %s" % (
sum(len(v) for v in subs.values()), len(subs), int(a.background * 100), len(windows),
" ".join("0x%02x" % i for i in sorted(background)) or "none"))
print("\n== 1. windows")
for label, dur, at, counts in windows:
fg = ["%02x=%d" % (i, c) for i, c in sorted(counts.items()) if i not in background]
bg = ["%02x=%d" % (i, counts[i]) for i in sorted(background) if i in counts]
print("%-14s %6d ms @%7d %s [bg %s]" % (label, dur, at, " ".join(fg) or "-", " ".join(bg)))
print("\n== 2. ids")
want = {int(x, 0) for x in a.id}
for i in sorted(set(seen) | set(subs) | set(want)):
if want and i not in want:
continue
where = ["%s:%d" % (label, counts[i]) for label, _d, _a, counts in windows if i in counts]
hs = subs.get(i, [])
print("0x%02x (%3d) windows %s" % (i, i, " ".join(where) or "never"))
if hs:
print(" handlers %s" % " ".join("0x%x/p%d" % (h, p) for h, p, _lr in hs))
elif i in table:
print(" table %s" % " ".join(table[i]))
for t, arg, lr, tid, p, mask in disps.get(i, [])[:3]:
print(" call @%d arg=0x%x lr=0x%x tid=%d player=%d mask=0x%04x" % (t, arg, lr, tid, p, mask))
if stat.get(i):
print(" dispatched from %s" % ", ".join(sorted(set(stat[i]))[:6]))
for wa, wb in a.diff:
ca = collections.Counter()
cb = collections.Counter()
for label, _d, _a, counts in windows:
if label == wa:
ca.update(counts)
if label == wb:
cb.update(counts)
only = sorted(set(ca) - set(cb))
print("\n== 3. in %s, not in %s: %s" % (wa, wb, " ".join("0x%02x=%d" % (i, ca[i]) for i in only) or "none"))
return 0
if __name__ == "__main__":
sys.exit(main())
+174
View File
@@ -0,0 +1,174 @@
/* GAMELOG - a passive gameplay-event logger for Stern Spike 2.
*
* Logs, with millisecond timestamps, to /dump/mode.log:
* EV <event name> - the game's bus events (game start, ball start/end,
* bonus end, tilt warning, tilt), player, ball, score
* SHOT <shot name> - every shot the game dispatches while a ball is up
* SCORES - all players' scores at each bonus end
* MODE START/END - the game's own modes (Island Scenes and the rest of
* the port's block_* set), with the score delta earned
* across the mode
* AWARD id=<n> - the game's award screens (extra ball = the
* extra_ball_award value, default 13)
*
* Extra balls: an award matching extra_ball_award marks the NEXT ball start
* as "ball N (EXTRA)" instead of advancing the ball number.
*
* The mode never begins(), never blocks, allocates nothing: it only watches.
*/
#include "pad_mode.h"
/* -nostdlib: the few libc symbols resolve from the game's own libc at load,
* exactly as the runtime's and event_probe.c's do. */
extern int clock_gettime(int, void *);
/* the runtime patch exposes the award screens it sees */
extern unsigned long gl_award_seq(void);
extern unsigned gl_last_award_id(void);
#define CLOCK_MONOTONIC 1
struct ts {
long sec, nsec;
};
static unsigned long ms(void)
{
struct ts t;
clock_gettime(CLOCK_MONOTONIC, &t);
return (unsigned long)t.sec * 1000ul + (unsigned long)t.nsec / 1000000ul;
}
static unsigned long t0; /* ms at .init, so stamps start near 0 */
static unsigned ball; /* ball in play, 1-based */
static unsigned extras_pending; /* extra balls awarded, not yet played */
static unsigned char in_game;
static unsigned long award_seq_seen;
static unsigned extra_ball_award = 13; /* OB_SYS_EXTRA_BALL; port: value extra_ball_award */
static int ev_game_start, ev_ball_start, ev_ball_end, ev_bonus_end, ev_tilt_warn, ev_tilt;
/* the player's score, comma-separated, into a caller buffer */
static void score_str(char *out, unsigned cap, unsigned player)
{
pm_commas(out, cap, pm_score(player));
}
static void scores_line(const char *tag)
{
char buf[128];
char one[32];
unsigned p, n = 0;
buf[0] = 0;
for (p = 1; p <= 4; p++) {
score_str(one, sizeof one, p);
n += (unsigned)pm_snprintf(buf + n, sizeof buf - n, "%sP%u=%s",
n ? " " : "", p, one);
}
pm_log("[GAMELOG] +%lums %s %s", ms() - t0, tag, buf);
}
static void on_init(void)
{
t0 = ms();
ball = 0;
in_game = 0;
extras_pending = 0;
award_seq_seen = gl_award_seq();
extra_ball_award = (unsigned)pm_port_value("extra_ball_award", 13);
ev_game_start = pm_event("game_start");
ev_ball_start = pm_event("ball_start");
ev_ball_end = pm_event("ball_end");
ev_bonus_end = pm_event("bonus_end");
ev_tilt_warn = pm_event("tilt_warning");
ev_tilt = pm_event("tilt");
pm_log("[GAMELOG] up: game %s %s, %d named shots; event ids %d/%d/%d/%d/%d/%d; extra_ball_award=%u",
pm_game(), pm_version(), pm_shot_count(),
ev_game_start, ev_ball_start, ev_ball_end,
ev_bonus_end, ev_tilt_warn, ev_tilt, extra_ball_award);
}
static void on_event(unsigned id)
{
const char *name = pm_event_name(id);
char one[32];
if (id == (unsigned)ev_game_start) {
ball = 0;
extras_pending = 0;
in_game = 1;
pm_log("[GAMELOG] +%lums EV %s - GAME START", ms() - t0,
name ? name : "game_start");
} else if (id == (unsigned)ev_ball_start) {
score_str(one, sizeof one, pm_player());
if (extras_pending) {
extras_pending--;
pm_log("[GAMELOG] +%lums EV %s player=%u ball=%u (EXTRA BALL) score=%s", ms() - t0,
name ? name : "ball_start", pm_player(), ball, one);
} else {
ball++;
pm_log("[GAMELOG] +%lums EV %s player=%u ball=%u score=%s", ms() - t0,
name ? name : "ball_start", pm_player(), ball, one);
}
} else if (id == (unsigned)ev_ball_end) {
score_str(one, sizeof one, pm_player());
pm_log("[GAMELOG] +%lums EV %s player=%u ball=%u score=%s", ms() - t0,
name ? name : "ball_end", pm_player(), ball, one);
} else if (id == (unsigned)ev_bonus_end) {
pm_log("[GAMELOG] +%lums EV %s", ms() - t0, name ? name : "bonus_end");
scores_line("SCORES");
} else if (id == (unsigned)ev_tilt_warn) {
pm_log("[GAMELOG] +%lums EV %s player=%u", ms() - t0,
name ? name : "tilt_warning", pm_player());
} else if (id == (unsigned)ev_tilt) {
pm_log("[GAMELOG] +%lums EV %s player=%u ball=%u", ms() - t0,
name ? name : "tilt", pm_player(), ball);
}
}
static void on_shot(unsigned long long shot)
{
const char *name;
char one[32];
if (!in_game) /* attract-mode dispatches are noise */
return;
name = pm_shot_name(shot);
score_str(one, sizeof one, pm_player());
if (name)
pm_log("[GAMELOG] +%lums SHOT %s player=%u ball=%u score=%s", ms() - t0,
name, pm_player(), ball, one);
else
pm_log("[GAMELOG] +%lums SHOT 0x%llx player=%u ball=%u score=%s", ms() - t0,
shot, pm_player(), ball, one);
}
static void on_tick(void)
{
/* award screens: the runtime hook counts them; log the new ones */
while (award_seq_seen != gl_award_seq()) {
award_seq_seen++;
if (gl_last_award_id() == extra_ball_award) {
extras_pending++;
pm_log("[GAMELOG] +%lums AWARD EXTRA BALL (id=%u) player=%u", ms() - t0,
gl_last_award_id(), pm_player());
} else {
char one[32];
score_str(one, sizeof one, pm_player());
pm_log("[GAMELOG] +%lums AWARD id=%u player=%u score=%s", ms() - t0,
gl_last_award_id(), pm_player(), one);
}
}
}
static const struct pm_mode gamelog = {
.name = "GAMELOG",
.init = on_init,
.tick = on_tick,
.event = on_event,
.shot = on_shot,
};
PM_REGISTER(gamelog);
+685
View File
@@ -0,0 +1,685 @@
/* pad_mode.h - write a game mode of your own for a Stern Spike 2 pinball machine, in C.
*
* Read MODE_SDK.md first, then template_mode.c, which uses almost every call below.
*
* WHAT A MODE IS. A few functions the game calls: once at boot, 60 times a second, on
* every shot, and when the ball drains. Your mode decides when it starts, what scores
* while it runs, and what the display, speakers and lights do - using the GAME'S OWN
* scoring, sound, light and display code through the calls in this header, so it
* behaves like a mode the game shipped with.
*
* HOW IT GETS INTO THE GAME. Your .c files are compiled together with
* pad_mode_runtime.c into one shared object (build_mode.sh), which the machine preloads
* into the game. At boot the runtime reads the PORT for the game on the card
* (ports/<game>-<version>.port): where each game function is in THIS build, plus that
* build's shot names, scenes and callouts. Nothing in this header is an address, so the
* same mode source runs on any game and version that has a port.
*
* THE SAFETY GATE. Before hooking anything the runtime checks, for every function the
* port names, the first two machine instructions against what the port says they
* should be. If the core ones do not match - a port for another game or version - it
* hooks NOTHING, calls none of your code, logs why, and the game runs stock. An address
* outside the game's own code is never read, and the core is checked before the rest.
*
* THE RULES THAT KEEP A GAME RUNNING (MODE_SDK.md explains each):
* 1. Never block. Every callback runs on a game thread; return quickly.
* 2. No malloc, no files beyond pm_trigger/pm_log, no libc beyond pm_snprintf.
* The object is built -nostdlib: there is no allocator inside it.
* 3. Keep state in static variables. Stack is fine for small buffers.
* 4. One of our modes runs at a time: call pm_begin() before you start, pm_end()
* when you finish, and do not start if pm_begin() says no.
* 5. A call that returns failure (0 / NULL) means this game's port lacks that
* capability, or the thing asked for is not there. Carry on without it.
*/
#ifndef PAD_MODE_H
#define PAD_MODE_H
#include <stdarg.h>
#include <stdint.h>
/* ---- a mode: define one of these and register it ----------------------------------
* Every callback is optional (leave it 0). All of them run on the game's own threads -
* which ones is logged at every boot (MODE_SDK.md, "What is measured").
*
* static const struct pm_mode my_mode = {
* .name = "RAMP FRENZY", .init = on_init, .tick = on_tick,
* .shot = on_shot, .ball_end = on_ball_end,
* };
* PM_REGISTER(my_mode);
*/
struct pm_mode {
const char *name; /* shown in the log; keep it short */
void (*init)(void); /* once, on the game's first tick (the game is up; its
scenes may still be loading - see pm_node) */
void (*tick)(void); /* 60 times a second, on the game's scheduler thread */
void (*shot)(uint64_t shot); /* every shot the game dispatches - see pm_shot() */
void (*ball_end)(void); /* the ball in play drained */
void (*event)(unsigned id); /* one of the game's EVENTS fired (ball start, multiball
start...): see pm_event(). Called from the tick, within
a tick of the game's own broadcast. */
};
#define PM_REGISTER(m) \
static const struct pm_mode *const pm__register_##m \
__attribute__((used, section("pm_modes"))) = &(m)
/* ---- what this game can do ----------------------------------------------------------
* A port lists only what was found and checked for its build. Ask before relying on a
* capability, or just call - a missing one fails safely. */
#define PM_CAN_CALLOUT 0x0001u /* pm_callout, pm_callout_nth */
#define PM_CAN_LIGHTS 0x0002u /* pm_lights */
#define PM_CAN_SCREENS 0x0004u /* pm_node, pm_text, pm_show, pm_set_text */
#define PM_CAN_CLIPS 0x0008u /* pm_clip */
#define PM_CAN_OWN_SOUND 0x0010u /* pm_callout_own_sound */
#define PM_CAN_MESSAGES 0x0020u /* pm_message_set / pm_message_restore */
#define PM_CAN_AWARD_SCREEN 0x0040u /* pm_award_screen */
#define PM_CAN_EVENTS 0x0080u /* .event callbacks, pm_event */
#define PM_CAN_SWITCH_SHOTS 0x0200u /* shots from switches: the port's `switch` lines (see shots) */
int pm_can(unsigned what); /* 1 if EVERY bit in `what` is available */
const char *pm_game(void); /* the port's game, e.g. "godzilla_pro" */
const char *pm_version(void); /* the port's version, e.g. "1.15" */
/* ---- the game, right now ----------------------------------------------------------- */
int pm_in_game(void); /* a game is being played (not attract, not a menu) */
unsigned pm_player(void); /* the player up, 1-4; 0 when no game */
uint64_t pm_score(unsigned player); /* that player's score */
/* ---- shots --------------------------------------------------------------------------
* A SHOT is a 64-bit mask the game hands every mode when a playfield switch means
* something: one bit per shot (a ramp, a target...). A single switch can dispatch twice -
* first 0x1 ("a playfield switch was hit"), then its own shot bit - so test bits, never
* compare the whole mask. On a title whose rules send no shot for some switches (The
* Beatles' standups and lanes), the port can map those switches to shot bits of their own
* (`switch` lines, PM_CAN_SWITCH_SHOTS): each hit comes to .shot like any other shot,
* from the tick. The port names this game's shots, switch ones included: */
uint64_t pm_shot(const char *name); /* the named shot's mask, or 0 if this game has none */
const char *pm_shot_name(uint64_t shot); /* the first named shot in `shot`, or 0 */
int pm_shot_count(void); /* how many named shots the port has */
const char *pm_shot_at(int i, uint64_t *mask); /* the i-th named shot (0-based), or 0 */
/* ---- one mode at a time ------------------------------------------------------------ */
int pm_begin(void); /* 1 = your mode is now the one running; 0 = another one is */
void pm_end(void); /* your mode stopped running */
int pm_running(void); /* your mode is the one running */
void pm_running_name(const char *name); /* PAD-363: after pm_begin, the name the runtime's own lines give
your mode while it runs (one object running several modes) */
unsigned pm_begun(void); /* PAD-413: how many times one of our modes has begun. Remember it at your end: when it
moves, another mode has the screen, so drop your ending (total, own screen) at once.
A full-screen clip of yours still playing is stopped by the runtime then. */
/* ---- scoring ------------------------------------------------------------------------
* Through the game's own scoring, so its playfield multiplier and its rules about when a
* score may be added apply. Returns what was actually added (can be 0). A title whose
* scores are 32-bit (The Beatles: the port's score_add32) multiplies the points and adds
* them with no carry check, so the points are cut to the room left below 4,294,967,295
* divided by the multiplier (the port's data score_mult): the score stops at the top
* instead of wrapping, and at the top an award adds 0. */
uint64_t pm_score_add(unsigned player, uint64_t points);
/* PAD-314: takes points away. The loss is written straight into the game's score table (the
* same u64[4] / u32[4] pm_score reads), cut to the player's score so it never passes 0 - the
* scores are unsigned, so below 0 would read as an enormous number - and the game's scoring
* call is then given 0 points so whatever it does on a change (its score display) runs. No
* multiplier: a loss is the flat number asked for. Returns what was taken (can be 0). */
uint64_t pm_score_sub(unsigned player, uint64_t points);
/* ---- sound --------------------------------------------------------------------------
* A callout is one of the game's own speech/sound requests, by id. The port names the
* useful ones by role: pm_callout_id("ten_seconds"), "countdown", "time_up". */
unsigned pm_callout_id(const char *role); /* 0 if the port has no such role */
void pm_callout(unsigned id);
void pm_callout_nth(unsigned id, unsigned n); /* a numbered variant (a countdown's "3"); on the
countdown role n counts from the port's
`value countdown_first` (the clip "one" is in) */
/* A sound the card never shipped: carried by the game's callout `carrier`, with the
* sound container key the build gave your appended sound (Modes tab / Write).
* Countdown variants n = 0..4 are proven; others are not measured. */
int pm_callout_own_sound(unsigned carrier, const unsigned char key[8]);
/* A sound REQUEST by id, straight to the game's sound code: no city variant is swapped in.
* A sound of your own is a request whose record the build re-pointed (Modes tab / Write,
* MODE_SDK.md "Sounds of your own"). Each returns 0 when the port lacks the call. */
int pm_sound(unsigned request); /* start it; 1 if the port has the call */
int pm_sound_active(unsigned request); /* 1 while a channel plays it */
int pm_sound_stop(unsigned request); /* stop every channel playing it; 1 if one was */
/* How a request competes for its sound bus (music, voice and each effect bus hold ONE sound
* at a time): a new request takes a bus from a playing one of LOWER priority, or of EQUAL
* priority when the playing one's steal flag (bit 0) is set; otherwise it is not played.
* Sets the request's priority and flags (-1 leaves one as it is) and returns the old pair as
* priority << 8 | flags, or -1 when the port has no request table (data sound_requests) or
* the record does not look like one. Only for a request the game never plays itself, such
* as the carrier of a sound of your own. */
int pm_sound_priority(unsigned request, int priority, int flags);
/* Item 150 follow-up (MODE_SDK.md "A music bed of your own"):
* pm_sound_sid points `request` at ONE sound id until it is called again with sid 0, which puts
* the request's own list back. The one music carrier plays a different bed for every mode this
* way: each bed is a sound id the build bound to an appended record and no request names.
* 1 = done; 0 when the port has no request table or the record does not look like one.
* pm_sound_fade fades every channel playing `request` down to silence over `ms`, then stops it
* (a stop at full level is a click); 1 if one was playing. Without the port's channel and voice
* offsets it stops at once. pm_sound() on a request still fading ends that fade first.
* pm_sound_playing fills up to `max` requests (and their bus bits: 0x01 music, 0x02 voice) of
* the channels playing now; returns how many play, or -1 when the port has no channel table. */
int pm_sound_sid(unsigned request, unsigned sid);
/* Item 163: a sound of your own with NO stock sound id touched. For ONE play, every lookup of the
* carrier's own record key `stock` takes `ours` instead - a record the build appended that no
* descriptor names - and `request` plays at `priority` (no steal flag); the key and priority come
* back by themselves once `ms` (+1/8 +1 s; 10 s when 0) have passed. Call it
* right before pm_sound(request). The carrier's descriptor still says the bus, the loop and how
* long it plays, so `ours` must be no longer than the carrier's own record. 1 = armed; 0 when
* the port has no sound_lookup site (then do NOT play: the carrier would play its own line). */
int pm_sound_swap(unsigned request, const unsigned char stock[8], const unsigned char ours[8], int priority, unsigned ms);
int pm_sound_fade(unsigned request, unsigned ms);
int pm_sound_playing(unsigned *requests, unsigned *buses, int max);
/* PAD-415: the game's own hit sounds, a rising run the port names (`value hit_sound_<n>`, n from 1, low to high: on
* Godzilla its eight pitched orchestra hits), so every hit of a mode is heard - while one of ours runs the game's
* rules see no shots, and play no sound of their own for them. pm_hit_sound(n) plays the n-th (past the last, the
* last), so a mode's hits can climb as it goes; never two within 100 ms (a spinner's run is one sound).
* 1 = played; 0 = the port names none, or too soon. pm_hit_sounds: how many the port names (0 = none here). */
int pm_hit_sound(int n);
int pm_hit_sounds(void);
/* ---- lights -------------------------------------------------------------------------
* One command in the game's own light language, e.g.
* "blele --sweep 0 --lts 224 --red 0 --green 255 --blue 0 --freq 10 --use_alpha 1 --alpha 255"
* The port supplies the owner id. Returns 1 only if it ran under a live show - without
* one the game takes the command and lights nothing. MODE_SDK.md, "Lights". */
int pm_lights(const char *command);
int pm_lights_as(unsigned owner, const char *command); /* a specific owner id */
/* ---- named inserts: the playfield's lamps, one by one (MODE_SDK.md "Lights: named inserts")
* The port's `lamp` lines name every insert on the playfield (the game's own names: "LEFT RAMP",
* "MASER", "POWERLINE LEFT"...) and the shots the game itself ties to each. A mode HOLDS an insert
* with a colour and a pattern; while it holds it, that is what the insert shows, on top of the
* game's own light shows, and every insert it does not hold keeps doing what the game wants (its
* modes, battles, multiballs, attract). Releasing hands the insert back to the game at once.
*
* It rides the game's own layering: the game lights through LAYERS (lamp groups), each with a
* priority, composited bottom to top every frame; a layer covers only the lights it holds. The
* runtime gives the mode a layer of its own at priority 255 (the top) unless the mode asks for a
* lower one, so a game show of a higher priority covers it there.
*
* pm_lamp_shot(pm_shot("Left ramp"), PM_RGB(255, 0, 0), PM_LAMP_BLINK, 0);
* pm_lamp_set("TANK 2,ADV TRAIN,TANK 3", PM_RGB(255, 200, 0), PM_LAMP_CHASE, 150);
* ...
* pm_lamp_release_all(); at the mode's end: the game has them back
*
* `names` is one insert or several separated by commas (case does not matter); a CHASE runs
* across them in the order given, one lit at a time. `period_ms` 0 takes the pattern's own
* (blink 500, pulse 1600, chase 150 a step). Each returns how many inserts it held or released;
* 0 when none of the names is in the port, or the port has no lamp lines (pm_can(PM_CAN_LAMPS)). */
#define PM_CAN_LAMPS 0x2000u /* pm_lamp_*: the port names inserts and the game's lamp layers */
#define PM_LAMP_SOLID 0
#define PM_LAMP_BLINK 1 /* on half the period, off half */
#define PM_LAMP_PULSE 2 /* breathes between dim and full */
#define PM_LAMP_CHASE 3 /* one insert of the set at a time, stepping every period */
#define PM_RGB(r, g, b) ((((unsigned)(r) & 255u) << 16) | (((unsigned)(g) & 255u) << 8) | ((unsigned)(b) & 255u))
int pm_lamp_count(void); /* how many inserts the port names */
const char *pm_lamp_at(int i, uint64_t *shots); /* the i-th insert's name, and its shots */
int pm_lamp_find(const char *name); /* its index, or -1 */
int pm_lamp_xy(int i, int *x, int *y); /* its place on the playfield picture (the port's
"at X,Y"; x 0-300 across, y 0-600 down); 0 = not placed */
int pm_lamp_paint(int i, unsigned rgb); /* hold ONE insert solid in rgb, quietly: a light
show paints them all every tick, only changes reach
the game; release them as usual */
int pm_lamp_set(const char *names, unsigned rgb, int pattern, unsigned period_ms);
int pm_lamp_shot(uint64_t shots, unsigned rgb, int pattern, unsigned period_ms); /* a shot's inserts */
int pm_lamp_all(unsigned rgb, int pattern, unsigned period_ms); /* every insert the port names */
int pm_lamp_release(const char *names);
int pm_lamp_release_shot(uint64_t shots);
int pm_lamp_release_all(void); /* every insert THIS mode holds */
/* PAD-415: a hit answers at once. The inserts of `shots` strobe in `rgb` (60 ms on, 60 off) for `ms` (0 = 480; at
* most 2000) over whatever the mode holds them in, then show it again; one the mode does not hold is held for the
* strobe only and handed back to the game after it, and one the mode releases while it strobes finishes the strobe
* first. Returns how many inserts strobe (0: none tied to those shots, or no PM_CAN_LAMPS). */
int pm_lamp_flash(uint64_t shots, unsigned rgb, unsigned ms);
/* This mode's layer priority, 1-255 (255 when never called): a game show above it covers its
* inserts. The inserts it holds move to the new layer. Returns the priority now in force. */
int pm_lamp_priority(unsigned priority);
/* The game's lamp layers right now, bottom to top: each one's priority, with 0x100 added for a
* layer of ours. Returns how many there are (up to max), or -1 without the port's layer list. */
int pm_lamp_layers(unsigned *priorities, int max);
/* ---- the display --------------------------------------------------------------------
* A SCREEN of your own is a node the build added to the game's "hud" scene (the Modes
* tab does this). For a mode in the card project's folder modes/<folder>/ its names are
* "PadMode_<folder>_Screen" and "PadMode_<folder>_Screen.PadMode_<folder>_Screen_Words"
* (MODE_SDK.md, "Screens"). `scene` is a role the port names
* ("hud") or a 40-hex scene id. A node is drawn only while its timeline AND this flag
* allow it, and a built screen is VISIBLE by default - so find it and hide it. A scene
* can load after your init runs: pm_node returns 0 until it has, so keep trying from
* your tick (twice a second is plenty) until it is found. */
const char *pm_scene_id(const char *role); /* the port's scene for a role */
void *pm_node(const char *scene, const char *path); /* a picture/group node, or 0 */
void *pm_text(const char *scene, const char *path); /* a text node, or 0 */
void pm_show(void *node, int on);
void pm_set_text(void *text, const char *words);
/* ---- clips --------------------------------------------------------------------------
* A full-screen video from the game's in-game video bank, by name - a stock clip, or one
* the build added. The runtime keeps drawing it every frame until it ends. */
int pm_clip(const char *name); /* 1 if the game started it */
int pm_clip_playing(void);
void pm_clip_stop(void);
/* ---- the backdrop: a clip BEHIND the HUD (hud-layers) ---------------------------------
* The game's own battles play their clip in the main-play background's place, with the score
* panel, the top bar and the timers drawn over it. pm_backdrop does the same for a mode: the clip
* (a stock one, or one the build added) loops in the city's place until the mode ends it (0 or
* ""), pm_end, or the ball ends. The game's own framed awards (the Maser) still play in that
* place and the loop comes back after them. A full-screen clip of yours (pm_clip, over
* everything, the HUD too) is played first when both are asked for: the loop waits for it. */
#define PM_CAN_BACKDROP 0x20000u /* pm_backdrop / pm_backdrop_once */
int pm_backdrop(const char *name); /* 1 = asked for; 0 = this port cannot */
int pm_backdrop_once(const char *name); /* play this once in the loop's place, then the loop again */
int pm_backdrop_showing(void); /* 1 while the backdrop is on the glass */
/* ---- the game's own message screens (optional, title specific) --------------------- */
int pm_message_set(unsigned id, const char *words); /* show `words` wherever id is shown */
void pm_message_restore(unsigned id);
int pm_award_screen(unsigned type, unsigned message_id, uint64_t value);
/* ---- anything else a port carries ---------------------------------------------------- */
const char *pm_port_text(const char *name); /* a `text` line, or 0 */
long pm_port_value(const char *name, long fallback); /* a `value` line, or fallback */
/* ---- helpers --------------------------------------------------------------------------- */
void pm_log(const char *fmt, ...) __attribute__((format(printf, 1, 2))); /* /dump/mode.log */
unsigned long pm_ms(void); /* milliseconds since boot */
int pm_trigger(const char *name); /* 1, once, when the file /dump/<name> appears */
long pm_read_file(const char *path, char *buf, unsigned long cap); /* bytes read, or -1 */
int pm_trigger_text(const char *name, char *out, unsigned cap); /* ...and its first line */
void pm_commas(char *out, unsigned cap, uint64_t value); /* 1234567 -> "1,234,567" */
int pm_snprintf(char *out, unsigned long cap, const char *fmt, ...)
__attribute__((format(printf, 3, 4)));
/* ---- the game's own modes ------------------------------------------------------------
* Is one of the GAME'S modes going on (a battle, a multiball)? Asked through the game's
* own mode manager, with the queries the game itself uses (MODE_SDK.md, "The game's own
* modes"). "Running" means ACTIVE to the game: the mode is started for the player up, or
* its timer or start sequence is still live. A mode that should not stack on the game's
* own asks before it starts. */
#define PM_STOCK_ANY 0x1u /* any of the game's modes */
#define PM_STOCK_MULTIBALL 0x2u /* a multiball */
#define PM_STOCK_BATTLE 0x4u /* a battle (Godzilla's kaiju battles) */
/* Of the kinds asked for, the first one active (checked BATTLE, then MULTIBALL, then ANY);
* 0 = none of them; -1 = this port cannot tell (no stock queries for a kind asked) */
int pm_stock_mode_running(unsigned kinds);
const char *pm_stock_mode_what(unsigned kind); /* "a battle", "a multiball", "a stock mode" */
/* PAD-347: STACKING. Stern never shows two modes' words at once: one mode has the middle of the
* screen, the others keep to their badges at the edge. While one of the game's own modes runs for
* the player up, the middle is the game's: a mode that keeps running beside it moves its words out
* of the way (the title and instruction line, the counters along the top, a screen of its own) and
* keeps only what sits at the edges. pm_aside() says when: the kind running (PM_STOCK_BATTLE, then
* PM_STOCK_MULTIBALL, then PM_STOCK_ANY) or 0. Cheap to call every tick (the game is asked at most
* five times a second); 0 on a port that cannot tell, and outside a game. */
int pm_aside(void);
/* PAD-347 / PAD-363: ISOLATION. A mode may keep the game's own modes from STARTING while it runs: the
* start of each mode the port names (generated from the game program; never a multiball) is refused at its
* entry, so that mode never begins and the rule that asked carries on. Which ones: the mode's own list
* (pm_block_list: n of the game's mode ids, 0-127 - the port's `text block_name_<id>` names them), or the
* port's checked defaults (`text block_default <ids>`). On Godzilla a blocking mode also keeps a battle from
* being lit or its select screen from opening at the scoop. 1 = blocking (0 to stop); ends by itself with the
* mode; 0 when the port cannot. pm_block_list is called before pm_block_game_modes; n = 0: the defaults again.
* An id the port does not name is left out. */
#define PM_CAN_BLOCK_GAME 0x40000u /* pm_block_game_modes, pm_block_list */
int pm_block_game_modes(int on);
int pm_block_list(const unsigned char *ids, int n);
/* PAD-398: while a mode of ours blocks, every rule of the game's the port names (`site block_rule_<n>`, `text
* block_rule_name_<n>`: Godzilla's Destruction Jackpot, its building locks, the bridge, the cities...) is shown
* no shots - nothing of the game's lights, locks, counts or awards - except the ones the mode KEEPS: n indices
* <n> of those lines. Called from the mode's own files as they are read (a mode file's `keep_rules`, a code
* mode's assets file); it holds for the calling mode from its next pm_block_game_modes(1). 0 when the port
* cannot block. */
int pm_block_rules_keep(const unsigned char *ns, int n);
int pm_block_rules_keep_names(const char *names); /* the same by name: "Destruction Jackpot, Bridge" */
/* ---- events -------------------------------------------------------------------------------
* The game's rules talk through numbered EVENTS (a ball started, a multiball started, the
* skill shot was made). The port names the ones measured for its build (`event` lines,
* MODE_SDK.md "Events"); a mode's .event callback gets each named one as it fires:
*
* static void on_event(unsigned id) { if ((int)id == pm_event("ball_start")) ... }
*
* Only events the port names are delivered, once per firing, on the tick thread. */
int pm_event(const char *name); /* the named event's id, or -1 if this port has none */
const char *pm_event_name(unsigned id); /* the port's name for an id, or 0 */
/* ---- the battle roster (item 146; title specific: Godzilla) ---------------------------
* Godzilla's battles are picked on its BATTLE SELECTION screen from a roster of seven
* slots (0 Ebirah, 1 Titanosaurus, 2 Gigan, 3 Megalon, 4 King Ghidorah, 5 Megalon & Gigan,
* 6 King Ghidorah & Gigan). A mode can CLAIM one slot: when the player picks it, the
* game's battle for that slot does not start and `on_pick(slot)` runs instead, on the
* game's thread, at the moment the battle would have started. on_pick returns 1 when it
* took the pick (the battle is skipped) and 0 to let the game's own battle run.
* The roster is a fixed-size table in the game's code: a mode takes a slot's place, it
* cannot add an eighth. The slot's name and art on the selection screen are the card
* build's job (MODE_SDK.md, "A monster in the roster"). */
#define PM_CAN_ROSTER 0x0100u /* pm_roster_claim (0x0080 is item 147's PM_CAN_EVENTS) */
int pm_roster_slots(void); /* 0 when the port has no roster */
int pm_roster_claim(unsigned slot, int (*on_pick)(unsigned slot)); /* 1 = claimed */
void pm_roster_release(unsigned slot);
/* When a mode started by a pick ends, call this: the battle rule then does what it does when one
* of the game's battles stops (the ramps light again, so another battle can be qualified). Until
* then the ramps stay dark and the scoop opens nothing, as during a battle of the game's own.
* It acts only on a pick a mode took and has not given back yet, and only for the player who picked:
* called while another player is up it waits until that player is up again, and after the game has
* ended it does nothing (a new game lights every player's ramps). A mode that never calls it gets it
* called at the end of the ball its pick was taken in. */
void pm_roster_done(void);
/* The slot's spoken name. When the cursor lands on a slot the selection screen plays that slot's
* callout (Godzilla: slot 0 says "Ebirah!"). A claimed slot is SILENT, so the old monster's name is
* never heard over the new art; this sets a sound request id of the mode's own for the slot it holds
* (0 = silent), and releasing the slot puts the game's back. 1 = written; 0 when the caller does not
* hold the slot or the port has no `data roster_screen_table`. */
int pm_roster_callout(unsigned slot, unsigned id);
/* ---- display priority (item 154 display; MODE_SDK.md "Display priority") ------------------
* The game decides what is on its screen with PRIORITIES, on two levels: its DISPLAY EFFECTS
* (152 on Godzilla, each with a priority 1-255: BATTLE IS LIT 177, a multiball's jackpot 184,
* the tilt warning 241), one at a time, a higher one taking over and a lower one waiting; and,
* inside its layered-display effect (priority 1), its LAYERED displays (the shot awards: LOOPS,
* the Maser and powerline clips; the mode starts and totals), ranked the same way among
* themselves. pm_display_priority makes your RUNNING mode, to that arbitration, a display of
* `priority` (1-255, the display-effect scale): until you call it with 0, or your mode ends,
* - a display effect of the game's comes through only when its own priority beats `priority`
* (the battle select screen 196, the tilt warning 241); a lower one waits (BATTLE IS LIT
* 177), or is refused, as it would be behind one of the game's own effects;
* - a layered display the game flags as a MODE START or TOTAL counts as one of the game's mode
* displays (priority 184): it comes through when that beats yours;
* - any other FULL-SCREEN layered display (LOOPS, combos, bonus) waits;
* - a FRAMED layered display (the Maser, powerline, building clips, drawn inside the score
* frame, under your screen) plays as the game layers it, except while your own clip plays.
* A display that comes through covers your screen for its length; your screen is in view again
* when it ends (nothing to call). Your clip stops being drawn the moment the game plays another
* clip. 1 = held; 0 = the port has no display arbitration (PM_CAN_DISPLAY_PRIORITY), or your
* mode is not the one running. */
#define PM_CAN_DISPLAY_PRIORITY 0x4000u /* pm_display_priority (0x0100 is the roster's) */
int pm_display_priority(unsigned priority);
/* 1 while a display of the game's that beat your priority has the screen (your screen is
* under it); 0 otherwise, and with no priority held. */
int pm_display_covered(void);
/* Your mode is over (as pm_end: another may begin at once) but its ENDING - a full-screen clip, its
* total - should keep the screen: the display priority it holds stays for `ms` more, so an award of
* the game's that waited through the mode does not take the one video surface from the ending clip.
* The hold goes when the time is up, another mode begins, the ball or the game ends, or you call
* pm_display_priority(0). 1 = ended (the hold kept if one was held); 0 = your mode was not running. */
int pm_end_holding(unsigned ms);
/* ---- the game's own rules: their shot handlers (item 160; MODE_SDK.md "Counts as") ----------
* A rule the game shipped with (Godzilla's battle vs Ebirah, its tank attack multiball) is a
* compiled object; the port names it (`rule <id> <vtable> <label>`) with the manager's get and
* the slot numbers of its build. Its SHOT HANDLER tests the raw shot mask for fixed bits, so a
* shot it does not know can only count for it by ARRIVING as the bit it tests. The runtime
* wraps that vtable slot (the object's vtable is checked against the port first) and, from
* stock.cfg beside the mode files (`counts_as <rule> <shot> -> <shot>`, re-read twice a
* second), REPLACES a mapped dispatch with the target bit while that bit is lit in the rule's
* own per-player mask - never ORed in, never once the rule has cleared it. The stand-in's
* inserts are held (pm_lamp_*) while the target is lit. An empty table plays every rule stock.
*
* From C a mode may add rows, or take a rule's handler over entirely (item 161): a hook sees
* every shot before the game's handler, may change *shot, and returns 1 to let the game's
* handler run with it or 0 to keep it from running at all. Every call answers 0 (or -1) on a
* port without `rule` lines (pm_can(PM_CAN_STOCK_RULES)). */
#define PM_CAN_STOCK_RULES 0x8000u /* the port names rules, the manager's get and the shot slot */
typedef int (*pm_stock_shot_fn)(unsigned rule, uint64_t *shot, void *object);
int pm_stock_rule_count(void); /* rules the port names */
int pm_stock_rule_at(int i, unsigned *id, const char **label); /* the i-th; 1 = filled */
void *pm_stock_rule_object(unsigned rule); /* the rule's object; 0 until the manager is built */
int pm_stock_rule_active(unsigned rule); /* 1 / 0 for the player up; -1 unknown */
uint64_t pm_stock_rule_field(unsigned rule); /* the rule's lit mask for the player up */
int pm_stock_rule_hook(unsigned rule, pm_stock_shot_fn fn); /* 1 = the handler is wrapped and yours */
void pm_stock_rule_unhook(unsigned rule); /* the wrap stays; it passes through again */
/* a counts-as row from C (kept across stock.cfg reloads); to = 0 removes the row for `from`.
* `to` must be ONE bit outside `from`. 1 = the table now holds it. */
int pm_stock_counts_as(unsigned rule, uint64_t from, uint64_t to);
/* ---- a multiball of your own (item 167; MODE_SDK.md "A multiball of your own") ---------------
* Every Spike 2 build shares the framework's ball code, and one call of it is how the game's
* own multiballs put balls in play: "serve balls until `balls` are in play, with a ball save".
* The port names it (`site multiball_serve`, found on every build by its code) beside the
* framework's count of the balls in play (`site balls_in_play`). A mode calls it when it
* starts; the game's trough and auto-launcher do the serving, its ball saver hands a drained
* ball back for `ballsave_s` seconds, and the game's own end of ball comes only when the LAST
* ball drains (the events, `.ball_end`, the bonus: all as the game's multiballs have them).
* The game never knows the multiball is yours: its mode manager runs nothing for it, so its
* own multiball display, jackpots and music stay off - the mode's screen, clip, sounds and
* lights are what the player sees and hears.
* pm_multiball_start: 1 = the game is serving; 0 = it refused (no game in play, a tilt, the
* port lacks the call). `balls` is the TOTAL wanted in play, 2 to 6, cut to the balls the
* machine has. pm_multiball_add asks for `n` more than are in play now (an add-a-ball).
* pm_balls_in_play: the framework's count (2 or more during a multiball; while balls are being
* served it answers the balls asked for); -1 when the port has no balls_in_play site. A mode
* ends its multiball itself: when the count has been 1 for a couple of seconds after the ball
* save is over (mode_file.c does exactly that). */
#define PM_CAN_MULTIBALL 0x10000u /* pm_multiball_start / pm_multiball_add (and pm_balls_in_play) */
int pm_multiball_start(unsigned balls, unsigned ballsave_s);
int pm_multiball_add(unsigned n, unsigned ballsave_s);
int pm_balls_in_play(void);
/* A ball save on its own, with no multiball (MODE_SDK.md "A ball save of your own"). The game's
* own one-ball saves (Godzilla's Planet X hurry-up: 10 s; its adjustment-timed mode-start save)
* go through the same framework call as a multiball, asking for no more balls than are in play,
* with a ball save: "serve balls until the count in play are in play, with a ball save". So
* pm_ball_save asks exactly that: for `seconds` (1-120) a drained ball is served back and the
* ball does not end. It needs what a multiball needs (PM_CAN_MULTIBALL). 1 = the save is on;
* 0 = the game refused (no game in play, a tilt) or the port lacks the call. During the game's
* own multiball it only lengthens that multiball's save (the framework keeps the longer). */
int pm_ball_save(unsigned seconds);
/* ---- the magnet (PAD-381; MODE_SDK.md "The magnet") ------------------------------------------
* A grab with the playfield magnet, for `ms` milliseconds. You ask for a time and nothing else:
* the powers are the operator's own magnet adjustments (draw power for the draw time, then hold
* power), and the runtime decides whether you may and lets go by itself. It is a coil on 48 V, so
* the limits are the runtime's and cannot be raised:
* - `ms` is clamped to 100..5000, the draw time included. The whole grab goes to the board as ONE
* command that ends by itself, and is never re-sent: if your mode, the runtime or the game
* stops, the magnet still lets go when that time is up.
* - only while your mode runs, in a game (not attract, not tilted), with the magnet not disabled
* in the settings, while none of the game's own magnet sequences runs, one grab at a time, 3 s
* from the end of one grab to the start of the next, at most 6 grabs a minute.
* - the runtime lets go at the grab's end, when you call pm_magnet_release, when your mode ends,
* when the ball ends, and when the game ends or tilts. If the game starts a magnet sequence of
* its own while you hold, yours steps aside and the game drives the magnet.
* - the grab runs as a game process of the runtime's that takes control of the magnet, as the
* game's own grabs do (without it the game switches the magnet off at once); if the game ends
* that process (a drain, a tilt), the magnet goes off the game's own way.
* pm_magnet_grab: 1 = grabbing; 0 = refused (the reason is in mode.log). Godzilla only (the port's
* `site coil_fire` and `value magnet_dev`); PM_CAN_COILS says whether this game has it. */
#define PM_CAN_COILS 0x200000u /* pm_magnet_grab / pm_magnet_release / pm_magnet_holding */
int pm_magnet_grab(unsigned ms);
void pm_magnet_release(void);
int pm_magnet_holding(void);
/* PAD-381: the same hold for any coil of that kind the port names (`text held_coils`; Godzilla Premium/LE:
* "magnet" the Godzilla magnet, "mg_magnet" the Mechagodzilla magnet, "bridge" the bridge diverter), with
* the same limits each: one command from a process of the runtime's that controls the coil, the coil's own
* powers, at most 5000 ms, 3 s between holds, 6 a minute, never while the game uses it.
* pm_magnet_grab(ms) is pm_coil_hold("magnet", ms). pm_coil_known: 1 when this game has that coil. */
int pm_coil_hold(const char *name, unsigned ms);
void pm_coil_release(const char *name);
int pm_coil_holding(const char *name);
int pm_coil_known(const char *name);
/* ---- the scoop (PAD-381; MODE_SDK.md "The scoop") ---------------------------------------------
* Hold a ball that lands in the scoop for `ms` milliseconds while your mode runs, then let the game
* kick it out. You ask for a time and nothing else: the kick-out is the game's own, at the operator's
* SCOOP KICK POWER, with the game's own retries, and nothing here fires a coil. The game's own use of
* a landing ball (its rules, a select screen) always comes first; your hold starts when it is done.
* - `ms` is 100..10000; 0 = no hold (and a ball held now goes). It applies to every ball that
* settles in the scoop while your mode runs.
* - the hold ends at its time, on pm_scoop_release, when your mode ends, and when the game ends or
* tilts.
* pm_scoop_hold: 1 = set; 0 = not the running mode, or no PM_CAN_SCOOP on this game (the port's
* `site scoop_handler`, `data scoop_slot`, `value scoop_event`). pm_scoop_holding: 1 while a ball is held. */
#define PM_CAN_SCOOP 0x100000u /* pm_scoop_hold / pm_scoop_release / pm_scoop_holding */
int pm_scoop_hold(unsigned ms);
void pm_scoop_release(void);
int pm_scoop_holding(void);
/* ---- the shield platform (PAD-379) ---------------------------------------------------------------
* Godzilla Premium/LE carries its three shield targets on a platform a motor turns. AWAY: the spinner
* side faces the player and the shields cannot be reached from the flippers - the game's home, and
* where most of its battles play. TOWARD: the shield targets face the flippers (parts of the game's
* Mechagodzilla multiball). pm_shield asks the game's own motor to turn the platform: 1 = turning (it
* takes about a second to get there) or already there; 0 = refused (an operator switched the motor off
* in the adjustments) or not on this machine (a Pro: its shield targets are fixed and always face the
* player; or the port has no shield lines). pm_shield_position says where it
* is now: PM_SHIELD_AWAY or PM_SHIELD_TOWARD once it has stopped there, 0 while it turns (or before
* the game has found it), -1 when there is no platform. A mode that turns it toward the player turns
* it back AWAY when it ends - unless the game's own mode began, which then has the platform
* (intricate_kit.h: kit_shields_in / kit_shields_out). An operator who switched the motor off in the
* adjustments keeps it AWAY: pm_shield says 0 and the position stays AWAY.
* PAD-392: the limits are the runtime's and cannot be raised - only while your mode runs, in a game, and
* never while one of the game's own modes or multiballs runs (the game has the platform then); 1.5 s from
* one move's start to the next, at most 12 moves a minute (0 = refused, the reason in mode.log). When
* your mode ends, the ball ends, or the game ends or tilts, the runtime turns the platform back where it
* was before your first move - unless the game has since turned it itself. PAD-409: from your first move
* until then, the game's own background return of the platform to its resting place (every ~3 s) waits, so
* it stays where you put it; the game's ball search and its own modes still move it.
* pm_shield_keep turns it and KEEPS it there while your mode runs: the game's ball search swings the
* platform and leaves it AWAY, so a platform found resting elsewhere for 1.5 s is turned back, within the
* same limits. It is kept only while the game's own shield feature (on Godzilla: Mechagodzilla Shield)
* sees no shots - your mode blocks the game's rules (PAD-398, the default) and does not keep that one
* counting. While that feature counts, the game turns the platform back about 2 s after every move, and
* pm_shield_keep turns it once and leaves it to the game. 1 = keeping it (0 = stop keeping; it stays where
* it is); 0 = no platform or your mode is not running. */
#define PM_CAN_SHIELD 0x80000u /* pm_shield / pm_shield_position / pm_shield_keep */
#define PM_SHIELD_AWAY 1
#define PM_SHIELD_TOWARD 2
int pm_shield(int where);
int pm_shield_position(void);
int pm_shield_keep(int where);
/* ---- the building (PAD-393) -----------------------------------------------------------------------
* Godzilla Premium/LE's building rides a stepper. pm_building asks the game to move it to a FLOOR, 0 (where
* the game keeps it at rest, beside its home switch) .. 3 (the furthest the game sends it), at the operator's own building speed settings: the
* floors are the game's own, so nothing a mode asks can drive it past the travel the game itself uses.
* The limits are the runtime's and cannot be raised:
* - only while your mode runs, in a game (not attract, not tilted); never while the building is busy
* (moving, homing, or one of the game's own building sequences runs), switched off in the settings
* (BUILDING STEPPER DISABLED) or faulted; 3 s from one move's start to the next; at most 6 a minute.
* - when your mode ends, the ball ends, or the game ends or tilts, the runtime puts the building back
* on the floor it was at before your first move - unless the game has since moved it itself.
* pm_building: 1 = sent (or already there); 0 = refused (the reason is in mode.log) or no building on this
* machine (a Pro, or a port without the building lines). pm_building_floor: the floor it is stopped at,
* -2 while it moves (or before the game has homed it), -1 when there is no building. */
#define PM_CAN_BUILDING 0x400000u /* pm_building / pm_building_floor */
int pm_building(int floor);
int pm_building_floor(void);
/* ---- the game's own light shows (PAD-411) -----------------------------------------------------------
* The game's playfield shows (a jackpot's, a multiball's start, the attract run's) are processes of its own:
* each makes a lamp group, plays its layered light-language commands (several effects over the game's light
* sets, with their delays and loops), sleeps through the show and stops every effect it started. The port
* names them (`site show_<n>`, `text show_name_<n>`); pm_game_show(n) plays show n exactly that way - the
* game's own process body, run as a process of the runtime's (`value show_proc`, an id the game never uses),
* so its timing, its colours and its clean-up are the game's. One at a time: a new one replaces one still
* playing. 1 = playing; 0 = refused (only while your mode runs, or in the first 2 s after it ended - its
* ending's show; in a game; no such show; the reason in mode.log). A show keeps playing after your mode ends (an ending's show is meant to), but never longer than
* 20 s - some of the game's run until stopped - and the ball ending stops it as it stops every process of the
* game's. pm_game_show_stop ends it now: the game's own exit hook takes its lights away. pm_game_show_playing:
* 1 while one plays. pm_game_shows: how many the port names (0 = none on this game). */
#define PM_CAN_GAME_SHOWS 0x800000u /* pm_game_show / pm_game_show_stop / pm_game_show_playing / pm_game_shows */
int pm_game_show(int n);
int pm_game_show_named(const char *name); /* the same by the port's name for it (`text show_name_<n>`, any case) */
void pm_game_show_stop(void);
int pm_game_show_playing(void);
int pm_game_shows(void);
/* ---- the shaker (PAD-414, PAD-474; MODE_SDK.md "The shaker") ---------------------------------------
* Godzilla Premium/LE can have a shaker motor in the cabinet (optional on a Premium, fitted on an LE), and so
* can every other Spike 2 title with one (PAD-474: the port's shaker lines, read by shaker_lines.py).
* pm_shake shakes it for `ms` at a `strength` through the game's own shake call - the one every shake of the
* game's goes through - so the operator's SHAKER MOTOR setting always applies (off = nothing; a lower
* setting cuts every shake shorter) and the board stops each shake by itself when its time is up. (Where the
* game's call takes a kind of shake, not a time - Aerosmith, Guardians, Elvira, Sword of Rage - the runtime
* sends the shake the way that call sends its own: the setting first, then one timed command at its power.)
* `strength` is one the game uses: PM_SHAKE_HARD (0, its battle hits and jackpots) .. PM_SHAKE_SOFT (3,
* its long rumbles); most titles shake at one strength only, PM_SHAKE_HARD. The limits are the runtime's and
* cannot be raised:
* - `ms` is at least 100 and never longer than the game's own longest shake at that strength (Godzilla:
* 1000 ms at 0 and 1, 5000 ms at 2 and 3; the port's `text shake_max_ms`).
* - only while your mode runs, in a game (not attract, not tilted); never with the shaker set to off (or
* none fitted), while one of the game's own shakes runs, or while one of yours does; at most 20 shakes
* and 15 s of shaking in any minute.
* - the runtime stops your shake when your mode ends, the ball ends, or the game ends or tilts (and on
* pm_shake_stop) - unless the game has since asked for a shake of its own, which it leaves to run.
* pm_shake_game plays one of the GAME's own shakes by name, as the program has it (the port's `text
* shake_<name>` lines): on Godzilla "hit" (200 ms, hard: a battle's shot), "big_hit" (334 ms), "jackpot"
* (500 ms, hard: every super jackpot), "rumble" (3000 ms, soft: O2 Destroyer), "multiball_start" (five
* shakes over 4 s: the Godzilla Multiball start); on the other titles the one the game calls most in each
* length: "tap", "short", "medium", "long", "rumble". Same limits; its steps count as one shake.
* pm_shake / pm_shake_game: 1 = shaking; 0 = refused (the reason is in mode.log) or no shaker on this
* game's port (PM_CAN_SHAKER). pm_shaking: 1 while a shake of yours runs. */
#define PM_CAN_SHAKER 0x1000000u /* pm_shake / pm_shake_game / pm_shake_stop / pm_shaking / pm_shake_outlast */
#define PM_SHAKE_HARD 0
#define PM_SHAKE_STRONG 1
#define PM_SHAKE_MEDIUM 2
#define PM_SHAKE_SOFT 3
int pm_shake(unsigned ms, unsigned strength);
int pm_shake_game(const char *name);
void pm_shake_stop(void);
int pm_shaking(void);
/* A shake as your mode ENDS: call pm_shake or pm_shake_game, then pm_shake_outlast, then end the mode - the
* end then leaves that one shake to run out (its own length); a ball end, game end or tilt still stops it. */
void pm_shake_outlast(void);
/* ---- the game's own mini-wizards (PAD-436; MODE_SDK.md "The game's own mini-wizards") ---------------------
* James Bond LE 1.06 has four, each with its insert on the Right ramp: 1 Chaos at Crab Key (Dr. No), 2 Ahoy
* Mr. Bond (From Russia With Love), 3 Goldfinger's Jackpot, 4 Duel on the Disco Volante (Thunderball). The game
* lights them when a part (henchman, villain, Q Branch, gadget) is collected in all six films, and the Right
* ramp starts the one selected. pm_game_wizard hands the player up one of them, the game's own way:
* PM_WIZARD_LIGHT select it and light it: the game's start shot (the Right ramp) starts it
* PM_WIZARD_START the same, then the game's own start of it at once - if the game would start one now (none
* running, nothing of its own in the way: its own check); otherwise it stays lit and the
* runtime starts it the moment the game would, that ball (then the start shot does)
* Until the game starts it, it is the only one lit and the one selected (PAD-457): the game's own lighting and
* its selection shots cannot hand the start shot another; what the game lit itself is lit again once it started.
* Several handed over start in the order given. It runs the game's mode: its shots, lights, screens, sounds and
* award, as when the game starts it. No mode of
* yours has to be running, and one that is keeps running beside it. A wizard the player has played this game
* plays again. PM_WIZARD_LIT (1): lit for the start shot; PM_WIZARD_STARTED (2); 0: not on this game, no game,
* no such wizard (mode.log says which). pm_game_wizard_named: the same by the port's name for it (`text
* wizard_name_<n>`, any case). pm_game_wizards: how many the port names (0 = none on this game).
* PAD-473, the C++ titles (MODE_SDK.md "The game's own mini-wizards on the other titles"): the port names each one's
* mode object, and PM_WIZARD_START calls its own START as the game's rules do - at once, or the moment none of the
* game's own modes is in its way (nor a mode of yours holding them off), that ball (PM_WIZARD_WAITING, 3, until
* then). PM_WIZARD_LIGHT starts the game's own mode that lights it for a start shot where it has one, and is a start
* where it has none. The game's own rules still light and start them too. */
#define PM_CAN_GAME_WIZARDS 0x2000000u /* pm_game_wizard / pm_game_wizard_named / pm_game_wizards */
#define PM_WIZARD_LIGHT 0
#define PM_WIZARD_START 1
#define PM_WIZARD_LIT 1
#define PM_WIZARD_STARTED 2
#define PM_WIZARD_WAITING 3 /* PAD-473, the C++ titles: to start the moment nothing of the game's is in its way */
int pm_game_wizard(int n, int how);
int pm_game_wizard_named(const char *name, int how);
int pm_game_wizards(void);
/* PAD-457: claim one - this card's modes hand it out, so the game's own lighting (a part in all six films) leaves it
* unlit from now on and it lights only when a mode hands it over; the game still lights the unclaimed ones. A mode
* that hands one over claims it as it loads (a mode file's `game_wizard`, a blocks mode's init). 1 = claimed, 0 = not
* on this game, no such wizard, or the port names no `site wizard_light` (then the game lights it too). */
int pm_game_wizard_claim(int n);
int pm_game_wizard_claim_named(const char *name);
/* PAD-503: the game's own check before it starts one of its mini-wizards (Bond: none of them running, no henchman,
* villain or Q Branch mode, no Victory Laps - the port's `site wizard_way`). 1 = nothing of the game's is in the way,
* 0 = something is, -1 = no game, or this game's port names no such check. From a tick, shot or event callback (the
* game's own thread). A blocks mode lit at a shot (Light the mode at) waits on it, as the game's own mini-wizards do. */
int pm_game_wizard_way(void);
/* PAD-503: a mode lit at a shot claims it, renewed every tick while it would start there now (the player up's, nothing
* of its own or the game's in its way; 0 = no claim). A hit on a claimed shot's switch holds the game's own mode starts
* off (the ones the port's block_start lines hook) while the game handles that switch, so the shot starts the lit mode
* and not, on James Bond's Right ramp, one of the game's henchman or villain modes. A claim not renewed for 250 ms is
* gone; one of your modes beginning ends a hold. Shots from switches only (the port's switch lines). 1 = claimed. */
int pm_lit_claim(uint64_t shots);
/* ---- the game's own status bar (PAD-521; MODE_SDK.md "The game's status bar") ------------------------------------
* James Bond LE shows each of its own modes in a coloured bar under the TV: the mode's name, the bar in the mode's
* colour, and its clock as a progress wipe draining along it (Goldfinger's Jackpot: gold). pm_status_bar puts a mode
* of yours there the same way: `words` (shown in the game's own lettering, so capitals), `rgb` (PM_RGB) and
* `percent` 0-100 of the wipe (-1 = no wipe). Call it again to change any of them (cheap when nothing changed).
* NULL or "" gives the bar back as it was. The runtime keeps it up while you hold it (a game display that hides it
* is answered on the next tick) and gives it back by itself when the game is over. 1 = shown; 0 = this game's port
* names no status bar (pm_can(PM_CAN_STATUS_BAR)), or the game has none yet. */
#define PM_CAN_STATUS_BAR 0x4000000u /* pm_status_bar */
int pm_status_bar(const char *words, unsigned rgb, int percent);
#endif
+9426
View File
File diff suppressed because it is too large Load Diff
+192
View File
@@ -0,0 +1,192 @@
/* pad_stock.h - rewrite the SHOT LOGIC of one of the game's OWN rules in C (item 161).
*
* Implemented in pad_mode_runtime.c ("STOCK RULES IN C") on item 160's per-rule wrap of the shot
* handler's vtable slot. Every address, offset, slot number and id below comes from the PORT
* (`site` / `data` / `value` lines; MODE_SDK.md, "Rewriting a stock rule's shot logic"); nothing in
* this header is an address, and a call whose port line is missing does nothing and says so once.
*
* WHAT A STOCK RULE IS. The game's own modes (a battle, a multiball) are compiled C++ objects, one
* per rule, each with a vtable (MODE_SDK.md, "The game's own modes"). Three of its virtuals matter
* here: START (v[8]), STOP(reason) (v[11]) and the SHOT HANDLER (v[42] on Premium 1.16, v[41] on
* Pro 1.15; the port's `stock_slot_*` values say which). The handler is called with every shot the
* game dispatches while the rule is active: (this, _, shot lo, shot hi, [sp] factor). It reads and
* writes the rule's own fields (its lit mask, its counters) and calls the engine (awards, shows,
* events, the stop). Rewriting a rule's shot logic = registering a C function that runs INSTEAD
* of that virtual, and calling the same engine pieces through the port.
*
* WHAT IT IS NOT. The rule's START, its timer, its display layers, its lamps and its ending are
* still the game's own. A rewrite changes what a shot DOES; the battle still starts from the
* select screen, still times out, and still ends with the game's own screens.
*
* THE RULES (pad_mode.h's still apply: no blocking, no malloc, static state):
* - A handler runs INSIDE the game's dispatch, on the game thread, with the rule object live.
* Return quickly; never start a mode of ours from it.
* - Reach the rule's fields only through the accessors here; every one reads its offset from
* the port, and returns 0 / does nothing when the port has no line for it.
* - `pm_stock_call_original` is the ONLY way to run the game's handler from yours; the wrap
* never re-enters your handler for that call, and no counts-as row is applied to it.
* - With no record registered for a rule, and no counts-as row, nothing is wrapped: the game
* runs stock (the wrap is installed only for what a card asks for).
*
* WHAT IS PROVEN. See MODE_SDK.md's section for the emulator runs. Item 158 proved (emulator,
* Premium 1.16) the facts it rests on: the slot numbers, the field and counter offsets, that a
* wrapped v[42] sees every shot with its factor, that caward_add / show_start / game_event are what
* the handlers call, and that the final blow ends the battle with reason 1 from inside the handler.
*/
#ifndef PAD_STOCK_H
#define PAD_STOCK_H
#include "pad_mode.h"
/* ---- a stock rule, and the player it acts for -------------------------------------------- */
struct pm_stock_rule; /* opaque: the runtime's handle on one rule object */
/* The rule with this id, or 0 when the port cannot reach the manager (`site stock_rule_get`,
* `data stock_mode_manager`, a `rule` line), the manager has not built it yet, or its object is not
* the port's. Godzilla: 4 tank attack, 12 Ebirah (`value ebirah_rule`, `value tank_rule`). */
struct pm_stock_rule *pm_stock_rule(unsigned id);
unsigned pm_stock_rule_id(const struct pm_stock_rule *r);
/* The player the game's own handler would act for: the current player (1-4), as the handlers
* read it (the same byte pm_player() reads). 0 when no game. */
unsigned pm_stock_player(void);
/* ---- registering a handler ------------------------------------------------------------------
* Return PM_STOCK_DONE when your code handled the shot (the game's handler is NOT run for it),
* PM_STOCK_PASS to let the game's handler run as if you were not there (after item 160's counts-as
* rows, when the card has any). `factor` is the dispatch's stack word (1 from a switch; the
* game's own spot helpers pass their own). `shot` is the mask as dispatched: test bits, never
* compare the whole mask (a Big loop arrives as 0x1000000001; every switch also sends 0x1). */
#define PM_STOCK_PASS 0
#define PM_STOCK_DONE 1
typedef int (*pm_stock_shot_handler)(struct pm_stock_rule *rule, uint64_t shot, unsigned factor);
struct pm_stock_handler {
unsigned rule_id; /* the rule this replaces */
pm_stock_shot_handler shot; /* runs instead of the game's handler */
void (*started)(struct pm_stock_rule *rule); /* optional: after the game's START ran */
void (*stopped)(struct pm_stock_rule *rule, unsigned reason);/* optional: before the game's STOP runs (1 = won) */
const char *name; /* shown in the log */
const struct pm_mode *mode; /* optional: the mode the log lines and the lamp
holds made from the callbacks belong to */
};
/* The full form, and the one-liner: PM_STOCK_HANDLER(12, my_ebirah_shot). Both put the record in
* the `pm_stock` section the runtime walks from its tick (twice a second, until the manager has
* built the rule), like PM_REGISTER. One record per rule; a second is refused and said. */
#define PM_STOCK_RULE(h) \
static const struct pm_stock_handler *const pm__stock_##h \
__attribute__((used, section("pm_stock"))) = &(h)
#define PM_STOCK_HANDLER(id, fn) \
static const struct pm_stock_handler pm__stock_rec_##fn = { (id), (fn), 0, 0, #fn, 0 }; \
PM_STOCK_RULE(pm__stock_rec_##fn)
/* The game's own handler for this rule, with these arguments, as it was before the wrap; what it
* returns (r0:r1) is passed back. From inside a handler or a started/stopped callback of the rule. */
uint64_t pm_stock_call_original(struct pm_stock_rule *rule, uint64_t shot, unsigned factor);
/* ---- the rule's state, through the port ------------------------------------------------------
* The lit-shot mask the rule keeps per player: u64 at obj + `value stock_field` + 8 * player.
* What the game's own v[25] reports as lit for most rules (tank keeps its own, see below), and
* what the game's handler gates on (Ebirah: a shot with no lit bit does nothing). */
uint64_t pm_stock_field(struct pm_stock_rule *rule, unsigned player);
int pm_stock_field_set(struct pm_stock_rule *rule, unsigned player, uint64_t mask);
/* The rule's own answers: lit shots (v[stock_slot_lit]), active (v[stock_slot_active]; -1 unknown),
* running for a player (the byte at obj + `value stock_running_at` + player; -1 unknown). */
uint64_t pm_stock_lit(struct pm_stock_rule *rule);
int pm_stock_active(struct pm_stock_rule *rule);
int pm_stock_running(struct pm_stock_rule *rule, unsigned player);
/* A word of the rule's own, at an offset the port names: `at` is the result of pm_stock_at(),
* -1 when the port has no such line (every call below then fails with 0). A per-player word
* is at obj + at + 4 * player (the game's counters index from 1); a plain word at obj + at. */
long pm_stock_at(const char *value_name); /* pm_port_value(name, -1), said once when missing */
int pm_stock_pw_get(struct pm_stock_rule *rule, long at, unsigned player, int *out);
int pm_stock_pw_set(struct pm_stock_rule *rule, long at, unsigned player, int value);
int pm_stock_w_get(struct pm_stock_rule *rule, long at, int *out);
int pm_stock_w_set(struct pm_stock_rule *rule, long at, int value);
int pm_stock_b_get(struct pm_stock_rule *rule, long at, int *out); /* a byte */
/* ---- the engine pieces the handlers call --------------------------------------------------
* An AWARD, the way the stock handler pays one: caward_add(the rule's own award object at
* obj + `value stock_award_at`, 0, value, index 0). Returns what the engine says it added.
* pm_stock_award_to pays through ANOTHER rule's award object (Ebirah under King of the Monsters
* pays through rule 20's). pm_stock_build is caward_build on the rule's award (tank pays half a
* kill into it). Sites: caward_add, caward_build. */
uint64_t pm_stock_award(struct pm_stock_rule *rule, uint64_t value);
uint64_t pm_stock_award_to(unsigned rule_id, uint64_t value);
uint64_t pm_stock_build(struct pm_stock_rule *rule, uint64_t value);
/* A SHOW (a light show / display cue by id, `site show_start`): returns the show's record, which
* the stock handlers then fill (a colour, a lamp id, a count; the offsets are the show's, not
* the rule's), or 0. Ebirah shows 347 and 359 on a spin, 148 on a stage; tank shows 144 on the
* destroyed tank's insert. */
void *pm_stock_show(unsigned id);
/* A GAME EVENT (`site game_event`: the rule-to-rule broadcast the handlers post; Ebirah 19 on the
* final blow, tank 51 on a Maser hit) with the value the handlers pass (the award total). The
* trailing arguments the handlers pass (a word, then a u64) are sent as 0. */
int pm_stock_event(unsigned id, uint64_t value);
/* A display EVENT record (`site event_post_replacing`), what the stage screens and the stop
* displays post; returns the record or 0. pm_stock_event_cancel removes one by id (`site event_cancel`). */
void *pm_stock_display_event(unsigned id, unsigned handler, unsigned flags);
int pm_stock_event_cancel(unsigned id);
/* A callout or a sound: the ordinary pad_mode.h calls (pm_callout, pm_sound). */
/* STOP the rule the way its own code does: v[stock_slot_stop](reason), 1 = completed (won), 0 =
* not. pm_stock_start runs its START (for a test; the game's own way is the select screen). Both
* go through the vtable, so a probe's wrap of those slots logs them. */
int pm_stock_stop(struct pm_stock_rule *rule, int won);
int pm_stock_start(struct pm_stock_rule *rule);
/* ---- Godzilla: battle vs Ebirah (rule `value ebirah_rule`) -----------------------------------
* Its own state (item 158): three per-player spin counters (`value ebirah_spin_left_at`,
* `ebirah_spin_top_at`, `ebirah_spin_shield_at`; the ctor's defaults 15 / 40 / 15), a stage index
* (`ebirah_stage_index_at`, which 5M / 10M / 15M award is next), a per-player stage count
* (`ebirah_stage_count_at`), and the KOTM byte (`ebirah_kotm_at`: awards go to King of the
* Monsters while it runs). Its handler tests RAW shot bits (`value ebirah_spin_left_bit` 0x200,
* `ebirah_spin_top_bit` 0x2000, `ebirah_spin_shield_bit` 0x20000) and, once every counter is 0,
* the FINAL shot (`ebirah_final_shot` 0x40, the Pop bumper, or bit 42) with the field set to
* `ebirah_final_mask_lo/hi`. */
#define PM_EBIRAH_LEFT 0
#define PM_EBIRAH_TOP 1
#define PM_EBIRAH_SHIELD 2
int pm_ebirah_spins(struct pm_stock_rule *rule, int which, unsigned player); /* -1 = no line */
int pm_ebirah_spins_set(struct pm_stock_rule *rule, int which, unsigned player, int n);
uint64_t pm_ebirah_spin_bit(int which); /* 0 = no line */
/* A STAGE AWARD, exactly as the game gives one: sets that spinner's counter to 1, lights its bit,
* and runs the game's handler with the spinner's own bit. The game then pays the last spin, gives
* the 5M / 10M / 15M award in completion order, shows its award screen, posts its reminder
* event, and, when every counter is 0, writes the final mask itself. Returns 1 when it ran. */
int pm_ebirah_stage_award(struct pm_stock_rule *rule, int which);
/* The FINAL BLOW, exactly as the game gives one: sets the field to the final mask and runs the
* game's handler with the final shot. The game pays 25,000,000, the finishing bonus, shows the
* final-blow screen, posts its events and STOPS the battle as WON (v[11](1)): the battle's own
* ending plays. Returns 1 when it ran. Ebirah's port lines; another rule has no final blow. */
int pm_stock_final_blow(struct pm_stock_rule *rule);
/* The stage awards the game's own vector holds (`data ebirah_stage_awards`, `value
* ebirah_stage_award_count`): 5,000,000 / 10,000,000 / 15,000,000 on Godzilla; 0 = no line. */
uint64_t pm_ebirah_stage_value(unsigned index);
/* ---- Godzilla: tank attack multiball (rule `value tank_rule`) ---------------------------------
* Tank keeps no lit mask: its shots are TANK RECORDS (a vector at obj + `value tank_records_at`
* of `tank_record_size`-byte records: active byte at +`tank_record_active_at`, position mask at
* +`tank_record_pos_at`, previous position at +`tank_record_prev_at`, destination at
* +`tank_record_dest_at`, value at +`tank_record_value_at`), walking the PATH (`data tank_path`,
* `value tank_path_entries` 16-byte entries {u64 mask, u16 lamp, u16 id, u32}). Counters:
* destroyed (`tank_destroyed_at`), level per player (`tank_level_at`), the tank value per
* player (`tank_value_at`, u64), the Maser multiplier (`tank_multiplier_at`). Nothing of the
* tank's has run under a C handler yet (desk). */
struct pm_tank_record { int active; uint64_t pos, prev, dest, value; };
int pm_tank_records(struct pm_stock_rule *rule, struct pm_tank_record *out, int max); /* how many were filled (at most max) */
int pm_tank_destroyed(struct pm_stock_rule *rule); /* -1 = no line */
int pm_tank_level(struct pm_stock_rule *rule, unsigned player);
int pm_tank_maser_phase(struct pm_stock_rule *rule); /* the game's own test (`site tank_maser_phase`) */
/* The game's own pieces (sites tank_destroy, tank_seed_wave, tank_advance): destroy record
* `index` with credit (the destroyed count, the value step, a respawn), seed a new wave (level +
* 1, three tanks), advance every tank one step and spawn. Each returns 1 when the site is there
* and it ran. pm_tank_path_index is the first path entry that ANDs the shot (from the port's
* data), -1 when none. */
int pm_tank_destroy(struct pm_stock_rule *rule, int index, int credit);
int pm_tank_seed_wave(struct pm_stock_rule *rule);
int pm_tank_advance(struct pm_stock_rule *rule);
int pm_tank_path_index(struct pm_stock_rule *rule, uint64_t shot);
int pm_tank_path_entry(unsigned index, uint64_t *mask, unsigned *lamp); /* from the port's data */
#endif
+853
View File
@@ -0,0 +1,853 @@
# king_kong_le 0.97 - worked out by the app from the ports of The Beatles 1.29, Deadpool LE 1.14, Deadpool Pro 1.16, Godzilla Premium/LE 1.16, Godzilla Pro 1.15, Jaws LE 1.02, TMNT Pro 1.59.
# Emulator-proven 2026-09-24 by a full build check (modes/gamecheck.sh play full, item 162): the
# runtime hooked the game, 36 of its 55 shots came from their switches, a drain ended each ball, and
# the 6 events below fired where their names say (a tilted ball, three balls to the game's end).
# Shots no press made (their switch is left out of the check, or needs two): Shot 0x20, Shot 0x40, Shot 0x80, Shot 0x100, Shot 0x400, Shot 0x10000, Shot 0x20000, Shot 0x40000, Shot 0x100000, Shot 0x1000000, Shot 0x1000000000, Shot 0x20000000000, Shot 0x40000000000, Shot 0x80000000000, Shot 0x200000000000, Shot 0x4000000000000, Shot 0x8000000000000, Shot 0x10000000000000, Shot 0x40000000000000.
# program sha1 034964e23f5e94a4f48e0f1e0a448834c4c4cc9c, portgen revision 3, 7 reference port(s) of framework generation B.
# 36 shot(s) the app could not name are named after the switch that made them in the check.
# Callouts heard in the emulator 2026-09-24 (item 163): countdown, time_up.
game king_kong_le
version 0.97
# ---- functions: site <name> <address> <first two instruction words> ----
# tick: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29, Jaws LE 1.02, Deadpool LE 1.14, Deadpool Pro 1.16, TMNT Pro 1.59]
site tick 0x00470704 0xe92d4038 0xe3a00037
# shot_dispatch: located (loose) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15]
site shot_dispatch 0x0013fc38 0xe92d41f0 0xe24dd008
# ball_end: located (strict) [Jaws LE 1.02, Deadpool LE 1.14, Deadpool Pro 1.16]
site ball_end 0x00144cd4 0xe92d40f8 0xe30f6d10
# sound_lookup: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29, Jaws LE 1.02, Deadpool LE 1.14, Deadpool Pro 1.16, TMNT Pro 1.59]
site sound_lookup 0x003609a8 0xe92d4038 0xe1a05000
# score_add: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, Jaws LE 1.02, Deadpool LE 1.14, Deadpool Pro 1.16, TMNT Pro 1.59]
site score_add 0x00533edc 0xe30817fe 0xe340108b
# callout: through the handler of hook 0x95, as in the reference [The Beatles 1.29, Jaws LE 1.02, Deadpool LE 1.14, Deadpool Pro 1.16, TMNT Pro 1.59]
site callout 0x002c7220 0xe92d4010 0xe303e900
# callout_nth: located (loose) [The Beatles 1.29, Jaws LE 1.02, Deadpool LE 1.14, Deadpool Pro 1.16, TMNT Pro 1.59]
site callout_nth 0x002c73d4 0xe92d4010 0xe303e900
# light_run: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, Jaws LE 1.02]
site light_run 0x002a24d4 0xeafffd83 0xe92d40f0
# lamp_group: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29, Jaws LE 1.02, Deadpool LE 1.14, Deadpool Pro 1.16, TMNT Pro 1.59]
site lamp_group 0x00459a58 0xe92d4038 0xe1a04000
# show_priority: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29, Jaws LE 1.02, Deadpool LE 1.14, Deadpool Pro 1.16, TMNT Pro 1.59]
site show_priority 0x00522950 0xe30b3c8c 0xe340308e
# award_screen: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29, Jaws LE 1.02, Deadpool LE 1.14, Deadpool Pro 1.16, TMNT Pro 1.59]
site award_screen 0x0051720c 0xe92d4070 0xe24dd008
# string_new: PLT stub for _ZNSsC1EPKcRKSaIcE [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29, Jaws LE 1.02, Deadpool LE 1.14, Deadpool Pro 1.16, TMNT Pro 1.59]
site string_new 0x000169c8 0xe28fc607 0xe28ccaf6
# dynamic_cast: PLT stub for __dynamic_cast [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29, Jaws LE 1.02, Deadpool LE 1.14, Deadpool Pro 1.16, TMNT Pro 1.59]
site dynamic_cast 0x000163bc 0xe28fc607 0xe28ccaf6
# resource_get: placed through 3 callers, e.g. the bl at 0x406098 [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29, Jaws LE 1.02, Deadpool LE 1.14, Deadpool Pro 1.16, TMNT Pro 1.59]
site resource_get 0x005caeb8 0xe92d43f0 0xe1a04000
# find_node: placed through 3 callers, e.g. the bl at 0x20940 [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29, Jaws LE 1.02]
site find_node 0x006574ec 0xe92d4070 0xe24dd008
# find_text: placed through 3 callers, e.g. the bl at 0x210b4 [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, Jaws LE 1.02]
site find_text 0x00656f88 0xe92d4070 0xe24dd008
# set_text: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29, Jaws LE 1.02, Deadpool LE 1.14, Deadpool Pro 1.16, TMNT Pro 1.59]
site set_text 0x0063acb4 0xe92d47f0 0xe1a06000
# clip_play: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, Jaws LE 1.02]
site clip_play 0x0007c8b4 0xe92d43f0 0xe24dd014
# clip_stop: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, Jaws LE 1.02]
site clip_stop 0x0007cb14 0xe92d4008 0xebfffeea
# video_player: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, Jaws LE 1.02]
site video_player 0x0007c6a4 0xe92d4010 0xe3004c8c
# video_surface: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, Jaws LE 1.02]
site video_surface 0x0007c6c8 0xe92d4070 0xe3005c8c
# surface_state: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29, Jaws LE 1.02, Deadpool LE 1.14, Deadpool Pro 1.16, TMNT Pro 1.59]
site surface_state 0x0064a218 0xe5900550 0xe2800008
# player_advance: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29, Jaws LE 1.02, Deadpool LE 1.14, Deadpool Pro 1.16, TMNT Pro 1.59]
site player_advance 0x00517604 0xe2501000 0x012fff1e
# display_draw: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29, Jaws LE 1.02, Deadpool LE 1.14, Deadpool Pro 1.16, TMNT Pro 1.59]
site display_draw 0x00537284 0xe3520001 0x812fff1e
# sound_play: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29]
site sound_play 0x002c7220 0xe92d4010 0xe303e900
# sound_nth: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29]
site sound_nth 0x002c73d4 0xe92d4010 0xe303e900
# sound_active: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29]
site sound_active 0x002c7994 0xe59f3034 0xe2831e62
# sound_stop: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29]
site sound_stop 0x002c7aec 0xe92d40f8 0xe1a06000
# sound_worker: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29]
site sound_worker 0x002c680c 0xe92d4ff0 0xe2507000
# site stock_any_running NOT PLACED: absent in the target (the function changed); no caller could be lined up; 10 of its cmode_manager's virtuals start as the reference's does
# site stock_battle_running NOT PLACED: absent in the target (the function changed); no caller could be lined up; 10 of its cmode_manager's virtuals start as the reference's does
# stock_multiball_running: placed through 2 callers, e.g. the bl at 0x13864c [Godzilla Premium/LE 1.16, Godzilla Pro 1.15]
site stock_multiball_running 0x0013fe2c 0xe92d40f8 0xe3036590
# hook_dispatch: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29, Deadpool LE 1.14]
site hook_dispatch 0x0048eed0 0xe35000cf 0xe92d4038
# site skill_shot_award NOT PLACED: absent in the target (the function changed); no caller could be lined up
# candidate: the reference skill_shot_award calls 0x3387f0, which is 0x371414 in the target - prove what it does before using it
# candidate: the reference skill_shot_award calls 0x2b3614, which is 0x3e19dc in the target - prove what it does before using it
# site mball_start NOT PLACED: absent in the target (the function changed); only 1 caller lined up
# candidate: the reference mball_start calls 0x3387f0, which is 0x371414 in the target - prove what it does before using it
# candidate: the reference mball_start calls 0x3f33e0, which is 0x51f33c in the target - prove what it does before using it
# mball_stop: located (loose) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15]
site mball_stop 0x00147ae8 0xe92d4010 0xe1a04000
# site roster_start NOT PLACED: absent in the target (the function changed); no caller could be lined up
# site roster_done NOT PLACED: absent in the target (the function changed); no caller could be lined up
# candidate: the reference roster_done calls 0x3387f0, which is 0x371414 in the target - prove what it does before using it
# site roster_city_done NOT PLACED: absent in the target (the function changed); no caller could be lined up
# candidate: the reference roster_city_done calls 0x3387f0, which is 0x371414 in the target - prove what it does before using it
# display_effect_start: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15]
site display_effect_start 0x005f51a4 0xe92d47f0 0xe2516000
# display_effect_next: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15]
site display_effect_next 0x005f5588 0xe3043f90 0xe3403093
# display_priority_now: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15]
site display_priority_now 0x005f5a1c 0xe5d0000e 0xe12fff1e
# layered_priority: located (loose) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15]
site layered_priority 0x00044160 0xe92d4008 0xe5903068
# site layered_waiter NOT PLACED: absent in the target (the function changed); no caller could be lined up
# candidate: the reference layered_waiter calls 0x379f0c, which is 0x4bf3f0 in the target - prove what it does before using it
# candidate: the reference layered_waiter calls 0x3d10c, which is 0x44330 in the target - prove what it does before using it
# candidate: the reference layered_waiter calls 0x3cfcc, which is 0x44160 in the target - prove what it does before using it
# stock_rule_get: located (loose) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15]
site stock_rule_get 0x0013fd10 0xe3510021 0xe92d4008
# site caward_add NOT PLACED: absent in the target (the function changed); no caller could be lined up
# candidate: the reference caward_add calls 0x2b2720, which is 0x534044 in the target - prove what it does before using it
# candidate: the reference caward_add calls 0x3387f0, which is 0x371414 in the target - prove what it does before using it
# site caward_build NOT PLACED: absent in the target (the function changed); no caller could be lined up
# candidate: the reference caward_build calls 0x3387f0, which is 0x371414 in the target - prove what it does before using it
# site caward_add_scaled NOT PLACED: absent in the target (the function changed); no caller could be lined up
# candidate: the reference caward_add_scaled calls 0x3387f0, which is 0x371414 in the target - prove what it does before using it
# candidate: the reference caward_add_scaled calls 0x2b2720, which is 0x534044 in the target - prove what it does before using it
# show_start: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15]
site show_start 0x005226f0 0xe92d47f0 0xe2508000
# game_event: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15]
site game_event 0x00512c5c 0xe92d47f0 0xe1a09000
# event_post_replacing: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15]
site event_post_replacing 0x0051f33c 0xe92d4070 0xe1a05001
# event_cancel: located (strict) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15]
site event_cancel 0x0051f2ac 0xe92d40f8 0xe30b6c88
# site ebirah_refill_counts NOT PLACED: absent in the target (the function changed); no caller could be lined up
# candidate: the reference ebirah_refill_counts calls 0x3387f0, which is 0x371414 in the target - prove what it does before using it
# site ebirah_stage_done NOT PLACED: absent in the target (the function changed); no caller could be lined up
# candidate: the reference ebirah_stage_done calls 0x3387f0, which is 0x371414 in the target - prove what it does before using it
# candidate: the reference ebirah_stage_done calls 0x3f33e0, which is 0x51f33c in the target - prove what it does before using it
# site ebirah_award_screen NOT PLACED: absent in the target (the function changed); no caller could be lined up
# candidate: the reference ebirah_award_screen calls 0x405908, which is 0x51720c in the target - prove what it does before using it
# candidate: the reference ebirah_award_screen calls 0x1f34d4, which is 0x5226f0 in the target - prove what it does before using it
# candidate: the reference ebirah_award_screen calls 0x43d2ac, which is 0x2c7220 in the target - prove what it does before using it
# site ebirah_final_mask NOT PLACED: absent in the target (the function changed); no caller could be lined up
# candidate: the reference ebirah_final_mask calls 0x3387f0, which is 0x371414 in the target - prove what it does before using it
# site tank_maser_phase NOT PLACED: absent in the target (the function changed); only 1 caller lined up
# candidate: the reference tank_maser_phase calls 0x3387f0, which is 0x371414 in the target - prove what it does before using it
# site tank_positions_or NOT PLACED: absent in the target (the function changed); no caller could be lined up
# site tank_pick_record NOT PLACED: absent in the target (the function changed); no caller could be lined up
# site tank_path_index NOT PLACED: absent in the target (the function changed); no caller could be lined up
# site tank_seed_wave NOT PLACED: absent in the target (the function changed); no caller could be lined up
# candidate: the reference tank_seed_wave calls 0x3387f0, which is 0x371414 in the target - prove what it does before using it
# candidate: the reference tank_seed_wave calls 0x2f790, which is 0x39d9c in the target - prove what it does before using it
# site tank_destroy NOT PLACED: absent in the target (the function changed); no caller could be lined up
# candidate: the reference tank_destroy calls 0x3387f0, which is 0x371414 in the target - prove what it does before using it
# site tank_advance NOT PLACED: absent in the target (the function changed); no caller could be lined up
# candidate: the reference tank_advance calls 0x5cf000, which is 0x69d960 in the target - prove what it does before using it
# site tank_award_screen_kill NOT PLACED: absent in the target (the function changed); no caller could be lined up
# candidate: the reference tank_award_screen_kill calls 0x109144, which is 0x210c28 in the target - prove what it does before using it
# candidate: the reference tank_award_screen_kill calls 0x405908, which is 0x51720c in the target - prove what it does before using it
# candidate: the reference tank_award_screen_kill calls 0x43d2ac, which is 0x2c7220 in the target - prove what it does before using it
# switch_hit: located (strict) [The Beatles 1.29]
site switch_hit 0x0057ba64 0xe2503000 0x012fff1e
# switch_hit2: located (strict) [The Beatles 1.29]
site switch_hit2 0x0057bb04 0xe92d4010 0xe2504000
# ---- globals: data <name> <address> ----
# cur_player: derived from 2 places, e.g. 0x338790+0x60 [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29, Jaws LE 1.02, Deadpool Pro 1.16, TMNT Pro 1.59]
data cur_player 0x00819088
# scores: derived from 2 places, e.g. 0x2b2524+0x8 [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, Jaws LE 1.02, Deadpool LE 1.14, Deadpool Pro 1.16, TMNT Pro 1.59]
data scores 0x008ec3a0
# mode_mask: derived from 2 places, e.g. 0x1c420+0xf8 [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29, Jaws LE 1.02, Deadpool LE 1.14, Deadpool Pro 1.16, TMNT Pro 1.59]
data mode_mask 0x008b87fe
# event_head: derived from 2 places, e.g. 0x52d08+0x30 [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29, Jaws LE 1.02, Deadpool LE 1.14, Deadpool Pro 1.16, TMNT Pro 1.59]
data event_head 0x008ebc88
# event_current: derived from 2 places, e.g. 0x4bc4c+0x4 [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29, Jaws LE 1.02, Deadpool LE 1.14, Deadpool Pro 1.16, TMNT Pro 1.59]
data event_current 0x008ebc8c
# resource_manager: a base 0x7f51d0 + field 0x80; the base derived from 2 places, e.g. 0x406020+0x54 [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29, Jaws LE 1.02, Deadpool LE 1.14, Deadpool Pro 1.16, TMNT Pro 1.59]
data resource_manager 0x008ea738
# typeinfo_resource: derived from 2 places, e.g. 0x2075c+0x54 [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29, Jaws LE 1.02, Deadpool LE 1.14, Deadpool Pro 1.16, TMNT Pro 1.59]
data typeinfo_resource 0x006f49c8
# typeinfo_scene_player: derived from 2 places, e.g. 0x2075c+0x58 [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29, Jaws LE 1.02, Deadpool LE 1.14, Deadpool Pro 1.16, TMNT Pro 1.59]
data typeinfo_scene_player 0x0078cb94
# display_holder: derived from 2 places, e.g. 0x404620+0x28 [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29, Jaws LE 1.02, Deadpool LE 1.14, Deadpool Pro 1.16, TMNT Pro 1.59]
data display_holder 0x008ea6b8
# data message_count NOT PLACED: left off - another title's layout
# data message_remap NOT PLACED: left off - another title's layout
# data message_table NOT PLACED: left off - another title's layout
# award_screen_arg: derived from 2 places, e.g. 0x3d564+0x8 [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29, Jaws LE 1.02, Deadpool LE 1.14, Deadpool Pro 1.16, TMNT Pro 1.59]
data award_screen_arg 0x0080fa70
# sound_channels: derived from 2 places, e.g. 0x43c3fc+0x50 [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29]
data sound_channels 0x008b3a0c
# sound_queue: derived from 2 places, e.g. 0x43d304+0xc [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29]
data sound_queue 0x008b4280
# data city_variants NOT PLACED: no code that loads it could be lined up
# sound_requests: derived from 2 places, e.g. 0x43c898+0x34 [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29]
data sound_requests 0x00887764
# sound_request_count: derived from 2 places, e.g. 0x43c898+0x14 [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29]
data sound_request_count 0x006f0468
# stock_mode_manager: derived from 2 places, e.g. 0x5652c+0xc [Godzilla Premium/LE 1.16, Godzilla Pro 1.15]
data stock_mode_manager 0x008a05d0
# data rule_battle NOT PLACED: only 1 place lined up (0x89b618 via 0x16518c+0x1c); 2 needed
# data roster_screen_table NOT PLACED: no code that loads it could be lined up
# rule_cities: derived from 2 places, e.g. 0x3c92c+0x10 [Godzilla Premium/LE 1.16, Godzilla Pro 1.15]
data rule_cities 0x008a0970
# display_effects: derived from 1 places, e.g. 0x4394d4+0xa4; ONE place is enough here: the runtime uses it only when the manager it points at holds award_screen_arg's table (disp_manager), so a wrong one holds nothing [Godzilla Premium/LE 1.16, Godzilla Pro 1.15]
data display_effects 0x00934f90
# data layered_displays NOT PLACED: no code that loads it could be lined up
# lamp_layers: derived from 2 places, e.g. 0x3a6010+0x8 [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29]
data lamp_layers 0x009329a8
# light_count: derived from 2 places, e.g. 0x1c8c10+0x4 [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29]
data light_count 0x008c9c54
# data ebirah_stage_awards NOT PLACED: no code that loads it could be lined up
# data ebirah_lamp_table NOT PLACED: no code that loads it could be lined up
# data tank_path NOT PLACED: no code that loads it could be lined up
# data tank_spot_list NOT PLACED: no code that loads it could be lined up
# data score_mult NOT PLACED: no code that loads it could be lined up
# ---- scenes ----
# scene hud: COPIED, unverified (no code that loads it could be lined up; the target program names the same id) [Jaws LE 1.02]
scene hud 9d57875196c613785a1eee010c55223a0f1aa821
# scene video_bank: derived: the target code loads it (derived from 1 places, e.g. 0x526d4+0x4) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, The Beatles 1.29, Jaws LE 1.02, Deadpool LE 1.14, Deadpool Pro 1.16, TMNT Pro 1.59]
scene video_bank 60ed7e5036b8ce09d35a3e101ea6fc1380b37d97
# scene attract: derived: the target code loads it (derived from 1 places, e.g. 0x2075c+0x28) [Godzilla Premium/LE 1.16, Godzilla Pro 1.15, Jaws LE 1.02]
scene attract 394c4a037fb26e12f536c42b2677bfed831f5c98
# ---- values: COPIED from the references (framework offsets), unverified; the switch_* and ball_end_event values read from this build's own framework code ----
value mode_mask_busy 0x210
value surface_playing 2
value clip_layer 0
value event_next 0x84
value event_flags 0x2
value event_show_flag 0x20
value event_show_id 0x96
value scene_player_scene 0x10
value node_visible_vfn 0xa8
value sound_channel_count 8
value sound_channel_size 196
value sound_channel_request 184
value sound_channel_voices 188
value sound_channel_bus 189
value sound_channel_voice_ptrs 92
value sound_voice_volume 48
value stock_slot_shot 42
value stock_slot_active 18
value stock_slot_lit 25
value stock_field 0x18
value stock_slot_start 11
value stock_slot_end 10
value stock_slot_stop 11
value stock_slot_running 12
value stock_slot_shot_entry 15
value stock_slot_mask 45
value stock_running_at 0x1b # + player: running byte
value stock_level_at 0x3c # + 4 * player: starts this game
value stock_award_at 0x50 # the rule's caward* (caward_add's first argument)
value ebirah_rule 12
value ebirah_spin_left_at 0x8c # + 4 * player, spins left on the Left spinner (15 at a refill)
value ebirah_spin_top_at 0x9c # Top spinner (40)
value ebirah_spin_shield_at 0xac # Shield ramp spinner (15)
value ebirah_stage_count_at 0xbc # + 4 * player: stages done (the 2-stage RuleCities/RuleAllies mark reads it)
value ebirah_stage_index_at 0xd0 # which of 5M / 10M / 15M is next (START resets it)
value ebirah_kotm_at 0xd4 # byte: King of the Monsters runs (awards go to rule 20)
value ebirah_spin_left_bit 0x200 # the raw shot bits the handler tests (each spinner's middle bit)
value ebirah_spin_top_bit 0x2000
value ebirah_final_shot 0x40 # the Pop bumper: the final blow (or bit 42, unnamed)
value ebirah_final_mask_lo 0x40 # the field once every counter is 0 (0x814cc writes 0x400_00000040)
value ebirah_final_mask_hi 0x400
value ebirah_reminder_event 0x14e
value ebirah_stage_award_count 3
value ebirah_stage_msg 0xce3 # award screen messages 0xce3 / 0xce4 / 0xce5 (left / top / shield), 0xce6 the final blow
value ebirah_stage_lamp_left 0x71 # 113 LEFT SPINNER, the lamp the stage screen names
value ebirah_stage_lamp_top 0x96 # 150 TOP SPINNER
value ebirah_stage_lamp_shield 0x7a # 122 ATTACK MECHA (the shield ramp spinner's insert)
value ebirah_lamp_entries 6
value tank_rule 4
value tank_records_at 0x68 # a std::vector (begin, end) of tank records
value tank_record_size 40
value tank_record_active_at 0
value tank_record_pos_at 8 # u64: the tank's position (a path mask)
value tank_record_prev_at 0x10 # u64: where it was before the last step (a grace window for the handler)
value tank_record_dest_at 0x18 # u64: its destination (the Godzilla target 0x80000)
value tank_record_value_at 0x20 # u64: what destroying it pays (2,500,000 - 500,000 a step, floor 1,500,000)
value tank_level_at 0x70 # + 4 * player
value tank_value_at 0x80 # + 8 * player, u64: this player's tank value (+150,000 a kill)
value tank_destroyed_at 0xa8 # u32: tanks destroyed this wave (4/5/6/7/8 by level light the Maser)
value tank_multiplier_at 0xac # the Maser multiplier (3 a wave, -1 per tank that reaches the target)
value tank_maser_hits_at 0xb0 # + 4 * (player - 1)
value tank_path_entries 6
value tank_path_entry_size 16
# ---- shots: read from the build's own cshot objects (55 single-bit shots from 56 cshot objects (u64 mask at +8, lamp at +0x10)), named after their inserts; prove them with the shot census ----
shot 0x1 X1
shot 0x2 Left slingshot
shot 0x4 Right slingshot
shot 0x8 Left inlane-inner
shot 0x10 Left inlane-outer
shot 0x20 Shot 0x20
shot 0x40 Shot 0x40
shot 0x80 Shot 0x80
shot 0x100 Shot 0x100
shot 0x200 Right inlane
shot 0x400 Shot 0x400
shot 0x800 4 Bank drop 1-top
shot 0x1000 4 Bank drop 2
shot 0x2000 4 Bank drop 3
shot 0x4000 4 Bank drop 4-bot
shot 0x8000 4 Bank drop 4-bot 2
shot 0x10000 Shot 0x10000
shot 0x20000 Shot 0x20000
shot 0x40000 Shot 0x40000
shot 0x80000 Left orbit spinner opto
shot 0x100000 Shot 0x100000
shot 0x200000 Left orbit spinner opto 2
shot 0x400000 Left orbit spinner opto 3
shot 0x800000 Right spinner opto
shot 0x1000000 Shot 0x1000000
shot 0x2000000 Right spinner opto 2
shot 0x4000000 Right spinner opto 3
shot 0x8000000 River exit
shot 0x10000000 Pit target-bot
shot 0x20000000 Pit target-mid
shot 0x40000000 Pit target-top
shot 0x80000000 X1 2
shot 0x100000000 Powerup target
shot 0x200000000 X2
shot 0x400000000 X3
shot 0x800000000 X4
shot 0x1000000000 Shot 0x1000000000
shot 0x2000000000 Left ramp made opto
shot 0x4000000000 Train enter opto
shot 0x8000000000 Biplane ramp made opto
shot 0x10000000000 Train lock opto
shot 0x20000000000 Shot 0x20000000000
shot 0x40000000000 Shot 0x40000000000
shot 0x80000000000 Shot 0x80000000000
shot 0x100000000000 Kick target
shot 0x200000000000 Shot 0x200000000000
shot 0x400000000000 2.42 Million bananas
shot 0x800000000000 Gong hit opto
shot 0x1000000000000 U-turn rollover
shot 0x2000000000000 Kong cave made
shot 0x4000000000000 Shot 0x4000000000000
shot 0x8000000000000 Shot 0x8000000000000
shot 0x10000000000000 Shot 0x10000000000000
shot 0x20000000000000 River enter
shot 0x40000000000000 Shot 0x40000000000000
# ---- events: COPIED, unverified - prove them with event_probe.c (MODE_SDK.md, Events) ----
# ---- events: the framework's event bus, each seen firing in the emulator 2026-09-24 ----
event game_start 0x4d
event ball_start 0x25
event ball_end 0x34
event bonus_end 0x28
event tilt_warning 0xc8
event tilt 0xc6
# ---- callouts, heard in the emulator 2026-09-24 (item 163): the runtime's callout / callout_nth
# sites played each id and the game's audio was transcribed: countdown 668 (clips 1-5 said "ONE", "TOO", "THREE", "Four.", "Five"); time_up 670 ("TIME'S UP!")
callout countdown 668
callout time_up 670
# ---- clip v2 (item 164): the video bank scene's VideoSurface, played on the surface itself - the
# game draws it. The typed find is find_node + 0x2f4 and the surface's functions sit at Godzilla LE's
# distances from surface_state; each prologue equals Godzilla LE's. A clip was seen on the glass in
# the emulator 2026-09-25 (item 164).
site surface_find 0x006577e0 0xe92d4070 0xe24dd008
site surface_set_video 0x0064eec4 0xe5d030d4 0xe92d4ff0
site surface_play 0x0064ccd0 0xe5d030d4 0xe92d43f0
site surface_stop 0x0064c480 0xe5d030d4 0xe92d40f0
# Insider Connected score gate (item 166): the request header constructor (the endpoint in r1) and the
# message-begin thunk every sender calls next; the runtime refuses the score reports there
# (MODE_SDK.md, "Insider Connected: no score leaves a machine that carries modes")
site agent_header 0x006a4530 0xe92d40f8 0xe2505000
site agent_begin 0x006aee9c 0xe590c004 0xe1a00001
# ---- examples a template mode can use on this game ----
text example_start_shot Pit target-bot
# ---- named inserts (item 164): framework lamp layers, derived from this build's program
# the slot layout and fade: Godzilla's, checked in the emulator (item 164)
value lamp_slot_size 40
value lamp_slot_level 2
value lamp_slot_alpha 3
value lamp_slot_fade 8
value lamp_slot_used 36
value lamp_group_prio 4
value lamp_group_next 20
value lamp_fade 3
# ---- lamps: lamp <light ids R,G,B | one> <shot mask> <name> - read by lamp_map.py ----
# 205 inserts on the TestMode/Rodeo_LE_Service_Playfield_Wireframe_300dpi_cropped picture; light table 0x872acc, device table 0x877f54, lamp table 0x830128 (315 lamps)
# shot masks: read from 55 cshot objects built by 0x266294
lamp 9 0x80 CLIFFHANGER # lamp 6, I/O group 7 index 24, at 58,446
lamp 10 0 LEFT RETURN 2 # lamp 7, I/O group 7 index 8, at 66,547
lamp 11 0 LEFT RETURN 2X # lamp 8, I/O group 7 index 9, at 76,553
lamp 12 0 LEFT RETURN 1 # lamp 9, I/O group 7 index 10, at 86,561
lamp 13 0 RIGHT RETURN 1 # lamp 10, I/O group 7 index 11, at 218,561
lamp 14 0 RIGHT RETURN 2X # lamp 11, I/O group 7 index 12, at 228,553
lamp 15 0 RIGHT RETURN 2 # lamp 12, I/O group 7 index 13, at 238,547
lamp 16 0 SHOOT AGAIN # lamp 13, I/O group 7 index 15, at 154,600
lamp 17 0 EIGHTH WONDER # lamp 14, I/O group 7 index 14, at 154,534
lamp 18 0 KONG MULTIBALL # lamp 15, I/O group 7 index 16, at 130,481
lamp 19 0 TREX DEFEATED # lamp 16, I/O group 7 index 17, at 127,506
lamp 20 0 BIPLANES DESTROYED # lamp 17, I/O group 7 index 18, at 123,530
lamp 21 0 CLIMB COMPLETE # lamp 18, I/O group 7 index 19, at 177,481
lamp 22 0 ISLAND SCENES # lamp 19, I/O group 7 index 20, at 180,506
lamp 23 0 SPIDER PIT CLEARED # lamp 20, I/O group 7 index 21, at 184,530
lamp 24 0 (K)ING KONG # lamp 21, I/O group 7 index 39, at 95,470
lamp 25 0 K(I)NG KONG # lamp 22, I/O group 7 index 38, at 107,455
lamp 26 0 KI(N)G KONG # lamp 23, I/O group 7 index 37, at 122,445
lamp 27 0 KIN(G) KONG # lamp 24, I/O group 7 index 36, at 139,439
lamp 28 0 KING (K)ONG # lamp 25, I/O group 7 index 34, at 167,439
lamp 29 0 KING K(O)NG # lamp 26, I/O group 7 index 33, at 185,445
lamp 30 0 KING KO(N)G # lamp 27, I/O group 7 index 32, at 200,455
lamp 31 0 KING KON(G) # lamp 28, I/O group 7 index 31, at 212,470
lamp 32 0 BASH GONG W/LIT # lamp 29, I/O group 7 index 35, at 154,453
lamp 33 0 GIANT SPIDER # lamp 30, I/O group 7 index 71, at 109,431
lamp 34 0 OCTOPUS INSECT # lamp 31, I/O group 7 index 70, at 117,418
lamp 35 0 2 LEGGED LIZARD # lamp 32, I/O group 7 index 66, at 119,403
lamp 36 0 THE PIT! # lamp 33, I/O group 7 index 61, at 122,386
lamp 37,38,39 0x12000000000000 KONG CAVE # lamp 34, I/O group 7 index 25, at 227,427
lamp 40 0 KONG CAVE MYSTERY # lamp 35, I/O group 7 index 28, at 243,404
lamp 41 0 EXTRA BALL # lamp 36, I/O group 7 index 30, at 256,383
lamp 42 0 100FT # lamp 37, I/O group 7 index 69, at 137,416
lamp 43 0 200FT # lamp 38, I/O group 7 index 68, at 154,416
lamp 44 0 300FT # lamp 39, I/O group 7 index 67, at 170,415
lamp 45 0 400FT # lamp 40, I/O group 7 index 65, at 136,400
lamp 46 0 500FT # lamp 41, I/O group 7 index 64, at 154,400
lamp 47 0 600FT # lamp 42, I/O group 7 index 63, at 172,400
lamp 48 0 700FT # lamp 43, I/O group 7 index 60, at 145,382
lamp 49 0 800FT # lamp 44, I/O group 7 index 59, at 162,382
lamp 50 0 900FT # lamp 45, I/O group 7 index 57, at 144,363
lamp 51 0 1000FT # lamp 46, I/O group 7 index 56, at 163,363
lamp 52 0 1100FT # lamp 47, I/O group 7 index 46, at 142,344
lamp 53 0 1200FT # lamp 48, I/O group 7 index 45, at 164,344
lamp 54 0 BUILDING ROOF # lamp 49, I/O group 7 index 42, at 153,326
lamp 55 0 1250FT # lamp 50, I/O group 8 index 24, at 153,300
lamp 56 0 BIPLANE 1 # lamp 51, I/O group 7 index 62, at 189,398
lamp 57 0 BIPLANE 2 # lamp 52, I/O group 7 index 58, at 180,379
lamp 58 0 BIPLANE 3 # lamp 53, I/O group 7 index 55, at 181,360
lamp 59 0 BIPLANE 4 # lamp 54, I/O group 7 index 44, at 182,341
lamp 60 0 ATTACK # lamp 55, I/O group 7 index 41, at 171,323
lamp 61 0x70000 SWEEP BONUS # lamp 56, I/O group 7 index 43, at 119,340
lamp 62 0x100000000 POWERUP # lamp 57, I/O group 7 index 40, at 138,314
lamp 63 0 LIGHT LOCK # lamp 58, I/O group 7 index 47, at 58,266
lamp 64 0 LIGHT COMBOS # lamp 59, I/O group 7 index 48, at 68,287
lamp 65 0 LIGHT JACKPOT # lamp 60, I/O group 7 index 49, at 80,310
lamp 66 0x800 4 BANK DROP 1- TOP # lamp 61, I/O group 7 index 53, at 73,256
lamp 67 0x1000 4 BANK DROP 2 # lamp 62, I/O group 7 index 52, at 80,272
lamp 68 0x2000 4 BANK DROP 3 # lamp 63, I/O group 7 index 51, at 88,286
lamp 69 0x4000 4 BANK DROP 4- BOT # lamp 64, I/O group 7 index 50, at 97,302
lamp 70 0 LOG BRIDGE # lamp 65, I/O group 8 index 12, at 30,232
lamp 71 0x1000000000 ISLAND TREASURE # lamp 66, I/O group 8 index 13, at 50,243
lamp 72,73,74 0x60000000000 LEFT ORBIT # lamp 67, I/O group 8 index 8, at 38,187
lamp 75 0 LEFT ORBIT CLIMB # lamp 68, I/O group 8 index 11, at 42,205
lamp 76 0x80000000 X1 # lamp 69, I/O group 8 index 18, at 59,190
lamp 77,78,79 0x2000000000 LEFT RAMP # lamp 70, I/O group 8 index 15, at 71,198
lamp 80 0 LEFT RAMP CLIMB # lamp 71, I/O group 8 index 14, at 70,216
lamp 81 0x400000000000 2.42 MILLION BANANAS # lamp 72, I/O group 8 index 42, at 212,104
lamp 82,83,84 0x8000000000 BIPLANE # lamp 73, I/O group 8 index 32, at 163,280
lamp 85 0 BIPLANE CLIMB # lamp 74, I/O group 8 index 31, at 78,289
lamp 86,87,88 0x1000000000000 U-TURN # lamp 75, I/O group 8 index 36, at 172,262
lamp 89 0 U-TURN CLIMB # lamp 76, I/O group 8 index 35, at 184,273
lamp 90,91,92 0x4000000000 CENTER RAMP # lamp 77, I/O group 8 index 52, at 153,216
lamp 93 0 CENTER RAMP CLIMB # lamp 78, I/O group 8 index 55, at 153,236
lamp 94 0x200000000 X2 # lamp 79, I/O group 8 index 50, at 175,215
lamp 95,96,97 0x80000000000 CENTER LANE # lamp 80, I/O group 8 index 46, at 189,204
lamp 98 0 CENTER LANE CLIMB # lamp 81, I/O group 8 index 45, at 189,221
lamp 99 0 LOST TEMPLE # lamp 82, I/O group 8 index 44, at 189,245
lamp 100 0x400000000 X3 # lamp 83, I/O group 8 index 59, at 230,275
lamp 101 0 ISLAND SCENE # lamp 84, I/O group 8 index 57, at 222,261
lamp 102 0 BANG GONG # lamp 85, I/O group 8 index 58, at 219,274
lamp 103,104,105 0x4800000000000 KONG GONG # lamp 86, I/O group 8 index 60, at 213,297
lamp 106 0x800000000 X4 # lamp 87, I/O group 8 index 63, at 236,293
lamp 107 0 NYC EVENT # lamp 88, I/O group 8 index 71, at 267,244
lamp 108 0 ISLAND LOCK # lamp 89, I/O group 8 index 70, at 262,260
lamp 109,110,111 0x8300000000000 RIGHT ORBIT # lamp 90, I/O group 8 index 67, at 255,284
lamp 112 0 RIGHT ORBIT CLIMB # lamp 91, I/O group 8 index 64, at 250,302
lamp 113 0 LEFT RAMP BB # lamp 92, I/O group 8 index 21, at 60,174
lamp 114 0 BIPLANE BB SUPER JACKPOT # lamp 93, I/O group 8 index 20, at 165,80
lamp 115 0 BIPLANE BB ADVANCE BIPLANE # lamp 94, I/O group 8 index 19, at 145,80
lamp 116 0 TRAIN LOCK 3 # lamp 95, I/O group 8 index 25, at 152,128
lamp 117 0 TRAIN LOCK 2 # lamp 96, I/O group 8 index 26, at 152,146
lamp 118 0 TRAIN LOCK 1 # lamp 97, I/O group 8 index 27, at 152,164
lamp 119 0 GONG LEFT # lamp 98, I/O group 8 index 3, at 206,253
lamp 120 0 GONG RIGHT # lamp 99, I/O group 8 index 4, at 243,257
lamp 122 0 BOTTOM GI-WHT # lamp 101, I/O group 7 index 0, at 0,0
lamp 123 0 ARCH GI-WHT # lamp 102, I/O group 7 index 1, at 0,0
lamp 124 0 TOP GI-WHT # lamp 103, I/O group 8 index 0, at 0,0
lamp 126 0x2 LOWER LEFT SPOT # lamp 105, I/O group 7 index 22, at 98,506
lamp 127 0x4 LOWER RIGHT SPOT # lamp 106, I/O group 7 index 23, at 208,506
lamp 128 0 TEMPLE FLASH # lamp 107, I/O group 8 index 40, at 39,294
lamp 129 0x70000000 PIT FLASH # lamp 108, I/O group 8 index 41, at 70,354
lamp 130 0x780000 LEFT SPINNER FLASH # lamp 109, I/O group 8 index 22, at 39,146
lamp 131 0x7800000 RIGHT SPINNER FLASH # lamp 110, I/O group 8 index 49, at 189,177
lamp 132 0 GONG FLASH # lamp 111, I/O group 8 index 56, at 230,228
lamp 133 0 KONG CAVE FLASHER # lamp 112, I/O group 8 index 39, at 281,371
lamp 136 0 TRAIN FLASH # lamp 115, I/O group 8 index 65, at 221,261
lamp 138 0 RIVER FLASH TOP # lamp 117, I/O group 8 index 5, at 25,353
lamp 139 0 RIVER FLASH MID # lamp 118, I/O group 8 index 6, at 25,366
lamp 140 0 RIVER FLASH BOT # lamp 119, I/O group 8 index 7, at 25,381
lamp 438,439,440 0 EXPRESSIVE LIGHTING 1 # lamp 143, I/O group 1 index 1, at 7,539
lamp 441,442,443 0 EXPRESSIVE LIGHTING 2 # lamp 144, I/O group 1 index 4, at 7,519
lamp 444,445,446 0 EXPRESSIVE LIGHTING 3 # lamp 145, I/O group 1 index 7, at 7,500
lamp 447,448,449 0 EXPRESSIVE LIGHTING 4 # lamp 146, I/O group 1 index 10, at 7,480
lamp 450,451,452 0 EXPRESSIVE LIGHTING 5 # lamp 147, I/O group 1 index 13, at 7,461
lamp 453,454,455 0 EXPRESSIVE LIGHTING 6 # lamp 148, I/O group 1 index 16, at 7,441
lamp 456,457,458 0 EXPRESSIVE LIGHTING 7 # lamp 149, I/O group 1 index 19, at 7,422
lamp 459,460,461 0 EXPRESSIVE LIGHTING 8 # lamp 150, I/O group 1 index 22, at 7,402
lamp 462,463,464 0 EXPRESSIVE LIGHTING 9 # lamp 151, I/O group 1 index 25, at 7,383
lamp 465,466,467 0 EXPRESSIVE LIGHTING 10 # lamp 152, I/O group 1 index 28, at 7,364
lamp 468,469,470 0 EXPRESSIVE LIGHTING 11 # lamp 153, I/O group 1 index 31, at 7,344
lamp 471,472,473 0 EXPRESSIVE LIGHTING 12 # lamp 154, I/O group 1 index 34, at 7,325
lamp 474,475,476 0 EXPRESSIVE LIGHTING 13 # lamp 155, I/O group 1 index 37, at 7,305
lamp 477,478,479 0 EXPRESSIVE LIGHTING 14 # lamp 156, I/O group 1 index 40, at 7,286
lamp 480,481,482 0 EXPRESSIVE LIGHTING 15 # lamp 157, I/O group 1 index 43, at 7,266
lamp 483,484,485 0 EXPRESSIVE LIGHTING 16 # lamp 158, I/O group 1 index 46, at 7,247
lamp 486,487,488 0 EXPRESSIVE LIGHTING 17 # lamp 159, I/O group 1 index 49, at 7,228
lamp 489,490,491 0 EXPRESSIVE LIGHTING 18 # lamp 160, I/O group 1 index 52, at 7,208
lamp 492,493,494 0 EXPRESSIVE LIGHTING 19 # lamp 161, I/O group 1 index 55, at 7,189
lamp 495,496,497 0 EXPRESSIVE LIGHTING 20 # lamp 162, I/O group 1 index 58, at 7,169
lamp 498,499,500 0 EXPRESSIVE LIGHTING 21 # lamp 163, I/O group 1 index 61, at 7,150
lamp 501,502,503 0 EXPRESSIVE LIGHTING 22 # lamp 164, I/O group 1 index 64, at 7,130
lamp 504,505,506 0 EXPRESSIVE LIGHTING 23 # lamp 165, I/O group 1 index 67, at 7,111
lamp 507,508,509 0 EXPRESSIVE LIGHTING 24 # lamp 166, I/O group 1 index 70, at 7,92
lamp 510,511,512 0 EXPRESSIVE LIGHTING 25 # lamp 167, I/O group 1 index 73, at 7,539
lamp 513,514,515 0 EXPRESSIVE LIGHTING 26 # lamp 168, I/O group 1 index 76, at 7,519
lamp 516,517,518 0 EXPRESSIVE LIGHTING 27 # lamp 169, I/O group 1 index 79, at 7,500
lamp 519,520,521 0 EXPRESSIVE LIGHTING 28 # lamp 170, I/O group 1 index 82, at 7,480
lamp 522,523,524 0 EXPRESSIVE LIGHTING 29 # lamp 171, I/O group 1 index 85, at 7,461
lamp 525,526,527 0 EXPRESSIVE LIGHTING 30 # lamp 172, I/O group 1 index 88, at 7,441
lamp 528,529,530 0 EXPRESSIVE LIGHTING 31 # lamp 173, I/O group 1 index 91, at 7,422
lamp 531,532,533 0 EXPRESSIVE LIGHTING 32 # lamp 174, I/O group 1 index 94, at 7,402
lamp 534,535,536 0 EXPRESSIVE LIGHTING 33 # lamp 175, I/O group 1 index 97, at 7,383
lamp 537,538,539 0 EXPRESSIVE LIGHTING 34 # lamp 176, I/O group 1 index 100, at 7,364
lamp 540,541,542 0 EXPRESSIVE LIGHTING 35 # lamp 177, I/O group 1 index 103, at 7,344
lamp 543,544,545 0 EXPRESSIVE LIGHTING 36 # lamp 178, I/O group 1 index 106, at 7,325
lamp 546,547,548 0 EXPRESSIVE LIGHTING 37 # lamp 179, I/O group 1 index 109, at 7,305
lamp 549,550,551 0 EXPRESSIVE LIGHTING 38 # lamp 180, I/O group 1 index 112, at 7,286
lamp 552,553,554 0 EXPRESSIVE LIGHTING 39 # lamp 181, I/O group 1 index 115, at 7,266
lamp 555,556,557 0 EXPRESSIVE LIGHTING 40 # lamp 182, I/O group 1 index 118, at 7,247
lamp 558,559,560 0 EXPRESSIVE LIGHTING 41 # lamp 183, I/O group 1 index 121, at 7,228
lamp 561,562,563 0 EXPRESSIVE LIGHTING 42 # lamp 184, I/O group 1 index 124, at 7,208
lamp 564,565,566 0 EXPRESSIVE LIGHTING 43 # lamp 185, I/O group 1 index 127, at 7,189
lamp 567,568,569 0 EXPRESSIVE LIGHTING 44 # lamp 186, I/O group 1 index 130, at 7,169
lamp 570,571,572 0 EXPRESSIVE LIGHTING 45 # lamp 187, I/O group 1 index 133, at 7,150
lamp 573,574,575 0 EXPRESSIVE LIGHTING 46 # lamp 188, I/O group 1 index 136, at 7,130
lamp 576,577,578 0 EXPRESSIVE LIGHTING 47 # lamp 189, I/O group 1 index 139, at 7,111
lamp 579,580,581 0 EXPRESSIVE LIGHTING 48 # lamp 190, I/O group 1 index 142, at 7,92
lamp 582,583,584 0 EXPRESSIVE LIGHTING 49 # lamp 191, I/O group 1 index 145, at 305,539
lamp 585,586,587 0 EXPRESSIVE LIGHTING 50 # lamp 192, I/O group 1 index 148, at 305,519
lamp 588,589,590 0 EXPRESSIVE LIGHTING 51 # lamp 193, I/O group 1 index 151, at 305,500
lamp 591,592,593 0 EXPRESSIVE LIGHTING 52 # lamp 194, I/O group 1 index 154, at 305,480
lamp 594,595,596 0 EXPRESSIVE LIGHTING 53 # lamp 195, I/O group 1 index 157, at 305,461
lamp 597,598,599 0 EXPRESSIVE LIGHTING 54 # lamp 196, I/O group 1 index 160, at 305,441
lamp 600,601,602 0 EXPRESSIVE LIGHTING 55 # lamp 197, I/O group 1 index 163, at 305,422
lamp 603,604,605 0 EXPRESSIVE LIGHTING 56 # lamp 198, I/O group 1 index 166, at 305,402
lamp 606,607,608 0 EXPRESSIVE LIGHTING 57 # lamp 199, I/O group 1 index 169, at 305,383
lamp 609,610,611 0 EXPRESSIVE LIGHTING 58 # lamp 200, I/O group 1 index 172, at 305,364
lamp 612,613,614 0 EXPRESSIVE LIGHTING 59 # lamp 201, I/O group 1 index 175, at 305,344
lamp 615,616,617 0 EXPRESSIVE LIGHTING 60 # lamp 202, I/O group 1 index 178, at 305,325
lamp 618,619,620 0 EXPRESSIVE LIGHTING 61 # lamp 203, I/O group 1 index 181, at 305,305
lamp 621,622,623 0 EXPRESSIVE LIGHTING 62 # lamp 204, I/O group 1 index 184, at 305,286
lamp 624,625,626 0 EXPRESSIVE LIGHTING 63 # lamp 205, I/O group 1 index 187, at 305,266
lamp 627,628,629 0 EXPRESSIVE LIGHTING 64 # lamp 206, I/O group 1 index 190, at 305,247
lamp 630,631,632 0 EXPRESSIVE LIGHTING 65 # lamp 207, I/O group 1 index 193, at 305,228
lamp 633,634,635 0 EXPRESSIVE LIGHTING 66 # lamp 208, I/O group 1 index 196, at 305,208
lamp 636,637,638 0 EXPRESSIVE LIGHTING 67 # lamp 209, I/O group 1 index 199, at 305,189
lamp 639,640,641 0 EXPRESSIVE LIGHTING 68 # lamp 210, I/O group 1 index 202, at 305,169
lamp 642,643,644 0 EXPRESSIVE LIGHTING 69 # lamp 211, I/O group 1 index 205, at 305,150
lamp 645,646,647 0 EXPRESSIVE LIGHTING 70 # lamp 212, I/O group 1 index 208, at 305,130
lamp 648,649,650 0 EXPRESSIVE LIGHTING 71 # lamp 213, I/O group 1 index 211, at 305,111
lamp 651,652,653 0 EXPRESSIVE LIGHTING 72 # lamp 214, I/O group 1 index 214, at 305,92
lamp 654,655,656 0 EXPRESSIVE LIGHTING 73 # lamp 215, I/O group 1 index 217, at 305,539
lamp 657,658,659 0 EXPRESSIVE LIGHTING 74 # lamp 216, I/O group 1 index 220, at 305,519
lamp 660,661,662 0 EXPRESSIVE LIGHTING 75 # lamp 217, I/O group 1 index 223, at 305,500
lamp 663,664,665 0 EXPRESSIVE LIGHTING 76 # lamp 218, I/O group 1 index 226, at 305,480
lamp 666,667,668 0 EXPRESSIVE LIGHTING 77 # lamp 219, I/O group 1 index 229, at 305,461
lamp 669,670,671 0 EXPRESSIVE LIGHTING 78 # lamp 220, I/O group 1 index 232, at 305,441
lamp 672,673,674 0 EXPRESSIVE LIGHTING 79 # lamp 221, I/O group 1 index 235, at 305,422
lamp 675,676,677 0 EXPRESSIVE LIGHTING 80 # lamp 222, I/O group 1 index 238, at 305,402
lamp 678,679,680 0 EXPRESSIVE LIGHTING 81 # lamp 223, I/O group 1 index 241, at 305,383
lamp 681,682,683 0 EXPRESSIVE LIGHTING 82 # lamp 224, I/O group 1 index 244, at 305,364
lamp 684,685,686 0 EXPRESSIVE LIGHTING 83 # lamp 225, I/O group 1 index 247, at 305,344
lamp 687,688,689 0 EXPRESSIVE LIGHTING 84 # lamp 226, I/O group 1 index 250, at 305,325
lamp 690,691,692 0 EXPRESSIVE LIGHTING 85 # lamp 227, I/O group 1 index 253, at 305,305
lamp 693,694,695 0 EXPRESSIVE LIGHTING 86 # lamp 228, I/O group 1 index 256, at 305,286
lamp 696,697,698 0 EXPRESSIVE LIGHTING 87 # lamp 229, I/O group 1 index 259, at 305,266
lamp 699,700,701 0 EXPRESSIVE LIGHTING 88 # lamp 230, I/O group 1 index 262, at 305,247
lamp 702,703,704 0 EXPRESSIVE LIGHTING 89 # lamp 231, I/O group 1 index 265, at 305,228
lamp 705,706,707 0 EXPRESSIVE LIGHTING 90 # lamp 232, I/O group 1 index 268, at 305,208
lamp 708,709,710 0 EXPRESSIVE LIGHTING 91 # lamp 233, I/O group 1 index 271, at 305,189
lamp 711,712,713 0 EXPRESSIVE LIGHTING 92 # lamp 234, I/O group 1 index 274, at 305,169
lamp 714,715,716 0 EXPRESSIVE LIGHTING 93 # lamp 235, I/O group 1 index 277, at 305,150
lamp 717,718,719 0 EXPRESSIVE LIGHTING 94 # lamp 236, I/O group 1 index 280, at 305,130
lamp 720,721,722 0 EXPRESSIVE LIGHTING 95 # lamp 237, I/O group 1 index 283, at 305,111
lamp 723,724,725 0 EXPRESSIVE LIGHTING 96 # lamp 238, I/O group 1 index 286, at 305,92
# ---- the game's own modes (item 164): its mode TABLE, walked by the runtime (MODE_SDK.md "The game's own
# modes"): the array the get-mode-by-id accessor reads, the cmode class's ACTIVE slot (Godzilla's 14, found
# by its body), START (active - 6), the typeinfos that tell a cmode and a multiball (cmode_mball) apart
data stock_mode_table 0x008a6898
value stock_mode_count 34
data typeinfo_cmode 0x00724964
data typeinfo_cmode_mball 0x0073535c
data typeinfo_vmi 0x00720da8
# ---- the framework's count of the balls in play (The Beatles 1.29 0x1fd194, found here by its code,
# 20/20 words alike); two or more is a multiball (the stack section) and a multiball of the mode's own
# watches it (item 167)
site balls_in_play 0x00599fa0 0xe92d4010 0xe3014f98
# ---- a multiball of the mode's own (item 167): the framework's start-a-multiball (The Beatles 1.29
# 0x1fd310, found here by its code, 24/24 words alike): serve balls until r0 are in play, r1 0, r2 the
# ball save in ticks (62 a second), r3 0xbb as this build's own multiballs pass it, two zero words on the
# stack. pm_multiball_start / pm_multiball_add; a mode file's `multiball` line; the tab's Multiball.
site multiball_serve 0x0059a11c 0xe92d41f0 0xe308e7fe
# EMU 2026-09-26 (mb_batch.sh): served 3 on a mode file's start, both jackpot shots paid, add-a-ball to 4, drains 4-3-2-1, END (one ball left). PROVEN.
# PAD-363: the game's own modes a mode of ours may keep from starting (game_mode_blocks.py, from the
# game program: each mode's start = its vtable + 8 + 4 * the title's start slot). Never a multiball.
# left out: 1 Kong Multiball (multiball), 2 The Pit Multiball Giant Spider (multiball), 3 The Pit Multiball Octopus Insect (multiball), 4 The Pit Multiball Two Legged Lizard (multiball), 10 Island Lock Victory Challenge (multiball), 11 Trex Battle (multiball), 12 Trex Boss Battle (multiball), 13 Spider Queen (multiball), 14 Lost Temple Multiball (multiball), 15 Eighth Wonder (multiball), 33 Golden Gary (multiball)
site block_start_5 0x00103a90 0xe92d4ff0 0xe24dd014
data block_obj_5 0x008a6ff0
text block_name_5 Island Scene Escape the Swamp
site block_start_6 0x0010dda0 0xe92d4ff0 0xe24dd00c
data block_obj_6 0x008a70d0
text block_name_6 Island Scene Save Ann
site block_start_7 0x000fa34c 0xe92d4ff0 0xe24dd014
data block_obj_7 0x008a7190
text block_name_7 Island Scene Cross the Chasm
site block_start_8 0x00115778 0xe92d4ff0 0xe24dd01c
data block_obj_8 0x008a7260
text block_name_8 Island Scene Pterodactyl Attack
site block_start_9 0x0011dd2c 0xe92d4ff0 0xe24dd014
data block_obj_9 0x008a7350
text block_name_9 Island Scene Stegosaurus Encounter
site block_start_16 0x0010732c 0xe5901000 0xe3a02ce1
data block_obj_16 0x008a7428
text block_name_16 Island Scene Save Ann Hurry Up
site block_start_17 0x000c2e60 0xe5901000 0xe3a02000
data block_obj_17 0x008a74d8
text block_name_17 Crash the Gate Hurry Up
site block_start_18 0x000b9a2c 0xe92d4ff0 0xe24dd01c
data block_obj_18 0x008a79f8
text block_name_18 Biplane Attack
site block_start_19 0x000d1a70 0xe92d4ff0 0xe24dd01c
data block_obj_19 0x008a7af0
text block_name_19 Deadeye
site block_start_20 0x00147bc4 0xe5903000 0xe3a01000
data block_obj_20 0x008a7bd0
text block_name_20 Nyc Event Manhattan Rampage
site block_start_21 0x00147bc4 0xe5903000 0xe3a01000
data block_obj_21 0x008a7d38
text block_name_21 Nyc Event Stage Fright
site block_start_22 0x00147bc4 0xe5903000 0xe3a01000
data block_obj_22 0x008a7e18
text block_name_22 Nyc Event Window Shopping
site block_start_23 0x0016b030 0xe92d4ff0 0xe24dd01c
data block_obj_23 0x008a7f20
text block_name_23 Rapids
site block_start_24 0x0017a990 0xe92d4ff0 0xe24dd014
data block_obj_24 0x008a7ff0
text block_name_24 Super Cave Target
site block_start_25 0x000c797c 0xe92d4ff0 0xe24dd01c
data block_obj_25 0x008a8080
text block_name_25 Crash the Gate
site block_start_26 0x000bf608 0xe92d4ff0 0xe24dd014
data block_obj_26 0x008a8120
text block_name_26 Cliffhanger
site block_start_27 0x00127aec 0xe92d4ff0 0xe24dd01c
data block_obj_27 0x008a81c8
text block_name_27 King Kombos
site block_start_28 0x00180a50 0xe92d4ff0 0xe24dd01c
data block_obj_28 0x008a83f0
text block_name_28 Super Kombos
site block_start_29 0x001892dc 0xe92d4ff0 0xe24dd014
data block_obj_29 0x008a8618
text block_name_29 Super Rapids
site block_start_30 0x0019081c 0xe92d4ff0 0xe24dd014
data block_obj_30 0x008a86c8
text block_name_30 Super Spinners
site block_start_31 0x001bf4a8 0xe92d4ff0 0xe24dd014
data block_obj_31 0x008a8768
text block_name_31 Treasure Hunt
site block_start_32 0x00198578 0xe92d4ff0 0xe24dd014
data block_obj_32 0x008a8840
text block_name_32 Terror on the Tower
# ---- held coils (PAD-381's runtime; PAD-394): King Kong's ControlCoil objects a mode may hold, the way the
# Godzilla Premium's magnets and bridge are held (MODE_SDK.md "Held coils"). King Kong's ControlCoil is
# Godzilla's: its take/give (+44 the controlling process), and the vtable slots the runtime calls - v[29] pulse
# power, v[30] pulse ms, v[31] hold power, v[36], v[40] disabled, v[54..58] - are the same functions at the
# same slots (two more virtuals at the end, 61). The framework calls drafted from Godzilla Pro 1.16 by
# port_tool.py (strict); each getter found by its class's vtable (the function-static object the ControlCoil
# constructor builds, its device the constructor's r1) and checked by the tests. magnet_proc: no process call
# of this program names 13185..13188.
site coil_fire 0x004b0498 0xe3500000 0xe52de004
site adjustment 0x003e19dc 0xe92d40f0 0xe2504000
site proc_exists 0x0051f65c 0xe30b2c88 0xe340208e
site proc_create 0x0051f02c 0xe30bcc88 0xe340c08e
site proc_sleep 0x0051f15c 0xe30b3c88 0xe340308e
site coil_take 0x0005e18c 0xe92d40f8 0xe30b7c8c
site coil_give 0x0005e250 0xe3510000 0xe92d4038
value magnet_proc 13185
# spider_magnet = SpiderMagnet (its powers from adjustments 370..; the spider's magnet), log_diverter =
# LogDiverter, ramp_diverter = CenterRampDiverter. Never here: the TrainLock post (it holds locked balls),
# the VUKs, the trough, flippers, slingshots and pops.
text held_coils spider_magnet log_diverter ramp_diverter
site spider_magnet_get 0x0007b2bc 0xe92d40f0 0xe3004a2c
value spider_magnet_dev 10
text spider_magnet_label spider magnet
site log_diverter_get 0x00076f14 0xe92d40f0 0xe30045f0
value log_diverter_dev 13
text log_diverter_label log diverter
site ramp_diverter_get 0x002a5348 0xe92d4070 0xe3034160
value ramp_diverter_dev 15
text ramp_diverter_label center ramp diverter
# PAD-398: every rule of the game's that reads shots (game_mode_blocks.read_rules, from the game
# program): while a mode of ours runs, each sees none of its shots unless the mode keeps it (no
# block_rule_lo/hi lines: every bit); the battle rule is block_battle_shots. `text block_rule_name_<n>`
# is what the Modes tab lists.
site block_rule_0 0x001dc8e4 0xe92d4ff0 0xe24dd03c # RuleBananaCombos::v[27]
text block_rule_name_0 Banana Combos
site block_rule_1 0x001e24cc 0xe92d4ff0 0xe24dd01c # RuleBiplaneRamp::v[27]
text block_rule_name_1 Biplane Ramp
site block_rule_2 0x001ea22c 0xe92d40f0 0xe24dd00c # RuleBuildingClimb::v[27]
text block_rule_name_2 Building Climb
site block_rule_3 0x001ee9f4 0xe92d47f0 0xe1a06002 # RuleDeadeye::v[27]
text block_rule_name_3 Deadeye
site block_rule_4 0x001f70e0 0xe92d4ff0 0xe24dd014 # RuleDropTargets::v[27]
text block_rule_name_4 Drop Targets
site block_rule_5 0x00202748 0xe92d41f0 0xe1a05000 # RuleIslandScenes::v[27]
text block_rule_name_5 Island Scenes
site block_rule_6 0x0020b010 0xe92d4ff8 0xe1a06002 # RuleKongLetters::v[27]
text block_rule_name_6 Kong Letters
site block_rule_7 0x00210e38 0xe92d4ff8 0xe1a04002 # RuleKongLocks::v[27]
text block_rule_name_7 Kong Locks
site block_rule_8 0x002153d8 0xe30113b8 0xe340108a # RuleLeapOfFaith::v[27]
text block_rule_name_8 Leap of Faith
site block_rule_9 0x002174c0 0xe92d4038 0xe3a000a6 # RuleLogBridge::v[27]
text block_rule_name_9 Log Bridge
site block_rule_10 0x0021fc5c 0xe92d47f0 0xe2037010 # RuleLostTemple::v[27]
text block_rule_name_10 Lost Temple
site block_rule_11 0x00226428 0xe2033601 0xe3a02000 # RuleMapSegments::v[27]
text block_rule_name_11 Map Segments
site block_rule_12 0x0022a424 0xe2033901 0xe3a02000 # RuleMillionBananas::v[27]
text block_rule_name_12 Million Bananas
site block_rule_13 0x0022fb58 0xe92d4030 0xe2024102 # RuleMultipliers::v[27]
text block_rule_name_13 Multipliers
site block_rule_14 0x00235bec 0xe92d4070 0xe1a04002 # RuleMystery::v[27]
text block_rule_name_14 Mystery
site block_rule_15 0x002384c0 0xe92d4370 0xe1a04002 # RuleRicochet::v[27]
text block_rule_name_15 Ricochet
site block_rule_16 0x0023ee10 0xe92d4ff0 0xe24dd00c # RuleRiver::v[27]
text block_rule_name_16 River
site block_rule_17 0x00244f3c 0xe92d40f8 0xe1a05003 # RuleSkillShot::v[27]
text block_rule_name_17 Skill Shot
site block_rule_18 0x0024cf94 0xe92d4ff0 0xe24dd01c # RuleTheCliffs::v[27]
text block_rule_name_18 The Cliffs
site block_rule_19 0x002528c8 0xe92d4ff0 0xe1a08002 # RuleThePit::v[27]
text block_rule_name_19 The Pit
site block_rule_20 0x0026d838 0xe2022001 0xe3a03000 # UIBonusScreen::v[27]
text block_rule_name_20 UI Bonus Screen
# ---- PAD-420: the scoop (a mode holds a ball there; the kick-out stays the game's). The process
# calls, the scoop's handler and its slot as the mechanisms helper placed them; seen holding in the
# emulator (see SCOOP_PROVEN)
site scoop_handler 0x000acea0 0xe30837fe 0xe2401002
data scoop_slot 0x008566d0
value scoop_event 2
# ---- PAD-420: the game's own clean-up of a show's lamp groups (pad_mode_runtime.c show_exit_hook): the
# game's "free every lamp group this process owns" (its process exit calls it for a show it started
# itself), and where a process record keeps its exit hooks ({fn, arg} pairs). Found by shows/procexit.py.
site lamp_free_owner 0x0059e78c 0xe92d4010 0xe1a04000
value proc_exit_hooks 0x120
value proc_exit_slots 4
# ---- PAD-420: the game's own light shows (pad_mode_runtime.c "the game's own light shows", PAD-411's
# route): each a process body of the game's that lights lamps, played as process show_proc (an id the game
# never uses), its lamp groups freed as it ends (lamp_free_owner). Every candidate was played in the emulator
# with the lamp groups the show process owned recorded, and on the light-language builds the light runner's
# commands (shows/scan_job3.sh, scanlog3.so); each is named from what it did - its colours (the RGB inserts its
# groups lit, or the commands' --red/--green/--blue), on/off toggling (a strobe), in-between levels (a fade),
# lights coming on in turn (a chase), how many lights next to the build's biggest show (wide: 80% or more,
# small: 25% or fewer) - and how long it ran (shows/finalname.py; on Godzilla LE's ten it gives PAD-411's
# kinds). None reaches the game's sound, score, coil, award, clip or event calls (shows/showsafe.py), and none
# is one the game crashed after in the scan. show_kind: flashy (a mode's start), subdued (its end), accent (a
# moment).
value show_proc 13201
site show_1 0x0004be64 0xe92d4010 0xeb135ab8 # 480 lights in its lamp groups, 5 light-runner commands, no colour given, 4.4 s
text show_name_1 Wide strobe
text show_kind_1 flashy
value show_secs_1 4
site show_2 0x000a5e54 0xe92d4070 0xeb11f2bc # 228 lights in its lamp groups, 0 light-runner commands, no colour given, 4.8 s
text show_name_2 Chase
text show_kind_2 flashy
value show_secs_2 5
site show_3 0x000c2590 0xe92d4ff0 0xe24dd03c # 220 lights in its lamp groups, 0 light-runner commands, red, 2.8 s
text show_name_3 Red strobe
text show_kind_3 flashy
value show_secs_3 3
site show_4 0x0028fa68 0xe92d4ff0 0xe30b4c8c # 216 lights in its lamp groups, 4 light-runner commands, no colour given, 11.7 s
text show_name_4 Strobe
text show_kind_4 flashy
value show_secs_4 8
site show_5 0x0028ee04 0xe92d4ff0 0xe30b4c8c # 168 lights in its lamp groups, 6 light-runner commands, no colour given, 11.5 s
text show_name_5 Fast strobe
text show_kind_5 flashy
value show_secs_5 8
site show_6 0x000a5b5c 0xe92d4070 0xeb11f37a # 117 lights in its lamp groups, 0 light-runner commands, white, 2.8 s
text show_name_6 Small white glow
text show_kind_6 flashy
value show_secs_6 3
site show_7 0x000f2f60 0xe92d4038 0xeb10be79 # 484 lights in its lamp groups, 88 light-runner commands, yellow, white, 4.0 s
text show_name_7 Wide yellow and white fade
text show_kind_7 subdued
value show_secs_7 4
site show_8 0x001d2aac 0xe92d4070 0xeb0d3fa6 # 476 lights in its lamp groups, 50 light-runner commands, red, white, 6.1 s
text show_name_8 Wide red and white fade
text show_kind_8 subdued
value show_secs_8 6
site show_9 0x001c7718 0xe92d4070 0xeb0d6c8b # 476 lights in its lamp groups, 50 light-runner commands, blue, white, 6.1 s
text show_name_9 Wide blue and white fade
text show_kind_9 subdued
value show_secs_9 6
site show_10 0x00137b48 0xe92d4010 0xeb0fab7f # 348 lights in its lamp groups, 8 light-runner commands, magenta, 3.5 s
text show_name_10 Magenta fade
text show_kind_10 subdued
value show_secs_10 4
site show_11 0x0028cba0 0xe92d4010 0xeb0a5769 # 494 lights in its lamp groups, 5 light-runner commands, green, white, 1.5 s
text show_name_11 Wide green and white fade
text show_kind_11 accent
value show_secs_11 1
site show_12 0x0028ccac 0xe92d4038 0xeb0a5726 # 490 lights in its lamp groups, 0 light-runner commands, blue, white, 1.7 s
text show_name_12 Wide blue and white strobe
text show_kind_12 accent
value show_secs_12 2
# ---- PAD-474: the shaker motor, read by shaker_lines.py from the game's own shake routine ----------
# shake(ms, strength 0..3 (powers 51/36/31/23 /255), force): adjustment 347 (AD_SHAKER_MOTOR), drive 7; 8 constant calls
site shake 0x00264f04 0xe92d40f0 0xe1a04000
site shake_stop 0x00264fb8 0xe52de004 0xe24dd00c
site drive_left 0x004b03ec 0xe3500000 0xe92d4038
value shake_adj 347
value shake_drive 7
text shake_max_ms 500 0 1500 200
text shake_setting_ms 0 100 334 500 5000
text shake_tap 0:100:0
text shake_short 0:200:0
text shake_medium 0:334:0
text shake_long 0:1500:2
# ---- PAD-473: the game's own mini-wizards (MODE_SDK.md "The game's own mini-wizards on the other titles"): each one's
# mode object, started by its own START (v[11]) as the game's rules start it and asked its ACTIVE (v[18]);
# the names and what earns each are the game's rulesheet's, the objects this build's own (stock_scan_cpp).
data wizard_obj_1 0x008a8080 # cmode_crash_the_gate
text wizard_name_1 Crash the Gate
text wizard_film_1 all five island scenes
data wizard_obj_2 0x008a7640 # cmode_trex_battle, a multiball
text wizard_name_2 T-Rex Battle
text wizard_film_2 KING KONG spelled
data wizard_obj_3 0x008a7738 # cmode_trex_boss_battle, a multiball
text wizard_name_3 T-Rex Boss Battle
text wizard_film_3 KING KONG spelled again
+104
View File
@@ -0,0 +1,104 @@
{1, "QR SCANNER STATUS READY"},
{2, "QR SCANNER STATUS STRING"},
{3, "QR SCANNER STATUS ENCODED DATA"},
{4, "QR SCANNER STATUS MISSED STRING"},
{5, "QR SCANNER STATUS OVERRUN"},
{6, "QR SCANNER STATUS UNSUPPORTED_AP"},
{7, "QR SCANNER STATUS FIFO OVERRUN"},
{8, "QR SCANNER STATUS CONFIG"},
{9, "QR SCANNER STATUS ERROR"},
{10, "QR SCANNER STATUS ERR1"},
{11, "QR SCANNER STATUS ERR2"},
{12, "QR SCANNER STATUS ERR3"},
{13, "QR SCANNER STATUS ERR4"},
{14, "QR SCANNER STATUS ERR5"},
{15, "QR SCANNER STATUS ERR6"},
{16, "QR SCANNER STATUS NO READER"},
{17, "DIP 1"},
{18, "DIP 2"},
{19, "DIP 3"},
{20, "DIP 4"},
{21, "DIP 5"},
{22, "DIP 6"},
{23, "DIP 7"},
{24, "DIP 8"},
{25, "SERVICE SELECT"},
{26, "SERVICE PLUS"},
{27, "SERVICE MINUS"},
{28, "SERVICE BACK"},
{29, "HEADPHONE DETECT"},
{30, "HEADPHONE KIT CABLE DETECT"},
{31, "VOLUME ENCODER 1"},
{32, "VOLUME ENCODER 2"},
{33, "COIN DOOR INTERLOCK"},
{34, "LOCKDOWN BUTTON"},
{35, "TICKET NOTCH"},
{36, "START BUTTON"},
{37, "TOURNAMENT START BUTTON"},
{38, "TILT PENDULUM"},
{39, "LEFT COIN"},
{40, "CENTER COIN"},
{41, "RIGHT COIN"},
{42, "FOURTH COIN"},
{43, "FIFTH COIN"},
{44, "SIXTH COIN"},
{45, "SLAM TILT"},
{46, "X1"},
{47, "X2"},
{48, "X3"},
{49, "KONG CAVE VUK"},
{50, "X4"},
{51, "SHOOTER EXIT"},
{52, "UP LEFT FLIPPER EOS"},
{53, "UP RIGHT FLIPPER EOS"},
{54, "LEFT FLIPPER EOS"},
{55, "LEFT OUTLANE-LOWER"},
{56, "LEFT INLANE-INNER"},
{57, "RIGHT INLANE"},
{58, "RIGHT OUTLANE"},
{59, "LEFT OUTLANE-UPPER"},
{60, "LEFT INLANE-OUTER"},
{61, "RIVER EXIT"},
{62, "RIGHT FLIPPER BUTTON"},
{63, "LEFT FLIPPER BUTTON"},
{64, "UP RIGHT FLIPPER BUTTON"},
{65, "UP LEFT FLIPPER BUTTON"},
{66, "SHOOTER LANE"},
{67, "RIGHT SLINGSHOT"},
{68, "LEFT SLINGSHOT"},
{69, "RIGHT FLIPPER EOS"},
{70, "TROUGH 6"},
{71, "TROUGH 5"},
{72, "TROUGH 4"},
{73, "TROUGH 3"},
{74, "TROUGH 2"},
{75, "TROUGH 1"},
{76, "TROUGH JAM"},
{77, "RIVER ENTER"},
{78, "ISLAND TREASURE"},
{79, "U-TURN ROLLOVER"},
{80, "LEFT ORBIT SPINNER OPTO"},
{81, "POWERUP TARGET"},
{82, "KICK TARGET"},
{83, "TRAIN ENTER OPTO"},
{84, "TRAIN LOCK OPTO"},
{85, "KONG CAVE MADE"},
{86, "GONG HIT OPTO"},
{87, "KONG GONG VUK OPTO"},
{88, "RIGHT SPINNER OPTO"},
{89, "RIGHT ORBIT"},
{90, "CENTER RAMP MADE OPTO"},
{91, "LEFT ORBIT ENTRANCE OPTO"},
{92, "LEFT RAMP MADE OPTO"},
{93, "BIPLANE RAMP MADE OPTO"},
{94, "CENTER SHOT ENTER OPTO"},
{95, "2.42 MILLION BANANAS"},
{96, "4 BANK DROP 1-TOP"},
{97, "4 BANK DROP 2"},
{98, "4 BANK DROP 3"},
{99, "4 BANK DROP 4-BOT"},
{100, "PIT TARGET-TOP"},
{101, "PIT TARGET-MID"},
{102, "PIT TARGET-BOT"},
{103, "SPIDER MAGNET FIRED VIRTUAL"},
{104, "PUNCHBACK FIRED VIRTUAL"},