feat: 46 GTE library seeds + DuckStation oracle SW readback + debug tooling

Add 46 seeds for the libgte helper functions at BFC34E00-BFC36D00.
These wrap individual GTE operations (RTPS, RTPT, NCLIP, MVMVA, etc.)
and were never discovered because they're called via JALR function
pointers, not direct JAL. Coverage: 52→114 gte_execute, 166→429
LWC2/SWC2. Emitted functions: 1178→1214. Seeds: 960→1006.

Fix DuckStation oracle VRAM readback by setting
UseSoftwareRendererForReadbacks=true in settings.ini.

Add debug/analysis tools: GTE audit, VRAM stitching, rapid polling,
GPU info, divergence comparison, jump table dumps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthew Stanley
2026-04-24 00:29:06 -07:00
parent 20625d5803
commit 101fd2e1a9
17 changed files with 5098 additions and 2356 deletions
+85 -3
View File
@@ -1,7 +1,7 @@
/* AUTO-GENERATED by psxrecomp-bios --emit-full. DO NOT EDIT.
*
* BIOS SHA256: 71af94d1e47a68c11e8fdb9f8368040601514a42a5a399cda48c7d3bff1e99d3
* Dispatch entries: 1174
* Dispatch entries: 1215
*/
#include "cpu_state.h"
@@ -621,6 +621,7 @@ extern void func_1FC20F8C(CPUState* cpu);
extern void func_1FC20FE8(CPUState* cpu);
extern void func_1FC21044(CPUState* cpu);
extern void func_1FC210C0(CPUState* cpu);
extern void func_1FC210F0(CPUState* cpu);
extern void func_1FC21128(CPUState* cpu);
extern void func_1FC21184(CPUState* cpu);
extern void func_1FC21200(CPUState* cpu);
@@ -923,6 +924,7 @@ extern void func_1FC34AA0(CPUState* cpu);
extern void func_1FC34AF0(CPUState* cpu);
extern void func_1FC34B08(CPUState* cpu);
extern void func_1FC34B58(CPUState* cpu);
extern void func_1FC34E00(CPUState* cpu);
extern void func_1FC34EC0(CPUState* cpu);
extern void func_1FC34EC4(CPUState* cpu);
extern void func_1FC35128(CPUState* cpu);
@@ -959,9 +961,44 @@ extern void func_1FC35F38(CPUState* cpu);
extern void func_1FC35F78(CPUState* cpu);
extern void func_1FC35FA0(CPUState* cpu);
extern void func_1FC35FD0(CPUState* cpu);
extern void func_1FC36000(CPUState* cpu);
extern void func_1FC36024(CPUState* cpu);
extern void func_1FC36068(CPUState* cpu);
extern void func_1FC36094(CPUState* cpu);
extern void func_1FC360E0(CPUState* cpu);
extern void func_1FC36104(CPUState* cpu);
extern void func_1FC3614C(CPUState* cpu);
extern void func_1FC36178(CPUState* cpu);
extern void func_1FC361A0(CPUState* cpu);
extern void func_1FC361CC(CPUState* cpu);
extern void func_1FC361F4(CPUState* cpu);
extern void func_1FC3621C(CPUState* cpu);
extern void func_1FC36244(CPUState* cpu);
extern void func_1FC3625C(CPUState* cpu);
extern void func_1FC36280(CPUState* cpu);
extern void func_1FC36294(CPUState* cpu);
extern void func_1FC362BC(CPUState* cpu);
extern void func_1FC362D0(CPUState* cpu);
extern void func_1FC3632C(CPUState* cpu);
extern void func_1FC36388(CPUState* cpu);
extern void func_1FC363A0(CPUState* cpu);
extern void func_1FC363F0(CPUState* cpu);
extern void func_1FC36458(CPUState* cpu);
extern void func_1FC364DC(CPUState* cpu);
extern void func_1FC3653C(CPUState* cpu);
extern void func_1FC365C4(CPUState* cpu);
extern void func_1FC365F8(CPUState* cpu);
extern void func_1FC36684(CPUState* cpu);
extern void func_1FC36740(CPUState* cpu);
extern void func_1FC367D4(CPUState* cpu);
extern void func_1FC36880(CPUState* cpu);
extern void func_1FC36940(CPUState* cpu);
extern void func_1FC36998(CPUState* cpu);
extern void func_1FC36A3C(CPUState* cpu);
extern void func_1FC36B14(CPUState* cpu);
extern void func_1FC36BEC(CPUState* cpu);
extern void func_1FC36C60(CPUState* cpu);
extern void func_1FC36CA4(CPUState* cpu);
extern void func_1FC36CF0(CPUState* cpu);
extern void func_1FC36D1C(CPUState* cpu);
extern void func_1FC36D48(CPUState* cpu);
@@ -1156,7 +1193,11 @@ extern void func_1FC41BA8(CPUState* cpu);
extern void func_1FC41C04(CPUState* cpu);
extern void func_1FC41C0C(CPUState* cpu);
extern void func_1FC41C80(CPUState* cpu);
extern void func_1FC41D18(CPUState* cpu);
extern void func_1FC41DA4(CPUState* cpu);
extern void func_1FC41DC8(CPUState* cpu);
extern void func_1FC41DFC(CPUState* cpu);
extern void func_1FC41E14(CPUState* cpu);
extern void func_1FC41E30(CPUState* cpu);
extern void func_1FC41ED8(CPUState* cpu);
extern void func_1FC41F10(CPUState* cpu);
@@ -1200,7 +1241,7 @@ typedef struct {
PsxRecompFunc func;
} DispatchEntry;
static const DispatchEntry dispatch_table[1174] = {
static const DispatchEntry dispatch_table[1215] = {
{ 0x00000500u, func_00000500 },
{ 0x00000510u, func_00000510 },
{ 0x00000520u, func_00000520 },
@@ -1804,6 +1845,7 @@ static const DispatchEntry dispatch_table[1174] = {
{ 0x1FC20FE8u, func_1FC20FE8 },
{ 0x1FC21044u, func_1FC21044 },
{ 0x1FC210C0u, func_1FC210C0 },
{ 0x1FC210F0u, func_1FC210F0 },
{ 0x1FC21128u, func_1FC21128 },
{ 0x1FC21184u, func_1FC21184 },
{ 0x1FC21200u, func_1FC21200 },
@@ -2106,6 +2148,7 @@ static const DispatchEntry dispatch_table[1174] = {
{ 0x1FC34AF0u, func_1FC34AF0 },
{ 0x1FC34B08u, func_1FC34B08 },
{ 0x1FC34B58u, func_1FC34B58 },
{ 0x1FC34E00u, func_1FC34E00 },
{ 0x1FC34EC0u, func_1FC34EC0 },
{ 0x1FC34EC4u, func_1FC34EC4 },
{ 0x1FC35128u, func_1FC35128 },
@@ -2142,9 +2185,44 @@ static const DispatchEntry dispatch_table[1174] = {
{ 0x1FC35F78u, func_1FC35F78 },
{ 0x1FC35FA0u, func_1FC35FA0 },
{ 0x1FC35FD0u, func_1FC35FD0 },
{ 0x1FC36000u, func_1FC36000 },
{ 0x1FC36024u, func_1FC36024 },
{ 0x1FC36068u, func_1FC36068 },
{ 0x1FC36094u, func_1FC36094 },
{ 0x1FC360E0u, func_1FC360E0 },
{ 0x1FC36104u, func_1FC36104 },
{ 0x1FC3614Cu, func_1FC3614C },
{ 0x1FC36178u, func_1FC36178 },
{ 0x1FC361A0u, func_1FC361A0 },
{ 0x1FC361CCu, func_1FC361CC },
{ 0x1FC361F4u, func_1FC361F4 },
{ 0x1FC3621Cu, func_1FC3621C },
{ 0x1FC36244u, func_1FC36244 },
{ 0x1FC3625Cu, func_1FC3625C },
{ 0x1FC36280u, func_1FC36280 },
{ 0x1FC36294u, func_1FC36294 },
{ 0x1FC362BCu, func_1FC362BC },
{ 0x1FC362D0u, func_1FC362D0 },
{ 0x1FC3632Cu, func_1FC3632C },
{ 0x1FC36388u, func_1FC36388 },
{ 0x1FC363A0u, func_1FC363A0 },
{ 0x1FC363F0u, func_1FC363F0 },
{ 0x1FC36458u, func_1FC36458 },
{ 0x1FC364DCu, func_1FC364DC },
{ 0x1FC3653Cu, func_1FC3653C },
{ 0x1FC365C4u, func_1FC365C4 },
{ 0x1FC365F8u, func_1FC365F8 },
{ 0x1FC36684u, func_1FC36684 },
{ 0x1FC36740u, func_1FC36740 },
{ 0x1FC367D4u, func_1FC367D4 },
{ 0x1FC36880u, func_1FC36880 },
{ 0x1FC36940u, func_1FC36940 },
{ 0x1FC36998u, func_1FC36998 },
{ 0x1FC36A3Cu, func_1FC36A3C },
{ 0x1FC36B14u, func_1FC36B14 },
{ 0x1FC36BECu, func_1FC36BEC },
{ 0x1FC36C60u, func_1FC36C60 },
{ 0x1FC36CA4u, func_1FC36CA4 },
{ 0x1FC36CF0u, func_1FC36CF0 },
{ 0x1FC36D1Cu, func_1FC36D1C },
{ 0x1FC36D48u, func_1FC36D48 },
@@ -2339,7 +2417,11 @@ static const DispatchEntry dispatch_table[1174] = {
{ 0x1FC41C04u, func_1FC41C04 },
{ 0x1FC41C0Cu, func_1FC41C0C },
{ 0x1FC41C80u, func_1FC41C80 },
{ 0x1FC41D18u, func_1FC41D18 },
{ 0x1FC41DA4u, func_1FC41DA4 },
{ 0x1FC41DC8u, func_1FC41DC8 },
{ 0x1FC41DFCu, func_1FC41DFC },
{ 0x1FC41E14u, func_1FC41E14 },
{ 0x1FC41E30u, func_1FC41E30 },
{ 0x1FC41ED8u, func_1FC41ED8 },
{ 0x1FC41F10u, func_1FC41F10 },
@@ -2395,7 +2477,7 @@ void psx_dispatch(CPUState* cpu, uint32_t addr) {
for (;;) {
cpu->pc = 0;
uint32_t phys = normalize(addr);
int lo = 0, hi = 1174 - 1;
int lo = 0, hi = 1215 - 1;
int found = 0;
while (lo <= hi) {
int mid = (lo + hi) / 2;
+4174 -2334
View File
File diff suppressed because it is too large Load Diff
+1 -17
View File
@@ -1,19 +1,3 @@
[
{"address": "0xBFC02324", "reason": "addr=0xBFC0233C raw=0x44801000 opcode=0x11: top-level opcode 0x11 not implemented in Phase 1a"},
{"address": "0xBFC336F0", "reason": "addr=0xBFC3370C raw=0x48C8E800 opcode=0x12: top-level opcode 0x12 not implemented in Phase 1a"},
{"address": "0xBFC33C98", "reason": "addr=0xBFC33C98 raw=0x4884F000 opcode=0x12: top-level opcode 0x12 not implemented in Phase 1a"},
{"address": "0xBFC33CC0", "reason": "addr=0xBFC33CCC raw=0x012A4822 opcode=0x00: SPECIAL funct 0x22 not implemented in Phase 1a strict translator"},
{"address": "0xBFC33F00", "reason": "addr=0xBFC33F0C raw=0x48C46800 opcode=0x12: top-level opcode 0x12 not implemented in Phase 1a"},
{"address": "0xBFC33F20", "reason": "addr=0xBFC33F2C raw=0x48C4A800 opcode=0x12: top-level opcode 0x12 not implemented in Phase 1a"},
{"address": "0xBFC33FB4", "reason": "addr=0xBFC33FBC raw=0x48C4C000 opcode=0x12: top-level opcode 0x12 not implemented in Phase 1a"},
{"address": "0xBFC33FCC", "reason": "addr=0xBFC33FCC raw=0x48C4D000 opcode=0x12: top-level opcode 0x12 not implemented in Phase 1a"},
{"address": "0xBFC33FD8", "reason": "addr=0xBFC33FD8 raw=0x48C4D800 opcode=0x12: top-level opcode 0x12 not implemented in Phase 1a"},
{"address": "0xBFC33FE4", "reason": "addr=0xBFC33FE4 raw=0x48C4E000 opcode=0x12: top-level opcode 0x12 not implemented in Phase 1a"},
{"address": "0xBFC35AD8", "reason": "addr=0xBFC35AEC raw=0x48C80000 opcode=0x12: top-level opcode 0x12 not implemented in Phase 1a"},
{"address": "0xBFC35B08", "reason": "addr=0xBFC35B1C raw=0x48C84000 opcode=0x12: top-level opcode 0x12 not implemented in Phase 1a"},
{"address": "0xBFC35B38", "reason": "addr=0xBFC35B4C raw=0x48C88000 opcode=0x12: top-level opcode 0x12 not implemented in Phase 1a"},
{"address": "0xBFC35B68", "reason": "addr=0xBFC35B74 raw=0x48C82800 opcode=0x12: top-level opcode 0x12 not implemented in Phase 1a"},
{"address": "0xBFC36068", "reason": "addr=0xBFC36068 raw=0xC8800000 opcode=0x32: top-level opcode 0x32 not implemented in Phase 1a"},
{"address": "0xBFC362D0", "reason": "addr=0xBFC362D0 raw=0x484D0000 opcode=0x12: top-level opcode 0x12 not implemented in Phase 1a"},
{"address": "0xBFC36740", "reason": "addr=0xBFC36740 raw=0xC8800000 opcode=0x32: top-level opcode 0x32 not implemented in Phase 1a"}
{"address": "0xBFC02324", "reason": "addr=0xBFC0233C raw=0x44801000 opcode=0x11: top-level opcode 0x11 not implemented in Phase 1a"}
]
+186 -2
View File
@@ -1,7 +1,7 @@
{
"schema": "psxrecomp-v4 phase2 seeds",
"source": "generated/ghidra_function_starts.json + phase1c seeds + dispatch_miss_seeds + preserved manual",
"seed_count": 960,
"source": "generated/ghidra_function_starts.json + phase1c seeds + dispatch_miss_seeds + preserved manual + gte_lib_seeds",
"seed_count": 1006,
"seeds": [
{
"address": "0xBFC00000",
@@ -4802,6 +4802,190 @@
"address": "0xBFC41E14",
"label": "dispatch_miss_0xBFC41E14",
"rationale": "Phase 4.5 dispatch miss from 3D rendering seeds"
},
{
"address": "0xBFC34E00",
"name": "gte_lib_BFC34E00"
},
{
"address": "0xBFC3586C",
"name": "gte_lib_BFC3586C"
},
{
"address": "0xBFC359B4",
"name": "gte_lib_BFC359B4"
},
{
"address": "0xBFC35AD8",
"name": "gte_lib_BFC35AD8"
},
{
"address": "0xBFC35B08",
"name": "gte_lib_BFC35B08"
},
{
"address": "0xBFC35B38",
"name": "gte_lib_BFC35B38"
},
{
"address": "0xBFC35B68",
"name": "gte_lib_BFC35B68"
},
{
"address": "0xBFC36000",
"name": "gte_lib_BFC36000"
},
{
"address": "0xBFC36024",
"name": "gte_lib_BFC36024"
},
{
"address": "0xBFC36068",
"name": "gte_lib_BFC36068"
},
{
"address": "0xBFC36094",
"name": "gte_lib_BFC36094"
},
{
"address": "0xBFC360E0",
"name": "gte_lib_BFC360E0"
},
{
"address": "0xBFC36104",
"name": "gte_lib_BFC36104"
},
{
"address": "0xBFC3614C",
"name": "gte_lib_BFC3614C"
},
{
"address": "0xBFC36178",
"name": "gte_lib_BFC36178"
},
{
"address": "0xBFC361A0",
"name": "gte_lib_BFC361A0"
},
{
"address": "0xBFC361CC",
"name": "gte_lib_BFC361CC"
},
{
"address": "0xBFC361F4",
"name": "gte_lib_BFC361F4"
},
{
"address": "0xBFC3621C",
"name": "gte_lib_BFC3621C"
},
{
"address": "0xBFC36244",
"name": "gte_lib_BFC36244"
},
{
"address": "0xBFC3625C",
"name": "gte_lib_BFC3625C"
},
{
"address": "0xBFC36280",
"name": "gte_lib_BFC36280"
},
{
"address": "0xBFC36294",
"name": "gte_lib_BFC36294"
},
{
"address": "0xBFC362BC",
"name": "gte_lib_BFC362BC"
},
{
"address": "0xBFC362D0",
"name": "gte_lib_BFC362D0"
},
{
"address": "0xBFC3632C",
"name": "gte_lib_BFC3632C"
},
{
"address": "0xBFC36388",
"name": "gte_lib_BFC36388"
},
{
"address": "0xBFC363A0",
"name": "gte_lib_BFC363A0"
},
{
"address": "0xBFC363F0",
"name": "gte_lib_BFC363F0"
},
{
"address": "0xBFC36458",
"name": "gte_lib_BFC36458"
},
{
"address": "0xBFC364DC",
"name": "gte_lib_BFC364DC"
},
{
"address": "0xBFC3653C",
"name": "gte_lib_BFC3653C"
},
{
"address": "0xBFC365C4",
"name": "gte_lib_BFC365C4"
},
{
"address": "0xBFC365F8",
"name": "gte_lib_BFC365F8"
},
{
"address": "0xBFC36684",
"name": "gte_lib_BFC36684"
},
{
"address": "0xBFC36740",
"name": "gte_lib_BFC36740"
},
{
"address": "0xBFC367D4",
"name": "gte_lib_BFC367D4"
},
{
"address": "0xBFC36880",
"name": "gte_lib_BFC36880"
},
{
"address": "0xBFC36940",
"name": "gte_lib_BFC36940"
},
{
"address": "0xBFC36998",
"name": "gte_lib_BFC36998"
},
{
"address": "0xBFC36A3C",
"name": "gte_lib_BFC36A3C"
},
{
"address": "0xBFC36B14",
"name": "gte_lib_BFC36B14"
},
{
"address": "0xBFC36BEC",
"name": "gte_lib_BFC36BEC"
},
{
"address": "0xBFC36C60",
"name": "gte_lib_BFC36C60"
},
{
"address": "0xBFC36CA4",
"name": "gte_lib_BFC36CA4"
},
{
"address": "0xBFC36FF0",
"name": "gte_lib_BFC36FF0"
}
],
"excluded": [
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env python3
"""Compare two images and report pixel differences."""
import sys
from PIL import Image
a = Image.open(sys.argv[1])
b = Image.open(sys.argv[2])
diff = 0
for y in range(a.height):
for x in range(a.width):
if a.getpixel((x, y)) != b.getpixel((x, y)):
diff += 1
print(f'Pixels different: {diff} out of {a.width * a.height}')
+42
View File
@@ -0,0 +1,42 @@
#!/usr/bin/env python3
"""Capture DuckStation oracle VRAM as screenshot via debug server."""
import socket, json, sys
from PIL import Image
port = int(sys.argv[1]) if len(sys.argv) > 1 else 4371
out = sys.argv[2] if len(sys.argv) > 2 else "ds_screenshot.png"
s = socket.create_connection(('localhost', port))
cmd = '{"cmd":"read_vram","x":0,"y":0,"w":640,"h":480}\n'
s.sendall(cmd.encode())
data = b''
while True:
chunk = s.recv(65536)
if not chunk:
break
data += chunk
if b'\n' in data:
break
s.close()
resp = json.loads(data.split(b'\n')[0])
if not resp.get('ok'):
print('Error:', resp)
sys.exit(1)
pixels = bytes.fromhex(resp['pixels'])
w, h = resp['w'], resp['h']
img = Image.new('RGB', (w, h))
for y in range(h):
for x in range(w):
off = (y * w + x) * 2
if off + 1 >= len(pixels):
break
val = pixels[off] | (pixels[off + 1] << 8)
r = (val & 0x1F) << 3
g = ((val >> 5) & 0x1F) << 3
b = ((val >> 10) & 0x1F) << 3
img.putpixel((x, y), (r, g, b))
img.save(out)
print(f'Saved {out} ({w}x{h})')
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env python3
"""Stitch DuckStation VRAM into a full screenshot using small tile reads."""
import socket, json, sys
from PIL import Image
port = int(sys.argv[1]) if len(sys.argv) > 1 else 4371
out = sys.argv[2] if len(sys.argv) > 2 else "ds_screenshot.png"
FULL_W, FULL_H = 320, 240
TILE = 32
img = Image.new('RGB', (FULL_W, FULL_H))
for ty in range(0, FULL_H, TILE):
for tx in range(0, FULL_W, TILE):
tw = min(TILE, FULL_W - tx)
th = min(TILE, FULL_H - ty)
try:
s = socket.create_connection(('localhost', port), timeout=5)
cmd = f'{{"cmd":"read_vram","x":{tx},"y":{ty},"w":{tw},"h":{th}}}\n'
s.sendall(cmd.encode())
data = b''
while True:
chunk = s.recv(65536)
if not chunk:
break
data += chunk
if b'\n' in data:
break
s.close()
resp = json.loads(data.split(b'\n')[0])
if not resp.get('ok'):
continue
pixels = bytes.fromhex(resp['pixels'])
rw, rh = resp['w'], resp['h']
for py in range(rh):
for px in range(rw):
off = (py * rw + px) * 2
if off + 1 >= len(pixels):
break
val = pixels[off] | (pixels[off + 1] << 8)
r = (val & 0x1F) << 3
g = ((val >> 5) & 0x1F) << 3
b = ((val >> 10) & 0x1F) << 3
img.putpixel((tx + px, ty + py), (r, g, b))
except Exception as e:
print(f'Tile ({tx},{ty}) failed: {e}', file=sys.stderr)
img.save(out)
print(f'Saved {out} ({FULL_W}x{FULL_H})')
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env python3
"""Stitch VRAM tiles with configurable start position."""
import socket, json, sys
from PIL import Image
port = int(sys.argv[1]) if len(sys.argv) > 1 else 4371
out = sys.argv[2] if len(sys.argv) > 2 else "vram_capture.png"
START_X = int(sys.argv[3]) if len(sys.argv) > 3 else 0
START_Y = int(sys.argv[4]) if len(sys.argv) > 4 else 0
FULL_W = int(sys.argv[5]) if len(sys.argv) > 5 else 320
FULL_H = int(sys.argv[6]) if len(sys.argv) > 6 else 240
TILE = 32
img = Image.new('RGB', (FULL_W, FULL_H))
for ty in range(0, FULL_H, TILE):
for tx in range(0, FULL_W, TILE):
tw = min(TILE, FULL_W - tx)
th = min(TILE, FULL_H - ty)
try:
s = socket.create_connection(('localhost', port), timeout=5)
cmd = f'{{"cmd":"read_vram","x":{START_X+tx},"y":{START_Y+ty},"w":{tw},"h":{th}}}\n'
s.sendall(cmd.encode())
data = b''
while b'\n' not in data:
data += s.recv(65536)
s.close()
resp = json.loads(data.split(b'\n')[0])
if not resp.get('ok'):
continue
pixels = bytes.fromhex(resp['pixels'])
rw, rh = resp['w'], resp['h']
for py in range(rh):
for px in range(rw):
off = (py * rw + px) * 2
if off + 1 >= len(pixels):
break
val = pixels[off] | (pixels[off + 1] << 8)
r = (val & 0x1F) << 3
g = ((val >> 5) & 0x1F) << 3
b = ((val >> 10) & 0x1F) << 3
img.putpixel((tx + px, ty + py), (r, g, b))
except Exception as e:
print(f'Tile ({START_X+tx},{START_Y+ty}) failed: {e}', file=sys.stderr)
img.save(out)
print(f'Saved {out} ({FULL_W}x{FULL_H} from VRAM {START_X},{START_Y})')
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env python3
"""Dump a jump table from PSX RAM via debug server."""
import socket, json, struct, sys
port = int(sys.argv[1]) if len(sys.argv) > 1 else 4370
addr = int(sys.argv[2], 16) if len(sys.argv) > 2 else 0x8005AD0C
count = int(sys.argv[3]) if len(sys.argv) > 3 else 24
base_state = int(sys.argv[4]) if len(sys.argv) > 4 else 2
s = socket.create_connection(('localhost', port), timeout=5)
cmd = f'{{"cmd":"read_ram","addr":"0x{addr:08X}","len":{count*4}}}\n'
s.sendall(cmd.encode())
data = b''
while b'\n' not in data:
data += s.recv(65536)
s.close()
resp = json.loads(data.split(b'\n')[0])
raw = bytes.fromhex(resp['hex'])
for i in range(count):
val = struct.unpack_from('<I', raw, i * 4)[0]
state = base_state + i
print(f' state {state:2d} (idx {i:2d}): 0x{val:08X}')
+65
View File
@@ -0,0 +1,65 @@
#!/usr/bin/env python3
"""Find all JAL instructions targeting GTE library, with correct address decoding."""
import struct
from collections import defaultdict
ROM_PATH = "bios/SCPH1001.BIN"
ROM_BASE = 0xBFC00000
CODE_REGIONS = [
(0xBFC00000, 0xBFC0DC60),
(0xBFC10000, 0xBFC16760),
(0xBFC18000, 0xBFC42800),
]
# GTE library ROM range
GTE_ROM_LO = 0xBFC34E00
GTE_ROM_HI = 0xBFC37000
# Corresponding RAM range (shell: ROM BFC18000 -> RAM 0x80030000)
SHELL_ROM_BASE = 0xBFC18000
SHELL_RAM_BASE = 0x80030000
def rom_to_ram(rom_addr):
return SHELL_RAM_BASE + (rom_addr - SHELL_ROM_BASE)
GTE_RAM_LO = rom_to_ram(GTE_ROM_LO)
GTE_RAM_HI = rom_to_ram(GTE_ROM_HI)
with open(ROM_PATH, "rb") as f:
rom = f.read()
callers = []
for lo, hi in CODE_REGIONS:
for off in range(lo - ROM_BASE, hi - ROM_BASE, 4):
word = struct.unpack_from("<I", rom, off)[0]
opcode = (word >> 26) & 0x3F
if opcode == 0x03: # JAL
target26 = word & 0x03FFFFFF
# Shell code runs from RAM, so PC[31:28] = 0x8
# For kernel code running from ROM, PC[31:28] = 0xB
# But kernel would call through RAM addresses too after shell copy
# The recompiler uses 0x8 prefix for RAM targets
target_ram = 0x80000000 | (target26 << 2)
caller_rom = ROM_BASE + off
if GTE_RAM_LO <= target_ram < GTE_RAM_HI:
target_rom = SHELL_ROM_BASE + (target_ram - SHELL_RAM_BASE)
callers.append((caller_rom, target_ram, target_rom))
print(f"JAL calls to GTE library (RAM {GTE_RAM_LO:#x}-{GTE_RAM_HI:#x}):")
print(f"Found: {len(callers)}")
by_target = defaultdict(list)
for caller_rom, target_ram, target_rom in callers:
by_target[target_rom].append(caller_rom)
for target in sorted(by_target):
callers_list = by_target[target]
ram = rom_to_ram(target)
print(f"\n Target ROM 0x{target:08X} (RAM 0x{ram:08X}):")
for c in callers_list:
region = "kernel1" if c < 0xBFC10000 else ("kernel2" if c < 0xBFC18000 else "shell")
# Find approximate function
func_rom = (c & ~0xFFF) # rough 4KB alignment
print(f" Called from 0x{c:08X} ({region})")
+105
View File
@@ -0,0 +1,105 @@
#!/usr/bin/env python3
"""Find all function entry points in the GTE library region by scanning for jr $ra patterns."""
import struct, json
ROM_PATH = "bios/SCPH1001.BIN"
ROM_BASE = 0xBFC00000
# GTE library region (from the audit: BFC34F00 to BFC36D00)
# Actually scan a wider range to catch everything
SCAN_LO = 0xBFC34E00
SCAN_HI = 0xBFC37000
with open(ROM_PATH, "rb") as f:
rom = f.read()
# Find all jr $ra instructions in the scan range
JR_RA = 0x03E00008
NOP = 0x00000000
entries = []
off_lo = SCAN_LO - ROM_BASE
off_hi = SCAN_HI - ROM_BASE
# Strategy: after every "jr $ra; nop" pair, the next non-NOP word starts a new function
i = off_lo
while i < off_hi - 8:
word = struct.unpack_from("<I", rom, i)[0]
if word == JR_RA:
# Found jr $ra at offset i. Delay slot is at i+4.
delay = struct.unpack_from("<I", rom, i + 4)[0]
# Next instruction after delay slot
next_off = i + 8
if next_off < off_hi:
next_word = struct.unpack_from("<I", rom, next_off)[0]
if next_word != NOP and next_word != JR_RA:
entry_addr = ROM_BASE + next_off
entries.append(entry_addr)
i += 4
# Also check the very first instruction in the range as a potential entry
first_word = struct.unpack_from("<I", rom, off_lo)[0]
first_addr = ROM_BASE + off_lo
if first_addr not in entries:
entries.insert(0, first_addr)
# Deduplicate and sort
entries = sorted(set(entries))
# Load existing seeds to check which are new
existing_seeds = set()
seed_file = "recompiler/seeds/phase2_ghidra_seeds.json"
try:
with open(seed_file) as f:
data = json.load(f)
for s in data.get("seeds", []):
existing_seeds.add(int(s["address"], 16))
except:
pass
print(f"Found {len(entries)} potential function entries in {SCAN_LO:#010x}-{SCAN_HI:#010x}")
new_count = 0
for addr in entries:
# Show the first few bytes for verification
off = addr - ROM_BASE
words = [struct.unpack_from("<I", rom, off + j*4)[0] for j in range(3)]
mnemonics = []
for w in words:
op = (w >> 26) & 0x3F
if op == 0x32:
mnemonics.append("lwc2")
elif op == 0x3A:
mnemonics.append("swc2")
elif op == 0x12:
mnemonics.append("cop2")
elif op == 0x09:
mnemonics.append("addiu")
elif op == 0x0F:
mnemonics.append("lui")
elif op == 0x00:
func = w & 0x3F
if func == 0x08:
mnemonics.append("jr")
elif func == 0x21:
mnemonics.append("addu")
elif func == 0x00 and w == 0:
mnemonics.append("nop")
else:
mnemonics.append(f"r{func:#04x}")
else:
mnemonics.append(f"op{op:#04x}")
is_new = addr not in existing_seeds
marker = "NEW" if is_new else "exists"
if is_new:
new_count += 1
print(f" {addr:#010x}: {' '.join(mnemonics):30s} [{marker}]")
print(f"\n{new_count} new seeds to add")
# Output as seed entries
if new_count > 0:
print("\nSeed JSON entries:")
for addr in entries:
if addr not in existing_seeds:
print(f' {{"address": "0x{addr:08X}", "name": "gte_lib_{addr:08X}"}}')
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env python3
"""Check emitted function sizes vs expected ROM span."""
import re, sys
with open('generated/SCPH1001_full.c') as f:
lines = f.readlines()
funcs = []
for i, line in enumerate(lines, 1):
m = re.match(r'^void func_1FC([0-9A-Fa-f]+)\(CPUState\* cpu\) \{', line)
if m:
addr = int(m.group(1), 16) + 0xBFC00000
funcs.append((addr, i))
targets = [0xBFC34EC4, 0xBFC35FD0, 0xBFC36068, 0xBFC362D0, 0xBFC36740, 0xBFC36CF0]
for t in targets:
for i, (addr, lineno) in enumerate(funcs):
if addr == t:
next_line = funcs[i + 1][1] if i + 1 < len(funcs) else len(lines)
code_lines = next_line - lineno
rom_addrs = set()
for li in range(lineno - 1, min(next_line - 1, len(lines))):
m2 = re.search(r'0x(BFC[0-9A-Fa-f]{5}):', lines[li])
if m2:
rom_addrs.add(int(m2.group(1), 16))
if rom_addrs:
lo = min(rom_addrs)
hi = max(rom_addrs)
rom_bytes = hi - lo + 4
else:
lo = hi = rom_bytes = 0
print(f'func_1FC{t - 0xBFC00000:05X}: {code_lines:4d} C lines, '
f'ROM 0x{lo:08X}-0x{hi:08X} ({rom_bytes:5d} bytes = {rom_bytes // 4:4d} insns)')
break
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env python3
"""Read GPU state from debug server."""
import socket, json, sys
port = int(sys.argv[1]) if len(sys.argv) > 1 else 4370
s = socket.create_connection(('localhost', port), timeout=5)
s.sendall(b'{"cmd":"gpu_state"}\n')
data = b''
while b'\n' not in data:
data += s.recv(4096)
s.close()
resp = json.loads(data.split(b'\n')[0])
if resp.get('ok'):
for k, v in sorted(resp.items()):
if k not in ('ok', 'id'):
print(f' {k}: {v}')
else:
print(f'Error: {resp}')
+102
View File
@@ -0,0 +1,102 @@
#!/usr/bin/env python3
"""Audit GTE instructions: compare ROM contents vs generated code."""
import struct, re, sys
ROM_PATH = "bios/SCPH1001.BIN"
GEN_PATH = "generated/SCPH1001_full.c"
ROM_BASE = 0xBFC00000
# 1. Scan ROM for all COP2 (GTE) instructions
with open(ROM_PATH, "rb") as f:
rom = f.read()
rom_gte = [] # (rom_addr, instruction_word)
for off in range(0, len(rom), 4):
word = struct.unpack_from("<I", rom, off)[0]
opcode = (word >> 26) & 0x3F
if opcode == 0x12: # COP2
bit25 = (word >> 25) & 1
if bit25 == 1: # GTE command (not MFC2/MTC2/CFC2/CTC2)
rom_addr = ROM_BASE + off
rom_gte.append((rom_addr, word))
# Also find LWC2/SWC2 (COP2 load/store) — opcodes 0x32 (LWC2) and 0x3A (SWC2)
rom_lwc2_swc2 = []
for off in range(0, len(rom), 4):
word = struct.unpack_from("<I", rom, off)[0]
opcode = (word >> 26) & 0x3F
if opcode in (0x32, 0x3A): # LWC2 or SWC2
rom_addr = ROM_BASE + off
rom_lwc2_swc2.append((rom_addr, word))
# 2. Scan generated code for gte_execute addresses
gen_gte_addrs = set()
with open(GEN_PATH, "r") as f:
for line in f:
# Pattern: /* 0xBFCxxxxx: xxxxxxxx gte cmd ... */
m = re.search(r'/\* 0x(BFC[0-9A-Fa-f]{5}):', line)
if m and 'gte' in line.lower():
gen_gte_addrs.add(int(m.group(1), 16))
# Also match gte_execute calls with preceding comment
if 'gte_execute' in line:
# Find the ROM address from the comment above
pass
# Better approach: find all ROM addresses that have gte_execute
# by looking for the comment pattern before gte_execute
gen_gte_addrs2 = set()
with open(GEN_PATH, "r") as f:
prev_addr = None
for line in f:
m = re.search(r'/\* 0x(BFC[0-9A-Fa-f]{5}):', line)
if m:
prev_addr = int(m.group(1), 16)
if 'gte_execute' in line and prev_addr:
gen_gte_addrs2.add(prev_addr)
# Also find all lwc2/swc2 in generated code
gen_cop2_load_addrs = set()
with open(GEN_PATH, "r") as f:
for line in f:
if 'lwc2' in line.lower() or 'swc2' in line.lower():
m = re.search(r'/\* 0x(BFC[0-9A-Fa-f]{5}):', line)
if m:
gen_cop2_load_addrs.add(int(m.group(1), 16))
# 3. Compare
print(f"ROM GTE commands (COP2 bit25=1): {len(rom_gte)}")
print(f"ROM LWC2/SWC2 instructions: {len(rom_lwc2_swc2)}")
print(f"Generated gte_execute calls: {len(gen_gte_addrs2)}")
print(f"Generated lwc2/swc2 accesses: {len(gen_cop2_load_addrs)}")
print()
# Missing GTE commands
missing_gte = [(addr, word) for addr, word in rom_gte if addr not in gen_gte_addrs2]
print(f"MISSING GTE commands: {len(missing_gte)}")
# Group by 256-byte region to show function clusters
from collections import defaultdict
regions = defaultdict(list)
for addr, word in missing_gte:
region = addr & ~0xFF
func_code = (word & 0x3F)
regions[region].append((addr, word, func_code))
print(f"Missing spread across {len(regions)} regions:")
for region in sorted(regions):
entries = regions[region]
codes = [f"0x{e[2]:02X}" for e in entries]
addrs = [f"0x{e[0]:08X}" for e in entries]
print(f" 0x{region:08X}: {len(entries)} missing — ops: {', '.join(codes)}")
for a, w, c in entries:
print(f" 0x{a:08X}: 0x{w:08X} (op {c:#04x})")
# Missing LWC2/SWC2
missing_loads = [(addr, word) for addr, word in rom_lwc2_swc2 if addr not in gen_cop2_load_addrs]
print(f"\nMissing LWC2/SWC2: {len(missing_loads)} out of {len(rom_lwc2_swc2)}")
if missing_loads:
load_regions = defaultdict(list)
for addr, word in missing_loads:
load_regions[addr & ~0xFF].append(addr)
for region in sorted(load_regions):
print(f" 0x{region:08X}: {len(load_regions[region])} missing")
+101
View File
@@ -0,0 +1,101 @@
#!/usr/bin/env python3
"""Check which ROM functions in the GTE-heavy region are emitted."""
import struct, re
ROM_PATH = "bios/SCPH1001.BIN"
GEN_PATH = "generated/SCPH1001_full.c"
ROM_BASE = 0xBFC00000
# Code regions from CLAUDE.md
CODE_REGIONS = [
(0xBFC00000, 0xBFC0DC60), # kernel 1
(0xBFC10000, 0xBFC16760), # kernel 2
(0xBFC18000, 0xBFC42800), # shell
]
def in_code_region(addr):
return any(lo <= addr < hi for lo, hi in CODE_REGIONS)
# Scan ROM for GTE in code regions only
with open(ROM_PATH, "rb") as f:
rom = f.read()
gte_in_code = []
for off in range(0, len(rom), 4):
word = struct.unpack_from("<I", rom, off)[0]
opcode = (word >> 26) & 0x3F
addr = ROM_BASE + off
if not in_code_region(addr):
continue
if opcode == 0x12 and (word >> 25) & 1: # COP2 command
gte_in_code.append((addr, word))
elif opcode in (0x32, 0x3A): # LWC2/SWC2
gte_in_code.append((addr, word))
# Find all emitted function ranges
emitted = [] # (start_addr, end_addr_or_None)
with open(GEN_PATH, "r") as f:
for line in f:
m = re.match(r'^void func_1FC([0-9A-Fa-f]+)\(CPUState\* cpu\) \{', line)
if m:
addr = 0xBFC00000 + int(m.group(1), 16)
emitted.append(addr)
emitted.sort()
emitted_set = set(emitted)
# Find all gte_execute ROM addresses
gen_gte = set()
with open(GEN_PATH, "r") as f:
prev_addr = None
for line in f:
m = re.search(r'/\* 0x(BFC[0-9A-Fa-f]{5}):', line)
if m:
prev_addr = int(m.group(1), 16)
if ('gte_execute' in line or 'gte_data' in line or 'gte_ctrl' in line) and prev_addr:
gen_gte.add(prev_addr)
# Focus on BFC34F00-BFC36D00 region
print("=== GTE instructions in BFC34F00-BFC36D00 ===")
region_gte = [(a, w) for a, w in gte_in_code if 0xBFC34F00 <= a < 0xBFC36D00]
print(f"Total in region: {len(region_gte)}")
missing = [(a, w) for a, w in region_gte if a not in gen_gte]
present = [(a, w) for a, w in region_gte if a in gen_gte]
print(f"Present in generated: {len(present)}")
print(f"Missing: {len(missing)}")
# Check which emitted functions are in this range
funcs_in_range = [a for a in emitted if 0xBFC34F00 <= a < 0xBFC36D00]
print(f"\nEmitted functions in range: {len(funcs_in_range)}")
for a in funcs_in_range:
print(f" func_1FC{a-0xBFC00000:05X} (0x{a:08X})")
# For each missing GTE, find nearest emitted function
print(f"\nMissing GTE instructions by function:")
from collections import defaultdict
by_func = defaultdict(list)
for addr, word in missing:
# Find the function this address belongs to
best = None
for fa in emitted:
if fa <= addr:
best = fa
else:
break
by_func[best].append((addr, word))
for func_addr in sorted(by_func):
entries = by_func[func_addr]
if func_addr:
name = f"func_1FC{func_addr-0xBFC00000:05X}"
in_dispatch = func_addr in emitted_set
else:
name = "(no function found)"
in_dispatch = False
print(f" {name}: {len(entries)} missing GTE instructions")
for a, w in entries:
op = w & 0x3F
opcode = (w >> 26) & 0x3F
kind = "COP2cmd" if opcode == 0x12 else ("LWC2" if opcode == 0x32 else "SWC2")
print(f" 0x{a:08X}: 0x{w:08X} ({kind} op=0x{op:02x})")
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env python3
"""Rapidly poll a RAM address and report unique values seen."""
import socket, json, struct, sys, time
port = int(sys.argv[1])
addr = int(sys.argv[2], 16)
duration = float(sys.argv[3]) if len(sys.argv) > 3 else 5.0
label = sys.argv[4] if len(sys.argv) > 4 else "value"
seen = {}
reads = 0
start = time.time()
while time.time() - start < duration:
try:
s = socket.create_connection(('localhost', port), timeout=1)
cmd = f'{{"cmd":"read_ram","addr":"0x{addr:08X}","len":4}}\n'
s.sendall(cmd.encode())
data = b''
while b'\n' not in data:
data += s.recv(4096)
s.close()
resp = json.loads(data.split(b'\n')[0])
if resp.get('ok'):
raw = bytes.fromhex(resp['hex'])
val = struct.unpack_from('<I', raw, 0)[0]
seen[val] = seen.get(val, 0) + 1
reads += 1
except:
pass
elapsed = time.time() - start
print(f'{label} at 0x{addr:08X}: {reads} reads in {elapsed:.1f}s')
for val, count in sorted(seen.items()):
print(f' 0x{val:08X} ({val:5d}): seen {count} times ({100*count/reads:.1f}%)')
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env python3
import socket, json, struct, sys
port = int(sys.argv[1])
addr = int(sys.argv[2], 16)
count = int(sys.argv[3]) if len(sys.argv) > 3 else 4
s = socket.create_connection(('localhost', port), timeout=5)
s.sendall(f'{{"cmd":"read_ram","addr":"0x{addr:08X}","len":{count*4}}}\n'.encode())
data = b''
while b'\n' not in data:
data += s.recv(4096)
s.close()
d = json.loads(data.split(b'\n')[0])
raw = bytes.fromhex(d['hex'])
for i in range(count):
v = struct.unpack_from('<I', raw, i * 4)[0]
print(f' [{i}] 0x{v:08X}')