/* pad_mode_runtime.c - the half of every mode.so you do not write (item 134). * * It reads the game's PORT, checks it against the running game, hooks the game's tick, * shot dispatch and end of ball, and implements pad_mode.h on the game's own functions. * Every call here was first proven in the emulator on Godzilla Pro 1.15 with the * addresses as constants (items 125-133, tools/spike2_emu/modes/MODE_API.md); the only * change is that addresses, globals and struct offsets now come from the port. * * Built -nostdlib: libc is declared by hand below and resolved from the game's own libc * when the object loads. There is no allocator, so everything is static. */ #include "pad_mode.h" #include "pad_stock.h" /* item 161: the game's own rules in C */ 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 void *memset(void *, int, __SIZE_TYPE__); extern int vsnprintf(char *, __SIZE_TYPE__, const char *, va_list); extern int clock_gettime(int, void *); extern int mprotect(void *, __SIZE_TYPE__, int); #define O_RDONLY 0 #define O_WRONLY 1 #define O_CREAT 0100 #define O_APPEND 02000 #define CLOCK_MONOTONIC 1 #define U __attribute__((unused)) /* ---- the log ---------------------------------------------------------------------- */ static int log_fd = -1, log_lines; static struct { long s, ns; } t0; static const struct pm_mode *current; /* whose callback is running */ unsigned long pm_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 log_raw(const char *who, const char *fmt, va_list ap) { char b[400]; int n, m; if (log_fd < 0 || log_lines > 40000) return; log_lines++; n = pm_snprintf(b, sizeof b, "%8lu [%s] ", pm_ms(), who); if (n < 0 || n >= (int)sizeof b) return; m = vsnprintf(b + n, sizeof b - (unsigned long)n, fmt, ap); if (m < 0) return; n += m; if (n >= (int)sizeof b - 1) n = (int)sizeof b - 2; if (b[n - 1] != '\n') b[n++] = '\n'; write(log_fd, b, (unsigned long)n); } void pm_log(const char *fmt, ...) { va_list ap; va_start(ap, fmt); log_raw(current && current->name ? current->name : "mode", fmt, ap); va_end(ap); } static void say(const char *fmt, ...) __attribute__((format(printf, 1, 2))); static void say(const char *fmt, ...) { va_list ap; va_start(ap, fmt); log_raw("pad", fmt, ap); va_end(ap); } int pm_snprintf(char *out, unsigned long cap, const char *fmt, ...) { va_list ap; int n; va_start(ap, fmt); n = vsnprintf(out, cap, fmt, ap); va_end(ap); return n; } /* ---- small string helpers (no libc string functions here) --------------------------- */ static int str_eq(const char *a, const char *b) { if (!a || !b) return 0; while (*a && *a == *b) { a++; b++; } return *a == *b; } static void str_copy(char *dst, unsigned cap, const char *src, unsigned n) { unsigned i; for (i = 0; i < n && i + 1 < cap && src[i]; i++) dst[i] = src[i]; dst[i] = 0; } 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 uint64_t number(const char **p, int *ok) { const char *s = *p; uint64_t x = 0; int base = 10, got = 0, neg = 0; while (*s == ' ' || *s == '\t') s++; if (s[0] == '-') { neg = 1; s++; } /* item 163: `value countdown_step -1` */ 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; got = 1; } while (*s == ' ' || *s == '\t') s++; *p = s; if (ok) *ok = got; return neg ? (uint64_t)0 - x : x; } void pm_commas(char *out, unsigned cap, uint64_t v) { char rev[32]; unsigned n = 0, i; if (!cap) return; do { if (n && n % 4 == 3) rev[n++] = ','; rev[n++] = (char)('0' + v % 10); v /= 10; } while (v && n + 2 < sizeof rev); for (i = 0; i < n && i + 1 < cap; i++) out[i] = rev[n - 1 - i]; out[i] = 0; } /* ---- the port ------------------------------------------------------------------------ * Key-per-line, `#` comments: * game version * site a function: its address and first two instruction words * data a global's address * value a struct offset, a flag, an id * shot a named shot * switch a switch whose hit is a shot: the switches section below * callout scene <40-hex id> * text anything else a mode may want * The candidates are every *.port in a ports directory (the machine says which game it is on by * which port's sites verify - port_select), then the single files, the old one-game layout. */ static const char *const PORT_DIRS[] = { "/usr/local/padmode/ports", "/dump/ports" }; static const char *const PORT_FILES[] = { "/usr/local/padmode/game.port", "/dump/game.port" }; /* The port is read in PORT_CHUNK pieces, a line at a time, up to PORT_MAX bytes. It was ONE read * into a buffer of the port's cap (16 KB, then 32 KB when the lamp lines took Godzilla's past 16 KB); * with the display lines too Godzilla Premium 1.16's is 23.6 KB. A port longer than PORT_MAX, or a * line a full table cannot take, is said in the boot log, never dropped silently. */ #define PORT_MAX 131072 #define PORT_CHUNK 4096 #define N_SITES 256 /* PAD-363: a title's mode starts (Venom names 50) */ #define N_DATA 224 #define N_VALUES 192 /* PAD-381: Godzilla Premium/LE names 129 (held coils, scoop and PAD-379's shield) */ #define N_SHOTS 64 #define N_ROLES 32 #define N_TEXTS 192 /* PAD-363: each mode a mode may hold off is named */ #define N_SWITCHES 64 /* a build's playfield switches as shots (the switch drain): up to 64, one bit each */ #define N_SWITCH_IDS 256 /* switch ids a `switch` line may name, and a switch_hit site may pass */ /* Limits a port must keep (port_words.py checks them): names up to 39 characters, a shot * name or a text value up to 159. */ struct site { char name[40]; unsigned addr, w0, w1; int ok; }; struct named { char name[40]; long value; }; struct shot { char name[160]; uint64_t mask; }; struct text { char name[40]; char value[160]; }; struct switch_shot { unsigned id; uint64_t mask; }; static struct { char game[32], version[24], path[128]; struct site site[N_SITES]; int n_site; struct named data[N_DATA]; int n_data; struct named value[N_VALUES]; int n_value; struct shot shot[N_SHOTS]; int n_shot; struct switch_shot sw[N_SWITCHES]; int n_switch; struct named callout[N_ROLES]; int n_callout; struct text scene[N_ROLES]; int n_scene; struct text text[N_TEXTS]; int n_text; int dropped, too_long; /* lines a full table could not take; 1 when the port is past PORT_MAX */ } port; static unsigned can; /* PM_CAN_* the verified port provides */ 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' && n + 1 < cap) out[n++] = *s++; out[n] = 0; while (*s && *s != ' ' && *s != '\t' && *s != '\n') s++; /* a too-long word is cut */ while (*s == ' ' || *s == '\t') s++; return s; } static void rest(const char *s, char *out, unsigned cap) { unsigned n = 0; while (s[n] && s[n] != '\n' && s[n] != '\r' && n + 1 < cap) { out[n] = s[n]; n++; } while (n && (out[n - 1] == ' ' || out[n - 1] == '\t')) n--; out[n] = 0; } static void event_line(const char *s); /* `event `: the events section below */ static void lamp_line(const char *s); /* `lamp `: the lights section */ static void rule_line(const char *s); /* `rule