fix(cd): GPU IRQ semantics — halted GPU captures nothing; auth magic stops stomping game GPU code

Two related fixes for BIOS-mode CD titles:

1. Hover Strike B-skip lockup (deterministic at press frames
   150/200/550/650): GPUSetIRQLine latched+dispatched interrupts while
   the GPU was halted (GPUGO=0).  A stale DSA-response latch captured
   during the driver's stopped-GPU maintenance window dispatched at
   restart before the new GPU program's r31 stack init, pushing the
   return address into GPU code (the PIT stub's movei operand at
   $F03114) — the game's schedule clock at $5DC2E froze and the 68K
   polled forever.  A halted RISC samples nothing: asserts are now
   dropped while stopped (the source's level re-edges after restart),
   GPUHandleIRQs bails while halted, and the G_CTRL write handler
   applies GO before processing the CPU->GPU INT0 strobe so a single
   GO|INT0 write still works.  The BUTCH GPU latch assert is now
   edge-paced (once per service cycle, re-armed by the ISR's BUTCH+2
   ack write) instead of once per BUTCHExec tick — per-tick assert
   kept a latch pending at every instant, per-onset starves transfers,
   and edges arriving during a stopped window stay armed so the first
   tick after restart re-asserts them.

2. Myst BIOS boot crash (GPU flies to $ACC5A186 at the post-load
   Pause): the BIOS strategy's auth-magic hook wrote $03D0DEAD to
   $F03000 on EVERY 68K execution of PC=$005E40.  That address is
   BIOS-phase code, but Myst loads its game code at $5000-$1D380 and
   hot paths cross $5E40 — each crossing overwrote the first two words
   of Myst's GPU IRQ0 handler stub (`movei #$F0D000,r0 / jump (r0)`;
   Myst uses IRQ0 as its 68K->GPU command doorbell, G_CTRL=$05 kicks).
   The corrupted slot dispatched through stale r0 into garbage.  The
   hook is now gated on the boot stub not yet having been injected —
   after BIOS->game handoff the game owns that RAM.  Myst now boots
   and runs its command loop (it renders black in headless — separate
   issue, likely its CDDA-driven attract flow; the crash is gone).

Verified: Hover Strike B-press sweep 150/200/350/550/650 all clean;
Myst BIOS 1500 frames no crash signature; full suite passes (56103);
CD boot matrix 9 titles x 2 modes no regressions, Baldies HLE improved
0/1 -> 1/1.  test_hle_bios 9d/9d-cd and test_butch_cd DSA-routing test
updated to pin the new halted-GPU semantics (latch mechanics now run
with GPUGO=1; new sub-assert pins assert-drop while halted).

Tooling: cd_wedge_probe gains --ram-dump / --snap / --snap-prefix
(full main-RAM + GPU-RAM snapshots) and fixes the jaguarMainRAM
pointer-variable dereference bug that made all prior RAM dumps read
host heap instead of emulated RAM.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
Joseph MattielloandClaude Fable 5 committed 2026-07-18 07:17:03 -04:00
1 parent b872ed54a3
commit e92b6750d8
8 files changed
+213 -17

No files matched your search

+1
View File
@@ -39,6 +39,7 @@ test/test_*
test/tools/test_memory_map
test/tools/test_op_gpu_object
test/tools/test_dsp_audio_diag
test/tools/cd_wedge_probe
test/tools/sram_test
test/dump_caller
test/dump_pc
+1 -1
View File
@@ -98,7 +98,7 @@ To add a new probe: create `test/harness/foo_probe.h` + `.c`, resolve symbols vi
- `test/sram_test.sh` — SRAM round-trip
- `test/tools/cd_boot_matrix.sh` — per-title CD boot-stage matrix (HLE + BIOS mode) vs `docs/cd-boot-matrix.md`; env knobs `CD_MATRIX_FRAMES`, `CD_MATRIX_TIMEOUT`, `CD_MATRIX_MAX_RUNS`, `CD_MATRIX_LOGDIR`, `CD_MATRIX_OUT`, `CD_MATRIX_ROMS_ROOT`; chunked/resumable across invocations
- CD trace ring: core option `virtualjaguar_cd_trace` (or env `VJ_CD_TRACE=1` for headless use) records `DSA_TX`/`DSA_RX`, `SEEK_START`/`SEEK_DONE`, `FIFO_FILL`/`FIFO_DRAIN`, `STOP`, `HLE_READ` events; dumped to the log on `cd_seek_wedge` or on request
- `test/tools/cd_wedge_probe.c` — catches intermittent CD lockups: detects a frozen framebuffer (`--arm N --freeze-frames N`, script the repro with `--press`), then dumps 68K registers, the pcQueue traceback ring, CD counters + trace ring, and a RAM hexdump around the stuck PC; exits 42 when caught so a retry loop can distinguish "caught" from "ran clean". Found the Hover Strike instant-CD_read code-stomp.
- `test/tools/cd_wedge_probe.c` — catches intermittent CD lockups: detects a frozen framebuffer (`--arm N --freeze-frames N`, script the repro with `--press`), then dumps 68K registers, the pcQueue traceback ring, CD counters + trace ring, and a RAM hexdump around the stuck PC; exits 42 when caught so a retry loop can distinguish "caught" from "ran clean". `--ram-dump BASE` writes full 2MB main-RAM + GPU-RAM snapshots at wedge time; `--snap FRAME` (repeatable, with `--snap-prefix BASE`) snapshots mid-run for clean-vs-wedged state diffing. Found the Hover Strike instant-CD_read code-stomp and the B-skip stale-GPU-IRQ-latch corruption. Caveat: a static loading screen can false-positive short freeze windows — confirm real wedges with `--freeze-frames 900`.
- `test/tools/cd_visual_verify.c` — automated visual+audio verification for CD titles (`make cd-visual CD_VISUAL_DISC=<image.cue>`): per-second frame-motion timeline, non-black coverage, audio RMS, periodic screenshots (PPM; `sips -s format png` to view). Replaces most "boot it on a device and look" checks — an agent can Read the PNGs directly. Headless read-path caveat still applies for final "looks right" sign-off.
### Build-identity guard (stale-binary protection)
+42 -3
View File
@@ -874,8 +874,22 @@ void BUTCHExec(uint32_t cycles)
* BIOS hasn't installed its EXT1 trampoline (Hover Strike,
* Primal Rage), corrupting the stack with a bogus return address.
* Keep the JERRY pending bit (so JINTCTRL reads see it) but skip
* BLIND m68k_set_irq dual-delivery. */
GPUSetIRQLine(GPUIRQ_DSP, ASSERT_LINE);
* BLIND m68k_set_irq dual-delivery.
*
* The GPU latch assert lives INSIDE the edge gate below: real
* BUTCH raises its IRQ line once per condition onset (FIFO
* crossing half-full, DSA response arriving), and TOM latches
* that edge. Asserting every BUTCHExec tick while the level
* holds re-latches GPU IRQ1 immediately after every ISR ack —
* an IRQ storm that livelocks the GPU (RTI -> instant
* redispatch), and guarantees a stale pending latch whenever
* the 68K stop/reprograms/restarts the GPU. That pending latch
* dispatched at restart BEFORE the new program's `movei
* #$F04000,r31` stack init, pushing the return address through
* a stale r31 into GPU code — Hover Strike's B-skip lockup
* (press B at the boot logo: the CD driver's timer stub movei
* at $F03110 got its operand overwritten, the game's schedule
* clock at $5DC2E froze, and the 68K polled it forever). */
/* 68K delivery IS required when software asks for it: gate on
* JINTCTRL's external-interrupt enable, exactly like the JERRY
@@ -893,6 +907,11 @@ void BUTCHExec(uint32_t cycles)
* Level delivery every halfline is an IRQ storm that starves the
* 68K (4 matrix titles regressed to wall-clock hangs when this
* was level-triggered). */
/* Edge-paced (see the BUTCH+2 ack comment in CDROMWriteWord):
* assert the GPU latch once per service cycle, not per tick. */
if (!cdPrevShouldIRQ)
GPUSetIRQLine(GPUIRQ_DSP, ASSERT_LINE);
if (!cdPrevShouldIRQ)
{
int tomEna = TOMIRQEnabled(IRQ_DSP);
@@ -912,7 +931,14 @@ void BUTCHExec(uint32_t cycles)
m68k_set_irq(2);
}
}
cdPrevShouldIRQ = shouldIRQ;
/* The edge is only "consumed" if a RUNNING GPU could capture it —
* a halted GPU's interrupt logic is not clocked (GPUSetIRQLine
* drops asserts while stopped). Keeping the tracker false across
* a stopped window means the still-high line re-asserts on the
* first tick after the 68K restarts the GPU; marking it true there
* would swallow the edge forever and stall the transfer (the
* press-B@550/650 variant of the Hover Strike lockup). */
cdPrevShouldIRQ = shouldIRQ && GPUIsRunning();
}
}
@@ -1370,6 +1396,19 @@ void CDROMWriteWord(uint32_t offset, uint16_t data, uint32_t who/*=UNKNOWN*/)
prevSubEna = data;
}
SET16(cdRam, offset, data & 0x007F); // Store only enable bits (0-6)
/* Writing the enable word is how software services/acks the BUTCH
* interrupt (the GPU ISR entry does a RMW here every invocation).
* Real BUTCH re-evaluates its IRQ line after the ack: if a condition
* (FIFO half-full, DSA response) still holds, the line rises again
* and TOM latches a NEW edge. Re-arm the edge detector so BUTCHExec
* asserts once per SERVICE CYCLE — not once per condition onset
* (starves transfers: a single missed edge kills the stream) and
* not once per tick (IRQ storm: the GPU latch is pending at every
* instant, so any 68K stop/reprogram/restart of the GPU dispatches
* into the new program before its `movei #$F04000,r31` stack init
* and pushes a return address through stale r31 into GPU code —
* Hover Strike's B-skip lockup). */
cdPrevShouldIRQ = false;
CD_LOG("WriteWord BUTCH+2: data=0x%04X enables=0x%02X [PC=$%06X]\n",
data, data & 0x7F, m68k_get_reg(NULL, M68K_REG_PC));
return;
+12 -2
View File
@@ -43,8 +43,18 @@ static bool bios_instruction_hook(uint32_t m68kPC)
{
/* GPU auth magic — boot ROM checks this to verify GPU ran auth code.
* Empirically still load-bearing: removing it makes every BIOS disc
* loop at $0050B6 because the GPU never naturally writes the magic. */
if (m68kPC == 0x005E40)
* loop at $0050B6 because the GPU never naturally writes the magic.
*
* Gated on the boot stub NOT having been injected yet: $005E40 is a
* BIOS-phase PC, but after handoff the GAME owns that RAM. Myst
* loads its 68K code at $5000-$1D380 and hot paths cross $5E40 —
* every crossing re-stomped $F03000 with the magic, overwriting the
* first two words of Myst's GPU IRQ0 handler stub
* (`movei #$F0D000,r0 / jump (r0)`). The 68K's next command kick
* (G_CTRL=$05, Myst's 68K->GPU doorbell) then dispatched IRQ0 into
* the corrupted slot and the GPU flew off through a stale r0 —
* frozen black screen during boot. */
if (m68kPC == 0x005E40 && !cdBootStubInjected)
{
GPUWriteLong(0xF03000, 0x03D0DEAD, 0);
return true;
+34 -2
View File
@@ -182,6 +182,7 @@ void (*gpu_opcode[64])()=
static uint8_t gpu_ram_8[0x1000];
uint32_t gpu_pc;
/* Diagnostic IRQ counters (see gpu.h). Pure observability — incremented on
* GPUSetIRQLine(line, ASSERT_LINE), reset in GPUReset. */
uint32_t gpu_irq0_count = 0;
@@ -597,6 +598,14 @@ void GPUWriteLong(uint32_t offset, uint32_t data, uint32_t who/*=UNKNOWN*/)
data &= ~0x02;
}
/* Apply GO and the other persistent control bits BEFORE
* processing the CPU->GPU interrupt request below: a single
* write may set GPUGO and INT0 together, and the interrupt
* is only captured by a RUNNING GPU (see GPUSetIRQLine) —
* so the GO bit must land first. $06 (the two transient
* interrupt-request bits) never persists in gpu_control. */
gpu_control = (gpu_control & 0xF7C0) | (data & ~(0xF7C0 | 0x06));
// check for CPU -> GPU interrupt #0
if (data & 0x04)
{
@@ -606,8 +615,6 @@ void GPUWriteLong(uint32_t offset, uint32_t data, uint32_t who/*=UNKNOWN*/)
data &= ~0x04;
}
gpu_control = (gpu_control & 0xF7C0) | (data & (~0xF7C0));
// if gpu wasn't running but is now running, execute a few cycles
#ifdef GPU_SINGLE_STEPPING
if (gpu_control & 0x18)
@@ -657,6 +664,18 @@ void GPUHandleIRQs(void)
{
uint32_t bits, mask;
uint32_t which = 0; //Isn't there a #pragma to disable this warning???
/* A halted GPU (G_CTRL GPUGO=0) latches interrupts but cannot service
* them: the dispatch sequence (push return address via r31, jump to
* vector) is executed by the RISC core itself, and a stopped core
* executes nothing. Dispatching here while stopped pushes a return
* address through the STOPPED context's r31 — if the 68K halted the
* GPU mid-handler (as Hover Strike's CD driver does around seeks),
* that push lands inside GPU code/variables and corrupts them.
* Pending latches are serviced by GPUExec() when the GPU next runs. */
if (!GPU_RUNNING)
return;
// Bail out if we're already in an interrupt!
if (gpu_flags & IMASK)
return;
@@ -717,6 +736,19 @@ void GPUSetIRQLine(int irqline, int state)
if (state)
{
/* A halted GPU (GPUGO=0) does not capture interrupts: the RISC
* core's clock is stopped, so nothing samples the interrupt lines
* and no latch accumulates. Dropping the assert here is what real
* silicon does — the source's LEVEL either still holds when the
* GPU is restarted (and re-edges once software services it) or the
* condition was consumed by the 68K in the meantime (e.g. a polled
* DSA response) and no interrupt should be seen at all. Latching
* here instead meant every 68K stop/reprogram/restart of the GPU
* could dispatch a stale interrupt into the new program before its
* r31 stack init, pushing a return address into GPU code — Hover
* Strike's B-skip lockup. */
if (!GPU_RUNNING)
return;
/* Diagnostic counters — see gpu.h */
if (irqline == 0) gpu_irq0_count++;
else if (irqline == 3) gpu_irq3_count++;
+8 -1
View File
@@ -399,7 +399,14 @@ TEST(butch_dsa_irq_routes_to_gpu_irq1)
core.GPUReset();
/* Mimic the CD BIOS interrupt setup exactly:
* G_FLAGS = INT_ENA1 only; BUTCH = master enable + DSA RX enable. */
* G_FLAGS = INT_ENA1 only; BUTCH = master enable + DSA RX enable.
* GPUGO must be set: the BIOS's GPU is RUNNING (parked in its wait
* loop) when the DSA response arrives — a halted GPU captures no
* interrupts at all (asserts are dropped, not latched; that stale
* latch dispatching at restart was Hover Strike's B-skip lockup).
* INT_ENA1 + a pending latch dispatches immediately on assert, so
* the "running" GPU never needs GPUExec to run garbage code. */
core.GPUWriteLong(0xF02114, 0x00000001, CALLER_M68K);
core.GPUWriteLong(0xF02100, GPU_FLAGS_INT_ENA1, CALLER_M68K);
core.CDROMWriteWord(BUTCH_INT_CTRL + 2,
BUTCH_INT_ENABLE | BUTCH_INT_RBUF_EN, CALLER_M68K);
+28 -6
View File
@@ -1122,12 +1122,17 @@ static void test_jerry_jintctrl_multi_pending_selective_clear(void)
* latch lives in gpu_control bits 6..10, mirrored as INT_LAT0..4.
* GPUSetIRQLine(line, ASSERT_LINE) sets bit (6+line); the latch is
* sticky until SW writes the matching CINTxFLAG bit (bits 9..13) to
* gpu_flags via the memory-mapped F02100 path. With GPU off (HLE
* mode) and INT_ENA cleared, GPUHandleIRQs must NOT advance gpu_pc.
* gpu_flags via the memory-mapped F02100 path.
*
* We deliberately keep INT_ENA0..4 = 0 so HandleIRQs takes the
* "!bits" early-out and never dispatches; this lets us inspect the
* latch directly without perturbing R31/SP or GPU RAM.
* A HALTED GPU (GPUGO=0) captures no interrupts at all: the RISC
* clock is stopped, so asserts are dropped, not latched (see
* GPUSetIRQLine). Latching a stale interrupt across a 68K
* stop/reprogram/restart cycle dispatched it into the new program
* before its r31 stack init — Hover Strike's B-skip lockup. The
* latch mechanics below therefore run with GPUGO=1; INT_ENA0..4
* stay 0 so HandleIRQs takes the "!bits" early-out and never
* dispatches (and the harness never calls GPUExec), which lets us
* inspect the latch without perturbing R31/SP, gpu_pc, or GPU RAM.
*
* Observables (memory-mapped via GPUReadLong):
* $F02100 - gpu_flags (returns gpu_flags & 0xFFFFC1FF)
@@ -1159,6 +1164,20 @@ static void test_gpu_irq_latch_redispatch(void)
p_GPUWriteLong(0xF02100, 0x00000000, WHO_M68K); /* INT_ENA=0, IMASK=0 */
pc_before = p_gpu_pc ? *p_gpu_pc : p_GPUReadLong(0xF02110, WHO_M68K);
/* --- Sub-assert 0: halted GPU captures no interrupts --- */
p_GPUWriteLong(0xF02114, 0x00000000, WHO_M68K); /* GPUGO=0 */
p_GPUSetIRQLine(0, ASSERT_LINE_LOCAL);
ctrl = p_GPUReadLong(0xF02114, WHO_M68K);
if (!(ctrl & 0x40))
PASS("assert while GPUGO=0 is dropped (halted GPU latches nothing)");
else
FAIL("ctrl=$%08X (want bit6 clear: halted GPU must not latch)", ctrl);
/* Latch mechanics below need a running GPU (GPUGO=1). INT_ENA
* stays 0, so nothing can dispatch, and the harness never calls
* GPUExec — the "running" GPU executes no instructions. */
p_GPUWriteLong(0xF02114, 0x00000001, WHO_M68K);
/* --- Sub-assert 1: latch survives without enable --- */
p_GPUSetIRQLine(0, ASSERT_LINE_LOCAL);
ctrl = p_GPUReadLong(0xF02114, WHO_M68K);
@@ -1244,7 +1263,9 @@ static void test_gpu_irq_latch_redispatch(void)
* CD BIOS installs its CD-data ISR.
*
* Pure latch test: INT_ENA all 0 / IMASK 0 so no dispatch happens;
* we just inspect gpu_control bits 6 and 7.
* we just inspect gpu_control bits 6 and 7. GPUGO is set for the
* duration — a halted GPU drops asserts instead of latching them
* (see Test 9d sub-assert 0).
* ================================================================ */
static void test_butch_gpu_irq_line_mapping(void)
{
@@ -1260,6 +1281,7 @@ static void test_butch_gpu_irq_line_mapping(void)
/* Clear any latch and disable enables so this is a pure-latch test. */
p_GPUWriteLong(0xF02100, 0x00003E00, WHO_M68K); /* CINT0..4FLAG -> clear latch */
p_GPUWriteLong(0xF02100, 0x00000000, WHO_M68K); /* INT_ENA=0, IMASK=0 */
p_GPUWriteLong(0xF02114, 0x00000001, WHO_M68K); /* GPUGO=1: halted GPU drops asserts */
/* IRQ line 0 (GPUIRQ_CPU == 0, EXT1, vector $F03000) -> bit 6. */
p_GPUSetIRQLine(0, ASSERT_LINE_LOCAL);
+87 -2
View File
@@ -46,8 +46,53 @@ typedef struct {
unsigned arm_frame;
unsigned freeze_frames;
int wedged;
const char *ram_dump_path;
unsigned snap_frames[8];
unsigned n_snaps;
const char *snap_prefix;
} wp_state;
/* Dump main RAM (2MB) and GPU local RAM ($F03000-$F03FFF) to files. */
static void wp_snapshot(harness_config *cfg, const char *base)
{
/* jaguarMainRAM is a POINTER variable (into jagMemSpace) — dlsym gives
* the address of the pointer; dereference to get the RAM base. */
uint8_t **ramp = (uint8_t **)harness_dlsym(cfg, "jaguarMainRAM");
uint8_t *ram = ramp ? *ramp : NULL;
uint32_t (*p_gpurl)(uint32_t, uint32_t) =
(uint32_t (*)(uint32_t, uint32_t))harness_dlsym(cfg, "GPUReadLong");
uint32_t *p_gpupc = (uint32_t *)harness_dlsym(cfg, "gpu_pc");
char path[1024];
FILE *f;
if (ram) {
snprintf(path, sizeof(path), "%s.ram", base);
f = fopen(path, "wb");
if (f) {
fwrite(ram, 1, 0x200000, f);
fclose(f);
fprintf(stderr, "[WEDGE-PROBE] main RAM -> %s\n", path);
}
}
if (p_gpurl) {
uint32_t a;
snprintf(path, sizeof(path), "%s.gpu", base);
f = fopen(path, "wb");
if (f) {
for (a = 0xF03000; a < 0xF04000; a += 4) {
uint32_t v = p_gpurl(a, 0);
uint8_t b[4];
b[0] = (uint8_t)(v >> 24); b[1] = (uint8_t)(v >> 16);
b[2] = (uint8_t)(v >> 8); b[3] = (uint8_t)v;
fwrite(b, 1, 4, f);
}
fclose(f);
fprintf(stderr, "[WEDGE-PROBE] GPU RAM -> %s (gpu_pc=$%06X)\n",
path, p_gpupc ? *p_gpupc : 0);
}
}
}
static wp_state g_st;
static void wp_video_cb(void *ud, const void *data, unsigned w, unsigned h,
@@ -121,13 +166,32 @@ static void wp_dump(harness_config *cfg, unsigned frame)
fprintf(stderr, "\n");
}
/* GPU PC history ring (TEMP Myst diag in gpu.c) — newest last. */
{
uint32_t *ring = (uint32_t *)harness_dlsym(cfg, "gpu_pc_ring");
uint32_t *rptr = (uint32_t *)harness_dlsym(cfg, "gpu_pc_ring_ptr");
uint32_t *rfrz = (uint32_t *)harness_dlsym(cfg, "gpu_pc_ring_frozen");
if (ring && rptr) {
unsigned n, idx = (*rptr - 64) & 511;
fprintf(stderr, "[WEDGE-PROBE] GPU PC trail (frozen=%u, oldest first):",
rfrz ? *rfrz : 0);
for (n = 0; n < 64; n++) {
if ((n % 6) == 0) fprintf(stderr, "\n[WEDGE-PROBE] ");
fprintf(stderr, "$%06X ", ring[idx]);
idx = (idx + 1) & 511;
}
fprintf(stderr, "\n");
}
}
if (p_cdsum) p_cdsum();
if (p_cddump) p_cddump();
/* Hex-dump main RAM around the spinning code so the wait loop can be
* disassembled offline, plus the HLE DSP done-flag and CDDA mailbox. */
{
uint8_t *ram = (uint8_t *)harness_dlsym(cfg, "jaguarMainRAM");
uint8_t **ramp = (uint8_t **)harness_dlsym(cfg, "jaguarMainRAM");
uint8_t *ram = ramp ? *ramp : NULL;
uint32_t (*p_dspr)(uint32_t, uint32_t) =
(uint32_t (*)(uint32_t, uint32_t))harness_dlsym(cfg, "DSPReadLong");
@@ -153,6 +217,10 @@ static void wp_dump(harness_config *cfg, unsigned frame)
}
}
}
/* Full main-RAM + GPU-RAM snapshot for offline stack walking /
* disassembly of the code the 68K wedged in. */
if (g_st.ram_dump_path)
wp_snapshot(cfg, g_st.ram_dump_path);
if (p_dspr) {
fprintf(stderr, "[WEDGE-PROBE] DSP $F1B4C8 (HLE done flag) = $%08X\n",
p_dspr(0xF1B4C8, 0));
@@ -166,6 +234,15 @@ static void wp_dump(harness_config *cfg, unsigned frame)
static bool wp_frame_cb(void *ud, unsigned frame)
{
harness_config *cfg = (harness_config *)ud;
unsigned i;
for (i = 0; i < g_st.n_snaps; i++) {
if (g_st.snap_frames[i] == frame && g_st.snap_prefix) {
char base[900];
snprintf(base, sizeof(base), "%s_f%u", g_st.snap_prefix, frame);
wp_snapshot(cfg, base);
}
}
if (frame >= g_st.arm_frame && g_st.same_frames >= g_st.freeze_frames) {
g_st.wedged = 1;
@@ -188,6 +265,13 @@ int main(int argc, char **argv)
g_st.arm_frame = (unsigned)atoi(argv[i + 1]);
else if (strcmp(argv[i], "--freeze-frames") == 0 && i + 1 < argc)
g_st.freeze_frames = (unsigned)atoi(argv[i + 1]);
else if (strcmp(argv[i], "--ram-dump") == 0 && i + 1 < argc)
g_st.ram_dump_path = argv[i + 1];
else if (strcmp(argv[i], "--snap") == 0 && i + 1 < argc &&
g_st.n_snaps < 8)
g_st.snap_frames[g_st.n_snaps++] = (unsigned)atoi(argv[i + 1]);
else if (strcmp(argv[i], "--snap-prefix") == 0 && i + 1 < argc)
g_st.snap_prefix = argv[i + 1];
}
cfg.frames = 4800;
@@ -200,7 +284,8 @@ int main(int argc, char **argv)
if (!harness_init_from_args(&cfg, argc, argv)) return 1;
if (!cfg.rom_path) {
fprintf(stderr, "usage: cd_wedge_probe [core] <disc.cue> [--frames N] "
"[--arm N] [--freeze-frames N] [--press F:BTN[:HOLD]]... "
"[--arm N] [--freeze-frames N] [--ram-dump FILE] "
"[--press F:BTN[:HOLD]]... "
"[--system-dir DIR]\n");
return 1;
}