CTR graphical diagnostic

This commit is contained in:
Alex Vanderveen
2026-08-08 01:19:15 -04:00
parent ad4c991f56
commit ea8f1308e6
2 changed files with 535 additions and 2 deletions
+512 -2
View File
@@ -2267,6 +2267,368 @@ static inline void cyc_watch_observe(uint32_t block_leader_phys)
if (s_cyc_watch_hits >= s_cyc_watch_max_hits) s_cyc_watch_armed = 0;
}
/* ---- pc_probe: multi-PC block-leader counters + reg samples (default-off) ----
* Arm via TCP `pc_probe_arm` or env PSX_PC_PROBE / PSX_ND_INTRO_PROBE.
* Samples $t0/$fp/$v0/frame at matched leaders. Disarmed = one branch.
* nd_intro=2 also fills mode/depth/ot_base/ot_index from GP + game struct. */
#define PC_PROBE_MAX_PCS 16
#define PC_PROBE_SAMPLE_CAP 64
typedef struct {
uint32_t pc;
uint64_t count;
uint32_t last_t0;
uint32_t last_fp;
uint32_t last_v0;
uint32_t last_mode;
uint32_t last_depth;
uint32_t last_ot_base;
uint32_t last_ot_index;
uint32_t last_frame;
uint32_t t0_zero;
uint32_t t0_nonzero;
} PcProbeSlot;
typedef struct {
uint32_t pc;
uint32_t frame;
uint32_t t0;
uint32_t fp;
uint32_t v0;
uint32_t mode;
uint32_t depth;
uint32_t ot_base;
uint32_t ot_index;
} PcProbeSample;
static volatile int s_pc_probe_armed = 0;
static int s_pc_probe_n = 0;
static PcProbeSlot s_pc_probe[PC_PROBE_MAX_PCS];
static PcProbeSample s_pc_probe_samples[PC_PROBE_SAMPLE_CAP];
static uint32_t s_pc_probe_sample_n = 0;
static uint32_t s_pc_probe_sample_max = 32;
static void pc_probe_clear_state(void)
{
s_pc_probe_armed = 0;
s_pc_probe_n = 0;
s_pc_probe_sample_n = 0;
memset(s_pc_probe, 0, sizeof(s_pc_probe));
memset(s_pc_probe_samples, 0, sizeof(s_pc_probe_samples));
}
static int pc_probe_add_pc(uint32_t raw)
{
uint32_t phys = raw & 0x1FFFFFFFu;
if (phys == 0) return 0;
for (int i = 0; i < s_pc_probe_n; i++) {
if (s_pc_probe[i].pc == phys) return 1;
}
if (s_pc_probe_n >= PC_PROBE_MAX_PCS) return 0;
s_pc_probe[s_pc_probe_n].pc = phys;
s_pc_probe_n++;
return 1;
}
/* Comma/space-separated hex list, e.g. "0x80044D10,0x80044E58". */
static int pc_probe_parse_list(const char *list)
{
if (!list || !*list) return 0;
char tmp[512];
snprintf(tmp, sizeof(tmp), "%s", list);
int added = 0;
for (char *p = tmp, *tok; (tok = strtok(p, ", \t\n")) != NULL; p = NULL) {
uint32_t raw = (uint32_t)strtoul(tok, NULL, 0);
if (pc_probe_add_pc(raw)) added++;
}
return added;
}
static void pc_probe_arm_nd_intro_defaults(void)
{
/* NdIntroMeshDraw post-RTPT funnel (block leaders with cyc_observe). */
static const uint32_t k[] = {
0x80044580u, /* after dispatch: $fp = v0 OT ptr */
0x80044C80u, /* face clip start */
0x80044CB0u, /* passed neg outcode */
0x80044CDCu, /* passed hi outcode → alloc */
0x80044D10u, /* $t0 == 0 ? skip : emit */
0x80044D18u, /* emit continue */
0x80044DA4u, /* PolyG4 build */
0x80044E58u, /* clip skip */
0x80044EC8u, /* $t0==0 / OOM skip */
};
for (size_t i = 0; i < sizeof(k) / sizeof(k[0]); i++)
pc_probe_add_pc(k[i]);
}
/* OT/depth leafs of NdIntroDrawDispatch — which path returns the OT slot. */
static void pc_probe_arm_nd_intro_ot_defaults(void)
{
static const uint32_t k[] = {
0x80044580u, /* after jal: v0 → $fp OT slot */
0x800440A0u, /* dispatch entry (mode half @ gp+0x4DE) */
0x80044120u, /* mode0 depth scale using *(game+0x1D04) */
0x80044154u, /* return OT base via *(game+buf*4+0x18C8) */
0x800441A8u, /* return *(game+0x25C)+0xFFC (near-ish) */
0x800441C0u, /* mode2 entry */
0x80044268u, /* mode1/ shared OT-base return */
0x80044DA4u, /* PolyG4 emit: confirm $fp sticky */
};
for (size_t i = 0; i < sizeof(k) / sizeof(k[0]); i++)
pc_probe_add_pc(k[i]);
}
/* Textured wood funnel — live path is func_8006A52C (not dead twin 0x8006A6B8). */
static void pc_probe_arm_nd_intro_wood_defaults(void)
{
static const uint32_t k[] = {
0x8006A52Cu, /* NdIntroWoodEmit entry */
0x8006A564u, /* RTPT GTE caller_ra (sticky) */
0x8006A57Cu, /* post-RTPT face loop */
0x8006A590u, /* FLAG read / face setup */
0x8006A5B4u, /* NCLIP */
0x8006A610u, /* next-face hub (skip + post-emit) */
0x8006A69Cu, /* RTPS single-vert return (bgez $0 → loop) */
0x8006A6B8u, /* dead twin entry — expect count=0 */
};
for (size_t i = 0; i < sizeof(k) / sizeof(k[0]); i++)
pc_probe_add_pc(k[i]);
}
/* Depth compare: wood OTZ vs digit/glow OT link ptrs vs MeshDraw OtFar.
* Only block-leader PCs (cyc_observe); mid-block AddPrim sites never fire. */
static void pc_probe_arm_nd_intro_depth_defaults(void)
{
static const uint32_t k[] = {
0x8006A608u, /* wood jalr emit — GTE OTZ */
0x80023094u, /* DigitGt4 entry — $t7 already OT slot */
0x80053E10u, /* glow color ori — $a2=prim; OT via game+0x147C later */
0x800440A0u, /* DrawDispatch entry — GP mode/depth */
0x80044580u, /* MeshDraw after dispatch */
0x800441A8u, /* OtFar leaf */
0x80052F98u, /* DigitFx entry */
0x800444ECu, /* MeshDraw entry — sample $ra */
0x80044DA4u, /* PolyG4 emit hub — lighting bytes on stack */
};
for (size_t i = 0; i < sizeof(k) / sizeof(k[0]); i++)
pc_probe_add_pc(k[i]);
}
/* Wood DL / helper selection: batch setup + which emit helper is bound. */
static void pc_probe_arm_nd_intro_wood_dl_defaults(void)
{
static const uint32_t k[] = {
0x8006AAF0u, /* wood batch setup: a0=stream, ra=material desc */
0x8006AB58u, /* fallthrough past flag gates (accepted batch) */
0x8006ACE0u, /* after lw s5,96(ra) — helper bound */
0x8006A52Cu, /* wood emit entry */
0x8006A608u, /* wood jalr emit — also GTE OTZ/SXY via wood_ot */
0x8006AE90u, /* alt helper — expect 0 in ND */
0x8006AD20u, /* wood batch epilogue / next-opcode hub */
0x80044580u, /* MeshDraw post-RTPT — compare SX band */
};
for (size_t i = 0; i < sizeof(k) / sizeof(k[0]); i++)
pc_probe_add_pc(k[i]);
}
/* Resolve double-buffered OT base: *(*(0x8008D2AC) + 0x18C8 + (*(+0xC)<<2)). */
static void pc_probe_ot_context(CPUState *cpu, uint32_t v0_ot,
uint32_t *mode_out, uint32_t *depth_out,
uint32_t *ot_base_out, uint32_t *ot_index_out)
{
uint32_t mode = 0, depth = 0, ot_base = 0, ot_index = 0xFFFFFFFFu;
if (cpu) {
uint32_t gp = cpu->gpr[28];
mode = psx_read_word(gp + 0x4D4u);
/* Depth countdown half lives at gp+0x4D8 (signed). */
depth = (uint32_t)(int32_t)(int16_t)(psx_read_word(gp + 0x4D8u) & 0xFFFFu);
uint32_t game = psx_read_word(0x8008D2ACu);
if (game) {
uint32_t buf = psx_read_word(game + 0xCu);
ot_base = psx_read_word(game + (buf << 2) + 0x18C8u);
if (ot_base && v0_ot) {
int32_t delta = (int32_t)(v0_ot - ot_base);
if ((delta & 3) == 0 && delta >= 0 && delta < (1 << 20))
ot_index = (uint32_t)(delta >> 2);
}
}
}
if (mode_out) *mode_out = mode;
if (depth_out) *depth_out = depth;
if (ot_base_out) *ot_base_out = ot_base;
if (ot_index_out) *ot_index_out = ot_index;
}
static inline void pc_probe_observe(uint32_t block_leader_phys)
{
if (!s_pc_probe_armed || s_pc_probe_n <= 0) return;
extern CPUState *debug_cpu_ptr;
CPUState *cpu = debug_cpu_ptr;
for (int i = 0; i < s_pc_probe_n; i++) {
if (s_pc_probe[i].pc != block_leader_phys) continue;
PcProbeSlot *s = &s_pc_probe[i];
s->count++;
if (cpu) {
uint32_t t0 = cpu->gpr[8];
uint32_t fp = cpu->gpr[30];
uint32_t v0 = cpu->gpr[2];
uint32_t a1 = cpu->gpr[5];
uint32_t a2 = cpu->gpr[6];
uint32_t t7 = cpu->gpr[15];
uint32_t otz = cpu->gte_data[7] & 0xFFFFu;
uint32_t mode = 0, depth = 0, ot_base = 0, ot_index = 0xFFFFFFFFu;
/* Wood: a2 is stack-table value (often prim-ish); OT slot ≈ ot_base+OTZ.
* Digit GT4 AddPrim @23180: $t7 = OT slot. Glow @53EB8: $a1 = OT head. */
int wood_ot = (block_leader_phys == 0x0006A608u ||
block_leader_phys == 0x0006A600u);
int digit_ot = (block_leader_phys == 0x00023094u);
int glow_ot = (block_leader_phys == 0x00053E10u);
int dispatch = (block_leader_phys == 0x000440A0u);
uint32_t ot_ptr = 0;
if (digit_ot)
ot_ptr = t7; /* caller-supplied OT slot */
else if (glow_ot) {
/* OT head at game+0x147C (same as AddPrim a few insns later). */
uint32_t game = psx_read_word(0x8008D2ACu);
ot_ptr = game ? psx_read_word(game + 0x147Cu) : 0;
} else if (block_leader_phys == 0x00044580u ||
block_leader_phys == 0x000441A8u)
ot_ptr = v0; /* dispatch result / OtFar v0 */
else if (wood_ot)
ot_ptr = 0; /* resolve via OTZ after ot_base known */
else
ot_ptr = fp ? fp : v0;
pc_probe_ot_context(cpu, ot_ptr, &mode, &depth, &ot_base, &ot_index);
if (wood_ot) {
/* Prefer OTZ as OT index (PsyQ AddPrim(ot+OTZ) style). */
if (ot_base && otz < (1u << 20)) {
ot_ptr = ot_base + (otz << 2);
ot_index = otz;
}
t0 = a2;
depth = otz;
v0 = otz;
/* Pack max screen-X of SXY0/1/2 into mode for extent compares. */
{
int32_t sx_max = -0x8000;
for (int si = 12; si <= 14; si++) {
int32_t sx = (int32_t)(int16_t)(cpu->gte_data[si] & 0xFFFFu);
if (sx > sx_max) sx_max = sx;
}
mode = (uint32_t)sx_max;
}
} else if (digit_ot) {
t0 = t7;
v0 = ot_index;
} else if (glow_ot) {
t0 = ot_ptr;
v0 = ot_index;
depth = a2; /* prim ptr */
} else if (dispatch) {
/* Fresh GP mode/depth before leaf runs; v0 not OT yet. */
t0 = mode;
v0 = depth;
} else if (block_leader_phys == 0x000444ECu) {
/* MeshDraw entry: expose caller $ra (no jal-site in EXE). */
t0 = cpu->gpr[31];
v0 = cpu->gpr[2]; /* entry gate */
} else if (block_leader_phys == 0x00044DA4u) {
/* PolyG4 emit: sp+0x40/0x41 hold computed shade bytes. */
uint32_t sp = cpu->gpr[29];
t0 = psx_read_word(sp + 0x40u) & 0xFFFFu;
v0 = fp; /* OT link ptr */
} else if (block_leader_phys == 0x0006A52Cu) {
/* Wood emit entry: $t9=face list, $at=ctx, $s5=emit helper. */
t0 = cpu->gpr[25]; /* t9 */
v0 = cpu->gpr[1]; /* at */
depth = cpu->gpr[21]; /* s5 */
} else if (block_leader_phys == 0x0006ACE0u) {
/* Post helper-select: $s5 set, $a2=model.
* ra may be clobbered by pre-emit jalr; trust s5 + model. */
t0 = cpu->gpr[21]; /* s5 helper */
v0 = cpu->gpr[6]; /* a2 model */
if (v0) {
mode = psx_read_word(v0 + 184u); /* flags */
/* face-list ptr @a2+200; depth = first face word */
uint32_t faces = psx_read_word(v0 + 200u);
depth = faces ? psx_read_word(faces) : 0;
ot_base = faces;
ot_index = faces ? psx_read_word(faces + 4u) : 0;
}
} else if (block_leader_phys == 0x0006AAF0u) {
/* Wood batch setup: $a0 stream, $ra = model (helpers at +92).
* mode=flags@+184, depth=helper@+96, ot_base=name word@+8. */
t0 = cpu->gpr[4]; /* a0 */
v0 = cpu->gpr[31]; /* model */
if (v0) {
mode = psx_read_word(v0 + 184u); /* draw flags */
depth = psx_read_word(v0 + 96u); /* s5 helper */
ot_base = psx_read_word(v0 + 8u); /* name/tag */
ot_index = psx_read_word(v0 + 92u); /* pre-emit */
}
} else if (block_leader_phys == 0x0006AB58u) {
/* Fallthrough past flag bne — batch accepted. */
t0 = cpu->gpr[6]; /* a2 */
v0 = t0 ? psx_read_word(t0 + 184u) : 0;
depth = cpu->gpr[31]; /* desc */
} else if (block_leader_phys == 0x00044580u) {
/* MeshDraw post-dispatch: also pack GTE max SX into mode. */
{
int32_t sx_max = -0x8000;
for (int si = 12; si <= 14; si++) {
int32_t sx = (int32_t)(int16_t)(cpu->gte_data[si] & 0xFFFFu);
if (sx > sx_max) sx_max = sx;
}
mode = (uint32_t)sx_max;
}
} else if (block_leader_phys == 0x000444E8u) {
/* True MeshDraw entry: lh gp+0x4DC → v0 gate (observe pre-lh). */
t0 = cpu->gpr[28]; /* gp */
v0 = cpu->gpr[2];
} else if (block_leader_phys == 0x00069BB0u) {
/* Sibling matrix/OT setup entry: a1=ctx (s4+360), a0=mesh. */
t0 = cpu->gpr[5]; /* a1 */
v0 = cpu->gpr[4]; /* a0 */
if (t0) {
mode = psx_read_word(t0 + 244u); /* OT base field before +4092 */
depth = mode ? (mode + 4092u) : 0;
ot_base = mode;
}
} else if (block_leader_phys == 0x00069C34u ||
block_leader_phys == 0x00069CC4u) {
/* Face-loop jal / entry: a3 = OT origin (last slot). */
t0 = cpu->gpr[7]; /* a3 */
v0 = cpu->gpr[5]; /* a1 */
mode = cpu->gpr[4]; /* a0 mesh */
depth = t0;
ot_base = (t0 >= 4092u) ? (t0 - 4092u) : 0;
}
s->last_t0 = t0;
s->last_fp = fp;
s->last_v0 = v0;
s->last_mode = mode;
s->last_depth = depth;
s->last_ot_base = ot_base;
s->last_ot_index = ot_index;
s->last_frame = (uint32_t)s_frame_count;
if (t0 == 0) s->t0_zero++;
else s->t0_nonzero++;
if (s_pc_probe_sample_n < s_pc_probe_sample_max &&
s_pc_probe_sample_n < PC_PROBE_SAMPLE_CAP) {
PcProbeSample *sm = &s_pc_probe_samples[s_pc_probe_sample_n++];
sm->pc = block_leader_phys;
sm->frame = (uint32_t)s_frame_count;
sm->t0 = t0;
sm->fp = fp;
sm->v0 = v0;
sm->mode = mode;
sm->depth = depth;
sm->ot_base = ot_base;
sm->ot_index = ot_index;
}
}
return;
}
}
/* Exported per-basic-block-leader cycle observer. Emitted by the recompiler at
* EVERY compiled block leader (under #ifndef PSX_NO_DEBUG_TOOLS, so prod builds
* emit nothing — zero overhead) so native's cycle observation matches Beetle's
@@ -2280,11 +2642,58 @@ void debug_server_cyc_observe(uint32_t block_leader_phys) {
return;
#else
if (s_fmv_quiet) return;
cyc_watch_observe(block_leader_phys & 0x1FFFFFFFu);
uint32_t phys = block_leader_phys & 0x1FFFFFFFu;
/* ND debug: PSX_ND_WOOD_CLEAR80=1 clears model+184 bit0x80 at the flag-load
* leader so 0x5CF/'cras' models enter NdIntroWoodBatchSetup's textured path.
* Fires before lw v1,184(a2) @ AB3C. Default-off. */
if (phys == 0x0006AB3Cu) {
static int s_clear80 = -1;
if (s_clear80 < 0) {
const char *e = getenv("PSX_ND_WOOD_CLEAR80");
s_clear80 = (e && *e && *e != '0') ? 1 : 0;
if (s_clear80)
fprintf(stdout, "psxrecomp: PSX_ND_WOOD_CLEAR80 enabled\n");
}
if (s_clear80 && debug_cpu_ptr) {
uint32_t a2 = debug_cpu_ptr->gpr[6];
if (a2) {
uint32_t fl = psx_read_word(a2 + 184u);
if (fl & 0x80u)
psx_write_word(a2 + 184u, fl & ~0x80u);
}
}
}
/* ND debug: PSX_ND_SIB_OT_LIFT=<n> adds n*4 to $a3 at sibling face-loop
* entry 0x80069CC4 (a3 = *(a1+244)+4092 = last OT slot). Sibling PolyFT3
* right-flap faces use face_hi≈0 → farthest bucket (drawn before additive
* 0x36 glow). Negative n (e.g. -800) moves them nearer in the 1024-entry OT.
* Applied once per a3 value. */
if (phys == 0x00069CC4u && debug_cpu_ptr) {
static int s_sib_lift = -2; /* -2 unset, 0 disabled, else delta */
static uint32_t s_sib_lifted_a3 = 0;
if (s_sib_lift == -2) {
const char *e = getenv("PSX_ND_SIB_OT_LIFT");
if (e && *e) {
s_sib_lift = atoi(e);
fprintf(stdout, "psxrecomp: PSX_ND_SIB_OT_LIFT=%d\n", s_sib_lift);
} else {
s_sib_lift = 0;
}
}
if (s_sib_lift != 0) {
uint32_t a3 = debug_cpu_ptr->gpr[7];
if (a3 && a3 != s_sib_lifted_a3) {
debug_cpu_ptr->gpr[7] = a3 + (uint32_t)(int32_t)(s_sib_lift * 4);
s_sib_lifted_a3 = debug_cpu_ptr->gpr[7];
}
}
}
cyc_watch_observe(phys);
pc_probe_observe(phys);
/* #2 lockstep comparator: per-basic-block compiled-vs-interp check. Self-gates
* on the armed frame window; ~free (one branch) when disarmed. */
{ extern void ls_at_leader(uint32_t, CPUState*); extern CPUState *debug_cpu_ptr;
ls_at_leader(block_leader_phys & 0x1FFFFFFFu, debug_cpu_ptr); }
ls_at_leader(phys, debug_cpu_ptr); }
#endif
}
@@ -9034,6 +9443,81 @@ static void handle_cyc_watch_clear(int id, const char *json)
send_ok(id);
}
/* pc_probe_arm — {"pcs":"0xA,0xB", "n":32, "nd_intro":1..5} or empty pcs+nd_intro.
* 1=PolyG4 clip 2=OT leaves 3=wood 4=depth 5=wood DL/helper bind. */
static void handle_pc_probe_arm(int id, const char *json)
{
pc_probe_clear_state();
int n = json_get_int(json, "n", 32);
if (n < 1) n = 1;
if (n > PC_PROBE_SAMPLE_CAP) n = PC_PROBE_SAMPLE_CAP;
s_pc_probe_sample_max = (uint32_t)n;
int nd = json_get_int(json, "nd_intro", 0);
char pcs[512];
const char *have_pcs = json_get_str(json, "pcs", pcs, sizeof(pcs));
if (nd == 5) pc_probe_arm_nd_intro_wood_dl_defaults();
else if (nd == 4) pc_probe_arm_nd_intro_depth_defaults();
else if (nd == 3) pc_probe_arm_nd_intro_wood_defaults();
else if (nd == 2) pc_probe_arm_nd_intro_ot_defaults();
else if (nd) pc_probe_arm_nd_intro_defaults();
if (have_pcs) pc_probe_parse_list(pcs);
if (s_pc_probe_n <= 0) {
send_err(id, "pc_probe_arm needs pcs=... and/or nd_intro=1..5");
return;
}
s_pc_probe_armed = 1;
send_fmt("{\"id\":%d,\"ok\":true,\"armed\":1,\"n_pcs\":%d,\"sample_max\":%u,\"nd_intro\":%d}",
id, s_pc_probe_n, s_pc_probe_sample_max, nd);
}
static void handle_pc_probe_dump(int id, const char *json)
{
(void)json;
char buf[512];
snprintf(buf, sizeof(buf),
"{\"id\":%d,\"ok\":true,\"armed\":%d,\"n_pcs\":%d,\"sample_max\":%u,"
"\"samples_n\":%u,\"slots\":[",
id, s_pc_probe_armed ? 1 : 0, s_pc_probe_n, s_pc_probe_sample_max,
s_pc_probe_sample_n);
send_line(buf);
for (int i = 0; i < s_pc_probe_n; i++) {
const PcProbeSlot *s = &s_pc_probe[i];
snprintf(buf, sizeof(buf),
"%s{\"pc\":\"0x%08X\",\"count\":%llu,\"t0_zero\":%u,\"t0_nonzero\":%u,"
"\"last_t0\":\"0x%08X\",\"last_fp\":\"0x%08X\",\"last_v0\":\"0x%08X\","
"\"last_mode\":\"0x%08X\",\"last_depth\":\"0x%08X\","
"\"last_ot_base\":\"0x%08X\",\"last_ot_index\":\"0x%08X\","
"\"last_frame\":%u}",
(i == 0) ? "" : ",",
s->pc, (unsigned long long)s->count, s->t0_zero, s->t0_nonzero,
s->last_t0, s->last_fp, s->last_v0,
s->last_mode, s->last_depth, s->last_ot_base, s->last_ot_index,
s->last_frame);
send_line(buf);
}
send_line("],\"samples\":[");
for (uint32_t i = 0; i < s_pc_probe_sample_n; i++) {
const PcProbeSample *sm = &s_pc_probe_samples[i];
snprintf(buf, sizeof(buf),
"%s{\"pc\":\"0x%08X\",\"frame\":%u,\"t0\":\"0x%08X\","
"\"fp\":\"0x%08X\",\"v0\":\"0x%08X\",\"mode\":\"0x%08X\","
"\"depth\":\"0x%08X\",\"ot_base\":\"0x%08X\",\"ot_index\":\"0x%08X\"}",
(i == 0) ? "" : ",",
sm->pc, sm->frame, sm->t0, sm->fp, sm->v0,
sm->mode, sm->depth, sm->ot_base, sm->ot_index);
send_line(buf);
}
send_line("]}");
}
static void handle_pc_probe_clear(int id, const char *json)
{
(void)json;
pc_probe_clear_state();
send_ok(id);
}
/* Liveness/freeze diagnostic: returns a single snapshot of every counter
* that distinguishes "stuck in a tight handler loop" from "just slow" or
* "starved on TCP poll". Pass {"window":N} (default 256) to also include
@@ -12734,6 +13218,9 @@ static const CmdEntry s_commands[] = {
{ "cyc_watch", handle_cyc_watch },
{ "cyc_watch_dump", handle_cyc_watch_dump },
{ "cyc_watch_clear", handle_cyc_watch_clear },
{ "pc_probe_arm", handle_pc_probe_arm },
{ "pc_probe_dump", handle_pc_probe_dump },
{ "pc_probe_clear", handle_pc_probe_clear },
{ "mmio_dump", handle_mmio_dump },
{ "mmio_clear", handle_mmio_clear },
{ "capture_freeze", handle_capture_freeze },
@@ -12933,6 +13420,29 @@ void debug_server_init(int port)
if (s_rwatch_hi > s_rwatch_lo) g_ram_read_watch_active = 1;
}
}
/* PSX_ND_INTRO_PROBE=1 PolyG4; =2 OT; =3 wood; =4 depth; =5 wood DL.
* PSX_PC_PROBE="0xA,0xB" arms an explicit list (can combine). */
{
const char *nd = getenv("PSX_ND_INTRO_PROBE");
const char *pcs = getenv("PSX_PC_PROBE");
if ((nd && *nd && *nd != '0') || (pcs && *pcs)) {
pc_probe_clear_state();
s_pc_probe_sample_max = 48;
if (nd && *nd && *nd != '0') {
int ndv = (int)strtol(nd, NULL, 0);
if (ndv == 5) pc_probe_arm_nd_intro_wood_dl_defaults();
else if (ndv == 4) pc_probe_arm_nd_intro_depth_defaults();
else if (ndv == 3) pc_probe_arm_nd_intro_wood_defaults();
else if (ndv == 2) pc_probe_arm_nd_intro_ot_defaults();
else pc_probe_arm_nd_intro_defaults();
}
if (pcs && *pcs) pc_probe_parse_list(pcs);
if (s_pc_probe_n > 0) {
s_pc_probe_armed = 1;
fprintf(stdout, "psxrecomp: pc_probe armed (%d pcs)\n", s_pc_probe_n);
}
}
}
}
#ifdef _WIN32
+23
View File
@@ -437,6 +437,29 @@ def build_cmd(args):
if len(args) > 2:
d["nth"] = int(args[2])
return d, pretty_json
elif cmd == "pc_probe_arm":
# pc_probe_arm [nd_intro=1|2|3|nd|nd_ot|nd_wood] [pcs=0xA,0xB] [n=32]
d = {"cmd": "pc_probe_arm"}
for a in args[1:]:
if "=" in a:
k, v = a.split("=", 1)
if k in ("n", "nd_intro") and v.lstrip("-").isdigit():
d[k] = int(v)
else:
d[k] = v
elif a in ("nd", "nd_intro"):
d["nd_intro"] = 1
elif a in ("nd_ot", "ot"):
d["nd_intro"] = 2
elif a in ("nd_wood", "wood"):
d["nd_intro"] = 3
elif a in ("nd_depth", "depth"):
d["nd_intro"] = 4
return d, pretty_json
elif cmd == "pc_probe_dump":
return {"cmd": "pc_probe_dump"}, pretty_json
elif cmd == "pc_probe_clear":
return {"cmd": "pc_probe_clear"}, pretty_json
elif cmd == "fntrace_arm":
# <target_hex|0xFFFFFFFF=all|0=clear>
return {"cmd": "fntrace_arm", "target": args[1]}, pretty_json