This commit is contained in:
vetusmagnus
2026-10-13 20:04:31 -04:00
commit 28916eaf7d
208 changed files with 23516 additions and 0 deletions
+113
View File
@@ -0,0 +1,113 @@
#!/usr/bin/env python3
"""gen_glthread.py: writes host/src/gs/gs_gl_thread.h from the GL function list
in host/src/gs/gs_gl_api.h (GSGL_FUNCS).
The GL renderer (gs_gl.c) calls GL through p_glX. With the GL thread on, each
p_glX records the call (its arguments, and copies of the data it points at) in
a queue that the GL thread, which owns the context, replays through r_glX (the
real entry points). Calls that return something or write through a pointer
(glGet*, glGen*, glCreate*, glReadPixels, glMapBufferRange...) are run by the
GL thread while the caller waits. Without the GL thread p_glX calls r_glX.
Run it again after changing GSGL_FUNCS: python3 tools/gen_glthread.py
"""
import os, re
HERE = os.path.dirname(os.path.abspath(__file__))
API = os.path.join(HERE, "..", "host", "src", "gs", "gs_gl_api.h")
OUT = os.path.join(HERE, "..", "host", "src", "gs", "gs_gl_thread.h")
src = open(API).read()
funcs = re.findall(r"X\((gl\w+),\s*([^,]+?),\s*\(([^)]*)\)\)", src)
SCALAR = {"GLenum", "GLuint", "GLbitfield", "GLint", "GLsizei", "GLboolean", "GLubyte", "GLsizeiptr", "GLintptr"}
FLOAT = {"GLfloat"}
# calls whose pointer argument is data to copy into the queue (bytes: a C expression)
DATA = {
"glTexImage2D": (8, "glt_pix_bytes(a3, a4, a6, a7)"),
"glTexSubImage2D": (8, "glt_pix_bytes(a4, a5, a6, a7)"),
"glBufferData": (2, "(size_t)a1"),
"glBufferSubData": (3, "(size_t)a2"),
"glUniform4iv": (2, "(size_t)a1 * 16"),
"glUniform1iv": (2, "(size_t)a1 * 4"),
"glDeleteTextures": (1, "(size_t)a0 * 4"),
"glDeleteFramebuffers": (1, "(size_t)a0 * 4"),
}
# pointer arguments that are offsets into the bound buffer, not data
OFFSET_PTR = {"glVertexAttribPointer": 5, "glVertexAttribIPointer": 4}
out = ['/* generated by tools/gen_glthread.py from gs_gl_api.h: do not edit */',
'/* included by gs_gl.c, after the GL thread helpers (glt_cmd, glt_call, glt_q, glt_pix_bytes) */']
ids = ["GLT_" + f[0] for f in funcs]
out.append("enum { " + ", ".join(ids) + ", GLT_SWAP, GLT_VB, GLT_CALLBACK, GLT_N };")
out.append("#define GSGL_DEF(name, ret, args) PFN_##name r_##name;\nGSGL_FUNCS(GSGL_DEF)\n#undef GSGL_DEF")
def conv_in(t, name):
"""an argument to its u64 slot"""
t = t.strip()
if t in FLOAT: return "glt_fbits(%s)" % name
if "*" in t: return "(u64)(uintptr_t)(%s)" % name
return "(u64)(s64)(%s)" % name
def conv_out(t, slot):
"""a u64 slot back to the argument's type"""
t = t.strip()
if t in FLOAT: return "glt_bitsf(%s)" % slot
if "*" in t: return "(%s)(uintptr_t)(%s)" % (t, slot)
return "(%s)(%s)" % (t, slot)
replay = []
for name, ret, args in funcs:
ret = ret.strip()
ts = [] if args.strip() in ("void", "") else [a.strip() for a in args.split(",")]
n = len(ts)
params = ", ".join("%s a%d" % (t, i) for i, t in enumerate(ts)) or "void"
call_r = "r_%s(%s)" % (name, ", ".join("a%d" % i for i in range(n)))
ptr_args = [i for i, t in enumerate(ts) if "*" in t]
data = DATA.get(name)
is_async = ret == "void" and (not ptr_args or data or (name in OFFSET_PTR and ptr_args == [OFFSET_PTR[name]]))
body = []
if name == "glPixelStorei": body.append(" glt_track_store(a0, a1); /* the unpack layout the queued texel copies are sized by */")
if is_async:
body.append(" if (!glt_q) { %s; return; }" % call_r)
if data:
pi, expr = data
body.append(" size_t nb = a%d ? %s : 0;" % (pi, expr))
body.append(" u64 *c = glt_cmd(%s, %d, nb);" % ("GLT_" + name, n))
else:
body.append(" u64 *c = glt_cmd(%s, %d, 0);" % ("GLT_" + name, n))
for i, t in enumerate(ts):
if data and i == data[0]:
body.append(" c[%d] = 0; if (nb) memcpy(c + %d, a%d, nb);" % (i, n, i))
else:
body.append(" c[%d] = %s;" % (i, conv_in(t, "a%d" % i)))
# replay: the data pointer is the blob after the arguments
rargs = []
for i, t in enumerate(ts):
if data and i == data[0]: rargs.append("blob_n ? (%s)(const void *)(c + %d) : (%s)0" % (t, n, t))
else: rargs.append(conv_out(t, "c[%d]" % i))
replay.append(" case GLT_%s: r_%s(%s); break;" % (name, name, ", ".join(rargs)))
else:
if ret == "void": body.append(" if (!glt_q) { %s; return; }" % call_r)
else: body.append(" if (!glt_q) return %s;" % call_r)
body.append(" u64 a[%d + 1]; u64 r = 0;" % n)
for i, t in enumerate(ts): body.append(" a[%d] = %s;" % (i, conv_in(t, "a%d" % i)))
body.append(" glt_call(%s, a, %d, &r);" % ("GLT_" + name, n))
if ret != "void":
if ret in FLOAT: body.append(" return glt_bitsf(r);")
elif "*" in ret: body.append(" return (%s)(uintptr_t)r;" % ret)
else: body.append(" return (%s)r;" % ret)
rargs = ", ".join(conv_out(t, "c[%d]" % i) for i, t in enumerate(ts))
if ret == "void": replay.append(" case GLT_%s: r_%s(%s); break;" % (name, name, rargs))
elif "*" in ret: replay.append(" case GLT_%s: *rp = (u64)(uintptr_t)r_%s(%s); break;" % (name, name, rargs))
else: replay.append(" case GLT_%s: *rp = (u64)r_%s(%s); break;" % (name, name, rargs))
out.append("static %s p_%s(%s)\n{\n%s\n}" % (ret, name, params, "\n".join(body)))
nargs = [str(0 if a.strip() in ("void", "") else len(a.split(","))) for _, _, a in funcs]
out.append("static const unsigned char glt_nargs[GLT_N] = { " + ", ".join(nargs) + ", 0, 1, 0 }; /* ..., SWAP, VB, CALLBACK */")
out.append("/* the GL thread: one recorded call (c: its arguments, then its data; rp: where a result goes) */")
out.append("static void glt_exec(u32 id, const u64 *c, size_t blob_n, u64 *rp)\n{\n (void)blob_n; (void)rp;\n switch (id) {")
out += replay
out.append(" default: glt_exec_extra(id, c, blob_n); break;\n }\n}")
open(OUT, "w").write("\n".join(out) + "\n")
print("wrote %s: %d GL functions" % (OUT, len(funcs)))
File diff suppressed because it is too large Load Diff
+110
View File
@@ -0,0 +1,110 @@
#!/usr/bin/env python3
"""level_dump.py OUT_DIR LEVEL [LEVEL...] [--iso ISO] [--repo DECOMP_REPO]
Writes each level's program (the code overlay ParseBin loads over the
executable's main segment) to OUT_DIR/level_NN/: one file per record
(lit, bss, data, vtbl, camvtbl, sndvtbl, text) and manifest.json with the
load addresses and the entry point, the layout of OpenRAC's own dump
(tools/overlays.py dump, baserom/overlays/level_NN/).
The bytes come from the user's own disc image, read the way the game finds
them: the table of contents at LBA 1500, the level's header, the header's
"data" sector range, whose first word is the offset of the code overlay
(docs/ASSETS.md, "Code overlays"). If the decomp repo already has an OpenRAC
dump of the level (baserom/overlays/level_NN/manifest.json), that is used
instead. Nothing here is ever committed: it is game data.
recomp.py translates the level's assembly-only functions from text.bin.
"""
import json, os, shutil, struct, sys
SECTOR = 2048
TOC_LBA, TOC_SIZE = 1500, 0x2960
LEVEL_TABLE = 0x28C8 # D_0013A548: (header LBA, size) per level
LEVEL_HEADER_SIZE = 0x2434
RECORD_NAMES = ("lit", "bss", "data", "vtbl", "camvtbl", "sndvtbl", "text")
def read(f, lba, size):
f.seek(lba * SECTOR)
d = f.read(size)
if len(d) != size:
sys.exit("level_dump: short read from the disc image at LBA %d" % lba)
return d
def wad(raw, repo):
"""the overlay is stored plain on PAL v2.00; a WAD-packed one is unpacked
with OpenRAC's decoder (tools/extract/formats.py)"""
sys.path.insert(0, os.path.join(repo, "tools", "extract"))
from formats import wad as unwad
return unwad(raw)
def overlay_records(raw):
"""(entry point, [(address, size, type, bytes)]), as ParseBin (func_0012DA38) walks them"""
entry = struct.unpack_from("<I", raw, 12)[0]
pos, recs = 0, []
while pos + 16 <= len(raw):
addr, size, kind, e = struct.unpack_from("<4I", raw, pos)
if e != entry:
break
recs.append((addr, size, kind, raw[pos + 16:pos + 16 + size]))
pos += 16 + size
if len(recs) != len(RECORD_NAMES):
sys.exit("level_dump: %d overlay records, expected %d" % (len(recs), len(RECORD_NAMES)))
return entry, recs
def dump_from_iso(iso, level, out, repo):
with open(iso, "rb") as f:
toc = read(f, TOC_LBA, TOC_SIZE)
if struct.unpack_from("<II", toc, 0) != (1, TOC_SIZE):
sys.exit("level_dump: %s does not look like the PAL v2.00 disc (table of contents)" % iso)
lba, _ = struct.unpack_from("<II", toc, LEVEL_TABLE + level * 8)
hdr = read(f, lba, LEVEL_HEADER_SIZE)
if struct.unpack_from("<II", hdr, 0) != (level, LEVEL_HEADER_SIZE):
sys.exit("level_dump: unexpected header for level %d" % level)
start, count = struct.unpack_from("<II", hdr, 8) # the "data" range
data = read(f, start, count * SECTOR)
off, size = struct.unpack_from("<ii", data, 0)
raw = data[off:off + size]
if raw[:3] == b"WAD":
raw = wad(raw, repo)
entry, recs = overlay_records(raw)
os.makedirs(out, exist_ok=True)
man = {"level": level, "entry_point": entry, "records": [], "source": "disc image"}
for name, (addr, sz, kind, b) in zip(RECORD_NAMES, recs):
open(os.path.join(out, name + ".bin"), "wb").write(b)
man["records"].append({"name": name, "address": addr, "bytes": sz, "type": kind})
json.dump(man, open(os.path.join(out, "manifest.json"), "w"), indent=1)
def main():
args = sys.argv[1:]
iso = repo = None
rest = []
while args:
a = args.pop(0)
if a == "--iso": iso = args.pop(0)
elif a == "--repo": repo = args.pop(0)
else: rest.append(a)
out_dir, levels = rest[0], [int(x) for x in rest[1:]]
for lv in levels:
out = os.path.join(out_dir, "level_%02d" % lv)
if os.path.exists(os.path.join(out, "manifest.json")):
continue
ready = os.path.join(repo or "", "baserom", "overlays", "level_%02d" % lv)
if repo and os.path.exists(os.path.join(ready, "manifest.json")):
shutil.copytree(ready, out, dirs_exist_ok=True)
print("level %02d: from OpenRAC's dump %s" % (lv, ready))
continue
if not iso or not os.path.exists(iso):
sys.exit("level_dump: level %02d needs the disc image (build.py --iso PATH, RAC1_ISO, or iso= in "
"build/<platform>/rac1_wrapper.cfg)" % lv)
dump_from_iso(iso, lv, out, repo or "")
print("level %02d: read from %s" % (lv, iso))
if __name__ == "__main__":
main()
File diff suppressed because it is too large Load Diff
+59
View File
@@ -0,0 +1,59 @@
# Small text fixes applied to the decomp's C before it is compiled for the PC.
# Each is (file name, exact old text, new text). They are for places where the
# C is right for the EE calling convention but not for x86.
PATCHES = [
# func_003894A0(colour a, colour b, float t): on the EE the float travels in
# $f12 whatever its position, so these files declare it first. On x86 the
# order matters; the macro puts the arguments back in definition order.
("3A5478.c", "extern s32 func_003894A0(f32, s32, s32);",
"extern s32 func_003894A0(s32, s32, f32);\n#define func_003894A0(f, a, b) func_003894A0((a), (b), (f))"),
("3B5BF8.c", "extern s32 func_003894A0(f32, s32, s32);",
"extern s32 func_003894A0(s32, s32, f32);\n#define func_003894A0(f, a, b) func_003894A0((a), (b), (f))"),
# func_0038C3C0(a, unused, c, f) calls func_00389D18(str, len, scale) with
# only the float: on the EE, str and len are still a and unused in $a0/$a1.
("3896E8.c", "t = ((s32 (*)(f32))func_00389D18)(1.0f);",
"t = ((s32 (*)(u8 *, s32, f32))func_00389D18)((u8 *)a, unused, 1.0f);"),
# func_0038C9B8's second and seventh arguments are 64-bit on the EE (one
# register each) and are passed on as such to func_0038B1E8; its only
# caller (3E5378.c) passes them as 64-bit values.
("3896E8.c", "void func_0038C9B8(s32 a, s32 b, s32 c, s32 d, s32 e, s32 g, s32 h, f32 x) {",
"void func_0038C9B8(s32 a, long b, s32 c, s32 d, s32 e, s32 g, unsigned long h, f32 x) {"),
# RAC1. func_L00_001FE940(u128 *v, ...) copies *v with a 16-byte load; its
# caller (func_L00_002A7078) passes a float[4] member of a local union, which
# the EE stack frame keeps 16-byte aligned and the x86 one does not: copy it
# as four words (the pointer goes through an integer so that the compiler
# does not assume the vector type's alignment for it).
("drawquad_001FD1D8.c", " *tp = *v;\n",
" { const unsigned int *s_ = (const unsigned int *)(unsigned int)v; unsigned int *d_ = (unsigned int *)(unsigned int)tp;\n"
" d_[0] = s_[0]; d_[1] = s_[1]; d_[2] = s_[2]; d_[3] = s_[3]; }\n"),
# func_L00_002EABD0 (a camera rig's aim) divides by distances that are 0
# on the first frame; the EE gives +-MAX there and FastArcSin a finite
# angle, x86 gives inf/NaN and the camera's matrices stay NaN (nothing of
# Veldin is drawn once the gameplay camera takes over)
("vendor_002E1660.c", " y = func_001F9FC0(e1 / x);\n", " y = func_001F9FC0(ee_fdiv(e1, x));\n"),
("vendor_002E1660.c", " r = func_001F9FC0(h / bc);\n", " r = func_001F9FC0(ee_fdiv(h, bc));\n"),
("vendor_002E1660.c", " r = 1.5707964f - func_001F9FC0(e / x);\n", " r = 1.5707964f - func_001F9FC0(ee_fdiv(e, x));\n"),
# func_L00_001EB6A8 is void in the C but ends with FastAddRots(a, *p), whose
# result stays in $f0 on the EE; every caller (the camera rigs) uses it as
# the function's float result. On x86 a void function returns nothing (the
# callers read an empty x87 register: NaN in the camera's matrices).
("camera_001EB508.c", "void func_L00_001EB6A8(float *p, float a, float b, float c, float d, float lim) {",
"float func_L00_001EB6A8(float *p, float a, float b, float c, float d, float lim) {"),
("camera_001EB508.c", " *p = -func_001F9B88(r);\n }\n func_001FA748(a, *p);\n}",
" *p = -func_001F9B88(r);\n }\n return func_001FA748(a, *p);\n}"),
# func_001F7EF8 (drawquad, hand-written) reads its caller's quad as one
# block: 4 positions at +0, the colours at +64, the UVs at +80 and the GS
# words at +112 -- on the EE these callers' locals sit in that order in the
# frame. On x86 separate locals are laid out as the compiler likes, so they
# are put in one struct here.
("draw.c", " FRow m[4];\n int colors[4];\n float uv[4][2];\n unsigned long pkt[4];\n",
" struct { FRow m[4]; int colors[4]; float uv[4][2]; unsigned long pkt[4]; } q4c30_;\n"
"#define m q4c30_.m\n#define colors q4c30_.colors\n#define uv q4c30_.uv\n#define pkt q4c30_.pkt\n"),
("draw.c", " func_001F7EF8(m, 0, 0);\n }\n}\n",
" func_001F7EF8(m, 0, 0);\n }\n}\n#undef m\n#undef colors\n#undef uv\n#undef pkt\n"),
("space.c", " Vec4 quad_positions[4];\n int colors[4];\n float texture_coordinates[4][2];\n u64 quad_state[4];\n",
" struct { Vec4 quad_positions[4]; int colors[4]; float texture_coordinates[4][2]; u64 quad_state[4]; } q33ab8_;\n"
"#define quad_positions q33ab8_.quad_positions\n#define colors q33ab8_.colors\n#define texture_coordinates q33ab8_.texture_coordinates\n#define quad_state q33ab8_.quad_state\n"),
("space.c", " func_001F7EF8(quad_positions, 0, 0);\n }\n}\n",
" func_001F7EF8(quad_positions, 0, 0);\n }\n}\n#undef quad_positions\n#undef colors\n#undef texture_coordinates\n#undef quad_state\n"),
]
+148
View File
@@ -0,0 +1,148 @@
"""vu_isa.py: decoding of PS2 VU micro-mode instructions (shared by vu_recomp.py).
A VU micro instruction is a 64-bit pair: the lower word (at the lower address)
holds the lower-unit op (load/store, integer, branch, DIV/EFU...), the upper
word the FMAC op plus the I/E/M/D/T bits. With the I bit set the lower word is
a float immediate for the I register (LOI).
"""
XYZW = "xyzw"
def dest(w):
d = (w >> 21) & 15
return [i for i in range(4) if d & (8 >> i)]
def dstr(w):
return "".join(XYZW[i] for i in dest(w))
class Up:
"""decoded upper op: name, kind, fd/fs/ft, comps, bc ('x'..'w', 'q', 'i' or None), acc (writes ACC)"""
def __init__(self, name, w, **kw):
self.name, self.w = name, w
self.comps = dest(w)
self.ft, self.fs, self.fd = (w >> 16) & 31, (w >> 11) & 31, (w >> 6) & 31
self.bc = None
self.__dict__.update(kw)
def text(self):
return "%s.%s ft=%d fs=%d fd=%d%s" % (self.name, dstr(self.w), self.ft, self.fs, self.fd,
(" bc=" + self.bc) if self.bc else "")
_ARITH1 = {0x00: "add", 0x04: "sub", 0x08: "madd", 0x0C: "msub", 0x10: "max", 0x14: "mini", 0x18: "mul"}
_S1 = {0x1C: ("mul", "q"), 0x1D: ("max", "i"), 0x1E: ("mul", "i"), 0x1F: ("mini", "i"),
0x20: ("add", "q"), 0x21: ("madd", "q"), 0x22: ("add", "i"), 0x23: ("madd", "i"),
0x24: ("sub", "q"), 0x25: ("msub", "q"), 0x26: ("sub", "i"), 0x27: ("msub", "i"),
0x28: ("add", None), 0x29: ("madd", None), 0x2A: ("mul", None), 0x2B: ("max", None),
0x2C: ("sub", None), 0x2D: ("msub", None), 0x2F: ("mini", None)}
_ARITH2 = {0x00: "add", 0x04: "sub", 0x08: "madd", 0x0C: "msub", 0x18: "mul"}
_S2 = {0x1C: ("mul", "q"), 0x20: ("add", "q"), 0x21: ("madd", "q"), 0x22: ("add", "i"), 0x23: ("madd", "i"),
0x24: ("sub", "q"), 0x25: ("msub", "q"), 0x26: ("sub", "i"), 0x27: ("msub", "i"), 0x1E: ("mul", "i"),
0x28: ("add", None), 0x29: ("madd", None), 0x2A: ("mul", None), 0x2C: ("sub", None), 0x2D: ("msub", None)}
def decode_upper(w):
op = w & 0x3F
if op < 0x3C:
if op < 0x1C:
base = op & 0x3C
if base in _ARITH1:
return Up(_ARITH1[base], w, kind="arith", bc=XYZW[op & 3], acc=False)
if op in _S1:
n, bc = _S1[op]
return Up(n, w, kind="arith", bc=bc, acc=False)
if op == 0x2E:
return Up("opmsub", w, kind="opmsub", acc=False)
return Up("?up%02X" % op, w, kind="bad")
op2 = ((w >> 6) & 31) << 2 | (w & 3)
if op2 < 0x1C and (op2 & 0x3C) in _ARITH2 and op2 not in range(0x10, 0x18):
return Up(_ARITH2[op2 & 0x3C] + "a", w, kind="arith", bc=XYZW[op2 & 3], acc=True, base=_ARITH2[op2 & 0x3C])
if op2 in _S2:
n, bc = _S2[op2]
return Up(n + "a", w, kind="arith", bc=bc, acc=True, base=n)
if 0x10 <= op2 <= 0x13:
return Up("itof%d" % (0, 4, 12, 15)[op2 & 3], w, kind="itof", shift=(0, 4, 12, 15)[op2 & 3])
if 0x14 <= op2 <= 0x17:
return Up("ftoi%d" % (0, 4, 12, 15)[op2 & 3], w, kind="ftoi", shift=(0, 4, 12, 15)[op2 & 3])
if op2 == 0x1D: return Up("abs", w, kind="abs")
if op2 == 0x1F: return Up("clipw", w, kind="clip")
if op2 == 0x2E: return Up("opmula", w, kind="opmula")
if op2 == 0x2F: return Up("nop", w, kind="nop")
return Up("?up2_%02X" % op2, w, kind="bad")
class Lo:
def __init__(self, name, w, **kw):
self.name, self.w = name, w
self.comps = dest(w)
self.it, self.is_, self.id_ = (w >> 16) & 31, (w >> 11) & 31, (w >> 6) & 31
self.ft, self.fs, self.fd = self.it, self.is_, self.id_
self.fsf, self.ftf = (w >> 21) & 3, (w >> 23) & 3
self.__dict__.update(kw)
def text(self):
return "%s.%s it=%d is=%d id=%d %s" % (self.name, dstr(self.w), self.it, self.is_, self.id_,
" ".join("%s=%s" % (k, getattr(self, k)) for k in ("imm",) if hasattr(self, k)))
def s11(w):
v = w & 0x7FF
return v - 0x800 if v & 0x400 else v
_LOWER_S2 = {0x30: "move", 0x31: "mr32", 0x34: "lqi", 0x35: "sqi", 0x36: "lqd", 0x37: "sqd", 0x38: "div",
0x39: "sqrt", 0x3A: "rsqrt", 0x3B: "waitq", 0x3C: "mtir", 0x3D: "mfir", 0x3E: "ilwr", 0x3F: "iswr",
0x40: "rnext", 0x41: "rget", 0x42: "rinit", 0x43: "rxor", 0x64: "mfp", 0x68: "xtop", 0x69: "xitop",
0x6C: "xgkick", 0x70: "esadd", 0x71: "ersadd", 0x72: "eleng", 0x73: "erleng", 0x74: "eatanxy",
0x75: "eatanxz", 0x76: "esum", 0x78: "esqrt", 0x79: "ersqrt", 0x7A: "ercpr", 0x7B: "waitp",
0x7C: "esin", 0x7D: "eatan", 0x7E: "eexp"}
_LOWER = {0x00: "lq", 0x01: "sq", 0x04: "ilw", 0x05: "isw", 0x08: "iaddiu", 0x09: "isubiu", 0x10: "fceq",
0x11: "fcset", 0x12: "fcand", 0x13: "fcor", 0x14: "fseq", 0x15: "fsset", 0x16: "fsand", 0x17: "fsor",
0x18: "fmeq", 0x1A: "fmand", 0x1B: "fmor", 0x1C: "fcget", 0x20: "b", 0x21: "bal", 0x24: "jr",
0x25: "jalr", 0x28: "ibeq", 0x29: "ibne", 0x2C: "ibltz", 0x2D: "ibgtz", 0x2E: "iblez", 0x2F: "ibgez"}
BRANCHES = {"b", "bal", "jr", "jalr", "ibeq", "ibne", "ibltz", "ibgtz", "iblez", "ibgez"}
def decode_lower(w):
op = w >> 25
if op == 0x40:
f = w & 0x3F
if f < 0x3C:
n = {0x30: "iadd", 0x31: "isub", 0x32: "iaddi", 0x34: "iand", 0x35: "ior"}.get(f)
if n is None:
return Lo("?lo40_%02X" % f, w)
l = Lo(n, w)
if n == "iaddi":
imm = (w >> 6) & 31
l.imm = imm - 32 if imm & 16 else imm
return l
op2 = ((w >> 6) & 31) << 2 | (w & 3)
n = _LOWER_S2.get(op2)
if n is None:
return Lo("?lo2_%02X" % op2, w)
return Lo(n, w)
n = _LOWER.get(op)
if n is None:
return Lo("?lo%02X" % op, w)
l = Lo(n, w)
if n in ("lq", "sq", "ilw", "isw", "b", "bal", "ibeq", "ibne", "ibltz", "ibgtz", "iblez", "ibgez"):
l.imm = s11(w)
elif n in ("iaddiu", "isubiu"):
l.imm = ((w >> 10) & 0x7800) | (w & 0x7FF)
elif n in ("fceq", "fcset", "fcand", "fcor"):
l.imm = w & 0xFFFFFF
elif n in ("fseq", "fsset", "fsand", "fsor"):
l.imm = ((w >> 10) & 0x800) | (w & 0x7FF)
return l
def decode_pair(lo, hi):
"""returns (upper, lower or None, flags dict, i_imm or None)"""
up = decode_upper(hi)
fl = {"I": (hi >> 31) & 1, "E": (hi >> 30) & 1, "M": (hi >> 29) & 1, "D": (hi >> 28) & 1, "T": (hi >> 27) & 1}
if fl["I"]:
return up, None, fl, lo
return up, decode_lower(lo), fl, None
+579
View File
@@ -0,0 +1,579 @@
#!/usr/bin/env python3
"""vu_recomp.py GEN_DIR DECOMP_REPO [--dis OUT_DIR]
Ahead-of-time translator for the game's VU microprograms (VU0 and VU1 micro
mode). The microcode lives in the executable's .vutext section, listed by its
.DVP.ovlytab overlay table: one entry per MPG upload (VU address, size, load
address). Uploads of one program image share a group id in the overlay's
name (".DVP.overlay..<vu address>.<group>.<line>.<n>").
Each image is translated to one C function over an RcVu0 (host/include/vu.h):
a switch on the start address, then one labelled piece of C per instruction
pair. The function is compiled with the rest of the host program; nothing is
decoded at run time. Images that fit VU0's 4 KB micro memory are translated
for both VUs, the rest for VU1 only. The bytes of every upload are kept too,
so the runtime (host/src/gfx/vu.c) can tell which image is in micro memory
after the game's MPG uploads.
Semantics kept:
- upper and lower ops of a pair both read the registers as they were before
the pair; the upper op's result wins when both write the same register;
with the I bit the lower word is LOI's immediate (the pair's own upper
op still reads the old I: the game's sine code depends on it).
- branches and E (end) have one delay slot pair.
- FMAC results that overflow are +-MAX, as on the VU (which has no inf
or NaN): x86 would give inf, and then NaN (inf * 0, inf - inf), where
the VU keeps finite numbers (vu_fc).
- MAC/status/clip flags reach FSAND/FMAND/FCAND... 4 cycles after the op
issues, Q 7 cycles after DIV/SQRT and 13 after RSQRT: the code counts
cycles, including the stalls on VF registers that are still being written
(4-cycle latency, per component), which are worked out here.
Writes GEN_DIR/vu_prog_<n>.c (one per image and VU) and GEN_DIR/vu_table.c.
"""
import os, re, struct, sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from vu_isa import decode_pair, BRANCHES, XYZW
GEN, REPO = sys.argv[1], sys.argv[2]
DIS = sys.argv[sys.argv.index("--dis") + 1] if "--dis" in sys.argv else None
EXE = os.environ.get("UYA_EXE") or os.path.join(REPO, "baserom", "SCES_509.16") # build.py: the copy read from the disc image
img = open(EXE, "rb").read()
# ---- the executable's sections ------------------------------------------------
e_shoff, = struct.unpack_from("<I", img, 0x20)
shentsize, shnum, shstrndx = struct.unpack_from("<HHH", img, 0x2E)
secs = [struct.unpack_from("<10I", img, e_shoff + i * shentsize) for i in range(shnum)]
strtab_off = secs[shstrndx][4]
def secname(s):
o = strtab_off + s[0]
return img[o:img.index(b"\0", o)].decode()
bysec = {secname(s): s for s in secs}
vutext = bysec[".vutext"]
VT_ADDR, VT_OFF, VT_SIZE = vutext[3], vutext[4], vutext[5]
tab = bysec[".DVP.ovlytab"]
ostr = bysec[".DVP.ovlystrtab"]
tabd = img[tab[4]:tab[4] + tab[5]]
strd = img[ostr[4]:ostr[4] + ostr[5]]
groups = {}
order = []
for k in range(0, len(tabd) // 12 * 12, 12):
no, lma, vma = struct.unpack_from("<3I", tabd, k)
name = strd[no:strd.index(b"\0", no)].decode()
size = bysec[name][5]
parts = name.split(".") # ['', 'DVP', 'overlay', '', '0x0', group, line, n]
g = parts[5]
o = VT_OFF + (lma - VT_ADDR)
data = img[o:o + size]
if g not in groups:
groups[g] = []
order.append(g)
groups[g].append((vma, data))
# ---- register use (for stalls) ------------------------------------------------
def up_rw(u):
"""VF (reg, comp) read and written by an upper op"""
r, w = [], []
k = u.kind
if k in ("nop", "bad"):
return r, w
if k == "arith":
for c in u.comps:
r.append((u.fs, c))
if u.bc in ("x", "y", "z", "w"):
r.append((u.ft, XYZW.index(u.bc)))
elif u.bc is None:
r.append((u.ft, c))
if not u.acc:
w.append((u.fd, c))
elif k in ("opmula", "opmsub"):
for c in (0, 1, 2):
r += [(u.fs, c), (u.ft, c)]
if k == "opmsub":
w += [(u.fd, c) for c in u.comps if c < 3]
elif k in ("itof", "ftoi", "abs"):
r += [(u.fs, c) for c in u.comps]
w += [(u.ft, c) for c in u.comps]
elif k == "clip":
r += [(u.fs, 0), (u.fs, 1), (u.fs, 2), (u.ft, 3)]
return r, w
def lo_rw(l):
r, w = [], []
if l is None:
return r, w
n = l.name
if n in ("lq", "lqi", "lqd"):
w += [(l.ft, c) for c in l.comps]
elif n in ("sq", "sqi", "sqd"):
r += [(l.fs, c) for c in l.comps]
elif n == "move":
r += [(l.fs, c) for c in l.comps]
w += [(l.ft, c) for c in l.comps]
elif n == "mr32":
r += [(l.fs, (c + 1) & 3) for c in l.comps]
w += [(l.ft, c) for c in l.comps]
elif n == "mtir":
r.append((l.fs, l.fsf))
elif n == "mfir":
w += [(l.ft, c) for c in l.comps]
elif n == "div":
r += [(l.fs, l.fsf), (l.ft, l.ftf)]
elif n == "sqrt":
r.append((l.ft, l.ftf))
elif n == "rsqrt":
r += [(l.fs, l.fsf), (l.ft, l.ftf)]
return [x for x in r if x[0]], [x for x in w if x[0]]
FLAG_UPPER = {"add", "sub", "mul", "madd", "msub", "adda", "suba", "mula", "madda", "msuba"}
class Image:
def __init__(self, gid, blocks, vu):
self.gid, self.blocks, self.vu = gid, sorted(blocks), vu
self.mem = {}
for vma, data in self.blocks:
for k in range(0, len(data), 8):
self.mem[vma + k] = struct.unpack_from("<II", data, k)
self.warn = []
def dis_image(im, out):
with open(out, "w") as f:
for pc in sorted(im.mem):
lo, hi = im.mem[pc]
u, l, fl, imm = decode_pair(lo, hi)
fs = "".join(x for x in "IEMDT" if fl[x])
ls = "loi %08X" % imm if imm is not None else l.text()
f.write("%04X %-3s %-45s | %s\n" % (pc, fs, u.text(), ls))
# ---- translation ----------------------------------------------------------------
def gen_image(im):
vu = im.vu
MEM = "VU%d_MEM" % vu
PCMASK = 0x3FF8 if vu == 1 else 0xFF8
pcs = sorted(im.mem)
dec = {}
for pc in pcs:
lo, hi = im.mem[pc]
dec[pc] = decode_pair(lo, hi)
reads_sm = any(d[1] is not None and d[1].name in ("fsand", "fsor", "fseq", "fmand", "fmor", "fmeq") for d in dec.values())
# ---- static cycles: stalls on VF registers still being written ----
stall, qstall = {}, {}
ready, t, qready = {}, 0, None
prev_end = True
for pc in pcs:
u, l, fl, imm = dec[pc]
if prev_end or (pc - 8) not in dec:
ready, t, qready = {}, 0, None
ti = t + 1
r1, w1 = up_rw(u)
r2, w2 = lo_rw(l)
need = max([ready.get(x, 0) for x in r1 + r2] + [0])
s = max(0, need - ti)
ti += s
qs = 0
if l is not None and l.name in ("waitq", "div", "sqrt", "rsqrt") and qready is not None and qready > ti:
qs = qready - ti
ti += qs
if l is not None and l.name in ("div", "sqrt", "rsqrt"):
qready = ti + (13 if l.name == "rsqrt" else 7)
stall[pc] = s + qs
for x in w1 + w2:
ready[x] = ti + 4
t = ti
# a new straight-line run starts after an unconditional jump's or E's delay slot
prev_end = False
p = pc - 8
if p in dec:
pu, pl, pf, pimm = dec[p]
if pf["E"] or (pl is not None and pl.name in ("b", "jr")):
prev_end = True
used_q = False
# ---- branch VI delay: a conditional branch (or JR/JALR) whose VI register
# was written by the instruction right before it reads the value the
# register had before that write (before the earliest of a run of such
# writes, up to 4 back), unless the branch stalls or the write reads flags
# (FCAND and friends). This is the hardware's integer pipeline; PCSX2's
# microVU models it the same way (analyzeBranchVI). ----
FLAGREAD = {"fcand", "fcor", "fceq", "fcget", "fsand", "fsor", "fseq", "fmand", "fmor", "fmeq"}
def vi_written(lw):
if lw is None: return 0
n = lw.name
if n in ("iadd", "isub", "iand", "ior"): return lw.id_ & 15
if n in ("iaddi", "iaddiu", "isubiu", "ilw", "ilwr", "mtir", "xtop", "xitop", "bal", "jalr"): return lw.it & 15
if n in ("lqi", "lqd"): return lw.is_ & 15
if n in ("sqi", "sqd"): return lw.it & 15
return 0
targets = set()
for pc_ in pcs:
l_ = dec[pc_][1]
if l_ is not None and l_.name in ("b", "bal", "ibeq", "ibne", "ibltz", "ibgtz", "iblez", "ibgez"):
targets.add((pc_ + 8 + l_.imm * 8) & PCMASK)
vib_save, vib_use = {}, {}
for pc_ in pcs:
l_ = dec[pc_][1]
if l_ is None or l_.name not in ("ibeq", "ibne", "ibltz", "ibgtz", "iblez", "ibgez", "jr", "jalr"): continue
if stall[pc_] or pc_ in targets: continue
regs = [l_.is_ & 15] + ([l_.it & 15] if l_.name in ("ibeq", "ibne") else [])
for x in regs:
if not x: continue
p_ = pc_ - 8
if p_ not in dec or dec[p_][1] is None or dec[p_][1].name in FLAGREAD or vi_written(dec[p_][1]) != x: continue
j_ = p_
for k_ in range(3):
q_ = j_ - 8
if j_ in targets or q_ not in dec or stall[j_]: break
lq_ = dec[q_][1]
if lq_ is None or lq_.name in FLAGREAD or vi_written(lq_) != x: break
j_ = q_
vib_save[j_] = x
vib_use[pc_] = x
break
def VI(r):
r &= 15
return "0u" if r == 0 else "v->vi[%d]" % r
def setvi(r, e):
r &= 15
return "" if r == 0 else "v->vi[%d] = (%s) & 0xFFFF;" % (r, e)
def U32(r):
return "((u32 *)v->vf[%d])" % r
def upper(pc, u):
"""(compute, commit) code for the upper op"""
nonlocal used_q
k = u.kind
if k == "nop":
return "", ""
if k == "bad":
im.warn.append("%04X upper %s" % (pc, u.name))
return 'vu_bad(%d, 0x%04X, "%s");' % (vu, pc, u.name), ""
comp, commit = [], []
F = lambda r, c: "v->vf[%d][%d]" % (r, c)
if k == "arith":
base = u.base if u.acc else u.name
if u.bc == "q":
used_q = True
comp.append("f32 qv = vu_q(v, cyc);")
def B(c):
if u.bc in ("x", "y", "z", "w"): return F(u.ft, XYZW.index(u.bc))
if u.bc == "q": return "qv"
if u.bc == "i": return "v->i"
return F(u.ft, c)
for c in u.comps:
a, b = F(u.fs, c), B(c)
e = {"add": "%s + %s" % (a, b), "sub": "%s - %s" % (a, b), "mul": "%s * %s" % (a, b),
"madd": "v->acc[%d] + vu_fc(%s * %s)" % (c, a, b), "msub": "v->acc[%d] - vu_fc(%s * %s)" % (c, a, b),
"max": "fmaxf(%s, %s)" % (a, b), "mini": "fminf(%s, %s)" % (a, b)}[base]
comp.append("f32 u%d = %s;" % (c, e))
for c in u.comps:
if u.acc: commit.append("v->acc[%d] = vu_fc(u%d);" % (c, c))
elif u.fd: commit.append("v->vf[%d][%d] = vu_fc(u%d);" % (u.fd, c, c))
if reads_sm and base in ("add", "sub", "mul", "madd", "msub"):
commit.append("vu_fl_push(v, cyc, %s);" % (" | ".join("vu_macbits(u%d, %d)" % (c, c) for c in u.comps) or "0"))
elif k in ("opmula", "opmsub"):
ex = {0: (1, 2), 1: (2, 0), 2: (0, 1)}
cs = [c for c in u.comps if c < 3]
for c in cs:
a, b = ex[c]
p = "%s * %s" % (F(u.fs, a), F(u.ft, b))
comp.append("f32 u%d = %s;" % (c, p if k == "opmula" else "v->acc[%d] - vu_fc(%s)" % (c, p)))
for c in cs:
if k == "opmula": commit.append("v->acc[%d] = vu_fc(u%d);" % (c, c))
elif u.fd: commit.append("v->vf[%d][%d] = vu_fc(u%d);" % (u.fd, c, c))
if reads_sm:
commit.append("vu_fl_push(v, cyc, %s);" % (" | ".join("vu_macbits(u%d, %d)" % (c, c) for c in cs) or "0"))
elif k == "itof":
for c in u.comps:
comp.append("u32 n%d = vu_itof(%s[%d], %.9ef);" % (c, U32(u.fs), c, 1.0 / (1 << u.shift)))
if u.ft:
commit += ["%s[%d] = n%d;" % (U32(u.ft), c, c) for c in u.comps]
elif k == "ftoi":
for c in u.comps:
comp.append("s32 n%d = vu_ftoi(%s, %.1ff);" % (c, F(u.fs, c), float(1 << u.shift)))
if u.ft:
commit += ["%s[%d] = (u32)n%d;" % (U32(u.ft), c, c) for c in u.comps]
elif k == "abs":
for c in u.comps:
comp.append("u32 n%d = %s[%d] & 0x7FFFFFFFu;" % (c, U32(u.fs), c))
if u.ft:
commit += ["%s[%d] = n%d;" % (U32(u.ft), c, c) for c in u.comps]
elif k == "clip":
comp.append("f32 cw = v->vf[%d][3];" % u.ft)
comp.append("f32 cs[3] = { v->vf[%d][0], v->vf[%d][1], v->vf[%d][2] };" % (u.fs, u.fs, u.fs))
commit.append("vu_clipw(v, cyc, cs, cw);")
return " ".join(comp), " ".join(commit)
def lower(pc, l):
"""(pre, code, kind, target) ; kind in None, 'b' (static target), 'j' (npc)"""
nonlocal used_q
n = l.name
c0 = l.comps[0] if l.comps else 0
if n.startswith("?"):
im.warn.append("%04X lower %s" % (pc, n))
return "", 'vu_bad(%d, 0x%04X, "%s");' % (vu, pc, n), None, None
if n == "move":
if not l.comps or not l.ft: return "", "", None, None
return "", " ".join("%s[%d] = %s[%d];" % (U32(l.ft), c, U32(l.fs), c) for c in l.comps), None, None
if n == "mr32":
if not l.ft: return "", "", None, None
s = "{ u32 m0 = %s[1], m1 = %s[2], m2 = %s[3], m3 = %s[0]; " % (U32(l.fs), U32(l.fs), U32(l.fs), U32(l.fs))
return "", s + " ".join("%s[%d] = m%d;" % (U32(l.ft), c, c) for c in l.comps) + " }", None, None
if n in ("lq", "lqi", "lqd"):
reg = l.is_
pre = setvi(reg, VI(reg) + " - 1") + " " if n == "lqd" else ""
post = " " + setvi(reg, VI(reg) + " + 1") if n == "lqi" else ""
addr = VI(reg) + (" + %d" % l.imm if n == "lq" else "")
body = " ".join("%s[%d] = m[%d];" % (U32(l.ft), c, c) for c in l.comps) if l.ft else ""
return "", "%s{ const u32 *m = %s(%s); %s }%s" % (pre, MEM, addr, body, post), None, None
if n in ("sq", "sqi", "sqd"):
reg = l.it
pre = setvi(reg, VI(reg) + " - 1") + " " if n == "sqd" else ""
post = " " + setvi(reg, VI(reg) + " + 1") if n == "sqi" else ""
addr = VI(reg) + (" + %d" % l.imm if n == "sq" else "")
body = " ".join("m[%d] = %s[%d];" % (c, U32(l.fs), c) for c in l.comps)
return "", "%s{ u32 *m = %s(%s); %s }%s" % (pre, MEM, addr, body, post), None, None
if n in ("ilw", "ilwr"):
addr = VI(l.is_) + (" + %d" % l.imm if n == "ilw" else "")
return "", setvi(l.it, "%s(%s)[%d]" % (MEM, addr, c0)), None, None
if n in ("isw", "iswr"):
addr = VI(l.is_) + (" + %d" % l.imm if n == "isw" else "")
return "", "{ u32 *m = %s(%s); %s }" % (MEM, addr, " ".join("m[%d] = %s;" % (c, VI(l.it)) for c in l.comps)), None, None
if n in ("iadd", "isub", "iand", "ior"):
op = {"iadd": "+", "isub": "-", "iand": "&", "ior": "|"}[n]
return "", setvi(l.id_, "%s %s %s" % (VI(l.is_), op, VI(l.it))), None, None
if n == "iaddi":
return "", setvi(l.it, "%s + (%d)" % (VI(l.is_), l.imm)), None, None
if n == "iaddiu":
return "", setvi(l.it, "%s + %d" % (VI(l.is_), l.imm)), None, None
if n == "isubiu":
return "", setvi(l.it, "%s - %d" % (VI(l.is_), l.imm)), None, None
if n == "mtir":
return "", setvi(l.it, "%s[%d]" % (U32(l.fs), l.fsf)), None, None
if n == "mfir":
if not l.ft: return "", "", None, None
return "", " ".join("%s[%d] = (u32)(s32)(s16)%s;" % (U32(l.ft), c, VI(l.is_)) for c in l.comps), None, None
F = lambda r, c: "v->vf[%d][%d]" % (r, c)
if n == "div":
used_q = True
return "cyc = vu_qwait(v, cyc);", "vu_qset(v, cyc, 7, vu_div(%s, %s));" % (F(l.fs, l.fsf), F(l.ft, l.ftf)), None, None
if n == "sqrt":
used_q = True
return "cyc = vu_qwait(v, cyc);", "vu_qset(v, cyc, 7, vu_sqrt(%s));" % F(l.ft, l.ftf), None, None
if n == "rsqrt":
used_q = True
return "cyc = vu_qwait(v, cyc);", "vu_qset(v, cyc, 13, vu_rsqrt(%s, %s));" % (F(l.fs, l.fsf), F(l.ft, l.ftf)), None, None
if n == "waitq":
return "cyc = vu_qwait(v, cyc);", "", None, None
if n == "fcand": return "", setvi(1, "(vu_clip(v, cyc) & 0x%X) != 0" % l.imm), None, None
if n == "fcor": return "", setvi(1, "((vu_clip(v, cyc) | 0x%X) & 0xFFFFFF) == 0xFFFFFF" % l.imm), None, None
if n == "fceq": return "", setvi(1, "(vu_clip(v, cyc) & 0xFFFFFF) == 0x%X" % l.imm), None, None
if n == "fcset": return "", "vu_fcset(v, cyc, 0x%X);" % l.imm, None, None
if n == "fcget": return "", setvi(l.it, "vu_clip(v, cyc) & 0xFFF"), None, None
if n == "fsand": return "", setvi(l.it, "vu_status(v, cyc) & 0x%X" % l.imm), None, None
if n == "fsor": return "", setvi(l.it, "vu_status(v, cyc) | 0x%X" % l.imm), None, None
if n == "fseq": return "", setvi(l.it, "(vu_status(v, cyc) & 0xFFF) == 0x%X" % l.imm), None, None
if n == "fsset": return "", "vu_fsset(v, cyc, 0x%X);" % l.imm, None, None
if n == "fmand": return "", setvi(l.it, "%s & vu_mac(v, cyc)" % VI(l.is_)), None, None
if n == "fmor": return "", setvi(l.it, "%s | vu_mac(v, cyc)" % VI(l.is_)), None, None
if n == "fmeq": return "", setvi(l.it, "(%s & 0xFFFF) == vu_mac(v, cyc)" % VI(l.is_)), None, None
if n == "xgkick":
if vu != 1:
im.warn.append("%04X xgkick on VU0" % pc)
return "", 'vu_bad(0, 0x%04X, "xgkick");' % pc, None, None
return "", "vu_xgkick(%s);" % VI(l.is_), None, None
if n == "xtop": return "", setvi(l.it, "vu_vif_top(%d)" % vu), None, None
if n == "xitop": return "", setvi(l.it, "vu_vif_itop(%d)" % vu), None, None
if n in BRANCHES:
link = (pc + 16) // 8
if n == "b":
return "", "", "b", (pc + 8 + l.imm * 8) & PCMASK
if n == "bal":
return "", setvi(l.it, "%d" % link), "b", (pc + 8 + l.imm * 8) & PCMASK
JV = "vib" if vib_use.get(pc) == (l.is_ & 15) else VI(l.is_)
if n == "jr":
return "", "npc = (%s << 3) & 0x%X;" % (JV, PCMASK), "j", None
if n == "jalr":
return "", "npc = (%s << 3) & 0x%X; %s" % (JV, PCMASK, setvi(l.it, "%d" % link)), "j", None
BV = (lambda r: "vib" if vib_use.get(pc) == (r & 15) else VI(r))
cond = {"ibeq": "%s == %s" % (BV(l.it), BV(l.is_)), "ibne": "%s != %s" % (BV(l.it), BV(l.is_)),
"ibltz": "(s16)%s < 0" % BV(l.is_), "ibgtz": "(s16)%s > 0" % BV(l.is_),
"iblez": "(s16)%s <= 0" % BV(l.is_), "ibgez": "(s16)%s >= 0" % BV(l.is_)}[n]
return "", "bt = %s;" % cond, "c", (pc + 8 + l.imm * 8) & PCMASK
im.warn.append("%04X lower %s not translated" % (pc, n))
return "", 'vu_bad(%d, 0x%04X, "%s");' % (vu, pc, n), None, None
def pair(pc):
"""C for one pair; returns (code, flow kind, target)"""
u, l, fl, imm = dec[pc]
out = ["VU_TRACE(0x%04X); cyc += %d;" % (pc, 1 + stall[pc])]
if pc in vib_save: out.append("vib = %s;" % VI(vib_save[pc]))
uc, ucommit = upper(pc, u)
pre = code = ""
kind = tgt = None
if l is not None:
pre, code, kind, tgt = lower(pc, l)
if pre: out.append(pre)
if imm is not None: # LOI: the pair's upper op still reads the old I
code = "v->i = vu_f(0x%08Xu);" % imm
if uc or ucommit:
out.append("{ %s %s %s }" % (uc, code, ucommit))
elif code:
out.append(code)
return " ".join(x for x in out if x), kind, tgt, fl["E"]
body = []
lab = lambda pc: "L_%04X" % pc
import re as _re
def idle(pc): # a pair that does nothing but count its cycle
c_, k_, t_, e_ = pair(pc)
return k_ is None and not e_ and not _re.sub(r"VU_TRACE\(0x[0-9A-F]+\);|cyc \+= \d+;|\s", "", c_)
for pc in pcs:
code, kind, tgt, ebit = pair(pc)
body.append("%s: %s" % (lab(pc), code))
nxt = pc + 8
if kind is None and not ebit:
if nxt not in dec:
body.append(" vu_bad(%d, 0x%04X, \"ran off the end\"); goto done;" % (vu, nxt))
continue
# a branch or the end: its delay slot pair runs here as well
if nxt in dec:
dcode, dkind, dtgt, debit = pair(nxt)
if dkind is not None:
im.warn.append("%04X branch in a delay slot (ignored)" % nxt)
if debit and not ebit:
im.warn.append("%04X E bit in a branch delay slot" % nxt)
body.append(" /* delay slot */ " + dcode)
else:
im.warn.append("%04X delay slot outside the image" % pc)
if ebit:
if kind is not None:
im.warn.append("%04X branch with the E bit" % pc)
body.append(" v->tpc = 0x%04X; goto done;" % ((pc + 16) & PCMASK))
continue
if kind == "b" and tgt == pc - 16 and tgt in dec and pair(tgt)[1] == "c" and idle(tgt + 8) and (nxt not in dec or idle(nxt)) \
and "vi[" in pair(tgt)[0] and not _re.search(r"vf\[|acc\[|VU\d_MEM", pair(tgt)[0]):
# a wait loop on a VI register the EE writes (ctc2): park here and
# let the EE resume the program (rc_vu0_resume) when it writes one
body.append(" v->tpc = 0x%04X; v->susp = 1; goto done; /* wait for the EE */" % tgt)
elif kind == "b":
body.append(" goto %s;" % lab(tgt) if tgt in dec else " pc = 0x%04X; goto dispatch;" % tgt)
elif kind == "c":
body.append(" if (bt) %s" % ("goto %s;" % lab(tgt) if tgt in dec else "{ pc = 0x%04X; goto dispatch; }" % tgt))
if nxt + 8 in dec:
body.append(" goto %s;" % lab(nxt + 8))
else:
body.append(" goto done;")
elif kind == "j":
body.append(" pc = npc; goto dispatch;")
name = "vup_%s_vu%d" % (im.gid, vu)
out = ["/* VU%d microprogram image %s, translated from the executable by tools/vu_recomp.py */" % (vu, im.gid),
'#include "vu.h"', "",
"void %s(RcVu0 *v, u32 pc)" % name, "{",
" u32 cyc = 0, npc = 0, vib = 0; int bt = 0; (void)npc; (void)bt; (void)vib;",
" v->st_int = v->status; v->cl_int = v->clip;",
"dispatch:",
" switch (pc & 0x%X) {" % PCMASK]
for pc in pcs:
out.append(" case 0x%04X: goto %s;" % (pc, lab(pc)))
out.append(' default: vu_bad(%d, pc, "start address"); goto done;' % vu)
out.append(" }")
out += body
out += ["done:", " vu_settle(v);", "}"]
return name, "\n".join(out) + "\n"
def extra_images():
"""Microcode the game uploads that is not in .vutext (none is known for
RAC1): the runtime reports such an upload and, with UYA_VUDUMP=dir, saves
it as vu<N>_<addr>_<hash>.bin. Files of that name in RAC1_VU_EXTRA (or
the vu_extra folder next to build.py) are translated as images of their
own, one upload each."""
d = os.environ.get("RAC1_VU_EXTRA") or os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "vu_extra")
out = []
if not os.path.isdir(d):
return out
for fn in sorted(os.listdir(d)):
m = re.fullmatch(r"vu([01])_([0-9A-Fa-f]{4})_([0-9A-Fa-f]{8})\.bin", fn)
if not m:
continue
data = open(os.path.join(d, fn), "rb").read()
data = data[:len(data) // 8 * 8]
out.append(Image("x%s_%s" % (m.group(2).upper(), m.group(3).upper()), [(int(m.group(2), 16), data)], int(m.group(1))))
return out
def main():
os.makedirs(GEN, exist_ok=True)
images = []
for g in order:
blocks = groups[g]
size = max(vma + len(d) for vma, d in blocks)
kicks = any(decode_pair(*struct.unpack_from("<II", d, k))[1] is not None and
decode_pair(*struct.unpack_from("<II", d, k))[1].name == "xgkick"
for vma, d in blocks for k in range(0, len(d), 8))
# XGKICK only exists on VU1; anything bigger than 4 KB is VU1 code too
for vu in ((0, 1) if size <= 0x1000 and not kicks else (1,)):
images.append(Image(g, blocks, vu))
images += extra_images()
if DIS:
os.makedirs(DIS, exist_ok=True)
for im in images:
if im.vu == 1 or len([i for i in images if i.gid == im.gid]) == 1:
dis_image(im, os.path.join(DIS, "%s.txt" % im.gid))
tbl = ['/* VU microprogram images (tools/vu_recomp.py) */', '#include "vu.h"', "",
"typedef struct { u32 vma, size; const u32 *words; } VuBlock;",
"typedef struct { const char *name; int vu; void (*fn)(RcVu0 *, u32); int nblk; const VuBlock *blk; } VuImage;", ""]
names = []
nwarn = 0
for im in images:
name, src = gen_image(im)
path = os.path.join(GEN, "%s.c" % name)
old = open(path).read() if os.path.exists(path) else None
if old != src:
open(path, "w").write(src)
tbl.append("void %s(RcVu0 *, u32);" % name)
for vma, d in im.blocks:
ws = struct.unpack("<%dI" % (len(d) // 4), d)
tbl.append("static const u32 %s_b%04X[%d] = {" % (name, vma, len(ws)))
for k in range(0, len(ws), 8):
tbl.append(" " + ", ".join("0x%08X" % x for x in ws[k:k + 8]) + ",")
tbl.append("};")
tbl.append("static const VuBlock %s_blk[] = { %s };" % (name, ", ".join(
"{ 0x%04X, 0x%X, %s_b%04X }" % (vma, len(d), name, vma) for vma, d in im.blocks)))
names.append((name, im))
for w in im.warn:
nwarn += 1
print("vu_recomp: image %s VU%d: %s" % (im.gid, im.vu, w))
tbl.append("const VuImage vu_images[] = {")
for name, im in names:
tbl.append(' { "%s", %d, %s, %d, %s_blk },' % (im.gid, im.vu, name, len(im.blocks), name))
tbl.append("};")
tbl.append("const int vu_image_count = %d;" % len(names))
src = "\n".join(tbl) + "\n"
path = os.path.join(GEN, "vu_table.c")
if not os.path.exists(path) or open(path).read() != src:
open(path, "w").write(src)
print("vu_recomp: %d microprogram images (%d pairs) translated, %d warnings" % (
len(names), sum(len(im.mem) for _, im in names), nwarn))
main()